MacViews: Use Mac's "Constrained Window Button" style for Button::STYLE_BUTTON LabelB...
[chromium-blink-merge.git] / chrome / browser / ui / views / toolbar / chevron_menu_button.cc
blob57ca0382cdff836ca12d07af5564d28594afd754
1 // Copyright 2014 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.
5 #include "chrome/browser/ui/views/toolbar/chevron_menu_button.h"
7 #include "base/location.h"
8 #include "base/memory/scoped_vector.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/browser/extensions/extension_action.h"
13 #include "chrome/browser/extensions/extension_action_icon_factory.h"
14 #include "chrome/browser/extensions/extension_context_menu_model.h"
15 #include "chrome/browser/extensions/extension_toolbar_model.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
19 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
20 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
21 #include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
22 #include "extensions/common/extension.h"
23 #include "ui/views/border.h"
24 #include "ui/views/controls/button/label_button_border.h"
25 #include "ui/views/controls/menu/menu_delegate.h"
26 #include "ui/views/controls/menu/menu_item_view.h"
27 #include "ui/views/controls/menu/menu_runner.h"
28 #include "ui/views/metrics.h"
30 namespace {
32 // In the browser actions container's chevron menu, a menu item view's icon
33 // comes from ToolbarActionView::GetIconWithBadge() when the menu item view is
34 // created. But, the browser action's icon may not be loaded in time because it
35 // is read from file system in another thread.
36 // The IconUpdater will update the menu item view's icon when the browser
37 // action's icon has been updated.
38 class IconUpdater : public ExtensionActionIconFactory::Observer {
39 public:
40 IconUpdater(views::MenuItemView* menu_item_view,
41 ToolbarActionView* represented_view)
42 : menu_item_view_(menu_item_view),
43 represented_view_(represented_view) {
44 DCHECK(menu_item_view);
45 DCHECK(represented_view);
46 view_controller()->set_icon_observer(this);
48 ~IconUpdater() override { view_controller()->set_icon_observer(nullptr); }
50 // ExtensionActionIconFactory::Observer:
51 void OnIconUpdated() override {
52 menu_item_view_->SetIcon(
53 represented_view_->GetImage(views::Button::STATE_NORMAL));
56 private:
57 ExtensionActionViewController* view_controller() {
58 // Since the chevron overflow menu is only used in a world where toolbar
59 // actions are only extensions, this cast is safe.
60 return static_cast<ExtensionActionViewController*>(
61 represented_view_->view_controller());
64 // The menu item view whose icon might be updated.
65 views::MenuItemView* menu_item_view_;
67 // The view this icon updater is helping represent in the chevron overflow
68 // menu. When its icon changes, this updates the corresponding menu item
69 // view's icon.
70 ToolbarActionView* represented_view_;
72 DISALLOW_COPY_AND_ASSIGN(IconUpdater);
75 } // namespace
77 // This class handles the overflow menu for browser actions.
78 class ChevronMenuButton::MenuController : public views::MenuDelegate {
79 public:
80 MenuController(ChevronMenuButton* owner,
81 BrowserActionsContainer* browser_actions_container,
82 bool for_drop);
83 ~MenuController() override;
85 // Shows the overflow menu.
86 void RunMenu(views::Widget* widget);
88 // Closes the overflow menu (and its context menu if open as well).
89 void CloseMenu();
91 private:
92 // views::MenuDelegate:
93 bool IsCommandEnabled(int id) const override;
94 void ExecuteCommand(int id) override;
95 bool ShowContextMenu(views::MenuItemView* source,
96 int id,
97 const gfx::Point& p,
98 ui::MenuSourceType source_type) override;
99 void DropMenuClosed(views::MenuItemView* menu) override;
100 // These drag functions offer support for dragging icons into the overflow
101 // menu.
102 bool GetDropFormats(
103 views::MenuItemView* menu,
104 int* formats,
105 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) override;
106 bool AreDropTypesRequired(views::MenuItemView* menu) override;
107 bool CanDrop(views::MenuItemView* menu,
108 const ui::OSExchangeData& data) override;
109 int GetDropOperation(views::MenuItemView* item,
110 const ui::DropTargetEvent& event,
111 DropPosition* position) override;
112 int OnPerformDrop(views::MenuItemView* menu,
113 DropPosition position,
114 const ui::DropTargetEvent& event) override;
115 // These three drag functions offer support for dragging icons out of the
116 // overflow menu.
117 bool CanDrag(views::MenuItemView* menu) override;
118 void WriteDragData(views::MenuItemView* sender,
119 ui::OSExchangeData* data) override;
120 int GetDragOperations(views::MenuItemView* sender) override;
122 // Returns the offset into |views_| for the given |id|.
123 size_t IndexForId(int id) const;
125 // The owning ChevronMenuButton.
126 ChevronMenuButton* owner_;
128 // A pointer to the browser action container.
129 BrowserActionsContainer* browser_actions_container_;
131 // The overflow menu for the menu button. Owned by |menu_runner_|.
132 views::MenuItemView* menu_;
134 // Resposible for running the menu.
135 scoped_ptr<views::MenuRunner> menu_runner_;
137 // The index into the ToolbarActionView vector, indicating where to start
138 // picking browser actions to draw.
139 int start_index_;
141 // Whether this controller is being used for drop.
142 bool for_drop_;
144 // The vector keeps all icon updaters associated with menu item views in the
145 // controller. The icon updater will update the menu item view's icon when
146 // the browser action view's icon has been updated.
147 ScopedVector<IconUpdater> icon_updaters_;
149 DISALLOW_COPY_AND_ASSIGN(MenuController);
152 ChevronMenuButton::MenuController::MenuController(
153 ChevronMenuButton* owner,
154 BrowserActionsContainer* browser_actions_container,
155 bool for_drop)
156 : owner_(owner),
157 browser_actions_container_(browser_actions_container),
158 menu_(NULL),
159 start_index_(
160 browser_actions_container_->VisibleBrowserActionsAfterAnimation()),
161 for_drop_(for_drop) {
162 menu_ = new views::MenuItemView(this);
163 menu_runner_.reset(new views::MenuRunner(
164 menu_, for_drop_ ? views::MenuRunner::FOR_DROP : 0));
165 menu_->set_has_icons(true);
167 size_t command_id = 1; // Menu id 0 is reserved, start with 1.
168 for (size_t i = start_index_;
169 i < browser_actions_container_->num_toolbar_actions(); ++i) {
170 ToolbarActionView* view =
171 browser_actions_container_->GetToolbarActionViewAt(i);
172 views::MenuItemView* menu_item = menu_->AppendMenuItemWithIcon(
173 command_id,
174 view->view_controller()->GetActionName(),
175 view->GetImage(views::Button::STATE_NORMAL));
177 // Set the tooltip for this item.
178 menu_->SetTooltip(
179 view->view_controller()->GetTooltip(view->GetCurrentWebContents()),
180 command_id);
182 icon_updaters_.push_back(new IconUpdater(menu_item, view));
184 ++command_id;
188 ChevronMenuButton::MenuController::~MenuController() {
191 void ChevronMenuButton::MenuController::RunMenu(views::Widget* window) {
192 gfx::Rect bounds = owner_->bounds();
193 gfx::Point screen_loc;
194 views::View::ConvertPointToScreen(owner_, &screen_loc);
195 bounds.set_x(screen_loc.x());
196 bounds.set_y(screen_loc.y());
198 if (menu_runner_->RunMenuAt(window,
199 owner_,
200 bounds,
201 views::MENU_ANCHOR_TOPRIGHT,
202 ui::MENU_SOURCE_NONE) ==
203 views::MenuRunner::MENU_DELETED)
204 return;
206 if (!for_drop_) {
207 // Give the context menu (if any) a chance to execute the user-selected
208 // command.
209 base::ThreadTaskRunnerHandle::Get()->PostTask(
210 FROM_HERE, base::Bind(&ChevronMenuButton::MenuDone,
211 owner_->weak_factory_.GetWeakPtr()));
215 void ChevronMenuButton::MenuController::CloseMenu() {
216 menu_->Cancel();
219 bool ChevronMenuButton::MenuController::IsCommandEnabled(int id) const {
220 ToolbarActionView* view =
221 browser_actions_container_->GetToolbarActionViewAt(start_index_ + id - 1);
222 return view->view_controller()->IsEnabled(view->GetCurrentWebContents());
225 void ChevronMenuButton::MenuController::ExecuteCommand(int id) {
226 browser_actions_container_->GetToolbarActionViewAt(start_index_ + id - 1)->
227 view_controller()->ExecuteAction(true);
230 bool ChevronMenuButton::MenuController::ShowContextMenu(
231 views::MenuItemView* source,
232 int id,
233 const gfx::Point& p,
234 ui::MenuSourceType source_type) {
235 ToolbarActionView* view = browser_actions_container_->GetToolbarActionViewAt(
236 start_index_ + id - 1);
237 ExtensionActionViewController* view_controller =
238 static_cast<ExtensionActionViewController*>(view->view_controller());
239 if (!view_controller->extension()->ShowConfigureContextMenus())
240 return false;
242 scoped_refptr<ExtensionContextMenuModel> context_menu_contents =
243 new ExtensionContextMenuModel(view_controller->extension(),
244 view_controller->browser(),
245 ExtensionContextMenuModel::OVERFLOWED,
246 view_controller);
247 views::MenuRunner context_menu_runner(context_menu_contents.get(),
248 views::MenuRunner::HAS_MNEMONICS |
249 views::MenuRunner::IS_NESTED |
250 views::MenuRunner::CONTEXT_MENU);
252 // We can ignore the result as we delete ourself.
253 // This blocks until the user chooses something or dismisses the menu.
254 if (context_menu_runner.RunMenuAt(owner_->GetWidget(),
255 NULL,
256 gfx::Rect(p, gfx::Size()),
257 views::MENU_ANCHOR_TOPLEFT,
258 source_type) ==
259 views::MenuRunner::MENU_DELETED)
260 return true;
262 // The user is done with the context menu, so we can close the underlying
263 // menu.
264 menu_->Cancel();
266 return true;
269 void ChevronMenuButton::MenuController::DropMenuClosed(
270 views::MenuItemView* menu) {
271 owner_->MenuDone();
274 bool ChevronMenuButton::MenuController::GetDropFormats(
275 views::MenuItemView* menu,
276 int* formats,
277 std::set<OSExchangeData::CustomFormat>* custom_formats) {
278 return BrowserActionDragData::GetDropFormats(custom_formats);
281 bool ChevronMenuButton::MenuController::AreDropTypesRequired(
282 views::MenuItemView* menu) {
283 return BrowserActionDragData::AreDropTypesRequired();
286 bool ChevronMenuButton::MenuController::CanDrop(
287 views::MenuItemView* menu, const OSExchangeData& data) {
288 return BrowserActionDragData::CanDrop(
289 data, browser_actions_container_->browser()->profile());
292 int ChevronMenuButton::MenuController::GetDropOperation(
293 views::MenuItemView* item,
294 const ui::DropTargetEvent& event,
295 DropPosition* position) {
296 // Don't allow dropping from the BrowserActionContainer into slot 0 of the
297 // overflow menu since once the move has taken place the item you are dragging
298 // falls right out of the menu again once the user releases the button
299 // (because we don't shrink the BrowserActionContainer when you do this).
300 if ((item->GetCommand() == 0) && (*position == DROP_BEFORE)) {
301 BrowserActionDragData drop_data;
302 if (!drop_data.Read(event.data()))
303 return ui::DragDropTypes::DRAG_NONE;
305 if (drop_data.index() < browser_actions_container_->VisibleBrowserActions())
306 return ui::DragDropTypes::DRAG_NONE;
309 return ui::DragDropTypes::DRAG_MOVE;
312 int ChevronMenuButton::MenuController::OnPerformDrop(
313 views::MenuItemView* menu,
314 DropPosition position,
315 const ui::DropTargetEvent& event) {
316 BrowserActionDragData drop_data;
317 if (!drop_data.Read(event.data()))
318 return ui::DragDropTypes::DRAG_NONE;
320 size_t drop_index = IndexForId(menu->GetCommand());
322 // When not dragging within the overflow menu (dragging an icon into the menu)
323 // subtract one to get the right index.
324 if (position == DROP_BEFORE &&
325 drop_data.index() < browser_actions_container_->VisibleBrowserActions())
326 --drop_index;
328 ToolbarActionsBar::DragType drag_type =
329 drop_data.index() < browser_actions_container_->VisibleBrowserActions() ?
330 ToolbarActionsBar::DRAG_TO_OVERFLOW :
331 ToolbarActionsBar::DRAG_TO_SAME;
332 browser_actions_container_->toolbar_actions_bar()->OnDragDrop(
333 drop_data.index(), drop_index, drag_type);
335 if (for_drop_)
336 owner_->MenuDone();
337 return ui::DragDropTypes::DRAG_MOVE;
340 bool ChevronMenuButton::MenuController::CanDrag(views::MenuItemView* menu) {
341 return true;
344 void ChevronMenuButton::MenuController::WriteDragData(
345 views::MenuItemView* sender, OSExchangeData* data) {
346 size_t drag_index = IndexForId(sender->GetCommand());
347 BrowserActionDragData drag_data(
348 browser_actions_container_->GetIdAt(drag_index), drag_index);
349 drag_data.Write(browser_actions_container_->browser()->profile(), data);
352 int ChevronMenuButton::MenuController::GetDragOperations(
353 views::MenuItemView* sender) {
354 return ui::DragDropTypes::DRAG_MOVE;
357 size_t ChevronMenuButton::MenuController::IndexForId(int id) const {
358 // The index of the view being dragged (GetCommand gives a 1-based index into
359 // the overflow menu).
360 DCHECK_GT(browser_actions_container_->VisibleBrowserActions() + id, 0u);
361 return browser_actions_container_->VisibleBrowserActions() + id - 1;
364 ChevronMenuButton::ChevronMenuButton(
365 BrowserActionsContainer* browser_actions_container)
366 : views::MenuButton(NULL, base::string16(), this, false),
367 browser_actions_container_(browser_actions_container),
368 weak_factory_(this) {
371 ChevronMenuButton::~ChevronMenuButton() {
374 void ChevronMenuButton::CloseMenu() {
375 if (menu_controller_)
376 menu_controller_->CloseMenu();
379 scoped_ptr<views::LabelButtonBorder> ChevronMenuButton::CreateDefaultBorder()
380 const {
381 // The chevron resource was designed to not have any insets.
382 scoped_ptr<views::LabelButtonBorder> border =
383 views::MenuButton::CreateDefaultBorder();
384 border->set_insets(gfx::Insets());
385 return border.Pass();
388 bool ChevronMenuButton::GetDropFormats(
389 int* formats,
390 std::set<OSExchangeData::CustomFormat>* custom_formats) {
391 return BrowserActionDragData::GetDropFormats(custom_formats);
394 bool ChevronMenuButton::AreDropTypesRequired() {
395 return BrowserActionDragData::AreDropTypesRequired();
398 bool ChevronMenuButton::CanDrop(const OSExchangeData& data) {
399 return BrowserActionDragData::CanDrop(
400 data, browser_actions_container_->browser()->profile());
403 void ChevronMenuButton::OnDragEntered(const ui::DropTargetEvent& event) {
404 DCHECK(!weak_factory_.HasWeakPtrs());
405 if (!menu_controller_) {
406 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
407 FROM_HERE, base::Bind(&ChevronMenuButton::ShowOverflowMenu,
408 weak_factory_.GetWeakPtr(), true),
409 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
413 int ChevronMenuButton::OnDragUpdated(const ui::DropTargetEvent& event) {
414 return ui::DragDropTypes::DRAG_MOVE;
417 void ChevronMenuButton::OnDragExited() {
418 weak_factory_.InvalidateWeakPtrs();
421 int ChevronMenuButton::OnPerformDrop(const ui::DropTargetEvent& event) {
422 weak_factory_.InvalidateWeakPtrs();
423 return ui::DragDropTypes::DRAG_MOVE;
426 void ChevronMenuButton::OnMenuButtonClicked(views::View* source,
427 const gfx::Point& point) {
428 DCHECK_EQ(this, source);
429 // The menu could already be open if a user dragged an item over it but
430 // ultimately dropped elsewhere (as in that case the menu will close on a
431 // timer). In this case, the click should close the open menu.
432 if (menu_controller_)
433 menu_controller_->CloseMenu();
434 else
435 ShowOverflowMenu(false);
438 void ChevronMenuButton::ShowOverflowMenu(bool for_drop) {
439 // We should never try to show an overflow menu when one is already visible.
440 DCHECK(!menu_controller_);
441 menu_controller_.reset(new MenuController(
442 this, browser_actions_container_, for_drop));
443 menu_controller_->RunMenu(GetWidget());
446 void ChevronMenuButton::MenuDone() {
447 menu_controller_.reset();