1 // Copyright 2019 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/extensions/extensions_menu_item_view.h"
6
7 #include <utility>
8
9 #include "base/metrics/user_metrics.h"
10 #include "base/metrics/user_metrics_action.h"
11 #include "chrome/app/vector_icons/vector_icons.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
15 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
16 #include "chrome/browser/ui/views/bubble_menu_item_factory.h"
17 #include "chrome/browser/ui/views/extensions/extension_context_menu_controller.h"
18 #include "chrome/browser/ui/views/extensions/extensions_menu_button.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/gfx/color_utils.h"
22 #include "ui/gfx/paint_vector_icon.h"
23 #include "ui/views/animation/ink_drop_host_view.h"
24 #include "ui/views/controls/button/button.h"
25 #include "ui/views/controls/button/image_button.h"
26 #include "ui/views/controls/button/image_button_factory.h"
27 #include "ui/views/layout/flex_layout.h"
28 #include "ui/views/layout/flex_layout_types.h"
29 #include "ui/views/vector_icons.h"
30 #include "ui/views/view_class_properties.h"
31
32 namespace {
33 constexpr int kSecondaryIconSizeDp = 16;
34 // Set secondary item insets to get to square buttons.
35 constexpr gfx::Insets kSecondaryButtonInsets = gfx::Insets(
36 (ExtensionsMenuItemView::kMenuItemHeightDp - kSecondaryIconSizeDp) / 2);
37 constexpr int EXTENSION_CONTEXT_MENU = 13;
38 constexpr int EXTENSION_PINNING = 14;
39 } // namespace
40
41 // static
42 constexpr gfx::Size ExtensionsMenuItemView::kIconSize;
43 constexpr char ExtensionsMenuItemView::kClassName[];
44
ExtensionsMenuItemView(Browser * browser,std::unique_ptr<ToolbarActionViewController> controller,bool allow_pinning)45 ExtensionsMenuItemView::ExtensionsMenuItemView(
46 Browser* browser,
47 std::unique_ptr<ToolbarActionViewController> controller,
48 bool allow_pinning)
49 : profile_(browser->profile()),
50 primary_action_button_(new ExtensionsMenuButton(browser,
51 this,
52 controller.get(),
53 allow_pinning)),
54 controller_(std::move(controller)),
55 model_(ToolbarActionsModel::Get(profile_)) {
56 // Set so the extension button receives enter/exit on children to retain hover
57 // status when hovering child views.
58 SetNotifyEnterExitOnChild(true);
59
60 context_menu_controller_ = std::make_unique<ExtensionContextMenuController>(
61 nullptr, controller_.get());
62
63 views::FlexLayout* layout_manager_ =
64 SetLayoutManager(std::make_unique<views::FlexLayout>());
65 layout_manager_->SetOrientation(views::LayoutOrientation::kHorizontal)
66 .SetIgnoreDefaultMainAxisMargins(true);
67
68 AddChildView(primary_action_button_);
69 primary_action_button_->SetProperty(
70 views::kFlexBehaviorKey,
71 views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
72 views::MaximumFlexSizeRule::kUnbounded));
73
74 if (primary_action_button_->CanShowIconInToolbar()) {
75 auto pin_button = CreateBubbleMenuItem(
76 EXTENSION_PINNING,
77 base::BindRepeating(&ExtensionsMenuItemView::PinButtonPressed,
78 base::Unretained(this)));
79 pin_button->SetBorder(views::CreateEmptyBorder(kSecondaryButtonInsets));
80
81 pin_button_ = pin_button.get();
82 AddChildView(std::move(pin_button));
83 }
84 UpdatePinButton();
85
86 auto context_menu_button = CreateBubbleMenuItem(
87 EXTENSION_CONTEXT_MENU, views::Button::PressedCallback());
88 context_menu_button->SetBorder(
89 views::CreateEmptyBorder(kSecondaryButtonInsets));
90 context_menu_button->SetTooltipText(
91 l10n_util::GetStringUTF16(IDS_EXTENSIONS_MENU_CONTEXT_MENU_TOOLTIP));
92 context_menu_button->SetButtonController(
93 std::make_unique<views::MenuButtonController>(
94 context_menu_button.get(),
95 base::BindRepeating(&ExtensionsMenuItemView::ContextMenuPressed,
96 base::Unretained(this)),
97 std::make_unique<views::Button::DefaultButtonControllerDelegate>(
98 context_menu_button.get())));
99 context_menu_button_ = AddChildView(std::move(context_menu_button));
100 }
101
102 ExtensionsMenuItemView::~ExtensionsMenuItemView() = default;
103
GetClassName() const104 const char* ExtensionsMenuItemView::GetClassName() const {
105 return kClassName;
106 }
107
OnThemeChanged()108 void ExtensionsMenuItemView::OnThemeChanged() {
109 views::View::OnThemeChanged();
110 const SkColor icon_color =
111 GetAdjustedIconColor(GetNativeTheme()->GetSystemColor(
112 ui::NativeTheme::kColorId_MenuIconColor));
113
114 if (pin_button_)
115 pin_button_->SetInkDropBaseColor(icon_color);
116 views::SetImageFromVectorIconWithColor(context_menu_button_,
117 kBrowserToolsIcon,
118 kSecondaryIconSizeDp, icon_color);
119 UpdatePinButton();
120 }
121
UpdatePinButton()122 void ExtensionsMenuItemView::UpdatePinButton() {
123 if (!pin_button_)
124 return;
125
126 bool is_force_pinned =
127 model_ && model_->IsActionForcePinned(controller_->GetId());
128 int pin_button_string_id = 0;
129 if (is_force_pinned)
130 pin_button_string_id = IDS_EXTENSIONS_PINNED_BY_ADMIN;
131 else if (IsPinned())
132 pin_button_string_id = IDS_EXTENSIONS_UNPIN_FROM_TOOLBAR;
133 else
134 pin_button_string_id = IDS_EXTENSIONS_PIN_TO_TOOLBAR;
135 pin_button_->SetTooltipText(l10n_util::GetStringUTF16(pin_button_string_id));
136 // Extension pinning is not available in Incognito as it leaves a trace of
137 // user activity.
138 pin_button_->SetEnabled(!is_force_pinned && !profile_->IsOffTheRecord());
139
140 SkColor unpinned_icon_color =
141 GetAdjustedIconColor(GetNativeTheme()->GetSystemColor(
142 ui::NativeTheme::kColorId_MenuIconColor));
143 SkColor icon_color =
144 IsPinned() ? GetAdjustedIconColor(GetNativeTheme()->GetSystemColor(
145 ui::NativeTheme::kColorId_ProminentButtonColor))
146 : unpinned_icon_color;
147 views::SetImageFromVectorIconWithColor(
148 pin_button_, IsPinned() ? views::kUnpinIcon : views::kPinIcon,
149 kSecondaryIconSizeDp, icon_color);
150 }
151
IsContextMenuRunning() const152 bool ExtensionsMenuItemView::IsContextMenuRunning() const {
153 return context_menu_controller_->IsMenuRunning();
154 }
155
IsPinned() const156 bool ExtensionsMenuItemView::IsPinned() const {
157 // |model_| can be null in unit tests.
158 return model_ && model_->IsActionPinned(controller_->GetId());
159 }
160
ContextMenuPressed()161 void ExtensionsMenuItemView::ContextMenuPressed() {
162 base::RecordAction(base::UserMetricsAction(
163 "Extensions.Toolbar.MoreActionsButtonPressedFromMenu"));
164 // TODO(crbug.com/998298): Cleanup the menu source type.
165 context_menu_controller_->ShowContextMenuForViewImpl(
166 context_menu_button_, context_menu_button_->GetMenuPosition(),
167 ui::MenuSourceType::MENU_SOURCE_MOUSE);
168 }
169
PinButtonPressed()170 void ExtensionsMenuItemView::PinButtonPressed() {
171 base::RecordAction(
172 base::UserMetricsAction("Extensions.Toolbar.PinButtonPressed"));
173 model_->SetActionVisibility(controller_->GetId(), !IsPinned());
174 }
175
176 ExtensionsMenuButton*
primary_action_button_for_testing()177 ExtensionsMenuItemView::primary_action_button_for_testing() {
178 return primary_action_button_;
179 }
180
GetAdjustedIconColor(SkColor icon_color) const181 SkColor ExtensionsMenuItemView::GetAdjustedIconColor(SkColor icon_color) const {
182 const SkColor background_color = GetNativeTheme()->GetSystemColor(
183 ui::NativeTheme::kColorId_BubbleBackground);
184 if (background_color != SK_ColorTRANSPARENT) {
185 return color_utils::BlendForMinContrast(icon_color, background_color).color;
186 }
187 return icon_color;
188 }
189