AppLauncher ver.0.6.3, ver.0.6.4

Firefox拡張機能 AppLauncher のバージョンアップを行いました。

AppLauncher とは

AppLauncher は Firefox拡張機能です。 右クリックメニュー (コンテキストメニュー) から外部アプリケーション (IE などのブラウザだとかその他色々) を起動することを目的に開発しています。

とりあえず現状では以下のようなことが可能です。

ダウンロードは下記ページにて行うことができます。

何か問題がありましたらブログへのコメントやメールにてお知らせいただければと思います。 要望とかもお待ちしております。 なお、ver.0.6.3 および ver.0.6.4 は Firefox 3.0 - 3.6 に対応しています。

AppLauncher ver.0.6.3 公開 (2010-01-23)

AppLauncher ver.0.6.2 までは Firefox 3.5 までに対応していましたが、Firefox 3.6 がリリースされたことを受けて Firefox 3.0 - 3.6 に対応した ver.0.6.3 を公開しました。
また、ver.0.6.3 よりアドオンの自動更新に対応しました。

AppLauncher ver.0.6.4 公開 (2010-02-14)

AppLauncher ver.0.6.3 では 「現在表示しているページの URL」 と 「現在表示しているページの URL を URI エンコードしたもの」 を設定項目内に指定することができましたが、「リンクを右クリックしてコンテキストメニューを開いたときに、そのリンク先 URL」 も設定項目内に指定したい、という要望があったため、それを可能にしました。
設定項目内に "&turl;" と書くと、リンクを右クリックしてコンテキストメニューを開いた場合はそのリンク先 URL が、そうでない場合は現在表示しているページの URL に置換されるようになりました。

AppLauncher ver.0.6.2

Firefox 拡張機能 AppLauncher のバグを見つけたので修正しました。

AppLauncher とは

AppLauncher は Firefox拡張機能です。 右クリックメニュー (コンテキストメニュー) から外部アプリケーション (Firefox 以外のブラウザだとかその他色々) を起動することを目的に開発しています。

とりあえず現状では以下のようなことが可能です。

AppLauncher ver.0.6.2 公開

Ver.0.6.1 において、インストールした直後に初めて設定変更を行おうとすると例外が発生するというバグを発見しました。 これは、ユーザ設定がないにもかかわらず文字列があるつもりで処理をしていたことが原因です。 同じ原因により、インストール直後に右クリックから "AppLauncher" の項目を選択しても、サブメニューが開かないという問題も発生していました。

この問題を修正したものを ver.0.6.2 として公開しました。 ダウンロードやインストールは下記ページよりお願いします。

何か問題がありましたらブログへのコメントやメールにてお知らせいただければと思います。 要望とかもお待ちしております。 なお、ver.0.6.2 は Firefox 3.0 - 3.5 に対応しています。

AppLauncher ver.0.6.1

本日院試の結果発表があったのですが、無事合格していました! 良かった良かった。 しかし友達が 2 人不合格だったのでちょっと悲しいです。 ついでに農工大から受けに来てた人もダメだったみたいで・・・うう。

んで今日は Firefox 拡張機能 AppLauncher のバグフィクスをしたので公開します。

AppLauncher とは

AppLauncher は Firefox拡張機能です。 右クリックメニュー (コンテキストメニュー) から外部アプリケーション (Firefox 以外のブラウザだとかその他色々) を起動することを目的に開発しています。

とりあえず現状では以下のようなことが可能です。

* 現在見ているページを IETester や IEOperaSafari などで開く
* 現在見ているページをはてなブックマークなどに追加する (ためのページを Firefox で開く)

AppLauncher ver.0.6.1 公開

Ver.0.6 では、設定項目の中に日本語 (というか非 ascii 文字) が混ざっていると正常に設定が保存されないという問題があったのですが、ver.0.6.1 ではその問題を解決しました。 ダウンロード、インストールは下記リンクよりお願いします。

何か問題がありましたらブログへのコメントやメールにてお知らせいただければと思います。 要望とかもお待ちしております。 なお、ver.0.6.1 は Firefox 3.0 - 3.5 に対応しています。

Firefox 拡張機能の設定項目の保存時に日本語が化ける

Firefox拡張機能でユーザに設定項目を入力させる場合、その設定内容は nsIPrefBranch を使って保存したり読み込んだりすることが多いと思います。

参考: 4章:XPCOM活用術〜高度な処理を実現する〜 (Firefox 拡張機能開発チュートリアル)

んで文字列を保存するときに、日本語等の非 ascii 文字も問題なく保存、読出しができるものだと思っていたのですが違うみたいですね。 以下のコードでテストしました。

