486 lines
17 KiB
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;
|
|
}
|