Page 1 of 1

Table Monitor - a real-time monitor for Hollywood tables

Posted: Wed May 20, 2026 8:46 pm
by Flinx
Since it can sometimes be difficult to keep track of complex table structures, I built a debugging tool that displays a table in a dedicated window.
And since I think it might be helpful to others as well, I'm posting it here.
The functions should be able to be integrated into any Hollywood program that has the ability to open new windows. If you save the script under the name “TableMonitor v1.0.hws”, it will be included as follows:
@INCLUDE "TableMonitor v1.0.hws"
You can then display an existing table by calling p_TableMonitorDisplayCreate():
idtable=p_TableMonitorDisplayCreate(Table, "Title")
The arguments Table and “Title” are the table name and the window title. The return value idtable is needed if you want to close the monitor window:
p_TableMonitorDisplayClose(idtable)
You can also simply close the window with the mouse.
If necessary, additional arguments not listed here control the position, size, color, and font size. You can find them in the function description and in the sample script.
In the open window, you will then see the table contents in a format similar to that generated by SerializeTable(). Initially, I had also used this function, but since it is not suitable for all table structures, I built a corresponding Hollywood function as a replacement. If you prefer to use SerializeTable() if possible (because it is faster), you can control this with #MDUSESERIALIZETABLE (at the beginning of the script).
If the displayed table contents are larger than the window, you can scroll the window contents using the arrow keys; PgUp and PgDn also work, and Esc resets the position.

TableMonitor v1.0.hws:

Code: Select all

Const #MDUSESERIALIZETABLE=False

