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