Neue Armatur
Wie jede Version von Mac OS X bringt Tiger einige neue Programme mit, die vor allem Endanwender beeindrucken sollen. Die Technik darunter bietet jedoch auch Entwicklern Reizvolles.
- Christian Kirsch
Version 10.4 von Apples Mac OS X (Tiger) enthält ein paar neue Werkzeuge mit griffigen Namen und nützlichen Funktionen. „Dashboard“ (Armaturenbrett) ist eine Sammlung von Widgets, die per Tastendruck oder Mausklick in einer halbtransparenten Ebene auf dem Desktop erscheinen. Im Lieferumfang finden sich unter anderem eine Weltzeituhr, Wettervorhersage, Einheitenumrechner und Schnellsuche im Adressbuch. In den ersten vier Wochen nach Tiger kamen alleine bei Apple über 200 weitere Dashboard-Widgets anderer Entwickler hinzu.
Wer ein solches Widget benutzen möchte, lädt es von der Website herunter und schiebt die Datei in ~/Library/Widgets. „Datei“ ist in diesem Fall jedoch nur eine „Façon de parler“ - wie bei großen Applikationen für Mac OS X sind Widgets in Wirklichkeit Verzeichnisse mit der Namenserweiterung wdgt. Sie enthalten lediglich HTML-, CSS- und Javascript- sowie zwei Bilddateien. Mit ein bisschen Lektüre sowohl der Dokumentation als auch der Quellen anderer Widgets lässt sich eine eigene kleine Anwendung schnell zusammenstellen.
Als Beispiel für die Dashboard-Technik dient ein Widget, das den Füllgrad der verfügbaren Festplatten zeigt (siehe Abbildung). Es bedient sich dazu des Unix-Kommandos df, dessen Mac-Variante man im Parameter -T eine Liste der interessierenden Dateisysteme mitgeben kann. df -Thfs liefert folglich Informationen über alle HFS-Dateisysteme, df -Thfs,smb zusätzlich zu allen via SMB eingehängten Verzeichnissen. Das Verzeichnis DiskUsage.wdgt enthält sechs Dateien, zwei davon sind lediglich Bildchen, die für das Folgende keine Rolle spielen.
Eigenschaften regeln Widget-Rechte
Info.plist enthält Informationen über das Widget im XML-Format. Die zugrunde liegende DTD ist jedoch leider recht spärlich, denn sie definiert neben dem Element key lediglich einige Datentypen wie string oder integer. Zwingend erforderlich ist der Eintrag MainHTML, der den Namen der HTML-Datei angibt, die Dashboard als erstes laden soll. Außerdem spielt im vorliegenden Fall AllowSystem eine wichtige Rolle. Fehlt dieser Eintrag, ist die Benutzung von Systemaufrufen wie df nicht möglich. Leider beschränkt sich die gesamte Sicherheits„philosophie“ auf Einträge des Widget-Autors in Info.plist.
Ähnlich übersichtlich wie die Property-Datei fällt der HTML-Code aus (Listing 1). Er bindet am Anfang die Javascript-Datei für die Ereignisbehandlung und das Stylesheet ein. Die folgenden HTML-Elemente sind im Wesentlichen div-Container für Vorder- und Rückseite des Widgets sowie die „Info“-Box in der rechten unteren Ecke („flip“ und „fliprollie“). Diese erscheint als pulsierendes „i“ in einem Kreis, sobald der Anwender die Maus über das Widget bewegt. HTML, CSS und Javascript für diesen Teil stammen direkt aus der Apple-Dokumentation.
Listing 1: df.html
Der HTML-Code des Widgets besteht fast nur aus der Definition von div-Elementen und ihrer Event-Handler.
<html>
<head>
<style type="text/css">
@import "df.css";
</style>
<script type="text/javascript" src="df.js"></script>
<title>Disk usage</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body id="body" onload='setup();'>
<div id="front" onmousemove="mouseMove(event);"
onmouseout="mouseExit(event);">
<div id="main">Loading<</div>
<div class="flip" id="flip" onclick="showBack(event);"
onmouseover="enterFlip(event);" onmouseout="exitFlip(event);">
</div>
<div class="flip" id="fliprollie"></div>
</div>
<div id="back">
Filesystems
<div id="back_prefs">
HFS <input id="hfs" name="hfs" type="checkbox" value=1>
UFS <input id="ufs" name="ufs" type="checkbox" value=1>
CD/DVD <input id="cd" name="cd" type="checkbox" value=1>
SMB <input id="smb" name="smb" type="checkbox" value=1>
<div id="done">
<input id="done_button" name="done_button" type="button" value="Done"
onclick="setPrefs();hideBack();getData();">
</div>
</div>
</div>
</body>
</html>
Ein Klick auf das „i“ im Kreis sorgt dafür, dass statt der Vorder- die Rückseite des Widgets erscheint:
<div class="flip" id="flip" onclick="showBack(event);" ...
Diese enthält Konfigurationsoptionen, hier Checkboxen für die interessierenden Dateisysteme HFS, UFS, CD-ROM und SMB. Der HTML-Code dafür steckt im Container back. Die Platzierung der Boxen durch feste Leerzeichen hält das Listing kurz, in einer echten Anwendung würde man hier ebenfalls geeignete Style-Vorgaben benutzen. Die als onclick-Handler registrierte Javascript-Funktion showBack() ändert die display-Eigenschaften von Vorder- und Rückseite von block zu none und umgekehrt. Das Klammern dieser Änderung durch die Dashboard-Funktionen prepareForTransition() und performTransition() stellt sicher, dass der Übergang wie das Drehen eines Blatt Papiers erscheint.
Javascript als Schwerarbeiter
Alle Arbeiten am Widget erledigen die Javascript-Funktionen in df.js (Listing 2). Wiedergegeben sind hier nur die für die Anzeige der Plattendaten, alle Listings sind über den iX-Listingservice erhältlich. Die Funktion setup() übernimmt das Anzeigen der Daten. Da sie als Onload-Handler für das body-Element der HTML-Datei definiert ist, führt Dashboard sie beim ersten Laden des Widgets aus. Vor allem während der Entwicklung hilft die Prüfung if (window.widget). Das widget-Objekt ist nämlich Teil der Dashboard-Laufzeitumgebung und während der Ausführung im Browser nicht vorhanden.
Listing 2: df.js
Die Anzeige der Daten und das Speichern der Präferenzen erledigen Javascript-Funktionen.
var df_zeilen=new Array();
var df_werte=new Array();
var filesystems="hfs";
function setup() {
if (window.widget)
filesystems=widget.preferenceForKey("filesystems");
getData();
document.getElementById("hfs").checked=filesystems.match(/hfs/);
...
}
function getData() {
// var o='<table><tr><td></td><td id="table_head">50%</td></tr>'; // Output
var o='<div>';
if (window.widget) {
var df_out = widget.system("/bin/df -T"+filesystems, null).outputString;
} else {
var df_out=
"Filesystem 512-blocks Used Avail Capacity Mounted on\n" +
"/dev/disk0s10 116936960 73593120 42831840 63% /\n"+
"/dev/disk2 61440 47440 14000 77% /Volumes/GraphicConverter X";
}
df_zeilen = df_out.split("\n");
for (i=1; i <df_zeilen.length; i++) {
df_werte = df_zeilen[i].split(/\s+/);
var percent = parseInt(df_werte[4]);
var mountpt = cutoff(df_werte.slice(5).join(" "));
if (percent && mountpt) {
var color;
if (percent <50) {
color="green";
} else if (percent <80) {
color ="yellow";
} else {
color="red";
}
o += "<div class='line'><div class='left'>"+
mountpt+
"</div><div class='right' style='width:"+percent+
"px;background-color:"+color+";'>"+percent+"%</div></div>";
}
}
o += "</div>";
document.getElementById("main").innerHTML=o;
window.resizeTo(250,15*(Math.max(5,df_zeilen.length)));
}
function setPrefs(e) {
var f = new Array;
if (document.getElementById("hfs").checked)
f.push("hfs");
if (document.getElementById("ufs").checked)
f.push("ufs");
filesystems=f.join(",");
if (window.widget)
widget.setPreferenceForKey(filesystems,"filesystems");
}
function cutoff(s) {
if (!s) return null;
var l=s.length;
if (l <22) {
return s;
} else {
return "... "+s.substr(-18,18);
}
}
...
Ein Browser eignet sich jedoch - gegebenenfalls mit passenden Erweiterungen - besser zum Entwickeln und Testen von Widgets als Dashboard selbst. Vor allem Javascript-Fehler sind in Dashboard lästig zu lokalisieren, während Firefox/Mozilla mit ihrer Javascript-Konsole und dem DOM-Inspektor geignete Hilfsmittel bereitstellen. Das Kapseln des nur im Dashboard zulässigen Codes mit if (window.widget) verhindert, dass er im Browser Fehler auslöst. Weiter unten dient das Konstrukt zum Beispiel dafür, statische Testdaten statt der dynamischen Ausgabe von df bereitzustellen.
Das Ermitteln und Anzeigen der Daten übernimmt die Funktion getData(). Sie zerlegt die Ausgabe von df in Zeilen. Anschließend extrahiert eine Schleife aus jeder Zeile nach der Überschrift (for i = 1; ...) den Füllungsgrad und den Namen des Dateisystems. Da Mac OS X hinsichtlich deren Länge nicht kleinlich ist, andererseits das Widget nicht übermäßig breit werden soll, schneidet cutoff() gegebenenfalls von vorne ein paar Zeichen ab und ersetzt sie durch „...“. Zum Anzeigen der Daten verwendet getData() leere div-Elemente, als deren Hintergrundfarbe es je nach Füllungsgrad des Dateisystems grün, gelb oder rot benutzt. Ein abschließender Aufruf von resizeTo() sorgt dafür, dass das Widget allen Balken genügend Platz bietet.
Dashboard bietet Funktionen zum Setzen und Lesen von Präferenzen. Diese kommen hier in setPrefs() und setup() zum Einsatz. Zum Speichern der Einstellungen dient ein simpler String, der die interessierenden Dateisystemtypen durch Kommata getrennt enthält. Ein komplettes Widget sollte neben den hier besprochenen Handler für die Ereignisse onshow und onhide implementieren, die Dashboard beim Darstellen beziehungsweise Verstecken des Widgets aufruft. Außerdem benötigen die sprachabhängigen Strings Übersetzungen. Informationen zu diesen und allen anderen Dashboard-Themen gibt es bei developer.apple.com/documentation/AppleApplications/Dashboard-date.html. (ck)