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 16namespace gtk_tree { 17 18gint 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 27gint 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 34gint 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 43void 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 59bool 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 70void 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 85TableAdapter::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 92void TableAdapter::SetModel(ui::TableModel* table_model) { 93 table_model_ = table_model; 94 table_model_->SetObserver(this); 95} 96 97bool 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 112static 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 118void 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 163int 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 178void 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 201void 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 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 246void 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 274void 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 282void 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 315gboolean 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 328gboolean 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 346TreeAdapter::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 362TreeAdapter::~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 368void TreeAdapter::Init() { 369 gtk_tree_store_clear(tree_store_); 370 Fill(NULL, tree_model_->GetRoot()); 371} 372 373 374ui::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 382void 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, UTF16ToUTF8(node->GetTitle()).c_str(), 392 COL_NODE_PTR, node, 393 -1); 394} 395 396void 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 409GtkTreePath* 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 423bool 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 435void 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 452void 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 468void 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 478