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