Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / toolbar / chevron_menu_button.cc
blobe029740938d84a2c64f92ac179df36d99701c654
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/profiles/profile.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/extensions/extension_action_view_controller.h"
18 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
19 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
20 #include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
21 #include "extensions/common/extension.h"
22 #include "ui/views/border.h"
23 #include "ui/views/controls/button/label_button_border.h"
24 #include "ui/views/controls/menu/menu_delegate.h"
25 #include "ui/views/controls/menu/menu_item_view.h"
26 #include "ui/views/controls/menu/menu_runner.h"
27 #include "ui/views/metrics.h"
29 namespace {
31 // In the browser actions container's chevron menu, a menu item view's icon
32 // comes from ToolbarActionView::GetIconWithBadge() when the menu item view is
33 // created. But, the browser action's icon may not be loaded in time because it
34 // is read from file system in another thread.
35 // The IconUpdater will update the menu item view's icon when the browser
36 // action's icon has been updated.
37 class IconUpdater : public ExtensionActionIconFactory::Observer {
38 public:
39 IconUpdater(views::MenuItemView* menu_item_view,
40 ToolbarActionView* represented_view)
41 : menu_item_view_(menu_item_view),
42 represented_view_(represented_view) {
43 DCHECK(menu_item_view);
44 DCHECK(represented_view);
45 view_controller()->set_icon_observer(this);
47 ~IconUpdater() override { view_controller()->set_icon_observer(nullptr); }
49 // ExtensionActionIconFactory::Observer:
50 void OnIconUpdated() override {
51 menu_item_view_->SetIcon(
52 represented_view_->GetImage(views::Button::STATE_NORMAL));
55 private:
56 ExtensionActionViewController* view_controller() {
57 // Since the chevron overflow menu is only used in a world where toolbar
58 // actions are only extensions, this cast is safe.
59 return static_cast<ExtensionActionViewController*>(
60 represented_view_->view_controller());
63 // The menu item view whose icon might be updated.
64 views::MenuItemView* menu_item_view_;
66 // The view this icon updater is helping represent in the chevron overflow
67 // menu. When its icon changes, this updates the corresponding menu item
68 // view's icon.
69 ToolbarActionView* represented_view_;
71 DISALLOW_COPY_AND_ASSIGN(IconUpdater);
74 } // namespace
76 // This class handles the overflow menu for browser actions.
77 class ChevronMenuButton::MenuController : public views::MenuDelegate {
78 public:
79 MenuController(ChevronMenuButton* owner,
80 BrowserActionsContainer* browser_actions_container,
81 bool for_drop);
82 ~MenuController() override;
84 // Shows the overflow menu.
85 void RunMenu(views::Widget* widget);
87 // Closes the overflow menu (and its context menu if open as well).
88 void CloseMenu();
90 private:
91 // views::MenuDelegate:
92 bool IsCommandEnabled(int id) const override;
93 void ExecuteCommand(int id) override;
94 bool ShowContextMenu(views::MenuItemView* source,
95 int id,
96 const gfx::Point& p,
97 ui::MenuSourceType source_type) override;
98 void DropMenuClosed(views::MenuItemView* menu) override;
99 // These drag functions offer support for dragging icons into the overflow
100 // menu.
101 bool GetDropFormats(
102 views::MenuItemView* menu,
103 int* formats,
104 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) override;
105 bool AreDropTypesRequired(views::MenuItemView* menu) override;
106 bool CanDrop(views::MenuItemView* menu,
107 const ui::OSExchangeData& data) override;
108 int GetDropOperation(views::MenuItemView* item,
109 const ui::DropTargetEvent& event,
110 DropPosition* position) override;
111 int OnPerformDrop(views::MenuItemView* menu,
112 DropPosition position,
113 const ui::DropTargetEvent& event) override;
114 // These three drag functions offer support for dragging icons out of the
115 // overflow menu.
116 bool CanDrag(views::MenuItemView* menu) override;
117 void WriteDragData(views::MenuItemView* sender,
118 ui::OSExchangeData* data) override;
119 int GetDragOperations(views::MenuItemView* sender) override;
121 // Returns the offset into |views_| for the given |id|.
122 size_t IndexForId(int id) const;
124 // The owning ChevronMenuButton.
125 ChevronMenuButton* owner_;
127 // A pointer to the browser action container.
128 BrowserActionsContainer* browser_actions_container_;
130 // The overflow menu for the menu button. Owned by |menu_runner_|.
131 views::MenuItemView* menu_;
133 // Resposible for running the menu.
134 scoped_ptr<views::MenuRunner> menu_runner_;
136 // The index into the ToolbarActionView vector, indicating where to start
137 // picking browser actions to draw.
138 int start_index_;
140 // Whether this controller is being used for drop.
141 bool for_drop_;
143 // The vector keeps all icon updaters associated with menu item views in the
144 // controller. The icon updater will update the menu item view's icon when
145 // the browser action view's icon has been updated.
146 ScopedVector<IconUpdater> icon_updaters_;
148 DISALLOW_COPY_AND_ASSIGN(MenuController);
151 ChevronMenuButton::MenuController::MenuController(
152 ChevronMenuButton* owner,
153 BrowserActionsContainer* browser_actions_container,
154 bool for_drop)
155 : owner_(owner),
156 browser_actions_container_(browser_actions_container),
157 menu_(NULL),
158 start_index_(
159 browser_actions_container_->VisibleBrowserActionsAfterAnimation()),
160 for_drop_(for_drop) {
161 menu_ = new views::MenuItemView(this);
162 menu_runner_.reset(new views::MenuRunner(
163 menu_, for_drop_ ? views::MenuRunner::FOR_DROP : 0));
164 menu_->set_has_icons(true);
166 size_t command_id = 1; // Menu id 0 is reserved, start with 1.
167 for (size_t i = start_index_;
168 i < browser_actions_container_->num_toolbar_actions(); ++i) {
169 ToolbarActionView* view =
170 browser_actions_container_->GetToolbarActionViewAt(i);
171 views::MenuItemView* menu_item = menu_->AppendMenuItemWithIcon(
172 command_id,
173 view->view_controller()->GetActionName(),
174 view->GetImage(views::Button::STATE_NORMAL));
176 // Set the tooltip for this item.
177 menu_->SetTooltip(
178 view->view_controller()->GetTooltip(view->GetCurrentWebContents()),
179 command_id);
181 icon_updaters_.push_back(new IconUpdater(menu_item, view));
183 ++command_id;
187 ChevronMenuButton::MenuController::~MenuController() {
190 void ChevronMenuButton::MenuController::RunMenu(views::Widget* window) {
191 gfx::Rect bounds = owner_->bounds();
192 gfx::Point screen_loc;
193 views::View::ConvertPointToScreen(owner_, &screen_loc);
194 bounds.set_x(screen_loc.x());
195 bounds.set_y(screen_loc.y());
197 if (menu_runner_->RunMenuAt(window,
198 owner_,
199 bounds,
200 views::MENU_ANCHOR_TOPRIGHT,
201 ui::MENU_SOURCE_NONE) ==
202 views::MenuRunner::MENU_DELETED)
203 return;
205 if (!for_drop_) {
206 // Give the context menu (if any) a chance to execute the user-selected
207 // command.
208 base::ThreadTaskRunnerHandle::Get()->PostTask(
209 FROM_HERE, base::Bind(&ChevronMenuButton::MenuDone,
210 owner_->weak_factory_.GetWeakPtr()));
214 void ChevronMenuButton::MenuController::CloseMenu() {
215 menu_->Cancel();
218 bool ChevronMenuButton::MenuController::IsCommandEnabled(int id) const {
219 ToolbarActionView* view =
220 browser_actions_container_->GetToolbarActionViewAt(start_index_ + id - 1);
221 return view->view_controller()->IsEnabled(view->GetCurrentWebContents());
224 void ChevronMenuButton::MenuController::ExecuteCommand(int id) {
225 browser_actions_container_->GetToolbarActionViewAt(start_index_ + id - 1)->
226 view_controller()->ExecuteAction(true);
229 bool ChevronMenuButton::MenuController::ShowContextMenu(
230 views::MenuItemView* source,
231 int id,
232 const gfx::Point& p,
233 ui::MenuSourceType source_type) {
234 ToolbarActionView* view = browser_actions_container_->GetToolbarActionViewAt(
235 start_index_ + id - 1);
236 ExtensionActionViewController* view_controller =
237 static_cast<ExtensionActionViewController*>(view->view_controller());
238 if (!view_controller->extension()->ShowConfigureContextMenus())
239 return false;
241 scoped_ptr<extensions::ExtensionContextMenuModel> context_menu_contents(
242 new extensions::ExtensionContextMenuModel(
243 view_controller->extension(), view_controller->browser(),
244 extensions::ExtensionContextMenuModel::OVERFLOWED, view_controller));
245 views::MenuRunner context_menu_runner(context_menu_contents.get(),
246 views::MenuRunner::HAS_MNEMONICS |
247 views::MenuRunner::IS_NESTED |
248 views::MenuRunner::CONTEXT_MENU);
250 // We can ignore the result as we delete ourself.
251 // This blocks until the user chooses something or dismisses the menu.
252 if (context_menu_runner.RunMenuAt(owner_->GetWidget(),
253 NULL,
254 gfx::Rect(p, gfx::Size()),
255 views::MENU_ANCHOR_TOPLEFT,
256 source_type) ==
257 views::MenuRunner::MENU_DELETED)
258 return true;
260 // The user is done with the context menu, so we can close the underlying
261 // menu.
262 menu_->Cancel();
264 return true;
267 void ChevronMenuButton::MenuController::DropMenuClosed(
268 views::MenuItemView* menu) {
269 owner_->MenuDone();
272 bool ChevronMenuButton::MenuController::GetDropFormats(
273 views::MenuItemView* menu,
274 int* formats,
275 std::set<OSExchangeData::CustomFormat>* custom_formats) {
276 return BrowserActionDragData::GetDropFormats(custom_formats);
279 bool ChevronMenuButton::MenuController::AreDropTypesRequired(
280 views::MenuItemView* menu) {
281 return BrowserActionDragData::AreDropTypesRequired();
284 bool ChevronMenuButton::MenuController::CanDrop(
285 views::MenuItemView* menu, const OSExchangeData& data) {
286 return BrowserActionDragData::CanDrop(
287 data, browser_actions_container_->browser()->profile());
290 int ChevronMenuButton::MenuController::GetDropOperation(
291 views::MenuItemView* item,
292 const ui::DropTargetEvent& event,
293 DropPosition* position) {
294 // Don't allow dropping from the BrowserActionContainer into slot 0 of the
295 // overflow menu since once the move has taken place the item you are dragging
296 // falls right out of the menu again once the user releases the button
297 // (because we don't shrink the BrowserActionContainer when you do this).
298 if ((item->GetCommand() == 0) && (*position == DROP_BEFORE)) {
299 BrowserActionDragData drop_data;
300 if (!drop_data.Read(event.data()))
301 return ui::DragDropTypes::DRAG_NONE;
303 if (drop_data.index() < browser_actions_container_->VisibleBrowserActions())
304 return ui::DragDropTypes::DRAG_NONE;
307 return ui::DragDropTypes::DRAG_MOVE;
310 int ChevronMenuButton::MenuController::OnPerformDrop(
311 views::MenuItemView* menu,
312 DropPosition position,
313 const ui::DropTargetEvent& event) {
314 BrowserActionDragData drop_data;
315 if (!drop_data.Read(event.data()))
316 return ui::DragDropTypes::DRAG_NONE;
318 size_t drop_index = IndexForId(menu->GetCommand());
320 // When not dragging within the overflow menu (dragging an icon into the menu)
321 // subtract one to get the right index.
322 if (position == DROP_BEFORE &&
323 drop_data.index() < browser_actions_container_->VisibleBrowserActions())
324 --drop_index;
326 ToolbarActionsBar::DragType drag_type =
327 drop_data.index() < browser_actions_container_->VisibleBrowserActions() ?
328 ToolbarActionsBar::DRAG_TO_OVERFLOW :
329 ToolbarActionsBar::DRAG_TO_SAME;
330 browser_actions_container_->toolbar_actions_bar()->OnDragDrop(
331 drop_data.index(), drop_index, drag_type);
333 if (for_drop_)
334 owner_->MenuDone();
335 return ui::DragDropTypes::DRAG_MOVE;
338 bool ChevronMenuButton::MenuController::CanDrag(views::MenuItemView* menu) {
339 return true;
342 void ChevronMenuButton::MenuController::WriteDragData(
343 views::MenuItemView* sender, OSExchangeData* data) {
344 size_t drag_index = IndexForId(sender->GetCommand());
345 BrowserActionDragData drag_data(
346 browser_actions_container_->GetIdAt(drag_index), drag_index);
347 drag_data.Write(browser_actions_container_->browser()->profile(), data);
350 int ChevronMenuButton::MenuController::GetDragOperations(
351 views::MenuItemView* sender) {
352 return ui::DragDropTypes::DRAG_MOVE;
355 size_t ChevronMenuButton::MenuController::IndexForId(int id) const {
356 // The index of the view being dragged (GetCommand gives a 1-based index into
357 // the overflow menu).
358 DCHECK_GT(browser_actions_container_->VisibleBrowserActions() + id, 0u);
359 return browser_actions_container_->VisibleBrowserActions() + id - 1;
362 ChevronMenuButton::ChevronMenuButton(
363 BrowserActionsContainer* browser_actions_container)
364 : views::MenuButton(NULL, base::string16(), this, false),
365 browser_actions_container_(browser_actions_container),
366 weak_factory_(this) {
369 ChevronMenuButton::~ChevronMenuButton() {
372 void ChevronMenuButton::CloseMenu() {
373 if (menu_controller_)
374 menu_controller_->CloseMenu();
377 scoped_ptr<views::LabelButtonBorder> ChevronMenuButton::CreateDefaultBorder()
378 const {
379 // The chevron resource was designed to not have any insets.
380 scoped_ptr<views::LabelButtonBorder> border =
381 views::MenuButton::CreateDefaultBorder();
382 border->set_insets(gfx::Insets());
383 return border.Pass();
386 bool ChevronMenuButton::GetDropFormats(
387 int* formats,
388 std::set<OSExchangeData::CustomFormat>* custom_formats) {
389 return BrowserActionDragData::GetDropFormats(custom_formats);
392 bool ChevronMenuButton::AreDropTypesRequired() {
393 return BrowserActionDragData::AreDropTypesRequired();
396 bool ChevronMenuButton::CanDrop(const OSExchangeData& data) {
397 return BrowserActionDragData::CanDrop(
398 data, browser_actions_container_->browser()->profile());
401 void ChevronMenuButton::OnDragEntered(const ui::DropTargetEvent& event) {
402 DCHECK(!weak_factory_.HasWeakPtrs());
403 if (!menu_controller_) {
404 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
405 FROM_HERE, base::Bind(&ChevronMenuButton::ShowOverflowMenu,
406 weak_factory_.GetWeakPtr(), true),
407 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
411 int ChevronMenuButton::OnDragUpdated(const ui::DropTargetEvent& event) {
412 return ui::DragDropTypes::DRAG_MOVE;
415 void ChevronMenuButton::OnDragExited() {
416 weak_factory_.InvalidateWeakPtrs();
419 int ChevronMenuButton::OnPerformDrop(const ui::DropTargetEvent& event) {
420 weak_factory_.InvalidateWeakPtrs();
421 return ui::DragDropTypes::DRAG_MOVE;
424 void ChevronMenuButton::OnMenuButtonClicked(views::View* source,
425 const gfx::Point& point) {
426 DCHECK_EQ(this, source);
427 // The menu could already be open if a user dragged an item over it but
428 // ultimately dropped elsewhere (as in that case the menu will close on a
429 // timer). In this case, the click should close the open menu.
430 if (menu_controller_)
431 menu_controller_->CloseMenu();
432 else
433 ShowOverflowMenu(false);
436 void ChevronMenuButton::ShowOverflowMenu(bool for_drop) {
437 // We should never try to show an overflow menu when one is already visible.
438 DCHECK(!menu_controller_);
439 menu_controller_.reset(new MenuController(
440 this, browser_actions_container_, for_drop));
441 menu_controller_->RunMenu(GetWidget());
444 void ChevronMenuButton::MenuDone() {
445 menu_controller_.reset();