summaryrefslogtreecommitdiffstats
path: root/app/core/gimpdata.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimpdata.c')
-rw-r--r--app/core/gimpdata.c1245
1 files changed, 1245 insertions, 0 deletions
diff --git a/app/core/gimpdata.c b/app/core/gimpdata.c
new file mode 100644
index 0000000..e4640f3
--- /dev/null
+++ b/app/core/gimpdata.c
@@ -0,0 +1,1245 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdata.c
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.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 <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpdata.h"
+#include "gimpmarshal.h"
+#include "gimptag.h"
+#include "gimptagged.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ DIRTY,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_FILE,
+ PROP_WRITABLE,
+ PROP_DELETABLE,
+ PROP_MIME_TYPE
+};
+
+
+struct _GimpDataPrivate
+{
+ GFile *file;
+ GQuark mime_type;
+ guint writable : 1;
+ guint deletable : 1;
+ guint dirty : 1;
+ guint internal : 1;
+ gint freeze_count;
+ gint64 mtime;
+
+ /* Identifies the GimpData object across sessions. Used when there
+ * is not a filename associated with the object.
+ */
+ gchar *identifier;
+
+ GList *tags;
+};
+
+#define GIMP_DATA_GET_PRIVATE(obj) (((GimpData *) (obj))->priv)
+
+
+static void gimp_data_tagged_iface_init (GimpTaggedInterface *iface);
+
+static void gimp_data_constructed (GObject *object);
+static void gimp_data_finalize (GObject *object);
+static void gimp_data_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_data_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_data_name_changed (GimpObject *object);
+static gint64 gimp_data_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_data_is_name_editable (GimpViewable *viewable);
+
+static void gimp_data_real_dirty (GimpData *data);
+static GimpData * gimp_data_real_duplicate (GimpData *data);
+static gint gimp_data_real_compare (GimpData *data1,
+ GimpData *data2);
+
+static gboolean gimp_data_add_tag (GimpTagged *tagged,
+ GimpTag *tag);
+static gboolean gimp_data_remove_tag (GimpTagged *tagged,
+ GimpTag *tag);
+static GList * gimp_data_get_tags (GimpTagged *tagged);
+static gchar * gimp_data_get_identifier (GimpTagged *tagged);
+static gchar * gimp_data_get_checksum (GimpTagged *tagged);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpData, gimp_data, GIMP_TYPE_VIEWABLE,
+ G_ADD_PRIVATE (GimpData)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
+ gimp_data_tagged_iface_init))
+
+#define parent_class gimp_data_parent_class
+
+static guint data_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_data_class_init (GimpDataClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ data_signals[DIRTY] =
+ g_signal_new ("dirty",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDataClass, dirty),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->constructed = gimp_data_constructed;
+ object_class->finalize = gimp_data_finalize;
+ object_class->set_property = gimp_data_set_property;
+ object_class->get_property = gimp_data_get_property;
+
+ gimp_object_class->name_changed = gimp_data_name_changed;
+ gimp_object_class->get_memsize = gimp_data_get_memsize;
+
+ viewable_class->name_editable = TRUE;
+ viewable_class->is_name_editable = gimp_data_is_name_editable;
+
+ klass->dirty = gimp_data_real_dirty;
+ klass->save = NULL;
+ klass->get_extension = NULL;
+ klass->copy = NULL;
+ klass->duplicate = gimp_data_real_duplicate;
+ klass->compare = gimp_data_real_compare;
+
+ g_object_class_install_property (object_class, PROP_FILE,
+ g_param_spec_object ("file", NULL, NULL,
+ G_TYPE_FILE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WRITABLE,
+ g_param_spec_boolean ("writable", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_DELETABLE,
+ g_param_spec_boolean ("deletable", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_MIME_TYPE,
+ g_param_spec_string ("mime-type", NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_data_tagged_iface_init (GimpTaggedInterface *iface)
+{
+ iface->add_tag = gimp_data_add_tag;
+ iface->remove_tag = gimp_data_remove_tag;
+ iface->get_tags = gimp_data_get_tags;
+ iface->get_identifier = gimp_data_get_identifier;
+ iface->get_checksum = gimp_data_get_checksum;
+}
+
+static void
+gimp_data_init (GimpData *data)
+{
+ GimpDataPrivate *private = gimp_data_get_instance_private (data);
+
+ data->priv = private;
+
+ private->writable = TRUE;
+ private->deletable = TRUE;
+ private->dirty = TRUE;
+
+ /* freeze the data object during construction */
+ gimp_data_freeze (data);
+}
+
+static void
+gimp_data_constructed (GObject *object)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ if (! GIMP_DATA_GET_CLASS (object)->save)
+ private->writable = FALSE;
+
+ gimp_data_thaw (GIMP_DATA (object));
+}
+
+static void
+gimp_data_finalize (GObject *object)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ g_clear_object (&private->file);
+
+ if (private->tags)
+ {
+ g_list_free_full (private->tags, (GDestroyNotify) g_object_unref);
+ private->tags = NULL;
+ }
+
+ g_clear_pointer (&private->identifier, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_data_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpData *data = GIMP_DATA (object);
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (data);
+
+ switch (property_id)
+ {
+ case PROP_FILE:
+ gimp_data_set_file (data,
+ g_value_get_object (value),
+ private->writable,
+ private->deletable);
+ break;
+
+ case PROP_WRITABLE:
+ private->writable = g_value_get_boolean (value);
+ break;
+
+ case PROP_DELETABLE:
+ private->deletable = g_value_get_boolean (value);
+ break;
+
+ case PROP_MIME_TYPE:
+ if (g_value_get_string (value))
+ private->mime_type = g_quark_from_string (g_value_get_string (value));
+ else
+ private->mime_type = 0;
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_data_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_FILE:
+ g_value_set_object (value, private->file);
+ break;
+
+ case PROP_WRITABLE:
+ g_value_set_boolean (value, private->writable);
+ break;
+
+ case PROP_DELETABLE:
+ g_value_set_boolean (value, private->deletable);
+ break;
+
+ case PROP_MIME_TYPE:
+ g_value_set_string (value, g_quark_to_string (private->mime_type));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_data_name_changed (GimpObject *object)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ private->dirty = TRUE;
+
+ if (GIMP_OBJECT_CLASS (parent_class)->name_changed)
+ GIMP_OBJECT_CLASS (parent_class)->name_changed (object);
+}
+
+static gint64
+gimp_data_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_object_get_memsize (G_OBJECT (private->file));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_data_is_name_editable (GimpViewable *viewable)
+{
+ return gimp_data_is_writable (GIMP_DATA (viewable)) &&
+ ! gimp_data_is_internal (GIMP_DATA (viewable));
+}
+
+static void
+gimp_data_real_dirty (GimpData *data)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (data));
+
+ /* Emit the "name-changed" to signal general dirtiness, our name
+ * changed implementation will also set the "dirty" flag to TRUE.
+ */
+ gimp_object_name_changed (GIMP_OBJECT (data));
+}
+
+static GimpData *
+gimp_data_real_duplicate (GimpData *data)
+{
+ if (GIMP_DATA_GET_CLASS (data)->copy)
+ {
+ GimpData *new = g_object_new (G_OBJECT_TYPE (data), NULL);
+
+ gimp_data_copy (new, data);
+
+ return new;
+ }
+
+ return NULL;
+}
+
+static gint
+gimp_data_real_compare (GimpData *data1,
+ GimpData *data2)
+{
+ GimpDataPrivate *private1 = GIMP_DATA_GET_PRIVATE (data1);
+ GimpDataPrivate *private2 = GIMP_DATA_GET_PRIVATE (data2);
+
+ /* move the internal objects (like the FG -> BG) gradient) to the top */
+ if (private1->internal != private2->internal)
+ return private1->internal ? -1 : 1;
+
+ /* keep user-deletable objects above system resource files */
+ if (private1->deletable != private2->deletable)
+ return private1->deletable ? -1 : 1;
+
+ return gimp_object_name_collate ((GimpObject *) data1,
+ (GimpObject *) data2);
+}
+
+static gboolean
+gimp_data_add_tag (GimpTagged *tagged,
+ GimpTag *tag)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+ GList *list;
+
+ for (list = private->tags; list; list = g_list_next (list))
+ {
+ GimpTag *this = GIMP_TAG (list->data);
+
+ if (gimp_tag_equals (tag, this))
+ return FALSE;
+ }
+
+ private->tags = g_list_prepend (private->tags, g_object_ref (tag));
+
+ return TRUE;
+}
+
+static gboolean
+gimp_data_remove_tag (GimpTagged *tagged,
+ GimpTag *tag)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+ GList *list;
+
+ for (list = private->tags; list; list = g_list_next (list))
+ {
+ GimpTag *this = GIMP_TAG (list->data);
+
+ if (gimp_tag_equals (tag, this))
+ {
+ private->tags = g_list_delete_link (private->tags, list);
+ g_object_unref (this);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static GList *
+gimp_data_get_tags (GimpTagged *tagged)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+
+ return private->tags;
+}
+
+static gchar *
+gimp_data_get_identifier (GimpTagged *tagged)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+ gchar *identifier = NULL;
+
+ if (private->file)
+ {
+ const gchar *data_dir = gimp_data_directory ();
+ const gchar *gimp_dir = gimp_directory ();
+ gchar *path = g_file_get_path (private->file);
+ gchar *tmp;
+
+ if (g_str_has_prefix (path, data_dir))
+ {
+ tmp = g_strconcat ("${gimp_data_dir}",
+ path + strlen (data_dir),
+ NULL);
+ identifier = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
+ g_free (tmp);
+ }
+ else if (g_str_has_prefix (path, gimp_dir))
+ {
+ tmp = g_strconcat ("${gimp_dir}",
+ path + strlen (gimp_dir),
+ NULL);
+ identifier = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
+ g_free (tmp);
+ }
+ else if (g_str_has_prefix (path, MYPAINT_BRUSHES_DIR))
+ {
+ tmp = g_strconcat ("${mypaint_brushes_dir}",
+ path + strlen (MYPAINT_BRUSHES_DIR),
+ NULL);
+ identifier = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
+ g_free (tmp);
+ }
+ else
+ {
+ identifier = g_filename_to_utf8 (path, -1,
+ NULL, NULL, NULL);
+ }
+
+ if (! identifier)
+ {
+ g_printerr ("%s: failed to convert '%s' to utf8.\n",
+ G_STRFUNC, path);
+ identifier = g_strdup (path);
+ }
+
+ g_free (path);
+ }
+ else if (private->internal)
+ {
+ identifier = g_strdup (private->identifier);
+ }
+
+ return identifier;
+}
+
+static gchar *
+gimp_data_get_checksum (GimpTagged *tagged)
+{
+ return NULL;
+}
+
+/**
+ * gimp_data_save:
+ * @data: object whose contents are to be saved.
+ * @error: return location for errors or %NULL
+ *
+ * Save the object. If the object is marked as "internal", nothing
+ * happens. Otherwise, it is saved to disk, using the file name set
+ * by gimp_data_set_file(). If the save is successful, the object is
+ * marked as not dirty. If not, an error message is returned using
+ * the @error argument.
+ *
+ * Returns: %TRUE if the object is internal or the save is successful.
+ **/
+gboolean
+gimp_data_save (GimpData *data,
+ GError **error)
+{
+ GimpDataPrivate *private;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_return_val_if_fail (private->writable == TRUE, FALSE);
+
+ if (private->internal)
+ {
+ private->dirty = FALSE;
+ return TRUE;
+ }
+
+ g_return_val_if_fail (G_IS_FILE (private->file), FALSE);
+
+ if (GIMP_DATA_GET_CLASS (data)->save)
+ {
+ GOutputStream *output;
+
+ output = G_OUTPUT_STREAM (g_file_replace (private->file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, error));
+
+ if (output)
+ {
+ success = GIMP_DATA_GET_CLASS (data)->save (data, output, error);
+
+ if (success)
+ {
+ if (! g_output_stream_close (output, NULL, error))
+ {
+ g_prefix_error (error,
+ _("Error saving '%s': "),
+ gimp_file_get_utf8_name (private->file));
+ success = FALSE;
+ }
+ }
+ else
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_cancellable_cancel (cancellable);
+ if (error && *error)
+ {
+ g_prefix_error (error,
+ _("Error saving '%s': "),
+ gimp_file_get_utf8_name (private->file));
+ }
+ else
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_WRITE,
+ _("Error saving '%s'"),
+ gimp_file_get_utf8_name (private->file));
+ }
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ }
+
+ g_object_unref (output);
+ }
+ }
+
+ if (success)
+ {
+ GFileInfo *info = g_file_query_info (private->file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ if (info)
+ {
+ private->mtime =
+ g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ g_object_unref (info);
+ }
+
+ private->dirty = FALSE;
+ }
+
+ return success;
+}
+
+/**
+ * gimp_data_dirty:
+ * @data: a #GimpData object.
+ *
+ * Marks @data as dirty. Unless the object is frozen, this causes
+ * its preview to be invalidated, and emits a "dirty" signal. If the
+ * object is frozen, the function has no effect.
+ **/
+void
+gimp_data_dirty (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->freeze_count == 0)
+ g_signal_emit (data, data_signals[DIRTY], 0);
+}
+
+void
+gimp_data_clean (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ private->dirty = FALSE;
+}
+
+gboolean
+gimp_data_is_dirty (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->dirty;
+}
+
+/**
+ * gimp_data_freeze:
+ * @data: a #GimpData object.
+ *
+ * Increments the freeze count for the object. A positive freeze count
+ * prevents the object from being treated as dirty. Any call to this
+ * function must be followed eventually by a call to gimp_data_thaw().
+ **/
+void
+gimp_data_freeze (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ private->freeze_count++;
+}
+
+/**
+ * gimp_data_thaw:
+ * @data: a #GimpData object.
+ *
+ * Decrements the freeze count for the object. If the freeze count
+ * drops to zero, the object is marked as dirty, and the "dirty"
+ * signal is emitted. It is an error to call this function without
+ * having previously called gimp_data_freeze().
+ **/
+void
+gimp_data_thaw (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_return_if_fail (private->freeze_count > 0);
+
+ private->freeze_count--;
+
+ if (private->freeze_count == 0)
+ gimp_data_dirty (data);
+}
+
+gboolean
+gimp_data_is_frozen (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->freeze_count > 0;
+}
+
+/**
+ * gimp_data_delete_from_disk:
+ * @data: a #GimpData object.
+ * @error: return location for errors or %NULL
+ *
+ * Deletes the object from disk. If the object is marked as "internal",
+ * nothing happens. Otherwise, if the file exists whose name has been
+ * set by gimp_data_set_file(), it is deleted. Obviously this is
+ * a potentially dangerous function, which should be used with care.
+ *
+ * Returns: %TRUE if the object is internal to Gimp, or the deletion is
+ * successful.
+ **/
+gboolean
+gimp_data_delete_from_disk (GimpData *data,
+ GError **error)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_return_val_if_fail (private->file != NULL, FALSE);
+ g_return_val_if_fail (private->deletable == TRUE, FALSE);
+
+ if (private->internal)
+ return TRUE;
+
+ return g_file_delete (private->file, NULL, error);
+}
+
+const gchar *
+gimp_data_get_extension (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ if (GIMP_DATA_GET_CLASS (data)->get_extension)
+ return GIMP_DATA_GET_CLASS (data)->get_extension (data);
+
+ return NULL;
+}
+
+/**
+ * gimp_data_set_file:
+ * @data: A #GimpData object
+ * @file: File to assign to @data.
+ * @writable: %TRUE if we want to be able to write to this file.
+ * @deletable: %TRUE if we want to be able to delete this file.
+ *
+ * This function assigns a file to @data, and sets some flags
+ * according to the properties of the file. If @writable is %TRUE,
+ * and the user has permission to write or overwrite the requested
+ * file name, and a "save" method exists for @data's object type, then
+ * @data is marked as writable.
+ **/
+void
+gimp_data_set_file (GimpData *data,
+ GFile *file,
+ gboolean writable,
+ gboolean deletable)
+{
+ GimpDataPrivate *private;
+ gchar *path;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (G_IS_FILE (file));
+
+ path = g_file_get_path (file);
+
+ g_return_if_fail (path != NULL);
+ g_return_if_fail (g_path_is_absolute (path));
+
+ g_free (path);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->internal)
+ return;
+
+ g_set_object (&private->file, file);
+
+ private->writable = FALSE;
+ private->deletable = FALSE;
+
+ /* if the data is supposed to be writable or deletable,
+ * still check if it really is
+ */
+ if (writable || deletable)
+ {
+ GFileInfo *info;
+
+ if (g_file_query_exists (private->file, NULL)) /* check if it exists */
+ {
+ info = g_file_query_info (private->file,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ /* and we can write it */
+ if (info)
+ {
+ if (g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ private->writable = writable ? TRUE : FALSE;
+ private->deletable = deletable ? TRUE : FALSE;
+ }
+
+ g_object_unref (info);
+ }
+ }
+ else /* OR it doesn't exist */
+ {
+ GFile *parent = g_file_get_parent (private->file);
+
+ info = g_file_query_info (parent,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ /* and we can write to its parent directory */
+ if (info)
+ {
+ if (g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ private->writable = writable ? TRUE : FALSE;
+ private->deletable = deletable ? TRUE : FALSE;
+ }
+
+ g_object_unref (info);
+ }
+
+ g_object_unref (parent);
+ }
+
+ /* if we can't save, we are not writable */
+ if (! GIMP_DATA_GET_CLASS (data)->save)
+ private->writable = FALSE;
+ }
+}
+
+GFile *
+gimp_data_get_file (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->file;
+}
+
+/**
+ * gimp_data_create_filename:
+ * @data: a #Gimpdata object.
+ * @dest_dir: directory in which to create a file name.
+ *
+ * This function creates a unique file name to be used for saving
+ * a representation of @data in the directory @dest_dir. If the
+ * user does not have write permission in @dest_dir, then @data
+ * is marked as "not writable", so you should check on this before
+ * assuming that @data can be saved.
+ **/
+void
+gimp_data_create_filename (GimpData *data,
+ GFile *dest_dir)
+{
+ GimpDataPrivate *private;
+ gchar *safename;
+ gchar *basename;
+ GFile *file;
+ gint i;
+ gint unum = 1;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (G_IS_FILE (dest_dir));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->internal)
+ return;
+
+ safename = g_strstrip (g_strdup (gimp_object_get_name (data)));
+
+ if (safename[0] == '.')
+ safename[0] = '-';
+
+ for (i = 0; safename[i]; i++)
+ if (strchr ("\\/*?\"`'<>{}|\n\t ;:$^&", safename[i]))
+ safename[i] = '-';
+
+ basename = g_strconcat (safename, gimp_data_get_extension (data), NULL);
+
+ file = g_file_get_child_for_display_name (dest_dir, basename, &error);
+ g_free (basename);
+
+ if (! file)
+ {
+ g_warning ("gimp_data_create_filename:\n"
+ "g_file_get_child_for_display_name() failed for '%s': %s",
+ gimp_object_get_name (data), error->message);
+ g_clear_error (&error);
+ g_free (safename);
+ return;
+ }
+
+ while (g_file_query_exists (file, NULL))
+ {
+ g_object_unref (file);
+
+ basename = g_strdup_printf ("%s-%d%s",
+ safename,
+ unum++,
+ gimp_data_get_extension (data));
+
+ file = g_file_get_child_for_display_name (dest_dir, basename, NULL);
+ g_free (basename);
+ }
+
+ g_free (safename);
+
+ gimp_data_set_file (data, file, TRUE, TRUE);
+
+ g_object_unref (file);
+}
+
+static const gchar *tag_blacklist[] = { "brushes",
+ "dynamics",
+ "patterns",
+ "palettes",
+ "gradients",
+ "tool-presets" };
+
+/**
+ * gimp_data_set_folder_tags:
+ * @data: a #Gimpdata object.
+ * @top_directory: the top directory of the currently processed data
+ * hierarchy.
+ *
+ * Sets tags based on all folder names below top_directory. So if the
+ * data's filename is e.g.
+ * /home/foo/.config/GIMP/X.Y/brushes/Flowers/Roses/rose.gbr, it will
+ * add "Flowers" and "Roses" tags.
+ *
+ * if the top directory (as passed, or as derived from the data's
+ * filename) does not end with one of the default data directory names
+ * (brushes, patterns etc), its name will be added as tag too.
+ **/
+void
+gimp_data_set_folder_tags (GimpData *data,
+ GFile *top_directory)
+{
+ GimpDataPrivate *private;
+ gchar *tmp;
+ gchar *dirname;
+ gchar *top_path;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (G_IS_FILE (top_directory));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->internal)
+ return;
+
+ g_return_if_fail (private->file != NULL);
+
+ tmp = g_file_get_path (private->file);
+ dirname = g_path_get_dirname (tmp);
+ g_free (tmp);
+
+ top_path = g_file_get_path (top_directory);
+
+ g_return_if_fail (g_str_has_prefix (dirname, top_path));
+
+ /* walk up the hierarchy and set each folder on the way as tag,
+ * except the top_directory
+ */
+ while (strcmp (dirname, top_path))
+ {
+ gchar *basename = g_path_get_basename (dirname);
+ GimpTag *tag = gimp_tag_new (basename);
+
+ gimp_tag_set_internal (tag, TRUE);
+ gimp_tagged_add_tag (GIMP_TAGGED (data), tag);
+ g_object_unref (tag);
+ g_free (basename);
+
+ tmp = g_path_get_dirname (dirname);
+ g_free (dirname);
+ dirname = tmp;
+ }
+
+ g_free (top_path);
+
+ if (dirname)
+ {
+ gchar *basename = g_path_get_basename (dirname);
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tag_blacklist); i++)
+ {
+ if (! strcmp (basename, tag_blacklist[i]))
+ break;
+ }
+
+ if (i == G_N_ELEMENTS (tag_blacklist))
+ {
+ GimpTag *tag = gimp_tag_new (basename);
+
+ gimp_tag_set_internal (tag, TRUE);
+ gimp_tagged_add_tag (GIMP_TAGGED (data), tag);
+ g_object_unref (tag);
+ }
+
+ g_free (basename);
+ g_free (dirname);
+ }
+}
+
+const gchar *
+gimp_data_get_mime_type (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return g_quark_to_string (private->mime_type);
+}
+
+gboolean
+gimp_data_is_writable (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->writable;
+}
+
+gboolean
+gimp_data_is_deletable (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->deletable;
+}
+
+void
+gimp_data_set_mtime (GimpData *data,
+ gint64 mtime)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ private->mtime = mtime;
+}
+
+gint64
+gimp_data_get_mtime (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), 0);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->mtime;
+}
+
+gboolean
+gimp_data_is_copyable (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ return GIMP_DATA_GET_CLASS (data)->copy != NULL;
+}
+
+/**
+ * gimp_data_copy:
+ * @data: a #GimpData object
+ * @src_data: the #GimpData object to copy from
+ *
+ * Copies @src_data to @data. Only the object data is copied: the
+ * name, file name, preview, etc. are not copied.
+ **/
+void
+gimp_data_copy (GimpData *data,
+ GimpData *src_data)
+{
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (GIMP_IS_DATA (src_data));
+ g_return_if_fail (GIMP_DATA_GET_CLASS (data)->copy != NULL);
+ g_return_if_fail (GIMP_DATA_GET_CLASS (data)->copy ==
+ GIMP_DATA_GET_CLASS (src_data)->copy);
+
+ if (data != src_data)
+ GIMP_DATA_GET_CLASS (data)->copy (data, src_data);
+}
+
+gboolean
+gimp_data_is_duplicatable (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ if (GIMP_DATA_GET_CLASS (data)->duplicate == gimp_data_real_duplicate)
+ return gimp_data_is_copyable (data);
+ else
+ return GIMP_DATA_GET_CLASS (data)->duplicate != NULL;
+}
+
+/**
+ * gimp_data_duplicate:
+ * @data: a #GimpData object
+ *
+ * Creates a copy of @data, if possible. Only the object data is
+ * copied: the newly created object is not automatically given an
+ * object name, file name, preview, etc.
+ *
+ * Returns: the newly created copy, or %NULL if @data cannot be copied.
+ **/
+GimpData *
+gimp_data_duplicate (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ if (gimp_data_is_duplicatable (data))
+ {
+ GimpData *new = GIMP_DATA_GET_CLASS (data)->duplicate (data);
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (new);
+
+ g_object_set (new,
+ "name", NULL,
+ "writable", GIMP_DATA_GET_CLASS (new)->save != NULL,
+ "deletable", TRUE,
+ NULL);
+
+ g_clear_object (&private->file);
+
+ return new;
+ }
+
+ return NULL;
+}
+
+/**
+ * gimp_data_make_internal:
+ * @data: a #GimpData object.
+ *
+ * Mark @data as "internal" to Gimp, which means that it will not be
+ * saved to disk. Note that if you do this, later calls to
+ * gimp_data_save() and gimp_data_delete_from_disk() will
+ * automatically return successfully without giving any warning.
+ *
+ * The identifier name shall be an untranslated globally unique string
+ * that identifies the internal object across sessions.
+ **/
+void
+gimp_data_make_internal (GimpData *data,
+ const gchar *identifier)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_clear_object (&private->file);
+
+ g_free (private->identifier);
+ private->identifier = g_strdup (identifier);
+
+ private->writable = FALSE;
+ private->deletable = FALSE;
+ private->internal = TRUE;
+}
+
+gboolean
+gimp_data_is_internal (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->internal;
+}
+
+/**
+ * gimp_data_compare:
+ * @data1: a #GimpData object.
+ * @data2: another #GimpData object.
+ *
+ * Compares two data objects for use in sorting. Objects marked as
+ * "internal" come first, then user-writable objects, then system data
+ * files. In these three groups, the objects are sorted alphabetically
+ * by name, using gimp_object_name_collate().
+ *
+ * Return value: -1 if @data1 compares before @data2,
+ * 0 if they compare equal,
+ * 1 if @data1 compares after @data2.
+ **/
+gint
+gimp_data_compare (GimpData *data1,
+ GimpData *data2)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data1), 0);
+ g_return_val_if_fail (GIMP_IS_DATA (data2), 0);
+ g_return_val_if_fail (GIMP_DATA_GET_CLASS (data1)->compare ==
+ GIMP_DATA_GET_CLASS (data2)->compare, 0);
+
+ return GIMP_DATA_GET_CLASS (data1)->compare (data1, data2);
+}
+
+/**
+ * gimp_data_error_quark:
+ *
+ * This function is used to implement the GIMP_DATA_ERROR macro. It
+ * shouldn't be called directly.
+ *
+ * Return value: the #GQuark to identify error in the GimpData error domain.
+ **/
+GQuark
+gimp_data_error_quark (void)
+{
+ return g_quark_from_static_string ("gimp-data-error-quark");
+}