summaryrefslogtreecommitdiffstats
path: root/app/core/gimpitemtree.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimpitemtree.c')
-rw-r--r--app/core/gimpitemtree.c714
1 files changed, 714 insertions, 0 deletions
diff --git a/app/core/gimpitemtree.c b/app/core/gimpitemtree.c
new file mode 100644
index 0000000..7746afa
--- /dev/null
+++ b/app/core/gimpitemtree.c
@@ -0,0 +1,714 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpitemtree.c
+ * Copyright (C) 2010 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 <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitem.h"
+#include "gimpitemstack.h"
+#include "gimpitemtree.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+ PROP_CONTAINER_TYPE,
+ PROP_ITEM_TYPE,
+ PROP_ACTIVE_ITEM
+};
+
+
+typedef struct _GimpItemTreePrivate GimpItemTreePrivate;
+
+struct _GimpItemTreePrivate
+{
+ GimpImage *image;
+
+ GType container_type;
+ GType item_type;
+
+ GimpItem *active_item;
+
+ GHashTable *name_hash;
+};
+
+#define GIMP_ITEM_TREE_GET_PRIVATE(object) \
+ ((GimpItemTreePrivate *) gimp_item_tree_get_instance_private ((GimpItemTree *) (object)))
+
+
+/* local function prototypes */
+
+static void gimp_item_tree_constructed (GObject *object);
+static void gimp_item_tree_dispose (GObject *object);
+static void gimp_item_tree_finalize (GObject *object);
+static void gimp_item_tree_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_item_tree_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_item_tree_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_item_tree_uniquefy_name (GimpItemTree *tree,
+ GimpItem *item,
+ const gchar *new_name);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpItemTree, gimp_item_tree, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_item_tree_parent_class
+
+
+static void
+gimp_item_tree_class_init (GimpItemTreeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_item_tree_constructed;
+ object_class->dispose = gimp_item_tree_dispose;
+ object_class->finalize = gimp_item_tree_finalize;
+ object_class->set_property = gimp_item_tree_set_property;
+ object_class->get_property = gimp_item_tree_get_property;
+
+ gimp_object_class->get_memsize = gimp_item_tree_get_memsize;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image",
+ NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_CONTAINER_TYPE,
+ g_param_spec_gtype ("container-type",
+ NULL, NULL,
+ GIMP_TYPE_ITEM_STACK,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_ITEM_TYPE,
+ g_param_spec_gtype ("item-type",
+ NULL, NULL,
+ GIMP_TYPE_ITEM,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_ACTIVE_ITEM,
+ g_param_spec_object ("active-item",
+ NULL, NULL,
+ GIMP_TYPE_ITEM,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_item_tree_init (GimpItemTree *tree)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ private->name_hash = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+gimp_item_tree_constructed (GObject *object)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_IMAGE (private->image));
+ gimp_assert (g_type_is_a (private->container_type, GIMP_TYPE_ITEM_STACK));
+ gimp_assert (g_type_is_a (private->item_type, GIMP_TYPE_ITEM));
+ gimp_assert (private->item_type != GIMP_TYPE_ITEM);
+
+ tree->container = g_object_new (private->container_type,
+ "name", g_type_name (private->item_type),
+ "children-type", private->item_type,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ NULL);
+}
+
+static void
+gimp_item_tree_dispose (GObject *object)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ gimp_item_tree_set_active_item (tree, NULL);
+
+ gimp_container_foreach (tree->container,
+ (GFunc) gimp_item_removed, NULL);
+
+ gimp_container_clear (tree->container);
+ g_hash_table_remove_all (private->name_hash);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_item_tree_finalize (GObject *object)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_clear_pointer (&private->name_hash, g_hash_table_unref);
+ g_clear_object (&tree->container);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_item_tree_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ private->image = g_value_get_object (value); /* don't ref */
+ break;
+ case PROP_CONTAINER_TYPE:
+ private->container_type = g_value_get_gtype (value);
+ break;
+ case PROP_ITEM_TYPE:
+ private->item_type = g_value_get_gtype (value);
+ break;
+ case PROP_ACTIVE_ITEM:
+ private->active_item = g_value_get_object (value); /* don't ref */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_item_tree_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value, private->image);
+ break;
+ case PROP_CONTAINER_TYPE:
+ g_value_set_gtype (value, private->container_type);
+ break;
+ case PROP_ITEM_TYPE:
+ g_value_set_gtype (value, private->item_type);
+ break;
+ case PROP_ACTIVE_ITEM:
+ g_value_set_object (value, private->active_item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_item_tree_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (tree->container), gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+
+/* public functions */
+
+GimpItemTree *
+gimp_item_tree_new (GimpImage *image,
+ GType container_type,
+ GType item_type)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (g_type_is_a (container_type, GIMP_TYPE_ITEM_STACK), NULL);
+ g_return_val_if_fail (g_type_is_a (item_type, GIMP_TYPE_ITEM), NULL);
+
+ return g_object_new (GIMP_TYPE_ITEM_TREE,
+ "image", image,
+ "container-type", container_type,
+ "item-type", item_type,
+ NULL);
+}
+
+GimpItem *
+gimp_item_tree_get_active_item (GimpItemTree *tree)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), NULL);
+
+ return GIMP_ITEM_TREE_GET_PRIVATE (tree)->active_item;
+}
+
+void
+gimp_item_tree_set_active_item (GimpItemTree *tree,
+ GimpItem *item)
+{
+ GimpItemTreePrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM_TREE (tree));
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_if_fail (item == NULL ||
+ G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type));
+ g_return_if_fail (item == NULL || gimp_item_get_tree (item) == tree);
+
+ if (item != private->active_item)
+ {
+ private->active_item = item;
+
+ g_object_notify (G_OBJECT (tree), "active-item");
+ }
+}
+
+GimpItem *
+gimp_item_tree_get_item_by_name (GimpItemTree *tree,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_hash_table_lookup (GIMP_ITEM_TREE_GET_PRIVATE (tree)->name_hash,
+ name);
+}
+
+gboolean
+gimp_item_tree_get_insert_pos (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem **parent,
+ gint *position)
+{
+ GimpItemTreePrivate *private;
+ GimpContainer *container;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), FALSE);
+ g_return_val_if_fail (parent != NULL, FALSE);
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type),
+ FALSE);
+ g_return_val_if_fail (! gimp_item_is_attached (item), FALSE);
+ g_return_val_if_fail (gimp_item_get_image (item) == private->image, FALSE);
+ g_return_val_if_fail (*parent == NULL ||
+ *parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ G_TYPE_CHECK_INSTANCE_TYPE (*parent, private->item_type),
+ FALSE);
+ g_return_val_if_fail (*parent == NULL ||
+ *parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_get_tree (*parent) == tree, FALSE);
+ g_return_val_if_fail (*parent == NULL ||
+ *parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (*parent)),
+ FALSE);
+ g_return_val_if_fail (position != NULL, FALSE);
+
+ /* if we want to insert in the active item's parent container */
+ if (*parent == GIMP_IMAGE_ACTIVE_PARENT)
+ {
+ if (private->active_item)
+ {
+ /* if the active item is a branch, add to the top of that
+ * branch; add to the active item's parent container
+ * otherwise
+ */
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (private->active_item)))
+ {
+ *parent = private->active_item;
+ *position = 0;
+ }
+ else
+ {
+ *parent = gimp_item_get_parent (private->active_item);
+ }
+ }
+ else
+ {
+ /* use the toplevel container if there is no active item */
+ *parent = NULL;
+ }
+ }
+
+ if (*parent)
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (*parent));
+ else
+ container = tree->container;
+
+ /* if we want to add on top of the active item */
+ if (*position == -1)
+ {
+ if (private->active_item)
+ *position =
+ gimp_container_get_child_index (container,
+ GIMP_OBJECT (private->active_item));
+
+ /* if the active item is not in the specified parent container,
+ * fall back to index 0
+ */
+ if (*position == -1)
+ *position = 0;
+ }
+
+ /* don't add at a non-existing index */
+ *position = CLAMP (*position, 0, gimp_container_get_n_children (container));
+
+ return TRUE;
+}
+
+void
+gimp_item_tree_add_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *parent,
+ gint position)
+{
+ GimpItemTreePrivate *private;
+ GimpContainer *container;
+ GimpContainer *children;
+
+ g_return_if_fail (GIMP_IS_ITEM_TREE (tree));
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type));
+ g_return_if_fail (! gimp_item_is_attached (item));
+ g_return_if_fail (gimp_item_get_image (item) == private->image);
+ g_return_if_fail (parent == NULL ||
+ G_TYPE_CHECK_INSTANCE_TYPE (parent, private->item_type));
+ g_return_if_fail (parent == NULL || gimp_item_get_tree (parent) == tree);
+ g_return_if_fail (parent == NULL ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (parent)));
+
+ gimp_item_tree_uniquefy_name (tree, item, NULL);
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ {
+ GList *list = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (children));
+
+ while (list)
+ {
+ gimp_item_tree_uniquefy_name (tree, list->data, NULL);
+
+ list = g_list_remove (list, list->data);
+ }
+ }
+
+ if (parent)
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (parent));
+ else
+ container = tree->container;
+
+ if (parent)
+ gimp_viewable_set_parent (GIMP_VIEWABLE (item),
+ GIMP_VIEWABLE (parent));
+
+ gimp_container_insert (container, GIMP_OBJECT (item), position);
+
+ /* if the item came from the undo stack, reset its "removed" state */
+ if (gimp_item_is_removed (item))
+ gimp_item_unset_removed (item);
+}
+
+GimpItem *
+gimp_item_tree_remove_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *new_active)
+{
+ GimpItemTreePrivate *private;
+ GimpItem *parent;
+ GimpContainer *container;
+ GimpContainer *children;
+ gint index;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), NULL);
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type),
+ NULL);
+ g_return_val_if_fail (gimp_item_get_tree (item) == tree, NULL);
+
+ parent = gimp_item_get_parent (item);
+ container = gimp_item_get_container (item);
+ index = gimp_item_get_index (item);
+
+ g_object_ref (item);
+
+ g_hash_table_remove (private->name_hash,
+ gimp_object_get_name (item));
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ {
+ GList *list = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (children));
+
+ while (list)
+ {
+ g_hash_table_remove (private->name_hash,
+ gimp_object_get_name (list->data));
+
+ list = g_list_remove (list, list->data);
+ }
+ }
+
+ gimp_container_remove (container, GIMP_OBJECT (item));
+
+ if (parent)
+ gimp_viewable_set_parent (GIMP_VIEWABLE (item), NULL);
+
+ gimp_item_removed (item);
+
+ if (! new_active)
+ {
+ gint n_children = gimp_container_get_n_children (container);
+
+ if (n_children > 0)
+ {
+ index = CLAMP (index, 0, n_children - 1);
+
+ new_active =
+ GIMP_ITEM (gimp_container_get_child_by_index (container, index));
+ }
+ else if (parent)
+ {
+ new_active = parent;
+ }
+ }
+
+ g_object_unref (item);
+
+ return new_active;
+}
+
+gboolean
+gimp_item_tree_reorder_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *new_parent,
+ gint new_index,
+ gboolean push_undo,
+ const gchar *undo_desc)
+{
+ GimpItemTreePrivate *private;
+ GimpContainer *container;
+ GimpContainer *new_container;
+ gint n_items;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), FALSE);
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type),
+ FALSE);
+ g_return_val_if_fail (gimp_item_get_tree (item) == tree, FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ G_TYPE_CHECK_INSTANCE_TYPE (new_parent,
+ private->item_type),
+ FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ gimp_item_get_tree (new_parent) == tree, FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (new_parent)),
+ FALSE);
+ g_return_val_if_fail (item != new_parent, FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ ! gimp_viewable_is_ancestor (GIMP_VIEWABLE (item),
+ GIMP_VIEWABLE (new_parent)),
+ FALSE);
+
+ container = gimp_item_get_container (item);
+
+ if (new_parent)
+ new_container = gimp_viewable_get_children (GIMP_VIEWABLE (new_parent));
+ else
+ new_container = tree->container;
+
+ n_items = gimp_container_get_n_children (new_container);
+
+ if (new_container == container)
+ n_items--;
+
+ new_index = CLAMP (new_index, 0, n_items);
+
+ if (new_container != container ||
+ new_index != gimp_item_get_index (item))
+ {
+ if (push_undo)
+ gimp_image_undo_push_item_reorder (private->image, undo_desc, item);
+
+ if (new_container != container)
+ {
+ g_object_ref (item);
+
+ gimp_container_remove (container, GIMP_OBJECT (item));
+
+ gimp_viewable_set_parent (GIMP_VIEWABLE (item),
+ GIMP_VIEWABLE (new_parent));
+
+ gimp_container_insert (new_container, GIMP_OBJECT (item), new_index);
+
+ g_object_unref (item);
+ }
+ else
+ {
+ gimp_container_reorder (container, GIMP_OBJECT (item), new_index);
+ }
+ }
+
+ return TRUE;
+}
+
+void
+gimp_item_tree_rename_item (GimpItemTree *tree,
+ GimpItem *item,
+ const gchar *new_name,
+ gboolean push_undo,
+ const gchar *undo_desc)
+{
+ GimpItemTreePrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM_TREE (tree));
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type));
+ g_return_if_fail (gimp_item_get_tree (item) == tree);
+ g_return_if_fail (new_name != NULL);
+
+ if (strcmp (new_name, gimp_object_get_name (item)))
+ {
+ if (push_undo)
+ gimp_image_undo_push_item_rename (gimp_item_get_image (item),
+ undo_desc, item);
+
+ gimp_item_tree_uniquefy_name (tree, item, new_name);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_item_tree_uniquefy_name (GimpItemTree *tree,
+ GimpItem *item,
+ const gchar *new_name)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ if (new_name)
+ {
+ g_hash_table_remove (private->name_hash,
+ gimp_object_get_name (item));
+
+ gimp_object_set_name (GIMP_OBJECT (item), new_name);
+ }
+
+ /* Remove any trailing whitespace. */
+ if (gimp_object_get_name (item))
+ {
+ gchar *name = g_strchomp (g_strdup (gimp_object_get_name (item)));
+
+ gimp_object_take_name (GIMP_OBJECT (item), name);
+ }
+
+ if (g_hash_table_lookup (private->name_hash,
+ gimp_object_get_name (item)))
+ {
+ gchar *name = g_strdup (gimp_object_get_name (item));
+ gchar *new_name = NULL;
+ gint number = 0;
+ gint precision = 1;
+ GRegex *end_numbers = g_regex_new (" ?#([0-9]+)\\s*$", 0, 0, NULL);
+ GMatchInfo *match_info = NULL;
+
+ if (g_regex_match (end_numbers, name, 0, &match_info))
+ {
+ gchar *match;
+ gint start_pos;
+
+ match = g_match_info_fetch (match_info, 1);
+ if (match && match[0] == '0')
+ {
+ precision = strlen (match);
+ }
+ number = atoi (match);
+ g_free (match);
+
+ g_match_info_fetch_pos (match_info, 0,
+ &start_pos, NULL);
+ name[start_pos] = '\0';
+ }
+ g_match_info_free (match_info);
+ g_regex_unref (end_numbers);
+
+ do
+ {
+ number++;
+
+ g_free (new_name);
+
+ new_name = g_strdup_printf ("%s #%.*d",
+ name,
+ precision,
+ number);
+ }
+ while (g_hash_table_lookup (private->name_hash, new_name));
+
+ g_free (name);
+
+ gimp_object_take_name (GIMP_OBJECT (item), new_name);
+ }
+
+ g_hash_table_insert (private->name_hash,
+ (gpointer) gimp_object_get_name (item),
+ item);
+}