| 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 |