Anyone have a simple IDE template

BlitzMax Forums/BlitzMax Programming/Anyone have a simple IDE template

Before I re-program what has probably been programmed a dozen times, does anyone have some source code for a simple text editor?

Well, I came up with this. The undo/redo stuff was pretty tricky, but I think it is working correctly now:
Framework BRL.Win32MaxGUI
Import brl.retro

Extern "Win32"
	Function OpenClipboard%(hwnd%)
	Function CloseClipboard%()
	Function EmptyClipboard%()
	Function IsClipboardFormatAvailable%(format%)
	Function GetClipboardData:Byte Ptr(Format:Int)
	Function SetClipboardData(format%, hMem:Byte Ptr)
	Function GlobalAlloc(Flags:Int, Bytes:Int)
	Function GlobalFree(Mem:Int)
	Function GlobalLock:Byte Ptr(Mem:Int)
	Function GlobalUnlock(Mem:Int)
End Extern 

Const CF_TEXT%=$1
Const CF_BITMAP%=2
Const GMEM_MOVEABLE%=$2
Const GMEM_DDESHARE%=$2000

AppTitle="Script Editor"
AppVersion$="1.00"

Global REQUESTFILEPATH$

Global MAINWINDOW:TGadget=CreateWindow("Script Editor",0,0,800,600,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_MENU|WINDOW_STATUS|WINDOW_HIDDEN|WINDOW_CENTER)

Global MAINPANEL:TGadget=CreatePanel(0,0,MAINWINDOW.clientwidth(),MAINWINDOW.clientheight(),MAINWINDOW)
MAINPANEL.setlayout 1,1,1,1

Global TEXTEDITOR:TGadget=CreateTextArea(0,0,MAINPANEL.clientwidth(),MAINPANEL.clientheight(),MAINPANEL)
TEXTEDITOR.setlayout 1,1,1,1
TEXTEDITOR.setcolor 240,240,240

font:TGUIFont=LoadGuiFont("Courier New",10)
If font SetGadgetFont TEXTEDITOR,font

'=================================================================
'File
'=================================================================

Type TSourcefile
	Global list:TList=New TList
	Global current:TSourcefile
	
	Field link:TLink
	Field path$
	Field changed=False
	Field gadget:TGadget
	
	Method New()
		link=list.addlast(Self)
		If Not current current=Self
	EndMethod

	Method kill()
		RemoveLink link
		link=Null
		path=""
		gadget=Null
		If current=Self current=Null
	EndMethod

EndType

sourcefile:TSourcefile=New TSourcefile
sourcefile.gadget=TEXTEDITOR

'=================================================================
'
'=================================================================

'=================================================================
'Undo
'=================================================================
Type TUndoContext
	Field gadget:TGadget
	Field undostates:TList=New TList
	Field current:TUndoState
	
	Method kill()
		gadget=Null
		undostates.clear()
		undostates=Null
		flush
	EndMethod
		
	Method Undo()
		If current
			If current.link.prevlink()
				undostate:TUndostate=TUndostate(current.link.prevlink().value())
				If undostate undostate.restore
			EndIf
		EndIf
	EndMethod 

	Method Redo()
		If current
			If current.link.nextlink()
				undostate:TUndostate=TUndostate(current.link.nextlink().value())
				If undostate undostate.restore
			EndIf
		EndIf
	EndMethod

	Method Flush()
		For undostate:TUndostate=EachIn undostates
			undostate.kill()
		Next
	EndMethod
	
	Method CreateUndoState()
		undostate:TUndostate=New TUndostate
		If current
			undostate.link=undostates.insertafterlink(undostate,current.link)
		Else
			undostate.link=undostates.addlast(undostate)
		EndIf
		current=undostate
		undostate.clearafter
		If undostates.count()>200 RemoveLink undostates.firstlink()
		undostate.context=Self
		undostate.updatetext
		undostate.updateselection
	EndMethod

	Method UndoAvailable:Int()
		If undostates.count()>1 Return True
	EndMethod
	
	Method RedoAvailable:Int()
		If current
			If current.link.nextlink() Return True
		EndIf
	EndMethod

