Undo Implementation for 7.4

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Undo Implementation for 7.4

Andre Schnoor
As promised last week, here's a full undo implementation for 7.4. It's
simple, really small and works fine. I haven't tested it with 7.4.1,
though. I don't have write access to the public repository (and don't
want it, because it might happen I accidentally publish our products
;-). Hence, if anybody is willing to adopt this child - go ahead. It's
in the public domain.

It takes a little time to get used to actually making use of "undo" in
VW - it has never been there. Anyway, it already saved me more than once.

Hopefully this will make a lot of people happy.

Regards,
Andre

BTW: The package name is "TextEditorExtensions" because I meant to add
more enhancements in the future. Unfortunately, I don't have the time,
so the package should perhaps be renamed.
--

Here's the package comment:

This package adds multiple undo levels to TextEditorController (Redo,
however, is not yet implemented)

Tested with VW 7.4.

The implementation follows a simple, yet effective approach. There is no
sophisticated notion of different edit actions or the like. Before a
change is applied to the text, a copy of the current text is saved to an
undo stack, along with the current selection indexes. These are simply
restored upon "undo". Even larger texts should not impose problems,
given todays average memory and CPU resources. Series of continous
character typing are treated as a single block of action, i.e. "undo"
will restore the state prior to when typing started. This saves room on
the stack (and memory footprint) and pretty well suits practical needs.
The default number of undo levels is 32 (may be easily changed).

<?xml version="1.0"?>

<st-source>
<time-stamp>From VisualWorks®, 7.4 of 5. Dezember 2005 on 10. Juli 2006 at 8:19:29</time-stamp>
<!-- Package TextEditorExtensions(1.1,andre)= -->


<component-property>
<name>TextEditorExtensions</name> <type>package</type>
<property>comment</property> <value>'This package adds multiple undo levels to TextEditorController (Redo, however, is not yet implemented)

Tested with VW 7.4.

The implementation follows a simple, yet effective approach. There is no sophisticated notion of different edit actions or the like. Before a change is applied to the text, a copy of the current text is saved to an undo stack, along with the current selection indexes. These are simply restored upon "undo". Even larger texts should not impose problems, given todays average memory and CPU resources. Series of continous character typing are treated as a single block of action, i.e. "undo" will restore the state prior to when typing started. This saves room on the stack (and memory footprint) and pretty well suits practical needs. The default number of undo levels is 32 (may be easily changed).

(c) 2006 Andre Schnoor, free for any kind of use.'</value>
</component-property>

<class>
<name>TextEditorController</name>
<environment>UI</environment>
<super>UI.ParagraphEditor</super>
<private>false</private>
<indexed-type>none</indexed-type>
<inst-vars>keyboardProcessor keyboardHook readOnly accepted autoAccept continuousAccept tabMeansNextField tabRequiresControl dispatcher undoStack typing </inst-vars>
<class-inst-vars></class-inst-vars>
<imports>
                        private Graphics.TextConstants.*
                        </imports>
<category>UIBasics-Controllers</category>
<attributes>
<package>TextEditorExtensions</package>
</attributes>
</class>

<comment>
<class-id>UI.TextEditorController</class-id>
<body>An enhancement of ParagraphEditor that lets the focus shifting information come from the KeyboardProcessor, plus other bells and whistles.

Instance Variables:
        keyboardProcessor &lt;KeyboardProcessor&gt;  The keyboard processor of which the controller is a client
        keyboardHook &lt;BlockClosure | nil&gt;  A block with two arguments: the event and the controller, used for intercepting characters; if the event is handled, nil should be returned, otherwise the event.
        readOnly &lt;Boolean&gt;  Whether input will be accepted
        accepted &lt;Boolean&gt;  Whether input has been accepted and passed to the model
        autoAccept &lt;Boolean&gt; Whether to force an accept on relinquishing keyboard focus
        continuousAccept &lt;Boolean&gt; Whether to force an accept on each keystroke
        tabMeansNextField &lt;Boolean&gt;  Whether tab should cause focus shift to the next client of the keyboardProcessor
        tabRequiresControl &lt;Boolean&gt; Whether tab or control-tab should cause focus shift to the next client of the keyboardProcessor.  Only valid if tabMeansNextField is true.
        dispatcher &lt;UIDispatcher | nil&gt; translates user interactions to messages


