UnderstandingUndo

Webs: Faemalia -:- Greatprawn -:- Playground -:- Technical -:- Tweak
Technical Web Sections: Register -:- Users -:- Changes -:- Index -:- Search -:- Statistics

Here are some notes on the inner workings of Undo in Dia. Nothing groundbreaking here, but it's good to keep track of it.

I'll use the current undo thing I'm adding as an example: The autoroute setting for orthconn. For this setting, I need to remember whether the undo is for turning autoroute on or off, and when turning autoroute off, I need to restore the original layout.

Undo is based on a stack of changes. The objects placed on this stack are Change instances (app/change.h). Each Change has three function pointers to it: Apply, Revert and Free. Apply changes what needs to be changed when this action happens (when it's first done, and at redo), whereas Revert changes what needs to be done to undo the change. Free is for cleanup, when the change is no longer remembered.

Most actual changes are associated with an object, and so take the form of an ObjectChange? instance. ObjectChange? has the above three functions, plus it remembers the object itself.

Let's start looking at the autoroute undo. First, let's make a structure that contains information for autoroute changes. Since this is the change that turns autorouting on, we'll need to remember what the layout was without autorouting to be able to revert (undo).

struct AutorouteChange {
   ObjectChange change;
  Point *points;
}

Notice that it has ObjectChange? as it's "parent". This structure is filled out by autoroute_create_change():

autoroute_create_change(OrthConn *orth, gboolean on)
{
      struct AutorouteChange *change;
      int i;
              
      change = g_new(struct AutorouteChange, 1);
               
      change->obj_change.apply = (ObjectChangeApplyFunc) autoroute_change_apply;
      change->obj_change.revert = (ObjectChangeRevertFunc) autoroute_change_revert;
      change->obj_change.free = (ObjectChangeFreeFunc) autoroute_change_free;
              
      change->points = g_new(Point, orth->numpoints);
      for (i = 0; i < orth->numpoints; i++)
          change->points[i] = orth->points[i];

      return (ObjectChange *)change;
}

Now, let's make the Apply and Revert functions for turning autorouting on.

static void
autoroute_change_apply(struct AutorouteChange *change, Object *obj)
{
     OrthConn *orth = (OrthConn*)obj;
          
     orth->autorouting = TRUE;
     autoroute_layout_orthconn(orth, obj->handles[0]->connected_to,
                                               obj->handles[1]->connected_to);
}

We call autoroute_layout_orthconn() to be sure that the data in the orthconn gets changed.

static void
autoroute_change_revert(struct AutorouteChange *change, Object *obj)
{
     OrthConn *orth = (OrthConn*)obj;
          
     orth->autorouting = TRUE;
     autoroute_layout_orthconn(orth, obj->handles[0]->connected_to,
                                               obj->handles[1]->connected_to);
}

Note to self: Why doesn't the stack contain ObjectChange? instances in a GList? Change only has prev and next links different from ObjectChange?, and this may simplify things a lot.

More coming.

-- LarsClausen - 31 Jul 2003


Edit -:- Attach -:- Ref-By -:- Printable -:- More