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/gtk_tree.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/strings/utf_string_conversions.h" | |
9 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
10 #include "third_party/skia/include/core/SkBitmap.h" | |
11 #include "ui/base/models/table_model.h" | |
12 #include "ui/gfx/gtk_util.h" | |
13 #include "ui/gfx/image/image.h" | |
14 #include "ui/gfx/image/image_skia.h" | |
15 | |
16 namespace gtk_tree { | |
17 | |
18 gint GetRowNumForPath(GtkTreePath* path) { | |
19 gint* indices = gtk_tree_path_get_indices(path); | |
20 if (!indices) { | |
21 NOTREACHED(); | |
22 return -1; | |
23 } | |
24 return indices[0]; | |
25 } | |
26 | |
27 gint GetRowNumForIter(GtkTreeModel* model, GtkTreeIter* iter) { | |
28 GtkTreePath* path = gtk_tree_model_get_path(model, iter); | |
29 int row = GetRowNumForPath(path); | |
30 gtk_tree_path_free(path); | |
31 return row; | |
32 } | |
33 | |
34 gint GetTreeSortChildRowNumForPath(GtkTreeModel* sort_model, | |
35 GtkTreePath* sort_path) { | |
36 GtkTreePath *child_path = gtk_tree_model_sort_convert_path_to_child_path( | |
37 GTK_TREE_MODEL_SORT(sort_model), sort_path); | |
38 int row = GetRowNumForPath(child_path); | |
39 gtk_tree_path_free(child_path); | |
40 return row; | |
41 } | |
42 | |
43 void SelectAndFocusRowNum(int row, GtkTreeView* tree_view) { | |
44 GtkTreeModel* model = gtk_tree_view_get_model(tree_view); | |
45 if (!model) { | |
46 NOTREACHED(); | |
47 return; | |
48 } | |
49 GtkTreeIter iter; | |
50 if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row)) { | |
51 NOTREACHED(); | |
52 return; | |
53 } | |
54 GtkTreePath* path = gtk_tree_model_get_path(model, &iter); | |
55 gtk_tree_view_set_cursor(tree_view, path, NULL, FALSE); | |
56 gtk_tree_path_free(path); | |
57 } | |
58 | |
59 bool RemoveRecursively(GtkTreeStore* tree_store, GtkTreeIter* iter) { | |
60 GtkTreeIter child; | |
61 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store), &child, iter)) { | |
62 while (true) { | |
63 if (!RemoveRecursively(tree_store, &child)) | |
64 break; | |
65 } | |
66 } | |
67 return gtk_tree_store_remove(tree_store, iter); | |
68 } | |
69 | |
70 void GetSelectedIndices(GtkTreeSelection* selection, std::set<int>* out) { | |
71 GList* list = gtk_tree_selection_get_selected_rows( | |
72 selection, NULL); | |
73 GList* node; | |
74 for (node = list; node != NULL; node = node->next) { | |
75 out->insert( | |
76 gtk_tree::GetRowNumForPath(static_cast<GtkTreePath*>(node->data))); | |
77 } | |
78 g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); | |
79 g_list_free(list); | |
80 } | |
81 | |
82 //////////////////////////////////////////////////////////////////////////////// | |
83 // TableAdapter | |
84 | |
85 TableAdapter::TableAdapter(Delegate* delegate, GtkListStore* list_store, | |
86 ui::TableModel* table_model) | |
87 : delegate_(delegate), list_store_(list_store), table_model_(table_model) { | |
88 if (table_model) | |
89 table_model->SetObserver(this); | |
90 } | |
91 | |
92 void TableAdapter::SetModel(ui::TableModel* table_model) { | |
93 table_model_ = table_model; | |
94 table_model_->SetObserver(this); | |
95 } | |
96 | |
97 bool TableAdapter::IsGroupRow(GtkTreeIter* iter) const { | |
98 if (!table_model_->HasGroups()) | |
99 return false; | |
100 gboolean is_header = false; | |
101 gboolean is_separator = false; | |
102 gtk_tree_model_get(GTK_TREE_MODEL(list_store_), | |
103 iter, | |
104 COL_IS_HEADER, | |
105 &is_header, | |
106 COL_IS_SEPARATOR, | |
107 &is_separator, | |
108 -1); | |
109 return is_header || is_separator; | |
110 } | |
111 | |
112 static int OffsetForGroupIndex(size_t group_index) { | |
113 // Every group consists of a header and a separator row, and there is a blank | |
114 // row between groups. | |
115 return 3 * group_index + 2; | |
116 } | |
117 | |
118 void TableAdapter::MapListStoreIndicesToModelRows( | |
119 const std::set<int>& list_store_indices, | |
120 RemoveRowsTableModel::Rows* model_rows) { | |
121 if (!table_model_->HasGroups()) { | |
122 for (std::set<int>::const_iterator it = list_store_indices.begin(); | |
123 it != list_store_indices.end(); | |
124 ++it) { | |
125 model_rows->insert(*it); | |
126 } | |
127 return; | |
128 } | |
129 | |
130 const ui::TableModel::Groups& groups = table_model_->GetGroups(); | |
131 ui::TableModel::Groups::const_iterator group_it = groups.begin(); | |
132 for (std::set<int>::const_iterator list_store_it = list_store_indices.begin(); | |
133 list_store_it != list_store_indices.end(); | |
134 ++list_store_it) { | |
135 int list_store_index = *list_store_it; | |
136 GtkTreeIter iter; | |
137 bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), | |
138 &iter, | |
139 NULL, | |
140 list_store_index); | |
141 if (!rv) { | |
142 NOTREACHED(); | |
143 return; | |
144 } | |
145 int group = -1; | |
146 gtk_tree_model_get(GTK_TREE_MODEL(list_store_), | |
147 &iter, | |
148 COL_GROUP_ID, | |
149 &group, | |
150 -1); | |
151 while (group_it->id != group) { | |
152 ++group_it; | |
153 if (group_it == groups.end()) { | |
154 NOTREACHED(); | |
155 return; | |
156 } | |
157 } | |
158 int offset = OffsetForGroupIndex(group_it - groups.begin()); | |
159 model_rows->insert(list_store_index - offset); | |
160 } | |
161 } | |
162 | |
163 int TableAdapter::GetListStoreIndexForModelRow(int model_row) const { | |
164 if (!table_model_->HasGroups()) | |
165 return model_row; | |
166 int group = table_model_->GetGroupID(model_row); | |
167 const ui::TableModel::Groups& groups = table_model_->GetGroups(); | |
168 for (ui::TableModel::Groups::const_iterator it = groups.begin(); | |
169 it != groups.end(); ++it) { | |
170 if (it->id == group) { | |
171 return model_row + OffsetForGroupIndex(it - groups.begin()); | |
172 } | |
173 } | |
174 NOTREACHED(); | |
175 return -1; | |
176 } | |
177 | |
178 void TableAdapter::AddNodeToList(int row) { | |
179 GtkTreeIter iter; | |
180 int list_store_index = GetListStoreIndexForModelRow(row); | |
181 if (list_store_index == 0) { | |
182 gtk_list_store_prepend(list_store_, &iter); | |
183 } else { | |
184 GtkTreeIter sibling; | |
185 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &sibling, NULL, | |
186 list_store_index - 1); | |
187 gtk_list_store_insert_after(list_store_, &iter, &sibling); | |
188 } | |
189 | |
190 if (table_model_->HasGroups()) { | |
191 gtk_list_store_set(list_store_, | |
192 &iter, | |
193 COL_WEIGHT, PANGO_WEIGHT_NORMAL, | |
194 COL_WEIGHT_SET, TRUE, | |
195 COL_GROUP_ID, table_model_->GetGroupID(row), | |
196 -1); | |
197 } | |
198 delegate_->SetColumnValues(row, &iter); | |
199 } | |
200 | |
201 void TableAdapter::OnModelChanged() { | |
202 delegate_->OnAnyModelUpdateStart(); | |
203 gtk_list_store_clear(list_store_); | |
204 delegate_->OnModelChanged(); | |
205 | |
206 if (table_model_->HasGroups()) { | |
207 const ui::TableModel::Groups& groups = table_model_->GetGroups(); | |
208 for (ui::TableModel::Groups::const_iterator it = groups.begin(); | |
209 it != groups.end(); ++it) { | |
210 GtkTreeIter iter; | |
211 if (it != groups.begin()) { | |
212 // Blank row between groups. | |
213 gtk_list_store_append(list_store_, &iter); | |
214 gtk_list_store_set(list_store_, &iter, COL_IS_HEADER, TRUE, -1); | |
215 } | |
216 // Group title. | |
217 gtk_list_store_append(list_store_, &iter); | |
218 gtk_list_store_set(list_store_, | |
219 &iter, | |
220 COL_WEIGHT, | |
221 PANGO_WEIGHT_BOLD, | |
222 COL_WEIGHT_SET, | |
223 TRUE, | |
224 COL_TITLE, | |
225 base::UTF16ToUTF8(it->title).c_str(), | |
226 COL_IS_HEADER, | |
227 TRUE, | |
228 -1); | |
229 // Group separator. | |
230 gtk_list_store_append(list_store_, &iter); | |
231 gtk_list_store_set(list_store_, | |
232 &iter, | |
233 COL_IS_HEADER, | |
234 TRUE, | |
235 COL_IS_SEPARATOR, | |
236 TRUE, | |
237 -1); | |
238 } | |
239 } | |
240 | |
241 for (int i = 0; i < table_model_->RowCount(); ++i) | |
242 AddNodeToList(i); | |
243 delegate_->OnAnyModelUpdate(); | |
244 } | |
245 | |
246 void TableAdapter::OnItemsChanged(int start, int length) { | |
247 if (length == 0) | |
248 return; | |
249 delegate_->OnAnyModelUpdateStart(); | |
250 int list_store_index = GetListStoreIndexForModelRow(start); | |
251 GtkTreeIter iter; | |
252 bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), | |
253 &iter, | |
254 NULL, | |
255 list_store_index); | |
256 for (int i = 0; i < length; ++i) { | |
257 if (!rv) { | |
258 NOTREACHED(); | |
259 return; | |
260 } | |
261 while (IsGroupRow(&iter)) { | |
262 rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter); | |
263 if (!rv) { | |
264 NOTREACHED(); | |
265 return; | |
266 } | |
267 } | |
268 delegate_->SetColumnValues(start + i, &iter); | |
269 rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter); | |
270 } | |
271 delegate_->OnAnyModelUpdate(); | |
272 } | |
273 | |
274 void TableAdapter::OnItemsAdded(int start, int length) { | |
275 delegate_->OnAnyModelUpdateStart(); | |
276 for (int i = 0; i < length; ++i) { | |
277 AddNodeToList(start + i); | |
278 } | |
279 delegate_->OnAnyModelUpdate(); | |
280 } | |
281 | |
282 void TableAdapter::OnItemsRemoved(int start, int length) { | |
283 if (length == 0) | |
284 return; | |
285 delegate_->OnAnyModelUpdateStart(); | |
286 // When this method is called, the model has already removed the items, so | |
287 // accessing items in the model from |start| on may not be possible anymore. | |
288 // Therefore we use the item right before that, if it exists. | |
289 int list_store_index = 0; | |
290 if (start > 0) | |
291 list_store_index = GetListStoreIndexForModelRow(start - 1) + 1; | |
292 GtkTreeIter iter; | |
293 bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), | |
294 &iter, | |
295 NULL, | |
296 list_store_index); | |
297 if (!rv) { | |
298 NOTREACHED(); | |
299 return; | |
300 } | |
301 for (int i = 0; i < length; ++i) { | |
302 while (IsGroupRow(&iter)) { | |
303 rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter); | |
304 if (!rv) { | |
305 NOTREACHED(); | |
306 return; | |
307 } | |
308 } | |
309 gtk_list_store_remove(list_store_, &iter); | |
310 } | |
311 delegate_->OnAnyModelUpdate(); | |
312 } | |
313 | |
314 // static | |
315 gboolean TableAdapter::OnCheckRowIsSeparator(GtkTreeModel* model, | |
316 GtkTreeIter* iter, | |
317 gpointer user_data) { | |
318 gboolean is_separator; | |
319 gtk_tree_model_get(model, | |
320 iter, | |
321 COL_IS_SEPARATOR, | |
322 &is_separator, | |
323 -1); | |
324 return is_separator; | |
325 } | |
326 | |
327 // static | |
328 gboolean TableAdapter::OnSelectionFilter(GtkTreeSelection* selection, | |
329 GtkTreeModel* model, | |
330 GtkTreePath* path, | |
331 gboolean path_currently_selected, | |
332 gpointer user_data) { | |
333 GtkTreeIter iter; | |
334 if (!gtk_tree_model_get_iter(model, &iter, path)) { | |
335 NOTREACHED(); | |
336 return TRUE; | |
337 } | |
338 gboolean is_header; | |
339 gtk_tree_model_get(model, &iter, COL_IS_HEADER, &is_header, -1); | |
340 return !is_header; | |
341 } | |
342 | |
343 //////////////////////////////////////////////////////////////////////////////// | |
344 // TreeAdapter | |
345 | |
346 TreeAdapter::TreeAdapter(Delegate* delegate, ui::TreeModel* tree_model) | |
347 : delegate_(delegate), | |
348 tree_model_(tree_model) { | |
349 tree_store_ = gtk_tree_store_new(COL_COUNT, | |
350 GDK_TYPE_PIXBUF, | |
351 G_TYPE_STRING, | |
352 G_TYPE_POINTER); | |
353 tree_model->AddObserver(this); | |
354 | |
355 std::vector<gfx::ImageSkia> icons; | |
356 tree_model->GetIcons(&icons); | |
357 for (size_t i = 0; i < icons.size(); ++i) { | |
358 pixbufs_.push_back(gfx::GdkPixbufFromSkBitmap(*icons[i].bitmap())); | |
359 } | |
360 } | |
361 | |
362 TreeAdapter::~TreeAdapter() { | |
363 g_object_unref(tree_store_); | |
364 for (size_t i = 0; i < pixbufs_.size(); ++i) | |
365 g_object_unref(pixbufs_[i]); | |
366 } | |
367 | |
368 void TreeAdapter::Init() { | |
369 gtk_tree_store_clear(tree_store_); | |
370 Fill(NULL, tree_model_->GetRoot()); | |
371 } | |
372 | |
373 | |
374 ui::TreeModelNode* TreeAdapter::GetNode(GtkTreeIter* iter) { | |
375 ui::TreeModelNode* node; | |
376 gtk_tree_model_get(GTK_TREE_MODEL(tree_store_), iter, | |
377 COL_NODE_PTR, &node, | |
378 -1); | |
379 return node; | |
380 } | |
381 | |
382 void TreeAdapter::FillRow(GtkTreeIter* iter, ui::TreeModelNode* node) { | |
383 GdkPixbuf* pixbuf = NULL; | |
384 int icon_index = tree_model_->GetIconIndex(node); | |
385 if (icon_index >= 0 && icon_index < static_cast<int>(pixbufs_.size())) | |
386 pixbuf = pixbufs_[icon_index]; | |
387 else | |
388 pixbuf = GtkThemeService::GetFolderIcon(true).ToGdkPixbuf(); | |
389 gtk_tree_store_set(tree_store_, iter, | |
390 COL_ICON, pixbuf, | |
391 COL_TITLE, base::UTF16ToUTF8(node->GetTitle()).c_str(), | |
392 COL_NODE_PTR, node, | |
393 -1); | |
394 } | |
395 | |
396 void TreeAdapter::Fill(GtkTreeIter* parent_iter, | |
397 ui::TreeModelNode* parent_node) { | |
398 if (parent_iter) | |
399 FillRow(parent_iter, parent_node); | |
400 GtkTreeIter iter; | |
401 int child_count = tree_model_->GetChildCount(parent_node); | |
402 for (int i = 0; i < child_count; ++i) { | |
403 ui::TreeModelNode* node = tree_model_->GetChild(parent_node, i); | |
404 gtk_tree_store_append(tree_store_, &iter, parent_iter); | |
405 Fill(&iter, node); | |
406 } | |
407 } | |
408 | |
409 GtkTreePath* TreeAdapter::GetTreePath(ui::TreeModelNode* node) { | |
410 GtkTreePath* path = gtk_tree_path_new(); | |
411 ui::TreeModelNode* parent = node; | |
412 while (parent) { | |
413 parent = tree_model_->GetParent(parent); | |
414 if (parent) { | |
415 int idx = tree_model_->GetIndexOf(parent, node); | |
416 gtk_tree_path_prepend_index(path, idx); | |
417 node = parent; | |
418 } | |
419 } | |
420 return path; | |
421 } | |
422 | |
423 bool TreeAdapter::GetTreeIter(ui::TreeModelNode* node, GtkTreeIter* iter) { | |
424 GtkTreePath* path = GetTreePath(node); | |
425 bool rv = false; | |
426 // Check the path ourselves since gtk_tree_model_get_iter prints a warning if | |
427 // given an empty path. The path will be empty when it points to the root | |
428 // node and we are using SetRootShown(false). | |
429 if (gtk_tree_path_get_depth(path) > 0) | |
430 rv = gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_store_), iter, path); | |
431 gtk_tree_path_free(path); | |
432 return rv; | |
433 } | |
434 | |
435 void TreeAdapter::TreeNodesAdded(ui::TreeModel* model, | |
436 ui::TreeModelNode* parent, | |
437 int start, | |
438 int count) { | |
439 delegate_->OnAnyModelUpdateStart(); | |
440 GtkTreeIter parent_iter; | |
441 GtkTreeIter* parent_iter_ptr = NULL; | |
442 GtkTreeIter iter; | |
443 if (GetTreeIter(parent, &parent_iter)) | |
444 parent_iter_ptr = &parent_iter; | |
445 for (int i = 0; i < count; ++i) { | |
446 gtk_tree_store_insert(tree_store_, &iter, parent_iter_ptr, start + i); | |
447 Fill(&iter, tree_model_->GetChild(parent, start + i)); | |
448 } | |
449 delegate_->OnAnyModelUpdate(); | |
450 } | |
451 | |
452 void TreeAdapter::TreeNodesRemoved(ui::TreeModel* model, | |
453 ui::TreeModelNode* parent, | |
454 int start, | |
455 int count) { | |
456 delegate_->OnAnyModelUpdateStart(); | |
457 GtkTreeIter iter; | |
458 GtkTreePath* path = GetTreePath(parent); | |
459 gtk_tree_path_append_index(path, start); | |
460 gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_store_), &iter, path); | |
461 gtk_tree_path_free(path); | |
462 for (int i = 0; i < count; ++i) { | |
463 RemoveRecursively(tree_store_, &iter); | |
464 } | |
465 delegate_->OnAnyModelUpdate(); | |
466 } | |
467 | |
468 void TreeAdapter::TreeNodeChanged(ui::TreeModel* model, | |
469 ui::TreeModelNode* node) { | |
470 delegate_->OnAnyModelUpdateStart(); | |
471 GtkTreeIter iter; | |
472 if (GetTreeIter(node, &iter)) | |
473 FillRow(&iter, node); | |
474 delegate_->OnAnyModelUpdate(); | |
475 } | |
476 | |
477 } // namespace gtk_tree | |
OLD | NEW |