diff options
Diffstat (limited to 'liblogdiag')
| -rw-r--r-- | liblogdiag/ld-diagram-object.c | 414 | ||||
| -rw-r--r-- | liblogdiag/ld-diagram-object.h | 16 | ||||
| -rw-r--r-- | liblogdiag/ld-diagram-symbol.c | 8 | ||||
| -rw-r--r-- | liblogdiag/ld-diagram.c | 378 | ||||
| -rw-r--r-- | liblogdiag/ld-diagram.h | 7 | ||||
| -rw-r--r-- | liblogdiag/ld-marshal.list | 1 | 
6 files changed, 493 insertions, 331 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,311 +246,164 @@ ld_diagram_object_set_storage (LdDiagramObject *self, JsonObject *storage)  }  /** - * ld_diagram_object_get_data: + * ld_diagram_object_changed:   * @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. + * @action: an #LdUndoAction object specifying the change.   * - * 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. + * Emit the #LdDiagramObject::changed signal.   */  void -ld_diagram_object_set_data (LdDiagramObject *self, -	const GValue *data, const gchar *first_element, ...) +ld_diagram_object_changed (LdDiagramObject *self, LdUndoAction *action)  { -	va_list args; +	g_return_if_fail (LD_IS_DIAGRAM_OBJECT (self)); +	g_return_if_fail (LD_IS_UNDO_ACTION (action)); -	va_start (args, first_element); -	ld_diagram_object_set_data_valist (self, data, first_element, args); -	va_end (args); +	g_signal_emit (self, LD_DIAGRAM_OBJECT_GET_CLASS (self)->changed_signal, 0, +		action);  }  /** - * ld_diagram_object_get_data_valist: + * ld_diagram_object_get_data_for_param:   * @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. + * @data: (out): where the data will be stored. + * @pspec: the parameter to read data for. This must be a property of @self.   * - * Return value: %TRUE if successful. + * 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().   */ -gboolean -ld_diagram_object_get_data_valist (LdDiagramObject *self, -	GValue *data, GType type, const gchar *first_element, va_list var_args) +void +ld_diagram_object_get_data_for_param (LdDiagramObject *self, +	GValue *data, GParamSpec *pspec)  { -	const gchar **elements; +	JsonObject *storage; +	JsonNode *node; +	const gchar *name; +	GValue json_value;  	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; +	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 = args_to_strv (first_element, var_args); -	ld_diagram_object_set_datav (self, data, elements); -	g_free (elements); -} +	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; -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; +	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; -	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; +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)); -		if (!arg) -			break; -	} -	return strv; +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_get_datav: + * ld_diagram_object_set_data_for_param:   * @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. + * @data: the data. + * @pspec: the parameter to put data for.   * - * Return value: %TRUE if successful. + * Put data for a parameter into internal storage.   */ -gboolean -ld_diagram_object_get_datav (LdDiagramObject *self, -	GValue *data, GType type, const gchar **elements) +void +ld_diagram_object_set_data_for_param (LdDiagramObject *self, +	const GValue *data, GParamSpec *pspec)  { -	JsonObject *object; +	LdUndoAction *action; +	SetParamActionData *action_data; +	JsonObject *storage; +	const gchar *name;  	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); +	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)); -	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; -	} +	storage = ld_diagram_object_get_storage (self); +	name = g_param_spec_get_name (pspec); -	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; +	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)); -		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; -} +	node = json_object_get_member (storage, name); +	action_data->old_node = node ? json_node_copy (node) : NULL; -/** - * 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; +	node = json_node_new (JSON_NODE_VALUE); +	json_node_set_value (node, data); +	action_data->new_node = json_node_copy (node); -	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); +	json_object_set_member (storage, name, node); -	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]; -	} +	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); +} -	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; -	} +static void +on_set_param_undo (gpointer user_data) +{ +	SetParamActionData *data; +	JsonObject *storage; -	/* 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); -	} +	data = user_data; +	storage = ld_diagram_object_get_storage (data->self); -	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); +	json_object_set_member (storage, data->param_name, +		json_node_copy (data->old_node));  } -/** - * ld_diagram_object_get_data_for_param: - * @self: an #LdDiagramObject object. - * @data: (out): an uninitialized storage for the data. - * @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. - */ -void -ld_diagram_object_get_data_for_param (LdDiagramObject *self, -	GValue *data, GParamSpec *pspec) +static void +on_set_param_redo (gpointer user_data)  { -	const gchar *elements[2]; +	SetParamActionData *data; +	JsonObject *storage; -	g_return_if_fail (LD_IS_DIAGRAM_OBJECT (self)); -	g_return_if_fail (data != NULL); -	g_return_if_fail (G_IS_PARAM_SPEC (pspec)); -	g_return_if_fail (g_type_is_a (pspec->owner_type, LD_TYPE_DIAGRAM_OBJECT)); +	data = user_data; +	storage = ld_diagram_object_get_storage (data->self); -	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); -	} +	json_object_set_member (storage, data->param_name, +		json_node_copy (data->new_node));  } -/** - * ld_diagram_object_set_data_for_param: - * @self: an #LdDiagramObject object. - * @data: the data. - * @pspec: the parameter to put data for. - * - * Put data for a parameter into internal storage. - */ -void -ld_diagram_object_set_data_for_param (LdDiagramObject *self, -	const GValue *data, GParamSpec *pspec) +static void +on_set_param_destroy (gpointer user_data)  { -	const gchar *elements[2]; - -	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)); +	SetParamActionData *data; -	elements[0] = g_param_spec_get_name (pspec); -	elements[1] = NULL; -	ld_diagram_object_set_datav (self, data, elements); +	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 <string.h> -  #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); @@ -97,6 +133,26 @@ ld_diagram_class_init (LdDiagramClass *klass)  	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  | 
