blob: bc573d28f8420d7faece93710c37134896cac440 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/avatar_menu.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/grit/theme_resources.h"
#include "components/signin/core/common/profile_management_switches.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/views/background.h"
#if defined(OS_WIN)
#include "chrome/browser/ui/views/frame/taskbar_decorator_win.h"
#endif
BrowserNonClientFrameView::BrowserNonClientFrameView(BrowserFrame* frame,
BrowserView* browser_view)
: frame_(frame),
browser_view_(browser_view),
profile_indicator_icon_(nullptr) {
// The profile manager may by null in tests.
if (g_browser_process->profile_manager()) {
g_browser_process->profile_manager()->
GetProfileAttributesStorage().AddObserver(this);
}
}
BrowserNonClientFrameView::~BrowserNonClientFrameView() {
// The profile manager may by null in tests.
if (g_browser_process->profile_manager()) {
g_browser_process->profile_manager()->
GetProfileAttributesStorage().RemoveObserver(this);
}
}
void BrowserNonClientFrameView::OnBrowserViewInitViewsComplete() {
UpdateMinimumSize();
}
void BrowserNonClientFrameView::OnMaximizedStateChanged() {}
gfx::ImageSkia BrowserNonClientFrameView::GetIncognitoAvatarIcon() const {
const SkColor icon_color = color_utils::PickContrastingColor(
SK_ColorWHITE, gfx::kChromeIconGrey, GetFrameColor());
return gfx::CreateVectorIcon(kIncognitoIcon, icon_color);
}
SkColor BrowserNonClientFrameView::GetToolbarTopSeparatorColor() const {
const auto color_id =
ShouldPaintAsActive()
? ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR
: ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_INACTIVE;
return ShouldPaintAsThemed() ? GetThemeProvider()->GetColor(color_id)
: ThemeProperties::GetDefaultColor(
color_id, browser_view_->IsIncognito());
}
views::View* BrowserNonClientFrameView::GetProfileSwitcherView() const {
return nullptr;
}
void BrowserNonClientFrameView::UpdateClientArea() {}
void BrowserNonClientFrameView::UpdateMinimumSize() {}
void BrowserNonClientFrameView::ChildPreferredSizeChanged(views::View* child) {
if (child == GetProfileSwitcherView()) {
// Perform a re-layout if the avatar button has changed, since that can
// affect the size of the tabs.
frame()->GetRootView()->Layout();
}
}
void BrowserNonClientFrameView::VisibilityChanged(views::View* starting_from,
bool is_visible) {
// UpdateTaskbarDecoration() calls DrawTaskbarDecoration(), but that does
// nothing if the window is not visible. So even if we've already gotten the
// up-to-date decoration, we need to run the update procedure again here when
// the window becomes visible.
if (is_visible)
OnProfileAvatarChanged(base::FilePath());
}
bool BrowserNonClientFrameView::ShouldPaintAsThemed() const {
return browser_view_->IsBrowserTypeNormal();
}
SkColor BrowserNonClientFrameView::GetFrameColor(bool active) const {
ThemeProperties::OverwritableByUserThemeProperty color_id =
active ? ThemeProperties::COLOR_FRAME
: ThemeProperties::COLOR_FRAME_INACTIVE;
return ShouldPaintAsThemed()
? GetThemeProviderForProfile()->GetColor(color_id)
: ThemeProperties::GetDefaultColor(color_id,
browser_view_->IsIncognito());
}
gfx::ImageSkia BrowserNonClientFrameView::GetFrameImage(bool active) const {
const ui::ThemeProvider* tp = frame_->GetThemeProvider();
int frame_image_id = active ? IDR_THEME_FRAME : IDR_THEME_FRAME_INACTIVE;
return ShouldPaintAsThemed() && (tp->HasCustomImage(frame_image_id) ||
tp->HasCustomImage(IDR_THEME_FRAME))
? *tp->GetImageSkiaNamed(frame_image_id)
: gfx::ImageSkia();
}
gfx::ImageSkia BrowserNonClientFrameView::GetFrameOverlayImage(
bool active) const {
if (browser_view_->IsIncognito() || !browser_view_->IsBrowserTypeNormal())
return gfx::ImageSkia();
const ui::ThemeProvider* tp = frame_->GetThemeProvider();
int frame_overlay_image_id =
active ? IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE;
return tp->HasCustomImage(frame_overlay_image_id)
? *tp->GetImageSkiaNamed(frame_overlay_image_id)
: gfx::ImageSkia();
}
SkColor BrowserNonClientFrameView::GetFrameColor() const {
return GetFrameColor(ShouldPaintAsActive());
}
gfx::ImageSkia BrowserNonClientFrameView::GetFrameImage() const {
return GetFrameImage(ShouldPaintAsActive());
}
gfx::ImageSkia BrowserNonClientFrameView::GetFrameOverlayImage() const {
return GetFrameOverlayImage(ShouldPaintAsActive());
}
void BrowserNonClientFrameView::UpdateProfileIndicatorIcon() {
if (!profile_indicator_icon_) {
profile_indicator_icon_ = new ProfileIndicatorIcon();
profile_indicator_icon_->set_id(VIEW_ID_PROFILE_INDICATOR_ICON);
AddChildView(profile_indicator_icon_);
// Invalidate here because adding a child does not invalidate the layout.
InvalidateLayout();
frame_->GetRootView()->Layout();
}
gfx::Image icon;
const Profile* profile = browser_view()->browser()->profile();
if (profile->GetProfileType() == Profile::INCOGNITO_PROFILE) {
icon = gfx::Image(GetIncognitoAvatarIcon());
} else {
#if defined(OS_CHROMEOS)
AvatarMenu::GetImageForMenuButton(profile->GetPath(), &icon);
#else
NOTREACHED();
#endif
}
profile_indicator_icon_->SetIcon(icon);
}
void BrowserNonClientFrameView::PaintToolbarBackground(
gfx::Canvas* canvas) const {
gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
if (toolbar_bounds.IsEmpty())
return;
gfx::Point toolbar_origin(toolbar_bounds.origin());
ConvertPointToTarget(browser_view(), this, &toolbar_origin);
toolbar_bounds.set_origin(toolbar_origin);
const ui::ThemeProvider* tp = GetThemeProvider();
const int x = toolbar_bounds.x();
const int y = toolbar_bounds.y();
const int w = toolbar_bounds.width();
// Background.
if (tp->HasCustomImage(IDR_THEME_TOOLBAR)) {
canvas->TileImageInt(*tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR),
x + GetThemeBackgroundXInset(),
y - GetTopInset(false) - GetLayoutInsets(TAB).top(), x,
y, w, toolbar_bounds.height());
} else {
canvas->FillRect(toolbar_bounds,
tp->GetColor(ThemeProperties::COLOR_TOOLBAR));
}
// Top stroke.
gfx::ScopedCanvas scoped_canvas(canvas);
gfx::Rect tabstrip_bounds =
GetMirroredRect(GetBoundsForTabStrip(browser_view()->tabstrip()));
canvas->ClipRect(tabstrip_bounds, SkClipOp::kDifference);
gfx::Rect separator_rect(x, y, w, 0);
separator_rect.set_y(tabstrip_bounds.bottom());
BrowserView::Paint1pxHorizontalLine(canvas, GetToolbarTopSeparatorColor(),
separator_rect, true);
// Toolbar/content separator.
BrowserView::Paint1pxHorizontalLine(
canvas, tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BOTTOM_SEPARATOR),
toolbar_bounds, true);
}
void BrowserNonClientFrameView::ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) {
if (details.is_add && details.child == this)
UpdateProfileIcons();
}
void BrowserNonClientFrameView::ActivationChanged(bool active) {
// On Windows, while deactivating the widget, this is called before the
// active HWND has actually been changed. Since we want the avatar state to
// reflect that the window is inactive, we force NonClientFrameView to see the
// "correct" state as an override.
set_active_state_override(&active);
UpdateProfileIcons();
set_active_state_override(nullptr);
// Changing the activation state may change the toolbar top separator color
// that's used as the stroke around tabs/the new tab button.
browser_view_->tabstrip()->SchedulePaint();
// Changing the activation state may change the visible frame color.
SchedulePaint();
}
bool BrowserNonClientFrameView::DoesIntersectRect(const views::View* target,
const gfx::Rect& rect) const {
DCHECK_EQ(target, this);
if (!views::ViewTargeterDelegate::DoesIntersectRect(this, rect)) {
// |rect| is outside the frame's bounds.
return false;
}
if (!browser_view()->IsTabStripVisible()) {
// Claim |rect| if it is above the top of the topmost client area view.
return rect.y() < GetTopInset(false);
}
// If the rect is outside the bounds of the client area, claim it.
gfx::RectF rect_in_client_view_coords_f(rect);
View::ConvertRectToTarget(this, frame()->client_view(),
&rect_in_client_view_coords_f);
gfx::Rect rect_in_client_view_coords =
gfx::ToEnclosingRect(rect_in_client_view_coords_f);
if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords))
return true;
// Otherwise, claim |rect| only if it is above the bottom of the tabstrip in
// a non-tab portion.
TabStrip* tabstrip = browser_view()->tabstrip();
if (!tabstrip || !browser_view()->IsTabStripVisible())
return false;
gfx::RectF rect_in_tabstrip_coords_f(rect);
View::ConvertRectToTarget(this, tabstrip, &rect_in_tabstrip_coords_f);
gfx::Rect rect_in_tabstrip_coords =
gfx::ToEnclosingRect(rect_in_tabstrip_coords_f);
if (rect_in_tabstrip_coords.y() >= tabstrip->GetLocalBounds().bottom()) {
// |rect| is below the tabstrip.
return false;
}
if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) {
// Claim |rect| if it is in a non-tab portion of the tabstrip.
return tabstrip->IsRectInWindowCaption(rect_in_tabstrip_coords);
}
// We claim |rect| because it is above the bottom of the tabstrip, but
// not in the tabstrip itself. In particular, the avatar label/button is left
// of the tabstrip and the window controls are right of the tabstrip.
return true;
}
void BrowserNonClientFrameView::OnProfileAdded(
const base::FilePath& profile_path) {
OnProfileAvatarChanged(profile_path);
}
void BrowserNonClientFrameView::OnProfileWasRemoved(
const base::FilePath& profile_path,
const base::string16& profile_name) {
OnProfileAvatarChanged(profile_path);
}
void BrowserNonClientFrameView::OnProfileAvatarChanged(
const base::FilePath& profile_path) {
UpdateTaskbarDecoration();
UpdateProfileIcons();
}
void BrowserNonClientFrameView::OnProfileHighResAvatarLoaded(
const base::FilePath& profile_path) {
UpdateTaskbarDecoration();
}
const ui::ThemeProvider*
BrowserNonClientFrameView::GetThemeProviderForProfile() const {
// Because the frame's accessor reads the ThemeProvider from the profile and
// not the widget, it can be called even before we're in a view hierarchy.
return frame_->GetThemeProvider();
}
void BrowserNonClientFrameView::UpdateTaskbarDecoration() {
#if defined(OS_WIN)
if (browser_view()->browser()->profile()->IsGuestSession() ||
// Browser process and profile manager may be null in tests.
(g_browser_process && g_browser_process->profile_manager() &&
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetNumberOfProfiles() <= 1)) {
chrome::DrawTaskbarDecoration(frame_->GetNativeWindow(), nullptr);
return;
}
// For popups and panels which don't have the avatar button, we still
// need to draw the taskbar decoration. Even though we have an icon on the
// window's relaunch details, we draw over it because the user may have
// pinned the badge-less Chrome shortcut which will cause Windows to ignore
// the relaunch details.
// TODO(calamity): ideally this should not be necessary but due to issues
// with the default shortcut being pinned, we add the runtime badge for
// safety. See crbug.com/313800.
gfx::Image decoration;
AvatarMenu::ImageLoadStatus status = AvatarMenu::GetImageForMenuButton(
browser_view()->browser()->profile()->GetPath(), &decoration);
UMA_HISTOGRAM_ENUMERATION(
"Profile.AvatarLoadStatus", status,
static_cast<int>(AvatarMenu::ImageLoadStatus::MAX) + 1);
// If the user is using a Gaia picture and the picture is still being loaded,
// wait until the load finishes. This taskbar decoration will be triggered
// again upon the finish of the picture load.
if (status == AvatarMenu::ImageLoadStatus::LOADING ||
status == AvatarMenu::ImageLoadStatus::PROFILE_DELETED) {
return;
}
chrome::DrawTaskbarDecoration(frame_->GetNativeWindow(), &decoration);
#endif
}