Stand der Technik oder bleeding edge?

An der Fakultät für Informatik des KIT ist ein Vortrag zum Thema “How Do Professional Developers Comprehend Software?” angekündigt. Insgesamt möchte ich das gar nicht beurteilen, aber über einen Satz bin ich gestolpert:

Overall, our results show a gap between program comprehension research and practice as we did not observe any use of state of the art comprehension tools and developers seem to be unaware of them.

Das offenbart ein sehr merkwürdiges Verständnis des Begriffes “Stand der Technik”. Wenn niemand das einsetzt und niemand das kennt, ist es offenbar nicht Stand der Technik. Über die Gründe kann man spekulieren. Vermutlich ist das Zeugs, an dem der Vortragende vermutlich forscht und das er nun zum Stand der Technik erklären möchte, dafür einfach noch nicht ausgereift genug, und wird es möglicherweise auch nie sein. Im besten Falle könnte man es dann als bleeding edge bezeichnen, im schlechtesten Fall geht es, wie so oft bei akademischen Lösungen, an den eigentlichen Problemen in der Praxis meilenweit vorbei.

Internal Server Error beim MDN

Ich hatte mich über Interpunktionsfehler im Mozilla-Entwicklerhandbuch des Mozilla Developer Networks (MDN) geärgert. Allerdings nur so geringfügig. Aus gutem Grund.

Als ich das kurz in einem IRC-Channel von Mozilla anmerkte, kam die Frage, ob ich schon einen Bug angelegt hätte. Nein, denn: Es hat mich nur geringfügig geändert, und sowas unterbricht immer den Arbeitsfluss. Aber wo schonmal die Frage kam, bin ich es halt doch angegangen.

MDN ist ein Wiki. Ich war mir ziemlich sicher, mich dort schonmal angemeldet zu haben. Ein Passwort war im Firefox aber nicht (mehr) gespeichert. Egal, also anmelden. MDN benutzt inzwischen “Persona”. Das hieß früher mal “BrowserID”, ist aber nicht ansatzweise browserbasiert. Es ist irgendwie eine Alternative zu OpenID und SAML SSO, ohne das einem erklärt wird, warum man sich für Single Sign On nochmal wieder ein Konto anlegen soll. Dort hatte ich mich eigentlich auch schonmal angemeldet. Aber vermutlich war das, als es noch nicht Persona hieß. Also hab ich dort eine E-Mail-Adresse registriert. Mit der bin ich dann zurück zu MDN. Dort sollte ich einen Benutzernamen angeben. “tessarakt” gibt es schon. Aha, habe ich also doch schon ein Konto. Weiter unten konnte man sich dann zuschicken lassen, welche E-Mail-Adresse mit dem Konto verbunden ist. Also angeklickt. Ergebnis:

Persona hilft natürlich auch nicht beim tatsächlichen Verwalten mehrere Identitäten. Es ist nur die zigste Neuimplementierung von etwas, was es schon längst gibt, ohne über die eigentlichen Probleme auch nur nachzudenken.

Jedenfalls war die zuerst angegebene Adresse offenbar nicht die, die zu dem MDN-Konto gehörte. Als ich die zweite bei Persona angab, behauptete die/der/das:

Wir haben gerade eine E-Mail an diese Adresse verschickt! Wenn Sie wirklich noch eine senden möchten, warten Sie ein oder zwei Minuten und versuchen Sie es dann erneut.

Nein, habt ihr nicht. Und einige Minuten später ging es immer noch nicht.

Fazit: Durch diverse Umstellungen lief das ganze natürlich nicht reibungslos “mal eben schnell”. Und bei Mozilla ist man offenbar damit beschäftigt, “lustige” Bildchen zu produzieren, um 500er-Fehler zu illustrieren, als dafür zu sorgen, dass die gar nicht erst auftreten. Mit sowas vertreibt man leicht die Leute, die man eigentlich bräuchte …

AeroFS

Neulich hatte ich überlegt, wie ich meine computerübergreifende Dokumentenablage am besten organisieren könnte. Eine Idee dazu war dann ein verteiltes Peer-to-Peer-Dateisystem. In den Kommentaren hatte ich dazu schon einiges gefunden.

Und eine Lösung kommt dem, was ich will, ziemlich nahe: AeroFS, entwickelt von einem Start-Up in Kalifornien. Dafür, dass das noch eine early beta ist, funktioniert es erstaunlich gut. Ich habe es jetzt auf drei Computern im Einsatz, und der Sync funktioniert letztendlich auch, wenn immer nur zwei davon gleichzeitig im Einsatz sind.

Meine Hauptkritikpunkte:

  • Es kann keine Symlinks, auch nicht innerhalb einer Bibliothek. Wie man das unter Windows umsetzt, ist mir letztlich egal, aber es sollte zumindest zwischen Linux-Systemen funktionieren.
  • Im Statusfenster sind ständig irgendwelche Metadatentransfers zu sehen, auch wenn sich nichts ändert. Das ist etwas verwirrend.
  • Man bekommt keine sinnvolle Rückmeldung über den Status der automatischen Synchronisierung. Das macht die Entscheidung, ob man den mobilen Computer jetzt abschalten und mitnehmen kann, etwas schwierig.
  • Synchronisierungsprobleme (beispielsweise Konflikte oder auf einer Plattform nicht zulässige Dateinamen) werden zwar angezeigt, diese Meldungen sind aber sehr versteckt.

Insgesamt erscheint mir der Ansatz aber vielversprechend. Hoffen wir, dass die weiterhin handwerklich saubere Arbeit abliefern, dann wird das schon.

Dokumentenablage

Ich denke gerade darüber nach, wie ich meine Dokumentenablage vernünftig organisiere. Zuerst einmal betrifft das die Ablage beispielsweise von Schreiben. Bisher habe ich dafür einen Ordner “korrespondenz”, aber nur auf meinem PC zu Hause. Nun hätte ich sowas aber auch gerne synchronisiert. Ich könnte es mit rsync, unison oder Ähnlichem probieren. Aber dafür muss man dann ja die zu synchronisierenden Rechner manuell abgleichen. Oder gibt es da auch etwas, was das automatisch anstößt, sobald die sich sehen können? Und klappt das auch mit mehr als zwei Rechnern zuverlässig? Das nächste, woran ich dann gedacht habe, ist ein Versionsverwaltungssystem. Allerdings frage ich mich zum Einen, ob das nicht Overkill ist. Und ich müsste dann mal vom altmodischen SVN weg und am Besten Git ausprobieren. Benutzt das jemand für solche Zwecke? Und eine History ist ja praktisch – allerdings könnte sie bei großen Binärdateien eher hinderlich sein. In der Uni ist mir sowas relativ egal, aber wenn ich den Speicherplatz selbst bereitstellen und bezahlen muss … Benutzt jemand SVN oder Git für so einen Zweck? Irgendwelche Idee, was man mit Binärdateien oder generierten Dateien am besten macht? Jedes Mal ein neues PDF einchecken, wenn man ein Komma ergänzt hat, ist ja auch nicht das Wahre … Ebenso bei Formaten wie ODT oder ODS …

Oder wären vielleicht verteilte Dateisysteme eine Alternative? Gibt es da mittlerweile etwas Ordentliches? Als ich vor Jahren mal geschaut hatte, sah das alles ziemlich kompliziert zu konfigurieren aus … Ideal wäre da eine Lösung, die automatisch ein Overlaynetzwerk aufbaut und darüber verschlüsselt kommuniziert.

Und die Sache ist halt, dass für manche Dateien Versionierung nützlich wäre, für andere aber Overkill. Das zu kombinieren, stelle ich mir irgendwie schwierig vor …

