| OLD | NEW |
| (Empty) |
| 1 // Copyright 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_dialog_window_controller.h" | |
| 6 | |
| 7 #include "base/mac/foundation_util.h" | |
| 8 #include "base/mac/scoped_nsobject.h" | |
| 9 #include "base/strings/sys_string_conversions.h" | |
| 10 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" | |
| 11 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_cocoa.h" | |
| 12 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h" | |
| 13 #import "chrome/browser/ui/cocoa/autofill/autofill_header.h" | |
| 14 #import "chrome/browser/ui/cocoa/autofill/autofill_input_field.h" | |
| 15 #import "chrome/browser/ui/cocoa/autofill/autofill_main_container.h" | |
| 16 #import "chrome/browser/ui/cocoa/autofill/autofill_section_container.h" | |
| 17 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h" | |
| 18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_wi
ndow.h" | |
| 19 #include "content/public/browser/web_contents.h" | |
| 20 #import "ui/base/cocoa/flipped_view.h" | |
| 21 #include "ui/base/cocoa/window_size_constants.h" | |
| 22 | |
| 23 // The minimum useful height of the contents area of the dialog. | |
| 24 const CGFloat kMinimumContentsHeight = 101; | |
| 25 | |
| 26 #pragma mark AutofillDialogWindow | |
| 27 | |
| 28 // Window class for the AutofillDialog. Its main purpose is the proper handling | |
| 29 // of layout requests - i.e. ensuring that layout is fully done before any | |
| 30 // updates of the display happen. | |
| 31 @interface AutofillDialogWindow : ConstrainedWindowCustomWindow { | |
| 32 @private | |
| 33 BOOL needsLayout_; // Indicates that the subviews need to be laid out. | |
| 34 } | |
| 35 | |
| 36 // Request a new layout for all subviews. Layout occurs right before -display | |
| 37 // or -displayIfNeeded are invoked. | |
| 38 - (void)requestRelayout; | |
| 39 | |
| 40 // Layout the window's subviews. Delegates to the controller. | |
| 41 - (void)performLayout; | |
| 42 | |
| 43 @end | |
| 44 | |
| 45 | |
| 46 @implementation AutofillDialogWindow | |
| 47 | |
| 48 - (void)requestRelayout { | |
| 49 needsLayout_ = YES; | |
| 50 | |
| 51 // Ensure displayIfNeeded: is sent on the next pass through the event loop. | |
| 52 [self setViewsNeedDisplay:YES]; | |
| 53 } | |
| 54 | |
| 55 - (void)performLayout { | |
| 56 if (needsLayout_) { | |
| 57 needsLayout_ = NO; | |
| 58 AutofillDialogWindowController* controller = | |
| 59 base::mac::ObjCCastStrict<AutofillDialogWindowController>( | |
| 60 [self windowController]); | |
| 61 [controller performLayout]; | |
| 62 } | |
| 63 } | |
| 64 | |
| 65 - (void)display { | |
| 66 [self performLayout]; | |
| 67 [super display]; | |
| 68 } | |
| 69 | |
| 70 - (void)displayIfNeeded { | |
| 71 [self performLayout]; | |
| 72 [super displayIfNeeded]; | |
| 73 } | |
| 74 | |
| 75 @end | |
| 76 | |
| 77 #pragma mark Field Editor | |
| 78 | |
| 79 @interface AutofillDialogFieldEditor : NSTextView | |
| 80 @end | |
| 81 | |
| 82 | |
| 83 @implementation AutofillDialogFieldEditor | |
| 84 | |
| 85 - (void)mouseDown:(NSEvent*)event { | |
| 86 // Delegate _must_ be notified before mouseDown is complete, since it needs | |
| 87 // to distinguish between mouseDown for already focused fields, and fields | |
| 88 // that will receive focus as part of the mouseDown. | |
| 89 AutofillTextField* textfield = | |
| 90 base::mac::ObjCCastStrict<AutofillTextField>([self delegate]); | |
| 91 [textfield onEditorMouseDown:self]; | |
| 92 [super mouseDown:event]; | |
| 93 } | |
| 94 | |
| 95 // Intercept key down messages and forward them to the text fields delegate. | |
| 96 // This needs to happen in the field editor, since it handles all keyDown | |
| 97 // messages for NSTextField. | |
| 98 - (void)keyDown:(NSEvent*)event { | |
| 99 AutofillTextField* textfield = | |
| 100 base::mac::ObjCCastStrict<AutofillTextField>([self delegate]); | |
| 101 if ([[textfield inputDelegate] keyEvent:event | |
| 102 forInput:textfield] != kKeyEventHandled) { | |
| 103 [super keyDown:event]; | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 @end | |
| 108 | |
| 109 | |
| 110 #pragma mark Window Controller | |
| 111 | |
| 112 @interface AutofillDialogWindowController () | |
| 113 | |
| 114 // Compute maximum allowed height for the dialog. | |
| 115 - (CGFloat)maxHeight; | |
| 116 | |
| 117 // Notification that the WebContent's view frame has changed. | |
| 118 - (void)onContentViewFrameDidChange:(NSNotification*)notification; | |
| 119 | |
| 120 - (AutofillDialogWindow*)autofillWindow; | |
| 121 | |
| 122 @end | |
| 123 | |
| 124 | |
| 125 @implementation AutofillDialogWindowController (NSWindowDelegate) | |
| 126 | |
| 127 - (id)windowWillReturnFieldEditor:(NSWindow*)window toObject:(id)client { | |
| 128 AutofillTextField* textfield = base::mac::ObjCCast<AutofillTextField>(client); | |
| 129 if (!textfield) | |
| 130 return nil; | |
| 131 | |
| 132 if (!fieldEditor_) { | |
| 133 fieldEditor_.reset([[AutofillDialogFieldEditor alloc] init]); | |
| 134 [fieldEditor_ setFieldEditor:YES]; | |
| 135 } | |
| 136 return fieldEditor_.get(); | |
| 137 } | |
| 138 | |
| 139 @end | |
| 140 | |
| 141 | |
| 142 @implementation AutofillDialogWindowController | |
| 143 | |
| 144 - (id)initWithWebContents:(content::WebContents*)webContents | |
| 145 dialog:(autofill::AutofillDialogCocoa*)dialog { | |
| 146 DCHECK(webContents); | |
| 147 | |
| 148 base::scoped_nsobject<ConstrainedWindowCustomWindow> window( | |
| 149 [[AutofillDialogWindow alloc] | |
| 150 initWithContentRect:ui::kWindowSizeDeterminedLater]); | |
| 151 | |
| 152 if ((self = [super initWithWindow:window])) { | |
| 153 [window setDelegate:self]; | |
| 154 webContents_ = webContents; | |
| 155 dialog_ = dialog; | |
| 156 | |
| 157 header_.reset([[AutofillHeader alloc] initWithDelegate:dialog->delegate()]); | |
| 158 | |
| 159 mainContainer_.reset([[AutofillMainContainer alloc] | |
| 160 initWithDelegate:dialog->delegate()]); | |
| 161 [mainContainer_ setTarget:self]; | |
| 162 | |
| 163 // This needs a flipped content view because otherwise the size | |
| 164 // animation looks odd. However, replacing the contentView for constrained | |
| 165 // windows does not work - it does custom rendering. | |
| 166 base::scoped_nsobject<NSView> flippedContentView( | |
| 167 [[FlippedView alloc] initWithFrame: | |
| 168 [[[self window] contentView] frame]]); | |
| 169 [flippedContentView setSubviews:@[ [header_ view], [mainContainer_ view] ]]; | |
| 170 [flippedContentView setAutoresizingMask: | |
| 171 (NSViewWidthSizable | NSViewHeightSizable)]; | |
| 172 [[[self window] contentView] addSubview:flippedContentView]; | |
| 173 } | |
| 174 return self; | |
| 175 } | |
| 176 | |
| 177 - (void)dealloc { | |
| 178 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 179 [super dealloc]; | |
| 180 } | |
| 181 | |
| 182 - (CGFloat)maxHeight { | |
| 183 NSRect dialogFrameRect = [[self window] frame]; | |
| 184 NSRect browserFrameRect = [webContents_->GetTopLevelNativeWindow() frame]; | |
| 185 dialogFrameRect.size.height = | |
| 186 NSMaxY(dialogFrameRect) - NSMinY(browserFrameRect); | |
| 187 dialogFrameRect = [[self window] contentRectForFrameRect:dialogFrameRect]; | |
| 188 return NSHeight(dialogFrameRect); | |
| 189 } | |
| 190 | |
| 191 - (void)onContentViewFrameDidChange:(NSNotification*)notification { | |
| 192 [self requestRelayout]; | |
| 193 } | |
| 194 | |
| 195 - (AutofillDialogWindow*)autofillWindow { | |
| 196 return base::mac::ObjCCastStrict<AutofillDialogWindow>([self window]); | |
| 197 } | |
| 198 | |
| 199 - (void)requestRelayout { | |
| 200 [[self autofillWindow] requestRelayout]; | |
| 201 } | |
| 202 | |
| 203 - (NSSize)preferredSize { | |
| 204 NSSize size; | |
| 205 | |
| 206 size = [mainContainer_ preferredSize]; | |
| 207 | |
| 208 // Always make room for the header. | |
| 209 CGFloat headerHeight = [header_ preferredSize].height; | |
| 210 size.height += headerHeight; | |
| 211 | |
| 212 // For the minimum height, account for both the header and the footer. Even | |
| 213 // though the footer will not be visible when the sign-in view is showing, | |
| 214 // this prevents the dialog's size from bouncing around. | |
| 215 CGFloat minHeight = kMinimumContentsHeight; | |
| 216 minHeight += [mainContainer_ decorationSizeForWidth:size.width].height; | |
| 217 minHeight += headerHeight; | |
| 218 | |
| 219 // Show as much of the main view as is possible without going past the | |
| 220 // bottom of the browser window, unless this would cause the dialog to be | |
| 221 // less tall than the minimum height. | |
| 222 size.height = std::min(size.height, [self maxHeight]); | |
| 223 size.height = std::max(size.height, minHeight); | |
| 224 | |
| 225 return size; | |
| 226 } | |
| 227 | |
| 228 - (void)performLayout { | |
| 229 NSRect contentRect = NSZeroRect; | |
| 230 contentRect.size = [self preferredSize]; | |
| 231 | |
| 232 CGFloat headerHeight = [header_ preferredSize].height; | |
| 233 NSRect headerRect, mainRect; | |
| 234 NSDivideRect(contentRect, &headerRect, &mainRect, headerHeight, NSMinYEdge); | |
| 235 | |
| 236 [[header_ view] setFrame:headerRect]; | |
| 237 [header_ performLayout]; | |
| 238 | |
| 239 [[mainContainer_ view] setFrame:mainRect]; | |
| 240 [mainContainer_ performLayout]; | |
| 241 | |
| 242 NSRect frameRect = [[self window] frameRectForContentRect:contentRect]; | |
| 243 [[self window] setFrame:frameRect display:YES]; | |
| 244 | |
| 245 [[self window] recalculateKeyViewLoop]; | |
| 246 | |
| 247 if (mainContainerBecameVisible_) { | |
| 248 [mainContainer_ scrollInitialEditorIntoViewAndMakeFirstResponder]; | |
| 249 mainContainerBecameVisible_ = NO; | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 - (IBAction)accept:(id)sender { | |
| 254 if ([mainContainer_ validate]) | |
| 255 dialog_->delegate()->OnAccept(); | |
| 256 else | |
| 257 [mainContainer_ makeFirstInvalidInputFirstResponder]; | |
| 258 } | |
| 259 | |
| 260 - (IBAction)cancel:(id)sender { | |
| 261 dialog_->delegate()->OnCancel(); | |
| 262 dialog_->PerformClose(); | |
| 263 } | |
| 264 | |
| 265 - (void)show { | |
| 266 // Resizing the browser causes the ConstrainedWindow to move. | |
| 267 // Observe that to allow resizes based on browser size. | |
| 268 // NOTE: This MUST come last after all initial setup is done, because there | |
| 269 // is an immediate notification post registration. | |
| 270 DCHECK([self window]); | |
| 271 [[NSNotificationCenter defaultCenter] | |
| 272 addObserver:self | |
| 273 selector:@selector(onContentViewFrameDidChange:) | |
| 274 name:NSWindowDidMoveNotification | |
| 275 object:[self window]]; | |
| 276 | |
| 277 [self updateNotificationArea]; | |
| 278 [self requestRelayout]; | |
| 279 } | |
| 280 | |
| 281 - (void)hide { | |
| 282 dialog_->delegate()->OnCancel(); | |
| 283 dialog_->PerformClose(); | |
| 284 } | |
| 285 | |
| 286 - (void)updateNotificationArea { | |
| 287 [mainContainer_ updateNotificationArea]; | |
| 288 } | |
| 289 | |
| 290 - (void)updateSection:(autofill::DialogSection)section { | |
| 291 [[mainContainer_ sectionForId:section] update]; | |
| 292 [mainContainer_ updateSaveInChrome]; | |
| 293 } | |
| 294 | |
| 295 - (void)fillSection:(autofill::DialogSection)section | |
| 296 forType:(autofill::ServerFieldType)type { | |
| 297 [[mainContainer_ sectionForId:section] fillForType:type]; | |
| 298 [mainContainer_ updateSaveInChrome]; | |
| 299 } | |
| 300 | |
| 301 - (void)updateForErrors { | |
| 302 [mainContainer_ validate]; | |
| 303 } | |
| 304 | |
| 305 - (void)getInputs:(autofill::FieldValueMap*)output | |
| 306 forSection:(autofill::DialogSection)section { | |
| 307 [[mainContainer_ sectionForId:section] getInputs:output]; | |
| 308 } | |
| 309 | |
| 310 - (NSString*)getCvc { | |
| 311 return [[mainContainer_ sectionForId:autofill::SECTION_CC] suggestionText]; | |
| 312 } | |
| 313 | |
| 314 - (BOOL)saveDetailsLocally { | |
| 315 return [mainContainer_ saveDetailsLocally]; | |
| 316 } | |
| 317 | |
| 318 - (void)modelChanged { | |
| 319 [mainContainer_ modelChanged]; | |
| 320 } | |
| 321 | |
| 322 - (void)updateErrorBubble { | |
| 323 [mainContainer_ updateErrorBubble]; | |
| 324 } | |
| 325 | |
| 326 - (void)validateSection:(autofill::DialogSection)section { | |
| 327 [[mainContainer_ sectionForId:section] validateFor:autofill::VALIDATE_EDIT]; | |
| 328 } | |
| 329 | |
| 330 @end | |
| OLD | NEW |