Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / passwords / manage_password_item_view_controller.mm
1 // Copyright 2014 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/passwords/manage_password_item_view_controller.h"
6
7 #include "base/logging.h"
8 #include "base/strings/string16.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "chrome/browser/ui/chrome_style.h"
11 #import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.h"
12 #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
13 #include "grit/generated_resources.h"
14 #include "skia/ext/skia_utils_mac.h"
15 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
16 #import "ui/base/cocoa/hover_image_button.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/gfx/image/image.h"
19 #include "ui/native_theme/common_theme.h"
20 #include "ui/resources/grit/ui_resources.h"
21 #include "ui/views/layout/layout_constants.h"
22
23 using namespace password_manager::mac::ui;
24
25 namespace {
26
27 const CGFloat kBorderWidth = 1;
28 const SkColor kHoverColor = SkColorSetARGBInline(0xFF, 0xEB, 0xEB, 0xEB);
29
30 NSColor* HoverColor() {
31   return gfx::SkColorToCalibratedNSColor(kHoverColor);
32 }
33
34 NSFont* LabelFont() {
35   return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
36 }
37
38 NSSize LabelSize(int resourceID) {
39   return [l10n_util::GetNSString(resourceID)
40       sizeWithAttributes:@{NSFontAttributeName : LabelFont()}];
41 }
42
43 CGFloat FirstFieldWidth() {
44   const CGFloat undoExplanationWidth =
45       LabelSize(IDS_MANAGE_PASSWORDS_DELETED).width;
46   const CGFloat kUsernameWidth =
47       ManagePasswordsBubbleModel::UsernameFieldWidth();
48   const CGFloat width = std::max(kUsernameWidth, undoExplanationWidth);
49   return width;
50 }
51
52 CGFloat SecondFieldWidth() {
53   const CGFloat undoLinkWidth =
54       LabelSize(IDS_MANAGE_PASSWORDS_UNDO).width;
55   const CGFloat kPasswordWidth =
56       ManagePasswordsBubbleModel::PasswordFieldWidth();
57   const CGFloat width = std::max(kPasswordWidth, undoLinkWidth);
58   return width;
59 }
60
61 CGFloat ItemWidth() {
62   const CGFloat width =
63       kFramePadding +
64       FirstFieldWidth() +
65       views::kItemLabelSpacing +
66       SecondFieldWidth() +
67       views::kItemLabelSpacing +
68       chrome_style::GetCloseButtonSize() +
69       kFramePadding;
70   return width;
71 }
72
73 void InitLabel(NSTextField* textField, const base::string16& text) {
74   [textField setStringValue:base::SysUTF16ToNSString(text)];
75   [textField setEditable:NO];
76   [textField setSelectable:NO];
77   [textField setDrawsBackground:NO];
78   [textField setBezeled:NO];
79   [textField setFont:LabelFont()];
80   [textField sizeToFit];
81 }
82
83 NSTextField* Label(const base::string16& text) {
84   base::scoped_nsobject<NSTextField> textField(
85       [[NSTextField alloc] initWithFrame:NSZeroRect]);
86   InitLabel(textField, text);
87   return textField.autorelease();
88 }
89
90 NSTextField* UsernameLabel(const base::string16& text) {
91   NSTextField* textField = Label(text);
92   [textField
93       setFrameSize:NSMakeSize(FirstFieldWidth(), NSHeight([textField frame]))];
94   return textField;
95 }
96
97 NSSecureTextField* PasswordLabel(const base::string16& text) {
98   base::scoped_nsobject<NSSecureTextField> textField(
99       [[NSSecureTextField alloc] initWithFrame:NSZeroRect]);
100   InitLabel(textField, text);
101   [textField
102       setFrameSize:NSMakeSize(SecondFieldWidth(), NSHeight([textField frame]))];
103   return textField.autorelease();
104 }
105
106 base::string16 GetDisplayUsername(const autofill::PasswordForm& form) {
107   return form.username_value.empty() ?
108       l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN) :
109       form.username_value;
110 }
111
112 }  // namespace
113
114 @implementation ManagePasswordItemUndoView
115 - (id)initWithTarget:(id)target action:(SEL)action {
116   if ((self = [super init])) {
117     // The button should look like a link.
118     undoButton_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
119     base::scoped_nsobject<HyperlinkButtonCell> cell([[HyperlinkButtonCell alloc]
120         initTextCell:l10n_util::GetNSString(IDS_MANAGE_PASSWORDS_UNDO)]);
121     [cell setControlSize:NSSmallControlSize];
122     [cell setShouldUnderline:NO];
123     [cell setUnderlineOnHover:NO];
124     [cell setTextColor:gfx::SkColorToCalibratedNSColor(
125         chrome_style::GetLinkColor())];
126     [undoButton_ setCell:cell.get()];
127     [undoButton_ sizeToFit];
128     [undoButton_ setTarget:target];
129     [undoButton_ setAction:action];
130
131     const CGFloat width = ItemWidth();
132     CGFloat curX = kFramePadding;
133     CGFloat curY = views::kRelatedControlVerticalSpacing;
134
135     // Add the explanation text.
136     NSTextField* label =
137         Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED));
138     [label setFrameOrigin:NSMakePoint(curX, curY)];
139     [self addSubview:label];
140
141     // The undo button should be right-aligned.
142     curX = width - kFramePadding - NSWidth([undoButton_ frame]);
143     [undoButton_ setFrameOrigin:NSMakePoint(curX, curY)];
144     [self addSubview:undoButton_ ];
145
146     // Move to the top-right of the delete button.
147     curX = NSMaxX([undoButton_ frame]) + kFramePadding;
148     curY = NSMaxY([undoButton_ frame]) + views::kRelatedControlVerticalSpacing;
149
150     // Update the frame.
151     DCHECK_EQ(width, curX);
152     [self setFrameSize:NSMakeSize(curX, curY)];
153   }
154   return self;
155 }
156 @end
157
158 @implementation ManagePasswordItemUndoView (Testing)
159 - (NSButton*)undoButton {
160   return undoButton_.get();
161 }
162 @end
163
164 @implementation ManagePasswordItemManageView
165 - (id)initWithForm:(const autofill::PasswordForm&)form
166             target:(id)target
167             action:(SEL)action {
168   if ((self = [super init])) {
169     deleteButton_.reset([[HoverImageButton alloc] initWithFrame:NSZeroRect]);
170     [deleteButton_ setFrameSize:NSMakeSize(chrome_style::GetCloseButtonSize(),
171                                            chrome_style::GetCloseButtonSize())];
172     [deleteButton_ setBordered:NO];
173     [[deleteButton_ cell] setHighlightsBy:NSNoCellMask];
174     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
175     [deleteButton_
176         setDefaultImage:bundle.GetImageNamed(IDR_CLOSE_2).ToNSImage()];
177     [deleteButton_
178         setHoverImage:bundle.GetImageNamed(IDR_CLOSE_2_H).ToNSImage()];
179     [deleteButton_
180         setPressedImage:bundle.GetImageNamed(IDR_CLOSE_2_P).ToNSImage()];
181     [deleteButton_ setTarget:target];
182     [deleteButton_ setAction:action];
183
184     const CGFloat width = ItemWidth();
185     CGFloat curX = kFramePadding;
186     CGFloat curY = views::kRelatedControlVerticalSpacing;
187
188     // Add the username.
189     usernameField_.reset([UsernameLabel(GetDisplayUsername(form)) retain]);
190     [usernameField_ setFrameOrigin:NSMakePoint(curX, curY)];
191     [self addSubview:usernameField_];
192
193     // Move to the right of the username and add the password.
194     curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
195     passwordField_.reset([PasswordLabel(form.password_value) retain]);
196     [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
197     [self addSubview:passwordField_];
198
199     // The delete button should be right-aligned.
200     curX = width - kFramePadding - NSWidth([deleteButton_ frame]);
201     [deleteButton_ setFrameOrigin:NSMakePoint(curX, curY)];
202     [self addSubview:deleteButton_];
203
204     // Move to the top-right of the delete button.
205     curX = NSMaxX([deleteButton_ frame]) + kFramePadding;
206     curY =
207         NSMaxY([deleteButton_ frame]) + views::kRelatedControlVerticalSpacing;
208
209     // Update the frame.
210     DCHECK_EQ(width, curX);
211     [self setFrameSize:NSMakeSize(curX, curY)];
212   }
213   return self;
214 }
215 @end
216
217 @implementation ManagePasswordItemManageView (Testing)
218 - (NSTextField*)usernameField {
219   return usernameField_.get();
220 }
221 - (NSSecureTextField*)passwordField {
222   return passwordField_.get();
223 }
224 - (NSButton*)deleteButton {
225   return deleteButton_.get();
226 }
227 @end
228
229 @implementation ManagePasswordItemPendingView
230
231 - (id)initWithForm:(const autofill::PasswordForm&)form {
232   if ((self = [super initWithFrame:NSZeroRect])) {
233     CGFloat curX = kFramePadding;
234     CGFloat curY = views::kRelatedControlVerticalSpacing;
235
236     // Add the username.
237     usernameField_.reset([UsernameLabel(GetDisplayUsername(form)) retain]);
238     [usernameField_ setFrameOrigin:NSMakePoint(curX, curY)];
239     [self addSubview:usernameField_];
240
241     // Move to the right of the username and add the password.
242     curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
243     passwordField_.reset([PasswordLabel(form.password_value) retain]);
244     [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
245     [self addSubview:passwordField_];
246
247     // Move to the top-right of the password.
248     curY =
249         NSMaxY([passwordField_ frame]) + views::kRelatedControlVerticalSpacing;
250
251     // Update the frame.
252     [self setFrameSize:NSMakeSize(ItemWidth(), curY)];
253   }
254   return self;
255 }
256
257 @end
258
259 @implementation ManagePasswordItemPendingView (Testing)
260
261 - (NSTextField*)usernameField {
262   return usernameField_.get();
263 }
264
265 - (NSSecureTextField*)passwordField {
266   return passwordField_.get();
267 }
268
269 @end
270
271 @interface ManagePasswordItemViewController ()
272 - (void)onDeleteClicked:(id)sender;
273 - (void)onUndoClicked:(id)sender;
274
275 // Find the next content view and repaint.
276 - (void)refresh;
277
278 // Find the next content view.
279 - (void)updateContent;
280
281 // Repaint the content.
282 - (void)layoutContent;
283 @end
284
285 @implementation ManagePasswordItemViewController
286
287 - (id)initWithModel:(ManagePasswordsBubbleModel*)model
288        passwordForm:(const autofill::PasswordForm&)passwordForm
289            position:(password_manager::ui::PasswordItemPosition)position {
290   if ((self = [super initWithNibName:nil bundle:nil])) {
291     model_ = model;
292     position_ = position;
293     passwordForm_ = passwordForm;
294     state_ = password_manager::ui::IsPendingState(model_->state())
295         ? MANAGE_PASSWORD_ITEM_STATE_PENDING
296         : MANAGE_PASSWORD_ITEM_STATE_MANAGE;
297     [self updateContent];
298   }
299   return self;
300 }
301
302 - (void)onDeleteClicked:(id)sender {
303   DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_MANAGE, state_);
304   state_ = MANAGE_PASSWORD_ITEM_STATE_DELETED;
305   [self refresh];
306   model_->OnPasswordAction(passwordForm_,
307                            ManagePasswordsBubbleModel::REMOVE_PASSWORD);
308 }
309
310 - (void)onUndoClicked:(id)sender {
311   DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_DELETED, state_);
312   state_ = MANAGE_PASSWORD_ITEM_STATE_MANAGE;
313   [self refresh];
314   model_->OnPasswordAction(passwordForm_,
315                            ManagePasswordsBubbleModel::ADD_PASSWORD);
316 }
317
318 - (void)refresh {
319   [self updateContent];
320   [self layoutContent];
321 }
322
323 - (void)updateContent {
324   switch (state_) {
325     default:
326       NOTREACHED();
327     case MANAGE_PASSWORD_ITEM_STATE_PENDING:
328       contentView_.reset(
329           [[ManagePasswordItemPendingView alloc] initWithForm:passwordForm_]);
330       return;
331     case MANAGE_PASSWORD_ITEM_STATE_MANAGE:
332       contentView_.reset([[ManagePasswordItemManageView alloc]
333           initWithForm:passwordForm_
334                 target:self
335                 action:@selector(onDeleteClicked:)]);
336       return;
337     case MANAGE_PASSWORD_ITEM_STATE_DELETED:
338       contentView_.reset([[ManagePasswordItemUndoView alloc]
339           initWithTarget:self
340                   action:@selector(onUndoClicked:)]);
341       return;
342   };
343 }
344
345 - (void)layoutContent {
346   // Update the view size according to the content view size.
347   const NSSize contentSize = [contentView_ frame].size;
348   [self.view setFrameSize:contentSize];
349
350   // Add the content.
351   [self.view setSubviews:@[ contentView_ ]];
352
353   // Add the borders, which go along the entire view.
354   SkColor borderSkColor;
355   ui::CommonThemeGetSystemColor(
356       ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor, &borderSkColor);
357   CGColorRef borderColor = gfx::CGColorCreateFromSkColor(borderSkColor);
358
359   // Mac views don't have backing layers by default.
360   base::scoped_nsobject<CALayer> rootLayer([[CALayer alloc] init]);
361   [rootLayer setFrame:NSRectToCGRect(self.view.frame)];
362   [self.view setLayer:rootLayer];
363   [self.view setWantsLayer:YES];
364
365   // The top border is only present for the first item.
366   if (position_ == password_manager::ui::FIRST_ITEM) {
367     base::scoped_nsobject<CALayer> topBorder([[CALayer alloc] init]);
368     [topBorder setBackgroundColor:borderColor];
369     [topBorder setFrame:CGRectMake(0,
370                                    contentSize.height - kBorderWidth,
371                                    contentSize.width,
372                                    kBorderWidth)];
373     [self.view.layer addSublayer:topBorder];
374   }
375
376   // The bottom border is always present.
377   base::scoped_nsobject<CALayer> bottomBorder([[CALayer alloc] init]);
378   [bottomBorder setBackgroundColor:borderColor];
379   [bottomBorder setFrame:CGRectMake(0, 0, contentSize.width, kBorderWidth)];
380   [self.view.layer addSublayer:bottomBorder];
381 }
382
383 - (void)loadView {
384   self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
385   [self layoutContent];
386 }
387
388 @end
389
390 @implementation ManagePasswordItemViewController (Testing)
391
392 - (ManagePasswordItemState)state {
393   return state_;
394 }
395
396 - (NSView*)contentView {
397   return contentView_.get();
398 }
399
400 - (autofill::PasswordForm)passwordForm {
401   return passwordForm_;
402 }
403
404 @end
405
406 @implementation ManagePasswordItemClickableView
407
408 - (void)drawRect:(NSRect)dirtyRect {
409   [super drawRect:dirtyRect];
410   if (hovering_) {
411     [HoverColor() setFill];
412     NSRectFill(dirtyRect);
413   }
414 }
415
416 - (void)mouseEntered:(NSEvent*)event {
417   hovering_ = YES;
418   [self setNeedsDisplay:YES];
419 }
420
421 - (void)mouseExited:(NSEvent*)event {
422   hovering_ = NO;
423   [self setNeedsDisplay:YES];
424 }
425
426 - (void)updateTrackingAreas {
427   [super updateTrackingAreas];
428   if (trackingArea_.get())
429     [self removeTrackingArea:trackingArea_.get()];
430   NSTrackingAreaOptions options =
431       NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
432   trackingArea_.reset([[CrTrackingArea alloc] initWithRect:[self bounds]
433                                                    options:options
434                                                      owner:self
435                                                   userInfo:nil]);
436   [self addTrackingArea:trackingArea_.get()];
437 }
438
439 @end