EndType

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

Type TUndostate
	Field link:TLink
	Field context:TUndoContext
	Field text$
	Field selectionposition:Int
	Field selectionlength:Int

	Method UpdateText()
		text=TextAreaText(context.gadget)
	EndMethod
	
	Method UpdateSelection()
		selectionposition=TextAreaCursor(context.gadget)
		selectionlength=TextAreaSelLen(context.gadget)
	EndMethod
	
	Method Restore()
		LockTextArea context.gadget
		context.gadget.settext text
		UnlockTextArea context.gadget
		SelectTextAreaText context.gadget,selectionposition,selectionlength
		context.current=Self
	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()
			context.current=TUndostate(link.prevlink().value())
		Else
			context.current=Null
		EndIf
		context=Null
		RemoveLink link
		link=Null
	EndMethod
	
EndType
'=================================================================
'
'=================================================================


'=================================================================
'Menu
'=================================================================
root:TGadget=WindowMenu(MAINWINDOW)

Const MENU_OPEN=1
Const MENU_EXIT=2
Const MENU_NEW=3
Const MENU_SAVE=4
Const MENU_SAVEAS=5
Const MENU_UNDO=6
Const MENU_REDO=7
Const MENU_CUT=8
Const MENU_COPY=9
Const MENU_PASTE=10
Const MENU_DELETE=11
Const MENU_SELECTALL=12
Const MENU_FIND=13
Const MENU_FINDNEXT=14
Const MENU_REPLACE=15
Const MENU_GOTO=16
Const MENU_RUN=17
Const MENU_DEBUG=18
Const MENU_BUILDEXE=19
Const MENU_HELP=20
Const MENU_ABOUT=21
Const MENU_CLOSE=22

menu:TGadget=CreateMenu("File",0,root)
CreateMenu("New",MENU_NEW,menu)
CreateMenu("",0,menu)
CreateMenu("Open...",MENU_OPEN,menu,KEY_O,MODIFIER_COMMAND)
CreateMenu("Save",MENU_SAVE,menu,KEY_S,MODIFIER_COMMAND)
CreateMenu("Save As...",MENU_SAVEAS,menu)
CreateMenu("",0,menu)
CreateMenu("",0,menu)
CreateMenu("Exit",MENU_EXIT,menu)

menu:TGadget=CreateMenu("Edit",0,root)
Global GADGET_MENU_UNDO:TGadget=CreateMenu("Undo",MENU_UNDO,menu,KEY_Z,MODIFIER_COMMAND)
Global GADGET_MENU_REDO:TGadget=CreateMenu("Redo",MENU_REDO,menu,KEY_Z,MODIFIER_COMMAND|MODIFIER_OPTION)
DisableGadget GADGET_MENU_UNDO
DisableGadget GADGET_MENU_REDO
CreateMenu("",0,menu)
CreateMenu("Cut",MENU_CUT,menu,KEY_X,MODIFIER_COMMAND)
CreateMenu("Copy",MENU_COPY,menu,KEY_C,MODIFIER_COMMAND)
CreateMenu("Paste",MENU_PASTE,menu,KEY_V,MODIFIER_COMMAND)
CreateMenu("Delete",MENU_DELETE,menu,KEY_DELETE)
CreateMenu("",0,menu)
CreateMenu("Select All",MENU_SELECTALL,menu)
CreateMenu("",0,menu)
CreateMenu("Find...",MENU_FIND,menu)
CreateMenu("Find Next",MENU_FINDNEXT,menu)
CreateMenu("Replace...",MENU_REPLACE,menu)
CreateMenu("Go To...",MENU_GOTO,menu)

menu:TGadget=CreateMenu("Compile",0,root)
CreateMenu("Run...",MENU_RUN,menu,KEY_F5)
CreateMenu("Debug...",MENU_DEBUG,menu,KEY_F6)
CreateMenu("Build EXE...",MENU_BUILDEXE,menu,KEY_F7)

