blob: f30ceec7df8269602336151de6cdcbf3c1e67947 [file] [log] [blame]
// Copyright 2013 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/toolbar/app_menu_button.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
#include "chrome/browser/ui/views/toolbar/app_menu.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "extensions/common/feature_switch.h"
#include "grit/theme_resources.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icons_public.h"
#include "ui/keyboard/keyboard_controller.h"
#include "ui/views/animation/button_ink_drop_delegate.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/menu/menu_listener.h"
#include "ui/views/metrics.h"
#include "ui/views/painter.h"
// static
bool AppMenuButton::g_open_app_immediately_for_testing = false;
AppMenuButton::AppMenuButton(ToolbarView* toolbar_view)
: views::MenuButton(base::string16(), toolbar_view, false),
severity_(AppMenuIconPainter::SEVERITY_NONE),
toolbar_view_(toolbar_view),
allow_extension_dragging_(
extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()),
destroyed_(nullptr),
margin_trailing_(0),
ink_drop_delegate_(new views::ButtonInkDropDelegate(this, this)),
weak_factory_(this) {
set_ink_drop_delegate(ink_drop_delegate_.get());
if (!ui::MaterialDesignController::IsModeMaterial())
icon_painter_.reset(new AppMenuIconPainter(this));
}
AppMenuButton::~AppMenuButton() {
if (destroyed_)
*destroyed_ = true;
}
void AppMenuButton::SetSeverity(AppMenuIconPainter::Severity severity,
bool animate) {
if (ui::MaterialDesignController::IsModeMaterial()) {
severity_ = severity;
UpdateIcon();
return;
}
icon_painter_->SetSeverity(severity, animate);
SchedulePaint();
}
void AppMenuButton::ShowMenu(bool for_drop) {
if (menu_ && menu_->IsShowing())
return;
#if defined(USE_AURA)
keyboard::KeyboardController* keyboard_controller =
keyboard::KeyboardController::GetInstance();
if (keyboard_controller && keyboard_controller->keyboard_visible()) {
keyboard_controller->HideKeyboard(
keyboard::KeyboardController::HIDE_REASON_AUTOMATIC);
}
#endif
Browser* browser = toolbar_view_->browser();
menu_.reset(new AppMenu(browser, for_drop ? AppMenu::FOR_DROP : 0));
menu_model_.reset(new AppMenuModel(toolbar_view_, browser));
menu_->Init(menu_model_.get());
FOR_EACH_OBSERVER(views::MenuListener, menu_listeners_, OnMenuOpened());
// Because running the menu below spins a nested message loop, |this| can be
// deleted by the time RunMenu() returns. To detect this, we set |destroyed_|
// (which is normally null) to point to a local. If our destructor runs during
// RunMenu(), then this local will be set to true on return, and we'll know
// it's not safe to access any member variables.
bool destroyed = false;
destroyed_ = &destroyed;
ink_drop_delegate()->OnAction(views::InkDropState::ACTIVATED);
base::TimeTicks menu_open_time = base::TimeTicks::Now();
menu_->RunMenu(this);
if (!for_drop) {
// Record the time-to-action for the menu. We don't record in the case of a
// drag-and-drop command because menus opened for drag-and-drop don't block
// the message loop.
UMA_HISTOGRAM_TIMES("Toolbar.AppMenuTimeToAction",
base::TimeTicks::Now() - menu_open_time);
}
if (!destroyed) {
ink_drop_delegate()->OnAction(views::InkDropState::DEACTIVATED);
destroyed_ = nullptr;
}
}
void AppMenuButton::CloseMenu() {
if (menu_)
menu_->CloseMenu();
menu_.reset();
}
bool AppMenuButton::IsMenuShowing() const {
return menu_ && menu_->IsShowing();
}
void AppMenuButton::AddMenuListener(views::MenuListener* listener) {
menu_listeners_.AddObserver(listener);
}
void AppMenuButton::RemoveMenuListener(views::MenuListener* listener) {
menu_listeners_.RemoveObserver(listener);
}
gfx::Size AppMenuButton::GetPreferredSize() const {
if (ui::MaterialDesignController::IsModeMaterial()) {
gfx::Size size(image()->GetPreferredSize());
const ui::ThemeProvider* provider = GetThemeProvider();
if (provider) {
gfx::Insets insets(GetLayoutInsets(TOOLBAR_BUTTON));
size.Enlarge(insets.width(), insets.height());
}
return size;
}
return ResourceBundle::GetSharedInstance().
GetImageSkiaNamed(IDR_TOOLBAR_BEZEL_HOVER)->size();
}
void AppMenuButton::ScheduleAppMenuIconPaint() {
SchedulePaint();
}
void AppMenuButton::UpdateIcon() {
DCHECK(ui::MaterialDesignController::IsModeMaterial());
SkColor color = gfx::kPlaceholderColor;
switch (severity_) {
case AppMenuIconPainter::SEVERITY_NONE:
color = GetThemeProvider()->GetColor(
ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
break;
case AppMenuIconPainter::SEVERITY_LOW:
color = gfx::kGoogleGreen700;
break;
case AppMenuIconPainter::SEVERITY_MEDIUM:
color = gfx::kGoogleYellow700;
break;
case AppMenuIconPainter::SEVERITY_HIGH:
color = gfx::kGoogleRed700;
break;
}
// TODO(estade): find a home for this constant.
const int kButtonSize = 16;
SetImage(views::Button::STATE_NORMAL,
gfx::CreateVectorIcon(gfx::VectorIconId::BROWSER_TOOLS, kButtonSize,
color));
}
void AppMenuButton::SetTrailingMargin(int margin) {
margin_trailing_ = margin;
UpdateThemedBorder();
const int inset = LabelButton::kFocusRectInset;
SetFocusPainter(views::Painter::CreateDashedFocusPainterWithInsets(
gfx::Insets(inset, inset, inset, inset + margin)));
InvalidateLayout();
}
void AppMenuButton::AddInkDropLayer(ui::Layer* ink_drop_layer) {
image()->SetPaintToLayer(true);
image()->SetFillsBoundsOpaquely(false);
views::MenuButton::AddInkDropLayer(ink_drop_layer);
}
void AppMenuButton::RemoveInkDropLayer(ui::Layer* ink_drop_layer) {
views::MenuButton::RemoveInkDropLayer(ink_drop_layer);
image()->SetFillsBoundsOpaquely(true);
image()->SetPaintToLayer(false);
}
gfx::Point AppMenuButton::GetInkDropCenter() const {
// ToolbarView extends the bounds of the app button to the right in maximized
// mode. So instead of using the center point of local bounds, we use the
// center point (adjusted for RTL layouts) of the preferred size, which
// doesn't change in maximized mode.
const int visible_width = GetPreferredSize().width();
return gfx::Point(
(GetMirroredXWithWidthInView(0, visible_width) + visible_width) / 2,
height() / 2);
}
const char* AppMenuButton::GetClassName() const {
return "AppMenuButton";
}
scoped_ptr<views::LabelButtonBorder> AppMenuButton::CreateDefaultBorder()
const {
scoped_ptr<views::LabelButtonBorder> border =
MenuButton::CreateDefaultBorder();
// Adjust border insets to follow the margin change,
// which will be reflected in where the border is painted
// through GetThemePaintRect().
gfx::Insets insets(border->GetInsets());
insets += gfx::Insets(0, 0, 0, margin_trailing_);
border->set_insets(insets);
return border;
}
gfx::Rect AppMenuButton::GetThemePaintRect() const {
gfx::Rect rect(MenuButton::GetThemePaintRect());
rect.Inset(0, 0, margin_trailing_, 0);
return rect;
}
bool AppMenuButton::GetDropFormats(
int* formats,
std::set<ui::Clipboard::FormatType>* format_types) {
return allow_extension_dragging_ ?
BrowserActionDragData::GetDropFormats(format_types) :
views::View::GetDropFormats(formats, format_types);
}
bool AppMenuButton::AreDropTypesRequired() {
return allow_extension_dragging_ ?
BrowserActionDragData::AreDropTypesRequired() :
views::View::AreDropTypesRequired();
}
bool AppMenuButton::CanDrop(const ui::OSExchangeData& data) {
return allow_extension_dragging_ ?
BrowserActionDragData::CanDrop(data,
toolbar_view_->browser()->profile()) :
views::View::CanDrop(data);
}
void AppMenuButton::OnDragEntered(const ui::DropTargetEvent& event) {
DCHECK(allow_extension_dragging_);
DCHECK(!weak_factory_.HasWeakPtrs());
if (!g_open_app_immediately_for_testing) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&AppMenuButton::ShowMenu, weak_factory_.GetWeakPtr(), true),
base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
} else {
ShowMenu(true);
}
}
int AppMenuButton::OnDragUpdated(const ui::DropTargetEvent& event) {
DCHECK(allow_extension_dragging_);
return ui::DragDropTypes::DRAG_MOVE;
}
void AppMenuButton::OnDragExited() {
DCHECK(allow_extension_dragging_);
weak_factory_.InvalidateWeakPtrs();
}
int AppMenuButton::OnPerformDrop(const ui::DropTargetEvent& event) {
DCHECK(allow_extension_dragging_);
return ui::DragDropTypes::DRAG_MOVE;
}
void AppMenuButton::OnPaint(gfx::Canvas* canvas) {
views::MenuButton::OnPaint(canvas);
if (ui::MaterialDesignController::IsModeMaterial())
return;
// Use GetPreferredSize() to center the icon inside the visible bounds rather
// than the whole size() (which may refer to hit test region extended to the
// end of the toolbar in maximized mode).
icon_painter_->Paint(canvas, GetThemeProvider(),
gfx::Rect(GetPreferredSize()),
AppMenuIconPainter::BEZEL_NONE);
}