Home | History | Annotate | Download | only in bookmarks
      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/bookmarks/bookmark_menu_delegate.h"
      6 
      7 #include "base/prefs/pref_service.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "chrome/browser/bookmarks/bookmark_model.h"
     10 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
     11 #include "chrome/browser/bookmarks/bookmark_node_data.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
     14 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
     15 #include "chrome/browser/ui/browser.h"
     16 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
     17 #include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h"
     18 #include "chrome/browser/ui/views/event_utils.h"
     19 #include "chrome/common/pref_names.h"
     20 #include "content/public/browser/page_navigator.h"
     21 #include "content/public/browser/user_metrics.h"
     22 #include "grit/generated_resources.h"
     23 #include "grit/theme_resources.h"
     24 #include "grit/ui_resources.h"
     25 #include "ui/base/dragdrop/os_exchange_data.h"
     26 #include "ui/base/l10n/l10n_util.h"
     27 #include "ui/base/resource/resource_bundle.h"
     28 #include "ui/base/window_open_disposition.h"
     29 #include "ui/views/controls/button/menu_button.h"
     30 #include "ui/views/controls/menu/menu_item_view.h"
     31 #include "ui/views/controls/menu/submenu_view.h"
     32 #include "ui/views/widget/widget.h"
     33 
     34 using content::PageNavigator;
     35 using content::UserMetricsAction;
     36 using views::MenuItemView;
     37 
     38 // Max width of a menu. There does not appear to be an OS value for this, yet
     39 // both IE and FF restrict the max width of a menu.
     40 static const int kMaxMenuWidth = 400;
     41 
     42 BookmarkMenuDelegate::BookmarkMenuDelegate(Browser* browser,
     43                                            PageNavigator* navigator,
     44                                            views::Widget* parent,
     45                                            int first_menu_id)
     46     : browser_(browser),
     47       profile_(browser->profile()),
     48       page_navigator_(navigator),
     49       parent_(parent),
     50       menu_(NULL),
     51       for_drop_(false),
     52       parent_menu_item_(NULL),
     53       next_menu_id_(first_menu_id),
     54       real_delegate_(NULL),
     55       is_mutating_model_(false),
     56       location_(bookmark_utils::LAUNCH_NONE){
     57 }
     58 
     59 BookmarkMenuDelegate::~BookmarkMenuDelegate() {
     60   BookmarkModelFactory::GetForProfile(profile_)->RemoveObserver(this);
     61 }
     62 
     63 void BookmarkMenuDelegate::Init(
     64     views::MenuDelegate* real_delegate,
     65     MenuItemView* parent,
     66     const BookmarkNode* node,
     67     int start_child_index,
     68     ShowOptions show_options,
     69     bookmark_utils::BookmarkLaunchLocation location) {
     70   BookmarkModelFactory::GetForProfile(profile_)->AddObserver(this);
     71   real_delegate_ = real_delegate;
     72   if (parent) {
     73     parent_menu_item_ = parent;
     74     int initial_count = parent->GetSubmenu() ?
     75         parent->GetSubmenu()->GetMenuItemCount() : 0;
     76     if ((start_child_index < node->child_count()) &&
     77         (initial_count > 0)) {
     78       parent->AppendSeparator();
     79     }
     80     BuildMenu(node, start_child_index, parent, &next_menu_id_);
     81     if (show_options == SHOW_PERMANENT_FOLDERS)
     82       BuildMenusForPermanentNodes(parent, &next_menu_id_);
     83   } else {
     84     menu_ = CreateMenu(node, start_child_index, show_options);
     85   }
     86 
     87   location_ = location;
     88 }
     89 
     90 void BookmarkMenuDelegate::SetPageNavigator(PageNavigator* navigator) {
     91   page_navigator_ = navigator;
     92   if (context_menu_.get())
     93     context_menu_->SetPageNavigator(navigator);
     94 }
     95 
     96 void BookmarkMenuDelegate::SetActiveMenu(const BookmarkNode* node,
     97                                          int start_index) {
     98   DCHECK(!parent_menu_item_);
     99   if (!node_to_menu_map_[node])
    100     CreateMenu(node, start_index, HIDE_PERMANENT_FOLDERS);
    101   menu_ = node_to_menu_map_[node];
    102 }
    103 
    104 string16 BookmarkMenuDelegate::GetTooltipText(
    105     int id,
    106     const gfx::Point& screen_loc) const {
    107   MenuIDToNodeMap::const_iterator i = menu_id_to_node_map_.find(id);
    108   // When removing bookmarks it may be possible to end up here without a node.
    109   if (i == menu_id_to_node_map_.end()) {
    110     DCHECK(is_mutating_model_);
    111     return string16();
    112   }
    113 
    114   const BookmarkNode* node = i->second;
    115   if (node->is_url()) {
    116     return BookmarkBarView::CreateToolTipForURLAndTitle(
    117         screen_loc, node->url(), node->GetTitle(), profile_,
    118         parent()->GetNativeView());
    119   }
    120   return string16();
    121 }
    122 
    123 bool BookmarkMenuDelegate::IsTriggerableEvent(views::MenuItemView* menu,
    124                                               const ui::Event& e) {
    125   return e.type() == ui::ET_GESTURE_TAP ||
    126          e.type() == ui::ET_GESTURE_TAP_DOWN ||
    127          event_utils::IsPossibleDispositionEvent(e);
    128 }
    129 
    130 void BookmarkMenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
    131   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
    132 
    133   const BookmarkNode* node = menu_id_to_node_map_[id];
    134   std::vector<const BookmarkNode*> selection;
    135   selection.push_back(node);
    136 
    137   chrome::OpenAll(parent_->GetNativeWindow(), page_navigator_, selection,
    138                   ui::DispositionFromEventFlags(mouse_event_flags),
    139                   profile_);
    140   bookmark_utils::RecordBookmarkLaunch(location_);
    141 }
    142 
    143 bool BookmarkMenuDelegate::ShouldExecuteCommandWithoutClosingMenu(
    144     int id, const ui::Event& event) {
    145   return (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) &&
    146          ui::DispositionFromEventFlags(event.flags()) == NEW_BACKGROUND_TAB;
    147 }
    148 
    149 bool BookmarkMenuDelegate::GetDropFormats(
    150     MenuItemView* menu,
    151     int* formats,
    152     std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
    153   *formats = ui::OSExchangeData::URL;
    154   custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
    155   return true;
    156 }
    157 
    158 bool BookmarkMenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
    159   return true;
    160 }
    161 
    162 bool BookmarkMenuDelegate::CanDrop(MenuItemView* menu,
    163                                    const ui::OSExchangeData& data) {
    164   // Only accept drops of 1 node, which is the case for all data dragged from
    165   // bookmark bar and menus.
    166 
    167   if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 ||
    168       !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
    169     return false;
    170 
    171   if (drop_data_.has_single_url())
    172     return true;
    173 
    174   const BookmarkNode* drag_node = drop_data_.GetFirstNode(profile_);
    175   if (!drag_node) {
    176     // Dragging a folder from another profile, always accept.
    177     return true;
    178   }
    179 
    180   // Drag originated from same profile and is not a URL. Only accept it if
    181   // the dragged node is not a parent of the node menu represents.
    182   if (menu_id_to_node_map_.find(menu->GetCommand()) ==
    183       menu_id_to_node_map_.end()) {
    184     // If we don't know the menu assume its because we're embedded. We'll
    185     // figure out the real operation when GetDropOperation is invoked.
    186     return true;
    187   }
    188   const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
    189   DCHECK(drop_node);
    190   while (drop_node && drop_node != drag_node)
    191     drop_node = drop_node->parent();
    192   return (drop_node == NULL);
    193 }
    194 
    195 int BookmarkMenuDelegate::GetDropOperation(
    196     MenuItemView* item,
    197     const ui::DropTargetEvent& event,
    198     views::MenuDelegate::DropPosition* position) {
    199   // Should only get here if we have drop data.
    200   DCHECK(drop_data_.is_valid());
    201 
    202   const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()];
    203   const BookmarkNode* drop_parent = node->parent();
    204   int index_to_drop_at = drop_parent->GetIndexOf(node);
    205   switch (*position) {
    206     case views::MenuDelegate::DROP_AFTER:
    207       if (node == BookmarkModelFactory::GetForProfile(
    208               profile_)->other_node() ||
    209           node == BookmarkModelFactory::GetForProfile(
    210               profile_)->mobile_node()) {
    211         // Dropping after these nodes makes no sense.
    212         *position = views::MenuDelegate::DROP_NONE;
    213       }
    214       index_to_drop_at++;
    215       break;
    216 
    217     case views::MenuDelegate::DROP_BEFORE:
    218       if (node == BookmarkModelFactory::GetForProfile(
    219               profile_)->mobile_node()) {
    220         // Dropping before this node makes no sense.
    221         *position = views::MenuDelegate::DROP_NONE;
    222       }
    223       break;
    224 
    225     case views::MenuDelegate::DROP_ON:
    226       drop_parent = node;
    227       index_to_drop_at = node->child_count();
    228       break;
    229 
    230     default:
    231       break;
    232   }
    233   DCHECK(drop_parent);
    234   return chrome::GetBookmarkDropOperation(profile_, event, drop_data_,
    235                                           drop_parent, index_to_drop_at);
    236 }
    237 
    238 int BookmarkMenuDelegate::OnPerformDrop(
    239     MenuItemView* menu,
    240     views::MenuDelegate::DropPosition position,
    241     const ui::DropTargetEvent& event) {
    242   const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
    243   DCHECK(drop_node);
    244   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
    245   DCHECK(model);
    246   const BookmarkNode* drop_parent = drop_node->parent();
    247   DCHECK(drop_parent);
    248   int index_to_drop_at = drop_parent->GetIndexOf(drop_node);
    249   switch (position) {
    250     case views::MenuDelegate::DROP_AFTER:
    251       index_to_drop_at++;
    252       break;
    253 
    254     case views::MenuDelegate::DROP_ON:
    255       DCHECK(drop_node->is_folder());
    256       drop_parent = drop_node;
    257       index_to_drop_at = drop_node->child_count();
    258       break;
    259 
    260     case views::MenuDelegate::DROP_BEFORE:
    261       if (drop_node == model->other_node() ||
    262           drop_node == model->mobile_node()) {
    263         // This can happen with SHOW_PERMANENT_FOLDERS.
    264         drop_parent = model->bookmark_bar_node();
    265         index_to_drop_at = drop_parent->child_count();
    266       }
    267       break;
    268 
    269     default:
    270       break;
    271   }
    272 
    273   return chrome::DropBookmarks(profile_, drop_data_,
    274                                drop_parent, index_to_drop_at);
    275 }
    276 
    277 bool BookmarkMenuDelegate::ShowContextMenu(MenuItemView* source,
    278                                            int id,
    279                                            const gfx::Point& p,
    280                                            ui::MenuSourceType source_type) {
    281   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
    282   std::vector<const BookmarkNode*> nodes;
    283   nodes.push_back(menu_id_to_node_map_[id]);
    284   bool close_on_delete = !parent_menu_item_ &&
    285       (nodes[0]->parent() == BookmarkModelFactory::GetForProfile(
    286           profile())->other_node() &&
    287        nodes[0]->parent()->child_count() == 1);
    288   context_menu_.reset(
    289       new BookmarkContextMenu(
    290           parent_,
    291           browser_,
    292           profile_,
    293           page_navigator_,
    294           nodes[0]->parent(),
    295           nodes,
    296           close_on_delete));
    297   context_menu_->set_observer(this);
    298   context_menu_->RunMenuAt(p, source_type);
    299   context_menu_.reset(NULL);
    300   return true;
    301 }
    302 
    303 bool BookmarkMenuDelegate::CanDrag(MenuItemView* menu) {
    304   const BookmarkNode* node = menu_id_to_node_map_[menu->GetCommand()];
    305   // Don't let users drag the other folder.
    306   return node->parent() != BookmarkModelFactory::GetForProfile(
    307       profile_)->root_node();
    308 }
    309 
    310 void BookmarkMenuDelegate::WriteDragData(MenuItemView* sender,
    311                                          ui::OSExchangeData* data) {
    312   DCHECK(sender && data);
    313 
    314   content::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"));
    315 
    316   BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]);
    317   drag_data.Write(profile_, data);
    318 }
    319 
    320 int BookmarkMenuDelegate::GetDragOperations(MenuItemView* sender) {
    321   return chrome::GetBookmarkDragOperation(
    322       profile_, menu_id_to_node_map_[sender->GetCommand()]);
    323 }
    324 
    325 int BookmarkMenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) {
    326   return kMaxMenuWidth;
    327 }
    328 
    329 void BookmarkMenuDelegate::BookmarkModelChanged() {
    330 }
    331 
    332 void BookmarkMenuDelegate::BookmarkNodeFaviconChanged(
    333     BookmarkModel* model,
    334     const BookmarkNode* node) {
    335   NodeToMenuMap::iterator menu_pair = node_to_menu_map_.find(node);
    336   if (menu_pair == node_to_menu_map_.end())
    337     return;  // We're not showing a menu item for the node.
    338 
    339   menu_pair->second->SetIcon(model->GetFavicon(node).AsImageSkia());
    340 }
    341 
    342 void BookmarkMenuDelegate::WillRemoveBookmarks(
    343     const std::vector<const BookmarkNode*>& bookmarks) {
    344   DCHECK(!is_mutating_model_);
    345   is_mutating_model_ = true;  // Set to false in DidRemoveBookmarks().
    346 
    347   // Remove the observer so that when the remove happens we don't prematurely
    348   // cancel the menu. The observer is added back in DidRemoveBookmarks().
    349   BookmarkModelFactory::GetForProfile(profile_)->RemoveObserver(this);
    350 
    351   // Remove the menu items.
    352   std::set<MenuItemView*> changed_parent_menus;
    353   for (std::vector<const BookmarkNode*>::const_iterator i(bookmarks.begin());
    354        i != bookmarks.end(); ++i) {
    355     NodeToMenuMap::iterator node_to_menu = node_to_menu_map_.find(*i);
    356     if (node_to_menu != node_to_menu_map_.end()) {
    357       MenuItemView* menu = node_to_menu->second;
    358       MenuItemView* parent = menu->GetParentMenuItem();
    359       // |parent| is NULL when removing a root. This happens when right clicking
    360       // to delete an empty folder.
    361       if (parent) {
    362         changed_parent_menus.insert(parent);
    363         parent->RemoveMenuItemAt(menu->parent()->GetIndexOf(menu));
    364       }
    365       node_to_menu_map_.erase(node_to_menu);
    366       menu_id_to_node_map_.erase(menu->GetCommand());
    367     }
    368   }
    369 
    370   // All the bookmarks in |bookmarks| should have the same parent. It's possible
    371   // to support different parents, but this would need to prune any nodes whose
    372   // parent has been removed. As all nodes currently have the same parent, there
    373   // is the DCHECK.
    374   DCHECK(changed_parent_menus.size() <= 1);
    375 
    376   // Remove any descendants of the removed nodes in |node_to_menu_map_|.
    377   for (NodeToMenuMap::iterator i(node_to_menu_map_.begin());
    378        i != node_to_menu_map_.end(); ) {
    379     bool ancestor_removed = false;
    380     for (std::vector<const BookmarkNode*>::const_iterator j(bookmarks.begin());
    381          j != bookmarks.end(); ++j) {
    382       if (i->first->HasAncestor(*j)) {
    383         ancestor_removed = true;
    384         break;
    385       }
    386     }
    387     if (ancestor_removed) {
    388       menu_id_to_node_map_.erase(i->second->GetCommand());
    389       node_to_menu_map_.erase(i++);
    390     } else {
    391       ++i;
    392     }
    393   }
    394 
    395   for (std::set<MenuItemView*>::const_iterator i(changed_parent_menus.begin());
    396        i != changed_parent_menus.end(); ++i)
    397     (*i)->ChildrenChanged();
    398 }
    399 
    400 void BookmarkMenuDelegate::DidRemoveBookmarks() {
    401   // Balances remove in WillRemoveBookmarksImpl.
    402   BookmarkModelFactory::GetForProfile(profile_)->AddObserver(this);
    403   DCHECK(is_mutating_model_);
    404   is_mutating_model_ = false;
    405 }
    406 
    407 MenuItemView* BookmarkMenuDelegate::CreateMenu(const BookmarkNode* parent,
    408                                                int start_child_index,
    409                                                ShowOptions show_options) {
    410   MenuItemView* menu = new MenuItemView(real_delegate_);
    411   menu->SetCommand(next_menu_id_++);
    412   menu_id_to_node_map_[menu->GetCommand()] = parent;
    413   menu->set_has_icons(true);
    414   BuildMenu(parent, start_child_index, menu, &next_menu_id_);
    415   if (show_options == SHOW_PERMANENT_FOLDERS)
    416     BuildMenusForPermanentNodes(menu, &next_menu_id_);
    417   return menu;
    418 }
    419 
    420 void BookmarkMenuDelegate::BuildMenusForPermanentNodes(
    421     views::MenuItemView* menu,
    422     int* next_menu_id) {
    423   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
    424   bool added_separator = false;
    425   BuildMenuForPermanentNode(model->other_node(), menu, next_menu_id,
    426                             &added_separator);
    427   BuildMenuForPermanentNode(model->mobile_node(), menu, next_menu_id,
    428                             &added_separator);
    429 }
    430 
    431 void BookmarkMenuDelegate::BuildMenuForPermanentNode(
    432     const BookmarkNode* node,
    433     MenuItemView* menu,
    434     int* next_menu_id,
    435     bool* added_separator) {
    436   if (!node->IsVisible() || node->GetTotalNodeCount() == 1)
    437     return;  // No children, don't create a menu.
    438 
    439   if (!*added_separator) {
    440     *added_separator = true;
    441     menu->AppendSeparator();
    442   }
    443   int id = *next_menu_id;
    444   (*next_menu_id)++;
    445   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    446   gfx::ImageSkia* folder_icon = rb.GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER);
    447   MenuItemView* submenu = menu->AppendSubMenuWithIcon(
    448       id, node->GetTitle(), *folder_icon);
    449   BuildMenu(node, 0, submenu, next_menu_id);
    450   menu_id_to_node_map_[id] = node;
    451 }
    452 
    453 void BookmarkMenuDelegate::BuildMenu(const BookmarkNode* parent,
    454                                      int start_child_index,
    455                                      MenuItemView* menu,
    456                                      int* next_menu_id) {
    457   node_to_menu_map_[parent] = menu;
    458   DCHECK(parent->empty() || start_child_index < parent->child_count());
    459   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    460   for (int i = start_child_index; i < parent->child_count(); ++i) {
    461     const BookmarkNode* node = parent->GetChild(i);
    462     const int id = *next_menu_id;
    463     (*next_menu_id)++;
    464 
    465     menu_id_to_node_map_[id] = node;
    466     if (node->is_url()) {
    467       const gfx::Image& image = BookmarkModelFactory::GetForProfile(
    468           profile_)->GetFavicon(node);
    469       const gfx::ImageSkia* icon = image.IsEmpty() ?
    470           rb.GetImageSkiaNamed(IDR_DEFAULT_FAVICON) : image.ToImageSkia();
    471       node_to_menu_map_[node] =
    472           menu->AppendMenuItemWithIcon(id, node->GetTitle(), *icon);
    473     } else if (node->is_folder()) {
    474       gfx::ImageSkia* folder_icon =
    475           rb.GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER);
    476       MenuItemView* submenu = menu->AppendSubMenuWithIcon(
    477           id, node->GetTitle(), *folder_icon);
    478       BuildMenu(node, 0, submenu, next_menu_id);
    479     } else {
    480       NOTREACHED();
    481     }
    482   }
    483 }
    484