Upstream version 10.39.225.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 }  // namespace
107
108 @implementation ManagePasswordItemUndoView
109 - (id)initWithTarget:(id)target action:(SEL)action {
110   if ((self = [super init])) {
111     // The button should look like a link.
112     undoButton_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
113     base::scoped_nsobject<HyperlinkButtonCell> cell([[HyperlinkButtonCell alloc]
114         initTextCell:l10n_util::GetNSString(IDS_MANAGE_PASSWORDS_UNDO)]);
115     [cell setControlSize:NSSmallControlSize];
116     [cell setShouldUnderline:NO];
117     [cell setUnderlineOnHover:NO];
118     [cell setTextColor:gfx::SkColorToCalibratedNSColor(
119         chrome_style::GetLinkColor())];
120     [undoButton_ setCell:cell.get()];
121     [undoButton_ sizeToFit];
122     [undoButton_ setTarget:target];
123     [undoButton_ setAction:action];
124
125     const CGFloat width = ItemWidth();
126     CGFloat curX = kFramePadding;
127     CGFloat curY = views::kRelatedControlVerticalSpacing;
128
129     // Add the explanation text.
130     NSTextField* label =
131         Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED));
132     [label setFrameOrigin:NSMakePoint(curX, curY)];
133     [self addSubview:label];
134
135     // The undo button should be right-aligned.
136     curX = width - kFramePadding - NSWidth([undoButton_ frame]);
137     [undoButton_ setFrameOrigin:NSMakePoint(curX, curY)];
138     [self addSubview:undoButton_ ];
139
140     // Move to the top-right of the delete button.
141     curX = NSMaxX([undoButton_ frame]) + kFramePadding;
142     curY = NSMaxY([undoButton_ frame]) + views::kRelatedControlVerticalSpacing;
143
144     // Update the frame.
145     DCHECK_EQ(width, curX);
146     [self setFrameSize:NSMakeSize(curX, curY)];
147   }
148   return self;
149 }
150 @end
151
152 @implementation ManagePasswordItemUndoView (Testing)
153 - (NSButton*)undoButton {
154   return undoButton_.get();
155 }
156 @end
157
158 @implementation ManagePasswordItemManageView
159 - (id)initWithForm:(const autofill::PasswordForm&)form
160             target:(id)target
161             action:(SEL)action {
162   if ((self = [super init])) {
163     deleteButton_.reset([[HoverImageButton alloc] initWithFrame:NSZeroRect]);
164     [deleteButton_ setFrameSize:NSMakeSize(chrome_style::GetCloseButtonSize(),
165                                            chrome_style::GetCloseButtonSize())];
166     [deleteButton_ setBordered:NO];
167     [[deleteButton_ cell] setHighlightsBy:NSNoCellMask];
168     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
169     [deleteButton_
170         setDefaultImage:bundle.GetImageNamed(IDR_CLOSE_2).ToNSImage()];
171     [deleteButton_
172         setHoverImage:bundle.GetImageNamed(IDR_CLOSE_2_H).ToNSImage()];
173     [deleteButton_
174         setPressedImage:bundle.GetImageNamed(IDR_CLOSE_2_P).ToNSImage()];
175     [deleteButton_ setTarget:target];
176     [deleteButton_ setAction:action];
177
178     const CGFloat width = ItemWidth();
179     CGFloat curX = kFramePadding;
180     CGFloat curY = views::kRelatedControlVerticalSpacing;
181
182     // Add the username.
183     usernameField_.reset([UsernameLabel(form.username_value) retain]);
184     [usernameField_ setFrameOrigin:NSMakePoint(curX, curY)];
185     [self addSubview:usernameField_];
186
187     // Move to the right of the username and add the password.
188     curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
189     passwordField_.reset([PasswordLabel(form.password_value) retain]);
190     [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
191     [self addSubview:passwordField_];
192
193     // The delete button should be right-aligned.
194     curX = width - kFramePadding - NSWidth([deleteButton_ frame]);
195     [deleteButton_ setFrameOrigin:NSMakePoint(curX, curY)];
196     [self addSubview:deleteButton_];
197
198     // Move to the top-right of the delete button.
199     curX = NSMaxX([deleteButton_ frame]) + kFramePadding;
200     curY =
201         NSMaxY([deleteButton_ frame]) + views::kRelatedControlVerticalSpacing;
202
203     // Update the frame.
204     DCHECK_EQ(width, curX);
205     [self setFrameSize:NSMakeSize(curX, curY)];
206   }
207   return self;
208 }
209 @end
210
211 @implementation ManagePasswordItemManageView (Testing)
212 - (NSTextField*)usernameField {
213   return usernameField_.get();
214 }
215 - (NSSecureTextField*)passwordField {
216   return passwordField_.get();
217 }
218 - (NSButton*)deleteButton {
219   return deleteButton_.get();
220 }
221 @end
222
223 @implementation ManagePasswordItemPendingView
224
225 - (id)initWithForm:(const autofill::PasswordForm&)form {
226   if ((self = [super initWithFrame:NSZeroRect])) {
227     CGFloat curX = kFramePadding;
228     CGFloat curY = views::kRelatedControlVerticalSpacing;
229
230     // Add the username.
231     usernameField_.reset([UsernameLabel(form.username_value) retain]);
232     [usernameField_ setFrameOrigin:NSMakePoint(curX, curY)];
233     [self addSubview:usernameField_];
234
235     // Move to the right of the username and add the password.
236     curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
237     passwordField_.reset([PasswordLabel(form.password_value) retain]);
238     [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
239     [self addSubview:passwordField_];
240
241     // Move to the top-right of the password.
242     curY =
243         NSMaxY([passwordField_ frame]) + views::kRelatedControlVerticalSpacing;
244
245     // Update the frame.
246     [self setFrameSize:NSMakeSize(ItemWidth(), curY)];
247   }
248   return self;
249 }
250
251 @end
252
253 @implementation ManagePasswordItemPendingView (Testing)
254
255 - (NSTextField*)usernameField {
256   return usernameField_.get();
257 }
258
259 - (NSSecureTextField*)passwordField {
260   return passwordField_.get();
261 }
262
263 @end
264
265 @interface ManagePasswordItemViewController ()
266 - (void)onDeleteClicked:(id)sender;
267 - (void)onUndoClicked:(id)sender;
268
269 // Find the next content view and repaint.
270 - (void)refresh;
271
272 // Find the next content view.
273 - (void)updateContent;
274
275 // Repaint the content.
276 - (void)layoutContent;
277 @end
278
279 @implementation ManagePasswordItemViewController
280
281 - (id)initWithModel:(ManagePasswordsBubbleModel*)model
282        passwordForm:(const autofill::PasswordForm&)passwordForm
283            position:(password_manager::ui::PasswordItemPosition)position {
284   if ((self = [super initWithNibName:nil bundle:nil])) {
285     model_ = model;
286     position_ = position;
287     passwordForm_ = passwordForm;
288     state_ = password_manager::ui::IsPendingState(model_->state())
289         ? MANAGE_PASSWORD_ITEM_STATE_PENDING
290         : MANAGE_PASSWORD_ITEM_STATE_MANAGE;
291     [self updateContent];
292   }
293   return self;
294 }
295
296 - (void)onDeleteClicked:(id)sender {
297   DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_MANAGE, state_);
298   state_ = MANAGE_PASSWORD_ITEM_STATE_DELETED;
299   [self refresh];
300   model_->OnPasswordAction(passwordForm_,
301                            ManagePasswordsBubbleModel::REMOVE_PASSWORD);
302 }
303
304 - (void)onUndoClicked:(id)sender {
305   DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_DELETED, state_);
306   state_ = MANAGE_PASSWORD_ITEM_STATE_MANAGE;
307   [self refresh];
308   model_->OnPasswordAction(passwordForm_,
309                            ManagePasswordsBubbleModel::ADD_PASSWORD);
310 }
311
312 - (void)refresh {
313   [self updateContent];
314   [self layoutContent];
315 }
316
317 - (void)updateContent {
318   switch (state_) {
319     default:
320       NOTREACHED();
321     case MANAGE_PASSWORD_ITEM_STATE_PENDING:
322       contentView_.reset(
323           [[ManagePasswordItemPendingView alloc] initWithForm:passwordForm_]);
324       return;
325     case MANAGE_PASSWORD_ITEM_STATE_MANAGE:
326       contentView_.reset([[ManagePasswordItemManageView alloc]
327           initWithForm:passwordForm_
328                 target:self
329                 action:@selector(onDeleteClicked:)]);
330       return;
331     case MANAGE_PASSWORD_ITEM_STATE_DELETED:
332       contentView_.reset([[ManagePasswordItemUndoView alloc]
333           initWithTarget:self
334                   action:@selector(onUndoClicked:)]);
335       return;
336   };
337 }
338
339 - (void)layoutContent {
340   // Update the view size according to the content view size.
341   const NSSize contentSize = [contentView_ frame].size;
342   [self.view setFrameSize:contentSize];
343
344   // Add the content.
345   [self.view setSubviews:@[ contentView_ ]];
346
347   // Add the borders, which go along the entire view.
348   SkColor borderSkColor;
349   ui::CommonThemeGetSystemColor(
350       ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor, &borderSkColor);
351   CGColorRef borderColor = gfx::CGColorCreateFromSkColor(borderSkColor);
352
353   // Mac views don't have backing layers by default.
354   base::scoped_nsobject<CALayer> rootLayer([[CALayer alloc] init]);
355   [rootLayer setFrame:NSRectToCGRect(self.view.frame)];
356   [self.view setLayer:rootLayer];
357   [self.view setWantsLayer:YES];
358
359   // The top border is only present for the first item.
360   if (position_ == password_manager::ui::FIRST_ITEM) {
361     base::scoped_nsobject<CALayer> topBorder([[CALayer alloc] init]);
362     [topBorder setBackgroundColor:borderColor];
363     [topBorder setFrame:CGRectMake(0,
364                                    contentSize.height - kBorderWidth,
365                                    contentSize.width,
366                                    kBorderWidth)];
367     [self.view.layer addSublayer:topBorder];
368   }
369
370   // The bottom border is always present.
371   base::scoped_nsobject<CALayer> bottomBorder([[CALayer alloc] init]);
372   [bottomBorder setBackgroundColor:borderColor];
373   [bottomBorder setFrame:CGRectMake(0, 0, contentSize.width, kBorderWidth)];
374   [self.view.layer addSublayer:bottomBorder];
375 }
376
377 - (void)loadView {
378   self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
379   [self layoutContent];
380 }
381
382 @end
383
384 @implementation ManagePasswordItemViewController (Testing)
385
386 - (ManagePasswordItemState)state {
387   return state_;
388 }
389
390 - (NSView*)contentView {
391   return contentView_.get();
392 }
393
394 - (autofill::PasswordForm)passwordForm {
395   return passwordForm_;
396 }
397
398 @end
399
400 @implementation ManagePasswordItemClickableView
401
402 - (void)drawRect:(NSRect)dirtyRect {
403   [super drawRect:dirtyRect];
404   if (hovering_) {
405     [HoverColor() setFill];
406     NSRectFill(dirtyRect);
407   }
408 }
409
410 - (void)mouseEntered:(NSEvent*)event {
411   hovering_ = YES;
412   [self setNeedsDisplay:YES];
413 }
414
415 - (void)mouseExited:(NSEvent*)event {
416   hovering_ = NO;
417   [self setNeedsDisplay:YES];
418 }
419
420 - (void)updateTrackingAreas {
421   [super updateTrackingAreas];
422   if (trackingArea_.get())
423     [self removeTrackingArea:trackingArea_.get()];
424   NSTrackingAreaOptions options =
425       NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
426   trackingArea_.reset([[CrTrackingArea alloc] initWithRect:[self bounds]
427                                                    options:options
428                                                      owner:self
429                                                   userInfo:nil]);
430   [self addTrackingArea:trackingArea_.get()];
431 }
432
433 @end