4.9 Beispiel 3: Der Objekt-Browser

Was der integrierten Benutzeroberfläche von Director fehlt, ist die Möglichkeit, Objektwerte und Objektabhängigkeiten darzustellen. Während der Inhalt von primitiven Variablen und Listen im Nachrichtenfenster, Debugger und Watcher im Klartext dargestellt wird, erhält man bei Objekten und Verhalten nur dessen Referenz. Dieses Beispiel, ein selbstgeschriebener Objektbrowser, soll diesen Missstand beseitigen.

Der Objekt-Browser soll folgende Funktion aufweisen: ausgehend von den Globalen soll man sich von komplexen Datentypen, also auch Listen, deren Eigenschaften und Werte anzeigen lassen können. Handelt es sich ebenfalls um komplexe Datentypen, soll es möglich sein, dessen Eigenschaften anzeigen zu lassen. Zusätzlich soll es möglich sein, wie in einem Internetbrowser, in der History vor- und zurückzugehen. Außerdem ist es sinnvoll, den Objektpfad der aktuellen Eigenschaft anzuzeigen, um ihn z.B. in den Watcher kopieren zu können. Es bietet sich an, diesen Browser als Lingo-Xtra zu programmieren. Dabei wird von dem Umstand profitiert, dass ein Director-Film, der im Xtra-Ordner liegt, im Xtra-Menü der Menüleiste angezeigt wird. Wählt man diesen Film aus, so öffnet sich ein weiteres Fenster, in dem der Film läuft. Der Film stellt ein von Director geöffnetes MIAW (Movie in a Window) dar, entsprechend gelten zusätzlichen Event-Handler, wie on activateWindow, on openWindow und on closeWindow.

Um alle globalen Variablen zu erhalten, kann man die Lingo-Eigenschaft the globals benutzen, die eine Eigenschaftliste aller globalen Variablen zurückliefert. Das Problem ist, dass der Objekt-Browser selbst auch globale Variablen verwalten muss, oder zumindest ein globales Objekt mit eigenen Eigenschaften, d.h. er würde selbst immer in seiner eigenen Globalenliste auftauchen. Wir vermeiden diese Problem, indem wir den Objektbrowser als statisches Objekt programmieren.

Beim Öffnen des Objekt-Browsers werden die Eigenschaften des ObjectBrowser-Objekt initialisiert. Da dieses Objekt statisch sein soll, wird keine neue Instanz mit den new-Handler gebildet, sondern eine eigener Handler namens init() dazu aufgerufen.

on openWindow
-- Fenster ohne Titelleise darstellen
(the activeWindow).windowtype = 2
-- statisches Objektscript initialisieren
script("ObjectBrowser").init()
end openWindow

Wenn das Fenster wieder aktiviert wird, z.B. wenn es per Mausklick in den Vordergrund geholt wird, wird automatisch die update()-Methode aufgerufen.

on activateWindow
script("ObjectBrowser").update()
end activateWindow

Wichtig ist außerdem, dass beim Schließen des Fensters der MIAW mit dem Befehl forget() vollständig aus dem Speicher entfernt wird. Ohne diesen Befehl bleibt eine unsichtbare Instanz im Hintergrund erhalten.

on closeWindow
forget the activeWindow
end closeWindow

Die Oberfläche des Objekt-Browsers sieht folgendermaßen aus: Wenn man auf den Button "the globals" klickt, werden alle Globalen inklusive der Director-Eigenschaft the actorlist im Eigenschaftenfenster angezeigt. Wenn diese mit der Maus markiert werden, lässt sich unterhalb des Fensters der Typ der Eigenschaft bzw. Globalen ablesen. Dieser entspricht dem Wert Lingo-Funktion ilk(object), also z.B. #instance für eine Objektinstanz und #integer für eine Ganzzahl.

Darunter befindet sich der absolute Objektpfad der angewählten Eigenschaft, also die genaue Verknüpfung von Eigenschaften und Indizes in der Punktnotation, um diesen Wert in Filmskripten oder im Nachrichtenfenster anzusprechen. Das untere Feld stellt den Wert der eingestellten Eigenschaft dar. Der Objektpfad und der Wert lassen sich durch einen Mausklick auf das entsprechende Feld in die Zwischenablage kopieren (dargestellt durch den Cursor ). Damit lässt sich z.B. der Objektpfad in das Watcher-Fenster übertragen, um den Wert einer Eigenschaft ständig zu überwachen.

