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 #include "chrome/browser/ui/gtk/bookmarks/bookmark_sub_menu_model_gtk.h" 6 7 #include "base/stl_util.h" 8 #include "base/strings/string16.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "chrome/app/chrome_command_ids.h" 11 #include "chrome/browser/bookmarks/bookmark_model.h" 12 #include "chrome/browser/bookmarks/bookmark_model_factory.h" 13 #include "chrome/browser/bookmarks/bookmark_stats.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/browser/ui/browser.h" 16 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h" 17 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 18 #include "chrome/browser/ui/gtk/menu_gtk.h" 19 #include "grit/generated_resources.h" 20 #include "grit/theme_resources.h" 21 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h" 22 #include "ui/base/l10n/l10n_util.h" 23 #include "ui/base/resource/resource_bundle.h" 24 #include "ui/base/window_open_disposition.h" 25 26 using content::OpenURLParams; 27 using content::PageNavigator; 28 29 // Per chrome/app/chrome_command_ids.h, values < 4000 are for "dynamic menu 30 // items". We only use one command id for all the bookmarks, because we handle 31 // bookmark item activations directly. So we pick a suitably large random value 32 // and use that to avoid accidental conflicts with other dynamic items. 33 static const int kBookmarkItemCommandId = 1759; 34 35 BookmarkNodeMenuModel::BookmarkNodeMenuModel( 36 ui::SimpleMenuModel::Delegate* delegate, 37 BookmarkModel* model, 38 const BookmarkNode* node, 39 PageNavigator* page_navigator, 40 Profile* profile) 41 : SimpleMenuModel(delegate), 42 model_(model), 43 node_(node), 44 page_navigator_(page_navigator), 45 profile_(profile) { 46 DCHECK(page_navigator_); 47 } 48 49 BookmarkNodeMenuModel::~BookmarkNodeMenuModel() { 50 Clear(); 51 } 52 53 void BookmarkNodeMenuModel::Clear() { 54 SimpleMenuModel::Clear(); 55 STLDeleteElements(&submenus_); 56 } 57 58 void BookmarkNodeMenuModel::MenuWillShow() { 59 Clear(); 60 PopulateMenu(); 61 } 62 63 void BookmarkNodeMenuModel::MenuClosed() { 64 Clear(); 65 } 66 67 void BookmarkNodeMenuModel::ActivatedAt(int index) { 68 NavigateToMenuItem(index, CURRENT_TAB); 69 } 70 71 void BookmarkNodeMenuModel::ActivatedAt(int index, int event_flags) { 72 NavigateToMenuItem(index, ui::DispositionFromEventFlags(event_flags)); 73 } 74 75 void BookmarkNodeMenuModel::PopulateMenu() { 76 DCHECK(submenus_.empty()); 77 for (int i = 0; i < node_->child_count(); ++i) { 78 const BookmarkNode* child = node_->GetChild(i); 79 if (child->is_folder()) { 80 AddSubMenuForNode(child); 81 } else { 82 // Ironically the label will end up getting converted back to UTF8 later. 83 // We need to escape any Windows-style "&" characters since they will be 84 // converted in MenuGtk outside of our control here. 85 const base::string16 label = UTF8ToUTF16( 86 ui::EscapeWindowsStyleAccelerators(BuildMenuLabelFor(child))); 87 // No command id. We override ActivatedAt below to handle activations. 88 AddItem(kBookmarkItemCommandId, label); 89 GdkPixbuf* node_icon = GetPixbufForNode( 90 child, 91 model_, 92 GtkThemeService::GetFrom(profile_)->UsingNativeTheme()); 93 SetIcon(GetItemCount() - 1, gfx::Image(node_icon)); 94 // TODO(mdm): set up an observer to watch for icon load events and set 95 // the icons in response. 96 } 97 } 98 } 99 100 void BookmarkNodeMenuModel::AddSubMenuForNode(const BookmarkNode* node) { 101 DCHECK(node->is_folder()); 102 // Ironically the label will end up getting converted back to UTF8 later. 103 // We need to escape any Windows-style "&" characters since they will be 104 // converted in MenuGtk outside of our control here. 105 const base::string16 label = 106 UTF8ToUTF16(ui::EscapeWindowsStyleAccelerators(BuildMenuLabelFor(node))); 107 // Don't pass in the delegate, if any. Bookmark submenus don't need one. 108 BookmarkNodeMenuModel* submenu = 109 new BookmarkNodeMenuModel(NULL, model_, node, page_navigator_, profile_); 110 // No command id. Nothing happens if you click on the submenu itself. 111 AddSubMenu(kBookmarkItemCommandId, label, submenu); 112 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 113 const gfx::Image& folder_icon = rb.GetImageNamed(IDR_BOOKMARK_BAR_FOLDER); 114 SetIcon(GetItemCount() - 1, folder_icon); 115 submenus_.push_back(submenu); 116 } 117 118 void BookmarkNodeMenuModel::NavigateToMenuItem( 119 int index, 120 WindowOpenDisposition disposition) { 121 const BookmarkNode* node = node_->GetChild(index); 122 DCHECK(node); 123 RecordBookmarkLaunch(node, BOOKMARK_LAUNCH_LOCATION_WRENCH_MENU); 124 page_navigator_->OpenURL(OpenURLParams( 125 node->url(), content::Referrer(), disposition, 126 content::PAGE_TRANSITION_AUTO_BOOKMARK, 127 false)); // is_renderer_initiated 128 } 129 130 BookmarkSubMenuModel::BookmarkSubMenuModel( 131 ui::SimpleMenuModel::Delegate* delegate, 132 Browser* browser) 133 : BookmarkNodeMenuModel(delegate, NULL, NULL, browser, browser->profile()), 134 browser_(browser), 135 fixed_items_(0), 136 bookmark_end_(0), 137 menu_(NULL), 138 menu_showing_(false) { 139 } 140 141 BookmarkSubMenuModel::~BookmarkSubMenuModel() { 142 if (model()) 143 model()->RemoveObserver(this); 144 } 145 146 void BookmarkSubMenuModel::Loaded(BookmarkModel* model, bool ids_reassigned) { 147 // For now, just close the menu when the bookmarks are finished loading. 148 // TODO(mdm): it would be slicker to just populate the menu while it's open. 149 BookmarkModelChanged(); 150 } 151 152 void BookmarkSubMenuModel::BookmarkModelChanged() { 153 if (menu_showing_ && menu_) 154 menu_->Cancel(); 155 } 156 157 void BookmarkSubMenuModel::BookmarkModelBeingDeleted( 158 BookmarkModel* model) { 159 set_model(NULL); 160 // All our submenus will still have pointers to the model, but this call 161 // should force the menu to close, which will cause them to be deleted. 162 BookmarkModelChanged(); 163 } 164 165 void BookmarkSubMenuModel::MenuWillShow() { 166 menu_showing_ = true; 167 Clear(); 168 AddCheckItemWithStringId(IDC_SHOW_BOOKMARK_BAR, IDS_SHOW_BOOKMARK_BAR); 169 AddItemWithStringId(IDC_SHOW_BOOKMARK_MANAGER, IDS_BOOKMARK_MANAGER); 170 AddItemWithStringId(IDC_IMPORT_SETTINGS, IDS_IMPORT_SETTINGS_MENU_LABEL); 171 AddSeparator(ui::NORMAL_SEPARATOR); 172 AddItemWithStringId(IDC_BOOKMARK_PAGE, IDS_BOOKMARK_THIS_PAGE); 173 AddItemWithStringId(IDC_BOOKMARK_ALL_TABS, IDS_BOOKMARK_OPEN_PAGES); 174 fixed_items_ = bookmark_end_ = GetItemCount(); 175 if (!model()) { 176 set_model(BookmarkModelFactory::GetForProfile(browser_->profile())); 177 if (!model()) 178 return; 179 model()->AddObserver(this); 180 } 181 // We can't do anything further if the model isn't loaded yet. 182 if (!model()->loaded()) 183 return; 184 // The node count includes the node itself, so 1 means empty. 185 if (model()->bookmark_bar_node()->GetTotalNodeCount() > 1) { 186 AddSeparator(ui::NORMAL_SEPARATOR); 187 fixed_items_ = GetItemCount(); 188 if (!node()) 189 set_node(model()->bookmark_bar_node()); 190 // PopulateMenu() won't clear the items we added above. 191 PopulateMenu(); 192 } 193 bookmark_end_ = GetItemCount(); 194 195 // We want only one separator after the top-level bookmarks and before the 196 // other node and/or mobile node. 197 AddSeparator(ui::NORMAL_SEPARATOR); 198 if (model()->other_node()->GetTotalNodeCount() > 1) 199 AddSubMenuForNode(model()->other_node()); 200 if (model()->mobile_node()->GetTotalNodeCount() > 1) 201 AddSubMenuForNode(model()->mobile_node()); 202 RemoveTrailingSeparators(); 203 } 204 205 void BookmarkSubMenuModel::MenuClosed() { 206 menu_showing_ = false; 207 BookmarkNodeMenuModel::MenuClosed(); 208 } 209 210 void BookmarkSubMenuModel::ActivatedAt(int index) { 211 // Because this is also overridden in BookmarkNodeMenuModel which doesn't know 212 // we might be prepending items, we have to adjust the index for it. 213 if (index >= fixed_items_ && index < bookmark_end_) 214 BookmarkNodeMenuModel::ActivatedAt(index - fixed_items_); 215 else 216 SimpleMenuModel::ActivatedAt(index); 217 } 218 219 void BookmarkSubMenuModel::ActivatedAt(int index, int event_flags) { 220 // Because this is also overridden in BookmarkNodeMenuModel which doesn't know 221 // we might be prepending items, we have to adjust the index for it. 222 if (index >= fixed_items_ && index < bookmark_end_) 223 BookmarkNodeMenuModel::ActivatedAt(index - fixed_items_, event_flags); 224 else 225 SimpleMenuModel::ActivatedAt(index, event_flags); 226 } 227 228 bool BookmarkSubMenuModel::IsEnabledAt(int index) const { 229 // We don't want the delegate interfering with bookmark items. 230 return index >= fixed_items_ || SimpleMenuModel::IsEnabledAt(index); 231 } 232 233 bool BookmarkSubMenuModel::IsVisibleAt(int index) const { 234 // We don't want the delegate interfering with bookmark items. 235 return index >= fixed_items_ || SimpleMenuModel::IsVisibleAt(index); 236 } 237 238 // static 239 bool BookmarkSubMenuModel::IsBookmarkItemCommandId(int command_id) { 240 return command_id == kBookmarkItemCommandId; 241 } 242