From 18f5da95291ec99192d1b594cd3939a75328273d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Mon, 31 Jan 2011 22:04:37 +0100 Subject: Add an undo framework to LdDiagram. Modify LdDiagram and LdDiagramObject to use it. Trash commit c2403fdcf7885d7da1efa2c0dfb3e294f760ca9c. --- liblogdiag/ld-diagram-object.c | 418 ++++++++++++++--------------------------- liblogdiag/ld-diagram-object.h | 16 +- liblogdiag/ld-diagram-symbol.c | 8 +- liblogdiag/ld-diagram.c | 378 +++++++++++++++++++++++++++++++++---- liblogdiag/ld-diagram.h | 7 + liblogdiag/ld-marshal.list | 1 - 6 files changed, 495 insertions(+), 333 deletions(-) diff --git a/liblogdiag/ld-diagram-object.c b/liblogdiag/ld-diagram-object.c index 166aed1..7c83f34 100644 --- a/liblogdiag/ld-diagram-object.c +++ b/liblogdiag/ld-diagram-object.c @@ -31,6 +31,23 @@ struct _LdDiagramObjectPrivate JsonObject *storage; }; +typedef struct _SetParamActionData SetParamActionData; + +/* + * SetParamActionData: + * @self: the object this action has happened on. + * @param_name: the name of the parameter that has been changed. + * @old_node: the old node. + * @new_node: the new node. + */ +struct _SetParamActionData +{ + LdDiagramObject *self; + gchar *param_name; + JsonNode *old_node; + JsonNode *new_node; +}; + enum { PROP_0, @@ -45,7 +62,9 @@ static void ld_diagram_object_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void ld_diagram_object_dispose (GObject *gobject); -static const gchar **args_to_strv (const gchar *first_arg, va_list args); +static void on_set_param_undo (gpointer user_data); +static void on_set_param_redo (gpointer user_data); +static void on_set_param_destroy (gpointer user_data); G_DEFINE_TYPE (LdDiagramObject, ld_diagram_object, G_TYPE_OBJECT); @@ -92,19 +111,17 @@ ld_diagram_object_class_init (LdDiagramObjectClass *klass) g_object_class_install_property (object_class, PROP_Y, pspec); /** - * LdDiagramObject::data-changed: + * LdDiagramObject::changed: * @self: an #LdDiagramObject object. - * @path: path to the data. - * @old_value: (allow-none): the old value of data. - * @new_value: (allow-none): the new value of data. + * @action: an #LdUndoAction object. * - * Some data have been changed in internal storage. + * The object has been changed. */ - klass->data_changed_signal = g_signal_new - ("data-changed", G_TYPE_FROM_CLASS (klass), + klass->changed_signal = g_signal_new + ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - ld_marshal_VOID__BOXED_BOXED_BOXED, G_TYPE_NONE, 3, - G_TYPE_STRV, G_TYPE_VALUE, G_TYPE_VALUE); + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, LD_TYPE_UNDO_ACTION); g_type_class_add_private (klass, sizeof (LdDiagramObjectPrivate)); } @@ -121,7 +138,6 @@ ld_diagram_object_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { LdDiagramObject *self; - GValue tmp_value; self = LD_DIAGRAM_OBJECT (object); switch (property_id) @@ -131,10 +147,7 @@ ld_diagram_object_get_property (GObject *object, guint property_id, break; case PROP_X: case PROP_Y: - memset (&tmp_value, 0, sizeof (GValue)); - ld_diagram_object_get_data_for_param (self, &tmp_value, pspec); - g_value_copy (&tmp_value, value); - g_value_unset (&tmp_value); + ld_diagram_object_get_data_for_param (self, value, pspec); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -233,290 +246,81 @@ ld_diagram_object_set_storage (LdDiagramObject *self, JsonObject *storage) } /** - * ld_diagram_object_get_data: - * @self: an #LdDiagramObject object. - * @data: (out): an uninitialized storage for the data. - * @type: requested type of data. %G_TYPE_NONE for any. - * @first_element: the first element of path to the data. - * @...: optional remaining elements, followed by %NULL. - * - * Retrieve data from internal storage. - * - * Return value: %TRUE if successful. - */ -gboolean -ld_diagram_object_get_data (LdDiagramObject *self, - GValue *data, GType type, const gchar *first_element, ...) -{ - va_list args; - gboolean result; - - va_start (args, first_element); - result = ld_diagram_object_get_data_valist (self, - data, type, first_element, args); - va_end (args); - return result; -} - -/** - * ld_diagram_object_set_data: - * @self: an #LdDiagramObject object. - * @data: (allow-none): the data. %NULL just removes the current data. - * @first_element: the first element of path where the data will be stored. - * @...: optional remaining elements, followed by %NULL. - * - * Put data into internal storage. - */ -void -ld_diagram_object_set_data (LdDiagramObject *self, - const GValue *data, const gchar *first_element, ...) -{ - va_list args; - - va_start (args, first_element); - ld_diagram_object_set_data_valist (self, data, first_element, args); - va_end (args); -} - -/** - * ld_diagram_object_get_data_valist: - * @self: an #LdDiagramObject object. - * @data: (out): an uninitialized storage for the data. - * @type: requested type of data. %G_TYPE_NONE for any. - * @first_element: the first element of path to the data. - * @var_args: optional remaining elements, followed by %NULL. - * - * Retrieve data from internal storage. - * - * Return value: %TRUE if successful. - */ -gboolean -ld_diagram_object_get_data_valist (LdDiagramObject *self, - GValue *data, GType type, const gchar *first_element, va_list var_args) -{ - const gchar **elements; - gboolean result; - - elements = args_to_strv (first_element, var_args); - result = ld_diagram_object_get_datav (self, data, type, elements); - g_free (elements); - return result; -} - -/** - * ld_diagram_object_set_data_valist: + * ld_diagram_object_changed: * @self: an #LdDiagramObject object. - * @data: (allow-none): the data. %NULL just removes the current data. - * @first_element: the first element of path where the data will be stored. - * @var_args: optional remaining elements, followed by %NULL. + * @action: an #LdUndoAction object specifying the change. * - * Put data into internal storage. + * Emit the #LdDiagramObject::changed signal. */ void -ld_diagram_object_set_data_valist (LdDiagramObject *self, - const GValue *data, const gchar *first_element, va_list var_args) -{ - const gchar **elements; - - elements = args_to_strv (first_element, var_args); - ld_diagram_object_set_datav (self, data, elements); - g_free (elements); -} - -static const gchar ** -args_to_strv (const gchar *first_arg, va_list args) +ld_diagram_object_changed (LdDiagramObject *self, LdUndoAction *action) { - const gchar **strv, *arg; - size_t strv_len = 0, strv_size = 8; - - strv = g_malloc (strv_size * sizeof (gchar *)); - for (arg = first_arg; ; arg = va_arg (args, const gchar *)) - { - if (strv_len == strv_size) - strv = g_realloc (strv, (strv_size <<= 1) * sizeof (gchar *)); - strv[strv_len++] = arg; - - if (!arg) - break; - } - return strv; -} - -/** - * ld_diagram_object_get_datav: - * @self: an #LdDiagramObject object. - * @data: (out): an uninitialized storage for the data. - * @type: requested type of data. %G_TYPE_NONE for any. - * @elements: an array of elements of path to the data, terminated by %NULL. - * - * Retrieve data from internal storage. - * - * Return value: %TRUE if successful. - */ -gboolean -ld_diagram_object_get_datav (LdDiagramObject *self, - GValue *data, GType type, const gchar **elements) -{ - JsonObject *object; - JsonNode *node; - guint i; - - g_return_val_if_fail (LD_IS_DIAGRAM_OBJECT (self), FALSE); - g_return_val_if_fail (data != NULL, FALSE); - g_return_val_if_fail (elements != NULL && *elements, FALSE); - - object = ld_diagram_object_get_storage (self); - node = json_object_get_member (object, elements[0]); - for (i = 1; elements[i]; i++) - { - if (!node) - return FALSE; - if (!JSON_NODE_HOLDS_OBJECT (node)) - { - g_warning ("%s: unable to get a member of a non-object node", - G_STRFUNC); - return FALSE; - } - object = json_node_get_object (node); - node = json_object_get_member (object, elements[i]); - } - if (!node) - return FALSE; - if (!JSON_NODE_HOLDS_VALUE (node)) - { - g_warning ("%s: unable to read from a non-value node", G_STRFUNC); - return FALSE; - } - - if (type == G_TYPE_NONE) - { - json_node_get_value (node, data); - return TRUE; - } - if (g_value_type_transformable (json_node_get_value_type (node), type)) - { - GValue json_value; - - memset (&json_value, 0, sizeof (GValue)); - json_node_get_value (node, &json_value); - g_value_init (data, type); - g_value_transform (&json_value, data); - g_value_unset (&json_value); - return TRUE; - } - g_warning ("%s: unable to get value of type `%s' from node of type `%s'", - G_STRFUNC, g_type_name (type), json_node_type_name (node)); - return FALSE; -} - -/** - * ld_diagram_object_set_datav: - * @self: an #LdDiagramObject object. - * @data: (allow-none): the data. %NULL just removes the current data. - * @elements: an array of elements of path where the data will be stored, - * terminated by %NULL. - * - * Put data into internal storage. - */ -void ld_diagram_object_set_datav (LdDiagramObject *self, - const GValue *data, const gchar **elements) -{ - GValue tmp_value, *old_value; - JsonObject *object, *new_object; - JsonNode *node, *new_node; - const gchar *last_element; - guint i; - g_return_if_fail (LD_IS_DIAGRAM_OBJECT (self)); - g_return_if_fail (!data || G_IS_VALUE (data)); - g_return_if_fail (elements != NULL && *elements); - - object = ld_diagram_object_get_storage (self); - node = json_object_get_member (object, elements[0]); - last_element = elements[0]; - for (i = 1; elements[i]; i++) - { - if (!node || JSON_NODE_HOLDS_NULL (node)) - { - new_object = json_object_new (); - json_object_set_object_member (object, last_element, new_object); - object = new_object; - node = NULL; - } - else if (!JSON_NODE_HOLDS_OBJECT (node)) - { - g_warning ("%s: unable to get a member of a non-object node", - G_STRFUNC); - return; - } - else - { - object = json_node_get_object (node); - node = json_object_get_member (object, elements[i]); - } - last_element = elements[i]; - } + g_return_if_fail (LD_IS_UNDO_ACTION (action)); - if (!node || JSON_NODE_HOLDS_NULL (node)) - old_value = NULL; - else if (!JSON_NODE_HOLDS_VALUE (node)) - { - g_warning ("%s: unable to replace a non-value node", G_STRFUNC); - return; - } - else - { - memset (&tmp_value, 0, sizeof (GValue)); - json_node_get_value (node, &tmp_value); - old_value = &tmp_value; - } - - /* We have to remove it first due to a bug in json-glib. */ - json_object_remove_member (object, last_element); - if (data) - { - new_node = json_node_new (JSON_NODE_VALUE); - json_node_set_value (new_node, data); - json_object_set_member (object, last_element, new_node); - } - - if (old_value || data) - g_signal_emit (self, LD_DIAGRAM_OBJECT_GET_CLASS (self) - ->data_changed_signal, 0, old_value, data); - if (old_value) - g_value_unset (old_value); + g_signal_emit (self, LD_DIAGRAM_OBJECT_GET_CLASS (self)->changed_signal, 0, + action); } /** * ld_diagram_object_get_data_for_param: * @self: an #LdDiagramObject object. - * @data: (out): an uninitialized storage for the data. + * @data: (out): where the data will be stored. * @pspec: the parameter to read data for. This must be a property of @self. * * Retrieve data for a parameter from internal storage. If there's no data * corresponding to this parameter, the value is set to the default. + * This method invokes ld_diagram_object_changed(). */ void ld_diagram_object_get_data_for_param (LdDiagramObject *self, GValue *data, GParamSpec *pspec) { - const gchar *elements[2]; + JsonObject *storage; + JsonNode *node; + const gchar *name; + GValue json_value; + gboolean result; g_return_if_fail (LD_IS_DIAGRAM_OBJECT (self)); - g_return_if_fail (data != NULL); + g_return_if_fail (G_IS_VALUE (data)); g_return_if_fail (G_IS_PARAM_SPEC (pspec)); - g_return_if_fail (g_type_is_a (pspec->owner_type, LD_TYPE_DIAGRAM_OBJECT)); - elements[0] = g_param_spec_get_name (pspec); - elements[1] = NULL; - if (!ld_diagram_object_get_datav (self, data, pspec->value_type, elements)) - { - g_value_init (data, pspec->value_type); - g_param_value_set_default (pspec, data); - g_object_set_property (G_OBJECT (self), elements[0], data); - } + storage = ld_diagram_object_get_storage (self); + name = g_param_spec_get_name (pspec); + node = json_object_get_member (storage, name); + if (!node || json_node_is_null (node)) + goto ld_diagram_object_get_data_default; + if (!JSON_NODE_HOLDS_VALUE (node)) + goto ld_diagram_object_get_data_warn; + + memset (&json_value, 0, sizeof (json_value)); + json_node_get_value (node, &json_value); + result = g_param_value_convert (pspec, &json_value, data, FALSE); + g_value_unset (&json_value); + if (result) + return; + +ld_diagram_object_get_data_warn: + g_warning ("%s: unable to get parameter `%s' of type `%s'" + " from node of type `%s'; setting the parameter to it's default value", + G_STRFUNC, name, G_PARAM_SPEC_TYPE_NAME (pspec), + json_node_type_name (node)); + +ld_diagram_object_get_data_default: + g_param_value_set_default (pspec, data); + g_object_set_property (G_OBJECT (self), name, data); } +/* We have to remove it first due to a bug in json-glib. */ +#define json_object_set_member(object, name, node) \ + G_STMT_START \ + { \ + json_object_remove_member (object, name); \ + json_object_set_member (object, name, node); \ + } \ + G_STMT_END + /** * ld_diagram_object_set_data_for_param: * @self: an #LdDiagramObject object. @@ -529,15 +333,77 @@ void ld_diagram_object_set_data_for_param (LdDiagramObject *self, const GValue *data, GParamSpec *pspec) { - const gchar *elements[2]; + LdUndoAction *action; + SetParamActionData *action_data; + JsonObject *storage; + const gchar *name; + JsonNode *node; g_return_if_fail (LD_IS_DIAGRAM_OBJECT (self)); g_return_if_fail (G_IS_VALUE (data)); g_return_if_fail (G_IS_PARAM_SPEC (pspec)); - elements[0] = g_param_spec_get_name (pspec); - elements[1] = NULL; - ld_diagram_object_set_datav (self, data, elements); + storage = ld_diagram_object_get_storage (self); + name = g_param_spec_get_name (pspec); + + action_data = g_slice_new (SetParamActionData); + action_data->self = g_object_ref (self); + action_data->param_name = g_strdup (g_param_spec_get_name (pspec)); + + node = json_object_get_member (storage, name); + action_data->old_node = node ? json_node_copy (node) : NULL; + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, data); + action_data->new_node = json_node_copy (node); + + json_object_set_member (storage, name, node); + + action = ld_undo_action_new (on_set_param_undo, on_set_param_redo, + on_set_param_destroy, action_data); + ld_diagram_object_changed (self, action); + g_object_unref (action); +} + +static void +on_set_param_undo (gpointer user_data) +{ + SetParamActionData *data; + JsonObject *storage; + + data = user_data; + storage = ld_diagram_object_get_storage (data->self); + + json_object_set_member (storage, data->param_name, + json_node_copy (data->old_node)); +} + +static void +on_set_param_redo (gpointer user_data) +{ + SetParamActionData *data; + JsonObject *storage; + + data = user_data; + storage = ld_diagram_object_get_storage (data->self); + + json_object_set_member (storage, data->param_name, + json_node_copy (data->new_node)); +} + +static void +on_set_param_destroy (gpointer user_data) +{ + SetParamActionData *data; + + data = user_data; + g_object_unref (data->self); + g_free (data->param_name); + if (data->old_node) + json_node_free (data->old_node); + if (data->new_node) + json_node_free (data->new_node); + g_slice_free (SetParamActionData, data); } /** diff --git a/liblogdiag/ld-diagram-object.h b/liblogdiag/ld-diagram-object.h index 5c82f9c..91b01ab 100644 --- a/liblogdiag/ld-diagram-object.h +++ b/liblogdiag/ld-diagram-object.h @@ -49,7 +49,7 @@ struct _LdDiagramObjectClass /*< private >*/ GObjectClass parent_class; - guint data_changed_signal; + guint changed_signal; }; @@ -58,19 +58,7 @@ GType ld_diagram_object_get_type (void) G_GNUC_CONST; LdDiagramObject *ld_diagram_object_new (JsonObject *storage); JsonObject *ld_diagram_object_get_storage (LdDiagramObject *self); void ld_diagram_object_set_storage (LdDiagramObject *self, JsonObject *storage); - -gboolean ld_diagram_object_get_data (LdDiagramObject *self, - GValue *data, GType type, const gchar *first_element, ...); -gboolean ld_diagram_object_get_data_valist (LdDiagramObject *self, - GValue *data, GType type, const gchar *first_element, va_list var_args); -gboolean ld_diagram_object_get_datav (LdDiagramObject *self, - GValue *data, GType type, const gchar **elements); -void ld_diagram_object_set_data (LdDiagramObject *self, - const GValue *data, const gchar *first_element, ...); -void ld_diagram_object_set_data_valist (LdDiagramObject *self, - const GValue *data, const gchar *first_element, va_list var_args); -void ld_diagram_object_set_datav (LdDiagramObject *self, - const GValue *data, const gchar **elements); +void ld_diagram_object_changed (LdDiagramObject *self, LdUndoAction *action); void ld_diagram_object_get_data_for_param (LdDiagramObject *self, GValue *data, GParamSpec *pspec); diff --git a/liblogdiag/ld-diagram-symbol.c b/liblogdiag/ld-diagram-symbol.c index f0a47e5..370d6ec 100644 --- a/liblogdiag/ld-diagram-symbol.c +++ b/liblogdiag/ld-diagram-symbol.c @@ -8,8 +8,6 @@ * */ -#include - #include "liblogdiag.h" #include "config.h" @@ -67,16 +65,12 @@ ld_diagram_symbol_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { LdDiagramObject *self; - GValue tmp_value; self = LD_DIAGRAM_OBJECT (object); switch (property_id) { case PROP_CLASS: - memset (&tmp_value, 0, sizeof (GValue)); - ld_diagram_object_get_data_for_param (self, &tmp_value, pspec); - g_value_copy (&tmp_value, value); - g_value_unset (&tmp_value); + ld_diagram_object_get_data_for_param (self, value, pspec); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); diff --git a/liblogdiag/ld-diagram.c b/liblogdiag/ld-diagram.c index cd1fcdf..ba20762 100644 --- a/liblogdiag/ld-diagram.c +++ b/liblogdiag/ld-diagram.c @@ -23,6 +23,12 @@ /* * LdDiagramPrivate: * @modified: whether the diagram has been modified. + * @lock_history: whether the history stacks are currently locked. + * @in_user_action: how many times a user action has been initiated. + * @undo_stack: a stack of actions that can be undone, + * each containing a #GList of #LdUndoAction subactions. + * @redo_stack: a stack of undone actions that can be redone, + * each containing a #GList of #LdUndoAction subactions. * @objects: all objects in the diagram. * @selection: all currently selected objects. * @connections: connections between objects. @@ -30,16 +36,37 @@ struct _LdDiagramPrivate { gboolean modified; + gboolean lock_history; + guint in_user_action; + GList *undo_stack; + GList *redo_stack; GList *objects; GList *selection; GList *connections; }; +typedef struct _ObjectActionData ObjectActionData; + +/* + * ObjectActionData: + * @self: an #LdDiagram object. + * @object: an #LdDiagramObject object. + * @pos: the position at which the object has been inserted or removed. + */ +struct _ObjectActionData +{ + LdDiagram *self; + LdDiagramObject *object; + gint pos; +}; + enum { PROP_0, - PROP_MODIFIED + PROP_MODIFIED, + PROP_CAN_UNDO, + PROP_CAN_REDO }; static void ld_diagram_get_property (GObject *object, guint property_id, @@ -48,9 +75,7 @@ static void ld_diagram_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void ld_diagram_dispose (GObject *gobject); static void ld_diagram_finalize (GObject *gobject); - -static void on_object_data_changed (LdDiagramObject *self, - gchar **path, GValue *old_value, GValue *new_value, gpointer user_data); +static void ld_diagram_real_changed (LdDiagram *self); static gboolean write_signature (GOutputStream *stream, GError **error); @@ -64,9 +89,20 @@ static JsonNode *serialize_diagram (LdDiagram *self); static JsonNode *serialize_object (LdDiagramObject *object); static const gchar *get_object_class_string (GType type); +static void push_undo_action (LdDiagram *self, LdUndoAction *action); +static void destroy_action_stack (GList **stack); + +static void on_object_changed (LdDiagramObject *object, + LdUndoAction *action, gpointer user_data); +static void on_object_notify_storage (LdDiagramObject *object, + GParamSpec *pspec, gpointer user_data); + +static void on_object_action_insert (gpointer user_data); +static void on_object_action_remove (gpointer user_data); +static void on_object_action_destroy (gpointer user_data); + static void install_object (LdDiagramObject *object, LdDiagram *self); static void uninstall_object (LdDiagramObject *object, LdDiagram *self); -static void ld_diagram_real_changed (LdDiagram *self); static void ld_diagram_unselect_all_internal (LdDiagram *self); @@ -96,6 +132,26 @@ ld_diagram_class_init (LdDiagramClass *klass) FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_MODIFIED, pspec); +/** + * LdDiagram:can-undo: + * + * Whether any action can be undone. + */ + pspec = g_param_spec_boolean ("can-undo", "Can undo", + "Whether any action can be undone.", + FALSE, G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_CAN_UNDO, pspec); + +/** + * LdDiagram:can-redo: + * + * Whether any undone action can be redone. + */ + pspec = g_param_spec_boolean ("can-redo", "Can redo", + "Whether any undone action can be redone.", + FALSE, G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_CAN_REDO, pspec); + /** * LdDiagram::changed: * @self: an #LdDiagram object. @@ -142,6 +198,12 @@ ld_diagram_get_property (GObject *object, guint property_id, case PROP_MODIFIED: g_value_set_boolean (value, ld_diagram_get_modified (self)); break; + case PROP_CAN_UNDO: + g_value_set_boolean (value, ld_diagram_can_undo (self)); + break; + case PROP_CAN_REDO: + g_value_set_boolean (value, ld_diagram_can_redo (self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -220,7 +282,7 @@ ld_diagram_new (void) * ld_diagram_clear: * @self: an #LdDiagram object. * - * Clear the whole diagram with it's objects and selection. + * Clear the whole diagram, including it's objects and history. */ void ld_diagram_clear (LdDiagram *self) @@ -250,6 +312,12 @@ ld_diagram_clear (LdDiagram *self) changed = TRUE; } + destroy_action_stack (&self->priv->undo_stack); + destroy_action_stack (&self->priv->redo_stack); + + g_object_notify (G_OBJECT (self), "can-undo"); + g_object_notify (G_OBJECT (self), "can-redo"); + if (changed) g_signal_emit (self, LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); @@ -264,7 +332,7 @@ ld_diagram_clear (LdDiagram *self) * @filename: a filename. * @error: (allow-none): return location for a #GError, or %NULL. * - * Load a file into the diagram. + * Clear the diagram and load a file into it. * * Return value: %TRUE if the file could be loaded, %FALSE otherwise. */ @@ -291,8 +359,13 @@ ld_diagram_load_from_file (LdDiagram *self, ld_diagram_clear (self); + self->priv->lock_history = TRUE; + local_error = NULL; deserialize_diagram (self, json_parser_get_root (parser), &local_error); + + self->priv->lock_history = FALSE; + g_object_unref (parser); if (local_error) { @@ -539,23 +612,235 @@ ld_diagram_set_modified (LdDiagram *self, gboolean value) } static void -on_object_data_changed (LdDiagramObject *self, gchar **path, - GValue *old_value, GValue *new_value, gpointer user_data) +on_object_changed (LdDiagramObject *object, + LdUndoAction *action, gpointer user_data) +{ + LdDiagram *self; + + self = LD_DIAGRAM (user_data); + push_undo_action (self, action); + + g_signal_emit (self, + LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); +} + +static void +on_object_notify_storage (LdDiagramObject *object, + GParamSpec *pspec, gpointer user_data) +{ + g_warning ("storage of a diagram object has changed"); +} + +/** + * ld_diagram_can_undo: + * @self: an #LdDiagram object. + * + * Return value: whether any action can be undone. + */ +gboolean +ld_diagram_can_undo (LdDiagram *self) +{ + return self->priv->undo_stack != NULL; +} + +/** + * ld_diagram_can_redo: + * @self: an #LdDiagram object. + * + * Return value: whether any undone action can be redone. + */ +gboolean +ld_diagram_can_redo (LdDiagram *self) +{ + return self->priv->redo_stack != NULL; +} + +static void +push_undo_action (LdDiagram *self, LdUndoAction *action) +{ + GList **undo_list; + + if (self->priv->lock_history) + return; + if (self->priv->redo_stack) + destroy_action_stack (&self->priv->redo_stack); + + if (!self->priv->in_user_action) + self->priv->undo_stack = g_list_prepend (self->priv->undo_stack, NULL); + undo_list = (GList **) &self->priv->undo_stack->data; + + g_object_ref (action); + *undo_list = g_list_prepend (*undo_list, action); + + g_object_notify (G_OBJECT (self), "can-undo"); + g_object_notify (G_OBJECT (self), "can-redo"); +} + +static void +destroy_action_stack (GList **stack) +{ + GList *action, *sub; + + for (action = *stack; action; action = g_list_next (action)) + { + for (sub = action->data; sub; sub = g_list_next (sub)) + g_object_unref (sub->data); + g_list_free (action->data); + } + g_list_free (*stack); + *stack = NULL; +} + +/** + * ld_diagram_undo: + * @self: an #LdDiagram object. + * + * Undo the last action. + */ +void +ld_diagram_undo (LdDiagram *self) +{ + GList *action, *sub; + + g_return_if_fail (LD_IS_DIAGRAM (self)); + g_return_if_fail (self->priv->in_user_action == 0); + + if (!self->priv->undo_stack) + return; + + self->priv->lock_history = TRUE; + + action = self->priv->undo_stack; + self->priv->undo_stack = g_list_remove_link (action, action); + for (sub = g_list_last (action->data); sub; sub = g_list_previous (sub)) + ld_undo_action_undo (sub->data); + self->priv->redo_stack = g_list_concat (action, self->priv->redo_stack); + + self->priv->lock_history = FALSE; + + g_object_notify (G_OBJECT (self), "can-undo"); + g_object_notify (G_OBJECT (self), "can-redo"); + + g_signal_emit (self, + LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); +} + +/** + * ld_diagram_redo: + * @self: an #LdDiagram object. + * + * Redo the last undone action. + */ +void +ld_diagram_redo (LdDiagram *self) +{ + GList *action, *sub; + + g_return_if_fail (LD_IS_DIAGRAM (self)); + g_return_if_fail (self->priv->in_user_action == 0); + + if (!self->priv->redo_stack) + return; + + self->priv->lock_history = TRUE; + + action = self->priv->redo_stack; + self->priv->redo_stack = g_list_remove_link (action, action); + for (sub = g_list_last (action->data); sub; sub = g_list_previous (sub)) + ld_undo_action_redo (sub->data); + self->priv->undo_stack = g_list_concat (action, self->priv->undo_stack); + + self->priv->lock_history = FALSE; + + g_object_notify (G_OBJECT (self), "can-undo"); + g_object_notify (G_OBJECT (self), "can-redo"); + + g_signal_emit (self, + LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); +} + +/** + * ld_diagram_begin_user_action: + * @self: an #LdDiagram object. + * + * Begin an indivisible user action. This function can be called + * multiple times. Each call has to be ended with a call to + * ld_diagram_end_user_action(). + */ +void +ld_diagram_begin_user_action (LdDiagram *self) +{ + g_return_if_fail (LD_IS_DIAGRAM (self)); + + /* Push an empty action on the stack. */ + if (!self->priv->in_user_action++) + self->priv->undo_stack = g_list_prepend (self->priv->undo_stack, NULL); +} + +/** + * ld_diagram_end_user_action: + * @self: an #LdDiagram object. + * + * End an indivisible user action. + */ +void +ld_diagram_end_user_action (LdDiagram *self) +{ + g_return_if_fail (LD_IS_DIAGRAM (self)); + g_return_if_fail (self->priv->in_user_action > 0); + + /* If the action on the stack is empty, discard it. */ + if (!--self->priv->in_user_action && !self->priv->undo_stack->data) + self->priv->undo_stack = g_list_delete_link + (self->priv->undo_stack, self->priv->undo_stack); +} + +static void +on_object_action_remove (gpointer user_data) { + ObjectActionData *data; + + data = user_data; + ld_diagram_remove_object (data->self, data->object); +} + +static void +on_object_action_insert (gpointer user_data) +{ + ObjectActionData *data; + + data = user_data; + ld_diagram_insert_object (data->self, data->object, data->pos); +} + +static void +on_object_action_destroy (gpointer user_data) +{ + ObjectActionData *data; + + data = user_data; + g_object_unref (data->self); + g_object_unref (data->object); + g_slice_free (ObjectActionData, data); } static void install_object (LdDiagramObject *object, LdDiagram *self) { - g_signal_connect (object, "data-changed", - G_CALLBACK (on_object_data_changed), self); + g_signal_connect (object, "changed", + G_CALLBACK (on_object_changed), self); + g_signal_connect (object, "notify::storage", + G_CALLBACK (on_object_notify_storage), self); g_object_ref (object); } static void uninstall_object (LdDiagramObject *object, LdDiagram *self) { - g_signal_handlers_disconnect_by_func (object, on_object_data_changed, self); + g_signal_handlers_disconnect_by_func (object, + on_object_changed, self); + g_signal_handlers_disconnect_by_func (object, + on_object_notify_storage, self); g_object_unref (object); } @@ -584,6 +869,9 @@ ld_diagram_get_objects (LdDiagram *self) void ld_diagram_insert_object (LdDiagram *self, LdDiagramObject *object, gint pos) { + LdUndoAction *action; + ObjectActionData *action_data; + g_return_if_fail (LD_IS_DIAGRAM (self)); g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object)); @@ -593,6 +881,16 @@ ld_diagram_insert_object (LdDiagram *self, LdDiagramObject *object, gint pos) self->priv->objects = g_list_insert (self->priv->objects, object, pos); install_object (object, self); + action_data = g_slice_new (ObjectActionData); + action_data->self = g_object_ref (self); + action_data->object = g_object_ref (object); + action_data->pos = pos; + + action = ld_undo_action_new (on_object_action_remove, + on_object_action_insert, on_object_action_destroy, action_data); + push_undo_action (self, action); + g_object_unref (action); + g_signal_emit (self, LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); } @@ -607,17 +905,39 @@ ld_diagram_insert_object (LdDiagram *self, LdDiagramObject *object, gint pos) void ld_diagram_remove_object (LdDiagram *self, LdDiagramObject *object) { + LdUndoAction *action; + ObjectActionData *action_data; + guint pos; + GList *link; + g_return_if_fail (LD_IS_DIAGRAM (self)); g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object)); - if (!g_list_find (self->priv->objects, object)) + pos = 0; + for (link = self->priv->objects; link; link = g_list_next (link)) + { + if (link->data == object) + break; + pos++; + } + if (!link) return; ld_diagram_unselect (self, object); - self->priv->objects = g_list_remove (self->priv->objects, object); + self->priv->objects = g_list_delete_link (self->priv->objects, link); uninstall_object (object, self); + action_data = g_slice_new (ObjectActionData); + action_data->self = g_object_ref (self); + action_data->object = g_object_ref (object); + action_data->pos = pos; + + action = ld_undo_action_new (on_object_action_insert, + on_object_action_remove, on_object_action_destroy, action_data); + push_undo_action (self, action); + g_object_unref (action); + g_signal_emit (self, LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); } @@ -645,32 +965,20 @@ ld_diagram_get_selection (LdDiagram *self) void ld_diagram_remove_selection (LdDiagram *self) { - LdDiagramObject *object; - gboolean changed; - GList *iter; + GList *selection_copy, *iter; g_return_if_fail (LD_IS_DIAGRAM (self)); - for (iter = self->priv->selection; iter; iter = g_list_next (iter)) - { - object = LD_DIAGRAM_OBJECT (iter->data); - g_object_unref (object); - - self->priv->objects = g_list_remove (self->priv->objects, object); - uninstall_object (object, self); - } + /* We still retain references in the object list. */ + selection_copy = g_list_copy (self->priv->selection); + ld_diagram_unselect_all (self); - changed = self->priv->selection != NULL; - g_list_free (self->priv->selection); - self->priv->selection = NULL; + ld_diagram_begin_user_action (self); + for (iter = selection_copy; iter; iter = g_list_next (iter)) + ld_diagram_remove_object (self, LD_DIAGRAM_OBJECT (iter->data)); + ld_diagram_end_user_action (self); - if (changed) - { - g_signal_emit (self, - LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); - g_signal_emit (self, - LD_DIAGRAM_GET_CLASS (self)->selection_changed_signal, 0); - } + g_list_free (selection_copy); } /** diff --git a/liblogdiag/ld-diagram.h b/liblogdiag/ld-diagram.h index c530121..52f0453 100644 --- a/liblogdiag/ld-diagram.h +++ b/liblogdiag/ld-diagram.h @@ -90,6 +90,13 @@ gboolean ld_diagram_save_to_file (LdDiagram *self, gboolean ld_diagram_get_modified (LdDiagram *self); void ld_diagram_set_modified (LdDiagram *self, gboolean value); +gboolean ld_diagram_can_undo (LdDiagram *self); +gboolean ld_diagram_can_redo (LdDiagram *self); +void ld_diagram_undo (LdDiagram *self); +void ld_diagram_redo (LdDiagram *self); +void ld_diagram_begin_user_action (LdDiagram *self); +void ld_diagram_end_user_action (LdDiagram *self); + GList *ld_diagram_get_objects (LdDiagram *self); void ld_diagram_insert_object (LdDiagram *self, LdDiagramObject *object, gint pos); diff --git a/liblogdiag/ld-marshal.list b/liblogdiag/ld-marshal.list index 01a0328..20c67c0 100644 --- a/liblogdiag/ld-marshal.list +++ b/liblogdiag/ld-marshal.list @@ -1,3 +1,2 @@ VOID:OBJECT,OBJECT VOID:OBJECT,STRING -VOID:BOXED,BOXED,BOXED -- cgit v1.2.3