| OLD | NEW |
| (Empty) |
| 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 base::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 base::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 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); | |
| 238 } else { | |
| 239 NOTREACHED() << "Impossible type mismatch"; | |
| 240 } | |
| 241 | |
| 242 return ret_val; | |
| 243 } | |
| OLD | NEW |