PIKApp/app/core/pikataggedcontainer.c

486 lines
17 KiB
C

/* 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 <aurisj@svn.gnome.org>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gio/gio.h>
#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;
}