| 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 |