Home > Latest topics

Latest topics > マルチプルタブハンドラで複数タブのドラッグ&ドロップを実現する仕組み

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

マルチプルタブハンドラで複数タブのドラッグ&ドロップを実現する仕組み - Feb 26, 2008

まーこんな記事誰も読みたがらんだろうとは思いますが。ここ数日取り組んでたマルチプルタブハンドラでの複数タブの同時ドラッグ&ドロップ処理について一段落付いたので、やったことのまとめを文章で残しておこうかなと。

さて。タブのドラッグ&ドロップの処理になんかの機能や処理を加えようと思ったら、まず思いつくのは、gBrowser(tabbrowser要素)をnsDragAndDropのオブザーバとして利用するために定義されているonDragStart、onDragOver、onDragExit、canDrop、onDrop、getSupportedFlavoursあたりのメソッドを上書きするというやり方。実際、ツリー型タブではそうしてる。Tab Mix Plusの場合はもいっこ上のレベル、ondraggestureなどのイベントハンドラを書き換えてnsDragAndDropに渡すオブザーバそのものを入れ換えるという事をやってるけど、まあ、これも要するに同じことですね。

ただ、マルチプルタブハンドラの場合はその方法は使いたくなかった、というか、使えなかった。自分の使い方ではツリー型タブとの組み合わせが必須なんだけど、そうなると、もしマルチプルタブハンドラの実装を同様の方法でやってしまったら、組み合わせて使うと二つの拡張機能が同じ場所(nsDragAndDrop用のメソッド)を互いに上書きしようとするわけで、これはもう衝突すること間違いなしなワケですよ。だから何としても別の方法を考えなきゃならんかった。

そこで考えたのが、TabMoveイベントを捕捉して「選択されたタブのうち一つが移動された」ことを検知し、あとから他のタブをそれに合わせて移動するというやり方です。

これを実際にやってるのは、multipletab.jsで定義してるMultipleTabServiceのmoveBundledTabsOfメソッド。当初の考えでは、こんな感じでやろうと思ってた。

  1. タブの移動が終わった直後にこのメソッドに処理が渡ってくる。
  2. TabMoveイベントのイベントオブジェクトのdetailプロパティで「移動されたタブの、移動前の位置」が分かるので、それを手がかりに、すべてのタブの「並べ替え前の位置」を計算する。
  3. 移動されたタブの「移動前の位置」と、選択された他のタブとの位置関係から、選択された他のタブそれぞれの「新しい配置場所」を計算する。
  4. 3で求めた位置にそれぞれのタブを実際に移動する。

テスト用のコードを書いて色々やってるうちに、2と3のステップは最適化して一つにまとめることができた。それが今あるMultipleTabServiceオブジェクトのcalculateDeltaForNewPositionメソッド。このメソッドに「移動されたタブの今の位置」「移動されたタブの元の位置」「選択されたタブの配列」を渡すことで、それぞれのタブの新しい配置場所を計算して、配列で返すという風になっている。

ただ、「新しい配置場所」を絶対配置で返そうとすると色々ややこしくなって破綻してしまった。タブを一つ移動するごとに他のタブのインデックスが変わるので、全部のタブの位置をあらかじめ求めておくというのは僕の脳味噌では不可能だったのですよアハハハ。なのでcalculateDeltaForNewPositionメソッドでは最終的に、「ユーザのドラッグ&ドロップ操作によって移動されたタブ」の位置を基準として、それぞれのタブを置くべき位置のオフセット値を返すようにしている。

言葉で言うより図で説明した方が早いね。

[A][B][C][D][E][F][G][H]

こういう8つのタブがあって、このうちDからHまでの5つが選択されているとしよう。ここでタブFをドラッグしてAとBの間に移動したとする。するとタブの配置はこうなる。

[A][F][B][C][D][E][G][H]

ここからmoveBundledTabsOfメソッドの処理が始まる。ここでcalculateDeltaForNewPositionメソッドに必要なパラメータを渡してやると、[0, 0, 1, 2]という配列が帰ってくる。この配列の値はそれぞれ、D、E、G、Hのタブに対応した「移動先インデックスのオフセット値」だ。

