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),
33 controller_([[BookmarkMenuCocoaController alloc] initWithBridge:this
35 if (GetBookmarkModel())
36 ObserveBookmarkModel();
39 BookmarkMenuBridge::~BookmarkMenuBridge() {
40 BookmarkModel* model = GetBookmarkModel();
42 model->RemoveObserver(this);
43 [controller_ release];
46 void BookmarkMenuBridge::BookmarkModelLoaded(BookmarkModel* model,
47 bool ids_reassigned) {
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,
61 DCHECK(bookmark_menu);
65 BookmarkModel* model = GetBookmarkModel();
66 if (!model || !model->loaded())
70 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
72 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage());
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();
90 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER_MANAGED).ToNSImage();
91 AddNodeAsSubmenu(bookmark_menu, managedNode, image, !is_submenu);
93 if (!supervisedNode->empty()) {
94 // Most users never see this node, so the image is only loaded if needed.
95 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
97 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER_SUPERVISED).ToNSImage();
98 AddNodeAsSubmenu(bookmark_menu, supervisedNode, image, !is_submenu);
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
105 if (!model->other_node()->empty()) {
106 [bookmark_menu addItem:[NSMenuItem separatorItem]];
107 AddNodeAsSubmenu(bookmark_menu,
113 // If the "Mobile Bookmarks" folder has any content, make a submenu for it and
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(),
130 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
131 NSMenu* bookmark_menu = BookmarkMenu();
132 if (bookmark_menu == nil)
135 ClearBookmarkMenu(bookmark_menu);
138 void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model,
139 const BookmarkNode* old_parent,
141 const BookmarkNode* new_parent,
146 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
147 const BookmarkNode* parent,
152 void BookmarkMenuBridge::BookmarkNodeRemoved(
153 BookmarkModel* model,
154 const BookmarkNode* parent,
156 const BookmarkNode* node,
157 const std::set<GURL>& removed_urls) {
161 void BookmarkMenuBridge::BookmarkAllUserNodesRemoved(
162 BookmarkModel* model,
163 const std::set<GURL>& removed_urls) {
167 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model,
168 const BookmarkNode* node) {
169 NSMenuItem* item = MenuItemForNode(node);
171 ConfigureMenuItem(node, item, true);
174 void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model,
175 const BookmarkNode* node) {
176 NSMenuItem* item = MenuItemForNode(node);
178 ConfigureMenuItem(node, item, false);
181 void BookmarkMenuBridge::BookmarkNodeChildrenReordered(
182 BookmarkModel* model, const BookmarkNode* node) {
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);
199 BookmarkModelLoaded(model, false);
202 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() {
205 return BookmarkModelFactory::GetForProfile(profile_);
208 Profile* BookmarkMenuBridge::GetProfile() {
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:)) ||
231 [item isSeparatorItem]) {
232 // This will eventually [obj release] all its kids, if it has
234 [menu removeItem:item];
241 void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu,
242 const BookmarkNode* node,
244 bool add_extra_items) {
245 NSString* title = SysUTF16ToNSString(node->GetTitle());
246 NSMenuItem* items = [[[NSMenuItem alloc]
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();
262 NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU);
264 [[[NSMenuItem alloc] initWithTitle:empty_string
266 keyEquivalent:@""] autorelease];
268 } else for (int i = 0; i < child_count; i++) {
269 const BookmarkNode* child = node->GetChild(i);
270 NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child];
272 [[[NSMenuItem alloc] initWithTitle:title
274 keyEquivalent:@""] autorelease];
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
283 ConfigureMenuItem(child, item, false);
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);
309 void BookmarkMenuBridge::AddItemToMenu(int command_id,
311 const BookmarkNode* node,
314 NSString* title = l10n_util::GetNSStringWithFixup(message_id);
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.
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:);
325 action = @selector(openAllBookmarksIncognitoWindow:);
327 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
329 keyEquivalent:@""] autorelease];
330 [item setTarget:controller_];
331 [item setTag:node->id()];
332 [item setEnabled:enabled];
336 void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node,
340 [item setTitle:[BookmarkMenuCocoaController menuTitleForNode:node]];
341 [item setTarget:controller_];
342 [item setAction:@selector(openBookmarkMenuItem:)];
343 [item setTag:node->id()];
345 [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
346 // Check to see if we have a favicon.
347 NSImage* favicon = nil;
348 BookmarkModel* model = GetBookmarkModel();
350 const gfx::Image& image = model->GetFavicon(node);
351 if (!image.IsEmpty())
352 favicon = image.ToNSImage();
354 // If we do not have a loaded favicon, use the default site image instead.
356 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
357 favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage();
359 [item setImage:favicon];
362 NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) {
365 std::map<const BookmarkNode*, NSMenuItem*>::iterator it =
366 bookmark_nodes_.find(node);
367 if (it == bookmark_nodes_.end())