root/chrome/browser/ui/gtk/gtk_tree.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetRowNumForPath
  2. GetRowNumForIter
  3. GetTreeSortChildRowNumForPath
  4. SelectAndFocusRowNum
  5. RemoveRecursively
  6. GetSelectedIndices
  7. table_model_
  8. SetModel
  9. IsGroupRow
  10. OffsetForGroupIndex
  11. MapListStoreIndicesToModelRows
  12. GetListStoreIndexForModelRow
  13. AddNodeToList
  14. OnModelChanged
  15. OnItemsChanged
  16. OnItemsAdded
  17. OnItemsRemoved
  18. OnCheckRowIsSeparator
  19. OnSelectionFilter
  20. tree_model_
  21. Init
  22. GetNode
  23. FillRow
  24. Fill
  25. GetTreePath
  26. GetTreeIter
  27. TreeNodesAdded
  28. TreeNodesRemoved
  29. TreeNodeChanged

// 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,
                         base::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, base::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

/* [<][>][^][v][top][bottom][index][help] */