| OLD | NEW |
| (Empty) |
| 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_contro
ller.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 } | |
| OLD | NEW |