Logo Search packages:      
Sourcecode: inkscape version File versions  Download package

event.cpp

/*
 * Repr transaction logging
 *
 * Authors:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   MenTaLguY  <mental@rydia.net>
 *
 * Copyright (C) 2004-2005 MenTaLguY
 * Copyright (C) 1999-2003 authors
 * Copyright (C) 2001-2002 Ximian, Inc.
 * g++ port Copyright (C) 2003 Nathan Hurst
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#include "repr.h"
#include "event.h"
#include "event-fns.h"
#include "util/list.h"
#include "util/reverse-list.h"
#include "xml/node.h"
#include "xml/document.h"
#include "xml/session.h"
#include "xml/node-observer.h"
#include "debug/event-tracker.h"
#include "debug/simple-event.h"
#include <cstring>

using Inkscape::Util::List;
using Inkscape::Util::reverse_list;

int Inkscape::XML::Event::_next_serial=0;

using Inkscape::XML::Session;

void
sp_repr_begin_transaction (Inkscape::XML::Document *doc)
{
      using Inkscape::Debug::SimpleEvent;
      using Inkscape::Debug::EventTracker;
      using Inkscape::Debug::Event;

      EventTracker<SimpleEvent<Event::XML> > tracker("begin-transaction");

      g_assert(doc != NULL);
      Session *session=doc->session();
      g_assert(session != NULL);
      session->beginTransaction();
}

void
sp_repr_rollback (Inkscape::XML::Document *doc)
{
      using Inkscape::Debug::SimpleEvent;
      using Inkscape::Debug::EventTracker;
      using Inkscape::Debug::Event;

      EventTracker<SimpleEvent<Event::XML> > tracker("rollback");

      g_assert(doc != NULL);
      Session *session=doc->session();
      g_assert(session != NULL);
      session->rollback();
}

void
sp_repr_commit (Inkscape::XML::Document *doc)
{
      using Inkscape::Debug::SimpleEvent;
      using Inkscape::Debug::EventTracker;
      using Inkscape::Debug::Event;

      EventTracker<SimpleEvent<Event::XML> > tracker("commit");

      g_assert(doc != NULL);
      Session *session=doc->session();
      g_assert(session != NULL);
      session->commit();
}

Inkscape::XML::Event *
sp_repr_commit_undoable (Inkscape::XML::Document *doc)
{
      using Inkscape::Debug::SimpleEvent;
      using Inkscape::Debug::EventTracker;
      using Inkscape::Debug::Event;

      EventTracker<SimpleEvent<Event::XML> > tracker("commit");

      g_assert(doc != NULL);
      Session *session=doc->session();
      g_assert(session != NULL);
      return session->commitUndoable();
}

namespace {

class LogPerformer : public Inkscape::XML::NodeObserver {
public:
      typedef Inkscape::XML::Node Node;

      static LogPerformer &instance() {
            static LogPerformer singleton;
            return singleton;
      }

      void notifyChildAdded(Node &parent, Node &child, Node *ref) {
            parent.addChild(&child, ref);
      }

      void notifyChildRemoved(Node &parent, Node &child, Node *old_ref) {
            parent.removeChild(&child);
      }

      void notifyChildOrderChanged(Node &parent, Node &child,
                               Node *old_ref, Node *new_ref)
      {
            parent.changeOrder(&child, new_ref);
      }

      void notifyAttributeChanged(Node &node, GQuark name,
                              Inkscape::Util::SharedCStringPtr old_value,
                            Inkscape::Util::SharedCStringPtr new_value)
      {
            node.setAttribute(g_quark_to_string(name), new_value);
      }

      void notifyContentChanged(Node &node,
                            Inkscape::Util::SharedCStringPtr old_value,
                          Inkscape::Util::SharedCStringPtr new_value)
      {
            node.setContent(new_value);
      }
};

}

void Inkscape::XML::undo_log_to_observer(
      Inkscape::XML::Event const *log,
      Inkscape::XML::NodeObserver &observer
) {
      for ( Event const *action = log ; action ; action = action->next ) {
            action->undoOne(observer);
      }
}

void
sp_repr_undo_log (Inkscape::XML::Event *log)
{
      using Inkscape::Debug::SimpleEvent;
      using Inkscape::Debug::EventTracker;
      using Inkscape::Debug::Event;

      EventTracker<SimpleEvent<Event::XML> > tracker("undo-log");

      if (log) {
            g_assert(!log->repr->session()->inTransaction());
      }

      Inkscape::XML::undo_log_to_observer(log, LogPerformer::instance());
}

void Inkscape::XML::EventAdd::_undoOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
}

void Inkscape::XML::EventDel::_undoOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyChildAdded(*this->repr, *this->child, this->ref);
}

void Inkscape::XML::EventChgAttr::_undoOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyAttributeChanged(*this->repr, this->key, this->newval, this->oldval);
}

void Inkscape::XML::EventChgContent::_undoOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyContentChanged(*this->repr, this->newval, this->oldval);
}

void Inkscape::XML::EventChgOrder::_undoOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyChildOrderChanged(*this->repr, *this->child, this->newref, this->oldref);
}

void Inkscape::XML::replay_log_to_observer(
      Inkscape::XML::Event const *log,
      Inkscape::XML::NodeObserver &observer
) {
      List<Inkscape::XML::Event const &> reversed =
        reverse_list<Inkscape::XML::Event::ConstIterator>(log, NULL);
      for ( ; reversed ; ++reversed ) {
            reversed->replayOne(observer);
      }
}

void
sp_repr_replay_log (Inkscape::XML::Event *log)
{
      using Inkscape::Debug::SimpleEvent;
      using Inkscape::Debug::EventTracker;
      using Inkscape::Debug::Event;

      EventTracker<SimpleEvent<Event::XML> > tracker("replay-log");

      if (log) {
            // Nodes created by the whiteboard deserializer tend to not 
            // have an associated session.  This conditional hacks a way around that,
            // but what's the best way to fix the whiteboard code so that new nodes
            // will have an associated session? -- yipdw
            if (log->repr->session()) {
                  g_assert(!log->repr->session()->inTransaction());
            }
      }

      Inkscape::XML::replay_log_to_observer(log, LogPerformer::instance());
}

void Inkscape::XML::EventAdd::_replayOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyChildAdded(*this->repr, *this->child, this->ref);
}

void Inkscape::XML::EventDel::_replayOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
}

void Inkscape::XML::EventChgAttr::_replayOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyAttributeChanged(*this->repr, this->key, this->oldval, this->newval);
}

void Inkscape::XML::EventChgContent::_replayOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyContentChanged(*this->repr, this->oldval, this->newval);
}

void Inkscape::XML::EventChgOrder::_replayOne(
      Inkscape::XML::NodeObserver &observer
) const {
      observer.notifyChildOrderChanged(*this->repr, *this->child, this->oldref, this->newref);
}

Inkscape::XML::Event *
sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b)
{
      Inkscape::XML::Event *action;
      Inkscape::XML::Event **prev_ptr;

      if (!b) return a;
      if (!a) return b;

      /* find the earliest action in the second log */
      /* (also noting the pointer that references it, so we can
       *  replace it later) */
      prev_ptr = &b;
      for ( action = b ; action->next ; action = action->next ) {
            prev_ptr = &action->next;
      }

      /* add the first log after it */
      action->next = a;

      /* optimize the result */
      *prev_ptr = action->optimizeOne();

      return b;
}

