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.
5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_tree_model.h"
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"
18 const char* kCellRendererTextKey
= "__CELL_RENDERER_TEXT__";
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
28 gtk_tree_store_set(store
,
31 GtkThemeService::GetFolderIcon(true).ToGdkPixbuf(),
33 base::UTF16ToUTF8(node
->GetTitle()).c_str(),
36 // We don't want to use node->is_folder() because that
38 // user edit "Bookmarks Bar" and "Other Bookmarks".
40 node
->type() == BookmarkNode::FOLDER
,
44 // Helper function for CommitTreeStoreDifferencesBetween() which recursively
45 // merges changes back from a GtkTreeStore into a tree of BookmarkNodes. This
46 // function only works on non-root nodes; our caller handles that special case.
47 void RecursiveResolve(BookmarkModel
* bb_model
,
48 const BookmarkNode
* bb_node
,
49 GtkTreeStore
* tree_store
,
50 GtkTreeIter
* parent_iter
,
51 GtkTreePath
* selected_path
,
52 const BookmarkNode
** selected_node
) {
53 GtkTreePath
* current_path
=
54 gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store
), parent_iter
);
55 if (gtk_tree_path_compare(current_path
, selected_path
) == 0)
56 *selected_node
= bb_node
;
57 gtk_tree_path_free(current_path
);
59 GtkTreeIter child_iter
;
60 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store
), &child_iter
,
63 int64 id
= GetIdFromTreeIter(GTK_TREE_MODEL(tree_store
), &child_iter
);
64 base::string16 title
=
65 GetTitleFromTreeIter(GTK_TREE_MODEL(tree_store
), &child_iter
);
66 const BookmarkNode
* child_bb_node
= NULL
;
68 child_bb_node
= bb_model
->AddFolder(
69 bb_node
, bb_node
->child_count(), title
);
71 // Set the value in the model so if we lookup the id later we get the
74 g_value_init(&value
, G_TYPE_INT64
);
75 g_value_set_int64(&value
, child_bb_node
->id());
76 gtk_tree_store_set_value(tree_store
, &child_iter
, ITEM_ID
, &value
);
78 // Existing node, reset the title (BookmarkModel ignores changes if the
79 // title is the same).
80 for (int j
= 0; j
< bb_node
->child_count(); ++j
) {
81 const BookmarkNode
* node
= bb_node
->GetChild(j
);
82 if (node
->is_folder() && node
->id() == id
) {
87 DCHECK(child_bb_node
);
88 bb_model
->SetTitle(child_bb_node
, title
);
90 RecursiveResolve(bb_model
, child_bb_node
, tree_store
, &child_iter
,
91 selected_path
, selected_node
);
92 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store
), &child_iter
));
96 // Update the folder name in the GtkTreeStore.
97 void OnFolderNameEdited(GtkCellRendererText
* render
,
98 gchar
* path
, gchar
* new_folder_name
, GtkTreeStore
* tree_store
) {
99 GtkTreeIter folder_iter
;
100 GtkTreePath
* tree_path
= gtk_tree_path_new_from_string(path
);
101 gboolean rv
= gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_store
),
102 &folder_iter
, tree_path
);
105 tree_store
, &folder_iter
, FOLDER_NAME
, new_folder_name
, -1);
106 gtk_tree_path_free(tree_path
);
111 GtkTreeStore
* MakeFolderTreeStore() {
112 return gtk_tree_store_new(FOLDER_STORE_NUM_COLUMNS
, GDK_TYPE_PIXBUF
,
113 G_TYPE_STRING
, G_TYPE_INT64
, G_TYPE_BOOLEAN
);
116 void AddToTreeStore(BookmarkModel
* model
, int64 selected_id
,
117 GtkTreeStore
* store
, GtkTreeIter
* selected_iter
) {
118 const BookmarkNode
* root_node
= model
->root_node();
119 for (int i
= 0; i
< root_node
->child_count(); ++i
) {
120 const BookmarkNode
* child
= root_node
->GetChild(i
);
121 if (child
->IsVisible())
122 AddToTreeStoreAt(child
, selected_id
, store
, selected_iter
, NULL
);
126 GtkWidget
* MakeTreeViewForStore(GtkTreeStore
* store
) {
127 GtkTreeViewColumn
* column
= gtk_tree_view_column_new();
128 GtkCellRenderer
* image_renderer
= gtk_cell_renderer_pixbuf_new();
129 gtk_tree_view_column_pack_start(column
, image_renderer
, FALSE
);
130 gtk_tree_view_column_add_attribute(column
, image_renderer
,
131 "pixbuf", FOLDER_ICON
);
132 GtkCellRenderer
* text_renderer
= gtk_cell_renderer_text_new();
133 g_object_set(text_renderer
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
134 g_signal_connect(text_renderer
, "edited", G_CALLBACK(OnFolderNameEdited
),
136 gtk_tree_view_column_pack_start(column
, text_renderer
, TRUE
);
137 gtk_tree_view_column_set_attributes(column
, text_renderer
,
139 "editable", IS_EDITABLE
,
142 GtkWidget
* tree_view
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
));
143 // Let |tree_view| own the store.
144 g_object_unref(store
);
145 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view
), FALSE
);
146 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view
), column
);
147 g_object_set_data(G_OBJECT(tree_view
), kCellRendererTextKey
, text_renderer
);
151 GtkCellRenderer
* GetCellRendererText(GtkTreeView
* tree_view
) {
152 return static_cast<GtkCellRenderer
*>(
153 g_object_get_data(G_OBJECT(tree_view
), kCellRendererTextKey
));
156 void AddToTreeStoreAt(const BookmarkNode
* node
, int64 selected_id
,
157 GtkTreeStore
* store
, GtkTreeIter
* selected_iter
,
158 GtkTreeIter
* parent
) {
159 if (!node
->is_folder())
163 AddSingleNodeToTreeStore(store
, node
, &iter
, parent
);
164 if (selected_iter
&& node
->id() == selected_id
) {
165 // Save the iterator. Since we're using a GtkTreeStore, we're
166 // guaranteed that the iterator will remain valid as long as the above
167 // appended item exists.
168 *selected_iter
= iter
;
171 for (int i
= 0; i
< node
->child_count(); ++i
) {
172 AddToTreeStoreAt(node
->GetChild(i
), selected_id
, store
, selected_iter
,
177 const BookmarkNode
* CommitTreeStoreDifferencesBetween(
178 BookmarkModel
* bb_model
, GtkTreeStore
* tree_store
, GtkTreeIter
* selected
) {
179 const BookmarkNode
* node_to_return
= NULL
;
180 GtkTreeModel
* tree_model
= GTK_TREE_MODEL(tree_store
);
182 GtkTreePath
* selected_path
= gtk_tree_model_get_path(tree_model
, selected
);
184 GtkTreeIter tree_root
;
185 if (!gtk_tree_model_get_iter_first(tree_model
, &tree_root
))
186 NOTREACHED() << "Impossible missing bookmarks case";
188 // The top level of this tree is weird and needs to be special cased. The
189 // BookmarksNode tree is rooted on a root node while the GtkTreeStore has a
190 // set of top level nodes that are the root BookmarksNode's children. These
191 // items in the top level are not editable and therefore don't need the extra
192 // complexity of trying to modify their title.
193 const BookmarkNode
* root_node
= bb_model
->root_node();
195 DCHECK(GetIdFromTreeIter(tree_model
, &tree_root
) != 0)
196 << "It should be impossible to add another toplevel node";
198 int64 id
= GetIdFromTreeIter(tree_model
, &tree_root
);
199 const BookmarkNode
* child_node
= NULL
;
200 for (int j
= 0; j
< root_node
->child_count(); ++j
) {
201 const BookmarkNode
* node
= root_node
->GetChild(j
);
202 if (node
->is_folder() && node
->id() == id
) {
209 GtkTreeIter child_iter
= tree_root
;
210 RecursiveResolve(bb_model
, child_node
, tree_store
, &child_iter
,
211 selected_path
, &node_to_return
);
212 } while (gtk_tree_model_iter_next(tree_model
, &tree_root
));
214 gtk_tree_path_free(selected_path
);
215 return node_to_return
;
218 int64
GetIdFromTreeIter(GtkTreeModel
* model
, GtkTreeIter
* iter
) {
219 GValue value
= { 0, };
221 gtk_tree_model_get_value(model
, iter
, ITEM_ID
, &value
);
222 if (G_VALUE_HOLDS_INT64(&value
))
223 ret_val
= g_value_get_int64(&value
);
225 NOTREACHED() << "Impossible type mismatch";
230 base::string16
GetTitleFromTreeIter(GtkTreeModel
* model
, GtkTreeIter
* iter
) {
231 GValue value
= { 0, };
232 base::string16 ret_val
;
233 gtk_tree_model_get_value(model
, iter
, FOLDER_NAME
, &value
);
234 if (G_VALUE_HOLDS_STRING(&value
)) {
235 const gchar
* utf8str
= g_value_get_string(&value
);
236 ret_val
= base::UTF8ToUTF16(utf8str
);
237 g_value_unset(&value
);
239 NOTREACHED() << "Impossible type mismatch";