ではまずDを移動してみる。Dのオフセット値は0なので、Fの現在のインデックス「1」にそれを足した「1」が、Dの新しいインデックスになる。

[A][D][F][B][C][E][G][H]

はい、Dが無事にFの左隣に来ました。Dの新しいインデックスを求める時は、Fその他のタブがスライドして移動したあとの最終的なインデックスを求めなきゃいけない、っていうのがミソ。タブの配置のことがよく分からんうちは、「Fのインデックスが1で、その左隣に移動するんだから、新しいインデックスは0だ!」って思ってmoveTabTo(tabD, 0)とか書いてしまいがちだけど、それは大間違い……え、そんなアホな間違いをするのは僕だけですか?

次はタブEの移動。Eのオフセット値は0なので、Fの現在のインデックス「2」にそれを足した「2」が、Eの移動先になる。

[A][D][E][F][B][C][G][H]

はい、これで元々Fより左に並んでたタブが元と同じ順番で移動されました。次はGの移動。Gのオフセット値は1なので、Fの現在位置「3」にそれを足した「4」の位置がGの移動先になる。

[A][D][E][F][G][B][C][H]

最後、Hの移動。Hのオフセット値は2なので、Fの現在位置「3」にそれを足した「5」がHの移動先になる。

[A][D][E][F][G][H][B][C]

以上の通り、きちんとDからHまでのタブが元通りの順番でタブのドロップ先におさまりました。

マルチプルタブハンドラでは、ここでDからHまでのどのタブをドラッグしても、結果が同じなるようにしてある。calculateDeltaForNewPositionメソッドの返り値は、Fをドラッグ&ドロップした時は[0, 0, 1, 2]だけど、Dをドロップした時は[1, 2, 3, 4]になるし、Hをドロップした時は[0, 0, 0, 0]になる。タブを左から右にドラッグ&ドロップした場合も、また返り値は変化する。とまあこのように、面倒な計算はcalculateDeltaForNewPositionに丸投げして、moveBundledTabsOfはその返り値に従ってタブを移動していくだけ、という案配です。僕は頭の出来があんまり宜しくないので、このcalculateDeltaForNewPositionをうまく動くようにするだけでだいぶ難儀した。もう一度同じ物作れっていわれても、多分できないと思う……

以上が、同じウィンドウの中でのタブの移動の場合。同じウィンドウの中でCtrl-ドラッグによってタブを複製する(※Firefox 3の新機能)場合は、処理はmoveBundledTabsOfメソッドではなくduplicateBundledTabsOfメソッドに渡ってくる。これもやってることは基本的に同じで、違うのは以下の点だけ。

  • calculateDeltaForNewPositionに渡す引数のうち「移動前のインデックス」と「移動後のインデックス」が、「複製元のタブのインデックス」と「複製されたタブのインデックス」になる。
  • 配置(移動)するタブが、選択されたタブではなく、その複製のタブになる。

異なるウィンドウからのドラッグ&ドロップによるウィンドウ間のタブの移動も、やはりduplicateBundledTabsOfメソッドが行う(移動元ウィンドウから移動先ウィンドウにタブを複製して、移動元ウィンドウのタブを閉じる、という手順)。

ただ、duplicateBundledTabsOfに処理が渡ってくるようにするためには、「タブの複製」を検知する必要がある。ここだけは仕方なく、duplicateTabメソッドを上書きして独自のイベントを発行するようにし、そのイベントから「複製元のタブ」「複製先のタブ」を取得するようにしている。

……とまあこんな感じでめんどくさい事をしまくってやっとのことで完成したんだけど、自分がマルチプルタブハンドラを使う時は、十中八九、ツリー型タブと組み合わせて使うのがデフォなんですよね。で、そのツリー型タブはというと、ここで書いた処理をまるっきりスキップして、選択されたタブ(ツリー)の移動・複製・ウィンドウ間の移動処理を自分自身で実装してるんですよね。つまり僕の環境ではこのエントリで解説した処理が実際に走ることが理論上無い。まったくひどい話です。

分類:Mozilla > XUL, , , , , , 時刻:01:42 | Comments/Trackbacks (0) | Edit

Comments/Trackbacks

TrackBack ping me at


の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2008-02-26_multipletab.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2020年11月30日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき