bookmark_menu_bridge.mm revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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. 4 5#import <AppKit/AppKit.h> 6 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 "components/bookmarks/browser/bookmark_model.h" 20#include "grit/generated_resources.h" 21#include "grit/theme_resources.h" 22#include "grit/ui_resources.h" 23#include "ui/base/l10n/l10n_util.h" 24#include "ui/base/resource/resource_bundle.h" 25#include "ui/gfx/image/image.h" 26 27BookmarkMenuBridge::BookmarkMenuBridge(Profile* profile, NSMenu* menu) 28 : menuIsValid_(false), 29 profile_(profile), 30 controller_([[BookmarkMenuCocoaController alloc] initWithBridge:this 31 andMenu:menu]) { 32 if (GetBookmarkModel()) 33 ObserveBookmarkModel(); 34} 35 36BookmarkMenuBridge::~BookmarkMenuBridge() { 37 BookmarkModel* model = GetBookmarkModel(); 38 if (model) 39 model->RemoveObserver(this); 40 [controller_ release]; 41} 42 43NSMenu* BookmarkMenuBridge::BookmarkMenu() { 44 return [controller_ menu]; 45} 46 47void BookmarkMenuBridge::BookmarkModelLoaded(BookmarkModel* model, 48 bool ids_reassigned) { 49 InvalidateMenu(); 50} 51 52void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) { 53 UpdateMenuInternal(bookmark_menu, false); 54} 55 56void BookmarkMenuBridge::UpdateSubMenu(NSMenu* bookmark_menu) { 57 UpdateMenuInternal(bookmark_menu, true); 58} 59 60void BookmarkMenuBridge::UpdateMenuInternal(NSMenu* bookmark_menu, 61 bool is_submenu) { 62 DCHECK(bookmark_menu); 63 if (menuIsValid_) 64 return; 65 66 BookmarkModel* model = GetBookmarkModel(); 67 if (!model || !model->loaded()) 68 return; 69 70 if (!folder_image_) { 71 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 72 folder_image_.reset( 73 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage()); 74 } 75 76 ClearBookmarkMenu(bookmark_menu); 77 78 // Add at most one separator for the bookmark bar and the managed bookmarks 79 // folder. 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(); 89 NSImage* image = 90 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER_MANAGED).ToNSImage(); 91 AddNodeAsSubmenu(bookmark_menu, managedNode, image, !is_submenu); 92 } 93 if (!barNode->empty()) 94 AddNodeToMenu(barNode, bookmark_menu, !is_submenu); 95 96 // If the "Other Bookmarks" folder has any content, make a submenu for it and 97 // fill it in. 98 if (!model->other_node()->empty()) { 99 [bookmark_menu addItem:[NSMenuItem separatorItem]]; 100 AddNodeAsSubmenu(bookmark_menu, 101 model->other_node(), 102 folder_image_, 103 !is_submenu); 104 } 105 106 // If the "Mobile Bookmarks" folder has any content, make a submenu for it and 107 // fill it in. 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]]; 113 114 AddNodeAsSubmenu(bookmark_menu, 115 model->mobile_node(), 116 folder_image_, 117 !is_submenu); 118 } 119 120 menuIsValid_ = true; 121} 122 123void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) { 124 NSMenu* bookmark_menu = BookmarkMenu(); 125 if (bookmark_menu == nil) 126 return; 127 128 ClearBookmarkMenu(bookmark_menu); 129} 130 131void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model, 132 const BookmarkNode* old_parent, 133 int old_index, 134 const BookmarkNode* new_parent, 135 int new_index) { 136 InvalidateMenu(); 137} 138 139void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model, 140 const BookmarkNode* parent, 141 int index) { 142 InvalidateMenu(); 143} 144 145void BookmarkMenuBridge::BookmarkNodeRemoved( 146 BookmarkModel* model, 147 const BookmarkNode* parent, 148 int old_index, 149 const BookmarkNode* node, 150 const std::set<GURL>& removed_urls) { 151 InvalidateMenu(); 152} 153 154void BookmarkMenuBridge::BookmarkAllUserNodesRemoved( 155 BookmarkModel* model, 156 const std::set<GURL>& removed_urls) { 157 InvalidateMenu(); 158} 159 160void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model, 161 const BookmarkNode* node) { 162 NSMenuItem* item = MenuItemForNode(node); 163 if (item) 164 ConfigureMenuItem(node, item, true); 165} 166 167void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model, 168 const BookmarkNode* node) { 169 NSMenuItem* item = MenuItemForNode(node); 170 if (item) 171 ConfigureMenuItem(node, item, false); 172} 173 174void BookmarkMenuBridge::BookmarkNodeChildrenReordered( 175 BookmarkModel* model, const BookmarkNode* node) { 176 InvalidateMenu(); 177} 178 179void BookmarkMenuBridge::ResetMenu() { 180 ClearBookmarkMenu(BookmarkMenu()); 181} 182 183void BookmarkMenuBridge::BuildMenu() { 184 UpdateMenu(BookmarkMenu()); 185} 186 187// Watch for changes. 188void BookmarkMenuBridge::ObserveBookmarkModel() { 189 BookmarkModel* model = GetBookmarkModel(); 190 model->AddObserver(this); 191 if (model->loaded()) 192 BookmarkModelLoaded(model, false); 193} 194 195BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() { 196 if (!profile_) 197 return NULL; 198 return BookmarkModelFactory::GetForProfile(profile_); 199} 200 201Profile* BookmarkMenuBridge::GetProfile() { 202 return profile_; 203} 204 205void 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:)) || 219 [item hasSubmenu] || 220 [item isSeparatorItem]) { 221 // This will eventually [obj release] all its kids, if it has 222 // any. 223 [menu removeItem:item]; 224 } else { 225 // Leave it alone. 226 } 227 } 228} 229 230void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu, 231 const BookmarkNode* node, 232 NSImage* image, 233 bool add_extra_items) { 234 NSString* title = SysUTF16ToNSString(node->GetTitle()); 235 NSMenuItem* items = [[[NSMenuItem alloc] 236 initWithTitle:title 237 action:nil 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); 244} 245 246// TODO(jrg): limit the number of bookmarks in the menubar? 247void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu, 248 bool add_extra_items) { 249 int child_count = node->child_count(); 250 if (!child_count) { 251 NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU); 252 NSMenuItem* item = 253 [[[NSMenuItem alloc] initWithTitle:empty_string 254 action:nil 255 keyEquivalent:@""] autorelease]; 256 [menu addItem:item]; 257 } else for (int i = 0; i < child_count; i++) { 258 const BookmarkNode* child = node->GetChild(i); 259 NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child]; 260 NSMenuItem* item = 261 [[[NSMenuItem alloc] initWithTitle:title 262 action:nil 263 keyEquivalent:@""] autorelease]; 264 [menu addItem:item]; 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 271 } else { 272 ConfigureMenuItem(child, item, false); 273 } 274 } 275 276 if (add_extra_items) { 277 // Add menus for 'Open All Bookmarks'. 278 [menu addItem:[NSMenuItem separatorItem]]; 279 bool enabled = child_count != 0; 280 281 IncognitoModePrefs::Availability incognito_availability = 282 IncognitoModePrefs::GetAvailability(profile_->GetPrefs()); 283 bool incognito_enabled = 284 enabled && incognito_availability != IncognitoModePrefs::DISABLED; 285 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); 295 } 296} 297 298void BookmarkMenuBridge::AddItemToMenu(int command_id, 299 int message_id, 300 const BookmarkNode* node, 301 NSMenu* menu, 302 bool enabled) { 303 NSString* title = l10n_util::GetNSStringWithFixup(message_id); 304 SEL action; 305 if (!enabled) { 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. 308 action = nil; 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:); 313 } else { 314 action = @selector(openAllBookmarksIncognitoWindow:); 315 } 316 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title 317 action:action 318 keyEquivalent:@""] autorelease]; 319 [item setTarget:controller_]; 320 [item setTag:node->id()]; 321 [item setEnabled:enabled]; 322 [menu addItem:item]; 323} 324 325void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node, 326 NSMenuItem* item, 327 bool set_title) { 328 if (set_title) 329 [item setTitle:[BookmarkMenuCocoaController menuTitleForNode:node]]; 330 [item setTarget:controller_]; 331 [item setAction:@selector(openBookmarkMenuItem:)]; 332 [item setTag:node->id()]; 333 if (node->is_url()) 334 [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]]; 335 // Check to see if we have a favicon. 336 NSImage* favicon = nil; 337 BookmarkModel* model = GetBookmarkModel(); 338 if (model) { 339 const gfx::Image& image = model->GetFavicon(node); 340 if (!image.IsEmpty()) 341 favicon = image.ToNSImage(); 342 } 343 // If we do not have a loaded favicon, use the default site image instead. 344 if (!favicon) { 345 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 346 favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage(); 347 } 348 [item setImage:favicon]; 349} 350 351NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) { 352 if (!node) 353 return nil; 354 std::map<const BookmarkNode*, NSMenuItem*>::iterator it = 355 bookmark_nodes_.find(node); 356 if (it == bookmark_nodes_.end()) 357 return nil; 358 return it->second; 359} 360