Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(482)

Side by Side Diff: chrome/browser/ui/views/toolbar/chevron_menu_button.cc

Issue 597783002: Make the chevron menu button responsible for legacy overflow menu logic (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Latest master + comment Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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_context_menu_model.h"
12 #include "chrome/browser/extensions/extension_toolbar_model.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
16 #include "chrome/browser/ui/views/toolbar/browser_action_view.h"
17 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
18 #include "extensions/browser/extension_registry.h"
19 #include "extensions/common/extension.h"
20 #include "extensions/common/extension_set.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 BrowserActionView::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 BrowserActionView::IconObserver {
37 public:
38 IconUpdater(views::MenuItemView* menu_item_view, BrowserActionView* view)
39 : menu_item_view_(menu_item_view),
40 view_(view) {
41 DCHECK(menu_item_view);
42 DCHECK(view);
43 view->set_icon_observer(this);
44 }
45 virtual ~IconUpdater() {
46 view_->set_icon_observer(NULL);
47 }
48
49 // BrowserActionView::IconObserver:
50 virtual void OnIconUpdated(const gfx::ImageSkia& icon) OVERRIDE {
51 menu_item_view_->SetIcon(icon);
52 }
53
54 private:
55 // The menu item view whose icon might be updated.
56 views::MenuItemView* menu_item_view_;
57
58 // The view to be observed. When its icon changes, update the corresponding
59 // menu item view's icon.
60 BrowserActionView* view_;
61
62 DISALLOW_COPY_AND_ASSIGN(IconUpdater);
63 };
64
65 } // namespace
66
67 // This class handles the overflow menu for browser actions.
68 class ChevronMenuButton::MenuController : public views::MenuDelegate {
69 public:
70 MenuController(ChevronMenuButton* owner,
71 BrowserActionsContainer* browser_actions_container,
72 bool for_drop);
73 virtual ~MenuController();
74
75 // Shows the overflow menu.
76 void RunMenu(views::Widget* widget);
77
78 // Closes the overflow menu (and its context menu if open as well).
79 void CloseMenu();
80
81 private:
82 // views::MenuDelegate:
83 virtual bool IsCommandEnabled(int id) const OVERRIDE;
84 virtual void ExecuteCommand(int id) OVERRIDE;
85 virtual bool ShowContextMenu(views::MenuItemView* source,
86 int id,
87 const gfx::Point& p,
88 ui::MenuSourceType source_type) OVERRIDE;
89 virtual void DropMenuClosed(views::MenuItemView* menu) OVERRIDE;
90 // These drag functions offer support for dragging icons into the overflow
91 // menu.
92 virtual bool GetDropFormats(
93 views::MenuItemView* menu,
94 int* formats,
95 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
96 virtual bool AreDropTypesRequired(views::MenuItemView* menu) OVERRIDE;
97 virtual bool CanDrop(views::MenuItemView* menu,
98 const ui::OSExchangeData& data) OVERRIDE;
99 virtual int GetDropOperation(views::MenuItemView* item,
100 const ui::DropTargetEvent& event,
101 DropPosition* position) OVERRIDE;
102 virtual int OnPerformDrop(views::MenuItemView* menu,
103 DropPosition position,
104 const ui::DropTargetEvent& event) OVERRIDE;
105 // These three drag functions offer support for dragging icons out of the
106 // overflow menu.
107 virtual bool CanDrag(views::MenuItemView* menu) OVERRIDE;
108 virtual void WriteDragData(views::MenuItemView* sender,
109 ui::OSExchangeData* data) OVERRIDE;
110 virtual int GetDragOperations(views::MenuItemView* sender) OVERRIDE;
111
112 // Returns the offset into |views_| for the given |id|.
113 size_t IndexForId(int id) const;
114
115 // The owning ChevronMenuButton.
116 ChevronMenuButton* owner_;
117
118 // A pointer to the browser action container.
119 BrowserActionsContainer* browser_actions_container_;
120
121 // The overflow menu for the menu button. Owned by |menu_runner_|.
122 views::MenuItemView* menu_;
123
124 // Resposible for running the menu.
125 scoped_ptr<views::MenuRunner> menu_runner_;
126
127 // The index into the BrowserActionView vector, indicating where to start
128 // picking browser actions to draw.
129 int start_index_;
130
131 // Whether this controller is being used for drop.
132 bool for_drop_;
133
134 // The vector keeps all icon updaters associated with menu item views in the
135 // controller. The icon updater will update the menu item view's icon when
136 // the browser action view's icon has been updated.
137 ScopedVector<IconUpdater> icon_updaters_;
138
139 DISALLOW_COPY_AND_ASSIGN(MenuController);
140 };
141
142 ChevronMenuButton::MenuController::MenuController(
143 ChevronMenuButton* owner,
144 BrowserActionsContainer* browser_actions_container,
145 bool for_drop)
146 : owner_(owner),
147 browser_actions_container_(browser_actions_container),
148 menu_(NULL),
149 start_index_(
150 browser_actions_container_->VisibleBrowserActionsAfterAnimation()),
151 for_drop_(for_drop) {
152 menu_ = new views::MenuItemView(this);
153 menu_runner_.reset(new views::MenuRunner(
154 menu_, for_drop_ ? views::MenuRunner::FOR_DROP : 0));
155 menu_->set_has_icons(true);
156
157 size_t command_id = 1; // Menu id 0 is reserved, start with 1.
158 for (size_t i = start_index_;
159 i < browser_actions_container_->num_browser_actions(); ++i) {
160 BrowserActionView* view =
161 browser_actions_container_->GetBrowserActionViewAt(i);
162 views::MenuItemView* menu_item = menu_->AppendMenuItemWithIcon(
163 command_id,
164 base::UTF8ToUTF16(view->extension()->name()),
165 view->GetIconWithBadge());
166
167 // Set the tooltip for this item.
168 base::string16 tooltip = base::UTF8ToUTF16(
169 view->extension_action()->GetTitle(
170 view->view_controller()->GetCurrentTabId()));
171 menu_->SetTooltip(tooltip, command_id);
172
173 icon_updaters_.push_back(new IconUpdater(menu_item, view));
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 BrowserActionView* view =
213 browser_actions_container_->GetBrowserActionViewAt(start_index_ + id - 1);
214 return view->IsEnabled(view->view_controller()->GetCurrentTabId());
215 }
216
217 void ChevronMenuButton::MenuController::ExecuteCommand(int id) {
218 browser_actions_container_->GetBrowserActionViewAt(start_index_ + id - 1)->
219 view_controller()->ExecuteActionByUser();
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 BrowserActionView* view = browser_actions_container_->GetBrowserActionViewAt(
228 start_index_ + id - 1);
229 if (!view->extension()->ShowConfigureContextMenus())
230 return false;
231
232 scoped_refptr<ExtensionContextMenuModel> context_menu_contents =
233 new ExtensionContextMenuModel(view->extension(),
234 view->view_controller()->browser(),
235 view->view_controller());
236 views::MenuRunner context_menu_runner(context_menu_contents.get(),
237 views::MenuRunner::HAS_MNEMONICS |
238 views::MenuRunner::IS_NESTED |
239 views::MenuRunner::CONTEXT_MENU);
240
241 // We can ignore the result as we delete ourself.
242 // This blocks until the user chooses something or dismisses the menu.
243 if (context_menu_runner.RunMenuAt(owner_->GetWidget(),
244 NULL,
245 gfx::Rect(p, gfx::Size()),
246 views::MENU_ANCHOR_TOPLEFT,
247 source_type) ==
248 views::MenuRunner::MENU_DELETED)
249 return true;
250
251 // The user is done with the context menu, so we can close the underlying
252 // menu.
253 menu_->Cancel();
254
255 return true;
256 }
257
258 void ChevronMenuButton::MenuController::DropMenuClosed(
259 views::MenuItemView* menu) {
260 owner_->MenuDone();
261 }
262
263 bool ChevronMenuButton::MenuController::GetDropFormats(
264 views::MenuItemView* menu,
265 int* formats,
266 std::set<OSExchangeData::CustomFormat>* custom_formats) {
267 return BrowserActionDragData::GetDropFormats(custom_formats);
268 }
269
270 bool ChevronMenuButton::MenuController::AreDropTypesRequired(
271 views::MenuItemView* menu) {
272 return BrowserActionDragData::AreDropTypesRequired();
273 }
274
275 bool ChevronMenuButton::MenuController::CanDrop(
276 views::MenuItemView* menu, const OSExchangeData& data) {
277 return BrowserActionDragData::CanDrop(data,
278 browser_actions_container_->profile());
279 }
280
281 int ChevronMenuButton::MenuController::GetDropOperation(
282 views::MenuItemView* item,
283 const ui::DropTargetEvent& event,
284 DropPosition* position) {
285 // Don't allow dropping from the BrowserActionContainer into slot 0 of the
286 // overflow menu since once the move has taken place the item you are dragging
287 // falls right out of the menu again once the user releases the button
288 // (because we don't shrink the BrowserActionContainer when you do this).
289 if ((item->GetCommand() == 0) && (*position == DROP_BEFORE)) {
290 BrowserActionDragData drop_data;
291 if (!drop_data.Read(event.data()))
292 return ui::DragDropTypes::DRAG_NONE;
293
294 if (drop_data.index() < browser_actions_container_->VisibleBrowserActions())
295 return ui::DragDropTypes::DRAG_NONE;
296 }
297
298 return ui::DragDropTypes::DRAG_MOVE;
299 }
300
301 int ChevronMenuButton::MenuController::OnPerformDrop(
302 views::MenuItemView* menu,
303 DropPosition position,
304 const ui::DropTargetEvent& event) {
305 BrowserActionDragData drop_data;
306 if (!drop_data.Read(event.data()))
307 return ui::DragDropTypes::DRAG_NONE;
308
309 size_t drop_index = IndexForId(menu->GetCommand());
310
311 // When not dragging within the overflow menu (dragging an icon into the menu)
312 // subtract one to get the right index.
313 if (position == DROP_BEFORE &&
314 drop_data.index() < browser_actions_container_->VisibleBrowserActions())
315 --drop_index;
316
317 Profile* profile = browser_actions_container_->profile();
318 // Move the extension in the model.
319 const extensions::Extension* extension =
320 extensions::ExtensionRegistry::Get(profile)->
321 enabled_extensions().GetByID(drop_data.id());
322 extensions::ExtensionToolbarModel* toolbar_model =
323 extensions::ExtensionToolbarModel::Get(profile);
324 if (profile->IsOffTheRecord())
325 drop_index = toolbar_model->IncognitoIndexToOriginal(drop_index);
326 toolbar_model->MoveExtensionIcon(extension, drop_index);
327
328 // If the extension was moved to the overflow menu from the main bar, notify
329 // the owner.
330 if (drop_data.index() < browser_actions_container_->VisibleBrowserActions())
331 browser_actions_container_->NotifyActionMovedToOverflow();
332
333 if (for_drop_)
334 owner_->MenuDone();
335 return ui::DragDropTypes::DRAG_MOVE;
336 }
337
338 bool ChevronMenuButton::MenuController::CanDrag(views::MenuItemView* menu) {
339 return true;
340 }
341
342 void ChevronMenuButton::MenuController::WriteDragData(
343 views::MenuItemView* sender, OSExchangeData* data) {
344 size_t drag_index = IndexForId(sender->GetCommand());
345 const extensions::Extension* extension =
346 browser_actions_container_->GetBrowserActionViewAt(drag_index)->
347 extension();
348 BrowserActionDragData drag_data(extension->id(), drag_index);
349 drag_data.Write(browser_actions_container_->profile(), data);
350 }
351
352 int ChevronMenuButton::MenuController::GetDragOperations(
353 views::MenuItemView* sender) {
354 return ui::DragDropTypes::DRAG_MOVE;
355 }
356
357 size_t ChevronMenuButton::MenuController::IndexForId(int id) const {
358 // The index of the view being dragged (GetCommand gives a 1-based index into
359 // the overflow menu).
360 DCHECK_GT(browser_actions_container_->VisibleBrowserActions() + id, 0u);
361 return browser_actions_container_->VisibleBrowserActions() + id - 1;
362 }
363
364 ChevronMenuButton::ChevronMenuButton(
365 BrowserActionsContainer* browser_actions_container)
366 : views::MenuButton(NULL, base::string16(), this, false),
367 browser_actions_container_(browser_actions_container),
368 weak_factory_(this) {
369 }
370
371 ChevronMenuButton::~ChevronMenuButton() {
372 }
373
374 void ChevronMenuButton::CloseMenu() {
375 if (menu_controller_)
376 menu_controller_->CloseMenu();
377 }
378
379 scoped_ptr<views::LabelButtonBorder> ChevronMenuButton::CreateDefaultBorder()
380 const {
381 // The chevron resource was designed to not have any insets.
382 scoped_ptr<views::LabelButtonBorder> border =
383 views::MenuButton::CreateDefaultBorder();
384 border->set_insets(gfx::Insets());
385 return border.Pass();
386 }
387
388 bool ChevronMenuButton::GetDropFormats(
389 int* formats,
390 std::set<OSExchangeData::CustomFormat>* custom_formats) {
391 return BrowserActionDragData::GetDropFormats(custom_formats);
392 }
393
394 bool ChevronMenuButton::AreDropTypesRequired() {
395 return BrowserActionDragData::AreDropTypesRequired();
396 }
397
398 bool ChevronMenuButton::CanDrop(const OSExchangeData& data) {
399 return BrowserActionDragData::CanDrop(
400 data, browser_actions_container_->profile());
401 }
402
403 void ChevronMenuButton::OnDragEntered(const ui::DropTargetEvent& event) {
404 DCHECK(!weak_factory_.HasWeakPtrs());
405 if (!menu_controller_) {
406 base::MessageLoop::current()->PostDelayedTask(
407 FROM_HERE,
408 base::Bind(&ChevronMenuButton::ShowOverflowMenu,
409 weak_factory_.GetWeakPtr(),
410 true),
411 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
412 }
413 }
414
415 int ChevronMenuButton::OnDragUpdated(const ui::DropTargetEvent& event) {
416 return ui::DragDropTypes::DRAG_MOVE;
417 }
418
419 void ChevronMenuButton::OnDragExited() {
420 weak_factory_.InvalidateWeakPtrs();
421 }
422
423 int ChevronMenuButton::OnPerformDrop(const ui::DropTargetEvent& event) {
424 return ui::DragDropTypes::DRAG_MOVE;
425 }
426
427 void ChevronMenuButton::OnMenuButtonClicked(views::View* source,
428 const gfx::Point& point) {
429 DCHECK_EQ(this, source);
430 ShowOverflowMenu(false);
431 }
432
433 void ChevronMenuButton::ShowOverflowMenu(bool for_drop) {
434 DCHECK(!menu_controller_);
435 menu_controller_.reset(new MenuController(
436 this, browser_actions_container_, for_drop));
437 menu_controller_->RunMenu(GetWidget());
438 }
439
440 void ChevronMenuButton::MenuDone() {
441 menu_controller_.reset();
442 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698