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