blob: 86a84ad7eeca6807c529f61dd5234c6d5b16a0a4 [file] [log] [blame]
// Copyright (c) 2011 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/panels/panel_browser_view.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "chrome/browser/ui/panels/panel.h"
#include "chrome/browser/ui/panels/panel_bounds_animation.h"
#include "chrome/browser/ui/panels/panel_browser_frame_view.h"
#include "chrome/browser/ui/panels/panel_manager.h"
#include "chrome/browser/ui/views/frame/browser_frame.h"
#include "chrome/browser/ui/webui/chrome_web_ui.h"
#include "chrome/browser/ui/webui/task_manager_dialog.h"
#include "chrome/common/chrome_notification_types.h"
#include "content/public/browser/notification_service.h"
#include "grit/chromium_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/controls/label.h"
#include "ui/views/widget/widget.h"
using content::WebContents;
namespace {
// The threshold to differentiate the short click and long click.
const int kShortClickThresholdMs = 200;
// Delay before click-to-minimize is allowed after the attention has been
// cleared.
const int kSuspendMinimizeOnClickIntervalMs = 500;
}
NativePanel* Panel::CreateNativePanel(Browser* browser, Panel* panel,
const gfx::Rect& bounds) {
PanelBrowserView* view = new PanelBrowserView(browser, panel, bounds);
(new BrowserFrame(view))->InitBrowserFrame();
return view;
}
PanelBrowserView::PanelBrowserView(Browser* browser, Panel* panel,
const gfx::Rect& bounds)
: BrowserView(browser),
panel_(panel),
bounds_(bounds),
closed_(false),
focused_(false),
mouse_pressed_(false),
mouse_dragging_state_(NO_DRAGGING),
is_drawing_attention_(false),
old_focused_view_(NULL) {
}
PanelBrowserView::~PanelBrowserView() {
panel_->OnNativePanelClosed();
}
void PanelBrowserView::Init() {
if (!panel_->manager()->is_full_screen()) {
// TODO(prasadt): Implement this code.
// HideThePanel.
}
BrowserView::Init();
GetWidget()->non_client_view()->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
}
void PanelBrowserView::Show() {
if (!panel_->manager()->is_full_screen())
BrowserView::Show();
}
void PanelBrowserView::ShowInactive() {
if (!panel_->manager()->is_full_screen())
BrowserView::ShowInactive();
}
void PanelBrowserView::Close() {
GetWidget()->RemoveObserver(this);
closed_ = true;
// Cancel any currently running animation since we're closing down.
if (bounds_animator_.get())
bounds_animator_.reset();
::BrowserView::Close();
}
void PanelBrowserView::Deactivate() {
if (!IsActive())
return;
#if defined(OS_WIN) && !defined(USE_AURA)
gfx::NativeWindow native_window = NULL;
BrowserWindow* browser_window =
panel_->manager()->GetNextBrowserWindowToActivate(panel_.get());
if (browser_window)
native_window = browser_window->GetNativeHandle();
else
native_window = ::GetDesktopWindow();
if (native_window)
::SetForegroundWindow(native_window);
else
::SetFocus(NULL);
#else
NOTIMPLEMENTED();
BrowserView::Deactivate();
#endif
}
bool PanelBrowserView::CanResize() const {
return false;
}
bool PanelBrowserView::CanMaximize() const {
return false;
}
void PanelBrowserView::SetBounds(const gfx::Rect& bounds) {
SetBoundsInternal(bounds, true);
}
void PanelBrowserView::SetBoundsInternal(const gfx::Rect& new_bounds,
bool animate) {
if (bounds_ == new_bounds)
return;
bounds_ = new_bounds;
// No animation if the panel is being dragged.
if (!animate || mouse_dragging_state_ == DRAGGING_STARTED) {
::BrowserView::SetBounds(new_bounds);
return;
}
animation_start_bounds_ = GetBounds();
bounds_animator_.reset(new PanelBoundsAnimation(
this, panel(), animation_start_bounds_, new_bounds));
bounds_animator_->Start();
}
void PanelBrowserView::UpdateTitleBar() {
::BrowserView::UpdateTitleBar();
GetFrameView()->UpdateTitleBar();
}
bool PanelBrowserView::GetSavedWindowPlacement(
gfx::Rect* bounds,
ui::WindowShowState* show_state) const {
*bounds = bounds_;
*show_state = ui::SHOW_STATE_NORMAL;
return true;
}
void PanelBrowserView::OnWidgetActivationChanged(views::Widget* widget,
bool active) {
::BrowserView::OnWidgetActivationChanged(widget, active);
#if defined(OS_WIN) && !defined(USE_AURA)
// The panel window is in focus (actually accepting keystrokes) if it is
// active and belongs to a foreground application.
bool focused = active &&
GetFrameView()->GetWidget()->GetNativeView() == ::GetForegroundWindow();
#else
NOTIMPLEMENTED();
bool focused = active;
#endif
if (focused_ == focused)
return;
focused_ = focused;
GetFrameView()->OnFocusChanged(focused);
if (focused_) {
// Expand the panel if needed.
if (panel_->expansion_state() == Panel::MINIMIZED)
panel_->SetExpansionState(Panel::EXPANDED);
// Clear the attention state if needed.
if (is_drawing_attention_)
StopDrawingAttention();
}
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PANEL_CHANGED_ACTIVE_STATUS,
content::Source<Panel>(panel()),
content::NotificationService::NoDetails());
}
bool PanelBrowserView::AcceleratorPressed(
const ui::Accelerator& accelerator) {
if (mouse_pressed_ && accelerator.key_code() == ui::VKEY_ESCAPE) {
OnTitlebarMouseCaptureLost();
return true;
}
// No other accelerator is allowed when the drag begins.
if (mouse_dragging_state_ == DRAGGING_STARTED)
return true;
return BrowserView::AcceleratorPressed(accelerator);
}
void PanelBrowserView::AnimationEnded(const ui::Animation* animation) {
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
content::Source<Panel>(panel()),
content::NotificationService::NoDetails());
}
void PanelBrowserView::AnimationProgressed(const ui::Animation* animation) {
gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween(
animation_start_bounds_, bounds_);
::BrowserView::SetBounds(new_bounds);
}
void PanelBrowserView::OnDisplayChanged() {
BrowserView::OnDisplayChanged();
panel_->manager()->OnDisplayChanged();
}
void PanelBrowserView::OnWorkAreaChanged() {
BrowserView::OnWorkAreaChanged();
panel_->manager()->OnDisplayChanged();
}
bool PanelBrowserView::WillProcessWorkAreaChange() const {
return true;
}
void PanelBrowserView::ShowPanel() {
Show();
}
void PanelBrowserView::ShowPanelInactive() {
ShowInactive();
}
gfx::Rect PanelBrowserView::GetPanelBounds() const {
return bounds_;
}
void PanelBrowserView::SetPanelBounds(const gfx::Rect& bounds) {
SetBoundsInternal(bounds, true);
}
void PanelBrowserView::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
SetBoundsInternal(bounds, false);
}
void PanelBrowserView::ClosePanel() {
Close();
}
void PanelBrowserView::ActivatePanel() {
Activate();
}
void PanelBrowserView::DeactivatePanel() {
Deactivate();
}
bool PanelBrowserView::IsPanelActive() const {
return IsActive();
}
gfx::NativeWindow PanelBrowserView::GetNativePanelHandle() {
return GetNativeHandle();
}
void PanelBrowserView::UpdatePanelTitleBar() {
UpdateTitleBar();
}
void PanelBrowserView::UpdatePanelLoadingAnimations(bool should_animate) {
UpdateLoadingAnimations(should_animate);
}
void PanelBrowserView::ShowTaskManagerForPanel() {
#if defined(WEBUI_TASK_MANAGER)
TaskManagerDialog::Show();
#else
// Uses WebUI TaskManager when swiches is set. It is beta feature.
if (ChromeWebUI::IsMoreWebUI()) {
TaskManagerDialog::Show();
} else {
ShowTaskManager();
}
#endif // defined(WEBUI_TASK_MANAGER)
}
FindBar* PanelBrowserView::CreatePanelFindBar() {
return CreateFindBar();
}
void PanelBrowserView::NotifyPanelOnUserChangedTheme() {
UserChangedTheme();
}
void PanelBrowserView::PanelWebContentsFocused(WebContents* contents) {
WebContentsFocused(contents);
}
void PanelBrowserView::PanelCut() {
Cut();
}
void PanelBrowserView::PanelCopy() {
Copy();
}
void PanelBrowserView::PanelPaste() {
Paste();
}
void PanelBrowserView::DrawAttention() {
// Don't draw attention for active panel.
if (is_drawing_attention_ || focused_)
return;
is_drawing_attention_ = true;
// Bring up the titlebar to get people's attention.
if (panel_->expansion_state() == Panel::MINIMIZED)
panel_->SetExpansionState(Panel::TITLE_ONLY);
GetFrameView()->SchedulePaint();
}
bool PanelBrowserView::IsDrawingAttention() const {
return is_drawing_attention_;
}
void PanelBrowserView::StopDrawingAttention() {
if (!is_drawing_attention_)
return;
is_drawing_attention_ = false;
// This function is called from OnWidgetActivationChanged to clear the
// attention, per one of the following user interactions:
// 1) clicking on the title-bar
// 2) clicking on the client area
// 3) switching to the panel via keyboard
// For case 1, we do not want the expanded panel to be minimized since the
// user clicks on it to mean to clear the attention.
attention_cleared_time_ = base::TimeTicks::Now();
// Restore the panel.
if (panel_->expansion_state() == Panel::TITLE_ONLY)
panel_->SetExpansionState(Panel::EXPANDED);
GetFrameView()->SchedulePaint();
}
bool PanelBrowserView::PreHandlePanelKeyboardEvent(
const NativeWebKeyboardEvent& event,
bool* is_keyboard_shortcut) {
return PreHandleKeyboardEvent(event, is_keyboard_shortcut);
}
void PanelBrowserView::FullScreenModeChanged(bool is_full_screen) {
if (is_full_screen) {
if (frame()->IsVisible()) {
frame()->Hide();
}
} else {
ShowInactive();
}
}
void PanelBrowserView::HandlePanelKeyboardEvent(
const NativeWebKeyboardEvent& event) {
HandleKeyboardEvent(event);
}
gfx::Size PanelBrowserView::WindowSizeFromContentSize(
const gfx::Size& content_size) const {
gfx::Size frame = GetFrameView()->NonClientAreaSize();
return gfx::Size(content_size.width() + frame.width(),
content_size.height() + frame.height());
}
gfx::Size PanelBrowserView::ContentSizeFromWindowSize(
const gfx::Size& window_size) const {
gfx::Size frame = GetFrameView()->NonClientAreaSize();
return gfx::Size(window_size.width() - frame.width(),
window_size.height() - frame.height());
}
int PanelBrowserView::TitleOnlyHeight() const {
return GetFrameView()->NonClientTopBorderHeight();
}
Browser* PanelBrowserView::GetPanelBrowser() const {
return browser();
}
void PanelBrowserView::DestroyPanelBrowser() {
DestroyBrowser();
}
gfx::Size PanelBrowserView::IconOnlySize() const {
return GetFrameView()->IconOnlySize();
}
void PanelBrowserView::EnsurePanelFullyVisible() {
#if defined(OS_WIN) && !defined(USE_AURA)
::SetWindowPos(GetNativeHandle(), HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
#else
NOTIMPLEMENTED();
#endif
}
PanelBrowserFrameView* PanelBrowserView::GetFrameView() const {
return static_cast<PanelBrowserFrameView*>(frame()->GetFrameView());
}
bool PanelBrowserView::OnTitlebarMousePressed(const gfx::Point& location) {
// |location| is in the view's coordinate system. Convert it to the screen
// coordinate system.
mouse_location_ = location;
views::View::ConvertPointToScreen(this, &mouse_location_);
mouse_pressed_ = true;
mouse_pressed_time_ = base::TimeTicks::Now();
mouse_dragging_state_ = NO_DRAGGING;
return true;
}
bool PanelBrowserView::OnTitlebarMouseDragged(const gfx::Point& location) {
if (!mouse_pressed_)
return false;
if (!panel_->draggable())
return true;
gfx::Point last_mouse_location = mouse_location_;
// |location| is in the view's coordinate system. Convert it to the screen
// coordinate system.
mouse_location_ = location;
views::View::ConvertPointToScreen(this, &mouse_location_);
int delta_x = mouse_location_.x() - last_mouse_location.x();
int delta_y = mouse_location_.y() - last_mouse_location.y();
if (mouse_dragging_state_ == NO_DRAGGING &&
ExceededDragThreshold(delta_x, delta_y)) {
// When a drag begins, we do not want to the client area to still receive
// the focus.
old_focused_view_ = GetFocusManager()->GetFocusedView();
GetFocusManager()->SetFocusedView(GetFrameView());
panel_->manager()->StartDragging(panel_.get());
mouse_dragging_state_ = DRAGGING_STARTED;
}
if (mouse_dragging_state_ == DRAGGING_STARTED)
panel_->manager()->Drag(delta_x);
return true;
}
bool PanelBrowserView::OnTitlebarMouseReleased() {
if (mouse_dragging_state_ == DRAGGING_STARTED) {
// When a drag ends, restore the focus.
if (old_focused_view_) {
GetFocusManager()->SetFocusedView(old_focused_view_);
old_focused_view_ = NULL;
}
return EndDragging(false);
}
// If the panel drag was cancelled before the mouse is released, do not treat
// this as a click.
if (mouse_dragging_state_ != NO_DRAGGING)
return true;
// Do not minimize the panel when we just clear the attention state. This is
// a hack to prevent the panel from being minimized when the user clicks on
// the title-bar to clear the attention.
if (panel_->expansion_state() == Panel::EXPANDED &&
base::TimeTicks::Now() - attention_cleared_time_ <
base::TimeDelta::FromMilliseconds(kSuspendMinimizeOnClickIntervalMs)) {
return true;
}
// Ignore long clicks. Treated as a canceled click to be consistent with Mac.
if (base::TimeTicks::Now() - mouse_pressed_time_ >
base::TimeDelta::FromMilliseconds(kShortClickThresholdMs))
return true;
Panel::ExpansionState new_expansion_state =
(panel_->expansion_state() != Panel::EXPANDED) ? Panel::EXPANDED
: Panel::MINIMIZED;
panel_->SetExpansionState(new_expansion_state);
return true;
}
bool PanelBrowserView::OnTitlebarMouseCaptureLost() {
if (mouse_dragging_state_ == DRAGGING_STARTED)
return EndDragging(true);
return true;
}
bool PanelBrowserView::EndDragging(bool cancelled) {
// Only handle clicks that started in our window.
if (!mouse_pressed_)
return false;
mouse_pressed_ = false;
mouse_dragging_state_ = DRAGGING_ENDED;
panel_->manager()->EndDragging(cancelled);
return true;
}
void PanelBrowserView::SetPanelAppIconVisibility(bool visible) {
#if defined(OS_WIN) && !defined(USE_AURA)
gfx::NativeWindow native_window = GetNativeHandle();
::ShowWindow(native_window, SW_HIDE);
int style = ::GetWindowLong(native_window, GWL_EXSTYLE);
if (visible)
style &= (~WS_EX_TOOLWINDOW);
else
style |= WS_EX_TOOLWINDOW;
::SetWindowLong(native_window, GWL_EXSTYLE, style);
::ShowWindow(native_window, SW_SHOWNA);
#else
NOTIMPLEMENTED();
#endif
}
// NativePanelTesting implementation.
class NativePanelTestingWin : public NativePanelTesting {
public:
explicit NativePanelTestingWin(PanelBrowserView* panel_browser_view);
private:
virtual void PressLeftMouseButtonTitlebar(
const gfx::Point& point) OVERRIDE;
virtual void ReleaseMouseButtonTitlebar() OVERRIDE;
virtual void DragTitlebar(int delta_x, int delta_y) OVERRIDE;
virtual void CancelDragTitlebar() OVERRIDE;
virtual void FinishDragTitlebar() OVERRIDE;
virtual bool VerifyDrawingAttention() const OVERRIDE;
virtual bool VerifyActiveState(bool is_active) OVERRIDE;
virtual bool IsWindowSizeKnown() const OVERRIDE;
virtual bool IsAnimatingBounds() const OVERRIDE;
PanelBrowserView* panel_browser_view_;
};
// static
NativePanelTesting* NativePanelTesting::Create(NativePanel* native_panel) {
return new NativePanelTestingWin(static_cast<PanelBrowserView*>(
native_panel));
}
NativePanelTestingWin::NativePanelTestingWin(
PanelBrowserView* panel_browser_view) :
panel_browser_view_(panel_browser_view) {
PanelBrowserFrameView* frame_view = panel_browser_view_->GetFrameView();
frame_view->title_label_->SetAutoColorReadabilityEnabled(false);
}
void NativePanelTestingWin::PressLeftMouseButtonTitlebar(
const gfx::Point& point) {
panel_browser_view_->OnTitlebarMousePressed(point);
}
void NativePanelTestingWin::ReleaseMouseButtonTitlebar() {
panel_browser_view_->OnTitlebarMouseReleased();
}
void NativePanelTestingWin::DragTitlebar(int delta_x, int delta_y) {
gfx::Point new_mouse_location = panel_browser_view_->mouse_location_;
new_mouse_location.Offset(delta_x, delta_y);
// Convert from the screen coordinate system to the view's coordinate system
// since OnTitlebarMouseDragged takes the point in the latter.
views::View::ConvertPointToView(NULL, panel_browser_view_,
&new_mouse_location);
panel_browser_view_->OnTitlebarMouseDragged(new_mouse_location);
}
void NativePanelTestingWin::CancelDragTitlebar() {
panel_browser_view_->OnTitlebarMouseCaptureLost();
}
void NativePanelTestingWin::FinishDragTitlebar() {
panel_browser_view_->OnTitlebarMouseReleased();
}
bool NativePanelTestingWin::VerifyDrawingAttention() const {
PanelBrowserFrameView* frame_view = panel_browser_view_->GetFrameView();
SkColor attention_color = frame_view->GetTitleColor(
PanelBrowserFrameView::PAINT_FOR_ATTENTION);
return attention_color == frame_view->title_label_->enabled_color();
}
bool NativePanelTestingWin::VerifyActiveState(bool is_active) {
PanelBrowserFrameView* frame_view = panel_browser_view_->GetFrameView();
PanelBrowserFrameView::PaintState expected_paint_state =
is_active ? PanelBrowserFrameView::PAINT_AS_ACTIVE
: PanelBrowserFrameView::PAINT_AS_INACTIVE;
if (frame_view->paint_state_ != expected_paint_state)
return false;
SkColor expected_color = frame_view->GetTitleColor(expected_paint_state);
return expected_color == frame_view->title_label_->enabled_color();
}
bool NativePanelTestingWin::IsWindowSizeKnown() const {
return true;
}
bool NativePanelTestingWin::IsAnimatingBounds() const {
return panel_browser_view_->bounds_animator_.get() &&
panel_browser_view_->bounds_animator_->is_animating();
}