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