- add sources.
[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.h"
11 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
12 #include "chrome/browser/prefs/incognito_mode_prefs.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/profiles/profile_manager.h"
15 #include "chrome/browser/ui/browser_list.h"
16 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
17 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller.h"
18 #include "grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "grit/ui_resources.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/image/image.h"
24
25 BookmarkMenuBridge::BookmarkMenuBridge(Profile* profile, NSMenu* menu)
26     : menuIsValid_(false),
27       profile_(profile),
28       controller_([[BookmarkMenuCocoaController alloc] initWithBridge:this
29                                                               andMenu:menu]) {
30   if (GetBookmarkModel())
31     ObserveBookmarkModel();
32 }
33
34 BookmarkMenuBridge::~BookmarkMenuBridge() {
35   BookmarkModel* model = GetBookmarkModel();
36   if (model)
37     model->RemoveObserver(this);
38   [controller_ release];
39 }
40
41 NSMenu* BookmarkMenuBridge::BookmarkMenu() {
42   return [controller_ menu];
43 }
44
45 void BookmarkMenuBridge::Loaded(BookmarkModel* model, bool ids_reassigned) {
46   InvalidateMenu();
47 }
48
49 void BookmarkMenuBridge::UpdateMenu(NSMenu* bookmark_menu) {
50   UpdateMenuInternal(bookmark_menu, false);
51 }
52
53 void BookmarkMenuBridge::UpdateSubMenu(NSMenu* bookmark_menu) {
54   UpdateMenuInternal(bookmark_menu, true);
55 }
56
57 void BookmarkMenuBridge::UpdateMenuInternal(NSMenu* bookmark_menu,
58                                             bool is_submenu) {
59   DCHECK(bookmark_menu);
60   if (menuIsValid_)
61     return;
62
63   BookmarkModel* model = GetBookmarkModel();
64   if (!model || !model->loaded())
65     return;
66
67   if (!folder_image_) {
68     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
69     folder_image_.reset(
70         rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).CopyNSImage());
71   }
72
73   ClearBookmarkMenu(bookmark_menu);
74
75   // Add bookmark bar items, if any.
76   const BookmarkNode* barNode = model->bookmark_bar_node();
77   CHECK(barNode);
78   if (!barNode->empty()) {
79     [bookmark_menu addItem:[NSMenuItem separatorItem]];
80     AddNodeToMenu(barNode, bookmark_menu, !is_submenu);
81   }
82
83   // If the "Other Bookmarks" folder has any content, make a submenu for it and
84   // fill it in.
85   if (!model->other_node()->empty()) {
86     [bookmark_menu addItem:[NSMenuItem separatorItem]];
87     AddNodeAsSubmenu(bookmark_menu,
88                      model->other_node(),
89                      !is_submenu);
90   }
91
92   // If the "Mobile Bookmarks" folder has any content, make a submenu for it and
93   // fill it in.
94   if (!model->mobile_node()->empty()) {
95     // Add a separator if we did not already add one due to a non-empty
96     // "Other Bookmarks" folder.
97     if (model->other_node()->empty())
98       [bookmark_menu addItem:[NSMenuItem separatorItem]];
99
100     AddNodeAsSubmenu(bookmark_menu,
101                      model->mobile_node(),
102                      !is_submenu);
103   }
104
105   menuIsValid_ = true;
106 }
107
108 void BookmarkMenuBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
109   NSMenu* bookmark_menu = BookmarkMenu();
110   if (bookmark_menu == nil)
111     return;
112
113   ClearBookmarkMenu(bookmark_menu);
114 }
115
116 void BookmarkMenuBridge::BookmarkNodeMoved(BookmarkModel* model,
117                                            const BookmarkNode* old_parent,
118                                            int old_index,
119                                            const BookmarkNode* new_parent,
120                                            int new_index) {
121   InvalidateMenu();
122 }
123
124 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
125                                            const BookmarkNode* parent,
126                                            int index) {
127   InvalidateMenu();
128 }
129
130 void BookmarkMenuBridge::BookmarkNodeRemoved(BookmarkModel* model,
131                                              const BookmarkNode* parent,
132                                              int old_index,
133                                              const BookmarkNode* node) {
134   InvalidateMenu();
135 }
136
137 void BookmarkMenuBridge::BookmarkAllNodesRemoved(BookmarkModel* model) {
138   InvalidateMenu();
139 }
140
141 void BookmarkMenuBridge::BookmarkNodeChanged(BookmarkModel* model,
142                                              const BookmarkNode* node) {
143   NSMenuItem* item = MenuItemForNode(node);
144   if (item)
145     ConfigureMenuItem(node, item, true);
146 }
147
148 void BookmarkMenuBridge::BookmarkNodeFaviconChanged(BookmarkModel* model,
149                                                     const BookmarkNode* node) {
150   NSMenuItem* item = MenuItemForNode(node);
151   if (item)
152     ConfigureMenuItem(node, item, false);
153 }
154
155 void BookmarkMenuBridge::BookmarkNodeChildrenReordered(
156     BookmarkModel* model, const BookmarkNode* node) {
157   InvalidateMenu();
158 }
159
160 void BookmarkMenuBridge::ResetMenu() {
161   ClearBookmarkMenu(BookmarkMenu());
162 }
163
164 void BookmarkMenuBridge::BuildMenu() {
165   UpdateMenu(BookmarkMenu());
166 }
167
168 // Watch for changes.
169 void BookmarkMenuBridge::ObserveBookmarkModel() {
170   BookmarkModel* model = GetBookmarkModel();
171   model->AddObserver(this);
172   if (model->loaded())
173     Loaded(model, false);
174 }
175
176 BookmarkModel* BookmarkMenuBridge::GetBookmarkModel() {
177   if (!profile_)
178     return NULL;
179   return BookmarkModelFactory::GetForProfile(profile_);
180 }
181
182 Profile* BookmarkMenuBridge::GetProfile() {
183   return profile_;
184 }
185
186 void BookmarkMenuBridge::ClearBookmarkMenu(NSMenu* menu) {
187   bookmark_nodes_.clear();
188   // Recursively delete all menus that look like a bookmark. Also delete all
189   // separator items since we explicitly add them back in. This deletes
190   // everything except the first item ("Add Bookmark...").
191   NSArray* items = [menu itemArray];
192   for (NSMenuItem* item in items) {
193     // Convention: items in the bookmark list which are bookmarks have
194     // an action of openBookmarkMenuItem:.  Also, assume all items
195     // with submenus are submenus of bookmarks.
196     if (([item action] == @selector(openBookmarkMenuItem:)) ||
197         ([item action] == @selector(openAllBookmarks:)) ||
198         ([item action] == @selector(openAllBookmarksNewWindow:)) ||
199         ([item action] == @selector(openAllBookmarksIncognitoWindow:)) ||
200         [item hasSubmenu] ||
201         [item isSeparatorItem]) {
202       // This will eventually [obj release] all its kids, if it has
203       // any.
204       [menu removeItem:item];
205     } else {
206       // Leave it alone.
207     }
208   }
209 }
210
211 void BookmarkMenuBridge::AddNodeAsSubmenu(NSMenu* menu,
212                                           const BookmarkNode* node,
213                                           bool add_extra_items) {
214   NSString* title = SysUTF16ToNSString(node->GetTitle());
215   NSMenuItem* items = [[[NSMenuItem alloc]
216                             initWithTitle:title
217                                    action:nil
218                             keyEquivalent:@""] autorelease];
219   [items setImage:folder_image_];
220   [menu addItem:items];
221   NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
222   [menu setSubmenu:submenu forItem:items];
223   AddNodeToMenu(node, submenu, add_extra_items);
224 }
225
226 // TODO(jrg): limit the number of bookmarks in the menubar?
227 void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu,
228                                        bool add_extra_items) {
229   int child_count = node->child_count();
230   if (!child_count) {
231     NSString* empty_string = l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU);
232     NSMenuItem* item =
233         [[[NSMenuItem alloc] initWithTitle:empty_string
234                                     action:nil
235                              keyEquivalent:@""] autorelease];
236     [menu addItem:item];
237   } else for (int i = 0; i < child_count; i++) {
238     const BookmarkNode* child = node->GetChild(i);
239     NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child];
240     NSMenuItem* item =
241         [[[NSMenuItem alloc] initWithTitle:title
242                                     action:nil
243                              keyEquivalent:@""] autorelease];
244     [menu addItem:item];
245     bookmark_nodes_[child] = item;
246     if (child->is_folder()) {
247       [item setImage:folder_image_];
248       NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease];
249       [menu setSubmenu:submenu forItem:item];
250       AddNodeToMenu(child, submenu, add_extra_items);  // recursive call
251     } else {
252       ConfigureMenuItem(child, item, false);
253     }
254   }
255
256   if (add_extra_items) {
257     // Add menus for 'Open All Bookmarks'.
258     [menu addItem:[NSMenuItem separatorItem]];
259     bool enabled = child_count != 0;
260
261     IncognitoModePrefs::Availability incognito_availability =
262         IncognitoModePrefs::GetAvailability(profile_->GetPrefs());
263     bool incognito_enabled =
264         enabled && incognito_availability != IncognitoModePrefs::DISABLED;
265
266     AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL,
267                   IDS_BOOKMARK_BAR_OPEN_ALL,
268                   node, menu, enabled);
269     AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
270                   IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
271                   node, menu, enabled);
272     AddItemToMenu(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
273                   IDS_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
274                   node, menu, incognito_enabled);
275   }
276 }
277
278 void BookmarkMenuBridge::AddItemToMenu(int command_id,
279                                        int message_id,
280                                        const BookmarkNode* node,
281                                        NSMenu* menu,
282                                        bool enabled) {
283   NSString* title = l10n_util::GetNSStringWithFixup(message_id);
284   SEL action;
285   if (!enabled) {
286     // A nil action makes a menu item appear disabled. NSMenuItem setEnabled
287     // will not reflect the disabled state until the item title is set again.
288     action = nil;
289   } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL) {
290     action = @selector(openAllBookmarks:);
291   } else if (command_id == IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW) {
292     action = @selector(openAllBookmarksNewWindow:);
293   } else {
294     action = @selector(openAllBookmarksIncognitoWindow:);
295   }
296   NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
297                                                  action:action
298                                           keyEquivalent:@""] autorelease];
299   [item setTarget:controller_];
300   [item setTag:node->id()];
301   [item setEnabled:enabled];
302   [menu addItem:item];
303 }
304
305 void BookmarkMenuBridge::ConfigureMenuItem(const BookmarkNode* node,
306                                            NSMenuItem* item,
307                                            bool set_title) {
308   if (set_title)
309     [item setTitle:[BookmarkMenuCocoaController menuTitleForNode:node]];
310   [item setTarget:controller_];
311   [item setAction:@selector(openBookmarkMenuItem:)];
312   [item setTag:node->id()];
313   if (node->is_url())
314     [item setToolTip:[BookmarkMenuCocoaController tooltipForNode:node]];
315   // Check to see if we have a favicon.
316   NSImage* favicon = nil;
317   BookmarkModel* model = GetBookmarkModel();
318   if (model) {
319     const gfx::Image& image = model->GetFavicon(node);
320     if (!image.IsEmpty())
321       favicon = image.ToNSImage();
322   }
323   // If we do not have a loaded favicon, use the default site image instead.
324   if (!favicon) {
325     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
326     favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage();
327   }
328   [item setImage:favicon];
329 }
330
331 NSMenuItem* BookmarkMenuBridge::MenuItemForNode(const BookmarkNode* node) {
332   if (!node)
333     return nil;
334   std::map<const BookmarkNode*, NSMenuItem*>::iterator it =
335       bookmark_nodes_.find(node);
336   if (it == bookmark_nodes_.end())
337     return nil;
338   return it->second;
339 }