Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / bookmarks / bookmark_menu_bridge.mm
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 "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"
26
27 BookmarkMenuBridge::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
36 BookmarkMenuBridge::~BookmarkMenuBridge() {
37   BookmarkModel* model = GetBookmarkModel();
38   if (model)
39     model->RemoveObserver(this);
40   [controller_ release];
41 }
42
43 NSMenu* BookmarkMenuBridge::BookmarkMenu() {
44   return [controller_ menu];
45 }
46
47 void BookmarkMenuBridge::BookmarkModelLoaded(BookmarkModel* model,
48                                              bool ids_reassigned) {
49   InvalidateMenu();
50 }
51
52 void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) {
53   UpdateMenuInternal(bookmark_menu, false);
54 }
55
56 void BookmarkMenuBridge::UpdateSubMenu(NSMenu* bookmark_menu) {
57   UpdateMenuInternal(bookmark_menu, true);
58 }
59
60 void 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
123 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
124   NSMenu* bookmark_menu = BookmarkMenu();
125   if (bookmark_menu == nil)
126     return;
127
128   ClearBookmarkMenu(bookmark_menu);
129 }
130
131 void 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
139 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
140                                            const BookmarkNode* parent,
141                                            int index) {
142   InvalidateMenu();
143 }
144
145 void 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
154 void BookmarkMenuBridge::BookmarkAllUserNodesRemoved(
155     BookmarkModel* model,
156     const std::set<GURL>& removed_urls) {
157   InvalidateMenu();
158 }
159
160 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model,
161                                              const BookmarkNode* node) {
162   NSMenuItem* item = MenuItemForNode(node);
163   if (item)
164     ConfigureMenuItem(node, item, true);
165 }
166
167 void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model,
168                                                     const BookmarkNode* node) {
169   NSMenuItem* item = MenuItemForNode(node);
170   if (item)
171     ConfigureMenuItem(node, item, false);
172 }
173
174 void BookmarkMenuBridge::BookmarkNodeChildrenReordered(
175     BookmarkModel* model, const BookmarkNode* node) {
176   InvalidateMenu();
177 }
178
179 void BookmarkMenuBridge::ResetMenu() {
180   ClearBookmarkMenu(BookmarkMenu());
181 }
182
183 void BookmarkMenuBridge::BuildMenu() {
184   UpdateMenu(BookmarkMenu());
185 }
186
187 // Watch for changes.
188 void BookmarkMenuBridge::ObserveBookmarkModel() {
189   BookmarkModel* model = GetBookmarkModel();
190   model->AddObserver(this);
191   if (model->loaded())
192     BookmarkModelLoaded(model, false);
193 }
194
195 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() {
196   if (!profile_)
197     return NULL;
198   return BookmarkModelFactory::GetForProfile(profile_);
199 }
200
201 Profile* BookmarkMenuBridge::GetProfile() {
202   return profile_;
203 }
204
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:)) ||
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
230 void 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?
247 void 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
298 void 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
325 void 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
351 NSMenuItem* 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 }