ページ

2008年3月24日月曜日

画面キャプチャその17 - 複数のウィンドウをキャプチャ(7)

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

引き続き、複数ウィンドウのキャプチャ。今回はコード解説。

まずはマスク(clipping path)を作る個所。ウィンドウが選択(mouseDown:)された時に作成する。

マウスカーソル下のウィンドウの当たり判定を行い、選択されたウィンドウを特定する。

- (void)mouseDown:(NSEvent *)theEvent
{
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, j;
CGRect rect = CGRectZero;
CGWindowID hit_window_id;

BOOL hit_flag = NO;
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))) {
if ([self isDockWindow:window]) {
hit_flag = YES;
CFNumberGetValue(CFDictionaryGetValue(window, kCGWindowNumber),
kCGWindowIDCFNumberType, &hit_window_id);
break;
}
}
}



続いてマスク作り。
 NSRect rect2 = NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
if (hit_flag) {
Window *w = [self windowInList:hit_window_id];
if (w) {
[_list removeObject:w];
} else {
w = [[[Window alloc] initWithID:hit_window_id Rect:rect2 Order:i*10+1] autorelease];
[_list addObject:w];

NSBezierPath* path = [NSBezierPath bezierPath];
for (j=0; j < i; j++) {
window = CFArrayGetValueAtIndex(window_list, j);
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(window, kCGWindowBounds), &rect);
NSRect rect3 = NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
if (NSIntersectsRect(rect2, rect3)) {
[path appendBezierPathWithRect:rect3];
}
}
[w setClippingPath:path];
}
[self setNeedsDisplayInRect:rect2];
[self setNeedsDisplay:YES];
}
CFRelease(window_list);


前回説明したアルゴリズムの中のマスク作成の実装がこれ。選択されたウィンドウの上に重なっているウィンドウを見つけ、そのRectを NSBezierPathのパスとして合成していく。作成したパスは描画時に使うので Windowクラスのメンバ変数 _cliiping_pathに取っておく。


次は描画コード。
- (void)drawRect:(NSRect)rect {
// Drawing code here.
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.75] set];
NSRectFill(rect);

NSMutableArray* array = [NSMutableArray arrayWithArray:_list];
if (_spot_window) {
[array addObject:_spot_window];
}
NSSortDescriptor* sort = [[[NSSortDescriptor alloc] initWithKey:@"order" ascending:NO] autorelease];
NSArray *list = [array sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
for (Window* w in list) {
if (w == _spot_window) {
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.25] set];
NSRectFill([w rect]);
[[NSColor colorWithDeviceRed:1.0 green:1.0 blue:1.0 alpha:0.75] set];
// NSFrameRectWithWidth([w rect], 5.0);
} else {
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0] set];
NSRectFill([w rect]);
}
if (![[w clippingPath] isEmpty]) {
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.75] set];
[NSGraphicsContext saveGraphicsState];
[[w clippingPath] setClip];
NSRectFill([w rect]);
[NSGraphicsContext restoreGraphicsState];
}
}
}

これも前回のアルゴリズムに沿ってコーディングしただけ。 _spot_windowが入り込んで若干わかりずらくなっているが、これは mouseMove: 時にマウスカーソル下のウィンドウを指している。このウィンドウは選択はされていないが、マウスカーソルが載っている時にハイライトさせる必要があるので、今回のクリッピングと同じ仕組みで処理させている(最初に配列に足している)。

- - - -
mouseDon: と mouseMove: で似たコードが存在するようになってしまった。うまくまとめたかったが、複雑になりそうで結局コピーとなった。うまい方法があるといいのだが。