ページ

2008年6月30日月曜日

タイマーダイアログ(その7)

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

次はタイマーの値を増減できるようにする。左下に「+」と「ー」のボタンを追加した。


このボタンを押した場合に次のような意味も持たせる。
・一時停止する
・初期値(サンプルでは10秒)も合わせて変更する

「自明な表示や操作は省き、経験則に従った最小限の操作で目的が達成できる」というのがユーザインターフェイスを作る上での私のポリシーなので、それ合わせて上記のようにしてみた。ただユーザインターフェイスは実際に使わないと分からないので、この辺りは何度か試行錯誤しながらブラッシュアップしていく。

ボタンの追加はPNG画像を用意しておなじみの(?)ThinButtonBar クラスを使う。影を付けたくなかったのでその拡張を加えてある。

サンプル:TimerDialog-05.zip

2008年6月29日日曜日

タイマーダイアログ(その6)

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

タイマー開始ボタンを押したら、一時停止ボタンへ差し替える処理を加える。

最初は開始ボタンで、


開始後、一時停止ボタンになる。


ソース:TimerDialog-04.zip


今回の動作を行わせる為に、ThinButtonBar へグループという概念を追加した。

まずボタンを作る際にグループ(文字列)を指定するようにした。

[_button_bar addButtonWithImageResource:@"icon_cancel"
alterImageResource:@"icon_cancel2"
tag:TAG_CANCEL_TIMER
tooltip:NSLocalizedString(@"CancelTimer", @"")
group:nil];

[_button_bar addButtonWithImageResource:@"icon_start_timer"
alterImageResource:@"icon_start_timer2"
tag:TAG_START_TIMER
tooltip:NSLocalizedString(@"StartTimer", @"")
group:@"START_PAUSE"];

[_button_bar addButtonWithImageResource:@"icon_pause_timer"
alterImageResource:@"icon_pause_timer2"
tag:TAG_PAUSE_TIMER
tooltip:NSLocalizedString(@"PauseTimer", @"")
group:@"START_PAUSE"];

上記では2番目(開始ボタン)と3番目(一時停止ボタン)が同じ "START_PAUSE"グループに所属する。


ThinButtonBar にグループを管理する _group_list を追加した。これを使い、ボタン登録時に同じグループのボタンが存在する場合は従来の管理リスト _list へは追加せず、_group_list へ追加するようにした。ボタンの描画やヒットテストは _list を対象としているので特に修正はいらない。

ボタンが押された場合、そのボタンがグループに所属する場合にはグループ内の次の順番のボタンに _listを置き換える。これで開始ボタンを押した後に一時停止ボタンに表示が変わる。
ThinButton.m

    :
NSArray* group_array = [_group_list objectForKey:[hitButton group]];

if (group_array) {
NSUInteger i = 0;
ThinButton* button;
for (button in group_array) {
i++;
if (button == hitButton) {
break;
}
}
if (i == [group_array count]) {
i = 0;
}
button = [group_array objectAtIndex:i];
[self exchangeButtonFrom:hitButton To:button];
}
    :


タイマー完了後は元の開始ボタンに表示を変える為に #resetGroup を呼出している。このメソッドはグループ内の0番目のボタンへ戻す処理を行う。
- (void)resetGroup:(NSString*)group
{
NSArray* group_array = [_group_list objectForKey:group];
if (group_array && [group_array count] > 0) {
ThinButton *new = [group_array objectAtIndex:0];
for (ThinButton *old in _list) {
if ([group isEqualToString:[old group]]) {
[self exchangeButtonFrom:old To:new];
}
}
}
}

2008年6月28日土曜日

SimpleCap (10) メニューキャプチャ時に背景を暗くする

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

メニューキャプチャ時に背景を暗くしてみた。

SimpleCapで"Menu"を選ぶ。


するとメニューを除き暗くなる。


メニューを開き少し待つと、メニューのキャプチャが取れる。


メニュー以外が暗くなると、メニューが注目されているのがわかるのでこの方法を取る事にしよう。
ただこの方法だとコンテキストメニューが取れないが、それは範囲選択+タイマーでキャプチャする(実用上問題ないだろう)。

2008年6月27日金曜日

SimpleCap (9) ボタンに色をつけてみる

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

ボタンは控えめな(渋い?)白と灰色をベースとした色調にしている。


だが、特にウィンドウキャプチャでは使っていると視認性が悪い。そこで色をつけてみた。


ちょっと安っぽい感じもするが、視認性は上がった。当面これで使ってみる。

