Code archives/BlitzPlus Gui/TextArea undo module

This code has been declared by its author to be Public Domain code.

Download source code

TextArea undo module by JoshK2007
Command set for undo/redo with textarea gadgets
'Rem
Module leadwerks.textareaundo

Import BRL.FileSystem
Import BRL.System
Import BRL.Win32MaxGUI
Import BRL.Retro


'EndRem

Private

Type TUndoContext
	Global list:TList=New TList
	
	Field link:TLink
	Field gadget:TGadget
	Field undostates:TList=New TList
	Field current:TUndoState
	Field change
	Field disabled

	Method New()
		link=list.addlast(Self)
	EndMethod
	
	Method CreateUndoState:TUndostate(force=0)
		If current current.clearafter
		undostate:TUndoState=New TUndoState
		undostate.context=Self
		undostate.link=undostates.addlast(undostate)
		undostate.selpos=TextAreaCursor(gadget)
		undostate.sellen=TextAreaSelLen(gadget)
		undostate.update
		undostate.force=force
		current=undostate
		Return undostate
	EndMethod
		
	Method CanUndo:TUndoState(undostate:TUndoState=Null)
		If undostate=Null undostate=current
		If undostate
			If undostate.link.prevlink()
				prevundostate:TUndoState=TUndoState(undostate.link.prevlink().value())
				If prevundostate.force=-1
					Return TUndoState(prevundostate.link.prevlink().value())
				Else
					Return prevundostate
				EndIf
			EndIf
		EndIf
		Return Null
	EndMethod
	
	Method CanRedo:TUndoState(undostate:TUndoState=Null)
		If undostate=Null undostate=current
		If undostate
			If undostate.link.nextlink()
				nextundostate:TUndoState=TUndoState(undostate.link.nextlink().value())
				If nextundostate.force=1
					Return TUndoState(nextundostate.link.nextlink().value())
				Else
					Return nextundostate
				EndIf
			EndIf
		EndIf
		Return Null
	EndMethod

	Method Undo:Int()
		If current
			If canundo()
				current.undo
				current=canundo()
				Return True
			EndIf
		EndIf
		Return False
	EndMethod

	Method Redo:Int()
		undostate:TUndoState=CanRedo()
		If undostate
			undostate.redo
			current=undostate
			Return True
		EndIf
		Return False
	EndMethod

	Method Flush()
		For undostate:TUndoState=EachIn undostates
			undostate.kill
		Next
		undostates.clear
		current=Null
		createundostate
	EndMethod
	
	Method Kill()
		flush
		undostates=Null
		gadget=Null
		RemoveLink link
		link=Null
	EndMethod

	Function Callback:Object(id:Int,data:Object,context:Object)
		Local event:TEvent=TEvent(data)
		Select event.id
			
			Case EVENT_GADGETSELECT
				For undocontext:TUndoContext=EachIn TUndoContext.list
					If event.source=undocontext.gadget
						If undocontext.current
							If Not undocontext.disabled
								If undocontext.current.text=TextAreaText(undocontext.gadget)
									undocontext.current.selpos=TextAreaCursor(undocontext.gadget)
									undocontext.current.sellen=TextAreaSelLen(undocontext.gadget)
									undocontext.current.removetext=TextAreaText(undocontext.gadget,undocontext.current.selpos,undocontext.current.sellen)
								EndIf
							EndIf
						EndIf
						Exit
					EndIf
				Next
			
			Case EVENT_GADGETACTION
				For undocontext:TUndoContext=EachIn TUndoContext.list
					If event.source=undocontext.gadget
						If Not undocontext.disabled
							If undocontext.current
								If undocontext.current.text=TextAreaText(undocontext.gadget) Return Null
							EndIf
							undostate:TUndoState=undocontext.createundostate()
							event.data=undostate.change
						EndIf
						Exit
					EndIf
				Next
				
		EndSelect
		Return data
	EndFunction
	
EndType

Function CreateUndoContext:TUndocontext(gadget:TGadget)
	undocontext:TUndoContext=New TUndoContext
	undocontext.gadget=gadget
	undocontext.createundostate
	Return undocontext
EndFunction