Und was damit noch gar nicht gelöst ist, ist die Metadatenverwaltung (Beispiel: Wann wurde ein Brief abgeschickt, worauf ist er eine Antwort, etc.) und Verwaltungsfunktionen wie Wiedervorlage etc.

Ich freue mich über sämtliche Ideen zu diesem Thema …

Kalender synchronisieren mit freier Software

Nachdem ich mir jetzt einen virtuellen Server gemietet habe, dachte ich, es wäre doch eine gute Idee, der Abhängigkeit von Google zu entfliehen und meinen Kalender selbst zu hosten.

DAViCal läuft inzwischen auch (mit SSL und CACert-Zertifikat, auf einem name-based virtual host, was dank client name indication auch mit SSL geht, und vom DNS abgesehen auch unabhängig von diesem seltsamen Plesk Power Panel). Auch einen iCalendar-Export meines Google-Kalenders habe ich schon erfolgreich importieren können.

Vor dem Umstieg frage ich mich jetzt allerdings noch, wie da die Synchronisation mit verschiedenen Endgeräten aussieht. Ein paar Erwägungen:

  • Meinen privaten Kalender selbst zwischen Lightning-Instanzen zu synchronisieren, ist wohl problemlos.
  • Im Gegensatz zum Google-Kalender dürfte jetzt auch erstmals die Synchronisation von Tasks funktionieren.
  • Aber wie sieht es mit der Android-Synchronisation aus? Muss man dafür tatsächlich irgendwelche Fremdapps installieren, die dann vermutlich weniger reibungslos funktionieren als die eingebaute Synchronisation mit Google?
  • Bisher konnte ich ICS-Feeds im Google-Kalender (im Web) hinzufügen. In Lightning sind die dann leider nicht automatisch aufgetaucht, aber auf dem Android-Smartphone. Gibt es eine Möglichkeit, solche “Kalender-Abos” interoperabel und standardbasiert zu synchronisieren?

Scheduling wäre ein weiterer Punkt, den ich mal ausprobieren müsste …

Patch-Queue – wie organisieren?

