Hatena::ブログ(Diary)

vivid memo このページをアンテナに追加 RSSフィード

vivid code というサイトのメモ代わりに記事を書いていました。
現在ははてなブログに移行し、「ひだまりソケットは壊れない」 というブログで記事を書いています。 はてな id も id:nobuoka に変更しました。

2009-05-21

AppLauncher ver.0.4 〜 リスト形式 (可変個数) の設定を行う 〜

Firefox の拡張機能 (add-on の一種) を作ってみよう、ということで作り始めた AppLauncher。 前回は、Firefox のコンテキストメニューから外部アプリケーションを起動するところまで作成しました。

今回はコンテキストメニューから起動する外部アプリケーションの設定を行うための、設定画面の作成と、設定の保存に関して作成しました。

AppLauncher ver.0.4 の作成

Firefox から外部アプリケーションを開く、という拡張機能 AppLauncher を作っていきます。

Ver.0.4 では、起動する外部アプリケーションの設定を行う部分を実装します。

なお、現時点 (ver.0.3) でのファイル構成は以下のようになっています。

[applauncher-0.3.xpi]
    +--[chrome.manifest]
    +--[install.rdf]
    +--[content]
           +--[launcher.xul]
           +--[launcher.js]
           +--[prefs.xul]

設定画面の作成

f:id:vividcode:20090521174725p:image

可変個数の設定を編集できるように、上図のような設定画面を作成します。 "prefs.xul" ファイルを下記のように変更しました。

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://applauncher/content/prefs.css" type="text/css"?>
<!-- 設定ウィンドウ
     cf. dialog について: https://developer.mozilla.org/ja/XUL_Tutorial/Creating_Dialogs -->
<dialog id="info.vividcode.applauncher.prefwindow" title="AppLauncher の設定"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        buttons="accept,cancel">
  <!-- JavaScript を読み込み -->
  <script type="application/x-javascript" src="chrome://applauncher/content/AppLauncher.js" />
  <script type="application/x-javascript" src="chrome://applauncher/content/prefs.js" />
  <!-- 実際の表示を規定 -->
  <vbox>
    
    <listbox id="info.vividcode.applauncher.prefwindow.listbox">
      <listhead>
        <listheader label="Name" />
        <listheader label="Path" />
        <listheader label="Arguments" />
      </listhead>
      <listcols>
        <listcol/>
        <listcol flex="1"/>
        <listcol flex="1"/>
      </listcols>
    </listbox>
    
    <groupbox>
      <caption label="adding your application" />
      <vbox>
        <grid>
          <columns>
            <column flex="1"/>
            <column flex="2"/>
          </columns>
          <rows>
            <row>
              <label control="info.vividcode.applauncher.prefwindow.adding.name" value="Name" />
              <textbox id="info.vividcode.applauncher.prefwindow.adding.name" />
            </row>
            <row>
              <label control="info.vividcode.applauncher.prefwindow.adding.path" value="Path" />
              <textbox id="info.vividcode.applauncher.prefwindow.adding.path" />
            </row>
            <row>
              <label control="info.vividcode.applauncher.prefwindow.adding.args" value="Arguments" />
              <textbox id="info.vividcode.applauncher.prefwindow.adding.args" />
            </row>
          </rows>
        </grid>
        <button label="add" id="info.vividcode.applauncher.prefwindow.adding.button" />
      </vbox>
    </groupbox>
  </vbox>
</dialog>

新しい項目の追加や設定の保存など、動作部分の実装

設定用の画面は XUL で規定しましたが、「Add」 ボタンの動作や、「OK」 ボタンの動作などはまだ決まっていません。 それらの動作は JavaScript で実装します。

"prefs.js" を以下のように編集しました。 「OK」 ボタンが押されたときに、設定画面の要素を (DOM オブジェクトとして) 取得し、XMLSerializer を使って文字列に変換、その文字列を pref として保存する、という動作です。 逆に保存している設定を読み込むときは DOMParser を使用します。

// AppLauncher の設定に関する JavaScript

/**
 * ユーザが設定画面に入力した値を XML 形式で保存する.
 */
AppLauncher.savePrefs = function() {
    // 設定画面の要素を取得
    var listbox = document.getElementById("info.vividcode.applauncher.prefwindow.listbox");
    var items   = listbox.getElementsByTagNameNS(this.XUL_NS, "listitem");
    // 保存用の XML Document を新たに生成
    var prefNode  = document.implementation.createDocument(this.PREFS_NS, "appList", null);
    // 保存用 XML に値を追加していく
    for( var i = 0; i < items.length; i++ ) {
        var cells = items[i].getElementsByTagNameNS(this.XUL_NS, "listcell");
        var app = document.createElementNS(this.PREFS_NS, "app");
        app.setAttribute( "name", cells[0].getAttribute("label") );
        app.setAttribute( "path", cells[1].getAttribute("label") );
        app.setAttribute( "args", cells[2].getAttribute("label") );
        prefNode.documentElement.appendChild(app);
    }
    // DOM を XML 文にして保存
    // cf. https://developer.mozilla.org/ja/XMLSerializer
    var serializer = new XMLSerializer();
    var prefStr = serializer.serializeToString(prefNode);
    this.setCharPref("appList", prefStr);
    window.alert("accept!:\n" + prefStr);
};

/**
 * ユーザの設定画面に以前の設定を読み出す.
 */
