/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis * * pikalist.c * Copyright (C) 2001-2016 Michael Natterer * * 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, see . */ #include "config.h" #include #include /* strcmp */ #include #include "libpikabase/pikabase.h" #include "core-types.h" #include "pika-memsize.h" #include "pikalist.h" enum { PROP_0, PROP_UNIQUE_NAMES, PROP_SORT_FUNC, PROP_APPEND }; static void pika_list_finalize (GObject *object); static void pika_list_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_list_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gint64 pika_list_get_memsize (PikaObject *object, gint64 *gui_size); static void pika_list_add (PikaContainer *container, PikaObject *object); static void pika_list_remove (PikaContainer *container, PikaObject *object); static void pika_list_reorder (PikaContainer *container, PikaObject *object, gint new_index); static void pika_list_clear (PikaContainer *container); static gboolean pika_list_have (PikaContainer *container, PikaObject *object); static void pika_list_foreach (PikaContainer *container, GFunc func, gpointer user_data); static PikaObject * pika_list_search (PikaContainer *container, PikaContainerSearchFunc func, gpointer user_data); static gboolean pika_list_get_unique_names (PikaContainer *container); static GList * pika_list_get_children_by_name (PikaContainer *container, const gchar *name); static PikaObject * pika_list_get_child_by_name (PikaContainer *container, const gchar *name); static PikaObject * pika_list_get_child_by_index (PikaContainer *container, gint index); static gint pika_list_get_child_index (PikaContainer *container, PikaObject *object); static void pika_list_uniquefy_name (PikaList *pika_list, PikaObject *object); static void pika_list_object_renamed (PikaObject *object, PikaList *list); G_DEFINE_TYPE (PikaList, pika_list, PIKA_TYPE_CONTAINER) #define parent_class pika_list_parent_class static void pika_list_class_init (PikaListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaContainerClass *container_class = PIKA_CONTAINER_CLASS (klass); object_class->finalize = pika_list_finalize; object_class->set_property = pika_list_set_property; object_class->get_property = pika_list_get_property; pika_object_class->get_memsize = pika_list_get_memsize; container_class->add = pika_list_add; container_class->remove = pika_list_remove; container_class->reorder = pika_list_reorder; container_class->clear = pika_list_clear; container_class->have = pika_list_have; container_class->foreach = pika_list_foreach; container_class->search = pika_list_search; container_class->get_unique_names = pika_list_get_unique_names; container_class->get_children_by_name = pika_list_get_children_by_name; container_class->get_child_by_name = pika_list_get_child_by_name; container_class->get_child_by_index = pika_list_get_child_by_index; container_class->get_child_index = pika_list_get_child_index; g_object_class_install_property (object_class, PROP_UNIQUE_NAMES, g_param_spec_boolean ("unique-names", NULL, NULL, FALSE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_SORT_FUNC, g_param_spec_pointer ("sort-func", NULL, NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_APPEND, g_param_spec_boolean ("append", NULL, NULL, FALSE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void pika_list_init (PikaList *list) { list->queue = g_queue_new (); list->unique_names = FALSE; list->sort_func = NULL; list->append = FALSE; } static void pika_list_finalize (GObject *object) { PikaList *list = PIKA_LIST (object); if (list->queue) { g_queue_free (list->queue); list->queue = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_list_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaList *list = PIKA_LIST (object); switch (property_id) { case PROP_UNIQUE_NAMES: list->unique_names = g_value_get_boolean (value); break; case PROP_SORT_FUNC: pika_list_set_sort_func (list, g_value_get_pointer (value)); break; case PROP_APPEND: list->append = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_list_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaList *list = PIKA_LIST (object); switch (property_id) { case PROP_UNIQUE_NAMES: g_value_set_boolean (value, list->unique_names); break; case PROP_SORT_FUNC: g_value_set_pointer (value, list->sort_func); break; case PROP_APPEND: g_value_set_boolean (value, list->append); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gint64 pika_list_get_memsize (PikaObject *object, gint64 *gui_size) { PikaList *list = PIKA_LIST (object); gint64 memsize = 0; if (pika_container_get_policy (PIKA_CONTAINER (list)) == PIKA_CONTAINER_POLICY_STRONG) { memsize += pika_g_queue_get_memsize_foreach (list->queue, (PikaMemsizeFunc) pika_object_get_memsize, gui_size); } else { memsize += pika_g_queue_get_memsize (list->queue, 0); } return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gint pika_list_sort_func (gconstpointer a, gconstpointer b, gpointer user_data) { GCompareFunc func = user_data; return func (a, b); } static void pika_list_add (PikaContainer *container, PikaObject *object) { PikaList *list = PIKA_LIST (container); if (list->unique_names) pika_list_uniquefy_name (list, object); if (list->unique_names || list->sort_func) g_signal_connect (object, "name-changed", G_CALLBACK (pika_list_object_renamed), list); if (list->sort_func) { g_queue_insert_sorted (list->queue, object, pika_list_sort_func, list->sort_func); } else if (list->append) { g_queue_push_tail (list->queue, object); } else { g_queue_push_head (list->queue, object); } PIKA_CONTAINER_CLASS (parent_class)->add (container, object); } static void pika_list_remove (PikaContainer *container, PikaObject *object) { PikaList *list = PIKA_LIST (container); if (list->unique_names || list->sort_func) g_signal_handlers_disconnect_by_func (object, pika_list_object_renamed, list); g_queue_remove (list->queue, object); PIKA_CONTAINER_CLASS (parent_class)->remove (container, object); } static void pika_list_reorder (PikaContainer *container, PikaObject *object, gint new_index) { PikaList *list = PIKA_LIST (container); g_queue_remove (list->queue, object); if (new_index == pika_container_get_n_children (container) - 1) g_queue_push_tail (list->queue, object); else g_queue_push_nth (list->queue, object, new_index); } static void pika_list_clear (PikaContainer *container) { PikaList *list = PIKA_LIST (container); while (g_queue_peek_head (list->queue)) pika_container_remove (container, g_queue_peek_head (list->queue)); } static gboolean pika_list_have (PikaContainer *container, PikaObject *object) { PikaList *list = PIKA_LIST (container); return g_queue_find (list->queue, object) ? TRUE : FALSE; } static void pika_list_foreach (PikaContainer *container, GFunc func, gpointer user_data) { PikaList *list = PIKA_LIST (container); g_queue_foreach (list->queue, func, user_data); } static PikaObject * pika_list_search (PikaContainer *container, PikaContainerSearchFunc func, gpointer user_data) { PikaList *list = PIKA_LIST (container); GList *iter; iter = list->queue->head; while (iter) { PikaObject *object = iter->data; iter = g_list_next (iter); if (func (object, user_data)) return object; } return NULL; } static gboolean pika_list_get_unique_names (PikaContainer *container) { PikaList *list = PIKA_LIST (container); return list->unique_names; } static GList * pika_list_get_children_by_name (PikaContainer *container, const gchar *name) { PikaList *list = PIKA_LIST (container); GList *children = NULL; GList *iter; for (iter = list->queue->head; iter; iter = g_list_next (iter)) { PikaObject *object = iter->data; if (! strcmp (pika_object_get_name (object), name)) { children = g_list_prepend (children, object); if (list->unique_names) return children; } } return children; } static PikaObject * pika_list_get_child_by_name (PikaContainer *container, const gchar *name) { PikaList *list = PIKA_LIST (container); GList *glist; for (glist = list->queue->head; glist; glist = g_list_next (glist)) { PikaObject *object = glist->data; if (! strcmp (pika_object_get_name (object), name)) return object; } return NULL; } static PikaObject * pika_list_get_child_by_index (PikaContainer *container, gint index) { PikaList *list = PIKA_LIST (container); return g_queue_peek_nth (list->queue, index); } static gint pika_list_get_child_index (PikaContainer *container, PikaObject *object) { PikaList *list = PIKA_LIST (container); return g_queue_index (list->queue, (gpointer) object); } /** * pika_list_new: * @children_type: the #GType of objects the list is going to hold * @unique_names: if the list should ensure that all its children * have unique names. * * Creates a new #PikaList object. Since #PikaList is a #PikaContainer * implementation, it holds PikaObjects. Thus @children_type must be * PIKA_TYPE_OBJECT or a type derived from it. * * The returned list has the #PIKA_CONTAINER_POLICY_STRONG. * * Returns: a new #PikaList object **/ PikaContainer * pika_list_new (GType children_type, gboolean unique_names) { PikaList *list; g_return_val_if_fail (g_type_is_a (children_type, PIKA_TYPE_OBJECT), NULL); list = g_object_new (PIKA_TYPE_LIST, "children-type", children_type, "policy", PIKA_CONTAINER_POLICY_STRONG, "unique-names", unique_names ? TRUE : FALSE, NULL); /* for debugging purposes only */ pika_object_set_static_name (PIKA_OBJECT (list), g_type_name (children_type)); return PIKA_CONTAINER (list); } /** * pika_list_new_weak: * @children_type: the #GType of objects the list is going to hold * @unique_names: if the list should ensure that all its children * have unique names. * * Creates a new #PikaList object. Since #PikaList is a #PikaContainer * implementation, it holds PikaObjects. Thus @children_type must be * PIKA_TYPE_OBJECT or a type derived from it. * * The returned list has the #PIKA_CONTAINER_POLICY_WEAK. * * Returns: a new #PikaList object **/ PikaContainer * pika_list_new_weak (GType children_type, gboolean unique_names) { PikaList *list; g_return_val_if_fail (g_type_is_a (children_type, PIKA_TYPE_OBJECT), NULL); list = g_object_new (PIKA_TYPE_LIST, "children-type", children_type, "policy", PIKA_CONTAINER_POLICY_WEAK, "unique-names", unique_names ? TRUE : FALSE, NULL); /* for debugging purposes only */ pika_object_set_static_name (PIKA_OBJECT (list), g_type_name (children_type)); return PIKA_CONTAINER (list); } /** * pika_list_reverse: * @list: a #PikaList * * Reverses the order of elements in a #PikaList. **/ void pika_list_reverse (PikaList *list) { g_return_if_fail (PIKA_IS_LIST (list)); if (pika_container_get_n_children (PIKA_CONTAINER (list)) > 1) { pika_container_freeze (PIKA_CONTAINER (list)); g_queue_reverse (list->queue); pika_container_thaw (PIKA_CONTAINER (list)); } } /** * pika_list_set_sort_func: * @list: a #PikaList * @sort_func: a #GCompareFunc * * Sorts the elements of @list using pika_list_sort() and remembers the * passed @sort_func in order to keep the list ordered across inserting * or renaming children. **/ void pika_list_set_sort_func (PikaList *list, GCompareFunc sort_func) { g_return_if_fail (PIKA_IS_LIST (list)); if (sort_func != list->sort_func) { if (sort_func) pika_list_sort (list, sort_func); list->sort_func = sort_func; g_object_notify (G_OBJECT (list), "sort-func"); } } /** * pika_list_get_sort_func: * @list: a #PikaList * * Returns the @list's sort function, see pika_list_set_sort_func(). * * Returns: The @list's sort function. **/ GCompareFunc pika_list_get_sort_func (PikaList *list) { g_return_val_if_fail (PIKA_IS_LIST (list), NULL); return list->sort_func; } /** * pika_list_sort: * @list: a #PikaList * @sort_func: a #GCompareFunc * * Sorts the elements of a #PikaList according to the given @sort_func. * See g_list_sort() for a detailed description of this function. **/ void pika_list_sort (PikaList *list, GCompareFunc sort_func) { g_return_if_fail (PIKA_IS_LIST (list)); g_return_if_fail (sort_func != NULL); if (pika_container_get_n_children (PIKA_CONTAINER (list)) > 1) { pika_container_freeze (PIKA_CONTAINER (list)); g_queue_sort (list->queue, pika_list_sort_func, sort_func); pika_container_thaw (PIKA_CONTAINER (list)); } } /** * pika_list_sort_by_name: * @list: a #PikaList * * Sorts the #PikaObject elements of a #PikaList by their names. **/ void pika_list_sort_by_name (PikaList *list) { g_return_if_fail (PIKA_IS_LIST (list)); pika_list_sort (list, (GCompareFunc) pika_object_name_collate); } /* private functions */ static void pika_list_uniquefy_name (PikaList *pika_list, PikaObject *object) { gchar *name = (gchar *) pika_object_get_name (object); GList *list; if (! name) return; for (list = pika_list->queue->head; list; list = g_list_next (list)) { PikaObject *object2 = list->data; const gchar *name2 = pika_object_get_name (object2); if (object != object2 && name2 && ! strcmp (name, name2)) break; } if (list) { gchar *ext; gchar *new_name = NULL; gint unique_ext = 0; name = g_strdup (name); ext = strrchr (name, '#'); if (ext) { gchar ext_str[8]; unique_ext = atoi (ext + 1); g_snprintf (ext_str, sizeof (ext_str), "%d", unique_ext); /* check if the extension really is of the form "#" */ if (! strcmp (ext_str, ext + 1)) { if (ext > name && *(ext - 1) == ' ') ext--; *ext = '\0'; } else { unique_ext = 0; } } do { unique_ext++; g_free (new_name); new_name = g_strdup_printf ("%s #%d", name, unique_ext); for (list = pika_list->queue->head; list; list = g_list_next (list)) { PikaObject *object2 = list->data; const gchar *name2 = pika_object_get_name (object2); if (object != object2 && name2 && ! strcmp (new_name, name2)) break; } } while (list); g_free (name); pika_object_take_name (object, new_name); } } static void pika_list_object_renamed (PikaObject *object, PikaList *list) { if (list->unique_names) { g_signal_handlers_block_by_func (object, pika_list_object_renamed, list); pika_list_uniquefy_name (list, object); g_signal_handlers_unblock_by_func (object, pika_list_object_renamed, list); } if (list->sort_func) { GList *glist; gint old_index; gint new_index = 0; old_index = g_queue_index (list->queue, object); for (glist = list->queue->head; glist; glist = g_list_next (glist)) { PikaObject *object2 = PIKA_OBJECT (glist->data); if (object == object2) continue; if (list->sort_func (object, object2) > 0) new_index++; else break; } if (new_index != old_index) pika_container_reorder (PIKA_CONTAINER (list), object, new_index); } }