Object Reference:
A TextEditorController is used by a TextEditorView, the text widget that is provided by the Palette. Its subclass, InputBoxController, is used for single-line text views (input fields and combo boxes).
A TextEditorController is equipped to take keyboard focus from its window's KeyboardProcessor. In addition to the many abilities it inherits, especially from ParagraphEditor, a TextEditorController knows how to:
        -Enable the application to intervene in the normal processing of keyboard input, via its keyboardHook (initialize-release protocol).
        -Treat a &lt;Tab&gt; character as either a regular tab character or as a command for shifting keyboard focus to the next widget in the tab sequence (initialize-release)
        -Ignore keyboard input, for a read-only view (accessing)
        -Keep track of whether changes in the text have been accepted by the information model (accessing)
        -Accept changes whenever keyboard focus shifts away (accessing)
        -Accept changes each time a character is entered, for keystroke-by-keystroke validation (accessing)
</body>
</comment>




<methods>
<class-id>UI.TextEditorController</class-id> <category>menu messages</category>

<body package="TextEditorExtensions" selector="accept">accept
        "Save the current text of the text being edited as the current acceptable
        version for purposes of canceling."

        self requestValueChange ifFalse: [^false].
        self privateAccept.
        self undoClear.</body>

<body package="TextEditorExtensions" selector="cancel">cancel

        super cancel.
        self undoClear.</body>

<body package="TextEditorExtensions" selector="undo">undo
        "Reset the state of the paragraph prior to the previous cut or paste edit."

        self undoRestore.
</body>
</methods>

<methods>
<class-id>UI.TextEditorController</class-id> <category>private-undo</category>

<body package="TextEditorExtensions" selector="undoLevels">undoLevels
        "Increase at will"
        ^32</body>

<body package="TextEditorExtensions" selector="undoStack">undoStack
        "Answer the undo stack"

        ^undoStack ifNil:[
                undoStack := OrderedCollection new: self undoLevels ].</body>

<body package="TextEditorExtensions" selector="undoSave">undoSave
        "Save current text and selection"

        typing := false. "break a sequence of continous typing"

        self undoStack addLast: (Array
                with: self text copy
                with: self selectionStartIndex -&gt; self selectionStopIndex
                with: self beginTypeInIndex).

        undoStack size &gt; self undoLevels
                ifTrue:[ undoStack removeFirst ].</body>

<body package="TextEditorExtensions" selector="undoClear">undoClear
        "Reset the undo stack"

        undoStack := nil.</body>

<body package="TextEditorExtensions" selector="undoRestore">undoRestore
        "Resore previously saved text and selection"

        | previous |
        self undoStack isEmpty ifTrue:[ ^self ].
        previous := undoStack removeLast.
        self deselect.
        view editText: (previous at: 1).
        self
                selectionStartIndex: (previous at: 2) key    
                stopIndex: (previous at: 2) value.

        self resetTypein; beginTypeInIndex: (previous at: 3).
        self selectAndScroll.
        textHasChanged := true.
        typing := false. "break a sequence of continous typing"</body>
</methods>

<methods>
<class-id>UI.TextEditorController</class-id> <category>private</category>

<body package="TextEditorExtensions" selector="appendToSelection:">appendToSelection: aString
        "Assuming this is only sent while typing, it is a good hook to save the current
         text to the undo stack (only if we are not already typing in a row)."

        typing == true ifFalse:[
                self undoSave.
                typing := true ].
        ^super appendToSelection: aString</body>

