glade -> maxgui -> event callbacks

BlitzMax Forums/BlitzMax GUI Programming/glade -> maxgui -> event callbacks

Heres the prize!
example.bmx
Import "loadglade.bmx"

LoadGlade("test1\test1.glade")

Global edit1:guiwrap=findGadget("entry1")
Global label1:guiwrap=findGadget("label1")
Global button1:guiwrap=findGadget("button1")

Global mainwindow:guiwrap=findGadget("window1")

' you can share callback functions ie button2.setActionCallBack(quitme)
mainwindow.setCloseCallBack(quitme)
edit1.setActionCallBack(edit1action)
button1.setActionCallBack(button1action)

processGUI()

Function quitme()		;	End		;	End Function

Function edit1action()	;	SetGadgetText(label1.maxgad,GadgetText(edit1.maxgad))	;	End Function

Function button1action()
	SetGadgetText(edit1.maxgad,"")
	SetGadgetText(label1.maxgad,"")
End Function

Dunno about you but I'm tired of long winded event select clauses... Delphi introduced me to event callback ages ago and its a powerful idea.

For the time being I'm using a couple of Tmap's to convert pointers but I could get round the need for both if only all BRL objects had a UserData:Object field...

andhow its a bit hackerish cause I tried out a bunch of different ideas.

Idealy it would need a more specific editor (but glade will fill the gap for a fair while!) it only has callbacks for 2 event types three gui object types and the minimum of gadget properties.

That said even with such a limited implementation it shows the convienience of this approch in terms of rapid application development.

I'd really welcome peoples feedback on this as I think that a community user interface system like this would greatly speed up gui development


loadglade.bmx
Import BaH.Libxml

Global gadList:TList=New TList
Global pointermap:tmap=New Tmap
Global gadgetmap:tmap=New Tmap

Type guiWrap 
	Field maxgad:Tgadget
	Field props:tmap
	
	Field closeCallback()=Null
	Field actionCallback()=Null
	
	Function create:guiWrap()
		Local g:guiWrap=New guiWrap
		g.props=New Tmap
		Return g
	EndFunction
	
	Method setCloseCallBack(ccb:Byte Ptr)
		closeCallback=ccb
	EndMethod

	Method setActionCallBack(acb:Byte Ptr)
		actionCallback=acb
	EndMethod
EndType

Function processGUI()
	While WaitEvent()
		Local eg:Tgadget
		Local gw:guiwrap
		eg=Tgadget(EventSource())
		If eg Then gw=guiwrap(MapValueForKey(gadgetmap,eg))
		Select EventID()
			Case EVENT_WINDOWCLOSE
				If gw.closeCallback Then gw.closeCallback()
			Case EVENT_GADGETACTION
				If gw.actionCallback Then gw.actionCallback()
		End Select
	Wend
EndFunction


Function findGadget:guiwrap(name:String)
	Return guiwrap(MapValueForKey(pointermap,name))
EndFunction

Function loadGlade(docname:String)

	parseDoc(docname)
	createInteface()

EndFunction

Function createInteface()
	Global lastwindow:guiwrap

	For Local g:guiWrap=EachIn GadList
		Local id:String,x:Int,y:Int,w:Int,h:Int,title:String,label:String,text:String
		Local parentid:guiWrap,pid:String

		id=String(g.props.ValueForKey("id"))
		x=Int(String(g.props.ValueForKey("x")))
		y=Int(String(g.props.ValueForKey("y")))
		w=Int(String(g.props.ValueForKey("width_request")))
		h=Int(String(g.props.ValueForKey("height_request")))
		title=String(g.props.ValueForKey("id"))
		label=String(g.props.ValueForKey("label"))
		text=String(g.props.ValueForKey("text"))
		pid=String(g.props.ValueForKey("id"))

		If w=0 Or h=0 Then Print "WARNING you must explicitly set width and height in glade for "+pid

		Select String(g.props.ValueForKey("class"))
			Case "GtkWindow"
				g=guiWrap.create()
				g.maxgad=CreateWindow(title,x,y,w,h)
				lastwindow=g	' this is a temp bodge  todo can gadget parents be specified after creation?
			Case "GtkButton"
				g=guiWrap.create()
				g.maxgad=CreateButton(label,x,y,w,h,lastwindow.maxgad)
			Case "GtkLabel"
				g=guiWrap.create()
				g.maxgad=CreateLabel(label,x,y,w,h,lastwindow.maxgad)
			Case "GtkEntry"
				g=guiWrap.create()
				g.maxgad=CreateTextField(x,y,w,h,lastwindow.maxgad)
				SetGadgetText g.maxgad,text
		EndSelect
		pointermap.insert(pid,g)
		If g.maxgad Then gadgetmap.insert(g.maxgad,g)
	Next

EndFunction

