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 16using content::NativeWebKeyboardEvent; 17using 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 24NativePanel* 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 30PanelCocoa::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 42PanelCocoa::~PanelCocoa() { 43} 44 45bool PanelCocoa::IsClosed() const { 46 return !controller_; 47} 48 49void 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 60void 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 78gfx::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. 84void PanelCocoa::SetPanelBounds(const gfx::Rect& bounds) { 85 setBoundsInternal(bounds, true); 86} 87 88void PanelCocoa::SetPanelBoundsInstantly(const gfx::Rect& bounds) { 89 setBoundsInternal(bounds, false); 90} 91 92void 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 109void 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 132void PanelCocoa::ActivatePanel() { 133 if (!is_shown_) 134 return; 135 136 [controller_ activate]; 137} 138 139void PanelCocoa::DeactivatePanel() { 140 [controller_ deactivate]; 141} 142 143bool 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 153void PanelCocoa::PreventActivationByOS(bool prevent_activation) { 154 [controller_ preventBecomingKeyWindow:prevent_activation]; 155 return; 156} 157 158gfx::NativeWindow PanelCocoa::GetNativePanelWindow() { 159 return [controller_ window]; 160} 161 162void PanelCocoa::UpdatePanelTitleBar() { 163 if (!is_shown_) 164 return; 165 [controller_ updateTitleBar]; 166} 167 168void PanelCocoa::UpdatePanelLoadingAnimations(bool should_animate) { 169 [controller_ updateThrobber:should_animate]; 170} 171 172void PanelCocoa::PanelWebContentsFocused(content::WebContents* contents) { 173 // Nothing to do. 174} 175 176void PanelCocoa::PanelCut() { 177 // Nothing to do since we do not have panel-specific system menu on Mac. 178} 179 180void PanelCocoa::PanelCopy() { 181 // Nothing to do since we do not have panel-specific system menu on Mac. 182} 183 184void PanelCocoa::PanelPaste() { 185 // Nothing to do since we do not have panel-specific system menu on Mac. 186} 187 188void PanelCocoa::DrawAttention(bool draw_attention) { 189 DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0); 190 191 PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView]; 192 if (draw_attention) 193 [titlebar drawAttention]; 194 else 195 [titlebar stopDrawingAttention]; 196 197 if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) { 198 if (draw_attention) { 199 DCHECK(!attention_request_id_); 200 attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest]; 201 } else { 202 [NSApp cancelUserAttentionRequest:attention_request_id_]; 203 attention_request_id_ = 0; 204 } 205 } 206} 207 208bool PanelCocoa::IsDrawingAttention() const { 209 PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView]; 210 return [titlebar isDrawingAttention]; 211} 212 213void PanelCocoa::HandlePanelKeyboardEvent( 214 const NativeWebKeyboardEvent& event) { 215 if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char) 216 return; 217 218 ChromeEventProcessingWindow* event_window = 219 static_cast<ChromeEventProcessingWindow*>([controller_ window]); 220 DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]); 221 [event_window redispatchKeyEvent:event.os_event]; 222} 223 224void PanelCocoa::FullScreenModeChanged(bool is_full_screen) { 225 if (!is_shown_) { 226 // If the panel window is not shown due to that a Chrome tab window is in 227 // fullscreen mode when the panel is being created, we need to show the 228 // panel window now. In addition, its titlebar needs to be updated since it 229 // is not done at the panel creation time. 230 if (!is_full_screen) { 231 ShowPanelInactive(); 232 UpdatePanelTitleBar(); 233 } 234 235 // No need to proceed when the panel window was not shown previously. 236 // We either show the panel window or do not show it depending on current 237 // full screen state. 238 return; 239 } 240 [controller_ fullScreenModeChanged:is_full_screen]; 241} 242 243bool PanelCocoa::IsPanelAlwaysOnTop() const { 244 return always_on_top_; 245} 246 247void PanelCocoa::SetPanelAlwaysOnTop(bool on_top) { 248 if (always_on_top_ == on_top) 249 return; 250 always_on_top_ = on_top; 251 [controller_ updateWindowLevel]; 252 [controller_ updateWindowCollectionBehavior]; 253} 254 255void PanelCocoa::UpdatePanelMinimizeRestoreButtonVisibility() { 256 [controller_ updateTitleBarMinimizeRestoreButtonVisibility]; 257} 258 259void PanelCocoa::SetWindowCornerStyle(panel::CornerStyle corner_style) { 260 corner_style_ = corner_style; 261 262 // TODO(dimich): investigate how to support it on Mac. 263} 264 265void PanelCocoa::MinimizePanelBySystem() { 266 [controller_ miniaturize]; 267} 268 269bool PanelCocoa::IsPanelMinimizedBySystem() const { 270 return [controller_ isMiniaturized]; 271} 272 273bool PanelCocoa::IsPanelShownOnActiveDesktop() const { 274 return [[controller_ window] isOnActiveSpace]; 275} 276 277void PanelCocoa::ShowShadow(bool show) { 278 [controller_ showShadow:show]; 279} 280 281void PanelCocoa::PanelExpansionStateChanging( 282 Panel::ExpansionState old_state, Panel::ExpansionState new_state) { 283 [controller_ updateWindowLevel:(new_state != Panel::EXPANDED)]; 284} 285 286void PanelCocoa::AttachWebContents(content::WebContents* contents) { 287 [controller_ webContentsInserted:contents]; 288} 289 290void PanelCocoa::DetachWebContents(content::WebContents* contents) { 291 [controller_ webContentsDetached:contents]; 292} 293 294gfx::Size PanelCocoa::WindowSizeFromContentSize( 295 const gfx::Size& content_size) const { 296 NSRect content = NSMakeRect(0, 0, 297 content_size.width(), content_size.height()); 298 NSRect frame = [controller_ frameRectForContentRect:content]; 299 return gfx::Size(NSWidth(frame), NSHeight(frame)); 300} 301 302gfx::Size PanelCocoa::ContentSizeFromWindowSize( 303 const gfx::Size& window_size) const { 304 NSRect frame = NSMakeRect(0, 0, window_size.width(), window_size.height()); 305 NSRect content = [controller_ contentRectForFrameRect:frame]; 306 return gfx::Size(NSWidth(content), NSHeight(content)); 307} 308 309int PanelCocoa::TitleOnlyHeight() const { 310 return [controller_ titlebarHeightInScreenCoordinates]; 311} 312 313Panel* PanelCocoa::panel() const { 314 return panel_.get(); 315} 316 317void PanelCocoa::DidCloseNativeWindow() { 318 DCHECK(!IsClosed()); 319 controller_ = NULL; 320 panel_->OnNativePanelClosed(); 321} 322 323// NativePanelTesting implementation. 324class CocoaNativePanelTesting : public NativePanelTesting { 325 public: 326 CocoaNativePanelTesting(NativePanel* native_panel); 327 virtual ~CocoaNativePanelTesting() { } 328 // Overridden from NativePanelTesting 329 virtual void PressLeftMouseButtonTitlebar( 330 const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE; 331 virtual void ReleaseMouseButtonTitlebar( 332 panel::ClickModifier modifier) OVERRIDE; 333 virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE; 334 virtual void CancelDragTitlebar() OVERRIDE; 335 virtual void FinishDragTitlebar() OVERRIDE; 336 virtual bool VerifyDrawingAttention() const OVERRIDE; 337 virtual bool VerifyActiveState(bool is_active) OVERRIDE; 338 virtual bool VerifyAppIcon() const OVERRIDE; 339 virtual bool VerifySystemMinimizeState() const OVERRIDE; 340 virtual bool IsWindowVisible() const OVERRIDE; 341 virtual bool IsWindowSizeKnown() const OVERRIDE; 342 virtual bool IsAnimatingBounds() const OVERRIDE; 343 virtual bool IsButtonVisible( 344 panel::TitlebarButtonType button_type) const OVERRIDE; 345 virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE; 346 virtual bool EnsureApplicationRunOnForeground() OVERRIDE; 347 348 private: 349 PanelTitlebarViewCocoa* titlebar() const; 350 // Weak, assumed always to outlive this test API object. 351 PanelCocoa* native_panel_window_; 352}; 353 354NativePanelTesting* PanelCocoa::CreateNativePanelTesting() { 355 return new CocoaNativePanelTesting(this); 356} 357 358CocoaNativePanelTesting::CocoaNativePanelTesting(NativePanel* native_panel) 359 : native_panel_window_(static_cast<PanelCocoa*>(native_panel)) { 360} 361 362PanelTitlebarViewCocoa* CocoaNativePanelTesting::titlebar() const { 363 return [native_panel_window_->controller_ titlebarView]; 364} 365 366void CocoaNativePanelTesting::PressLeftMouseButtonTitlebar( 367 const gfx::Point& mouse_location, panel::ClickModifier modifier) { 368 // Convert from platform-indepedent screen coordinates to Cocoa's screen 369 // coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen 370 // coordinates. 371 int modifierFlags = 372 (modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0); 373 [titlebar() pressLeftMouseButtonTitlebar: 374 cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location) 375 modifiers:modifierFlags]; 376} 377 378void CocoaNativePanelTesting::ReleaseMouseButtonTitlebar( 379 panel::ClickModifier modifier) { 380 int modifierFlags = 381 (modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0); 382 [titlebar() releaseLeftMouseButtonTitlebar:modifierFlags]; 383} 384 385void CocoaNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) { 386 // Convert from platform-indepedent screen coordinates to Cocoa's screen 387 // coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen 388 // coordinates. 389 [titlebar() dragTitlebar: 390 cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location)]; 391} 392 393void CocoaNativePanelTesting::CancelDragTitlebar() { 394 [titlebar() cancelDragTitlebar]; 395} 396 397void CocoaNativePanelTesting::FinishDragTitlebar() { 398 [titlebar() finishDragTitlebar]; 399} 400 401bool CocoaNativePanelTesting::VerifyDrawingAttention() const { 402 return [titlebar() isDrawingAttention]; 403} 404 405bool CocoaNativePanelTesting::VerifyActiveState(bool is_active) { 406 // TODO(jianli): to be implemented. 407 return false; 408} 409 410bool CocoaNativePanelTesting::VerifyAppIcon() const { 411 // Nothing to do since panel does not show dock icon. 412 return true; 413} 414 415bool CocoaNativePanelTesting::VerifySystemMinimizeState() const { 416 // TODO(jianli): to be implemented. 417 return true; 418} 419 420bool CocoaNativePanelTesting::IsWindowVisible() const { 421 return [[native_panel_window_->controller_ window] isVisible]; 422} 423 424bool CocoaNativePanelTesting::IsWindowSizeKnown() const { 425 return true; 426} 427 428bool CocoaNativePanelTesting::IsAnimatingBounds() const { 429 if ([native_panel_window_->controller_ isAnimatingBounds]) 430 return true; 431 StackedPanelCollection* stack = native_panel_window_->panel()->stack(); 432 if (!stack) 433 return false; 434 return stack->IsAnimatingPanelBounds(native_panel_window_->panel()); 435} 436 437bool CocoaNativePanelTesting::IsButtonVisible( 438 panel::TitlebarButtonType button_type) const { 439 switch (button_type) { 440 case panel::CLOSE_BUTTON: 441 return ![[titlebar() closeButton] isHidden]; 442 case panel::MINIMIZE_BUTTON: 443 return ![[titlebar() minimizeButton] isHidden]; 444 case panel::RESTORE_BUTTON: 445 return ![[titlebar() restoreButton] isHidden]; 446 default: 447 NOTREACHED(); 448 } 449 return false; 450} 451 452panel::CornerStyle CocoaNativePanelTesting::GetWindowCornerStyle() const { 453 return native_panel_window_->corner_style_; 454} 455 456bool CocoaNativePanelTesting::EnsureApplicationRunOnForeground() { 457 if ([NSApp isActive]) 458 return true; 459 [NSApp activateIgnoringOtherApps:YES]; 460 return [NSApp isActive]; 461} 462