GIDForums  

Go Back   GIDForums > Computer Programming Forums > C Programming Language
User Name
Password
Register FAQ Members List Calendar Search Today's Posts Mark Forums Read

 
 
Thread Tools Search this Thread Rate Thread
  #1  
Old 14-Jul-2009, 12:56
Howard_L Howard_L is offline
Regular Member
 
Join Date: Apr 2007
Location: Maryland/PA, USA
Posts: 802
Howard_L is a jewel in the roughHoward_L is a jewel in the roughHoward_L is a jewel in the rough

How to implement an "undo" routine in a program


I have wondered about this and thought I'd ask.
I guess it would depend on what you were doing in the program that you wanted to 'undo'.
A huge example would be say in a graphical window program like Firefox where you can udo all kinds of stuff from the text changes in this box to the url bar.
Would each of them need a separate 'undo' routine?
Is it impoemented through system API?
Other examples would be a Excel or OpenOffice Calc, Paint or Gimp or Notepad or Gedit or even the non graphical vim editor in linux whichhas an undo capability.

Take Vim , how do you think they implement that edit mode > hit 'u' undo?
Do you store a list of changes somehow?
Thanks, Howard
  #2  
Old 14-Jul-2009, 13:11
fakepoo fakepoo is offline
Regular Member
 
Join Date: Oct 2007
Posts: 713
fakepoo is a jewel in the roughfakepoo is a jewel in the roughfakepoo is a jewel in the rough

Re: how to implement an "undo" routine in a program


My guess is that you would gather everything that would constitute the program state (such as text in text boxes, selected indices of list boxes, etc) into a single object. Then you would have a routine that would load one of these objects so that your program would reflect that state. You would have a collection of these state objects and every time you change something, update this object and add it to the collection so that you can retrieve it later.
  #3  
Old 14-Jul-2009, 13:41
davekw7x davekw7x is offline
Outstanding Member
 
Join Date: Feb 2004
Location: Left Coast, USA
Posts: 5,218
davekw7x is a splendid one to beholddavekw7x is a splendid one to beholddavekw7x is a splendid one to beholddavekw7x is a splendid one to beholddavekw7x is a splendid one to beholddavekw7x is a splendid one to beholddavekw7x is a splendid one to behold

Re: how to implement an "undo" routine in a program


Quote:
Originally Posted by Howard_L
I have wondered about this...


In general terms, the action may be equivalent to the following (implementation details may be different, but maybe this is a starting point for a Gedanken experiment at least).

For a given application, every command that is "undoable" has some action that saves the "delta" from the state prior to the command and the state after the command. So, if you delete some text, you save the text in a "delta" object.

Sometimes there are two stacks: an "undo" stack and a "redo" stack. Delta objects of undoable commands are saved on the "undo" stack. That is, if your last command was to delete some text, the text (as well as its position in the file and other characteristics necessary to restore it) is saved on the "undo" stack. Then if you If execute an "undo" command, it restores the text, that stuff is removed from the "undo" stack and stored on the "redo" stack. That way you can "redo" the previous command(s).

With vim, try entering some text. Then "undo" it, so the newly entered text is deleted. Now, if you decide you want it there after all, you don't have to type it in again, just issue a "redo" command.

You see, the delta objects on the stacks have all information needed to get you from there to here: in this case the text itself and, of course the command (whether it was "insert" or "delete" or whatever).

Stuff like that.


Here are a couple of articles that I think are interesting, and maybe there are enough details to experiment with your own application:

http://us.geocities.com/evilsnack/undo.htm

and

http://us.geocities.com/evilsnack/undo2.htm


Finally...

According to vim documentation: http://vimdoc.sourceforge.net/htmldoc/usr_02.html#02.5 and http://vimdoc.sourceforge.net/htmldoc/usr_32.html rather than using an "undo" stack and a "redo" stack, the command history is stored in a tree. You can navigate around the tree in interesting ways. (Ways that I, maybe, don't need very much, but I am continually amazed at the general excellence and flexibility and downright usefulness of vim.) Mind-boggling, right? Or is it just me?

