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. 4 5 #include "chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.h" 6 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_context_menu_model.h" 11 #include "chrome/browser/extensions/extension_toolbar_model.h" 12 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/browser_list.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/controls/menu/menu_item_view.h" 22 #include "ui/views/controls/menu/menu_runner.h" 23 24 // In the browser actions container's chevron menu, a menu item view's icon 25 // comes from BrowserActionView::GetIconWithBadge() when the menu item view is 26 // created. But, the browser action's icon may not be loaded in time because it 27 // is read from file system in another thread. 28 // The IconUpdater will update the menu item view's icon when the browser 29 // action's icon has been updated. 30 class IconUpdater : public BrowserActionView::IconObserver { 31 public: 32 IconUpdater(views::MenuItemView* menu_item_view, BrowserActionView* view) 33 : menu_item_view_(menu_item_view), 34 view_(view) { 35 DCHECK(menu_item_view); 36 DCHECK(view); 37 view->set_icon_observer(this); 38 } 39 virtual ~IconUpdater() { 40 view_->set_icon_observer(NULL); 41 } 42 43 // Overridden from BrowserActionView::IconObserver: 44 virtual void OnIconUpdated(const gfx::ImageSkia& icon) OVERRIDE { 45 menu_item_view_->SetIcon(icon); 46 } 47 48 private: 49 // The menu item view whose icon might be updated. 50 views::MenuItemView* menu_item_view_; 51 52 // The view to be observed. When its icon changes, update the corresponding 53 // menu item view's icon. 54 BrowserActionView* view_; 55 56 DISALLOW_COPY_AND_ASSIGN(IconUpdater); 57 }; 58 59 BrowserActionOverflowMenuController::BrowserActionOverflowMenuController( 60 BrowserActionsContainer* owner, 61 Browser* browser, 62 views::MenuButton* menu_button, 63 const std::vector<BrowserActionView*>& views, 64 int start_index, 65 bool for_drop) 66 : owner_(owner), 67 browser_(browser), 68 observer_(NULL), 69 menu_button_(menu_button), 70 menu_(NULL), 71 views_(views), 72 start_index_(start_index), 73 for_drop_(for_drop) { 74 menu_ = new views::MenuItemView(this); 75 menu_runner_.reset(new views::MenuRunner( 76 menu_, for_drop_ ? views::MenuRunner::FOR_DROP : 0)); 77 menu_->set_has_icons(true); 78 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( 83 command_id, 84 base::UTF8ToUTF16(view->extension()->name()), 85 view->GetIconWithBadge()); 86 87 // Set the tooltip for this item. 88 base::string16 tooltip = base::UTF8ToUTF16( 89 view->extension_action()->GetTitle( 90 view->view_controller()->GetCurrentTabId())); 91 menu_->SetTooltip(tooltip, command_id); 92 93 icon_updaters_.push_back(new IconUpdater(menu_item, view)); 94 95 ++command_id; 96 } 97 } 98 99 BrowserActionOverflowMenuController::~BrowserActionOverflowMenuController() { 100 if (observer_) 101 observer_->NotifyMenuDeleted(this); 102 } 103 104 bool BrowserActionOverflowMenuController::RunMenu(views::Widget* window) { 105 gfx::Rect bounds = menu_button_->bounds(); 106 gfx::Point screen_loc; 107 views::View::ConvertPointToScreen(menu_button_, &screen_loc); 108 bounds.set_x(screen_loc.x()); 109 bounds.set_y(screen_loc.y()); 110 111 views::MenuAnchorPosition anchor = views::MENU_ANCHOR_TOPRIGHT; 112 // As we maintain our own lifetime we can safely ignore the result. 113 ignore_result(menu_runner_->RunMenuAt( 114 window, menu_button_, bounds, anchor, ui::MENU_SOURCE_NONE)); 115 if (!for_drop_) { 116 // Give the context menu (if any) a chance to execute the user-selected 117 // command. 118 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); 119 } 120 return true; 121 } 122 123 void BrowserActionOverflowMenuController::CancelMenu() { 124 menu_->Cancel(); 125 } 126 127 void BrowserActionOverflowMenuController::NotifyBrowserActionViewsDeleting() { 128 icon_updaters_.clear(); 129 } 130 131 bool BrowserActionOverflowMenuController::IsCommandEnabled(int id) const { 132 BrowserActionView* view = views_[start_index_ + id - 1]; 133 return view->IsEnabled(view->view_controller()->GetCurrentTabId()); 134 } 135 136 void BrowserActionOverflowMenuController::ExecuteCommand(int id) { 137 views_[start_index_ + id - 1]->view_controller()->ExecuteActionByUser(); 138 } 139 140 bool BrowserActionOverflowMenuController::ShowContextMenu( 141 views::MenuItemView* source, 142 int id, 143 const gfx::Point& p, 144 ui::MenuSourceType source_type) { 145 BrowserActionView* view = views_[start_index_ + id - 1]; 146 if (!view->extension()->ShowConfigureContextMenus()) 147 return false; 148 149 scoped_refptr<ExtensionContextMenuModel> context_menu_contents = 150 new ExtensionContextMenuModel( 151 view->extension(), browser_, view->view_controller()); 152 views::MenuRunner context_menu_runner(context_menu_contents.get(), 153 views::MenuRunner::HAS_MNEMONICS | 154 views::MenuRunner::IS_NESTED | 155 views::MenuRunner::CONTEXT_MENU); 156 157 // We can ignore the result as we delete ourself. 158 // This blocks until the user choses something or dismisses the menu. 159 ignore_result(context_menu_runner.RunMenuAt(menu_button_->GetWidget(), 160 NULL, 161 gfx::Rect(p, gfx::Size()), 162 views::MENU_ANCHOR_TOPLEFT, 163 source_type)); 164 165 // The user is done with the context menu, so we can close the underlying 166 // menu. 167 menu_->Cancel(); 168 169 return true; 170 } 171 172 void BrowserActionOverflowMenuController::DropMenuClosed( 173 views::MenuItemView* menu) { 174 delete this; 175 } 176 177 bool BrowserActionOverflowMenuController::GetDropFormats( 178 views::MenuItemView* menu, 179 int* formats, 180 std::set<OSExchangeData::CustomFormat>* custom_formats) { 181 return BrowserActionDragData::GetDropFormats(custom_formats); 182 } 183 184 bool BrowserActionOverflowMenuController::AreDropTypesRequired( 185 views::MenuItemView* menu) { 186 return BrowserActionDragData::AreDropTypesRequired(); 187 } 188 189 bool BrowserActionOverflowMenuController::CanDrop( 190 views::MenuItemView* menu, const OSExchangeData& data) { 191 return BrowserActionDragData::CanDrop(data, owner_->profile()); 192 } 193 194 int BrowserActionOverflowMenuController::GetDropOperation( 195 views::MenuItemView* item, 196 const ui::DropTargetEvent& event, 197 DropPosition* position) { 198 // Don't allow dropping from the BrowserActionContainer into slot 0 of the 199 // overflow menu since once the move has taken place the item you are dragging 200 // falls right out of the menu again once the user releases the button 201 // (because we don't shrink the BrowserActionContainer when you do this). 202 if ((item->GetCommand() == 0) && (*position == DROP_BEFORE)) { 203 BrowserActionDragData drop_data; 204 if (!drop_data.Read(event.data())) 205 return ui::DragDropTypes::DRAG_NONE; 206 207 if (drop_data.index() < owner_->VisibleBrowserActions()) 208 return ui::DragDropTypes::DRAG_NONE; 209 } 210 211 return ui::DragDropTypes::DRAG_MOVE; 212 } 213 214 int BrowserActionOverflowMenuController::OnPerformDrop( 215 views::MenuItemView* menu, 216 DropPosition position, 217 const ui::DropTargetEvent& event) { 218 BrowserActionDragData drop_data; 219 if (!drop_data.Read(event.data())) 220 return ui::DragDropTypes::DRAG_NONE; 221 222 size_t drop_index = IndexForId(menu->GetCommand()); 223 224 // When not dragging within the overflow menu (dragging an icon into the menu) 225 // subtract one to get the right index. 226 if (position == DROP_BEFORE && 227 drop_data.index() < owner_->VisibleBrowserActions()) 228 --drop_index; 229 230 // Move the extension in the model. 231 const extensions::Extension* extension = 232 extensions::ExtensionRegistry::Get(browser_->profile())-> 233 enabled_extensions().GetByID(drop_data.id()); 234 extensions::ExtensionToolbarModel* toolbar_model = 235 extensions::ExtensionToolbarModel::Get(browser_->profile()); 236 if (browser_->profile()->IsOffTheRecord()) 237 drop_index = toolbar_model->IncognitoIndexToOriginal(drop_index); 238 toolbar_model->MoveExtensionIcon(extension, drop_index); 239 240 // If the extension was moved to the overflow menu from the main bar, notify 241 // the owner. 242 if (drop_data.index() < owner_->VisibleBrowserActions()) 243 owner_->NotifyActionMovedToOverflow(); 244 245 if (for_drop_) 246 delete this; 247 return ui::DragDropTypes::DRAG_MOVE; 248 } 249 250 bool BrowserActionOverflowMenuController::CanDrag(views::MenuItemView* menu) { 251 return true; 252 } 253 254 void BrowserActionOverflowMenuController::WriteDragData( 255 views::MenuItemView* sender, OSExchangeData* data) { 256 size_t drag_index = IndexForId(sender->GetCommand()); 257 const extensions::Extension* extension = views_[drag_index]->extension(); 258 BrowserActionDragData drag_data(extension->id(), drag_index); 259 drag_data.Write(owner_->profile(), data); 260 } 261 262 int BrowserActionOverflowMenuController::GetDragOperations( 263 views::MenuItemView* sender) { 264 return ui::DragDropTypes::DRAG_MOVE; 265 } 266 267 size_t BrowserActionOverflowMenuController::IndexForId(int id) const { 268 // The index of the view being dragged (GetCommand gives a 1-based index into 269 // the overflow menu). 270 DCHECK_GT(owner_->VisibleBrowserActions() + id, 0u); 271 return owner_->VisibleBrowserActions() + id - 1; 272 } 273