<body package="TextEditorExtensions" selector="replaceSelectionWith:">replaceSelectionWith: aText
        "Assumed this is only called for user editing (not transcript logging),
         this is a good place to hook in to save the current text and selection
         to the undo stack."

        self undoSave.
        ^super replaceSelectionWith: aText</body>
</methods>


<methods>
<class-id>UI.TextEditorView</class-id> <category>updating</category>

<body package="TextEditorExtensions" selector="updateDisplayContents">updateDisplayContents
        "Make the text that is displayed be the contents of the receiver's model. We
        have to update the cached text even if we are not open right now, to follow
        any changes in the model"

        | contents |
        controller notNil "###### Added this"
                ifTrue:[ controller undoClear ].

        contents := self getContents asText.
        ( displayContents text equalStringAndEmphasis: contents ) not
                ifTrue:
                        [self editText: contents.
                        self isOpen ifTrue: [self simpleRedisplay]]
                ifFalse: [self isOpen ifTrue: [self resetController]]</body>
</methods>


<methods>
<class-id>UI.Controller</class-id> <category>private-undo</category>

<body package="TextEditorExtensions" selector="undoClear">undoClear
        "Downwards compatibility with TextEditorController:
         Sent by Subclasses of  TextEditorView when updating
         their contents from the model."</body>
</methods>



</st-source>
Reply | Threaded
Open this post in threaded view
|

RE: Undo Implementation for 7.4

Steven Kelly
Thanks Andre!!

I've published this to the public repository as TextEditorUndo. Here are
my initial findings (in 7.4.1):

- Seems like the simplest thing that could possibly work - and it does!
It even seems to remember text emphasis.

- basically doesn't work at all if continuousAccept is on (solution:
#accept should only call #undoClear if continuousAccept is off)

- if you type in one place then moving to another place with mouse or
cursor keys and type there, only one undo is saved (solution: make
changing the cursor position save an undo?)

- as you said, no redo. That's easy enough to do, either with an
undoIndex into the undoStack (anything after that is redo), or with an
undoStack and redoStack.

I'll poke around a bit more and try to correct the above problems. Even
as it is, though, it's better than VW's own undo.

Thanks again!
Steve

> -----Original Message-----
> From: [hidden email] [mailto:[hidden email]]
> Sent: 10 July 2006 09:40
> To: vwnc-list
> Subject: Undo Implementation for 7.4
>
> As promised last week, here's a full undo implementation for 7.4. It's
> simple, really small and works fine. I haven't tested it with 7.4.1,
> though. I don't have write access to the public repository (and don't
> want it, because it might happen I accidentally publish our products
> ;-). Hence, if anybody is willing to adopt this child - go ahead. It's
> in the public domain.
>
> It takes a little time to get used to actually making use of "undo" in
> VW - it has never been there. Anyway, it already saved me more than
once.

>
> Hopefully this will make a lot of people happy.
>
> Regards,
> Andre
>
> BTW: The package name is "TextEditorExtensions" because I meant to add
> more enhancements in the future. Unfortunately, I don't have the time,
> so the package should perhaps be renamed.
> --
>
> Here's the package comment:
>
> This package adds multiple undo levels to TextEditorController (Redo,
> however, is not yet implemented)
>
> Tested with VW 7.4.
>
> The implementation follows a simple, yet effective approach. There is
no
> sophisticated notion of different edit actions or the like. Before a
> change is applied to the text, a copy of the current text is saved to
an
> undo stack, along with the current selection indexes. These are simply
> restored upon "undo". Even larger texts should not impose problems,
> given todays average memory and CPU resources. Series of continous
> character typing are treated as a single block of action, i.e. "undo"
> will restore the state prior to when typing started. This saves room
on
> the stack (and memory footprint) and pretty well suits practical
needs.
> The default number of undo levels is 32 (may be easily changed).