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