2008年6月26日木曜日

タイマーダイアログ(その5)

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

数字でカウントダウンを表示してみる。TimerView からTimerController を参照するようにし、1秒毎に再描画させる。

描画コード。
TimerWindowView.m


- (void)drawRect:(NSRect)rect
{
    :
    :
int count = [_controller lastCount];
NSPoint p;

NSMutableAttributedString* str =
[[NSMutableAttributedString alloc]
initWithString:[NSString stringWithFormat:@"%d", count]];
[str addAttribute:NSFontAttributeName value:[NSFont fontWithName:@"Arial" size:72]
range:NSMakeRange(0, 1)];
[str addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor]
range:NSMakeRange(0, 1)];
NSSize str_size = [str size];
p.x = (wb.size.width - str_size.width) /2;
p.y = (wb.size.height - str_size.height) /2 - COUNT_STR_OFFSET;

[[NSColor whiteColor] set];

[NSGraphicsContext saveGraphicsState];
[_shadow set];
[str drawAtPoint:p];
[NSGraphicsContext restoreGraphicsState];

Arialフォント 72pt で数字を描く。描画位置はウィンドウの(おおまか)中心にくるように計算してある。

するとこんな感じになる。


- - - -
まずは最低限のものができた。次は現在別々になっている開始・一時停止ボタンをまとめてみよう。

2008年6月25日水曜日

タイマーダイアログ(その4)

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

アイコンを作図し、以前開発したThinButtonBarクラスを使ってボタン表示した。


TimerController に表示のコントロールもまかせる。
TimerController.m

- (id)init
{
self = [super init];
if (self) {
_interval = 1.0;
_times = 0;
_timer = nil;
[self reset];

_window = [[TimerWindow alloc] init];
_view = [[TimerWindowView alloc] initWithFrame:[_window frame]];
[_window setContentView:_view];
[_window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[_window makeKeyAndOrderFront:self];

_button_bar = [[ThinButtonBar alloc] initWithFrame:NSZeroRect];

[_button_bar addButtonWithImageResource:@"icon_start_timer"
alterImageResource:@"icon_start_timer2"
tag:TAG_START_TIMER
tooltip:NSLocalizedString(@"StartTimer", @"")];

[_button_bar addButtonWithImageResource:@"icon_pause_timer"
alterImageResource:@"icon_pause_timer2"
tag:TAG_PAUSE_TIMER
tooltip:NSLocalizedString(@"PauseTimer", @"")];

[_button_bar addButtonWithImageResource:@"icon_cancel"
alterImageResource:@"icon_cancel2"
tag:TAG_CANCEL_TIMER
tooltip:NSLocalizedString(@"CancelTimer", @"")];

[_view addSubview:_button_bar];
[_button_bar setDelegate:self];
[_button_bar setPosition:SC_BUTTON_POSITION_CENTER];
[_button_bar setButtonBarWithFrame:[_view frame]];
[_button_bar show];
}
return self;
}



ソース:TimerDialog-03.zip
- - - -
後はカウント値の表示か。

2008年6月24日火曜日

タイマーダイアログ(その3)

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

機能の実装から入る。インターフェイスは次のようにしよう。



TimerControllerは「停止」「実行中」「一時停止」に3種類の状態を持つ。状態遷移と各メソッドは次のように対応づけられる。


ソースは取り立てて難しいことはやっていないので割愛する。

動作確認の為に簡単な GUIを付けてみた。


よさそうだ。

ソース:TimerDialog-02.zip

- - - -
機能の実装ができた。後はこれを同表現するかだ。次回からデザインを入れていく。

2008年6月23日月曜日

タイマーダイアログ(その2)

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

角を丸くしてみた。それだけ。


- - - -
タイマーダイアログのクラス構成は次のようになる。機能の実装をまず優先して、見た目はその後変えていく事にしよう。

2008年6月22日日曜日

タイマーダイアログ(その1)

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

SimpleCap ではタイマーが重要な役割を果たす。範囲選択やスクリーン全体、メニューキャプチャ、そして今後予定しているアプリケーションキャプチャなど様々な場面でタイマーを利用する。今まではカウントダウンなどの視覚効果をきちんと実装してこなかった。各機能の実装が進んできたのでここらで共通に使えるタイマーのユーザインターフェイスを用意する事にする。これを「タイマーダイアログ」と呼ぶことにする。イメージとしてはダイアログの形態を取り、カウントダウン表示のほか、一時停止や秒数調整など簡単な操作が行えるようなものを考えている。表示は HUDのような半透明を考えている。ただしこれまで SimpleCapは白を基調としてきたのでこれを踏襲する。

今回はまず手始めにカスタムウィンドウ(ダイアログ)を作る。

カスタムの NSWindow と NSView を用意する。

TimerWindow.m

- (id)init
{
NSRect rect = NSMakeRect(100, 100, 400, 250);

self = [super initWithContentRect:rect
styleMask:NSTexturedBackgroundWindowMask
backing:NSBackingStoreBuffered
defer:NO];
if (self) {
[self setReleasedWhenClosed:YES];
[self setDisplaysWhenScreenProfileChanges:YES];
[self setBackgroundColor:[NSColor clearColor]];
[self setOpaque:NO];
[self setHasShadow:NO];
[self setIgnoresMouseEvents:NO];
}

return self;
}


NSTexturedBackgroundWindowMask を使うとウィンドウのどの部分でもドラッグして移動できるようになる。

まずは黒地と白枠を描く。
TimerWindowView.m
- (void)drawRect:(NSRect)rect {
// Drawing code here.
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.3] set];
NSRectFill(rect);
[[NSColor whiteColor] set];
NSFrameRectWithWidth(rect, 5);
[[NSColor lightGrayColor] set];
NSFrameRectWithWidth(rect, 0.5);
}


