OLD | NEW |
---|---|
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/strings/sys_string_conversions.h" | |
6 #include "chrome/browser/ui/autofill/autofill_dialog_models.h" | |
5 #include "chrome/browser/ui/autofill/card_unmask_prompt_controller.h" | 7 #include "chrome/browser/ui/autofill/card_unmask_prompt_controller.h" |
6 #include "chrome/browser/ui/cocoa/autofill/card_unmask_prompt_view_bridge.h" | 8 #include "chrome/browser/ui/cocoa/autofill/card_unmask_prompt_view_bridge.h" |
9 #import "chrome/browser/ui/cocoa/autofill/autofill_pop_up_button.h" | |
7 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h" | 10 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h" |
8 #include "chrome/browser/ui/chrome_style.h" | 11 #include "chrome/browser/ui/chrome_style.h" |
12 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_control_u tils.h" | |
9 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sh eet.h" | 13 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sh eet.h" |
10 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_wi ndow.h" | 14 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_wi ndow.h" |
11 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h" | 15 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h" |
12 #include "grit/generated_resources.h" | 16 #include "grit/generated_resources.h" |
13 #include "ui/base/cocoa/window_size_constants.h" | 17 #include "ui/base/cocoa/window_size_constants.h" |
14 #include "ui/base/l10n/l10n_util.h" | 18 #include "ui/base/l10n/l10n_util.h" |
15 | 19 |
16 namespace { | 20 namespace { |
21 | |
17 const CGFloat kButtonGap = 6.0f; | 22 const CGFloat kButtonGap = 6.0f; |
23 const CGFloat kDialogContentMinWidth = 210.0f; | |
24 const CGFloat kCvcInputWidth = 64.0f; | |
25 | |
18 } // namespace | 26 } // namespace |
19 | 27 |
20 namespace autofill { | 28 namespace autofill { |
21 | 29 |
22 // static | 30 // static |
23 CardUnmaskPromptView* CardUnmaskPromptView::CreateAndShow( | 31 CardUnmaskPromptView* CardUnmaskPromptView::CreateAndShow( |
24 CardUnmaskPromptController* controller) { | 32 CardUnmaskPromptController* controller) { |
25 return new CardUnmaskPromptViewBridge(controller); | 33 return new CardUnmaskPromptViewBridge(controller); |
26 } | 34 } |
27 | 35 |
28 #pragma mark CardUnmaskPromptViewBridge | 36 #pragma mark CardUnmaskPromptViewBridge |
29 | 37 |
30 CardUnmaskPromptViewBridge::CardUnmaskPromptViewBridge( | 38 CardUnmaskPromptViewBridge::CardUnmaskPromptViewBridge( |
31 CardUnmaskPromptController* controller) | 39 CardUnmaskPromptController* controller) |
32 : controller_(controller) { | 40 : controller_(controller) { |
33 sheet_controller_.reset([[CardUnmaskPromptViewCocoa alloc] | 41 view_controller_.reset([[CardUnmaskPromptViewCocoa alloc] |
34 initWithWebContents:controller_->GetWebContents() | 42 initWithWebContents:controller_->GetWebContents() |
35 bridge:this]); | 43 bridge:this]); |
44 | |
45 // Setup the constrained window that will show the view. | |
46 base::scoped_nsobject<NSWindow> window([[ConstrainedWindowCustomWindow alloc] | |
47 initWithContentRect:[[view_controller_ view] bounds]]); | |
48 [window setContentView:[view_controller_ view]]; | |
36 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet( | 49 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet( |
37 [[CustomConstrainedWindowSheet alloc] | 50 [[CustomConstrainedWindowSheet alloc] initWithCustomWindow:window]); |
38 initWithCustomWindow:[sheet_controller_ window]]); | |
39 constrained_window_.reset( | 51 constrained_window_.reset( |
40 new ConstrainedWindowMac(this, controller_->GetWebContents(), sheet)); | 52 new ConstrainedWindowMac(this, controller_->GetWebContents(), sheet)); |
41 } | 53 } |
42 | 54 |
43 CardUnmaskPromptViewBridge::~CardUnmaskPromptViewBridge() { | 55 CardUnmaskPromptViewBridge::~CardUnmaskPromptViewBridge() { |
44 } | 56 } |
45 | 57 |
46 void CardUnmaskPromptViewBridge::ControllerGone() { | 58 void CardUnmaskPromptViewBridge::ControllerGone() { |
59 controller_ = nullptr; | |
60 PerformClose(); | |
47 } | 61 } |
48 | 62 |
49 void CardUnmaskPromptViewBridge::DisableAndWaitForVerification() { | 63 void CardUnmaskPromptViewBridge::DisableAndWaitForVerification() { |
50 } | 64 } |
51 | 65 |
52 void CardUnmaskPromptViewBridge::GotVerificationResult(bool success) { | 66 void CardUnmaskPromptViewBridge::GotVerificationResult(bool success) { |
53 } | 67 } |
54 | 68 |
55 void CardUnmaskPromptViewBridge::OnConstrainedWindowClosed( | 69 void CardUnmaskPromptViewBridge::OnConstrainedWindowClosed( |
56 ConstrainedWindowMac* window) { | 70 ConstrainedWindowMac* window) { |
57 constrained_window_.reset(); | 71 constrained_window_.reset(); |
58 controller_->OnUnmaskDialogClosed(); | 72 if (controller_) |
73 controller_->OnUnmaskDialogClosed(); | |
74 } | |
75 | |
76 CardUnmaskPromptController* CardUnmaskPromptViewBridge::GetController() { | |
77 return controller_; | |
59 } | 78 } |
60 | 79 |
61 void CardUnmaskPromptViewBridge::PerformClose() { | 80 void CardUnmaskPromptViewBridge::PerformClose() { |
62 constrained_window_->CloseWebContentsModalDialog(); | 81 constrained_window_->CloseWebContentsModalDialog(); |
63 } | 82 } |
64 | 83 |
65 } // autofill | 84 } // autofill |
66 | 85 |
67 #pragma mark CardUnmaskPromptViewCocoa | 86 #pragma mark CardUnmaskPromptViewCocoa |
68 | 87 |
69 @implementation CardUnmaskPromptViewCocoa | 88 @implementation CardUnmaskPromptViewCocoa |
70 | 89 |
90 + (AutofillPopUpButton*)buildDatePopupWithModel:(ui::ComboboxModel&)model { | |
91 AutofillPopUpButton* popup = | |
92 [[AutofillPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; | |
93 | |
94 for (int i = 0; i < model.GetItemCount(); ++i) { | |
95 [popup addItemWithTitle:base::SysUTF16ToNSString(model.GetItemAt(i))]; | |
96 } | |
97 [popup setDefaultValue:base::SysUTF16ToNSString( | |
98 model.GetItemAt(model.GetDefaultIndex()))]; | |
99 [popup sizeToFit]; | |
100 return popup; | |
101 } | |
102 | |
71 - (id)initWithWebContents:(content::WebContents*)webContents | 103 - (id)initWithWebContents:(content::WebContents*)webContents |
72 bridge:(autofill::CardUnmaskPromptViewBridge*)bridge { | 104 bridge:(autofill::CardUnmaskPromptViewBridge*)bridge { |
73 DCHECK(webContents); | 105 DCHECK(webContents); |
74 DCHECK(bridge); | 106 DCHECK(bridge); |
75 | 107 |
76 NSRect frame = NSMakeRect(0, 0, 550, 600); | 108 if ((self = [super initWithNibName:nil bundle:nil])) { |
77 base::scoped_nsobject<ConstrainedWindowCustomWindow> window( | |
78 [[ConstrainedWindowCustomWindow alloc] initWithContentRect:frame]); | |
79 if ((self = [super initWithWindow:window])) { | |
80 webContents_ = webContents; | 109 webContents_ = webContents; |
81 bridge_ = bridge; | 110 bridge_ = bridge; |
82 | |
83 [self buildWindowButtons]; | |
84 } | 111 } |
85 return self; | 112 return self; |
86 } | 113 } |
87 | 114 |
88 - (IBAction)closeSheet:(id)sender { | 115 - (IBAction)closeSheet:(id)sender { |
89 bridge_->PerformClose(); | 116 bridge_->PerformClose(); |
90 } | 117 } |
91 | 118 |
92 - (void)buildWindowButtons { | 119 - (void)loadView { |
93 base::scoped_nsobject<NSView> buttonContainer( | 120 autofill::CardUnmaskPromptController* controller = bridge_->GetController(); |
94 [[NSView alloc] initWithFrame:NSZeroRect]); | 121 DCHECK(controller); |
95 | 122 |
96 base::scoped_nsobject<NSButton> button( | 123 base::scoped_nsobject<NSBox> mainView( |
124 [[NSBox alloc] initWithFrame:NSZeroRect]); | |
125 [mainView setBoxType:NSBoxCustom]; | |
126 [mainView setBorderType:NSNoBorder]; | |
127 [mainView setTitlePosition:NSNoTitle]; | |
128 [mainView | |
129 setContentViewMargins:NSMakeSize(chrome_style::kHorizontalPadding, 0.0)]; | |
130 | |
131 // Title label. | |
132 NSTextField* title = constrained_window::CreateLabel(); | |
133 NSAttributedString* titleString = | |
134 constrained_window::GetAttributedLabelString( | |
135 SysUTF16ToNSString(controller->GetWindowTitle()), | |
136 chrome_style::kTitleFontStyle, NSNaturalTextAlignment, | |
137 NSLineBreakByWordWrapping); | |
138 [title setAttributedStringValue:titleString]; | |
139 [title sizeToFit]; | |
140 [mainView addSubview:title]; | |
141 | |
142 // Instructions label. | |
143 NSTextField* instructions = constrained_window::CreateLabel(); | |
144 NSAttributedString* instructionsString = | |
145 constrained_window::GetAttributedLabelString( | |
146 SysUTF16ToNSString(controller->GetInstructionsMessage()), | |
147 chrome_style::kTextFontStyle, NSNaturalTextAlignment, | |
148 NSLineBreakByWordWrapping); | |
149 [instructions setAttributedStringValue:instructionsString]; | |
150 [mainView addSubview:instructions]; | |
151 | |
152 // Expiration date. | |
153 base::scoped_nsobject<AutofillPopUpButton> monthPopup; | |
154 base::scoped_nsobject<AutofillPopUpButton> yearPopup; | |
155 NSSize expirationDateSize = NSZeroSize; | |
156 if (controller->ShouldRequestExpirationDate()) { | |
157 // Month. | |
158 autofill::MonthComboboxModel monthModel; | |
159 monthPopup.reset( | |
160 [CardUnmaskPromptViewCocoa buildDatePopupWithModel:monthModel]); | |
161 [mainView addSubview:monthPopup]; | |
162 | |
163 // Year. | |
164 autofill::YearComboboxModel yearModel; | |
165 yearPopup.reset( | |
166 [CardUnmaskPromptViewCocoa buildDatePopupWithModel:yearModel]); | |
167 [mainView addSubview:yearPopup]; | |
168 | |
169 // Dimensions. | |
groby-ooo-7-16
2015/03/03 18:06:54
That is kind of awkward. Wrap the comboboxes in a
bondd
2015/03/04 20:45:30
Done.
| |
170 expirationDateSize.width = NSWidth([monthPopup frame]) + | |
171 NSWidth([yearPopup frame]) + kButtonGap * 2; | |
172 expirationDateSize.height = | |
173 std::max(NSHeight([monthPopup frame]), NSHeight([yearPopup frame])); | |
174 } | |
175 | |
176 // CVC text input. | |
177 base::scoped_nsobject<NSTextField> cvcInput( | |
178 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
179 [[cvcInput cell] | |
180 setPlaceholderString:l10n_util::GetNSString( | |
181 IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC)]; | |
182 [[cvcInput cell] setScrollable:YES]; | |
groby-ooo-7-16
2015/03/03 18:06:54
We know the maximum size of a CVC - can we just pi
bondd
2015/03/04 20:45:30
What I've got here is the same functionality as Vi
Evan Stade
2015/03/05 02:38:02
I'm with Rachel here. UX designers aren't going to
bondd
2015/03/06 23:57:15
Apparently it is not trivial to do in OSX either:
| |
183 [cvcInput sizeToFit]; | |
184 [mainView addSubview:cvcInput]; | |
185 | |
186 // CVC image. | |
187 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
188 NSImage* cvcImage = | |
189 rb.GetNativeImageNamed(controller->GetCvcImageRid()).ToNSImage(); | |
190 base::scoped_nsobject<NSImageView> cvcImageView( | |
191 [[NSImageView alloc] initWithFrame:NSZeroRect]); | |
192 [cvcImageView setImage:cvcImage]; | |
193 [mainView addSubview:cvcImageView]; | |
194 | |
195 // Cancel button. | |
196 base::scoped_nsobject<NSButton> cancelButton( | |
97 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]); | 197 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]); |
98 [button setTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)]; | 198 [cancelButton setTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)]; |
99 [button setKeyEquivalent:kKeyEquivalentEscape]; | 199 [cancelButton setKeyEquivalent:kKeyEquivalentEscape]; |
100 [button setTarget:self]; | 200 [cancelButton setTarget:self]; |
101 [button setAction:@selector(closeSheet:)]; | 201 [cancelButton setAction:@selector(closeSheet:)]; |
102 [button sizeToFit]; | 202 [cancelButton sizeToFit]; |
103 [buttonContainer addSubview:button]; | 203 [mainView addSubview:cancelButton]; |
104 | 204 |
105 CGFloat nextX = NSMaxX([button frame]) + kButtonGap; | 205 // Verify button. |
106 button.reset([[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]); | 206 base::scoped_nsobject<NSButton> verifyButton( |
107 [button setFrameOrigin:NSMakePoint(nextX, 0)]; | 207 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]); |
108 [button setTitle:l10n_util::GetNSStringWithFixup( | 208 // TODO(bondd): use l10n string. |
groby-ooo-7-16
2015/03/03 18:06:54
Please do so :)
bondd
2015/03/04 20:45:30
Same TODO exists in the Views implementation:
http
Evan Stade
2015/03/05 02:38:02
right. It's less a matter of "the value of this st
| |
109 IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON)]; | 209 [verifyButton setTitle:@"Verify"]; |
110 [button setKeyEquivalent:kKeyEquivalentReturn]; | 210 [verifyButton setKeyEquivalent:kKeyEquivalentReturn]; |
111 [button setTarget:self]; | 211 [verifyButton setTarget:self]; |
112 [button setAction:@selector(closeSheet:)]; | 212 [verifyButton setAction:@selector(closeSheet:)]; |
113 [button sizeToFit]; | 213 [verifyButton sizeToFit]; |
114 [buttonContainer addSubview:button]; | 214 [mainView addSubview:verifyButton]; |
115 | 215 |
116 const CGFloat dialogOffset = NSWidth([[self window] frame]) - | 216 // Calculate input row dimensions. |
117 chrome_style::kHorizontalPadding - | 217 NSSize inputRowSize; |
118 NSMaxX([button frame]); | 218 inputRowSize.width = kCvcInputWidth + kButtonGap + [cvcImage size].width + |
groby-ooo-7-16
2015/03/03 18:06:54
You can get that width by just positioning the ele
bondd
2015/03/04 20:45:30
Done.
| |
119 [buttonContainer | 219 expirationDateSize.width; |
120 setFrame:NSMakeRect(dialogOffset, chrome_style::kClientBottomPadding, | 220 CGFloat maxCvcHeight = |
121 NSMaxX([button frame]), NSMaxY([button frame]))]; | 221 std::max(NSHeight([cvcInput frame]), [cvcImage size].height); |
222 inputRowSize.height = std::max(maxCvcHeight, expirationDateSize.height); | |
122 | 223 |
123 [[[self window] contentView] addSubview:buttonContainer]; | 224 // Calculate dialog content width. |
225 CGFloat contentWidth = std::max(NSWidth([title frame]), inputRowSize.width); | |
226 contentWidth = std::max(contentWidth, kDialogContentMinWidth); | |
227 | |
228 // Layout the elements, starting at the bottom and moving up. | |
229 | |
230 NSPoint pos = NSMakePoint(contentWidth, chrome_style::kClientBottomPadding); | |
231 | |
232 // Verify and Cancel buttons. | |
233 pos.x -= NSWidth([verifyButton frame]); | |
234 [verifyButton setFrameOrigin:pos]; | |
235 pos.x -= kButtonGap + NSWidth([cancelButton frame]); | |
236 [cancelButton setFrameOrigin:pos]; | |
237 pos.y += NSHeight([cancelButton frame]); | |
238 | |
239 // Get start position of input row. | |
240 pos.x = 0.0; | |
241 pos.y += chrome_style::kRowPadding; | |
242 | |
243 // Expiration date. | |
groby-ooo-7-16
2015/03/03 18:06:54
This entire thing becomes
[expirationView setFram
bondd
2015/03/04 20:45:30
Done.
| |
244 if (controller->ShouldRequestExpirationDate()) { | |
245 [monthPopup setFrame:NSMakeRect(pos.x, pos.y, NSWidth([monthPopup frame]), | |
246 inputRowSize.height)]; | |
247 pos.x += NSWidth([monthPopup frame]) + kButtonGap; | |
248 | |
249 [yearPopup setFrame:NSMakeRect(pos.x, pos.y, NSWidth([yearPopup frame]), | |
250 inputRowSize.height)]; | |
251 pos.x += NSWidth([yearPopup frame]) + kButtonGap; | |
252 } | |
253 | |
254 // Center cvcInput vertically in the input row. | |
255 CGFloat cvcInputY = | |
256 pos.y + ceil((inputRowSize.height - NSHeight([cvcInput frame])) * 0.5); | |
257 [cvcInput setFrame:NSMakeRect(pos.x, cvcInputY, kCvcInputWidth, | |
258 NSHeight([cvcInput frame]))]; | |
259 pos.x += NSWidth([cvcInput frame]) + kButtonGap; | |
260 | |
261 // CVC image. | |
262 [cvcImageView setFrame:NSMakeRect(pos.x, pos.y, [cvcImage size].width, | |
263 inputRowSize.height)]; | |
groby-ooo-7-16
2015/03/03 18:06:54
Why not just use the original image size? And do a
bondd
2015/03/04 20:45:30
Done.
| |
264 pos.x += NSWidth([cvcImageView frame]); | |
265 | |
266 // Instruction label. | |
267 pos.x = 0.0; | |
groby-ooo-7-16
2015/03/03 18:06:54
pos = NSMakePoint(0.0, NSMaxY([cvcImage frame]) +
bondd
2015/03/04 20:45:30
Done.
| |
268 pos.y += inputRowSize.height + chrome_style::kRowPadding; | |
269 [instructions setFrameOrigin:pos]; | |
270 NSSize instructionsSize = [[instructions cell] | |
271 cellSizeForBounds:NSMakeRect(0.0, 0.0, contentWidth, CGFLOAT_MAX)]; | |
272 [instructions setFrameSize:instructionsSize]; | |
273 | |
274 // Title label. | |
275 pos.y += instructionsSize.height + chrome_style::kRowPadding; | |
groby-ooo-7-16
2015/03/03 18:06:54
NSMakePoint(0.0, NSMaxY([instructions frame] + chr
bondd
2015/03/04 20:45:30
Done.
| |
276 [title setFrameOrigin:pos]; | |
277 | |
278 // Dialog size. | |
279 CGFloat dialogHeight = NSMaxY([title frame]) + chrome_style::kTitleTopPadding; | |
groby-ooo-7-16
2015/03/03 18:06:54
Shouldn't the bottom have the same padding? In whi
bondd
2015/03/04 20:45:30
AFAICT the padding is different for top and bottom
| |
280 | |
281 [mainView | |
282 setFrameSize:NSMakeSize( | |
groby-ooo-7-16
2015/03/03 18:06:54
[mainView sizeToFit] should do.
bondd
2015/03/04 20:45:30
See above comment - top and bottom padding are dif
| |
283 contentWidth + [mainView contentViewMargins].width * 2.0, | |
284 dialogHeight)]; | |
285 [self setView:mainView]; | |
124 } | 286 } |
125 | 287 |
126 @end | 288 @end |
OLD | NEW |