Bei einem Doppelklick auf eine Eigenschaft vom Typ Objektinstanz oder Liste werden wiederum die Eigenschaften dieses Objektes oder Liste im Eigenschaftsfenster angezeigt. Somit kann man den gesamten Baum von Objektabhängigkeiten per Mausklick entlanggehen. Mit dem Pfeil nach links kann man wieder eine Ebene zurückspringen, mit dem Pfeil nach rechts entsprechend eine Ebene vorspringen. Der Button "update" aktualisiert die Anzeige für das gerade angezeigte Objekt.

Mit der Minimieren-Schaltfläche auf der Titelleiste lässt sich das Fenster verkleinern, um den Blick auf dahinterliegende Fenster freizugeben. Anschließend lässt es sich durch die Maximieren-Schaltfläche wieder auf die Originalgröße zurücksetzen. Ein Ziehen der Titelleiste verschiebt außerdem das ganze Fenster. Mit der Schließen-Schaltfläche lässt sich das Fenster schließen.9

Die Programmierung des Objektbrowser basiert auf zwei Lingo-Konstrukten. Um die Eigenschaften eines Objektes zu bestimmen, werden die Methoden count() zur Ermittlung der Zahl der Objekteigenschaften, sowie getPropAt() zur Ermittlung der Eigenschaftsnamen benutzt. Mit der Lingo-Funktion value() wird der Wert einer Variablen abgefragt, die als Zeichenkette vorliegt. So liefert value("VariablenName") den Wert der Variablen VariablenName zurück. Praktischerweise funktioniert dies auch für einen ganzen Objektpfad. Dieser wird einfach als Zeichenkette aufgebaut, indem auf jeder neuen Ebene mit dem Verknüpfungsoperator "&" eine neue Eigenschaft angehängt wird (z.B. "gSpielObjekt" & "." -> "mPuzzleteileListe" à "gSpielObjekt.mPuzzleteileListe"; value("gSpielObjekt.mPuzzleteileListe") -> Wert der Eigenschaft mPuzzleteileListe des Objekts gSpielObjekt).

Hier ist das Skript des ObjektBrowser-Objektes. Das vollständige Programm befindet sich auf der beiliegenden CD. Es muss in den Ordner "Xtras" im Director-Installationsverzeichnis kopiert werden und kann dann über das Xtra-Menü aufgerufen werden.

-- statisches Objekt

property mCurrentProperties, mCurrentLevel, mHistory
property mHomeButtonSprite, mBackButtonSprite, mForwardButtonSprite
property mOutputMember, mObjectPathMember, mObjectValueMember
property mObjectTypeMember

on init me
-- initialisiert das Objekt
-- welche Objekte, Globalen oder Eigenschaften werden im Moment
-- angezeigt?
mCurrentProperties = []
-- auf welcher Ebene befinden wird uns?
mCurrentLevel = 0
-- die History der Objekte: jeder Eintrag entspricht einer Ebene
mHistory = []
-- Sprites der Buttons
mHomeButtonSprite = sprite(2)
mBackButtonSprite = sprite(4)
mForwardButtonSprite = sprite(5)
-- Text-Member
mOutputMember = member("output")
mObjectPathMember = member("objectPath")
mObjectValueMember = member("objectValue")
mObjectTypeMember = member("objectType")
-- oberste Ebene einlesen
me.home()
end init

on home me
-- zeigt die oberste Ebene an
-- oberste Ebene
mCurrentLevel = 0
-- History löschen
mHistory.deleteAll()
me.update()
end home

on back me
-- geht in der history einen Schritt zurück
if mCurrentLevel > 0 then
mCurrentLevel = mCurrentLevel - 1
me.update()
else
-- oberste Ebene
beep
end if
end back

on forward me
-- geht in der history einen Schritt vorwärts
if mHistory.count() > mCurrentLevel then
mCurrentLevel = mCurrentLevel + 1
me.update()
else
-- kein Eintrag mehr in der History
beep
end if
end forward

on update me
-- liest die Eigenschaften des aktuell angezeigten Objekts
-- erneut aus
if mCurrentLevel = 0 then
mCurrentProperties = generatePropertyList(VOID)
-- Value-fenster löschen
mObjectPathMember.text = EMPTY
mObjectValueMember.text = EMPTY
mObjectTypeMember.text = "<the globals>"
else
displayedObjectPath = me.displayedObjectPath()
thisPropertyValue = value(displayedObjectPath)
mCurrentProperties = me.generatePropertyList(thisPropertyValue)
-- Value-fenster updaten
mObjectPathMember.text = displayedObjectPath
mObjectValueMember.text = string(thisPropertyValue)
mObjectTypeMember.text = me.typeText(thisPropertyValue)
end if
me.repaint()
end update