The original vi editor only had one level of undo. If you did an "undo" and then another "undo", the second "undo" undid the first "undo," so the original was restored. If you did a couple of "bad" things, you couldn't go back more than one command. Bummer. If you decide to try some undo stuff with your own program, maybe start with one level of undo. Then decide whether you want a stack or tree (or whatever) to hold the command history. Sounds like fun!

The multi-level undo of vim encourages us to experiment with all kinds of good stuff in trying to groom the contents of text files, since we can always execute repetitive "undo" steps to keep from destroying file contents and maybe preserve that little wisp of sanity that (I like to think) remains in my tired old head.


Quote:
Originally Posted by Howard_L
...I guess it would depend...

Actually, according to my muse (She Whose Name Must Not Be Spoken), the answer to all questions is: "It depends." She's funny that way.

Regards,

Dave
  #4  
Old 14-Jul-2009, 18:14
L7Sqr L7Sqr is offline
Member
 
Join Date: Jul 2005
Location: constant limbo
Posts: 234
L7Sqr is a jewel in the roughL7Sqr is a jewel in the rough

Re: how to implement an "undo" routine in a program


According to the '4 Horsemen' (Gamma, Helm, Johnson, Vlissides), the pattern for this is called the Command pattern AKA Action or Transaction.
There is a complete example of this in their book (Design Patterns) but the general idea is this: coupled with the support of another pattern, Memento, you save state and provide an execute/unexecute interface. The example in the book is as follows (with some modifications to expose the UnExecute and Memento)
CPP / C++ / C Code:
class Command {
public:
   virtual ~Command ();
   virtual void Execute () = 0;
   virtual void UnExecute () = 0;
protected:
   Command ();
};

class PasteCommand : public Command {
public:
   PasteCommand(Document*);
   virtual void Execute ();
   virtual void UnExecute ();
private:
   Document * _document;
   State * _state;
};

PasteCommand::PasteCommand(Document *d) : _document(d) {}

void PasteCommand::Execute () {
   _state = _document->state ();
   _document->Paste ();
}

void PasteCommand::UnExecute () {
   _document->reset(_state);
}
The example goes on to explain templating to support generic commands and other interesting ideas.
__________________
My personal site: Utilities for text processing, debugging, testing and plotting
  #5  
Old 14-Jul-2009, 23:08
Howard_L Howard_L is offline
Regular Member
 
Join Date: Apr 2007
Location: Maryland/PA, USA
Posts: 802
Howard_L is a jewel in the roughHoward_L is a jewel in the roughHoward_L is a jewel in the rough

Re: how to implement an "undo" routine in a program


well, I stepped in it again didn't I....
Oh well , no pain - no gain and all that , I will have to do some reading and thinking for shure.
Seems like a good place to start would be editing a string or paragraph.
Thanks for the ideas you guys . I'll be back after I get something going... hopefully
  #6  
Old 21-Jul-2009, 03:31
Mexican Bob's Avatar
Mexican Bob Mexican Bob is offline
Member
 
Join Date: Mar 2008
Location: Chicxulub, Yucatán
Posts: 226
Mexican Bob is a jewel in the roughMexican Bob is a jewel in the roughMexican Bob is a jewel in the rough

Re: how to implement an "undo" routine in a program


Quote:
Originally Posted by L7Sqr
According to the '4 Horsemen' (Gamma, Helm, Johnson, Vlissides)

...never heard GoF referred to has 4 Horsemen before, but I'm thinking that, while interesting to the problem domain, references to DPs in the context of a C (as opposed to C++) programming forum section would be less desirable than a C-centric undo/redo example.

However, you *can* implement it in C using opaque pointers without the data protection benefit. It seems that that would have been more interesting in the context of this forum.

Perhaps better than a code fragment would be a link?

While certainly dated, here is an example in code that (with minor modifications) illustrates the point:

http://www.vincehuston.org/dp/MementoDemosCpp
...this site is a rather useful DP site.

Here is the code "cleaned up" so that it compiles (some formatting may be lost):

CPP / C++ / C Code:
#include <iostream>

using namespace std;

