PIKApp/app/core/pikatag.c

447 lines
10 KiB
C
Raw Permalink Normal View History

2023-09-26 00:35:21 +02:00
/* 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 Spencer Kimball and Peter Mattis
*
* pikatag.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 <glib-object.h>
#include <string.h>
#include "core-types.h"
#include "pikatag.h"
#define PIKA_TAG_INTERNAL_PREFIX "pika:"
G_DEFINE_TYPE (PikaTag, pika_tag, G_TYPE_OBJECT)
#define parent_class pika_tag_parent_class
static void
pika_tag_class_init (PikaTagClass *klass)
{
}
static void
pika_tag_init (PikaTag *tag)
{
tag->tag = 0;
tag->collate_key = 0;
tag->internal = FALSE;
}
/**
* pika_tag_new:
* @tag_string: a tag name.
*
* If given tag name is not valid, an attempt will be made to fix it.
*
* Returns: (nullable) (transfer full): a new #PikaTag object,
* or %NULL if tag string is invalid and cannot be fixed.
**/
PikaTag *
pika_tag_new (const char *tag_string)
{
PikaTag *tag;
gchar *tag_name;
gchar *case_folded;
gchar *collate_key;
g_return_val_if_fail (tag_string != NULL, NULL);
tag_name = pika_tag_string_make_valid (tag_string);
if (! tag_name)
return NULL;
tag = g_object_new (PIKA_TYPE_TAG, NULL);
tag->tag = g_quark_from_string (tag_name);
case_folded = g_utf8_casefold (tag_name, -1);
collate_key = g_utf8_collate_key (case_folded, -1);
tag->collate_key = g_quark_from_string (collate_key);
g_free (collate_key);
g_free (case_folded);
g_free (tag_name);
return tag;
}
/**
* pika_tag_try_new:
* @tag_string: a tag name.
*
* Similar to pika_tag_new(), but returns %NULL if tag is surely not equal
* to any of currently created tags. It is useful for tag querying to avoid
* unneeded comparisons. If tag is created, however, it does not mean that
* it would necessarily match with some other tag.
*
* Returns: (nullable) (transfer full): new #PikaTag object,
* or %NULL if tag will not match with any other #PikaTag.
**/
PikaTag *
pika_tag_try_new (const char *tag_string)
{
PikaTag *tag;
gchar *tag_name;
gchar *case_folded;
gchar *collate_key;
GQuark tag_quark;
GQuark collate_key_quark;
tag_name = pika_tag_string_make_valid (tag_string);
if (! tag_name)
return NULL;
case_folded = g_utf8_casefold (tag_name, -1);
collate_key = g_utf8_collate_key (case_folded, -1);
collate_key_quark = g_quark_try_string (collate_key);
g_free (collate_key);
g_free (case_folded);
if (! collate_key_quark)
{
g_free (tag_name);
return NULL;
}
tag_quark = g_quark_from_string (tag_name);
g_free (tag_name);
if (! tag_quark)
return NULL;
tag = g_object_new (PIKA_TYPE_TAG, NULL);
tag->tag = tag_quark;
tag->collate_key = collate_key_quark;
return tag;
}
/**
* pika_tag_get_internal:
* @tag: a pika tag.
*
* Retrieve internal status of the tag.
*
* Returns: internal status of tag. Internal tags are not saved.
**/
gboolean
pika_tag_get_internal (PikaTag *tag)
{
g_return_val_if_fail (PIKA_IS_TAG (tag), FALSE);
return tag->internal;
}
/**
* pika_tag_set_internal:
* @tag: a pika tag.
* @internal: desired tag internal status
*
* Set internal status of the tag. Internal tags are usually automatically
* generated and will not be saved into users tag cache.
*
**/
void
pika_tag_set_internal (PikaTag *tag, gboolean internal)
{
g_return_if_fail (PIKA_IS_TAG (tag));
tag->internal = internal;
}
/**
* pika_tag_get_name:
* @tag: a pika tag.
*
* Retrieve name of the tag.
*
* Returns: name of tag.
**/
const gchar *
pika_tag_get_name (PikaTag *tag)
{
g_return_val_if_fail (PIKA_IS_TAG (tag), NULL);
return g_quark_to_string (tag->tag);
}
/**
* pika_tag_get_hash:
* @tag: a pika tag.
*
* Hashing function which is useful, for example, to store #PikaTag in
* a #GHashTable.
*
* Returns: hash value for tag.
**/
guint
pika_tag_get_hash (PikaTag *tag)
{
g_return_val_if_fail (PIKA_IS_TAG (tag), -1);
return tag->collate_key;
}
/**
* pika_tag_equals:
* @tag: a pika tag.
* @other: another pika tag to compare with.
*
* Compares tags for equality according to tag comparison rules.
*
* Returns: TRUE if tags are equal, FALSE otherwise.
**/
gboolean
pika_tag_equals (PikaTag *tag,
PikaTag *other)
{
g_return_val_if_fail (PIKA_IS_TAG (tag), FALSE);
g_return_val_if_fail (PIKA_IS_TAG (other), FALSE);
return tag->collate_key == other->collate_key;
}
/**
* pika_tag_compare_func:
* @p1: pointer to left-hand #PikaTag object.
* @p2: pointer to right-hand #PikaTag object.
*
* Compares tags according to tag comparison rules. Useful for sorting
* functions.
*
* Returns: meaning of return value is the same as in strcmp().
**/
int
pika_tag_compare_func (const void *p1,
const void *p2)
{
PikaTag *t1 = PIKA_TAG (p1);
PikaTag *t2 = PIKA_TAG (p2);
return g_strcmp0 (g_quark_to_string (t1->collate_key),
g_quark_to_string (t2->collate_key));
}
/**
* pika_tag_compare_with_string:
* @tag: a #PikaTag object.
* @tag_string: the string to compare to.
*
* Compares tag and a string according to tag comparison rules. Similar to
* pika_tag_compare_func(), but can be used without creating temporary tag
* object.
*
* Returns: meaning of return value is the same as in strcmp().
**/
gint
pika_tag_compare_with_string (PikaTag *tag,
const gchar *tag_string)
{
gchar *case_folded;
const gchar *collate_key;
gchar *collate_key2;
gint result;
g_return_val_if_fail (PIKA_IS_TAG (tag), 0);
g_return_val_if_fail (tag_string != NULL, 0);
collate_key = g_quark_to_string (tag->collate_key);
case_folded = g_utf8_casefold (tag_string, -1);
collate_key2 = g_utf8_collate_key (case_folded, -1);
result = g_strcmp0 (collate_key, collate_key2);
g_free (collate_key2);
g_free (case_folded);
return result;
}
/**
* pika_tag_has_prefix:
* @tag: a #PikaTag object.
* @prefix_string: the prefix to compare to.
*
* Compares tag and a prefix according to tag comparison rules. Similar to
* pika_tag_compare_with_string(), but does not work on the collate key
* because that can't be matched partially.
*
* Returns: wheher #tag starts with @prefix_string.
**/
gboolean
pika_tag_has_prefix (PikaTag *tag,
const gchar *prefix_string)
{
gchar *case_folded1;
gchar *case_folded2;
gboolean has_prefix;
g_return_val_if_fail (PIKA_IS_TAG (tag), FALSE);
g_return_val_if_fail (prefix_string != NULL, FALSE);
case_folded1 = g_utf8_casefold (g_quark_to_string (tag->tag), -1);
case_folded2 = g_utf8_casefold (prefix_string, -1);
has_prefix = g_str_has_prefix (case_folded1, case_folded2);
g_free (case_folded1);
g_free (case_folded2);
g_printerr ("'%s' has prefix '%s': %d\n",
g_quark_to_string (tag->tag), prefix_string, has_prefix);
return has_prefix;
}
/**
* pika_tag_string_make_valid:
* @tag_string: a text string.
*
* Tries to create a valid tag string from given @tag_string.
*
* Returns: (transfer full) (nullable): a newly allocated tag string in case
* given @tag_string was valid or could be fixed, otherwise %NULL. Allocated
* value should be freed using g_free().
**/
gchar *
pika_tag_string_make_valid (const gchar *tag_string)
{
gchar *tag;
GString *buffer;
gchar *tag_cursor;
gunichar c;
g_return_val_if_fail (tag_string, NULL);
tag = g_utf8_normalize (tag_string, -1, G_NORMALIZE_ALL);
if (! tag)
return NULL;
tag = g_strstrip (tag);
if (! *tag)
{
g_free (tag);
return NULL;
}
buffer = g_string_new ("");
tag_cursor = tag;
if (g_str_has_prefix (tag_cursor, PIKA_TAG_INTERNAL_PREFIX))
{
tag_cursor += strlen (PIKA_TAG_INTERNAL_PREFIX);
}
do
{
c = g_utf8_get_char (tag_cursor);
tag_cursor = g_utf8_next_char (tag_cursor);
if (g_unichar_isprint (c)
&& ! pika_tag_is_tag_separator (c))
{
g_string_append_unichar (buffer, c);
}
} while (c);
g_free (tag);
tag = g_string_free (buffer, FALSE);
tag = g_strstrip (tag);
if (! *tag)
{
g_free (tag);
return NULL;
}
return tag;
}
/**
* pika_tag_is_tag_separator:
* @c: Unicode character.
*
* Defines a set of characters that are considered tag separators. The
* tag separators are hand-picked from the set of characters with the
* Terminal_Punctuation property as specified in the version 5.1.0 of
* the Unicode Standard.
*
* Returns: %TRUE if the character is a tag separator.
*/
gboolean
pika_tag_is_tag_separator (gunichar c)
{
switch (c)
{
case 0x002C: /* COMMA */
case 0x060C: /* ARABIC COMMA */
case 0x07F8: /* NKO COMMA */
case 0x1363: /* ETHIOPIC COMMA */
case 0x1802: /* MONGOLIAN COMMA */
case 0x1808: /* MONGOLIAN MANCHU COMMA */
case 0x3001: /* IDEOGRAPHIC COMMA */
case 0xA60D: /* VAI COMMA */
case 0xFE50: /* SMALL COMMA */
case 0xFF0C: /* FULLWIDTH COMMA */
case 0xFF64: /* HALFWIDTH IDEOGRAPHIC COMMA */
return TRUE;
default:
return FALSE;
}
}
/**
* pika_tag_or_null_ref:
* @tag: a #PikaTag
*
* A simple wrapper around g_object_ref() that silently accepts %NULL.
**/
void
pika_tag_or_null_ref (PikaTag *tag_or_null)
{
if (tag_or_null)
{
g_return_if_fail (PIKA_IS_TAG (tag_or_null));
g_object_ref (tag_or_null);
}
}
/**
* pika_tag_or_null_unref:
* @tag: a #PikaTag
*
* A simple wrapper around g_object_unref() that silently accepts %NULL.
**/
void
pika_tag_or_null_unref (PikaTag *tag_or_null)
{
if (tag_or_null)
{
g_return_if_fail (PIKA_IS_TAG (tag_or_null));
g_object_unref (tag_or_null);
}
}