Home | History | Annotate | Download | only in bookmarks
      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