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