1// Copyright (c) 2011 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/browser_action_overflow_menu_controller.h"
6
7#include "base/utf_string_conversions.h"
8#include "chrome/browser/extensions/extension_context_menu_model.h"
9#include "chrome/browser/profiles/profile.h"
10#include "chrome/browser/ui/browser_list.h"
11#include "chrome/browser/ui/views/browser_actions_container.h"
12#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
13#include "chrome/common/extensions/extension.h"
14#include "chrome/common/extensions/extension_action.h"
15#include "ui/gfx/canvas_skia.h"
16#include "views/controls/menu/menu_2.h"
17#include "views/controls/menu/menu_item_view.h"
18
19BrowserActionOverflowMenuController::BrowserActionOverflowMenuController(
20    BrowserActionsContainer* owner,
21    views::MenuButton* menu_button,
22    const std::vector<BrowserActionView*>& views,
23    int start_index)
24    : owner_(owner),
25      observer_(NULL),
26      menu_button_(menu_button),
27      views_(&views),
28      start_index_(start_index),
29      for_drop_(false) {
30  menu_.reset(new views::MenuItemView(this));
31  menu_->set_has_icons(true);
32
33  size_t command_id = 1;  // Menu id 0 is reserved, start with 1.
34  for (size_t i = start_index; i < views_->size(); ++i) {
35    BrowserActionView* view = (*views_)[i];
36    scoped_ptr<gfx::Canvas> canvas(view->GetIconWithBadge());
37    menu_->AppendMenuItemWithIcon(
38        command_id,
39        UTF8ToWide(view->button()->extension()->name()),
40        canvas->AsCanvasSkia()->ExtractBitmap());
41
42    // Set the tooltip for this item.
43    std::wstring tooltip = UTF8ToWide(
44        view->button()->extension()->browser_action()->GetTitle(
45            owner_->GetCurrentTabId()));
46    menu_->SetTooltip(tooltip, command_id);
47
48    ++command_id;
49  }
50}
51
52BrowserActionOverflowMenuController::~BrowserActionOverflowMenuController() {
53  if (observer_)
54    observer_->NotifyMenuDeleted(this);
55}
56
57bool BrowserActionOverflowMenuController::RunMenu(gfx::NativeWindow window,
58                                                  bool for_drop) {
59  for_drop_ = for_drop;
60
61  gfx::Rect bounds = menu_button_->bounds();
62  gfx::Point screen_loc;
63  views::View::ConvertPointToScreen(menu_button_, &screen_loc);
64  bounds.set_x(screen_loc.x());
65  bounds.set_y(screen_loc.y());
66
67  views::MenuItemView::AnchorPosition anchor = base::i18n::IsRTL() ?
68      views::MenuItemView::TOPLEFT : views::MenuItemView::TOPRIGHT;
69  if (for_drop) {
70    menu_->RunMenuForDropAt(window, bounds, anchor);
71  } else {
72    menu_->RunMenuAt(window, menu_button_, bounds, anchor, false);
73    // Give the context menu (if any) a chance to execute the user-selected
74    // command.
75    MessageLoop::current()->DeleteSoon(FROM_HERE, this);
76  }
77  return true;
78}
79
80void BrowserActionOverflowMenuController::CancelMenu() {
81  menu_->Cancel();
82}
83
84void BrowserActionOverflowMenuController::ExecuteCommand(int id) {
85  BrowserActionView* view = (*views_)[start_index_ + id - 1];
86  owner_->OnBrowserActionExecuted(view->button(),
87                                  false);  // inspect_with_devtools
88}
89
90bool BrowserActionOverflowMenuController::ShowContextMenu(
91    views::MenuItemView* source,
92    int id,
93    const gfx::Point& p,
94    bool is_mouse_gesture) {
95  const Extension* extension =
96      (*views_)[start_index_ + id - 1]->button()->extension();
97  if (!extension->ShowConfigureContextMenus())
98    return false;
99
100  context_menu_contents_ = new ExtensionContextMenuModel(
101      extension,
102      owner_->browser(),
103      owner_);
104  context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get()));
105  // This blocks until the user choses something or dismisses the menu.
106  context_menu_menu_->RunContextMenuAt(p);
107
108  // The user is done with the context menu, so we can close the underlying
109  // menu.
110  menu_->Cancel();
111
112  return true;
113}
114
115void BrowserActionOverflowMenuController::DropMenuClosed(
116    views::MenuItemView* menu) {
117  delete this;
118}
119
120bool BrowserActionOverflowMenuController::GetDropFormats(
121    views::MenuItemView* menu,
122    int* formats,
123    std::set<OSExchangeData::CustomFormat>* custom_formats) {
124  custom_formats->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
125  return true;
126}
127
128bool BrowserActionOverflowMenuController::AreDropTypesRequired(
129    views::MenuItemView* menu) {
130  return true;
131}
132
133bool BrowserActionOverflowMenuController::CanDrop(
134    views::MenuItemView* menu, const OSExchangeData& data) {
135  BrowserActionDragData drop_data;
136  if (!drop_data.Read(data))
137    return false;
138  return drop_data.IsFromProfile(owner_->profile());
139}
140
141int BrowserActionOverflowMenuController::GetDropOperation(
142    views::MenuItemView* item,
143    const views::DropTargetEvent& event,
144    DropPosition* position) {
145  // Don't allow dropping from the BrowserActionContainer into slot 0 of the
146  // overflow menu since once the move has taken place the item you are dragging
147  // falls right out of the menu again once the user releases the button
148  // (because we don't shrink the BrowserActionContainer when you do this).
149  if ((item->GetCommand() == 0) && (*position == DROP_BEFORE)) {
150    BrowserActionDragData drop_data;
151    if (!drop_data.Read(event.data()))
152      return ui::DragDropTypes::DRAG_NONE;
153
154    if (drop_data.index() < owner_->VisibleBrowserActions())
155      return ui::DragDropTypes::DRAG_NONE;
156  }
157
158  return ui::DragDropTypes::DRAG_MOVE;
159}
160
161int BrowserActionOverflowMenuController::OnPerformDrop(
162    views::MenuItemView* menu,
163    DropPosition position,
164    const views::DropTargetEvent& event) {
165  BrowserActionDragData drop_data;
166  if (!drop_data.Read(event.data()))
167    return ui::DragDropTypes::DRAG_NONE;
168
169  size_t drop_index;
170  ViewForId(menu->GetCommand(), &drop_index);
171
172  // When not dragging within the overflow menu (dragging an icon into the menu)
173  // subtract one to get the right index.
174  if (position == DROP_BEFORE &&
175      drop_data.index() < owner_->VisibleBrowserActions())
176    --drop_index;
177
178  owner_->MoveBrowserAction(drop_data.id(), drop_index);
179
180  if (for_drop_)
181    delete this;
182  return ui::DragDropTypes::DRAG_MOVE;
183}
184
185bool BrowserActionOverflowMenuController::CanDrag(views::MenuItemView* menu) {
186  return true;
187}
188
189void BrowserActionOverflowMenuController::WriteDragData(
190    views::MenuItemView* sender, OSExchangeData* data) {
191  size_t drag_index;
192  BrowserActionView* view = ViewForId(sender->GetCommand(), &drag_index);
193  std::string id = view->button()->extension()->id();
194
195  BrowserActionDragData drag_data(id, drag_index);
196  drag_data.Write(owner_->profile(), data);
197}
198
199int BrowserActionOverflowMenuController::GetDragOperations(
200    views::MenuItemView* sender) {
201  return ui::DragDropTypes::DRAG_MOVE;
202}
203
204BrowserActionView* BrowserActionOverflowMenuController::ViewForId(
205    int id, size_t* index) {
206  // The index of the view being dragged (GetCommand gives a 1-based index into
207  // the overflow menu).
208  size_t view_index = owner_->VisibleBrowserActions() + id - 1;
209  if (index)
210    *index = view_index;
211  return owner_->GetBrowserActionViewAt(view_index);
212}
213