OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #import "chrome/browser/ui/cocoa/browser/password_generation_bubble_controller.h " | |
6 | |
7 #include "base/sys_string_conversions.h" | |
8 #include "base/mac/foundation_util.h" | |
9 #include "chrome/common/autofill_messages.h" | |
10 #include "chrome/browser/autofill/password_generator.h" | |
11 #include "chrome/browser/password_manager/password_manager.h" | |
12 #include "chrome/browser/ui/browser.h" | |
13 #include "chrome/browser/ui/browser_window.h" | |
14 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | |
15 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | |
16 #include "chrome/browser/ui/cocoa/key_equivalent_constants.h" | |
17 #import "chrome/browser/ui/cocoa/styled_text_field_cell.h" | |
18 #import "chrome/browser/ui/cocoa/tracking_area.h" | |
19 #include "content/public/browser/render_view_host.h" | |
20 #include "content/public/common/password_form.h" | |
21 #include "grit/generated_resources.h" | |
22 #include "grit/theme_resources.h" | |
23 #include "ui/base/l10n/l10n_util_mac.h" | |
24 #include "ui/base/resource/resource_bundle.h" | |
25 | |
26 namespace { | |
27 | |
28 // Size of the border in the bubble. | |
29 const CGFloat kBorderSize = 9.0; | |
30 | |
31 // Visible size of the textfield. | |
32 const CGFloat kTextFieldHeight = 20.0; | |
33 const CGFloat kTextFieldWidth = 172.0; | |
34 | |
35 // Frame padding necessary to make the textfield the correct visible size. | |
36 const CGFloat kTextFieldTopPadding = 3.0; | |
37 | |
38 // Visible size of the button | |
39 const CGFloat kButtonWidth = 63.0; | |
40 const CGFloat kButtonHeight = 20.0; | |
41 | |
42 // Padding that is added to the frame around the button to make it the | |
43 // correct visible size. Determined via visual inspection. | |
44 const CGFloat kButtonHorizontalPadding = 6.5; | |
45 const CGFloat kButtonVerticalPadding = 3.0; | |
46 | |
47 // Visible size of the title. | |
48 const CGFloat kTitleWidth = 170.0; | |
49 const CGFloat kTitleHeight = 15.0; | |
50 | |
51 // Space between the title and the textfield. | |
52 const CGFloat kVerticalSpacing = 13.0; | |
53 | |
54 // Space between the textfield and the button. | |
55 const CGFloat kHorizontalSpacing = 4.0; | |
56 | |
57 // We don't actually want the border to be kBorderSize on top as there is | |
58 // whitespace in the title text that makes it looks substantially bigger. | |
59 const CGFloat kTopBorderOffset = 3.0; | |
60 | |
61 const CGFloat kIconSize = 26.0; | |
62 | |
63 } // namespace | |
64 | |
65 // Customized StyledTextFieldCell to display one button decoration that changes | |
66 // on hover. | |
67 @interface PasswordGenerationTextFieldCell : StyledTextFieldCell { | |
68 @private | |
69 PasswordGenerationBubbleController* controller_; | |
70 BOOL hovering_; | |
71 scoped_nsobject<NSImage> normalImage_; | |
72 scoped_nsobject<NSImage> hoverImage_; | |
73 } | |
74 | |
75 - (void)setUpWithController:(PasswordGenerationBubbleController*)controller | |
76 normalImage:(NSImage*)normalImage | |
77 hoverImage:(NSImage*)hoverImage; | |
78 - (void)mouseEntered:(NSEvent*)theEvent | |
79 inView:(PasswordGenerationTextField*)controlView; | |
80 - (void)mouseExited:(NSEvent*)theEvent | |
81 inView:(PasswordGenerationTextField*)controlView; | |
82 - (BOOL)mouseDown:(NSEvent*)theEvent | |
83 inView:(PasswordGenerationTextField*)controlView; | |
84 - (void)setUpTrackingAreaInRect:(NSRect)frame | |
85 ofView:(PasswordGenerationTextField*)controlView; | |
86 // Exposed for testing. | |
87 - (NSRect)getIconFrame:(NSRect)cellFrame; | |
88 @end | |
89 | |
90 @implementation PasswordGenerationTextField | |
91 | |
92 + (Class)cellClass { | |
93 return [PasswordGenerationTextFieldCell class]; | |
94 } | |
95 | |
96 - (PasswordGenerationTextFieldCell*)cell { | |
97 return base::mac::ObjCCastStrict<PasswordGenerationTextFieldCell>( | |
98 [super cell]); | |
99 } | |
100 | |
101 - (id)initWithFrame:(NSRect)frame | |
102 withController:(PasswordGenerationBubbleController*)controller | |
103 normalImage:(NSImage*)normalImage | |
104 hoverImage:(NSImage*)hoverImage { | |
105 self = [super initWithFrame:frame]; | |
106 if (self) { | |
107 PasswordGenerationTextFieldCell* cell = [self cell]; | |
108 [cell setUpWithController:controller | |
109 normalImage:normalImage | |
110 hoverImage:hoverImage]; | |
111 [cell setUpTrackingAreaInRect:[self bounds] ofView:self]; | |
112 } | |
113 return self; | |
114 } | |
115 | |
116 - (void)mouseEntered:(NSEvent*)theEvent { | |
117 [[self cell] mouseEntered:theEvent inView:self]; | |
118 } | |
119 | |
120 - (void)mouseExited:(NSEvent*)theEvent { | |
121 [[self cell] mouseExited:theEvent inView:self]; | |
122 } | |
123 | |
124 - (void)mouseDown:(NSEvent*)theEvent { | |
Scott Hess - ex-Googler
2012/11/28 20:01:06
I'm looking at this and wondering what happens if
Garrett Casto
2012/11/30 20:56:26
Changed.
| |
125 // Let the cell handle the click if it's in the decoration. | |
126 if (![[self cell] mouseDown:theEvent inView:self]) { | |
127 [[self currentEditor] mouseDown:theEvent]; | |
128 } | |
129 } | |
130 | |
131 - (NSRect)getIconFrame { | |
132 return [[self cell] getIconFrame:[self bounds]]; | |
133 } | |
134 | |
135 @end | |
136 | |
137 @implementation PasswordGenerationTextFieldCell | |
138 | |
139 - (void)setUpWithController:(PasswordGenerationBubbleController*)controller | |
140 normalImage:(NSImage*)normalImage | |
141 hoverImage:(NSImage*)hoverImage { | |
142 controller_ = controller; | |
143 hovering_ = NO; | |
144 normalImage_.reset([normalImage retain]); | |
145 hoverImage_.reset([hoverImage retain]); | |
146 [self setLineBreakMode:NSLineBreakByTruncatingTail]; | |
147 [self setTruncatesLastVisibleLine:YES]; | |
148 } | |
149 | |
150 - (void)splitFrame:(NSRect*)cellFrame toIconFrame:(NSRect*)iconFrame { | |
151 NSDivideRect(*cellFrame, iconFrame, cellFrame, | |
152 kIconSize, NSMaxXEdge); | |
153 } | |
154 | |
155 - (NSRect)getIconFrame:(NSRect)cellFrame { | |
156 NSRect iconFrame; | |
157 [self splitFrame:&cellFrame toIconFrame:&iconFrame]; | |
158 return iconFrame; | |
159 } | |
160 | |
161 - (NSRect)getTextFrame:(NSRect)cellFrame { | |
162 NSRect iconFrame; | |
163 [self splitFrame:&cellFrame toIconFrame:&iconFrame]; | |
164 return cellFrame; | |
165 } | |
166 | |
167 - (BOOL)eventIsInDecoration:(NSEvent*)theEvent | |
168 inView:(PasswordGenerationTextField*)controlView { | |
169 NSPoint mouseLocation = [controlView convertPoint:[theEvent locationInWindow] | |
170 fromView:nil]; | |
171 NSRect cellFrame = [controlView bounds]; | |
172 return NSMouseInRect(mouseLocation, | |
173 [self getIconFrame:cellFrame], | |
174 [controlView isFlipped]); | |
175 } | |
176 | |
177 - (void)mouseEntered:(NSEvent*)theEvent | |
178 inView:(PasswordGenerationTextField*)controlView { | |
179 hovering_ = YES; | |
180 [controlView setNeedsDisplay:YES]; | |
181 } | |
182 | |
183 - (void)mouseExited:(NSEvent*)theEvent | |
184 inView:(PasswordGenerationTextField*)controlView { | |
185 hovering_ = NO; | |
186 [controlView setNeedsDisplay:YES]; | |
187 } | |
188 | |
189 - (BOOL)mouseDown:(NSEvent*)theEvent | |
190 inView:(PasswordGenerationTextField*)controlView { | |
191 if ([self eventIsInDecoration:theEvent inView:controlView]) { | |
192 [controller_ regeneratePassword]; | |
193 return YES; | |
194 } | |
195 return NO; | |
196 } | |
197 | |
198 - (NSImage*)getImage { | |
199 if (hovering_) | |
200 return hoverImage_; | |
201 return normalImage_; | |
202 } | |
203 | |
204 - (NSRect)adjustFrameForFrame:(NSRect)frame { | |
205 // By default, there appears to be a 2 pixel gap between what is considered | |
206 // part of the textFrame and what is considered part of the icon. | |
207 // TODO(gcasto): This really should be fixed in StyledTextFieldCell, as it | |
208 // looks like the location bar also suffers from this issue. | |
209 frame.size.width += 2; | |
Scott Hess - ex-Googler
2012/11/28 20:01:06
Per comment I made probably against an earlier pat
Garrett Casto
2012/11/30 20:56:26
Looks right to me.
| |
210 return frame; | |
211 } | |
212 | |
213 - (NSRect)textFrameForFrame:(NSRect)cellFrame { | |
214 // Baseclass insets the rect by baselineAdjust. | |
215 NSRect textFrame = [super textFrameForFrame:cellFrame]; | |
216 textFrame = [self getTextFrame:textFrame]; | |
217 return [self adjustFrameForFrame:textFrame]; | |
218 } | |
219 | |
220 - (NSRect)textCursorFrameForFrame:(NSRect)cellFrame { | |
221 NSRect textFrame = [self getTextFrame:cellFrame]; | |
222 return [self adjustFrameForFrame:textFrame]; | |
223 } | |
224 | |
225 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
226 NSImage* image = [self getImage]; | |
227 NSRect iconFrame = [self getIconFrame:cellFrame]; | |
228 // Center the image in the available space. At the moment the image is | |
229 // slightly larger than the frame so we crop it. | |
230 // Offset the full difference on the left hand side since the border on the | |
231 // right takes up some space. Offset half the vertical difference on the | |
232 // bottom so that the image stays vertically centered. | |
233 const CGFloat x_offset = [image size].width - NSWidth(iconFrame); | |
234 const CGFloat y_offset = ([image size].height - (NSHeight(iconFrame))) / 2.0; | |
Scott Hess - ex-Googler
2012/11/28 20:01:06
xOffset, yOffset.
Garrett Casto
2012/11/30 20:56:26
Done.
| |
235 NSRect croppedRect = NSMakeRect(x_offset, | |
236 y_offset, | |
237 NSWidth(iconFrame), | |
238 NSHeight(iconFrame)); | |
239 | |
240 [image drawInRect:iconFrame | |
241 fromRect:croppedRect | |
242 operation:NSCompositeSourceOver | |
243 fraction:1.0 | |
244 respectFlipped:YES | |
245 hints:nil]; | |
246 | |
247 [super drawInteriorWithFrame:cellFrame inView:controlView]; | |
248 } | |
249 | |
250 - (void)setUpTrackingAreaInRect:(NSRect)frame | |
251 ofView:(PasswordGenerationTextField*)view { | |
252 NSRect iconFrame = [self getIconFrame:frame]; | |
253 scoped_nsobject<CrTrackingArea> area( | |
254 [[CrTrackingArea alloc] initWithRect:iconFrame | |
255 options:NSTrackingMouseEnteredAndExited | | |
256 NSTrackingActiveAlways | |
257 owner:view | |
258 userInfo:nil]); | |
259 [view addTrackingArea:area]; | |
260 } | |
261 | |
262 - (CGFloat)baselineAdjust { | |
263 return 1.0; | |
264 } | |
265 | |
266 - (CGFloat)cornerRadius { | |
267 return 4.0; | |
268 } | |
269 | |
270 - (BOOL)shouldDrawBezel { | |
271 return YES; | |
272 } | |
273 | |
274 @end | |
275 | |
276 @implementation PasswordGenerationBubbleController | |
277 | |
278 - (id)initWithWindow:(NSWindow*)parentWindow | |
279 anchoredAt:(NSPoint)point | |
280 renderViewHost:(content::RenderViewHost*)renderViewHost | |
281 passwordManager:(PasswordManager*)passwordManager | |
282 usingGenerator:(autofill::PasswordGenerator*)passwordGenerator | |
283 forForm:(const content::PasswordForm&)form { | |
284 int width = (kBorderSize*2 + | |
285 kTextFieldWidth + | |
286 kHorizontalSpacing + | |
287 kButtonWidth); | |
288 int height = (kBorderSize*2 + | |
289 kTextFieldHeight + | |
290 kVerticalSpacing + | |
291 kTitleHeight - | |
292 kTopBorderOffset + | |
293 info_bubble::kBubbleArrowHeight); | |
Scott Hess - ex-Googler
2012/11/28 20:01:06
These should probably both be CGFloat.
Garrett Casto
2012/11/30 20:56:26
Done.
| |
294 NSRect contentRect = NSMakeRect(0, 0, width, height); | |
295 scoped_nsobject<InfoBubbleWindow> window( | |
296 [[InfoBubbleWindow alloc] initWithContentRect:contentRect | |
297 styleMask:NSBorderlessWindowMask | |
298 backing:NSBackingStoreBuffered | |
299 defer:NO]); | |
300 if (self = [super initWithWindow:window | |
301 parentWindow:parentWindow | |
302 anchoredAt:point]) { | |
303 passwordGenerator_ = passwordGenerator; | |
304 renderViewHost_ = renderViewHost; | |
305 passwordManager_ = passwordManager; | |
306 form_ = form; | |
307 [[self bubble] setArrowLocation:info_bubble::kTopLeft]; | |
308 [self performLayout]; | |
309 } | |
310 | |
311 return self; | |
312 } | |
313 | |
314 - (void)performLayout { | |
315 NSView* contentView = [[self window] contentView]; | |
316 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
317 | |
318 textField_ = | |
319 [[PasswordGenerationTextField alloc] | |
320 initWithFrame:NSMakeRect(kBorderSize, | |
321 kBorderSize, | |
322 kTextFieldWidth, | |
323 kTextFieldHeight + kTextFieldTopPadding) | |
324 withController:self | |
325 normalImage:rb.GetNativeImageNamed(IDR_RELOAD_DIMMED).ToNSImage() | |
326 hoverImage:rb.GetNativeImageNamed(IDR_RELOAD).ToNSImage()]; | |
327 gfx::Font smallBoldFont = | |
328 rb.GetFont(ResourceBundle::SmallFont).DeriveFont(0, gfx::Font::BOLD); | |
329 [textField_ setFont:smallBoldFont.GetNativeFont()]; | |
330 [textField_ | |
331 setStringValue:base::SysUTF8ToNSString(passwordGenerator_->Generate())]; | |
332 [contentView addSubview:textField_]; | |
333 | |
334 int button_x = (kBorderSize + | |
335 kTextFieldWidth + | |
336 kHorizontalSpacing - | |
337 kButtonVerticalPadding); | |
Scott Hess - ex-Googler
2012/11/28 20:01:06
CGFloat here, too. kButtonVerticalPadding when ca
Garrett Casto
2012/11/30 20:56:26
Yep, good catch. I was trying to figure out why th
| |
338 int button_y = kBorderSize - kButtonVerticalPadding; | |
339 NSButton* button = | |
340 [[NSButton alloc] initWithFrame:NSMakeRect( | |
341 button_x, | |
342 button_y, | |
343 kButtonWidth + 2 * kButtonHorizontalPadding, | |
344 kButtonHeight + 2 * kButtonVerticalPadding)]; | |
345 [button setBezelStyle:NSRoundedBezelStyle]; | |
346 [button setTitle:l10n_util::GetNSString(IDS_PASSWORD_GENERATION_BUTTON_TEXT)]; | |
347 [button setTarget:self]; | |
348 [button setAction:@selector(fillPassword:)]; | |
349 [contentView addSubview:button]; | |
350 | |
351 NSTextField* title = [[NSTextField alloc] | |
352 initWithFrame:NSMakeRect( | |
353 kBorderSize, | |
354 kBorderSize + kTextFieldHeight + kVerticalSpacing, | |
Scott Hess - ex-Googler
2012/11/28 20:01:06
As with the earlier comment, this could be NSMaxY(
Garrett Casto
2012/11/30 20:56:26
This isn't equivalent. The way I've been trying to
| |
355 kTitleWidth, | |
356 kTitleHeight)]; | |
357 [title setEditable:NO]; | |
358 [title setBordered:NO]; | |
359 [title setStringValue:l10n_util::GetNSString( | |
360 IDS_PASSWORD_GENERATION_BUBBLE_TITLE)]; | |
361 [contentView addSubview:title]; | |
362 } | |
363 | |
364 - (IBAction)fillPassword:(id)sender { | |
365 renderViewHost_->Send( | |
366 new AutofillMsg_GeneratedPasswordAccepted( | |
367 renderViewHost_->GetRoutingID(), | |
368 base::SysNSStringToUTF16([textField_ stringValue]))); | |
369 passwordManager_->SetFormHasGeneratedPassword(form_); | |
370 [self close]; | |
371 } | |
372 | |
373 - (void)regeneratePassword { | |
374 [textField_ | |
375 setStringValue:base::SysUTF8ToNSString(passwordGenerator_->Generate())]; | |
376 } | |
377 | |
378 @end | |
OLD | NEW |