1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3
* Authors: Iain Holmes <iain@gnome.org>
5
* Copyright 2002 - 2006 Iain Holmes
7
* This file is free software; you can redistribute it and/or
8
* modify it under the terms of version 2 of the GNU Library General Public
9
* License as published by the Free Software Foundation;
11
* This library is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* Library General Public License for more details.
16
* You should have received a copy of the GNU Library General Public
17
* License along with this library; if not, write to the
18
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19
* Boston, MA 02111-1307, USA.
27
#include <glib/gi18n.h>
29
#include "koto-undo-manager.h"
31
G_DEFINE_TYPE (KotoUndoManager, koto_undo_manager, G_TYPE_OBJECT);
33
#define GET_PRIVATE(o) \
34
(G_TYPE_INSTANCE_GET_PRIVATE ((o), KOTO_TYPE_UNDO_MANAGER, KotoUndoManagerPrivate))
41
static guint signals[LAST_SIGNAL];
43
struct _KotoUndoManagerPrivate {
44
GList *contexts, *undo, *redo;
46
KotoUndoContext *working;
49
struct _KotoUndoContext {
57
context_free (KotoUndoContext *ctxt)
62
for (u = ctxt->undoables; u; u = u->next) {
63
KotoUndoable *undoable = u->data;
65
koto_undoable_free (undoable);
68
g_list_free (ctxt->undoables);
73
static KotoUndoContext *
74
context_new (const char *name)
76
KotoUndoContext *ctxt;
78
ctxt = g_new (KotoUndoContext, 1);
80
ctxt->name = g_strdup (name);
82
ctxt->undoables = NULL;
88
context_undo (KotoUndoContext *ctxt)
92
for (p = ctxt->undoables; p; p = p->next) {
93
KotoUndoable *u = p->data;
102
context_redo (KotoUndoContext *ctxt)
106
for (p = g_list_last (ctxt->undoables); p; p = p->prev) {
107
KotoUndoable *u = p->data;
110
u->redo (u->closure);
116
finalize (GObject *object)
118
KotoUndoManager *manager;
119
KotoUndoManagerPrivate *priv;
122
manager = KOTO_UNDO_MANAGER (object);
123
priv = manager->priv;
125
for (p = priv->contexts; p; p = p->next) {
126
KotoUndoContext *ctxt = p->data;
129
g_list_free (priv->contexts);
131
G_OBJECT_CLASS (koto_undo_manager_parent_class)->finalize (object);
135
koto_undo_manager_class_init (KotoUndoManagerClass *klass)
137
GObjectClass *object_class;
139
g_type_class_add_private (klass, sizeof (KotoUndoManagerPrivate));
141
object_class = G_OBJECT_CLASS (klass);
143
object_class->finalize = finalize;
145
signals[CHANGED] = g_signal_new ("changed",
146
G_TYPE_FROM_CLASS (klass),
149
G_STRUCT_OFFSET (KotoUndoManagerClass, changed),
151
g_cclosure_marshal_VOID__VOID,
157
koto_undo_manager_init (KotoUndoManager *manager)
159
manager->priv = GET_PRIVATE (manager);
163
koto_undo_manager_new (void)
165
KotoUndoManager *manager;
167
manager = g_object_new (KOTO_TYPE_UNDO_MANAGER, NULL);
172
koto_undo_manager_can_undo (KotoUndoManager *manager)
174
return (manager->priv->undo != NULL);
178
koto_undo_manager_can_redo (KotoUndoManager *manager)
180
return (manager->priv->redo != NULL);
183
/* FIXME: Should these return copies of the names
184
so that they're threadsafe? */
186
koto_undo_manager_get_undo_name (KotoUndoManager *manager)
188
if (manager->priv->undo) {
189
KotoUndoContext *ctxt = manager->priv->undo->data;
197
koto_undo_manager_get_redo_name (KotoUndoManager *manager)
199
if (manager->priv->redo) {
200
KotoUndoContext *ctxt = manager->priv->redo->data;
208
koto_undo_manager_context_begin (KotoUndoManager *manager,
211
KotoUndoContext *ctxt;
213
if (manager->priv->working != NULL) {
214
manager->priv->working->count++;
216
return manager->priv->working;
219
ctxt = context_new (name);
221
manager->priv->working = ctxt;
227
koto_undo_manager_context_begin_formatted (KotoUndoManager *manager,
228
const char *format, ...)
230
KotoUndoContext *ctxt;
234
va_start (args, format);
236
name = g_strdup_vprintf (format, args);
238
ctxt = koto_undo_manager_context_begin (manager, name);
247
koto_undo_manager_context_end (KotoUndoManager *manager,
248
KotoUndoContext *ctxt)
250
/* TODO: handle start then end without any adds correctly */
251
KotoUndoManagerPrivate *priv;
253
priv = manager->priv;
257
if (ctxt->count > 0) {
261
if (priv->contexts == NULL) {
262
priv->contexts = g_list_append (NULL, ctxt);
263
priv->undo = priv->contexts;
265
GList *pruned = priv->redo;
268
if (pruned != NULL) {
271
/* Disconnect pruned */
272
if (priv->redo && priv->redo->prev) {
273
priv->redo->prev->next = NULL;
276
/* If redo->prev == NULL then we've pruned everything */
277
if (priv->redo && priv->redo->prev == NULL) {
278
priv->contexts = NULL;
284
for (p = pruned; p; p = p->next) {
285
KotoUndoContext *c = p->data;
289
g_list_free (pruned);
292
/* Append our new context and move the position pointer on */
293
n = g_list_append (priv->undo, ctxt);
294
if (priv->undo == NULL) {
297
priv->undo = priv->undo->next;
299
/* If priv->contexts is NULL, then we pruned everything and need
300
to start a new list */
301
if (priv->contexts == NULL) {
302
priv->contexts = priv->undo;
307
priv->working = NULL;
309
g_signal_emit (manager, signals[CHANGED], 0);
313
koto_undo_manager_context_cancel (KotoUndoManager *manager,
314
KotoUndoContext *ctxt)
316
g_return_if_fail (manager->priv->working == ctxt);
318
manager->priv->working = NULL;
323
koto_undo_manager_undo (KotoUndoManager *manager)
326
if (manager->priv->undo) {
327
context_undo ((KotoUndoContext *) manager->priv->undo->data);
328
manager->priv->redo = manager->priv->undo;
329
manager->priv->undo = manager->priv->undo->prev;
331
g_signal_emit (manager, signals[CHANGED], 0);
336
koto_undo_manager_redo (KotoUndoManager *manager)
338
if (manager->priv->redo) {
339
context_redo ((KotoUndoContext *) manager->priv->redo->data);
340
manager->priv->undo = manager->priv->redo;
341
manager->priv->redo = manager->priv->redo->next;
343
g_signal_emit (manager, signals[CHANGED], 0);
348
koto_undo_manager_get_history (KotoUndoManager *manager)
350
GList *history = NULL;
352
KotoUndoHistory *hist;
354
hist = g_new (KotoUndoHistory, 1);
355
hist->name = g_strdup (_("Original Sample"));
356
hist->current = FALSE;
359
history = g_list_prepend (history, hist);
361
for (p = manager->priv->contexts; p; p = p->next) {
362
KotoUndoContext *ctxt = p->data;
364
hist = g_new (KotoUndoHistory, 1);
365
hist->name = g_strdup (ctxt->name);
368
history = g_list_append (history, hist);
369
if (p == manager->priv->undo) {
370
hist->current = TRUE;
372
hist->current = FALSE;
380
koto_undo_context_add (KotoUndoContext *ctxt,
381
KotoUndoable *undoable)
383
ctxt->undoables = g_list_prepend (ctxt->undoables, undoable);