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:

  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);
  }

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.

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

Improvements to LEDA adaptors, from Jens Mueller
@@ -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);
   }

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:

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

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.

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

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

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

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.

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

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

  <prefpane id="quickerfilerOptions.general.prefpane" label="General" selected="true">

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 …

    <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>

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.

    <vbox>

Die vbox dient der Gliederung bzw. Layout-Zwecken.

      <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>

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.

      <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">

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!

          <radio id="quickerfiler.search.root.allfolders.radio"
            label="All folders"
            value="msgaccounts:/"/>

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.

          <hbox>
            <radio id="quickerfiler.search.root.account.radio"
              label="This account"
              value="-- account --"/>

            <hbox flex="1" pack="end">

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?
              <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">

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:

                <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>

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:

      <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>

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:

  </prefpane>

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

  <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>

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:

onPaneLoad: function onPaneLoad()
{
  this.updateTreeView();
},

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:

      <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>

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:

          <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>

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:

  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');
      }
    }
  }

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:

<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>

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:

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();
  }

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:

  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();
  }

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).

  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();
  }

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:

InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("myApp.properties");
Properties properties = new Properties();
properties.load(inputStream);

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:

shared.loader=${catalina.home}/shared/classes

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:

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}

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:

<?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>

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:

window.addEventListener('load', function () { sQuickerFilerMessengerOverlay.onLoad(); }, false);

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:

<?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>

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):

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);

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:

<?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>

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 …

Nominatim

Als ich gerade auf Openstreetmap.org war, habe ich gemerkt, daß dort jetzt ein neuer Geocoder im Einsatz ist, der offenbar deutlich besser funktioniert (und auch schneller reagiert, aber das muß ja nichts mit der Software zu tun haben) als der alte. Reverse-Geocoding kann er auch. Das macht für viele Usecases Google Maps überflüssig. Well done, Nominatim.

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?

Opensource-Heizungsregelung

In unserer WG haben wir eine Etagenheizung ohne Außentemperaturfühler, und Heizkörper mit einfachen Reglern ohne Thermostat. Das Ganze verstößt nicht nur gegen die Energieeinsparungsverordnung (“Zentralheizungen müssen beim Einbau in Gebäude mit zentralen selbsttätig wirkenden Einrichtungen zur Verringerung und Abschaltung der Wärmezufuhr sowie zur Ein- und Ausschaltung elektrischer Antriebe in Abhängigkeit von der Außentemperatur oder einer anderen geeigneten Führungsgröße und der Zeit ausgestattet werden.” (§ 14), ), sondern ist auch extrem nervig, weil man in den Räumen keine konstante Temperatur halten kann.

Mein Mitbewohner hat sich schon einen elektronischen Heizungsthermostat gekauft (aber noch nicht montiert). Klar, als Wiwi guckt er erstmal, was es auf dem Markt gibt.

Ich finde die Teile irgendwie etwas unflexibel, und wenn man was besseres will (mit zentralem Regler, der die Ventile per Funk steuert), wird man für Steuerung und zwei Regler leicht deutlich über 100 € los. Als Informatiker frage ich mich da: Gibt es da nicht auch eine Open-Source-Lösung? Naja, wohl nichts Fertiges, aber die Möglichkeiten werden schon diskutiert. Gefunden habe ich da eine Diskussion in einem Webforum und in der Newsgroup de.sci.electronics. Werde mir das mal durchlesen und gucken, was realistischerweise machbar ist.

Damit wären ja schon coole Sachen denkbar: Koppelung mit Wettervorhersage, Ferneinschaltung von der Uni aus, bevor man nach Hause kommt, etc. pp.

VMWare: NTFS-Partition vergrößern mit Linux-Bordmitteln

Da ich das Ganze schonmal gemacht habe und nicht mehr ganz zusammenbekomme, wird es dieses Mal notiert.

Erster Schritt: Die VMWare-Platte vergrößern, mit
vmware-vdiskmanager -x 20Gb WindowsXPPro32bit.vmdk

