- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / bookmarks / bookmark_tree_model.cc
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,
29                      iter,
30                      FOLDER_ICON,
31                      GtkThemeService::GetFolderIcon(true).ToGdkPixbuf(),
32                      FOLDER_NAME,
33                      UTF16ToUTF8(node->GetTitle()).c_str(),
34                      ITEM_ID,
35                      node->id(),
36                      // We don't want to use node->is_folder() because that
37                      // would let the
38                      // user edit "Bookmarks Bar" and "Other Bookmarks".
39                      IS_EDITABLE,
40                      node->type() == BookmarkNode::FOLDER,
41                      -1);
42 }
43
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);
58
59   GtkTreeIter child_iter;
60   if (gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store), &child_iter,
61                                    parent_iter)) {
62     do {
63       int64 id = GetIdFromTreeIter(GTK_TREE_MODEL(tree_store), &child_iter);
64       string16 title =
65           GetTitleFromTreeIter(GTK_TREE_MODEL(tree_store), &child_iter);
66       const BookmarkNode* child_bb_node = NULL;
67       if (id == 0) {
68         child_bb_node = bb_model->AddFolder(
69             bb_node, bb_node->child_count(), title);
70
71         // Set the value in the model so if we lookup the id later we get the
72         // real id and not 0.
73         GValue value  = { 0 };
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);
77       } else {
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) {
83             child_bb_node = node;
84             break;
85           }
86         }
87         DCHECK(child_bb_node);
88         bb_model->SetTitle(child_bb_node, title);
89       }
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));
93   }
94 }
95
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);
103   DCHECK(rv);
104   gtk_tree_store_set(
105       tree_store, &folder_iter, FOLDER_NAME, new_folder_name, -1);
106   gtk_tree_path_free(tree_path);
107 }
108
109 }  // namespace
110
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);
114 }
115
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);
123   }
124 }
125
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),
135                    store);
136   gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
137   gtk_tree_view_column_set_attributes(column, text_renderer,
138                                       "text", FOLDER_NAME,
139                                       "editable", IS_EDITABLE,
140                                       NULL);
141
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);
148   return tree_view;
149 }
150
151 GtkCellRenderer* GetCellRendererText(GtkTreeView* tree_view) {
152   return static_cast<GtkCellRenderer*>(
153       g_object_get_data(G_OBJECT(tree_view), kCellRendererTextKey));
154 }
155
156 void AddToTreeStoreAt(const BookmarkNode* node, int64 selected_id,
157                       GtkTreeStore* store, GtkTreeIter* selected_iter,
158                       GtkTreeIter* parent) {
159   if (!node->is_folder())
160     return;
161
162   GtkTreeIter iter;
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;
169   }
170
171   for (int i = 0; i < node->child_count(); ++i) {
172     AddToTreeStoreAt(node->GetChild(i), selected_id, store, selected_iter,
173                      &iter);
174   }
175 }
176
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);
181
182   GtkTreePath* selected_path = gtk_tree_model_get_path(tree_model, selected);
183
184   GtkTreeIter tree_root;
185   if (!gtk_tree_model_get_iter_first(tree_model, &tree_root))
186     NOTREACHED() << "Impossible missing bookmarks case";
187
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();
194   do {
195     DCHECK(GetIdFromTreeIter(tree_model, &tree_root) != 0)
196         << "It should be impossible to add another toplevel node";
197
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) {
203         child_node = node;
204         break;
205       }
206     }
207     DCHECK(child_node);
208
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));
213
214   gtk_tree_path_free(selected_path);
215   return node_to_return;
216 }
217
218 int64 GetIdFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter) {
219   GValue value = { 0, };
220   int64 ret_val = -1;
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);
224   else
225     NOTREACHED() << "Impossible type mismatch";
226
227   return ret_val;
228 }
229
230 string16 GetTitleFromTreeIter(GtkTreeModel* model, GtkTreeIter* iter) {
231   GValue value = { 0, };
232   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 = UTF8ToUTF16(utf8str);
237     g_value_unset(&value);
238   } else {
239     NOTREACHED() << "Impossible type mismatch";
240   }
241
242   return ret_val;
243 }