Type TUndostate
	Field link:TLink
	Field context:TUndoContext
	Field text$
	Field undotext$
	Field redotext$
	Field selpos
	Field sellen
	Field undolen
	Field undopos
	Field change
	Field force
	
	Field removetext$
	Field removeposition
	
	Field addtext$
	Field addposition

	Method Update()
		text$=TextAreaText(context.gadget)
		selpos=TextAreaCursor(context.gadget)
		sellen=TextAreaSelLen(context.gadget)
		
		removetext=TextAreaText(context.gadget,selpos,sellen)
		
		lastundostate:TUndoState=context.canundo(Self)
		If lastundostate
			change=text.length-lastundostate.text.length
			change=change+lastundostate.sellen
		Else
			change=text.length
		EndIf
				
		If change<0
			undopos=selpos
			undolen=-change
			If lastundostate
				undotext=Mid(lastundostate.text,undopos+1,-change)
			EndIf
		Else
			undopos=selpos-change
			undolen=change
			If lastundostate
				undotext=Mid(text,undopos+1,change)
			EndIf
		EndIf

	EndMethod

	Method Undo()
		start=TextAreaCursor(context.gadget)
		prevundostate:TUndoState=context.canundo(Self)
		nextundostate:TUndoState=context.canredo(Self)
		LockTextArea context.gadget
		If change>0
			SetTextAreaText context.gadget,"",undopos,undolen
		Else
			SetTextAreaText context.gadget,undotext,undopos,0
		EndIf
		UnlockTextArea context.gadget
		prevundostate:TUndoState=context.canundo(Self)
		If prevundostate		
			SetTextAreaText context.gadget,prevundostate.removetext,prevundostate.selpos,0
			SelectTextAreaText context.gadget,prevundostate.selpos,prevundostate.sellen
		Else
			SelectTextAreaText context.gadget,0,0
		EndIf
		
		stop=TextAreaCursor(context.gadget)
		size=Abs(start-stop)+TextAreaSelLen(context.gadget)
		context.disabled=True
		EmitEvent CreateEvent(EVENT_GADGETACTION,context.gadget,size)
		context.disabled=False
	EndMethod
	
	Method Redo()
		start=TextAreaCursor(context.gadget)
		prevundostate:TUndoState=context.canundo(Self)
		nextundostate:TUndoState=context.canredo(Self)
		LockTextArea context.gadget
		If change<0
			SetTextAreaText context.gadget,"",undopos,undolen
		Else
			If prevundostate l=prevundostate.sellen
			SetTextAreaText context.gadget,undotext,undopos,l
		EndIf
		UnlockTextArea context.gadget
		SelectTextAreaText context.gadget,selpos,sellen
		
		stop=TextAreaCursor(context.gadget)
		size=Abs(start-stop)+TextAreaSelLen(context.gadget)
		context.disabled=True
		EmitEvent CreateEvent(EVENT_GADGETACTION,context.gadget,size)
		context.disabled=False
	EndMethod
	
	Method ClearAfter()
		Local sublink:TLink
		Local nextsublink:TLink
		sublink=link.nextlink()
		While sublink
			nextsublink=sublink.nextlink()
			TUndoState(sublink.value()).kill
			sublink=nextsublink			
		Wend
	EndMethod
	
	Method Kill()
		If link.prevlink()
			current=TUndostate(link.prevlink().value())
		Else
			current=Null
		EndIf
		RemoveLink link
		link=Null
	EndMethod

EndType

AddHook EmitEventHook,TUndoContext.Callback

Function FindUndoContext:TUndoContext(gadget:TGadget)
	For undocontext:TUndoContext=EachIn TUndoContext.list
		If undocontext.gadget=gadget Return undocontext
	Next
	undocontext=CreateUndoContext(gadget)
	Return undocontext
EndFunction

Public

'Helper Functions
Function TextAreaUndo:Int(gadget:TGadget)
	Return FindUndoContext(gadget).undo()
EndFunction

Function TextAreaRedo:Int(gadget:TGadget)
	Return FindUndoContext(gadget).redo()
EndFunction

Function TextAreaCanUndo:Int(gadget:TGadget)
	Return (FindUndoContext(gadget).canundo()<>Null)
EndFunction

Function TextAreaCanRedo:Int(gadget:TGadget)
	Return (FindUndoContext(gadget).canredo()<>Null)
EndFunction

Function TextAreaFlushUndos:Int(gadget:TGadget)
	FindUndoContext(gadget).flush()
EndFunction

Function TextAreaCreateUndoState()
	FindUndoContext(gadget).createundostate
EndFunction

Comments

danielos2008
well, it looks good, but can you do that also for BB+ ?
I'd be great!
Anyway, it is in the wrong Section
"Code archives/BlitzPlus Gui/TextArea undo module"...


Code Archives Forum