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

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

Powered by Google App Engine
This is Rietveld 408576698