diff options
Diffstat (limited to '')
-rw-r--r-- | app/core/gimpdata.c | 1245 |
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"); +} |