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_tree_model.h"
      6 
      7 #include <gtk/gtk.h>
      8 
      9 #include "base/strings/string_util.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/bookmarks/bookmark_model.h"
     12 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
     13 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     14 #include "ui/gfx/image/image.h"
     15 
     16 namespace {
     17 
     18 const char* kCellRendererTextKey = "__CELL_RENDERER_TEXT__";
     19 
     20 void AddSingleNodeToTreeStore(GtkTreeStore* store, const BookmarkNode* node,
     21                               GtkTreeIter *iter, GtkTreeIter* parent) {
     22   gtk_tree_store_append(store, iter, parent);
     23   // It would be easy to show a different icon when the folder is open (as they
     24   // do on Windows, for example), using pixbuf-expander-closed and
     25   // pixbuf-expander-open. Unfortunately there is no GTK_STOCK_OPEN_DIRECTORY
     26   // (and indeed, Nautilus does not render an expanded directory any
     27   // differently).
     28   gtk_tree_store_set(store, iter,
     29       bookmark_utils::FOLDER_ICON,
     30       GtkThemeService::GetFolderIcon(true).ToGdkPixbuf(),
     31       bookmark_utils::FOLDER_NAME,
     32       UTF16ToUTF8(node->GetTitle()).c_str(),
     33       bookmark_utils::ITEM_ID, node->id(),
     34       // We don't want to use node->is_folder() because that would let the
     35       // user edit "Bookmarks Bar" and "Other Bookmarks".
     36       bookmark_utils::IS_EDITABLE, node->type() == BookmarkNode::FOLDER,
     37       -1);
     38 }
     39 
     40 // Helper function for CommitTreeStoreDifferencesBetween() which recursively
     41 // merges changes back from a GtkTreeStore into a tree of BookmarkNodes. This
     42 // function only works on non-root nodes; our caller handles that special case.
     43 void RecursiveResolve(BookmarkModel* bb_model,
     44                       const BookmarkNode* bb_node,
     45                       GtkTreeStore* tree_store,
     46                       GtkTreeIter* parent_iter,
     47                       GtkTreePath* selected_path,
     48                       const BookmarkNode** selected_node) {
     49   GtkTreePath* current_path =
     50       gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store), parent_iter);
     51   if (gtk_tree_path_compare(current_path, selected_path) == 0)
     52     *selected_node = bb_node;
     53   gtk_tree_path_free(current_path);
     54 
     55   GtkTreeIter child_iter;
     56   if (gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store), &child_iter,
     57                                    parent_iter)) {
     58     do {
     59       int64 id = bookmark_utils::GetIdFromTreeIter(GTK_TREE_MODEL(tree_store),
     60                                                    &child_iter);
     61       string16 title =
     62           bookmark_utils::GetTitleFromTreeIter(GTK_TREE_MODEL(tree_store),
     63                                                &child_iter);
     64       const BookmarkNode* child_bb_node = NULL;
     65       if (id == 0) {
     66         child_bb_node = bb_model->AddFolder(
     67             bb_node, bb_node->child_count(), title);
     68 
     69         // Set the value in the model so if we lookup the id later we get the
     70         // real id and not 0.
     71         GValue value  = { 0 };
     72         g_value_init(&value, G_TYPE_INT64);
     73         g_value_set_int64(&value, child_bb_node->id());
     74         gtk_tree_store_set_value(tree_store, &child_iter,
     75                                  bookmark_utils::ITEM_ID, &value);
     76       } else {
     77         // Existing node, reset the title (BookmarkModel ignores changes if the
     78         // title is the same).
     79         for (int j = 0; j < bb_node->child_count(); ++j) {
     80           const BookmarkNode* node = bb_node->GetChild(j);
     81           if (node->is_folder() && node->id() == id) {
     82             child_bb_node = node;
     83             break;
     84           }
     85         }
     86         DCHECK(child_bb_node);
     87         bb_model->SetTitle(child_bb_node, title);
     88       }
     89       RecursiveResolve(bb_model, child_bb_node, tree_store, &child_iter,
     90                        selected_path, selected_node);
     91     } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &child_iter));
     92   }
     93 }
     94 
     95 // Update the folder name in the GtkTreeStore.
     96 void OnFolderNameEdited(GtkCellRendererText* render,
     97     gchar* path, gchar* new_folder_name, GtkTreeStore* tree_store) {
     98   GtkTreeIter folder_iter;
     99   GtkTreePath* tree_path = gtk_tree_path_new_from_string(path);
    100   gboolean rv = gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_store),
    101                                         &folder_iter, tree_path);
    102   DCHECK(rv);
    103   gtk_tree_store_set(tree_store, &folder_iter,
    104                      bookmark_utils::FOLDER_NAME, new_folder_name,
    105                      -1);
    106   gtk_tree_path_free(tree_path);
    107 }
    108 
    109 }  // namespace
    110 
    111 namespace bookmark_utils {
    112 
    113 GtkTreeStore* MakeFolderTreeStore() {
    114   return gtk_tree_store_new(FOLDER_STORE_NUM_COLUMNS, GDK_TYPE_PIXBUF,
    115                             G_TYPE_STRING, G_TYPE_INT64, G_TYPE_BOOLEAN);
    116 }
    117 
    118 void AddToTreeStore(BookmarkModel* model, int64 selected_id,
    119                     GtkTreeStore* store, GtkTreeIter* selected_iter) {
    120   const BookmarkNode* root_node = model->root_node();
    121   for (int i = 0; i < root_node->child_count(); ++i) {
    122     const BookmarkNode* child = root_node->GetChild(i);
    123     if (child->IsVisible())
    124       AddToTreeStoreAt(child, selected_id, store, selected_iter, NULL);
    125   }
    126 }
    127 
    128 GtkWidget* MakeTreeViewForStore(GtkTreeStore* store) {
    129   GtkTreeViewColumn* column = gtk_tree_view_column_new();
    130   GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new();
    131   gtk_tree_view_column_pack_start(column, image_renderer, FALSE);
    132   gtk_tree_view_column_add_attribute(column, image_renderer,
    133                                      "pixbuf", FOLDER_ICON);
    134   GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new();
    135   g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
    136   g_signal_connect(text_renderer, "edited", G_CALLBACK(OnFolderNameEdited),
    137                    store);
    138   gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
    139   gtk_tree_view_column_set_attributes(column, text_renderer,
    140                                       "text", FOLDER_NAME,
    141                                       "editable", IS_EDITABLE,
    142                                       NULL);
    143 
    144   GtkWidget* tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
    145   // Let |tree_view| own the store.
    146   g_object_unref(store);
    147   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
    148   gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
    149   g_object_set_data(G_OBJECT(tree_view), kCellRendererTextKey, text_renderer);
    150   return tree_view;
    151 }
    152 
    153 GtkCellRenderer* GetCellRendererText(GtkTreeView* tree_view) {
    154   return static_cast<GtkCellRenderer*>(
    155       g_object_get_data(G_OBJECT(tree_view), kCellRendererTextKey));
    156 }
    157 
    158 void AddToTreeStoreAt(const BookmarkNode* node, int64 selected_id,
    159                       GtkTreeStore* store, GtkTreeIter* selected_iter,
    160                       GtkTreeIter* parent) {
    161   if (!node->is_folder())
    162     return;
    163 
    164   GtkTreeIter iter;
    165   AddSingleNodeToTreeStore(store, node, &iter, parent);
    166   if (selected_iter && node->id() == selected_id) {
    167      // Save the iterator. Since we're using a GtkTreeStore, we're
    168      // guaranteed that the iterator will remain valid as long as the above
    169      // appended item exists.
    170      *selected_iter = iter;
    171   }
    172 
    173   for (int i = 0; i < node->child_count(); ++i) {
    174     AddToTreeStoreAt(node->GetChild(i), selected_id, store, selected_iter,
    175                      &iter);
    176   }
    177 }
    178 
    179 const BookmarkNode* CommitTreeStoreDifferencesBetween(
    180     BookmarkModel* bb_model, GtkTreeStore* tree_store, GtkTreeIter* selected) {
    181   const BookmarkNode* node_to_return = NULL;
    182   GtkTreeModel* tree_model = GTK_TREE_MODEL(tree_store);
    183 
    184   GtkTreePath* selected_path = gtk_tree_model_get_path(tree_model, selected);
    185 
    186   GtkTreeIter tree_root;
    187   if (!gtk_tree_model_get_iter_first(tree_model, &tree_root))
    188     NOTREACHED() << "Impossible missing bookmarks case";
    189 
    190   // The top level of this tree is weird and needs to be special cased. The
    191   // BookmarksNode tree is rooted on a root node while the GtkTreeStore has a
    192   // set of top level nodes that are the root BookmarksNode's children. These
    193   // items in the top level are not editable and therefore don't need the extra
    194   // complexity of trying to modify their title.
    195   const BookmarkNode* root_node = bb_model->root_node();
    196   do {
    197     DCHECK(GetIdFromTreeIter(tree_model, &tree_root) != 0)
    198         << "It should be impossible to add another toplevel node";
    199 
    200     int64 id = GetIdFromTreeIter(tree_model, &tree_root);
    201     const BookmarkNode* child_node = NULL;
    202     for (int j = 0; j < root_node->child_count(); ++j) {
    203       const BookmarkNode* node = root_node->GetChild(j);
    204       if (node->is_folder() && node->id() == id) {
    205         child_node = node;
    206         break;
    207       }
    208     }
    209     DCHECK(child_node);
    210 
    211     GtkTreeIter child_iter = tree_root;
    212     RecursiveResolve(bb_model, child_node, tree_store, &child_iter,
    213                      selected_path, &node_to_return);
    214   } while (gtk_tree_model_iter_next(tree_model, &tree_root));
    215 
    216   gtk_tree_path_free(selected_path);
    217   return node_to_return;
    218 }
    219 
    220 int64 GetIdFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter) {
    221   GValue value = { 0, };
    222   int64 ret_val = -1;
    223   gtk_tree_model_get_value(model, iter, ITEM_ID, &value);
    224   if (G_VALUE_HOLDS_INT64(&value))
    225     ret_val = g_value_get_int64(&value);
    226   else
    227     NOTREACHED() << "Impossible type mismatch";
    228 
    229   return ret_val;
    230 }
    231 
    232 string16 GetTitleFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter) {
    233   GValue value = { 0, };
    234   string16 ret_val;
    235   gtk_tree_model_get_value(model, iter, FOLDER_NAME, &value);
    236   if (G_VALUE_HOLDS_STRING(&value)) {
    237     const gchar* utf8str = g_value_get_string(&value);
    238     ret_val = UTF8ToUTF16(utf8str);
    239     g_value_unset(&value);
    240   } else {
    241     NOTREACHED() << "Impossible type mismatch";
    242   }
    243 
    244   return ret_val;
    245 }
    246 
    247 }  // namespace bookmark_utils
    248