OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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/gtk/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/gtk/bookmark_utils_gtk.h" | |
13 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
14 | |
15 namespace { | |
16 | |
17 const char* kCellRendererTextKey = "__CELL_RENDERER_TEXT__"; | |
18 | |
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, GtkThemeProvider::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. | |
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->AddGroup(bb_node, bb_node->GetChildCount(), | |
59 title); | |
60 } else { | |
61 // Existing node, reset the title (BBModel ignores changes if the title | |
62 // is the same). | |
63 for (int j = 0; j < bb_node->GetChildCount(); ++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. | |
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 | |
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 | |
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->GetChildCount(); ++i) { | |
107 AddToTreeStoreAt(root_node->GetChild(i), selected_id, store, selected_iter, | |
108 NULL); | |
109 } | |
110 } | |
111 | |
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 | |
137 GtkCellRenderer* GetCellRendererText(GtkTreeView* tree_view) { | |
138 return static_cast<GtkCellRenderer*>( | |
139 g_object_get_data(G_OBJECT(tree_view), kCellRendererTextKey)); | |
140 } | |
141 | |
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->GetChildCount(); ++i) { | |
158 AddToTreeStoreAt(node->GetChild(i), selected_id, store, selected_iter, | |
159 &iter); | |
160 } | |
161 } | |
162 | |
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->GetChildCount(); ++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 | |
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 | |
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 | |
OLD | NEW |