Home | History | Annotate | Download | only in extensions
      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