ページ

2009年1月20日火曜日

内容によってウィンドウのサイズを変える

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

SimpleCapのプリファレンスは NSTabViewを使っていて、ツールバーによって表示内容を切り替えている。


ただウィンドウのサイズを固定にしているので、表示内容が少ない時は間抜けな状態になる。



Mail.app などでは表示内容の大きさに合わせて、ウィンドウの大きさ(高さ)を調節している。




これを SimpleCapのプリファレンスでも実現したい。まずはサンプルを作って試してみよう。
まずは動いたところから。

2つのタブがあり、初期状態はボタン「A」が表示される。ここで2番目のタブを選択してみる。


するとボタン「B」が表示され、ウィンドウはそれにあった大きさに縮む。


考え方は次の通り、「A」表示から「B」表示へ移る直前は次のような状態になっている。


座標系は左下が原点なのでウィンドウのY座標は Wy となっている。「B」ボタンのビュー内の位置を (Vx, Vy)とする。ビュー内も原点は左下となる。一方、ウィンドウの大きさが伸縮する場合左上が固定点になるので、このY座標を origin_y とする。これはウィンドウのY座標 Wy と高さから求めることができる。高さはInterfaceBuilderで指定した初期値(すなわち最大の高さ)をとっておきこれ (_window_size)を使う。

さてこれをどう縮ませるか。縮んだ時の状態が次の図。


ウィンドウの高さは、ボタン「B」のY座標の Vy 分だけ縮む。これにマージン(MARGIN_BOTTOM)を考慮すると縮む高さ diff_y が求められる。これを初期高さ _window_size.height から引けば、縮んだ後の最終的な高さがも止められる。

一方、ウィンドウは左下が原点の座標系を使っているので、左上固定で縮む場合、ウィンドウのY座標も変更する必要がある。新しいウィンドウのY座標 Wy' は origin_y を求めておけば、ここから縮んだ後の高さ(_window_size.height - diff_y)を引けば求められる。


これで縮むウィンドウができた。

なお NSWindowのサイズ指定にはいくつかメソッドがあるが、setFrame:display:animate: を使うとサイズの変更がアニメーションで表示される。


最後にソースコード解説。
ElasticWindow.h

@interface AppController : NSObject {

IBOutlet NSTabView* _tab_view;
IBOutlet NSWindow* _window;

NSSize _window_size;
}
@end

IBのアウトレットでタブとウィンドウをつかんでいる。_window_sizeは初期のウィンドウサイズ。


ElasticWindow.m
@implementation AppController

#define MARGIN_BOTTOM 10

- (void)update
{
NSTabViewItem *item = [_tab_view selectedTabViewItem];
NSArray* subviews = [[item view] subviews];

NSRect view_frame = NSZeroRect;

for (NSView* view in subviews) {
view_frame = NSUnionRect(view_frame, [view frame]);
}

NSRect window_frame = [_window frame];

CGFloat diff_y = view_frame.origin.y - MARGIN_BOTTOM;

CGFloat origin_y = window_frame.origin.y + window_frame.size.height;

window_frame.size.height = _window_size.height - diff_y;
window_frame.origin.y = origin_y - window_frame.size.height;

[_window setFrame:window_frame display:YES animate:YES];
}

- (void)awakeFromNib
{
_window_size = [_window frame].size;
[self update];
}


- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
[self update];
}

@end

updateメソッドが今回のキモ。上記で解説したアルゴリズムが実装されている。ビュー内のコントロール位置はサブビューを subviewsで取り出して NSUnionRectで最大領域を求め使っている。


サンプル:ElasticWindow-1.zip

今回は長かった。。