1 // Copyright (c) 2012 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/extensions/browser_action_overflow_menu_controller.h"
7 #include "base/message_loop/message_loop.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/extensions/extension_action.h"
10 #include "chrome/browser/extensions/extension_action_manager.h"
11 #include "chrome/browser/extensions/extension_context_menu_model.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser_list.h"
14 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
15 #include "chrome/browser/ui/views/toolbar/browser_action_view.h"
16 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
17 #include "extensions/common/extension.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/views/controls/menu/menu_item_view.h"
20 #include "ui/views/controls/menu/menu_runner.h"
21 #include "ui/views/controls/menu/submenu_view.h"
22 #include "ui/views/widget/widget.h"
24 // In the browser actions container's chevron menu, a menu item view's icon
25 // comes from BrowserActionView::GetIconWithBadge() (which comes from the
26 // browser action button's icon) when the menu item view is created. But, the
27 // browser action button's icon may not be loaded in time because it is read
28 // from file system in another thread.
29 // The IconUpdater will update the menu item view's icon when the browser
30 // action button's icon has been updated.
31 class IconUpdater
: public BrowserActionButton::IconObserver
{
33 IconUpdater(views::MenuItemView
* menu_item_view
,
34 BrowserActionButton
* button
)
35 : menu_item_view_(menu_item_view
),
37 DCHECK(menu_item_view
);
39 button
->set_icon_observer(this);
41 virtual ~IconUpdater() {
42 button_
->set_icon_observer(NULL
);
45 // Overridden from BrowserActionButton::IconObserver:
46 virtual void OnIconUpdated(const gfx::ImageSkia
& icon
) OVERRIDE
{
47 menu_item_view_
->SetIcon(icon
);
51 // The menu item view whose icon might be updated.
52 views::MenuItemView
* menu_item_view_
;
54 // The button to be observed. When its icon changes, update the corresponding
55 // menu item view's icon.
56 BrowserActionButton
* button_
;
58 DISALLOW_COPY_AND_ASSIGN(IconUpdater
);
61 BrowserActionOverflowMenuController::BrowserActionOverflowMenuController(
62 BrowserActionsContainer
* owner
,
64 views::MenuButton
* menu_button
,
65 const std::vector
<BrowserActionView
*>& views
,
70 menu_button_(menu_button
),
73 start_index_(start_index
),
75 menu_
= new views::MenuItemView(this);
76 menu_runner_
.reset(new views::MenuRunner(menu_
));
77 menu_
->set_has_icons(true);
79 size_t command_id
= 1; // Menu id 0 is reserved, start with 1.
80 for (size_t i
= start_index
; i
< views_
->size(); ++i
) {
81 BrowserActionView
* view
= (*views_
)[i
];
82 views::MenuItemView
* menu_item
= menu_
->AppendMenuItemWithIcon(
84 base::UTF8ToUTF16(view
->button()->extension()->name()),
85 view
->GetIconWithBadge());
87 // Set the tooltip for this item.
88 base::string16 tooltip
= base::UTF8ToUTF16(
89 extensions::ExtensionActionManager::Get(owner_
->profile())->
90 GetBrowserAction(*view
->button()->extension())->
91 GetTitle(owner_
->GetCurrentTabId()));
92 menu_
->SetTooltip(tooltip
, command_id
);
94 icon_updaters_
.push_back(new IconUpdater(menu_item
, view
->button()));
100 BrowserActionOverflowMenuController::~BrowserActionOverflowMenuController() {
102 observer_
->NotifyMenuDeleted(this);
105 bool BrowserActionOverflowMenuController::RunMenu(views::Widget
* window
,
107 for_drop_
= for_drop
;
109 gfx::Rect bounds
= menu_button_
->bounds();
110 gfx::Point screen_loc
;
111 views::View::ConvertPointToScreen(menu_button_
, &screen_loc
);
112 bounds
.set_x(screen_loc
.x());
113 bounds
.set_y(screen_loc
.y());
115 views::MenuAnchorPosition anchor
= views::MENU_ANCHOR_TOPRIGHT
;
116 // As we maintain our own lifetime we can safely ignore the result.
117 ignore_result(menu_runner_
->RunMenuAt(window
, menu_button_
, bounds
, anchor
,
118 ui::MENU_SOURCE_NONE
, for_drop_
? views::MenuRunner::FOR_DROP
: 0));
120 // Give the context menu (if any) a chance to execute the user-selected
122 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
127 void BrowserActionOverflowMenuController::CancelMenu() {
131 bool BrowserActionOverflowMenuController::IsCommandEnabled(int id
) const {
132 BrowserActionView
* view
= (*views_
)[start_index_
+ id
- 1];
133 return view
->button()->IsEnabled(owner_
->GetCurrentTabId());
136 void BrowserActionOverflowMenuController::ExecuteCommand(int id
) {
137 BrowserActionView
* view
= (*views_
)[start_index_
+ id
- 1];
138 owner_
->OnBrowserActionExecuted(view
->button());
141 bool BrowserActionOverflowMenuController::ShowContextMenu(
142 views::MenuItemView
* source
,
145 ui::MenuSourceType source_type
) {
146 const extensions::Extension
* extension
=
147 (*views_
)[start_index_
+ id
- 1]->button()->extension();
148 if (!extension
->ShowConfigureContextMenus())
151 scoped_refptr
<ExtensionContextMenuModel
> context_menu_contents
=
152 new ExtensionContextMenuModel(extension
, browser_
, owner_
);
153 views::MenuRunner
context_menu_runner(context_menu_contents
.get());
155 // We can ignore the result as we delete ourself.
156 // This blocks until the user choses something or dismisses the menu.
157 ignore_result(context_menu_runner
.RunMenuAt(
158 menu_button_
->GetWidget(),
160 gfx::Rect(p
, gfx::Size()),
161 views::MENU_ANCHOR_TOPLEFT
,
163 views::MenuRunner::HAS_MNEMONICS
| views::MenuRunner::IS_NESTED
|
164 views::MenuRunner::CONTEXT_MENU
));
166 // The user is done with the context menu, so we can close the underlying
173 void BrowserActionOverflowMenuController::DropMenuClosed(
174 views::MenuItemView
* menu
) {
178 bool BrowserActionOverflowMenuController::GetDropFormats(
179 views::MenuItemView
* menu
,
181 std::set
<OSExchangeData::CustomFormat
>* custom_formats
) {
182 custom_formats
->insert(BrowserActionDragData::GetBrowserActionCustomFormat());
186 bool BrowserActionOverflowMenuController::AreDropTypesRequired(
187 views::MenuItemView
* menu
) {
191 bool BrowserActionOverflowMenuController::CanDrop(
192 views::MenuItemView
* menu
, const OSExchangeData
& data
) {
193 BrowserActionDragData drop_data
;
194 if (!drop_data
.Read(data
))
196 return drop_data
.IsFromProfile(owner_
->profile());
199 int BrowserActionOverflowMenuController::GetDropOperation(
200 views::MenuItemView
* item
,
201 const ui::DropTargetEvent
& event
,
202 DropPosition
* position
) {
203 // Don't allow dropping from the BrowserActionContainer into slot 0 of the
204 // overflow menu since once the move has taken place the item you are dragging
205 // falls right out of the menu again once the user releases the button
206 // (because we don't shrink the BrowserActionContainer when you do this).
207 if ((item
->GetCommand() == 0) && (*position
== DROP_BEFORE
)) {
208 BrowserActionDragData drop_data
;
209 if (!drop_data
.Read(event
.data()))
210 return ui::DragDropTypes::DRAG_NONE
;
212 if (drop_data
.index() < owner_
->VisibleBrowserActions())
213 return ui::DragDropTypes::DRAG_NONE
;
216 return ui::DragDropTypes::DRAG_MOVE
;
219 int BrowserActionOverflowMenuController::OnPerformDrop(
220 views::MenuItemView
* menu
,
221 DropPosition position
,
222 const ui::DropTargetEvent
& event
) {
223 BrowserActionDragData drop_data
;
224 if (!drop_data
.Read(event
.data()))
225 return ui::DragDropTypes::DRAG_NONE
;
228 ViewForId(menu
->GetCommand(), &drop_index
);
230 // When not dragging within the overflow menu (dragging an icon into the menu)
231 // subtract one to get the right index.
232 if (position
== DROP_BEFORE
&&
233 drop_data
.index() < owner_
->VisibleBrowserActions())
236 owner_
->MoveBrowserAction(drop_data
.id(), drop_index
);
240 return ui::DragDropTypes::DRAG_MOVE
;
243 bool BrowserActionOverflowMenuController::CanDrag(views::MenuItemView
* menu
) {
247 void BrowserActionOverflowMenuController::WriteDragData(
248 views::MenuItemView
* sender
, OSExchangeData
* data
) {
250 BrowserActionView
* view
= ViewForId(sender
->GetCommand(), &drag_index
);
251 std::string id
= view
->button()->extension()->id();
253 BrowserActionDragData
drag_data(id
, drag_index
);
254 drag_data
.Write(owner_
->profile(), data
);
257 int BrowserActionOverflowMenuController::GetDragOperations(
258 views::MenuItemView
* sender
) {
259 return ui::DragDropTypes::DRAG_MOVE
;
262 BrowserActionView
* BrowserActionOverflowMenuController::ViewForId(
263 int id
, size_t* index
) {
264 // The index of the view being dragged (GetCommand gives a 1-based index into
265 // the overflow menu).
266 size_t view_index
= owner_
->VisibleBrowserActions() + id
- 1;
269 return owner_
->GetBrowserActionViewAt(view_index
);