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