Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
document-undo.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Undo/Redo stack implementation
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * MenTaLguY <mental@rydia.net>
8 * Abhishek Sharma
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 v2+, 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 efficient 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 */
47
48#include "document-undo.h"
49
50#include <glibmm/ustring.h> // for ustring, operator==
51#include <vector> // for vector
52
53#include "document.h" // for SPDocument
54#include "event.h" // for Event
55#include "inkscape.h" // for Application, INKSCAPE
56#include "composite-undo-stack-observer.h" // for CompositeUndoStackObserver
57
58#include "debug/event-tracker.h" // for EventTracker
59#include "debug/event.h" // for Event
60#include "debug/simple-event.h" // for SimpleEvent
61#include "debug/timestamp.h" // for timestamp
62#include "object/sp-lpe-item.h" // for sp_lpe_item_update_pathef...
63#include "object/sp-root.h" // for SPRoot
64#include "preferences.h"
65#include "xml/event-fns.h" // for sp_repr_begin_transaction
66
67namespace Inkscape::XML {
68class Event;
69} // namespace Inkscape::XML
70
71
72/*
73 * Undo & redo
74 */
75
77{
78 g_assert (doc != nullptr);
79
80 if ( sensitive == doc->sensitive )
81 return;
82
83 if (sensitive) {
85 } else {
87 doc->partial,
89 );
90 }
91
92 doc->sensitive = sensitive;
93}
94
96 g_assert(document != nullptr);
97
98 return document->sensitive;
99}
100
102 Glib::ustring const &event_description,
103 Glib::ustring const &icon_name,
104 unsigned int object_modified_tag)
105{
106 if (doc->sensitive) {
107 maybeDone(doc, nullptr, event_description, icon_name, object_modified_tag);
108 }
109}
110
112{
113 doc->actionkey.clear();
114}
115
117{
118 doc->action_expires = seconds;
119}
120
121namespace {
122
126
127typedef SimpleEvent<Event::INTERACTION> InteractionEvent;
128
129class CommitEvent : public InteractionEvent {
130public:
131
132 CommitEvent(SPDocument *doc, const gchar *key, const gchar* event_description, const gchar *icon_name)
133 : InteractionEvent("commit")
134 {
135 _addProperty("timestamp", timestamp());
136 _addProperty("document", doc->serial());
137
138 if (key) {
139 _addProperty("merge-key", key);
140 }
141
142 if (event_description) {
143 _addProperty("description", event_description);
144 }
145
146 if (icon_name) {
147 _addProperty("icon-name", icon_name);
148 }
149 }
150};
151
152}
153
154// 'key' is used to coalesce changes of the same type.
155// 'event_description' and 'icon_name' are used in the Undo History dialog.
157 const gchar *key,
158 Glib::ustring const &event_description,
159 Glib::ustring const &icon_name,
160 unsigned int object_modified_tag)
161{
162 g_assert (doc != nullptr);
163 g_assert (doc->sensitive);
164 if ( key && !*key ) {
165 g_warning("Blank undo key specified.");
166 }
167
168 bool limit_undo = Inkscape::Preferences::get()->getBool("/options/undo/limit");
169 auto undo_size = Inkscape::Preferences::get()->getInt("/options/undo/size", 200);
170
171 // Undo size zero will cause crashes when changing the preference during an active document
172 assert(undo_size > 0);
173
174 doc->before_commit_signal.emit();
175 // This is only used for output to debug log file (and not for undo).
176 Inkscape::Debug::EventTracker<CommitEvent> tracker(doc, key, event_description.c_str(), icon_name.c_str());
177
178 doc->collectOrphans();
179 doc->ensureUpToDate(object_modified_tag);
180
182
184 doc->partial = nullptr;
185
186 if (!log) {
188 return;
189 }
190
191 bool expired = doc->undo_timer.is_active() && doc->undo_timer.elapsed() > doc->action_expires;
192 if (key && !expired && !doc->actionkey.empty() && (doc->actionkey == key) && !doc->undo.empty()) {
193 (doc->undo.back())->event = sp_repr_coalesce_log ((doc->undo.back())->event, log);
194 } else {
195 Inkscape::Event *event = new Inkscape::Event(log, event_description, icon_name);
196 doc->undo.push_back(event);
198 }
199
200 if ( key ) {
201 doc->actionkey = key;
202 // Action key will expire in 10 seconds by default
203 doc->undo_timer.start();
204 doc->action_expires = 10.0;
205 } else {
206 doc->actionkey.clear();
207 doc->undo_timer.stop();
208 }
209
210 doc->virgin = FALSE;
213 doc->commit_signal.emit();
214
215 // Keeping the undo stack to a reasonable size is done when we're not maybeDone.
216 // Note: Redo does not need the same controls since in theory it should never be
217 // able to get larger than the undo size as it's only populated with undo items.
218 if (!key) {
219 // We remove undo items from the front of the stack
220 while (limit_undo && (int)doc->undo.size() > undo_size) {
221 Inkscape::Event *e = doc->undo.front();
223 doc->undo.pop_front();
224 delete e;
225 }
226 }
227}
228
230{
231 g_assert (doc != nullptr);
232 g_assert (doc->sensitive);
233 done(doc, "undozone", "");
234 // ensure tere is something to undo (extension crach can do nothing)
235 if (!doc->undo.empty() && doc->undo.back()->description == "undozone") {
236 undo(doc);
237 clearRedo(doc);
238 }
239}
240
241// Member function for friend access to SPDocument privates.
244 if (log || doc.partial) {
245 g_warning ("Incomplete undo transaction (added to next undo):");
247 if (!doc.undo.empty()) {
248 Inkscape::Event* undo_stack_top = doc.undo.back();
249 undo_stack_top->event = sp_repr_coalesce_log(undo_stack_top->event, doc.partial);
250 } else {
252 }
253 doc.partial = nullptr;
254 }
255}
256
257// Member function for friend access to SPDocument privates.
260 doc.ensureUpToDate();
261
264
265 if (update_log != nullptr) {
266 g_warning("Document was modified while being updated after undo operation");
267 sp_repr_debug_print_log(update_log);
268
269 //Coalesce the update changes with the last action performed by user
270 if (!doc.undo.empty()) {
271 Inkscape::Event* undo_stack_top = doc.undo.back();
272 undo_stack_top->event = sp_repr_coalesce_log(undo_stack_top->event, update_log);
273 } else {
274 sp_repr_free_log(update_log);
275 }
276 }
277}
278
280{
283
284 gboolean ret;
285
286 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
287 g_assert (doc != nullptr);
288 g_assert (doc->sensitive);
289
290 doc->sensitive = FALSE;
291 doc->seeking = true;
292
293 doc->actionkey.clear();
294
295 finish_incomplete_transaction(*doc);
296 if (! doc->undo.empty()) {
297 Inkscape::Event *log = doc->undo.back();
298 doc->undo.pop_back();
299 sp_repr_undo_log (log->event);
300 perform_document_update(*doc);
301 doc->redo.push_back(log);
304 ret = TRUE;
305 } else {
306 ret = FALSE;
307 }
308
310 doc->update_lpobjs();
311 doc->sensitive = TRUE;
312 doc->seeking = false;
313 if (ret) INKSCAPE.external_change();
314 return ret;
315}
316
318{
321
322 gboolean ret;
323
324 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
325
326 g_assert (doc != nullptr);
327 g_assert (doc->sensitive);
328 doc->sensitive = FALSE;
329 doc->seeking = true;
330 doc->actionkey.clear();
331
332 finish_incomplete_transaction(*doc);
333 if (! doc->redo.empty()) {
334 Inkscape::Event *log = doc->redo.back();
335 doc->redo.pop_back();
336 sp_repr_replay_log (log->event);
337 doc->undo.push_back(log);
338 perform_document_update(*doc);
339
342 ret = TRUE;
343 } else {
344 ret = FALSE;
345 }
346
348 doc->update_lpobjs();
349 doc->sensitive = TRUE;
350 doc->seeking = false;
351 if (ret) {
352 INKSCAPE.external_change();
354 }
355 return ret;
356}
357
359{
360 if (! doc->undo.empty())
362 while (! doc->undo.empty()) {
363 Inkscape::Event *e = doc->undo.back();
364 doc->undo.pop_back();
365 delete e;
366 }
367}
368
370{
371 if (!doc->redo.empty())
373
374 while (! doc->redo.empty()) {
375 Inkscape::Event *e = doc->redo.back();
376 doc->redo.pop_back();
377 delete e;
378 }
379}
380
381/*
382 Local Variables:
383 mode:c++
384 c-file-style:"stroustrup"
385 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
386 indent-tabs-mode:nil
387 fill-column:99
388 End:
389*/
390// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
void undo(SPDocument *document)
void notifyUndoCommitEvent(Event *log) override
Notify all registered UndoStackObservers of an event log being committed to the undo stack.
void notifyRedoEvent(Event *log) override
Notify all registered UndoStackObservers of a redo event.
void notifyClearUndoEvent() override
Triggered when the undo log is cleared.
void notifyUndoExpired(Event *log) override
Notify all registered UndoStackObservers of an event log being expired from the back of the undo stac...
void notifyClearRedoEvent() override
Triggered when the redo log is cleared.
void notifyUndoEvent(Event *log) override
Notify all registered UndoStackObservers of an undo event.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void setKeyExpires(SPDocument *document, double timeout)
Set the timeout for the last maybeDone call.
static bool getUndoSensitive(SPDocument const *document)
static void setUndoSensitive(SPDocument *doc, bool sensitive)
Set undo sensitivity.
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static gboolean redo(SPDocument *document)
static void perform_document_update(SPDocument &document)
static void finish_incomplete_transaction(SPDocument &document)
static gboolean undo(SPDocument *document)
static void cancel(SPDocument *document)
static void clearRedo(SPDocument *document)
static void resetKey(SPDocument *document)
static void clearUndo(SPDocument *document)
XML::Event * event
Definition event.h:41
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
Enumeration of all XML event types.
Definition event.h:53
Typed SVG document implementation.
Definition document.h:103
void collectOrphans()
Definition document.cpp:329
Inkscape::XML::Document * rdoc
Our Inkscape::XML::Document.
Definition document.h:392
Inkscape::XML::Event * partial
Definition document.h:434
SPDocument::CommitSignal commit_signal
Definition document.h:479
void update_lpobjs()
Definition document.cpp:659
Inkscape::CompositeUndoStackObserver undoStackObservers
Definition document.h:438
void setModifiedSinceSave(bool const modified=true)
Indicate to the user if the document has been modified since the last save by displaying a "*" in fro...
bool seeking
Definition document.h:443
bool sensitive
Definition document.h:433
SPDocument::BeforeCommitSignal before_commit_signal
Definition document.h:480
unsigned long serial() const
Definition document.h:360
std::deque< Inkscape::Event * > redo
Definition document.h:436
bool virgin
Has the document never been touched?
Definition document.h:385
void emitReconstructionFinish()
double action_expires
Definition document.h:446
std::deque< Inkscape::Event * > undo
Definition document.h:435
Glib::Timer undo_timer
Definition document.h:447
Glib::ustring actionkey
Definition document.h:445
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
TODO: insert short description here.
TODO: insert short description here.
void sp_repr_undo_log(Inkscape::XML::Event *log)
Definition event.cpp:137
Inkscape::XML::Event * sp_repr_commit_undoable(Inkscape::XML::Document *doc)
Definition event.cpp:69
void sp_repr_replay_log(Inkscape::XML::Event *log)
Definition event.cpp:205
void sp_repr_debug_print_log(Inkscape::XML::Event const *log)
Definition event.cpp:526
Inkscape::XML::Event * sp_repr_coalesce_log(Inkscape::XML::Event *a, Inkscape::XML::Event *b)
Definition event.cpp:259
void sp_repr_free_log(Inkscape::XML::Event *log)
Definition event.cpp:285
void sp_repr_begin_transaction(Inkscape::XML::Document *doc)
Definition event.cpp:30
std::shared_ptr< std::string > timestamp()
Definition timestamp.cpp:25
@Inkscape XML tree.
static cairo_user_data_key_t key
Singleton class to access the preferences file in a convenient way.
Piecewise< SBasis > log(Interval in)
Definition pw-funcs.cpp:37
Base class for live path effect items.
SPRoot: SVG <svg> implementation.