panel_view.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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 48namespace { 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. 53const 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. 60struct AcceleratorMapping { 61 ui::KeyboardCode keycode; 62 int modifiers; 63 int command_id; 64}; 65const 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 87const 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. 101class 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 129NativePanelTestingViews::NativePanelTestingViews(PanelView* panel_view) 130 : panel_view_(panel_view) { 131} 132 133NativePanelTestingViews::~NativePanelTestingViews() { 134} 135 136void NativePanelTestingViews::PressLeftMouseButtonTitlebar( 137 const gfx::Point& mouse_location, panel::ClickModifier modifier) { 138 panel_view_->OnTitlebarMousePressed(mouse_location); 139} 140 141void NativePanelTestingViews::ReleaseMouseButtonTitlebar( 142 panel::ClickModifier modifier) { 143 panel_view_->OnTitlebarMouseReleased(modifier); 144} 145 146void NativePanelTestingViews::DragTitlebar(const gfx::Point& mouse_location) { 147 panel_view_->OnTitlebarMouseDragged(mouse_location); 148} 149 150void NativePanelTestingViews::CancelDragTitlebar() { 151 panel_view_->OnTitlebarMouseCaptureLost(); 152} 153 154void NativePanelTestingViews::FinishDragTitlebar() { 155 panel_view_->OnTitlebarMouseReleased(panel::NO_MODIFIER); 156} 157 158bool NativePanelTestingViews::VerifyDrawingAttention() const { 159 base::MessageLoop::current()->RunUntilIdle(); 160 return panel_view_->GetFrameView()->GetPaintState() == 161 PanelFrameView::PAINT_FOR_ATTENTION; 162} 163 164bool 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 170bool 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 190bool 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 214bool NativePanelTestingViews::IsWindowVisible() const { 215 return panel_view_->window()->IsVisible(); 216} 217 218bool NativePanelTestingViews::IsWindowSizeKnown() const { 219 return true; 220} 221 222bool NativePanelTestingViews::IsAnimatingBounds() const { 223 return panel_view_->IsAnimatingBounds(); 224} 225 226bool 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 243panel::CornerStyle NativePanelTestingViews::GetWindowCornerStyle() const { 244 return panel_view_->GetFrameView()->corner_style(); 245} 246 247bool NativePanelTestingViews::EnsureApplicationRunOnForeground() { 248 // Not needed on views. 249 return true; 250} 251 252} // namespace 253 254// static 255NativePanel* 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. 265PanelView::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 338PanelView::~PanelView() { 339} 340 341void PanelView::ShowPanel() { 342 ShowPanelInactive(); 343 ActivatePanel(); 344} 345 346void 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 355gfx::Rect PanelView::GetPanelBounds() const { 356 return bounds_; 357} 358 359void PanelView::SetPanelBounds(const gfx::Rect& bounds) { 360 SetBoundsInternal(bounds, true); 361} 362 363void PanelView::SetPanelBoundsInstantly(const gfx::Rect& bounds) { 364 SetBoundsInternal(bounds, false); 365} 366 367void 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) 389bool 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 404void PanelView::AnimationEnded(const gfx::Animation* animation) { 405 panel_->manager()->OnPanelAnimationEnded(panel_.get()); 406} 407 408void 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 414void 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 460void 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 485void PanelView::ActivatePanel() { 486 window_->Activate(); 487} 488 489void 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 506bool PanelView::IsPanelActive() const { 507 return focused_; 508} 509 510void 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 526gfx::NativeWindow PanelView::GetNativePanelWindow() { 527 return window_->GetNativeWindow(); 528} 529 530void PanelView::UpdatePanelTitleBar() { 531 UpdateWindowTitle(); 532 UpdateWindowIcon(); 533} 534 535void PanelView::UpdatePanelLoadingAnimations(bool should_animate) { 536 GetFrameView()->UpdateThrobber(); 537} 538 539void PanelView::PanelWebContentsFocused(content::WebContents* contents) { 540 web_view_->OnWebContentsFocused(contents); 541} 542 543void PanelView::PanelCut() { 544 // Nothing to do since we do not have panel-specific system menu. 545 NOTREACHED(); 546} 547 548void PanelView::PanelCopy() { 549 // Nothing to do since we do not have panel-specific system menu. 550 NOTREACHED(); 551} 552 553void PanelView::PanelPaste() { 554 // Nothing to do since we do not have panel-specific system menu. 555 NOTREACHED(); 556} 557 558void 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 591bool PanelView::IsDrawingAttention() const { 592 return is_drawing_attention_; 593} 594 595void 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 609void 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 630bool PanelView::IsPanelAlwaysOnTop() const { 631 return always_on_top_; 632} 633 634void 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 645void PanelView::UpdatePanelMinimizeRestoreButtonVisibility() { 646 GetFrameView()->UpdateTitlebarMinimizeRestoreButtonVisibility(); 647} 648 649void PanelView::SetWindowCornerStyle(panel::CornerStyle corner_style) { 650 GetFrameView()->SetWindowCornerStyle(corner_style); 651} 652 653void 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 697gfx::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 704gfx::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 711int PanelView::TitleOnlyHeight() const { 712 return panel::kTitlebarHeight; 713} 714 715void PanelView::MinimizePanelBySystem() { 716 window_->Minimize(); 717} 718 719bool PanelView::IsPanelMinimizedBySystem() const { 720 return window_->IsMinimized(); 721} 722 723bool 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 733void 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 746void PanelView::AttachWebContents(content::WebContents* contents) { 747 web_view_->SetWebContents(contents); 748} 749 750void PanelView::DetachWebContents(content::WebContents* contents) { 751 web_view_->SetWebContents(NULL); 752} 753 754NativePanelTesting* PanelView::CreateNativePanelTesting() { 755 return new NativePanelTestingViews(this); 756} 757 758void PanelView::OnDisplayChanged() { 759 panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged(); 760} 761 762void PanelView::OnWorkAreaChanged() { 763 panel_->manager()->display_settings_provider()->OnDisplaySettingsChanged(); 764} 765 766bool PanelView::WillProcessWorkAreaChange() const { 767 return true; 768} 769 770views::View* PanelView::GetContentsView() { 771 return this; 772} 773 774views::NonClientFrameView* PanelView::CreateNonClientFrameView( 775 views::Widget* widget) { 776 PanelFrameView* frame_view = new PanelFrameView(this); 777 frame_view->Init(); 778 return frame_view; 779} 780 781bool PanelView::CanResize() const { 782 return true; 783} 784 785bool PanelView::CanMaximize() const { 786 return false; 787} 788 789base::string16 PanelView::GetWindowTitle() const { 790 return panel_->GetWindowTitle(); 791} 792 793gfx::ImageSkia PanelView::GetWindowAppIcon() { 794 gfx::Image app_icon = panel_->app_icon(); 795 if (app_icon.IsEmpty()) 796 return GetWindowIcon(); 797 else 798 return *app_icon.ToImageSkia(); 799} 800 801gfx::ImageSkia PanelView::GetWindowIcon() { 802 gfx::Image icon = panel_->GetCurrentPageIcon(); 803 return icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia(); 804} 805 806void PanelView::WindowClosing() { 807 // When closing a panel via window.close, API or the close button, 808 // ClosePanel() is called first, destroying the native |window_| 809 // which results in this method being called. ClosePanel() sets 810 // |window_closed_| to NULL. 811 // If we still have a |window_closed_| here, the close was triggered by the 812 // OS, (e.g. clicking on taskbar menu), which destroys the native |window_| 813 // without invoking ClosePanel() beforehand. 814 if (!window_closed_) { 815 panel_->OnWindowClosing(); 816 ClosePanel(); 817 DCHECK(window_closed_); 818 } 819} 820 821void PanelView::DeleteDelegate() { 822 delete this; 823} 824 825void PanelView::OnWindowBeginUserBoundsChange() { 826 user_resizing_ = true; 827 panel_->OnPanelStartUserResizing(); 828 829#if defined(OS_WIN) 830 StackedPanelCollection* stack = panel_->stack(); 831 if (stack) { 832 // Listen to WM_SIZING message in order to find out whether the interior 833 // edge is being resized such that the specific maximum size could be 834 // passed to the system. 835 if (panel_->stack()->GetPanelBelow(panel_.get())) { 836 ui::HWNDSubclass::AddFilterToTarget(views::HWNDForWidget(window_), this); 837 user_resizing_interior_stacked_panel_edge_ = false; 838 } 839 840 // Keep track of the original full size of the resizing panel such that it 841 // can be restored to this size once it is shrunk to minimized state. 842 original_full_size_of_resizing_panel_ = panel_->full_size(); 843 844 // Keep track of the original full size of the panel below the resizing 845 // panel such that it can be restored to this size once it is shrunk to 846 // minimized state. 847 Panel* below_panel = stack->GetPanelBelow(panel_.get()); 848 if (below_panel && !below_panel->IsMinimized()) { 849 original_full_size_of_panel_below_resizing_panel_ = 850 below_panel->full_size(); 851 } 852 } 853#endif 854} 855 856void PanelView::OnWindowEndUserBoundsChange() { 857 user_resizing_ = false; 858 panel_->OnPanelEndUserResizing(); 859 860 // No need to proceed with post-resizing update when there is no size change. 861 gfx::Rect new_bounds = window_->GetWindowBoundsInScreen(); 862 if (bounds_ == new_bounds) 863 return; 864 bounds_ = new_bounds; 865 866 panel_->IncreaseMaxSize(bounds_.size()); 867 panel_->set_full_size(bounds_.size()); 868 869#if defined(OS_WIN) 870 StackedPanelCollection* stack = panel_->stack(); 871 if (stack) { 872 // No need to listen to WM_SIZING message any more. 873 ui::HWNDSubclass::RemoveFilterFromAllTargets(this); 874 875 // If the height of resizing panel shrinks close to the titlebar height, 876 // treate it as minimized. This could occur when the user is dragging 877 // 1) the top edge of the top panel downward to shrink it; or 878 // 2) the bottom edge of any panel upward to shrink it. 879 if (panel_->GetBounds().height() < 880 kStackedPanelHeightShrinkThresholdToBecomeMinimized) { 881 stack->MinimizePanel(panel_.get()); 882 panel_->set_full_size(original_full_size_of_resizing_panel_); 883 } 884 885 // If the height of panel below the resizing panel shrinks close to the 886 // titlebar height, treat it as minimized. This could occur when the user 887 // is dragging the bottom edge of non-bottom panel downward to expand it 888 // and also shrink the panel below. 889 Panel* below_panel = stack->GetPanelBelow(panel_.get()); 890 if (below_panel && !below_panel->IsMinimized() && 891 below_panel->GetBounds().height() < 892 kStackedPanelHeightShrinkThresholdToBecomeMinimized) { 893 stack->MinimizePanel(below_panel); 894 below_panel->set_full_size( 895 original_full_size_of_panel_below_resizing_panel_); 896 } 897 } 898#endif 899 900 panel_->collection()->RefreshLayout(); 901} 902 903views::Widget* PanelView::GetWidget() { 904 return window_; 905} 906 907const views::Widget* PanelView::GetWidget() const { 908 return window_; 909} 910 911void PanelView::UpdateLoadingAnimations(bool should_animate) { 912 GetFrameView()->UpdateThrobber(); 913} 914 915void PanelView::UpdateWindowTitle() { 916 window_->UpdateWindowTitle(); 917 GetFrameView()->UpdateTitle(); 918} 919 920void PanelView::UpdateWindowIcon() { 921 window_->UpdateWindowIcon(); 922 GetFrameView()->UpdateIcon(); 923} 924 925void PanelView::Layout() { 926 // |web_view_| might not be created yet when the window is first created. 927 if (web_view_) 928 web_view_->SetBounds(0, 0, width(), height()); 929} 930 931gfx::Size PanelView::GetMinimumSize() const { 932 // If the panel is minimized, it can be rendered to very small size, like 933 // 4-pixel lines when it is docked. Otherwise, its size should not be less 934 // than its minimum size. 935 return panel_->IsMinimized() ? gfx::Size() : 936 gfx::Size(panel::kPanelMinWidth, panel::kPanelMinHeight); 937} 938 939gfx::Size PanelView::GetMaximumSize() const { 940 // If the user is resizing a stacked panel by its bottom edge, make sure its 941 // height cannot grow more than what the panel below it could offer. This is 942 // because growing a stacked panel by y amount will shrink the panel below it 943 // by same amount and we do not want the panel below it being shrunk to be 944 // smaller than the titlebar. 945#if defined(OS_WIN) 946 if (panel_->stack() && user_resizing_interior_stacked_panel_edge_) { 947 Panel* below_panel = panel_->stack()->GetPanelBelow(panel_.get()); 948 if (below_panel && !below_panel->IsMinimized()) { 949 return gfx::Size(0, below_panel->GetBounds().bottom() - 950 panel_->GetBounds().y() - panel::kTitlebarHeight); 951 } 952 } 953#endif 954 return gfx::Size(); 955} 956 957bool PanelView::AcceleratorPressed(const ui::Accelerator& accelerator) { 958 if (mouse_pressed_ && accelerator.key_code() == ui::VKEY_ESCAPE) { 959 OnTitlebarMouseCaptureLost(); 960 return true; 961 } 962 963 // No other accelerator is allowed when the drag begins. 964 if (mouse_dragging_state_ == DRAGGING_STARTED) 965 return true; 966 967 const std::map<ui::Accelerator, int>& accelerator_table = 968 GetAcceleratorTable(); 969 std::map<ui::Accelerator, int>::const_iterator iter = 970 accelerator_table.find(accelerator); 971 DCHECK(iter != accelerator_table.end()); 972 return panel_->ExecuteCommandIfEnabled(iter->second); 973} 974 975void PanelView::OnWidgetDestroying(views::Widget* widget) { 976 window_ = NULL; 977} 978 979void PanelView::OnWidgetActivationChanged(views::Widget* widget, bool active) { 980#if defined(OS_WIN) 981 // WM_NCACTIVATED could be sent when an active window is being destroyed on 982 // Windows. We need to guard against this. 983 if (window_closed_) 984 return; 985 986 bool focused = active; 987 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_NATIVE) { 988 // The panel window is in focus (actually accepting keystrokes) if it is 989 // active and belongs to a foreground application. 990 focused = active && 991 views::HWNDForWidget(widget) == ::GetForegroundWindow(); 992 } 993#else 994 bool focused = active; 995#endif 996 997 if (focused_ == focused) 998 return; 999 focused_ = focused; 1000 1001 // Expand the panel if the minimized panel is activated by means other than 1002 // clicking on its titlebar. This is the workaround to support restoring the 1003 // minimized panel by other means, like alt-tabbing, win-tabbing, or clicking 1004 // the taskbar icon. Note that this workaround does not work for one edge 1005 // case: the mouse happens to be at the minimized panel when the user tries to 1006 // bring up the panel with the above alternatives. 1007 // When the user clicks on the minimized panel, the panel expansion will be 1008 // done when we process the mouse button pressed message. 1009#if defined(OS_WIN) 1010 if (focused_ && panel_->IsMinimized() && 1011 panel_->collection()->type() == PanelCollection::DOCKED && 1012 gfx::Screen::GetScreenFor(widget->GetNativeWindow())-> 1013 GetWindowUnderCursor() != widget->GetNativeWindow()) { 1014 panel_->Restore(); 1015 } 1016#endif 1017 1018 panel()->OnActiveStateChanged(focused); 1019 1020 // Give web contents view a chance to set focus to the appropriate element. 1021 if (focused_) { 1022 content::WebContents* web_contents = panel_->GetWebContents(); 1023 if (web_contents) 1024 web_contents->RestoreFocus(); 1025 } 1026} 1027 1028void PanelView::OnWidgetBoundsChanged(views::Widget* widget, 1029 const gfx::Rect& new_bounds) { 1030 if (user_resizing_) 1031 panel()->collection()->OnPanelResizedByMouse(panel(), new_bounds); 1032} 1033 1034bool PanelView::OnTitlebarMousePressed(const gfx::Point& mouse_location) { 1035 mouse_pressed_ = true; 1036 mouse_dragging_state_ = NO_DRAGGING; 1037 last_mouse_location_ = mouse_location; 1038 return true; 1039} 1040 1041bool PanelView::OnTitlebarMouseDragged(const gfx::Point& mouse_location) { 1042 if (!mouse_pressed_) 1043 return false; 1044 1045 if (mouse_dragging_state_ == NO_DRAGGING && 1046 ExceededDragThreshold(mouse_location - last_mouse_location_)) { 1047 // When a drag begins, we do not want to the client area to still receive 1048 // the focus. We do not need to do this for the unfocused minimized panel. 1049 if (!panel_->IsMinimized()) { 1050 old_focused_view_ = GetFocusManager()->GetFocusedView(); 1051 GetFocusManager()->SetFocusedView(GetFrameView()); 1052 } 1053 1054 panel_->manager()->StartDragging(panel_.get(), last_mouse_location_); 1055 mouse_dragging_state_ = DRAGGING_STARTED; 1056 } 1057 if (mouse_dragging_state_ == DRAGGING_STARTED) { 1058 panel_->manager()->Drag(mouse_location); 1059 1060 // Once in drag, update |last_mouse_location_| on each drag fragment, since 1061 // we already dragged the panel up to the current mouse location. 1062 last_mouse_location_ = mouse_location; 1063 } 1064 return true; 1065} 1066 1067bool PanelView::OnTitlebarMouseReleased(panel::ClickModifier modifier) { 1068 if (mouse_dragging_state_ != NO_DRAGGING) { 1069 // Ensure dragging a minimized panel does not leave it activated. 1070 // Windows activates a panel on mouse-down, regardless of our attempts 1071 // to prevent activation of a minimized panel. Now that we know mouse-down 1072 // resulted in a mouse-drag, we need to ensure the minimized panel is 1073 // deactivated. 1074 if (panel_->IsMinimized() && focused_) 1075 panel_->Deactivate(); 1076 1077 if (mouse_dragging_state_ == DRAGGING_STARTED) { 1078 // When a drag ends, restore the focus. 1079 if (old_focused_view_) { 1080 GetFocusManager()->SetFocusedView(old_focused_view_); 1081 old_focused_view_ = NULL; 1082 } 1083 return EndDragging(false); 1084 } 1085 1086 // The panel drag was cancelled before the mouse is released. Do not 1087 // treat this as a click. 1088 return true; 1089 } 1090 1091 panel_->OnTitlebarClicked(modifier); 1092 return true; 1093} 1094 1095bool PanelView::OnTitlebarMouseCaptureLost() { 1096 if (mouse_dragging_state_ == DRAGGING_STARTED) 1097 return EndDragging(true); 1098 return true; 1099} 1100 1101bool PanelView::EndDragging(bool cancelled) { 1102 // Only handle clicks that started in our window. 1103 if (!mouse_pressed_) 1104 return false; 1105 mouse_pressed_ = false; 1106 1107 mouse_dragging_state_ = DRAGGING_ENDED; 1108 panel_->manager()->EndDragging(cancelled); 1109 return true; 1110} 1111 1112PanelFrameView* PanelView::GetFrameView() const { 1113 return static_cast<PanelFrameView*>(window_->non_client_view()->frame_view()); 1114} 1115 1116bool PanelView::IsAnimatingBounds() const { 1117 if (bounds_animator_.get() && bounds_animator_->is_animating()) 1118 return true; 1119 StackedPanelCollection* stack = panel_->stack(); 1120 if (!stack) 1121 return false; 1122 return stack->IsAnimatingPanelBounds(panel_.get()); 1123} 1124 1125bool PanelView::IsWithinResizingArea(const gfx::Point& mouse_location) const { 1126 gfx::Rect bounds = window_->GetWindowBoundsInScreen(); 1127 DCHECK(bounds.Contains(mouse_location)); 1128 return mouse_location.x() < bounds.x() + kResizeInsideBoundsSize || 1129 mouse_location.x() >= bounds.right() - kResizeInsideBoundsSize || 1130 mouse_location.y() < bounds.y() + kResizeInsideBoundsSize || 1131 mouse_location.y() >= bounds.bottom() - kResizeInsideBoundsSize; 1132} 1133 1134#if defined(OS_WIN) 1135void PanelView::UpdateWindowAttribute(int attribute_index, 1136 int attribute_value_to_set, 1137 int attribute_value_to_reset, 1138 bool update_frame) { 1139 HWND native_window = views::HWNDForWidget(window_); 1140 int value = ::GetWindowLong(native_window, attribute_index); 1141 int expected_value = value; 1142 if (attribute_value_to_set) 1143 expected_value |= attribute_value_to_set; 1144 if (attribute_value_to_reset) 1145 expected_value &= ~attribute_value_to_reset; 1146 if (value != expected_value) 1147 ::SetWindowLong(native_window, attribute_index, expected_value); 1148 1149 // Per MSDN, if any of the frame styles is changed, SetWindowPos with the 1150 // SWP_FRAMECHANGED flag must be called in order for the cached window data 1151 // to be updated properly. 1152 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx 1153 if (update_frame) { 1154 ::SetWindowPos(native_window, NULL, 0, 0, 0, 0, 1155 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | 1156 SWP_NOZORDER | SWP_NOACTIVATE); 1157 } 1158} 1159#endif 1160