menu:TGadget=CreateMenu("Help",0,root,KEY_F1)
CreateMenu("User Guide...",MENU_HELP,menu)
CreateMenu("About",MENU_ABOUT,menu)

MAINWINDOW.updatemenu()
'=================================================================
'
'=================================================================

Global undocontext:TUndocontext=CreateUndoContext(TEXTEDITOR)
undocontext.createundostate

'=================================================================
'About window
'=================================================================
w=284
h=192
Global ABOUTWINDOW:Tgadget=CreateWindow("About",0,0,w,h,group,WINDOW_TITLEBAR|WINDOW_HIDDEN|WINDOW_DIALOG|WINDOW_CENTER)
x=8
y=4
panel:TGadget=CreatePanel(x,y,ABOUTWINDOW.clientwidth()-2*x,ABOUTWINDOW.clientheight()-y-8-23-8,ABOUTWINDOW,PANEL_GROUP)
CreateLabel(AppTitle+" "+AppVersion+Chr(13)+Chr(10)+"Leadwerks Corporation 2007"+Chr(13)+Chr(10)+"All Rights Reserved.",0,panel.clientheight()/2-24,panel.clientwidth(),panel.clientheight(),panel,LABEL_CENTER)
w=80
h=23
CreateButton("OK",(ABOUTWINDOW.clientwidth()-w)/2,ABOUTWINDOW.clientheight()-h-8,w,h,ABOUTWINDOW)
'=================================================================
'
'=================================================================

UpdateStatusbar()
ShowGadget MAINWINDOW
ActivateGadget TEXTEDITOR

'=================================================================
'Main Loop
'=================================================================
Repeat
	WaitEvent()
	EvaluateEvent(EventID(),EventSource(),EventData())
Forever
'=================================================================
'
'=================================================================


