Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / bookmarks / bookmark_menu_bridge.mm
blob74fc155be5f0053b30c4627504814780bc24b18b
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.
5 #import <AppKit/AppKit.h>
7 #include "base/strings/sys_string_conversions.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #import "chrome/browser/app_controller_mac.h"
10 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
11 #include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
12 #include "chrome/browser/prefs/incognito_mode_prefs.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/profiles/profile_manager.h"
15 #include "chrome/browser/ui/browser_list.h"
16 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
17 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "components/bookmarks/browser/bookmark_model.h"
20 #include "components/bookmarks/managed/managed_bookmark_service.h"
21 #include "grit/theme_resources.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/gfx/image/image.h"
25 #include "ui/resources/grit/ui_resources.h"
27 using bookmarks::BookmarkModel;
28 using bookmarks::BookmarkNode;
30 BookmarkMenuBridge::BookmarkMenuBridge(Profile* profile, NSMenu* menu)
31     : menuIsValid_(false),
32       profile_(profile),
33       controller_([[BookmarkMenuCocoaController alloc] initWithBridge:this
34                                                               andMenu:menu]) {
35   if (GetBookmarkModel())
36     ObserveBookmarkModel();
39 BookmarkMenuBridge::~BookmarkMenuBridge() {
40   BookmarkModel* model = GetBookmarkModel();
41   if (model)
42     model->RemoveObserver(this);
43   [controller_ release];
46 void BookmarkMenuBridge::BookmarkModelLoaded(BookmarkModel* model,
47                                              bool ids_reassigned) {
48   InvalidateMenu();
51 void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) {
52   UpdateMenuInternal(bookmark_menu, false);
55 void BookmarkMenuBridge::UpdateSubMenu(NSMenu* bookmark_menu) {
56   UpdateMenuInternal(bookmark_menu, true);
59 void BookmarkMenuBridge::UpdateMenuInternal(NSMenu* bookmark_menu,
60                                             bool is_submenu) {
61   DCHECK(bookmark_menu);
62   if (menuIsValid_)
63     return;
65   BookmarkModel* model = GetBookmarkModel();
66   if (!model || !model->loaded())
67     return;
69   if (!folder_image_) {
70     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
71     folder_image_.reset(
72         rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage());
73   }
75   ClearBookmarkMenu(bookmark_menu);
77   // Add at most one separator for the bookmark bar and the managed and
78   // supervised bookmarks folders.
79   bookmarks::ManagedBookmarkService* managed =
80       ManagedBookmarkServiceFactory::GetForProfile(profile_);
81   const BookmarkNode* barNode = model->bookmark_bar_node();
82   const BookmarkNode* managedNode = managed->managed_node();
83   const BookmarkNode* supervisedNode = managed->supervised_node();
84   if (!barNode->empty() || !managedNode->empty() || !supervisedNode->empty())
85     [bookmark_menu addItem:[NSMenuItem separatorItem]];
86   if (!managedNode->empty()) {
87     // Most users never see this node, so the image is only loaded if needed.
88     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
89     NSImage* image =
90         rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER_MANAGED).ToNSImage();
91     AddNodeAsSubmenu(bookmark_menu, managedNode, image, !is_submenu);
92   }
93   if (!supervisedNode->empty()) {
94     // Most users never see this node, so the image is only loaded if needed.
95     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
96     NSImage* image =
97         rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER_SUPERVISED).ToNSImage();
98     AddNodeAsSubmenu(bookmark_menu, supervisedNode, image, !is_submenu);
99   }
100   if (!barNode->empty())
101     AddNodeToMenu(barNode, bookmark_menu, !is_submenu);
103   // If the "Other Bookmarks" folder has any content, make a submenu for it and
104   // fill it in.
105   if (!model->other_node()->empty()) {
106     [bookmark_menu addItem:[NSMenuItem separatorItem]];
107     AddNodeAsSubmenu(bookmark_menu,
108                      model->other_node(),
109                      folder_image_,
110                      !is_submenu);
111   }
113   // If the "Mobile Bookmarks" folder has any content, make a submenu for it and
114   // fill it in.
115   if (!model->mobile_node()->empty()) {
116     // Add a separator if we did not already add one due to a non-empty
117     // "Other Bookmarks" folder.
118     if (model->other_node()->empty())
119       [bookmark_menu addItem:[NSMenuItem separatorItem]];
121     AddNodeAsSubmenu(bookmark_menu,
122                      model->mobile_node(),
123                      folder_image_,
124                      !is_submenu);
125   }
127   menuIsValid_ = true;
130 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
131   NSMenu* bookmark_menu = BookmarkMenu();
132   if (bookmark_menu == nil)
133     return;
135   ClearBookmarkMenu(bookmark_menu);
138 void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model,
139                                            const BookmarkNode* old_parent,
140                                            int old_index,
141                                            const BookmarkNode* new_parent,
142                                            int new_index) {
143   InvalidateMenu();
146 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
147                                            const BookmarkNode* parent,
148                                            int index) {
149   InvalidateMenu();
152 void BookmarkMenuBridge::BookmarkNodeRemoved(
153     BookmarkModel* model,
154     const BookmarkNode* parent,
155     int old_index,
156     const BookmarkNode* node,
157     const std::set<GURL>& removed_urls) {
158   InvalidateMenu();
161 void BookmarkMenuBridge::BookmarkAllUserNodesRemoved(
162     BookmarkModel* model,
163     const std::set<GURL>& removed_urls) {
164   InvalidateMenu();
167 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model,
168                                              const BookmarkNode* node) {
169   NSMenuItem* item = MenuItemForNode(node);
170   if (item)
171     ConfigureMenuItem(node, item, true);
174 void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model,
175                                                     const BookmarkNode* node) {
176   NSMenuItem* item = MenuItemForNode(node);
177   if (item)
178     ConfigureMenuItem(node, item, false);
181 void BookmarkMenuBridge::BookmarkNodeChildrenReordered(
182     BookmarkModel* model, const BookmarkNode* node) {
183   InvalidateMenu();
186 void BookmarkMenuBridge::ResetMenu() {
187   ClearBookmarkMenu(BookmarkMenu());
190 void BookmarkMenuBridge::BuildMenu() {
191   UpdateMenu(BookmarkMenu());
194 // Watch for changes.
195 void BookmarkMenuBridge::ObserveBookmarkModel() {
196   BookmarkModel* model = GetBookmarkModel();
197   model->AddObserver(this);
198   if (model->loaded())
199     BookmarkModelLoaded(model, false);
202 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() {
203   if (!profile_)
204     return NULL;
205   return BookmarkModelFactory::GetForProfile(profile_);
208 Profile* BookmarkMenuBridge::GetProfile() {
209   return profile_;
212 NSMenu* BookmarkMenuBridge::BookmarkMenu() {
213   return [controller_ menu];
216 void BookmarkMenuBridge::ClearBookmarkMenu(NSMenu* menu) {
217   bookmark_nodes_.clear();
218   // Recursively delete all menus that look like a bookmark. Also delete all
219   // separator items since we explicitly add them back in. This deletes
220   // everything except the first item ("Add Bookmark...").
221   NSArray* items = [menu itemArray];
222   for (NSMenuItem* item in items) {
223     // Convention: items in the bookmark list which are bookmarks have
224     // an action of openBookmarkMenuItem:.  Also, assume all items
225     // with submenus are submenus of bookmarks.
226     if (([item action] == @selector(openBookmarkMenuItem:)) ||
227         ([item action] == @selector(openAllBookmarks:)) ||
228         ([item action] == @selector(openAllBookmarksNewWindow:)) ||
229         ([item action] == @selector(openAllBookmarksIncognitoWindow:)) ||
230         [item hasSubmenu] ||
231         [item isSeparatorItem]) {
232       // This will eventually [obj release] all its kids, if it has
233       // any.
234       [menu removeItem:item];
235     } else {
236       // Leave it alone.
237     }
238   }
241 void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu,
242                                           const BookmarkNode* node,
243                                           NSImage* image,
244                                           bool add_extra_items) {
245   NSString* title = SysUTF16ToNSString(node->GetTitle());
246   NSMenuItem* items = [[[NSMenuItem alloc]
247                             initWithTitle:title
248                                    action:nil
249                             keyEquivalent:@""] autorelease];
250   [items setImage:image];
251   [menu addItem:items];
252   NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
253   [menu setSubmenu:submenu forItem:items];
254   AddNodeToMenu(node, submenu, add_extra_items);
257 // TODO(jrg): limit the number of bookmarks in the menubar?
258 void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu,
259                                        bool add_extra_items) {
260   int child_count = node->child_count();
261   if (!child_count) {
262     NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU);
263     NSMenuItem* item =
264         [[[NSMenuItem alloc] initWithTitle:empty_string
265                                     action:nil
266                              keyEquivalent:@""] autorelease];
267     [menu addItem:item];
268   } else for (int i = 0; i < child_count; i++) {
269     const BookmarkNode* child = node->GetChild(i);
270     NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child];
271     NSMenuItem* item =
272         [[[NSMenuItem alloc] initWithTitle:title
273                                     action:nil
274                              keyEquivalent:@""] autorelease];
275     [menu addItem:item];
276     bookmark_nodes_[child] = item;
277     if (child->is_folder()) {
278       [item setImage:folder_image_];
279       NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
280       [menu setSubmenu:submenu forItem:item];
281       AddNodeToMenu(child, submenu, add_extra_items);  // recursive call
282     } else {
283       ConfigureMenuItem(child, item, false);
284     }
285   }
287   if (add_extra_items) {
288     // Add menus for 'Open All Bookmarks'.
289     [menu addItem:[NSMenuItem separatorItem]];
290     bool enabled = child_count != 0;
292     IncognitoModePrefs::Availability incognito_availability =
293         IncognitoModePrefs::GetAvailability(profile_->GetPrefs());
294     bool incognito_enabled =
295         enabled && incognito_availability != IncognitoModePrefs::DISABLED;
297     AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL,
298                   IDS_BOOKMARK_BAR_OPEN_ALL,
299                   node, menu, enabled);
300     AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
301                   IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
302                   node, menu, enabled);
303     AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
304                   IDS_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
305                   node, menu, incognito_enabled);
306   }
309 void BookmarkMenuBridge::AddItemToMenu(int command_id,
310                                        int message_id,
311                                        const BookmarkNode* node,
312                                        NSMenu* menu,
313                                        bool enabled) {
314   NSString* title = l10n_util::GetNSStringWithFixup(message_id);
315   SEL action;
316   if (!enabled) {
317     // A nil action makes a menu item appear disabled. NSMenuItem setEnabled
318     // will not reflect the disabled state until the item title is set again.
319     action = nil;
320   } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL) {
321     action = @selector(openAllBookmarks:);
322   } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW) {
323     action = @selector(openAllBookmarksNewWindow:);
324   } else {
325     action = @selector(openAllBookmarksIncognitoWindow:);
326   }
327   NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
328                                                  action:action
329                                           keyEquivalent:@""] autorelease];
330   [item setTarget:controller_];
331   [item setTag:node->id()];
332   [item setEnabled:enabled];
333   [menu addItem:item];
336 void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node,
337                                            NSMenuItem* item,
338                                            bool set_title) {
339   if (set_title)
340     [item setTitle:[BookmarkMenuCocoaController menuTitleForNode:node]];
341   [item setTarget:controller_];
342   [item setAction:@selector(openBookmarkMenuItem:)];
343   [item setTag:node->id()];
344   if (node->is_url())
345     [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
346   // Check to see if we have a favicon.
347   NSImage* favicon = nil;
348   BookmarkModel* model = GetBookmarkModel();
349   if (model) {
350     const gfx::Image& image = model->GetFavicon(node);
351     if (!image.IsEmpty())
352       favicon = image.ToNSImage();
353   }
354   // If we do not have a loaded favicon, use the default site image instead.
355   if (!favicon) {
356     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
357     favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage();
358   }
359   [item setImage:favicon];
362 NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) {
363   if (!node)
364     return nil;
365   std::map<const BookmarkNode*, NSMenuItem*>::iterator it =
366       bookmark_nodes_.find(node);
367   if (it == bookmark_nodes_.end())
368     return nil;
369   return it->second;