たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
公開した後から言うのもナンだけど、横長のタブバーでツリー表示にはやっぱり無理があるよねぇ……横長の時は多段タブバー、縦長の時はツリー、というのが最強のような気がする。縦長で多数列とか横長でツリーとか、逆の組み合わせは正直言って使いにくいことこの上ない。設定UIからは削って隠し機能止まりにしといた方がよかったかも。「できる」というのと「するべき」というのとは別問題だ、ということを痛感した。
View Source in Tab。その名の通り、ソースの表示をタブで行うようにするだけという物。ツリー型タブの機能として加えようとしてて、よく考えたらこれツリー関係の機能関係なくね? と思ったので別途リリースすることにした。
作り始める前に「そういえばそんな名前のアドオンがすでにあったような気がするんだが……」と思って「View Source in Tab」で検索してみたけど見あたらなかったのでそれなりに作り込んでさあそろそろ公開するか、と思ったところでふと「Open Source in Tab」で検索してみたらあっさり見つかったし
まあそれでもせっかく作っちゃったから公開しておく。一応、Open Source in TabやView Source Choiceに比べて以下の点でView Source in Tabの方が高機能ではあります。
でも徹底的に探したわけではないので、これで後から「実はすでにView Source in Tabという名前のアドオンがあるんだが」という風に気がついたら、もうどうしようもない。→ありました
25日追記。無難なところで「Source Viewer Tab(ソース表示タブ)」に改名しておきました……
ツリー型タブのAPIと称して3つのメソッドの使い方を公開しました。以下のようにして使ってください。
// 一つだけタブを開く場合
if ('TreeStyleTabService' in window)
TreeStyleTabService.readyToOpenChildTab(gBrowser.selectedTab);
gBrowser.addTab('http://www.example.jp/');
// 複数のタブを開く場合
if ('TreeStyleTabService' in window)
TreeStyleTabService.readyToOpenChildTab(gBrowser.selectedTab, true);
gBrowser.addTab('http://www.example.jp/');
gBrowser.addTab('http://www.example.com/');
gBrowser.addTab('http://www.google.co.jp/');
if ('TreeStyleTabService' in window)
TreeStyleTabService.stopToOpenChildTab(gBrowser.selectedTab);
アドオンとかユーザースクリプトとかで、現在のタブに関連した情報を新規タブで開くような機能を持ってる物は、これを入れておくとユーザ(主に僕)が幸せになれると思います!
先日から作業してたものの成果がそれなりの形になったので、公開しました。
これだけでも当然使えるけど、僕としては情報化タブとマルチプルタブハンドラの二つと組み合わせて使うのがお勧めだと思ってます。
実際に組み合わせて使ってる様子:
元々、使い勝手のいいツリー表示アドオンが出てきたらそっち使う気でいたんだけど、TabKitが案外期待外れだった(←うわー言っちゃったー)ので一念発起して自分好みの物を作った。インデント幅を自動調整するようにしたとか、ドラッグ&ドロップでのタブの移動とツリー編集の挙動をIllustratorのレイヤツリー風にしたとか、TBEのタブツリーより完成度は地味に高くなってると思う。
突貫工事で作ったワリには案外ちゃんと動いてくれているなあ、というのが率直な感想。なめらかスクロールの実装やサブツリーの開閉まわりのコードをTBEからコピってきて使った箇所が少しだけあるけど、改めて今TBEのコードを見てみたら、タブのスクロール一つとってもとんでもなく面倒なことをしていたんだなあということに、我ながら改めて驚かされる。だって、普通にボックスの内容をスクロールさせるメソッドがなかったんですよ? バインディングを使って不可視のスクロールバーを取得して強引にスクロールの制御を行ってたとか根本的にバッドノウハウの塊すぎて、読み解くのも一苦労でした。あの頃から比べたらFirefoxの方でAPIを用意してくれてるところが増えてだいぶ楽になったもんです。
以下、検討事項。
一昨日から取りかかってる奴だけど、サブツリーの開閉を実装して、やっと実用的なレベルになってきた。ツリーの状態もタブセッションの情報の一部として記憶させているので、セッションをまたいでツリーを維持できるし、クラッシュした後の復帰でもちゃんとツリーが復元される、はず。
フォーカス制御やタブの親子関係の保持・取得についてはほとんど全部新規に書いたけど、ツリーの開閉関係はTBEの実装に基づいている。いやまあ、もちろんコードは新しく書き直してるんだけど、アルゴリズム(と言っていいのか?)の部分ね。
あともう一息だー
ようやく手を着けることにしました。TBEの機能を分割してFirefox 2向けに移植する作業の続きの中で一番めんどくさそうな、タブのツリー表示。Tab KitとかTab Treeとか、すでに新しくて素晴らしい物がどんどん出てきてるんだけど、まあ、マイペースで行くことにします。
サムネイルは情報化タブのもの。タブの縦置きは現在はVertigo任せ。公開版ではもちろん自前で縦置きするようにする予定だけど、今は「ツリー表示」のための部分に集中して開発するために敢えて放置してる。
親子関係の構築にあたっては、TBEでは「タブを開く」処理を乗っ取って引数でゴチャゴチャやってたんだけど、Tab Treeの発想が物凄くシンプルだったので、試しにそれベースで実装してみている。
TODO(優先順位が高い物から順に):
target
やwindow.open()
で開かれたタブも親子関係の中に組み入れるTBEに存在した、別のドメインのページを読み込んだ時にタブの親子関係を自動的に破棄するとかの細かい部分については、実装してみてはいたものの自分が使ってる限りでは全然役に立ったことがないので、サクッと省略の方向で。
昨日XULとCSSであれこれ頑張ってみていて浮き彫りになってきた、XULとCSSのバッドノウハウの話の続き。
前のエントリで書いたことを踏まえて、toolbarbuttonの中のlabelを、XBLとstackを使って影付きテキストに置換するということをやってみたんだけど、そうすると今度は、ボタンを押しても何も反応しなくなってしまったじゃあないですか。こりゃ大変だ。
よーく観察してみると、ボタンのラベル文字列の部分はクリックしても無反応(ボタンの外観はちゃんと「押した」風になるんだけど、commandイベントが発行されない。コードを色々書き換えてみたけど、clickイベントすら発行されなかった。)だけど、それ以外の部分――ボタンの枠線とラベル文字列の間の余白部分あたりをクリックすると、ちゃんとボタンを押したものと認識される、ということに気がついた。ということはやっぱり、stackを使って影付きテキストにしたlabel要素が癌だってことだ。
んで条件を変えつつ色々試してみたら、以下のことが分かった。
言い換えると、「完全に不透明で、且つ、positionがstaticであるlabel要素」をクリックした時にだけ、そのtoolbarbuttonがクリックされたものとして正しく認識されるようだ、ということ。
というわけで苦肉の策として、ボタン上にポインタが乗っている時(:hover状態)はpositionもopacityも初期値(staticで不透明)にする、という風にしてみたところ、やっとちゃんとボタンをクリックできるようになってくれた。positionがstaticということはz-indexでの重ね合わせ順の制御が効かなくなるんだけど、影の方のopacityが1になればstackの原則通り内容は文書中の登場順に描画されるようになるので、トータルでは問題なくなる。:hoverの時だけ影が濃くなる(不透明になる)のは、まぁ、:hover時の特別なハイライト表示にも見えるので、結果オーライと言えよう。
ちなみに、この問題はtoolbarbutton要素だけでなく、menubar要素直下のmenu要素内にあるlabelについても起こる。こちらについても同じような対策で行けるかと思って、事実ほとんどの所はそれで良かったんだけど、:hoverの時だけということにしておくと、展開されたメニューを選択した後に再びmenuの部分にポインタを載せた時に、positionとopacityのリセット用指定が反映されないという現象が起こってしまったので、代わりに[_moz-menuactive="true"]
という具合に内部処理用の属性を使ってみることにした。
そんな感じで、現行Geckoにはこういういやーなバグがまだまだ潜んでいて、案外苦労してしまうことが少なくない。そこら辺が改善された新しいバージョンっていつ出るのかなあ……
昨日XULとCSSであれこれ頑張ってみていて浮き彫りになってきた、XULとCSSのバッドノウハウいくつか。
まず一つ目。一般的なWebページ制作では、要素同士を重ね合わせるにはCSSのポジショニングを使うのが一般的で、XULでも同じ方法を使えるんだけど、もう一つの方法として、XULにはstackという要素がある。
通常、XULではhbox(内容が横に並ぶボックス)またはvbox(内容が縦に並ぶボックス)の2つのボックスをコンテナとして使い、縦横二方向のボックスの並びだけでGUIを作る、というのが原則となっている。この原則を覆すのがstackというコンテナ要素だ。多くのXULのコンテナ要素においては、内容に含めた要素のボックスが縦または横に並ぶのに対し、stackは、内容に含めた要素のボックスが同じ座標に重ねて表示される、という特徴がある。
チュートリアルではこれを使って影付き文字を作る例を示しているけれども、これを見て当然思いつくのが、影を半透明にして背景に自然に馴染ませたいという要求だろう。
<stack>
<description value="Shadowed"
style="color: black; opacity: 0.5;"
top="1" left="1"/>
<description value="Shadowed"
style="color: white;"/>
</stack>
ところが、この例は実際には意図通りに表示されてくれない。Geckoのバグだと思うけど、stackの中に一つでも半透明の要素があると、stack全体が半透明になってしまうのである。つまり、ここでは影だけを半透明にしたいのに、実際には前景の文字まで半透明になってしまって却って字が読みにくくなる。
で、色々試してみた結果、stackを二重にするとこの問題を回避できるっぽい、という事が分かった。
<stack>
<stack>
<description value="Shadowed"
style="color: black; opacity: 0.5;"
top="1" left="1"/>
</stack>
<description value="Shadowed"
style="color: white;"/>
</stack>
こうすると、内側のstackは全体が半透明になってしまうけど、外側のstackにはその影響は及ばないので、影だけを半透明にできる。(ここでは影のテキストが一つだけしかないけど、実際にはText Shadowと同様の方法で複数の薄い影をずらしながら重ねたかったということなので、stackを二重にしている。)
ただ、今度は入れ替わりに、要素の重ね合わせの順番がおかしくなるという問題が起こってしまった。XULのstackは先に入っている要素ほど下に、後に入っている要素ほど上に描画されるはずなんだけど、どういうわけか、半透明にした要素は不透明の要素よりも上に強制的に描画されてしまうようだ。
(……と思ってたんだけどもしかしたらこれは勘違いかもしれない。stack内の要素はtop属性とleft属性で場所を動かせるんだけど、もしかしたら、これらの属性がある場合は強制的にその要素がposition: relative
にされてしまって、通常のposition: static
な要素の上に表示されるようになってしまった、ということだけなのかも……これはまだ検証してない。)
そこでさらに、z-indexを使って重ね合わせの順番を指定してみたところ、やっと「半透明の影の上に不透明の文字を重ねる」という結果を得ることができた。
<stack>
<stack style="position: relative; z-index: 1;">
<description value="Shadowed"
style="color: black; opacity: 0.5;"
top="1" left="1"/>
</stack>
<description value="Shadowed"
style="color: white; position: relative; z-index: 2;"/>
</stack>
position: relative
を指定しておかないとz-indexの指定が効かない(z-indexはpositionがstatic以外の要素にしか効果がない)ので、それもセットで指定してある。
これでやっと問題を解決できたか、と思ってたんだけど、あろう事か今度はまたさらに別の問題にぶち当たってしまった。というところで、話は次のエントリへ続きます。
XPIパッケージ生成用のバッチファイルとかシェルスクリプトとかを更新した。
Windows用のバッチファイルはCygwinにおもくそ依存してます。Bash用シェルスクリプトは最初の行のパスさえ直せば多分他の環境でも普通に使えると思う(Ubuntu 6.06ではそのまま動いてる)。使い方は前に書いた解説を参照してください。
秀丸マクロ(Sync.mac、xul_updates.mac、xul_updates2.mac)はいずれもパスやファイルの構成を決め打ちで作ってるんで汎用性はないけど、まあ、似たようなことをやりたい人の参考になれば幸いです。(この辺もそのうちもっと汎用的なバッチファイルなりシェルスクリプトなりに置き換えたいんだけど……)
sedの使い方が少し分かってきたので、もうちょっと複雑なことができるようになりそう……かも?
Chromeウィンドウでは、window.open()
やwindow.openDialog()
の第3引数でalwaysRaised
フラグを指定することで、「常に最前面に表示」状態のウィンドウを開くことができる(通常のスクリプトではこのフラグを使うには特権が必要)。じゃあ、すでに開かれているウィンドウを最前面にすることはできないのか? というのが今日のお題。
フォクすけクロックを使ってみて、Firefoxのウィンドウの下に時計が隠れてしまうのは不便きわまりないと思ったので、これをどうにかしたかった。最初は、前述のalwaysRaised
を使った方法でいけるかなと思ったんだけど、Firefoxのアドオンとして動作する時はこれでいいけどXULRunnerアプリとして動作する時には「開く元の親ウィンドウ」が無いからこれじゃダメだ、と気がついた。
んでちょっと調べてみた所によると、どうもnsIXULWindowインターフェースのzLevel
というプロパティをいじることで、ウィンドウの重ね合わせの優先順位を動的に変更できるようだということが分かってきた。
以下のようにいくつかのインターフェースを経由することで、Chromeウィンドウ(nsIDOMWindow)からnsIXULWindowのインターフェースに辿り着くことができる。
var Ci = Components.interfaces;
var XULWindow = window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIXULWindow);
XULWindow.zLevel = Ci.nsIXULWindow.raisedZ;
zLevel
に指定可能な値はnsIXULWindowインターフェースにおいて定数プロパティとして定義されていて、以下の種類がある。
プロパティ | 実際の値 | 意味 |
---|---|---|
Ci.nsIXULWindow.lowestZ | 0 | すべてのウィンドウの最背面 |
Ci.nsIXULWindow.loweredZ | 4 | すべてのChromeウィンドウの最背面 |
Ci.nsIXULWindow.normalZ | 5 | 通常 |
Ci.nsIXULWindow.raisedZ | 6 | すべてのChromeウィンドウの最前面 |
Ci.nsIXULWindow.highestZ | 9 | すべてのウィンドウの最前面 |
lowestZ
とloweredZ
の違いは、前者がエクスプローラその他Windowsネイティブのアプリケーションのウィンドウも含めてすべての最背面になるのに対して、後者はあくまでFirefoxのウィンドウの中での最背面になるだけであるということ。loweredZ
を指定したChromeウィンドウが他のネイティブアプリのウィンドウの下にある時、そのChromeウィンドウをクリックすると、そのChromeウィンドウがネイティブアプリのウィンドウの上に表示されるようになると同時に、それに「押し上げられる」形で、FirefoxのウィンドウすべてがそのChromeウィンドウより前面に表示される。
highestZ
とraisedZ
もそれと同様に、後者を指定したウィンドウは上にネイティブアプリのウィンドウが重なりうるけど、前者にはいかなるウィンドウも上には重ならない……のかと思いきや、こちらはどっちを指定してみてもraisedZ
で期待される通りの挙動にしかならなかった(Firefoxのウィンドウの中での最前面になるだけで、他のネイティブアプリのウィンドウが前面にくると、その下に隠れてしまう)。これってバグ?
ちなみに、数値で1~3を指定した場合はloweredZ
(4)を指定したのと同じ挙動になるようだ。多分7~8はraisedZ
(6)と同じで、それ以上はすべてhighestZ
(9)と同じになるんだろうと思うけど……前述の通りhighestZ
とraisedZ
は実際の挙動に違いが全然無いので、それを確認することはできなかった。
あと、ずっと前にEz Sidebarでこの問題にぶち当たってから気になってるもののずっと直ってないみたいなんだけど、raisedZ
以上が指定されたウィンドウがあると、通常のウィンドウで開いたモーダルダイアログが親ウィンドウの下に潜ってしまうという問題が起こる。パスワードの入力を求めるダイアログ等がFirefoxのウィンドウの下に潜ってしまって使い物にならないので、これはマジで困る。どうにかならんものだろうか。
――ということで対策を考えてみた。
var XULWindow = window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIXULWindow);
var observer = {
observe : function(aSubect, aTopic, aData)
{
if (aTopic == "xul-window-registered") {
XULWindow.zLevel = Ci.nsIXULWindow.normalZ;
window.setTimeout(function() {
XULWindow.zLevel = Ci.nsIXULWindow.highestZ;
}, 250);
}
}
};
var ObserverService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
ObserverService.addObserver(MakeOnTopObserver,
"xul-window-registered", false);
新しいウィンドウが開かれる直前にウィンドウのzLevel
を元に戻して、ウィンドウが開かれた後にまた最前面に戻す。こうすれば最後の問題は回避できる。