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