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