Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(70)

Side by Side Diff: chrome/browser/ui/cocoa/website_settings/chooser_bubble_ui_cocoa.mm

Issue 1473393003: Add chooser permission UI code for Mac (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@wpu_1
Patch Set: merged changes from master to fix merge conflicts Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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/website_settings/chooser_bubble_ui_cocoa.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <vector>
10
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_finder.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #import "chrome/browser/ui/chrome_style.h"
18 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
19 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
20 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
21 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
22 #import "chrome/browser/ui/cocoa/hover_close_button.h"
23 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
24 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
25 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
26 #include "chrome/browser/ui/website_settings/chooser_bubble_delegate.h"
27 #include "chrome/grit/generated_resources.h"
28 #include "content/public/browser/native_web_keyboard_event.h"
29 #include "ui/base/cocoa/window_size_constants.h"
30 #include "ui/base/l10n/l10n_util_mac.h"
31
32 namespace {
33
34 // Chooser bubble width.
35 const CGFloat kChooserBubbleWidth = 320.0f;
36
37 // Chooser bubble height.
38 const CGFloat kChooserBubbleHeight = 220.0f;
39
40 // Distance between the bubble border and the view that is closest to the
41 // border.
42 const CGFloat kMarginX = 20.0f;
43 const CGFloat kMarginY = 20.0f;
44
45 // Distance between two views inside the bubble.
46 const CGFloat kHorizontalPadding = 10.0f;
47 const CGFloat kVerticalPadding = 10.0f;
48
49 const CGFloat kTitlePaddingX = 50.0f;
50 }
51
52 @interface ChooserBubbleUiController
53 : BaseBubbleController<NSTableViewDataSource, NSTableViewDelegate> {
54 @private
55 // Bridge to the C++ class that created this object.
56 ChooserBubbleUiCocoa* bridge_;
57
58 base::scoped_nsobject<NSTextField> titleView_;
59 base::scoped_nsobject<NSButton> closeButton_;
60 base::scoped_nsobject<NSScrollView> scrollView_;
61 base::scoped_nsobject<NSTableColumn> tableColumn_;
62 base::scoped_nsobject<NSTableView> tableView_;
63 base::scoped_nsobject<NSButton> connectButton_;
64 base::scoped_nsobject<NSButton> cancelButton_;
65
66 Browser* browser_; // Weak.
67 ChooserOptions* chooser_options_; // Weak.
68 ChooserBubbleDelegate* chooser_bubble_delegate_; // Weak.
69 }
70
71 // Designated initializer. |browser| and |bridge| must both be non-nil.
72 - (id)initWithBrowser:(Browser*)browser
73 initWithChooserOptions:(ChooserOptions*)chooser_options
74 initWithChooserBubbleDelegate:
75 (ChooserBubbleDelegate*)chooser_bubble_delegate
76 bridge:(ChooserBubbleUiCocoa*)bridge;
77
78 // Makes the bubble visible.
79 - (void)show;
80
81 // Will reposition the bubble based in case the anchor or parent should change.
82 - (void)updateAnchorPosition;
83
84 // Will calculate the expected anchor point for this bubble.
85 // Should only be used outside this class for tests.
86 - (NSPoint)getExpectedAnchorPoint;
87
88 // Returns true of the browser has support for the location bar.
89 // Should only be used outside this class for tests.
90 - (bool)hasLocationBar;
91
92 // Update table view when chooser options were initialized.
93 - (void)onOptionsInitialized;
94
95 // Update table view when chooser option was added.
96 - (void)onOptionAdded:(NSInteger)index;
97
98 // Update table view when chooser option was removed.
99 - (void)onOptionRemoved:(NSInteger)index;
100
101 // Update table view when chooser options changed.
102 - (void)updateTableView;
103
104 // Determines if the bubble has an anchor in a corner or no anchor at all.
105 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation;
106
107 // Returns the expected parent for this bubble.
108 - (NSWindow*)getExpectedParentWindow;
109
110 // Returns an autoreleased NSView displaying the title for the bubble
111 // requesting settings for |host|.
112 - (NSTextField*)bubbleTitle;
113
114 // Returns an autoreleased NSView displaying the close 'x' button.
115 - (NSButton*)closeButton;
116
117 // Returns an autoreleased NSView of a button with |title| and |action|.
118 - (NSButton*)buttonWithTitle:(NSString*)title action:(SEL)action;
119
120 // Returns an autoreleased NSView displaying a connect button.
121 - (NSButton*)connectButton;
122
123 // Returns an autoreleased NSView displaying a cancel button.
124 - (NSButton*)cancelButton;
125
126 // Called when the 'Connect' button is pressed.
127 - (void)onConnect:(id)sender;
128
129 // Called when the 'Cancel' button is pressed.
130 - (void)onCancel:(id)sender;
131
132 // Called when the 'close' button is pressed.
133 - (void)onClose:(id)sender;
134
135 @end
136
137 @implementation ChooserBubbleUiController
138
139 - (id)initWithBrowser:(Browser*)browser
140 initWithChooserOptions:(ChooserOptions*)chooser_options
141 initWithChooserBubbleDelegate:
142 (ChooserBubbleDelegate*)chooser_bubble_delegate
143 bridge:(ChooserBubbleUiCocoa*)bridge {
144 DCHECK(browser);
145 DCHECK(chooser_options);
146 DCHECK(chooser_bubble_delegate);
147 DCHECK(bridge);
148 browser_ = browser;
149 chooser_options_ = chooser_options;
150 chooser_bubble_delegate_ = chooser_bubble_delegate;
151
152 base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
153 initWithContentRect:ui::kWindowSizeDeterminedLater
154 styleMask:NSBorderlessWindowMask
155 backing:NSBackingStoreBuffered
156 defer:NO]);
157 [window setAllowedAnimations:info_bubble::kAnimateNone];
158 [window setReleasedWhenClosed:NO];
159 if ((self = [super initWithWindow:window
160 parentWindow:[self getExpectedParentWindow]
161 anchoredAt:NSZeroPoint])) {
162 [self setShouldCloseOnResignKey:NO];
163 [self setShouldOpenAsKeyWindow:YES];
164 [[self bubble] setArrowLocation:[self getExpectedArrowLocation]];
165 bridge_ = bridge;
166 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
167 [center addObserver:self
168 selector:@selector(parentWindowDidMove:)
169 name:NSWindowDidMoveNotification
170 object:[self getExpectedParentWindow]];
171 }
172 return self;
173 }
174
175 - (void)windowWillClose:(NSNotification*)notification {
176 bridge_->OnBubbleClosing();
177 [super windowWillClose:notification];
178 }
179
180 - (void)parentWindowWillToggleFullScreen:(NSNotification*)notification {
181 // Override the base class implementation, which would have closed the bubble.
182 }
183
184 - (void)parentWindowDidResize:(NSNotification*)notification {
185 DCHECK(bridge_);
186 [self setAnchorPoint:[self getExpectedAnchorPoint]];
187 }
188
189 - (void)parentWindowDidMove:(NSNotification*)notification {
190 DCHECK(bridge_);
191 [self setAnchorPoint:[self getExpectedAnchorPoint]];
192 }
193
194 - (void)show {
195 NSView* view = [[self window] contentView];
196 [view setSubviews:@[]];
197
198 // ------------------------------------
199 // | Chooser bubble title x |
200 // | -------------------------------- |
201 // | | option 0 | |
202 // | | option 1 | |
203 // | | option 2 | |
204 // | | | |
205 // | | | |
206 // | | | |
207 // | -------------------------------- |
208 // | [ Connect] [ Cancel ] |
209 // ------------------------------------
210
211 // Determine the dimensions of the bubble.
212 // Once the height and width are set, the buttons and permission menus can
213 // be laid out correctly.
214 NSRect bubbleFrame =
215 NSMakeRect(0, 0, kChooserBubbleWidth, kChooserBubbleHeight);
216
217 // Create the views.
218 // Title.
219 titleView_.reset([[self bubbleTitle] retain]);
220 CGFloat titleOriginX = kMarginX;
221 CGFloat titleHeight = NSHeight([titleView_ frame]);
222 CGFloat titleOriginY = kChooserBubbleHeight - kMarginY - titleHeight;
223 [titleView_ setFrameOrigin:NSMakePoint(titleOriginX, titleOriginY)];
224 [view addSubview:titleView_];
225
226 // Close button.
227 // 'x' button in the upper-right-hand corner.
228 closeButton_.reset([[self closeButton] retain]);
229 CGFloat closeButtonOriginX =
230 kChooserBubbleWidth - kMarginX - NSWidth([closeButton_ frame]);
231 CGFloat closeButtonOriginY =
232 kChooserBubbleHeight - kMarginY - NSHeight([closeButton_ frame]);
233 [closeButton_
234 setFrameOrigin:NSMakePoint(closeButtonOriginX, closeButtonOriginY)];
235 [view addSubview:closeButton_];
236
237 // Connect button.
238 connectButton_.reset([[self connectButton] retain]);
239 // Cancel button.
240 cancelButton_.reset([[self cancelButton] retain]);
241 CGFloat connectButtonWidth = NSWidth([connectButton_ frame]);
242 CGFloat connectButtonHeight = NSHeight([connectButton_ frame]);
243 CGFloat cancelButtonWidth = NSWidth([cancelButton_ frame]);
244
245 // ScollView embedding with TableView.
246 CGFloat scrollViewWidth = kChooserBubbleWidth - 2 * kMarginX;
247 CGFloat scrollViewHeight = kChooserBubbleHeight - 2 * kMarginY -
248 2 * kVerticalPadding - titleHeight -
249 connectButtonHeight;
250 NSRect scrollFrame =
251 NSMakeRect(kMarginX, kMarginY + connectButtonHeight + kVerticalPadding,
252 scrollViewWidth, scrollViewHeight);
253 scrollView_.reset([[[NSScrollView alloc] initWithFrame:scrollFrame] retain]);
254 [scrollView_ setBorderType:NSBezelBorder];
255 [scrollView_ setHasVerticalScroller:YES];
256 [scrollView_ setHasHorizontalScroller:YES];
257 [scrollView_ setAutohidesScrollers:YES];
258
259 // TableView.
260 tableView_.reset([[[NSTableView alloc] initWithFrame:NSZeroRect] retain]);
261 tableColumn_.reset([[[NSTableColumn alloc] initWithIdentifier:@""] retain]);
262 [tableColumn_ setWidth:scrollViewWidth];
263 [tableView_ addTableColumn:tableColumn_];
264 [tableView_ setDelegate:self];
265 [tableView_ setDataSource:self];
266 // Make the column title invisible.
267 [tableView_ setHeaderView:nil];
268 [tableView_ setFocusRingType:NSFocusRingTypeNone];
269
270 [scrollView_ setDocumentView:tableView_];
271 [view addSubview:scrollView_];
272
273 // Set connect button and cancel button to the right place.
274 CGFloat connectButtonOriginX = kChooserBubbleWidth - kMarginX -
275 kHorizontalPadding - connectButtonWidth -
276 cancelButtonWidth;
277 CGFloat connectButtonOriginY = kMarginY;
278 [connectButton_
279 setFrameOrigin:NSMakePoint(connectButtonOriginX, connectButtonOriginY)];
280 [connectButton_ setEnabled:NO];
281 [view addSubview:connectButton_];
282
283 CGFloat cancelButtonOriginX =
284 kChooserBubbleWidth - kMarginX - cancelButtonWidth;
285 CGFloat cancelButtonOriginY = kMarginY;
286 [cancelButton_
287 setFrameOrigin:NSMakePoint(cancelButtonOriginX, cancelButtonOriginY)];
288 [view addSubview:cancelButton_];
289
290 bubbleFrame = [[self window] frameRectForContentRect:bubbleFrame];
291 if ([[self window] isVisible]) {
292 // Unfortunately, calling -setFrame followed by -setFrameOrigin (called
293 // within -setAnchorPoint) causes flickering. Avoid the flickering by
294 // manually adjusting the new frame's origin so that the top left stays the
295 // same, and only calling -setFrame.
296 NSRect currentWindowFrame = [[self window] frame];
297 bubbleFrame.origin = currentWindowFrame.origin;
298 bubbleFrame.origin.y = bubbleFrame.origin.y +
299 currentWindowFrame.size.height -
300 bubbleFrame.size.height;
301 [[self window] setFrame:bubbleFrame display:YES];
302 } else {
303 [[self window] setFrame:bubbleFrame display:NO];
304 [self setAnchorPoint:[self getExpectedAnchorPoint]];
305 [self showWindow:nil];
306 [[self window] makeFirstResponder:nil];
307 [[self window] setInitialFirstResponder:connectButton_.get()];
308 }
309 }
310
311 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
312 const std::vector<base::string16>& device_names =
313 chooser_options_->GetOptions();
314 if (device_names.empty()) {
315 return 1;
316 } else {
317 return static_cast<NSInteger>(device_names.size());
318 }
319 }
320
321 - (id)tableView:(NSTableView*)tableView
322 objectValueForTableColumn:(NSTableColumn*)tableColumn
323 row:(NSInteger)rowIndex {
324 const std::vector<base::string16>& device_names =
325 chooser_options_->GetOptions();
326 if (device_names.empty()) {
327 DCHECK(rowIndex == 0);
328 return l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_NO_DEVICES_FOUND_PROMPT);
329 } else {
330 if (rowIndex >= 0 && rowIndex < static_cast<int>(device_names.size())) {
331 return base::SysUTF16ToNSString(device_names[rowIndex]);
332 } else {
333 return @"";
334 }
335 }
336 }
337
338 - (BOOL)tableView:(NSTableView*)aTableView
339 shouldEditTableColumn:(NSTableColumn*)aTableColumn
340 row:(NSInteger)rowIndex {
341 return NO;
342 }
343
344 - (void)onOptionsInitialized {
345 [self updateTableView];
346 }
347
348 - (void)onOptionAdded:(NSInteger)index {
349 [self updateTableView];
350 }
351
352 - (void)onOptionRemoved:(NSInteger)index {
353 // Table view will automatically selects the next item if the current
354 // item is removed, so here it tracks if the removed item is the item
355 // that was previously selected, if so, disselect it.
Reilly Grant (use Gerrit) 2015/12/02 19:25:53 s/disselect/deselect/
juncai 2015/12/03 05:19:08 Done.
356 if ([tableView_ selectedRow] == index)
357 [tableView_ deselectRow:index];
358
359 [self updateTableView];
360 }
361
362 - (void)updateTableView {
363 const std::vector<base::string16>& device_names =
364 chooser_options_->GetOptions();
365 if (device_names.empty()) {
366 [tableView_ setEnabled:NO];
367 } else {
368 [tableView_ setEnabled:YES];
369 }
Reilly Grant (use Gerrit) 2015/12/02 19:25:53 There might be some obscure bool -> BOOL conversio
juncai 2015/12/03 05:19:08 Done.
370
371 [tableView_ reloadData];
372 }
373
374 - (void)tableViewSelectionDidChange:(NSNotification*)aNotification {
375 if ([tableView_ numberOfSelectedRows] == 0)
376 [connectButton_ setEnabled:NO];
377 else
378 [connectButton_ setEnabled:YES];
Reilly Grant (use Gerrit) 2015/12/02 19:25:53 This one will definitely work: [connectButton_ se
juncai 2015/12/03 05:19:08 Done.
379 }
380
381 - (void)updateAnchorPosition {
382 [self setParentWindow:[self getExpectedParentWindow]];
383 [self setAnchorPoint:[self getExpectedAnchorPoint]];
384 }
385
386 - (NSPoint)getExpectedAnchorPoint {
387 NSPoint anchor;
388 if ([self hasLocationBar]) {
389 LocationBarViewMac* location_bar =
390 [[[self getExpectedParentWindow] windowController] locationBarBridge];
391 anchor = location_bar->GetPageInfoBubblePoint();
392 } else {
393 // Center the bubble if there's no location bar.
394 NSRect contentFrame = [[[self getExpectedParentWindow] contentView] frame];
395 anchor = NSMakePoint(NSMidX(contentFrame), NSMaxY(contentFrame));
396 }
397
398 return [[self getExpectedParentWindow] convertBaseToScreen:anchor];
399 }
400
401 - (bool)hasLocationBar {
402 return browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
403 }
404
405 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation {
406 return [self hasLocationBar] ? info_bubble::kTopLeft : info_bubble::kNoArrow;
407 }
408
409 - (NSWindow*)getExpectedParentWindow {
410 DCHECK(browser_->window());
411 return browser_->window()->GetNativeWindow();
412 }
413
414 - (NSTextField*)bubbleTitle {
415 NSTextField* titleView =
416 [[[NSTextField alloc] initWithFrame:NSZeroRect] autorelease];
417 [titleView setDrawsBackground:NO];
418 [titleView setBezeled:NO];
419 [titleView setEditable:NO];
420 [titleView setSelectable:NO];
421 [titleView setStringValue:l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_PROMPT)];
422 [titleView setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
423 [titleView sizeToFit];
424 NSRect titleFrame = [titleView frame];
425 [titleView setFrameSize:NSMakeSize(NSWidth(titleFrame) + kTitlePaddingX,
426 NSHeight(titleFrame))];
427 return titleView;
428 }
429
430 - (NSButton*)closeButton {
431 int dimension = chrome_style::GetCloseButtonSize();
432 NSRect frame = NSMakeRect(0, 0, dimension, dimension);
433 NSButton* button =
434 [[[WebUIHoverCloseButton alloc] initWithFrame:frame] autorelease];
435 [button setAction:@selector(onClose:)];
436 [button setTarget:self];
437 return button;
438 }
439
440 - (NSButton*)buttonWithTitle:(NSString*)title action:(SEL)action {
441 NSButton* button =
442 [[[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect] autorelease];
443 [button setButtonType:NSMomentaryPushInButton];
444 [button setTitle:title];
445 [button setTarget:self];
446 [button setAction:action];
447 [button sizeToFit];
448 return button;
449 }
450
451 - (NSButton*)connectButton {
452 NSString* connectTitle =
453 l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_CONNECT_BUTTON_TEXT);
454 return [self buttonWithTitle:connectTitle action:@selector(onConnect:)];
455 }
456
457 - (NSButton*)cancelButton {
458 NSString* cancelTitle =
459 l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_CANCEL_BUTTON_TEXT);
460 return [self buttonWithTitle:cancelTitle action:@selector(onCancel:)];
461 }
462
463 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB {
464 NSRect frameA = [viewA frame];
465 NSRect frameB = [viewB frame];
466 CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB));
467 [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))];
468 [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))];
469 return width;
470 }
471
472 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB {
473 NSRect frameA = [viewA frame];
474 NSRect frameB = [viewB frame];
475 frameA.origin.y =
476 NSMinY(frameB) + std::floor((NSHeight(frameB) - NSHeight(frameA)) / 2);
477 [viewA setFrameOrigin:frameA.origin];
478 }
479
480 - (void)onConnect:(id)sender {
481 NSInteger row = [tableView_ selectedRow];
482 chooser_bubble_delegate_->Select(row);
483 [self close];
484 }
485
486 - (void)onCancel:(id)sender {
487 [self close];
488 }
489
490 - (void)onClose:(id)sender {
491 [self close];
492 }
493
494 @end
495
496 ChooserBubbleUiCocoa::ChooserBubbleUiCocoa(
497 Browser* browser,
498 ChooserOptions* chooser_options,
499 ChooserBubbleDelegate* chooser_bubble_delegate)
500 : browser_(browser),
501 chooser_options_(chooser_options),
502 chooser_bubble_delegate_(chooser_bubble_delegate),
503 chooser_bubble_ui_controller_(nil) {
504 DCHECK(browser);
505 DCHECK(chooser_options);
506 DCHECK(chooser_bubble_delegate);
507 chooser_options_->set_observer(this);
508 }
509
510 ChooserBubbleUiCocoa::~ChooserBubbleUiCocoa() {
511 chooser_options_->set_observer(nullptr);
512 if (chooser_bubble_ui_controller_) {
513 [chooser_bubble_ui_controller_ close];
514 chooser_bubble_ui_controller_ = nil;
515 }
516 }
517
518 void ChooserBubbleUiCocoa::Show(BubbleReference bubble_reference) {
519 if (!chooser_bubble_ui_controller_) {
520 chooser_bubble_ui_controller_ = [[ChooserBubbleUiController alloc]
521 initWithBrowser:browser_
522 initWithChooserOptions:chooser_options_
523 initWithChooserBubbleDelegate:chooser_bubble_delegate_
524 bridge:this];
525 }
526
527 [chooser_bubble_ui_controller_ show];
528 }
529
530 void ChooserBubbleUiCocoa::Close() {
531 if (chooser_bubble_ui_controller_) {
532 [chooser_bubble_ui_controller_ close];
533 chooser_bubble_ui_controller_ = nil;
534 }
535 }
536
537 void ChooserBubbleUiCocoa::UpdateAnchorPosition() {
538 [chooser_bubble_ui_controller_ updateAnchorPosition];
539 }
540
541 void ChooserBubbleUiCocoa::OnOptionsInitialized() {
542 [chooser_bubble_ui_controller_ onOptionsInitialized];
543 }
544
545 void ChooserBubbleUiCocoa::OnOptionAdded(int index) {
546 [chooser_bubble_ui_controller_ onOptionAdded:index];
547 }
548
549 void ChooserBubbleUiCocoa::OnOptionRemoved(int index) {
550 [chooser_bubble_ui_controller_ onOptionRemoved:index];
551 }
552
553 void ChooserBubbleUiCocoa::OnBubbleClosing() {
554 chooser_bubble_ui_controller_ = nil;
555 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698