// Copyright (c) 2012 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/ui/gtk/gtk_tree.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/models/table_model.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.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<int>* 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<GtkTreePath*>(node->data)));
}
g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
g_list_free(list);
}
////////////////////////////////////////////////////////////////////////////////
// TableAdapter
TableAdapter::TableAdapter(Delegate* delegate, GtkListStore* list_store,
ui::TableModel* table_model)
: delegate_(delegate), list_store_(list_store), table_model_(table_model) {
if (table_model)
table_model->SetObserver(this);
}
void TableAdapter::SetModel(ui::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<int>& list_store_indices,
RemoveRowsTableModel::Rows* model_rows) {
if (!table_model_->HasGroups()) {
for (std::set<int>::const_iterator it = list_store_indices.begin();
it != list_store_indices.end();
++it) {
model_rows->insert(*it);
}
return;
}
const ui::TableModel::Groups& groups = table_model_->GetGroups();
ui::TableModel::Groups::const_iterator group_it = groups.begin();
for (std::set<int>::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 ui::TableModel::Groups& groups = table_model_->GetGroups();
for (ui::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 ui::TableModel::Groups& groups = table_model_->GetGroups();
for (ui::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,
UTF16ToUTF8(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, ui::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<gfx::ImageSkia> icons;
tree_model->GetIcons(&icons);
for (size_t i = 0; i < icons.size(); ++i) {
pixbufs_.push_back(gfx::GdkPixbufFromSkBitmap(*icons[i].bitmap()));
}
}
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());
}
ui::TreeModelNode* TreeAdapter::GetNode(GtkTreeIter* iter) {
ui::TreeModelNode* node;
gtk_tree_model_get(GTK_TREE_MODEL(tree_store_), iter,
COL_NODE_PTR, &node,
-1);
return node;
}
void TreeAdapter::FillRow(GtkTreeIter* iter, ui::TreeModelNode* node) {
GdkPixbuf* pixbuf = NULL;
int icon_index = tree_model_->GetIconIndex(node);
if (icon_index >= 0 && icon_index < static_cast<int>(pixbufs_.size()))
pixbuf = pixbufs_[icon_index];
else
pixbuf = GtkThemeService::GetFolderIcon(true).ToGdkPixbuf();
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,
ui::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) {
ui::TreeModelNode* node = tree_model_->GetChild(parent_node, i);
gtk_tree_store_append(tree_store_, &iter, parent_iter);
Fill(&iter, node);
}
}
GtkTreePath* TreeAdapter::GetTreePath(ui::TreeModelNode* node) {
GtkTreePath* path = gtk_tree_path_new();
ui::TreeModelNode* parent = node;
while (parent) {
parent = tree_model_->GetParent(parent);
if (parent) {
int idx = tree_model_->GetIndexOf(parent, node);
gtk_tree_path_prepend_index(path, idx);
node = parent;
}
}
return path;
}
bool TreeAdapter::GetTreeIter(ui::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(ui::TreeModel* model,
ui::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(ui::TreeModel* model,
ui::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(ui::TreeModel* model,
ui::TreeModelNode* node) {
delegate_->OnAnyModelUpdateStart();
GtkTreeIter iter;
if (GetTreeIter(node, &iter))
FillRow(&iter, node);
delegate_->OnAnyModelUpdate();
}
} // namespace gtk_tree