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