Dann die VM mit einer gparted-Boot-CD starten. Dazu muß man darauf achten, daß das VM-Fenster auch wirklich den Tastatur-Fokus hat und dann beim Power-On der VM auf die Esc-Taste hacken.

gparted startet dann, und man sieht, daß die Platte freien Speicherplatz hat. Mit Resize/Move kann man dann die Partition vergrößern.

Dabei laufen die Schritte

  • calibrate /dev/hda1
  • calculate new size and position of /dev/hda1
  • grow partition from 11.99 GiB to 19.99 GiB
  • check filesystem on /dev/hda1 for errors and (if possible) fix them

problemlos durch, aber bei “grow filesystem to fill the partition” hapert es.

Also ein Terminal starten, und dort erst mal ntfsresize ein paar Infos ausspucken lassen.

Dabei stehen dann unter anderem die aktuelle Größe von “volume” (also Dateisystem) und “device” (also Partition).

Erstmal ntfsresize -n -s /dev/hda1 – Simulation klappt.

Problem: Ich habe das Windows falsch heruntergefahren, Journal ist hinüber, also nochmal Rebooten (also: gleich darauf achten, das Windows sauber herunterzufahren). Das kostet also wieder ein Minuten.

Danach ging es dann ohne “-n” wunderbar. Also reboot eintippen, und gut. Beim nächsten Neustart überprüft Windows dann automatisch das Dateisystem, und dann ist alles fertig – man hat eine größere Platte.

Visual Studio SP1

Gerade habe ich unter Windows mal routinemäßig Updates installiert. Aber für das hier:

Visual Studio 2005 Service Pack 1
Date last published: 4/5/2007
Visual Studio 2005 Service Pack 1 updates Microsoft Visual Studio 2005, Microsoft Visual Studio 2005 Team System, Microsoft Visual Studio 2005 Tools for the Microsoft Office System, and Microsoft Visual Studio 2005 Team Explorer with the latest security and stability enhancements to help keep those systems up-to-date, reliable, and secure. The goal of all of our service packs is to increase the overall quality of the existing product features while maintaining a high level of compatibility. The installation of this service pack might take up to several hours. After you install this item, you may have to restart your computer.
System Requirements
Recommended CPU: Not specified.
Recommended memory: Not specified.
Recommended hard disk space: 6200 MB

muß ich erstmal die Platte vergrößern …

Character Description Language

I just stumbled across some pretty cool project at the junction of linguistics and IT. Of course, again, it concerns Chinese language processing …

The Character Description Language‘s aim is to provide a description language for Han ideographs. The project seems to be well-organized, and they have captured 56k CJK characters, including all from the BMP.

This data would probably be very useful for developing an Input Method Engine using a graphic tablet, or showing the decomposition of characters into their constituent parts. Alas, I have as of yet not been able to find the database – is it commercial stuff (namely, Wenlin) safely locked away from the interested public? That would really be a pity …

Paper eingereicht

Das Wochenende war mal echt Streß. Diverse Nachtstunden habe ich mit der Arbeit an meinem Paper für AGTIVE 2007 verbracht, dazu haben wir uns noch an den Feiertagen (Pfingstsonntag und -montag) im Institut gesprochen, um uns gegenseitig Hilfestellung bei Messungen, Formulierungen etc. zu geben. Gestern war dann Endspurt: 23:00 war die offizielle Deadline (da die wohl bei der Angabe der Zeitzone GMT+1 nicht an die Sommerzeit gedacht haben, eigentlich doch 24:00), bis dahin wurde noch fleißig an den Papers geschraubt. Kurz vor dem echten Endspurt gab es um ca. 20:00 noch Pizza, und nach Abgabe wurde dann eine Flasche Diabetikersekt geleert (das Zeug steht hier haufenweise im Kühlschrank) …

Mal sehen, ob es angenommen wird, cool wär’s ja schon …