タイマーウィンドウにも QuickConfig をつけよう。
範囲選択(もしくはウィンドウ)用の設定メニューが開く。
選んでいる間はタイマーはストップするようにしている。
2009年2月14日土曜日
2009年2月13日金曜日
QuickConfig - プリファレンス設定をその場で
時々キャプチャ直前にプリファレンスの設定を変えたい場合がある。頻繁に設定を変える場合もいちいちプリファレンスを開くのがおっくう。要望も出たので対応することにする。何か名前があると開発しやすいので Quick Config(クイックコンフィグ)と名付けてみた。
ユーザインターフェイスだが、例えば範囲選択なら枠線が表示された状態で設定ができると良い。そこで専用のボタンを用意して、これを押すと設定メニューが開くようにしてみた。こんな感じ。
メニューの内容はプリファレンスで設定できるものと同じ。
少し独特なインターフェイスになってしまった。他のボタンの動作と違ってメニューが開くことを伝えたいので白点を加えてみた。![]()
実装は Interface Builderであらかじめメニューを用意しておき、コードからこれを参照してポップアップさせる。
[NSMenu popUpContextMenu:[_app_controller selectionConfigMenu] withEvent:event forView:view];
メニューの各値はおなじみの Cococa Bindings で UserDefaultsと紐づけてある。あいわらず楽だ。
2009年2月12日木曜日
ウィンドウを前へ、前へ、一番前へ
プリファンスウィンドウを開くと一番上に来ないことがある。キャプチャ中に設定を変えようと呼出すが現れない。他のウィンドウに埋もれて出てこないこともたびたび。
不便と思いつつなんとなく放っておいたが表示個所を見直してみた。今はプリファレンスを開くのに NSWindow#makeKeyAndOrderFront: を使っている。
[_window makeKeyAndOrderFront:self];
このメソッドでは弱くて?他のウィンドウに負けてしまうようだ。
もう少しマニュアルを眺めていると NSWindow#orderWindow:relativeTo: というメソッドを見つけた。これを代わりに使ってみよう。
[_window orderWindow:NSWindowAbove relativeTo:0];
マニュアルによれば上記引数の組み合わせで一番上に来るらしい。
結果は○。一番上に表示されるようになった。

さてこれで解決と思いきや、範囲選択中にプリファレンスを開くとまた埋もれたまま一番上に出てこない。

