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