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/chrome_bookmark_client.h"
12 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
13 #include "chrome/browser/prefs/incognito_mode_prefs.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/profiles/profile_manager.h"
16 #include "chrome/browser/ui/browser_list.h"
17 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
18 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "components/bookmarks/browser/bookmark_model.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 BookmarkMenuBridge::BookmarkMenuBridge(Profile* profile, NSMenu* menu)
28 : menuIsValid_(false),
30 controller_([[BookmarkMenuCocoaController alloc] initWithBridge:this
32 if (GetBookmarkModel())
33 ObserveBookmarkModel();
36 BookmarkMenuBridge::~BookmarkMenuBridge() {
37 BookmarkModel* model = GetBookmarkModel();
39 model->RemoveObserver(this);
40 [controller_ release];
43 NSMenu* BookmarkMenuBridge::BookmarkMenu() {
44 return [controller_ menu];
47 void BookmarkMenuBridge::BookmarkModelLoaded(BookmarkModel* model,
48 bool ids_reassigned) {
52 void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) {
53 UpdateMenuInternal(bookmark_menu, false);
56 void BookmarkMenuBridge::UpdateSubMenu(NSMenu* bookmark_menu) {
57 UpdateMenuInternal(bookmark_menu, true);
60 void BookmarkMenuBridge::UpdateMenuInternal(NSMenu* bookmark_menu,
62 DCHECK(bookmark_menu);
66 BookmarkModel* model = GetBookmarkModel();
67 if (!model || !model->loaded())
71 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
73 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage());
76 ClearBookmarkMenu(bookmark_menu);
78 // Add at most one separator for the bookmark bar and the managed bookmarks
80 ChromeBookmarkClient* client =
81 ChromeBookmarkClientFactory::GetForProfile(profile_);
82 const BookmarkNode* barNode = model->bookmark_bar_node();
83 const BookmarkNode* managedNode = client->managed_node();
84 if (!barNode->empty() || !managedNode->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 (!barNode->empty())
94 AddNodeToMenu(barNode, bookmark_menu, !is_submenu);
96 // If the "Other Bookmarks" folder has any content, make a submenu for it and
98 if (!model->other_node()->empty()) {
99 [bookmark_menu addItem:[NSMenuItem separatorItem]];
100 AddNodeAsSubmenu(bookmark_menu,
106 // If the "Mobile Bookmarks" folder has any content, make a submenu for it and
108 if (!model->mobile_node()->empty()) {
109 // Add a separator if we did not already add one due to a non-empty
110 // "Other Bookmarks" folder.
111 if (model->other_node()->empty())
112 [bookmark_menu addItem:[NSMenuItem separatorItem]];
114 AddNodeAsSubmenu(bookmark_menu,
115 model->mobile_node(),
123 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
124 NSMenu* bookmark_menu = BookmarkMenu();
125 if (bookmark_menu == nil)
128 ClearBookmarkMenu(bookmark_menu);
131 void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model,
132 const BookmarkNode* old_parent,
134 const BookmarkNode* new_parent,
139 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
140 const BookmarkNode* parent,
145 void BookmarkMenuBridge::BookmarkNodeRemoved(
146 BookmarkModel* model,
147 const BookmarkNode* parent,
149 const BookmarkNode* node,
150 const std::set<GURL>& removed_urls) {
154 void BookmarkMenuBridge::BookmarkAllUserNodesRemoved(
155 BookmarkModel* model,
156 const std::set<GURL>& removed_urls) {
160 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model,
161 const BookmarkNode* node) {
162 NSMenuItem* item = MenuItemForNode(node);
164 ConfigureMenuItem(node, item, true);
167 void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model,
168 const BookmarkNode* node) {
169 NSMenuItem* item = MenuItemForNode(node);
171 ConfigureMenuItem(node, item, false);
174 void BookmarkMenuBridge::BookmarkNodeChildrenReordered(
175 BookmarkModel* model, const BookmarkNode* node) {
179 void BookmarkMenuBridge::ResetMenu() {
180 ClearBookmarkMenu(BookmarkMenu());
183 void BookmarkMenuBridge::BuildMenu() {
184 UpdateMenu(BookmarkMenu());
187 // Watch for changes.
188 void BookmarkMenuBridge::ObserveBookmarkModel() {
189 BookmarkModel* model = GetBookmarkModel();
190 model->AddObserver(this);
192 BookmarkModelLoaded(model, false);
195 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() {
198 return BookmarkModelFactory::GetForProfile(profile_);
201 Profile* BookmarkMenuBridge::GetProfile() {
205 void BookmarkMenuBridge::ClearBookmarkMenu(NSMenu* menu) {
206 bookmark_nodes_.clear();
207 // Recursively delete all menus that look like a bookmark. Also delete all
208 // separator items since we explicitly add them back in. This deletes
209 // everything except the first item ("Add Bookmark...").
210 NSArray* items = [menu itemArray];
211 for (NSMenuItem* item in items) {
212 // Convention: items in the bookmark list which are bookmarks have
213 // an action of openBookmarkMenuItem:. Also, assume all items
214 // with submenus are submenus of bookmarks.
215 if (([item action] == @selector(openBookmarkMenuItem:)) ||
216 ([item action] == @selector(openAllBookmarks:)) ||
217 ([item action] == @selector(openAllBookmarksNewWindow:)) ||
218 ([item action] == @selector(openAllBookmarksIncognitoWindow:)) ||
220 [item isSeparatorItem]) {
221 // This will eventually [obj release] all its kids, if it has
223 [menu removeItem:item];
230 void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu,
231 const BookmarkNode* node,
233 bool add_extra_items) {
234 NSString* title = SysUTF16ToNSString(node->GetTitle());
235 NSMenuItem* items = [[[NSMenuItem alloc]
238 keyEquivalent:@""] autorelease];
239 [items setImage:image];
240 [menu addItem:items];
241 NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
242 [menu setSubmenu:submenu forItem:items];
243 AddNodeToMenu(node, submenu, add_extra_items);
246 // TODO(jrg): limit the number of bookmarks in the menubar?
247 void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu,
248 bool add_extra_items) {
249 int child_count = node->child_count();
251 NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU);
253 [[[NSMenuItem alloc] initWithTitle:empty_string
255 keyEquivalent:@""] autorelease];
257 } else for (int i = 0; i < child_count; i++) {
258 const BookmarkNode* child = node->GetChild(i);
259 NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child];
261 [[[NSMenuItem alloc] initWithTitle:title
263 keyEquivalent:@""] autorelease];
265 bookmark_nodes_[child] = item;
266 if (child->is_folder()) {
267 [item setImage:folder_image_];
268 NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
269 [menu setSubmenu:submenu forItem:item];
270 AddNodeToMenu(child, submenu, add_extra_items); // recursive call
272 ConfigureMenuItem(child, item, false);
276 if (add_extra_items) {
277 // Add menus for 'Open All Bookmarks'.
278 [menu addItem:[NSMenuItem separatorItem]];
279 bool enabled = child_count != 0;
281 IncognitoModePrefs::Availability incognito_availability =
282 IncognitoModePrefs::GetAvailability(profile_->GetPrefs());
283 bool incognito_enabled =
284 enabled && incognito_availability != IncognitoModePrefs::DISABLED;
286 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL,
287 IDS_BOOKMARK_BAR_OPEN_ALL,
288 node, menu, enabled);
289 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
290 IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
291 node, menu, enabled);
292 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
293 IDS_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
294 node, menu, incognito_enabled);
298 void BookmarkMenuBridge::AddItemToMenu(int command_id,
300 const BookmarkNode* node,
303 NSString* title = l10n_util::GetNSStringWithFixup(message_id);
306 // A nil action makes a menu item appear disabled. NSMenuItem setEnabled
307 // will not reflect the disabled state until the item title is set again.
309 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL) {
310 action = @selector(openAllBookmarks:);
311 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW) {
312 action = @selector(openAllBookmarksNewWindow:);
314 action = @selector(openAllBookmarksIncognitoWindow:);
316 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
318 keyEquivalent:@""] autorelease];
319 [item setTarget:controller_];
320 [item setTag:node->id()];
321 [item setEnabled:enabled];
325 void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node,
329 [item setTitle:[BookmarkMenuCocoaController menuTitleForNode:node]];
330 [item setTarget:controller_];
331 [item setAction:@selector(openBookmarkMenuItem:)];
332 [item setTag:node->id()];
334 [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
335 // Check to see if we have a favicon.
336 NSImage* favicon = nil;
337 BookmarkModel* model = GetBookmarkModel();
339 const gfx::Image& image = model->GetFavicon(node);
340 if (!image.IsEmpty())
341 favicon = image.ToNSImage();
343 // If we do not have a loaded favicon, use the default site image instead.
345 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
346 favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage();
348 [item setImage:favicon];
351 NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) {
354 std::map<const BookmarkNode*, NSMenuItem*>::iterator it =
355 bookmark_nodes_.find(node);
356 if (it == bookmark_nodes_.end())