Add an undo framework to LdDiagram.

Modify LdDiagram and LdDiagramObject to use it.

Trash commit c2403fdcf7.
This commit is contained in:
Přemysl Eric Janouch 2011-01-31 22:04:37 +01:00
parent caf06ff4e8
commit 18f5da9529
6 changed files with 495 additions and 333 deletions

View File

@ -31,6 +31,23 @@ struct _LdDiagramObjectPrivate
JsonObject *storage; 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 enum
{ {
PROP_0, PROP_0,
@ -45,7 +62,9 @@ static void ld_diagram_object_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec); const GValue *value, GParamSpec *pspec);
static void ld_diagram_object_dispose (GObject *gobject); 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); 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); g_object_class_install_property (object_class, PROP_Y, pspec);
/** /**
* LdDiagramObject::data-changed: * LdDiagramObject::changed:
* @self: an #LdDiagramObject object. * @self: an #LdDiagramObject object.
* @path: path to the data. * @action: an #LdUndoAction object.
* @old_value: (allow-none): the old value of data.
* @new_value: (allow-none): the new value of data.
* *
* Some data have been changed in internal storage. * The object has been changed.
*/ */
klass->data_changed_signal = g_signal_new klass->changed_signal = g_signal_new
("data-changed", G_TYPE_FROM_CLASS (klass), ("changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, G_SIGNAL_RUN_LAST, 0, NULL, NULL,
ld_marshal_VOID__BOXED_BOXED_BOXED, G_TYPE_NONE, 3, g_cclosure_marshal_VOID__OBJECT,
G_TYPE_STRV, G_TYPE_VALUE, G_TYPE_VALUE); G_TYPE_NONE, 1, LD_TYPE_UNDO_ACTION);
g_type_class_add_private (klass, sizeof (LdDiagramObjectPrivate)); 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) GValue *value, GParamSpec *pspec)
{ {
LdDiagramObject *self; LdDiagramObject *self;
GValue tmp_value;
self = LD_DIAGRAM_OBJECT (object); self = LD_DIAGRAM_OBJECT (object);
switch (property_id) switch (property_id)
@ -131,10 +147,7 @@ ld_diagram_object_get_property (GObject *object, guint property_id,
break; break;
case PROP_X: case PROP_X:
case PROP_Y: case PROP_Y:
memset (&tmp_value, 0, sizeof (GValue)); ld_diagram_object_get_data_for_param (self, value, pspec);
ld_diagram_object_get_data_for_param (self, &tmp_value, pspec);
g_value_copy (&tmp_value, value);
g_value_unset (&tmp_value);
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 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: * ld_diagram_object_changed:
* @self: an #LdDiagramObject object. * @self: an #LdDiagramObject object.
* @data: (out): an uninitialized storage for the data. * @action: an #LdUndoAction object specifying the change.
* @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. * Emit the #LdDiagramObject::changed signal.
*
* 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 void
ld_diagram_object_set_data (LdDiagramObject *self, ld_diagram_object_changed (LdDiagramObject *self, LdUndoAction *action)
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:
* @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.
*
* Put data into internal storage.
*/
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)
{
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 (LD_IS_DIAGRAM_OBJECT (self));
g_return_if_fail (!data || G_IS_VALUE (data)); g_return_if_fail (LD_IS_UNDO_ACTION (action));
g_return_if_fail (elements != NULL && *elements);
object = ld_diagram_object_get_storage (self); g_signal_emit (self, LD_DIAGRAM_OBJECT_GET_CLASS (self)->changed_signal, 0,
node = json_object_get_member (object, elements[0]); action);
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];
}
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);
} }
/** /**
* ld_diagram_object_get_data_for_param: * ld_diagram_object_get_data_for_param:
* @self: an #LdDiagramObject object. * @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. * @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 * Retrieve data for a parameter from internal storage. If there's no data
* corresponding to this parameter, the value is set to the default. * corresponding to this parameter, the value is set to the default.
* This method invokes ld_diagram_object_changed().
*/ */
void void
ld_diagram_object_get_data_for_param (LdDiagramObject *self, ld_diagram_object_get_data_for_param (LdDiagramObject *self,
GValue *data, GParamSpec *pspec) 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 (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_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); storage = ld_diagram_object_get_storage (self);
elements[1] = NULL; name = g_param_spec_get_name (pspec);
if (!ld_diagram_object_get_datav (self, data, pspec->value_type, elements)) node = json_object_get_member (storage, name);
{ if (!node || json_node_is_null (node))
g_value_init (data, pspec->value_type); 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_param_value_set_default (pspec, data);
g_object_set_property (G_OBJECT (self), elements[0], 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: * ld_diagram_object_set_data_for_param:
* @self: an #LdDiagramObject object. * @self: an #LdDiagramObject object.
@ -529,15 +333,77 @@ void
ld_diagram_object_set_data_for_param (LdDiagramObject *self, ld_diagram_object_set_data_for_param (LdDiagramObject *self,
const GValue *data, GParamSpec *pspec) 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 (LD_IS_DIAGRAM_OBJECT (self));
g_return_if_fail (G_IS_VALUE (data)); g_return_if_fail (G_IS_VALUE (data));
g_return_if_fail (G_IS_PARAM_SPEC (pspec)); g_return_if_fail (G_IS_PARAM_SPEC (pspec));
elements[0] = g_param_spec_get_name (pspec); storage = ld_diagram_object_get_storage (self);
elements[1] = NULL; name = g_param_spec_get_name (pspec);
ld_diagram_object_set_datav (self, data, elements);
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);
} }
/** /**

View File

@ -49,7 +49,7 @@ struct _LdDiagramObjectClass
/*< private >*/ /*< private >*/
GObjectClass parent_class; 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); LdDiagramObject *ld_diagram_object_new (JsonObject *storage);
JsonObject *ld_diagram_object_get_storage (LdDiagramObject *self); JsonObject *ld_diagram_object_get_storage (LdDiagramObject *self);
void ld_diagram_object_set_storage (LdDiagramObject *self, JsonObject *storage); void ld_diagram_object_set_storage (LdDiagramObject *self, JsonObject *storage);
void ld_diagram_object_changed (LdDiagramObject *self, LdUndoAction *action);
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_get_data_for_param (LdDiagramObject *self, void ld_diagram_object_get_data_for_param (LdDiagramObject *self,
GValue *data, GParamSpec *pspec); GValue *data, GParamSpec *pspec);

