blob: b8f8abafffcd58e13da3da4ebdf11e5865ce439f [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/cocoa/panels/panel_cocoa.h"
#include "base/logging.h"
#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
#import "chrome/browser/ui/cocoa/panels/panel_titlebar_view_cocoa.h"
#import "chrome/browser/ui/cocoa/panels/panel_utils_cocoa.h"
#import "chrome/browser/ui/cocoa/panels/panel_window_controller_cocoa.h"
#include "chrome/browser/ui/panels/panel.h"
#include "chrome/browser/ui/panels/stacked_panel_collection.h"
#include "content/public/browser/native_web_keyboard_event.h"
using content::NativeWebKeyboardEvent;
using content::WebContents;
namespace {
// Use this instead of 0 for minimum size of a window when doing opening and
// closing animations, since OSX window manager does not like 0-sized windows
// (according to avi@).
const int kMinimumWindowSize = 1;
} // namespace
// This creates a shim window class, which in turn creates a Cocoa window
// controller which in turn creates actual NSWindow by loading a nib.
// Overall chain of ownership is:
// PanelWindowControllerCocoa -> PanelCocoa -> Panel.
// static
NativePanel* Panel::CreateNativePanel(Panel* panel,
const gfx::Rect& bounds,
bool always_on_top) {
return new PanelCocoa(panel, bounds, always_on_top);
}
PanelCocoa::PanelCocoa(Panel* panel,
const gfx::Rect& bounds,
bool always_on_top)
: panel_(panel),
bounds_(bounds),
always_on_top_(always_on_top),
is_shown_(false),
attention_request_id_(0),
corner_style_(panel::ALL_ROUNDED) {
controller_ = [[PanelWindowControllerCocoa alloc] initWithPanel:this];
}
PanelCocoa::~PanelCocoa() {
}
bool PanelCocoa::IsClosed() const {
return !controller_;
}
void PanelCocoa::ShowPanel() {
ShowPanelInactive();
ActivatePanel();
// |-makeKeyAndOrderFront:| won't send |-windowDidBecomeKey:| until we
// return to the runloop. This causes extension tests that wait for the
// active status change notification to fail, so we send an active status
// notification here.
panel_->OnActiveStateChanged(true);
}
void PanelCocoa::ShowPanelInactive() {
if (IsClosed())
return;
// This method may be called several times, meaning 'ensure it's shown'.
// Animations don't look good when repeated - hence this flag is needed.
if (is_shown_) {
return;
}
// A call to SetPanelBounds() before showing it is required to set
// the panel frame properly.
SetPanelBoundsInstantly(bounds_);
is_shown_ = true;
NSRect finalFrame = cocoa_utils::ConvertRectToCocoaCoordinates(bounds_);
[controller_ revealAnimatedWithFrame:finalFrame];
}
gfx::Rect PanelCocoa::GetPanelBounds() const {
return bounds_;
}
// |bounds| is the platform-independent screen coordinates, with (0,0) at
// top-left of the primary screen.
void PanelCocoa::SetPanelBounds(const gfx::Rect& bounds) {
setBoundsInternal(bounds, true);
}
void PanelCocoa::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
setBoundsInternal(bounds, false);
}
void PanelCocoa::setBoundsInternal(const gfx::Rect& bounds, bool animate) {
// We will call SetPanelBoundsInstantly() once before showing the panel
// and it should set the panel frame correctly.
if (bounds_ == bounds && is_shown_)
return;
bounds_ = bounds;
// Safely ignore calls to animate bounds before the panel is shown to
// prevent the window from loading prematurely.
if (animate && !is_shown_)
return;
NSRect frame = cocoa_utils::ConvertRectToCocoaCoordinates(bounds);
[controller_ setPanelFrame:frame animate:animate];
}
void PanelCocoa::ClosePanel() {
if (IsClosed())
return;
NSWindow* window = [controller_ window];
// performClose: contains a nested message loop which can cause reentrancy
// if the browser is terminating and closing all the windows.
// Use this version that corresponds to protocol of performClose: but does not
// spin a nested loop.
// TODO(dimich): refactor similar method from BWC and reuse here.
if ([controller_ windowShouldClose:window]) {
// Make sure that the panel window is not associated with the underlying
// stack window because otherwise hiding the panel window could cause all
// other panel windows in the same stack to disappear.
NSWindow* stackWindow = [window parentWindow];
if (stackWindow)
[stackWindow removeChildWindow:window];
[window orderOut:nil];
[window close];
}
}
void PanelCocoa::ActivatePanel() {
if (!is_shown_)
return;
[controller_ activate];
}
void PanelCocoa::DeactivatePanel() {
[controller_ deactivate];
}
bool PanelCocoa::IsPanelActive() const {
// TODO(dcheng): It seems like a lot of these methods can be called before
// our NSWindow is created. Do we really need to check in every one of these
// methods if the NSWindow is created, or is there a better way to
// gracefully handle this?
if (!is_shown_)
return false;
return [[controller_ window] isMainWindow];
}
void PanelCocoa::PreventActivationByOS(bool prevent_activation) {
[controller_ preventBecomingKeyWindow:prevent_activation];
return;
}
gfx::NativeWindow PanelCocoa::GetNativePanelWindow() {
return [controller_ window];
}
void PanelCocoa::UpdatePanelTitleBar() {
if (!is_shown_)
return;
[controller_ updateTitleBar];
}
void PanelCocoa::UpdatePanelLoadingAnimations(bool should_animate) {
[controller_ updateThrobber:should_animate];
}
void PanelCocoa::PanelWebContentsFocused(content::WebContents* contents) {
// Nothing to do.
}
void PanelCocoa::PanelCut() {
// Nothing to do since we do not have panel-specific system menu on Mac.
}
void PanelCocoa::PanelCopy() {
// Nothing to do since we do not have panel-specific system menu on Mac.
}
void PanelCocoa::PanelPaste() {
// Nothing to do since we do not have panel-specific system menu on Mac.
}
void PanelCocoa::DrawAttention(bool draw_attention) {
DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0);
PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView];
if (draw_attention)
[titlebar drawAttention];
else
[titlebar stopDrawingAttention];
if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) {
if (draw_attention) {
DCHECK(!attention_request_id_);
attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest];
} else {
[NSApp cancelUserAttentionRequest:attention_request_id_];
attention_request_id_ = 0;
}
}
}
bool PanelCocoa::IsDrawingAttention() const {
PanelTitlebarViewCocoa* titlebar = [controller_ titlebarView];
return [titlebar isDrawingAttention];
}
void PanelCocoa::HandlePanelKeyboardEvent(
const NativeWebKeyboardEvent& event) {
if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char)
return;
ChromeEventProcessingWindow* event_window =
static_cast<ChromeEventProcessingWindow*>([controller_ window]);
DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]);
[event_window redispatchKeyEvent:event.os_event];
}
void PanelCocoa::FullScreenModeChanged(bool is_full_screen) {
if (!is_shown_) {
// If the panel window is not shown due to that a Chrome tab window is in
// fullscreen mode when the panel is being created, we need to show the
// panel window now. In addition, its titlebar needs to be updated since it
// is not done at the panel creation time.
if (!is_full_screen) {
ShowPanelInactive();
UpdatePanelTitleBar();
}
// No need to proceed when the panel window was not shown previously.
// We either show the panel window or do not show it depending on current
// full screen state.
return;
}
[controller_ fullScreenModeChanged:is_full_screen];
}
bool PanelCocoa::IsPanelAlwaysOnTop() const {
return always_on_top_;
}
void PanelCocoa::SetPanelAlwaysOnTop(bool on_top) {
if (always_on_top_ == on_top)
return;
always_on_top_ = on_top;
[controller_ updateWindowLevel];
[controller_ updateWindowCollectionBehavior];
}
void PanelCocoa::UpdatePanelMinimizeRestoreButtonVisibility() {
[controller_ updateTitleBarMinimizeRestoreButtonVisibility];
}
void PanelCocoa::SetWindowCornerStyle(panel::CornerStyle corner_style) {
corner_style_ = corner_style;
// TODO(dimich): investigate how to support it on Mac.
}
void PanelCocoa::MinimizePanelBySystem() {
[controller_ miniaturize];
}
bool PanelCocoa::IsPanelMinimizedBySystem() const {
return [controller_ isMiniaturized];
}
bool PanelCocoa::IsPanelShownOnActiveDesktop() const {
return [[controller_ window] isOnActiveSpace];
}
void PanelCocoa::ShowShadow(bool show) {
[controller_ showShadow:show];
}
void PanelCocoa::PanelExpansionStateChanging(
Panel::ExpansionState old_state, Panel::ExpansionState new_state) {
[controller_ updateWindowLevel:(new_state != Panel::EXPANDED)];
}
void PanelCocoa::AttachWebContents(content::WebContents* contents) {
[controller_ webContentsInserted:contents];
}
void PanelCocoa::DetachWebContents(content::WebContents* contents) {
[controller_ webContentsDetached:contents];
}
gfx::Size PanelCocoa::WindowSizeFromContentSize(
const gfx::Size& content_size) const {
NSRect content = NSMakeRect(0, 0,
content_size.width(), content_size.height());
NSRect frame = [controller_ frameRectForContentRect:content];
return gfx::Size(NSWidth(frame), NSHeight(frame));
}
gfx::Size PanelCocoa::ContentSizeFromWindowSize(
const gfx::Size& window_size) const {
NSRect frame = NSMakeRect(0, 0, window_size.width(), window_size.height());
NSRect content = [controller_ contentRectForFrameRect:frame];
return gfx::Size(NSWidth(content), NSHeight(content));
}
int PanelCocoa::TitleOnlyHeight() const {
return [controller_ titlebarHeightInScreenCoordinates];
}
Panel* PanelCocoa::panel() const {
return panel_.get();
}
void PanelCocoa::DidCloseNativeWindow() {
DCHECK(!IsClosed());
controller_ = NULL;
panel_->OnNativePanelClosed();
}
// NativePanelTesting implementation.
class CocoaNativePanelTesting : public NativePanelTesting {
public:
CocoaNativePanelTesting(NativePanel* native_panel);
virtual ~CocoaNativePanelTesting() { }
// Overridden from NativePanelTesting
virtual void PressLeftMouseButtonTitlebar(
const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE;
virtual void ReleaseMouseButtonTitlebar(
panel::ClickModifier modifier) OVERRIDE;
virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE;
virtual void CancelDragTitlebar() OVERRIDE;
virtual void FinishDragTitlebar() OVERRIDE;
virtual bool VerifyDrawingAttention() const OVERRIDE;
virtual bool VerifyActiveState(bool is_active) OVERRIDE;
virtual bool VerifyAppIcon() const OVERRIDE;
virtual bool VerifySystemMinimizeState() const OVERRIDE;
virtual bool IsWindowSizeKnown() const OVERRIDE;
virtual bool IsAnimatingBounds() const OVERRIDE;
virtual bool IsButtonVisible(
panel::TitlebarButtonType button_type) const OVERRIDE;
virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE;
virtual bool EnsureApplicationRunOnForeground() OVERRIDE;
private:
PanelTitlebarViewCocoa* titlebar() const;
// Weak, assumed always to outlive this test API object.
PanelCocoa* native_panel_window_;
};
NativePanelTesting* PanelCocoa::CreateNativePanelTesting() {
return new CocoaNativePanelTesting(this);
}
CocoaNativePanelTesting::CocoaNativePanelTesting(NativePanel* native_panel)
: native_panel_window_(static_cast<PanelCocoa*>(native_panel)) {
}
PanelTitlebarViewCocoa* CocoaNativePanelTesting::titlebar() const {
return [native_panel_window_->controller_ titlebarView];
}
void CocoaNativePanelTesting::PressLeftMouseButtonTitlebar(
const gfx::Point& mouse_location, panel::ClickModifier modifier) {
// Convert from platform-indepedent screen coordinates to Cocoa's screen
// coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen
// coordinates.
int modifierFlags =
(modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0);
[titlebar() pressLeftMouseButtonTitlebar:
cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location)
modifiers:modifierFlags];
}
void CocoaNativePanelTesting::ReleaseMouseButtonTitlebar(
panel::ClickModifier modifier) {
int modifierFlags =
(modifier == panel::APPLY_TO_ALL ? NSShiftKeyMask : 0);
[titlebar() releaseLeftMouseButtonTitlebar:modifierFlags];
}
void CocoaNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) {
// Convert from platform-indepedent screen coordinates to Cocoa's screen
// coordinates because PanelTitlebarViewCocoa method takes Cocoa's screen
// coordinates.
[titlebar() dragTitlebar:
cocoa_utils::ConvertPointToCocoaCoordinates(mouse_location)];
}
void CocoaNativePanelTesting::CancelDragTitlebar() {
[titlebar() cancelDragTitlebar];
}
void CocoaNativePanelTesting::FinishDragTitlebar() {
[titlebar() finishDragTitlebar];
}
bool CocoaNativePanelTesting::VerifyDrawingAttention() const {
return [titlebar() isDrawingAttention];
}
bool CocoaNativePanelTesting::VerifyActiveState(bool is_active) {
// TODO(jianli): to be implemented.
return false;
}
bool CocoaNativePanelTesting::VerifyAppIcon() const {
// Nothing to do since panel does not show dock icon.
return true;
}
bool CocoaNativePanelTesting::VerifySystemMinimizeState() const {
// TODO(jianli): to be implemented.
return true;
}
bool CocoaNativePanelTesting::IsWindowSizeKnown() const {
return true;
}
bool CocoaNativePanelTesting::IsAnimatingBounds() const {
if ([native_panel_window_->controller_ isAnimatingBounds])
return true;
StackedPanelCollection* stack = native_panel_window_->panel()->stack();
if (!stack)
return false;
return stack->IsAnimatingPanelBounds(native_panel_window_->panel());
}
bool CocoaNativePanelTesting::IsButtonVisible(
panel::TitlebarButtonType button_type) const {
switch (button_type) {
case panel::CLOSE_BUTTON:
return ![[titlebar() closeButton] isHidden];
case panel::MINIMIZE_BUTTON:
return ![[titlebar() minimizeButton] isHidden];
case panel::RESTORE_BUTTON:
return ![[titlebar() restoreButton] isHidden];
default:
NOTREACHED();
}
return false;
}
panel::CornerStyle CocoaNativePanelTesting::GetWindowCornerStyle() const {
return native_panel_window_->corner_style_;
}
bool CocoaNativePanelTesting::EnsureApplicationRunOnForeground() {
if ([NSApp isActive])
return true;
[NSApp activateIgnoringOtherApps:YES];
return [NSApp isActive];
}