今度は同じアプリ内での順番が問題になっているようだ。再び #makeKeyAndOrderFront: を持ち出し、#orderWindow:relativeTo: と一緒に使う。
[_window makeKeyAndOrderFront:self];
[_window orderWindow:NSWindowAbove relativeTo:0];
よさそうだ。ただ使っているとやっぱり上に来ないケースがある。どうもアプリがアクティブにならないと駄目なようだ。
そこで最終手段?である。NSApplication#activateIgnoringOtherApps: を使う。
[NSApp activateIgnoringOtherApps:YES];
[_window makeKeyAndOrderFront:self];
これは強力で試した限りでは必ず一番上に来た。難点は他のアプリからアクティブ状態を奪い取ってしまうことだが、そもそもプリファレンスで設定を行えばそうなるのでこれは問題ないか。
しばらくこれで使ってみることにしよう。
2009年2月11日水曜日
Cocoa Bindingsを使ったメニューの排他選択
前回 SimpleViewerにコンテキストメニューを追加したが、この中で背景は Black, Checkboard, White の3つから一つ選択するようになっている。
例えば Blackにチェックがついている状態でCheckboardを選ぶと、Balckのチェックが外れ Checkboardにチェックが付く。これを実装するには少し工夫が必要で以前はTarget/Actionを使って力技で対処した。
Cocoaの日々: simpleCapへコンテキストメニューを追加 (2)
今回はTarget/Actionではなく Cocoa Bindings で対処してみた。
NSMenuItemの Value へモデルを紐付け(バインディイング)しておくと、その値に応じてメニューにチェックが付いたり、外れたりする。例えば下の図では NSMenuItem "Balck" に backgroundBlack というモデルを紐づけてある。モデルのオーナーは "Simple Viewer Controller" にした。これは InterfaceBuilder上でインスタンス化してあって扱いやすいため。同様に "Checkboard"には backgroundCheckboard、"White"には backgroundWhite という名前を紐づけておく。
次はモデルの実装を行う。Cocoa Bindings では Key-Value-Codingの仕組みを採用しているので実体はなんであれ setter/getter を用意してやるとそれが使われる。先ほどの backgroundBlack というモデルの場合、下記のようなインターフェイスを用意すればいい。
- (BOOL)backgroundBlack;
- (void)setBackgroundBlack:(BOOL)flag;
同様に他のモデルも用意してやる。
- (BOOL)backgroundWhite;
- (void)setBackgroundWhite:(BOOL)flag;
- (BOOL)backgroundCheckboard;
- (void)setBackgroundCheckboard:(BOOL)flag;
これでメニューが表示される時、もしくは選択された場合は自動的にこれらのメソッドが呼出される。
さて実装だが、背景の値はUserDefaultsで管理している。この実体は整数値で 0=Black, 1=Checkboard, 2=Whiteと割当てある。
それを踏まえた上で backgroundBlackの実装を見てみよう。まずメニューを表示する時には現在の値をチェックする為に getter である #backgroundBlack が呼出される。チェックを付けるか否かは背景値が 0(=Black)かどうかで判断できる。こんな感じ。
- (BOOL)backgroundBlack{
return ([[UserDefaults valueForKey:UDKEY_VIEWER_BACKGROUND] intValue] == 0);
}次はメニューで選択された時。この場合は setterにあたる #setBackgroundBlack: が呼ばれる。
- (void)setBackgroundBlack:(BOOL)flag
{
if (flag) {
[UserDefaults setValue:[NSNumber numberWithInt:0] forKey:UDKEY_VIEWER_BACKGROUND];
[self setBackgroundCheckboard:NO];
[self setBackgroundWhite:NO];
[UserDefaults save];
} else if ([[UserDefaults valueForKey:UDKEY_VIEWER_BACKGROUND] intValue] == 0) {
[self setBackgroundCheckboard:YES];
}
}
もし flag==YES ならチェックされたことになるので、他の checkboard, white の値を NOにする。これが排他処理のポイント。そして後半の else if は、元々チェックが付いている状態で選択がされた時の処理を行っている。何もしないと見た目はチェックが外れてしまうので、今回は次の checkboardにチェックが付く様にしておいた。blackからチェックを外さないというインターフェイスもあるが、今回のBindingsだけを使う方法では難しい。特に害は無いと思うので今回はこれで行こう。
他の2つの設定についても同じ処理を書けばできあがり。
- - - -
もっとスマートな方法があると良いのだが
今はこれが精一杯か。
(良い方法があれば是非教えて欲しい)
2009年2月10日火曜日
SimpleViewer - コンテキストメニュー
SimpleViewerにコンテキストメニューをつける。
枠線の表示や背景の切り替えはプレファレンスで行っていたが、これらは画像を見ながら切り替えができた方がいい。ついでに直前の方法によるキャプチャと(ペーストボードへの)コピーも入れてみよう。
投稿者
xcatsan
時刻:
6:10
ラベル: cocoa, SimpleViewer
2009年2月9日月曜日
2009年2月8日日曜日
デスクトップアイコンを消す(その1)Selection編
シェアウェアにデスクトップアイコンを一時的に消すソフトがあるらしい。面白い。SimpleCapでもこれをやってみよう。
まずは Selectionから。
プリファレンスで制御できるようにする。

実際にキャプチャしてみよう。まずは通常のキャプチャ。デスクトップアイコンが写っている。
プリファレンスで "Exclude Desktop Icons"を選びキャプチャ。アイコンがそっくり消えている。
- - - -
デスクトップアイコンを消すのは簡単で CGWindowListCreateImage( )関数の CGWindowImageOption引数へ kCGWindowListExcludeDesktopElements を加えれば良い。ただこのオプションは背景画像まで取り除いてしまう。
そこでこのオプションを使ってキャプチャした後、背景画像を合成してやる。デスクトップ画像は以前マルチスクリーン対応で用意した DesktopWindowクラスを使うと比較的容易に作れる。
NSArray* desktop_window_list = [[DesktopWindow sharedDesktopWindow] CGWindowIDlist];
CGWindowID *windowIDs = calloc([desktop_window_list count], sizeof(CGWindowID));
int widx = 0;
for (NSNumber* num in desktop_window_list) {
windowIDs[widx++] = [num unsignedIntValue];
}
CFArrayRef windowIDsArray = CFArrayCreate(kCFAllocatorDefault, (const void**)windowIDs, widx, NULL);
CGImageRef cgimage_desktop = CGWindowListCreateImageFromArray(NSRectToCGRect(_rect), windowIDsArray, kCGWindowImageDefault);
これを別途キャプチャした画像と合成すれば良い。 こんな感じ。
NSBitmapImageRep *bitmap_desktop = [[[NSBitmapImageRep alloc] initWithCGImage:cgimage_desktop] autorelease];
NSImage* image_desktop = [[[NSImage alloc] init] autorelease];
[image_desktop addRepresentation:bitmap_desktop];
[image_desktop lockFocus];
[src_image drawAtPoint:NSZeroPoint
fromRect:NSZeroRect
operation:NSCompositeSourceOver fraction:1.0];
[image_desktop unlockFocus];
src_image = image_desktop;



