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
15namespace {
16
17const char* kCellRendererTextKey = "__CELL_RENDERER_TEXT__";
18
19void 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.
41void 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.
81void 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
96namespace bookmark_utils {
97
98GtkTreeStore* 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
103void 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
112GtkWidget* 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
137GtkCellRenderer* GetCellRendererText(GtkTreeView* tree_view) {
138  return static_cast<GtkCellRenderer*>(
139      g_object_get_data(G_OBJECT(tree_view), kCellRendererTextKey));
140}
141
142void 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
163const 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
204int64 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
216string16 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