void
sp_repr_free_log (Inkscape::XML::Event *log)
{
      while (log) {
            Inkscape::XML::Event *action;
            action = log;
            log = action->next;
            delete action;
      }
}

namespace {

template <typename T> struct ActionRelations;

template <>
struct ActionRelations<Inkscape::XML::EventAdd> {
      typedef Inkscape::XML::EventDel Opposite;
};

template <>
struct ActionRelations<Inkscape::XML::EventDel> {
      typedef Inkscape::XML::EventAdd Opposite;
};

template <typename A>
Inkscape::XML::Event *cancel_add_or_remove(A *action) {
      typedef typename ActionRelations<A>::Opposite Opposite;
      Opposite *opposite=dynamic_cast<Opposite *>(action->next);

      if ( opposite && opposite->repr == action->repr &&
           opposite->child == action->child &&
           opposite->ref == action->ref )
      {
            Inkscape::XML::Event *remaining=opposite->next;

            delete opposite;
            delete action;

            return remaining;
      } else {
            return action;
      }
}

}

Inkscape::XML::Event *Inkscape::XML::EventAdd::_optimizeOne() {
      return cancel_add_or_remove(this);
}

Inkscape::XML::Event *Inkscape::XML::EventDel::_optimizeOne() {
      return cancel_add_or_remove(this);
}

