BathyScaphe ネタ(その3)

Finder の振る舞い

Finder のカラム表示で、コンテキストメニューを出してみよう。ただし、今選択されている項目とは、違う項目を直接 control-クリック(ないしは右クリック)して、出してみる。すると、選択項目は変わらずそのまま選択状態を保ちつつ、クリックした項目がコンテキストメニューの対象として青い枠で囲まれる(下図)。



Finder のこの振る舞いはなかなか良くできていると日頃から感心しているものの一つだ。つまらないエピソードにも見えるが、こういう振る舞いをするアプリケーションは意外にも少ない。普通は、選択されている項目と違う項目に対してコンテキストメニューを出そうとすると、まずその項目が選択されてしまうものが多いのだ。

BathyScaphe でも真似したい

もっとも、ほとんどのケースでは「メニューを出す項目を選択状態にする」振る舞いで特に困ることもないし、十分だと思うが、時々選択状態を変えて欲しくない状況というのもある。BathyScaphe の掲示板リストがその例だ。コンテキストメニューを出そうとすると、選択項目が移動して、つられてスレッド一覧の読み込みが始まってしまう*1。場合によっては、これが煩わしいと感じていた人も居たのではないだろうか?


そこで、さっきからごちょごちょと Finder-like な振る舞いへの改造工事を行っていた。この改造のキーポイントは以下の三つだ:

  1. 右クリックされた項目を枠で囲む
  2. コンテキストメニューのアクションを処理する時は、selectedRow が使えない
  3. 用が済んだら枠を消す必要がある


1. はそう難しくない。NSFrameRectWithWidth と rectOfRow: の合わせ技で枠を描いてやればよかった。2. は、クリックされた列のインデックスをビューに保持させるようにして、それを見に行くようにすることでなんとかなった。さて、3. はどうするか……?正確には、「いつ枠を消せばいいのか」ということだ。メニューのアクションが実行された後か?いや、それでは不十分である。なぜなら、メニューで何も項目を選択しないまま(=何のアクションも発しないまま)、メニューを閉じてしまうかもしれないからだ。

そこで今日の素敵な収穫

…と考えつつ、NSMenu のドキュメントを探したら、ぴったりのものを見つけた!

NSMenuDidEndTrackingNotification


Posted when menu tracking ends, even if no action is sent. The notification object is the main menu bar ([NSApp mainMenu]) or the root menu of a popup button. This notification does not contain a userInfo dictionary.

これを捕まえて、メニューが消えたときに枠を消してやればよい。ちなみにこの Notification は Mac OS X 10.3 以降で追加されたもの。やぁ、ありがたやありがたや。全体的に Mac OS X 10.3 では、こう、かゆい所に手が届くような API の追加/拡張 が多く行われたようなイメージを抱いているのだが、今日、ますますそんな思いを強くした次第。


そんな訳で、(まだ細かいバグが残っているのだが追記:メニュー項目の validation にミスがあったのだが、さっき直した)Finder-like なコンテキストメニューの振る舞いを掲示板リストに実装できたと思う。リリースノートには「コンテキストメニューの処理を改善しました。」の一行しか書くことがないようなことだけど、裏には結構時間がかかってたりするものだという訳で、長々と今日の日記ネタにしてみましたとさ。おしまい。

*1:SledgeHammer 以降の場合。1.0.2 以前では選択項目が移動しないかわりに、どこでクリックしても選択項目に対してコンテキストメニューが作用してしまうので、もっとよろしくない