'=================================================================
'Event Handling
'=================================================================
Function EvaluateEvent:Int(id,source:Object=Null,data:Int=0)
	Select id
	
		Case EVENT_WINDOWCLOSE
			End
		
		Case EVENT_GADGETSELECT
			Select source
			
				Case TEXTEDITOR
					If undocontext.current
						If TextAreaText(undocontext.gadget)=undocontext.current.text
							undocontext.current.updateselection
						EndIf
					EndIf
					UpdateStatusbar()

			EndSelect
		
		Case EVENT_GADGETACTION
			Select source
			
				Case TEXTEDITOR
					undocontext.createundostate
					EnableGadget GADGET_MENU_UNDO
					MAINWINDOW.updatemenu
					TSourcefile.current.changed=True

			EndSelect
			
		Case EVENT_MENUACTION
			Select data

				Case MENU_SELECTALL
					SelectTextAreaText TEXTEDITOR

				Case MENU_CUT
					If TextAreaSelLen(TEXTEDITOR)
						If Not SetClipboardText(TextAreaSelection(TEXTEDITOR))
							Notify "Failed to write text to clipboard."
						EndIf
						RemoveTextAreaSelection(TEXTEDITOR)
						undocontext.createundostate
						EnableGadget GADGET_MENU_UNDO
						MAINWINDOW.updatemenu
					EndIf
					
				Case MENU_COPY
					If TextAreaSelLen(TEXTEDITOR)
						SetClipboardText TextAreaSelection(TEXTEDITOR)
					EndIf
					
				Case MENU_PASTE
					text$=ClipboardText()
					If text<>""
						RemoveTextAreaSelection TEXTEDITOR
						InsertTextAreaText TEXTEDITOR,text
						undocontext.createundostate
						EnableGadget GADGET_MENU_UNDO
						MAINWINDOW.updatemenu
					EndIf

				Case MENU_DELETE
					If TextAreaSelLen(TEXTEDITOR)
						RemoveTextAreaSelection(TEXTEDITOR)
						undocontext.createundostate
						EnableGadget GADGET_MENU_UNDO
						MAINWINDOW.updatemenu
					EndIf				
				
				Case MENU_UNDO
					undocontext.undo
					If undocontext.undoavailable()
						EnableGadget GADGET_MENU_UNDO
					Else
						DisableGadget GADGET_MENU_UNDO
					EndIf
					If undocontext.redoavailable()
						EnableGadget GADGET_MENU_REDO
					Else
						DisableGadget GADGET_MENU_REDO
					EndIf
					MAINWINDOW.updatemenu
					
				Case MENU_REDO
					undocontext.redo
					If undocontext.undoavailable()
						EnableGadget GADGET_MENU_UNDO
					Else
						DisableGadget GADGET_MENU_UNDO
					EndIf
					If undocontext.redoavailable()
						EnableGadget GADGET_MENU_REDO
					Else
						DisableGadget GADGET_MENU_REDO
					EndIf
					MAINWINDOW.updatemenu
					
				Case MENU_ABOUT
					ShowGadget ABOUTWINDOW
					DisableGadget MAINWINDOW
					Repeat
						Select WaitEvent()
							Case EVENT_WINDOWCLOSE
								Exit
							Case EVENT_GADGETACTION
								Exit
						EndSelect
					Forever
					EnableGadget MAINWINDOW
					HideGadget ABOUTWINDOW
				
				Case MENU_NEW
					If Not EvaluateEvent(EVENT_MENUACTION,Null,MENU_CLOSE) Return False
					TEXTEDITOR.settext ""
					undocontext.flush()
					undocontext.createundostate
					DisableGadget GADGET_MENU_UNDO
					DisableGadget GADGET_MENU_REDO
					MAINWINDOW.updatemenu
					TSourcefile.current=New TSourcefile
					TSourcefile.current.gadget=TEXTEDITOR
					UpdateStatusbar
						
				Case MENU_EXIT
					If Not EvaluateEvent(EVENT_MENUACTION,Null,MENU_CLOSE) Return
					End

				Case MENU_CLOSE
					If TSourceFile.current
						If TSourceFile.current.changed
							Select Proceed("Save changes to "+Chr(34)+TSourceFile.current.path+Chr(34)+"?")
								Case 1'Yes
									If Not EvaluateEvent(EVENT_MENUACTION,Null,MENU_SAVE) Return False
								Case -1'Cancel
									Return False
								Case 0' No
							EndSelect
						EndIf
						TSourceFile.current.kill()
					EndIf
					Return True
					
				Case MENU_SAVEAS
					If TSourcefile.current
						file$=RequestFile("Save As","Script File (*.script):script;Text Documents (*.txt):txt;All Files:*",True,REQUESTFILEPATH)
						If file<>""
							stream:TStream=WriteFile(file)
							If stream
								REQUESTFILEPATH=ExtractDir(file)+""
								For n=0 To TextAreaLen(TSourcefile.current.gadget,TEXTAREA_LINES)-1
									s$=TextAreaText(TSourcefile.current.gadget,n,1,TEXTAREA_LINES)
									stream.writeline Left(s,s.length-1)
								Next
								stream.close
								TSourcefile.current.changed=False
								TSourcefile.current.path=file
								Return True
							Else
								Notify "Failed to write file "+Chr(34)+file+Chr(34)+"."
								Return False
							EndIf
						EndIf
					EndIf
				
				Case MENU_SAVE
					If TSourcefile.current
						If TSourcefile.current.path=""
							Return EvaluateEvent(EVENT_MENUACTION,Null,MENU_SAVEAS)
						Else
							stream:TStream=WriteFile(TSourcefile.current.path)
							If stream
								For n=0 To TextAreaLen(TSourcefile.current.gadget,TEXTAREA_LINES)-1
									s$=TextAreaText(TSourcefile.current.gadget,n,1,TEXTAREA_LINES)
									stream.writeline Left(s,s.length-1)
								Next
								stream.close
								TSourcefile.current.changed=False
								Return True
							Else
								Notify "Failed to write file "+Chr(34)+file+Chr(34)+"."
								Return False
							EndIf
						EndIf
					EndIf
				
				Case MENU_OPEN
					If Not EvaluateEvent(EVENT_MENUACTION,Null,MENU_CLOSE) Return False
					file$=RequestFile("Open","Script File (*.script):script;Text Documents (*.txt):txt;All Files:*",0,REQUESTFILEPATH)
					If file<>""
						stream:TStream=ReadFile(file)
						If stream
							REQUESTFILEPATH=ExtractDir(file)+""
							LockTextArea TEXTEDITOR
							SetTextAreaText TEXTEDITOR,""
							While Not stream.eof()
								AddTextAreaText TEXTEDITOR,stream.readline()+Chr(13)+Chr(10)
							Wend
							UnlockTextArea TEXTEDITOR
							SelectTextAreaText TEXTEDITOR,0,0
							stream.close()
							stream=Null
							undocontext.flush()
							undocontext.createundostate
							DisableGadget GADGET_MENU_UNDO
							DisableGadget GADGET_MENU_REDO
							MAINWINDOW.updatemenu
							TSourcefile.current=New TSourcefile
							TSourcefile.current.path=file
							TSourcefile.current.gadget=TEXTEDITOR
							UpdateStatusbar
						Else
							Notify "Failed to load file "+Chr(34)+file+Chr(34)+"."
						EndIf
					EndIf
									
			EndSelect
	EndSelect
