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