Extentsion開発日誌:XBLに手を出す

めずらしく長続きしてモチベーションが衰えることなく開発中。経緯とか何を作っているかはhogehoge - Extension開発中を見てちょ。
UI向上のためにtextboxがフォーカスされている時にCtrl + SPACEでメニューを出したくなっただが、onkeypressでイベントを取ってキーコードがどうたらとかが面倒だったのでXBLに手を出してみた。

XBL

あたりを参考にした。

<?xml version="1.0" encoding="utf-8"?>
<bindings xmlns="http://www.mozilla.org/xbl"
    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="editorbox" extends="chrome://global/content/bindings/textbox.xml#textarea">
    <handlers>
        <handler event="keypress" modifiers="control" key=" "><![CDATA[
            var menu = document.getElementById('editToolsPopup');
            menu.showPopup(this,this.boxObject.screenX,this.boxObject.screenY,"popup",null,null);
        ]]></handler>
    </handlers>
</binding>
</bindings>

Ctrl + SPACEkeypressイベントが発生すると後述のXULのpopup要素がポップアップされる。既存のXBLも継承するためにDOM Inspectorで調べてextends属性も追加しておく。
最初、keycode="VK_SPACE"で取ろうとしていたが何度やってもイベントを取れないので闇雲にkey=" "でやってみたら何故か取れた。SPACEは特殊キーだと思ってわざわざVK_SPACEでやっていたオイラは一体....。
本当はポップアップメニューの位置をマウス位置にしたかったけど、keypressのイベントではどうも位置を取れないようで諦めた。

XULの一部

<popup id="editToolsPopup">
    <menuitem label="Order List" class="heAddOL" accesskey="o" oncommand="editSelection('+');"/>
    <menuitem label="Unorder List" class="heAddUL" accesskey="u" oncommand="editSelection('-');"/>
    <menuitem label="Add Date" class="heAddDate" accesskey="d" oncommand="editSelection('date');"/>
    <menuitem label="Add Time" class="heAddTime" accesskey="t" oncommand="editSelection('time');"/>
</popup>
...
<textbox id="hatena-edit" multiline="true" flex="1"/>

editSelection(hoge);はフォーム中のカーソル位置を取得し、行頭に+やら-を追加するJavaScript関数。
それぞれにaccesskeyを割り当てる。これでCtrl + SPACE,oで行頭に+が追加されると言う具合。

CSSの一部

あとはCSSでバインドしておけばOK。

#hatena-edit {
	-moz-binding: url(chrome://hatenaeditor/content/textbox.xml#editorbox);
}

JavaScriptの一部

editSelection(hoge);の中身。Firefoxのみを考えれば良いからテキストボックス中のカーソル位置とかの取得が楽チンだね。

var edit = document.getElementById('hatena-edit');
...(省略)...
function editSelection( str ) {
    var text = edit.value;
    var start = edit.selectionStart;
    var end = edit.selectionEnd;
    var startText = text.substring(0, start);
    var endText = text.substring(end, text.length );
    var sel = text.substring( start, end);
    switch ( str ) {
        case '-':
        case '+':
            if ( start == end ) {
                var startLine = startText.split('\n');
                var endLine = endText.split('\n');
                var currentLine = str + startLine.pop() + endLine.shift();
                edit.value = startLine.concat( currentLine, endLine).join('\n');
            } else {
                edit.value = [ startText, '\n'+str+sel+'\n', endText ].join('');
            }
            break;
        case 'date':
      // yyyy-mm-dd を挿入
            ...(省略)...
            break;
        case 'time':
            // HH:MM:SS を挿入
            ...(省略)...
            break;
    }
}