OLD | NEW |
| (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 <stddef.h> | |
8 | |
9 #include <algorithm> | |
10 #include <cmath> | |
11 | |
12 #include "base/mac/scoped_nsobject.h" | |
13 #include "base/memory/ptr_util.h" | |
14 #include "base/strings/sys_string_conversions.h" | |
15 #include "chrome/browser/chooser_controller/chooser_controller.h" | |
16 #include "chrome/browser/ui/browser.h" | |
17 #include "chrome/browser/ui/browser_window.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/device_chooser_content_view_cocoa.h" | |
22 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | |
23 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | |
24 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" | |
25 #include "chrome/browser/ui/website_settings/chooser_bubble_delegate.h" | |
26 #include "components/bubble/bubble_controller.h" | |
27 #include "content/public/browser/native_web_keyboard_event.h" | |
28 #include "ui/base/cocoa/cocoa_base_utils.h" | |
29 #include "ui/base/cocoa/window_size_constants.h" | |
30 | |
31 std::unique_ptr<BubbleUi> ChooserBubbleDelegate::BuildBubbleUi() { | |
32 return base::MakeUnique<ChooserBubbleUiCocoa>(browser_, | |
33 std::move(chooser_controller_)); | |
34 } | |
35 | |
36 @interface ChooserBubbleUiController | |
37 : BaseBubbleController<NSTableViewDataSource, NSTableViewDelegate> { | |
38 @private | |
39 // Bridge to the C++ class that created this object. | |
40 ChooserBubbleUiCocoa* bridge_; // Weak. | |
41 bool buttonPressed_; | |
42 | |
43 base::scoped_nsobject<DeviceChooserContentViewCocoa> | |
44 deviceChooserContentView_; | |
45 NSTableView* tableView_; // Weak. | |
46 NSButton* connectButton_; // Weak. | |
47 NSButton* cancelButton_; // Weak. | |
48 | |
49 Browser* browser_; // Weak. | |
50 } | |
51 | |
52 // Designated initializer. |browser| and |bridge| must both be non-nil. | |
53 - (id)initWithBrowser:(Browser*)browser | |
54 chooserController:(std::unique_ptr<ChooserController>)chooserController | |
55 bridge:(ChooserBubbleUiCocoa*)bridge; | |
56 | |
57 // Makes the bubble visible. | |
58 - (void)show; | |
59 | |
60 // Will reposition the bubble based in case the anchor or parent should change. | |
61 - (void)updateAnchorPosition; | |
62 | |
63 // Will calculate the expected anchor point for this bubble. | |
64 // Should only be used outside this class for tests. | |
65 - (NSPoint)getExpectedAnchorPoint; | |
66 | |
67 // Returns true if the browser has support for the location bar. | |
68 // Should only be used outside this class for tests. | |
69 - (bool)hasLocationBar; | |
70 | |
71 // Update |tableView_| when chooser options changed. | |
72 - (void)updateTableView; | |
73 | |
74 // Determines if the bubble has an anchor in a corner or no anchor at all. | |
75 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation; | |
76 | |
77 // Returns the expected parent for this bubble. | |
78 - (NSWindow*)getExpectedParentWindow; | |
79 | |
80 // Called when the "Connect" button is pressed. | |
81 - (void)onConnect:(id)sender; | |
82 | |
83 // Called when the "Cancel" button is pressed. | |
84 - (void)onCancel:(id)sender; | |
85 | |
86 @end | |
87 | |
88 @implementation ChooserBubbleUiController | |
89 | |
90 - (id)initWithBrowser:(Browser*)browser | |
91 chooserController:(std::unique_ptr<ChooserController>)chooserController | |
92 bridge:(ChooserBubbleUiCocoa*)bridge { | |
93 DCHECK(browser); | |
94 DCHECK(chooserController); | |
95 DCHECK(bridge); | |
96 | |
97 browser_ = browser; | |
98 | |
99 base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc] | |
100 initWithContentRect:ui::kWindowSizeDeterminedLater | |
101 styleMask:NSBorderlessWindowMask | |
102 backing:NSBackingStoreBuffered | |
103 defer:NO]); | |
104 [window setAllowedAnimations:info_bubble::kAnimateNone]; | |
105 [window setReleasedWhenClosed:NO]; | |
106 if ((self = [super initWithWindow:window | |
107 parentWindow:[self getExpectedParentWindow] | |
108 anchoredAt:NSZeroPoint])) { | |
109 [self setShouldCloseOnResignKey:YES]; | |
110 [self setShouldOpenAsKeyWindow:YES]; | |
111 [[self bubble] setArrowLocation:[self getExpectedArrowLocation]]; | |
112 bridge_ = bridge; | |
113 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | |
114 [center addObserver:self | |
115 selector:@selector(parentWindowDidMove:) | |
116 name:NSWindowDidMoveNotification | |
117 object:[self getExpectedParentWindow]]; | |
118 | |
119 base::string16 chooserTitle = chooserController->GetTitle(); | |
120 deviceChooserContentView_.reset([[DeviceChooserContentViewCocoa alloc] | |
121 initWithChooserTitle:base::SysUTF16ToNSString(chooserTitle) | |
122 chooserController:std::move(chooserController)]); | |
123 | |
124 tableView_ = [deviceChooserContentView_ tableView]; | |
125 connectButton_ = [deviceChooserContentView_ connectButton]; | |
126 cancelButton_ = [deviceChooserContentView_ cancelButton]; | |
127 | |
128 [connectButton_ setTarget:self]; | |
129 [connectButton_ setAction:@selector(onConnect:)]; | |
130 [cancelButton_ setTarget:self]; | |
131 [cancelButton_ setAction:@selector(onCancel:)]; | |
132 [tableView_ setDelegate:self]; | |
133 [tableView_ setDataSource:self]; | |
134 | |
135 [[[self window] contentView] addSubview:deviceChooserContentView_.get()]; | |
136 } | |
137 | |
138 return self; | |
139 } | |
140 | |
141 - (void)windowWillClose:(NSNotification*)notification { | |
142 [[NSNotificationCenter defaultCenter] | |
143 removeObserver:self | |
144 name:NSWindowDidMoveNotification | |
145 object:nil]; | |
146 if (!buttonPressed_) | |
147 [deviceChooserContentView_ close]; | |
148 bridge_->OnBubbleClosing(); | |
149 [super windowWillClose:notification]; | |
150 } | |
151 | |
152 - (void)parentWindowWillToggleFullScreen:(NSNotification*)notification { | |
153 // Override the base class implementation, which would have closed the bubble. | |
154 } | |
155 | |
156 - (void)parentWindowDidResize:(NSNotification*)notification { | |
157 [self setAnchorPoint:[self getExpectedAnchorPoint]]; | |
158 } | |
159 | |
160 - (void)parentWindowDidMove:(NSNotification*)notification { | |
161 DCHECK(bridge_); | |
162 [self setAnchorPoint:[self getExpectedAnchorPoint]]; | |
163 } | |
164 | |
165 - (void)show { | |
166 NSRect bubbleFrame = | |
167 [[self window] frameRectForContentRect:[deviceChooserContentView_ frame]]; | |
168 if ([[self window] isVisible]) { | |
169 // Unfortunately, calling -setFrame followed by -setFrameOrigin (called | |
170 // within -setAnchorPoint) causes flickering. Avoid the flickering by | |
171 // manually adjusting the new frame's origin so that the top left stays the | |
172 // same, and only calling -setFrame. | |
173 NSRect currentWindowFrame = [[self window] frame]; | |
174 bubbleFrame.origin = currentWindowFrame.origin; | |
175 bubbleFrame.origin.y = bubbleFrame.origin.y + | |
176 currentWindowFrame.size.height - | |
177 bubbleFrame.size.height; | |
178 [[self window] setFrame:bubbleFrame display:YES]; | |
179 } else { | |
180 [[self window] setFrame:bubbleFrame display:NO]; | |
181 [self setAnchorPoint:[self getExpectedAnchorPoint]]; | |
182 [self showWindow:nil]; | |
183 [[self window] makeFirstResponder:nil]; | |
184 [[self window] setInitialFirstResponder:tableView_]; | |
185 } | |
186 } | |
187 | |
188 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView { | |
189 return [deviceChooserContentView_ numberOfOptions]; | |
190 } | |
191 | |
192 - (NSView*)tableView:(NSTableView*)tableView | |
193 viewForTableColumn:(NSTableColumn*)tableColumn | |
194 row:(NSInteger)row { | |
195 return [deviceChooserContentView_ createTableRowView:row].autorelease(); | |
196 } | |
197 | |
198 - (BOOL)tableView:(NSTableView*)aTableView | |
199 shouldEditTableColumn:(NSTableColumn*)aTableColumn | |
200 row:(NSInteger)rowIndex { | |
201 return NO; | |
202 } | |
203 | |
204 - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row { | |
205 return [deviceChooserContentView_ tableRowViewHeight:row]; | |
206 } | |
207 | |
208 - (void)updateTableView { | |
209 [deviceChooserContentView_ updateTableView]; | |
210 } | |
211 | |
212 - (void)tableViewSelectionDidChange:(NSNotification*)aNotification { | |
213 [deviceChooserContentView_ updateContentRowColor]; | |
214 [connectButton_ setEnabled:[tableView_ numberOfSelectedRows] > 0]; | |
215 } | |
216 | |
217 // Selection changes (while the mouse button is still down). | |
218 - (void)tableViewSelectionIsChanging:(NSNotification*)aNotification { | |
219 [deviceChooserContentView_ updateContentRowColor]; | |
220 [connectButton_ setEnabled:[tableView_ numberOfSelectedRows] > 0]; | |
221 } | |
222 | |
223 - (void)updateAnchorPosition { | |
224 [self setParentWindow:[self getExpectedParentWindow]]; | |
225 [self setAnchorPoint:[self getExpectedAnchorPoint]]; | |
226 } | |
227 | |
228 - (NSPoint)getExpectedAnchorPoint { | |
229 NSPoint anchor; | |
230 if ([self hasLocationBar]) { | |
231 LocationBarViewMac* locationBar = | |
232 [[[self getExpectedParentWindow] windowController] locationBarBridge]; | |
233 anchor = locationBar->GetPageInfoBubblePoint(); | |
234 } else { | |
235 // Center the bubble if there's no location bar. | |
236 NSRect contentFrame = [[[self getExpectedParentWindow] contentView] frame]; | |
237 anchor = NSMakePoint(NSMidX(contentFrame), NSMaxY(contentFrame)); | |
238 } | |
239 | |
240 return ui::ConvertPointFromWindowToScreen([self getExpectedParentWindow], | |
241 anchor); | |
242 } | |
243 | |
244 - (bool)hasLocationBar { | |
245 return browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR); | |
246 } | |
247 | |
248 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation { | |
249 return [self hasLocationBar] ? info_bubble::kTopLeading | |
250 : info_bubble::kNoArrow; | |
251 } | |
252 | |
253 - (NSWindow*)getExpectedParentWindow { | |
254 DCHECK(browser_->window()); | |
255 return browser_->window()->GetNativeWindow(); | |
256 } | |
257 | |
258 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB { | |
259 NSRect frameA = [viewA frame]; | |
260 NSRect frameB = [viewB frame]; | |
261 CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB)); | |
262 [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))]; | |
263 [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))]; | |
264 return width; | |
265 } | |
266 | |
267 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB { | |
268 NSRect frameA = [viewA frame]; | |
269 NSRect frameB = [viewB frame]; | |
270 frameA.origin.y = | |
271 NSMinY(frameB) + std::floor((NSHeight(frameB) - NSHeight(frameA)) / 2); | |
272 [viewA setFrameOrigin:frameA.origin]; | |
273 } | |
274 | |
275 - (void)onConnect:(id)sender { | |
276 buttonPressed_ = true; | |
277 [deviceChooserContentView_ accept]; | |
278 if (self.bubbleReference) | |
279 self.bubbleReference->CloseBubble(BUBBLE_CLOSE_ACCEPTED); | |
280 [self close]; | |
281 } | |
282 | |
283 - (void)onCancel:(id)sender { | |
284 buttonPressed_ = true; | |
285 [deviceChooserContentView_ cancel]; | |
286 if (self.bubbleReference) | |
287 self.bubbleReference->CloseBubble(BUBBLE_CLOSE_CANCELED); | |
288 [self close]; | |
289 } | |
290 | |
291 @end | |
292 | |
293 ChooserBubbleUiCocoa::ChooserBubbleUiCocoa( | |
294 Browser* browser, | |
295 std::unique_ptr<ChooserController> chooser_controller) | |
296 : browser_(browser), | |
297 chooser_bubble_ui_controller_(nil) { | |
298 DCHECK(browser_); | |
299 DCHECK(chooser_controller); | |
300 chooser_bubble_ui_controller_ = [[ChooserBubbleUiController alloc] | |
301 initWithBrowser:browser_ | |
302 chooserController:std::move(chooser_controller) | |
303 bridge:this]; | |
304 } | |
305 | |
306 ChooserBubbleUiCocoa::~ChooserBubbleUiCocoa() { | |
307 if (chooser_bubble_ui_controller_) { | |
308 [chooser_bubble_ui_controller_ close]; | |
309 chooser_bubble_ui_controller_ = nil; | |
310 } | |
311 } | |
312 | |
313 void ChooserBubbleUiCocoa::Show(BubbleReference bubble_reference) { | |
314 [chooser_bubble_ui_controller_ setBubbleReference:bubble_reference]; | |
315 [chooser_bubble_ui_controller_ show]; | |
316 [chooser_bubble_ui_controller_ updateTableView]; | |
317 } | |
318 | |
319 void ChooserBubbleUiCocoa::Close() { | |
320 if (chooser_bubble_ui_controller_) { | |
321 [chooser_bubble_ui_controller_ close]; | |
322 chooser_bubble_ui_controller_ = nil; | |
323 } | |
324 } | |
325 | |
326 void ChooserBubbleUiCocoa::UpdateAnchorPosition() { | |
327 if (chooser_bubble_ui_controller_) | |
328 [chooser_bubble_ui_controller_ updateAnchorPosition]; | |
329 } | |
330 | |
331 void ChooserBubbleUiCocoa::OnBubbleClosing() { | |
332 chooser_bubble_ui_controller_ = nil; | |
333 } | |
OLD | NEW |