EndFunction
'=================================================================
'
'=================================================================

Function ClearAfterLink(link:TLink)
	Local sublink:TLink
	Local nextsublink:TLink
	sublink=link.nextlink()
	While sublink
		nextsublink=sublink.nextlink()
		RemoveLink sublink
		sublink=nextsublink
	Wend
EndFunction

Function RemoveTextAreaSelection(textarea:TGadget)
	p=TextAreaCursor(textarea)
	l=TextAreaSelLen(textarea)
	If l=0 Return
	firstpart$=TextAreaText(textarea,0,p)
	lastpart$=TextAreaText(textarea,p+l,TextAreaLen(textarea)-p-l)
	SetTextAreaText textarea,firstpart+lastpart
	SelectTextAreaText textarea,p,0
EndFunction

Function TextAreaSelection$(textarea:TGadget)
	Return TextAreaText(textarea,TextAreaCursor(textarea),TextAreaSelLen(textarea))
EndFunction

Function InsertTextAreaText(textarea:TGadget,text$,pos=-1)
	If pos=-1 pos=TextAreaCursor(textarea)
	firstpart$=TextAreaText(textarea,0,pos)
	lastpart$=TextAreaText(textarea,pos,TextAreaLen(textarea)-pos)
	LockTextArea textarea
	SetTextAreaText textarea,firstpart+text+lastpart
	UnlockTextArea textarea
	SelectTextAreaText textarea,pos+Len(text),0
EndFunction

Function ClipboardText$()
	Local htext:Byte Ptr
	Local text$
	If OpenClipboard(0)
		htext=GetClipboardData(CF_TEXT)
		text$=String.fromcstring(htext)
		CloseClipboard()
		MemFree htext
	EndIf
	Return text
EndFunction

Function SetClipboardText:Int(text$)
	Local result:Int=False
	If text="" Return
	Local htext:Byte Ptr=text.ToCString()
	Local Memblock:Int=GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, text.length+1)
	Local DataBuf:Byte Ptr=GlobalLock(Memblock)
	MemCopy DataBuf, htext, text.length
	If OpenClipboard(0)
		EmptyClipboard
		SetClipboardData CF_TEXT, DataBuf
		CloseClipboard
		result=True
	EndIf
	MemFree htext
	GlobalUnlock Memblock
	GlobalFree Memblock
	Return result
End Function

Function UpdateStatusbar()
	SetStatusText MAINWINDOW,"Line "+TextAreaCursor(TEXTEDITOR,TEXTAREA_LINES)
EndFunction


I just wanted to say good job so far..
and in just over two hours?! wow.. :)

I edited it so everything works around an "UndoContext" type. This way you can have multiple undo systems running for different text areas. I've tested it quite a bit now, and it works really nicely.

@Leadwerks: Can I add your code template to BLIde? Next BLIde version will have code templates functionality. I will put you as the author of the template. If you have any problem with that, let me know it, and I'll remove your template from the BLIde set up.