AppLauncher.loadPrefs = function() {
    // 以前の設定を DOM として取得
    var appListStr = this.getPref("appList");
    var parser  = new DOMParser();
    var appList = parser.parseFromString(appListStr, "text/xml");
    var items   = appList.getElementsByTagNameNS(this.PREFS_NS, "app");
    // 設定画面の要素を取得
    var listbox = document.getElementById("info.vividcode.applauncher.prefwindow.listbox");
    //var items   = listbox.getElementsByTagNameNS(this.XUL_NS, "listitem");
    // 設定画面に要素を追加していく
    for( var i = 0; i < items.length; i++ ) {
        var item = document.createElementNS(this.XUL_NS, "listitem");
        var cell = document.createElementNS(this.XUL_NS, "listcell");
        cell.setAttribute( "label", items[i].getAttribute("name") );
        item.appendChild(cell);
        cell = document.createElementNS(this.XUL_NS, "listcell");
        cell.setAttribute( "label", items[i].getAttribute("path") );
        item.appendChild(cell);
        cell = document.createElementNS(this.XUL_NS, "listcell");
        cell.setAttribute( "label", items[i].getAttribute("args") );
        item.appendChild(cell);
        item.addEventListener("dblclick", this.elDeletingApp, false);
        listbox.appendChild(item);
    }
};

AppLauncher.addNewApprication = function() {
    var listitem = document.createElementNS(AppLauncher.XUL_NS, "listitem");
    var listcell = document.createElementNS(AppLauncher.XUL_NS, "listcell");
    listcell.setAttribute("label", document.getElementById("info.vividcode.applauncher.prefwindow.adding.name").value);
    listitem.appendChild(listcell);
    var listcell = document.createElementNS(AppLauncher.XUL_NS, "listcell");
    listcell.setAttribute("label", document.getElementById("info.vividcode.applauncher.prefwindow.adding.path").value);
    listitem.appendChild(listcell);
    var listcell = document.createElementNS(AppLauncher.XUL_NS, "listcell");
    listcell.setAttribute("label", document.getElementById("info.vividcode.applauncher.prefwindow.adding.args").value);
    listitem.appendChild(listcell);
    listitem.addEventListener("dblclick", this.elDeletingApp, false);
    document.getElementById("info.vividcode.applauncher.prefwindow.listbox").appendChild(listitem);
};

AppLauncher.deleteSelectedApprication = function() {
    var listbox = document.getElementById("info.vividcode.applauncher.prefwindow.listbox");
    var items = listbox.selectedItems;
    // 選択項目が無い
    if( items.length == 0 ) {
        throw new Error("選択されている項目がありません.");
    }
    // 2 つ以上選択されている
    else if( items.length > 1 ) {
        throw new Error("2 つ以上のアイテムが選択されています.");
    }
    listbox.removeChild( items[0] );
    /*
    var listitem = document.createElementNS(AppLauncher.XUL_NS, "listitem");
    var listcell = document.createElementNS(AppLauncher.XUL_NS, "listcell");
    listcell.setAttribute("label", document.getElementById("info.vividcode.applauncher.prefwindow.adding.name").value);
    listitem.appendChild(listcell);
    var listcell = document.createElementNS(AppLauncher.XUL_NS, "listcell");
    listcell.setAttribute("label", document.getElementById("info.vividcode.applauncher.prefwindow.adding.path").value);
    listitem.appendChild(listcell);
    var listcell = document.createElementNS(AppLauncher.XUL_NS, "listcell");
    listcell.setAttribute("label", document.getElementById("info.vividcode.applauncher.prefwindow.adding.args").value);
    listitem.appendChild(listcell);
    document.getElementById("info.vividcode.applauncher.prefwindow.listbox").appendChild(listitem);
    */
};

AppLauncher.elAddingApp = function(evt) {
    AppLauncher.addNewApprication();
};

AppLauncher.elDeletingApp = function(evt) {
    AppLauncher.deleteSelectedApprication();
};

/**
 * OK ボタンの実行をキャッチするイベントリスナ.
 */
AppLauncher.accept = function(evt) {
    AppLauncher.savePrefs();
};

AppLauncher.cancel = function(evt) {
    window.alert("cancel...");
};

AppLauncher.initPref = function(evt) {
    var item = document.getElementById("info.vividcode.applauncher.prefwindow");
    if( item ) {
        // イベントリスナの追加
        item.addEventListener("dialogaccept", AppLauncher.accept, false);
        item.addEventListener("dialogcancel", AppLauncher.cancel, false);
    }
    item = document.getElementById("info.vividcode.applauncher.prefwindow.adding.button");
    if( item ) {
        // イベントリスナの追加
        item.addEventListener("command", AppLauncher.elAddingApp, false);
    }
    AppLauncher.loadPrefs();
};

// 初期化関数をロード時に実行する
window.addEventListener("load", AppLauncher.initPref, false);

その他の変更箇所

あとはコンテキストメニューを設定したとおりに動作するようにするなど、ちゃんと動くために必要な変更を行いました。

一段落?

今回の ver.0.4 で個人的に欲しかった機能 (Firefox のコンテキストメニューから IE やら IETester を起動させるという機能) が完成しました。 まだまだ改良の余地はありますけどちまちま改造していこうかと思います。

トラックバック - http://d.hatena.ne.jp/vividcode/20090521/1242896580