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
BookmarkMenuController(Browser * browser,Profile * profile,PageNavigator * navigator,gfx::NativeWindow parent,const BookmarkNode * node,int start_child_index)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
RunMenuAt(BookmarkBarView * bookmark_bar,bool for_drop)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
RunMenuAt(views::MenuButton * button,MenuItemView::AnchorPosition position,bool for_drop)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
Cancel()87 void BookmarkMenuController::Cancel() {
88 menu_->Cancel();
89 }
90
GetTooltipText(int id,const gfx::Point & screen_loc)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
IsTriggerableEvent(const views::MouseEvent & e)102 bool BookmarkMenuController::IsTriggerableEvent(const views::MouseEvent& e) {
103 return event_utils::IsPossibleDispositionEvent(e);
104 }
105
ExecuteCommand(int id,int mouse_event_flags)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
GetDropFormats(MenuItemView * menu,int * formats,std::set<ui::OSExchangeData::CustomFormat> * custom_formats)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
AreDropTypesRequired(MenuItemView * menu)130 bool BookmarkMenuController::AreDropTypesRequired(MenuItemView* menu) {
131 return true;
132 }
133
CanDrop(MenuItemView * menu,const ui::OSExchangeData & data)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
GetDropOperation(MenuItemView * item,const views::DropTargetEvent & event,DropPosition * position)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
OnPerformDrop(MenuItemView * menu,DropPosition position,const views::DropTargetEvent & event)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
ShowContextMenu(MenuItemView * source,int id,const gfx::Point & p,bool is_mouse_gesture)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
DropMenuClosed(MenuItemView * menu)227 void BookmarkMenuController::DropMenuClosed(MenuItemView* menu) {
228 delete this;
229 }
230
CanDrag(MenuItemView * menu)231 bool BookmarkMenuController::CanDrag(MenuItemView* menu) {
232 return true;
233 }
234
WriteDragData(MenuItemView * sender,ui::OSExchangeData * data)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
GetDragOperations(MenuItemView * sender)246 int BookmarkMenuController::GetDragOperations(MenuItemView* sender) {
247 return bookmark_utils::BookmarkDragOperation(profile_,
248 menu_id_to_node_map_[sender->GetCommand()]);
249 }
250
GetSiblingMenu(views::MenuItemView * menu,const gfx::Point & screen_point,views::MenuItemView::AnchorPosition * anchor,bool * has_mnemonics,views::MenuButton ** button)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
GetMaxWidthForMenu()280 int BookmarkMenuController::GetMaxWidthForMenu() {
281 return kMaxMenuWidth;
282 }
283
BookmarkModelChanged()284 void BookmarkMenuController::BookmarkModelChanged() {
285 menu_->Cancel();
286 }
287
BookmarkNodeFaviconLoaded(BookmarkModel * model,const BookmarkNode * node)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
WillRemoveBookmarks(const std::vector<const BookmarkNode * > & bookmarks)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
DidRemoveBookmarks()314 void BookmarkMenuController::DidRemoveBookmarks() {
315 profile_->GetBookmarkModel()->AddObserver(this);
316 }
317
CreateMenu(const BookmarkNode * parent,int start_child_index)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
BuildMenu(const BookmarkNode * parent,int start_child_index,MenuItemView * menu,int * next_menu_id)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
~BookmarkMenuController()362 BookmarkMenuController::~BookmarkMenuController() {
363 profile_->GetBookmarkModel()->RemoveObserver(this);
364 if (observer_)
365 observer_->BookmarkMenuDeleted(this);
366 STLDeleteValues(&node_to_menu_map_);
367 }
368
GetMenuByID(int id)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
WillRemoveBookmarksImpl(const std::vector<const BookmarkNode * > & bookmarks,std::set<views::MenuItemView * > * removed_menus)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