// Purpose.  Memento design pattern
// 
// Discussion.  A memento is an object that stores a snapshot of the
// internal state of another object.  It can be leveraged to support
// multi-level undo of the Command pattern.  In this example, before a
// command is run against the Number object, Number's current state is
// saved in Command's static memento history list, and the command itself
// is saved in the static command history list.  Undo() simply "pops" the
// memento history list and reinstates Number's state from the memento.
// Redo() "pops" the command history list.  Note that Number's encapsula-
// tion is preserved, and Memento is wide open to Number.

class Number;

class Memento {
public:
    Memento( int val ) { _state = val;}
private:
    friend class Number;  // not essential, but p287 suggests this
    int  _state;
};

class Number {
public:
    Number( int value )                    { _value = value;}
    void dubble()                          { _value = 2 * _value;}
    void half()                            { _value = _value / 2;}
    int getValue()                         { return _value;}
    Memento* createMemento()               { return new Memento( _value );}
    void  reinstateMemento( Memento* mem ) { _value = mem->_state ;}
private:
    int _value;
};

class Command {
public:
    typedef void (Number::* Action)();
    Command( Number* receiver, Action action ) {
        _receiver = receiver;
        _action = action;}
    virtual void execute() {
        _mementoList[_numCommands] = _receiver->createMemento();
        _commandList[_numCommands] = this;
        if (_numCommands > _highWater) _highWater = _numCommands;
        _numCommands++;
        (_receiver->*_action)();
    }
    static void undo() {
        if (_numCommands == 0) {
            cout << "*** Attempt to run off the end!! ***" << endl;
            return;
        }
        _commandList[_numCommands-1]->_receiver->
        reinstateMemento( _mementoList[_numCommands-1] );
        _numCommands--;
    }
    void static redo() {
        if (_numCommands > _highWater) {
            cout << "*** Attempt to run off the end!! ***" << endl;
            return;
        }
        (_commandList[_numCommands]->_receiver->*(_commandList[_numCommands]->_action))();
        _numCommands++;
    }
    virtual ~Command() {
    }
protected:
    Number*         _receiver;
    Action          _action;
    static Command* _commandList[20];
    static Memento* _mementoList[20];
    static int      _numCommands;
    static int      _highWater;
};

Command* Command::_commandList[];
Memento* Command::_mementoList[];
int      Command::_numCommands = 0;
int      Command::_highWater   = 0;

int main() {
    int i;
    cout << "Integer: ";
    cin >> i;
    Number*   object = new Number(i);
    Command*  commands[3];
    commands[1] = new Command( object, &Number::dubble );
    commands[2] = new Command( object, &Number::half );

    cout << "Exit[0], Double[1], Half[2], Undo[3], Redo[4]: ";
    cin >> i;
    while (i) {
        if (i == 3)
            Command::undo();
        else if (i == 4)
            Command::redo();
        else
            commands[i]->execute();
        cout << "   " << object->getValue() << endl;
        cout << "Exit[0], Double[1], Half[2], Undo[3], Redo[4]: ";
        cin >> i;
    }
    return 0;
}

// Integer: 11
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 2
//    5
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 1
//    10
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 2
//    5
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
//    10
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
//    5
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
//    11
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 3
// *** Attempt to run off the end!! ***
//    11
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
//    5
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
//    10
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
//    5
// Exit[0], Double[1], Half[2], Undo[3], Redo[4]: 4
// *** Attempt to run off the end!! ***
//    5



...still of questionable use in a C-context.


MxB
  #7  
Old 21-Jul-2009, 12:59
L7Sqr L7Sqr is offline
Member
 
Join Date: Jul 2005
Location: constant limbo
Posts: 234
L7Sqr is a jewel in the roughL7Sqr is a jewel in the rough

Re: How to implement an "undo" routine in a program


Quote:
Originally Posted by Mexican Bob
references to DPs in the context of a C (as opposed to C++) programming forum section would be less desirable than a C-centric undo/redo example
I disagree completely. Design Patterns are a key element in understanding how to solve well-known problems. They are language agnostic and provide a common way to describe these very situations.
__________________
My personal site: Utilities for text processing, debugging, testing and plotting
  #8  