View File

@ -8,8 +8,6 @@
* *
*/ */
#include <string.h>
#include "liblogdiag.h" #include "liblogdiag.h"
#include "config.h" #include "config.h"
@ -67,16 +65,12 @@ ld_diagram_symbol_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec) GValue *value, GParamSpec *pspec)
{ {
LdDiagramObject *self; LdDiagramObject *self;
GValue tmp_value;
self = LD_DIAGRAM_OBJECT (object); self = LD_DIAGRAM_OBJECT (object);
switch (property_id) switch (property_id)
{ {
case PROP_CLASS: case PROP_CLASS:
memset (&tmp_value, 0, sizeof (GValue)); ld_diagram_object_get_data_for_param (self, value, pspec);
ld_diagram_object_get_data_for_param (self, &tmp_value, pspec);
g_value_copy (&tmp_value, value);
g_value_unset (&tmp_value);
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);

View File

@ -23,6 +23,12 @@
/* /*
* LdDiagramPrivate: * LdDiagramPrivate:
* @modified: whether the diagram has been modified. * @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. * @objects: all objects in the diagram.
* @selection: all currently selected objects. * @selection: all currently selected objects.
* @connections: connections between objects. * @connections: connections between objects.
@ -30,16 +36,37 @@
struct _LdDiagramPrivate struct _LdDiagramPrivate
{ {
gboolean modified; gboolean modified;
gboolean lock_history;
guint in_user_action;
GList *undo_stack;
GList *redo_stack;
GList *objects; GList *objects;
GList *selection; GList *selection;
GList *connections; 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 enum
{ {
PROP_0, PROP_0,
PROP_MODIFIED PROP_MODIFIED,
PROP_CAN_UNDO,
PROP_CAN_REDO
}; };
static void ld_diagram_get_property (GObject *object, guint property_id, 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); const GValue *value, GParamSpec *pspec);
static void ld_diagram_dispose (GObject *gobject); static void ld_diagram_dispose (GObject *gobject);
static void ld_diagram_finalize (GObject *gobject); static void ld_diagram_finalize (GObject *gobject);
static void ld_diagram_real_changed (LdDiagram *self);
static void on_object_data_changed (LdDiagramObject *self,
gchar **path, GValue *old_value, GValue *new_value, gpointer user_data);
static gboolean write_signature (GOutputStream *stream, GError **error); 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 JsonNode *serialize_object (LdDiagramObject *object);
static const gchar *get_object_class_string (GType type); 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 install_object (LdDiagramObject *object, LdDiagram *self);
static void uninstall_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); static void ld_diagram_unselect_all_internal (LdDiagram *self);
@ -96,6 +132,26 @@ ld_diagram_class_init (LdDiagramClass *klass)
FALSE, G_PARAM_READWRITE); FALSE, G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_MODIFIED, pspec); 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: * LdDiagram::changed:
* @self: an #LdDiagram object. * @self: an #LdDiagram object.
@ -142,6 +198,12 @@ ld_diagram_get_property (GObject *object, guint property_id,
case PROP_MODIFIED: case PROP_MODIFIED:
g_value_set_boolean (value, ld_diagram_get_modified (self)); g_value_set_boolean (value, ld_diagram_get_modified (self));
break; 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: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
} }
@ -220,7 +282,7 @@ ld_diagram_new (void)
* ld_diagram_clear: * ld_diagram_clear:
* @self: an #LdDiagram object. * @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 void
ld_diagram_clear (LdDiagram *self) ld_diagram_clear (LdDiagram *self)
@ -250,6 +312,12 @@ ld_diagram_clear (LdDiagram *self)
changed = TRUE; 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) if (changed)
g_signal_emit (self, g_signal_emit (self,
LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0);
@ -264,7 +332,7 @@ ld_diagram_clear (LdDiagram *self)
* @filename: a filename. * @filename: a filename.
* @error: (allow-none): return location for a #GError, or %NULL. * @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. * 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); ld_diagram_clear (self);
self->priv->lock_history = TRUE;
local_error = NULL; local_error = NULL;
deserialize_diagram (self, json_parser_get_root (parser), &local_error); deserialize_diagram (self, json_parser_get_root (parser), &local_error);
self->priv->lock_history = FALSE;
g_object_unref (parser); g_object_unref (parser);
if (local_error) if (local_error)
{ {
@ -539,23 +612,235 @@ ld_diagram_set_modified (LdDiagram *self, gboolean value)
} }
static void static void
on_object_data_changed (LdDiagramObject *self, gchar **path, on_object_changed (LdDiagramObject *object,
GValue *old_value, GValue *new_value, gpointer user_data) 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 static void
install_object (LdDiagramObject *object, LdDiagram *self) install_object (LdDiagramObject *object, LdDiagram *self)
{ {
g_signal_connect (object, "data-changed", g_signal_connect (object, "changed",
G_CALLBACK (on_object_data_changed), self); G_CALLBACK (on_object_changed), self);
g_signal_connect (object, "notify::storage",
G_CALLBACK (on_object_notify_storage), self);
g_object_ref (object); g_object_ref (object);
} }
static void static void
uninstall_object (LdDiagramObject *object, LdDiagram *self) 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); g_object_unref (object);
} }
@ -584,6 +869,9 @@ ld_diagram_get_objects (LdDiagram *self)
void void
ld_diagram_insert_object (LdDiagram *self, LdDiagramObject *object, gint pos) 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 (self));
g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object)); 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); self->priv->objects = g_list_insert (self->priv->objects, object, pos);
install_object (object, self); 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, g_signal_emit (self,
LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0);
} }
@ -607,17 +905,39 @@ ld_diagram_insert_object (LdDiagram *self, LdDiagramObject *object, gint pos)
void void
ld_diagram_remove_object (LdDiagram *self, LdDiagramObject *object) 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 (self));
g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object)); 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; return;
ld_diagram_unselect (self, object); 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); 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, g_signal_emit (self,
LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0); LD_DIAGRAM_GET_CLASS (self)->changed_signal, 0);
} }
@ -645,32 +965,20 @@ ld_diagram_get_selection (LdDiagram *self)
void void
ld_diagram_remove_selection (LdDiagram *self) ld_diagram_remove_selection (LdDiagram *self)
{ {
LdDiagramObject *object; GList *selection_copy, *iter;
gboolean changed;
GList *iter;
g_return_if_fail (LD_IS_DIAGRAM (self)); g_return_if_fail (LD_IS_DIAGRAM (self));
for (iter = self->priv->selection; iter; iter = g_list_next (iter)) /* We still retain references in the object list. */
{ selection_copy = g_list_copy (self->priv->selection);
object = LD_DIAGRAM_OBJECT (iter->data); ld_diagram_unselect_all (self);
g_object_unref (object);
self->priv->objects = g_list_remove (self->priv->objects, object); ld_diagram_begin_user_action (self);
uninstall_object (object, 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);
changed = self->priv->selection != NULL; g_list_free (selection_copy);
g_list_free (self->priv->selection);
self->priv->selection = NULL;
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);
}
} }
/** /**

View File

@ -90,6 +90,13 @@ gboolean ld_diagram_save_to_file (LdDiagram *self,
gboolean ld_diagram_get_modified (LdDiagram *self); gboolean ld_diagram_get_modified (LdDiagram *self);
void ld_diagram_set_modified (LdDiagram *self, gboolean value); 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); GList *ld_diagram_get_objects (LdDiagram *self);
void ld_diagram_insert_object (LdDiagram *self, void ld_diagram_insert_object (LdDiagram *self,
LdDiagramObject *object, gint pos); LdDiagramObject *object, gint pos);

View File

@ -1,3 +1,2 @@
VOID:OBJECT,OBJECT VOID:OBJECT,OBJECT
VOID:OBJECT,STRING VOID:OBJECT,STRING
VOID:BOXED,BOXED,BOXED