mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-12 20:00:09 +00:00
treelistmodel: Refactor to add GtkTreeListRow
This patch does multiple things: 1. Add a custom persistent per-row object. 2. Move all per-row API to that object. This means notifications are now possible. 3. Add a "passthrough" construct-only property to the TreeListModel that influences if the model returns these new object or passes through the ones from the model. This greatly simplifies the code needed to be written for widgetry, because one can just connect the per-row object to the expanders that expand and collapse rows. As an added power feature, these objects can also be passed through further models (like filter models). It also adds kind of a hack to Adwaita to make the test look neat.
This commit is contained in:
parent
4f70f72349
commit
4b5fb5ec79
@ -28,6 +28,7 @@
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_AUTOEXPAND,
|
||||
PROP_PASSTHROUGH,
|
||||
PROP_ROOT_MODEL,
|
||||
NUM_PROPERTIES
|
||||
};
|
||||
@ -38,6 +39,7 @@ typedef struct _TreeAugment TreeAugment;
|
||||
struct _TreeNode
|
||||
{
|
||||
GListModel *model;
|
||||
GtkTreeListRow *row;
|
||||
GtkCssRbTree *children;
|
||||
union {
|
||||
TreeNode *parent;
|
||||
@ -65,6 +67,7 @@ struct _GtkTreeListModel
|
||||
GDestroyNotify user_destroy;
|
||||
|
||||
guint autoexpand : 1;
|
||||
guint passthrough : 1;
|
||||
};
|
||||
|
||||
struct _GtkTreeListModelClass
|
||||
@ -72,6 +75,18 @@ struct _GtkTreeListModelClass
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
struct _GtkTreeListRow
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
TreeNode *node; /* NULL when the row has been destroyed */
|
||||
};
|
||||
|
||||
struct _GtkTreeListRowClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
|
||||
|
||||
static GtkTreeListModel *
|
||||
@ -290,73 +305,47 @@ gtk_tree_list_model_get_nth (GtkTreeListModel *self,
|
||||
g_return_val_if_reached (NULL);
|
||||
}
|
||||
|
||||
static GType
|
||||
gtk_tree_list_model_get_item_type (GListModel *list)
|
||||
static GListModel *
|
||||
tree_node_create_model (GtkTreeListModel *self,
|
||||
TreeNode *node)
|
||||
{
|
||||
GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);
|
||||
TreeNode *parent = node->parent;
|
||||
GListModel *model;
|
||||
GObject *item;
|
||||
|
||||
return g_list_model_get_item_type (self->root_node.model);
|
||||
}
|
||||
item = g_list_model_get_item (parent->model,
|
||||
tree_node_get_local_position (parent->children, node));
|
||||
model = self->create_func (item, self->user_data);
|
||||
g_object_unref (item);
|
||||
if (model == NULL)
|
||||
node->empty = TRUE;
|
||||
|
||||
static guint
|
||||
gtk_tree_list_model_get_n_items (GListModel *list)
|
||||
{
|
||||
GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);
|
||||
|
||||
return tree_node_get_n_children (&self->root_node);
|
||||
return model;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
gtk_tree_list_model_get_item (GListModel *list,
|
||||
guint position)
|
||||
tree_node_get_item (TreeNode *node)
|
||||
{
|
||||
GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);
|
||||
TreeNode *node, *parent;
|
||||
|
||||
node = gtk_tree_list_model_get_nth (self, position);
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
TreeNode *parent;
|
||||
|
||||
parent = node->parent;
|
||||
return g_list_model_get_item (parent->model,
|
||||
tree_node_get_local_position (parent->children, node));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_model_init (GListModelInterface *iface)
|
||||
static GtkTreeListRow *
|
||||
tree_node_get_row (TreeNode *node)
|
||||
{
|
||||
iface->get_item_type = gtk_tree_list_model_get_item_type;
|
||||
iface->get_n_items = gtk_tree_list_model_get_n_items;
|
||||
iface->get_item = gtk_tree_list_model_get_item;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkTreeListModel, gtk_tree_list_model, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tree_list_model_model_init))
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_augment (GtkCssRbTree *tree,
|
||||
gpointer _aug,
|
||||
gpointer _node,
|
||||
gpointer left,
|
||||
gpointer right)
|
||||
{
|
||||
TreeAugment *aug = _aug;
|
||||
|
||||
aug->n_items = 1;
|
||||
aug->n_items += tree_node_get_n_children (_node);
|
||||
aug->n_local = 1;
|
||||
|
||||
if (left)
|
||||
if (node->row)
|
||||
{
|
||||
TreeAugment *left_aug = gtk_css_rb_tree_get_augment (tree, left);
|
||||
aug->n_items += left_aug->n_items;
|
||||
aug->n_local += left_aug->n_local;
|
||||
return g_object_ref (node->row);
|
||||
}
|
||||
if (right)
|
||||
else
|
||||
{
|
||||
TreeAugment *right_aug = gtk_css_rb_tree_get_augment (tree, right);
|
||||
aug->n_items += right_aug->n_items;
|
||||
aug->n_local += right_aug->n_local;
|
||||
node->row = g_object_new (GTK_TYPE_TREE_LIST_ROW, NULL);
|
||||
node->row->node = node;
|
||||
|
||||
return node->row;
|
||||
}
|
||||
}
|
||||
|
||||
@ -439,11 +428,16 @@ gtk_tree_list_model_items_changed_cb (GListModel *model,
|
||||
tree_added);
|
||||
}
|
||||
|
||||
static void gtk_tree_list_row_destroy (GtkTreeListRow *row);
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_clear_node (gpointer data)
|
||||
{
|
||||
TreeNode *node = data;
|
||||
|
||||
if (node->row)
|
||||
gtk_tree_list_row_destroy (node->row);
|
||||
|
||||
if (node->model)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (node->model,
|
||||
@ -455,6 +449,33 @@ gtk_tree_list_model_clear_node (gpointer data)
|
||||
gtk_css_rb_tree_unref (node->children);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_augment (GtkCssRbTree *tree,
|
||||
gpointer _aug,
|
||||
gpointer _node,
|
||||
gpointer left,
|
||||
gpointer right)
|
||||
{
|
||||
TreeAugment *aug = _aug;
|
||||
|
||||
aug->n_items = 1;
|
||||
aug->n_items += tree_node_get_n_children (_node);
|
||||
aug->n_local = 1;
|
||||
|
||||
if (left)
|
||||
{
|
||||
TreeAugment *left_aug = gtk_css_rb_tree_get_augment (tree, left);
|
||||
aug->n_items += left_aug->n_items;
|
||||
aug->n_local += left_aug->n_local;
|
||||
}
|
||||
if (right)
|
||||
{
|
||||
TreeAugment *right_aug = gtk_css_rb_tree_get_augment (tree, right);
|
||||
aug->n_items += right_aug->n_items;
|
||||
aug->n_local += right_aug->n_local;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_init_node (GtkTreeListModel *list,
|
||||
TreeNode *self,
|
||||
@ -485,6 +506,111 @@ gtk_tree_list_model_init_node (GtkTreeListModel *list,
|
||||
}
|
||||
}
|
||||
|
||||
static guint
|
||||
gtk_tree_list_model_expand_node (GtkTreeListModel *self,
|
||||
TreeNode *node)
|
||||
{
|
||||
GListModel *model;
|
||||
|
||||
if (node->empty)
|
||||
return 0;
|
||||
|
||||
if (node->model != NULL)
|
||||
return 0;
|
||||
|
||||
model = tree_node_create_model (self, node);
|
||||
|
||||
if (model == NULL)
|
||||
return 0;
|
||||
|
||||
if (!g_type_is_a (g_list_model_get_item_type (model), g_list_model_get_item_type (self->root_node.model)))
|
||||
{
|
||||
g_critical ("The GtkTreeListModelCreateModelFunc for %p returned a model with item type \"%s\" "
|
||||
"but \"%s\" is required.",
|
||||
self,
|
||||
g_type_name (g_list_model_get_item_type (model)),
|
||||
g_type_name (g_list_model_get_item_type (self->root_node.model)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
gtk_tree_list_model_init_node (self, node, model);
|
||||
|
||||
tree_node_mark_dirty (node);
|
||||
|
||||
return tree_node_get_n_children (node);
|
||||
}
|
||||
|
||||
static guint
|
||||
gtk_tree_list_model_collapse_node (GtkTreeListModel *self,
|
||||
TreeNode *node)
|
||||
{
|
||||
guint n_items;
|
||||
|
||||
if (node->model == NULL)
|
||||
return 0;
|
||||
|
||||
n_items = tree_node_get_n_children (node);
|
||||
|
||||
g_clear_pointer (&node->children, gtk_css_rb_tree_unref);
|
||||
g_clear_object (&node->model);
|
||||
|
||||
tree_node_mark_dirty (node);
|
||||
|
||||
return n_items;
|
||||
}
|
||||
|
||||
|
||||
static GType
|
||||
gtk_tree_list_model_get_item_type (GListModel *list)
|
||||
{
|
||||
GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);
|
||||
|
||||
if (self->passthrough)
|
||||
return GTK_TYPE_TREE_LIST_ROW;
|
||||
else
|
||||
return g_list_model_get_item_type (self->root_node.model);
|
||||
}
|
||||
|
||||
static guint
|
||||
gtk_tree_list_model_get_n_items (GListModel *list)
|
||||
{
|
||||
GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);
|
||||
|
||||
return tree_node_get_n_children (&self->root_node);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
gtk_tree_list_model_get_item (GListModel *list,
|
||||
guint position)
|
||||
{
|
||||
GtkTreeListModel *self = GTK_TREE_LIST_MODEL (list);
|
||||
TreeNode *node;
|
||||
|
||||
node = gtk_tree_list_model_get_nth (self, position);
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
if (self->passthrough)
|
||||
{
|
||||
return tree_node_get_item (node);
|
||||
}
|
||||
else
|
||||
{
|
||||
return tree_node_get_row (node);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_model_init (GListModelInterface *iface)
|
||||
{
|
||||
iface->get_item_type = gtk_tree_list_model_get_item_type;
|
||||
iface->get_n_items = gtk_tree_list_model_get_n_items;
|
||||
iface->get_item = gtk_tree_list_model_get_item;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkTreeListModel, gtk_tree_list_model, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tree_list_model_model_init))
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
@ -499,6 +625,10 @@ gtk_tree_list_model_set_property (GObject *object,
|
||||
gtk_tree_list_model_set_autoexpand (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
case PROP_PASSTHROUGH:
|
||||
self->passthrough = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -519,6 +649,10 @@ gtk_tree_list_model_get_property (GObject *object,
|
||||
g_value_set_boolean (value, self->autoexpand);
|
||||
break;
|
||||
|
||||
case PROP_PASSTHROUGH:
|
||||
g_value_set_boolean (value, self->passthrough);
|
||||
break;
|
||||
|
||||
case PROP_ROOT_MODEL:
|
||||
g_value_set_object (value, self->root_node.model);
|
||||
break;
|
||||
@ -562,6 +696,20 @@ gtk_tree_list_model_class_init (GtkTreeListModelClass *class)
|
||||
FALSE,
|
||||
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
/**
|
||||
* GtkTreeListModel:passthrough:
|
||||
*
|
||||
* If %FALSE, the #GListModel functions for this object return custom
|
||||
* #GtkTreeListRow objects.
|
||||
* If %TRUE, the values of the child models are pass through unmodified.
|
||||
*/
|
||||
properties[PROP_PASSTHROUGH] =
|
||||
g_param_spec_boolean ("passthrough",
|
||||
P_("passthrough"),
|
||||
P_("If child model values are passed through"),
|
||||
FALSE,
|
||||
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
/**
|
||||
* GtkTreeListModel:root-model:
|
||||
*
|
||||
@ -586,6 +734,7 @@ gtk_tree_list_model_init (GtkTreeListModel *self)
|
||||
|
||||
/**
|
||||
* gtk_tree_list_model_new:
|
||||
* @passthrough: %TRUE to pass through items from the models
|
||||
* @root: The #GListModel to use as root
|
||||
* @autoexpand: %TRUE to set the autoexpand property and expand the @root model
|
||||
* @create_func: Function to call to create the #GListModel for the children
|
||||
@ -598,7 +747,8 @@ gtk_tree_list_model_init (GtkTreeListModel *self)
|
||||
* Returns: a newly created #GtkTreeListModel.
|
||||
**/
|
||||
GtkTreeListModel *
|
||||
gtk_tree_list_model_new (GListModel *root,
|
||||
gtk_tree_list_model_new (gboolean passthrough,
|
||||
GListModel *root,
|
||||
gboolean autoexpand,
|
||||
GtkTreeListModelCreateModelFunc create_func,
|
||||
gpointer user_data,
|
||||
@ -611,6 +761,7 @@ gtk_tree_list_model_new (GListModel *root,
|
||||
|
||||
self = g_object_new (GTK_TYPE_TREE_LIST_MODEL,
|
||||
"autoexpand", autoexpand,
|
||||
"passthrough", passthrough,
|
||||
NULL);
|
||||
|
||||
self->create_func = create_func;
|
||||
@ -622,6 +773,29 @@ gtk_tree_list_model_new (GListModel *root,
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_model_get_passthrough:
|
||||
* @self: a #GtkTreeListModel
|
||||
*
|
||||
* If this function returns %FALSE, the #GListModel functions for @self
|
||||
* return custom #GtkTreeListRow objects. You need to call
|
||||
* gtk_tree_list_row_get_item() on these objects to get the original
|
||||
* item.
|
||||
*
|
||||
* If %TRUE, the values of the child models are passed through in their
|
||||
* original state. You then need to call gtk_tree_list_model_get_row()
|
||||
* to get the custom #GtkTreeListRows.
|
||||
*
|
||||
* Returns: %TRUE if the model is passing through original row items
|
||||
**/
|
||||
gboolean
|
||||
gtk_tree_list_model_get_passthrough (GtkTreeListModel *self)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE);
|
||||
|
||||
return self->passthrough;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_model_set_autoexpand:
|
||||
* @self: a #GtkTreeListModel
|
||||
@ -663,21 +837,225 @@ gtk_tree_list_model_get_autoexpand (GtkTreeListModel *self)
|
||||
return self->autoexpand;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_model_get_row:
|
||||
* @self: a #GtkTreeListModel
|
||||
* @position: the position of the row to fetch
|
||||
*
|
||||
* Gets the row object for the given row. If @position is greater than
|
||||
* the number of items in @self, %NULL is returned.
|
||||
*
|
||||
* The row object can be used to expand and collapse rows as well as
|
||||
* to inspect its position in the tree. See its documentation for details.
|
||||
*
|
||||
* This row object is persistent and will refer to the current item as
|
||||
* long as the row is present in @self, independent of other rows being
|
||||
* added or removed.
|
||||
*
|
||||
* If @self is set to not be passthrough, this function is equivalent
|
||||
* to calling g_list_model_get_item().
|
||||
*
|
||||
* Returns: (nullable) (transfer: full): The row item
|
||||
**/
|
||||
GtkTreeListRow *
|
||||
gtk_tree_list_model_get_row (GtkTreeListModel *self,
|
||||
guint position)
|
||||
{
|
||||
TreeNode *node;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), NULL);
|
||||
|
||||
node = gtk_tree_list_model_get_nth (self, position);
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
return tree_node_get_row (node);
|
||||
}
|
||||
|
||||
/*** ROW ***/
|
||||
|
||||
enum {
|
||||
ROW_PROP_0,
|
||||
ROW_PROP_DEPTH,
|
||||
ROW_PROP_EXPANDABLE,
|
||||
ROW_PROP_EXPANDED,
|
||||
ROW_PROP_ITEM,
|
||||
NUM_ROW_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *row_properties[NUM_ROW_PROPERTIES] = { NULL, };
|
||||
|
||||
G_DEFINE_TYPE (GtkTreeListRow, gtk_tree_list_row, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
gtk_tree_list_row_destroy (GtkTreeListRow *self)
|
||||
{
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
/* FIXME: We could check some properties to avoid excess notifies */
|
||||
g_object_notify_by_pspec (G_OBJECT (self), row_properties[ROW_PROP_DEPTH]);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), row_properties[ROW_PROP_EXPANDABLE]);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), row_properties[ROW_PROP_EXPANDED]);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), row_properties[ROW_PROP_ITEM]);
|
||||
|
||||
self->node = NULL;
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_row_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkTreeListRow *self = GTK_TREE_LIST_ROW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case ROW_PROP_EXPANDED:
|
||||
gtk_tree_list_row_set_expanded (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_row_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkTreeListRow *self = GTK_TREE_LIST_ROW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case ROW_PROP_DEPTH:
|
||||
g_value_set_uint (value, gtk_tree_list_row_get_depth (self));
|
||||
break;
|
||||
|
||||
case ROW_PROP_EXPANDABLE:
|
||||
g_value_set_boolean (value, gtk_tree_list_row_is_expandable (self));
|
||||
break;
|
||||
|
||||
case ROW_PROP_EXPANDED:
|
||||
g_value_set_boolean (value, gtk_tree_list_row_get_expanded (self));
|
||||
break;
|
||||
|
||||
case ROW_PROP_ITEM:
|
||||
g_value_take_object (value, gtk_tree_list_row_get_item (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_row_dispose (GObject *object)
|
||||
{
|
||||
GtkTreeListRow *self = GTK_TREE_LIST_ROW (object);
|
||||
|
||||
if (self->node)
|
||||
self->node->row = NULL;
|
||||
|
||||
G_OBJECT_CLASS (gtk_tree_list_row_parent_class)->dispose (object);
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_tree_list_row_class_init (GtkTreeListRowClass *class)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
|
||||
|
||||
gobject_class->set_property = gtk_tree_list_row_set_property;
|
||||
gobject_class->get_property = gtk_tree_list_row_get_property;
|
||||
gobject_class->dispose = gtk_tree_list_row_dispose;
|
||||
|
||||
/**
|
||||
* GtkTreeListRow:depth:
|
||||
*
|
||||
* The depth in the tree of this row
|
||||
*/
|
||||
row_properties[ROW_PROP_DEPTH] =
|
||||
g_param_spec_uint ("depth",
|
||||
P_("Depth"),
|
||||
P_("Depth in the tree"),
|
||||
0, G_MAXUINT, 0,
|
||||
GTK_PARAM_READABLE);
|
||||
|
||||
/**
|
||||
* GtkTreeListRow:expandable:
|
||||
*
|
||||
* If this row can ever be expanded
|
||||
*/
|
||||
row_properties[ROW_PROP_EXPANDABLE] =
|
||||
g_param_spec_boolean ("expandable",
|
||||
P_("Expandable"),
|
||||
P_("If this row can ever be expanded"),
|
||||
FALSE,
|
||||
GTK_PARAM_READABLE);
|
||||
|
||||
/**
|
||||
* GtkTreeListRow:expanded:
|
||||
*
|
||||
* If this row is currently expanded
|
||||
*/
|
||||
row_properties[ROW_PROP_EXPANDED] =
|
||||
g_param_spec_boolean ("expanded",
|
||||
P_("Expanded"),
|
||||
P_("If this row is currently expanded"),
|
||||
FALSE,
|
||||
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
/**
|
||||
* GtkTreeListRow:item:
|
||||
*
|
||||
* The item held in this row
|
||||
*/
|
||||
row_properties[ROW_PROP_ITEM] =
|
||||
g_param_spec_object ("item",
|
||||
P_("Item"),
|
||||
P_("The item held in this row"),
|
||||
G_TYPE_OBJECT,
|
||||
GTK_PARAM_READABLE);
|
||||
|
||||
g_object_class_install_properties (gobject_class, NUM_ROW_PROPERTIES, row_properties);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_row_init (GtkTreeListRow *self)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_row_get_depth:
|
||||
* @self: a #GtkTreeListRow
|
||||
*
|
||||
* Gets the depth of this row. Rows that correspond to items in
|
||||
* the root model have a depth of zero, rows corresponding to items
|
||||
* of models of direct children of the root model have a depth of
|
||||
* 1 and so on.
|
||||
*
|
||||
* The depth of a row never changes until the row is destroyed.
|
||||
*
|
||||
* Returns: The depth of this row
|
||||
**/
|
||||
guint
|
||||
gtk_tree_list_model_get_depth (GtkTreeListModel *self,
|
||||
guint position)
|
||||
gtk_tree_list_row_get_depth (GtkTreeListRow *self)
|
||||
{
|
||||
TreeNode *node;
|
||||
guint depth;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE);
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), 0);
|
||||
|
||||
node = gtk_tree_list_model_get_nth (self, position);
|
||||
if (node == NULL)
|
||||
if (self->node == NULL)
|
||||
return 0;
|
||||
|
||||
depth = 0;
|
||||
for (node = node->parent;
|
||||
for (node = self->node->parent;
|
||||
!node->is_root;
|
||||
node = node->parent)
|
||||
depth++;
|
||||
@ -685,131 +1063,105 @@ gtk_tree_list_model_get_depth (GtkTreeListModel *self,
|
||||
return depth;
|
||||
}
|
||||
|
||||
static GListModel *
|
||||
tree_node_create_model (GtkTreeListModel *self,
|
||||
TreeNode *node)
|
||||
{
|
||||
TreeNode *parent = node->parent;
|
||||
GListModel *model;
|
||||
GObject *item;
|
||||
|
||||
item = g_list_model_get_item (parent->model,
|
||||
tree_node_get_local_position (parent->children, node));
|
||||
model = self->create_func (item, self->user_data);
|
||||
g_object_unref (item);
|
||||
if (model == NULL)
|
||||
node->empty = TRUE;
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
static guint
|
||||
gtk_tree_list_model_expand_node (GtkTreeListModel *self,
|
||||
TreeNode *node)
|
||||
{
|
||||
GListModel *model;
|
||||
|
||||
if (node->empty)
|
||||
return 0;
|
||||
|
||||
if (node->model != NULL)
|
||||
return 0;
|
||||
|
||||
model = tree_node_create_model (self, node);
|
||||
|
||||
if (model == NULL)
|
||||
return 0;
|
||||
|
||||
g_assert (g_list_model_get_item_type (model) == g_list_model_get_item_type (self->root_node.model));
|
||||
gtk_tree_list_model_init_node (self, node, model);
|
||||
|
||||
tree_node_mark_dirty (node);
|
||||
|
||||
return tree_node_get_n_children (node);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_tree_list_model_collapse_node (GtkTreeListModel *self,
|
||||
guint position,
|
||||
TreeNode *node)
|
||||
{
|
||||
guint n_items;
|
||||
|
||||
if (node->model == NULL)
|
||||
return;
|
||||
|
||||
n_items = tree_node_get_n_children (node);
|
||||
|
||||
g_clear_pointer (&node->children, gtk_css_rb_tree_unref);
|
||||
g_clear_object (&node->model);
|
||||
|
||||
tree_node_mark_dirty (node);
|
||||
|
||||
if (n_items > 0)
|
||||
g_list_model_items_changed (G_LIST_MODEL (self), position + 1, n_items, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_row_set_expanded:
|
||||
* @self: a #GtkTreeListRow
|
||||
* @expanded: %TRUE if the row should be expanded
|
||||
*
|
||||
* Expands or collapses a row.
|
||||
*
|
||||
* If a row is expanded, the model of calling the
|
||||
* #GtkTreeListModelCreateModelFunc for the row's item will
|
||||
* be inserted after this row. If a row is collapsed, those
|
||||
* items will be removed from the model.
|
||||
*
|
||||
* If the row is not expandable, this function does nothing.
|
||||
**/
|
||||
void
|
||||
gtk_tree_list_model_set_expanded (GtkTreeListModel *self,
|
||||
guint position,
|
||||
gboolean expanded)
|
||||
gtk_tree_list_row_set_expanded (GtkTreeListRow *self,
|
||||
gboolean expanded)
|
||||
{
|
||||
TreeNode *node;
|
||||
GtkTreeListModel *list;
|
||||
gboolean was_expanded;
|
||||
guint n_items;
|
||||
|
||||
g_return_if_fail (GTK_IS_TREE_LIST_MODEL (self));
|
||||
g_return_if_fail (GTK_IS_TREE_LIST_ROW (self));
|
||||
|
||||
node = gtk_tree_list_model_get_nth (self, position);
|
||||
if (node == NULL)
|
||||
if (self->node == NULL)
|
||||
return;
|
||||
|
||||
was_expanded = self->node->children != NULL;
|
||||
if (was_expanded == expanded)
|
||||
return;
|
||||
|
||||
list = tree_node_get_tree_list_model (self->node);
|
||||
|
||||
if (expanded)
|
||||
{
|
||||
n_items = gtk_tree_list_model_expand_node (self, node);
|
||||
n_items = gtk_tree_list_model_expand_node (list, self->node);
|
||||
if (n_items > 0)
|
||||
g_list_model_items_changed (G_LIST_MODEL (self), position + 1, 0, n_items);
|
||||
g_list_model_items_changed (G_LIST_MODEL (list), tree_node_get_position (self->node) + 1, 0, n_items);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_tree_list_model_collapse_node (self, position, node);
|
||||
n_items = gtk_tree_list_model_collapse_node (list, self->node);
|
||||
if (n_items > 0)
|
||||
g_list_model_items_changed (G_LIST_MODEL (list), tree_node_get_position (self->node) + 1, n_items, 0);
|
||||
}
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), row_properties[ROW_PROP_EXPANDED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_row_get_expanded:
|
||||
* @self: a #GtkTreeListRow
|
||||
*
|
||||
* Gets if a row is currently expanded.
|
||||
*
|
||||
* Returns: %TRUE if the row is expanded
|
||||
**/
|
||||
gboolean
|
||||
gtk_tree_list_model_get_expanded (GtkTreeListModel *self,
|
||||
guint position)
|
||||
gtk_tree_list_row_get_expanded (GtkTreeListRow *self)
|
||||
{
|
||||
TreeNode *node;
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), FALSE);
|
||||
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE);
|
||||
|
||||
node = gtk_tree_list_model_get_nth (self, position);
|
||||
if (node == NULL)
|
||||
if (self->node == NULL)
|
||||
return FALSE;
|
||||
|
||||
return node->children != NULL;
|
||||
return self->node->children != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_row_is_expandable:
|
||||
* @self: a #GtkTreeListRow
|
||||
*
|
||||
* Checks if a row can be expanded. This does not mean that the
|
||||
* row is actually expanded, this can be checked with
|
||||
* gtk_tree_list_row_get_expanded()
|
||||
*
|
||||
* If a row is expandable never changes until the row is destroyed.
|
||||
*
|
||||
* Returns: %TRUE if the row is expandable
|
||||
**/
|
||||
gboolean
|
||||
gtk_tree_list_model_is_expandable (GtkTreeListModel *self,
|
||||
guint position)
|
||||
gtk_tree_list_row_is_expandable (GtkTreeListRow *self)
|
||||
{
|
||||
GtkTreeListModel *list;
|
||||
GListModel *model;
|
||||
TreeNode *node;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_MODEL (self), FALSE);
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), FALSE);
|
||||
|
||||
node = gtk_tree_list_model_get_nth (self, position);
|
||||
if (node == NULL)
|
||||
if (self->node == NULL)
|
||||
return FALSE;
|
||||
|
||||
if (node->empty)
|
||||
if (self->node->empty)
|
||||
return FALSE;
|
||||
|
||||
if (node->model)
|
||||
if (self->node->model)
|
||||
return TRUE;
|
||||
|
||||
model = tree_node_create_model (self, node);
|
||||
list = tree_node_get_tree_list_model (self->node);
|
||||
model = tree_node_create_model (list, self->node);
|
||||
if (model)
|
||||
{
|
||||
g_object_unref (model);
|
||||
@ -819,3 +1171,26 @@ gtk_tree_list_model_is_expandable (GtkTreeListModel *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_tree_list_row_get_item:
|
||||
* @self: a #GtkTreeListRow
|
||||
*
|
||||
* Gets the item corresponding to this row,
|
||||
*
|
||||
* The value returned by this function never changes until the
|
||||
* row is destroyed.
|
||||
*
|
||||
* Returns: (nullable) (type GObject) (transfer full): The item of this row
|
||||
* or %NULL when the row was destroyed
|
||||
**/
|
||||
gpointer
|
||||
gtk_tree_list_row_get_item (GtkTreeListRow *self)
|
||||
{
|
||||
g_return_val_if_fail (GTK_IS_TREE_LIST_ROW (self), NULL);
|
||||
|
||||
if (self->node == NULL)
|
||||
return NULL;
|
||||
|
||||
return tree_node_get_item (self->node);
|
||||
}
|
||||
|
||||
|
@ -32,19 +32,25 @@
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_TREE_LIST_MODEL (gtk_tree_list_model_get_type ())
|
||||
#define GTK_TYPE_TREE_LIST_ROW (gtk_tree_list_row_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
G_DECLARE_FINAL_TYPE (GtkTreeListModel, gtk_tree_list_model, GTK, TREE_LIST_MODEL, GObject)
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
G_DECLARE_FINAL_TYPE (GtkTreeListRow, gtk_tree_list_row, GTK, TREE_LIST_ROW, GObject)
|
||||
|
||||
typedef GListModel * (* GtkTreeListModelCreateModelFunc) (gpointer item, gpointer data);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkTreeListModel * gtk_tree_list_model_new (GListModel *root,
|
||||
GtkTreeListModel * gtk_tree_list_model_new (gboolean passthrough,
|
||||
GListModel *root,
|
||||
gboolean autoexpand,
|
||||
GtkTreeListModelCreateModelFunc create_func,
|
||||
gpointer data,
|
||||
GDestroyNotify data_destroy);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_tree_list_model_get_passthrough (GtkTreeListModel *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_tree_list_model_set_autoexpand (GtkTreeListModel *self,
|
||||
gboolean autoexpand);
|
||||
@ -52,18 +58,20 @@ GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_tree_list_model_get_autoexpand (GtkTreeListModel *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
guint gtk_tree_list_model_get_depth (GtkTreeListModel *self,
|
||||
GtkTreeListRow * gtk_tree_list_model_get_row (GtkTreeListModel *self,
|
||||
guint position);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_tree_list_model_set_expanded (GtkTreeListModel *self,
|
||||
guint position,
|
||||
gpointer gtk_tree_list_row_get_item (GtkTreeListRow *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
guint gtk_tree_list_row_get_depth (GtkTreeListRow *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_tree_list_row_set_expanded (GtkTreeListRow *self,
|
||||
gboolean expanded);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_tree_list_model_get_expanded (GtkTreeListModel *self,
|
||||
guint position);
|
||||
gboolean gtk_tree_list_row_get_expanded (GtkTreeListRow *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_tree_list_model_is_expandable (GtkTreeListModel *self,
|
||||
guint position);
|
||||
gboolean gtk_tree_list_row_is_expandable (GtkTreeListRow *self);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
@ -3706,10 +3706,10 @@ expander {
|
||||
|
||||
&:disabled { color: $insensitive_fg_color; }
|
||||
&:disabled:backdrop { color: $backdrop_insensitive_color; }
|
||||
|
||||
&:checked { -gtk-icon-source: -gtk-icontheme('pan-down-symbolic'); }
|
||||
}
|
||||
|
||||
title > arrow:checked, title:checked > arrow { -gtk-icon-source: -gtk-icontheme('pan-down-symbolic'); }
|
||||
|
||||
title:hover > arrow {
|
||||
color: lighten($fg_color,30%); //only lightens the arrow
|
||||
}
|
||||
|
@ -25,9 +25,6 @@ create_list_model_for_directory (gpointer file,
|
||||
if (info == NULL)
|
||||
break;
|
||||
|
||||
if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
|
||||
continue;
|
||||
|
||||
child = g_file_get_child (file, g_file_info_get_name (info));
|
||||
g_list_store_append (store, child);
|
||||
g_object_unref (child);
|
||||
@ -38,54 +35,20 @@ create_list_model_for_directory (gpointer file,
|
||||
return G_LIST_MODEL (store);
|
||||
}
|
||||
|
||||
static GtkTreeListModel *
|
||||
get_tree_list_model (GtkWidget *row)
|
||||
{
|
||||
return GTK_TREE_LIST_MODEL (g_object_get_data (G_OBJECT (gtk_widget_get_parent (row)), "model"));
|
||||
}
|
||||
|
||||
static void
|
||||
expand_clicked (GtkWidget *button,
|
||||
GtkWidget *row)
|
||||
{
|
||||
gtk_tree_list_model_set_expanded (get_tree_list_model (row),
|
||||
gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row)),
|
||||
TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
collapse_clicked (GtkWidget *button,
|
||||
GtkWidget *row)
|
||||
{
|
||||
gtk_tree_list_model_set_expanded (get_tree_list_model (row),
|
||||
gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row)),
|
||||
FALSE);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
create_widget_for_model (gpointer file,
|
||||
create_widget_for_model (gpointer item,
|
||||
gpointer root)
|
||||
{
|
||||
GtkWidget *row, *box, *child;
|
||||
GFile *iter;
|
||||
GFile *file;
|
||||
guint depth;
|
||||
|
||||
row = gtk_list_box_row_new ();
|
||||
|
||||
depth = 0;
|
||||
for (iter = g_object_ref (g_file_get_parent (file));
|
||||
!g_file_equal (root, iter);
|
||||
g_set_object (&iter, g_file_get_parent (iter)))
|
||||
{
|
||||
g_object_unref (iter);
|
||||
depth++;
|
||||
}
|
||||
g_object_unref (iter);
|
||||
g_object_unref (iter);
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_container_add (GTK_CONTAINER (row), box);
|
||||
|
||||
depth = gtk_tree_list_row_get_depth (item);
|
||||
if (depth > 0)
|
||||
{
|
||||
child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
@ -93,17 +56,31 @@ create_widget_for_model (gpointer file,
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
}
|
||||
|
||||
child = gtk_button_new_from_icon_name ("list-remove-symbolic");
|
||||
gtk_button_set_relief (GTK_BUTTON (child), GTK_RELIEF_NONE);
|
||||
g_signal_connect (child, "clicked", G_CALLBACK (collapse_clicked), row);
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
|
||||
child = gtk_button_new_from_icon_name ("list-add-symbolic");
|
||||
gtk_button_set_relief (GTK_BUTTON (child), GTK_RELIEF_NONE);
|
||||
g_signal_connect (child, "clicked", G_CALLBACK (expand_clicked), row);
|
||||
if (gtk_tree_list_row_is_expandable (item))
|
||||
{
|
||||
GtkWidget *title, *arrow;
|
||||
|
||||
child = g_object_new (GTK_TYPE_BOX, "css-name", "expander", NULL);
|
||||
|
||||
title = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "title", NULL);
|
||||
gtk_button_set_relief (GTK_BUTTON (title), GTK_RELIEF_NONE);
|
||||
g_object_bind_property (item, "expanded", title, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
g_object_set_data_full (G_OBJECT (title), "make-sure-its-not-unreffed", g_object_ref (item), g_object_unref);
|
||||
gtk_container_add (GTK_CONTAINER (child), title);
|
||||
|
||||
arrow = g_object_new (GTK_TYPE_SPINNER, "css-name", "arrow", NULL);
|
||||
gtk_container_add (GTK_CONTAINER (title), arrow);
|
||||
}
|
||||
else
|
||||
{
|
||||
child = gtk_image_new (); /* empty whatever */
|
||||
}
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
|
||||
file = gtk_tree_list_row_get_item (item);
|
||||
child = gtk_label_new (g_file_get_basename (file));
|
||||
g_object_unref (file);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (box), child);
|
||||
|
||||
return row;
|
||||
@ -132,7 +109,8 @@ main (int argc, char *argv[])
|
||||
|
||||
root = g_file_new_for_path (g_get_current_dir ());
|
||||
dirmodel = create_list_model_for_directory (root, NULL);
|
||||
model = G_LIST_MODEL (gtk_tree_list_model_new (dirmodel,
|
||||
model = G_LIST_MODEL (gtk_tree_list_model_new (FALSE,
|
||||
dirmodel,
|
||||
FALSE,
|
||||
create_list_model_for_directory,
|
||||
NULL, NULL));
|
||||
|
@ -604,6 +604,11 @@ test_type (gconstpointer data)
|
||||
g_str_equal (pspec->name, "max-content-height")))
|
||||
continue;
|
||||
|
||||
/* expanding only works if rows are expandable */
|
||||
if (g_type_is_a (type, GTK_TYPE_TREE_LIST_ROW) &&
|
||||
g_str_equal (pspec->name, "expanded"))
|
||||
continue;
|
||||
|
||||
if (g_test_verbose ())
|
||||
g_print ("Property %s.%s\n", g_type_name (pspec->owner_type), pspec->name);
|
||||
|
||||
|
@ -167,7 +167,7 @@ new_model (guint size,
|
||||
GtkTreeListModel *tree;
|
||||
GString *changes;
|
||||
|
||||
tree = gtk_tree_list_model_new (G_LIST_MODEL (new_store (size, size, size)), expanded, create_sub_model_cb, NULL, NULL);
|
||||
tree = gtk_tree_list_model_new (TRUE, G_LIST_MODEL (new_store (size, size, size)), expanded, create_sub_model_cb, NULL, NULL);
|
||||
changes = g_string_new ("");
|
||||
g_object_set_qdata_full (G_OBJECT(tree), changes_quark, changes, free_changes);
|
||||
g_signal_connect (tree, "items-changed", G_CALLBACK (items_changed), changes);
|
||||
@ -185,21 +185,27 @@ test_expand (void)
|
||||
|
||||
for (i = g_list_model_get_n_items (G_LIST_MODEL (tree)); i > 0; i--)
|
||||
{
|
||||
gtk_tree_list_model_set_expanded (tree, i - 1, TRUE);
|
||||
GtkTreeListRow *row = gtk_tree_list_model_get_row (tree, i - 1);
|
||||
gtk_tree_list_row_set_expanded (row, TRUE);
|
||||
g_object_unref (row);
|
||||
}
|
||||
assert_model (tree, "100 100 90 80 70 60 50 40 30 20 10");
|
||||
assert_changes (tree, "1+10");
|
||||
|
||||
for (i = g_list_model_get_n_items (G_LIST_MODEL (tree)); i > 0; i--)
|
||||
{
|
||||
gtk_tree_list_model_set_expanded (tree, i - 1, TRUE);
|
||||
GtkTreeListRow *row = gtk_tree_list_model_get_row (tree, i - 1);
|
||||
gtk_tree_list_row_set_expanded (row, TRUE);
|
||||
g_object_unref (row);
|
||||
}
|
||||
assert_model (tree, "100 100 100 99 98 97 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 70 70 69 68 67 66 65 64 63 62 61 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2 1");
|
||||
assert_changes (tree, "11+10, 10+10, 9+10, 8+10, 7+10, 6+10, 5+10, 4+10, 3+10, 2+10");
|
||||
|
||||
for (i = g_list_model_get_n_items (G_LIST_MODEL (tree)); i > 0; i--)
|
||||
{
|
||||
gtk_tree_list_model_set_expanded (tree, i - 1, TRUE);
|
||||
GtkTreeListRow *row = gtk_tree_list_model_get_row (tree, i - 1);
|
||||
gtk_tree_list_row_set_expanded (row, TRUE);
|
||||
g_object_unref (row);
|
||||
}
|
||||
assert_model (tree, "100 100 100 99 98 97 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 70 70 69 68 67 66 65 64 63 62 61 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2 1");
|
||||
assert_changes (tree, "");
|
||||
|
Loading…
Reference in New Issue
Block a user