Old 23-Jul-2009, 07:12
Mexican Bob's Avatar
Mexican Bob Mexican Bob is offline
Member
 
Join Date: Mar 2008
Location: Chicxulub, Yucatán
Posts: 226
Mexican Bob is a jewel in the roughMexican Bob is a jewel in the roughMexican Bob is a jewel in the rough

Re: How to implement an "undo" routine in a program


Quote:
Originally Posted by L7Sqr
They are language agnostic and provide a common way to describe these very situations.

Okay, so how do you implement Observer in FORTRAN?

Have you tried applying DPs to assembly language? In a way, DPs are completely agnostic (unknown) in some languages.

My point, which probably wasn't very well taken was that a C++ code fragment as a solution in the context of a C programming forum isn't as helpful as a C-centric solution.

This is not to suggest that DPs are not useful for non-C++ programmers to learn, but in the context of Undo/Redo in C, where is your expose on using opaque pointers to implement "C-with classes" so that these patterns that you profess may be realized?

Just because C and C++ are similar syntactically, doesn't mean that they are the same language or that a C programmer will understand any of the advanced capabilities provided by C++. For someone with no exposure to C++, you may as well be speaking Russian. Some of the characters look familiar, but what do they all mean when put together?

In fact, an important point in my reply was that a link to design pattern content was probably more useful than a C++ code fragment that *partially* realizes the solution while does very little to actually disclose the pattern except through a bit of the internals. Why send the OP on a scavenger hunt to make heads or tails of the post? ...particularly if the OP is not a C++ programmer, which, in the context of a C forum, one would expect that C code would be preferred.

There is nothing "wrong" with discussing DPs to solve the OP's problem. However, there was substantially no discussion of the patterns or even any links to any relevant sites that do discuss applicable patterns. Of course, you named a few, so the eager reader could obviously google into it, but that's not my point. A C++ "implementation" is not a discussion of any DP in the context of a C forum.

My point was "references to Design Patterns" in that by invoking the ethos of the ethereal existence of some magical solution to common programming challenges and supporting such conjuring with a fragment of C++ code doesn't really qualify as "help" in the context of a C forum and is therefore "less desirable than a C-centric undo/redo example."

Of course, that's just my opinion. While your reply provided some gems in the rough for further research, it didn't well address the problem at hand. It didn't even go so far as to suggest that even though the C++ fragment was not C, but that DP-based implementations for C "could be found" somewhere.

And the "complete example" is given incompletely and is C++ code and not C code. We don't even have a one sentence defintion of what DPs are. Again, nothing that an eager reader couldn't google, rather, not exactly embracing the problem in a C-centric manner.

While all of this must undoubtedly sound extremely critical, particularly as "labored" as it must appear considering the wordcount, that is not my objective. No, my effort here is to challenge you to come up with more useful, more helpful replies, if you're so inclined. However, I'd also ask you to please consider not power selling me on the merits of a so-so post.

Just because DPs are useful and tend to be applicable across a varieity of languages, doesn't mean that your mentioning of them and a code fragment is all that useful. If you do want your posts to be more useful, and I believe that you do, then they should stand on their own. That is not to say that we all don't sometimes post something that "doesn't quite work," but when we do, let's not upsell it.

I guess that this means no more positive "reputation" for me from you, huh?


MxB
 
 

Recent GIDBlogProgramming ebook direct download available by crystalattice

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Equation solver RazoR C Programming Language 3 18-May-2008 10:24
Write a C++ program to implement a cash register program Computeretard66 C++ Forum 2 29-Feb-2008 09:05
Two-Tier data dissemination code installation problem nidhibansal1984 Computer Software Forum - Linux 6 16-Sep-2007 11:13
Text-Based Roulette Game mfm1983 C++ Forum 5 29-Nov-2006 13:20

Network Sites: GIDNetwork · GIDWebHosts · GIDSearch · Learning Journal by J de Silva, The

All times are GMT -6. The time now is 13:10.


vBulletin, Copyright © 2000 - 2009, Jelsoft Enterprises Ltd.