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