forked from AuroraMiddleware/gtk
a546ae32d7
Those property features don't seem to be in use anywhere. They are redundant since the docs cover the same information and more. They also created unnecessary translation work. Closes #4904
616 lines
16 KiB
C
616 lines
16 KiB
C
/*
|
|
* Copyright © 2018 Benjamin Otte
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkmaplistmodel.h"
|
|
|
|
#include "gtkrbtreeprivate.h"
|
|
#include "gtkintl.h"
|
|
#include "gtkprivate.h"
|
|
|
|
/**
|
|
* GtkMapListModel:
|
|
*
|
|
* A `GtkMapListModel` maps the items in a list model to different items.
|
|
*
|
|
* `GtkMapListModel` uses a [callback@Gtk.MapListModelMapFunc].
|
|
*
|
|
* Example: Create a list of `GtkEventControllers`
|
|
* ```c
|
|
* static gpointer
|
|
* map_to_controllers (gpointer widget,
|
|
* gpointer data)
|
|
* {
|
|
* gpointer result = gtk_widget_observe_controllers (widget);
|
|
* g_object_unref (widget);
|
|
* return result;
|
|
* }
|
|
*
|
|
* widgets = gtk_widget_observe_children (widget);
|
|
*
|
|
* controllers = gtk_map_list_model_new (widgets,
|
|
* map_to_controllers,
|
|
* NULL, NULL);
|
|
*
|
|
* model = gtk_flatten_list_model_new (GTK_TYPE_EVENT_CONTROLLER,
|
|
* controllers);
|
|
* ```
|
|
*
|
|
* `GtkMapListModel` will attempt to discard the mapped objects as soon as
|
|
* they are no longer needed and recreate them if necessary.
|
|
*/
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_HAS_MAP,
|
|
PROP_MODEL,
|
|
NUM_PROPERTIES
|
|
};
|
|
|
|
typedef struct _MapNode MapNode;
|
|
typedef struct _MapAugment MapAugment;
|
|
|
|
struct _MapNode
|
|
{
|
|
guint n_items;
|
|
gpointer item; /* can only be set when n_items == 1 */
|
|
};
|
|
|
|
struct _MapAugment
|
|
{
|
|
guint n_items;
|
|
};
|
|
|
|
struct _GtkMapListModel
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GListModel *model;
|
|
GtkMapListModelMapFunc map_func;
|
|
gpointer user_data;
|
|
GDestroyNotify user_destroy;
|
|
|
|
GtkRbTree *items; /* NULL if map_func == NULL */
|
|
};
|
|
|
|
struct _GtkMapListModelClass
|
|
{
|
|
GObjectClass parent_class;
|
|
};
|
|
|
|
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
|
|
|
|
static MapNode *
|
|
gtk_map_list_model_get_nth (GtkRbTree *tree,
|
|
guint position,
|
|
guint *out_start_pos)
|
|
{
|
|
MapNode *node, *tmp;
|
|
guint start_pos = position;
|
|
|
|
node = gtk_rb_tree_get_root (tree);
|
|
|
|
while (node)
|
|
{
|
|
tmp = gtk_rb_tree_node_get_left (node);
|
|
if (tmp)
|
|
{
|
|
MapAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
|
|
if (position < aug->n_items)
|
|
{
|
|
node = tmp;
|
|
continue;
|
|
}
|
|
position -= aug->n_items;
|
|
}
|
|
|
|
if (position < node->n_items)
|
|
{
|
|
start_pos -= position;
|
|
break;
|
|
}
|
|
position -= node->n_items;
|
|
|
|
node = gtk_rb_tree_node_get_right (node);
|
|
}
|
|
|
|
if (out_start_pos)
|
|
*out_start_pos = start_pos;
|
|
|
|
return node;
|
|
}
|
|
|
|
static GType
|
|
gtk_map_list_model_get_item_type (GListModel *list)
|
|
{
|
|
return G_TYPE_OBJECT;
|
|
}
|
|
|
|
static guint
|
|
gtk_map_list_model_get_n_items (GListModel *list)
|
|
{
|
|
GtkMapListModel *self = GTK_MAP_LIST_MODEL (list);
|
|
|
|
if (self->model == NULL)
|
|
return 0;
|
|
|
|
return g_list_model_get_n_items (self->model);
|
|
}
|
|
|
|
static gpointer
|
|
gtk_map_list_model_get_item (GListModel *list,
|
|
guint position)
|
|
{
|
|
GtkMapListModel *self = GTK_MAP_LIST_MODEL (list);
|
|
MapNode *node;
|
|
guint offset;
|
|
|
|
if (self->model == NULL)
|
|
return NULL;
|
|
|
|
if (self->items == NULL)
|
|
return g_list_model_get_item (self->model, position);
|
|
|
|
node = gtk_map_list_model_get_nth (self->items, position, &offset);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
if (node->item)
|
|
return g_object_ref (node->item);
|
|
|
|
if (offset != position)
|
|
{
|
|
MapNode *before = gtk_rb_tree_insert_before (self->items, node);
|
|
before->n_items = position - offset;
|
|
node->n_items -= before->n_items;
|
|
gtk_rb_tree_node_mark_dirty (node);
|
|
}
|
|
|
|
if (node->n_items > 1)
|
|
{
|
|
MapNode *after = gtk_rb_tree_insert_after (self->items, node);
|
|
after->n_items = node->n_items - 1;
|
|
node->n_items = 1;
|
|
gtk_rb_tree_node_mark_dirty (node);
|
|
}
|
|
|
|
node->item = self->map_func (g_list_model_get_item (self->model, position), self->user_data);
|
|
g_object_add_weak_pointer (node->item, &node->item);
|
|
|
|
return node->item;
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_model_init (GListModelInterface *iface)
|
|
{
|
|
iface->get_item_type = gtk_map_list_model_get_item_type;
|
|
iface->get_n_items = gtk_map_list_model_get_n_items;
|
|
iface->get_item = gtk_map_list_model_get_item;
|
|
}
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkMapListModel, gtk_map_list_model, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_map_list_model_model_init))
|
|
|
|
static void
|
|
gtk_map_list_model_items_changed_cb (GListModel *model,
|
|
guint position,
|
|
guint removed,
|
|
guint added,
|
|
GtkMapListModel *self)
|
|
{
|
|
MapNode *node;
|
|
guint start, end;
|
|
|
|
if (self->items == NULL)
|
|
{
|
|
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
|
|
return;
|
|
}
|
|
|
|
node = gtk_map_list_model_get_nth (self->items, position, &start);
|
|
g_assert (start <= position);
|
|
|
|
while (removed > 0)
|
|
{
|
|
end = start + node->n_items;
|
|
if (start == position && end <= position + removed)
|
|
{
|
|
MapNode *next = gtk_rb_tree_node_get_next (node);
|
|
removed -= node->n_items;
|
|
gtk_rb_tree_remove (self->items, node);
|
|
node = next;
|
|
}
|
|
else
|
|
{
|
|
if (end >= position + removed)
|
|
{
|
|
node->n_items -= removed;
|
|
removed = 0;
|
|
gtk_rb_tree_node_mark_dirty (node);
|
|
}
|
|
else if (start < position)
|
|
{
|
|
guint overlap = node->n_items - (position - start);
|
|
node->n_items -= overlap;
|
|
gtk_rb_tree_node_mark_dirty (node);
|
|
removed -= overlap;
|
|
start = position;
|
|
node = gtk_rb_tree_node_get_next (node);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (added)
|
|
{
|
|
if (node == NULL)
|
|
node = gtk_rb_tree_insert_before (self->items, NULL);
|
|
else if (node->item)
|
|
node = gtk_rb_tree_insert_after (self->items, node);
|
|
|
|
node->n_items += added;
|
|
gtk_rb_tree_node_mark_dirty (node);
|
|
}
|
|
|
|
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkMapListModel *self = GTK_MAP_LIST_MODEL (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_MODEL:
|
|
gtk_map_list_model_set_model (self, g_value_get_object (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkMapListModel *self = GTK_MAP_LIST_MODEL (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_HAS_MAP:
|
|
g_value_set_boolean (value, self->items != NULL);
|
|
break;
|
|
|
|
case PROP_MODEL:
|
|
g_value_set_object (value, self->model);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_clear_model (GtkMapListModel *self)
|
|
{
|
|
if (self->model == NULL)
|
|
return;
|
|
|
|
g_signal_handlers_disconnect_by_func (self->model, gtk_map_list_model_items_changed_cb, self);
|
|
g_clear_object (&self->model);
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_dispose (GObject *object)
|
|
{
|
|
GtkMapListModel *self = GTK_MAP_LIST_MODEL (object);
|
|
|
|
gtk_map_list_model_clear_model (self);
|
|
if (self->user_destroy)
|
|
self->user_destroy (self->user_data);
|
|
self->map_func = NULL;
|
|
self->user_data = NULL;
|
|
self->user_destroy = NULL;
|
|
g_clear_pointer (&self->items, gtk_rb_tree_unref);
|
|
|
|
G_OBJECT_CLASS (gtk_map_list_model_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_class_init (GtkMapListModelClass *class)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
|
|
|
|
gobject_class->set_property = gtk_map_list_model_set_property;
|
|
gobject_class->get_property = gtk_map_list_model_get_property;
|
|
gobject_class->dispose = gtk_map_list_model_dispose;
|
|
|
|
/**
|
|
* GtkMapListModel:has-map: (attributes org.gtk.Property.get=gtk_map_list_model_has_map)
|
|
*
|
|
* If a map is set for this model
|
|
*/
|
|
properties[PROP_HAS_MAP] =
|
|
g_param_spec_boolean ("has-map", NULL, NULL,
|
|
FALSE,
|
|
GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkMapListModel:model: (attributes org.gtk.Property.get=gtk_map_list_model_get_model org.gtk.Property.set=gtk_map_list_model_set_model)
|
|
*
|
|
* The model being mapped.
|
|
*/
|
|
properties[PROP_MODEL] =
|
|
g_param_spec_object ("model", NULL, NULL,
|
|
G_TYPE_LIST_MODEL,
|
|
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_init (GtkMapListModel *self)
|
|
{
|
|
}
|
|
|
|
|
|
static void
|
|
gtk_map_list_model_augment (GtkRbTree *map,
|
|
gpointer _aug,
|
|
gpointer _node,
|
|
gpointer left,
|
|
gpointer right)
|
|
{
|
|
MapNode *node = _node;
|
|
MapAugment *aug = _aug;
|
|
|
|
aug->n_items = node->n_items;
|
|
|
|
if (left)
|
|
{
|
|
MapAugment *left_aug = gtk_rb_tree_get_augment (map, left);
|
|
aug->n_items += left_aug->n_items;
|
|
}
|
|
if (right)
|
|
{
|
|
MapAugment *right_aug = gtk_rb_tree_get_augment (map, right);
|
|
aug->n_items += right_aug->n_items;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gtk_map_list_model_new:
|
|
* @model: (transfer full) (nullable): The model to map
|
|
* @map_func: (nullable): map function
|
|
* @user_data: (closure): user data passed to @map_func
|
|
* @user_destroy: destroy notifier for @user_data
|
|
*
|
|
* Creates a new `GtkMapListModel` for the given arguments.
|
|
*
|
|
* Returns: a new `GtkMapListModel`
|
|
*/
|
|
GtkMapListModel *
|
|
gtk_map_list_model_new (GListModel *model,
|
|
GtkMapListModelMapFunc map_func,
|
|
gpointer user_data,
|
|
GDestroyNotify user_destroy)
|
|
{
|
|
GtkMapListModel *result;
|
|
|
|
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
|
|
|
|
result = g_object_new (GTK_TYPE_MAP_LIST_MODEL,
|
|
"model", model,
|
|
NULL);
|
|
|
|
/* consume the reference */
|
|
g_clear_object (&model);
|
|
|
|
if (map_func)
|
|
gtk_map_list_model_set_map_func (result, map_func, user_data, user_destroy);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_clear_node (gpointer _node)
|
|
{
|
|
MapNode *node = _node;
|
|
|
|
if (node->item)
|
|
g_object_remove_weak_pointer (node->item, &node->item);
|
|
}
|
|
|
|
static void
|
|
gtk_map_list_model_init_items (GtkMapListModel *self)
|
|
{
|
|
if (self->map_func && self->model)
|
|
{
|
|
guint n_items;
|
|
|
|
if (self->items)
|
|
{
|
|
gtk_rb_tree_remove_all (self->items);
|
|
}
|
|
else
|
|
{
|
|
self->items = gtk_rb_tree_new (MapNode,
|
|
MapAugment,
|
|
gtk_map_list_model_augment,
|
|
gtk_map_list_model_clear_node,
|
|
NULL);
|
|
}
|
|
|
|
n_items = g_list_model_get_n_items (self->model);
|
|
if (n_items)
|
|
{
|
|
MapNode *node = gtk_rb_tree_insert_before (self->items, NULL);
|
|
node->n_items = g_list_model_get_n_items (self->model);
|
|
gtk_rb_tree_node_mark_dirty (node);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_clear_pointer (&self->items, gtk_rb_tree_unref);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gtk_map_list_model_set_map_func:
|
|
* @self: a `GtkMapListModel`
|
|
* @map_func: (nullable): map function
|
|
* @user_data: (closure): user data passed to @map_func
|
|
* @user_destroy: destroy notifier for @user_data
|
|
*
|
|
* Sets the function used to map items.
|
|
*
|
|
* The function will be called whenever an item needs to be mapped
|
|
* and must return the item to use for the given input item.
|
|
*
|
|
* Note that `GtkMapListModel` may call this function multiple times
|
|
* on the same item, because it may delete items it doesn't need anymore.
|
|
*
|
|
* GTK makes no effort to ensure that @map_func conforms to the item type
|
|
* of @self. It assumes that the caller knows what they are doing and the map
|
|
* function returns items of the appropriate type.
|
|
*/
|
|
void
|
|
gtk_map_list_model_set_map_func (GtkMapListModel *self,
|
|
GtkMapListModelMapFunc map_func,
|
|
gpointer user_data,
|
|
GDestroyNotify user_destroy)
|
|
{
|
|
gboolean was_maped, will_be_maped;
|
|
guint n_items;
|
|
|
|
g_return_if_fail (GTK_IS_MAP_LIST_MODEL (self));
|
|
g_return_if_fail (map_func != NULL || (user_data == NULL && !user_destroy));
|
|
|
|
was_maped = self->map_func != NULL;
|
|
will_be_maped = map_func != NULL;
|
|
|
|
if (!was_maped && !will_be_maped)
|
|
return;
|
|
|
|
if (self->user_destroy)
|
|
self->user_destroy (self->user_data);
|
|
|
|
self->map_func = map_func;
|
|
self->user_data = user_data;
|
|
self->user_destroy = user_destroy;
|
|
|
|
gtk_map_list_model_init_items (self);
|
|
|
|
if (self->model)
|
|
n_items = g_list_model_get_n_items (self->model);
|
|
else
|
|
n_items = 0;
|
|
if (n_items)
|
|
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
|
|
|
|
if (was_maped != will_be_maped)
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_MAP]);
|
|
}
|
|
|
|
/**
|
|
* gtk_map_list_model_set_model: (attributes org.gtk.Method.set_property=model)
|
|
* @self: a `GtkMapListModel`
|
|
* @model: (nullable): The model to be mapped
|
|
*
|
|
* Sets the model to be mapped.
|
|
*
|
|
* GTK makes no effort to ensure that @model conforms to the item type
|
|
* expected by the map function. It assumes that the caller knows what
|
|
* they are doing and have set up an appropriate map function.
|
|
*/
|
|
void
|
|
gtk_map_list_model_set_model (GtkMapListModel *self,
|
|
GListModel *model)
|
|
{
|
|
guint removed, added;
|
|
|
|
g_return_if_fail (GTK_IS_MAP_LIST_MODEL (self));
|
|
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
|
|
|
|
if (self->model == model)
|
|
return;
|
|
|
|
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
|
|
gtk_map_list_model_clear_model (self);
|
|
|
|
if (model)
|
|
{
|
|
self->model = g_object_ref (model);
|
|
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_map_list_model_items_changed_cb), self);
|
|
added = g_list_model_get_n_items (model);
|
|
}
|
|
else
|
|
{
|
|
added = 0;
|
|
}
|
|
|
|
gtk_map_list_model_init_items (self);
|
|
|
|
if (removed > 0 || added > 0)
|
|
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
|
|
}
|
|
|
|
/**
|
|
* gtk_map_list_model_get_model: (attributes org.gtk.Method.get_property=model)
|
|
* @self: a `GtkMapListModel`
|
|
*
|
|
* Gets the model that is currently being mapped or %NULL if none.
|
|
*
|
|
* Returns: (nullable) (transfer none): The model that gets mapped
|
|
*/
|
|
GListModel *
|
|
gtk_map_list_model_get_model (GtkMapListModel *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_MAP_LIST_MODEL (self), NULL);
|
|
|
|
return self->model;
|
|
}
|
|
|
|
/**
|
|
* gtk_map_list_model_has_map: (attributes org.gtk.Method.get_property=has-map)
|
|
* @self: a `GtkMapListModel`
|
|
*
|
|
* Checks if a map function is currently set on @self.
|
|
*
|
|
* Returns: %TRUE if a map function is set
|
|
*/
|
|
gboolean
|
|
gtk_map_list_model_has_map (GtkMapListModel *self)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_MAP_LIST_MODEL (self), FALSE);
|
|
|
|
return self->map_func != NULL;
|
|
}
|