/**
* 設定保存, 読出しで日本語が化けないかどうかのテスト
*/
var test = function() {
// 設定の情報を取得する XPCOM オブジェクトの生成
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var prefBranch = prefSvc.getBranch("extensions.applauncher.");
// 保存
prefBranch.setCharPref("test", "日本語テスト!");
// 読み出して、アラートで表示
// 実際はタイプ別に取得する関数を分けた方がいい (と思う) が, ここでは省略
window.alert( prefBranch.getCharPref("test") );
};

"日本語テスト!" と表示されるべきところですが、実際はよくわからん文字 (化けた文字) が表示されました。 そんなわけで日本語はそのままじゃ保存できないみたいです・・・。

日本語もちゃんと保存させるために

以下のように URL エンコード + エスケープすれば良いみたい。 4章:XPCOM活用術〜高度な処理を実現する〜 (Firefox 拡張機能開発チュートリアル) に書いてあるそのまんまですが。

/**
* 設定保存, 読出しで日本語が化けないかどうかのテスト
*/
var test = function() {
// 設定の情報を取得する XPCOM オブジェクトの生成
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var prefBranch = prefSvc.getBranch("extensions.applauncher.");
// 保存
prefBranch.setCharPref( "test", unescape(encodeURIComponent("日本語テスト!")) );
// 読み出して、アラートで表示
// 実際はタイプ別に取得する関数を分けた方がいい (と思う) が, ここでは省略
window.alert( decodeURIComponent(escape( prefBranch.getCharPref("test") )) );
};

AppLauncher ver.0.6

8 月 10 日に無事大学院の入試が終了しました! その後は最高で 40 度の熱が出てしまい寝込んでいたのですが、さすがに寝すぎて 「これ以上は寝られない! 暇!」 という状態になったので昨日の夕方から暇つぶしに AppLauncher の開発を再開しました。

AppLauncher に対する需要があるのかどうか謎ですが・・・。 まあ自分が便利に使えたらいいよね、なんて。

AppLauncher とは

AppLauncher は Firefox拡張機能です。 右クリックメニュー (コンテキストメニュー) から外部アプリケーション (Firefox 以外のブラウザだとかその他色々) を起動することを目的に開発しています。

とりあえず現状では以下のようなことが可能です。

AppLauncher ver.0.6 公開

昨日の夕方からせこせこ開発してましたが、ひと段落ついたので ver.0.6 として公開しました。 ダウンロードは以下のページよりお願いします。

何か問題がありましたらブログへのコメントやメールにてお知らせいただければと思います。 要望とかもお待ちしております。 なお、ver.0.6 は Firefox 3.0 - 3.5 に対応しています。

熱があるのにプログラミングするもんじゃないよね

熱があるのに暇つぶしにプログラムを書いてたわけだけど、ホントしょーもない間違いが多すぎて泣きそうになりました。 普段なら絶対しないようなミス。

文字列を二重引用符でくくるのを忘れたり、「function」 と書くつもりで 「file」 と書いてたり。 熱があるのにプログラミングなんてするもんじゃないよね!

XUL アプリ: 新しくページを読み込んだ時にコードを実行する方法

普通、web ページ内に置いた JavaScript コードがページ読み込み完了時に処理を行うためには、

window.addEventListener("load", func, false);
などと書きます。

が、XUL アプリケーションで上のような書き方をすると、ページ読み込み時ではなく XUL アプリケーションのロード時に処理 func が行われます。 ではどうすればいいかというと、window オブジェクトにイベントリスナを追加するのではなく、ブラウザのページ表示領域 (id="appcontent" の要素) に DOMContentLoaded イベントをキャッチするイベントリスナを追加すればよいのです。

var myExtension = {
init: function() {
// ページの表示領域取得
var appcontent = document.getElementById("appcontent");
if( appcontent ) {
// DOMContentLoaded イベントをキャッチするイベントリスナを追加
appcontent.addEventListener("DOMContentLoaded", myExtension.listener, true);
}
},
// DOMContentLoaded イベントをキャッチするイベントリスナ
listener: function( aEvent ) {
// "doc" はイベントが発生したドキュメント
var doc = aEvent.originalTarget;
// ここで処理を行う
}
}

window.addEventListener("load", function(evt) { myExtension.init(); }, false);

詳しくは MDC のページ読み込み時に処理を行う方法に関する文書 を見てください。

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]

設定画面の作成

可変個数の設定を編集できるように、上図のような設定画面を作成します。 "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 を起動させるという機能) が完成しました。 まだまだ改良の余地はありますけどちまちま改造していこうかと思います。