1 // Copyright (c) 2011 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 "app/mac/nsimage_cache.h" 8 #include "base/sys_string_conversions.h" 9 #include "chrome/app/chrome_command_ids.h" 10 #import "chrome/browser/app_controller_mac.h" 11 #include "chrome/browser/bookmarks/bookmark_model.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 "grit/generated_resources.h" 18 #include "grit/theme_resources.h" 19 #include "skia/ext/skia_utils_mac.h" 20 #include "ui/base/l10n/l10n_util.h" 21 #include "ui/base/resource/resource_bundle.h" 22 #include "ui/gfx/image.h" 23 24 BookmarkMenuBridge::BookmarkMenuBridge(Profile* profile) 25 : menuIsValid_(false), 26 profile_(profile), 27 controller_([[BookmarkMenuCocoaController alloc] initWithBridge:this]) { 28 if (GetBookmarkModel()) 29 ObserveBookmarkModel(); 30 } 31 32 BookmarkMenuBridge::~BookmarkMenuBridge() { 33 BookmarkModel *model = GetBookmarkModel(); 34 if (model) 35 model->RemoveObserver(this); 36 [controller_ release]; 37 } 38 39 NSMenu* BookmarkMenuBridge::BookmarkMenu() { 40 return [controller_ menu]; 41 } 42 43 void BookmarkMenuBridge::Loaded(BookmarkModel* model) { 44 InvalidateMenu(); 45 } 46 47 void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) { 48 DCHECK(bookmark_menu); 49 if (menuIsValid_) 50 return; 51 BookmarkModel* model = GetBookmarkModel(); 52 if (!model || !model->IsLoaded()) 53 return; 54 55 if (!folder_image_) { 56 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 57 folder_image_.reset( 58 [rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER) retain]); 59 } 60 61 ClearBookmarkMenu(bookmark_menu); 62 63 // Add bookmark bar items, if any. 64 const BookmarkNode* barNode = model->GetBookmarkBarNode(); 65 CHECK(barNode); 66 if (barNode->child_count()) { 67 [bookmark_menu addItem:[NSMenuItem separatorItem]]; 68 AddNodeToMenu(barNode, bookmark_menu); 69 } 70 71 // Create a submenu for "other bookmarks", and fill it in. 72 NSString* other_items_title = 73 l10n_util::GetNSString(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME); 74 [bookmark_menu addItem:[NSMenuItem separatorItem]]; 75 AddNodeAsSubmenu(bookmark_menu, 76 model->other_node(), 77 other_items_title); 78 79 menuIsValid_ = true; 80 } 81 82 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) { 83 NSMenu* bookmark_menu = BookmarkMenu(); 84 if (bookmark_menu == nil) 85 return; 86 87 ClearBookmarkMenu(bookmark_menu); 88 } 89 90 void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model, 91 const BookmarkNode* old_parent, 92 int old_index, 93 const BookmarkNode* new_parent, 94 int new_index) { 95 InvalidateMenu(); 96 } 97 98 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model, 99 const BookmarkNode* parent, 100 int index) { 101 InvalidateMenu(); 102 } 103 104 void BookmarkMenuBridge::BookmarkNodeRemoved(BookmarkModel* model, 105 const BookmarkNode* parent, 106 int old_index, 107 const BookmarkNode* node) { 108 InvalidateMenu(); 109 } 110 111 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model, 112 const BookmarkNode* node) { 113 NSMenuItem* item = MenuItemForNode(node); 114 if (item) 115 ConfigureMenuItem(node, item, true); 116 } 117 118 void BookmarkMenuBridge::BookmarkNodeFaviconLoaded(BookmarkModel* model, 119 const BookmarkNode* node) { 120 NSMenuItem* item = MenuItemForNode(node); 121 if (item) 122 ConfigureMenuItem(node, item, false); 123 } 124 125 void BookmarkMenuBridge::BookmarkNodeChildrenReordered( 126 BookmarkModel* model, const BookmarkNode* node) { 127 InvalidateMenu(); 128 } 129 130 // Watch for changes. 131 void BookmarkMenuBridge::ObserveBookmarkModel() { 132 BookmarkModel* model = GetBookmarkModel(); 133 model->AddObserver(this); 134 if (model->IsLoaded()) 135 Loaded(model); 136 } 137 138 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() { 139 if (!profile_) 140 return NULL; 141 return profile_->GetBookmarkModel(); 142 } 143 144 Profile* BookmarkMenuBridge::GetProfile() { 145 return profile_; 146 } 147 148 void BookmarkMenuBridge::ClearBookmarkMenu(NSMenu* menu) { 149 bookmark_nodes_.clear(); 150 // Recursively delete all menus that look like a bookmark. Also delete all 151 // separator items since we explicitly add them back in. This deletes 152 // everything except the first item ("Add Bookmark..."). 153 NSArray* items = [menu itemArray]; 154 for (NSMenuItem* item in items) { 155 // Convention: items in the bookmark list which are bookmarks have 156 // an action of openBookmarkMenuItem:. Also, assume all items 157 // with submenus are submenus of bookmarks. 158 if (([item action] == @selector(openBookmarkMenuItem:)) || 159 ([item action] == @selector(openAllBookmarks:)) || 160 ([item action] == @selector(openAllBookmarksNewWindow:)) || 161 ([item action] == @selector(openAllBookmarksIncognitoWindow:)) || 162 [item hasSubmenu] || 163 [item isSeparatorItem]) { 164 // This will eventually [obj release] all its kids, if it has 165 // any. 166 [menu removeItem:item]; 167 } else { 168 // Leave it alone. 169 } 170 } 171 } 172 173 void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu, 174 const BookmarkNode* node, 175 NSString* title) { 176 NSMenuItem* items = [[[NSMenuItem alloc] 177 initWithTitle:title 178 action:nil 179 keyEquivalent:@""] autorelease]; 180 [items setImage:folder_image_]; 181 [menu addItem:items]; 182 NSMenu* other_submenu = [[[NSMenu alloc] initWithTitle:title] 183 autorelease]; 184 [menu setSubmenu:other_submenu forItem:items]; 185 AddNodeToMenu(node, other_submenu); 186 } 187 188 // TODO(jrg): limit the number of bookmarks in the menubar? 189 void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu) { 190 int child_count = node->child_count(); 191 if (!child_count) { 192 NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU); 193 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:empty_string 194 action:nil 195 keyEquivalent:@""] autorelease]; 196 [menu addItem:item]; 197 } else for (int i = 0; i < child_count; i++) { 198 const BookmarkNode* child = node->GetChild(i); 199 NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child]; 200 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title 201 action:nil 202 keyEquivalent:@""] autorelease]; 203 [menu addItem:item]; 204 bookmark_nodes_[child] = item; 205 if (child->is_folder()) { 206 [item setImage:folder_image_]; 207 NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease]; 208 [menu setSubmenu:submenu forItem:item]; 209 AddNodeToMenu(child, submenu); // recursive call 210 } else { 211 ConfigureMenuItem(child, item, false); 212 } 213 } 214 215 // Add menus for 'Open All Bookmarks'. 216 [menu addItem:[NSMenuItem separatorItem]]; 217 bool enabled = child_count != 0; 218 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL, 219 IDS_BOOMARK_BAR_OPEN_ALL, 220 node, menu, enabled); 221 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, 222 IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW, 223 node, menu, enabled); 224 AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO, 225 IDS_BOOMARK_BAR_OPEN_INCOGNITO, 226 node, menu, enabled); 227 } 228 229 void BookmarkMenuBridge::AddItemToMenu(int command_id, 230 int message_id, 231 const BookmarkNode* node, 232 NSMenu* menu, 233 bool enabled) { 234 NSString* title = l10n_util::GetNSString(message_id); 235 SEL action; 236 if (!enabled) { 237 // A nil action makes a menu item appear disabled. NSMenuItem setEnabled 238 // will not reflect the disabled state until the item title is set again. 239 action = nil; 240 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL) { 241 action = @selector(openAllBookmarks:); 242 } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW) { 243 action = @selector(openAllBookmarksNewWindow:); 244 } else { 245 action = @selector(openAllBookmarksIncognitoWindow:); 246 } 247 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title 248 action:action 249 keyEquivalent:@""] autorelease]; 250 [item setTarget:controller_]; 251 [item setTag:node->id()]; 252 [item setEnabled:enabled]; 253 [menu addItem:item]; 254 } 255 256 void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node, 257 NSMenuItem* item, 258 bool set_title) { 259 if (set_title) { 260 NSString* title = [BookmarkMenuCocoaController menuTitleForNode:node]; 261 [item setTitle:title]; 262 } 263 [item setTarget:controller_]; 264 [item setAction:@selector(openBookmarkMenuItem:)]; 265 [item setTag:node->id()]; 266 if (node->is_url()) { 267 // Add a tooltip 268 std::string url_string = node->GetURL().possibly_invalid_spec(); 269 NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", 270 base::SysUTF16ToNSString(node->GetTitle()), 271 url_string.c_str()]; 272 [item setToolTip:tooltip]; 273 } 274 // Check to see if we have a favicon. 275 NSImage* favicon = nil; 276 BookmarkModel* model = GetBookmarkModel(); 277 if (model) { 278 const SkBitmap& bitmap = model->GetFavicon(node); 279 if (!bitmap.isNull()) 280 favicon = gfx::SkBitmapToNSImage(bitmap); 281 } 282 // Either we do not have a loaded favicon or the conversion from SkBitmap 283 // failed. Use the default site image instead. 284 if (!favicon) 285 favicon = app::mac::GetCachedImageWithName(@"nav.pdf"); 286 [item setImage:favicon]; 287 } 288 289 NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) { 290 if (!node) 291 return nil; 292 std::map<const BookmarkNode*, NSMenuItem*>::iterator it = 293 bookmark_nodes_.find(node); 294 if (it == bookmark_nodes_.end()) 295 return nil; 296 return it->second; 297 } 298