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/frame/opaque_browser_frame_view.h" 6 7 #include <algorithm> 8 #include <string> 9 10 #include "base/compiler_specific.h" 11 #include "base/prefs/pref_service.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/profiles/profiles_state.h" 15 #include "chrome/browser/signin/signin_header_helper.h" 16 #include "chrome/browser/themes/theme_properties.h" 17 #include "chrome/browser/ui/views/frame/browser_frame.h" 18 #include "chrome/browser/ui/views/frame/browser_view.h" 19 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h" 20 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h" 21 #include "chrome/browser/ui/views/profiles/avatar_label.h" 22 #include "chrome/browser/ui/views/profiles/avatar_menu_button.h" 23 #include "chrome/browser/ui/views/profiles/new_avatar_button.h" 24 #include "chrome/browser/ui/views/tab_icon_view.h" 25 #include "chrome/browser/ui/views/tabs/tab_strip.h" 26 #include "chrome/browser/ui/views/theme_image_mapper.h" 27 #include "chrome/browser/ui/views/toolbar/toolbar_view.h" 28 #include "chrome/common/pref_names.h" 29 #include "chrome/grit/generated_resources.h" 30 #include "components/signin/core/common/profile_management_switches.h" 31 #include "content/public/browser/notification_service.h" 32 #include "content/public/browser/web_contents.h" 33 #include "grit/theme_resources.h" 34 #include "ui/accessibility/ax_view_state.h" 35 #include "ui/base/hit_test.h" 36 #include "ui/base/l10n/l10n_util.h" 37 #include "ui/base/resource/resource_bundle.h" 38 #include "ui/base/theme_provider.h" 39 #include "ui/gfx/canvas.h" 40 #include "ui/gfx/font_list.h" 41 #include "ui/gfx/image/image.h" 42 #include "ui/gfx/image/image_skia.h" 43 #include "ui/gfx/path.h" 44 #include "ui/gfx/rect_conversions.h" 45 #include "ui/resources/grit/ui_resources.h" 46 #include "ui/views/controls/button/image_button.h" 47 #include "ui/views/controls/image_view.h" 48 #include "ui/views/controls/label.h" 49 #include "ui/views/layout/layout_constants.h" 50 #include "ui/views/views_delegate.h" 51 #include "ui/views/widget/root_view.h" 52 #include "ui/views/window/frame_background.h" 53 #include "ui/views/window/window_shape.h" 54 55 #if defined(OS_LINUX) 56 #include "ui/views/controls/menu/menu_runner.h" 57 #endif 58 59 using content::WebContents; 60 61 namespace { 62 63 // While resize areas on Windows are normally the same size as the window 64 // borders, our top area is shrunk by 1 px to make it easier to move the window 65 // around with our thinner top grabbable strip. (Incidentally, our side and 66 // bottom resize areas don't match the frame border thickness either -- they 67 // span the whole nonclient area, so there's no "dead zone" for the mouse.) 68 const int kTopResizeAdjust = 1; 69 70 // In the window corners, the resize areas don't actually expand bigger, but the 71 // 16 px at the end of each edge triggers diagonal resizing. 72 const int kResizeAreaCornerSize = 16; 73 74 // The content left/right images have a shadow built into them. 75 const int kContentEdgeShadowThickness = 2; 76 77 // The icon never shrinks below 16 px on a side. 78 const int kIconMinimumSize = 16; 79 80 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) 81 // The number of pixels to move the frame background image upwards when using 82 // the GTK+ theme and the titlebar is condensed. 83 const int kGTKThemeCondensedFrameTopInset = 15; 84 #endif 85 86 } // namespace 87 88 /////////////////////////////////////////////////////////////////////////////// 89 // OpaqueBrowserFrameView, public: 90 91 OpaqueBrowserFrameView::OpaqueBrowserFrameView(BrowserFrame* frame, 92 BrowserView* browser_view) 93 : BrowserNonClientFrameView(frame, browser_view), 94 layout_(new OpaqueBrowserFrameViewLayout(this)), 95 minimize_button_(NULL), 96 maximize_button_(NULL), 97 restore_button_(NULL), 98 close_button_(NULL), 99 window_icon_(NULL), 100 window_title_(NULL), 101 frame_background_(new views::FrameBackground()) { 102 SetLayoutManager(layout_); 103 104 minimize_button_ = InitWindowCaptionButton(IDR_MINIMIZE, 105 IDR_MINIMIZE_H, 106 IDR_MINIMIZE_P, 107 IDR_MINIMIZE_BUTTON_MASK, 108 IDS_ACCNAME_MINIMIZE, 109 VIEW_ID_MINIMIZE_BUTTON); 110 maximize_button_ = InitWindowCaptionButton(IDR_MAXIMIZE, 111 IDR_MAXIMIZE_H, 112 IDR_MAXIMIZE_P, 113 IDR_MAXIMIZE_BUTTON_MASK, 114 IDS_ACCNAME_MAXIMIZE, 115 VIEW_ID_MAXIMIZE_BUTTON); 116 restore_button_ = InitWindowCaptionButton(IDR_RESTORE, 117 IDR_RESTORE_H, 118 IDR_RESTORE_P, 119 IDR_RESTORE_BUTTON_MASK, 120 IDS_ACCNAME_RESTORE, 121 VIEW_ID_RESTORE_BUTTON); 122 close_button_ = InitWindowCaptionButton(IDR_CLOSE, 123 IDR_CLOSE_H, 124 IDR_CLOSE_P, 125 IDR_CLOSE_BUTTON_MASK, 126 IDS_ACCNAME_CLOSE, 127 VIEW_ID_CLOSE_BUTTON); 128 129 // Initializing the TabIconView is expensive, so only do it if we need to. 130 if (browser_view->ShouldShowWindowIcon()) { 131 window_icon_ = new TabIconView(this, this); 132 window_icon_->set_is_light(true); 133 window_icon_->set_id(VIEW_ID_WINDOW_ICON); 134 AddChildView(window_icon_); 135 window_icon_->Update(); 136 } 137 138 window_title_ = new views::Label( 139 browser_view->GetWindowTitle(), 140 gfx::FontList(BrowserFrame::GetTitleFontList())); 141 window_title_->SetVisible(browser_view->ShouldShowWindowTitle()); 142 window_title_->SetEnabledColor(SK_ColorWHITE); 143 window_title_->SetSubpixelRenderingEnabled(false); 144 window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 145 window_title_->set_id(VIEW_ID_WINDOW_TITLE); 146 AddChildView(window_title_); 147 148 if (browser_view->IsRegularOrGuestSession() && switches::IsNewAvatarMenu()) 149 UpdateNewStyleAvatarInfo(this, NewAvatarButton::THEMED_BUTTON); 150 else 151 UpdateAvatarInfo(); 152 153 if (!browser_view->IsOffTheRecord()) { 154 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, 155 content::NotificationService::AllSources()); 156 } 157 158 platform_observer_.reset(OpaqueBrowserFrameViewPlatformSpecific::Create( 159 this, layout_, browser_view->browser()->profile())); 160 } 161 162 OpaqueBrowserFrameView::~OpaqueBrowserFrameView() { 163 } 164 165 /////////////////////////////////////////////////////////////////////////////// 166 // OpaqueBrowserFrameView, BrowserNonClientFrameView implementation: 167 168 gfx::Rect OpaqueBrowserFrameView::GetBoundsForTabStrip( 169 views::View* tabstrip) const { 170 if (!tabstrip) 171 return gfx::Rect(); 172 173 return layout_->GetBoundsForTabStrip(tabstrip->GetPreferredSize(), width()); 174 } 175 176 int OpaqueBrowserFrameView::GetTopInset() const { 177 return browser_view()->IsTabStripVisible() ? 178 layout_->GetTabStripInsetsTop(false) : 179 layout_->NonClientTopBorderHeight(false); 180 } 181 182 int OpaqueBrowserFrameView::GetThemeBackgroundXInset() const { 183 return 0; 184 } 185 186 void OpaqueBrowserFrameView::UpdateThrobber(bool running) { 187 if (window_icon_) 188 window_icon_->Update(); 189 } 190 191 gfx::Size OpaqueBrowserFrameView::GetMinimumSize() const { 192 return layout_->GetMinimumSize(width()); 193 } 194 195 /////////////////////////////////////////////////////////////////////////////// 196 // OpaqueBrowserFrameView, views::NonClientFrameView implementation: 197 198 gfx::Rect OpaqueBrowserFrameView::GetBoundsForClientView() const { 199 return layout_->client_view_bounds(); 200 } 201 202 gfx::Rect OpaqueBrowserFrameView::GetWindowBoundsForClientBounds( 203 const gfx::Rect& client_bounds) const { 204 return layout_->GetWindowBoundsForClientBounds(client_bounds); 205 } 206 207 int OpaqueBrowserFrameView::NonClientHitTest(const gfx::Point& point) { 208 if (!bounds().Contains(point)) 209 return HTNOWHERE; 210 211 // See if the point is within the avatar menu button or within the avatar 212 // label. 213 if ((avatar_button() && 214 avatar_button()->GetMirroredBounds().Contains(point)) || 215 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point)) || 216 (new_avatar_button() && 217 new_avatar_button()->GetMirroredBounds().Contains(point))) 218 return HTCLIENT; 219 220 int frame_component = frame()->client_view()->NonClientHitTest(point); 221 222 // See if we're in the sysmenu region. We still have to check the tabstrip 223 // first so that clicks in a tab don't get treated as sysmenu clicks. 224 gfx::Rect sysmenu_rect(IconBounds()); 225 // In maximized mode we extend the rect to the screen corner to take advantage 226 // of Fitts' Law. 227 if (layout_->IsTitleBarCondensed()) 228 sysmenu_rect.SetRect(0, 0, sysmenu_rect.right(), sysmenu_rect.bottom()); 229 sysmenu_rect.set_x(GetMirroredXForRect(sysmenu_rect)); 230 if (sysmenu_rect.Contains(point)) 231 return (frame_component == HTCLIENT) ? HTCLIENT : HTSYSMENU; 232 233 if (frame_component != HTNOWHERE) 234 return frame_component; 235 236 // Then see if the point is within any of the window controls. 237 if (close_button_ && close_button_->visible() && 238 close_button_->GetMirroredBounds().Contains(point)) 239 return HTCLOSE; 240 if (restore_button_ && restore_button_->visible() && 241 restore_button_->GetMirroredBounds().Contains(point)) 242 return HTMAXBUTTON; 243 if (maximize_button_ && maximize_button_->visible() && 244 maximize_button_->GetMirroredBounds().Contains(point)) 245 return HTMAXBUTTON; 246 if (minimize_button_ && minimize_button_->visible() && 247 minimize_button_->GetMirroredBounds().Contains(point)) 248 return HTMINBUTTON; 249 250 views::WidgetDelegate* delegate = frame()->widget_delegate(); 251 if (!delegate) { 252 LOG(WARNING) << "delegate is NULL, returning safe default."; 253 return HTCAPTION; 254 } 255 int window_component = GetHTComponentForFrame(point, TopResizeHeight(), 256 NonClientBorderThickness(), kResizeAreaCornerSize, kResizeAreaCornerSize, 257 delegate->CanResize()); 258 // Fall back to the caption if no other component matches. 259 return (window_component == HTNOWHERE) ? HTCAPTION : window_component; 260 } 261 262 void OpaqueBrowserFrameView::GetWindowMask(const gfx::Size& size, 263 gfx::Path* window_mask) { 264 DCHECK(window_mask); 265 266 if (layout_->IsTitleBarCondensed() || frame()->IsFullscreen()) 267 return; 268 269 views::GetDefaultWindowMask(size, window_mask); 270 } 271 272 void OpaqueBrowserFrameView::ResetWindowControls() { 273 restore_button_->SetState(views::CustomButton::STATE_NORMAL); 274 minimize_button_->SetState(views::CustomButton::STATE_NORMAL); 275 maximize_button_->SetState(views::CustomButton::STATE_NORMAL); 276 // The close button isn't affected by this constraint. 277 } 278 279 void OpaqueBrowserFrameView::UpdateWindowIcon() { 280 window_icon_->SchedulePaint(); 281 } 282 283 void OpaqueBrowserFrameView::UpdateWindowTitle() { 284 if (!frame()->IsFullscreen()) 285 window_title_->SchedulePaint(); 286 } 287 288 void OpaqueBrowserFrameView::SizeConstraintsChanged() { 289 } 290 291 /////////////////////////////////////////////////////////////////////////////// 292 // OpaqueBrowserFrameView, views::View overrides: 293 294 void OpaqueBrowserFrameView::GetAccessibleState( 295 ui::AXViewState* state) { 296 state->role = ui::AX_ROLE_TITLE_BAR; 297 } 298 299 /////////////////////////////////////////////////////////////////////////////// 300 // OpaqueBrowserFrameView, views::ButtonListener implementation: 301 302 void OpaqueBrowserFrameView::ButtonPressed(views::Button* sender, 303 const ui::Event& event) { 304 if (sender == minimize_button_) { 305 frame()->Minimize(); 306 } else if (sender == maximize_button_) { 307 frame()->Maximize(); 308 } else if (sender == restore_button_) { 309 frame()->Restore(); 310 } else if (sender == close_button_) { 311 frame()->Close(); 312 } else if (sender == new_avatar_button()) { 313 browser_view()->ShowAvatarBubbleFromAvatarButton( 314 BrowserWindow::AVATAR_BUBBLE_MODE_DEFAULT, 315 signin::ManageAccountsParams()); 316 } 317 } 318 319 void OpaqueBrowserFrameView::OnMenuButtonClicked(views::View* source, 320 const gfx::Point& point) { 321 #if defined(OS_LINUX) 322 views::MenuRunner menu_runner(frame()->GetSystemMenuModel(), 323 views::MenuRunner::HAS_MNEMONICS); 324 ignore_result(menu_runner.RunMenuAt(browser_view()->GetWidget(), 325 window_icon_, 326 window_icon_->GetBoundsInScreen(), 327 views::MENU_ANCHOR_TOPLEFT, 328 ui::MENU_SOURCE_MOUSE)); 329 #endif 330 } 331 332 /////////////////////////////////////////////////////////////////////////////// 333 // OpaqueBrowserFrameView, TabIconView::TabContentsProvider implementation: 334 335 bool OpaqueBrowserFrameView::ShouldTabIconViewAnimate() const { 336 // This function is queried during the creation of the window as the 337 // TabIconView we host is initialized, so we need to NULL check the selected 338 // WebContents because in this condition there is not yet a selected tab. 339 WebContents* current_tab = browser_view()->GetActiveWebContents(); 340 return current_tab ? current_tab->IsLoading() : false; 341 } 342 343 gfx::ImageSkia OpaqueBrowserFrameView::GetFaviconForTabIconView() { 344 views::WidgetDelegate* delegate = frame()->widget_delegate(); 345 if (!delegate) { 346 LOG(WARNING) << "delegate is NULL, returning safe default."; 347 return gfx::ImageSkia(); 348 } 349 return delegate->GetWindowIcon(); 350 } 351 352 /////////////////////////////////////////////////////////////////////////////// 353 // OpaqueBrowserFrameView, protected: 354 355 void OpaqueBrowserFrameView::Observe( 356 int type, 357 const content::NotificationSource& source, 358 const content::NotificationDetails& details) { 359 switch (type) { 360 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: 361 if (browser_view() ->IsRegularOrGuestSession() && 362 switches::IsNewAvatarMenu()) { 363 UpdateNewStyleAvatarInfo(this, NewAvatarButton::THEMED_BUTTON); 364 } else { 365 UpdateAvatarInfo(); 366 } 367 break; 368 default: 369 NOTREACHED() << "Got a notification we didn't register for!"; 370 break; 371 } 372 } 373 374 /////////////////////////////////////////////////////////////////////////////// 375 // OpaqueBrowserFrameView, OpaqueBrowserFrameViewLayoutDelegate implementation: 376 377 bool OpaqueBrowserFrameView::ShouldShowWindowIcon() const { 378 views::WidgetDelegate* delegate = frame()->widget_delegate(); 379 return ShouldShowWindowTitleBar() && delegate && 380 delegate->ShouldShowWindowIcon(); 381 } 382 383 bool OpaqueBrowserFrameView::ShouldShowWindowTitle() const { 384 // |delegate| may be NULL if called from callback of InputMethodChanged while 385 // a window is being destroyed. 386 // See more discussion at http://crosbug.com/8958 387 views::WidgetDelegate* delegate = frame()->widget_delegate(); 388 return ShouldShowWindowTitleBar() && delegate && 389 delegate->ShouldShowWindowTitle(); 390 } 391 392 base::string16 OpaqueBrowserFrameView::GetWindowTitle() const { 393 return frame()->widget_delegate()->GetWindowTitle(); 394 } 395 396 int OpaqueBrowserFrameView::GetIconSize() const { 397 #if defined(OS_WIN) 398 // This metric scales up if either the titlebar height or the titlebar font 399 // size are increased. 400 return GetSystemMetrics(SM_CYSMICON); 401 #else 402 return std::max(BrowserFrame::GetTitleFontList().GetHeight(), 403 kIconMinimumSize); 404 #endif 405 } 406 407 bool OpaqueBrowserFrameView::ShouldLeaveOffsetNearTopBorder() const { 408 return frame()->ShouldLeaveOffsetNearTopBorder(); 409 } 410 411 gfx::Size OpaqueBrowserFrameView::GetBrowserViewMinimumSize() const { 412 return browser_view()->GetMinimumSize(); 413 } 414 415 bool OpaqueBrowserFrameView::ShouldShowCaptionButtons() const { 416 return ShouldShowWindowTitleBar(); 417 } 418 419 bool OpaqueBrowserFrameView::ShouldShowAvatar() const { 420 return browser_view()->ShouldShowAvatar(); 421 } 422 423 bool OpaqueBrowserFrameView::IsRegularOrGuestSession() const { 424 return browser_view()->IsRegularOrGuestSession(); 425 } 426 427 gfx::ImageSkia OpaqueBrowserFrameView::GetOTRAvatarIcon() const { 428 return browser_view()->GetOTRAvatarIcon(); 429 } 430 431 bool OpaqueBrowserFrameView::IsMaximized() const { 432 return frame()->IsMaximized(); 433 } 434 435 bool OpaqueBrowserFrameView::IsMinimized() const { 436 return frame()->IsMinimized(); 437 } 438 439 bool OpaqueBrowserFrameView::IsFullscreen() const { 440 return frame()->IsFullscreen(); 441 } 442 443 bool OpaqueBrowserFrameView::IsTabStripVisible() const { 444 return browser_view()->IsTabStripVisible(); 445 } 446 447 int OpaqueBrowserFrameView::GetTabStripHeight() const { 448 return browser_view()->GetTabStripHeight(); 449 } 450 451 gfx::Size OpaqueBrowserFrameView::GetTabstripPreferredSize() const { 452 gfx::Size s = browser_view()->tabstrip()->GetPreferredSize(); 453 return s; 454 } 455 456 /////////////////////////////////////////////////////////////////////////////// 457 // OpaqueBrowserFrameView, views::View overrides: 458 459 void OpaqueBrowserFrameView::OnPaint(gfx::Canvas* canvas) { 460 if (frame()->IsFullscreen()) 461 return; // Nothing is visible, so don't bother to paint. 462 463 if (layout_->IsTitleBarCondensed()) 464 PaintMaximizedFrameBorder(canvas); 465 else 466 PaintRestoredFrameBorder(canvas); 467 468 // The window icon and title are painted by their respective views. 469 /* TODO(pkasting): If this window is active, we should also draw a drop 470 * shadow on the title. This is tricky, because we don't want to hardcode a 471 * shadow color (since we want to work with various themes), but we can't 472 * alpha-blend either (since the Windows text APIs don't really do this). 473 * So we'd need to sample the background color at the right location and 474 * synthesize a good shadow color. */ 475 476 if (browser_view()->IsToolbarVisible()) 477 PaintToolbarBackground(canvas); 478 if (!layout_->IsTitleBarCondensed()) 479 PaintRestoredClientEdge(canvas); 480 } 481 482 /////////////////////////////////////////////////////////////////////////////// 483 // OpaqueBrowserFrameView, private: 484 485 // views::NonClientFrameView: 486 bool OpaqueBrowserFrameView::DoesIntersectRect(const views::View* target, 487 const gfx::Rect& rect) const { 488 CHECK_EQ(target, this); 489 if (!views::ViewTargeterDelegate::DoesIntersectRect(this, rect)) { 490 // |rect| is outside OpaqueBrowserFrameView's bounds. 491 return false; 492 } 493 494 // If the rect is outside the bounds of the client area, claim it. 495 gfx::RectF rect_in_client_view_coords_f(rect); 496 View::ConvertRectToTarget(this, frame()->client_view(), 497 &rect_in_client_view_coords_f); 498 gfx::Rect rect_in_client_view_coords = gfx::ToEnclosingRect( 499 rect_in_client_view_coords_f); 500 if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords)) 501 return true; 502 503 // Otherwise, claim |rect| only if it is above the bottom of the tabstrip in 504 // a non-tab portion. 505 TabStrip* tabstrip = browser_view()->tabstrip(); 506 if (!tabstrip || !browser_view()->IsTabStripVisible()) 507 return false; 508 509 gfx::RectF rect_in_tabstrip_coords_f(rect); 510 View::ConvertRectToTarget(this, tabstrip, &rect_in_tabstrip_coords_f); 511 gfx::Rect rect_in_tabstrip_coords = gfx::ToEnclosingRect( 512 rect_in_tabstrip_coords_f); 513 if (rect_in_tabstrip_coords.bottom() > tabstrip->GetLocalBounds().bottom()) { 514 // |rect| is below the tabstrip. 515 return false; 516 } 517 518 if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) { 519 // Claim |rect| if it is in a non-tab portion of the tabstrip. 520 return tabstrip->IsRectInWindowCaption(rect_in_tabstrip_coords); 521 } 522 523 // We claim |rect| because it is above the bottom of the tabstrip, but 524 // not in the tabstrip itself. In particular, the avatar label/button is left 525 // of the tabstrip and the window controls are right of the tabstrip. 526 return true; 527 } 528 529 views::ImageButton* OpaqueBrowserFrameView::InitWindowCaptionButton( 530 int normal_image_id, 531 int hot_image_id, 532 int pushed_image_id, 533 int mask_image_id, 534 int accessibility_string_id, 535 ViewID view_id) { 536 views::ImageButton* button = new views::ImageButton(this); 537 ui::ThemeProvider* tp = frame()->GetThemeProvider(); 538 button->SetImage(views::CustomButton::STATE_NORMAL, 539 tp->GetImageSkiaNamed(normal_image_id)); 540 button->SetImage(views::CustomButton::STATE_HOVERED, 541 tp->GetImageSkiaNamed(hot_image_id)); 542 button->SetImage(views::CustomButton::STATE_PRESSED, 543 tp->GetImageSkiaNamed(pushed_image_id)); 544 if (browser_view()->IsBrowserTypeNormal()) { 545 button->SetBackground( 546 tp->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND), 547 tp->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND), 548 tp->GetImageSkiaNamed(mask_image_id)); 549 } 550 button->SetAccessibleName( 551 l10n_util::GetStringUTF16(accessibility_string_id)); 552 button->set_id(view_id); 553 AddChildView(button); 554 return button; 555 } 556 557 int OpaqueBrowserFrameView::FrameBorderThickness(bool restored) const { 558 return layout_->FrameBorderThickness(restored); 559 } 560 561 int OpaqueBrowserFrameView::TopResizeHeight() const { 562 return FrameBorderThickness(false) - kTopResizeAdjust; 563 } 564 565 int OpaqueBrowserFrameView::NonClientBorderThickness() const { 566 return layout_->NonClientBorderThickness(); 567 } 568 569 gfx::Rect OpaqueBrowserFrameView::IconBounds() const { 570 return layout_->IconBounds(); 571 } 572 573 bool OpaqueBrowserFrameView::ShouldShowWindowTitleBar() const { 574 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) 575 // Do not show the custom title bar if the system title bar option is enabled. 576 if (!frame()->UseCustomFrame()) 577 return false; 578 #endif 579 580 // Do not show caption buttons if the window manager is forcefully providing a 581 // title bar (e.g., in Ubuntu Unity, if the window is maximized). 582 if (!views::ViewsDelegate::views_delegate) 583 return true; 584 return !views::ViewsDelegate::views_delegate->WindowManagerProvidesTitleBar( 585 IsMaximized()); 586 } 587 588 void OpaqueBrowserFrameView::PaintRestoredFrameBorder(gfx::Canvas* canvas) { 589 frame_background_->set_frame_color(GetFrameColor()); 590 frame_background_->set_theme_image(GetFrameImage()); 591 frame_background_->set_theme_overlay_image(GetFrameOverlayImage()); 592 frame_background_->set_top_area_height(GetTopAreaHeight()); 593 594 ui::ThemeProvider* tp = GetThemeProvider(); 595 frame_background_->SetSideImages( 596 tp->GetImageSkiaNamed(IDR_WINDOW_LEFT_SIDE), 597 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_CENTER), 598 tp->GetImageSkiaNamed(IDR_WINDOW_RIGHT_SIDE), 599 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_CENTER)); 600 frame_background_->SetCornerImages( 601 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_LEFT_CORNER), 602 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_RIGHT_CORNER), 603 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_LEFT_CORNER), 604 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_RIGHT_CORNER)); 605 frame_background_->PaintRestored(canvas, this); 606 607 // Note: When we don't have a toolbar, we need to draw some kind of bottom 608 // edge here. Because the App Window graphics we use for this have an 609 // attached client edge and their sizing algorithm is a little involved, we do 610 // all this in PaintRestoredClientEdge(). 611 } 612 613 void OpaqueBrowserFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) { 614 ui::ThemeProvider* tp = GetThemeProvider(); 615 frame_background_->set_frame_color(GetFrameColor()); 616 frame_background_->set_theme_image(GetFrameImage()); 617 frame_background_->set_theme_overlay_image(GetFrameOverlayImage()); 618 frame_background_->set_top_area_height(GetTopAreaHeight()); 619 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) 620 // The window manager typically shows a gradient in the native title bar (when 621 // the system title bar pref is set, or when maximized on Ubuntu). Hide the 622 // gradient in the tab strip (by shifting it up vertically) to avoid a 623 // double-gradient effect. 624 if (tp->UsingSystemTheme()) 625 frame_background_->set_maximized_top_inset(kGTKThemeCondensedFrameTopInset); 626 #endif 627 628 frame_background_->PaintMaximized(canvas, this); 629 630 // TODO(jamescook): Migrate this into FrameBackground. 631 if (!browser_view()->IsToolbarVisible()) { 632 // There's no toolbar to edge the frame border, so we need to draw a bottom 633 // edge. The graphic we use for this has a built in client edge, so we clip 634 // it off the bottom. 635 gfx::ImageSkia* top_center = tp->GetImageSkiaNamed(IDR_APP_TOP_CENTER); 636 int edge_height = top_center->height() - kClientEdgeThickness; 637 canvas->TileImageInt(*top_center, 0, 638 frame()->client_view()->y() - edge_height, width(), edge_height); 639 } 640 } 641 642 void OpaqueBrowserFrameView::PaintToolbarBackground(gfx::Canvas* canvas) { 643 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 644 if (toolbar_bounds.IsEmpty()) 645 return; 646 gfx::Point toolbar_origin(toolbar_bounds.origin()); 647 ConvertPointToTarget(browser_view(), this, &toolbar_origin); 648 toolbar_bounds.set_origin(toolbar_origin); 649 650 int x = toolbar_bounds.x(); 651 int w = toolbar_bounds.width(); 652 int y = toolbar_bounds.y(); 653 int h = toolbar_bounds.height(); 654 655 // Gross hack: We split the toolbar images into two pieces, since sometimes 656 // (popup mode) the toolbar isn't tall enough to show the whole image. The 657 // split happens between the top shadow section and the bottom gradient 658 // section so that we never break the gradient. 659 int split_point = kFrameShadowThickness * 2; 660 int bottom_y = y + split_point; 661 ui::ThemeProvider* tp = GetThemeProvider(); 662 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed( 663 IDR_CONTENT_TOP_LEFT_CORNER); 664 int bottom_edge_height = std::min(toolbar_left->height(), h) - split_point; 665 666 // Split our canvas out so we can mask out the corners of the toolbar 667 // without masking out the frame. 668 canvas->SaveLayerAlpha( 669 255, gfx::Rect(x - kClientEdgeThickness, y, w + kClientEdgeThickness * 3, 670 h)); 671 672 // Paint the bottom rect. 673 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height), 674 tp->GetColor(ThemeProperties::COLOR_TOOLBAR)); 675 676 // Tile the toolbar image starting at the frame edge on the left and where the 677 // horizontal tabstrip is (or would be) on the top. 678 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR); 679 canvas->TileImageInt(*theme_toolbar, 680 x + GetThemeBackgroundXInset(), 681 bottom_y - GetTopInset(), 682 x, bottom_y, w, theme_toolbar->height()); 683 684 // Draw rounded corners for the tab. 685 gfx::ImageSkia* toolbar_left_mask = 686 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER_MASK); 687 gfx::ImageSkia* toolbar_right_mask = 688 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_RIGHT_CORNER_MASK); 689 690 // We mask out the corners by using the DestinationIn transfer mode, 691 // which keeps the RGB pixels from the destination and the alpha from 692 // the source. 693 SkPaint paint; 694 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); 695 696 // Mask the left edge. 697 int left_x = x - kContentEdgeShadowThickness; 698 canvas->DrawImageInt(*toolbar_left_mask, 0, 0, toolbar_left_mask->width(), 699 split_point, left_x, y, toolbar_left_mask->width(), 700 split_point, false, paint); 701 canvas->DrawImageInt(*toolbar_left_mask, 0, 702 toolbar_left_mask->height() - bottom_edge_height, 703 toolbar_left_mask->width(), bottom_edge_height, left_x, bottom_y, 704 toolbar_left_mask->width(), bottom_edge_height, false, paint); 705 706 // Mask the right edge. 707 int right_x = 708 x + w - toolbar_right_mask->width() + kContentEdgeShadowThickness; 709 canvas->DrawImageInt(*toolbar_right_mask, 0, 0, toolbar_right_mask->width(), 710 split_point, right_x, y, toolbar_right_mask->width(), 711 split_point, false, paint); 712 canvas->DrawImageInt(*toolbar_right_mask, 0, 713 toolbar_right_mask->height() - bottom_edge_height, 714 toolbar_right_mask->width(), bottom_edge_height, right_x, bottom_y, 715 toolbar_right_mask->width(), bottom_edge_height, false, paint); 716 canvas->Restore(); 717 718 canvas->DrawImageInt(*toolbar_left, 0, 0, toolbar_left->width(), split_point, 719 left_x, y, toolbar_left->width(), split_point, false); 720 canvas->DrawImageInt(*toolbar_left, 0, 721 toolbar_left->height() - bottom_edge_height, toolbar_left->width(), 722 bottom_edge_height, left_x, bottom_y, toolbar_left->width(), 723 bottom_edge_height, false); 724 725 gfx::ImageSkia* toolbar_center = 726 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_CENTER); 727 canvas->TileImageInt(*toolbar_center, 0, 0, left_x + toolbar_left->width(), 728 y, right_x - (left_x + toolbar_left->width()), 729 split_point); 730 731 gfx::ImageSkia* toolbar_right = tp->GetImageSkiaNamed( 732 IDR_CONTENT_TOP_RIGHT_CORNER); 733 canvas->DrawImageInt(*toolbar_right, 0, 0, toolbar_right->width(), 734 split_point, right_x, y, toolbar_right->width(), split_point, false); 735 canvas->DrawImageInt(*toolbar_right, 0, 736 toolbar_right->height() - bottom_edge_height, toolbar_right->width(), 737 bottom_edge_height, right_x, bottom_y, toolbar_right->width(), 738 bottom_edge_height, false); 739 740 // Draw the content/toolbar separator. 741 canvas->FillRect( 742 gfx::Rect(x + kClientEdgeThickness, 743 toolbar_bounds.bottom() - kClientEdgeThickness, 744 w - (2 * kClientEdgeThickness), 745 kClientEdgeThickness), 746 ThemeProperties::GetDefaultColor( 747 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 748 } 749 750 void OpaqueBrowserFrameView::PaintRestoredClientEdge(gfx::Canvas* canvas) { 751 ui::ThemeProvider* tp = GetThemeProvider(); 752 int client_area_top = frame()->client_view()->y(); 753 int image_top = client_area_top; 754 755 gfx::Rect client_area_bounds = 756 layout_->CalculateClientAreaBounds(width(), height()); 757 SkColor toolbar_color = tp->GetColor(ThemeProperties::COLOR_TOOLBAR); 758 759 if (browser_view()->IsToolbarVisible()) { 760 // The client edge images always start below the toolbar corner images. The 761 // client edge filled rects start there or at the bottom of the toolbar, 762 // whichever is shorter. 763 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 764 765 gfx::ImageSkia* content_top_left_corner = 766 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER); 767 // TODO(oshima): Sanity checks for crbug.com/374273. Remove when it's fixed. 768 CHECK(content_top_left_corner); 769 CHECK(!content_top_left_corner->isNull()); 770 771 image_top += toolbar_bounds.y() + content_top_left_corner->height(); 772 client_area_top = std::min(image_top, 773 client_area_top + toolbar_bounds.bottom() - kClientEdgeThickness); 774 } else if (!browser_view()->IsTabStripVisible()) { 775 // The toolbar isn't going to draw a client edge for us, so draw one 776 // ourselves. 777 gfx::ImageSkia* top_left = tp->GetImageSkiaNamed(IDR_APP_TOP_LEFT); 778 gfx::ImageSkia* top_center = tp->GetImageSkiaNamed(IDR_APP_TOP_CENTER); 779 gfx::ImageSkia* top_right = tp->GetImageSkiaNamed(IDR_APP_TOP_RIGHT); 780 int top_edge_y = client_area_top - top_center->height(); 781 int height = client_area_top - top_edge_y; 782 783 canvas->DrawImageInt(*top_left, 0, 0, top_left->width(), height, 784 client_area_bounds.x() - top_left->width(), top_edge_y, 785 top_left->width(), height, false); 786 canvas->TileImageInt(*top_center, 0, 0, client_area_bounds.x(), top_edge_y, 787 client_area_bounds.width(), std::min(height, top_center->height())); 788 canvas->DrawImageInt(*top_right, 0, 0, top_right->width(), height, 789 client_area_bounds.right(), top_edge_y, 790 top_right->width(), height, false); 791 792 // Draw the toolbar color across the top edge. 793 canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness, 794 client_area_top - kClientEdgeThickness, 795 client_area_bounds.width() + (2 * kClientEdgeThickness), 796 kClientEdgeThickness), toolbar_color); 797 } 798 799 int client_area_bottom = 800 std::max(client_area_top, height() - NonClientBorderThickness()); 801 int image_height = client_area_bottom - image_top; 802 803 // Draw the client edge images. 804 gfx::ImageSkia* right = tp->GetImageSkiaNamed(IDR_CONTENT_RIGHT_SIDE); 805 canvas->TileImageInt(*right, client_area_bounds.right(), image_top, 806 right->width(), image_height); 807 canvas->DrawImageInt( 808 *tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_RIGHT_CORNER), 809 client_area_bounds.right(), client_area_bottom); 810 gfx::ImageSkia* bottom = tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_CENTER); 811 canvas->TileImageInt(*bottom, client_area_bounds.x(), 812 client_area_bottom, client_area_bounds.width(), 813 bottom->height()); 814 gfx::ImageSkia* bottom_left = 815 tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_LEFT_CORNER); 816 canvas->DrawImageInt(*bottom_left, 817 client_area_bounds.x() - bottom_left->width(), client_area_bottom); 818 gfx::ImageSkia* left = tp->GetImageSkiaNamed(IDR_CONTENT_LEFT_SIDE); 819 canvas->TileImageInt(*left, client_area_bounds.x() - left->width(), 820 image_top, left->width(), image_height); 821 822 // Draw the toolbar color so that the client edges show the right color even 823 // where not covered by the toolbar image. NOTE: We do this after drawing the 824 // images because the images are meant to alpha-blend atop the frame whereas 825 // these rects are meant to be fully opaque, without anything overlaid. 826 canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness, 827 client_area_top, kClientEdgeThickness, 828 client_area_bottom + kClientEdgeThickness - client_area_top), 829 toolbar_color); 830 canvas->FillRect(gfx::Rect(client_area_bounds.x(), client_area_bottom, 831 client_area_bounds.width(), kClientEdgeThickness), 832 toolbar_color); 833 canvas->FillRect(gfx::Rect(client_area_bounds.right(), client_area_top, 834 kClientEdgeThickness, 835 client_area_bottom + kClientEdgeThickness - client_area_top), 836 toolbar_color); 837 } 838 839 SkColor OpaqueBrowserFrameView::GetFrameColor() const { 840 bool is_incognito = browser_view()->IsOffTheRecord(); 841 ThemeProperties::OverwritableByUserThemeProperty color_id; 842 if (ShouldPaintAsActive()) { 843 color_id = is_incognito ? 844 ThemeProperties::COLOR_FRAME_INCOGNITO : 845 ThemeProperties::COLOR_FRAME; 846 } else { 847 color_id = is_incognito ? 848 ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE : 849 ThemeProperties::COLOR_FRAME_INACTIVE; 850 } 851 852 if (browser_view()->IsBrowserTypeNormal() || 853 platform_observer_->IsUsingSystemTheme()) { 854 return GetThemeProvider()->GetColor(color_id); 855 } 856 857 // Never theme app and popup windows unless the |platform_observer_| 858 // requested an override. 859 return ThemeProperties::GetDefaultColor(color_id); 860 } 861 862 gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameImage() const { 863 bool is_incognito = browser_view()->IsOffTheRecord(); 864 int resource_id; 865 if (browser_view()->IsBrowserTypeNormal()) { 866 if (ShouldPaintAsActive()) { 867 resource_id = is_incognito ? 868 IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME; 869 } else { 870 resource_id = is_incognito ? 871 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE; 872 } 873 return GetThemeProvider()->GetImageSkiaNamed(resource_id); 874 } 875 if (ShouldPaintAsActive()) { 876 resource_id = is_incognito ? 877 IDR_THEME_FRAME_INCOGNITO : IDR_FRAME; 878 } else { 879 resource_id = is_incognito ? 880 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE; 881 } 882 883 if (platform_observer_->IsUsingSystemTheme()) { 884 // We want to use theme images provided by the system theme when enabled, 885 // even if we are an app or popup window. 886 return GetThemeProvider()->GetImageSkiaNamed(resource_id); 887 } 888 889 // Otherwise, never theme app and popup windows. 890 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 891 return rb.GetImageSkiaNamed(chrome::MapThemeImage( 892 chrome::GetHostDesktopTypeForNativeWindow( 893 browser_view()->GetNativeWindow()), 894 resource_id)); 895 } 896 897 gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameOverlayImage() const { 898 ui::ThemeProvider* tp = GetThemeProvider(); 899 if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && 900 browser_view()->IsBrowserTypeNormal() && 901 !browser_view()->IsOffTheRecord()) { 902 return tp->GetImageSkiaNamed(ShouldPaintAsActive() ? 903 IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE); 904 } 905 return NULL; 906 } 907 908 int OpaqueBrowserFrameView::GetTopAreaHeight() const { 909 gfx::ImageSkia* frame_image = GetFrameImage(); 910 int top_area_height = frame_image->height(); 911 if (browser_view()->IsTabStripVisible()) { 912 top_area_height = std::max(top_area_height, 913 GetBoundsForTabStrip(browser_view()->tabstrip()).bottom()); 914 } 915 return top_area_height; 916 } 917