on repaint me
-- setzt die Anzeige auf neue Werte
-- und aktualisert die buttons
outputText = EMPTY
repeat with variable in mCurrentProperties
outputText = outputText & variable & RETURN
end repeat
-- Hilite entfernen
mOutputMember.line[mOutputMember.lines.count + 1].hilite()
mOutputMember.text = outputText
-- Buttons aktualisieren
if mCurrentLevel = 0 then
mHomeButtonSprite.blend = 50
mBackButtonSprite.blend = 50
else
mHomeButtonSprite.blend = 100
mBackButtonSprite.blend = 100
end if
if mHistory.count() > mCurrentLevel then
mForwardButtonSprite.blend = 100
else
mForwardButtonSprite.blend = 50
end if
end repaint

on displayValue me, propNum
-- zeigt den Wert und Pfad der Eigenschaft propNum an
if (propNum < 1) or (propNum > mCurrentProperties.count()) then
-- propNum nicht im gültigen Bereich
nothing
else
mOutputMember.line[propNum].hilite()
propPath = me.propertyPath(propNum)
prop = value(propPath)
mObjectPathMember.text = propPath
mObjectValueMember.text = string(prop)
mObjectTypeMember.text = me.typeText(prop)
end if
end displayValue

on browseProperty me, propNum
-- zeigt die Eigenschaften von Eigenschaft propNum an
if (propNum < 1) or (propNum > mCurrentProperties.count()) then
-- propNum nicht im gültigen Bereich
beep
else
propertyReference = me.propertyReference(propNum)
propertyPath = me.propertyPath(propNum)
thisProperty = value(propertyPath)
case thisProperty.ilk of
#list, #propList, #instance, #script:
-- lineare Liste, Eigenschaftliste, Child-Objekt oder Skript-Objekt
mCurrentProperties = me.generatePropertyList(thisProperty)
-- History-Einträge nach aktuellem Level löschen
repeat while mHistory.count() > mCurrentLevel
mHistory.deleteAt(mHistory.count())
end repeat
mHistory.add(propertyReference)
mCurrentLevel = mCurrentLevel + 1
me.repaint()
otherwise
-- nicht-browsebares Objekt
beep
end case
end if
end browseProperty

on displayedObjectPath me
-- gibt die Referenz auf das aktuell angezeigte Objekt als String zurück
objektPath = EMPTY
repeat with i = 1 to mCurrentLevel
objektPath = objektPath & mHistory[i]
end repeat
return objektPath
end displayedObjectPath

on propertyPath me, propNum
-- gibt die vollständige Referenz auf Eigenschaft propNum
-- des aktuell angezeigten Objekt als String zurück
return me.displayedObjectPath() & me.propertyReference(propNum)
end propertyPath

on propertyReference me, propNum
-- gibt die Referenz auf Eigenschaft propNum des aktuell angezeigten
-- Objekts releativ zum Objekt als String zurück
displayedObjectPath = me.displayedObjectPath()
displayedObject = value(displayedObjectPath)
if mCurrentLevel = 0 then
-- oberste Ebene
return mCurrentProperties[propNum]
else
case displayedObject.ilk of
#list:
-- lineare Liste
return "[" & propNum & "]"
#propList:
-- Eigenschaftliste
propName = displayedObject.getPropAt(propNum)
-- Typ der Eigenschaft prüfen
case value(propName).ilk of
#symbol:
-- Symbol
return "." & string(propName)
#string:
-- Zeichenkette
return "[" & QUOTE & string(propName) & QUOTE & "]"
otherwise
return "[" & string(propName) & "]"
end case
#instance, #script:
-- Child-Objekt oder Skript-Objekt
propName = displayedObject.getPropAt(propNum)
return "." & string(propName)
end case
end if
end propertyPath

on generatePropertyList me, prop
-- erzeugt eine Liste der Eigenschaften von prop
-- zur Darstellung im Output-Fenster
ListOfProperties = []
case prop.ilk of
#list:
-- lineare Liste
repeat with i = 1 to prop.count()
ListOfProperties.add(prop[i])
end repeat
#propList, #instance, #script:
-- Eigenschaftliste, Objektinstanz oder Skript-Objekt
repeat with i = 1 to prop.count()
ListOfProperties.add(prop.getPropAt(i))
end repeat
otherwise
-- oberste Ebene
repeat with i = 1 to (the globals).count()
ListOfProperties.add(string((the globals).getPropAt(i)))
end repeat
-- der actorlist hinzufügen
ListOfProperties.add("the actorlist")
end case
return ListOfProperties
end generatePropertyList

on typeText me, var
-- gibt den Typ der Variablen var als Zeichenkette zurück
return string(var.ilk)
end typeText

Quellcode des Beispiels

4.8 Statische Objekte und Instanzen 4.10 Die Speicherverwaltung in Lingo