Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
|
Devlin
2014/09/24 18:39:49
Much of this file is similar to browser_action_ove
| |
| 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 (showing the menu, | |
| 68 // drag and drop, etc). This class manages its own lifetime. | |
|
Finnur
2014/09/25 09:58:49
Oh yeah? Why is it a scoped_ptr then? :)
This look
Devlin
2014/09/25 15:56:49
Whoops, done.
| |
| 69 class ChevronMenuButton::MenuController : public views::MenuDelegate { | |
| 70 public: | |
| 71 MenuController(ChevronMenuButton* owner, | |
| 72 BrowserActionsContainer* browser_actions_container, | |
| 73 bool for_drop); | |
| 74 virtual ~MenuController(); | |
| 75 | |
| 76 // Shows the overflow menu. | |
| 77 void RunMenu(views::Widget* widget); | |
| 78 | |
| 79 // Closes the overflow menu (and its context menu if open as well). | |
| 80 void CloseMenu(); | |
| 81 | |
| 82 private: | |
| 83 // Overridden from views::MenuDelegate: | |
| 84 virtual bool IsCommandEnabled(int id) const OVERRIDE; | |
| 85 virtual void ExecuteCommand(int id) OVERRIDE; | |
| 86 virtual bool ShowContextMenu(views::MenuItemView* source, | |
| 87 int id, | |
| 88 const gfx::Point& p, | |
| 89 ui::MenuSourceType source_type) OVERRIDE; | |
| 90 virtual void DropMenuClosed(views::MenuItemView* menu) OVERRIDE; | |
| 91 // These drag functions offer support for dragging icons into the overflow | |
| 92 // menu. | |
| 93 virtual bool GetDropFormats( | |
| 94 views::MenuItemView* menu, | |
| 95 int* formats, | |
| 96 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) OVERRIDE; | |
| 97 virtual bool AreDropTypesRequired(views::MenuItemView* menu) OVERRIDE; | |
| 98 virtual bool CanDrop(views::MenuItemView* menu, | |
| 99 const ui::OSExchangeData& data) OVERRIDE; | |
| 100 virtual int GetDropOperation(views::MenuItemView* item, | |
| 101 const ui::DropTargetEvent& event, | |
| 102 DropPosition* position) OVERRIDE; | |
| 103 virtual int OnPerformDrop(views::MenuItemView* menu, | |
| 104 DropPosition position, | |
| 105 const ui::DropTargetEvent& event) OVERRIDE; | |
| 106 // These three drag functions offer support for dragging icons out of the | |
| 107 // overflow menu. | |
| 108 virtual bool CanDrag(views::MenuItemView* menu) OVERRIDE; | |
| 109 virtual void WriteDragData(views::MenuItemView* sender, | |
| 110 ui::OSExchangeData* data) OVERRIDE; | |
| 111 virtual int GetDragOperations(views::MenuItemView* sender) OVERRIDE; | |
| 112 | |
| 113 // Returns the offset into |views_| for the given |id|. | |
| 114 size_t IndexForId(int id) const; | |
| 115 | |
| 116 // The owning ChevronMenuButton. | |
| 117 ChevronMenuButton* owner_; | |
| 118 | |
| 119 // A pointer to the browser action container. | |
| 120 BrowserActionsContainer* browser_actions_container_; | |
| 121 | |
| 122 // The overflow menu for the menu button. Owned by |menu_runner_|. | |
| 123 views::MenuItemView* menu_; | |
| 124 | |
| 125 // Resposible for running the menu. | |
| 126 scoped_ptr<views::MenuRunner> menu_runner_; | |
| 127 | |
| 128 // The index into the BrowserActionView vector, indicating where to start | |
| 129 // picking browser actions to draw. | |
| 130 int start_index_; | |
| 131 | |
| 132 // Whether this controller is being used for drop. | |
| 133 bool for_drop_; | |
| 134 | |
| 135 // The vector keeps all icon updaters associated with menu item views in the | |
| 136 // controller. The icon updater will update the menu item view's icon when | |
| 137 // the browser action view's icon has been updated. | |
| 138 ScopedVector<IconUpdater> icon_updaters_; | |
| 139 | |
| 140 DISALLOW_COPY_AND_ASSIGN(MenuController); | |
| 141 }; | |
| 142 | |
| 143 ChevronMenuButton::MenuController::MenuController( | |
| 144 ChevronMenuButton* owner, | |
| 145 BrowserActionsContainer* browser_actions_container, | |
| 146 bool for_drop) | |
| 147 : owner_(owner), | |
| 148 browser_actions_container_(browser_actions_container), | |
| 149 menu_(NULL), | |
| 150 start_index_( | |
| 151 browser_actions_container_->VisibleBrowserActionsAfterAnimation()), | |
| 152 for_drop_(for_drop) { | |
| 153 menu_ = new views::MenuItemView(this); | |
| 154 menu_runner_.reset(new views::MenuRunner( | |
| 155 menu_, for_drop_ ? views::MenuRunner::FOR_DROP : 0)); | |
| 156 menu_->set_has_icons(true); | |
| 157 | |
| 158 size_t command_id = 1; // Menu id 0 is reserved, start with 1. | |
| 159 for (size_t i = start_index_; | |
| 160 i < browser_actions_container_->num_browser_actions(); ++i) { | |
| 161 BrowserActionView* view = | |
| 162 browser_actions_container_->GetBrowserActionViewAt(i); | |
| 163 views::MenuItemView* menu_item = menu_->AppendMenuItemWithIcon( | |
| 164 command_id, | |
| 165 base::UTF8ToUTF16(view->extension()->name()), | |
| 166 view->GetIconWithBadge()); | |
| 167 | |
| 168 // Set the tooltip for this item. | |
| 169 base::string16 tooltip = base::UTF8ToUTF16( | |
| 170 view->extension_action()->GetTitle( | |
| 171 view->view_controller()->GetCurrentTabId())); | |
| 172 menu_->SetTooltip(tooltip, command_id); | |
| 173 | |
| 174 icon_updaters_.push_back(new IconUpdater(menu_item, view)); | |
| 175 | |
| 176 ++command_id; | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 ChevronMenuButton::MenuController::~MenuController() { | |
| 181 } | |
| 182 | |
| 183 void ChevronMenuButton::MenuController::RunMenu(views::Widget* window) { | |
| 184 gfx::Rect bounds = owner_->bounds(); | |
| 185 gfx::Point screen_loc; | |
| 186 views::View::ConvertPointToScreen(owner_, &screen_loc); | |
| 187 bounds.set_x(screen_loc.x()); | |
| 188 bounds.set_y(screen_loc.y()); | |
| 189 | |
| 190 if (menu_runner_->RunMenuAt(window, | |
| 191 owner_, | |
| 192 bounds, | |
| 193 views::MENU_ANCHOR_TOPRIGHT, | |
| 194 ui::MENU_SOURCE_NONE) == | |
| 195 views::MenuRunner::MENU_DELETED) | |
| 196 return; | |
| 197 | |
| 198 if (!for_drop_) { | |
| 199 // Give the context menu (if any) a chance to execute the user-selected | |
| 200 // command. | |
| 201 base::MessageLoop::current()->PostTask( | |
| 202 FROM_HERE, | |
| 203 base::Bind(&ChevronMenuButton::MenuDone, | |
| 204 owner_->weak_factory_.GetWeakPtr())); | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 void ChevronMenuButton::MenuController::CloseMenu() { | |
| 209 menu_->Cancel(); | |
| 210 } | |
| 211 | |
| 212 bool ChevronMenuButton::MenuController::IsCommandEnabled(int id) const { | |
| 213 BrowserActionView* view = | |
| 214 browser_actions_container_->GetBrowserActionViewAt(start_index_ + id - 1); | |
| 215 return view->IsEnabled(view->view_controller()->GetCurrentTabId()); | |
| 216 } | |
| 217 | |
| 218 void ChevronMenuButton::MenuController::ExecuteCommand(int id) { | |
| 219 browser_actions_container_->GetBrowserActionViewAt(start_index_ + id - 1)-> | |
| 220 view_controller()->ExecuteActionByUser(); | |
| 221 } | |
| 222 | |
| 223 bool ChevronMenuButton::MenuController::ShowContextMenu( | |
| 224 views::MenuItemView* source, | |
| 225 int id, | |
| 226 const gfx::Point& p, | |
| 227 ui::MenuSourceType source_type) { | |
| 228 BrowserActionView* view = browser_actions_container_->GetBrowserActionViewAt( | |
| 229 start_index_ + id - 1); | |
| 230 if (!view->extension()->ShowConfigureContextMenus()) | |
| 231 return false; | |
| 232 | |
| 233 scoped_refptr<ExtensionContextMenuModel> context_menu_contents = | |
| 234 new ExtensionContextMenuModel(view->extension(), | |
| 235 view->view_controller()->browser(), | |
| 236 view->view_controller()); | |
| 237 views::MenuRunner context_menu_runner(context_menu_contents.get(), | |
| 238 views::MenuRunner::HAS_MNEMONICS | | |
| 239 views::MenuRunner::IS_NESTED | | |
| 240 views::MenuRunner::CONTEXT_MENU); | |
| 241 | |
| 242 // We can ignore the result as we delete ourself. | |
| 243 // This blocks until the user chooses something or dismisses the menu. | |
| 244 if (context_menu_runner.RunMenuAt(owner_->GetWidget(), | |
| 245 NULL, | |
| 246 gfx::Rect(p, gfx::Size()), | |
| 247 views::MENU_ANCHOR_TOPLEFT, | |
| 248 source_type) == | |
| 249 views::MenuRunner::MENU_DELETED) | |
| 250 return true; | |
| 251 | |
| 252 // The user is done with the context menu, so we can close the underlying | |
| 253 // menu. | |
| 254 menu_->Cancel(); | |
| 255 | |
| 256 return true; | |
| 257 } | |
| 258 | |
| 259 void ChevronMenuButton::MenuController::DropMenuClosed( | |
| 260 views::MenuItemView* menu) { | |
| 261 owner_->MenuDone(); | |
| 262 } | |
| 263 | |
| 264 bool ChevronMenuButton::MenuController::GetDropFormats( | |
| 265 views::MenuItemView* menu, | |
| 266 int* formats, | |
| 267 std::set<OSExchangeData::CustomFormat>* custom_formats) { | |
| 268 return BrowserActionDragData::GetDropFormats(custom_formats); | |
| 269 } | |
| 270 | |
| 271 bool ChevronMenuButton::MenuController::AreDropTypesRequired( | |
| 272 views::MenuItemView* menu) { | |
| 273 return BrowserActionDragData::AreDropTypesRequired(); | |
| 274 } | |
| 275 | |
| 276 bool ChevronMenuButton::MenuController::CanDrop( | |
| 277 views::MenuItemView* menu, const OSExchangeData& data) { | |
| 278 return BrowserActionDragData::CanDrop(data, | |
| 279 browser_actions_container_->profile()); | |
| 280 } | |
| 281 | |
| 282 int ChevronMenuButton::MenuController::GetDropOperation( | |
| 283 views::MenuItemView* item, | |
| 284 const ui::DropTargetEvent& event, | |
| 285 DropPosition* position) { | |
| 286 // Don't allow dropping from the BrowserActionContainer into slot 0 of the | |
| 287 // overflow menu since once the move has taken place the item you are dragging | |
| 288 // falls right out of the menu again once the user releases the button | |
| 289 // (because we don't shrink the BrowserActionContainer when you do this). | |
| 290 if ((item->GetCommand() == 0) && (*position == DROP_BEFORE)) { | |
| 291 BrowserActionDragData drop_data; | |
| 292 if (!drop_data.Read(event.data())) | |
| 293 return ui::DragDropTypes::DRAG_NONE; | |
| 294 | |
| 295 if (drop_data.index() < browser_actions_container_->VisibleBrowserActions()) | |
| 296 return ui::DragDropTypes::DRAG_NONE; | |
| 297 } | |
| 298 | |
| 299 return ui::DragDropTypes::DRAG_MOVE; | |
| 300 } | |
| 301 | |
| 302 int ChevronMenuButton::MenuController::OnPerformDrop( | |
| 303 views::MenuItemView* menu, | |
| 304 DropPosition position, | |
| 305 const ui::DropTargetEvent& event) { | |
| 306 BrowserActionDragData drop_data; | |
| 307 if (!drop_data.Read(event.data())) | |
| 308 return ui::DragDropTypes::DRAG_NONE; | |
| 309 | |
| 310 size_t drop_index = IndexForId(menu->GetCommand()); | |
| 311 | |
| 312 // When not dragging within the overflow menu (dragging an icon into the menu) | |
| 313 // subtract one to get the right index. | |
| 314 if (position == DROP_BEFORE && | |
| 315 drop_data.index() < browser_actions_container_->VisibleBrowserActions()) | |
| 316 --drop_index; | |
| 317 | |
| 318 Profile* profile = browser_actions_container_->profile(); | |
| 319 // Move the extension in the model. | |
| 320 const extensions::Extension* extension = | |
| 321 extensions::ExtensionRegistry::Get(profile)-> | |
| 322 enabled_extensions().GetByID(drop_data.id()); | |
| 323 extensions::ExtensionToolbarModel* toolbar_model = | |
| 324 extensions::ExtensionToolbarModel::Get(profile); | |
| 325 if (profile->IsOffTheRecord()) | |
| 326 drop_index = toolbar_model->IncognitoIndexToOriginal(drop_index); | |
| 327 toolbar_model->MoveExtensionIcon(extension, drop_index); | |
| 328 | |
| 329 // If the extension was moved to the overflow menu from the main bar, notify | |
| 330 // the owner. | |
| 331 if (drop_data.index() < browser_actions_container_->VisibleBrowserActions()) | |
| 332 browser_actions_container_->NotifyActionMovedToOverflow(); | |
| 333 | |
| 334 if (for_drop_) | |
| 335 owner_->MenuDone(); | |
| 336 return ui::DragDropTypes::DRAG_MOVE; | |
| 337 } | |
| 338 | |
| 339 bool ChevronMenuButton::MenuController::CanDrag(views::MenuItemView* menu) { | |
| 340 return true; | |
| 341 } | |
| 342 | |
| 343 void ChevronMenuButton::MenuController::WriteDragData( | |
| 344 views::MenuItemView* sender, OSExchangeData* data) { | |
| 345 size_t drag_index = IndexForId(sender->GetCommand()); | |
| 346 const extensions::Extension* extension = | |
| 347 browser_actions_container_->GetBrowserActionViewAt(drag_index)-> | |
| 348 extension(); | |
| 349 BrowserActionDragData drag_data(extension->id(), drag_index); | |
| 350 drag_data.Write(browser_actions_container_->profile(), data); | |
| 351 } | |
| 352 | |
| 353 int ChevronMenuButton::MenuController::GetDragOperations( | |
| 354 views::MenuItemView* sender) { | |
| 355 return ui::DragDropTypes::DRAG_MOVE; | |
| 356 } | |
| 357 | |
| 358 size_t ChevronMenuButton::MenuController::IndexForId(int id) const { | |
| 359 // The index of the view being dragged (GetCommand gives a 1-based index into | |
| 360 // the overflow menu). | |
| 361 DCHECK_GT(browser_actions_container_->VisibleBrowserActions() + id, 0u); | |
| 362 return browser_actions_container_->VisibleBrowserActions() + id - 1; | |
| 363 } | |
| 364 | |
| 365 ChevronMenuButton::ChevronMenuButton( | |
| 366 BrowserActionsContainer* browser_actions_container) | |
| 367 : views::MenuButton(NULL, base::string16(), this, false), | |
| 368 browser_actions_container_(browser_actions_container), | |
| 369 weak_factory_(this) { | |
| 370 } | |
| 371 | |
| 372 ChevronMenuButton::~ChevronMenuButton() { | |
| 373 } | |
| 374 | |
| 375 void ChevronMenuButton::CloseMenu() { | |
| 376 if (menu_controller_.get()) | |
| 377 menu_controller_->CloseMenu(); | |
| 378 } | |
| 379 | |
| 380 scoped_ptr<views::LabelButtonBorder> ChevronMenuButton::CreateDefaultBorder() | |
| 381 const { | |
| 382 // The chevron resource was designed to not have any insets. | |
| 383 scoped_ptr<views::LabelButtonBorder> border = | |
| 384 views::MenuButton::CreateDefaultBorder(); | |
| 385 border->set_insets(gfx::Insets()); | |
| 386 return border.Pass(); | |
| 387 } | |
| 388 | |
| 389 bool ChevronMenuButton::GetDropFormats( | |
| 390 int* formats, | |
| 391 std::set<OSExchangeData::CustomFormat>* custom_formats) { | |
| 392 return BrowserActionDragData::GetDropFormats(custom_formats); | |
| 393 } | |
| 394 | |
| 395 bool ChevronMenuButton::AreDropTypesRequired() { | |
| 396 return BrowserActionDragData::AreDropTypesRequired(); | |
| 397 } | |
| 398 | |
| 399 bool ChevronMenuButton::CanDrop(const OSExchangeData& data) { | |
| 400 return BrowserActionDragData::CanDrop( | |
| 401 data, browser_actions_container_->profile()); | |
| 402 } | |
| 403 | |
| 404 void ChevronMenuButton::OnDragEntered(const ui::DropTargetEvent& event) { | |
| 405 DCHECK(!weak_factory_.HasWeakPtrs()); | |
| 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 int ChevronMenuButton::OnDragUpdated(const ui::DropTargetEvent& event) { | |
| 415 return ui::DragDropTypes::DRAG_MOVE; | |
| 416 } | |
| 417 | |
| 418 void ChevronMenuButton::OnDragExited() { | |
| 419 weak_factory_.InvalidateWeakPtrs(); | |
| 420 } | |
| 421 | |
| 422 int ChevronMenuButton::OnPerformDrop(const ui::DropTargetEvent& event) { | |
| 423 return ui::DragDropTypes::DRAG_MOVE; | |
| 424 } | |
| 425 | |
| 426 void ChevronMenuButton::OnMenuButtonClicked(views::View* source, | |
| 427 const gfx::Point& point) { | |
| 428 DCHECK_EQ(this, source); | |
| 429 ShowOverflowMenu(false); | |
| 430 } | |
| 431 | |
| 432 void ChevronMenuButton::ShowOverflowMenu(bool for_drop) { | |
| 433 DCHECK(!menu_controller_.get()); | |
| 434 menu_controller_.reset(new MenuController( | |
| 435 this, browser_actions_container_, for_drop)); | |
| 436 menu_controller_->RunMenu(GetWidget()); | |
| 437 } | |
| 438 | |
| 439 void ChevronMenuButton::MenuDone() { | |
| 440 menu_controller_.reset(); | |
| 441 } | |
| OLD | NEW |