Infobar material design refresh: layout
[chromium-blink-merge.git] / chrome / browser / ui / views / toolbar / chevron_menu_button.cc
blob25b5f70ef59edbac9c4ca538ce413d4595d012eb
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_refptr<ExtensionContextMenuModel> context_menu_contents =
242 new ExtensionContextMenuModel(view_controller->extension(),
243 view_controller->browser(),
244 ExtensionContextMenuModel::OVERFLOWED,
245 view_controller);
246 views::MenuRunner context_menu_runner(context_menu_contents.get(),
247 views::MenuRunner::HAS_MNEMONICS |
248 views::MenuRunner::IS_NESTED |
249 views::MenuRunner::CONTEXT_MENU);
251 // We can ignore the result as we delete ourself.
252 // This blocks until the user chooses something or dismisses the menu.
253 if (context_menu_runner.RunMenuAt(owner_->GetWidget(),
254 NULL,
255 gfx::Rect(p, gfx::Size()),
256 views::MENU_ANCHOR_TOPLEFT,
257 source_type) ==
258 views::MenuRunner::MENU_DELETED)
259 return true;
261 // The user is done with the context menu, so we can close the underlying
262 // menu.
263 menu_->Cancel();
265 return true;
268 void ChevronMenuButton::MenuController::DropMenuClosed(
269 views::MenuItemView* menu) {
270 owner_->MenuDone();
273 bool ChevronMenuButton::MenuController::GetDropFormats(
274 views::MenuItemView* menu,
275 int* formats,
276 std::set<OSExchangeData::CustomFormat>* custom_formats) {
277 return BrowserActionDragData::GetDropFormats(custom_formats);
280 bool ChevronMenuButton::MenuController::AreDropTypesRequired(
281 views::MenuItemView* menu) {
282 return BrowserActionDragData::AreDropTypesRequired();
285 bool ChevronMenuButton::MenuController::CanDrop(
286 views::MenuItemView* menu, const OSExchangeData& data) {
287 return BrowserActionDragData::CanDrop(
288 data, browser_actions_container_->browser()->profile());
291 int ChevronMenuButton::MenuController::GetDropOperation(
292 views::MenuItemView* item,
293 const ui::DropTargetEvent& event,
294 DropPosition* position) {
295 // Don't allow dropping from the BrowserActionContainer into slot 0 of the
296 // overflow menu since once the move has taken place the item you are dragging
297 // falls right out of the menu again once the user releases the button
298 // (because we don't shrink the BrowserActionContainer when you do this).
299 if ((item->GetCommand() == 0) && (*position == DROP_BEFORE)) {
300 BrowserActionDragData drop_data;
301 if (!drop_data.Read(event.data()))
302 return ui::DragDropTypes::DRAG_NONE;
304 if (drop_data.index() < browser_actions_container_->VisibleBrowserActions())
305 return ui::DragDropTypes::DRAG_NONE;
308 return ui::DragDropTypes::DRAG_MOVE;
311 int ChevronMenuButton::MenuController::OnPerformDrop(
312 views::MenuItemView* menu,
313 DropPosition position,
314 const ui::DropTargetEvent& event) {
315 BrowserActionDragData drop_data;
316 if (!drop_data.Read(event.data()))
317 return ui::DragDropTypes::DRAG_NONE;
319 size_t drop_index = IndexForId(menu->GetCommand());
321 // When not dragging within the overflow menu (dragging an icon into the menu)
322 // subtract one to get the right index.
323 if (position == DROP_BEFORE &&
324 drop_data.index() < browser_actions_container_->VisibleBrowserActions())
325 --drop_index;
327 ToolbarActionsBar::DragType drag_type =
328 drop_data.index() < browser_actions_container_->VisibleBrowserActions() ?
329 ToolbarActionsBar::DRAG_TO_OVERFLOW :
330 ToolbarActionsBar::DRAG_TO_SAME;
331 browser_actions_container_->toolbar_actions_bar()->OnDragDrop(
332 drop_data.index(), drop_index, drag_type);
334 if (for_drop_)
335 owner_->MenuDone();
336 return ui::DragDropTypes::DRAG_MOVE;
339 bool ChevronMenuButton::MenuController::CanDrag(views::MenuItemView* menu) {
340 return true;
343 void ChevronMenuButton::MenuController::WriteDragData(
344 views::MenuItemView* sender, OSExchangeData* data) {
345 size_t drag_index = IndexForId(sender->GetCommand());
346 BrowserActionDragData drag_data(
347 browser_actions_container_->GetIdAt(drag_index), drag_index);
348 drag_data.Write(browser_actions_container_->browser()->profile(), data);
351 int ChevronMenuButton::MenuController::GetDragOperations(
352 views::MenuItemView* sender) {
353 return ui::DragDropTypes::DRAG_MOVE;
356 size_t ChevronMenuButton::MenuController::IndexForId(int id) const {
357 // The index of the view being dragged (GetCommand gives a 1-based index into
358 // the overflow menu).
359 DCHECK_GT(browser_actions_container_->VisibleBrowserActions() + id, 0u);
360 return browser_actions_container_->VisibleBrowserActions() + id - 1;
363 ChevronMenuButton::ChevronMenuButton(
364 BrowserActionsContainer* browser_actions_container)
365 : views::MenuButton(NULL, base::string16(), this, false),
366 browser_actions_container_(browser_actions_container),
367 weak_factory_(this) {
370 ChevronMenuButton::~ChevronMenuButton() {
373 void ChevronMenuButton::CloseMenu() {
374 if (menu_controller_)
375 menu_controller_->CloseMenu();
378 scoped_ptr<views::LabelButtonBorder> ChevronMenuButton::CreateDefaultBorder()
379 const {
380 // The chevron resource was designed to not have any insets.
381 scoped_ptr<views::LabelButtonBorder> border =
382 views::MenuButton::CreateDefaultBorder();
383 border->set_insets(gfx::Insets());
384 return border.Pass();
387 bool ChevronMenuButton::GetDropFormats(
388 int* formats,
389 std::set<OSExchangeData::CustomFormat>* custom_formats) {
390 return BrowserActionDragData::GetDropFormats(custom_formats);
393 bool ChevronMenuButton::AreDropTypesRequired() {
394 return BrowserActionDragData::AreDropTypesRequired();
397 bool ChevronMenuButton::CanDrop(const OSExchangeData& data) {
398 return BrowserActionDragData::CanDrop(
399 data, browser_actions_container_->browser()->profile());
402 void ChevronMenuButton::OnDragEntered(const ui::DropTargetEvent& event) {
403 DCHECK(!weak_factory_.HasWeakPtrs());
404 if (!menu_controller_) {
405 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
406 FROM_HERE, base::Bind(&ChevronMenuButton::ShowOverflowMenu,
407 weak_factory_.GetWeakPtr(), true),
408 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
412 int ChevronMenuButton::OnDragUpdated(const ui::DropTargetEvent& event) {
413 return ui::DragDropTypes::DRAG_MOVE;
416 void ChevronMenuButton::OnDragExited() {
417 weak_factory_.InvalidateWeakPtrs();
420 int ChevronMenuButton::OnPerformDrop(const ui::DropTargetEvent& event) {
421 weak_factory_.InvalidateWeakPtrs();
422 return ui::DragDropTypes::DRAG_MOVE;
425 void ChevronMenuButton::OnMenuButtonClicked(views::View* source,
426 const gfx::Point& point) {
427 DCHECK_EQ(this, source);
428 // The menu could already be open if a user dragged an item over it but
429 // ultimately dropped elsewhere (as in that case the menu will close on a
430 // timer). In this case, the click should close the open menu.
431 if (menu_controller_)
432 menu_controller_->CloseMenu();
433 else
434 ShowOverflowMenu(false);
437 void ChevronMenuButton::ShowOverflowMenu(bool for_drop) {
438 // We should never try to show an overflow menu when one is already visible.
439 DCHECK(!menu_controller_);
440 menu_controller_.reset(new MenuController(
441 this, browser_actions_container_, for_drop));
442 menu_controller_->RunMenu(GetWidget());
445 void ChevronMenuButton::MenuDone() {
446 menu_controller_.reset();