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/prefs/incognito_mode_prefs.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/profiles/profile_manager.h"
14 #include "chrome/browser/ui/browser_list.h"
15 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
16 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller.h"
17 #include "components/bookmarks/browser/bookmark_model.h"
18 #include "grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "grit/ui_resources.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/image/image.h"
25 BookmarkMenuBridge::BookmarkMenuBridge(Profile* profile, NSMenu* menu)
26 : menuIsValid_(false),
28 controller_([[BookmarkMenuCocoaController alloc] initWithBridge:this
30 if (GetBookmarkModel())
31 ObserveBookmarkModel();
34 BookmarkMenuBridge::~BookmarkMenuBridge() {
35 BookmarkModel* model = GetBookmarkModel();
37 model->RemoveObserver(this);
38 [controller_ release];
41 NSMenu* BookmarkMenuBridge::BookmarkMenu() {
42 return [controller_ menu];
45 void BookmarkMenuBridge::BookmarkModelLoaded(BookmarkModel* model,
46 bool ids_reassigned) {
50 void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) {
51 UpdateMenuInternal(bookmark_menu, false);
54 void BookmarkMenuBridge::UpdateSubMenu(NSMenu* bookmark_menu) {
55 UpdateMenuInternal(bookmark_menu, true);
58 void BookmarkMenuBridge::UpdateMenuInternal(NSMenu* bookmark_menu,
60 DCHECK(bookmark_menu);
64 BookmarkModel* model = GetBookmarkModel();
65 if (!model || !model->loaded())
69 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
71 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage());
74 ClearBookmarkMenu(bookmark_menu);
76 // Add bookmark bar items, if any.
77 const BookmarkNode* barNode = model->bookmark_bar_node();
79 if (!barNode->empty()) {
80 [bookmark_menu addItem:[NSMenuItem separatorItem]];
81 AddNodeToMenu(barNode, bookmark_menu, !is_submenu);
84 // If the "Other Bookmarks" folder has any content, make a submenu for it and
86 if (!model->other_node()->empty()) {
87 [bookmark_menu addItem:[NSMenuItem separatorItem]];
88 AddNodeAsSubmenu(bookmark_menu,
93 // If the "Mobile Bookmarks" folder has any content, make a submenu for it and
95 if (!model->mobile_node()->empty()) {
96 // Add a separator if we did not already add one due to a non-empty
97 // "Other Bookmarks" folder.
98 if (model->other_node()->empty())
99 [bookmark_menu addItem:[NSMenuItem separatorItem]];
101 AddNodeAsSubmenu(bookmark_menu,
102 model->mobile_node(),
109 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
110 NSMenu* bookmark_menu = BookmarkMenu();
111 if (bookmark_menu == nil)
114 ClearBookmarkMenu(bookmark_menu);
117 void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model,
118 const BookmarkNode* old_parent,
120 const BookmarkNode* new_parent,
125 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
126 const BookmarkNode* parent,
131 void BookmarkMenuBridge::BookmarkNodeRemoved(
132 BookmarkModel* model,
133 const BookmarkNode* parent,
135 const BookmarkNode* node,
136 const std::set<GURL>& removed_urls) {
140 void BookmarkMenuBridge::BookmarkAllNodesRemoved(
141 BookmarkModel* model,
142 const std::set<GURL>& removed_urls) {
146 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model,
147 const BookmarkNode* node) {
148 NSMenuItem* item = MenuItemForNode(node);
150 ConfigureMenuItem(node, item, true);
153 void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model,
154 const BookmarkNode* node) {
155 NSMenuItem* item = MenuItemForNode(node);
157 ConfigureMenuItem(node, item, false);
160 void BookmarkMenuBridge::BookmarkNodeChildrenReordered(
161 BookmarkModel* model, const BookmarkNode* node) {
165 void BookmarkMenuBridge::ResetMenu() {
166 ClearBookmarkMenu(BookmarkMenu());
169 void BookmarkMenuBridge::BuildMenu() {
170 UpdateMenu(BookmarkMenu());
173 // Watch for changes.
174 void BookmarkMenuBridge::ObserveBookmarkModel() {
175 BookmarkModel* model = GetBookmarkModel();
176 model->AddObserver(this);
178 BookmarkModelLoaded(model, false);
181 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() {
184 return BookmarkModelFactory::GetForProfile(profile_);
187 Profile* BookmarkMenuBridge::GetProfile() {
191 void BookmarkMenuBridge::ClearBookmarkMenu(NSMenu* menu) {
192 bookmark_nodes_.clear();
193 // Recursively delete all menus that look like a bookmark. Also delete all
194 // separator items since we explicitly add them back in. This deletes
195 // everything except the first item ("Add Bookmark...").
196 NSArray* items = [menu itemArray];
197 for (NSMenuItem* item in items) {
198 // Convention: items in the bookmark list which are bookmarks have
199 // an action of openBookmarkMenuItem:. Also, assume all items
200 // with submenus are submenus of bookmarks.
201 if (([item action] == @selector(openBookmarkMenuItem:)) ||
202 ([item action] == @selector(openAllBookmarks:)) ||
203 ([item action] == @selector(openAllBookmarksNewWindow:)) ||
204 ([item action] == @selector(openAllBookmarksIncognitoWindow:)) ||
206 [item isSeparatorItem]) {
207 // This will eventually [obj release] all its kids, if it has
209 [menu removeItem:item];
216 void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu,
217 const BookmarkNode* node,
218 bool add_extra_items) {
219 NSString* title = SysUTF16ToNSString(node->GetTitle());
220 NSMenuItem* items = [[[NSMenuItem alloc]
223 keyEquivalent:@""] autorelease];
224 [items setImage:folder_image_];
225 [menu addItem:items];
226 NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
227 [menu setSubmenu:submenu forItem:items];
228 AddNodeToMenu(node, submenu, add_extra_items);
231 // TODO(jrg): limit the number of bookmarks in the menubar?
232 void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu,
233 bool add_extra_items) {
234 int child_count = node->child_count();
236 NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU);
238 [[[NSMenuItem alloc] initWithTitle:empty_string
240 keyEquivalent:@""] autorelease];
242 } else for (int i = 0; i < child_count; i++) {
243 const BookmarkNode* child = node->GetChild(i);
244 NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child];
246 [[[NSMenuItem alloc] initWithTitle:title
248 keyEquivalent:@""] autorelease];
250 bookmark_nodes_[child] = item;
251 if (child->is_folder()) {
252 [item setImage:folder_image_];
253 NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
254 [menu setSubmenu:submenu forItem:item];
255 AddNodeToMenu(child, submenu, add_extra_items); // recursive call
257 ConfigureMenuItem(child, item, false);
261 if (add_extra_items) {
262 // Add menus for 'Open All Bookmarks'.
263 [menu addItem:[NSMenuItem separatorItem]];
264 bool enabled = child_count != 0;
266 IncognitoModePrefs::Availability incognito_availability =
267 IncognitoModePrefs::GetAvailability(profile_->GetPrefs());
268 bool incognito_enabled =
269 enabled && incognito_availability != IncognitoModePrefs::DISABLED;
271 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL,
272 IDS_BOOKMARK_BAR_OPEN_ALL,
273 node, menu, enabled);
274 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
275 IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
276 node, menu, enabled);
277 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
278 IDS_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
279 node, menu, incognito_enabled);
283 void BookmarkMenuBridge::AddItemToMenu(int command_id,
285 const BookmarkNode* node,
288 NSString* title = l10n_util::GetNSStringWithFixup(message_id);
291 // A nil action makes a menu item appear disabled. NSMenuItem setEnabled
292 // will not reflect the disabled state until the item title is set again.
294 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL) {
295 action = @selector(openAllBookmarks:);
296 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW) {
297 action = @selector(openAllBookmarksNewWindow:);
299 action = @selector(openAllBookmarksIncognitoWindow:);
301 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
303 keyEquivalent:@""] autorelease];
304 [item setTarget:controller_];
305 [item setTag:node->id()];
306 [item setEnabled:enabled];
310 void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node,
314 [item setTitle:[BookmarkMenuCocoaController menuTitleForNode:node]];
315 [item setTarget:controller_];
316 [item setAction:@selector(openBookmarkMenuItem:)];
317 [item setTag:node->id()];
319 [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
320 // Check to see if we have a favicon.
321 NSImage* favicon = nil;
322 BookmarkModel* model = GetBookmarkModel();
324 const gfx::Image& image = model->GetFavicon(node);
325 if (!image.IsEmpty())
326 favicon = image.ToNSImage();
328 // If we do not have a loaded favicon, use the default site image instead.
330 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
331 favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage();
333 [item setImage:favicon];
336 NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) {
339 std::map<const BookmarkNode*, NSMenuItem*>::iterator it =
340 bookmark_nodes_.find(node);
341 if (it == bookmark_nodes_.end())