- add sources.
[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 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
15 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
16 #import "chrome/browser/ui/cocoa/autofill/autofill_details_container.h"
17 #import "chrome/browser/ui/cocoa/autofill/autofill_notification_container.h"
18 #import "chrome/browser/ui/cocoa/hyperlink_text_view.h"
19 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #include "ui/base/cocoa/window_size_constants.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/gfx/range/range.h"
26
27 @interface AutofillMainContainer (Private)
28 - (void)buildWindowButtons;
29 - (void)layoutButtons;
30 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width;
31 @end
32
33
34 @implementation AutofillMainContainer
35
36 @synthesize target = target_;
37
38 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
39   if (self = [super init]) {
40     delegate_ = delegate;
41   }
42   return self;
43 }
44
45 - (void)loadView {
46   [self buildWindowButtons];
47
48   base::scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:NSZeroRect]);
49   [view setAutoresizesSubviews:YES];
50   [view setSubviews:@[buttonContainer_]];
51   [self setView:view];
52
53   [self layoutButtons];
54
55   // Set up Wallet icon.
56   buttonStripImage_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
57   [self updateWalletIcon];
58   [[self view] addSubview:buttonStripImage_];
59
60   // Set up "Save in Chrome" checkbox.
61   saveInChromeCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
62   [saveInChromeCheckbox_ setButtonType:NSSwitchButton];
63   [saveInChromeCheckbox_ setTitle:
64       base::SysUTF16ToNSString(delegate_->SaveLocallyText())];
65   [self updateSaveInChrome];
66   [saveInChromeCheckbox_ sizeToFit];
67   [[self view] addSubview:saveInChromeCheckbox_];
68
69   saveInChromeTooltip_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
70   [saveInChromeTooltip_ setImage:
71       ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
72           IDR_AUTOFILL_TOOLTIP_ICON).ToNSImage()];
73   [saveInChromeTooltip_ setToolTip:
74       base::SysUTF16ToNSString(delegate_->SaveLocallyTooltip())];
75   [saveInChromeTooltip_ setFrameSize:[[saveInChromeTooltip_ image] size]];
76   [[self view] addSubview:saveInChromeTooltip_];
77
78   detailsContainer_.reset(
79       [[AutofillDetailsContainer alloc] initWithDelegate:delegate_]);
80   NSSize frameSize = [[detailsContainer_ view] frame].size;
81   [[detailsContainer_ view] setFrameOrigin:
82       NSMakePoint(0, NSHeight([buttonContainer_ frame]))];
83   frameSize.height += NSHeight([buttonContainer_ frame]);
84   [[self view] setFrameSize:frameSize];
85   [[self view] addSubview:[detailsContainer_ view]];
86
87   legalDocumentsView_.reset(
88       [[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
89   [legalDocumentsView_ setEditable:NO];
90   [legalDocumentsView_ setBackgroundColor:
91       [NSColor colorWithCalibratedRed:0.96
92                                 green:0.96
93                                  blue:0.96
94                                 alpha:1.0]];
95   [legalDocumentsView_ setDrawsBackground:YES];
96   [legalDocumentsView_ setHidden:YES];
97   [legalDocumentsView_ setDelegate:self];
98   legalDocumentsSizeDirty_ = YES;
99   [[self view] addSubview:legalDocumentsView_];
100
101   notificationContainer_.reset(
102       [[AutofillNotificationContainer alloc] initWithDelegate:delegate_]);
103   [[self view] addSubview:[notificationContainer_ view]];
104 }
105
106 // Called when embedded links are clicked.
107 - (BOOL)textView:(NSTextView*)textView
108     clickedOnLink:(id)link
109           atIndex:(NSUInteger)charIndex {
110   int index = [base::mac::ObjCCastStrict<NSNumber>(link) intValue];
111   delegate_->LegalDocumentLinkClicked(
112       delegate_->LegalDocumentLinks()[index]);
113   return YES;
114 }
115
116 - (NSSize)preferredSize {
117   // Overall width is determined by |detailsContainer_|.
118   NSSize buttonSize = [buttonContainer_ frame].size;
119   NSSize buttonStripImageSize = [buttonStripImage_ frame].size;
120   NSSize buttonStripSize =
121       NSMakeSize(buttonSize.width + chrome_style::kHorizontalPadding +
122                      buttonStripImageSize.width,
123                  std::max(buttonSize.height, buttonStripImageSize.height));
124
125   NSSize detailsSize = [detailsContainer_ preferredSize];
126
127   NSSize size = NSMakeSize(std::max(buttonStripSize.width, detailsSize.width),
128                            buttonStripSize.height + detailsSize.height);
129   size.height += 2 * autofill::kDetailVerticalPadding;
130
131   if (![legalDocumentsView_ isHidden]) {
132     NSSize legalDocumentSize =
133         [self preferredLegalDocumentSizeForWidth:detailsSize.width];
134     size.height += legalDocumentSize.height + autofill::kVerticalSpacing;
135   }
136
137   NSSize notificationSize =
138       [notificationContainer_ preferredSizeForWidth:detailsSize.width];
139   size.height += notificationSize.height;
140   return size;
141 }
142
143 - (void)performLayout {
144   NSRect bounds = [[self view] bounds];
145
146   CGFloat currentY = 0.0;
147   if (![legalDocumentsView_ isHidden]) {
148     [legalDocumentsView_ setFrameSize:
149         [self preferredLegalDocumentSizeForWidth:NSWidth(bounds)]];
150     currentY = NSMaxY([legalDocumentsView_ frame]) + autofill::kVerticalSpacing;
151   }
152
153   NSRect buttonFrame = [buttonContainer_ frame];
154   buttonFrame.origin.y = currentY;
155   [buttonContainer_ setFrameOrigin:buttonFrame.origin];
156   currentY = NSMaxY(buttonFrame) + autofill::kDetailVerticalPadding;
157
158   NSPoint walletIconOrigin =
159       NSMakePoint(chrome_style::kHorizontalPadding, buttonFrame.origin.y);
160   [buttonStripImage_ setFrameOrigin:walletIconOrigin];
161   currentY = std::max(currentY, NSMaxY([buttonStripImage_ frame]));
162
163   NSRect checkboxFrame = [saveInChromeCheckbox_ frame];
164   [saveInChromeCheckbox_ setFrameOrigin:
165       NSMakePoint(chrome_style::kHorizontalPadding,
166                   NSMidY(buttonFrame) - NSHeight(checkboxFrame) / 2.0)];
167
168   NSRect tooltipFrame = [saveInChromeTooltip_ frame];
169   [saveInChromeTooltip_ setFrameOrigin:
170       NSMakePoint(NSMaxX([saveInChromeCheckbox_ frame]) + autofill::kButtonGap,
171                   NSMidY(buttonFrame) - (NSHeight(tooltipFrame) / 2.0))];
172
173   NSRect notificationFrame = NSZeroRect;
174   notificationFrame.size = [notificationContainer_ preferredSizeForWidth:
175       NSWidth(bounds)];
176
177   // Buttons/checkbox/legal take up lower part of view, notifications the
178   // upper part. Adjust the detailsContainer to take up the remainder.
179   CGFloat remainingHeight =
180       NSHeight(bounds) - currentY - NSHeight(notificationFrame) -
181       autofill::kDetailVerticalPadding;
182   NSRect containerFrame =
183       NSMakeRect(0, currentY, NSWidth(bounds), remainingHeight);
184   [[detailsContainer_ view] setFrame:containerFrame];
185   [detailsContainer_ performLayout];
186
187   notificationFrame.origin =
188       NSMakePoint(0, NSMaxY(containerFrame) + autofill::kDetailVerticalPadding);
189   [[notificationContainer_ view] setFrame:notificationFrame];
190   [notificationContainer_ performLayout];
191 }
192
193 - (void)buildWindowButtons {
194   if (buttonContainer_.get())
195     return;
196
197   buttonContainer_.reset([[GTMWidthBasedTweaker alloc] initWithFrame:
198       ui::kWindowSizeDeterminedLater]);
199   [buttonContainer_
200       setAutoresizingMask:(NSViewMinXMargin | NSViewMaxYMargin)];
201
202   base::scoped_nsobject<NSButton> button(
203       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
204   [button setTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)];
205   [button setKeyEquivalent:kKeyEquivalentEscape];
206   [button setTarget:target_];
207   [button setAction:@selector(cancel:)];
208   [button sizeToFit];
209   [buttonContainer_ addSubview:button];
210
211   CGFloat nextX = NSMaxX([button frame]) + autofill::kButtonGap;
212   button.reset([[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
213   [button setFrameOrigin:NSMakePoint(nextX, 0)];
214   [button  setTitle:l10n_util::GetNSStringWithFixup(
215        IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON)];
216   [button setKeyEquivalent:kKeyEquivalentReturn];
217   [button setTarget:target_];
218   [button setAction:@selector(accept:)];
219   [button sizeToFit];
220   [buttonContainer_ addSubview:button];
221
222   NSRect frame = NSMakeRect(
223       -NSMaxX([button frame]) - chrome_style::kHorizontalPadding, 0,
224       NSMaxX([button frame]), NSHeight([button frame]));
225   [buttonContainer_ setFrame:frame];
226 }
227
228 - (void)layoutButtons {
229   base::scoped_nsobject<GTMUILocalizerAndLayoutTweaker> layoutTweaker(
230       [[GTMUILocalizerAndLayoutTweaker alloc] init]);
231   [layoutTweaker tweakUI:buttonContainer_];
232 }
233
234 // Compute the preferred size for the legal documents text, given a width.
235 - (NSSize)preferredLegalDocumentSizeForWidth:(CGFloat)width {
236   // Only recompute if necessary (On text or frame width change).
237   if (!legalDocumentsSizeDirty_ && abs(legalDocumentsSize_.width-width) < 1.0)
238     return legalDocumentsSize_;
239
240   // There's no direct API to compute desired sizes - use layouting instead.
241   // Layout in a rect with fixed width and "infinite" height.
242   NSRect currentFrame = [legalDocumentsView_ frame];
243   [legalDocumentsView_ setFrame:NSMakeRect(0, 0, width, CGFLOAT_MAX)];
244
245   // Now use the layout manager to compute layout.
246   NSLayoutManager* layoutManager = [legalDocumentsView_ layoutManager];
247   NSTextContainer* textContainer = [legalDocumentsView_ textContainer];
248   [layoutManager ensureLayoutForTextContainer:textContainer];
249   NSRect newFrame = [layoutManager usedRectForTextContainer:textContainer];
250
251   // And finally, restore old frame.
252   [legalDocumentsView_ setFrame:currentFrame];
253   newFrame.size.width = width;
254
255   legalDocumentsSizeDirty_ = NO;
256   legalDocumentsSize_ = newFrame.size;
257   return legalDocumentsSize_;
258 }
259
260 - (AutofillSectionContainer*)sectionForId:(autofill::DialogSection)section {
261   return [detailsContainer_ sectionForId:section];
262 }
263
264 - (void)modelChanged {
265   [self updateSaveInChrome];
266   [self updateWalletIcon];
267   [detailsContainer_ modelChanged];
268 }
269
270 - (BOOL)saveDetailsLocally {
271   return [saveInChromeCheckbox_ state] == NSOnState;
272 }
273
274 - (void)updateLegalDocuments {
275   NSString* text = base::SysUTF16ToNSString(delegate_->LegalDocumentsText());
276
277   if ([text length]) {
278     NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
279     [legalDocumentsView_ setMessage:text
280                            withFont:font
281                        messageColor:[NSColor blackColor]];
282
283     const std::vector<gfx::Range>& link_ranges =
284         delegate_->LegalDocumentLinks();
285     for (size_t i = 0; i < link_ranges.size(); ++i) {
286       NSRange range = link_ranges[i].ToNSRange();
287       [legalDocumentsView_ addLinkRange:range
288                                withName:@(i)
289                               linkColor:[NSColor blueColor]];
290     }
291     legalDocumentsSizeDirty_ = YES;
292   }
293   [legalDocumentsView_ setHidden:[text length] == 0];
294
295   // Always request re-layout on state change.
296   id delegate = [[[self view] window] windowController];
297   if ([delegate respondsToSelector:@selector(requestRelayout)])
298     [delegate performSelector:@selector(requestRelayout)];
299 }
300
301 - (void)updateNotificationArea {
302   [notificationContainer_ setNotifications:delegate_->CurrentNotifications()];
303   id delegate = [[[self view] window] windowController];
304   if ([delegate respondsToSelector:@selector(requestRelayout)])
305     [delegate performSelector:@selector(requestRelayout)];
306 }
307
308 - (void)setAnchorView:(NSView*)anchorView {
309   [notificationContainer_ setAnchorView:anchorView];
310 }
311
312 - (BOOL)validate {
313   return [detailsContainer_ validate];
314 }
315
316 - (void)updateSaveInChrome {
317   [saveInChromeCheckbox_ setHidden:!delegate_->ShouldOfferToSaveInChrome()];
318   [saveInChromeTooltip_ setHidden:[saveInChromeCheckbox_ isHidden]];
319   [saveInChromeCheckbox_ setState:
320       (delegate_->ShouldSaveInChrome() ? NSOnState : NSOffState)];
321 }
322
323 - (void)updateWalletIcon {
324   gfx::Image image = delegate_->ButtonStripImage();
325   [buttonStripImage_ setHidden:image.IsEmpty()];
326   if (![buttonStripImage_ isHidden]) {
327     [buttonStripImage_ setImage:image.ToNSImage()];
328     [buttonStripImage_ setFrameSize:[[buttonStripImage_ image] size]];
329   }
330 }
331
332 - (void)updateErrorBubble {
333   [detailsContainer_ updateErrorBubble];
334 }
335
336 @end
337
338
339 @implementation AutofillMainContainer (Testing)
340
341 - (NSButton*)saveInChromeCheckboxForTesting {
342   return saveInChromeCheckbox_.get();
343 }
344
345 - (NSImageView*)buttonStripImageForTesting {
346   return buttonStripImage_.get();
347 }
348
349 - (NSImageView*)saveInChromeTooltipForTesting {
350   return saveInChromeTooltip_.get();
351 }
352
353 @end