| /* |
| * Copyright (C) 2015-2016 Savoir-faire Linux Inc. |
| * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include "gtkqtreemodel.h" |
| #include <gtk/gtk.h> |
| #include <QtCore/QAbstractItemModel> |
| #include <QtCore/QDebug> |
| #include "gtkaccessproxymodel.h" |
| |
| typedef union _int_ptr_t |
| { |
| gint value; |
| void *ptr; |
| } int_ptr_t; |
| |
| typedef struct _QIter { |
| gint stamp; |
| int_ptr_t row; |
| int_ptr_t column; |
| void *id; |
| gpointer user_data; |
| } QIter; |
| |
| typedef struct _GtkQTreeModelPrivate GtkQTreeModelPrivate; |
| |
| struct _GtkQTreeModel |
| { |
| GObject parent; |
| |
| /* private */ |
| GtkQTreeModelPrivate *priv; |
| }; |
| |
| struct _GtkQTreeModelClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| struct _GtkQTreeModelPrivate |
| { |
| GType *column_headers; |
| gint *column_roles; |
| gint *column_model_col; |
| |
| gint stamp; |
| gint n_columns; |
| |
| GtkAccessProxyModel *model; |
| }; |
| |
| /* static prototypes */ |
| |
| /* GtkTreeModel prototypes */ |
| static void gtk_q_tree_model_tree_model_init (GtkTreeModelIface * ); |
| static void gtk_q_tree_model_finalize (GObject * ); |
| static GtkTreeModelFlags gtk_q_tree_model_get_flags (GtkTreeModel * ); |
| static gint gtk_q_tree_model_get_n_columns (GtkTreeModel * ); |
| static GType gtk_q_tree_model_get_column_type (GtkTreeModel *, |
| gint ); |
| static gboolean gtk_q_tree_model_get_iter (GtkTreeModel *, |
| GtkTreeIter *, |
| GtkTreePath * ); |
| static GtkTreePath * gtk_q_tree_model_get_path (GtkTreeModel *, |
| GtkTreeIter * ); |
| static void gtk_q_tree_model_get_value (GtkTreeModel *, |
| GtkTreeIter *, |
| gint, |
| GValue * ); |
| static gboolean gtk_q_tree_model_iter_next (GtkTreeModel *, |
| GtkTreeIter * ); |
| static gboolean gtk_q_tree_model_iter_previous (GtkTreeModel *, |
| GtkTreeIter * ); |
| static gboolean gtk_q_tree_model_iter_children (GtkTreeModel *, |
| GtkTreeIter *, |
| GtkTreeIter * ); |
| static gboolean gtk_q_tree_model_iter_has_child (GtkTreeModel *, |
| GtkTreeIter * ); |
| static gint gtk_q_tree_model_iter_n_children (GtkTreeModel *, |
| GtkTreeIter * ); |
| static gboolean gtk_q_tree_model_iter_nth_child (GtkTreeModel *, |
| GtkTreeIter *, |
| GtkTreeIter *, |
| gint ); |
| static gboolean gtk_q_tree_model_iter_parent (GtkTreeModel *, |
| GtkTreeIter *, |
| GtkTreeIter * ); |
| |
| /* implementation prototypes */ |
| static void qmodelindex_to_iter (const QModelIndex &, |
| GtkTreeIter * ); |
| // static void gtk_q_tree_model_increment_stamp (GtkQTreeModel * ); |
| |
| static gint gtk_q_tree_model_length (GtkQTreeModel * ); |
| |
| static void gtk_q_tree_model_set_n_columns (GtkQTreeModel *, |
| gint ); |
| static void gtk_q_tree_model_set_column_type (GtkQTreeModel *, |
| gint, |
| gint, |
| gint, |
| GType ); |
| |
| /* End private prototypes */ |
| |
| /* define type, inherit from GObject, define implemented interface(s) */ |
| G_DEFINE_TYPE_WITH_CODE (GtkQTreeModel, gtk_q_tree_model, G_TYPE_OBJECT, |
| G_ADD_PRIVATE (GtkQTreeModel) |
| G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, |
| gtk_q_tree_model_tree_model_init)) |
| |
| #define GTK_Q_TREEMODEL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_Q_TREE_MODEL, GtkQTreeModelPrivate)) |
| |
| #define Q_ITER(iter) ((QIter *)iter) |
| |
| static void |
| gtk_q_tree_model_class_init(GtkQTreeModelClass *klass) |
| { |
| G_OBJECT_CLASS(klass)->finalize = gtk_q_tree_model_finalize; |
| } |
| |
| static void |
| gtk_q_tree_model_tree_model_init(GtkTreeModelIface *iface) |
| { |
| iface->get_flags = gtk_q_tree_model_get_flags; |
| iface->get_n_columns = gtk_q_tree_model_get_n_columns; |
| iface->get_column_type = gtk_q_tree_model_get_column_type; |
| iface->get_iter = gtk_q_tree_model_get_iter; |
| iface->get_path = gtk_q_tree_model_get_path; |
| iface->get_value = gtk_q_tree_model_get_value; |
| iface->iter_next = gtk_q_tree_model_iter_next; |
| iface->iter_previous = gtk_q_tree_model_iter_previous; |
| iface->iter_children = gtk_q_tree_model_iter_children; |
| iface->iter_has_child = gtk_q_tree_model_iter_has_child; |
| iface->iter_n_children = gtk_q_tree_model_iter_n_children; |
| iface->iter_nth_child = gtk_q_tree_model_iter_nth_child; |
| iface->iter_parent = gtk_q_tree_model_iter_parent; |
| } |
| |
| static void |
| gtk_q_tree_model_init(GtkQTreeModel *q_tree_model) |
| { |
| GtkQTreeModelPrivate *priv = GTK_Q_TREEMODEL_GET_PRIVATE(q_tree_model); |
| q_tree_model->priv = priv; |
| |
| priv->stamp = g_random_int(); |
| priv->model = NULL; |
| } |
| |
| /** |
| * gtk_q_tree_model_get_qmodel |
| * returns the original model from which this GtkQTreeModel is created |
| */ |
| QAbstractItemModel * |
| gtk_q_tree_model_get_qmodel(GtkQTreeModel *q_tree_model) |
| { |
| GtkQTreeModelPrivate *priv = GTK_Q_TREEMODEL_GET_PRIVATE(q_tree_model); |
| return priv->model->sourceModel(); |
| } |
| |
| /** |
| * gtk_q_tree_model_get_source_idx |
| * Returns the index of the original model used to create this GtkQTreeModel from |
| * the given iter, if there is one. |
| */ |
| QModelIndex |
| gtk_q_tree_model_get_source_idx(GtkQTreeModel *q_tree_model, GtkTreeIter *iter) |
| { |
| GtkQTreeModelPrivate *priv = GTK_Q_TREEMODEL_GET_PRIVATE(q_tree_model); |
| QIter *qiter = Q_ITER(iter); |
| GtkAccessProxyModel *proxy_model = priv->model; |
| QModelIndex proxy_idx = proxy_model->indexFromId(qiter->row.value, qiter->column.value, qiter->id); |
| if (proxy_idx.isValid()) { |
| return proxy_model->mapToSource(proxy_idx); |
| } else { |
| g_warning("could not get valid QModelIndex from given GtkTreeIter"); |
| return QModelIndex(); |
| } |
| } |
| |
| /** |
| * Takes a QModelIndex from the original QAbstractItemModel and returns a valid GtkTreeIter in the corresponding |
| * GtkQTreeModel |
| */ |
| gboolean |
| gtk_q_tree_model_source_index_to_iter(GtkQTreeModel *q_tree_model, const QModelIndex &idx, GtkTreeIter *iter) |
| { |
| GtkQTreeModelPrivate *priv = GTK_Q_TREEMODEL_GET_PRIVATE(q_tree_model); |
| |
| /* the the proxy_idx from the source idx */ |
| QModelIndex proxy_idx = priv->model->mapFromSource(idx); |
| if (!proxy_idx.isValid()) |
| return FALSE; |
| |
| /* make sure iter is valid */ |
| iter->stamp = priv->stamp; |
| |
| /* map the proxy idx to iter */ |
| Q_ITER(iter)->row.value = proxy_idx.row(); |
| Q_ITER(iter)->column.value = proxy_idx.column(); |
| Q_ITER(iter)->id = proxy_idx.internalPointer(); |
| return TRUE; |
| } |
| |
| /** |
| * helper method which recursively adds all the children of the given QModelIndex |
| */ |
| static void |
| insert_children(const QModelIndex &idx, GtkQTreeModel *gtk_model) |
| { |
| const auto children = gtk_model->priv->model->rowCount(idx); |
| for (int i = 0; i < children; ++i) { |
| GtkTreeIter iter_child; |
| auto idx_child = gtk_model->priv->model->index(i, 0, idx); |
| if (idx_child.isValid()) { |
| iter_child.stamp = gtk_model->priv->stamp; |
| qmodelindex_to_iter(idx_child, &iter_child); |
| if (auto path_child = gtk_q_tree_model_get_path(GTK_TREE_MODEL(gtk_model), &iter_child)) { |
| gtk_tree_model_row_inserted(GTK_TREE_MODEL(gtk_model), path_child, &iter_child); |
| gtk_tree_path_free(path_child); |
| } |
| insert_children(idx_child, gtk_model); |
| } |
| } |
| } |
| |
| /** |
| * gtk_q_tree_model_new: |
| * @model: QAbstractItemModel to which this model will bind. |
| * @n_columns: number of columns in the list store |
| * @...: model #Column, #GType and #Role for each column. |
| * |
| * Return value: a new #GtkQTreeModel |
| */ |
| GtkQTreeModel * |
| gtk_q_tree_model_new(QAbstractItemModel *model, size_t n_columns, ...) |
| { |
| GtkQTreeModel *retval; |
| va_list args; |
| gint i; |
| |
| g_return_val_if_fail(n_columns > 0, NULL); |
| |
| retval = (GtkQTreeModel *)g_object_new(GTK_TYPE_Q_TREE_MODEL, NULL); |
| gtk_q_tree_model_set_n_columns(retval, n_columns); |
| |
| /* get proxy model from given model */ |
| GtkAccessProxyModel* proxy_model = new GtkAccessProxyModel(); |
| proxy_model->setSourceModel(model); |
| retval->priv->model = proxy_model; |
| gint stamp = retval->priv->stamp; |
| |
| n_columns = 3*n_columns; |
| va_start (args, n_columns); |
| |
| for (i = 0; i < (gint)(n_columns/3); i++) { |
| /* first get the model column */ |
| gint model_col = va_arg(args, gint); |
| |
| /* then get the role of the QModel */ |
| gint role = va_arg(args, gint); |
| |
| /* then get the type the role will be interpreted as */ |
| GType type = va_arg(args, GType); |
| |
| /* TODO: check if its a type supported by our model |
| * if (! _gtk_tree_data_list_check_type (type)) |
| * { |
| * g_warning ("%s: Invalid type %s\n", G_STRLOC, g_type_name (type)); |
| * g_object_unref (retval); |
| * va_end (args); |
| * |
| * return NULL; |
| * } |
| */ |
| |
| gtk_q_tree_model_set_column_type (retval, i, model_col, role, type); |
| } |
| |
| va_end (args); |
| |
| gtk_q_tree_model_length(retval); |
| |
| /* connect signals */ |
| QObject::connect( |
| proxy_model, |
| &QAbstractItemModel::rowsInserted, |
| [=](const QModelIndex & parent, int first, int last) { |
| for( int row = first; row <= last; row++) { |
| GtkTreeIter iter; |
| QModelIndex idx = proxy_model->index(row, 0, parent); |
| iter.stamp = stamp; |
| qmodelindex_to_iter(idx, &iter); |
| GtkTreePath *path = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter); |
| gtk_tree_model_row_inserted(GTK_TREE_MODEL(retval), path, &iter); |
| gtk_tree_path_free(path); |
| } |
| } |
| ); |
| |
| QObject::connect( |
| proxy_model, |
| &QAbstractItemModel::rowsAboutToBeMoved, |
| [=](const QModelIndex & sourceParent, int sourceStart, int sourceEnd, |
| const QModelIndex & destinationParent, G_GNUC_UNUSED int destinationRow) { |
| /* if the sourceParent and the destinationParent are the same, then we can use the |
| * GtkTreeModel API to move the rows, otherwise we must first delete them and then |
| * re-insert them under the new parent */ |
| if (sourceParent == destinationParent) |
| return; |
| |
| /* first remove the row from old location |
| * then insert them at the new location on the "rowsMoved signal */ |
| for( int row = sourceStart; row <= sourceEnd; row++) { |
| QModelIndex idx = proxy_model->index(row, 0, sourceParent); |
| GtkTreeIter iter_old; |
| iter_old.stamp = stamp; |
| qmodelindex_to_iter(idx, &iter_old); |
| GtkTreePath *path_old = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter_old); |
| gtk_tree_model_row_deleted(GTK_TREE_MODEL(retval), path_old); |
| gtk_tree_path_free(path_old); |
| } |
| } |
| ); |
| |
| QObject::connect( |
| proxy_model, |
| &QAbstractItemModel::rowsMoved, |
| [=](const QModelIndex & sourceParent, int sourceStart, int sourceEnd, |
| const QModelIndex & destinationParent, int destinationRow) { |
| /* if the sourceParent and the destinationParent are the same, then we can use the |
| * GtkTreeModel API to move the rows, otherwise we must first delete them and then |
| * re-insert them under the new parent */ |
| if (sourceParent == destinationParent) { |
| GtkTreeIter *iter_parent = nullptr; |
| GtkTreePath *path_parent = nullptr; |
| if (sourceParent.isValid()) { |
| iter_parent = g_new0(GtkTreeIter, 1); |
| iter_parent->stamp = stamp; |
| qmodelindex_to_iter(sourceParent, iter_parent); |
| path_parent = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), iter_parent); |
| } else { |
| path_parent = gtk_tree_path_new(); |
| } |
| |
| /* gtk_tree_model_rows_reordered takes an array of integers mapping the current |
| * position of each child to its old position before the re-ordering, |
| * i.e. new_order [newpos] = oldpos |
| */ |
| const auto rows = proxy_model->rowCount(sourceParent); |
| gint new_order[rows]; |
| const auto destinationRowLast = destinationRow + (sourceEnd - sourceStart); |
| const auto totalMoved = sourceEnd - sourceStart + 1; |
| for (int row = 0; row < rows; ++row ) { |
| if ( (row < sourceStart && row < destinationRow) |
| || (row > sourceEnd && row > destinationRowLast) ) { |
| // if the row is outside of the bounds of change, then it stayed in the same place |
| new_order[row] = row; |
| } else { |
| if (row < destinationRow) { |
| // in front of the destination, so it used to be behind the rows that moved |
| new_order[row] = row + totalMoved; |
| } else if (row >= destinationRow && row <= destinationRowLast) { |
| // within the destination, so it was within the rows that moved |
| new_order[row] = sourceStart + (row - destinationRow); |
| } else { |
| // behind the destination, so it used to be in front of the rows that moved |
| new_order[row] = row - totalMoved; |
| } |
| } |
| } |
| gtk_tree_model_rows_reordered(GTK_TREE_MODEL(retval), path_parent, iter_parent, new_order); |
| |
| g_free(iter_parent); |
| gtk_tree_path_free(path_parent); |
| } else { |
| /* these rows should have been removed in the "rowsAboutToBeMoved" handler |
| * now insert them in the new location */ |
| for( int row = sourceStart; row <= sourceEnd; row++) { |
| GtkTreeIter iter_new; |
| QModelIndex idx = proxy_model->index(destinationRow, 0, destinationParent); |
| iter_new.stamp = stamp; |
| qmodelindex_to_iter(idx, &iter_new); |
| GtkTreePath *path_new = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter_new); |
| gtk_tree_model_row_inserted(GTK_TREE_MODEL(retval), path_new, &iter_new); |
| gtk_tree_path_free(path_new); |
| insert_children(idx, retval); |
| destinationRow++; |
| } |
| } |
| } |
| ); |
| |
| QObject::connect( |
| proxy_model, |
| &QAbstractItemModel::rowsRemoved, |
| [=](const QModelIndex & parent, int first, int last) { |
| GtkTreePath *parent_path = NULL; |
| if (parent.isValid()) { |
| GtkTreeIter iter_parent; |
| iter_parent.stamp = stamp; |
| qmodelindex_to_iter(parent, &iter_parent); |
| parent_path = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter_parent); |
| } else { |
| parent_path = gtk_tree_path_new(); |
| } |
| |
| /* go last to first, since the rows are being deleted */ |
| for( int row = last; row >= first; --row) { |
| // g_debug("deleting row: %d", row); |
| GtkTreePath *path = gtk_tree_path_copy(parent_path); |
| gtk_tree_path_append_index(path, row); |
| gtk_tree_model_row_deleted(GTK_TREE_MODEL(retval), path); |
| gtk_tree_path_free(path); |
| } |
| |
| gtk_tree_path_free(parent_path); |
| } |
| ); |
| |
| QObject::connect( |
| proxy_model, |
| &QAbstractItemModel::dataChanged, |
| [=](const QModelIndex & topLeft, const QModelIndex & bottomRight, |
| G_GNUC_UNUSED const QVector<int> & roles = QVector<int> ()) { |
| /* we have to assume only one column */ |
| int first = topLeft.row(); |
| int last = bottomRight.row(); |
| if (topLeft.column() != bottomRight.column() ) { |
| g_warning("more than one column is not supported!"); |
| } |
| /* the first idx IS topLeft, the reset are his siblings */ |
| GtkTreeIter iter; |
| QModelIndex idx = topLeft; |
| iter.stamp = stamp; |
| qmodelindex_to_iter(idx, &iter); |
| GtkTreePath *path = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter); |
| gtk_tree_model_row_changed(GTK_TREE_MODEL(retval), path, &iter); |
| gtk_tree_path_free(path); |
| for( int row = first + 1; row <= last; row++) { |
| idx = topLeft.sibling(row, 0); |
| qmodelindex_to_iter(idx, &iter); |
| path = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter); |
| gtk_tree_model_row_changed(GTK_TREE_MODEL(retval), path, &iter); |
| gtk_tree_path_free(path); |
| } |
| } |
| ); |
| |
| QObject::connect( |
| proxy_model, |
| &QAbstractItemModel::modelAboutToBeReset, |
| [=] () { |
| // g_debug("model about to be reset"); |
| /* nothing equvivalent eixists in GtkTreeModel, so simply delete all |
| * rows, and add all rows when the model is reset; |
| * we must delete the rows in ascending order */ |
| int row_count = proxy_model->rowCount(); |
| for (int row = row_count; row > 0; --row) { |
| // g_debug("deleting row %d", row -1); |
| QModelIndex idx = proxy_model->index(row - 1, 0); |
| GtkTreeIter iter; |
| iter.stamp = stamp; |
| qmodelindex_to_iter(idx, &iter); |
| GtkTreePath *path = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter); |
| gtk_tree_model_row_deleted(GTK_TREE_MODEL(retval), path); |
| gtk_tree_path_free(path); |
| } |
| } |
| ); |
| |
| QObject::connect( |
| proxy_model, |
| &QAbstractItemModel::modelReset, |
| [=] () { |
| // g_debug("model reset"); |
| /* now recursively add all the (new) rows */ |
| insert_children(QModelIndex(), retval); |
| } |
| ); |
| |
| // QObject::connect( |
| // proxy_model, |
| // &QAbstractItemModel::layoutAboutToBeChanged, |
| // [=] () { |
| // // g_debug("layout about to be changed"); |
| // /* nothing equvivalent eixists in GtkTreeModel, so simply delete all |
| // * rows, and add all rows when the model is reset; |
| // * we must delete the rows in ascending order */ |
| // int row_count = proxy_model->rowCount(); |
| // for (int row = row_count; row > 0; --row) { |
| // // g_debug("deleting row %d", row -1); |
| // QModelIndex idx = proxy_model->index(row - 1, 0); |
| // GtkTreeIter iter; |
| // iter.stamp = stamp; |
| // qmodelindex_to_iter(idx, &iter); |
| // GtkTreePath *path = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), &iter); |
| // gtk_tree_model_row_deleted(GTK_TREE_MODEL(retval), path); |
| // } |
| // } |
| // ); |
| // |
| // QObject::connect( |
| // proxy_model, |
| // &QAbstractItemModel::layoutChanged, |
| // [=] () { |
| // // g_debug("layout changed"); |
| // /* now add all the (new) rows */ |
| // int row_count = proxy_model->rowCount(); |
| // for (int row = 0; row < row_count; row++) { |
| // // g_debug("adding row %d", row); |
| // GtkTreeIter *iter_new = g_new0(GtkTreeIter, 1); |
| // QModelIndex idx = proxy_model->index(row, 0); |
| // iter_new->stamp = stamp; |
| // qmodelindex_to_iter(idx, iter_new); |
| // GtkTreePath *path_new = gtk_q_tree_model_get_path(GTK_TREE_MODEL(retval), iter_new); |
| // gtk_tree_model_row_inserted(GTK_TREE_MODEL(retval), path_new, iter_new); |
| // } |
| // } |
| // ); |
| |
| return retval; |
| } |
| |
| static gint |
| gtk_q_tree_model_length(GtkQTreeModel *q_tree_model) |
| { |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| return priv->model->rowCount(); |
| } |
| |
| static void |
| gtk_q_tree_model_set_n_columns(GtkQTreeModel *q_tree_model, |
| gint n_columns) |
| { |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| int i; |
| |
| if (priv->n_columns == n_columns) |
| return; |
| |
| priv->column_headers = g_renew (GType, priv->column_headers, n_columns); |
| for (i = priv->n_columns; i < n_columns; i++) |
| priv->column_headers[i] = G_TYPE_INVALID; |
| priv->n_columns = n_columns; |
| |
| priv->column_roles = g_renew (gint, priv->column_roles, n_columns); |
| for (i = priv->n_columns; i < n_columns; i++) |
| priv->column_roles[i] = -1; |
| |
| priv->column_model_col = g_renew (gint, priv->column_model_col, n_columns); |
| for (i = priv->n_columns; i < n_columns; i++) |
| priv->column_model_col[i] = 0; |
| } |
| |
| static void |
| gtk_q_tree_model_set_column_type(GtkQTreeModel *q_tree_model, |
| gint column, |
| gint model_col, |
| gint role, |
| GType type) |
| { |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| /* TODO: check if its a type supported by our model |
| * if (!_gtk_tree_data_list_check_type (type)) |
| * { |
| * g_warning ("%s: Invalid type %s\n", G_STRLOC, g_type_name (type)); |
| * return; |
| * } |
| */ |
| |
| priv->column_model_col[column] = model_col; |
| priv->column_headers[column] = type; |
| priv->column_roles[column] = role; |
| } |
| |
| |
| static void |
| gtk_q_tree_model_finalize(GObject *object) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (object); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| g_free(priv->column_model_col); |
| g_free(priv->column_headers); |
| g_free(priv->column_roles); |
| |
| /* delete the created proxy model */ |
| delete priv->model; |
| |
| G_OBJECT_CLASS(gtk_q_tree_model_parent_class)->finalize (object); |
| } |
| |
| static gboolean |
| iter_is_valid(GtkTreeIter *iter, |
| GtkQTreeModel *q_tree_model) |
| { |
| gboolean retval; |
| g_return_val_if_fail(iter != NULL, FALSE); |
| QIter *qiter = Q_ITER(iter); |
| retval = q_tree_model->priv->model->indexFromId(qiter->row.value, qiter->column.value, qiter->id).isValid(); |
| return retval; |
| } |
| |
| static void |
| qmodelindex_to_iter(const QModelIndex &idx, GtkTreeIter *iter) |
| { |
| Q_ITER(iter)->row.value = idx.row(); |
| Q_ITER(iter)->column.value = idx.column(); |
| Q_ITER(iter)->id = idx.internalPointer(); |
| } |
| |
| static gboolean |
| validate_index(gint stamp, const QModelIndex &idx, GtkTreeIter *iter) |
| { |
| if (idx.isValid()) { |
| iter->stamp = stamp; |
| qmodelindex_to_iter(idx, iter); |
| } else { |
| iter->stamp = 0; |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /* Start Fulfill the GtkTreeModel requirements */ |
| |
| /* flags supported by this interface */ |
| static GtkTreeModelFlags |
| gtk_q_tree_model_get_flags(G_GNUC_UNUSED GtkTreeModel *tree_model) |
| { |
| // TODO: possibly return based on the model? |
| return (GtkTreeModelFlags)(GTK_TREE_MODEL_ITERS_PERSIST); |
| } |
| |
| /* number of columns supported by this tree model */ |
| static gint |
| gtk_q_tree_model_get_n_columns(GtkTreeModel *tree_model) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL(tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| return priv->model->columnCount(); |
| } |
| |
| /* get given column type */ |
| static GType |
| gtk_q_tree_model_get_column_type(GtkTreeModel *tree_model, |
| gint index) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| g_return_val_if_fail (index < gtk_q_tree_model_get_n_columns(tree_model), G_TYPE_INVALID); |
| |
| return priv->column_headers[index]; |
| } |
| |
| /* Sets @iter to a valid iterator pointing to @path. If @path does |
| * not exist, @iter is set to an invalid iterator and %FALSE is returned. |
| */ |
| static gboolean |
| gtk_q_tree_model_get_iter(GtkTreeModel *tree_model, |
| GtkTreeIter *iter, |
| GtkTreePath *path) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| /* GtkTreePath is a list of indices, each one indicates |
| * the child of the previous one. |
| * Since GtkTreeModel only has rows (columns are only used to |
| * store data in each item), each index is basically the row |
| * at the given tree level. |
| * To get the iter, we want to get the QModelIndex. To get |
| * the QModelIndex we simply start at the first level and |
| * traverse the model the number of layers equal to the number |
| * of indices in the path. |
| */ |
| gint depth; |
| gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth); |
| QModelIndex idx = priv->model->index(indices[0], 0); |
| for(int layer = 1; layer < depth; layer++ ) { |
| /* check if previous iter is valid */ |
| if (!idx.isValid()) { |
| break; |
| } else { |
| idx = idx.child(indices[layer], 0); |
| } |
| } |
| |
| if (!idx.isValid()) { |
| iter->stamp = 0; |
| return FALSE; |
| } else { |
| /* we have a valid QModelIndex; turn it into an iter */ |
| iter->stamp = priv->stamp; |
| qmodelindex_to_iter(idx, iter); |
| } |
| |
| return TRUE; |
| } |
| |
| /* Returns a newly-created #GtkTreePath referenced by @iter. |
| * |
| * This path should be freed with gtk_tree_path_free(). |
| */ |
| static GtkTreePath * |
| gtk_q_tree_model_get_path(GtkTreeModel *tree_model, |
| GtkTreeIter *iter) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| GtkTreePath *path; |
| |
| g_return_val_if_fail (iter->stamp == priv->stamp, NULL); |
| g_return_val_if_fail (iter_is_valid(iter, q_tree_model), NULL); |
| |
| /* To get the path, we have to traverse from the child all the way up */ |
| path = gtk_tree_path_new(); |
| QIter *qiter = Q_ITER(iter); |
| QModelIndex idx = priv->model->indexFromId(qiter->row.value, qiter->column.value, qiter->id); |
| while( idx.isValid() ){ |
| gtk_tree_path_prepend_index(path, idx.row()); |
| idx = idx.parent(); |
| } |
| return path; |
| } |
| |
| static inline GType |
| get_fundamental_type(GType type) |
| { |
| GType result; |
| |
| result = G_TYPE_FUNDAMENTAL (type); |
| |
| if (result == G_TYPE_INTERFACE) { |
| if (g_type_is_a (type, G_TYPE_OBJECT)) |
| result = G_TYPE_OBJECT; |
| } |
| |
| return result; |
| } |
| |
| /* Initializes and sets @value to that at @column. */ |
| static void |
| gtk_q_tree_model_get_value(GtkTreeModel *tree_model, |
| GtkTreeIter *iter, |
| gint column, |
| GValue *value) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| g_return_if_fail (column < priv->n_columns); |
| g_return_if_fail (iter_is_valid(iter, q_tree_model)); |
| |
| /* get the data */ |
| QIter *qiter = Q_ITER(iter); |
| int model_col = priv->column_model_col[column]; |
| QModelIndex idx = priv->model->indexFromId(qiter->row.value, model_col, qiter->id); |
| int role = priv->column_roles[column]; |
| QVariant var = priv->model->data(idx, role); |
| GType type = priv->column_headers[column]; |
| g_value_init (value, type); |
| switch (get_fundamental_type (type)) |
| { |
| /* TODO: implement the rest of the data types the we plan to support |
| * otherwise get rid of the code and make sure un-implemented types |
| * are not allowed |
| */ |
| case G_TYPE_BOOLEAN: |
| g_value_set_boolean (value, (gboolean) var.toBool()); |
| break; |
| // case G_TYPE_CHAR: |
| // g_value_set_schar (value, (gchar) list->data.v_char); |
| // break; |
| // case G_TYPE_UCHAR: |
| // g_value_set_uchar (value, (guchar) list->data.v_uchar); |
| // break; |
| case G_TYPE_INT: |
| g_value_set_int (value, (gint) var.toInt()); |
| break; |
| case G_TYPE_UINT: |
| g_value_set_uint (value, (guint) var.toUInt()); |
| break; |
| // case G_TYPE_LONG: |
| // g_value_set_long (value, list->data.v_long); |
| // break; |
| // case G_TYPE_ULONG: |
| // g_value_set_ulong (value, list->data.v_ulong); |
| // break; |
| // case G_TYPE_INT64: |
| // g_value_set_int64 (value, list->data.v_int64); |
| // break; |
| // case G_TYPE_UINT64: |
| // g_value_set_uint64 (value, list->data.v_uint64); |
| // break; |
| // case G_TYPE_ENUM: |
| // g_value_set_enum (value, list->data.v_int); |
| // break; |
| // case G_TYPE_FLAGS: |
| // g_value_set_flags (value, list->data.v_uint); |
| // break; |
| // case G_TYPE_FLOAT: |
| // g_value_set_float (value, (gfloat) list->data.v_float); |
| // break; |
| // case G_TYPE_DOUBLE: |
| // g_value_set_double (value, (gdouble) list->data.v_double); |
| // break; |
| case G_TYPE_STRING: |
| { |
| QByteArray ba = var.toString().toUtf8(); |
| g_value_set_string(value, (gchar *)var.toByteArray().constData()); |
| } |
| break; |
| // case G_TYPE_POINTER: |
| // g_value_set_pointer (value, (gpointer) list->data.v_pointer); |
| // break; |
| // case G_TYPE_BOXED: |
| // g_value_set_boxed (value, (gpointer) list->data.v_pointer); |
| // break; |
| // case G_TYPE_VARIANT: |
| // g_value_set_variant (value, (gpointer) list->data.v_pointer); |
| // break; |
| // case G_TYPE_OBJECT: |
| // g_value_set_object (value, (GObject *) list->data.v_pointer); |
| // break; |
| default: |
| g_warning ("%s: Unsupported type (%s) retrieved.", G_STRLOC, g_type_name (value->g_type)); |
| break; |
| } |
| |
| return; |
| } |
| |
| /* Sets @iter to point to the node following it at the current level. |
| * |
| * If there is no next @iter, %FALSE is returned and @iter is set |
| * to be invalid. |
| */ |
| static gboolean |
| gtk_q_tree_model_iter_next(GtkTreeModel *tree_model, |
| GtkTreeIter *iter) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| g_return_val_if_fail (iter_is_valid(iter, q_tree_model), FALSE); |
| |
| QIter *qiter = Q_ITER(iter); |
| QModelIndex idx = priv->model->indexFromId(qiter->row.value, qiter->column.value, qiter->id); |
| idx = idx.sibling(idx.row()+1, idx.column()); |
| |
| /* validate */ |
| if (validate_index(priv->stamp, idx, iter) ) { |
| GtkTreePath *path = gtk_q_tree_model_get_path(tree_model, iter); |
| gtk_tree_path_free(path); |
| return TRUE; |
| } else { |
| return FALSE; |
| } |
| } |
| |
| /* Sets @iter to point to the previous node at the current level. |
| * |
| * If there is no previous @iter, %FALSE is returned and @iter is |
| * set to be invalid. |
| */ |
| static gboolean |
| gtk_q_tree_model_iter_previous(GtkTreeModel *tree_model, |
| GtkTreeIter *iter) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| g_return_val_if_fail (iter_is_valid(iter, q_tree_model), FALSE); |
| |
| QIter *qiter = Q_ITER(iter); |
| QModelIndex idx = priv->model->indexFromId(qiter->row.value, qiter->column.value, qiter->id); |
| idx = idx.sibling(idx.row()-1, idx.column()); |
| |
| /* validate */ |
| return validate_index(priv->stamp, idx, iter); |
| } |
| |
| /* Sets @iter to point to the first child of @parent. |
| * |
| * If @parent has no children, %FALSE is returned and @iter is |
| * set to be invalid. @parent will remain a valid node after this |
| * function has been called. |
| * |
| * If @parent is %NULL returns the first node |
| */ |
| static gboolean |
| gtk_q_tree_model_iter_children(GtkTreeModel *tree_model, |
| GtkTreeIter *iter, |
| GtkTreeIter *parent) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| QModelIndex idx; |
| |
| /* make sure parent if valid node if its not NULL */ |
| if (parent) |
| g_return_val_if_fail(iter_is_valid(parent, q_tree_model), FALSE); |
| |
| if (parent) { |
| /* get first child */ |
| QIter *qparent = Q_ITER(parent); |
| idx = priv->model->indexFromId(qparent->row.value, qparent->column.value, qparent->id); |
| idx = idx.child(0, 0); |
| } else { |
| /* parent is NULL, get the first node */ |
| idx = priv->model->index(0, 0); |
| } |
| |
| /* validate child */ |
| return validate_index(priv->stamp, idx, iter); |
| } |
| |
| /* Returns %TRUE if @iter has children, %FALSE otherwise. */ |
| static gboolean |
| gtk_q_tree_model_iter_has_child (GtkTreeModel *tree_model, |
| GtkTreeIter *iter) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| g_return_val_if_fail(iter_is_valid(iter, q_tree_model), FALSE); |
| |
| QIter *qiter = Q_ITER(iter); |
| QModelIndex idx = priv->model->indexFromId(qiter->row.value, qiter->column.value, qiter->id); |
| return priv->model->hasChildren(idx); |
| } |
| |
| /* Returns the number of children that @iter has. |
| * |
| * As a special case, if @iter is %NULL, then the number |
| * of toplevel nodes is returned. |
| */ |
| static gint |
| gtk_q_tree_model_iter_n_children (GtkTreeModel *tree_model, |
| GtkTreeIter *iter) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| |
| if (iter == NULL) |
| return gtk_q_tree_model_length(q_tree_model); |
| |
| g_return_val_if_fail(iter_is_valid(iter, q_tree_model), -1); |
| QIter *qiter = Q_ITER(iter); |
| QModelIndex idx = priv->model->indexFromId(qiter->row.value, qiter->column.value, qiter->id); |
| return priv->model->rowCount(idx); |
| } |
| |
| /* Sets @iter to be the child of @parent, using the given index. |
| * |
| * The first index is 0. If @n is too big, or @parent has no children, |
| * @iter is set to an invalid iterator and %FALSE is returned. @parent |
| * will remain a valid node after this function has been called. As a |
| * special case, if @parent is %NULL, then the @n<!-- -->th root node |
| * is set. |
| */ |
| static gboolean |
| gtk_q_tree_model_iter_nth_child(GtkTreeModel *tree_model, |
| GtkTreeIter *iter, |
| GtkTreeIter *parent, |
| gint n) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| QModelIndex idx; |
| |
| if (parent) { |
| g_return_val_if_fail(iter_is_valid(parent, q_tree_model), FALSE); |
| |
| /* get the nth child */ |
| QIter *qparent = Q_ITER(parent); |
| idx = priv->model->indexFromId(qparent->row.value, qparent->column.value, qparent->id); |
| idx = idx.child(n, 0); |
| } else { |
| idx = priv->model->index(n, 0); |
| } |
| |
| /* validate */ |
| return validate_index(priv->stamp, idx, iter); |
| } |
| |
| /* Sets @iter to be the parent of @child. |
| * |
| * If @child is at the toplevel, and doesn't have a parent, then |
| * @iter is set to an invalid iterator and %FALSE is returned. |
| * @child will remain a valid node after this function has been |
| * called. |
| */ |
| static gboolean |
| gtk_q_tree_model_iter_parent(GtkTreeModel *tree_model, |
| GtkTreeIter *iter, |
| GtkTreeIter *child) |
| { |
| GtkQTreeModel *q_tree_model = GTK_Q_TREE_MODEL (tree_model); |
| GtkQTreeModelPrivate *priv = q_tree_model->priv; |
| QModelIndex idx; |
| |
| g_return_val_if_fail(iter_is_valid(child, q_tree_model), FALSE); |
| |
| QIter *qchild = Q_ITER(child); |
| idx = priv->model->indexFromId(qchild->row.value, qchild->column.value, qchild->id); |
| idx = idx.parent(); |
| |
| /* validate */ |
| return validate_index(priv->stamp, idx, iter); |
| } |
| |
| /* End Fulfill the GtkTreeModel requirements */ |