これらを AppController で生成し紐づける。
AppController.m
- (void)awakeFromNib
{
_window = [[TimerWindow alloc] init];
_view = [[TimerWindowView alloc] initWithFrame:[_window frame]];
[_window setContentView:_view];
[_window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[_window makeKeyAndOrderFront:self];
}



さて実行してみる。ドラッグして移動もできる。いい感じだ。


ソース:TimerDialog-01.zip

- - - -
次回は枠を丸く(RoundRect)にしてみる。

2008年6月21日土曜日

SimpleCap (8) ウィンドウキャプチャ改良

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

メニューが一段落したのでウィンドウキャプチャの改良にとりかかる。従来はマウスカーソルを動かすと、その下のウィンドウが白くなり、クリックするとキャプチャが取れていた。これはこれでシンプルなのが良かったが今後もう少し機能追加する上では物足りない。そこでユーザインターフェイスの統一も兼ねて範囲選択同様に右上にボタンを追加し、これを使ってキャプチャさせることにした。

こんな感じ。ウィンドウをクリックすると右上にボタンが現れる。その上で、ファイル保存、又はペーストボードコピーができるようになっている。


背後に隠れているウィンドウをクリックすると、隠れた部分が半透明で表示されるようにした。


今のところはタイマー機能は持たせていないが使い道がありそうなら今後追加する。

2008年6月20日金曜日

SimpleCap (7) メニューキャプチャ組み込み

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

SimpleCap へ今までのメニューキャプチャの検証結果を反映した。"Menu" で機能が利用できる。もちろんこのキャプチャも SimpleCap で行った。


今はまだソースレベルだが、メニューバーを含まないモードも用意した。



Spotlightもこんな感じ。

2008年6月19日木曜日

メニューのキャプチャ(その12)Dockのメニュー

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

griffin-stewie さんより、Dockのメニューがあるとの指摘を受けた。早速 WindowListを調査してみる。

WindowListのタイマーを仕掛けてDockのメニューを開いておく。


出た。layer=101で普通のメニューであることがわかる。特徴としては Owner nameが"Dock"になっていること。



前回のサンプルでキャプチャしてみる。案の定、メニューバーが入っている。



処理としてはコンテキストメニューと同じなので、OwnerName=="Dock"を利用してフラグを立てた後、処理を合流させてやる。
AppController.m

 // (2) search normal menus
for (i=0; i < CFArrayGetCount(window_list); i++) {
   :
NSString *owner_name = (NSString*)CFDictionaryGetValue(window, kCGWindowOwnerName);
if ([owner_name isEqualToString:@"Dock"]) {
is_dockmenu = YES;
}
   :
// (3) calcurate rect
if (is_contextmenu || is_dockmenu) {
rect_all = CGRectNull;
   :



さて再チャレンジしてみよう。

できた。


ソース:MenuCapture-08.zip

- - - -
今度こそメニューが制覇できただろうか。

メニューのキャプチャ(その11)タイトルバーのメニュー

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

メニューがまだあった。タイトルバーでコマンドキーを押すと出てくるやつ。


前回までのサンプルでキャプチャすると無関係なメニューバーまで入ってしまう。


WindowList で見ると普通のメニューそのもの(一番上のもの)


これはどちらかというとウィンドウキャプチャのオプションとして捉えるべきかもしれない。
今回は対応せずウィンドウキャプチャのところで考えよう。

2008年6月18日水曜日

メニューのキャプチャ(その10)Spotlight

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

前回まででメニューキャプチャも完成と思いきや Spotlight のキャプチャがうまくできない。またもや WindowList で原因を調査してみよう。

下図のように Spotlight を使っている状態でウィンドウ情報を取って見る。


Spotlight は2つ現れていて、表の中の一番上のウィンドウがメニューバーのアイコンを表している。表の下から2番目の Spotlightが結果表示のウィンドウを表している。これを見ると Layerは23で通常のメニュー(kCGPopUpMenuWindowLevel=101)と異なっている。


なるほど。だから今までのコードではうまくキャプチャできないのか。Layer=23がヘッダファイルに無いか調べてみる。
CGWindowLevel.h

#define kCGBaseWindowLevel  CGWindowLevelForKey(kCGBaseWindowLevelKey) /* INT32_MIN */
#define kCGMinimumWindowLevel CGWindowLevelForKey(kCGMinimumWindowLevelKey) /* (kCGBaseWindowLevel + 1) */
#define kCGDesktopWindowLevel CGWindowLevelForKey(kCGDesktopWindowLevelKey) /* kCGMinimumWindowLevel */
#define kCGDesktopIconWindowLevel CGWindowLevelForKey(kCGDesktopIconWindowLevelKey) /* kCGMinimumWindowLevel + 20 */
#define kCGBackstopMenuLevel CGWindowLevelForKey(kCGBackstopMenuLevelKey) /* -20 */
#define kCGNormalWindowLevel CGWindowLevelForKey(kCGNormalWindowLevelKey) /* 0 */
#define kCGFloatingWindowLevel CGWindowLevelForKey(kCGFloatingWindowLevelKey) /* 3 */
#define kCGTornOffMenuWindowLevel CGWindowLevelForKey(kCGTornOffMenuWindowLevelKey) /* 3 */
#define kCGDockWindowLevel CGWindowLevelForKey(kCGDockWindowLevelKey) /* 20 */
#define kCGMainMenuWindowLevel CGWindowLevelForKey(kCGMainMenuWindowLevelKey) /* 24 */
#define kCGStatusWindowLevel CGWindowLevelForKey(kCGStatusWindowLevelKey) /* 25 */
#define kCGModalPanelWindowLevel CGWindowLevelForKey(kCGModalPanelWindowLevelKey) /* 8 */
#define kCGPopUpMenuWindowLevel CGWindowLevelForKey(kCGPopUpMenuWindowLevelKey) /* 101 */
#define kCGDraggingWindowLevel CGWindowLevelForKey(kCGDraggingWindowLevelKey) /* 500 */
#define kCGScreenSaverWindowLevel CGWindowLevelForKey(kCGScreenSaverWindowLevelKey) /* 1000 */
#define kCGCursorWindowLevel CGWindowLevelForKey(kCGCursorWindowLevelKey) /* 2000 */
#define kCGOverlayWindowLevel CGWindowLevelForKey(kCGOverlayWindowLevelKey) /* 102 */
#define kCGHelpWindowLevel CGWindowLevelForKey(kCGHelpWindowLevelKey) /* 200 */
#define kCGUtilityWindowLevel CGWindowLevelForKey(kCGUtilityWindowLevelKey) /* 19 */

#define kCGAssistiveTechHighWindowLevel CGWindowLevelForKey(kCGAssistiveTechHighWindowLevelKey) /* 1500 */


あ、23が見当たらない。。

まあいいか。定義は無いが Layer=23を狙い撃ちしてみよう。下記コードを追加する。
   } else if (layer == 23) {
[menu_windows addObject:[NSNumber numberWithInt:window_id]];
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(window, kCGWindowBounds), &rect);
if (CGRectEqualToRect(rect_all,CGRectZero)) {
rect_all = rect;
} else {
rect_all = CGRectUnion(rect, rect_all);
}
is_spotlight = YES;
}

layer=23 の場合もキャプチャ対象とした上でフラグ is_spotlight を立てておく。

その上でその後のステータスバーの処理へ合流させる。
  } else if (is_status_bar || is_spotlight) {
        :



これだけでOK。結果はこんな感じ。


ソース:MenuCapture-07.zip
- - - -
これでメニューは制覇できただろうか。

2008年6月17日火曜日

メニューのキャプチャ(その9)ステータスバー

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

前回までのサンプルでステータスバーのメニューをキャプチャするとメニュー全体が含まれてしまう。


この場合は右上のステータスバーだけをキャプチャに含めたい。さてどうするか。

下図のようにステータスバーのメニューを出した状態で WindowListを調べてみた。



上2つが開いているメニューとサブメニューを表す。ステータスバーは Layer=25 ( kCGStatusWindowLevel ) で表される、ここでは4つのウィンドウが該当する。上から順番に次のようになる(名称は OwnerName)。

 SystemUIServer ステータスバーのアイコン群を表す(図では Spaces から「湖」※ユーザ名までが範囲)
 SimpleCap  これはステータスバー左のカメラのアイコンを表す
 Spotlight   これは一番右の Spotlightアイコンを表す
 SystemUIServer  最後のものは透明なウィンドウでキャプチャには無関係。 

開いているウィンドウがステータスバーに所属するかどうかを判断する材料がないか、WindowListをじっと眺めて共通項を探してみる。

すると OwnerPIDが使えそうなのがわかった。今回の場合、メニュー2つと、ステータスバー(SystemUIServer)の OwnerPID が一致しているのがわかる。これを判断に使ってみよう。

で、結果から先に話すとうまくいった。こんな感じ。


悪くない。

サンプル:MenuCapture-06.zip

コードが長いので今回は細かい解説を割愛するが、アルゴリズムは次のようになっている。番号はソースコード上のコメントに対応している。

 (1) ステータスーバーのウィンドウ情報を最初に専用の配列へ保管しておく。
 (2) メニューウィンドウをすべて配列に保管する。
   この時、(1)のウィンドウと OnwerPIDが同じものがあるかどうか調べておく。
 (3) 次の3通りのケースについてキャプチャ範囲を調整する
   [a] Context Menu
   [b] ステータスバーのメニュー
   [c] 通常のメニュー
 (4) (3)のサイズがスクリーンをはみ出した場合、スクリーンに収まるように補正する

 最後にキャプチャ画像を作成する。

アルゴリズムが泥臭いせいもあってコードはわかりづらいかもしれない。



念のため通常メニューの動作確認。問題なし。


コンテキストメニューも大丈夫。



- - - -
それにしても WindowListは役に立つ。これはこれでちゃんとしたツールに仕上げておきたいな。

2008年6月16日月曜日

メニューのキャプチャ(その8)メニューバーを含める(2)

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

前回メニューバーを入れるようにしたがコンテキストメニューのキャプチャでも入るようになってしまった。コンテキストメニューの場合はメニューバーを入れないようにする。


コンテキストメニューかどうかはウィンドウ名でしか判別できないようなのでこれで判断する。ちょっと泥臭いが手を加える。
AppController.m

  int menubar_window_id;

for (i=0; i < CFArrayGetCount(list); i++) {
w = CFArrayGetValueAtIndex(list, i);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowLayer),
kCFNumberIntType, &layer);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),
kCGWindowIDCFNumberType, &window_id);

