Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / autofill / autofill_main_container.mm
1 // Copyright (c) 2013 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/autofill/autofill_main_container.h"
6
7 #include <algorithm>
8 #include <cmath>
9
10 #include "base/mac/foundation_util.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
13 #include "chrome/browser/ui/chrome_style.h"
14 #import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h"
15 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
16 #import "chrome/browser/ui/cocoa/autofill/autofill_notification_container.h"
17 #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h"
18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
19 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
20 #include "grit/theme_resources.h"
21 #include "skia/ext/skia_utils_mac.h"
22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #import "ui/base/cocoa/controls/blue_label_button.h"
24 #import "ui/base/cocoa/controls/hyperlink_text_view.h"
25 #include "ui/base/cocoa/window_size_constants.h"
26 #include "ui/gfx/range/range.h"
27
28 namespace {
29
30 // Padding between buttons and the last suggestion or details view. The mock
31 // has a total of 30px - but 10px are already provided by details/suggestions.
32 const CGFloat kButtonVerticalPadding = 20.0;
33
34 // Padding around the text for the legal documents.
35 const CGFloat kLegalDocumentsPadding = 20.0;
36
37 // The font color for the legal documents text. Set to match the Views
38 // implementation.
39 const SkColor kLegalDocumentsTextColor = SkColorSetRGB(102, 102, 102);
40
41 }  // namespace
42
43 @interface AutofillMainContainer (Private)
44 - (void)buildWindowButtons;
45 - (void)layoutButtons;
46 - (void)updateButtons;
47 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width;
48 @end
49
50
51 @implementation AutofillMainContainer
52
53 @synthesize target = target_;
54
55 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
56   if (self = [super init]) {
57     delegate_ = delegate;
58   }
59   return self;
60 }
61
62 - (void)loadView {
63   [self buildWindowButtons];
64
65   base::scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:NSZeroRect]);
66   [view setAutoresizesSubviews:YES];
67   [view setSubviews:@[buttonContainer_]];
68   [self setView:view];
69
70   // Set up Wallet icon.
71   buttonStripImage_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
72   [self updateWalletIcon];
73   [[self view] addSubview:buttonStripImage_];
74
75   // Set up "Save in Chrome" checkbox.
76   saveInChromeCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
77   [saveInChromeCheckbox_ setButtonType:NSSwitchButton];
78   [saveInChromeCheckbox_ setTitle:
79       base::SysUTF16ToNSString(delegate_->SaveLocallyText())];
80   [saveInChromeCheckbox_ sizeToFit];
81   [[self view] addSubview:saveInChromeCheckbox_];
82
83   saveInChromeTooltip_.reset(
84       [[AutofillTooltipController alloc]
85             initWithArrowLocation:info_bubble::kTopCenter]);
86   [saveInChromeTooltip_ setImage:
87       ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
88           IDR_AUTOFILL_TOOLTIP_ICON).ToNSImage()];
89   [saveInChromeTooltip_ setMessage:
90       base::SysUTF16ToNSString(delegate_->SaveLocallyTooltip())];
91   [[self view] addSubview:[saveInChromeTooltip_ view]];
92   [self updateSaveInChrome];
93
94   detailsContainer_.reset(
95       [[AutofillDetailsContainer alloc] initWithDelegate:delegate_]);
96   NSSize frameSize = [[detailsContainer_ view] frame].size;
97   [[detailsContainer_ view] setFrameOrigin:
98       NSMakePoint(0, NSHeight([buttonContainer_ frame]))];
99   frameSize.height += NSHeight([buttonContainer_ frame]);
100   [[self view] setFrameSize:frameSize];
101   [[self view] addSubview:[detailsContainer_ view]];
102
103   legalDocumentsView_.reset(
104       [[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
105   [legalDocumentsView_ setEditable:NO];
106   [legalDocumentsView_ setBackgroundColor:
107       [NSColor colorWithCalibratedRed:0.96
108                                 green:0.96
109                                  blue:0.96
110                                 alpha:1.0]];
111   [legalDocumentsView_ setDrawsBackground:YES];
112   [legalDocumentsView_ setTextContainerInset:
113       NSMakeSize(kLegalDocumentsPadding, kLegalDocumentsPadding)];
114   [legalDocumentsView_ setHidden:YES];
115   [legalDocumentsView_ setDelegate:self];
116   legalDocumentsSizeDirty_ = YES;
117   [[self view] addSubview:legalDocumentsView_];
118
119   notificationContainer_.reset(
120       [[AutofillNotificationContainer alloc] initWithDelegate:delegate_]);
121   [[self view] addSubview:[notificationContainer_ view]];
122 }
123
124 // Called when embedded links are clicked.
125 - (BOOL)textView:(NSTextView*)textView
126     clickedOnLink:(id)link
127           atIndex:(NSUInteger)charIndex {
128   int index = [base::mac::ObjCCastStrict<NSNumber>(link) intValue];
129   delegate_->LegalDocumentLinkClicked(
130       delegate_->LegalDocumentLinks()[index]);
131   return YES;
132 }
133
134 - (NSSize)decorationSizeForWidth:(CGFloat)width {
135   NSSize buttonSize = [buttonContainer_ frame].size;
136   NSSize buttonStripImageSize = [buttonStripImage_ frame].size;
137   NSSize buttonStripSize =
138       NSMakeSize(buttonSize.width + chrome_style::kHorizontalPadding +
139                      buttonStripImageSize.width,
140                  std::max(buttonSize.height + kButtonVerticalPadding,
141                           buttonStripImageSize.height) +
142                      chrome_style::kClientBottomPadding);
143
144   NSSize size = NSMakeSize(std::max(buttonStripSize.width, width),
145                            buttonStripSize.height);
146   if (![legalDocumentsView_ isHidden]) {
147     NSSize legalDocumentSize =
148         [self preferredLegalDocumentSizeForWidth:width];
149     size.height += legalDocumentSize.height + autofill::kVerticalSpacing;
150   }
151
152   NSSize notificationSize =
153       [notificationContainer_ preferredSizeForWidth:width];
154   size.height += notificationSize.height;
155
156   return size;
157 }
158
159 - (NSSize)preferredSize {
160   NSSize detailsSize = [detailsContainer_ preferredSize];
161   NSSize decorationSize = [self decorationSizeForWidth:detailsSize.width];
162
163   NSSize size = NSMakeSize(std::max(decorationSize.width, detailsSize.width),
164                            decorationSize.height + detailsSize.height);
165   size.height += autofill::kDetailVerticalPadding;
166
167   return size;
168 }
169
170 - (void)performLayout {
171   NSRect bounds = [[self view] bounds];
172
173   CGFloat currentY = 0.0;
174   if (![legalDocumentsView_ isHidden]) {
175     [legalDocumentsView_ setFrameSize:
176         [self preferredLegalDocumentSizeForWidth:NSWidth(bounds)]];
177     currentY = NSMaxY([legalDocumentsView_ frame]) + autofill::kVerticalSpacing;
178   }
179
180   NSRect buttonFrame = [buttonContainer_ frame];
181   buttonFrame.origin.y = currentY + chrome_style::kClientBottomPadding;
182   [buttonContainer_ setFrameOrigin:buttonFrame.origin];
183   currentY = NSMaxY(buttonFrame) + kButtonVerticalPadding;
184
185   NSPoint walletIconOrigin =
186       NSMakePoint(chrome_style::kHorizontalPadding, buttonFrame.origin.y);
187   [buttonStripImage_ setFrameOrigin:walletIconOrigin];
188   currentY = std::max(currentY, NSMaxY([buttonStripImage_ frame]));
189
190   NSRect checkboxFrame = [saveInChromeCheckbox_ frame];
191   [saveInChromeCheckbox_ setFrameOrigin:
192       NSMakePoint(chrome_style::kHorizontalPadding,
193                   NSMidY(buttonFrame) - NSHeight(checkboxFrame) / 2.0)];
194
195   NSRect tooltipFrame = [[saveInChromeTooltip_ view] frame];
196   [[saveInChromeTooltip_ view] setFrameOrigin:
197       NSMakePoint(NSMaxX([saveInChromeCheckbox_ frame]) + autofill::kButtonGap,
198                   NSMidY(buttonFrame) - (NSHeight(tooltipFrame) / 2.0))];
199
200   NSRect notificationFrame = NSZeroRect;
201   notificationFrame.size = [notificationContainer_ preferredSizeForWidth:
202       NSWidth(bounds)];
203
204   // Buttons/checkbox/legal take up lower part of view, notifications the
205   // upper part. Adjust the detailsContainer to take up the remainder.
206   CGFloat remainingHeight =
207       NSHeight(bounds) - currentY - NSHeight(notificationFrame);
208   NSRect containerFrame =
209       NSMakeRect(0, currentY, NSWidth(bounds), remainingHeight);
210   [[detailsContainer_ view] setFrame:containerFrame];
211   [detailsContainer_ performLayout];
212
213   notificationFrame.origin = NSMakePoint(0, NSMaxY(containerFrame));
214   [[notificationContainer_ view] setFrame:notificationFrame];
215   [notificationContainer_ performLayout];
216 }
217
218 - (void)buildWindowButtons {
219   if (buttonContainer_.get())
220     return;
221
222   buttonContainer_.reset([[GTMWidthBasedTweaker alloc] initWithFrame:
223       ui::kWindowSizeDeterminedLater]);
224   [buttonContainer_
225       setAutoresizingMask:(NSViewMinXMargin | NSViewMaxYMargin)];
226
227   base::scoped_nsobject<NSButton> button(
228       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
229   [button setKeyEquivalent:kKeyEquivalentEscape];
230   [button setTarget:target_];
231   [button setAction:@selector(cancel:)];
232   [button sizeToFit];
233   [buttonContainer_ addSubview:button];
234
235   CGFloat nextX = NSMaxX([button frame]) + autofill::kButtonGap;
236   button.reset([[BlueLabelButton alloc] initWithFrame:NSZeroRect]);
237   [button setFrameOrigin:NSMakePoint(nextX, 0)];
238   [button setKeyEquivalent:kKeyEquivalentReturn];
239   [button setTarget:target_];
240   [button setAction:@selector(accept:)];
241   [buttonContainer_ addSubview:button];
242   [self updateButtons];
243
244   NSRect frame = NSMakeRect(
245       -NSMaxX([button frame]) - chrome_style::kHorizontalPadding, 0,
246       NSMaxX([button frame]), NSHeight([button frame]));
247   [buttonContainer_ setFrame:frame];
248 }
249
250 - (void)layoutButtons {
251   base::scoped_nsobject<GTMUILocalizerAndLayoutTweaker> layoutTweaker(
252       [[GTMUILocalizerAndLayoutTweaker alloc] init]);
253   [layoutTweaker tweakUI:buttonContainer_];
254
255   // Now ensure both buttons have the same height. The second button is
256   // known to be the larger one.
257   CGFloat buttonHeight =
258       NSHeight([[[buttonContainer_ subviews] objectAtIndex:1] frame]);
259
260   // Force first button to be the same height.
261   NSView* button = [[buttonContainer_ subviews] objectAtIndex:0];
262   NSSize buttonSize = [button frame].size;
263   buttonSize.height = buttonHeight;
264   [button setFrameSize:buttonSize];
265 }
266
267 - (void)updateButtons {
268   NSButton* button = base::mac::ObjCCastStrict<NSButton>(
269       [[buttonContainer_ subviews] objectAtIndex:0]);
270   [button setTitle:base::SysUTF16ToNSString(delegate_->CancelButtonText())];
271   button = base::mac::ObjCCastStrict<NSButton>(
272     [[buttonContainer_ subviews] objectAtIndex:1]);
273   [button setTitle:base::SysUTF16ToNSString(delegate_->ConfirmButtonText())];
274   [self layoutButtons];
275 }
276
277 // Compute the preferred size for the legal documents text, given a width.
278 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width {
279   // Only recompute if necessary (On text or frame width change).
280   if (!legalDocumentsSizeDirty_ &&
281       std::abs(legalDocumentsSize_.width - width) < 1.0) {
282     return legalDocumentsSize_;
283   }
284
285   // There's no direct API to compute desired sizes - use layouting instead.
286   // Layout in a rect with fixed width and "infinite" height.
287   NSRect currentFrame = [legalDocumentsView_ frame];
288   [legalDocumentsView_ setFrame:NSMakeRect(0, 0, width, CGFLOAT_MAX)];
289
290   // Now use the layout manager to compute layout.
291   NSLayoutManager* layoutManager = [legalDocumentsView_ layoutManager];
292   NSTextContainer* textContainer = [legalDocumentsView_ textContainer];
293   [layoutManager ensureLayoutForTextContainer:textContainer];
294   NSRect newFrame = [layoutManager usedRectForTextContainer:textContainer];
295
296   // And finally, restore old frame.
297   [legalDocumentsView_ setFrame:currentFrame];
298   newFrame.size.width = width;
299
300   // Account for the padding around the text.
301   newFrame.size.height += 2 * kLegalDocumentsPadding;
302
303   legalDocumentsSizeDirty_ = NO;
304   legalDocumentsSize_ = newFrame.size;
305   return legalDocumentsSize_;
306 }
307
308 - (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
309   return [detailsContainer_ sectionForId:section];
310 }
311
312 - (void)modelChanged {
313   [self updateSaveInChrome];
314   [self updateWalletIcon];
315   [self updateButtons];
316   [detailsContainer_ modelChanged];
317 }
318
319 - (BOOL)saveDetailsLocally {
320   return [saveInChromeCheckbox_ state] == NSOnState;
321 }
322
323 - (void)updateLegalDocuments {
324   NSString* text = base::SysUTF16ToNSString(delegate_->LegalDocumentsText());
325
326   if ([text length]) {
327     NSFont* font =
328         [NSFont labelFontOfSize:[[legalDocumentsView_ font] pointSize]];
329     NSColor* color = gfx::SkColorToCalibratedNSColor(kLegalDocumentsTextColor);
330     [legalDocumentsView_ setMessage:text withFont:font messageColor:color];
331
332     const std::vector<gfx::Range>& link_ranges =
333         delegate_->LegalDocumentLinks();
334     for (size_t i = 0; i < link_ranges.size(); ++i) {
335       NSRange range = link_ranges[i].ToNSRange();
336       [legalDocumentsView_ addLinkRange:range
337                                withName:@(i)
338                               linkColor:[NSColor blueColor]];
339     }
340     legalDocumentsSizeDirty_ = YES;
341   }
342   [legalDocumentsView_ setHidden:[text length] == 0];
343
344   // Always request re-layout on state change.
345   id delegate = [[[self view] window] windowController];
346   if ([delegate respondsToSelector:@selector(requestRelayout)])
347     [delegate performSelector:@selector(requestRelayout)];
348 }
349
350 - (void)updateNotificationArea {
351   [notificationContainer_ setNotifications:delegate_->CurrentNotifications()];
352   id delegate = [[[self view] window] windowController];
353   if ([delegate respondsToSelector:@selector(requestRelayout)])
354     [delegate performSelector:@selector(requestRelayout)];
355 }
356
357 - (void)setAnchorView:(NSView*)anchorView {
358   [notificationContainer_ setAnchorView:anchorView];
359 }
360
361 - (BOOL)validate {
362   return [detailsContainer_ validate];
363 }
364
365 - (void)updateSaveInChrome {
366   [saveInChromeCheckbox_ setHidden:!delegate_->ShouldOfferToSaveInChrome()];
367   [[saveInChromeTooltip_ view] setHidden:[saveInChromeCheckbox_ isHidden]];
368   [saveInChromeCheckbox_ setState:
369       (delegate_->ShouldSaveInChrome() ? NSOnState : NSOffState)];
370 }
371
372 - (void)makeFirstInvalidInputFirstResponder {
373   NSView* field = [detailsContainer_ firstInvalidField];
374   if (!field)
375     return;
376
377   [detailsContainer_ scrollToView:field];
378   [[[self view] window] makeFirstResponder:field];
379 }
380
381 - (void)updateWalletIcon {
382   gfx::Image image = delegate_->ButtonStripImage();
383   [buttonStripImage_ setHidden:image.IsEmpty()];
384   if (![buttonStripImage_ isHidden]) {
385     [buttonStripImage_ setImage:image.ToNSImage()];
386     [buttonStripImage_ setFrameSize:[[buttonStripImage_ image] size]];
387   }
388 }
389
390 - (void)scrollInitialEditorIntoViewAndMakeFirstResponder {
391   // Try to focus on the first invalid field. If there isn't one, focus on the
392   // first editable field instead.
393   NSView* field = [detailsContainer_ firstInvalidField];
394   if (!field)
395     field = [detailsContainer_ firstVisibleField];
396   if (!field)
397     return;
398
399   [detailsContainer_ scrollToView:field];
400   [[[self view] window] makeFirstResponder:field];
401 }
402
403 - (void)updateErrorBubble {
404   [detailsContainer_ updateErrorBubble];
405 }
406
407 @end
408
409
410 @implementation AutofillMainContainer (Testing)
411
412 - (NSButton*)saveInChromeCheckboxForTesting {
413   return saveInChromeCheckbox_.get();
414 }
415
416 - (NSImageView*)buttonStripImageForTesting {
417   return buttonStripImage_.get();
418 }
419
420 - (NSButton*)saveInChromeTooltipForTesting {
421   return base::mac::ObjCCast<NSButton>([saveInChromeTooltip_ view]);
422 }
423
424 @end