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