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 "event.h"
17 #include "event-fns.h"
18 #include "util/reverse-list.h"
19 #include "xml/document.h"
20 #include "xml/node-observer.h"
21 #include "debug/event-tracker.h"
22 #include "debug/simple-event.h"
24 using Inkscape::Util::List;
25 using Inkscape::Util::reverse_list;
27 int Inkscape::XML::Event::_next_serial=0;
29 using Inkscape::XML::Session;
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 Session *session=doc->session();
42 g_assert(session != NULL);
43 session->beginTransaction();
44 }
46 void
47 sp_repr_rollback (Inkscape::XML::Document *doc)
48 {
49 using Inkscape::Debug::SimpleEvent;
50 using Inkscape::Debug::EventTracker;
51 using Inkscape::Debug::Event;
53 EventTracker<SimpleEvent<Event::XML> > tracker("rollback");
55 g_assert(doc != NULL);
56 Session *session=doc->session();
57 g_assert(session != NULL);
58 session->rollback();
59 }
61 void
62 sp_repr_commit (Inkscape::XML::Document *doc)
63 {
64 using Inkscape::Debug::SimpleEvent;
65 using Inkscape::Debug::EventTracker;
66 using Inkscape::Debug::Event;
68 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
70 g_assert(doc != NULL);
71 Session *session=doc->session();
72 g_assert(session != NULL);
73 session->commit();
74 }
76 Inkscape::XML::Event *
77 sp_repr_commit_undoable (Inkscape::XML::Document *doc)
78 {
79 using Inkscape::Debug::SimpleEvent;
80 using Inkscape::Debug::EventTracker;
81 using Inkscape::Debug::Event;
83 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
85 g_assert(doc != NULL);
86 Session *session=doc->session();
87 g_assert(session != NULL);
88 return session->commitUndoable();
89 }
91 namespace {
93 class LogPerformer : public Inkscape::XML::NodeObserver {
94 public:
95 typedef Inkscape::XML::Node Node;
97 static LogPerformer &instance() {
98 static LogPerformer singleton;
99 return singleton;
100 }
102 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
103 parent.addChild(&child, ref);
104 }
106 void notifyChildRemoved(Node &parent, Node &child, Node *old_ref) {
107 parent.removeChild(&child);
108 }
110 void notifyChildOrderChanged(Node &parent, Node &child,
111 Node *old_ref, Node *new_ref)
112 {
113 parent.changeOrder(&child, new_ref);
114 }
116 void notifyAttributeChanged(Node &node, GQuark name,
117 Inkscape::Util::ptr_shared<char> old_value,
118 Inkscape::Util::ptr_shared<char> new_value)
119 {
120 node.setAttribute(g_quark_to_string(name), new_value);
121 }
123 void notifyContentChanged(Node &node,
124 Inkscape::Util::ptr_shared<char> old_value,
125 Inkscape::Util::ptr_shared<char> new_value)
126 {
127 node.setContent(new_value);
128 }
129 };
131 }
133 void Inkscape::XML::undo_log_to_observer(
134 Inkscape::XML::Event const *log,
135 Inkscape::XML::NodeObserver &observer
136 ) {
137 for ( Event const *action = log ; action ; action = action->next ) {
138 action->undoOne(observer);
139 }
140 }
142 void
143 sp_repr_undo_log (Inkscape::XML::Event *log)
144 {
145 using Inkscape::Debug::SimpleEvent;
146 using Inkscape::Debug::EventTracker;
147 using Inkscape::Debug::Event;
149 EventTracker<SimpleEvent<Event::XML> > tracker("undo-log");
151 if (log) {
152 g_assert(!log->repr->session()->inTransaction());
153 }
155 Inkscape::XML::undo_log_to_observer(log, LogPerformer::instance());
156 }
158 void Inkscape::XML::EventAdd::_undoOne(
159 Inkscape::XML::NodeObserver &observer
160 ) const {
161 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
162 }
164 void Inkscape::XML::EventDel::_undoOne(
165 Inkscape::XML::NodeObserver &observer
166 ) const {
167 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
168 }
170 void Inkscape::XML::EventChgAttr::_undoOne(
171 Inkscape::XML::NodeObserver &observer
172 ) const {
173 observer.notifyAttributeChanged(*this->repr, this->key, this->newval, this->oldval);
174 }
176 void Inkscape::XML::EventChgContent::_undoOne(
177 Inkscape::XML::NodeObserver &observer
178 ) const {
179 observer.notifyContentChanged(*this->repr, this->newval, this->oldval);
180 }
182 void Inkscape::XML::EventChgOrder::_undoOne(
183 Inkscape::XML::NodeObserver &observer
184 ) const {
185 observer.notifyChildOrderChanged(*this->repr, *this->child, this->newref, this->oldref);
186 }
188 void Inkscape::XML::replay_log_to_observer(
189 Inkscape::XML::Event const *log,
190 Inkscape::XML::NodeObserver &observer
191 ) {
192 List<Inkscape::XML::Event const &> reversed =
193 reverse_list<Inkscape::XML::Event::ConstIterator>(log, NULL);
194 for ( ; reversed ; ++reversed ) {
195 reversed->replayOne(observer);
196 }
197 }
199 void
200 sp_repr_replay_log (Inkscape::XML::Event *log)
201 {
202 using Inkscape::Debug::SimpleEvent;
203 using Inkscape::Debug::EventTracker;
204 using Inkscape::Debug::Event;
206 EventTracker<SimpleEvent<Event::XML> > tracker("replay-log");
208 if (log) {
209 // Nodes created by the whiteboard deserializer tend to not
210 // have an associated session. This conditional hacks a way around that,
211 // but what's the best way to fix the whiteboard code so that new nodes
212 // will have an associated session? -- yipdw
213 if (log->repr->session()) {
214 g_assert(!log->repr->session()->inTransaction());
215 }
216 }
218 Inkscape::XML::replay_log_to_observer(log, LogPerformer::instance());
219 }
221 void Inkscape::XML::EventAdd::_replayOne(
222 Inkscape::XML::NodeObserver &observer
223 ) const {
224 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
225 }
227 void Inkscape::XML::EventDel::_replayOne(
228 Inkscape::XML::NodeObserver &observer
229 ) const {
230 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
231 }
233 void Inkscape::XML::EventChgAttr::_replayOne(
234 Inkscape::XML::NodeObserver &observer
235 ) const {
236 observer.notifyAttributeChanged(*this->repr, this->key, this->oldval, this->newval);
237 }
239 void Inkscape::XML::EventChgContent::_replayOne(
240 Inkscape::XML::NodeObserver &observer
241 ) const {
242 observer.notifyContentChanged(*this->repr, this->oldval, this->newval);
243 }
245 void Inkscape::XML::EventChgOrder::_replayOne(
246 Inkscape::XML::NodeObserver &observer
247 ) const {
248 observer.notifyChildOrderChanged(*this->repr, *this->child, this->oldref, this->newref);
249 }
251 Inkscape::XML::Event *
252 sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b)
253 {
254 Inkscape::XML::Event *action;
255 Inkscape::XML::Event **prev_ptr;
257 if (!b) return a;
258 if (!a) return b;
260 /* find the earliest action in the second log */
261 /* (also noting the pointer that references it, so we can
262 * replace it later) */
263 prev_ptr = &b;
264 for ( action = b ; action->next ; action = action->next ) {
265 prev_ptr = &action->next;
266 }
268 /* add the first log after it */
269 action->next = a;
271 /* optimize the result */
272 *prev_ptr = action->optimizeOne();
274 return b;
275 }
277 void
278 sp_repr_free_log (Inkscape::XML::Event *log)
279 {
280 while (log) {
281 Inkscape::XML::Event *action;
282 action = log;
283 log = action->next;
284 delete action;
285 }
286 }
288 namespace {
290 template <typename T> struct ActionRelations;
292 template <>
293 struct ActionRelations<Inkscape::XML::EventAdd> {
294 typedef Inkscape::XML::EventDel Opposite;
295 };
297 template <>
298 struct ActionRelations<Inkscape::XML::EventDel> {
299 typedef Inkscape::XML::EventAdd Opposite;
300 };
302 template <typename A>
303 Inkscape::XML::Event *cancel_add_or_remove(A *action) {
304 typedef typename ActionRelations<A>::Opposite Opposite;
305 Opposite *opposite=dynamic_cast<Opposite *>(action->next);
307 if ( opposite && opposite->repr == action->repr &&
308 opposite->child == action->child &&
309 opposite->ref == action->ref )
310 {
311 Inkscape::XML::Event *remaining=opposite->next;
313 delete opposite;
314 delete action;
316 return remaining;
317 } else {
318 return action;
319 }
320 }
322 }
324 Inkscape::XML::Event *Inkscape::XML::EventAdd::_optimizeOne() {
325 return cancel_add_or_remove(this);
326 }
328 Inkscape::XML::Event *Inkscape::XML::EventDel::_optimizeOne() {
329 return cancel_add_or_remove(this);
330 }
332 Inkscape::XML::Event *Inkscape::XML::EventChgAttr::_optimizeOne() {
333 Inkscape::XML::EventChgAttr *chg_attr=dynamic_cast<Inkscape::XML::EventChgAttr *>(this->next);
335 /* consecutive chgattrs on the same key can be combined */
336 if ( chg_attr && chg_attr->repr == this->repr &&
337 chg_attr->key == this->key )
338 {
339 /* replace our oldval with the prior action's */
340 this->oldval = chg_attr->oldval;
342 /* discard the prior action */
343 this->next = chg_attr->next;
344 delete chg_attr;
345 }
347 return this;
348 }
350 Inkscape::XML::Event *Inkscape::XML::EventChgContent::_optimizeOne() {
351 Inkscape::XML::EventChgContent *chg_content=dynamic_cast<Inkscape::XML::EventChgContent *>(this->next);
353 /* consecutive content changes can be combined */
354 if ( chg_content && chg_content->repr == this->repr ) {
355 /* replace our oldval with the prior action's */
356 this->oldval = chg_content->oldval;
358 /* get rid of the prior action*/
359 this->next = chg_content->next;
360 delete chg_content;
361 }
363 return this;
364 }
366 Inkscape::XML::Event *Inkscape::XML::EventChgOrder::_optimizeOne() {
367 Inkscape::XML::EventChgOrder *chg_order=dynamic_cast<Inkscape::XML::EventChgOrder *>(this->next);
369 /* consecutive chgorders for the same child may be combined or
370 * canceled out */
371 if ( chg_order && chg_order->repr == this->repr &&
372 chg_order->child == this->child )
373 {
374 if ( chg_order->oldref == this->newref ) {
375 /* cancel them out */
376 Inkscape::XML::Event *after=chg_order->next;
378 delete chg_order;
379 delete this;
381 return after;
382 } else {
383 /* combine them */
384 this->oldref = chg_order->oldref;
386 /* get rid of the other one */
387 this->next = chg_order->next;
388 delete chg_order;
390 return this;
391 }
392 } else {
393 return this;
394 }
395 }
397 namespace {
399 class LogPrinter : public Inkscape::XML::NodeObserver {
400 public:
401 typedef Inkscape::XML::Node Node;
403 static LogPrinter &instance() {
404 static LogPrinter singleton;
405 return singleton;
406 }
408 static Glib::ustring node_to_string(Node const &node) {
409 Glib::ustring result;
410 char const *type_name=NULL;
411 switch (node.type()) {
412 case Inkscape::XML::DOCUMENT_NODE:
413 type_name = "Document";
414 break;
415 case Inkscape::XML::ELEMENT_NODE:
416 type_name = "Element";
417 break;
418 case Inkscape::XML::TEXT_NODE:
419 type_name = "Text";
420 break;
421 case Inkscape::XML::COMMENT_NODE:
422 type_name = "Comment";
423 break;
424 default:
425 g_assert_not_reached();
426 }
427 char buffer[40];
428 result.append("#<");
429 result.append(type_name);
430 result.append(":");
431 snprintf(buffer, 40, "0x%p", &node);
432 result.append(buffer);
433 result.append(">");
435 return result;
436 }
438 static Glib::ustring ref_to_string(Node *ref) {
439 if (ref) {
440 return node_to_string(*ref);
441 } else {
442 return "beginning";
443 }
444 }
446 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
447 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());
448 }
450 void notifyChildRemoved(Node &parent, Node &child, Node *ref) {
451 g_warning("Event: Removed %s from %s", node_to_string(parent).c_str(), node_to_string(child).c_str());
452 }
454 void notifyChildOrderChanged(Node &parent, Node &child,
455 Node *old_ref, Node *new_ref)
456 {
457 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());
458 }
460 void notifyAttributeChanged(Node &node, GQuark name,
461 Inkscape::Util::ptr_shared<char> old_value,
462 Inkscape::Util::ptr_shared<char> new_value)
463 {
464 if (new_value) {
465 g_warning("Event: Set attribute %s to \"%s\" on %s", g_quark_to_string(name), new_value.pointer(), node_to_string(node).c_str());
466 } else {
467 g_warning("Event: Unset attribute %s on %s", g_quark_to_string(name), node_to_string(node).c_str());
468 }
469 }
471 void notifyContentChanged(Node &node,
472 Inkscape::Util::ptr_shared<char> old_value,
473 Inkscape::Util::ptr_shared<char> new_value)
474 {
475 if (new_value) {
476 g_warning("Event: Set content of %s to \"%s\"", node_to_string(node).c_str(), new_value.pointer());
477 } else {
478 g_warning("Event: Unset content of %s", node_to_string(node).c_str());
479 }
480 }
481 };
483 }
485 void
486 sp_repr_debug_print_log(Inkscape::XML::Event const *log) {
487 Inkscape::XML::replay_log_to_observer(log, LogPrinter::instance());
488 }