OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 #include "chrome/browser/ui/cocoa/panels/panel_cocoa.h" | |
6 | |
7 #include "base/logging.h" | |
8 #import "chrome/browser/ui/cocoa/chrome_event_processing_window.h" | |
9 #import "chrome/browser/ui/cocoa/panels/panel_titlebar_view_cocoa.h" | |
10 #import "chrome/browser/ui/cocoa/panels/panel_utils_cocoa.h" | |
11 #import "chrome/browser/ui/cocoa/panels/panel_window_controller_cocoa.h" | |
12 #include "chrome/browser/ui/panels/panel.h" | |
13 #include "chrome/browser/ui/panels/stacked_panel_collection.h" | |
14 #include "content/public/browser/native_web_keyboard_event.h" | |
15 | |
16 using content::NativeWebKeyboardEvent; | |
17 using content::WebContents; | |
18 | |
19 // This creates a shim window class, which in turn creates a Cocoa window | |
20 // controller which in turn creates actual NSWindow by loading a nib. | |
21 // Overall chain of ownership is: | |
22 // PanelWindowControllerCocoa -> PanelCocoa -> Panel. | |
23 // static | |
24 NativePanel* Panel::CreateNativePanel(Panel* panel, | |
25 const gfx::Rect& bounds, | |
26 bool always_on_top) { | |
27 return new PanelCocoa(panel, bounds, always_on_top); | |
28 } | |
29 | |
30 PanelCocoa::PanelCocoa(Panel* panel, | |
31 const gfx::Rect& bounds, | |
32 bool always_on_top) | |
33 : panel_(panel), | |
34 bounds_(bounds), | |
35 always_on_top_(always_on_top), | |
36 is_shown_(false), | |
37 attention_request_id_(0), | |
38 corner_style_(panel::ALL_ROUNDED) { | |
39 controller_ = [[PanelWindowControllerCocoa alloc] initWithPanel:this]; | |
40 } | |
41 | |
42 PanelCocoa::~PanelCocoa() { | |
43 } | |
44 | |
45 bool PanelCocoa::IsClosed() const { | |
46 return !controller_; | |
47 } | |
48 | |
49 void PanelCocoa::ShowPanel() { | |
50 ShowPanelInactive(); | |
51 ActivatePanel(); | |
52 | |
53 // |-makeKeyAndOrderFront:| won't send |-windowDidBecomeKey:| until we | |
54 // return to the runloop. This causes extension tests that wait for the | |
55 // active status change notification to fail, so we send an active status | |
56 // notification here. | |
57 panel_->OnActiveStateChanged(true); | |
58 } | |
59 | |
60 void PanelCocoa::ShowPanelInactive() { | |
61 if (IsClosed()) | |
62 return; | |
63 | |
64 // This method may be called several times, meaning 'ensure it's shown'. | |
65 // Animations don't look good when repeated - hence this flag is needed. | |
66 if (is_shown_) { | |
67 return; | |
68 } | |
69 // A call to SetPanelBounds() before showing it is required to set | |
70 // the panel frame properly. | |
71 SetPanelBoundsInstantly(bounds_); | |
72 is_shown_ = true; | |
73 | |
74 NSRect finalFrame = cocoa_utils::ConvertRectToCocoaCoordinates(bounds_); | |
75 [controller_ revealAnimatedWithFrame:finalFrame]; | |
76 } | |
77 | |
78 gfx::Rect PanelCocoa::GetPanelBounds() const { | |
79 return bounds_; | |
80 } | |
81 | |
82 // |bounds| is the platform-independent screen coordinates, with (0,0) at | |
83 // top-left of the primary screen. | |
84 void PanelCocoa::SetPanelBounds(const gfx::Rect& bounds) { | |
85 setBoundsInternal(bounds, true); | |
86 } | |
87 | |
88 void PanelCocoa::SetPanelBoundsInstantly(const gfx::Rect& bounds) { | |
89 setBoundsInternal(bounds, false); | |
90 } | |
91 | |
92 void PanelCocoa::setBoundsInternal(const gfx::Rect& bounds, bool animate) { | |
93 // We will call SetPanelBoundsInstantly() once before showing the panel | |
94 // and it should set the panel frame correctly. | |
95 if (bounds_ == bounds && is_shown_) | |
96 return; | |
97 | |
98 bounds_ = bounds; | |
99 | |
100 // Safely ignore calls to animate bounds before the panel is shown to | |
101 // prevent the window from loading prematurely. | |
102 if (animate && !is_shown_) | |
103 return; | |
104 | |
105 NSRect frame = cocoa_utils::ConvertRectToCocoaCoordinates(bounds); | |
106 [controller_ setPanelFrame:frame animate:animate]; | |
107 } | |
108 | |
109 void PanelCocoa::ClosePanel() { | |
110 if (IsClosed()) | |
111 return; | |
112 | |
113 NSWindow* window = [controller_ window]; | |
114 // performClose: contains a nested message loop which can cause reentrancy | |
115 // if the browser is terminating and closing all the windows. | |
116 // Use this version that corresponds to protocol of performClose: but does not | |
117 // spin a nested loop. | |
118 // TODO(dimich): refactor similar method from BWC and reuse here. | |
119 if ([controller_ windowShouldClose:window]) { | |
120 // Make sure that the panel window is not associated with the underlying | |
121 // stack window because otherwise hiding the panel window could cause all | |
122 // other panel windows in the same stack to disappear. | |
123 NSWindow* stackWindow = [window parentWindow]; | |
124 if (stackWindow) | |
125 [stackWindow removeChildWindow:window]; | |
126 | |
127 [window orderOut:nil]; | |
128 [window close]; | |
129 } | |
130 } | |
131 | |
132 void PanelCocoa::ActivatePanel() { | |
133 if (!is_shown_) | |
134 return; | |
135 | |
136 [controller_ activate]; | |
137 } | |
138 | |
139 void PanelCocoa::DeactivatePanel() { | |
140 [controller_ deactivate]; | |
141 } | |
142 | |
143 bool PanelCocoa::IsPanelActive() const { | |
144 // TODO(dcheng): It seems like a lot of these methods can be called before | |
145 // our NSWindow is created. Do we really need to check in every one of these | |
146 // methods if the NSWindow is created, or is there a better way to | |
147 // gracefully handle this? | |
148 if (!is_shown_) | |
149 return false; | |
150 return [[controller_ window] isMainWindow]; | |
151 } | |
152 | |
153 void PanelCocoa::PreventActivationByOS(bool prevent_activation) { | |
154 [controller_ preventBecomingKeyWindow:prevent_activation]; | |
155 return; | |
156 } | |
157 | |
158 gfx::NativeWindow PanelCocoa::GetNativePanelWindow() { | |
159 return [controller_ window]; | |
160 } | |
161 | |
162 void PanelCocoa::UpdatePanelTitleBar() { | |
163 if (!is_shown_) | |
164 return; | |
165 [controller_ updateTitleBar]; | |
166 } | |
167 | |
168 void PanelCocoa::UpdatePanelLoadingAnimations(bool should_animate) { | |
169 [controller_ updateThrobber:should_animate]; | |
170 } | |
171 | |
172 void PanelCocoa::PanelCut() { | |
173 // Nothing to do since we do not have panel-specific system menu on Mac. | |
174 } | |
175 | |
176 void PanelCocoa::PanelCopy() { | |
177 // Nothing to do since we do not have panel-specific system menu on Mac. | |
178 } | |
179 | |
180 void PanelCocoa::PanelPaste() { | |
181 // Nothing to do since we do not have panel-specific system menu on Mac. | |
182 } | |
183 | |
184 void PanelCocoa::DrawAttention(bool draw_attention) { | |
185 DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0); | |
186 | |
187 PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView]; | |
188 if (draw_attention) | |
189 [titlebar drawAttention]; | |
190 else | |
191 [titlebar stopDrawingAttention]; | |
192 | |
193 if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) { | |
194 if (draw_attention) { | |
195 DCHECK(!attention_request_id_); | |
196 attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest]; | |
197 } else { | |
198 [NSApp cancelUserAttentionRequest:attention_request_id_]; | |
199 attention_request_id_ = 0; | |
200 } | |
201 } | |
202 } | |
203 | |
204 bool PanelCocoa::IsDrawingAttention() const { | |
205 PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView]; | |
206 return [titlebar isDrawingAttention]; | |
207 } | |
208 | |
209 void PanelCocoa::HandlePanelKeyboardEvent( | |
210 const NativeWebKeyboardEvent& event) { | |
211 if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char) | |
212 return; | |
213 | |
214 ChromeEventProcessingWindow* event_window = | |
215 static_cast<ChromeEventProcessingWindow*>([controller_ window]); | |
216 DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]); | |
217 [event_window redispatchKeyEvent:event.os_event]; | |
218 } | |
219 | |
220 void PanelCocoa::FullScreenModeChanged(bool is_full_screen) { | |
221 if (!is_shown_) { | |
222 // If the panel window is not shown due to that a Chrome tab window is in | |
223 // fullscreen mode when the panel is being created, we need to show the | |
224 // panel window now. In addition, its titlebar needs to be updated since it | |
225 // is not done at the panel creation time. | |
226 if (!is_full_screen) { | |
227 ShowPanelInactive(); | |
228 UpdatePanelTitleBar(); | |
229 } | |
230 | |
231 // No need to proceed when the panel window was not shown previously. | |
232 // We either show the panel window or do not show it depending on current | |
233 // full screen state. | |
234 return; | |
235 } | |
236 [controller_ fullScreenModeChanged:is_full_screen]; | |
237 } | |
238 | |
239 bool PanelCocoa::IsPanelAlwaysOnTop() const { | |
240 return always_on_top_; | |
241 } | |
242 | |
243 void PanelCocoa::SetPanelAlwaysOnTop(bool on_top) { | |
244 if (always_on_top_ == on_top) | |
245 return; | |
246 always_on_top_ = on_top; | |
247 [controller_ updateWindowLevel]; | |
248 [controller_ updateWindowCollectionBehavior]; | |
249 } | |
250 | |
251 void PanelCocoa::UpdatePanelMinimizeRestoreButtonVisibility() { | |
252 [controller_ updateTitleBarMinimizeRestoreButtonVisibility]; | |
253 } | |
254 | |
255 void PanelCocoa::SetWindowCornerStyle(panel::CornerStyle corner_style) { | |
256 corner_style_ = corner_style; | |
257 | |
258 // TODO(dimich): investigate how to support it on Mac. | |
259 } | |
260 | |
261 void PanelCocoa::MinimizePanelBySystem() { | |
262 [controller_ miniaturize]; | |
263 } | |
264 | |
265 bool PanelCocoa::IsPanelMinimizedBySystem() const { | |
266 return [controller_ isMiniaturized]; | |
267 } | |
268 | |
269 bool PanelCocoa::IsPanelShownOnActiveDesktop() const { | |
270 return [[controller_ window] isOnActiveSpace]; | |
271 } | |
272 | |
273 void PanelCocoa::ShowShadow(bool show) { | |
274 [controller_ showShadow:show]; | |
275 } | |
276 | |
277 void PanelCocoa::PanelExpansionStateChanging( | |
278 Panel::ExpansionState old_state, Panel::ExpansionState new_state) { | |
279 [controller_ updateWindowLevel:(new_state != Panel::EXPANDED)]; | |
280 } | |
281 | |
282 void PanelCocoa::AttachWebContents(content::WebContents* contents) { | |
283 [controller_ webContentsInserted:contents]; | |
284 } | |
285 | |
286 void PanelCocoa::DetachWebContents(content::WebContents* contents) { | |
287 [controller_ webContentsDetached:contents]; | |
288 } | |
289 | |
290 gfx::Size PanelCocoa::WindowSizeFromContentSize( | |
291 const gfx::Size& content_size) const { | |
292 NSRect content = NSMakeRect(0, 0, | |
293 content_size.width(), content_size.height()); | |
294 NSRect frame = [controller_ frameRectForContentRect:content]; | |
295 return gfx::Size(NSWidth(frame), NSHeight(frame)); | |
296 } | |
297 | |
298 gfx::Size PanelCocoa::ContentSizeFromWindowSize( | |
299 const gfx::Size& window_size) const { | |
300 NSRect frame = NSMakeRect(0, 0, window_size.width(), window_size.height()); | |
301 NSRect content = [controller_ contentRectForFrameRect:frame]; | |
302 return gfx::Size(NSWidth(content), NSHeight(content)); | |
303 } | |
304 | |
305 int PanelCocoa::TitleOnlyHeight() const { | |
306 return [controller_ titlebarHeightInScreenCoordinates]; | |
307 } | |
308 | |
309 Panel* PanelCocoa::panel() const { | |
310 return panel_.get(); | |
311 } | |
312 | |
313 void PanelCocoa::DidCloseNativeWindow() { | |
314 DCHECK(!IsClosed()); | |
315 controller_ = NULL; | |
316 panel_->OnNativePanelClosed(); | |
317 } | |
318 | |
319 // NativePanelTesting implementation. | |
320 class CocoaNativePanelTesting : public NativePanelTesting { | |
321 public: | |
322 CocoaNativePanelTesting(NativePanel* native_panel); | |
323 ~CocoaNativePanelTesting() override {} | |
324 // Overridden from NativePanelTesting | |
325 void PressLeftMouseButtonTitlebar(const gfx::Point& mouse_location, | |
326 panel::ClickModifier modifier) override; | |
327 void ReleaseMouseButtonTitlebar(panel::ClickModifier modifier) override; | |
328 void DragTitlebar(const gfx::Point& mouse_location) override; | |
329 void CancelDragTitlebar() override; | |
330 void FinishDragTitlebar() override; | |
331 bool VerifyDrawingAttention() const override; | |
332 bool VerifyActiveState(bool is_active) override; | |
333 bool VerifyAppIcon() const override; | |
334 bool VerifySystemMinimizeState() const override; | |
335 bool IsWindowVisible() const override; | |
336 bool IsWindowSizeKnown() const override; | |
337 bool IsAnimatingBounds() const override; | |
338 bool IsButtonVisible(panel::TitlebarButtonType button_type) const override; | |
339 panel::CornerStyle GetWindowCornerStyle() const override; | |
340 bool EnsureApplicationRunOnForeground() override; | |
341 | |
342 private: | |
343 PanelTitlebarViewCocoa* titlebar() const; | |
344 // Weak, assumed always to outlive this test API object. | |
345 PanelCocoa* native_panel_window_; | |
346 }; | |
347 | |
348 NativePanelTesting* PanelCocoa::CreateNativePanelTesting() { | |
349 return new CocoaNativePanelTesting(this); | |
350 } | |
351 | |
352 CocoaNativePanelTesting::CocoaNativePanelTesting(NativePanel* native_panel) | |
353 : native_panel_window_(static_cast<PanelCocoa*>(native_panel)) { | |
354 } | |
355 | |
356 PanelTitlebarViewCocoa* CocoaNativePanelTesting::titlebar() const { | |
357 return [native_panel_window_->controller_ titlebarView]; | |
358 } | |
359 | |
360 void CocoaNativePanelTesting::PressLeftMouseButtonTitlebar( | |
361 const gfx::Point& mouse_location, panel::ClickModifier modifier) { | |
362 // Convert from platform-indepedent screen coordinates to Cocoa's screen | |
363 // coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen | |
364 // coordinates. | |
365 int modifierFlags = | |
366 (modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0); | |
367 [titlebar() pressLeftMouseButtonTitlebar: | |
368 cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location) | |
369 modifiers:modifierFlags]; | |
370 } | |
371 | |
372 void CocoaNativePanelTesting::ReleaseMouseButtonTitlebar( | |
373 panel::ClickModifier modifier) { | |
374 int modifierFlags = | |
375 (modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0); | |
376 [titlebar() releaseLeftMouseButtonTitlebar:modifierFlags]; | |
377 } | |
378 | |
379 void CocoaNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) { | |
380 // Convert from platform-indepedent screen coordinates to Cocoa's screen | |
381 // coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen | |
382 // coordinates. | |
383 [titlebar() dragTitlebar: | |
384 cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location)]; | |
385 } | |
386 | |
387 void CocoaNativePanelTesting::CancelDragTitlebar() { | |
388 [titlebar() cancelDragTitlebar]; | |
389 } | |
390 | |
391 void CocoaNativePanelTesting::FinishDragTitlebar() { | |
392 [titlebar() finishDragTitlebar]; | |
393 } | |
394 | |
395 bool CocoaNativePanelTesting::VerifyDrawingAttention() const { | |
396 return [titlebar() isDrawingAttention]; | |
397 } | |
398 | |
399 bool CocoaNativePanelTesting::VerifyActiveState(bool is_active) { | |
400 // TODO(jianli): to be implemented. | |
401 return false; | |
402 } | |
403 | |
404 bool CocoaNativePanelTesting::VerifyAppIcon() const { | |
405 // Nothing to do since panel does not show dock icon. | |
406 return true; | |
407 } | |
408 | |
409 bool CocoaNativePanelTesting::VerifySystemMinimizeState() const { | |
410 // TODO(jianli): to be implemented. | |
411 return true; | |
412 } | |
413 | |
414 bool CocoaNativePanelTesting::IsWindowVisible() const { | |
415 return [[native_panel_window_->controller_ window] isVisible]; | |
416 } | |
417 | |
418 bool CocoaNativePanelTesting::IsWindowSizeKnown() const { | |
419 return true; | |
420 } | |
421 | |
422 bool CocoaNativePanelTesting::IsAnimatingBounds() const { | |
423 if ([native_panel_window_->controller_ isAnimatingBounds]) | |
424 return true; | |
425 StackedPanelCollection* stack = native_panel_window_->panel()->stack(); | |
426 if (!stack) | |
427 return false; | |
428 return stack->IsAnimatingPanelBounds(native_panel_window_->panel()); | |
429 } | |
430 | |
431 bool CocoaNativePanelTesting::IsButtonVisible( | |
432 panel::TitlebarButtonType button_type) const { | |
433 switch (button_type) { | |
434 case panel::CLOSE_BUTTON: | |
435 return ![[titlebar() closeButton] isHidden]; | |
436 case panel::MINIMIZE_BUTTON: | |
437 return ![[titlebar() minimizeButton] isHidden]; | |
438 case panel::RESTORE_BUTTON: | |
439 return ![[titlebar() restoreButton] isHidden]; | |
440 default: | |
441 NOTREACHED(); | |
442 } | |
443 return false; | |
444 } | |
445 | |
446 panel::CornerStyle CocoaNativePanelTesting::GetWindowCornerStyle() const { | |
447 return native_panel_window_->corner_style_; | |
448 } | |
449 | |
450 bool CocoaNativePanelTesting::EnsureApplicationRunOnForeground() { | |
451 if ([NSApp isActive]) | |
452 return true; | |
453 [NSApp activateIgnoringOtherApps:YES]; | |
454 return [NSApp isActive]; | |
455 } | |
OLD | NEW |