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) 2007 MenTaLguY <mental@rydia.net>
11 * Copyright (C) 1999-2003 authors
12 * Copyright (C) 2001-2002 Ximian, Inc.
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 *
16 * Using the split document model gives sodipodi a very simple and clean
17 * undo implementation. Whenever mutation occurs in the XML tree,
18 * SPObject invokes one of the five corresponding handlers of its
19 * container document. This writes down a generic description of the
20 * given action, and appends it to the recent action list, kept by the
21 * document. There will be as many action records as there are mutation
22 * events, which are all kept and processed together in the undo
23 * stack. Two methods exist to indicate that the given action is completed:
24 *
25 * \verbatim
26 void sp_document_done (SPDocument *document);
27 void sp_document_maybe_done (SPDocument *document, const unsigned char *key) \endverbatim
28 *
29 * Both move the recent action list into the undo stack and clear the
30 * list afterwards. While the first method does an unconditional push,
31 * the second one first checks the key of the most recent stack entry. If
32 * the keys are identical, the current action list is appended to the
33 * existing stack entry, instead of pushing it onto its own. This
34 * behaviour can be used to collect multi-step actions (like winding the
35 * Gtk spinbutton) from the UI into a single undoable step.
36 *
37 * For controls implemented by Sodipodi itself, implementing undo as a
38 * single step is usually done in a more efficent way. Most controls have
39 * the abstract model of grab, drag, release, and change user
40 * action. During the grab phase, all modifications are done to the
41 * SPObject directly - i.e. they do not change XML tree, and thus do not
42 * generate undo actions either. Only at the release phase (normally
43 * associated with releasing the mousebutton), changes are written back
44 * to the XML tree, thus generating only a single set of undo actions.
45 * (Lauris Kaplinski)
46 */
48 #ifdef HAVE_CONFIG_H
49 # include "config.h"
50 #endif
54 #if HAVE_STRING_H
55 #endif
58 #if HAVE_STDLIB_H
59 #endif
61 #include <string>
62 #include <cstring>
63 #include "xml/repr.h"
64 #include "document-private.h"
65 #include "inkscape.h"
66 //#include "document-undo.h"
67 #include "debug/event-tracker.h"
68 #include "debug/simple-event.h"
69 #include "debug/timestamp.h"
70 #include "event.h"
73 /*
74 * Undo & redo
75 */
76 /**
77 * Set undo sensitivity.
78 *
79 * \note
80 * Since undo sensitivity needs to be nested, setting undo sensitivity
81 * should be done like this:
82 *\verbatim
83 bool saved = sp_document_get_undo_sensitive(document);
84 sp_document_set_undo_sensitive(document, false);
85 ... do stuff ...
86 sp_document_set_undo_sensitive(document, saved); \endverbatim
87 */
88 void
89 SPDocumentUndo::set_undo_sensitive (SPDocument *doc, bool sensitive)
90 {
91 g_assert (doc != NULL);
92 g_assert (doc->priv != NULL);
94 if ( sensitive == doc->priv->sensitive )
95 return;
97 if (sensitive) {
98 sp_repr_begin_transaction (doc->rdoc);
99 } else {
100 doc->priv->partial = sp_repr_coalesce_log (
101 doc->priv->partial,
102 sp_repr_commit_undoable (doc->rdoc)
103 );
104 }
106 doc->priv->sensitive = sensitive;
107 }
109 /*TODO: Throughout the inkscape code tree set/get_undo_sensitive are used for
110 * as is shown above. Perhaps it makes sense to create new functions,
111 * undo_ignore, and undo_recall to replace the start and end parts of the above.
112 * The main complexity with this is that they have to nest, so you have to store
113 * the saved bools in a stack. Perhaps this is why the above solution is better.
114 */
116 bool SPDocumentUndo::get_undo_sensitive(SPDocument const *document) {
117 g_assert(document != NULL);
118 g_assert(document->priv != NULL);
120 return document->priv->sensitive;
121 }
123 void
124 SPDocumentUndo::done (SPDocument *doc, const unsigned int event_type, Glib::ustring event_description)
125 {
126 maybe_done (doc, NULL, event_type, event_description);
127 }
129 void
130 SPDocumentUndo::reset_key (Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, GtkObject *base)
131 {
132 SPDocument *doc = (SPDocument *) base;
133 doc->actionkey = NULL;
134 }
136 namespace {
138 using Inkscape::Debug::Event;
139 using Inkscape::Debug::SimpleEvent;
140 using Inkscape::Util::share_static_string;
141 using Inkscape::Debug::timestamp;
142 using Inkscape::Verb;
144 typedef SimpleEvent<Event::INTERACTION> InteractionEvent;
146 class CommitEvent : public InteractionEvent {
147 public:
149 CommitEvent(SPDocument *doc, const gchar *key, const unsigned int type)
150 : InteractionEvent(share_static_string("commit"))
151 {
152 _addProperty(share_static_string("timestamp"), timestamp());
153 gchar *serial = g_strdup_printf("%lu", doc->serial());
154 _addProperty(share_static_string("document"), serial);
155 g_free(serial);
156 Verb *verb = Verb::get(type);
157 if (verb) {
158 _addProperty(share_static_string("context"), verb->get_id());
159 }
160 if (key) {
161 _addProperty(share_static_string("merge-key"), key);
162 }
163 }
164 };
166 }
168 void
169 SPDocumentUndo::maybe_done (SPDocument *doc, const gchar *key, const unsigned int event_type,
170 Glib::ustring event_description)
171 {
172 g_assert (doc != NULL);
173 g_assert (doc->priv != NULL);
174 g_assert (doc->priv->sensitive);
176 Inkscape::Debug::EventTracker<CommitEvent> tracker(doc, key, event_type);
178 doc->collectOrphans();
180 doc->ensure_up_to_date ();
182 SPDocumentUndo::clear_redo (doc);
184 Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
185 doc->priv->partial = NULL;
187 if (!log) {
188 sp_repr_begin_transaction (doc->rdoc);
189 return;
190 }
192 if (key && doc->actionkey && !strcmp (key, doc->actionkey) && doc->priv->undo) {
193 ((Inkscape::Event *)doc->priv->undo->data)->event =
194 sp_repr_coalesce_log (((Inkscape::Event *)doc->priv->undo->data)->event, log);
195 } else {
196 Inkscape::Event *event = new Inkscape::Event(log, event_type, event_description);
197 doc->priv->undo = g_slist_prepend (doc->priv->undo, event);
198 doc->priv->history_size++;
199 doc->priv->undoStackObservers.notifyUndoCommitEvent(event);
200 }
202 doc->actionkey = key;
204 doc->virgin = FALSE;
205 doc->setModifiedSinceSave();
207 sp_repr_begin_transaction (doc->rdoc);
209 doc->priv->commit_signal.emit();
210 }
212 void
213 SPDocumentUndo::cancel (SPDocument *doc)
214 {
215 g_assert (doc != NULL);
216 g_assert (doc->priv != NULL);
217 g_assert (doc->priv->sensitive);
219 sp_repr_rollback (doc->rdoc);
221 if (doc->priv->partial) {
222 sp_repr_undo_log (doc->priv->partial);
223 sp_repr_free_log (doc->priv->partial);
224 doc->priv->partial = NULL;
225 }
227 sp_repr_begin_transaction (doc->rdoc);
228 }
230 static void finish_incomplete_transaction(SPDocument &doc) {
231 SPDocumentPrivate &priv=*doc.priv;
232 Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
233 if (log || priv.partial) {
234 g_warning ("Incomplete undo transaction:");
235 priv.partial = sp_repr_coalesce_log(priv.partial, log);
236 sp_repr_debug_print_log(priv.partial);
237 Inkscape::Event *event = new Inkscape::Event(priv.partial);
238 priv.undo = g_slist_prepend(priv.undo, event);
239 priv.undoStackObservers.notifyUndoCommitEvent(event);
240 priv.partial = NULL;
241 }
242 }
244 gboolean
245 SPDocumentUndo::undo (SPDocument *doc)
246 {
247 using Inkscape::Debug::EventTracker;
248 using Inkscape::Debug::SimpleEvent;
250 gboolean ret;
252 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
254 g_assert (doc != NULL);
255 g_assert (doc->priv != NULL);
256 g_assert (doc->priv->sensitive);
258 doc->priv->sensitive = FALSE;
259 doc->priv->seeking = true;
261 doc->actionkey = NULL;
263 finish_incomplete_transaction(*doc);
265 if (doc->priv->undo) {
266 Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
267 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
268 sp_repr_undo_log (log->event);
269 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
271 doc->setModifiedSinceSave();
272 doc->priv->undoStackObservers.notifyUndoEvent(log);
274 ret = TRUE;
275 } else {
276 ret = FALSE;
277 }
279 sp_repr_begin_transaction (doc->rdoc);
281 doc->priv->sensitive = TRUE;
282 doc->priv->seeking = false;
284 if (ret)
285 inkscape_external_change();
287 return ret;
288 }
290 gboolean
291 SPDocumentUndo::redo (SPDocument *doc)
292 {
293 using Inkscape::Debug::EventTracker;
294 using Inkscape::Debug::SimpleEvent;
296 gboolean ret;
298 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
300 g_assert (doc != NULL);
301 g_assert (doc->priv != NULL);
302 g_assert (doc->priv->sensitive);
304 doc->priv->sensitive = FALSE;
305 doc->priv->seeking = true;
307 doc->actionkey = NULL;
309 finish_incomplete_transaction(*doc);
311 if (doc->priv->redo) {
312 Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
313 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
314 sp_repr_replay_log (log->event);
315 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
317 doc->setModifiedSinceSave();
318 doc->priv->undoStackObservers.notifyRedoEvent(log);
320 ret = TRUE;
321 } else {
322 ret = FALSE;
323 }
325 sp_repr_begin_transaction (doc->rdoc);
327 doc->priv->sensitive = TRUE;
328 doc->priv->seeking = false;
330 if (ret)
331 inkscape_external_change();
333 return ret;
334 }
336 void
337 SPDocumentUndo::clear_undo (SPDocument *doc)
338 {
339 if (doc->priv->undo)
340 doc->priv->undoStackObservers.notifyClearUndoEvent();
342 while (doc->priv->undo) {
343 GSList *current;
345 current = doc->priv->undo;
346 doc->priv->undo = current->next;
347 doc->priv->history_size--;
349 delete ((Inkscape::Event *) current->data);
350 g_slist_free_1 (current);
351 }
352 }
354 void
355 SPDocumentUndo::clear_redo (SPDocument *doc)
356 {
357 if (doc->priv->redo)
358 doc->priv->undoStackObservers.notifyClearRedoEvent();
360 while (doc->priv->redo) {
361 GSList *current;
363 current = doc->priv->redo;
364 doc->priv->redo = current->next;
365 doc->priv->history_size--;
367 delete ((Inkscape::Event *) current->data);
368 g_slist_free_1 (current);
369 }
370 }
371 /*
372 Local Variables:
373 mode:c++
374 c-file-style:"stroustrup"
375 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
376 indent-tabs-mode:nil
377 fill-column:99
378 End:
379 */
380 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :