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 <KeyboardProcessor> The keyboard processor of which the controller is a client keyboardHook <BlockClosure | nil> 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 <Boolean> Whether input will be accepted accepted <Boolean> Whether input has been accepted and passed to the model autoAccept <Boolean> Whether to force an accept on relinquishing keyboard focus continuousAccept <Boolean> Whether to force an accept on each keystroke tabMeansNextField <Boolean> Whether tab should cause focus shift to the next client of the keyboardProcessor tabRequiresControl <Boolean> Whether tab or control-tab should cause focus shift to the next client of the keyboardProcessor. Only valid if tabMeansNextField is true. dispatcher <UIDispatcher | nil> 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 <Tab> 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 -> self selectionStopIndex with: self beginTypeInIndex). undoStack size > 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> |
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 > > 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 > 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). |
Free forum by Nabble | Edit this page |