' todo check if gtkfixed causes "null" object....
Function parseNodeChildren(doc:TxmlDoc, node:TxmlNode, parentid:String,skip:Int=False,ntype:String="")
	Global currentGad:guiWrap

	Local uri:String
	
	Local list:TList = node.getChildren()

	For node = EachIn list

		If node.getName() = "widget" Then
			currentGad=guiWrap.create()
			ListAddLast GadList,currentGad
			If node.getattribute("class")<>"GtkFixed" Then
				currentGad.props.insert("class",node.getattribute("class"))
				MapInsert(currentGad.props,"id",node.getattribute("id"))
				MapInsert(currentGad.props,"parentid",parentid)
				parentid=node.getattribute("id")
				parseNodeChildren(doc,node,node.getattribute("id"))
			Else 
				parseNodeChildren(doc,node,parentid,True)
			End If
		End If
		
		If node.getName() = "child" Then parseNodeChildren(doc,node,parentid)
		If node.getName()= "property" And Not skip Then	MapInsert(currentGad.props,node.getattribute("name"),node.getContent())
		If node.getName()= "packing" And Not skip Then parseNodeChildren(doc,node,parentid,False,"packing")
	Next

End Function

Function parseDoc(docname:String)

        Local doc:TxmlDoc
        Local node:TxmlNode
        doc = TxmlDoc.parseFile(docname)
        
        If doc = Null Then
                Print "Document not parsed successfully."
                Return
        End If
        
        node = doc.getRootElement()
        If node = Null Then
                Print "empty document"
                doc.free()
                Return
        End If
        
        If node.getName() <> "glade-interface" Then
                Print "document of the wrong type, root node <> glade-interface"
                doc.free()
                Return
        End If
        
        parseNodeChildren(doc, node, "ROOT")
        doc.free()
End Function


test1.glade (load and visually edit widgets with glade)
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">

<glade-interface>

<widget class="GtkWindow" id="window1">
  <property name="width_request">500</property>
  <property name="height_request">400</property>
  <property name="visible">True</property>
  <property name="title" translatable="yes">window1</property>
  <property name="type">GTK_WINDOW_TOPLEVEL</property>
  <property name="window_position">GTK_WIN_POS_NONE</property>
  <property name="modal">False</property>
  <property name="resizable">True</property>
  <property name="destroy_with_parent">False</property>
  <property name="decorated">True</property>
  <property name="skip_taskbar_hint">False</property>
  <property name="skip_pager_hint">False</property>
  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
  <property name="focus_on_map">True</property>
  <property name="urgency_hint">False</property>

  <child>
    <widget class="GtkFixed" id="fixed1">
      <property name="visible">True</property>

      <child>
	<widget class="GtkButton" id="button1">
	  <property name="width_request">52</property>
	  <property name="height_request">25</property>
	  <property name="visible">True</property>
	  <property name="can_focus">True</property>
	  <property name="label" translatable="yes">button1</property>
	  <property name="use_underline">True</property>
	  <property name="relief">GTK_RELIEF_NORMAL</property>
	  <property name="focus_on_click">True</property>
	</widget>
	<packing>
	  <property name="x">40</property>
	  <property name="y">72</property>
	</packing>
      </child>

      <child>
	<widget class="GtkEntry" id="entry1">
	  <property name="width_request">158</property>
	  <property name="height_request">23</property>
	  <property name="visible">True</property>
	  <property name="can_focus">True</property>
	  <property name="editable">True</property>
	  <property name="visibility">True</property>
	  <property name="max_length">0</property>
	  <property name="text" translatable="yes"></property>
	  <property name="has_frame">True</property>
	  <property name="invisible_char">*</property>
	  <property name="activates_default">False</property>
	</widget>
	<packing>
	  <property name="x">96</property>
	  <property name="y">144</property>
	</packing>
      </child>

      <child>
	<widget class="GtkLabel" id="label1">
	  <property name="width_request">160</property>
	  <property name="height_request">16</property>
	  <property name="visible">True</property>
	  <property name="label" translatable="yes">label1</property>
	  <property name="use_underline">False</property>
	  <property name="use_markup">False</property>
	  <property name="justify">GTK_JUSTIFY_LEFT</property>
	  <property name="wrap">False</property>
	  <property name="selectable">False</property>
	  <property name="xalign">0.5</property>
	  <property name="yalign">0.5</property>
	  <property name="xpad">0</property>
	  <property name="ypad">0</property>
	  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
	  <property name="width_chars">-1</property>
	  <property name="single_line_mode">False</property>
	  <property name="angle">0</property>
	</widget>
	<packing>
	  <property name="x">96</property>
	  <property name="y">176</property>
	</packing>
      </child>
    </widget>
  </child>
</widget>

</glade-interface>


if the callback functions were in gui editor properties then the whole code could be automatically created...

This is awesome. Has anyone expanded on this stuff at all? Using glade to build interfaces would be much better than the terrible hack fest of tweak creation code / build, run / repeat.