カスタムなウィンドウを表示した状態でNSStatusItemをハイライトする

NSStatusItem は、その名の通りメニューバー (ステータスバー) の要素を表すクラスです。

Mac にはメニューバーに常駐するアプリがたくさんありますが、そのメニューバーに画像やビューを表示させたいときに NSStatusItem を使います。

// ステータスアイテムを作成する
NSStatusItem *statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];

// 画像やカスタムビューを設定する
statusItem.image = ... // 画像
statusItem.alternateImage = ... // ハイライト時に表示する画像

NSStatusItem には menu プロパティがあり、そこに NSMenu のインスタンスをセットしてあげることで、アイテムがクリックされた時にそのメニューを表示することができます。

f:id:questbeat:20130717213437p:plain

さて、アイテムをクリックすると青くハイライトされ、メニューが表示される、というのはお馴染みの動作かと思いますが、NSStatusItem は NSMenu が表示されている間だけしかハイライトされません。
なので、例えば NSMenu ではなくカスタムなポップアップウィンドウを自分で表示しようとした場合、NSStatusItem のハイライト状態を維持することができません。

ですが、これを普通にやってのけているアプリは結構あります。
例えば PopClip。

f:id:questbeat:20130717214958p:plain

Dropbox もそうですね。

f:id:questbeat:20130717222729p:plain

実際これらのアプリでどのように実装されているのかは分かりませんが、おそらく NSStatusItem の view プロパティにカスタムビューを設定して、そこにハイライト状態の画像を描画しているのだと思います。 (参考)

この実装はきっちりやろうとすると面倒で、あまり時間をかけたくない部分でもあるので、今後のためにモジュール化してみました。

questbeat/QBStatusItemView

使い方は簡単で、QBStatusItemView のインスタンスを NSStatusItem の view プロパティに設定するだけ。
あとはメニューを表示するべきタイミングでデリゲートメソッドが呼ばれます。

NSStatusItem *statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
statusItem.image = ...
statusItem.alternateImage = ...

QBStatusItemView *view = [[QBStatusItemView alloc] init];
view.delegate = self;

statusItem.view = view;

メニューアイテムのハイライトなんてどうでもいいと思われるかもしれませんが、標準の動作と少しでも違う箇所があれば意外と気になります。

例えば翻訳タブ。ハイライトのやり方がちょっと違う。

f:id:questbeat:20130717224929p:plain

そら案内。ハイライトしてほしい。

f:id:questbeat:20130717225113p:plain

もしこれから Mac のメニューバーアプリを作ろうとしている方がいれば、ぜひ気を配って頂きたい部分です。