1 /*
2 * Repr transaction logging
3 *
4 * Authors:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * MenTaLguY <mental@rydia.net>
7 *
8 * Copyright (C) 2004-2005 MenTaLguY
9 * Copyright (C) 1999-2003 authors
10 * Copyright (C) 2001-2002 Ximian, Inc.
11 * g++ port Copyright (C) 2003 Nathan Hurst
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #include <glib.h> // g_assert()
18 #include "event.h"
19 #include "event-fns.h"
20 #include "util/reverse-list.h"
21 #include "xml/document.h"
22 #include "xml/node-observer.h"
23 #include "debug/event-tracker.h"
24 #include "debug/simple-event.h"
26 using Inkscape::Util::List;
27 using Inkscape::Util::reverse_list;
29 int Inkscape::XML::Event::_next_serial=0;
31 void
32 sp_repr_begin_transaction (Inkscape::XML::Document *doc)
33 {
34 using Inkscape::Debug::SimpleEvent;
35 using Inkscape::Debug::EventTracker;
36 using Inkscape::Debug::Event;
38 EventTracker<SimpleEvent<Event::XML> > tracker("begin-transaction");
40 g_assert(doc != NULL);
41 doc->beginTransaction();
42 }
44 void
45 sp_repr_rollback (Inkscape::XML::Document *doc)
46 {
47 using Inkscape::Debug::SimpleEvent;
48 using Inkscape::Debug::EventTracker;
49 using Inkscape::Debug::Event;
51 EventTracker<SimpleEvent<Event::XML> > tracker("rollback");
53 g_assert(doc != NULL);
54 doc->rollback();
55 }
57 void
58 sp_repr_commit (Inkscape::XML::Document *doc)
59 {
60 using Inkscape::Debug::SimpleEvent;
61 using Inkscape::Debug::EventTracker;
62 using Inkscape::Debug::Event;
64 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
66 g_assert(doc != NULL);
67 doc->commit();
68 }
70 Inkscape::XML::Event *
71 sp_repr_commit_undoable (Inkscape::XML::Document *doc)
72 {
73 using Inkscape::Debug::SimpleEvent;
74 using Inkscape::Debug::EventTracker;
75 using Inkscape::Debug::Event;
77 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
79 g_assert(doc != NULL);
80 return doc->commitUndoable();
81 }
83 namespace {
85 class LogPerformer : public Inkscape::XML::NodeObserver {
86 public:
87 typedef Inkscape::XML::Node Node;
89 static LogPerformer &instance() {
90 static LogPerformer singleton;
91 return singleton;
92 }
94 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
95 parent.addChild(&child, ref);
96 }
98 void notifyChildRemoved(Node &parent, Node &child, Node */*old_ref*/) {
99 parent.removeChild(&child);
100 }
102 void notifyChildOrderChanged(Node &parent, Node &child,
103 Node */*old_ref*/, Node *new_ref)
104 {
105 parent.changeOrder(&child, new_ref);
106 }
108 void notifyAttributeChanged(Node &node, GQuark name,
109 Inkscape::Util::ptr_shared<char> /*old_value*/,
110 Inkscape::Util::ptr_shared<char> new_value)
111 {
112 node.setAttribute(g_quark_to_string(name), new_value);
113 }
115 void notifyContentChanged(Node &node,
116 Inkscape::Util::ptr_shared<char> /*old_value*/,
117 Inkscape::Util::ptr_shared<char> new_value)
118 {
119 node.setContent(new_value);
120 }
121 };
123 }
125 void Inkscape::XML::undo_log_to_observer(
126 Inkscape::XML::Event const *log,
127 Inkscape::XML::NodeObserver &observer
128 ) {
129 for ( Event const *action = log ; action ; action = action->next ) {
130 action->undoOne(observer);
131 }
132 }
134 void
135 sp_repr_undo_log (Inkscape::XML::Event *log)
136 {
137 using Inkscape::Debug::SimpleEvent;
138 using Inkscape::Debug::EventTracker;
139 using Inkscape::Debug::Event;
141 EventTracker<SimpleEvent<Event::XML> > tracker("undo-log");
143 if (log && log->repr) {
144 g_assert(!log->repr->document()->inTransaction());
145 }
147 Inkscape::XML::undo_log_to_observer(log, LogPerformer::instance());
148 }
150 void Inkscape::XML::EventAdd::_undoOne(
151 Inkscape::XML::NodeObserver &observer
152 ) const {
153 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
154 }
156 void Inkscape::XML::EventDel::_undoOne(
157 Inkscape::XML::NodeObserver &observer
158 ) const {
159 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
160 }
162 void Inkscape::XML::EventChgAttr::_undoOne(
163 Inkscape::XML::NodeObserver &observer
164 ) const {
165 observer.notifyAttributeChanged(*this->repr, this->key, this->newval, this->oldval);
166 }
168 void Inkscape::XML::EventChgContent::_undoOne(
169 Inkscape::XML::NodeObserver &observer
170 ) const {
171 observer.notifyContentChanged(*this->repr, this->newval, this->oldval);
172 }
174 void Inkscape::XML::EventChgOrder::_undoOne(
175 Inkscape::XML::NodeObserver &observer
176 ) const {
177 observer.notifyChildOrderChanged(*this->repr, *this->child, this->newref, this->oldref);
178 }
180 void Inkscape::XML::replay_log_to_observer(
181 Inkscape::XML::Event const *log,
182 Inkscape::XML::NodeObserver &observer
183 ) {
184 List<Inkscape::XML::Event const &> reversed =
185 reverse_list<Inkscape::XML::Event::ConstIterator>(log, NULL);
186 for ( ; reversed ; ++reversed ) {
187 reversed->replayOne(observer);
188 }
189 }
191 void
192 sp_repr_replay_log (Inkscape::XML::Event *log)
193 {
194 using Inkscape::Debug::SimpleEvent;
195 using Inkscape::Debug::EventTracker;
196 using Inkscape::Debug::Event;
198 EventTracker<SimpleEvent<Event::XML> > tracker("replay-log");
200 if (log) {
201 if (log->repr->document()) {
202 g_assert(!log->repr->document()->inTransaction());
203 }
204 }
206 Inkscape::XML::replay_log_to_observer(log, LogPerformer::instance());
207 }
209 void Inkscape::XML::EventAdd::_replayOne(
210 Inkscape::XML::NodeObserver &observer
211 ) const {
212 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
213 }
215 void Inkscape::XML::EventDel::_replayOne(
216 Inkscape::XML::NodeObserver &observer
217 ) const {
218 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
219 }
221 void Inkscape::XML::EventChgAttr::_replayOne(
222 Inkscape::XML::NodeObserver &observer
223 ) const {
224 observer.notifyAttributeChanged(*this->repr, this->key, this->oldval, this->newval);
225 }
227 void Inkscape::XML::EventChgContent::_replayOne(
228 Inkscape::XML::NodeObserver &observer
229 ) const {
230 observer.notifyContentChanged(*this->repr, this->oldval, this->newval);
231 }
233 void Inkscape::XML::EventChgOrder::_replayOne(
234 Inkscape::XML::NodeObserver &observer
235 ) const {
236 observer.notifyChildOrderChanged(*this->repr, *this->child, this->oldref, this->newref);
237 }
239 Inkscape::XML::Event *
240 sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b)
241 {
242 Inkscape::XML::Event *action;
243 Inkscape::XML::Event **prev_ptr;
245 if (!b) return a;
246 if (!a) return b;
248 /* find the earliest action in the second log */
249 /* (also noting the pointer that references it, so we can
250 * replace it later) */
251 prev_ptr = &b;
252 for ( action = b ; action->next ; action = action->next ) {
253 prev_ptr = &action->next;
254 }
256 /* add the first log after it */
257 action->next = a;
259 /* optimize the result */
260 *prev_ptr = action->optimizeOne();
262 return b;
263 }
265 void
266 sp_repr_free_log (Inkscape::XML::Event *log)
267 {
268 while (log) {
269 Inkscape::XML::Event *action;
270 action = log;
271 log = action->next;
272 delete action;
273 }
274 }
276 namespace {
278 template <typename T> struct ActionRelations;
280 template <>
281 struct ActionRelations<Inkscape::XML::EventAdd> {
282 typedef Inkscape::XML::EventDel Opposite;
283 };
285 template <>
286 struct ActionRelations<Inkscape::XML::EventDel> {
287 typedef Inkscape::XML::EventAdd Opposite;
288 };
290 template <typename A>
291 Inkscape::XML::Event *cancel_add_or_remove(A *action) {
292 typedef typename ActionRelations<A>::Opposite Opposite;
293 Opposite *opposite=dynamic_cast<Opposite *>(action->next);
295 if ( opposite && opposite->repr == action->repr &&
296 opposite->child == action->child &&
297 opposite->ref == action->ref )
298 {
299 Inkscape::XML::Event *remaining=opposite->next;
301 delete opposite;
302 delete action;
304 return remaining;
305 } else {
306 return action;
307 }
308 }
310 }
312 Inkscape::XML::Event *Inkscape::XML::EventAdd::_optimizeOne() {
313 return cancel_add_or_remove(this);
314 }
316 Inkscape::XML::Event *Inkscape::XML::EventDel::_optimizeOne() {
317 return cancel_add_or_remove(this);
318 }
320 Inkscape::XML::Event *Inkscape::XML::EventChgAttr::_optimizeOne() {
321 Inkscape::XML::EventChgAttr *chg_attr=dynamic_cast<Inkscape::XML::EventChgAttr *>(this->next);
323 /* consecutive chgattrs on the same key can be combined */
324 if ( chg_attr && chg_attr->repr == this->repr &&
325 chg_attr->key == this->key )
326 {
327 /* replace our oldval with the prior action's */
328 this->oldval = chg_attr->oldval;
330 /* discard the prior action */
331 this->next = chg_attr->next;
332 delete chg_attr;
333 }
335 return this;
336 }
338 Inkscape::XML::Event *Inkscape::XML::EventChgContent::_optimizeOne() {
339 Inkscape::XML::EventChgContent *chg_content=dynamic_cast<Inkscape::XML::EventChgContent *>(this->next);
341 /* consecutive content changes can be combined */
342 if ( chg_content && chg_content->repr == this->repr ) {
343 /* replace our oldval with the prior action's */
344 this->oldval = chg_content->oldval;
346 /* get rid of the prior action*/
347 this->next = chg_content->next;
348 delete chg_content;
349 }
351 return this;
352 }
354 Inkscape::XML::Event *Inkscape::XML::EventChgOrder::_optimizeOne() {
355 Inkscape::XML::EventChgOrder *chg_order=dynamic_cast<Inkscape::XML::EventChgOrder *>(this->next);
357 /* consecutive chgorders for the same child may be combined or
358 * canceled out */
359 if ( chg_order && chg_order->repr == this->repr &&
360 chg_order->child == this->child )
361 {
362 if ( chg_order->oldref == this->newref ) {
363 /* cancel them out */
364 Inkscape::XML::Event *after=chg_order->next;
366 delete chg_order;
367 delete this;
369 return after;
370 } else {
371 /* combine them */
372 this->oldref = chg_order->oldref;
374 /* get rid of the other one */
375 this->next = chg_order->next;
376 delete chg_order;
378 return this;
379 }
380 } else {
381 return this;
382 }
383 }
385 namespace {
387 class LogPrinter : public Inkscape::XML::NodeObserver {
388 public:
389 typedef Inkscape::XML::Node Node;
391 static LogPrinter &instance() {
392 static LogPrinter singleton;
393 return singleton;
394 }
396 static Glib::ustring node_to_string(Node const &node) {
397 Glib::ustring result;
398 char const *type_name=NULL;
399 switch (node.type()) {
400 case Inkscape::XML::DOCUMENT_NODE:
401 type_name = "Document";
402 break;
403 case Inkscape::XML::ELEMENT_NODE:
404 type_name = "Element";
405 break;
406 case Inkscape::XML::TEXT_NODE:
407 type_name = "Text";
408 break;
409 case Inkscape::XML::COMMENT_NODE:
410 type_name = "Comment";
411 break;
412 default:
413 g_assert_not_reached();
414 }
415 char buffer[40];
416 result.append("#<");
417 result.append(type_name);
418 result.append(":");
419 snprintf(buffer, 40, "0x%p", &node);
420 result.append(buffer);
421 result.append(">");
423 return result;
424 }
426 static Glib::ustring ref_to_string(Node *ref) {
427 if (ref) {
428 return node_to_string(*ref);
429 } else {
430 return "beginning";
431 }
432 }
434 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
435 g_warning("Event: Added %s to %s after %s", node_to_string(parent).c_str(), node_to_string(child).c_str(), ref_to_string(ref).c_str());
436 }
438 void notifyChildRemoved(Node &parent, Node &child, Node */*ref*/) {
439 g_warning("Event: Removed %s from %s", node_to_string(parent).c_str(), node_to_string(child).c_str());
440 }
442 void notifyChildOrderChanged(Node &parent, Node &child,
443 Node */*old_ref*/, Node *new_ref)
444 {
445 g_warning("Event: Moved %s after %s in %s", node_to_string(child).c_str(), ref_to_string(new_ref).c_str(), node_to_string(parent).c_str());
446 }
448 void notifyAttributeChanged(Node &node, GQuark name,
449 Inkscape::Util::ptr_shared<char> /*old_value*/,
450 Inkscape::Util::ptr_shared<char> new_value)
451 {
452 if (new_value) {
453 g_warning("Event: Set attribute %s to \"%s\" on %s", g_quark_to_string(name), new_value.pointer(), node_to_string(node).c_str());
454 } else {
455 g_warning("Event: Unset attribute %s on %s", g_quark_to_string(name), node_to_string(node).c_str());
456 }
457 }
459 void notifyContentChanged(Node &node,
460 Inkscape::Util::ptr_shared<char> /*old_value*/,
461 Inkscape::Util::ptr_shared<char> new_value)
462 {
463 if (new_value) {
464 g_warning("Event: Set content of %s to \"%s\"", node_to_string(node).c_str(), new_value.pointer());
465 } else {
466 g_warning("Event: Unset content of %s", node_to_string(node).c_str());
467 }
468 }
469 };
471 }
473 void
474 sp_repr_debug_print_log(Inkscape::XML::Event const *log) {
475 Inkscape::XML::replay_log_to_observer(log, LogPrinter::instance());
476 }