if (layer == kCGPopUpMenuWindowLevel) {
NSString *window_name = (NSString*)CFDictionaryGetValue(w, kCGWindowName);
if ([window_name isEqualToString:@"ContextMenu"]) {
is_contextmenu = YES;
}
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect);
if (CGRectEqualToRect(rect_all,CGRectZero)) {
rect_all = rect;
} else {
rect_all = CGRectUnion(rect, rect_all);
}
windowIDs[widx++] = window_id;
} else if (layer == kCGMainMenuWindowLevel) {
menubar_window_id = window_id;
}
}
if (!is_contextmenu) {
windowIDs[widx++] = menubar_window_id;
rect_all = CGRectUnion(CGRectZero, rect_all);
rect_all.size.width += 20;
rect_all.size.height += 25;
} else {
rect_all = CGRectNull;
}


これでOK。


ソース:MenuCapture-05.zip

2008年6月15日日曜日

メニューのキャプチャ(その7)メニューバーを含める

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

メニューのキャプチャはできたが、画面一番上のメニューバー部分が入っていない。せっかくなのでメニューバーもキャプチャ画像へ入れたい。やてみよう。


まずメニューバーがどんなウィンドウかを WindowList で調べる。


この中の "Shared Menubar" が目的のウィンドウのようだ。layer=24を CGWindowLevel.h で調べると定義が見つかった。
CGWindowLevel.h

