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.h"
11 #include "chrome/browser/bookmarks/bookmark_model_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 "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::Loaded(BookmarkModel* model, bool ids_reassigned) {
49 void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) {
50 UpdateMenuInternal(bookmark_menu, false);
53 void BookmarkMenuBridge::UpdateSubMenu(NSMenu* bookmark_menu) {
54 UpdateMenuInternal(bookmark_menu, true);
57 void BookmarkMenuBridge::UpdateMenuInternal(NSMenu* bookmark_menu,
59 DCHECK(bookmark_menu);
63 BookmarkModel* model = GetBookmarkModel();
64 if (!model || !model->loaded())
68 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
70 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage());
73 ClearBookmarkMenu(bookmark_menu);
75 // Add bookmark bar items, if any.
76 const BookmarkNode* barNode = model->bookmark_bar_node();
78 if (!barNode->empty()) {
79 [bookmark_menu addItem:[NSMenuItem separatorItem]];
80 AddNodeToMenu(barNode, bookmark_menu, !is_submenu);
83 // If the "Other Bookmarks" folder has any content, make a submenu for it and
85 if (!model->other_node()->empty()) {
86 [bookmark_menu addItem:[NSMenuItem separatorItem]];
87 AddNodeAsSubmenu(bookmark_menu,
92 // If the "Mobile Bookmarks" folder has any content, make a submenu for it and
94 if (!model->mobile_node()->empty()) {
95 // Add a separator if we did not already add one due to a non-empty
96 // "Other Bookmarks" folder.
97 if (model->other_node()->empty())
98 [bookmark_menu addItem:[NSMenuItem separatorItem]];
100 AddNodeAsSubmenu(bookmark_menu,
101 model->mobile_node(),
108 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
109 NSMenu* bookmark_menu = BookmarkMenu();
110 if (bookmark_menu == nil)
113 ClearBookmarkMenu(bookmark_menu);
116 void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model,
117 const BookmarkNode* old_parent,
119 const BookmarkNode* new_parent,
124 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
125 const BookmarkNode* parent,
130 void BookmarkMenuBridge::BookmarkNodeRemoved(BookmarkModel* model,
131 const BookmarkNode* parent,
133 const BookmarkNode* node) {
137 void BookmarkMenuBridge::BookmarkAllNodesRemoved(BookmarkModel* model) {
141 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model,
142 const BookmarkNode* node) {
143 NSMenuItem* item = MenuItemForNode(node);
145 ConfigureMenuItem(node, item, true);
148 void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model,
149 const BookmarkNode* node) {
150 NSMenuItem* item = MenuItemForNode(node);
152 ConfigureMenuItem(node, item, false);
155 void BookmarkMenuBridge::BookmarkNodeChildrenReordered(
156 BookmarkModel* model, const BookmarkNode* node) {
160 void BookmarkMenuBridge::ResetMenu() {
161 ClearBookmarkMenu(BookmarkMenu());
164 void BookmarkMenuBridge::BuildMenu() {
165 UpdateMenu(BookmarkMenu());
168 // Watch for changes.
169 void BookmarkMenuBridge::ObserveBookmarkModel() {
170 BookmarkModel* model = GetBookmarkModel();
171 model->AddObserver(this);
173 Loaded(model, false);
176 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() {
179 return BookmarkModelFactory::GetForProfile(profile_);
182 Profile* BookmarkMenuBridge::GetProfile() {
186 void BookmarkMenuBridge::ClearBookmarkMenu(NSMenu* menu) {
187 bookmark_nodes_.clear();
188 // Recursively delete all menus that look like a bookmark. Also delete all
189 // separator items since we explicitly add them back in. This deletes
190 // everything except the first item ("Add Bookmark...").
191 NSArray* items = [menu itemArray];
192 for (NSMenuItem* item in items) {
193 // Convention: items in the bookmark list which are bookmarks have
194 // an action of openBookmarkMenuItem:. Also, assume all items
195 // with submenus are submenus of bookmarks.
196 if (([item action] == @selector(openBookmarkMenuItem:)) ||
197 ([item action] == @selector(openAllBookmarks:)) ||
198 ([item action] == @selector(openAllBookmarksNewWindow:)) ||
199 ([item action] == @selector(openAllBookmarksIncognitoWindow:)) ||
201 [item isSeparatorItem]) {
202 // This will eventually [obj release] all its kids, if it has
204 [menu removeItem:item];
211 void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu,
212 const BookmarkNode* node,
213 bool add_extra_items) {
214 NSString* title = SysUTF16ToNSString(node->GetTitle());
215 NSMenuItem* items = [[[NSMenuItem alloc]
218 keyEquivalent:@""] autorelease];
219 [items setImage:folder_image_];
220 [menu addItem:items];
221 NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
222 [menu setSubmenu:submenu forItem:items];
223 AddNodeToMenu(node, submenu, add_extra_items);
226 // TODO(jrg): limit the number of bookmarks in the menubar?
227 void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu,
228 bool add_extra_items) {
229 int child_count = node->child_count();
231 NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU);
233 [[[NSMenuItem alloc] initWithTitle:empty_string
235 keyEquivalent:@""] autorelease];
237 } else for (int i = 0; i < child_count; i++) {
238 const BookmarkNode* child = node->GetChild(i);
239 NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child];
241 [[[NSMenuItem alloc] initWithTitle:title
243 keyEquivalent:@""] autorelease];
245 bookmark_nodes_[child] = item;
246 if (child->is_folder()) {
247 [item setImage:folder_image_];
248 NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
249 [menu setSubmenu:submenu forItem:item];
250 AddNodeToMenu(child, submenu, add_extra_items); // recursive call
252 ConfigureMenuItem(child, item, false);
256 if (add_extra_items) {
257 // Add menus for 'Open All Bookmarks'.
258 [menu addItem:[NSMenuItem separatorItem]];
259 bool enabled = child_count != 0;
261 IncognitoModePrefs::Availability incognito_availability =
262 IncognitoModePrefs::GetAvailability(profile_->GetPrefs());
263 bool incognito_enabled =
264 enabled && incognito_availability != IncognitoModePrefs::DISABLED;
266 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL,
267 IDS_BOOKMARK_BAR_OPEN_ALL,
268 node, menu, enabled);
269 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
270 IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
271 node, menu, enabled);
272 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
273 IDS_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
274 node, menu, incognito_enabled);
278 void BookmarkMenuBridge::AddItemToMenu(int command_id,
280 const BookmarkNode* node,
283 NSString* title = l10n_util::GetNSStringWithFixup(message_id);
286 // A nil action makes a menu item appear disabled. NSMenuItem setEnabled
287 // will not reflect the disabled state until the item title is set again.
289 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL) {
290 action = @selector(openAllBookmarks:);
291 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW) {
292 action = @selector(openAllBookmarksNewWindow:);
294 action = @selector(openAllBookmarksIncognitoWindow:);
296 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
298 keyEquivalent:@""] autorelease];
299 [item setTarget:controller_];
300 [item setTag:node->id()];
301 [item setEnabled:enabled];
305 void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node,
309 [item setTitle:[BookmarkMenuCocoaController menuTitleForNode:node]];
310 [item setTarget:controller_];
311 [item setAction:@selector(openBookmarkMenuItem:)];
312 [item setTag:node->id()];
314 [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
315 // Check to see if we have a favicon.
316 NSImage* favicon = nil;
317 BookmarkModel* model = GetBookmarkModel();
319 const gfx::Image& image = model->GetFavicon(node);
320 if (!image.IsEmpty())
321 favicon = image.ToNSImage();
323 // If we do not have a loaded favicon, use the default site image instead.
325 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
326 favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage();
328 [item setImage:favicon];
331 NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) {
334 std::map<const BookmarkNode*, NSMenuItem*>::iterator it =
335 bookmark_nodes_.find(node);
336 if (it == bookmark_nodes_.end())