/* 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 * * pikataggedcontainer.c * Copyright (C) 2008 Aurimas Juška * * 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 "core-types.h" #include "pika.h" #include "pikatag.h" #include "pikatagged.h" #include "pikataggedcontainer.h" enum { TAG_COUNT_CHANGED, LAST_SIGNAL }; static void pika_tagged_container_dispose (GObject *object); static gint64 pika_tagged_container_get_memsize (PikaObject *object, gint64 *gui_size); static void pika_tagged_container_clear (PikaContainer *container); static void pika_tagged_container_src_add (PikaFilteredContainer *filtered_container, PikaObject *object); static void pika_tagged_container_src_remove (PikaFilteredContainer *filtered_container, PikaObject *object); static void pika_tagged_container_src_freeze (PikaFilteredContainer *filtered_container); static void pika_tagged_container_src_thaw (PikaFilteredContainer *filtered_container); static gboolean pika_tagged_container_object_matches (PikaTaggedContainer *tagged_container, PikaObject *object); static void pika_tagged_container_tag_added (PikaTagged *tagged, PikaTag *tag, PikaTaggedContainer *tagged_container); static void pika_tagged_container_tag_removed (PikaTagged *tagged, PikaTag *tag, PikaTaggedContainer *tagged_container); static void pika_tagged_container_ref_tag (PikaTaggedContainer *tagged_container, PikaTag *tag); static void pika_tagged_container_unref_tag (PikaTaggedContainer *tagged_container, PikaTag *tag); static void pika_tagged_container_tag_count_changed (PikaTaggedContainer *tagged_container, gint tag_count); G_DEFINE_TYPE (PikaTaggedContainer, pika_tagged_container, PIKA_TYPE_FILTERED_CONTAINER) #define parent_class pika_tagged_container_parent_class static guint pika_tagged_container_signals[LAST_SIGNAL] = { 0, }; static void pika_tagged_container_class_init (PikaTaggedContainerClass *klass) { GObjectClass *g_object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaContainerClass *container_class = PIKA_CONTAINER_CLASS (klass); PikaFilteredContainerClass *filtered_class = PIKA_FILTERED_CONTAINER_CLASS (klass); g_object_class->dispose = pika_tagged_container_dispose; pika_object_class->get_memsize = pika_tagged_container_get_memsize; container_class->clear = pika_tagged_container_clear; filtered_class->src_add = pika_tagged_container_src_add; filtered_class->src_remove = pika_tagged_container_src_remove; filtered_class->src_freeze = pika_tagged_container_src_freeze; filtered_class->src_thaw = pika_tagged_container_src_thaw; klass->tag_count_changed = pika_tagged_container_tag_count_changed; pika_tagged_container_signals[TAG_COUNT_CHANGED] = g_signal_new ("tag-count-changed", PIKA_TYPE_TAGGED_CONTAINER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PikaTaggedContainerClass, tag_count_changed), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); } static void pika_tagged_container_init (PikaTaggedContainer *tagged_container) { tagged_container->tag_ref_counts = g_hash_table_new_full ((GHashFunc) pika_tag_get_hash, (GEqualFunc) pika_tag_equals, (GDestroyNotify) g_object_unref, (GDestroyNotify) NULL); } static void pika_tagged_container_dispose (GObject *object) { PikaTaggedContainer *tagged_container = PIKA_TAGGED_CONTAINER (object); if (tagged_container->filter) { g_list_free_full (tagged_container->filter, (GDestroyNotify) pika_tag_or_null_unref); tagged_container->filter = NULL; } g_clear_pointer (&tagged_container->tag_ref_counts, g_hash_table_unref); G_OBJECT_CLASS (parent_class)->dispose (object); } static gint64 pika_tagged_container_get_memsize (PikaObject *object, gint64 *gui_size) { gint64 memsize = 0; /* FIXME take members into account */ return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static void pika_tagged_container_clear (PikaContainer *container) { PikaFilteredContainer *filtered_container = PIKA_FILTERED_CONTAINER (container); PikaTaggedContainer *tagged_container = PIKA_TAGGED_CONTAINER (container); GList *list; for (list = PIKA_LIST (filtered_container->src_container)->queue->head; list; list = g_list_next (list)) { g_signal_handlers_disconnect_by_func (list->data, pika_tagged_container_tag_added, tagged_container); g_signal_handlers_disconnect_by_func (list->data, pika_tagged_container_tag_removed, tagged_container); } if (tagged_container->tag_ref_counts) { g_hash_table_remove_all (tagged_container->tag_ref_counts); tagged_container->tag_count = 0; } PIKA_CONTAINER_CLASS (parent_class)->clear (container); } static void pika_tagged_container_src_add (PikaFilteredContainer *filtered_container, PikaObject *object) { PikaTaggedContainer *tagged_container = PIKA_TAGGED_CONTAINER (filtered_container); GList *list; for (list = pika_tagged_get_tags (PIKA_TAGGED (object)); list; list = g_list_next (list)) { pika_tagged_container_ref_tag (tagged_container, list->data); } g_signal_connect (object, "tag-added", G_CALLBACK (pika_tagged_container_tag_added), tagged_container); g_signal_connect (object, "tag-removed", G_CALLBACK (pika_tagged_container_tag_removed), tagged_container); if (pika_tagged_container_object_matches (tagged_container, object)) { pika_container_add (PIKA_CONTAINER (tagged_container), object); } } static void pika_tagged_container_src_remove (PikaFilteredContainer *filtered_container, PikaObject *object) { PikaTaggedContainer *tagged_container = PIKA_TAGGED_CONTAINER (filtered_container); GList *list; g_signal_handlers_disconnect_by_func (object, pika_tagged_container_tag_added, tagged_container); g_signal_handlers_disconnect_by_func (object, pika_tagged_container_tag_removed, tagged_container); for (list = pika_tagged_get_tags (PIKA_TAGGED (object)); list; list = g_list_next (list)) { pika_tagged_container_unref_tag (tagged_container, list->data); } if (pika_tagged_container_object_matches (tagged_container, object)) { pika_container_remove (PIKA_CONTAINER (tagged_container), object); } } static void pika_tagged_container_src_freeze (PikaFilteredContainer *filtered_container) { pika_container_clear (PIKA_CONTAINER (filtered_container)); } static void pika_tagged_container_src_thaw (PikaFilteredContainer *filtered_container) { GList *list; for (list = PIKA_LIST (filtered_container->src_container)->queue->head; list; list = g_list_next (list)) { pika_tagged_container_src_add (filtered_container, list->data); } } /** * pika_tagged_container_new: * @src_container: container to be filtered. * * Creates a new #PikaTaggedContainer object which creates filtered * data view of #PikaTagged objects. It filters @src_container for * objects containing all of the filtering tags. Synchronization with * @src_container data is performed automatically. * * Returns: a new #PikaTaggedContainer object. **/ PikaContainer * pika_tagged_container_new (PikaContainer *src_container) { PikaTaggedContainer *tagged_container; GType children_type; GCompareFunc sort_func; g_return_val_if_fail (PIKA_IS_LIST (src_container), NULL); children_type = pika_container_get_children_type (src_container); sort_func = pika_list_get_sort_func (PIKA_LIST (src_container)); tagged_container = g_object_new (PIKA_TYPE_TAGGED_CONTAINER, "sort-func", sort_func, "children-type", children_type, "policy", PIKA_CONTAINER_POLICY_WEAK, "unique-names", FALSE, "src-container", src_container, NULL); return PIKA_CONTAINER (tagged_container); } /** * pika_tagged_container_set_filter: * @tagged_container: a #PikaTaggedContainer object. * @tags: list of #PikaTag objects. * * Sets list of tags to be used for filtering. Only objects which have * all of the tags assigned match filtering criteria. **/ void pika_tagged_container_set_filter (PikaTaggedContainer *tagged_container, GList *tags) { GList *new_filter; g_return_if_fail (PIKA_IS_TAGGED_CONTAINER (tagged_container)); if (tags) { GList *list; for (list = tags; list; list = g_list_next (list)) g_return_if_fail (list->data == NULL || PIKA_IS_TAG (list->data)); } if (! pika_container_frozen (PIKA_FILTERED_CONTAINER (tagged_container)->src_container)) { pika_tagged_container_src_freeze (PIKA_FILTERED_CONTAINER (tagged_container)); } /* ref new tags first, they could be the same as the old ones */ new_filter = g_list_copy (tags); g_list_foreach (new_filter, (GFunc) pika_tag_or_null_ref, NULL); g_list_free_full (tagged_container->filter, (GDestroyNotify) pika_tag_or_null_unref); tagged_container->filter = new_filter; if (! pika_container_frozen (PIKA_FILTERED_CONTAINER (tagged_container)->src_container)) { pika_tagged_container_src_thaw (PIKA_FILTERED_CONTAINER (tagged_container)); } } /** * pika_tagged_container_get_filter: * @tagged_container: a #PikaTaggedContainer object. * * Returns current tag filter. Tag filter is a list of PikaTag objects, which * must be contained by each object matching filter criteria. * * Returns: a list of PikaTag objects used as filter. This value should * not be modified or freed. **/ const GList * pika_tagged_container_get_filter (PikaTaggedContainer *tagged_container) { g_return_val_if_fail (PIKA_IS_TAGGED_CONTAINER (tagged_container), NULL); return tagged_container->filter; } static gboolean pika_tagged_container_object_matches (PikaTaggedContainer *tagged_container, PikaObject *object) { GList *filter_tags; for (filter_tags = tagged_container->filter; filter_tags; filter_tags = g_list_next (filter_tags)) { if (! filter_tags->data) { /* invalid tag - does not match */ return FALSE; } if (! pika_tagged_has_tag (PIKA_TAGGED (object), filter_tags->data)) { /* match for the tag was not found. * since query is of type AND, it whole fails. */ return FALSE; } } return TRUE; } static void pika_tagged_container_tag_added (PikaTagged *tagged, PikaTag *tag, PikaTaggedContainer *tagged_container) { pika_tagged_container_ref_tag (tagged_container, tag); if (pika_tagged_container_object_matches (tagged_container, PIKA_OBJECT (tagged)) && ! pika_container_have (PIKA_CONTAINER (tagged_container), PIKA_OBJECT (tagged))) { pika_container_add (PIKA_CONTAINER (tagged_container), PIKA_OBJECT (tagged)); } } static void pika_tagged_container_tag_removed (PikaTagged *tagged, PikaTag *tag, PikaTaggedContainer *tagged_container) { pika_tagged_container_unref_tag (tagged_container, tag); if (! pika_tagged_container_object_matches (tagged_container, PIKA_OBJECT (tagged)) && pika_container_have (PIKA_CONTAINER (tagged_container), PIKA_OBJECT (tagged))) { pika_container_remove (PIKA_CONTAINER (tagged_container), PIKA_OBJECT (tagged)); } } static void pika_tagged_container_ref_tag (PikaTaggedContainer *tagged_container, PikaTag *tag) { gint ref_count; ref_count = GPOINTER_TO_INT (g_hash_table_lookup (tagged_container->tag_ref_counts, tag)); ref_count++; g_hash_table_insert (tagged_container->tag_ref_counts, g_object_ref (tag), GINT_TO_POINTER (ref_count)); if (ref_count == 1) { tagged_container->tag_count++; g_signal_emit (tagged_container, pika_tagged_container_signals[TAG_COUNT_CHANGED], 0, tagged_container->tag_count); } } static void pika_tagged_container_unref_tag (PikaTaggedContainer *tagged_container, PikaTag *tag) { gint ref_count; ref_count = GPOINTER_TO_INT (g_hash_table_lookup (tagged_container->tag_ref_counts, tag)); ref_count--; if (ref_count > 0) { g_hash_table_insert (tagged_container->tag_ref_counts, g_object_ref (tag), GINT_TO_POINTER (ref_count)); } else { if (g_hash_table_remove (tagged_container->tag_ref_counts, tag)) { tagged_container->tag_count--; g_signal_emit (tagged_container, pika_tagged_container_signals[TAG_COUNT_CHANGED], 0, tagged_container->tag_count); } } } static void pika_tagged_container_tag_count_changed (PikaTaggedContainer *container, gint tag_count) { } /** * pika_tagged_container_get_tag_count: * @container: a #PikaTaggedContainer object. * * Get number of distinct tags that are currently assigned to all * objects in the container. The count is independent of currently * used filter, it is provided for all available objects (ie. empty * filter). * * Returns: number of distinct tags assigned to all objects in the * container. **/ gint pika_tagged_container_get_tag_count (PikaTaggedContainer *container) { g_return_val_if_fail (PIKA_IS_TAGGED_CONTAINER (container), 0); return container->tag_count; }