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