/****************************************************************
** Tabellenmonitor-Fenster erzeugen
** Diese Funktion wird vom zu testenden Programm aufgerufen. Das
** erzeugte Fenster kann durch p_TableMonitorDisplayClose()
** wieder geschlossen werden.
** Argumente:
   * table    anzuzeigende Tabelle (muß beim Aufruf bereits existieren)
   * title$   Name der Tabelle für den Fenstertitel
   * mdwidth  |
   * mdheight |
   * mdxpos   | Parameter für das Fenster
   * mdypos   |
   * mdcolor  |
** Rückgabewert:
   Tabelle {mdid - id des Displays, mdivid - id des Intervalls}
*/
Function p_TableMonitorDisplayCreate(table, title$, mdwidth, mdheight, mdxpos, mdypos, mdcolor, fontsize, fontcolor)
	Local mdivid
	If IsNil(mdwidth)   Then mdwidth=400
	If IsNil(mdheight)  Then mdheight=300
	If IsNil(mdxpos)    Then mdxpos=20
	If IsNil(mdypos)    Then mdypos=GetAttribute(#DISPLAY, 1, #ATTRYPOS)
	If IsNil(mdcolor)   Then mdcolor=0x222240
	If IsNil(fontcolor) Then fontcolor=#WHITE
	If IsNil(fontsize)  Then fontsize=15

	mdid=CreateDisplay(Nil, {Width=mdwidth, Height=mdheight, X=mdxpos, Y=mdypos, Color=mdcolor, Layers=False,
				Title="Table Monitor:  "..title$, Active=False, Sizeable=True,
				Hidden=False, NoLiveResize=True})
	OpenDisplay(mdid)
	Local xypos={x=2, y=2}
	SetMargins(xypos.x, 1e6, True)

	mdivid=SetInterval(Nil, p_TableMonitorIntervalDisplayUpdate, 50, {mdid=mdid, ttable=table, xypos=xypos, fontsize=fontsize, fontcolor=fontcolor})
	; Handler zum Verschieben der Anzeigeposition mit den Cursortasten:
	InstallEventHandler({OnKeyDown=p_TableMonitorEventPhysKey}, {mdid=mdid, ttable=table, xypos=xypos})
	; Handler zum Schließen dieses Monitor-Displays. Als Userdata wird dieselbe Tabelle wie beim
	; Rückgabewert übergeben, damit p_TableMonitorDisplayClose direkt damit aufgerufen werden kann:
	InstallEventHandler({CloseWindow=p_TableMonitorEventCloseWindow},{mdid=mdid, mdivid=mdivid})
	; Handler für Änderung der Fenstergröße:
	InstallEventHandler({SizeWindow=p_TableMonitorEventSizeWindow}, {mdid=mdid, xypos=xypos})						   
	SelectDisplay(1)
	Return({mdid=mdid, mdivid=mdivid})
EndFunction ;p_TableMonitorDisplayCreate


/****************************************************************
** Tabellenmonitor-Fenster schließen
** Argumente:
   * idTable - Tabelle {id des Displays, id des Intervalls}
*/
Function p_TableMonitorDisplayClose(idTable)
	gDebugExtrasum=Nil
	If Not IsNil(idTable)
		If HaveObject(#INTERVAL,idTable.mdivid) 
			ClearInterval(idTable.mdivid)
		EndIf
	EndIf
	If IsNil(idTable) Then Return
	If Not HaveObject(#DISPLAY,idTable.mdid) Then Return
	SelectDisplay(idTable.mdid,True)
	InstallEventHandler({CloseWindow=0}) ; Handler für Monitor-Display entfernen
	SelectDisplay(1)
	FreeDisplay(idTable.mdid)
EndFunction ;p_TableMonitorDisplayClose


/****************************************************************
** Event SizeWindow: Änderung der Fenstergröße
*/
Function p_TableMonitorEventSizeWindow(msg)
	SelectDisplay(msg.userdata.mdid, True)
	SetMargins(msg.userdata.xypos.x, 1e6, True)
	SelectDisplay(1, True)
EndFunction ;p_TableMonitorEventSizeWindow

/****************************************************************
** Intervall: Monitor-Variablenanzeigen aktualisieren
*/
Function p_TableMonitorIntervalDisplayUpdate(msg)
	If Not HaveObject(#DISPLAY,msg.userdata.mdid) Then Return
	SelectDisplay(msg.userdata.mdid, True)

	Local mdfontname$=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRFONTNAME)
	Local mdfontsize=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRFONTSIZE)
	Local mdfontcolor=GetFontColor()
	; Workaround für interne Fonts (#SANS, #SERIF und #MONOSPACE):
	Local internalFontnames={
			["Bitstream Vera Sans"]=":::default_sans",
			["Bitstream Vera Serif"]=":::default_serif",
			["Bitstream Vera Sans Mono"]=":::default_mono"}
	If RawGet(internalFontnames, mdfontname$) Then mdfontname$=internalFontnames[mdfontname$]

	SetFont(#MONOSPACE, msg.userdata.fontsize)
	SetFontColor(msg.userdata.fontcolor)

	BeginRefresh() ; ---↓
	Cls()
	Locate(msg.userdata.xypos.x, msg.userdata.xypos.y)
	Local tblerror=False
	If HaveItem(msg.userdata, "ttable")
		If GetType(msg.userdata.ttable)<>#TABLE
			NPrint("Is no table")
			tblerror=True
		EndIf
	Else
		; Hinweis: nachträgliches Löschen der Tabelle wird hier
		; nicht erkannt, die Tabelle bleibt in der Datenstruktur
		; des TableMonitors erhalten.
		NPrint("Table does not exist")
		tblerror=True
	EndIf
	If Not tblerror
		Local err, st$
		If #MDUSESERIALIZETABLE
			Local smode=GetSerializeMode()
			SetSerializeMode(#SERIALIZEMODE_HOLLYWOOD)
			err, st$= ?SerializeTable(msg.userdata.ttable)
			If err<>#ERR_NONE
				SetSerializeMode(#SERIALIZEMODE_NAMED)
				/* #SERIALIZEMODE_NAMED funktioniert auch mit String-Indexen, die
				Leerzeichen enthalten, aber nicht mit gemischten (num/string) Indexen */
				err, st$= ?SerializeTable(msg.userdata.ttable)
				If err<>#ERR_NONE
					st$=p_SerializeMixedTable(msg.userdata.ttable)
				EndIf
			EndIf
			SetSerializeMode(smode) ; zurück auf vorherigen Wert
		Else
			st$=p_SerializeMixedTable(msg.userdata.ttable)
		EndIf
		st$=ReplaceStr(ReplaceStr(st$, "[", "[["), "]", "]]")
		st$=ReplaceStr(st$, "\09","    ") ; Tabulatoren weg, weil NPrint sie als Leerzeilen ausgibt
		NPrint(st$)
	EndIf
	EndRefresh()   ; ---↑

	SetFont(mdfontname$, mdfontsize, {Engine=#FONTENGINE_INBUILT})
	SetFontColor(mdfontcolor)

	SelectDisplay(1, True)
EndFunction ;p_TableMonitorIntervalDisplayUpdate


/****************************************************************
** Tabelle als String ausgeben, die sich mit SerializeTable nicht
** ausgeben läßt, weil sie gemischte Indexe hat
** Argumente:
   * t  - anzuzeigende Tabelle
   * rc - Rekursionstiefe
*/
Function p_SerializeMixedTable(t, rc)
	If IsNil(rc) Then rc=0
	Local st$=""
	Local indent=""
	For Local i=1 To rc
		indent=indent.."    "
	Next
	If rc>500 Then Return("{} (Recursion stopped: too many subtables.)\n")
	st$=st$.."{\n"
	ForEach(t, Function(a, b)
			Local aquotes$=""
			If GetType(a)=#STRING Then aquotes$="\""
			If GetType(b)=#TABLE
				st$=st$..indent.."    "..aquotes$..a..aquotes$..": "..p_SerializeMixedTable(b, rc+1)
			Else
				Local bquotes$=""
				If GetType(b)=#STRING Then bquotes$="\""
				st$=st$.."    "..indent..aquotes$.. ToString(a)..aquotes$..": "..bquotes$..ToString(b)..bquotes$.."\n"
			EndIf
		   EndFunction
	)
	st$=st$..indent.."}\n"
	Return(st$)
EndFunction ; p_SerializeMixedTable

/****************************************************************
** Event CloseWindow: Reaktion auf Schließen des Fensters
*/
Function p_TableMonitorEventCloseWindow(msg)
	p_TableMonitorDisplayClose(msg.UserData)
EndFunction ; p_EventCloseWindow

/****************************************************************
** Event OnKeyDown: Reaktion auf Tastendruck
*/
Function p_TableMonitorEventPhysKey(msg)
	SelectDisplay(msg.userdata.mdid, True)
	Local mdheight=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRHEIGHT)
	Local mdfontsize=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRFONTSIZE)

	Switch (msg.action)
	Case "OnKeyDown":
		Local mstep=5
		Local msgkey=msg.key
		Switch msgkey				
		Case  "ESC":
			msg.userdata.xypos.x=2
			msg.userdata.xypos.y=2
		Case "DOWN":
			msg.userdata.xypos.y=msg.userdata.xypos.y-mstep
		Case "UP":
			msg.userdata.xypos.y=msg.userdata.xypos.y+mstep
		Case "LEFT":
			msg.userdata.xypos.x=msg.userdata.xypos.x+mstep
;			SetMargins(msg.userdata.xypos.x, 1e6, True)
		Case "RIGHT":
			msg.userdata.xypos.x=msg.userdata.xypos.x-mstep
;			SetMargins(msg.userdata.xypos.x, 1e6, True)
		Case "PAGEUP":
			msg.userdata.xypos.y=msg.userdata.xypos.y+mdheight-2*mdfontsize
		Case "PAGEDOWN":
			msg.userdata.xypos.y=msg.userdata.xypos.y-mdheight+2*mdfontsize
		EndSwitch
		SetMargins(msg.userdata.xypos.x, 1e6, True)
	EndSwitch
	SelectDisplay(1, True)
EndFunction ; p_TableMonitorEventPhysKey
The sample script below doesn’t do much of practical use, but its tables include all the structures I could think of, and you can see how the whole thing works. If anything is missing or has bugs, please let me know.

TableMonitor-Test v1.0.hws:

Code: Select all

@INCLUDE "TableMonitor v1.0.hws"

SetFont(#SANS,30)
NPrint("\nTableMonitor test\n")
NPrint("open four windows")
testTable1={"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt"..
" ut labore et dolore magna aliquyam erat, sed diam voluptua. ", "bb", 0, {0.0}}
idTable1=p_TableMonitorDisplayCreate(testTable1, "test1", 400, 250, 50, #TOP, 0x22aa22)

Sleep(300)
testTable2={"A","B", 0, c="c", subtable={0}}
idTable2=p_TableMonitorDisplayCreate(testTable2, "test2", 400, 250, 75, 300, 0x222288)

Sleep(300)
testTable3={"A","B", 0, c="c", ["string index with spaces"]="idxmspace" ,
	    subtable={"subtable content",{"sub2table content"}},
	    Function() EndFunction, ["function"]=Function() EndFunction, datetime=""}
idTable3=p_TableMonitorDisplayCreate(testTable3, "test3 (mixed index types)", 400, 250, 100, 600, 0xeebb33, 18, #BLACK)

Sleep(300)
idTable4=p_TableMonitorDisplayCreate(5,          "test4 (will close in 4 seconds)")

; close fourth example window ("no table") after four seconds:
iidt={}
iidt[0]=SetInterval(Nil, Function()
				NPrint("close the fourth window")
				p_TableMonitorDisplayClose(idTable4)
				ClearInterval(iidt[0]) ; only once
				iidt=Nil
			 EndFunction, 4000)

; make some changes in the tables:
SetInterval(Nil, Function() testTable1[0]=UnrightStr(testTable1[0],1)..LeftStr(testTable1[0],1)
			    testTable1[2]=testTable1[2]+1
			    testTable1[3][0]=testTable1[3][0]+#PI
		 EndFunction, 300)
SetInterval(Nil, Function() testTable2[2]=testTable2[2]+1
			    Local x=testTable2[0]
			    testTable2[0]=testTable2[1]
			    testTable2[1]=x
			    testTable2.c=Chr(48+Rnd(75))
			    testTable2.subtable[0]=Rnd(10)
		 EndFunction, 800)
SetInterval(Nil, Function() testTable3.datetime=GetDate(#DATELOCAL) EndFunction, 200)
	
; output text in the main window to check if the font settings are unmodified:	
iidm=SetInterval(Nil, Function() Cls Locate(2,2) NPrint(GetDate(#DATELOCAL)) EndFunction, 7000)
SetTimeout(Nil, Function() ChangeInterval(iidm, 1000) EndFunction, 8000)

NPrint("starting main loop")
Repeat
	WaitEvent()
Forever

Re: Table Monitor - a real-time monitor for Hollywood tables

Posted: Thu May 21, 2026 8:53 am
by Flinx
I forgot to mention that at the end of the interval function, SelectDisplay(1, True) is executed. Of course, this is only correct if your program uses Display 1 by default. Otherwise, you will need to adjust this line.
I haven’t found a way to determine which display is currently selected, so this isn’t handled automatically.