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 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, 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. 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->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. 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->child_count(); ++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->child_count(); ++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->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 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 232