ページ

2008年3月1日土曜日

画面キャプチャその2 - Window選択の視覚効果

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

今回はマウスカーソル下のウィンドウだけハイライト(明るく)表示する処理を加えてみる。こんな感じ。



ソース:FullScreenSample-2.zip

マウスカーソルの動きに従い、その下にあるウィンドウのみがハイライト表示されるようになる。


以前紹介した 10.5から導入された CG系の関数を使うと容易に実現できる。処理の流れが次のとおり。

 (0) 画面を覆う透明の NSWindowを作成する(※前回までの処理)
 (1) NSTrackingAreaを登録し、マウストラッキングを開始する
 (2) マウスカーソル下のウィンドウを探す
 (3) ウィンドウが存在したら、ハイライト表示にする

順にコードを追ってみる。


(1) NSTrackingAreaを登録し、マウストラッキングを開始する

MyViewの初期化時に行う。NSTrackingAreaの使い方は過去に紹介した(「NSTrackingArea」)。

_tracking_area = [[NSTrackingArea alloc] initWithRect:frame
options:(NSTrackingMouseMoved |NSTrackingActiveAlways)
owner:self userInfo:nil];
[self addTrackingArea:_tracking_area];



(2) マウスカーソル下のウィンドウを探す

(1)の登録によりmouseMoved メッセージが MyViewに投げられるようになる。そこで mouseMoved: メソッド内でマウスカーソル下にウィンドウが存在するかチェックする。このチェックに CGWindowListCopyWindowInfo (CGWindow.h) を使う。この関数は MacOSX 10.5から新しく導入されたもので画面上に表示されているウィンドウのリストを取得することができる。これらのウィンドウは他のアプリ(プロセス)のものもすべて含まれる。

取得した情報は CFDictionaryRef 型で取り出すことができる。

(取得情報例)
{
kCGWindowAlpha = 1;
kCGWindowBounds = {
Height = 22;
Width = 322;
X = 1072;
Y = 0;
};
kCGWindowIsOnscreen = 1;
kCGWindowLayer = 25;
kCGWindowMemoryUsage = 41820;
kCGWindowName = "";
kCGWindowNumber = 19;
kCGWindowOwnerName = SystemUIServer;
kCGWindowOwnerPID = 119;
kCGWindowSharingState = 1;
kCGWindowStoreType = 2;
}


ウィンドウリストは表示順に配列で返される。以下、ヘッダファイル(CGWindow.h)のコメントの抜粋。
/* CGWindowListCopyWindowInfo
* Copy the CFArray of CFDictionaries, with each dictionary containing descriptive information
* on a window within the user session.
* Returns NULL if called from outside of a GUI security session, or if no window server is running
* The returned CFArray may be empty if no windows meet the specified criteria. */




マウス下のウィンドウのチェックコードは次のとおり。
MyView#mouseMoved:
 CGWindowID window_id = (CGWindowID)[[self window] windowNumber];
NSPoint cp = [self convertPoint:[theEvent locationInWindow] fromView:nil];
CFArrayRef window_list =CGWindowListCopyWindowInfo((kCGWindowListOptionOnScreenOnly|kCGWindowListOptionOnScreenBelowWindow|kCGWindowListExcludeDesktopElements), window_id);
CFDictionaryRef window;
CFIndex i;
CGRect rect = CGRectZero;
CFStringRef window_name;
for (i=0; i < CFArrayGetCount(window_list); i++) {
window = CFArrayGetValueAtIndex(window_list, i);
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(window, kCGWindowBounds), &rect);
if (CGRectContainsPoint(rect, CGPointMake(cp.x, cp.y))) {
window_name = CFDictionaryGetValue(window, kCGWindowName);
if (window_name) {
break;
}
}
}


CG系関数(CG=CoreGraphics)は全てCoreFoundation系のクラスを使っている。これらは基本的に NS系のクラスにキャストして利用することができるが、ここではキャストせずに用意されている関数を使ってコードを書いた。"if (window_name)" の個所は不要なウィンドウを除く為の簡易チェック。通常使うアプリは window_name != nil なので window_nameを持つウィンドウのみを今回は対象にする(このチェックが無いと "Dock"という名のプロセスが大きな領域を占めていて必ずヒットしてしまっていた)。

今回使った CGWindowListCopyWindowInfo 関数は ADCからサンプルが出ているのでこちらを参考にすると良い。
Son Of Grab

ヘッダファイル(CGWindow.h)のコメントも参考になる。


ウィンドウが見つかったら NSRect _spot_rectへ取っておき、drawRect: でこの矩形領域のみハイライトする。
- (void)drawRect:(NSRect)rect {
// Drawing code here.
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.75] set];
NSRectFill([self frame]);
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0] set];  //ハイライト
NSRectFill(_spot_rect);
}



なお CGWindowListCopyWindowInfo で得られる座標系は左上が (0,0)になっている。この為、NSViewの座標系もこれに合わせる必要がある。

- (BOOL)isFlipped
{
return YES;
}


Filpさせて mouseDown: の中でビュー座標に変換する。
NSPoint cp = [self convertPoint:[theEvent locationInWindow] fromView:nil];



- - - -
ハイライト処理ではウィンドウの重なりを考慮していないので、下にあるウィンドウがヒットすると表示されていない部分までハイライトされてしまう。



このあたりの処置はまた今度。