Table Monitor - a real-time monitor for Hollywood tables

You can post your code snippets here for others to use and learn from
Post Reply
Flinx
Posts: 387
Joined: Sun Feb 14, 2021 9:54 am
Location: Germany

Table Monitor - a real-time monitor for Hollywood tables

Post 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
Flinx
Posts: 387
Joined: Sun Feb 14, 2021 9:54 am
Location: Germany

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

Post 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.
Flinx
Posts: 387
Joined: Sun Feb 14, 2021 9:54 am
Location: Germany

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

Post by Flinx »

Here is the second version of the Table Monitor.
The display should now be able to handle every string. If the content of a string is not recognized, it is treated as binary data, and the first 50 bytes are displayed in hexadecimal.
You can now also scroll through the window contents using the mouse scroll wheel.
Since I haven’t found a universal solution for handling a potentially active Select() call, you should make sure (as jPV recommended here) that no objects other than displays are selected with Select() in the main loop. After updating its window contents, the TableMonitor switches back to the previously active display.

The sample script uses all of the monitor’s features and also lists the controls.

TableMonitor v1.2.hws

Code: Select all

Const #MDUSESERIALIZETABLE=False

/****************************************************************
** Ein 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      |
   * nolinefeeds  Zeilenschaltungen von Zeichenketten nicht ausgeben (True/False)
** Rückgabewert:
   Tabelle {mdid - id des Displays, mdivid - id des Intervalls}
*/
Function p_TableMonitorDisplayCreate(table, title$, mdwidth, mdheight, mdxpos, mdypos, mdcolor, fontsize, fontcolor, nolinefeeds)
	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
	If IsNil(nolinefeeds)  Then nolinefeeds=False

	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)

	; Intervall zum Aktualisieren des Monitorfensters:
	mdivid=SetInterval(Nil, p_TableMonitorIntervalDisplayUpdate, 50, {mdid=mdid, ttable=table, xypos=xypos, fontsize=fontsize, fontcolor=fontcolor, nolinefeeds=nolinefeeds})
	; Handler zum Verschieben der Anzeigeposition mit den Cursortasten:
	InstallEventHandler({OnKeyDown=p_TableMonitorEventKeyMouse}, {mdid=mdid, ttable=table, xypos=xypos})
	; Handler zum vertikalen Verschieben der Anzeigeposition mit dem Scrollrad:
	InstallEventHandler({OnWheelDown=p_TableMonitorEventKeyMouse, OnWheelUp=p_TableMonitorEventKeyMouse}, {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
	Local did=p_TableMonitorGetSelectedDisplay()
	SelectDisplay(idTable.mdid,True)
	InstallEventHandler({CloseWindow=0}) ; Handler für Monitor-Display entfernen
	SelectDisplay(did)
	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

/****************************************************************
*/
Function p_TableMonitorGetSelectedDisplay()
	Local type, id = GetAttribute(#DISPLAY, 0, #ATTROUTPUTDEVICE)
	If type=#DISPLAY Then Return(id) Else Return(1)
EndFunction

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

	Local did=p_TableMonitorGetSelectedDisplay()

	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
		/*
		DebugPrint(msg.userdata.ttable)
		ForEach(msg.userdata.ttable, DebugPrint)
		ForEach(msg.userdata.ttable, Function(a, b)
						DebugPrint("--ttable:",a,b)
						ForEach(b, DebugPrint)
					     EndFunction
			)
		*/
		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_TableMonitorSerializeMixedTable(msg.userdata.ttable, msg.userdata.nolinefeeds)
				EndIf
			EndIf
			SetSerializeMode(smode) ; zurück auf vorherigen Wert
		Else
			st$=p_TableMonitorSerializeMixedTable(msg.userdata.ttable, msg.userdata.nolinefeeds)
		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(did, 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_TableMonitorSerializeMixedTable(t, nolinefeeds, 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_TableMonitorSerializeMixedTable(b, nolinefeeds, rc+1)
			Else
				Local bquotes$=""
				If GetType(b)=#STRING
					bquotes$="\""
					b=p_TableMonitorConvertToUTF8(b)
					If nolinefeeds
						b=ReplaceStr(ReplaceStr(b, "\n", "␊"), "\r", "␍")
					Else
						; Strings mit Zeilenschaltungen auf nächster Zeile beginnen lassen, damit die
						; erste Zeile auch links beginnt.
						; (Unten könnte man die Zeilen von b auch einzeln ausgeben und den Einzug zeilenweise
						; davorsetzen und damit an die Tabellenstruktur anpassen, dann wäre die erste Zeile kein Sonderfall.
						; Oder hier den Einzug vor allen \n einfügen.)
						If FindStr(b, "\n")>=0  Then b="\n"..b
					EndIf
				EndIf
				st$=st$.."    "..indent..aquotes$.. ToString(a)..aquotes$..": "..bquotes$..ToString(b)..bquotes$.."\n"
			EndIf
		   EndFunction
	)
	st$=st$..indent.."}\n"
	Return(st$)
EndFunction ; p_TableMonitorSerializeMixedTable

/****************************************************************
** 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_TableMonitorEventKeyMouse(msg)
	;DebugPrint(msg.action,":"..msg.key..":",msg.TimeStamp)
	
	SelectDisplay(msg.userdata.mdid, True)
	Local mdheight=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRHEIGHT)
	Local mdwidth= GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRWIDTH)
	Local mdfontsize=GetAttribute(#DISPLAY, msg.userdata.mdid, #ATTRFONTSIZE)

	Switch (msg.action)
	Case "OnKeyDown":
		Local mstep=5
		Switch msg.key
		Case  "HOME":
			msg.userdata.xypos.x=2
		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":
			If IsKeyDown("LSHIFT") Or IsKeyDown("RSHIFT")
				msg.userdata.xypos.x=msg.userdata.xypos.x+mdwidth-mdfontsize/2
			Else
				msg.userdata.xypos.x=msg.userdata.xypos.x+mstep
			EndIf
		Case "RIGHT":
			If IsKeyDown("LSHIFT") Or IsKeyDown("RSHIFT")
				msg.userdata.xypos.x=msg.userdata.xypos.x-mdwidth+mdfontsize/2
			Else
				msg.userdata.xypos.x=msg.userdata.xypos.x-mstep
			EndIf
			
		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)
	Case "OnWheelUp":
			
			If IsKeyDown("LCONTROL") Or IsKeyDown("RCONTROL")
				msg.userdata.xypos.x=msg.userdata.xypos.x+mdfontsize
			Else
				msg.userdata.xypos.y=msg.userdata.xypos.y+2*mdfontsize
			EndIf
	Case "OnWheelDown":
			
			If IsKeyDown("LCONTROL") Or IsKeyDown("RCONTROL")
				msg.userdata.xypos.x=msg.userdata.xypos.x-mdfontsize
			Else
				msg.userdata.xypos.y=msg.userdata.xypos.y-2*mdfontsize
			EndIf
	EndSwitch
		SetMargins(msg.userdata.xypos.x, 1e6, True)

	SelectDisplay(1, True)
EndFunction ; p_TableMonitorEventKeyMouse

/****************************************************************
** Die Kodierung des übergebenen Strings zu erraten versuchen und in UTF8 umwandeln
*/

Function p_TableMonitorConvertToUTF8(str$)
	Local UCS2BEBOM=False
	Local UCS2LEBOM=False
	Local i=0

	; auf UTF8-BOM überprüfen (EF BB BF)
	If StrLen(str$, #ENCODING_RAW)>2
		If ByteAsc(str$, 0)=0xEF And ByteAsc(str$, 1)=0xBB And ByteAsc(str$, 2)=0xBF
			; BOM entfernen
			str$=RightStr(str$, StrLen(str$, #ENCODING_RAW)-3, #ENCODING_RAW)
		EndIf
	EndIf

	; auf UCS-2-BE-BOM überprüfen (FE FF 00)
	If StrLen(str$, #ENCODING_RAW)>2
		If ByteAsc(str$, 0)=0xFE And ByteAsc(str$, 1)=0xFF And ByteAsc(str$, 2)=0x00
			UCS2BEBOM=True
			; BOM entfernen
			str$=RightStr(str$, StrLen(str$, #ENCODING_RAW)-2, #ENCODING_RAW)
		EndIf
	EndIf

	; auf UCS-2-LE-BOM überprüfen (FF FE xx 00)
	If StrLen(str$, #ENCODING_RAW)>3
		If ByteAsc(str$, 0)=0xFF And ByteAsc(str$, 1)=0xFE And ByteAsc(str$, 3)=0x00
			UCS2LEBOM=True
			; BOM entfernen
			str$=RightStr(str$, StrLen(str$, #ENCODING_RAW)-2, #ENCODING_RAW)
		EndIf
	EndIf

	; Sonderfall leerer String mit ausschließlich UCS-2-LE-BOM (FF FE)
	If StrLen(str$, #ENCODING_RAW)=2
		If ByteAsc(str$, 0)=0xFF And ByteAsc(str$, 1)=0xFE
			str$=""
		EndIf
	EndIf

	; Wenn mit UCS-2-BE-BOM oder UCS-2-LE-BOM kodiert ist, nach UTF-8 konvertieren.
	; (Die höherwertigen Bytes kommen bei BE zuerst und bei LE zuletzt.)
	If UCS2BEBOM
		i=0
		Local strneu$=""
		While  i < StrLen(str$, #ENCODING_RAW)
			strneu$=strneu$.. Chr(ByteVal(MidStr(str$, i, 2, #ENCODING_RAW),#SHORT))
			i=i+2
		Wend
		str$=strneu$
	EndIf

	If UCS2LEBOM
		i=0
		Local strneu$=""
		While  i < StrLen(str$, #ENCODING_RAW)
			Local c=MidStr(str$, i+1, 1, #ENCODING_RAW)..MidStr(str$, i, 1, #ENCODING_RAW)
			strneu$=strneu$.. Chr(ByteVal(c,#SHORT))
			i=i+2
		Wend
		str$=strneu$
	EndIf

	Local ok, pos = ValidateStr(str$) ; UTF8-Kodierung überprüfen
	If Not ok
		; Wenn kein UTF8, dann könnte es sowas wie Windows-Codepage 1252 sein,
		; also nachsehen ob keine ungewöhnlichen Zeichen vorkommen.
		; (Die Null (also "\000" gehört hier eigentlich auch hin, aber wie ich im Forum gesehen habe,
		; wissen manche nicht, daß Strings in Hollywood nicht nullterminiert sind. So werden also auch
		; nullterminierte Stings als Text angezeigt.)
		Local binaryindicators={"\001","\002","\003","\004","\005","\006","\015","\016","\017","\018","\019","\020","\021","\022","\023","\024","\025","\026","\028","\029","\030","\031","\127","\129","\141","\143","\144","\157"}
		Local isbinary=False
		ForEach(binaryindicators,
			Function(a, b)
				If FindStr(str$, b, False, 0, #ENCODING_RAW)>=0 Then isbinary=True
			EndFunction
		       )
		If isbinary
			Local hexstr$=""
			Local high=50 ; Anzahl anzuzeigender Hex-Bytes. (Evtl. als Argument übergeben?)
			Local bytes=Limit(StrLen(str$, #ENCODING_RAW)-1, 0, high-1)
			For Local i=0 To bytes
				hexstr$=hexstr$ ..FormatStr("%.2X", ByteVal(MidStr(str$,i,1, #ENCODING_RAW),#BYTE)).." "
			Next
			str$="(non-printable string, size "..ToString(StrLen(str$, #ENCODING_RAW))..", show "..ToString(high).." bytes hex) "..hexstr$
		Else
			str$=p_TableMonitorConvertCP1252ToUTF8(str$)
			ok, pos = ValidateStr(str$) ; paßt es jetzt?
			If Not ok
				str$=LeftStr(str$, pos, #ENCODING_RAW) ; unklaren Rest der Zeile ab pos weglassen
				; (Vielleicht sollte man in diesem Fall auch einfach alles hexadezimal ausgeben?)
			EndIf
		EndIf
	EndIf
	If StrLen(str$, #ENCODING_RAW)>0
		While ByteAsc(str$, StrLen(str$, #ENCODING_RAW)-1)=0 And StrLen(str$, #ENCODING_RAW)>1
			; Nullen am Ende entfernen
			str$=LeftStr(str$, StrLen(str$, #ENCODING_RAW)-1, #ENCODING_RAW)
		Wend
	EndIf
	
	Return(str$)
EndFunction ; p_TableMonitorConvertToUTF8

/****************************************************************
** Den übergebenen String von Windows-Codepage-1252 ("ANSI") in UTF8 umgewandeln
** Rückgabewert ist der konvertierte String
*/

Function p_TableMonitorConvertCP1252ToUTF8(str1$)

	Local str2$=""
	Local ConvertTable=
	{
		0xe282ac,0xefbfbd,0xe2809a,0xc692,0xe2809e,0xe280a6,0xe280a0,0xe280a1,
		0xcb86,0xe280b0,0xc5a0,0xe280b9,0xc592,0xefbfbd,0xc5bd,0xefbfbd,
		0xefbfbd,0xe28098,0xe28099,0xe2809c,0xe2809d,0xe280a2,0xe28093,0xe28094,
		0xcb9c,0xe284a2,0xc5a1,0xe280ba,0xc593,0xefbfbd,0xc5be,0xc5b8,
		0xc2a0,0xc2a1,0xc2a2,0xc2a3,0xc2a4,0xc2a5,0xc2a6,0xc2a7,
		0xc2a8,0xc2a9,0xc2aa,0xc2ab,0xc2ac,0xc2ad,0xc2ae,0xc2af,
		0xc2b0,0xc2b1,0xc2b2,0xc2b3,0xc2b4,0xc2b5,0xc2b6,0xc2b7,
		0xc2b8,0xc2b9,0xc2ba,0xc2bb,0xc2bc,0xc2bd,0xc2be,0xc2bf,
		0xc380,0xc381,0xc382,0xc383,0xc384,0xc385,0xc386,0xc387,
		0xc388,0xc389,0xc38a,0xc38b,0xc38c,0xc38d,0xc38e,0xc38f,
		0xc390,0xc391,0xc392,0xc393,0xc394,0xc395,0xc396,0xc397,
		0xc398,0xc399,0xc39a,0xc39b,0xc39c,0xc39d,0xc39e,0xc39f,
		0xc3a0,0xc3a1,0xc3a2,0xc3a3,0xc3a4,0xc3a5,0xc3a6,0xc3a7,
		0xc3a8,0xc3a9,0xc3aa,0xc3ab,0xc3ac,0xc3ad,0xc3ae,0xc3af,
		0xc3b0,0xc3b1,0xc3b2,0xc3b3,0xc3b4,0xc3b5,0xc3b6,0xc3b7,
		0xc3b8,0xc3b9,0xc3ba,0xc3bb,0xc3bc,0xc3bd,0xc3be,0xc3bf
	}
	For Local i=0 To StrLen(str1$, #ENCODING_RAW)-1
		char=ByteAsc(str1$, i)
		If char<128
			str2$=str2$..ByteChr(char) ; keine Konvertierung nötig
		Else
			Local utf=GetItem(ConvertTable, char-128)
			If utf>=65536
				utf3=Int(utf/65536)
				utf2=Int((utf-utf3*65536)/256)
				utf1=utf-utf3*65536-utf2*256
				str2$=str2$..ByteChr(utf3)..ByteChr(utf2)..ByteChr(utf1)
			Else
				utf2=Int(utf/256)
				utf1=utf-utf2*256
				str2$=str2$..ByteChr(utf2)..ByteChr(utf1)
			EndIf
		EndIf
	Next

	Return(str2$)
EndFunction ; p_TableMonitorConvertCP1252ToUTF8

/****************************************************************
** Den übergebenen String hexadezimal über das debug device ausgeben.
** Wird nicht benutzt (steht hier nur für Diagnosezwecke zur Verfügung)
*/
Function p_PrintHex(st$)
	Local Debugstr$=""
	For Local i=0 To StrLen(st$, #ENCODING_RAW)-1
		Debugstr$=Debugstr$ ..FormatStr("%.2X", ByteVal(MidStr(st$,i,1, #ENCODING_RAW),#BYTE)).." "
	Next
	DebugPrint(Debugstr$)
EndFunction
TableMonitor-Test v1.2.hws

Code: Select all

@INCLUDE "TableMonitor v1.2.hws"

SetDisplayAttributes({Title="TableMonitor Test v1.2"})
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, 0x118811)
Sleep(300)

For i=0 To 99 ; Make binary string
	bs$=bs$.."\000"
Next
testTable2={	"A","B", 0, c="c", subtable={0}, stringwithsquarebrackets$="[one bracket] [[two brackets]] ]][][[",
		binarystring$=bs$,
		stringwithlinefeeds$="Line 1\nLine 2\nLine 3 - no line feed:\\n", ; (To check the nolinefeed argument. Compare with testTable3)
		UCS2BEBOM$="\254\255\000\079\000\109\000\101\000\103\000\097\000\058\000\032\003\169", ; "Omega: Ω"
		UCS2LEBOM$="\255\254\079\000\109\000\101\000\103\000\097\000\058\000\032\000\169\003",
		UTF8BOM$="\239\187\191\079\109\101\103\097\058\032\206\169",
		UTF8$="\079\109\101\103\097\058\032\206\169",
		nullterminatedstring$="Hollywood strings are not null-terminated.\000"	    
}
idTable2=p_TableMonitorDisplayCreate(testTable2, "test2", 400, 250, 75, 300, 0x222288, 15, #WHITE, True)
Sleep(300)

testTable3={"A","B", 0, c$="c", ["string index with spaces"]="somecharacters" ,
	    ["string index with [square] [[brackets]] ]][][["]="nosquarebracketshere" ,
	    subtable={"subtable content",{"sub2table content"}},
	    stringwithlinefeeds$="Line 1\nLine 2\nLine 3 - no line feed:\\n",
	    Function() EndFunction, ["function"]=Function() EndFunction, datetime=""}
idTable3=p_TableMonitorDisplayCreate(testTable3, "test3 (mixed index types)", 400, 250, 100, 600, 0xffe077, 20, #BLACK, False)
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)

; Set up intervals for continuous 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)
			    For Local i=0 To StrLen(testTable2.binarystring$, #ENCODING_RAW)-1
				    testTable2.binarystring$=InsertStr(testTable2.binarystring$, ByteChr(Rnd(256)), i, True, #ENCODING_RAW)
			    Next
		 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() BeginRefresh()
				Cls
				Locate(2,2)
				NPrint(GetDate(#DATELOCAL))
				NPrint("\nControls:\nArrow keys\nShift + Horizontal arrow keys\nPage up/down\nScroll wheel\nCtrl + Scroll wheel\nHome\nEsc\n")
				EndRefresh()
		      EndFunction, 7000)
SetTimeout(Nil, Function() ChangeInterval(iidm, 1000) EndFunction, 8000)

/*
; Check if the selected display keeps stable:
NPrint("open and select display 2")
CreateDisplay(2, {X=#RIGHT})
OpenDisplay(2)
SelectDisplay(2, True)
*/
NPrint("starting main loop")
Repeat
	WaitEvent()
Forever
Post Reply