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/views/panels/panel_view.h"
      6 
      7 #include <map>
      8 #include "base/logging.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/app/chrome_command_ids.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/ui/host_desktop.h"
     14 #include "chrome/browser/ui/panels/panel.h"
     15 #include "chrome/browser/ui/panels/panel_bounds_animation.h"
     16 #include "chrome/browser/ui/panels/panel_manager.h"
     17 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
     18 #include "chrome/browser/ui/views/auto_keep_alive.h"
     19 #include "chrome/browser/ui/views/panels/panel_frame_view.h"
     20 #include "content/public/browser/render_view_host.h"
     21 #include "content/public/browser/render_widget_host_view.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "ui/aura/window.h"
     24 #include "ui/aura/window_tree_host.h"
     25 #include "ui/gfx/image/image.h"
     26 #include "ui/gfx/path.h"
     27 #include "ui/gfx/screen.h"
     28 #include "ui/views/controls/button/image_button.h"
     29 #include "ui/views/controls/webview/webview.h"
     30 #include "ui/views/widget/widget.h"
     31 
     32 #if defined(OS_WIN)
     33 #include "base/win/windows_version.h"
     34 #include "chrome/browser/shell_integration.h"
     35 #include "chrome/browser/ui/views/panels/taskbar_window_thumbnailer_win.h"
     36 #include "ui/base/win/shell.h"
     37 #include "ui/gfx/icon_util.h"
     38 #include "ui/views/win/hwnd_util.h"
     39 #endif
     40 
     41 #if defined(USE_X11) && !defined(OS_CHROMEOS)
     42 #include "chrome/browser/shell_integration_linux.h"
     43 #include "chrome/browser/ui/views/panels/x11_panel_resizer.h"
     44 #include "chrome/browser/web_applications/web_app.h"
     45 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
     46 #endif
     47 
     48 namespace {
     49 
     50 #if defined(OS_WIN)
     51 // If the height of a stacked panel shrinks below this threshold during the
     52 // user resizing, it will be treated as minimized.
     53 const int kStackedPanelHeightShrinkThresholdToBecomeMinimized =
     54     panel::kTitlebarHeight + 20;
     55 #endif
     56 
     57 // Supported accelerators.
     58 // Note: We can't use the acclerator table defined in chrome/browser/ui/views
     59 // due to checkdeps violation.
     60 struct AcceleratorMapping {
     61   ui::KeyboardCode keycode;
     62   int modifiers;
     63   int command_id;
     64 };
     65 const AcceleratorMapping kPanelAcceleratorMap[] = {
     66   { ui::VKEY_W, ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
     67   { ui::VKEY_W, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_CLOSE_WINDOW },
     68   { ui::VKEY_F4, ui::EF_ALT_DOWN, IDC_CLOSE_WINDOW },
     69   { ui::VKEY_R, ui::EF_CONTROL_DOWN, IDC_RELOAD },
     70   { ui::VKEY_F5, ui::EF_NONE, IDC_RELOAD },
     71   { ui::VKEY_R, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
     72       IDC_RELOAD_IGNORING_CACHE },
     73   { ui::VKEY_F5, ui::EF_CONTROL_DOWN, IDC_RELOAD_IGNORING_CACHE },
     74   { ui::VKEY_F5, ui::EF_SHIFT_DOWN, IDC_RELOAD_IGNORING_CACHE },
     75   { ui::VKEY_ESCAPE, ui::EF_NONE, IDC_STOP },
     76   { ui::VKEY_OEM_MINUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
     77   { ui::VKEY_SUBTRACT, ui::EF_CONTROL_DOWN, IDC_ZOOM_MINUS },
     78   { ui::VKEY_0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
     79   { ui::VKEY_NUMPAD0, ui::EF_CONTROL_DOWN, IDC_ZOOM_NORMAL },
     80   { ui::VKEY_OEM_PLUS, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
     81   { ui::VKEY_ADD, ui::EF_CONTROL_DOWN, IDC_ZOOM_PLUS },
     82   { ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, IDC_DEV_TOOLS },
     83   { ui::VKEY_J, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
     84     IDC_DEV_TOOLS_CONSOLE },
     85 };
     86 
     87 const std::map<ui::Accelerator, int>& GetAcceleratorTable() {
     88   static std::map<ui::Accelerator, int>* accelerators = NULL;
     89   if (!accelerators) {
     90     accelerators = new std::map<ui::Accelerator, int>();
     91     for (size_t i = 0; i < arraysize(kPanelAcceleratorMap); ++i) {
     92       ui::Accelerator accelerator(kPanelAcceleratorMap[i].keycode,
     93                                   kPanelAcceleratorMap[i].modifiers);
     94       (*accelerators)[accelerator] = kPanelAcceleratorMap[i].command_id;
     95     }
     96   }
     97   return *accelerators;
     98 }
     99 
    100 // NativePanelTesting implementation.
    101 class NativePanelTestingViews : public NativePanelTesting {
    102  public:
    103   explicit NativePanelTestingViews(PanelView* panel_view);
    104   virtual ~NativePanelTestingViews();
    105 
    106  private:
    107   virtual void PressLeftMouseButtonTitlebar(
    108       const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE;
    109   virtual void ReleaseMouseButtonTitlebar(
    110       panel::ClickModifier modifier) OVERRIDE;
    111   virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE;
    112   virtual void CancelDragTitlebar() OVERRIDE;
    113   virtual void FinishDragTitlebar() OVERRIDE;
    114   virtual bool VerifyDrawingAttention() const OVERRIDE;
    115   virtual bool VerifyActiveState(bool is_active) OVERRIDE;
    116   virtual bool VerifyAppIcon() const OVERRIDE;
    117   virtual bool VerifySystemMinimizeState() const OVERRIDE;
    118   virtual bool IsWindowVisible() const OVERRIDE;
    119   virtual bool IsWindowSizeKnown() const OVERRIDE;
    120   virtual bool IsAnimatingBounds() const OVERRIDE;
    121   virtual bool IsButtonVisible(
    122       panel::TitlebarButtonType button_type) const OVERRIDE;
    123   virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE;
    124   virtual bool EnsureApplicationRunOnForeground() OVERRIDE;
    125 
    126   PanelView* panel_view_;
    127 };
    128 
    129 NativePanelTestingViews::NativePanelTestingViews(PanelView* panel_view)
    130     : panel_view_(panel_view) {
    131 }
    132 
    133 NativePanelTestingViews::~NativePanelTestingViews() {
    134 }
    135 
    136 void NativePanelTestingViews::PressLeftMouseButtonTitlebar(
    137     const gfx::Point& mouse_location, panel::ClickModifier modifier) {
    138   panel_view_->OnTitlebarMousePressed(mouse_location);
    139 }
    140 
    141 void NativePanelTestingViews::ReleaseMouseButtonTitlebar(
    142     panel::ClickModifier modifier) {
    143   panel_view_->OnTitlebarMouseReleased(modifier);
    144 }
    145 
    146 void NativePanelTestingViews::DragTitlebar(const gfx::Point& mouse_location) {
    147   panel_view_->OnTitlebarMouseDragged(mouse_location);
    148 }
    149 
    150 void NativePanelTestingViews::CancelDragTitlebar() {
    151   panel_view_->OnTitlebarMouseCaptureLost();
    152 }
    153 
    154 void NativePanelTestingViews::FinishDragTitlebar() {
    155   panel_view_->OnTitlebarMouseReleased(panel::NO_MODIFIER);
    156 }
    157 
    158 bool NativePanelTestingViews::VerifyDrawingAttention() const {
    159   base::MessageLoop::current()->RunUntilIdle();
    160   return panel_view_->GetFrameView()->GetPaintState() ==
    161          PanelFrameView::PAINT_FOR_ATTENTION;
    162 }
    163 
    164 bool NativePanelTestingViews::VerifyActiveState(bool is_active) {
    165   return panel_view_->GetFrameView()->GetPaintState() ==
    166          (is_active ? PanelFrameView::PAINT_AS_ACTIVE
    167                     : PanelFrameView::PAINT_AS_INACTIVE);
    168 }
    169 
    170 bool NativePanelTestingViews::VerifyAppIcon() const {
    171 #if defined(OS_WIN)
    172   // We only care about Windows 7 and later.
    173   if (base::win::GetVersion() < base::win::VERSION_WIN7)
    174     return true;
    175 
    176   HWND native_window = views::HWNDForWidget(panel_view_->window());
    177   HICON app_icon = reinterpret_cast<HICON>(
    178       ::SendMessage(native_window, WM_GETICON, ICON_BIG, 0L));
    179   if (!app_icon)
    180     return false;
    181   scoped_ptr<SkBitmap> bitmap(IconUtil::CreateSkBitmapFromHICON(app_icon));
    182   return bitmap.get() &&
    183          bitmap->width() == panel::kPanelAppIconSize &&
    184          bitmap->height() == panel::kPanelAppIconSize;
    185 #else
    186   return true;
    187 #endif
    188 }
    189 
    190 bool NativePanelTestingViews::VerifySystemMinimizeState() const {
    191 #if defined(OS_WIN)
    192   HWND native_window = views::HWNDForWidget(panel_view_->window());
    193   WINDOWPLACEMENT placement;
    194   if (!::GetWindowPlacement(native_window, &placement))
    195     return false;
    196   if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_SHOWMINIMIZED)
    197     return true;
    198 
    199   // If the panel window has owner window, as in stacked mode, check its owner
    200   // window. Note that owner window, instead of parent window, is returned
    201   // though GWL_HWNDPARENT contains 'parent'.
    202   HWND owner_window =
    203       reinterpret_cast<HWND>(::GetWindowLongPtr(native_window,
    204                                                 GWLP_HWNDPARENT));
    205   if (!owner_window || !::GetWindowPlacement(owner_window, &placement))
    206     return false;
    207   return placement.showCmd == SW_MINIMIZE ||
    208          placement.showCmd == SW_SHOWMINIMIZED;
    209 #else
    210   return true;
    211 #endif
    212 }
    213 
    214 bool NativePanelTestingViews::IsWindowVisible() const {
    215   return panel_view_->window()->IsVisible();
    216 }
    217 
    218 bool NativePanelTestingViews::IsWindowSizeKnown() const {
    219   return true;
    220 }
    221 
    222 bool NativePanelTestingViews::IsAnimatingBounds() const {
    223   return panel_view_->IsAnimatingBounds();
    224 }
    225 
    226 bool NativePanelTestingViews::IsButtonVisible(
    227     panel::TitlebarButtonType button_type) const {
    228   PanelFrameView* frame_view = panel_view_->GetFrameView();
    229 
    230   switch (button_type) {
    231     case panel::CLOSE_BUTTON:
    232       return frame_view->close_button()->visible();
    233     case panel::MINIMIZE_BUTTON:
    234       return frame_view->minimize_button()->visible();
    235     case panel::RESTORE_BUTTON:
    236       return frame_view->restore_button()->visible();
    237     default:
    238       NOTREACHED();
    239   }
    240   return false;
    241 }
    242 
    243 panel::CornerStyle NativePanelTestingViews::GetWindowCornerStyle() const {
    244   return panel_view_->GetFrameView()->corner_style();
    245 }
    246 
    247 bool NativePanelTestingViews::EnsureApplicationRunOnForeground() {
    248   // Not needed on views.
    249   return true;
    250 }
    251 
    252 }  // namespace
    253 
    254 // static
    255 NativePanel* Panel::CreateNativePanel(Panel* panel,
    256                                       const gfx::Rect& bounds,
    257                                       bool always_on_top) {
    258   return new PanelView(panel, bounds, always_on_top);
    259 }
    260 
    261 // The panel window has to be created as always-on-top. We cannot create it
    262 // as non-always-on-top and then change it to always-on-top because Windows
    263 // system might deny making a window always-on-top if the application is not
    264 // a foreground application.
    265 PanelView::PanelView(Panel* panel, const gfx::Rect& bounds, bool always_on_top)
    266     : panel_(panel),
    267       bounds_(bounds),
    268       window_(NULL),
    269       window_closed_(false),
    270       web_view_(NULL),
    271       always_on_top_(always_on_top),
    272       focused_(false),
    273       user_resizing_(false),
    274 #if defined(OS_WIN)
    275       user_resizing_interior_stacked_panel_edge_(false),
    276 #endif
    277       mouse_pressed_(false),
    278       mouse_dragging_state_(NO_DRAGGING),
    279       is_drawing_attention_(false),
    280       force_to_paint_as_inactive_(false),
    281       old_focused_view_(NULL) {
    282   window_ = new views::Widget;
    283   views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
    284   params.delegate = this;
    285   params.remove_standard_frame = true;
    286   params.keep_on_top = always_on_top;
    287   params.visible_on_all_workspaces = always_on_top;
    288   params.bounds = bounds;
    289 
    290 #if defined(USE_X11) && !defined(OS_CHROMEOS)
    291   params.wm_class_name = web_app::GetWMClassFromAppName(panel->app_name());
    292   params.wm_class_class = shell_integration_linux::GetProgramClassName();
    293 #endif
    294 
    295   window_->Init(params);
    296   window_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM);
    297   window_->set_focus_on_creation(false);
    298   window_->AddObserver(this);
    299 
    300   // Prevent the browser process from shutting down while this window is open.
    301   keep_alive_.reset(new AutoKeepAlive(GetNativePanelWindow()));
    302 
    303   web_view_ = new views::WebView(NULL);
    304   AddChildView(web_view_);
    305 
    306   // Register accelarators supported by panels.
    307   views::FocusManager* focus_manager = GetFocusManager();
    308   const std::map<ui::Accelerator, int>& accelerator_table =
    309       GetAcceleratorTable();
    310   for (std::map<ui::Accelerator, int>::const_iterator iter =
    311            accelerator_table.begin();
    312        iter != accelerator_table.end(); ++iter) {
    313     focus_manager->RegisterAccelerator(
    314         iter->first, ui::AcceleratorManager::kNormalPriority, this);
    315   }
    316 
    317 #if defined(OS_WIN)
    318   ui::win::SetAppIdForWindow(
    319       ShellIntegration::GetAppModelIdForProfile(
    320           base::UTF8ToWide(panel->app_name()), panel->profile()->GetPath()),
    321       views::HWNDForWidget(window_));
    322   ui::win::PreventWindowFromPinning(views::HWNDForWidget(window_));
    323 #endif
    324 
    325 #if defined(USE_X11) && !defined(OS_CHROMEOS)
    326   // Swap the default non client event handler with one which handles resizes
    327   // for panels entirely within Chrome. This is needed because it is not
    328   // possible to tell when a resize performed by the window manager ends.
    329   views::DesktopWindowTreeHostX11* host =
    330       views::DesktopWindowTreeHostX11::GetHostForXID(
    331           window_->GetNativeView()->GetHost()->GetAcceleratedWidget());
    332   scoped_ptr<ui::EventHandler> resizer(
    333       new X11PanelResizer(panel_.get(), window_->GetNativeWindow()));
    334   host->SwapNonClientEventHandler(resizer.Pass());
    335 #endif
    336 }
    337 
    338 PanelView::~PanelView() {
    339 }
    340 
    341 void PanelView::ShowPanel() {
    342   ShowPanelInactive();
    343   ActivatePanel();
    344 }
    345 
    346 void PanelView::ShowPanelInactive() {
    347   if (window_->IsVisible())
    348     return;
    349   window_->ShowInactive();
    350   // No animation is used for initial creation of a panel on Win.
    351   // Signal immediately that pending actions can be performed.
    352   panel_->manager()->OnPanelAnimationEnded(panel_.get());
    353 }
    354 
    355 gfx::Rect PanelView::GetPanelBounds() const {
    356   return bounds_;
    357 }
    358 
    359 void PanelView::SetPanelBounds(const gfx::Rect& bounds) {
    360   SetBoundsInternal(bounds, true);
    361 }
    362 
    363 void PanelView::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
    364   SetBoundsInternal(bounds, false);
    365 }
    366 
    367 void PanelView::SetBoundsInternal(const gfx::Rect& new_bounds, bool animate) {
    368   if (bounds_ == new_bounds)
    369     return;
    370 
    371   bounds_ = new_bounds;
    372 
    373   if (!animate) {
    374     // If no animation is in progress, apply bounds change instantly. Otherwise,
    375     // continue the animation with new target bounds.
    376     if (!IsAnimatingBounds())
    377       SetWidgetBounds(bounds_);
    378     return;
    379   }
    380 
    381   animation_start_bounds_ = window_->GetWindowBoundsInScreen();
    382 
    383   bounds_animator_.reset(new PanelBoundsAnimation(
    384       this, panel_.get(), animation_start_bounds_, new_bounds));
    385   bounds_animator_->Start();
    386 }
    387 
    388 #if defined(OS_WIN)
    389 bool PanelView::FilterMessage(HWND hwnd,
    390                               UINT message,
    391                               WPARAM w_param,
    392                               LPARAM l_param,
    393                               LRESULT* l_result) {
    394   switch (message) {
    395     case WM_SIZING:
    396       if (w_param == WMSZ_BOTTOM)
    397         user_resizing_interior_stacked_panel_edge_ = true;
    398       break;
    399   }
    400   return false;
    401 }
    402 #endif
    403 
    404 void PanelView::AnimationEnded(const gfx::Animation* animation) {
    405   panel_->manager()->OnPanelAnimationEnded(panel_.get());
    406 }
    407 
    408 void PanelView::AnimationProgressed(const gfx::Animation* animation) {
    409   gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween(
    410       animation_start_bounds_, bounds_);
    411   SetWidgetBounds(new_bounds);
    412 }
    413 
    414 void PanelView::SetWidgetBounds(const gfx::Rect& new_bounds) {
    415 #if defined(OS_WIN)
    416   // An overlapped window is a top-level window that has a titlebar, border,
    417   // and client area. The Windows system will automatically put the shadow
    418   // around the whole window. Also the system will enforce the minimum height
    419   // (38 pixels based on observation) for the overlapped window such that it
    420   // will always has the space for the titlebar.
    421   //
    422   // On contrast, a popup window is a bare minimum window without border and
    423   // titlebar by default. It is often used for the popup menu and the window
    424   // with short life. The Windows system does not add the shadow around the
    425   // whole window though CS_DROPSHADOW class style could be passed to add the
    426   // drop shadow which is only around the right and bottom edges.
    427   //
    428   // The height of the title-only or minimized panel is smaller than the minimum
    429   // overlapped window height. If the panel still uses the overlapped window
    430   // style, Windows system will automatically increase the window height. To
    431   // work around this limitation, we temporarily change the window style to
    432   // popup when the height to set is smaller than the minimum overlapped window
    433   // height and then restore the window style to overlapped when the height
    434   // grows.
    435   static const int kMinimumOverlappedWindowHeight = 38;
    436   gfx::Rect old_bounds = GetWidget()->GetRestoredBounds();
    437   if (old_bounds.height() > kMinimumOverlappedWindowHeight &&
    438       new_bounds.height() <= kMinimumOverlappedWindowHeight) {
    439     // When the panel height shrinks below the minimum overlapped window height,
    440     // change the window style to popup such that we can show the title-only
    441     // and minimized panel without additional height being added by the system.
    442     UpdateWindowAttribute(GWL_STYLE,
    443                           WS_POPUP,
    444                           WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU,
    445                           true);
    446   } else if (old_bounds.height() <= kMinimumOverlappedWindowHeight &&
    447              new_bounds.height() > kMinimumOverlappedWindowHeight) {
    448     // Change the window style back to overlappped when the panel height grow
    449     // taller than the minimum overlapped window height.
    450     UpdateWindowAttribute(GWL_STYLE,
    451                           WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU,
    452                           WS_POPUP,
    453                           true);
    454   }
    455 #endif
    456 
    457   GetWidget()->SetBounds(new_bounds);
    458 }
    459 
    460 void PanelView::ClosePanel() {
    461   // We're already closing. Do nothing.
    462   if (window_closed_)
    463     return;
    464 
    465   if (!panel_->ShouldCloseWindow())
    466     return;
    467 
    468   // Cancel any currently running animation since we're closing down.
    469   if (bounds_animator_.get())
    470     bounds_animator_.reset();
    471 
    472   if (panel_->GetWebContents()) {
    473     // Still have web contents. Allow renderer to shut down.
    474     // When web contents are destroyed, we will be called back again.
    475     panel_->OnWindowClosing();
    476     return;
    477   }
    478 
    479   panel_->OnNativePanelClosed();
    480   if (window_)
    481     window_->Close();
    482   window_closed_ = true;
    483 }
    484 
    485 void PanelView::ActivatePanel() {
    486   window_->Activate();
    487 }
    488 
    489 void PanelView::DeactivatePanel() {
    490   if (!focused_)
    491     return;
    492 
    493 #if defined(OS_WIN)
    494   // Need custom behavior for always-on-top panels to avoid
    495   // the OS activating a minimized panel when this one is
    496   // deactivated.
    497   if (always_on_top_) {
    498     ::SetForegroundWindow(::GetDesktopWindow());
    499     return;
    500   }
    501 #endif
    502 
    503   window_->Deactivate();
    504 }
    505 
    506 bool PanelView::IsPanelActive() const {
    507   return focused_;
    508 }
    509 
    510 void PanelView::PreventActivationByOS(bool prevent_activation) {
    511 #if defined(OS_WIN)
    512   // Set the flags "NoActivate" to make sure the minimized panels do not get
    513   // activated by the OS. In addition, set "AppWindow" to make sure the
    514   // minimized panels do appear in the taskbar and Alt-Tab menu if it is not
    515   // in a stack.
    516   int value_to_change = WS_EX_NOACTIVATE;
    517   if (!panel_->stack())
    518     value_to_change |= WS_EX_APPWINDOW;
    519   if (prevent_activation)
    520     UpdateWindowAttribute(GWL_EXSTYLE, value_to_change, 0, false);
    521   else
    522     UpdateWindowAttribute(GWL_EXSTYLE, 0, value_to_change, false);
    523 #endif
    524 }
    525 
    526 gfx::NativeWindow PanelView::GetNativePanelWindow() {
    527   return window_->GetNativeWindow();
    528 }
    529 
    530 void PanelView::UpdatePanelTitleBar() {
    531   UpdateWindowTitle();
    532   UpdateWindowIcon();
    533 }
    534 
    535 void PanelView::UpdatePanelLoadingAnimations(bool should_animate) {
    536   GetFrameView()->UpdateThrobber();
    537 }
    538 
    539 void PanelView::PanelWebContentsFocused(content::WebContents* contents) {
    540   web_view_->OnWebContentsFocused(contents);
    541 }
    542 
    543 void PanelView::PanelCut() {
    544   // Nothing to do since we do not have panel-specific system menu.
    545   NOTREACHED();
    546 }
    547 
    548 void PanelView::PanelCopy() {
    549   // Nothing to do since we do not have panel-specific system menu.
    550   NOTREACHED();
    551 }
    552 
    553 void PanelView::PanelPaste() {
    554   // Nothing to do since we do not have panel-specific system menu.
    555   NOTREACHED();
    556 }
    557 
    558 void PanelView::DrawAttention(bool draw_attention) {
    559   DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0);
    560 
    561   if (is_drawing_attention_ == draw_attention)
    562     return;
    563   is_drawing_attention_ = draw_attention;
    564   GetFrameView()->SchedulePaint();
    565 
    566   if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) {
    567 #if defined(OS_WIN)
    568     // The default implementation of Widget::FlashFrame only flashes 5 times.
    569     // We need more than that.
    570     FLASHWINFO fwi;
    571     fwi.cbSize = sizeof(fwi);
    572     fwi.hwnd = views::HWNDForWidget(window_);
    573     if (draw_attention) {
    574       fwi.dwFlags = FLASHW_ALL;
    575       fwi.uCount = panel::kNumberOfTimesToFlashPanelForAttention;
    576       fwi.dwTimeout = 0;
    577     } else {
    578       // TODO(jianli): calling FlashWindowEx with FLASHW_STOP flag for the
    579       // panel window has the same problem as the stack window. However,
    580       // we cannot take the similar fix since there is no background window
    581       // to replace for the regular panel window. More investigation is needed.
    582       fwi.dwFlags = FLASHW_STOP;
    583     }
    584     ::FlashWindowEx(&fwi);
    585 #else
    586     window_->FlashFrame(draw_attention);
    587 #endif
    588   }
    589 }
    590 
    591 bool PanelView::IsDrawingAttention() const {
    592   return is_drawing_attention_;
    593 }
    594 
    595 void PanelView::HandlePanelKeyboardEvent(
    596     const content::NativeWebKeyboardEvent& event) {
    597   views::FocusManager* focus_manager = GetFocusManager();
    598   if (focus_manager->shortcut_handling_suspended())
    599     return;
    600 
    601   ui::Accelerator accelerator(
    602       static_cast<ui::KeyboardCode>(event.windowsKeyCode),
    603       content::GetModifiersFromNativeWebKeyboardEvent(event));
    604   if (event.type == blink::WebInputEvent::KeyUp)
    605     accelerator.set_type(ui::ET_KEY_RELEASED);
    606   focus_manager->ProcessAccelerator(accelerator);
    607 }
    608 
    609 void PanelView::FullScreenModeChanged(bool is_full_screen) {
    610   if (is_full_screen) {
    611     if (window_->IsVisible() && always_on_top_)
    612       window_->Hide();
    613   } else {
    614     if (!window_->IsVisible()) {
    615       ShowPanelInactive();
    616 
    617 #if defined(OS_WIN)
    618       // When hiding and showing again a top-most window that belongs to a
    619       // background application (i.e. the application is not a foreground one),
    620       // the window may loose top-most placement even though its WS_EX_TOPMOST
    621       // bit is still set. Re-issuing SetWindowsPos() returns the window to its
    622       // top-most placement.
    623       if (always_on_top_)
    624         window_->SetAlwaysOnTop(true);
    625 #endif
    626     }
    627   }
    628 }
    629 
    630 bool PanelView::IsPanelAlwaysOnTop() const {
    631   return always_on_top_;
    632 }
    633 
    634 void PanelView::SetPanelAlwaysOnTop(bool on_top) {
    635   if (always_on_top_ == on_top)
    636     return;
    637   always_on_top_ = on_top;
    638 
    639   window_->SetAlwaysOnTop(on_top);
    640   window_->SetVisibleOnAllWorkspaces(on_top);
    641   window_->non_client_view()->Layout();
    642   window_->client_view()->Layout();
    643 }
    644 
    645 void PanelView::UpdatePanelMinimizeRestoreButtonVisibility() {
    646   GetFrameView()->UpdateTitlebarMinimizeRestoreButtonVisibility();
    647 }
    648 
    649 void PanelView::SetWindowCornerStyle(panel::CornerStyle corner_style) {
    650   GetFrameView()->SetWindowCornerStyle(corner_style);
    651 }
    652 
    653 void PanelView::PanelExpansionStateChanging(Panel::ExpansionState old_state,
    654                                             Panel::ExpansionState new_state) {
    655 #if defined(OS_WIN)
    656   // Live preview is only available since Windows 7.
    657   if (base::win::GetVersion() < base::win::VERSION_WIN7)
    658     return;
    659 
    660   if (panel_->collection()->type() != PanelCollection::DOCKED)
    661     return;
    662 
    663   bool is_minimized = old_state != Panel::EXPANDED;
    664   bool will_be_minimized = new_state != Panel::EXPANDED;
    665   if (is_minimized == will_be_minimized)
    666     return;
    667 
    668   HWND native_window = views::HWNDForWidget(window_);
    669 
    670   if (!thumbnailer_.get()) {
    671     DCHECK(native_window);
    672     thumbnailer_.reset(new TaskbarWindowThumbnailerWin(native_window, NULL));
    673   }
    674 
    675   // Cache the image at this point.
    676   if (will_be_minimized) {
    677     // If the panel is still active (we will deactivate the minimizd panel at
    678     // later time), we need to paint it immediately as inactive so that we can
    679     // take a snapshot of inactive panel.
    680     if (focused_) {
    681       force_to_paint_as_inactive_ = true;
    682       ::RedrawWindow(native_window, NULL, NULL,
    683                      RDW_NOCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW);
    684     }
    685 
    686     // Start the thumbnailer and capture the snapshot now.
    687     thumbnailer_->Start();
    688     thumbnailer_->CaptureSnapshot();
    689   } else {
    690     force_to_paint_as_inactive_ = false;
    691     thumbnailer_->Stop();
    692   }
    693 
    694 #endif
    695 }
    696 
    697 gfx::Size PanelView::WindowSizeFromContentSize(
    698     const gfx::Size& content_size) const {
    699   gfx::Size frame = GetFrameView()->NonClientAreaSize();
    700   return gfx::Size(content_size.width() + frame.width(),
    701                    content_size.height() + frame.height());
    702 }
    703 
    704 gfx::Size PanelView::ContentSizeFromWindowSize(
    705     const gfx::Size& window_size) const {
    706   gfx::Size frame = GetFrameView()->NonClientAreaSize();
    707   return gfx::Size(window_size.width() - frame.width(),
    708                    window_size.height() - frame.height());
    709 }
    710 
    711 int PanelView::TitleOnlyHeight() const {
    712   return panel::kTitlebarHeight;
    713 }
    714 
    715 void PanelView::MinimizePanelBySystem() {
    716   window_->Minimize();
    717 }
    718 
    719 bool PanelView::IsPanelMinimizedBySystem() const {
    720   return window_->IsMinimized();
    721 }
    722 
    723 bool PanelView::IsPanelShownOnActiveDesktop() const {
    724 #if defined(OS_WIN)
    725   // Virtual desktop is not supported by the native Windows system.
    726   return true;
    727 #else
    728   NOTIMPLEMENTED();
    729   return true;
    730 #endif
    731 }
    732 
    733 void PanelView::ShowShadow(bool show) {
    734 #if defined(OS_WIN)
    735   // The overlapped window has the shadow while the popup window does not have
    736   // the shadow.
    737   int overlap_style = WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU;
    738   int popup_style = WS_POPUP;
    739   UpdateWindowAttribute(GWL_STYLE,
    740                         show ? overlap_style : popup_style,
    741                         show ? popup_style : overlap_style,
    742                         true);
    743 #endif
    744 }
    745 
    746 void PanelView::AttachWebContents(content::WebContents* contents) {
    747   web_view_->SetWebContents(contents);
    748 }
    749 
    750 void PanelView::DetachWebContents(content::WebContents* contents) {
    751   web_view_->SetWebContents(NULL);
    752 }
    753 
    754 NativePanelTesting* PanelView::CreateNativePanelTesting() {
    755   return new NativePanelTestingViews(this);
    756 }
    757 
    758 void PanelView::OnDisplayChanged() {
    759   panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged();
    760 }
    761 
    762 void PanelView::OnWorkAreaChanged() {
    763   panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged();
    764 }
    765 
    766 bool PanelView::WillProcessWorkAreaChange() const {
    767   return true;
    768 }
    769 
    770 views::View* PanelView::GetContentsView() {
    771   return this;
    772 }
    773 
    774 views::NonClientFrameView* PanelView::CreateNonClientFrameView(
    775     views::Widget* widget) {
    776   PanelFrameView* frame_view = new PanelFrameView(this);
    777   frame_view->Init();
    778   return frame_view;
    779 }
    780 
    781 bool PanelView::CanResize() const {
    782   return true;
    783 }
    784 
    785 bool PanelView::CanMaximize() const {
    786   return false;
    787 }
    788 
    789 bool PanelView::CanMinimize() const {
    790   return false;
    791 }
    792 
    793 base::string16 PanelView::GetWindowTitle() const {
    794   return panel_->GetWindowTitle();
    795 }
    796 
    797 gfx::ImageSkia PanelView::GetWindowAppIcon() {
    798   gfx::Image app_icon = panel_->app_icon();
    799   if (app_icon.IsEmpty())
    800     return GetWindowIcon();
    801   else
    802     return *app_icon.ToImageSkia();
    803 }
    804 
    805 gfx::ImageSkia PanelView::GetWindowIcon() {
    806   gfx::Image icon = panel_->GetCurrentPageIcon();
    807   return icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia();
    808 }
    809 
    810 void PanelView::WindowClosing() {
    811   // When closing a panel via window.close, API or the close button,
    812   // ClosePanel() is called first, destroying the native |window_|
    813   // which results in this method being called. ClosePanel() sets
    814   // |window_closed_| to NULL.
    815   // If we still have a |window_closed_| here, the close was triggered by the
    816   // OS, (e.g. clicking on taskbar menu), which destroys the native |window_|
    817   // without invoking ClosePanel() beforehand.
    818   if (!window_closed_) {
    819     panel_->OnWindowClosing();
    820     ClosePanel();
    821     DCHECK(window_closed_);
    822   }
    823 }
    824 
    825 void PanelView::DeleteDelegate() {
    826   delete this;
    827 }
    828 
    829 void PanelView::OnWindowBeginUserBoundsChange() {
    830   user_resizing_ = true;
    831   panel_->OnPanelStartUserResizing();
    832 
    833 #if defined(OS_WIN)
    834   StackedPanelCollection* stack = panel_->stack();
    835   if (stack) {
    836     // Listen to WM_SIZING message in order to find out whether the interior
    837     // edge is being resized such that the specific maximum size could be
    838     // passed to the system.
    839     if (panel_->stack()->GetPanelBelow(panel_.get())) {
    840       ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window_), this);
    841       user_resizing_interior_stacked_panel_edge_ = false;
    842     }
    843 
    844     // Keep track of the original full size of the resizing panel such that it
    845     // can be restored to this size once it is shrunk to minimized state.
    846     original_full_size_of_resizing_panel_ = panel_->full_size();
    847 
    848     // Keep track of the original full size of the panel below the resizing
    849     // panel such that it can be restored to this size once it is shrunk to
    850     // minimized state.
    851     Panel* below_panel = stack->GetPanelBelow(panel_.get());
    852     if (below_panel && !below_panel->IsMinimized()) {
    853       original_full_size_of_panel_below_resizing_panel_ =
    854           below_panel->full_size();
    855     }
    856   }
    857 #endif
    858 }
    859 
    860 void PanelView::OnWindowEndUserBoundsChange() {
    861   user_resizing_ = false;
    862   panel_->OnPanelEndUserResizing();
    863 
    864   // No need to proceed with post-resizing update when there is no size change.
    865   gfx::Rect new_bounds = window_->GetWindowBoundsInScreen();
    866   if (bounds_ == new_bounds)
    867     return;
    868   bounds_ = new_bounds;
    869 
    870   panel_->IncreaseMaxSize(bounds_.size());
    871   panel_->set_full_size(bounds_.size());
    872 
    873 #if defined(OS_WIN)
    874   StackedPanelCollection* stack = panel_->stack();
    875   if (stack) {
    876     // No need to listen to WM_SIZING message any more.
    877     ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
    878 
    879     // If the height of resizing panel shrinks close to the titlebar height,
    880     // treate it as minimized. This could occur when the user is dragging
    881     // 1) the top edge of the top panel downward to shrink it; or
    882     // 2) the bottom edge of any panel upward to shrink it.
    883     if (panel_->GetBounds().height() <
    884             kStackedPanelHeightShrinkThresholdToBecomeMinimized) {
    885       stack->MinimizePanel(panel_.get());
    886       panel_->set_full_size(original_full_size_of_resizing_panel_);
    887     }
    888 
    889     // If the height of panel below the resizing panel shrinks close to the
    890     // titlebar height, treat it as minimized. This could occur when the user
    891     // is dragging the bottom edge of non-bottom panel downward to expand it
    892     // and also shrink the panel below.
    893     Panel* below_panel = stack->GetPanelBelow(panel_.get());
    894     if (below_panel && !below_panel->IsMinimized() &&
    895         below_panel->GetBounds().height() <
    896             kStackedPanelHeightShrinkThresholdToBecomeMinimized) {
    897       stack->MinimizePanel(below_panel);
    898       below_panel->set_full_size(
    899           original_full_size_of_panel_below_resizing_panel_);
    900     }
    901   }
    902 #endif
    903 
    904   panel_->collection()->RefreshLayout();
    905 }
    906 
    907 views::Widget* PanelView::GetWidget() {
    908   return window_;
    909 }
    910 
    911 const views::Widget* PanelView::GetWidget() const {
    912   return window_;
    913 }
    914 
    915 void PanelView::UpdateLoadingAnimations(bool should_animate) {
    916   GetFrameView()->UpdateThrobber();
    917 }
    918 
    919 void PanelView::UpdateWindowTitle() {
    920   window_->UpdateWindowTitle();
    921   GetFrameView()->UpdateTitle();
    922 }
    923 
    924 void PanelView::UpdateWindowIcon() {
    925   window_->UpdateWindowIcon();
    926   GetFrameView()->UpdateIcon();
    927 }
    928 
    929 void PanelView::Layout() {
    930   // |web_view_| might not be created yet when the window is first created.
    931   if (web_view_)
    932     web_view_->SetBounds(0, 0, width(), height());
    933 }
    934 
    935 gfx::Size PanelView::GetMinimumSize() const {
    936   // If the panel is minimized, it can be rendered to very small size, like
    937   // 4-pixel lines when it is docked. Otherwise, its size should not be less
    938   // than its minimum size.
    939   return panel_->IsMinimized() ? gfx::Size() :
    940       gfx::Size(panel::kPanelMinWidth, panel::kPanelMinHeight);
    941 }
    942 
    943 gfx::Size PanelView::GetMaximumSize() const {
    944   // If the user is resizing a stacked panel by its bottom edge, make sure its
    945   // height cannot grow more than what the panel below it could offer. This is
    946   // because growing a stacked panel by y amount will shrink the panel below it
    947   // by same amount and we do not want the panel below it being shrunk to be
    948   // smaller than the titlebar.
    949 #if defined(OS_WIN)
    950   if (panel_->stack() && user_resizing_interior_stacked_panel_edge_) {
    951     Panel* below_panel = panel_->stack()->GetPanelBelow(panel_.get());
    952     if (below_panel && !below_panel->IsMinimized()) {
    953       return gfx::Size(0, below_panel->GetBounds().bottom() -
    954           panel_->GetBounds().y() - panel::kTitlebarHeight);
    955     }
    956   }
    957 #endif
    958   return gfx::Size();
    959 }
    960 
    961 bool PanelView::AcceleratorPressed(const ui::Accelerator& accelerator) {
    962   if (mouse_pressed_ && accelerator.key_code() == ui::VKEY_ESCAPE) {
    963     OnTitlebarMouseCaptureLost();
    964     return true;
    965   }
    966 
    967   // No other accelerator is allowed when the drag begins.
    968   if (mouse_dragging_state_ == DRAGGING_STARTED)
    969     return true;
    970 
    971   const std::map<ui::Accelerator, int>& accelerator_table =
    972       GetAcceleratorTable();
    973   std::map<ui::Accelerator, int>::const_iterator iter =
    974       accelerator_table.find(accelerator);
    975   DCHECK(iter != accelerator_table.end());
    976   return panel_->ExecuteCommandIfEnabled(iter->second);
    977 }
    978 
    979 void PanelView::OnWidgetDestroying(views::Widget* widget) {
    980   window_ = NULL;
    981 }
    982 
    983 void PanelView::OnWidgetActivationChanged(views::Widget* widget, bool active) {
    984 #if defined(OS_WIN)
    985   // WM_NCACTIVATED could be sent when an active window is being destroyed on
    986   // Windows. We need to guard against this.
    987   if (window_closed_)
    988     return;
    989 
    990   bool focused = active;
    991   if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_NATIVE) {
    992     // The panel window is in focus (actually accepting keystrokes) if it is
    993     // active and belongs to a foreground application.
    994     focused = active &&
    995         views::HWNDForWidget(widget) == ::GetForegroundWindow();
    996   }
    997 #else
    998   bool focused = active;
    999 #endif
   1000 
   1001   if (focused_ == focused)
   1002     return;
   1003   focused_ = focused;
   1004 
   1005   // Expand the panel if the minimized panel is activated by means other than
   1006   // clicking on its titlebar. This is the workaround to support restoring the
   1007   // minimized panel by other means, like alt-tabbing, win-tabbing, or clicking
   1008   // the taskbar icon. Note that this workaround does not work for one edge
   1009   // case: the mouse happens to be at the minimized panel when the user tries to
   1010   // bring up the panel with the above alternatives.
   1011   // When the user clicks on the minimized panel, the panel expansion will be
   1012   // done when we process the mouse button pressed message.
   1013 #if defined(OS_WIN)
   1014   if (focused_ && panel_->IsMinimized() &&
   1015       panel_->collection()->type() == PanelCollection::DOCKED &&
   1016       gfx::Screen::GetScreenFor(widget->GetNativeWindow())->
   1017           GetWindowUnderCursor() != widget->GetNativeWindow()) {
   1018     panel_->Restore();
   1019   }
   1020 #endif
   1021 
   1022   panel()->OnActiveStateChanged(focused);
   1023 
   1024    // Give web contents view a chance to set focus to the appropriate element.
   1025   if (focused_) {
   1026     content::WebContents* web_contents = panel_->GetWebContents();
   1027     if (web_contents)
   1028       web_contents->RestoreFocus();
   1029   }
   1030 }
   1031 
   1032 void PanelView::OnWidgetBoundsChanged(views::Widget* widget,
   1033                                       const gfx::Rect& new_bounds) {
   1034   if (user_resizing_)
   1035     panel()->collection()->OnPanelResizedByMouse(panel(), new_bounds);
   1036 }
   1037 
   1038 bool PanelView::OnTitlebarMousePressed(const gfx::Point& mouse_location) {
   1039   mouse_pressed_ = true;
   1040   mouse_dragging_state_ = NO_DRAGGING;
   1041   last_mouse_location_ = mouse_location;
   1042   return true;
   1043 }
   1044 
   1045 bool PanelView::OnTitlebarMouseDragged(const gfx::Point& mouse_location) {
   1046   if (!mouse_pressed_)
   1047     return false;
   1048 
   1049   if (mouse_dragging_state_ == NO_DRAGGING &&
   1050       ExceededDragThreshold(mouse_location - last_mouse_location_)) {
   1051     // When a drag begins, we do not want to the client area to still receive
   1052     // the focus. We do not need to do this for the unfocused minimized panel.
   1053     if (!panel_->IsMinimized()) {
   1054       old_focused_view_ = GetFocusManager()->GetFocusedView();
   1055       GetFocusManager()->SetFocusedView(GetFrameView());
   1056     }
   1057 
   1058     panel_->manager()->StartDragging(panel_.get(), last_mouse_location_);
   1059     mouse_dragging_state_ = DRAGGING_STARTED;
   1060   }
   1061   if (mouse_dragging_state_ == DRAGGING_STARTED) {
   1062     panel_->manager()->Drag(mouse_location);
   1063 
   1064     // Once in drag, update |last_mouse_location_| on each drag fragment, since
   1065     // we already dragged the panel up to the current mouse location.
   1066     last_mouse_location_ = mouse_location;
   1067   }
   1068   return true;
   1069 }
   1070 
   1071 bool PanelView::OnTitlebarMouseReleased(panel::ClickModifier modifier) {
   1072   if (mouse_dragging_state_ != NO_DRAGGING) {
   1073     // Ensure dragging a minimized panel does not leave it activated.
   1074     // Windows activates a panel on mouse-down, regardless of our attempts
   1075     // to prevent activation of a minimized panel. Now that we know mouse-down
   1076     // resulted in a mouse-drag, we need to ensure the minimized panel is
   1077     // deactivated.
   1078     if (panel_->IsMinimized() && focused_)
   1079       panel_->Deactivate();
   1080 
   1081     if (mouse_dragging_state_ == DRAGGING_STARTED) {
   1082       // When a drag ends, restore the focus.
   1083       if (old_focused_view_) {
   1084         GetFocusManager()->SetFocusedView(old_focused_view_);
   1085         old_focused_view_ = NULL;
   1086       }
   1087       return EndDragging(false);
   1088     }
   1089 
   1090     // The panel drag was cancelled before the mouse is released. Do not
   1091     // treat this as a click.
   1092     return true;
   1093   }
   1094 
   1095   panel_->OnTitlebarClicked(modifier);
   1096   return true;
   1097 }
   1098 
   1099 bool PanelView::OnTitlebarMouseCaptureLost() {
   1100   if (mouse_dragging_state_ == DRAGGING_STARTED)
   1101     return EndDragging(true);
   1102   return true;
   1103 }
   1104 
   1105 bool PanelView::EndDragging(bool cancelled) {
   1106   // Only handle clicks that started in our window.
   1107   if (!mouse_pressed_)
   1108     return false;
   1109   mouse_pressed_ = false;
   1110 
   1111   mouse_dragging_state_ = DRAGGING_ENDED;
   1112   panel_->manager()->EndDragging(cancelled);
   1113   return true;
   1114 }
   1115 
   1116 PanelFrameView* PanelView::GetFrameView() const {
   1117   return static_cast<PanelFrameView*>(window_->non_client_view()->frame_view());
   1118 }
   1119 
   1120 bool PanelView::IsAnimatingBounds() const {
   1121   if (bounds_animator_.get() && bounds_animator_->is_animating())
   1122     return true;
   1123   StackedPanelCollection* stack = panel_->stack();
   1124   if (!stack)
   1125     return false;
   1126   return stack->IsAnimatingPanelBounds(panel_.get());
   1127 }
   1128 
   1129 #if defined(OS_WIN)
   1130 void PanelView::UpdateWindowAttribute(int attribute_index,
   1131                                       int attribute_value_to_set,
   1132                                       int attribute_value_to_reset,
   1133                                       bool update_frame) {
   1134   HWND native_window = views::HWNDForWidget(window_);
   1135   int value = ::GetWindowLong(native_window, attribute_index);
   1136   int expected_value = value;
   1137   if (attribute_value_to_set)
   1138     expected_value |=  attribute_value_to_set;
   1139   if (attribute_value_to_reset)
   1140     expected_value &=  ~attribute_value_to_reset;
   1141   if (value != expected_value)
   1142     ::SetWindowLong(native_window, attribute_index, expected_value);
   1143 
   1144   // Per MSDN, if any of the frame styles is changed, SetWindowPos with the
   1145   // SWP_FRAMECHANGED flag must be called in order for the cached window data
   1146   // to be updated properly.
   1147   // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx
   1148   if (update_frame) {
   1149     ::SetWindowPos(native_window, NULL, 0, 0, 0, 0,
   1150                    SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
   1151                        SWP_NOZORDER | SWP_NOACTIVATE);
   1152   }
   1153 }
   1154 #endif
   1155