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