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/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