2018-09-20 03:18:34 +00:00
|
|
|
/*
|
|
|
|
* 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 "gtklistitemmanagerprivate.h"
|
|
|
|
|
2018-09-28 01:33:16 +00:00
|
|
|
#include "gtkwidgetprivate.h"
|
|
|
|
|
2018-09-20 03:18:34 +00:00
|
|
|
struct _GtkListItemManager
|
|
|
|
{
|
|
|
|
GObject parent_instance;
|
|
|
|
|
|
|
|
GtkWidget *widget;
|
|
|
|
GListModel *model;
|
|
|
|
GtkListItemFactory *factory;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _GtkListItemManagerClass
|
|
|
|
{
|
|
|
|
GObjectClass parent_class;
|
|
|
|
};
|
|
|
|
|
2018-09-22 20:11:27 +00:00
|
|
|
struct _GtkListItemManagerChange
|
|
|
|
{
|
|
|
|
GHashTable *items;
|
|
|
|
};
|
|
|
|
|
2018-09-20 03:18:34 +00:00
|
|
|
G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT)
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_manager_dispose (GObject *object)
|
|
|
|
{
|
|
|
|
GtkListItemManager *self = GTK_LIST_ITEM_MANAGER (object);
|
|
|
|
|
|
|
|
g_clear_object (&self->model);
|
|
|
|
g_clear_object (&self->factory);
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (gtk_list_item_manager_parent_class)->dispose (object);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_manager_class_init (GtkListItemManagerClass *klass)
|
|
|
|
{
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
|
|
|
|
object_class->dispose = gtk_list_item_manager_dispose;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_list_item_manager_init (GtkListItemManager *self)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
GtkListItemManager *
|
|
|
|
gtk_list_item_manager_new (GtkWidget *widget)
|
|
|
|
{
|
|
|
|
GtkListItemManager *self;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
|
|
|
|
|
|
|
|
self = g_object_new (GTK_TYPE_LIST_ITEM_MANAGER, NULL);
|
|
|
|
|
|
|
|
self->widget = widget;
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gtk_list_item_manager_set_factory (GtkListItemManager *self,
|
|
|
|
GtkListItemFactory *factory)
|
|
|
|
{
|
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
|
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory));
|
|
|
|
|
|
|
|
if (self->factory == factory)
|
|
|
|
return;
|
|
|
|
|
|
|
|
g_clear_object (&self->factory);
|
|
|
|
|
|
|
|
self->factory = g_object_ref (factory);
|
|
|
|
}
|
|
|
|
|
|
|
|
GtkListItemFactory *
|
|
|
|
gtk_list_item_manager_get_factory (GtkListItemManager *self)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
|
|
|
|
|
|
|
|
return self->factory;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gtk_list_item_manager_set_model (GtkListItemManager *self,
|
|
|
|
GListModel *model)
|
|
|
|
{
|
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
|
|
|
|
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
|
|
|
|
|
|
|
|
if (self->model == model)
|
|
|
|
return;
|
|
|
|
|
|
|
|
g_clear_object (&self->model);
|
|
|
|
|
|
|
|
if (model)
|
|
|
|
self->model = g_object_ref (model);
|
|
|
|
}
|
|
|
|
|
|
|
|
GListModel *
|
|
|
|
gtk_list_item_manager_get_model (GtkListItemManager *self)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
|
|
|
|
|
|
|
|
return self->model;
|
|
|
|
}
|
|
|
|
|
2018-09-22 20:11:27 +00:00
|
|
|
#if 0
|
2018-09-20 03:18:34 +00:00
|
|
|
/*
|
2018-09-22 20:11:27 +00:00
|
|
|
* gtk_list_item_manager_get_size:
|
2018-09-20 03:18:34 +00:00
|
|
|
* @self: a #GtkListItemManager
|
2018-09-22 20:11:27 +00:00
|
|
|
*
|
|
|
|
* Queries the number of widgets currently handled by @self.
|
|
|
|
*
|
|
|
|
* This includes both widgets that have been acquired and
|
|
|
|
* those currently waiting to be used again.
|
|
|
|
*
|
|
|
|
* Returns: Number of widgets handled by @self
|
|
|
|
**/
|
|
|
|
guint
|
|
|
|
gtk_list_item_manager_get_size (GtkListItemManager *self)
|
|
|
|
{
|
|
|
|
return g_hash_table_size (self->pool);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* gtk_list_item_manager_begin_change:
|
|
|
|
* @self: a #GtkListItemManager
|
|
|
|
*
|
|
|
|
* Begins a change operation in response to a model's items-changed
|
|
|
|
* signal.
|
|
|
|
* During an ongoing change operation, list items will not be discarded
|
|
|
|
* when released but will be kept around in anticipation of them being
|
|
|
|
* added back in a different posiion later.
|
|
|
|
*
|
|
|
|
* Once it is known that no more list items will be reused,
|
|
|
|
* gtk_list_item_manager_end_change() should be called. This should happen
|
|
|
|
* as early as possible, so the list items held for the change can be
|
|
|
|
* reqcquired.
|
|
|
|
*
|
|
|
|
* Returns: The object to use for this change
|
|
|
|
**/
|
|
|
|
GtkListItemManagerChange *
|
|
|
|
gtk_list_item_manager_begin_change (GtkListItemManager *self)
|
|
|
|
{
|
|
|
|
GtkListItemManagerChange *change;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
|
|
|
|
|
|
|
|
change = g_slice_new (GtkListItemManagerChange);
|
|
|
|
change->items = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
|
|
|
|
return change;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* gtk_list_item_manager_end_change:
|
|
|
|
* @self: a #GtkListItemManager
|
|
|
|
* @change: a change
|
|
|
|
*
|
|
|
|
* Ends a change operation begun with gtk_list_item_manager_begin_change()
|
|
|
|
* and releases all list items still cached.
|
2018-09-20 03:18:34 +00:00
|
|
|
**/
|
|
|
|
void
|
2018-09-22 20:11:27 +00:00
|
|
|
gtk_list_item_manager_end_change (GtkListItemManager *self,
|
|
|
|
GtkListItemManagerChange *change)
|
2018-09-20 03:18:34 +00:00
|
|
|
{
|
2018-09-22 20:11:27 +00:00
|
|
|
GHashTableIter iter;
|
|
|
|
gpointer list_item;
|
|
|
|
|
2018-09-20 03:18:34 +00:00
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
|
2018-09-22 20:11:27 +00:00
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
|
|
|
|
|
|
|
|
g_hash_table_iter_init (&iter, change->items);
|
|
|
|
while (g_hash_table_iter_next (&iter, NULL, &list_item))
|
|
|
|
{
|
|
|
|
gtk_list_item_manager_release_list_item (self, NULL, list_item);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_hash_table_unref (change->items);
|
|
|
|
g_slice_free (GtkListItemManagerChange, change);
|
2018-09-20 03:18:34 +00:00
|
|
|
}
|
|
|
|
|
2018-09-24 22:16:27 +00:00
|
|
|
/*
|
|
|
|
* gtk_list_item_manager_change_contains:
|
|
|
|
* @change: a #GtkListItemManagerChange
|
|
|
|
* @list_item: The item that may have been released into this change set
|
|
|
|
*
|
|
|
|
* Checks if @list_item has been released as part of @change but not been
|
|
|
|
* reacquired yet.
|
|
|
|
*
|
|
|
|
* This is useful to test before calling gtk_list_item_manager_end_change()
|
|
|
|
* if special actions need to be performed when important list items - like
|
|
|
|
* the focused item - are about to be deleted.
|
|
|
|
*
|
|
|
|
* Returns: %TRUE if the item is part of this change
|
|
|
|
**/
|
|
|
|
gboolean
|
|
|
|
gtk_list_item_manager_change_contains (GtkListItemManagerChange *change,
|
|
|
|
GtkWidget *list_item)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (change != NULL, FALSE);
|
|
|
|
g_return_val_if_fail (GTK_IS_LIST_ITEM (list_item), FALSE);
|
|
|
|
|
|
|
|
return g_hash_table_lookup (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (list_item))) == list_item;
|
|
|
|
}
|
|
|
|
|
2018-09-20 03:18:34 +00:00
|
|
|
/*
|
2018-09-21 03:32:00 +00:00
|
|
|
* gtk_list_item_manager_acquire_list_item:
|
2018-09-20 03:18:34 +00:00
|
|
|
* @self: a #GtkListItemManager
|
|
|
|
* @position: the row in the model to create a list item for
|
2018-09-28 00:05:46 +00:00
|
|
|
* @prev_sibling: the widget this widget should be inserted before or %NULL
|
|
|
|
* if it should be the first widget
|
2018-09-20 03:18:34 +00:00
|
|
|
*
|
2018-09-21 03:32:00 +00:00
|
|
|
* Creates a list item widget to use for @position. No widget may
|
2018-09-20 03:18:34 +00:00
|
|
|
* yet exist that is used for @position.
|
|
|
|
*
|
2018-09-21 03:32:00 +00:00
|
|
|
* When the returned item is no longer needed, the caller is responsible
|
|
|
|
* for calling gtk_list_item_manager_release_list_item().
|
|
|
|
* A particular case is when the row at @position is removed. In that case,
|
|
|
|
* all list items in the removed range must be released before
|
|
|
|
* gtk_list_item_manager_model_changed() is called.
|
|
|
|
*
|
2018-09-20 03:18:34 +00:00
|
|
|
* Returns: a properly setup widget to use in @position
|
|
|
|
**/
|
|
|
|
GtkWidget *
|
2018-09-21 03:32:00 +00:00
|
|
|
gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
|
|
|
|
guint position,
|
2018-09-28 00:05:46 +00:00
|
|
|
GtkWidget *prev_sibling)
|
2018-09-20 03:18:34 +00:00
|
|
|
{
|
2018-09-21 03:05:34 +00:00
|
|
|
GtkListItem *result;
|
2018-09-20 03:18:34 +00:00
|
|
|
gpointer item;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
|
2018-09-28 00:05:46 +00:00
|
|
|
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
|
2018-09-20 03:18:34 +00:00
|
|
|
|
|
|
|
result = gtk_list_item_factory_create (self->factory);
|
2018-09-22 20:11:27 +00:00
|
|
|
|
2018-09-20 03:18:34 +00:00
|
|
|
item = g_list_model_get_item (self->model, position);
|
2018-09-29 20:34:43 +00:00
|
|
|
gtk_list_item_factory_bind (self->factory, result, position, item, FALSE);
|
2018-09-20 03:18:34 +00:00
|
|
|
g_object_unref (item);
|
2018-09-28 00:05:46 +00:00
|
|
|
gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
|
2018-09-20 03:18:34 +00:00
|
|
|
|
2018-09-21 03:05:34 +00:00
|
|
|
return GTK_WIDGET (result);
|
2018-09-20 03:18:34 +00:00
|
|
|
}
|
2018-09-21 03:32:00 +00:00
|
|
|
|
2018-09-22 20:11:27 +00:00
|
|
|
/**
|
|
|
|
* gtk_list_item_manager_try_acquire_list_item_from_change:
|
|
|
|
* @self: a #GtkListItemManager
|
|
|
|
* @position: the row in the model to create a list item for
|
2018-09-28 00:05:46 +00:00
|
|
|
* @prev_sibling: the widget this widget should be inserted after or %NULL
|
|
|
|
* if it should be the first widget
|
2018-09-22 20:11:27 +00:00
|
|
|
*
|
|
|
|
* Like gtk_list_item_manager_acquire_list_item(), but only tries to acquire list
|
|
|
|
* items from those previously released as part of @change.
|
|
|
|
* If no matching list item is found, %NULL is returned and the caller should use
|
|
|
|
* gtk_list_item_manager_acquire_list_item().
|
|
|
|
*
|
|
|
|
* Returns: (nullable): a properly setup widget to use in @position or %NULL if
|
|
|
|
* no item for reuse existed
|
|
|
|
**/
|
|
|
|
GtkWidget *
|
|
|
|
gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
|
|
|
|
GtkListItemManagerChange *change,
|
|
|
|
guint position,
|
2018-09-28 00:05:46 +00:00
|
|
|
GtkWidget *prev_sibling)
|
2018-09-22 20:11:27 +00:00
|
|
|
{
|
|
|
|
GtkListItem *result;
|
|
|
|
gpointer item;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
|
2018-09-28 00:05:46 +00:00
|
|
|
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
|
2018-09-22 20:11:27 +00:00
|
|
|
|
|
|
|
/* XXX: can we avoid temporarily allocating items on failure? */
|
|
|
|
item = g_list_model_get_item (self->model, position);
|
|
|
|
if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result))
|
|
|
|
{
|
2018-09-29 20:34:43 +00:00
|
|
|
gtk_list_item_factory_update (self->factory, result, position, FALSE);
|
2018-09-28 00:05:46 +00:00
|
|
|
gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
|
2018-09-22 20:11:27 +00:00
|
|
|
/* XXX: Should we let the listview do this? */
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (result));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = NULL;
|
|
|
|
}
|
|
|
|
g_object_unref (item);
|
|
|
|
|
|
|
|
return GTK_WIDGET (result);
|
|
|
|
}
|
|
|
|
|
2018-09-28 01:33:16 +00:00
|
|
|
/**
|
|
|
|
* gtk_list_item_manager_move_list_item:
|
|
|
|
* @self: a #GtkListItemManager
|
|
|
|
* @list_item: an acquired #GtkListItem that should be moved to represent
|
|
|
|
* a different row
|
|
|
|
* @position: the new position of that list item
|
|
|
|
* @prev_sibling: the new previous sibling
|
|
|
|
*
|
|
|
|
* Moves the widget to represent a new position in the listmodel without
|
|
|
|
* releasing the item.
|
|
|
|
*
|
|
|
|
* This is most useful when scrolling.
|
|
|
|
**/
|
|
|
|
void
|
|
|
|
gtk_list_item_manager_move_list_item (GtkListItemManager *self,
|
|
|
|
GtkWidget *list_item,
|
|
|
|
guint position,
|
|
|
|
GtkWidget *prev_sibling)
|
|
|
|
{
|
|
|
|
gpointer item;
|
|
|
|
|
|
|
|
item = g_list_model_get_item (self->model, position);
|
2018-09-29 20:34:43 +00:00
|
|
|
gtk_list_item_factory_bind (self->factory, GTK_LIST_ITEM (list_item), position, item, FALSE);
|
2018-09-28 01:33:16 +00:00
|
|
|
gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling);
|
|
|
|
g_object_unref (item);
|
|
|
|
}
|
|
|
|
|
2018-09-24 02:01:37 +00:00
|
|
|
/**
|
|
|
|
* gtk_list_item_manager_update_list_item:
|
|
|
|
* @self: a #GtkListItemManager
|
|
|
|
* @item: a #GtkListItem that has been acquired
|
|
|
|
* @position: the new position of that list item
|
|
|
|
*
|
|
|
|
* Updates the position of the given @item. This function must be called whenever
|
|
|
|
* the position of an item changes, like when new items are added before it.
|
|
|
|
**/
|
|
|
|
void
|
|
|
|
gtk_list_item_manager_update_list_item (GtkListItemManager *self,
|
|
|
|
GtkWidget *item,
|
|
|
|
guint position)
|
|
|
|
{
|
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
|
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM (item));
|
|
|
|
|
2018-09-29 20:34:43 +00:00
|
|
|
gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, FALSE);
|
2018-09-24 02:01:37 +00:00
|
|
|
}
|
|
|
|
|
2018-09-21 03:32:00 +00:00
|
|
|
/*
|
|
|
|
* gtk_list_item_manager_release_list_item:
|
|
|
|
* @self: a #GtkListItemManager
|
2018-09-22 20:11:27 +00:00
|
|
|
* @change: (allow-none): The change associated with this release or
|
|
|
|
* %NULL if this is a final removal
|
2018-09-21 03:32:00 +00:00
|
|
|
* @item: an item previously acquired with
|
|
|
|
* gtk_list_item_manager_acquire_list_item()
|
|
|
|
*
|
|
|
|
* Releases an item that was previously acquired via
|
|
|
|
* gtk_list_item_manager_acquire_list_item() and is no longer in use.
|
|
|
|
**/
|
|
|
|
void
|
2018-09-22 20:11:27 +00:00
|
|
|
gtk_list_item_manager_release_list_item (GtkListItemManager *self,
|
|
|
|
GtkListItemManagerChange *change,
|
|
|
|
GtkWidget *item)
|
2018-09-21 03:32:00 +00:00
|
|
|
{
|
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
|
|
|
|
g_return_if_fail (GTK_IS_LIST_ITEM (item));
|
|
|
|
|
2018-09-22 20:11:27 +00:00
|
|
|
if (change != NULL)
|
|
|
|
{
|
|
|
|
if (g_hash_table_insert (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item))
|
|
|
|
return;
|
|
|
|
|
|
|
|
g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are.");
|
|
|
|
}
|
|
|
|
|
|
|
|
gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item));
|
2018-09-21 03:32:00 +00:00
|
|
|
gtk_widget_unparent (item);
|
|
|
|
}
|