// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/gtk/gtk_tree.h" #include "app/table_model.h" #include "base/logging.h" #include "base/utf_string_conversions.h" #include "chrome/browser/gtk/gtk_theme_provider.h" #include "gfx/gtk_util.h" #include "third_party/skia/include/core/SkBitmap.h" namespace gtk_tree { gint GetRowNumForPath(GtkTreePath* path) { gint* indices = gtk_tree_path_get_indices(path); if (!indices) { NOTREACHED(); return -1; } return indices[0]; } gint GetRowNumForIter(GtkTreeModel* model, GtkTreeIter* iter) { GtkTreePath* path = gtk_tree_model_get_path(model, iter); int row = GetRowNumForPath(path); gtk_tree_path_free(path); return row; } gint GetTreeSortChildRowNumForPath(GtkTreeModel* sort_model, GtkTreePath* sort_path) { GtkTreePath *child_path = gtk_tree_model_sort_convert_path_to_child_path( GTK_TREE_MODEL_SORT(sort_model), sort_path); int row = GetRowNumForPath(child_path); gtk_tree_path_free(child_path); return row; } void SelectAndFocusRowNum(int row, GtkTreeView* tree_view) { GtkTreeModel* model = gtk_tree_view_get_model(tree_view); if (!model) { NOTREACHED(); return; } GtkTreeIter iter; if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, row)) { NOTREACHED(); return; } GtkTreePath* path = gtk_tree_model_get_path(model, &iter); gtk_tree_view_set_cursor(tree_view, path, NULL, FALSE); gtk_tree_path_free(path); } bool RemoveRecursively(GtkTreeStore* tree_store, GtkTreeIter* iter) { GtkTreeIter child; if (gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store), &child, iter)) { while (true) { if (!RemoveRecursively(tree_store, &child)) break; } } return gtk_tree_store_remove(tree_store, iter); } void GetSelectedIndices(GtkTreeSelection* selection, std::set* out) { GList* list = gtk_tree_selection_get_selected_rows( selection, NULL); GList* node; for (node = list; node != NULL; node = node->next) { out->insert( gtk_tree::GetRowNumForPath(static_cast(node->data))); } g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); g_list_free(list); } //////////////////////////////////////////////////////////////////////////////// // TableAdapter TableAdapter::TableAdapter(Delegate* delegate, GtkListStore* list_store, TableModel* table_model) : delegate_(delegate), list_store_(list_store), table_model_(table_model) { if (table_model) table_model->SetObserver(this); } void TableAdapter::SetModel(TableModel* table_model) { table_model_ = table_model; table_model_->SetObserver(this); } bool TableAdapter::IsGroupRow(GtkTreeIter* iter) const { if (!table_model_->HasGroups()) return false; gboolean is_header = false; gboolean is_separator = false; gtk_tree_model_get(GTK_TREE_MODEL(list_store_), iter, COL_IS_HEADER, &is_header, COL_IS_SEPARATOR, &is_separator, -1); return is_header || is_separator; } static int OffsetForGroupIndex(size_t group_index) { // Every group consists of a header and a separator row, and there is a blank // row between groups. return 3 * group_index + 2; } void TableAdapter::MapListStoreIndicesToModelRows( const std::set& list_store_indices, RemoveRowsTableModel::Rows* model_rows) { if (!table_model_->HasGroups()) { for (std::set::const_iterator it = list_store_indices.begin(); it != list_store_indices.end(); ++it) { model_rows->insert(*it); } return; } const TableModel::Groups& groups = table_model_->GetGroups(); TableModel::Groups::const_iterator group_it = groups.begin(); for (std::set::const_iterator list_store_it = list_store_indices.begin(); list_store_it != list_store_indices.end(); ++list_store_it) { int list_store_index = *list_store_it; GtkTreeIter iter; bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &iter, NULL, list_store_index); if (!rv) { NOTREACHED(); return; } int group = -1; gtk_tree_model_get(GTK_TREE_MODEL(list_store_), &iter, COL_GROUP_ID, &group, -1); while (group_it->id != group) { ++group_it; if (group_it == groups.end()) { NOTREACHED(); return; } } int offset = OffsetForGroupIndex(group_it - groups.begin()); model_rows->insert(list_store_index - offset); } } int TableAdapter::GetListStoreIndexForModelRow(int model_row) const { if (!table_model_->HasGroups()) return model_row; int group = table_model_->GetGroupID(model_row); const TableModel::Groups& groups = table_model_->GetGroups(); for (TableModel::Groups::const_iterator it = groups.begin(); it != groups.end(); ++it) { if (it->id == group) { return model_row + OffsetForGroupIndex(it - groups.begin()); } } NOTREACHED(); return -1; } void TableAdapter::AddNodeToList(int row) { GtkTreeIter iter; int list_store_index = GetListStoreIndexForModelRow(row); if (list_store_index == 0) { gtk_list_store_prepend(list_store_, &iter); } else { GtkTreeIter sibling; gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &sibling, NULL, list_store_index - 1); gtk_list_store_insert_after(list_store_, &iter, &sibling); } if (table_model_->HasGroups()) { gtk_list_store_set(list_store_, &iter, COL_WEIGHT, PANGO_WEIGHT_NORMAL, COL_WEIGHT_SET, TRUE, COL_GROUP_ID, table_model_->GetGroupID(row), -1); } delegate_->SetColumnValues(row, &iter); } void TableAdapter::OnModelChanged() { delegate_->OnAnyModelUpdateStart(); gtk_list_store_clear(list_store_); delegate_->OnModelChanged(); if (table_model_->HasGroups()) { const TableModel::Groups& groups = table_model_->GetGroups(); for (TableModel::Groups::const_iterator it = groups.begin(); it != groups.end(); ++it) { GtkTreeIter iter; if (it != groups.begin()) { // Blank row between groups. gtk_list_store_append(list_store_, &iter); gtk_list_store_set(list_store_, &iter, COL_IS_HEADER, TRUE, -1); } // Group title. gtk_list_store_append(list_store_, &iter); gtk_list_store_set(list_store_, &iter, COL_WEIGHT, PANGO_WEIGHT_BOLD, COL_WEIGHT_SET, TRUE, COL_TITLE, WideToUTF8(it->title).c_str(), COL_IS_HEADER, TRUE, -1); // Group separator. gtk_list_store_append(list_store_, &iter); gtk_list_store_set(list_store_, &iter, COL_IS_HEADER, TRUE, COL_IS_SEPARATOR, TRUE, -1); } } for (int i = 0; i < table_model_->RowCount(); ++i) AddNodeToList(i); delegate_->OnAnyModelUpdate(); } void TableAdapter::OnItemsChanged(int start, int length) { if (length == 0) return; delegate_->OnAnyModelUpdateStart(); int list_store_index = GetListStoreIndexForModelRow(start); GtkTreeIter iter; bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &iter, NULL, list_store_index); for (int i = 0; i < length; ++i) { if (!rv) { NOTREACHED(); return; } while (IsGroupRow(&iter)) { rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter); if (!rv) { NOTREACHED(); return; } } delegate_->SetColumnValues(start + i, &iter); rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter); } delegate_->OnAnyModelUpdate(); } void TableAdapter::OnItemsAdded(int start, int length) { delegate_->OnAnyModelUpdateStart(); for (int i = 0; i < length; ++i) { AddNodeToList(start + i); } delegate_->OnAnyModelUpdate(); } void TableAdapter::OnItemsRemoved(int start, int length) { if (length == 0) return; delegate_->OnAnyModelUpdateStart(); // When this method is called, the model has already removed the items, so // accessing items in the model from |start| on may not be possible anymore. // Therefore we use the item right before that, if it exists. int list_store_index = 0; if (start > 0) list_store_index = GetListStoreIndexForModelRow(start - 1) + 1; GtkTreeIter iter; bool rv = gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store_), &iter, NULL, list_store_index); if (!rv) { NOTREACHED(); return; } for (int i = 0; i < length; ++i) { while (IsGroupRow(&iter)) { rv = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store_), &iter); if (!rv) { NOTREACHED(); return; } } gtk_list_store_remove(list_store_, &iter); } delegate_->OnAnyModelUpdate(); } // static gboolean TableAdapter::OnCheckRowIsSeparator(GtkTreeModel* model, GtkTreeIter* iter, gpointer user_data) { gboolean is_separator; gtk_tree_model_get(model, iter, COL_IS_SEPARATOR, &is_separator, -1); return is_separator; } // static gboolean TableAdapter::OnSelectionFilter(GtkTreeSelection* selection, GtkTreeModel* model, GtkTreePath* path, gboolean path_currently_selected, gpointer user_data) { GtkTreeIter iter; if (!gtk_tree_model_get_iter(model, &iter, path)) { NOTREACHED(); return TRUE; } gboolean is_header; gtk_tree_model_get(model, &iter, COL_IS_HEADER, &is_header, -1); return !is_header; } //////////////////////////////////////////////////////////////////////////////// // TreeAdapter TreeAdapter::TreeAdapter(Delegate* delegate, TreeModel* tree_model) : delegate_(delegate), tree_model_(tree_model) { tree_store_ = gtk_tree_store_new(COL_COUNT, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER); tree_model->AddObserver(this); std::vector icons; tree_model->GetIcons(&icons); for (size_t i = 0; i < icons.size(); ++i) { pixbufs_.push_back(gfx::GdkPixbufFromSkBitmap(&icons[i])); } } TreeAdapter::~TreeAdapter() { g_object_unref(tree_store_); for (size_t i = 0; i < pixbufs_.size(); ++i) g_object_unref(pixbufs_[i]); } void TreeAdapter::Init() { gtk_tree_store_clear(tree_store_); Fill(NULL, tree_model_->GetRoot()); } TreeModelNode* TreeAdapter::GetNode(GtkTreeIter* iter) { TreeModelNode* node; gtk_tree_model_get(GTK_TREE_MODEL(tree_store_), iter, COL_NODE_PTR, &node, -1); return node; } void TreeAdapter::FillRow(GtkTreeIter* iter, TreeModelNode* node) { GdkPixbuf* pixbuf = NULL; int icon_index = tree_model_->GetIconIndex(node); if (icon_index >= 0 && icon_index < static_cast(pixbufs_.size())) pixbuf = pixbufs_[icon_index]; else pixbuf = GtkThemeProvider::GetFolderIcon(true); gtk_tree_store_set(tree_store_, iter, COL_ICON, pixbuf, COL_TITLE, UTF16ToUTF8(node->GetTitle()).c_str(), COL_NODE_PTR, node, -1); } void TreeAdapter::Fill(GtkTreeIter* parent_iter, TreeModelNode* parent_node) { if (parent_iter) FillRow(parent_iter, parent_node); GtkTreeIter iter; int child_count = tree_model_->GetChildCount(parent_node); for (int i = 0; i < child_count; ++i) { TreeModelNode* node = tree_model_->GetChild(parent_node, i); gtk_tree_store_append(tree_store_, &iter, parent_iter); Fill(&iter, node); } } GtkTreePath* TreeAdapter::GetTreePath(TreeModelNode* node) { GtkTreePath* path = gtk_tree_path_new(); TreeModelNode* parent = node; while (parent) { parent = tree_model_->GetParent(parent); if (parent) { int idx = tree_model_->IndexOfChild(parent, node); gtk_tree_path_prepend_index(path, idx); node = parent; } } return path; } bool TreeAdapter::GetTreeIter(TreeModelNode* node, GtkTreeIter* iter) { GtkTreePath* path = GetTreePath(node); bool rv = false; // Check the path ourselves since gtk_tree_model_get_iter prints a warning if // given an empty path. The path will be empty when it points to the root // node and we are using SetRootShown(false). if (gtk_tree_path_get_depth(path) > 0) rv = gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_store_), iter, path); gtk_tree_path_free(path); return rv; } void TreeAdapter::TreeNodesAdded(TreeModel* model, TreeModelNode* parent, int start, int count) { delegate_->OnAnyModelUpdateStart(); GtkTreeIter parent_iter; GtkTreeIter* parent_iter_ptr = NULL; GtkTreeIter iter; if (GetTreeIter(parent, &parent_iter)) parent_iter_ptr = &parent_iter; for (int i = 0; i < count; ++i) { gtk_tree_store_insert(tree_store_, &iter, parent_iter_ptr, start + i); Fill(&iter, tree_model_->GetChild(parent, start + i)); } delegate_->OnAnyModelUpdate(); } void TreeAdapter::TreeNodesRemoved(TreeModel* model, TreeModelNode* parent, int start, int count) { delegate_->OnAnyModelUpdateStart(); GtkTreeIter iter; GtkTreePath* path = GetTreePath(parent); gtk_tree_path_append_index(path, start); gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_store_), &iter, path); gtk_tree_path_free(path); for (int i = 0; i < count; ++i) { RemoveRecursively(tree_store_, &iter); } delegate_->OnAnyModelUpdate(); } void TreeAdapter::TreeNodeChanged(TreeModel* model, TreeModelNode* node) { delegate_->OnAnyModelUpdateStart(); GtkTreeIter iter; if (GetTreeIter(node, &iter)) FillRow(&iter, node); delegate_->OnAnyModelUpdate(); } } // namespace gtk_tree