Inkscape::XML::Event *Inkscape::XML::EventChgAttr::_optimizeOne() {
      Inkscape::XML::EventChgAttr *chg_attr=dynamic_cast<Inkscape::XML::EventChgAttr *>(this->next);

      /* consecutive chgattrs on the same key can be combined */
      if ( chg_attr && chg_attr->repr == this->repr &&
           chg_attr->key == this->key )
      {
            /* replace our oldval with the prior action's */
            this->oldval = chg_attr->oldval;

            /* discard the prior action */
            this->next = chg_attr->next;
            delete chg_attr;
      }

      return this;
}

Inkscape::XML::Event *Inkscape::XML::EventChgContent::_optimizeOne() {
      Inkscape::XML::EventChgContent *chg_content=dynamic_cast<Inkscape::XML::EventChgContent *>(this->next);

      /* consecutive content changes can be combined */
      if ( chg_content && chg_content->repr == this->repr ) {
            /* replace our oldval with the prior action's */
            this->oldval = chg_content->oldval;

            /* get rid of the prior action*/
            this->next = chg_content->next;
            delete chg_content;
      }

      return this;
}

Inkscape::XML::Event *Inkscape::XML::EventChgOrder::_optimizeOne() {
      Inkscape::XML::EventChgOrder *chg_order=dynamic_cast<Inkscape::XML::EventChgOrder *>(this->next);

      /* consecutive chgorders for the same child may be combined or
       * canceled out */
      if ( chg_order && chg_order->repr == this->repr &&
           chg_order->child == this->child )
      {
            if ( chg_order->oldref == this->newref ) {
                  /* cancel them out */
                  Inkscape::XML::Event *after=chg_order->next;

                  delete chg_order;
                  delete this;

                  return after;
            } else {
                  /* combine them */
                  this->oldref = chg_order->oldref;

                  /* get rid of the other one */
                  this->next = chg_order->next;
                  delete chg_order;

                  return this;
            }
      } else {
            return this;
      }
}

namespace {

class LogPrinter : public Inkscape::XML::NodeObserver {
public:
      typedef Inkscape::XML::Node Node;

      static LogPrinter &instance() {
            static LogPrinter singleton;
            return singleton;
      }

      static Glib::ustring node_to_string(Node const &node) {
            Glib::ustring result;
            char const *type_name=NULL;
            switch (node.type()) {
            case Inkscape::XML::DOCUMENT_NODE:
                  type_name = "Document";
                  break;
            case Inkscape::XML::ELEMENT_NODE:
                  type_name = "Element";
                  break;
            case Inkscape::XML::TEXT_NODE:
                  type_name = "Text";
                  break;
            case Inkscape::XML::COMMENT_NODE:
                  type_name = "Comment";
                  break;
            default:
                  g_assert_not_reached();
            }
            char buffer[40];
            result.append("#<");
            result.append(type_name);
            result.append(":");
            snprintf(buffer, 40, "0x%p", &node);
            result.append(buffer);
            result.append(">");

            return result;
      }

      static Glib::ustring ref_to_string(Node *ref) {
            if (ref) {
                  return node_to_string(*ref);
            } else {
                  return "beginning";
            }
      }

      void notifyChildAdded(Node &parent, Node &child, Node *ref) {
            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());
      }

      void notifyChildRemoved(Node &parent, Node &child, Node *ref) {
            g_warning("Event: Removed %s from %s", node_to_string(parent).c_str(), node_to_string(child).c_str());
      }

      void notifyChildOrderChanged(Node &parent, Node &child,
                                   Node *old_ref, Node *new_ref)
      {
            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());
      }

      void notifyAttributeChanged(Node &node, GQuark name,
                                  Inkscape::Util::SharedCStringPtr old_value,
                            Inkscape::Util::SharedCStringPtr new_value)
      {
            if (new_value) {
                  g_warning("Event: Set attribute %s to \"%s\" on %s", g_quark_to_string(name), new_value.cString(), node_to_string(node).c_str());
            } else {
                  g_warning("Event: Unset attribute %s on %s", g_quark_to_string(name), node_to_string(node).c_str());
            }
      }

      void notifyContentChanged(Node &node,
                            Inkscape::Util::SharedCStringPtr old_value,
                          Inkscape::Util::SharedCStringPtr new_value)
      {
            if (new_value) {
                  g_warning("Event: Set content of %s to \"%s\"", node_to_string(node).c_str(), new_value.cString());
            } else {
                  g_warning("Event: Unset content of %s", node_to_string(node).c_str());
            }
      }
};

}

void
sp_repr_debug_print_log(Inkscape::XML::Event const *log) {
      Inkscape::XML::replay_log_to_observer(log, LogPrinter::instance());
}


Generated by  Doxygen 1.6.0   Back to index