Home | History | Annotate | Download | only in panels
      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::PanelWebContentsFocused(content::WebContents* contents) {
    173   // Nothing to do.
    174 }
    175 
    176 void PanelCocoa::PanelCut() {
    177   // Nothing to do since we do not have panel-specific system menu on Mac.
    178 }
    179 
    180 void PanelCocoa::PanelCopy() {
    181   // Nothing to do since we do not have panel-specific system menu on Mac.
    182 }
    183 
    184 void PanelCocoa::PanelPaste() {
    185   // Nothing to do since we do not have panel-specific system menu on Mac.
    186 }
    187 
    188 void 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 
    208 bool PanelCocoa::IsDrawingAttention() const {
    209   PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView];
    210   return [titlebar isDrawingAttention];
    211 }
    212 
    213 void 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 
    224 void 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 
    243 bool PanelCocoa::IsPanelAlwaysOnTop() const {
    244   return always_on_top_;
    245 }
    246 
    247 void 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 
    255 void PanelCocoa::UpdatePanelMinimizeRestoreButtonVisibility() {
    256   [controller_ updateTitleBarMinimizeRestoreButtonVisibility];
    257 }
    258 
    259 void PanelCocoa::SetWindowCornerStyle(panel::CornerStyle corner_style) {
    260   corner_style_ = corner_style;
    261 
    262   // TODO(dimich): investigate how to support it on Mac.
    263 }
    264 
    265 void PanelCocoa::MinimizePanelBySystem() {
    266   [controller_ miniaturize];
    267 }
    268 
    269 bool PanelCocoa::IsPanelMinimizedBySystem() const {
    270   return [controller_ isMiniaturized];
    271 }
    272 
    273 bool PanelCocoa::IsPanelShownOnActiveDesktop() const {
    274   return [[controller_ window] isOnActiveSpace];
    275 }
    276 
    277 void PanelCocoa::ShowShadow(bool show) {
    278   [controller_ showShadow:show];
    279 }
    280 
    281 void PanelCocoa::PanelExpansionStateChanging(
    282     Panel::ExpansionState old_state, Panel::ExpansionState new_state) {
    283   [controller_ updateWindowLevel:(new_state != Panel::EXPANDED)];
    284 }
    285 
    286 void PanelCocoa::AttachWebContents(content::WebContents* contents) {
    287   [controller_ webContentsInserted:contents];
    288 }
    289 
    290 void PanelCocoa::DetachWebContents(content::WebContents* contents) {
    291   [controller_ webContentsDetached:contents];
    292 }
    293 
    294 gfx::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 
    302 gfx::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 
    309 int PanelCocoa::TitleOnlyHeight() const {
    310   return [controller_ titlebarHeightInScreenCoordinates];
    311 }
    312 
    313 Panel* PanelCocoa::panel() const {
    314   return panel_.get();
    315 }
    316 
    317 void PanelCocoa::DidCloseNativeWindow() {
    318   DCHECK(!IsClosed());
    319   controller_ = NULL;
    320   panel_->OnNativePanelClosed();
    321 }
    322 
    323 // NativePanelTesting implementation.
    324 class 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 
    354 NativePanelTesting* PanelCocoa::CreateNativePanelTesting() {
    355   return new CocoaNativePanelTesting(this);
    356 }
    357 
    358 CocoaNativePanelTesting::CocoaNativePanelTesting(NativePanel* native_panel)
    359   : native_panel_window_(static_cast<PanelCocoa*>(native_panel)) {
    360 }
    361 
    362 PanelTitlebarViewCocoa* CocoaNativePanelTesting::titlebar() const {
    363   return [native_panel_window_->controller_ titlebarView];
    364 }
    365 
    366 void 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 
    378 void CocoaNativePanelTesting::ReleaseMouseButtonTitlebar(
    379     panel::ClickModifier modifier) {
    380   int modifierFlags =
    381       (modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0);
    382   [titlebar() releaseLeftMouseButtonTitlebar:modifierFlags];
    383 }
    384 
    385 void 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 
    393 void CocoaNativePanelTesting::CancelDragTitlebar() {
    394   [titlebar() cancelDragTitlebar];
    395 }
    396 
    397 void CocoaNativePanelTesting::FinishDragTitlebar() {
    398   [titlebar() finishDragTitlebar];
    399 }
    400 
    401 bool CocoaNativePanelTesting::VerifyDrawingAttention() const {
    402   return [titlebar() isDrawingAttention];
    403 }
    404 
    405 bool CocoaNativePanelTesting::VerifyActiveState(bool is_active) {
    406   // TODO(jianli): to be implemented.
    407   return false;
    408 }
    409 
    410 bool CocoaNativePanelTesting::VerifyAppIcon() const {
    411   // Nothing to do since panel does not show dock icon.
    412   return true;
    413 }
    414 
    415 bool CocoaNativePanelTesting::VerifySystemMinimizeState() const {
    416   // TODO(jianli): to be implemented.
    417   return true;
    418 }
    419 
    420 bool CocoaNativePanelTesting::IsWindowVisible() const {
    421   return [[native_panel_window_->controller_ window] isVisible];
    422 }
    423 
    424 bool CocoaNativePanelTesting::IsWindowSizeKnown() const {
    425   return true;
    426 }
    427 
    428 bool 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 
    437 bool 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 
    452 panel::CornerStyle CocoaNativePanelTesting::GetWindowCornerStyle() const {
    453   return native_panel_window_->corner_style_;
    454 }
    455 
    456 bool CocoaNativePanelTesting::EnsureApplicationRunOnForeground() {
    457   if ([NSApp isActive])
    458     return true;
    459   [NSApp activateIgnoringOtherApps:YES];
    460   return [NSApp isActive];
    461 }
    462