#define kCGMainMenuWindowLevel  CGWindowLevelForKey(kCGMainMenuWindowLevelKey) /* 24 */


これを狙い撃ちしてキャプチャ画像へ入れてみる。前回のコードに kCGMainMenuWindowLevel のケースも window_id に含めるコードを加える。
AppController.m
 if (layer == kCGMainMenuWindowLevel) {
windowIDs[widx++] = window_id;
}


ただメニューバーはスクリーンと同じ横幅を持っているので大きすぎる。できれば目的のメニューの幅だけ切り取れるようにしたい。そこでメニューの大きさを取っておく。サブメニューを含めることを考えてメニューの大きさを CGRectUnion で加えてすべて含む領域を作成する。

CGRect rect = CGRectZero;
CGRect rect_all = CGRectZero;
:
if (layer == kCGPopUpMenuWindowLevel) {
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect);
rect_all = CGRectUnion(rect, rect_all);
windowIDs[widx++] = window_id;
}


初期値を CGRectZero にしておくとうまい具合にメニューバーも含まれる。

結果はこう。うまくいった。


深いサブメニューもこの通り。



なお kCGWindowBounds で取得できる CGRect はメニューの影を含んでいない。この為、サイズを最後に補正してある。
rect_all.size.width += 20;
rect_all.size.height += 25;

