Home | History | Annotate | Download | only in bookmarks
      1 // Copyright (c) 2011 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_controller_views.h"
      6 
      7 #include "base/stl_util-inl.h"
      8 #include "base/utf_string_conversions.h"
      9 #include "chrome/browser/bookmarks/bookmark_model.h"
     10 #include "chrome/browser/bookmarks/bookmark_node_data.h"
     11 #include "chrome/browser/bookmarks/bookmark_utils.h"
     12 #include "chrome/browser/metrics/user_metrics.h"
     13 #include "chrome/browser/prefs/pref_service.h"
     14 #include "chrome/browser/profiles/profile.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/event_utils.h"
     18 #include "chrome/common/pref_names.h"
     19 #include "content/browser/tab_contents/page_navigator.h"
     20 #include "content/common/page_transition_types.h"
     21 #include "grit/app_resources.h"
     22 #include "grit/generated_resources.h"
     23 #include "grit/theme_resources.h"
     24 #include "ui/base/dragdrop/os_exchange_data.h"
     25 #include "ui/base/resource/resource_bundle.h"
     26 #include "views/controls/button/menu_button.h"
     27 
     28 using views::MenuItemView;
     29 
     30 // Max width of a menu. There does not appear to be an OS value for this, yet
     31 // both IE and FF restrict the max width of a menu.
     32 static const int kMaxMenuWidth = 400;
     33 
     34 BookmarkMenuController::BookmarkMenuController(Browser* browser,
     35                                                Profile* profile,
     36                                                PageNavigator* navigator,
     37                                                gfx::NativeWindow parent,
     38                                                const BookmarkNode* node,
     39                                                int start_child_index)
     40     : browser_(browser),
     41       profile_(profile),
     42       page_navigator_(navigator),
     43       parent_(parent),
     44       node_(node),
     45       menu_(NULL),
     46       observer_(NULL),
     47       for_drop_(false),
     48       bookmark_bar_(NULL),
     49       next_menu_id_(1) {
     50   menu_ = CreateMenu(node, start_child_index);
     51 }
     52 
     53 void BookmarkMenuController::RunMenuAt(BookmarkBarView* bookmark_bar,
     54                                        bool for_drop) {
     55   bookmark_bar_ = bookmark_bar;
     56   views::MenuButton* menu_button = bookmark_bar_->GetMenuButtonForNode(node_);
     57   DCHECK(menu_button);
     58   MenuItemView::AnchorPosition anchor;
     59   int start_index;
     60   bookmark_bar_->GetAnchorPositionAndStartIndexForButton(
     61       menu_button, &anchor, &start_index);
     62   RunMenuAt(menu_button, anchor, for_drop);
     63 }
     64 
     65 void BookmarkMenuController::RunMenuAt(
     66     views::MenuButton* button,
     67     MenuItemView::AnchorPosition position,
     68     bool for_drop) {
     69   gfx::Point screen_loc;
     70   views::View::ConvertPointToScreen(button, &screen_loc);
     71   // Subtract 1 from the height to make the popup flush with the button border.
     72   gfx::Rect bounds(screen_loc.x(), screen_loc.y(), button->width(),
     73                    button->height() - 1);
     74   for_drop_ = for_drop;
     75   profile_->GetBookmarkModel()->AddObserver(this);
     76   // The constructor creates the initial menu and that is all we should have
     77   // at this point.
     78   DCHECK(node_to_menu_map_.size() == 1);
     79   if (for_drop) {
     80     menu_->RunMenuForDropAt(parent_, bounds, position);
     81   } else {
     82     menu_->RunMenuAt(parent_, button, bounds, position, false);
     83     delete this;
     84   }
     85 }
     86 
     87 void BookmarkMenuController::Cancel() {
     88   menu_->Cancel();
     89 }
     90 
     91 std::wstring BookmarkMenuController::GetTooltipText(
     92     int id, const gfx::Point& screen_loc) {
     93   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
     94 
     95   const BookmarkNode* node = menu_id_to_node_map_[id];
     96   if (node->type() == BookmarkNode::URL)
     97     return BookmarkBarView::CreateToolTipForURLAndTitle(
     98         screen_loc, node->GetURL(), UTF16ToWide(node->GetTitle()), profile_);
     99   return std::wstring();
    100 }
    101 
    102 bool BookmarkMenuController::IsTriggerableEvent(const views::MouseEvent& e) {
    103   return event_utils::IsPossibleDispositionEvent(e);
    104 }
    105 
    106 void BookmarkMenuController::ExecuteCommand(int id, int mouse_event_flags) {
    107   DCHECK(page_navigator_);
    108   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
    109 
    110   const BookmarkNode* node = menu_id_to_node_map_[id];
    111   std::vector<const BookmarkNode*> selection;
    112   selection.push_back(node);
    113 
    114   WindowOpenDisposition initial_disposition =
    115       event_utils::DispositionFromEventFlags(mouse_event_flags);
    116 
    117   bookmark_utils::OpenAll(parent_, profile_, page_navigator_, selection,
    118                           initial_disposition);
    119 }
    120 
    121 bool BookmarkMenuController::GetDropFormats(
    122       MenuItemView* menu,
    123       int* formats,
    124       std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
    125   *formats = ui::OSExchangeData::URL;
    126   custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
    127   return true;
    128 }
    129 
    130 bool BookmarkMenuController::AreDropTypesRequired(MenuItemView* menu) {
    131   return true;
    132 }
    133 
    134 bool BookmarkMenuController::CanDrop(MenuItemView* menu,
    135                                      const ui::OSExchangeData& data) {
    136   // Only accept drops of 1 node, which is the case for all data dragged from
    137   // bookmark bar and menus.
    138 
    139   if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 ||
    140       !profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
    141     return false;
    142 
    143   if (drop_data_.has_single_url())
    144     return true;
    145 
    146   const BookmarkNode* drag_node = drop_data_.GetFirstNode(profile_);
    147   if (!drag_node) {
    148     // Dragging a folder from another profile, always accept.
    149     return true;
    150   }
    151 
    152   // Drag originated from same profile and is not a URL. Only accept it if
    153   // the dragged node is not a parent of the node menu represents.
    154   const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
    155   DCHECK(drop_node);
    156   while (drop_node && drop_node != drag_node)
    157     drop_node = drop_node->parent();
    158   return (drop_node == NULL);
    159 }
    160 
    161 int BookmarkMenuController::GetDropOperation(
    162     MenuItemView* item,
    163     const views::DropTargetEvent& event,
    164     DropPosition* position) {
    165   // Should only get here if we have drop data.
    166   DCHECK(drop_data_.is_valid());
    167 
    168   const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()];
    169   const BookmarkNode* drop_parent = node->parent();
    170   int index_to_drop_at = drop_parent->GetIndexOf(node);
    171   if (*position == DROP_AFTER) {
    172     index_to_drop_at++;
    173   } else if (*position == DROP_ON) {
    174     drop_parent = node;
    175     index_to_drop_at = node->child_count();
    176   }
    177   DCHECK(drop_parent);
    178   return bookmark_utils::BookmarkDropOperation(
    179       profile_, event, drop_data_, drop_parent, index_to_drop_at);
    180 }
    181 
    182 int BookmarkMenuController::OnPerformDrop(MenuItemView* menu,
    183                                           DropPosition position,
    184                                           const views::DropTargetEvent& event) {
    185   const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
    186   DCHECK(drop_node);
    187   BookmarkModel* model = profile_->GetBookmarkModel();
    188   DCHECK(model);
    189   const BookmarkNode* drop_parent = drop_node->parent();
    190   DCHECK(drop_parent);
    191   int index_to_drop_at = drop_parent->GetIndexOf(drop_node);
    192   if (position == DROP_AFTER) {
    193     index_to_drop_at++;
    194   } else if (position == DROP_ON) {
    195     DCHECK(drop_node->is_folder());
    196     drop_parent = drop_node;
    197     index_to_drop_at = drop_node->child_count();
    198   }
    199 
    200   int result = bookmark_utils::PerformBookmarkDrop(
    201       profile_, drop_data_, drop_parent, index_to_drop_at);
    202   if (for_drop_)
    203     delete this;
    204   return result;
    205 }
    206 
    207 bool BookmarkMenuController::ShowContextMenu(MenuItemView* source,
    208                                              int id,
    209                                              const gfx::Point& p,
    210                                              bool is_mouse_gesture) {
    211   DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
    212   std::vector<const BookmarkNode*> nodes;
    213   nodes.push_back(menu_id_to_node_map_[id]);
    214   context_menu_.reset(
    215       new BookmarkContextMenu(
    216           parent_,
    217           profile_,
    218           page_navigator_,
    219           nodes[0]->parent(),
    220           nodes));
    221   context_menu_->set_observer(this);
    222   context_menu_->RunMenuAt(p);
    223   context_menu_.reset(NULL);
    224   return true;
    225 }
    226 
    227 void BookmarkMenuController::DropMenuClosed(MenuItemView* menu) {
    228   delete this;
    229 }
    230 
    231 bool BookmarkMenuController::CanDrag(MenuItemView* menu) {
    232   return true;
    233 }
    234 
    235 void BookmarkMenuController::WriteDragData(MenuItemView* sender,
    236                                            ui::OSExchangeData* data) {
    237   DCHECK(sender && data);
    238 
    239   UserMetrics::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"),
    240                             profile_);
    241 
    242   BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]);
    243   drag_data.Write(profile_, data);
    244 }
    245 
    246 int BookmarkMenuController::GetDragOperations(MenuItemView* sender) {
    247   return bookmark_utils::BookmarkDragOperation(profile_,
    248       menu_id_to_node_map_[sender->GetCommand()]);
    249 }
    250 
    251 views::MenuItemView* BookmarkMenuController::GetSiblingMenu(
    252     views::MenuItemView* menu,
    253     const gfx::Point& screen_point,
    254     views::MenuItemView::AnchorPosition* anchor,
    255     bool* has_mnemonics,
    256     views::MenuButton** button) {
    257   if (!bookmark_bar_ || for_drop_)
    258     return NULL;
    259   gfx::Point bookmark_bar_loc(screen_point);
    260   views::View::ConvertPointToView(NULL, bookmark_bar_, &bookmark_bar_loc);
    261   int start_index;
    262   const BookmarkNode* node =
    263       bookmark_bar_->GetNodeForButtonAt(bookmark_bar_loc, &start_index);
    264   if (!node || !node->is_folder())
    265     return NULL;
    266 
    267   MenuItemView* alt_menu = node_to_menu_map_[node];
    268   if (!alt_menu)
    269     alt_menu = CreateMenu(node, start_index);
    270 
    271   menu_ = alt_menu;
    272 
    273   *button = bookmark_bar_->GetMenuButtonForNode(node);
    274   bookmark_bar_->GetAnchorPositionAndStartIndexForButton(
    275       *button, anchor, &start_index);
    276   *has_mnemonics = false;
    277   return alt_menu;
    278 }
    279 
    280 int BookmarkMenuController::GetMaxWidthForMenu() {
    281   return kMaxMenuWidth;
    282 }
    283 
    284 void BookmarkMenuController::BookmarkModelChanged() {
    285   menu_->Cancel();
    286 }
    287 
    288 void BookmarkMenuController::BookmarkNodeFaviconLoaded(
    289     BookmarkModel* model, const BookmarkNode* node) {
    290   NodeToMenuIDMap::iterator menu_pair = node_to_menu_id_map_.find(node);
    291   if (menu_pair == node_to_menu_id_map_.end())
    292     return;  // We're not showing a menu item for the node.
    293 
    294   // Iterate through the menus looking for the menu containing node.
    295   for (NodeToMenuMap::iterator i = node_to_menu_map_.begin();
    296        i != node_to_menu_map_.end(); ++i) {
    297     MenuItemView* menu_item = i->second->GetMenuItemByID(menu_pair->second);
    298     if (menu_item) {
    299       menu_item->SetIcon(model->GetFavicon(node));
    300       return;
    301     }
    302   }
    303 }
    304 
    305 void BookmarkMenuController::WillRemoveBookmarks(
    306     const std::vector<const BookmarkNode*>& bookmarks) {
    307   std::set<MenuItemView*> removed_menus;
    308 
    309   WillRemoveBookmarksImpl(bookmarks, &removed_menus);
    310 
    311   STLDeleteElements(&removed_menus);
    312 }
    313 
    314 void BookmarkMenuController::DidRemoveBookmarks() {
    315   profile_->GetBookmarkModel()->AddObserver(this);
    316 }
    317 
    318 MenuItemView* BookmarkMenuController::CreateMenu(const BookmarkNode* parent,
    319                                                  int start_child_index) {
    320   MenuItemView* menu = new MenuItemView(this);
    321   menu->SetCommand(next_menu_id_++);
    322   menu_id_to_node_map_[menu->GetCommand()] = parent;
    323   menu->set_has_icons(true);
    324   BuildMenu(parent, start_child_index, menu, &next_menu_id_);
    325   node_to_menu_map_[parent] = menu;
    326   return menu;
    327 }
    328 
    329 void BookmarkMenuController::BuildMenu(const BookmarkNode* parent,
    330                                        int start_child_index,
    331                                        MenuItemView* menu,
    332                                        int* next_menu_id) {
    333   DCHECK(!parent->child_count() ||
    334          start_child_index < parent->child_count());
    335   for (int i = start_child_index; i < parent->child_count(); ++i) {
    336     const BookmarkNode* node = parent->GetChild(i);
    337     int id = *next_menu_id;
    338 
    339     (*next_menu_id)++;
    340     if (node->is_url()) {
    341       SkBitmap icon = profile_->GetBookmarkModel()->GetFavicon(node);
    342       if (icon.width() == 0) {
    343         icon = *ResourceBundle::GetSharedInstance().
    344             GetBitmapNamed(IDR_DEFAULT_FAVICON);
    345       }
    346       menu->AppendMenuItemWithIcon(id, UTF16ToWide(node->GetTitle()), icon);
    347       node_to_menu_id_map_[node] = id;
    348     } else if (node->is_folder()) {
    349       SkBitmap* folder_icon = ResourceBundle::GetSharedInstance().
    350           GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER);
    351       MenuItemView* submenu = menu->AppendSubMenuWithIcon(id,
    352           UTF16ToWide(node->GetTitle()), *folder_icon);
    353       node_to_menu_id_map_[node] = id;
    354       BuildMenu(node, 0, submenu, next_menu_id);
    355     } else {
    356       NOTREACHED();
    357     }
    358     menu_id_to_node_map_[id] = node;
    359   }
    360 }
    361 
    362 BookmarkMenuController::~BookmarkMenuController() {
    363   profile_->GetBookmarkModel()->RemoveObserver(this);
    364   if (observer_)
    365     observer_->BookmarkMenuDeleted(this);
    366   STLDeleteValues(&node_to_menu_map_);
    367 }
    368 
    369 MenuItemView* BookmarkMenuController::GetMenuByID(int id) {
    370   for (NodeToMenuMap::const_iterator i = node_to_menu_map_.begin();
    371        i != node_to_menu_map_.end(); ++i) {
    372     MenuItemView* menu = i->second->GetMenuItemByID(id);
    373     if (menu)
    374       return menu;
    375   }
    376   return NULL;
    377 }
    378 
    379 void BookmarkMenuController::WillRemoveBookmarksImpl(
    380       const std::vector<const BookmarkNode*>& bookmarks,
    381       std::set<views::MenuItemView*>* removed_menus) {
    382   // Remove the observer so that when the remove happens we don't prematurely
    383   // cancel the menu.
    384   profile_->GetBookmarkModel()->RemoveObserver(this);
    385 
    386   // Remove the menu items.
    387   std::set<MenuItemView*> changed_parent_menus;
    388   for (std::vector<const BookmarkNode*>::const_iterator i = bookmarks.begin();
    389        i != bookmarks.end(); ++i) {
    390     NodeToMenuIDMap::iterator node_to_menu = node_to_menu_id_map_.find(*i);
    391     if (node_to_menu != node_to_menu_id_map_.end()) {
    392       MenuItemView* menu = GetMenuByID(node_to_menu->second);
    393       DCHECK(menu);  // If there an entry in node_to_menu_id_map_, there should
    394                      // be a menu.
    395       removed_menus->insert(menu);
    396       changed_parent_menus.insert(menu->GetParentMenuItem());
    397       menu->parent()->RemoveChildView(menu);
    398       node_to_menu_id_map_.erase(node_to_menu);
    399     }
    400   }
    401 
    402   // All the bookmarks in |bookmarks| should have the same parent. It's possible
    403   // to support different parents, but this would need to prune any nodes whose
    404   // parent has been removed. As all nodes currently have the same parent, there
    405   // is the DCHECK.
    406   DCHECK(changed_parent_menus.size() <= 1);
    407 
    408   for (std::set<MenuItemView*>::const_iterator i = changed_parent_menus.begin();
    409        i != changed_parent_menus.end(); ++i) {
    410     (*i)->ChildrenChanged();
    411   }
    412 
    413   // Remove any descendants of the removed nodes in node_to_menu_id_map_.
    414   for (NodeToMenuIDMap::iterator i = node_to_menu_id_map_.begin();
    415        i != node_to_menu_id_map_.end(); ) {
    416     bool ancestor_removed = false;
    417     for (std::vector<const BookmarkNode*>::const_iterator j = bookmarks.begin();
    418          j != bookmarks.end(); ++j) {
    419       if (i->first->HasAncestor(*j)) {
    420         ancestor_removed = true;
    421         break;
    422       }
    423     }
    424     if (ancestor_removed) {
    425       node_to_menu_id_map_.erase(i++);
    426     } else {
    427       ++i;
    428     }
    429   }
    430 }
    431