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