Ich habe in letzter Zeit angefangen, Patches für ein Open-Source-Projekt (Boost, konkret die Graph Library) zu erstellen. Da ich selbst dort keine Commit-Rechte habe, bin ich darauf angewiesen, dass Entwickler mit solchen Rechten meine Änderungen für mich einchecken. Und da die auch nicht 24h verfügbar sind, dauert es eine Weile, bis die Sachen eingecheckt sind. Ich will aber ggf. mit Folgeänderungen weitermachen, die die vom ersten Patch geänderten Stellen nochmals ändern. Und da geht dann schnell das Chaos los :-(

Bevor jetzt gleich tolle Tipps kommen, dass das mit diesem oder jenem Versionsverwaltungssystem ja alles ganz einfach geht: Das Chaos dürfte zuallererstmal dadurch verursacht sein, dass ich gar nicht so recht weiß, was ich von einer Lösung erwarte. Der erste Schritt ist also eine saubere Anforderungsanalyse:

  • Diff gegen noch nicht eingecheckte Patches: Ich möchte sehen, welche Änderungen sich in meiner working copy befinden, die über die bereits erzeugten (aber noch nicht eingecheckten) Patches hinausgehen. Die Differenz zum Trunk reicht also nicht.
  • Status von Patches: Ich muss Patches als erledigt markieren können, wenn sie eingecheckt sind.
  • Zusammenfassen von Patches: Wenn ich Patches noch nicht eingesendet habe (oder ggf. auch dann noch), will ich mehrere Patches zu einem großen zusammenfassen können.
  • Selektives Anwenden von Patches: Ich will leicht meine/eine Working Copy auf einen Stand bringen können, der den Trunk (oder eine bestimmte Revision) und nur bestimmte Patches enthält. Das ist beispielsweise nötig, um zu prüfen, dass die voneinander unabhängig sind.
  • Unterstützung bei der Erstellung von Patches: Optimal (aber auch optional und eher nice-to-have) wäre, aus den lokalen Änderungen in der lokalen working copy gegenüber dem Trunk (ggf. zuzüglich einer Auswahl von Patches) einen Patch erstellen zu können – aber nicht mit sämtlichen Änderungen, sondern Auswahlmöglichkeiten bezüglich der Dateien und der einzelnen Änderungen darin. Das geht aber zur Not auch manuell, ggf. mit Nachbearbeiten.

Hat jemand eine Idee, wie man diese Funktionalität am besten umsetzt? Gibt es für so etwas fertige Tools? Ist es vielleicht mit bestimmten Versionsverwaltungssystemen besonders einfach? Git wird ja ständig für alles Mögliche in höchsten Tönen gelobt. Nutzt es vielleicht auch hier? Wenn ja, wie lassen sich obige Anforderungen auf die Begrifflichkeiten von Git (oder einem anderen System) abbilden?

Boost, LEDA und meine wiederhergestellte Ehre

Neulich, als ich gerade Urlaub hatte, standen bei mir im Büro zwei Studenten, die einen Fehler in meiner Boost-Implementierung entdeckt haben wollten (wie mir danach mein Büromitbewohner berichtete). Zumindest habe ich mich gefreut, dass den Code jemand benutzen wollte. Allerdings haben die beiden sich dann nicht mehr gemeldet. Über Umwege erfuhr ich dann am Wochenende, was der Fehler ist: Es fehlt in leda_graph.hpp eine spitze Klammer:

[sourcecode language="cpp"] template <class vtype, class etype, class PropertyTag, class Key>
inline
typename boost::property_traits<
typename boost::property_map<leda::GRAPH<vtype, etype>,PropertyTag>::const_type
::value_type
get(PropertyTag p, const leda::GRAPH<vtype, etype>& g, const Key& key) {
return get(get(p, g), key);
}[/sourcecode]

Es hat also nicht nur jemand diese Templates auf bisher ungetestete Art und Weise verwendet – das hier knallt schon, wenn man leda_graph.hpp nur einbindet … Dass ich diesen Fehler verursacht haben sollte, erschien mir etwas unplausibel – denn ich habe den Header ja damals benutzt, und mein Programm kompilierte. Also habe ich mir die SVN-Historie angeschaut.

[sourcecode]————————————————————————
r36836 | dgregor | 2007-01-29 20:29:06 +0100 (Mo, 29. Jan 2007) | 2 Zeilen

Improvements to LEDA adaptors, from Jens Mueller[/sourcecode]

[sourcecode]
@@ -528,11 +884,11 @@
inline
typename boost::property_traits<
typename boost::property_map<leda::GRAPH<vtype, etype>,PropertyTag>::const_type
- >::value_type
+::value_type
get(PropertyTag p, const leda::GRAPH<vtype, etype>& g, const Key& key) {
return get(get(p, g), key);
}
[/sourcecode]

Die ersten Indizien sprachen aber gegen mich: In dem Checkin meines Patches von Douglas Gregor wurde auch das > entfernt. Trotzdem: Irgendwie konnte das doch nicht sein. Also habe ich die Mail herausgesucht, mit der ich den Patch eingereicht habe. Anscheinend habe ich damals den Programmcode im Body der Mail verschickt, statt als Anhang. Klar, mit der Policy von Boost, die Zeilen auf lesbare Länge zu begrenzen, hat das ja auch wunderbar funktioniert. Allerdings wurde dabei offenbar ein > als gequoteter Text interpretiert. Douglas hat dann einfach den Text in eine Datei kopiert und das fehlende > nicht bemerkt. Jens nix schuld, meine Ehre ist wiederhergestellt. Und da es keine automatisierten Tests für die LEDA-Bindings gibt (dafür bräuchte man ja eine LEDA-Installation, und die gab es damals nur gegen Geld), ist das niemandem aufgefallen. Viel irritierender finde ich aber, dass den Fehler vier Jahre lang niemand bemerkt hat :-(

Naja, jedenfalls wurde mir damals angeboten, Maintainer der LEDA-Bindings zu werden. Inzwischen gibt es eine kostenlose Version der LEDA-Bibliothek. Insofern eine gute Gelegenheit, auf das Angebot zurückzukommen. Anscheinend hat sich auch niemand um die Datei gekümmert, und sogar Verbesserungsvorschläge mit fertigen Patches wurden nicht bearbeitet. Vielleicht würde es sich ja lohnen, die wieder hervorzukramen …

Thunderbird-Addons: Optionen

Heute schauen wir uns, wieder am Beispiel von Quicker Filer 0.5.1, an, wie man in einem Thunderbird-Addon Einstellungen benutzt, die als Preferences persistiert werden.

In der install.rdf heißt es:

[sourcecode language="xml"]
<em:optionsURL>chrome://quickerfiler/content/options.xul</em:optionsURL>
[/sourcecode]

Damit wird ein Einstellungsdialog definiert. Standardmäßig wird dieser als Dialogfenster geöffnet. Über die Definition des Content Packages in der chrome.manifest (vgl. dieses Posting) ergibt sich also, dass der Dialog in content/options.xul definiert ist.

Gehen wir das also der Reihe nach durch.

[sourcecode language="xml"]
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
[/sourcecode]

Hier wird ein Stylesheet eingebunden, und zwar ein recht generisches.

[sourcecode language="xml"]
<prefwindow id="quickerfilerPrefWindow" title="Quicker Filer – Options" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
buttons="accept,cancel" height="550">
[/sourcecode]

Das Fenster ist ein prefwindow, also speziell für Einstellungsdialoge vorgesehen. Es gibt weiterführende Dokumentation, die aber bis auf ein Beispiel und einen Hinweis, wie man so ein Fenster aufruft, keine wesentlichen Infos enthält. Also gehen wir weiter unsere Datei im Detail durch.

Zwei der Attribute sind selbsterklärend: title definiert den Fenstertitel und height setzt die bevorzugte Höhe des Fensters in Pixeln. buttons definiert, welche Schaltflächen angezeigt werden, hier also OK und Abbrechen. Das dürfte üblicherweise eine gute Wahl sein.

[sourcecode language="xml"]
<script type="application/x-javascript" src="chrome://quickerfiler/content/options.js"/>
[/sourcecode]

Hier wird ein Script eingebunden. Es definiert einigige Funktionen, die dann in Event-Handlern aufgerufen werden – dazu also unten mehr.

[sourcecode language="xml"]
<prefpane id="quickerfilerOptions.general.prefpane" label="General" selected="true">
[/sourcecode]

Eine prefpane ist ein Panel, also wohl eine Seite, mit Einstellungen. Der Inhalt kann entweder aus einer gesonderten .xul-Datei geladen oder – wie hier – inline definiert werden.

label definiert eine Beschriftung. Ob es tatsächlich vorgesehen ist, selected direkt zu setzen, bezweifle ich gerade ein wenig …

[sourcecode language="xml"]
<preferences>
<preference id="extensions.quickerfiler.defaultfolder.text" name="extensions.quickerfiler.defaultfolder.text" type="string"/>
<preference id="extensions.quickerfiler.search.root" name="extensions.quickerfiler.search.root" type="string"/>
<preference id="extensions.quickerfiler.search.root.account" name="extensions.quickerfiler.search.root.account" type="string"/>
<preference id="extensions.quickerfiler.search.root.folder.text" name="extensions.quickerfiler.search.root.folder.text" type="string"/>
<preference id="extensions.quickerfiler.mkdir.enable" name="extensions.quickerfiler.mkdir.enable" type="bool"/>
<preference id="extensions.quickerfiler.debug.enable" name="extensions.quickerfiler.debug.enable" type="bool"/>
<preference id="extensions.quickerfiler.suggestlastfolder.enable" name="extensions.quickerfiler.suggestlastfolder.enable" type="bool"/>
<preference id="extensions.quickerfiler.copysentmessage.enable" name="extensions.quickerfiler.copysentmessage.enable" type="bool"/>
</preferences>
[/sourcecode]

Dieser Block beschreibt die Einstellungen, die in der prefpane geändert werden. Er besteht aus einzelnen preference-Elementen.

Die Attribute sind wieder recht selbsterklärend. name setzt den Namen der zu ändernden Einstellung (hier zweckmäßigerweise identisch zur ID des jeweiligen Elements), type legt fest, von welchem Typ der Wert ist.

[sourcecode language="xml"]
<vbox>
[/sourcecode]

Die vbox dient der Gliederung bzw. Layout-Zwecken.

[sourcecode language="xml"]
<groupbox>
<caption label="Default folder"/>
<description>Default folder for instant copy/move</description>
<textbox id="quickerfiler.defaultfolder.textbox"
preference="extensions.quickerfiler.defaultfolder.text"
type="autocomplete"
autocompletesearch="quickerfiler-autocomplete"
tabScrolling="true"
autoFill="true"
forceComplete="true"
showcommentcolumn="false" />
</groupbox>
[/sourcecode]

Die groupbox dient der Gruppierung von Elementen und wird in der Regel mit Umriss gezeichnet. Die caption wird in diesem Umnriss gezeichnet. Die description ist ein Textblock, der einfach angezeigt wird.

Die textbox kann Auto-Vervollständigung und entspricht der im letzten Artikel schon genauer erklärten. Genaueres also dort, und die Erläuterung, wie Auto-Vervollständigung im Hintergrund funktioniert, schieben wir immer noch nach hinten.

[sourcecode language="xml"]
<groupbox>
<caption label="Search Root"/>
<description>Determines the scope of the folder list</description>
<radiogroup id="quickerfiler.search.root.radiogroup"
preference="extensions.quickerfiler.search.root">
[/sourcecode]

Es folgt eine weitere groupbox mit caption und description. radiogroup definiert eine Gruppe von Radio-Buttons. Es ist die zugehörige preference angegeben. Wohlgemerkt verweist der Wert auf die ID des entsprechenden preference-Elements!

[sourcecode language="xml"]
<radio id="quickerfiler.search.root.allfolders.radio"
label="All folders"
value="msgaccounts:/"/>
[/sourcecode]

Hier wird ein einzelner Radio-Button definiert. Die Dokumentation weiß anscheinend nicht so recht, dass das value-Attribut im Zusammenhang mit Preferences eine feste Bedeutung hat. Vermutlich sollte man das mal explizit dokumentieren. label definiert die Beschriftung des Radio-Buttons.

[sourcecode language="xml"]
<hbox>
<radio id="quickerfiler.search.root.account.radio"
label="This account"
value="– account –"/>

<hbox flex="1" pack="end">
[/sourcecode]

Hier wird es layouttechnisch interessant. Der Radio-Button wird zusammen mit der menulist in eine hbox gesteckt. Die menulist wird nochmal in eine eigene hbox gesteckt, bei der zwei Attribute gesetzt sind. flex="1" sorgt dafür, dass sie sämtlichen freien Platz verbraucht. pack="end" lässt die menulist nach rechts rutschen. Was mir hier nicht klar ist:

  • Warum ist pack="end" noch nötig, wenn flex="1" angegeben ist? Wie kann in dem Fall noch Platz übrig sein?
  • Warum ist die menulist nochmal in eine hbox verpackt? Man könnte diese Attribute auch auf der menulist spezifizieren. Erzielt das ggf. auch den gewünscht Effekt? Wenn nein, warum nicht? Wenn ja, warum sollte man es so wie hier machen?

[sourcecode language="xml"]
<menulist id="quickerfiler.search.root.account.menulist"
preference="extensions.quickerfiler.search.root.account"
sortResource="http://home.netscape.com/NC-rdf#Name"
sortDirection="ascending"
datasources="rdf:msgaccountmanager rdf:mailnewsfolders"
containment="http://home.netscape.com/NC-rdf#child"
ref="msgaccounts:/" flex="1">
[/sourcecode]

menulist definiert eine Dropdown-Auswahlliste. preference legt wieder fest, welche Einstellung gesetzt wird. flex="1" (nochmal!) sorgt dafür, dass die menulist die komplette Breite ausfüllt.

Die restlichen Attribute (sortResource, sortDirection, datasources, containment, ref) beziehen sich darauf, wie man den Inhalt der Liste dynamisch aus einer RDF-Datenquelle zusammenstellen lässt.

Dazu dient auch das darauffolgende template:

[sourcecode language="xml"]
<template xmlns:nc="http://home.netscape.com/NC-rdf#">
<rule nc:ServerType="nntp"/>
<rule nc:IsDeferred="true"/>
<rule nc:IsServer="true">
<menupopup>
<menuitem uri="…"
value="…"
label="rdf:http://home.netscape.com/NC-rdf#Name"/>
</menupopup>
</rule>
</template>
[/sourcecode]

Dafür gibt es eine eigene Anleitung, und jemanden, der das alles für großen Schrott hält.

Insofern verschieben wir das lieber auf später – bis ich rausgefunden habe, ob das überhaupt etwas taugt. Jedenfalls erstellt das Template wohl ein menupopup mit vielen menuitems – aber ich verstehe schon nicht, warum/ob da nur ein menupopup, aber mehrere menuitems rauskommen …

Danach wird es zum Glück erstmal unspannend – daran merkt man, dass etwas hängen bleibt … Es werden einige noch offene Elemente geschlossen, und dann kommt eine weitere Radiobox, verbunden mit der Möglichkeit, einen Ordner auszuwählen . Hier kommt wieder die altbekannte Textbox mit Autovervollständigen zum Einsatz. So weit unproblematisch, aber da ist eine Sache, der ich noch auf den Grund gehen muss: In den Einstellungen wird definiert, was die Wurzel für die Suche nach Ordnern ist – es wäre aber unschön, wenn diese Einstellung auch bei der Festlegung der Wurzel zum Tragen kommt. Ich muss dementsprechend mal ergründen, warum sie es nicht tut – bzw., ob sie es vielleicht doch tut – das wäre dann wohl ein Bug.

Danach geht es auch relativ unspannend weiter:

[sourcecode language="xml"]
<groupbox>
<caption label="Suggest last folder"/>
<description>Search box fill be filled with the last transfer folder used</description>
<checkbox label="Suggest last folder" preference="extensions.quickerfiler.suggestlastfolder.enable"/>
</groupbox>
[/sourcecode]

Hier kommt zum ersten Mal eine checkbox vor. Die hat ein label und ist an eine bestimmte preference gebunden – alles easy. Danach kommen noch weitere checkboxes, die ich wiederum überspringe. Interessant ist in dem Zusammenhang nur die Entdeckung der Einstellung quickerfiler.debug.enable. Unter Linux habe ich nämlich Probleme – vielleicht hilft das ja beim Debuggen.

Jedenfalls ist damit:
[sourcecode language="xml"]
</prefpane>
[/sourcecode]

die erste Seite der Einstellungen fertig. Es folgt die zur Festlegung der Tastenkombinationen.

[sourcecode language="xml"]
<prefpane id="quickerfilerOptions.shortcuts.prefpane"
label="Keyboard Shortcut"
onpaneload="sQuickerFilerOptions.onPaneLoad();">
<preferences>
<preference id="extensions.quickerfiler.shortcuts.copy.key"
name="extensions.quickerfiler.shortcuts.copy.key"
type="string"/>

<!– … –>

<preference id="extensions.quickerfiler.shortcuts.selfolder.modifiers"
name="extensions.quickerfiler.shortcuts.selfolder.modifiers"
type="string"/>
</preferences>
[/sourcecode]

Zu Beginn wird wieder definiert, welche Einstellungen auf dieser Seite geändert werden können. Für diverse Aktionen (copy, move, inscopy, insmove, selfolder) wird jeweils eine Einstellung key und eine Einstellung modifiers. Aus Gründen ist das etwas gekürzt.

onpaneload definiert, dass beim Laden der Seite folgende Funktion ausgeführt wird:

[sourcecode language="javascript"]
onPaneLoad: function onPaneLoad()
{
this.updateTreeView();
},
[/sourcecode]

Die Funktion updateTreeView() füllt anscheinend einen Treeview anhand der Einstellungen mit den bis jetzt definierten Tastenkürzeln. Die genaue Funktionsweise schaue ich mir an, sobald ich die UI-Elemente selbst angeschaut habe.

In einer vbox folgt dann folgende groupbox:

[sourcecode language="xml"]
<groupbox>
<caption label="Available shortcuts"/>

<tree id="quickerfilerOptions.shortcuts.availableShortcutsTree"
onselect="sQuickerFilerOptions.onAvailableShortcutsTreeSelect();"
rows="5" hidecolumnpicker="true" seltype="single">

<treecols>
<treecol id="nameColumn" label="Name" flex="1"/>
<treecol id="shortcutColumn" label="Shortcut" flex="2"/>
</treecols>
[/sourcecode]

Bis hierhin wurde ersteinmal die Struktur des Baums definiert. Das Hauptelement ist ein tree. onselect wird ausgeführt, wenn eine Zeile im Baum ausgewählt wird. Hier werden wohl die Controls für die Auswahl der Tastenkombination für die entsprechende Aktion gesetzt. Genaueres dann unten, wenn wir uns anschauen, wie der Inhalt aufgebaut ist und initialisiert wird. rows gibt an, wieviele Zeilen gleichzeitig angezeigt werden. Das ist hier gerade die Anzahl der insgesamt existierenden Zeilen. seltype="single" bedeutet, dass es nicht möglich ist, mehrere Zeilen auf einmal auszuwählen. hidecolumnpicker="true" schließlich blendet das “Menü” zum Anzeigen und Verstecken einzelner Spalten aus. treecols enthält einzelne treecol-Elemente. Deren label-Attribut legt die Spaltenüberschrift fest.

trees sind im übrigen keine triviale Angelegenheit, weshalb es dazu auch ein Tutorial gibt. Im allgemeinen Fall können sie aus beliebigen Datenquellen mit großen Datenmengen dynamisch befüllt werden – beispielsweise die Nachrichten in einer Newsgroup. Diese Datenquellen heißen tree views. Netterweise gibt es auch einen vordefinierten treeview, bei dem die Daten aus XUL-Elementen kommen. Das nennt sich dann content tree. Was man dabei beachten muss: Dadurch, dass die Daten durch einen tree view geschleift werden, ist man nicht sehr flexibel, was man hier angeben kann. Es können wirklich nur für jede Tabellenzelle ein Text und ein Icon angegeben werden. Wie es das Tutorial ausdrückt:

Having said that the data to be displayed in a tree comes from a view and not from XUL tags, there happens to be a built-in tree view which gets its data from XUL tags.

Der Rest ist dann unspektakulär:

[sourcecode language="xml"]
<treechildren>
<treeitem>
<treerow>
<treecell label="Copy" value="copy"/>
<treecell label="disabled"/>
</treerow>
</treeitem>
<!– vier weitere <treeitem>-Elemente für die anderen Aktionen –>
</treechildren>
</tree>
</groupbox>
[/sourcecode]

Das Hauptelement ist treechildren. Dazu sagt die Doku:

This element is the body of the tree. For content trees, the content will be placed inside this element. This element is also used to define container rows in the tree.

Zum Glück haben wir einen content tree. Wie der Inhalt sonst definiert wird, oder was container rows sein sollen, steht da nämlich leider nicht.

Darin sind dann fünf treeitems, die jeweils eine treerow enthalten. Darin wiederum ist dann jeweils eine treecell pro Spalte. Dabei ist jeweils das Attribut label gesetzt, und in der linken Spalte noch value, offenbar zum Wiederfinden aus Scripts.

Und mit den oben noch zurückgestellten Scripts machen wir jetzt auch weiter. Zuerst die Funktion updateTreeView(), die (unter anderem) beim Laden der Pane ausgeführt wird:

[sourcecode language="javascript"]
updateTreeView: function updateTreeView()
{
var tree = document.getElementById(‘quickerfilerOptions.shortcuts.availableShortcutsTree’);
var rowCount = tree.view.rowCount;

for(var i = 0; i<rowCount; i++)
{
var shortcut = tree.view.getCellValue(i, tree.columns[0]);
var key = document.getElementById(‘extensions.quickerfiler.shortcuts.’+shortcut+’.key’).value;
var modifiers = document.getElementById(‘extensions.quickerfiler.shortcuts.’+shortcut+’.modifiers’).value;

if(key != undefined && key != ”)
{
var text = "";
if(modifiers)
text = modifiers.toUpperCase().split(‘ ‘).sort().join(‘ + ‘);
if(text)
text += ‘ + ‘;
text += key.toUpperCase();

tree.view.setCellText(i, tree.columns[1], text);
}
else
{
tree.view.setCellText(i, tree.columns[1], ‘disabled’);
}
}
}
[/sourcecode]

Diese Funktion holt sich zunächst das
-Element und geht alle Tabellenzeilen durch: Die Variable shortcut wird auf den value der Tabellenzelle in der ersten Spalte gesetzt. Dieses Attribut dient also, wie oben vermutet, nur dem “Wiedererkennen”. key und modifiers werden auf den Wert der entsprechenden Preferences gesetzt (ausgelesen aus den jeweiligen preference-Elementen). Falls diese Werte sinnvoll sind, wird daraus ein String zusammengebastelt, der dann in der zweiten Tabellenspalte angezeigt wird.

Interessant dürften noch die aufgerufenen Methoden (und benutzten Eigenschaften) des tree view (tree.view) sein, die im Interface nsITreeView definiert sind:

Ebenfalls bereits bemerkt hatten wir die Funktion onAvailableShortcutsTreeSelect. Die stellen wir auch weiterhin zurück, da sie Elemente bearbeitet, die wir noch nicht angeschaut haben.

In options.xul kommt nun eine weitere groupbox:

[sourcecode language="xml"]
<groupbox id="quickerfilerOptions.shortcuts.settingsGroupbox" hidden="true">
<caption label="Shortcut Settings"/>
<description>These settings apply to the selected shortcut above</description>

<hbox>
<vbox>
<groupbox>
<caption label="Key"/>

<menulist id="quickerfilerOptions.shortcuts.shortcutKey"
oncommand="sQuickerFilerOptions.onShortcutKeyMenulistCommand();">
<menupopup>
<menuitem value="" label="disabled"/>

<menuitem value="A" label="A"/>
<!– … –>
<menuitem value="Z" label="Z"/>

<menuitem value="/" label="/"/>
<!– … –>
<menuitem value="]" label="]"/>
</menupopup>
</menulist>
</groupbox>
</vbox>

<groupbox flex="1">
<caption label="Modifiers"/>

<checkbox id="quickerfilerOptions.shortcuts.shortcutModifier.accel"
label="Accel"
value="accel"
oncommand="sQuickerFilerOptions.onShortcutModifierCommand();"/>
<!– und dasselbe für alt, control, meta und shift –>
</groupbox>
</hbox>
</groupbox>
[/sourcecode]

Zu beachten ist, dass diese groupbox anfangs versteckt ist (hidden="true")! Sichtbar gemacht wird sie per Script (siehe unten), sobald im Treeview ein Befehl ausgewählt wird. Die eigentlichen Controls sind dann eine Dropdown-Liste (menulist), die alle möglichen Tasten enthält, und checkboxes für die verschiedenen Modifier. Das ist jetzt hinreichend langweilig, dass es der geneigte Leser gerne eigenständig in der Dokumentation nachlesen darf. Für Veränderungen sind Event-Listener definiert, auf die ich später noch eingehe. Damit ist die Beschreibung des Fensters dann auch schon zu Ende.

Jetzt also die Funktion, die ausgeführt wird, wenn man ein Kommando auswählt, um dafür ein Tastenkürzel zu vergeben:

[sourcecode language="javascript"]
onAvailableShortcutsTreeSelect: function onAvailableShortcutsTreeSelect()
{
var tree = document.getElementById(‘quickerfilerOptions.shortcuts.availableShortcutsTree’);
var shortcut = tree.view.getCellValue(tree.currentIndex, tree.columns[0]);

var keyEl = document.getElementById(‘extensions.quickerfiler.shortcuts.’+shortcut+’.key’);
var key = "";
var modifiers = "";
if(keyEl!=null)
{
key = keyEl.value;
if(key==null)
key = "";
else
modifiers = document.getElementById(‘extensions.quickerfiler.shortcuts.’+shortcut+’.modifiers’).value;
}

document.getElementById(‘quickerfilerOptions.shortcuts.settingsGroupbox’).hidden = false;
document.getElementById(‘quickerfilerOptions.shortcuts.shortcutKey’).value = key.toUpperCase();

for(var i in this.mModifiers)
{
document.getElementById(‘quickerfilerOptions.shortcuts.shortcutModifier.’+this.mModifiers[i]).checked = (modifiers.indexOf(this.mModifiers[i])>-1)
}

this.updateTreeView();
}
[/sourcecode]

Zunächst wird die aktuell ausgewählte Zeile der treelist bestimmt (tree.currentIndex). Diese Eigenschaft funktioniert nur bei seltype="single", aber das ist hier ja gesetzt. keyEl wird dann auf das preference-Element gesetzt. Ggf. werden aus diesem und dem für die Modifiers die entsprechenden Werte ausgelesen. Dann wird die groupbox angezeigt (hidden wird auf false gesetzt), der passende Eintrag der menulist wird selektiert, und die Chechboxes der entsprechenden Modifiers ggf. angekreuzt.

Bleiben noch die zwei Event-Listener, die auf Veränderungen an diesen Elementen reagieren:

[sourcecode language="javascript"]
onShortcutKeyMenulistCommand: function onShortcutKeyMenulistCommand()
{
// User selected another shortcut key from the pop up menu,
// set this as new shortcut key for the current selected command:
var tree = document.getElementById(‘quickerfilerOptions.shortcuts.availableShortcutsTree’)
var shortcut = tree.view.getCellValue(tree.currentIndex, tree.columns[0]);

document.getElementById(‘extensions.quickerfiler.shortcuts.’ + shortcut + ‘.key’).value =
document.getElementById(‘quickerfilerOptions.shortcuts.shortcutKey’).value;

this.updateTreeView();
}
[/sourcecode]

Eigentlich recht selbsterklärend: Wenn eine andere Taste ausgewählt wurde, wird die entsprechende preference gesetzt. updateTreeView() setzt dann für alle Zeilen des treeview die Werte neu (was im Grunde Overkill ist – aber immerhin bleiben die Zeilen an sich erhalten – sonst ginge bestimmt auch der Fokus verloren).

[sourcecode language="javascript"]
onShortcutModifierCommand: function onShortcutModifierCommand()
{
var tree = document.getElementById(‘quickerfilerOptions.shortcuts.availableShortcutsTree’);
var shortcut = tree.view.getCellValue(tree.currentIndex, tree.columns[0]);

var modifiers = [];
for(var i in this.mModifiers)
{
if(document.getElementById(‘quickerfilerOptions.shortcuts.shortcutModifier.’+this.mModifiers[i]).checked)
modifiers.push(this.mModifiers[i]);
}

document.getElementById(‘extensions.quickerfiler.shortcuts.’+shortcut+’.modifiers’).value = modifiers.join(‘ ‘);

this.updateTreeView();
}
[/sourcecode]

Auch das ist wieder selbsterklärend: Wenn sich ein Modifier geändert hat, wird die komplette Liste wieder erstellt und in der Preference gespeichert, danach werden die Werte des treelist neu gesetzt.

Damit wären wir auch am Ende dieses Teils unseres kleinen Kurses. Zum Lernen ist dieses Beispiel ja ganz nett – aber um einfach nur eine Tastenkombination zu setzen, ist das doch ziemlich viel Aufwand. Schöner wäre ein eigenes XUL-Control, bei dem man die Tastenkombination einfach eintippen kann. Daher habe ich das in Bug 673669 vorgeschlagen.

Und wieder der obligatorische Hinweis: Code-Schnipsel stammen aus Quicker Filer 0.5.1, sind Copyright (C) 2010 Eivind Rovik und stehen unter GPL (Version 3 oder jede spätere Version).

Properties in Tomcat

Wenn man danach sucht, wie man Properties in Webapps bzw. Servlets lädt, findet man viel Unsinn, bzw. nicht richtig durchdachte oder unvollständige Lösungen. Das hier ist daher ein Versuch, für mich selbst mal einen funktionierenden Ansatz zusammenzuschreiben.

Ausgangspunkt war dieser Blog-Eintrag. Zum Zugriff auf eine Properties-Datei wird dort folgender Code verwendet:

[sourcecode language="java"]
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("myApp.properties");
Properties properties = new Properties();
properties.load(inputStream);
[/sourcecode]

So weit, so gut. OK, das Laden von Konfiguration über einen Classloader erscheint auf den ersten Blick merkwürdig. Aber sei es drum …

Bloß: Wo lege ich die Konfigurationsdatei jetzt ab, so dass sie auch gefunden wird? Im WAR/AAR jedenfalls nicht – ich will diese Archive ja nicht neu packen, bloß weil sich (Deployment-spezifische) Einstellungen ändern. Ein anderes Blog brachte dann eine Lösung, jedenfalls für Tomcat: Man soll den shared class loader benutzen. Also habe ich meine Datei in $CATALINA_HOME/shared/classes angelegt. Schnell noch die Property shared.loader in conf/catalina.properties angepasst … Doch halt: Die ist schon da und ist leer, was laut Kommentar heißt, dass in $CATALINA_HOME/shared gesucht wird. Für mein Empfinden ist das etwas anderes als $CATALINA_HOME/shared/classes. Naja, egal. Ich hatte keine Lust auf weiteres Ausprobieren und habe die Property einfach neu gesetzt:

[sourcecode]
shared.loader=${catalina.home}/shared/classes
[/sourcecode]

Das tut jetzt sowohl für Webapps/Servlets als auch für Axis2-Webservices. Prima.

Thunderbird-Addons: Chrome-Manifest, XUL-Overlays

Und weiter geht es da, wo der letzte Teil aufgehört hat: Was steht eigentlich in diesem Chrome Registration Manifest so drin? Beispiel ist wieder Quicker Filer 0.5.1. Die chrome.manifest sieht dort so aus:

[sourcecode]
content quickerfiler content/

overlay chrome://messenger/content/messenger.xul chrome://quickerfiler/content/messengeroverlay.xul
overlay chrome://messenger/content/messengercompose/messengercompose.xul chrome://quickerfiler/content/messengercomposeoverlay.xul

component {1C28D908-9DD7-11E0-B67F-DCD94724019B} components/quickerfilerautocomplete.js
contract @mozilla.org/autocomplete/search;1?name=quickerfiler-autocomplete {1C28D908-9DD7-11E0-B67F-DCD94724019B}
[/sourcecode]

Der Reihe nach:

  • Die erste Zeile ist ein sog. content package.
    Das erste content ist ein Schlüsselwort. quickerfiler ist der Name des Packages. content/ ist ein URI, der auf den Inhalt des Packages zeigt – in diesem Fall ein relativer Pfad. Diese Registrierung sorgt dafür, dass Chrome-URIs der Form chrome://quickerfiler/content/... (man beachte, dass auch hier content auftaucht!) aufgelöst werden können, also die entsprechenden Dateien gefunden werden.
  • Die zweite und dritte Zeile definieren XUL-Overlays, also Erweiterungen von bestehenden Sichten der Benutzeroberfläche. Vorliegend werden das Hauptfenster (3-pane window) und das Fenster zum Verfassen neuer Mails erweitert – dazu aber später mehr.
  • Schließlich folgen noch die Definition einer Komponente und eines sog. Contracts (also einer Schnittstelle mit einer Implementierung). Mehr über Komponenten im allgemeinen und diese spezielle später.

Kommen wir also zu den XUL-Overlays. content/messengeroverlay.xul sieht wie folgt aus:

[sourcecode language="xml"]
<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript" src="chrome://quickerfiler/content/messengeroverlay.js"/>
<keyset>
<key id="quickerfiler_key_move" oncommand="sQuickerFilerMessengerOverlay.openDialog(‘move’);" modifiers="alt" key="q" />
<key id="quickerfiler_key_copy" oncommand="sQuickerFilerMessengerOverlay.openDialog(‘copy’);" />
<key id="quickerfiler_key_insmove" oncommand="sQuickerFilerMessengerOverlay.openDialog(‘insmove’);" />
<key id="quickerfiler_key_inscopy" oncommand="sQuickerFilerMessengerOverlay.openDialog(‘inscopy’);" />
<key id="quickerfiler_key_selfolder" oncommand="sQuickerFilerMessengerOverlay.openDialog(‘selfolder’);" modifiers="alt" key="r" />
</keyset>
</window>
[/sourcecode]

Das Ganze ist, wie schon erwähnt, ein Overlay für messenger.xul, das Hauptfenster von Thunderbird. Dieses Hauptfenster ist ziemlich kompliziert und importiert noch diverse andere XUL-Dokumente. Aber zum Glück kommt es darauf nicht wirklich an – das Overlay definiert einfach Aktionen, die mit Tastenkombinationen ausgelöst werden können und legt fest, welche Javascript-Kommandos in dem Fall ausgeführt werden. Die Ergänzung von Kontextmenüs o.ä. wäre vermutlich sehr viel komplizierter …

Die Bedeutung des script-Elements ist ziemlich offensichtlich, genau wie die von keyset und key. Außerdem gibt es noch ein Tutorial zu dem ganzen Thema. Was vielleicht noch interessant ist, dass nur zwei der key-Elemente auch tatsächlich schon eine Tastenkombination festlegen. Das geschieht zur Laufzeit: content/messengeroverlay.js registriert folgenden Event-Handler:

[sourcecode language="javascript"]
window.addEventListener(‘load’, function () { sQuickerFilerMessengerOverlay.onLoad(); }, false);
[/sourcecode]

Dieser Listener wird zum Fenster hinzugefügt also ausgeführt, wenn das Fenster (in diesem Fall also das Hauptfenster von Thunderbird geladen ist. sQuickerFilerMessengerOverlay.onLoad() lädt die Tastenkombinationen aus den Preferences und setzt die entsprechenden Eigenschaften der key-Elemente.

Das andere Overlay ist content/messengercomposeoverlay.xul:

[sourcecode language="xml"]
<?xml version="1.0"?>

<overlay id="sample" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script type="application/x-javascript" src="chrome://quickerfiler/content/common.js"/>
<script type="application/x-javascript" src="chrome://quickerfiler/content/messengercomposeoverlay.js"/>

<vbox id="addresses-box">
<toolbox id="quickerfiler.CopyToFolderToolbox">
<hbox align="center">
<label value="Copy message to:" />
<textbox id="quickerfiler.CopyToFolder" flex="1" class="toolbar" disableonsend="true"
type="autocomplete"
autocompletesearch="quickerfiler-autocomplete"
maxrows="14"
tabscrolling="true"
autoFill="true"
forcecomplete="true"
showcommentcolumn="false" />
</hbox>
</toolbox>
</vbox>

</overlay>[/sourcecode]

Dieses Overlay erweitert messengercompose.xul.

Die script-Elemente sind wieder selbsterklärend. Allerdings fällt auf, dass weiter unten keine Javascript-Befehle enthalten sind. Warum wird also überhaupt Code aufgerufen? Nun, content/messengercompose.js enthält folgende Befehle (die beim Laden des Skripts ausgeführt werden):

[sourcecode language="javascript"]
window.addEventListener(‘load’, function () { sQuickerFilerMessengerComposeOverlay.onOpen(); }, false);
window.addEventListener(‘compose-window-reopen’, function () { sQuickerFilerMessengerComposeOverlay.onOpen(); }, true);
window.addEventListener("compose-send-message", function(aEvent) { sQuickerFilerMessengerComposeOverlay.sendEventHandler(aEvent); }, true);
[/sourcecode]

Zur genauen Bedeutung der Events später mehr, wenn ich mir den Code anschaue.

Die taucht bereits im Dokument auf. Das heißt also, das dieses Element vom Overlay erweitert wird – in diesem Fall wird eine toolbox hinzugefügt (unter der Empfängerliste und der Betreffzeile. Warum ausgerechnet eine toolbox, ist mir übrigens nicht klar. Laut Dokumentation dienen diese als Container für toolbars – eine solche taucht hier aber nicht auf (ah, halt: Die Textbox unten hat class="toolbar" – vielleicht hat das ja etwas zu bedeuten). Darin ist dann eine hbox, ein einfacher Container, der der Gruppierung dient. Das label ist ebenfalls uninteressant (außer vielleicht für die Frage, wie das Ganze layouttechnisch funktioniert – dazu vielleicht ein anderes Mal mehr).

Das Spannende ist dagegen die textbox. Gehen wir einfach die Attribute einzeln durch:

  • flex: Dieses Attribut legt fest, wie freier Platz zwischen Elementen verschoben wird. In diesem Fall geht also sämtlicher freier Platz an diese Textbox.
  • class: Dieses Element wird in Stylesheets benutzt. Das deutet darauf hin, dass die Verwendung des toolbox-Elements auch nur layouttechnische Gründe hat.
  • disableonsend: Hier fehlt auf MDN Dokumentation, siehe Mozilla-Bug 670512.
  • type: Gibt den Typ der textbox an, wenn es nicht nur ein simples Textfeld sein soll. In diesem Fall, autocomplete, ist die Sache so kompliziert, dass es gesonderte Dokumentation dazu gibt. Die folgenden Attribute sind alle spezifisch für autocomplete. Im Zusammenhang ist das wohl Thema für einen eigenen Artikel …
  • autocompletesearch: Gibt an, wo die Vorschläge für die Autocompletion herkommen, in diesem Fall von einer speziellen Komponente statt von einer der vordefinierten
  • maxrows: Wie viele Vorschläge werden auf einmal angezeigt, also wie hoch ist das “Menü” höchstens?
  • tabscrolling: In diesem Fall iteriert man mit Tab durch die Vorschläge, anstatt zum nächsten Element zu gehen.
  • autoFill: Es findet automatische Vervollständigung während des Tippens statt.
  • forcecomplete: Wenn man zu einem anderen Element geht, wird die Eingabe zwangsweise zu einem Vorschlag vervollständigt. Das heißt aber offenbar nicht, dass es nicht möglich ist, auch anderen Text einzugeben als einen der Vorschläge.
  • showcommentcolumn: Es werden zu den Vorschlägen keine weiteren Kommentare angezeigt.

Außerdem gibt es noch zwei eigenständige XUL-Dialoge, options.xul und quickerfilerdialog.xul. Auf die werde ich eingehen, wenn ich alles andere durchhabe und dort noch interessante neue Dinge finde. Als nächstes sind erstmal der Javascript-Code der bisher vorgestellten Funktionalität sowie die Komponente für die Autovervollständigung dran. Es wird also unter anderem um XPCOM gehen.

Wieder der obligatorische Hinweis: Code-Schnipsel stammen aus Quicker Filer 0.5.1, sind Copyright (C) 2010 Eivind Rovik und stehen unter GPL (Version 3 oder jede spätere Version).

Thunderbird-Addons: Grundstruktur

Ich hatte ja gerade angedroht, hier mal ein Thunderbird-Addon genauer unter die Lupe zu nehmen, nämlich Quicker Filer, konkret die gerade aktuelle Version 0.5.1.

Packt man das Archiv quicker_filer-0.5.1-tb.xpi aus (mit unzip), kommen folgende Dateien zum Vorschein:

./chrome.manifest
./content
./content/common.js
./content/options.js
./content/options.xul
./content/messengercomposeoverlay.xul
./content/quickerfilerdialog.xul
./content/quickerfilerdialog.js
./content/messengeroverlay.js
./content/messengercomposeoverlay.js
./content/messengeroverlay.xul
./components
./components/quickerfilerautocomplete.js
./install.rdf

Einen Überblick, wie die Struktur so einer Extension aussehen soll, gibt diese Anleitung. Die Struktur so eines Bundles ist auch nochmal hier erläutert, wobei sich da einiges zu überschneiden scheint.

Ausgangspunkt ist jedenfalls erstmal die install.rdf, also ein Install Manifest. Im vorliegenden Fall sieht das so aus:

[sourcecode language="xml"]
<?xml version="1.0"?>

<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">

<Description about="urn:mozilla:install-manifest">
<em:id>qfiler@eivind.rovik</em:id>
<em:name>Quicker Filer</em:name>
<em:version>0.5.1</em:version>
<em:description>Quicker Filer [...]</em:description>
<em:creator>Eivind Rovik</em:creator>
<em:type>2</em:type>

<em:optionsURL>chrome://quickerfiler/content/options.xul</em:optionsURL>

<em:targetApplication>
<Description>
<em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
<em:minVersion>3.0</em:minVersion>
<em:maxVersion>5.*</em:maxVersion>
</Description>
</em:targetApplication>

</Description>
</RDF>
[/sourcecode]

Das “basic layout” stimmt also überein. Gehen wir die Eigenschaften mal der Reihe nach durch, erst die erforderlichen:

  • id: Hier im @-Format angegeben – das ist in der Tat deutlich anschaulicher als GUIDs …
  • version: Auch wenn man daraus durchaus eine Wissenschaft machen kann, ist es in diesem Fall recht einfach: Drei Zahlen, durch Punkte getrennt.
  • type: Hier wird es unintuitiv. “2″ steht für eine Extension – also das, worum es mir gerade auch geht.
  • targetApplication: Was hier zulässige Werte für id sind, ist auf dieser Seite aufgelistet. Thunderbird ist {3550f703-e582-4d05-9a08-453d09bdfdc6}. minVersion und maxVersion sagen aus, mit welchen Thunderbird-Versionen das Addon kompatibel ist, in diesem Fall also alle Versionen von 3.0 bis zu 5.* (das .* spielt mit dem Rapid Release Scheme wohl eh keine Rolle mehr …).
  • name: Der Name des Addons, hier also Quicker Filer.

Außerdem gibt es noch optionale Eigenschaften. Ich gehe jetzt nur auf die ein, die bei Quicker Filer gesetzt sind:

  • description: Eine einzeilige Beschreibung, die im UI (also dem Addon-Manager) angezeigt wird.
  • creator: Der Name des Entwicklers
  • optionsURL: Die chrome-URL eines Einstellungsdialogs für das Addon. Dazu in einem späteren Post mehr.

Schauen wir uns anhand der bereits genannten Seite die restlichen Dateien an:

  • chrome.manifest: Ein sog. Chrome Registration Manifest. Chrome ist alles, was zum UI der eigentlichen Anwendung gehört – bei Thunderbird also alles bis auf die angezeigten Mails, und eventuell auch angezeigte Webseiten. Im Prinzip wird dort der Aufbau des Pakets beschrieben – auf die Details werde ich in einem späteren Posting eingehen.
  • components: In diesem Verzeichnis liegen XPCOM-Komponenten – auch dazu später mehr.
  • content: Der Name dieses Verzeichnisses folgt keiner besonderen Konvention. In dem Verzeichnis liegen Teile des User-Interfaces, diese müssen aber in chrome.manifest registriert werden. Dazu also später mehr.

Damit endet der Überblick über die Struktur eines Addons und damit der erste Teil einer vermutlich längeren Serie von Postings. Zur Sicherheit noch der obligatorische Hinweis: Code-Schnipsel stammen aus Quicker Filer 0.5.1, sind Copyright (C) 2010 Eivind Rovik und stehen unter GPL (Version 3 oder jede spätere Version).

Begriff gesucht

Ich fange mal mit einem Beispiel an: Ein Compiler übersetzt ja ein Programm von einer Hochsprache in Maschinencode (jetzt mal ganz platt ausgedrückt). Operational gesehen ist das das Gleiche. Aber dabei geht (insbesondere, wenn man Optimierungen anwendet), Struktur verloren. Das führt dann beispielsweise dazu, dass Debugger nicht mehr richtig funktionieren (oder jedenfalls sehr viel schwerer zu schreiben sind). Wenn man das vermeidet und dafür sorgt, dass die Information aus dem Hochsprachenprogramm auch auf Maschinencodeebene verfügbar ist – wie könnte man das nennen? “Semantikerhaltende Transformation” trifft es nicht – die Semantik (im Sinne von Ausführungssemantik) bleibt ja erhalten. Vielleicht “strukturerhaltende Transformation”?

Oder vielleicht gibt es sogar Literatur, die sich mit dieser eher philosophischen Frage befasst?

Native Libraries in Java

Java erlaubt das Einbinden “nativer” Bibliotheken (also .so bzw. .dll, C-Bibliotheken halt). Allerdings gibt es da einige Tücken: Die Bibliothek muß mit System.loadLibrary("name"); explizit geladen werden. Sie ist dann nur in dem Classloader verfügbar, mit dem sie geladen wurde. Das allein ist noch nicht weiter schlimm – allerdings läßt sich eine Bibliothek auch nur einmal laden. Und “einmal” heißt einmal: Einmal in der Lebenszeit der gesamten JVM. Da ist es auch egal, ob es den Classloader noch gibt etc.

Problematisch wird das zum Beispiel in einem Servlet-Container. Gegeben seien zwei Servlets A und B, die beide libname.so verwenden wollen. Das zweite Servlet, das die Lib laden will, bekommt dann einen UnsatisfiedLinkError. Ein im Netz vorgeschlagener Workaround ist, dafür zu sorgen, daß die Lib vom System-Classloader geladen wird. Hört sich etwas kompliziert an … Eine etwas einfachere Möglichkeit, die auch prompt funktionierte: Einfach die Bibliothek verdoppeln (cp libname.so libname2.so), im zweiten Servlet System.loadLibrary("name2"); ausführen. Wenn die Libraries eh irgendwo lokal rumflattern, paßt das …

Intervall-Kalender-Kontrollelement

In vielen Webanwendungen ist es nötig, Zeitspannen (Anfangs- und Enddatum) auszuwählen. Ein gutes Beispiel sind Reiseseiten. Leider sind diese Controls oftmals grottig programmiert.

Eine ziemlich vorbildliche Lösung hat Opodo.de. Die Kernfeatures:

  • Wenn man einen Tag auswählt, der im aktuellen Monat in der Vergangenheit liegt, springt er auf den nächsten Monat.
  • Das Enddatum wird erhöht, wenn man das Startdatum ändert. Man muß also nicht die ganze Liste durchscrollen, um den gleichen Monat nochmal auszuwählen.
  • Das Control ist brauchbar mit der Tastatur (auch Zifferneingabe) zu bedienen.

IMO gehört sowas in jedes AJAX-Toolkit.

Computerzeitung

Letzte Woche bekam ich einen erfreulichen Brief von der Gesellschaft für Informatik:

  • Die unsägliche Computerzeitung wird eingestellt. (“muß ich Ihnen leider mitteilen”, “Wir bedauern die Einstellung der Computerzeitung.”)
  • Der Beitrag wird nicht erhöht.
  • Für den Beitrag gibt es künftig eine Spendenquittung.

Insgesamt also nicht nur eine gute, sondern eine sehr gute Nachricht :-)

Tiles@Home

Ich hoffe doch, daß das Projekt Openstreetmap hinlänglich bekannt ist. Eine Version der Karten wird verteilt, also von vielen Leuten auf dem PC zu Hause etc., gerendet: Dieses Projekt nennt sich Tiles at Home.

Seit Anfang der Woche wurde dort ein neuer Server installiert, der endlich mit mehr als ein paar Dutzend Clients klarkommt, und die Zahl der offenen Aufträge scheint rapide anzusteigen. Wenn ihr also Openstreetmap unterstützen wollt: Mitmachen!

Efficient XML Interchange

Ich hab’s ja eigentlich schon immer gesagt: XML ist eigentlich toll, aber das Serialisierungsformat ist Bloat. Und was sehe ich heute, als ich (aus ganz anderen Gründen) auf der W3C-Seite rumsurfe? Die sind gerade dabei, sowas zu entwickeln:

The development of the Efficient XML Interchange (EXI) format was guided by five design principles, namely, the format had to be general, minimal, efficient, flexible, and interoperable. The format satisfies these prerequisites, achieving generality, flexibility, and performance while at the same time keeping complexity in check.

Warum nicht gleich so :-)

Zentralität

Mir ist gerade aufgefallen, daß Zentralität sowohl ein Fachbegriff in der Informatik (dort speziell in der Graphentheorie) als auch in der Soziologie (dort speziell in der empirischen Sozialforschung) ist.

Ob sich da wohl Verbindungen finden lassen?