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