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

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

Powered by Google App Engine
This is Rietveld 408576698