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.
5 #include "chrome/browser/ui/views/panels/panel_view.h"
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"
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"
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"
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;
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;
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 },
87 const std::map<ui::Accelerator, int>& GetAcceleratorTable() {
88 static std::map<ui::Accelerator, int>* accelerators = NULL;
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;
100 // NativePanelTesting implementation.
101 class NativePanelTestingViews : public NativePanelTesting {
103 explicit NativePanelTestingViews(PanelView* panel_view);
104 ~NativePanelTestingViews() override;
107 void PressLeftMouseButtonTitlebar(const gfx::Point& mouse_location,
108 panel::ClickModifier modifier) override;
109 void ReleaseMouseButtonTitlebar(panel::ClickModifier modifier) override;
110 void DragTitlebar(const gfx::Point& mouse_location) override;
111 void CancelDragTitlebar() override;
112 void FinishDragTitlebar() override;
113 bool VerifyDrawingAttention() const override;
114 bool VerifyActiveState(bool is_active) override;
115 bool VerifyAppIcon() const override;
116 bool VerifySystemMinimizeState() const override;
117 bool IsWindowVisible() const override;
118 bool IsWindowSizeKnown() const override;
119 bool IsAnimatingBounds() const override;
120 bool IsButtonVisible(panel::TitlebarButtonType button_type) const override;
121 panel::CornerStyle GetWindowCornerStyle() const override;
122 bool EnsureApplicationRunOnForeground() override;
124 PanelView* panel_view_;
127 NativePanelTestingViews::NativePanelTestingViews(PanelView* panel_view)
128 : panel_view_(panel_view) {
131 NativePanelTestingViews::~NativePanelTestingViews() {
134 void NativePanelTestingViews::PressLeftMouseButtonTitlebar(
135 const gfx::Point& mouse_location, panel::ClickModifier modifier) {
136 panel_view_->OnTitlebarMousePressed(mouse_location);
139 void NativePanelTestingViews::ReleaseMouseButtonTitlebar(
140 panel::ClickModifier modifier) {
141 panel_view_->OnTitlebarMouseReleased(modifier);
144 void NativePanelTestingViews::DragTitlebar(const gfx::Point& mouse_location) {
145 panel_view_->OnTitlebarMouseDragged(mouse_location);
148 void NativePanelTestingViews::CancelDragTitlebar() {
149 panel_view_->OnTitlebarMouseCaptureLost();
152 void NativePanelTestingViews::FinishDragTitlebar() {
153 panel_view_->OnTitlebarMouseReleased(panel::NO_MODIFIER);
156 bool NativePanelTestingViews::VerifyDrawingAttention() const {
157 base::MessageLoop::current()->RunUntilIdle();
158 return panel_view_->GetFrameView()->GetPaintState() ==
159 PanelFrameView::PAINT_FOR_ATTENTION;
162 bool NativePanelTestingViews::VerifyActiveState(bool is_active) {
163 return panel_view_->GetFrameView()->GetPaintState() ==
164 (is_active ? PanelFrameView::PAINT_AS_ACTIVE
165 : PanelFrameView::PAINT_AS_INACTIVE);
168 bool NativePanelTestingViews::VerifyAppIcon() const {
170 // We only care about Windows 7 and later.
171 if (base::win::GetVersion() < base::win::VERSION_WIN7)
174 HWND native_window = views::HWNDForWidget(panel_view_->window());
175 HICON app_icon = reinterpret_cast<HICON>(
176 ::SendMessage(native_window, WM_GETICON, ICON_BIG, 0L));
179 scoped_ptr<SkBitmap> bitmap(IconUtil::CreateSkBitmapFromHICON(app_icon));
180 return bitmap.get() &&
181 bitmap->width() == panel::kPanelAppIconSize &&
182 bitmap->height() == panel::kPanelAppIconSize;
188 bool NativePanelTestingViews::VerifySystemMinimizeState() const {
190 HWND native_window = views::HWNDForWidget(panel_view_->window());
191 WINDOWPLACEMENT placement;
192 if (!::GetWindowPlacement(native_window, &placement))
194 if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_SHOWMINIMIZED)
197 // If the panel window has owner window, as in stacked mode, check its owner
198 // window. Note that owner window, instead of parent window, is returned
199 // though GWL_HWNDPARENT contains 'parent'.
201 reinterpret_cast<HWND>(::GetWindowLongPtr(native_window,
203 if (!owner_window || !::GetWindowPlacement(owner_window, &placement))
205 return placement.showCmd == SW_MINIMIZE ||
206 placement.showCmd == SW_SHOWMINIMIZED;
212 bool NativePanelTestingViews::IsWindowVisible() const {
213 return panel_view_->window()->IsVisible();
216 bool NativePanelTestingViews::IsWindowSizeKnown() const {
220 bool NativePanelTestingViews::IsAnimatingBounds() const {
221 return panel_view_->IsAnimatingBounds();
224 bool NativePanelTestingViews::IsButtonVisible(
225 panel::TitlebarButtonType button_type) const {
226 PanelFrameView* frame_view = panel_view_->GetFrameView();
228 switch (button_type) {
229 case panel::CLOSE_BUTTON:
230 return frame_view->close_button()->visible();
231 case panel::MINIMIZE_BUTTON:
232 return frame_view->minimize_button()->visible();
233 case panel::RESTORE_BUTTON:
234 return frame_view->restore_button()->visible();
241 panel::CornerStyle NativePanelTestingViews::GetWindowCornerStyle() const {
242 return panel_view_->GetFrameView()->corner_style();
245 bool NativePanelTestingViews::EnsureApplicationRunOnForeground() {
246 // Not needed on views.
253 NativePanel* Panel::CreateNativePanel(Panel* panel,
254 const gfx::Rect& bounds,
255 bool always_on_top) {
256 return new PanelView(panel, bounds, always_on_top);
259 // The panel window has to be created as always-on-top. We cannot create it
260 // as non-always-on-top and then change it to always-on-top because Windows
261 // system might deny making a window always-on-top if the application is not
262 // a foreground application.
263 PanelView::PanelView(Panel* panel, const gfx::Rect& bounds, bool always_on_top)
267 window_closed_(false),
269 always_on_top_(always_on_top),
271 user_resizing_(false),
273 user_resizing_interior_stacked_panel_edge_(false),
275 mouse_pressed_(false),
276 mouse_dragging_state_(NO_DRAGGING),
277 is_drawing_attention_(false),
278 force_to_paint_as_inactive_(false),
279 old_focused_view_(NULL) {
280 window_ = new views::Widget;
281 views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
282 params.delegate = this;
283 params.remove_standard_frame = true;
284 params.keep_on_top = always_on_top;
285 params.visible_on_all_workspaces = always_on_top;
286 params.bounds = bounds;
288 #if defined(USE_X11) && !defined(OS_CHROMEOS)
289 params.wm_class_name = web_app::GetWMClassFromAppName(panel->app_name());
290 params.wm_class_class = shell_integration_linux::GetProgramClassName();
293 window_->Init(params);
294 window_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM);
295 window_->set_focus_on_creation(false);
296 window_->AddObserver(this);
298 // Prevent the browser process from shutting down while this window is open.
299 keep_alive_.reset(new AutoKeepAlive(GetNativePanelWindow()));
301 web_view_ = new views::WebView(NULL);
302 AddChildView(web_view_);
304 // Register accelarators supported by panels.
305 views::FocusManager* focus_manager = GetFocusManager();
306 const std::map<ui::Accelerator, int>& accelerator_table =
307 GetAcceleratorTable();
308 for (std::map<ui::Accelerator, int>::const_iterator iter =
309 accelerator_table.begin();
310 iter != accelerator_table.end(); ++iter) {
311 focus_manager->RegisterAccelerator(
312 iter->first, ui::AcceleratorManager::kNormalPriority, this);
316 ui::win::SetAppIdForWindow(
317 ShellIntegration::GetAppModelIdForProfile(
318 base::UTF8ToWide(panel->app_name()), panel->profile()->GetPath()),
319 views::HWNDForWidget(window_));
320 ui::win::PreventWindowFromPinning(views::HWNDForWidget(window_));
323 #if defined(USE_X11) && !defined(OS_CHROMEOS)
324 // Swap the default non client event handler with one which handles resizes
325 // for panels entirely within Chrome. This is needed because it is not
326 // possible to tell when a resize performed by the window manager ends.
327 views::DesktopWindowTreeHostX11* host =
328 views::DesktopWindowTreeHostX11::GetHostForXID(
329 window_->GetNativeView()->GetHost()->GetAcceleratedWidget());
330 scoped_ptr<ui::EventHandler> resizer(
331 new X11PanelResizer(panel_.get(), window_->GetNativeWindow()));
332 host->SwapNonClientEventHandler(resizer.Pass());
336 PanelView::~PanelView() {
339 void PanelView::ShowPanel() {
344 void PanelView::ShowPanelInactive() {
345 if (window_->IsVisible())
347 window_->ShowInactive();
348 // No animation is used for initial creation of a panel on Win.
349 // Signal immediately that pending actions can be performed.
350 panel_->manager()->OnPanelAnimationEnded(panel_.get());
353 gfx::Rect PanelView::GetPanelBounds() const {
357 void PanelView::SetPanelBounds(const gfx::Rect& bounds) {
358 SetBoundsInternal(bounds, true);
361 void PanelView::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
362 SetBoundsInternal(bounds, false);
365 void PanelView::SetBoundsInternal(const gfx::Rect& new_bounds, bool animate) {
366 if (bounds_ == new_bounds)
369 bounds_ = new_bounds;
372 // If no animation is in progress, apply bounds change instantly. Otherwise,
373 // continue the animation with new target bounds.
374 if (!IsAnimatingBounds())
375 SetWidgetBounds(bounds_);
379 animation_start_bounds_ = window_->GetWindowBoundsInScreen();
381 bounds_animator_.reset(new PanelBoundsAnimation(
382 this, panel_.get(), animation_start_bounds_, new_bounds));
383 bounds_animator_->Start();
387 bool PanelView::FilterMessage(HWND hwnd,
394 if (w_param == WMSZ_BOTTOM)
395 user_resizing_interior_stacked_panel_edge_ = true;
402 void PanelView::AnimationEnded(const gfx::Animation* animation) {
403 panel_->manager()->OnPanelAnimationEnded(panel_.get());
406 void PanelView::AnimationProgressed(const gfx::Animation* animation) {
407 gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween(
408 animation_start_bounds_, bounds_);
409 SetWidgetBounds(new_bounds);
412 void PanelView::SetWidgetBounds(const gfx::Rect& new_bounds) {
414 // An overlapped window is a top-level window that has a titlebar, border,
415 // and client area. The Windows system will automatically put the shadow
416 // around the whole window. Also the system will enforce the minimum height
417 // (38 pixels based on observation) for the overlapped window such that it
418 // will always has the space for the titlebar.
420 // On contrast, a popup window is a bare minimum window without border and
421 // titlebar by default. It is often used for the popup menu and the window
422 // with short life. The Windows system does not add the shadow around the
423 // whole window though CS_DROPSHADOW class style could be passed to add the
424 // drop shadow which is only around the right and bottom edges.
426 // The height of the title-only or minimized panel is smaller than the minimum
427 // overlapped window height. If the panel still uses the overlapped window
428 // style, Windows system will automatically increase the window height. To
429 // work around this limitation, we temporarily change the window style to
430 // popup when the height to set is smaller than the minimum overlapped window
431 // height and then restore the window style to overlapped when the height
433 static const int kMinimumOverlappedWindowHeight = 38;
434 gfx::Rect old_bounds = GetWidget()->GetRestoredBounds();
435 if (old_bounds.height() > kMinimumOverlappedWindowHeight &&
436 new_bounds.height() <= kMinimumOverlappedWindowHeight) {
437 // When the panel height shrinks below the minimum overlapped window height,
438 // change the window style to popup such that we can show the title-only
439 // and minimized panel without additional height being added by the system.
440 UpdateWindowAttribute(GWL_STYLE,
442 WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU,
444 } else if (old_bounds.height() <= kMinimumOverlappedWindowHeight &&
445 new_bounds.height() > kMinimumOverlappedWindowHeight) {
446 // Change the window style back to overlappped when the panel height grow
447 // taller than the minimum overlapped window height.
448 UpdateWindowAttribute(GWL_STYLE,
449 WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU,
455 GetWidget()->SetBounds(new_bounds);
458 void PanelView::ClosePanel() {
459 // We're already closing. Do nothing.
463 if (!panel_->ShouldCloseWindow())
466 // Cancel any currently running animation since we're closing down.
467 if (bounds_animator_.get())
468 bounds_animator_.reset();
470 if (panel_->GetWebContents()) {
471 // Still have web contents. Allow renderer to shut down.
472 // When web contents are destroyed, we will be called back again.
473 panel_->OnWindowClosing();
477 panel_->OnNativePanelClosed();
480 window_closed_ = true;
483 void PanelView::ActivatePanel() {
487 void PanelView::DeactivatePanel() {
492 // Need custom behavior for always-on-top panels to avoid
493 // the OS activating a minimized panel when this one is
495 if (always_on_top_) {
496 ::SetForegroundWindow(::GetDesktopWindow());
501 window_->Deactivate();
504 bool PanelView::IsPanelActive() const {
508 void PanelView::PreventActivationByOS(bool prevent_activation) {
510 // Set the flags "NoActivate" to make sure the minimized panels do not get
511 // activated by the OS. In addition, set "AppWindow" to make sure the
512 // minimized panels do appear in the taskbar and Alt-Tab menu if it is not
514 int value_to_change = WS_EX_NOACTIVATE;
515 if (!panel_->stack())
516 value_to_change |= WS_EX_APPWINDOW;
517 if (prevent_activation)
518 UpdateWindowAttribute(GWL_EXSTYLE, value_to_change, 0, false);
520 UpdateWindowAttribute(GWL_EXSTYLE, 0, value_to_change, false);
524 gfx::NativeWindow PanelView::GetNativePanelWindow() {
525 return window_->GetNativeWindow();
528 void PanelView::UpdatePanelTitleBar() {
533 void PanelView::UpdatePanelLoadingAnimations(bool should_animate) {
534 GetFrameView()->UpdateThrobber();
537 void PanelView::PanelWebContentsFocused(content::WebContents* contents) {
538 web_view_->OnWebContentsFocused(contents);
541 void PanelView::PanelCut() {
542 // Nothing to do since we do not have panel-specific system menu.
546 void PanelView::PanelCopy() {
547 // Nothing to do since we do not have panel-specific system menu.
551 void PanelView::PanelPaste() {
552 // Nothing to do since we do not have panel-specific system menu.
556 void PanelView::DrawAttention(bool draw_attention) {
557 DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0);
559 if (is_drawing_attention_ == draw_attention)
561 is_drawing_attention_ = draw_attention;
562 GetFrameView()->SchedulePaint();
564 if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) {
566 // The default implementation of Widget::FlashFrame only flashes 5 times.
567 // We need more than that.
569 fwi.cbSize = sizeof(fwi);
570 fwi.hwnd = views::HWNDForWidget(window_);
571 if (draw_attention) {
572 fwi.dwFlags = FLASHW_ALL;
573 fwi.uCount = panel::kNumberOfTimesToFlashPanelForAttention;
576 // TODO(jianli): calling FlashWindowEx with FLASHW_STOP flag for the
577 // panel window has the same problem as the stack window. However,
578 // we cannot take the similar fix since there is no background window
579 // to replace for the regular panel window. More investigation is needed.
580 fwi.dwFlags = FLASHW_STOP;
582 ::FlashWindowEx(&fwi);
584 window_->FlashFrame(draw_attention);
589 bool PanelView::IsDrawingAttention() const {
590 return is_drawing_attention_;
593 void PanelView::HandlePanelKeyboardEvent(
594 const content::NativeWebKeyboardEvent& event) {
595 views::FocusManager* focus_manager = GetFocusManager();
596 if (focus_manager->shortcut_handling_suspended())
599 ui::Accelerator accelerator(
600 static_cast<ui::KeyboardCode>(event.windowsKeyCode),
601 content::GetModifiersFromNativeWebKeyboardEvent(event));
602 if (event.type == blink::WebInputEvent::KeyUp)
603 accelerator.set_type(ui::ET_KEY_RELEASED);
604 focus_manager->ProcessAccelerator(accelerator);
607 void PanelView::FullScreenModeChanged(bool is_full_screen) {
608 if (is_full_screen) {
609 if (window_->IsVisible() && always_on_top_)
612 if (!window_->IsVisible()) {
616 // When hiding and showing again a top-most window that belongs to a
617 // background application (i.e. the application is not a foreground one),
618 // the window may loose top-most placement even though its WS_EX_TOPMOST
619 // bit is still set. Re-issuing SetWindowsPos() returns the window to its
620 // top-most placement.
622 window_->SetAlwaysOnTop(true);
628 bool PanelView::IsPanelAlwaysOnTop() const {
629 return always_on_top_;
632 void PanelView::SetPanelAlwaysOnTop(bool on_top) {
633 if (always_on_top_ == on_top)
635 always_on_top_ = on_top;
637 window_->SetAlwaysOnTop(on_top);
638 window_->SetVisibleOnAllWorkspaces(on_top);
639 window_->non_client_view()->Layout();
640 window_->client_view()->Layout();
643 void PanelView::UpdatePanelMinimizeRestoreButtonVisibility() {
644 GetFrameView()->UpdateTitlebarMinimizeRestoreButtonVisibility();
647 void PanelView::SetWindowCornerStyle(panel::CornerStyle corner_style) {
648 GetFrameView()->SetWindowCornerStyle(corner_style);
651 void PanelView::PanelExpansionStateChanging(Panel::ExpansionState old_state,
652 Panel::ExpansionState new_state) {
654 // Live preview is only available since Windows 7.
655 if (base::win::GetVersion() < base::win::VERSION_WIN7)
658 if (panel_->collection()->type() != PanelCollection::DOCKED)
661 bool is_minimized = old_state != Panel::EXPANDED;
662 bool will_be_minimized = new_state != Panel::EXPANDED;
663 if (is_minimized == will_be_minimized)
666 HWND native_window = views::HWNDForWidget(window_);
668 if (!thumbnailer_.get()) {
669 DCHECK(native_window);
670 thumbnailer_.reset(new TaskbarWindowThumbnailerWin(native_window, NULL));
673 // Cache the image at this point.
674 if (will_be_minimized) {
675 // If the panel is still active (we will deactivate the minimizd panel at
676 // later time), we need to paint it immediately as inactive so that we can
677 // take a snapshot of inactive panel.
679 force_to_paint_as_inactive_ = true;
680 ::RedrawWindow(native_window, NULL, NULL,
681 RDW_NOCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW);
684 // Start the thumbnailer and capture the snapshot now.
685 thumbnailer_->Start();
686 thumbnailer_->CaptureSnapshot();
688 force_to_paint_as_inactive_ = false;
689 thumbnailer_->Stop();
695 gfx::Size PanelView::WindowSizeFromContentSize(
696 const gfx::Size& content_size) const {
697 gfx::Size frame = GetFrameView()->NonClientAreaSize();
698 return gfx::Size(content_size.width() + frame.width(),
699 content_size.height() + frame.height());
702 gfx::Size PanelView::ContentSizeFromWindowSize(
703 const gfx::Size& window_size) const {
704 gfx::Size frame = GetFrameView()->NonClientAreaSize();
705 return gfx::Size(window_size.width() - frame.width(),
706 window_size.height() - frame.height());
709 int PanelView::TitleOnlyHeight() const {
710 return panel::kTitlebarHeight;
713 void PanelView::MinimizePanelBySystem() {
717 bool PanelView::IsPanelMinimizedBySystem() const {
718 return window_->IsMinimized();
721 bool PanelView::IsPanelShownOnActiveDesktop() const {
723 // Virtual desktop is not supported by the native Windows system.
731 void PanelView::ShowShadow(bool show) {
733 // The overlapped window has the shadow while the popup window does not have
735 int overlap_style = WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU;
736 int popup_style = WS_POPUP;
737 UpdateWindowAttribute(GWL_STYLE,
738 show ? overlap_style : popup_style,
739 show ? popup_style : overlap_style,
744 void PanelView::AttachWebContents(content::WebContents* contents) {
745 web_view_->SetWebContents(contents);
748 void PanelView::DetachWebContents(content::WebContents* contents) {
749 web_view_->SetWebContents(NULL);
752 NativePanelTesting* PanelView::CreateNativePanelTesting() {
753 return new NativePanelTestingViews(this);
756 void PanelView::OnDisplayChanged() {
757 panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged();
760 void PanelView::OnWorkAreaChanged() {
761 panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged();
764 bool PanelView::WillProcessWorkAreaChange() const {
768 views::View* PanelView::GetContentsView() {
772 views::NonClientFrameView* PanelView::CreateNonClientFrameView(
773 views::Widget* widget) {
774 PanelFrameView* frame_view = new PanelFrameView(this);
779 bool PanelView::CanResize() const {
783 bool PanelView::CanMaximize() const {
787 bool PanelView::CanMinimize() const {
791 base::string16 PanelView::GetWindowTitle() const {
792 return panel_->GetWindowTitle();
795 gfx::ImageSkia PanelView::GetWindowAppIcon() {
796 gfx::Image app_icon = panel_->app_icon();
797 if (app_icon.IsEmpty())
798 return GetWindowIcon();
800 return *app_icon.ToImageSkia();
803 gfx::ImageSkia PanelView::GetWindowIcon() {
804 gfx::Image icon = panel_->GetCurrentPageIcon();
805 return icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia();
808 void PanelView::WindowClosing() {
809 // When closing a panel via window.close, API or the close button,
810 // ClosePanel() is called first, destroying the native |window_|
811 // which results in this method being called. ClosePanel() sets
812 // |window_closed_| to NULL.
813 // If we still have a |window_closed_| here, the close was triggered by the
814 // OS, (e.g. clicking on taskbar menu), which destroys the native |window_|
815 // without invoking ClosePanel() beforehand.
816 if (!window_closed_) {
817 panel_->OnWindowClosing();
819 DCHECK(window_closed_);
823 void PanelView::DeleteDelegate() {
827 void PanelView::OnWindowBeginUserBoundsChange() {
828 user_resizing_ = true;
829 panel_->OnPanelStartUserResizing();
832 StackedPanelCollection* stack = panel_->stack();
834 // Listen to WM_SIZING message in order to find out whether the interior
835 // edge is being resized such that the specific maximum size could be
836 // passed to the system.
837 if (panel_->stack()->GetPanelBelow(panel_.get())) {
838 ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window_), this);
839 user_resizing_interior_stacked_panel_edge_ = false;
842 // Keep track of the original full size of the resizing panel such that it
843 // can be restored to this size once it is shrunk to minimized state.
844 original_full_size_of_resizing_panel_ = panel_->full_size();
846 // Keep track of the original full size of the panel below the resizing
847 // panel such that it can be restored to this size once it is shrunk to
849 Panel* below_panel = stack->GetPanelBelow(panel_.get());
850 if (below_panel && !below_panel->IsMinimized()) {
851 original_full_size_of_panel_below_resizing_panel_ =
852 below_panel->full_size();
858 void PanelView::OnWindowEndUserBoundsChange() {
859 user_resizing_ = false;
860 panel_->OnPanelEndUserResizing();
862 // No need to proceed with post-resizing update when there is no size change.
863 gfx::Rect new_bounds = window_->GetWindowBoundsInScreen();
864 if (bounds_ == new_bounds)
866 bounds_ = new_bounds;
868 panel_->IncreaseMaxSize(bounds_.size());
869 panel_->set_full_size(bounds_.size());
872 StackedPanelCollection* stack = panel_->stack();
874 // No need to listen to WM_SIZING message any more.
875 ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
877 // If the height of resizing panel shrinks close to the titlebar height,
878 // treate it as minimized. This could occur when the user is dragging
879 // 1) the top edge of the top panel downward to shrink it; or
880 // 2) the bottom edge of any panel upward to shrink it.
881 if (panel_->GetBounds().height() <
882 kStackedPanelHeightShrinkThresholdToBecomeMinimized) {
883 stack->MinimizePanel(panel_.get());
884 panel_->set_full_size(original_full_size_of_resizing_panel_);
887 // If the height of panel below the resizing panel shrinks close to the
888 // titlebar height, treat it as minimized. This could occur when the user
889 // is dragging the bottom edge of non-bottom panel downward to expand it
890 // and also shrink the panel below.
891 Panel* below_panel = stack->GetPanelBelow(panel_.get());
892 if (below_panel && !below_panel->IsMinimized() &&
893 below_panel->GetBounds().height() <
894 kStackedPanelHeightShrinkThresholdToBecomeMinimized) {
895 stack->MinimizePanel(below_panel);
896 below_panel->set_full_size(
897 original_full_size_of_panel_below_resizing_panel_);
902 panel_->collection()->RefreshLayout();
905 views::Widget* PanelView::GetWidget() {
909 const views::Widget* PanelView::GetWidget() const {
913 void PanelView::UpdateLoadingAnimations(bool should_animate) {
914 GetFrameView()->UpdateThrobber();
917 void PanelView::UpdateWindowTitle() {
918 window_->UpdateWindowTitle();
919 GetFrameView()->UpdateTitle();
922 void PanelView::UpdateWindowIcon() {
923 window_->UpdateWindowIcon();
924 GetFrameView()->UpdateIcon();
927 void PanelView::Layout() {
928 // |web_view_| might not be created yet when the window is first created.
930 web_view_->SetBounds(0, 0, width(), height());
933 gfx::Size PanelView::GetMinimumSize() const {
934 // If the panel is minimized, it can be rendered to very small size, like
935 // 4-pixel lines when it is docked. Otherwise, its size should not be less
936 // than its minimum size.
937 return panel_->IsMinimized() ? gfx::Size() :
938 gfx::Size(panel::kPanelMinWidth, panel::kPanelMinHeight);
941 gfx::Size PanelView::GetMaximumSize() const {
942 // If the user is resizing a stacked panel by its bottom edge, make sure its
943 // height cannot grow more than what the panel below it could offer. This is
944 // because growing a stacked panel by y amount will shrink the panel below it
945 // by same amount and we do not want the panel below it being shrunk to be
946 // smaller than the titlebar.
948 if (panel_->stack() && user_resizing_interior_stacked_panel_edge_) {
949 Panel* below_panel = panel_->stack()->GetPanelBelow(panel_.get());
950 if (below_panel && !below_panel->IsMinimized()) {
951 return gfx::Size(0, below_panel->GetBounds().bottom() -
952 panel_->GetBounds().y() - panel::kTitlebarHeight);
959 bool PanelView::AcceleratorPressed(const ui::Accelerator& accelerator) {
960 if (mouse_pressed_ && accelerator.key_code() == ui::VKEY_ESCAPE) {
961 OnTitlebarMouseCaptureLost();
965 // No other accelerator is allowed when the drag begins.
966 if (mouse_dragging_state_ == DRAGGING_STARTED)
969 const std::map<ui::Accelerator, int>& accelerator_table =
970 GetAcceleratorTable();
971 std::map<ui::Accelerator, int>::const_iterator iter =
972 accelerator_table.find(accelerator);
973 DCHECK(iter != accelerator_table.end());
974 return panel_->ExecuteCommandIfEnabled(iter->second);
977 void PanelView::OnWidgetDestroying(views::Widget* widget) {
981 void PanelView::OnWidgetActivationChanged(views::Widget* widget, bool active) {
983 // WM_NCACTIVATED could be sent when an active window is being destroyed on
984 // Windows. We need to guard against this.
988 bool focused = active;
989 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_NATIVE) {
990 // The panel window is in focus (actually accepting keystrokes) if it is
991 // active and belongs to a foreground application.
993 views::HWNDForWidget(widget) == ::GetForegroundWindow();
996 bool focused = active;
999 if (focused_ == focused)
1003 // Expand the panel if the minimized panel is activated by means other than
1004 // clicking on its titlebar. This is the workaround to support restoring the
1005 // minimized panel by other means, like alt-tabbing, win-tabbing, or clicking
1006 // the taskbar icon. Note that this workaround does not work for one edge
1007 // case: the mouse happens to be at the minimized panel when the user tries to
1008 // bring up the panel with the above alternatives.
1009 // When the user clicks on the minimized panel, the panel expansion will be
1010 // done when we process the mouse button pressed message.
1012 if (focused_ && panel_->IsMinimized() &&
1013 panel_->collection()->type() == PanelCollection::DOCKED &&
1014 gfx::Screen::GetScreenFor(widget->GetNativeWindow())->
1015 GetWindowUnderCursor() != widget->GetNativeWindow()) {
1020 panel()->OnActiveStateChanged(focused);
1022 // Give web contents view a chance to set focus to the appropriate element.
1024 content::WebContents* web_contents = panel_->GetWebContents();
1026 web_contents->RestoreFocus();
1030 void PanelView::OnWidgetBoundsChanged(views::Widget* widget,
1031 const gfx::Rect& new_bounds) {
1033 panel()->collection()->OnPanelResizedByMouse(panel(), new_bounds);
1036 bool PanelView::OnTitlebarMousePressed(const gfx::Point& mouse_location) {
1037 mouse_pressed_ = true;
1038 mouse_dragging_state_ = NO_DRAGGING;
1039 last_mouse_location_ = mouse_location;
1043 bool PanelView::OnTitlebarMouseDragged(const gfx::Point& mouse_location) {
1044 if (!mouse_pressed_)
1047 if (mouse_dragging_state_ == NO_DRAGGING &&
1048 ExceededDragThreshold(mouse_location - last_mouse_location_)) {
1049 // When a drag begins, we do not want to the client area to still receive
1050 // the focus. We do not need to do this for the unfocused minimized panel.
1051 if (!panel_->IsMinimized()) {
1052 old_focused_view_ = GetFocusManager()->GetFocusedView();
1053 GetFocusManager()->SetFocusedView(GetFrameView());
1056 panel_->manager()->StartDragging(panel_.get(), last_mouse_location_);
1057 mouse_dragging_state_ = DRAGGING_STARTED;
1059 if (mouse_dragging_state_ == DRAGGING_STARTED) {
1060 panel_->manager()->Drag(mouse_location);
1062 // Once in drag, update |last_mouse_location_| on each drag fragment, since
1063 // we already dragged the panel up to the current mouse location.
1064 last_mouse_location_ = mouse_location;
1069 bool PanelView::OnTitlebarMouseReleased(panel::ClickModifier modifier) {
1070 if (mouse_dragging_state_ != NO_DRAGGING) {
1071 // Ensure dragging a minimized panel does not leave it activated.
1072 // Windows activates a panel on mouse-down, regardless of our attempts
1073 // to prevent activation of a minimized panel. Now that we know mouse-down
1074 // resulted in a mouse-drag, we need to ensure the minimized panel is
1076 if (panel_->IsMinimized() && focused_)
1077 panel_->Deactivate();
1079 if (mouse_dragging_state_ == DRAGGING_STARTED) {
1080 // When a drag ends, restore the focus.
1081 if (old_focused_view_) {
1082 GetFocusManager()->SetFocusedView(old_focused_view_);
1083 old_focused_view_ = NULL;
1085 return EndDragging(false);
1088 // The panel drag was cancelled before the mouse is released. Do not
1089 // treat this as a click.
1093 panel_->OnTitlebarClicked(modifier);
1097 bool PanelView::OnTitlebarMouseCaptureLost() {
1098 if (mouse_dragging_state_ == DRAGGING_STARTED)
1099 return EndDragging(true);
1103 bool PanelView::EndDragging(bool cancelled) {
1104 // Only handle clicks that started in our window.
1105 if (!mouse_pressed_)
1107 mouse_pressed_ = false;
1109 mouse_dragging_state_ = DRAGGING_ENDED;
1110 panel_->manager()->EndDragging(cancelled);
1114 PanelFrameView* PanelView::GetFrameView() const {
1115 return static_cast<PanelFrameView*>(window_->non_client_view()->frame_view());
1118 bool PanelView::IsAnimatingBounds() const {
1119 if (bounds_animator_.get() && bounds_animator_->is_animating())
1121 StackedPanelCollection* stack = panel_->stack();
1124 return stack->IsAnimatingPanelBounds(panel_.get());
1128 void PanelView::UpdateWindowAttribute(int attribute_index,
1129 int attribute_value_to_set,
1130 int attribute_value_to_reset,
1131 bool update_frame) {
1132 HWND native_window = views::HWNDForWidget(window_);
1133 int value = ::GetWindowLong(native_window, attribute_index);
1134 int expected_value = value;
1135 if (attribute_value_to_set)
1136 expected_value |= attribute_value_to_set;
1137 if (attribute_value_to_reset)
1138 expected_value &= ~attribute_value_to_reset;
1139 if (value != expected_value)
1140 ::SetWindowLong(native_window, attribute_index, expected_value);
1142 // Per MSDN, if any of the frame styles is changed, SetWindowPos with the
1143 // SWP_FRAMECHANGED flag must be called in order for the cached window data
1144 // to be updated properly.
1145 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx
1147 ::SetWindowPos(native_window, NULL, 0, 0, 0, 0,
1148 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
1149 SWP_NOZORDER | SWP_NOACTIVATE);