安易な方法だが実用上はこれで十分だろう。

ソース:MenuCapture-04.zip

2008年6月14日土曜日

メニューのキャプチャ(その6)サブメニュー(2)

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

サブメニューのキャプチャ画像を作ってみる。複数のウィンドウのキャプチャには CGWindowListCreateImageFromArray( ) が使える。使い方は以前画面キャプチャその18で紹介した。

主要なコードを下記へ示す。

AppController.m

  CGWindowID *windowIDs = calloc(20, sizeof(CGWindowID));

for (i=0; i < CFArrayGetCount(list); i++) {
w = CFArrayGetValueAtIndex(list, i);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowLayer),
kCFNumberIntType, &layer);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),
kCGWindowIDCFNumberType, &window_id);

if (layer == kCGPopUpMenuWindowLevel) {
windowIDs[widx++] = window_id;
}
}

if (widx > 0) {
NSString* path = [NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* filename = [path stringByAppendingPathComponent:@"cocoa_days_menu.png"];

CFArrayRef windowIDsArray = CFArrayCreate(kCFAllocatorDefault, (const void**)windowIDs, widx, NULL);
CGImageRef cgimage = CGWindowListCreateImageFromArray(CGRectNull, windowIDsArray, kCGWindowImageDefault);

NSBitmapImageRep *bitmap_rep = [[[NSBitmapImageRep alloc] initWithCGImage:cgimage] autorelease];
NSImage *image = [[[NSImage alloc] init] autorelease];
[image addRepresentation:bitmap_rep];

NSData* data = [bitmap_rep representationUsingType:NSPNGFileType
properties:[NSDictionary dictionary]];
[data writeToFile:filename atomically:YES];
}

free(windowIDs);


タイマー発火直後に layer==kCGPopUpMenuWindowLevel (101) のウィンドウを探し配列 windowIDs へ格納しておく。そしてこの配列を CFArrayRef に変換し、CGWindowListCreateImageFromArray ( )へ渡す。

実行結果。うまくいった。



3階層でもOK。


ソース:MenuCapture-03.zip

2008年6月13日金曜日

メニューのキャプチャ(その5)サブメニュー(1)

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

サブメニュー(階層メニュー)の場合はどうなるのか。WindowListアプリを使って調べてみた。

下記のようにメニューを開き、タイマーを仕掛けてみる。


サブメニューもウィンドウの1つとして現れた。両方ともレイヤーは 101。Number(=WindowID)が違っているのがわかる。30611 の Dock はサブメニュー、30610 の Appleはメインメニューを表している。通常 WindowIDは順番に振られると思うので、これを使えば階層関係がわかる。



メニュー単体は前回のサンプルでキャプチャできた。では、階層化メニューを一つの画像にキャプチャするにはどうしたらよいだろうか。

2008年6月12日木曜日

メニューのキャプチャ(その4)コンテキストメニュー

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

コンテキストメニューはどうか。WindowListアプリを使って調べてみよう。

下のようにコンテキストメニューを開きタイマーを仕掛ける。


結果は次の通り。前回までのメニューと同じ。レイヤーは 101で、名前が "ContextMenu" になっている。


キャプチャしてみた(前回のサンプルを使用)。