09bb4e85729da13d2927cbdc5ce37bde63e5841c
1 #define __SP_DOCUMENT_UNDO_C__
3 /** \file
4 * Undo/Redo stack implementation
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * MenTaLguY <mental@rydia.net>
9 *
10 * Copyright (C) 1999-2003 authors
11 * Copyright (C) 2001-2002 Ximian, Inc.
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 *
15 * Using the split document model gives sodipodi a very simple and clean
16 * undo implementation. Whenever mutation occurs in the XML tree,
17 * SPObject invokes one of the five corresponding handlers of its
18 * container document. This writes down a generic description of the
19 * given action, and appends it to the recent action list, kept by the
20 * document. There will be as many action records as there are mutation
21 * events, which are all kept and processed together in the undo
22 * stack. Two methods exist to indicate that the given action is completed:
23 *
24 * \verbatim
25 void sp_document_done (SPDocument *document);
26 void sp_document_maybe_done (SPDocument *document, const unsigned char *key) \endverbatim
27 *
28 * Both move the recent action list into the undo stack and clear the
29 * list afterwards. While the first method does an unconditional push,
30 * the second one first checks the key of the most recent stack entry. If
31 * the keys are identical, the current action list is appended to the
32 * existing stack entry, instead of pushing it onto its own. This
33 * behaviour can be used to collect multi-step actions (like winding the
34 * Gtk spinbutton) from the UI into a single undoable step.
35 *
36 * For controls implemented by Sodipodi itself, implementing undo as a
37 * single step is usually done in a more efficent way. Most controls have
38 * the abstract model of grab, drag, release, and change user
39 * action. During the grab phase, all modifications are done to the
40 * SPObject directly - i.e. they do not change XML tree, and thus do not
41 * generate undo actions either. Only at the release phase (normally
42 * associated with releasing the mousebutton), changes are written back
43 * to the XML tree, thus generating only a single set of undo actions.
44 * (Lauris Kaplinski)
45 */
47 #ifdef HAVE_CONFIG_H
48 # include "config.h"
49 #endif
53 #if HAVE_STRING_H
54 #endif
57 #if HAVE_STDLIB_H
58 #endif
60 #include "xml/repr.h"
61 #include "document-private.h"
62 #include "inkscape.h"
63 #include "debug/event-tracker.h"
64 #include "debug/simple-event.h"
65 #include "event.h"
68 /*
69 * Undo & redo
70 */
71 /**
72 * Set undo sensitivity.
73 *
74 * \note
75 * Since undo sensitivity needs to be nested, setting undo sensitivity
76 * should be done like this:
77 *\verbatim
78 gboolean saved = sp_document_get_undo_sensitive(document);
79 sp_document_set_undo_sensitive(document, FALSE);
80 ... do stuff ...
81 sp_document_set_undo_sensitive(document, saved); \endverbatim
82 */
83 void
84 sp_document_set_undo_sensitive (SPDocument *doc, gboolean sensitive)
85 {
86 g_assert (doc != NULL);
87 g_assert (doc->priv != NULL);
89 if ( !(sensitive) == !(doc->priv->sensitive) )
90 return;
92 if (sensitive) {
93 sp_repr_begin_transaction (doc->rdoc);
94 } else {
95 doc->priv->partial = sp_repr_coalesce_log (
96 doc->priv->partial,
97 sp_repr_commit_undoable (doc->rdoc)
98 );
99 }
101 doc->priv->sensitive = !!sensitive;
102 }
104 gboolean sp_document_get_undo_sensitive(SPDocument const *document) {
105 g_assert(document != NULL);
106 g_assert(document->priv != NULL);
108 return document->priv->sensitive;
109 }
111 void
112 sp_document_done (SPDocument *doc, const unsigned int event_type, Glib::ustring event_description)
113 {
114 sp_document_maybe_done (doc, NULL, event_type, event_description);
115 }
117 void
118 sp_document_reset_key (Inkscape::Application *inkscape, SPDesktop *desktop, GtkObject *base)
119 {
120 SPDocument *doc = (SPDocument *) base;
121 doc->actionkey = NULL;
122 }
124 void
125 sp_document_maybe_done (SPDocument *doc, const gchar *key, const unsigned int event_type,
126 Glib::ustring event_description)
127 {
128 g_assert (doc != NULL);
129 g_assert (doc->priv != NULL);
130 g_assert (doc->priv->sensitive);
132 doc->collectOrphans();
134 sp_document_ensure_up_to_date (doc);
136 sp_document_clear_redo (doc);
138 Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
139 doc->priv->partial = NULL;
141 if (!log) {
142 sp_repr_begin_transaction (doc->rdoc);
143 return;
144 }
146 if (key && doc->actionkey && !strcmp (key, doc->actionkey) && doc->priv->undo) {
147 doc->priv->undo->data =
148 new Inkscape::Event(sp_repr_coalesce_log (((Inkscape::Event *)
149 doc->priv->undo->data)->event, log));
150 } else {
151 Inkscape::Event *event = new Inkscape::Event(log, event_type, event_description);
152 doc->priv->undo = g_slist_prepend (doc->priv->undo, event);
153 doc->priv->history_size++;
154 doc->priv->undoStackObservers.notifyUndoCommitEvent(event);
155 }
157 doc->actionkey = key;
159 doc->virgin = FALSE;
160 if (!doc->rroot->attribute("sodipodi:modified")) {
161 doc->rroot->setAttribute("sodipodi:modified", "true");
162 }
164 sp_repr_begin_transaction (doc->rdoc);
166 doc->priv->commit_signal.emit();
167 }
169 void
170 sp_document_cancel (SPDocument *doc)
171 {
172 g_assert (doc != NULL);
173 g_assert (doc->priv != NULL);
174 g_assert (doc->priv->sensitive);
176 sp_repr_rollback (doc->rdoc);
178 if (doc->priv->partial) {
179 sp_repr_undo_log (doc->priv->partial);
180 sp_repr_free_log (doc->priv->partial);
181 doc->priv->partial = NULL;
182 }
184 sp_repr_begin_transaction (doc->rdoc);
185 }
187 namespace {
189 void finish_incomplete_transaction(SPDocument &doc) {
190 SPDocumentPrivate &priv=*doc.priv;
191 Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
192 if (log || priv.partial) {
193 g_warning ("Incomplete undo transaction:");
194 priv.partial = sp_repr_coalesce_log(priv.partial, log);
195 sp_repr_debug_print_log(priv.partial);
196 priv.undo = g_slist_prepend(priv.undo, priv.partial);
197 priv.partial = NULL;
198 }
199 }
201 }
203 gboolean
204 sp_document_undo (SPDocument *doc)
205 {
206 using Inkscape::Debug::EventTracker;
207 using Inkscape::Debug::SimpleEvent;
209 gboolean ret;
211 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
213 g_assert (doc != NULL);
214 g_assert (doc->priv != NULL);
215 g_assert (doc->priv->sensitive);
217 doc->priv->sensitive = FALSE;
219 doc->actionkey = NULL;
221 finish_incomplete_transaction(*doc);
223 if (doc->priv->undo) {
224 Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
225 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
226 sp_repr_undo_log (log->event);
227 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
229 doc->rroot->setAttribute("sodipodi:modified", "true");
230 doc->priv->undoStackObservers.notifyUndoEvent(log);
232 ret = TRUE;
233 } else {
234 ret = FALSE;
235 }
237 sp_repr_begin_transaction (doc->rdoc);
239 doc->priv->sensitive = TRUE;
241 if (ret)
242 inkscape_external_change();
244 return ret;
245 }
247 gboolean
248 sp_document_redo (SPDocument *doc)
249 {
250 using Inkscape::Debug::EventTracker;
251 using Inkscape::Debug::SimpleEvent;
253 gboolean ret;
255 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
257 g_assert (doc != NULL);
258 g_assert (doc->priv != NULL);
259 g_assert (doc->priv->sensitive);
261 doc->priv->sensitive = FALSE;
263 doc->actionkey = NULL;
265 finish_incomplete_transaction(*doc);
267 if (doc->priv->redo) {
268 Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
269 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
270 sp_repr_replay_log (log->event);
271 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
273 doc->rroot->setAttribute("sodipodi:modified", "true");
274 doc->priv->undoStackObservers.notifyRedoEvent(log);
276 ret = TRUE;
277 } else {
278 ret = FALSE;
279 }
281 sp_repr_begin_transaction (doc->rdoc);
283 doc->priv->sensitive = TRUE;
285 if (ret)
286 inkscape_external_change();
288 return ret;
289 }
291 void
292 sp_document_clear_undo (SPDocument *doc)
293 {
294 while (doc->priv->undo) {
295 GSList *current;
297 current = doc->priv->undo;
298 doc->priv->undo = current->next;
299 doc->priv->history_size--;
301 delete ((Inkscape::Event *) current->data);
302 g_slist_free_1 (current);
303 }
304 }
306 void
307 sp_document_clear_redo (SPDocument *doc)
308 {
309 while (doc->priv->redo) {
310 GSList *current;
312 current = doc->priv->redo;
313 doc->priv->redo = current->next;
314 doc->priv->history_size--;
316 delete ((Inkscape::Event *) current->data);
317 g_slist_free_1 (current);
318 }
319 }
320 /*
321 Local Variables:
322 mode:c++
323 c-file-style:"stroustrup"
324 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
325 indent-tabs-mode:nil
326 fill-column:99
327 End:
328 */
329 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :