ページ

2008年4月30日水曜日

RubberBand(その14)縦横比率を維持したままリサイズ

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

さて次は Shiftキーを押しながらリサイズした場合に縦横比率を維持する処理を加える。

マウスの移動量 dx, dy を元の範囲選択の横と縦の比に強制的に合わせる処理を入れてみた。

(擬似コード)

r = width / height;
dy = dx * r;


ところがこれではうまくいかない。そもそも移動量が(上記の場合には)dx に比例することになり、実際にドラッグするとマウスカーソルと Knobの位置がずれて不自然な動きとなる。


それならば、ということで移動ベクトル(dx,dy)を今度は回転させて横縦比の傾きに強制的に合わせてみることを考えてみた。


θが分かれば座標回転の行列式で新しい移動ベクトル(dx' , dy')は求められる。

(擬似コード)

t1 = atan2(height, width);
t2 = atan2(dy, dx);
t = t1 - t2; // これがθになる

dx' = dx*cos(t) - dy*sin(t);
dy' = dx*sin(t) + dy*cos(t);


が、これも不自然な動き。結局回転角度が大きくなるケースではマウスカーソルとknobの動きが一致せず、感覚的な動きにならない。

うむ。どうするか。

2008年4月29日火曜日

RubberBand(その13)決まった単位でリサイズ

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

コマンドキーを押しながらKnobをドラッグすると決まった単位(5ドットなど)でリサイズできるようにする。



ソース:RubberBand-10.zip

コマンドキー押下の判断は NSEvent#modifierFlags でできる。

resize_by_unit = [event modifierFlags] & NSCommandKeyMask


移動量が一定のサイズ(下記コードでは _resize_unit: 初期値 5.0)を超えた時だけリサイズを起こさせる。そして移動量を強制的に一定のサイズに合わせてしまう。例えば一定のサイズを 5.0とすると、これを超える移動量の場合だけリサイズさせる。移動量が 6.2なら 1.2を切り捨てて移動量は 5.0 にする。すると 5.0ドット単位でのリサイズが可能になる。一方、5.0に満たない移動の場合は、リサイズを見送る。

   if (resize_by_unit) {
if (fabs(dx) >= _resize_unit) {
mx = fmod(dx, _resize_unit);
dx = dx - mx;
pp.x = cp.x - mx;
} else {
dx = 0.0;
}
if (fabs(dy) >= _resize_unit) {
my = fmod(dy, _resize_unit);
dy = dy - my;
pp.y = cp.y - my;
} else {
dy = 0.0;
}
} else {
pp.x = cp.x;
pp.y = cp.y;
}


ここまでは簡単にできる。厄介なのがサイズを一定の単位に合わせる処理。例えば 5.0ドット単位でリサイズする場合、直前の横幅が 321であれば、移動方向によって初回のサイズを 320 もしくは 325 に合わせたい。先の処理だけだと 316 もしくは 326 になってしまう。そこで場合分けをしてKnobの位置に応じた補正処理を追加する。

 if (resize_by_unit) {
mx = fmod(_rect.size.width, _resize_unit);
my = fmod(_rect.size.height, _resize_unit);

if (mx && fabs(dx) > 0.0) {
if (rule.x > 0) {
if (dx > 0) {
_rect.origin.x += -(_resize_unit - mx) * rule.x;
_rect.size.width += -(_resize_unit - mx) * rule.w;
} else if (dx < 0) {
_rect.origin.x += mx * rule.x;
_rect.size.width += mx * rule.w;
}
} else {
if (dx > 0) {
_rect.origin.x += -mx * rule.x;
_rect.size.width += -mx * rule.w;
} else if (dx < 0) {
_rect.origin.x += (_resize_unit - mx) * rule.x;
_rect.size.width += (_resize_unit - mx) * rule.w;
}
}
}
:
:
}


あまりうまい方法がうかばずベタなコーディングとなってしまった。コードはKnobの位置と移動方向による場合分けでサイズを補正している。RubberBandのサイズ補正はKnobの位置によってはsizeだけでなく originも変更が必要なのでややこしい。

それはともあれできた。

2008年4月28日月曜日

RubberBand(その12)サイズ表示

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

サイズを表示する。



ソース:RubberBand-9.zip

以前紹介した縁取りと影付けを文字に対して行っている。

- (void)drawInformation
{
NSString *info =
[NSString stringWithFormat:@"%.0f x %.0f",
fabs(_rect.size.width), fabs(_rect.size.height)];
NSMutableDictionary *stringAttributes = [NSMutableDictionary dictionary];

[stringAttributes setObject:[NSFont boldSystemFontOfSize:14.0]
forKey: NSFontAttributeName];
[stringAttributes setObject:[NSColor whiteColor]
forKey:NSForegroundColorAttributeName];
[stringAttributes setObject:[NSColor blackColor]
forKey:NSStrokeColorAttributeName];
[stringAttributes setObject:[NSNumber numberWithFloat: -1.5]
forKey:NSStrokeWidthAttributeName];

NSPoint p = NSMakePoint(_rect.origin.x + 5, _rect.origin.y + 5);

[NSGraphicsContext saveGraphicsState];
[_shadow set];
[info drawAtPoint:p withAttributes: stringAttributes];
[NSGraphicsContext restoreGraphicsState];
}


最初は縁取りだけだったが、これだけだと数字が若干読みづらい。


そこで影を加えてみた。多少見やすくなった。

2008年4月27日日曜日

SimpleCap - ウィンドウが非アクティブになってしまう

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

キャプチャを実行すると SimpleCap アプリがアクティブになってしまい、直前までアクティブだったウィンドウが非アクティブになってしまう。


アクティブなウィンドウは、アクティブのままキャプチャしたい。

これは SimpleCap で使っている画面を覆っている半透明のウィンドウが NSWindow を使っているため。SimpleCapアプリがアクティブにならないとマウスイベントが取れない。いろいろ試しているうちに NSPanel にすればアプリが非アクティブでもマウスイベントが処理できることがわかった。


下記は NSWindow をやめて NSPanelにした例。


なおNSPanelにするだけではダメでウィンドウのスタイルに NSNonactivatingPanelMask を加えてやる必要がある。

2008年4月26日土曜日

RubberBand(その11)再利用

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

必要な機能は大体できたので後で再利用可能な形にしておく。NSViewに完全に依存しているのでこの単位で再利用することにする。すなわち RubberBandView(NSViewのサブクラス)を再利用することになる。再利用する場合、元の画像なりデータがあり、その大きさをを RubberBandViewを使って変更することになる。この時に必要なのは、RubberBandViewの初期値を設定することと、RubberBandViewが変更された時にそれを元画像へ伝えることの2つ。

前者は RubberBandView#setRubberBandFrame: を実装することで対応する。後者はデリゲートの仕組みを導入しよう。RubberBandView のメンバ変数に _delegate を追加し、大きさが変化する毎にこのオブジェクトへメッセージを送信する。ここではプロトコルを定義して、デリゲートはこのプロトコルに適合するよう条件づけてやる。

@protocol RubberBandDelegate
-(void)changedFrame:(NSRect)frame;
@end


@interface RubberBandView : NSView {

NSRect _rect;
id _delegate;
}


そしてサイズ変更や移動時に changedFrame: を投げてやる。


再利用可能にした RubberBandViewを使ったサンプルを作ってみた。

ソース:RubberBand-8.zip

前回そっくりだが構造が違う。サンプルではもう一枚のビュー(ImageView)を導入してここに画像を描いている。この ImageViewを RubberBandViewのデリゲートとして設定すると、RubberBandの変更にともない、ImageView上の画像の大きさや位置もそれに合わせて変更される。

関係はこんな感じ。

2008年4月25日金曜日

RubberBand(その10)枠線を点線に

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

画像を背景に配置して、実際の使用感を確かめてみよう。


悪くは無いが少し手を加えてみる。Preview.app 風に灰色と白の点線でどうだろうか。


これにしよう。


ソース:RubberBand-7.zip

点線の描画には NSBezierPath を使う。

 CGFloat dasharray[] = {3.0, 3.0};
NSBezierPath* path = [NSBezierPath bezierPath];
[path appendBezierPathWithRect:_rect];
[path setLineWidth:0.5];

[[NSColor grayColor] set];
[path setLineDash:dasharray count:2 phase:0.0];
[path stroke];

[[NSColor whiteColor] set];
[path setLineDash:dasharray count:2 phase:3.0];
[path stroke];


NSBezierPath#setLineDash:count:phase: を使うと簡単に描ける。
最初に渡す配列は描画と非描画のドットパターンを表す。例えば今回の {3.0, 3.0}は下のようなパターンを表す。最後の phaseはどっとパターンを何ドット目から開始するかを指定する(一種オフセットのようなもの)。

同じドットパターンで phase だけづらして描けば今回のような白と灰色の点線になる。

なおそのまま描画するとアンチエイリアスが効いてしまい線がぼやけてしまう。


そこで NSBezierPath#setShouldAntialias: を使い一時的にアンチエイリアスを切ってやる。
 NSGraphicsContext *gc = [NSGraphicsContext currentContext];
[gc saveGraphicsState];
[gc setShouldAntialias:NO];
:
(線の描画)
:
[gc restoreGraphicsState];


するとくっきりした線が描ける。

2008年4月24日木曜日

RubberBand(その9)マウスカーソルの変更

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

続いてはマウスカーソルの変更。マウスがKnobの上に来たら形状を変更する。
マウスカーソルの形状は標準でいくつか用意されている。

NSCursor Class Referenceより。



Setting the Current Cursor によれば、NSView#addCursorRect:cursor を使うと指定した領域にマウスが入ると自動的にカーソルの形状を替えることができる。

[aView addCursorRect:aRect cursor:aCursor];


また、Managing Cursor-Update Events によれば、ウィンドウやビューのサイズ変更のタイミングでNSView#resetCursorRects が自動的に呼ばれるので、この中で #addCursorRect:cursor: を使うのが良いと記述されている。今回もこれに従うことにする。

-(void)resetCursorRects
{
- (void)resetCursorRects
{
[self discardCursorRects];

NSCursor* crosshairCursor = [NSCursor crosshairCursor];
NSCursor* resizeUpDownCursor = [NSCursor resizeUpDownCursor];
NSCursor* resizeLeftRightCursor = [NSCursor resizeLeftRightCursor];

[self addCursorRect:MakeKnobRect(NSMinX(_rect), NSMinY(_rect))
cursor:crosshairCursor];
[self addCursorRect:MakeKnobRect(NSMidX(_rect), NSMinY(_rect))
cursor:resizeUpDownCursor];
[self addCursorRect:MakeKnobRect(NSMaxX(_rect), NSMinY(_rect))
cursor:crosshairCursor];
[self addCursorRect:MakeKnobRect(NSMinX(_rect), NSMidY(_rect))
cursor:resizeLeftRightCursor];
[self addCursorRect:MakeKnobRect(NSMaxX(_rect), NSMidY(_rect))
cursor:resizeLeftRightCursor];
[self addCursorRect:MakeKnobRect(NSMinX(_rect), NSMaxY(_rect))
cursor:crosshairCursor];
[self addCursorRect:MakeKnobRect(NSMidX(_rect), NSMaxY(_rect))
cursor:resizeUpDownCursor];
[self addCursorRect:MakeKnobRect(NSMaxX(_rect), NSMaxY(_rect))
cursor:crosshairCursor];
}
}


RubberBandの拡大縮小や移動ではこのメソッドが自動的に呼ばれないので、自前で呼出すようにする。

なお、自前で呼出す場合は古い位置での登録情報が残ったままになるので NSView#discardCursorRects を最初に呼んでおく。このメソッドで古い登録情報は消される。

RubberBand-6.zip

2008年4月23日水曜日

RubberBand(その8)枠の移動

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

枠をドラッグして移動できるようにする。

これは簡単にできる。

 if (NSPointInRect(cp, _rect)) {
dx = _rect.origin.x - cp.x;
dy = _rect.origin.y - cp.y;
while ([event type] != NSLeftMouseUp) {
event = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
cp = [self convertPoint:[event locationInWindow] fromView:nil];
_rect.origin.x = cp.x + dx;
_rect.origin.y = cp.y + dy;

[self setNeedsDisplay:YES];
}
}


ソース:RubberBand-5.zip

2008年4月22日火曜日

RubberBand(その7)拡大縮小処理(2)

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

前回の実装では Knobを元領域を超えてドラッグすると下記のように枠線が消えてしまっていた。

これはドラッグの結果、NSRect.size が負の値をとっていた為(だから描画されない)。
Knobを元領域を超えてドラッグするとは下記のようなイメージ。

元領域を超えてドラッグすると Knobの位置が実質的に入れ替わるため、意図しない Knobの位置の処理が適用されてしまうため。例えば上記例の様に左上の Knobをドラッグした時には、当然左上の Knobの処理が手適用される。この時左上の Knob様に定義された前回のアフィン変換のパラメータが適用される。しかし元領域を超えて右下へドラッグすると、枠に対する位置が左上ではなく、右下になる。つまりここに来たら右下用のパラメータを適用しなければならない。

Sketch ではこの動作に対応する為に Knobの位置変換用の対応配列を用意して、内部的に処理対象のKnobを入れ替えている。今回は範囲の拡大縮小ができれば良いだけなので Sketch の方法はとらずにもっと簡単に実装した。

表示の時に NSRect.sizeが負なら補正してしまう。

- (NSRect)normalizeRect:(NSRect)rect
{
NSRect rect2 = rect;

if (rect.size.width < 0) {
rect2.origin.x = rect.origin.x + rect.size.width;
rect2.size.width = -rect.size.width;
}
if (rect.size.height < 0) {
rect2.origin.y = rect.origin.y + rect.size.height;
rect2.size.height = -rect.size.height;
}
return rect2;
}
もちろんドラッグ操作が終わった後も直しておく必要がある。

すると元領域を超えたドラッグでも問題なく表示できる。




ソース:RubberBand-4.zip

2008年4月21日月曜日

RubberBand(その6)拡大縮小処理

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

Knob をつかんだ時の処理を加える。



ソース:RubberBand-3.zip

さてどう実装するか。Knobはつかんだ位置によって処理が微妙に異なる。

例えば左上のKnobを左上方向へ移動させた場合、Origin の移動+ Sizeの拡大となる。


一方、右下の Knobを右下へ移動すると Originは動かず、Sizeの拡大だけとなる。



移動量を dx, dy とすると、最初のケースは次のように表せる。

 x' = x + dx
y' = y + dy
w' = w - dx
h' = h - dy

このケースでは dx < 0 dy < 0

 x' = x + 0
y' = y + 0
w' = w + dx
h' = h + dy

このケースでは dx > 0, dy > 0。

実はこれらは行列として表せる。


2番目はこう。


これはアフィン変換と呼ばれる行列演算。
Knobの位置毎に Origin と Size の行列が定義できるから、これをパラメータ化すれば処理自体は1種類で済む。dx と dy は可変だが、行列で必要なのはその符号なのでパラメータとしては -1, 0, 1 として表せる。プログラムではパラメータを構造体で定義する。
typedef struct _ResizeRule {
CGFloat x, y, w, h;
} ResizeRule;


Knob毎のパラメータを用意してやる。
ResizeRule rules[8] = {
{1, 1,-1,-1}, // TOP_LEFT
{0, 1, 0,-1}, // TOP_MIDDLE
{0, 1, 1,-1}, // TOP_RIGHT
{1, 0,-1, 0}, // MIDDLE_LEFT
{0, 0, 1, 0}, // MIDDLE_RIGHT
{1, 0,-1, 1}, // BOTTOM_LEFT
{0, 0, 0, 1}, // BOTTOM_MIDDLE
{0, 0, 1, 1} // BOTTOM_RIGHT
};


rules のインデックス値と、Knobのenum値を合わせておくのを忘れずに。

パラメータが準備できたので後はこれを使う処理を実装するだけ。
- (void)mouseDown:(NSEvent*)event
{
NSPoint pp, cp;
cp = [self convertPoint:[event locationInWindow] fromView:nil];
int knob_type = [self knobAtPoint:cp];
CGFloat dx, dy;

if (knob_type != KNOB_NON) {
while ([event type] != NSLeftMouseUp) {
pp = cp;
event = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
cp = [self convertPoint:[event locationInWindow] fromView:nil];
dx = cp.x - pp.x;
dy = cp.y - pp.y;

ResizeRule rule = rules[knob_type];

_rect.origin.x += dx * rule.x;
_rect.origin.y += dy * rule.y;
_rect.size.width += dx * rule.w;
_rect.size.height += dy * rule.h;

[self setNeedsDisplay:YES];
}
}
}

これだけ。ずいぶんスッキリと実装できた。

- - - - -
Sketch より短くできた、なんて喜んでいたら、いくつか問題が見つかった。
次回はこれを直そう。

2008年4月20日日曜日

RubberBand(その5)マウスイベント処理

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

描画ができたので今回はマウスイベント処理に取りかかる。

RubberBand で必要な処理とは次の2つ。
(1) マウスクリック時にKnobがクリックされたかを判断
(2) Knobがクリックされた状態でドラッグされた場合、伸縮させる

今回はクリック時の判断処理を行う。

まずは mouseDown:を実装する。

- (void)mouseDown:(NSEvent*)event
{
NSPoint p = [self convertPoint:[event locationInWindow] fromView:nil];
int knob_type = [self knobAtPoint:p];

NSLog(@"%d", knob_type);
}


マウスクリック時にどの Knobが押されたのかを #knobAtPoint: から取得し表示する。Knobの種類は enumで定義しておく。
enum KNOB_TYPE {
KNOB_NON,
KNOB_TOP_LEFT , KNOB_TOP_MIDDLE , KNOB_TOP_RIGHT,
KNOB_MIDDLE_LEFT , KNOB_MIDDLE_RIGHT,
KNOB_DOWN_LEFT , KNOB_DOWN_MIDDLE , KNOB_DOWN_RIGHT
};


#knobAtPoint: では渡されたポイント位置にどの Knobがあるかをチェックする。描画同様、これも力技(Sketchがそうだったので。。)。
- (int)knobAtPoint:(NSPoint)p
{
int knob_type = KNOB_NON;
if (NSPointInRect(p, MakeKnobRect(NSMinX(_rect), NSMinY(_rect)))) {
knob_type = KNOB_TOP_LEFT;
} else if (NSPointInRect(p, MakeKnobRect(NSMidX(_rect), NSMinY(_rect)))) {
knob_type = KNOB_TOP_MIDDLE;
} else if (NSPointInRect(p, MakeKnobRect(NSMaxX(_rect), NSMinY(_rect)))) {
knob_type = KNOB_TOP_RIGHT;
} else if (NSPointInRect(p, MakeKnobRect(NSMinX(_rect), NSMidY(_rect)))) {
knob_type = KNOB_MIDDLE_LEFT;
} else if (NSPointInRect(p, MakeKnobRect(NSMaxX(_rect), NSMidY(_rect)))) {
knob_type = KNOB_MIDDLE_RIGHT;
} else if (NSPointInRect(p, MakeKnobRect(NSMinX(_rect), NSMaxY(_rect)))) {
knob_type = KNOB_DOWN_LEFT;
} else if (NSPointInRect(p, MakeKnobRect(NSMidX(_rect), NSMaxY(_rect)))) {
knob_type = KNOB_DOWN_MIDDLE;
} else if (NSPointInRect(p, MakeKnobRect(NSMaxX(_rect), NSMaxY(_rect)))) {
knob_type = KNOB_DOWN_RIGHT;
}
return knob_type;
}



実行して Knobをクリックすると位置に合わせた整数値がログに表示される。Knob以外の場所は 0 (KNOB_NON)となる。

コード:RubberBand-2.zip

- - - -
次は Knobをつかんだドラック処理の実装を行う。

2008年4月19日土曜日

RubberBand(その4)枠を描く

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

まずは枠を描いてみる。



サンプル:RubberBand-1.zip

カスタムビュー RubberBandView を用意して、ここに RubberBandを描く。

描く領域をインスタンス変数で保持する。

@interface RubberBandView : NSView {

NSRect _rect;
}


後は drawRect: で描くだけ。
- (void)drawRect:(NSRect)rect {
// Drawing code here.

[[NSColor whiteColor] set];
NSRectFill(rect);

[[NSColor grayColor] set];
NSFrameRectWithWidth(_rect, 0.5);

[self drawKnobs];
}


枠線を描いたら、drawKnobs で上下左右四隅にノブ(四角)を描く。Sketchを真似てみた。
- (void)drawKnobs
{
[self drawKnobAtPoint:NSMakePoint(NSMinX(_rect), NSMinY(_rect))];
[self drawKnobAtPoint:NSMakePoint(NSMidX(_rect), NSMinY(_rect))];
[self drawKnobAtPoint:NSMakePoint(NSMaxX(_rect), NSMinY(_rect))];
[self drawKnobAtPoint:NSMakePoint(NSMinX(_rect), NSMidY(_rect))];
[self drawKnobAtPoint:NSMakePoint(NSMaxX(_rect), NSMidY(_rect))];
[self drawKnobAtPoint:NSMakePoint(NSMinX(_rect), NSMaxY(_rect))];
[self drawKnobAtPoint:NSMakePoint(NSMidX(_rect), NSMaxY(_rect))];
[self drawKnobAtPoint:NSMakePoint(NSMaxX(_rect), NSMaxY(_rect))];
}


- (void)drawKnobAtPoint:(NSPoint)p
{
NSRect knob_box = NSMakeRect(p.x-KNOB_WIDTH/2.0, p.y-KNOB_WIDTH/2.0, KNOB_WIDTH, KNOB_WIDTH);
[[NSColor whiteColor] set];
NSRectFill(knob_box);

[[NSColor blackColor] set];
NSFrameRectWithWidth(knob_box, 0.5);
}


- - - -
描画は簡単にできた。次はマウスイベント処理を実装する。

2008年4月18日金曜日

RubberBand(その3)Sketchのソースを読む (2)

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

前回はイベントハンドリングの部分を読んだので、今回は描画コードを読み解く。

SKTGraphicView.m

- (void)drawRect:(NSRect)rect {
for (NSInteger index = graphicCount - 1; index>=0; index--) {
(1) SKTGraphic *graphic = [graphics objectAtIndex:index];
(2) [graphic drawContentsInView:self isBeingCreateOrEdited:(graphic==_creatingGraphic || graphic==_editingGraphic)];
if (drawSelectionHandles) {
(3)[graphic drawHandlesInView:self];
}
}
}

リストから図形をひとつずつ取り出して(1)、描画していく(2)。ハンドルがある場合はこれも描く(3)。

(3)の drawHandlesInView: を見てみよう。

SKTGraphic.m

- (void)drawHandlesInView:(NSView *)view {

// Draw handles at the corners and on the sides.
NSRect bounds = [self bounds];
[self drawHandleInView:view atPoint:NSMakePoint(NSMinX(bounds), NSMinY(bounds))];
[self drawHandleInView:view atPoint:NSMakePoint(NSMidX(bounds), NSMinY(bounds))];
[self drawHandleInView:view atPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds))];
[self drawHandleInView:view atPoint:NSMakePoint(NSMinX(bounds), NSMidY(bounds))];
[self drawHandleInView:view atPoint:NSMakePoint(NSMaxX(bounds), NSMidY(bounds))];
[self drawHandleInView:view atPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds))];
[self drawHandleInView:view atPoint:NSMakePoint(NSMidX(bounds), NSMaxY(bounds))];
[self drawHandleInView:view atPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];

}

単純に8つのハンドルを drawHandleInView:atPoint: を使って描画している。描画メソッドは次の通り。

- (void)drawHandleInView:(NSView *)view atPoint:(NSPoint)point {

// Figure out a rectangle that's centered on the point but lined up with device pixels.
NSRect handleBounds;
handleBounds.origin.x = point.x - SKTGraphicHandleHalfWidth;
handleBounds.origin.y = point.y - SKTGraphicHandleHalfWidth;
handleBounds.size.width = SKTGraphicHandleWidth;
handleBounds.size.height = SKTGraphicHandleWidth;
handleBounds = [view centerScanRect:handleBounds];

// Draw the shadow of the handle.
NSRect handleShadowBounds = NSOffsetRect(handleBounds, 1.0f, 1.0f);
[[NSColor controlDarkShadowColor] set];
NSRectFill(handleShadowBounds);

// Draw the handle itself.
[[NSColor knobColor] set];
NSRectFill(handleBounds);

}

影と本体を NSRectFill()で描画している。これだけ。
するとこんなのが表示される。


- - - - -
Sketchにおけるラバーバンド(ハンドル)の実装方法がわかった。こうして見ると特別複雑なことはやっていない(グリッドやルーラーが入ると若干わかりづらいところもあるが)。これを参考に今度は自前のラバーバンドを作ってみよう。

2008年4月17日木曜日

RubberBand(その2)Sketchのソースを読む (1)

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

Sketch のソースコードを眺めてみる。

まずはマウスの左ボタンを押した時の処理を追って見る。

SKTGraphicView.m
mouseDown:
(1) selectAndTrackMouseWithEvent:
(2)SKTGraphic *clickedGraphic = [self graphicUnderPoint:mouseLocation index:&clickedGraphicIndex isSelected:&clickedGraphicIsSelected handle:&clickedGraphicHandle];
if (clickedGraphic) {
if (clickedGraphicHandle != SKTGraphicNoHandle) {
// ラバーバンド上のノブ(ハンドル)がクリックされた -> 拡大縮小処理
(3) [self resizeGraphic:clickedGraphic usingHandle:clickedGraphicHandle withEvent:event];
} else {
if (modifyingExistingSelection) {
if (clickedGraphicIsSelected) {
// 既に選択されていた図形をクリックした -> 未選択にする
} else {
// 未選択状態の図形をクリッックした -> 選択状態にする
}
} else {
:

mouseDown: 内で、(1) selectAndTrackMouseWithEvent: が呼ばれる。
続いてマウスカーソル下に何があったかを (2)graphicUnderPoint:index:isSelected: でチェックする。
その結果、ラバーバンド上のハンドルがクリックされていたら、(3)resizeGraphic:usingHandle:withEvent: を呼出す。

ハンドルはラバーバンド上下左右、四隅についている四角のこと。これをドラッグして拡大縮小させる。コメントではノブ (knob)と表現している個所もある。



続いて(2)(3)を詳しくみていく。

まずは (2)graphicUnderPoint:index:isSelected:
- (SKTGraphic *)graphicUnderPoint:(NSPoint)point index:(NSUInteger *)outIndex isSelected:(BOOL *)outIsSelected handle:(NSInteger *)outHandle {
for (NSUInteger index = 0; index<graphicCount; index++) {
// 図形を1つ1つチェックする

if (NSPointInRect(point, [graphic drawingBounds])) {
// マウスカーソル直下にある図形が見つかった
if (graphicIsSelected) {
// その図形は直前まで選択状態にあった
NSInteger handle = [graphic handleUnderPoint:point]; // (4)クリックされたハンドルの番号を得る
if (handle!=SKTGraphicNoHandle) {
// ハンドルがクリックされた場合...
}
}
:
}

キャンパス上に配置された図形1つ1つについてヒットテストを行っていく。図形がヒットした場合は (4)handlerUnderPoint: を使い、ハンドルの番号を得る。ハンドルがクリックされていた場合は必要な情報を設定してメソッドを終わらせる。

(4)handlerUnderPoint: を見てみよう。
SKTGraphic.m

- (NSInteger)handleUnderPoint:(NSPoint)point {

// Check handles at the corners and on the sides.
NSInteger handle = SKTGraphicNoHandle;
NSRect bounds = [self bounds];
if ([self isHandleAtPoint:NSMakePoint(NSMinX(bounds), NSMinY(bounds)) underPoint:point]) {
handle = SKTGraphicUpperLeftHandle;
} else if ([self isHandleAtPoint:NSMakePoint(NSMidX(bounds), NSMinY(bounds)) underPoint:point]) {
handle = SKTGraphicUpperMiddleHandle;
} else if ([self isHandleAtPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds)) underPoint:point]) {
handle = SKTGraphicUpperRightHandle;
} else if ([self isHandleAtPoint:NSMakePoint(NSMinX(bounds), NSMidY(bounds)) underPoint:point]) {
handle = SKTGraphicMiddleLeftHandle;
} else if ([self isHandleAtPoint:NSMakePoint(NSMaxX(bounds), NSMidY(bounds)) underPoint:point]) {
handle = SKTGraphicMiddleRightHandle;
} else if ([self isHandleAtPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds)) underPoint:point]) {
handle = SKTGraphicLowerLeftHandle;
} else if ([self isHandleAtPoint:NSMakePoint(NSMidX(bounds), NSMaxY(bounds)) underPoint:point]) {
handle = SKTGraphicLowerMiddleHandle;
} else if ([self isHandleAtPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds)) underPoint:point]) {
handle = SKTGraphicLowerRightHandle;
}
return handle;

}

合計8つのハンドル(上下左右、四隅)について単純なヒットテストを行っている。ヒットした場合はそれぞれの位置に応じて定義された値(例:左上なら SKTGraphicUpperLeftHandle)を返す。ハンドルがクリックされていない場合は SKTGraphicNoHandlerを返す。


最後にハンドルをつかんでドラッグした時の拡大縮小処理を行なう (3)resizeGraphic:usingHandle:withEvent:
SKTGraphicView.m

- (void)resizeGraphic:(SKTGraphic *)graphic usingHandle:(NSInteger)handle withEvent:(NSEvent *)event {

while ([event type]!=NSLeftMouseUp) {
// イベントループを形成、左ボタンが離されるまで繰り返す
event = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
[self autoscroll:event];
NSPoint handleLocation = [self convertPoint:[event locationInWindow] fromView:nil];
if (_grid) {
// グリッドを使っている時は位置を強制的にグリッドに合わせる
handleLocation = [_grid constrainedPoint:handleLocation];
}
// 拡大縮小処理実行
handle = [graphic resizeByMovingHandle:handle toPoint:handleLocation];
}
}

resizeByMovingHandle:toPoint: の戻り値で handleを受け取っている。ということは、つかんでいるハンドルが入れ替わるということか?実際メソッドを見てみると左右反転、あるいは上下反転が行われている。これはハンドルをドラッグ中にハンドルが元図形の範囲を超えて反対側に行ってしまった場合を想定している。例えば、左のハンドルをドラッグすると通常は左方向の伸縮を制御することになる。左へドラッグすれば拡大、右へ行けば縮小。ところがドラッグしたまま図形の右端を超えると、これが逆転して、左へ行けば縮小、右へ行けば拡大となる。つまりこの時点では右のハンドルと同じ動作になっていることになる。

イベントハンドリングはざっとこんなところか。

2008年4月16日水曜日

RubberBand(その1)

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

Rubber Band とは画像などの周りに表示される拡大縮小に使う枠の事。

例えばエクセルならこんな感じ。割とオーソドックスな表現。



プレビュー.app はちょっと凝っていて、マウスでつかむ部分が円形で縁取りになっている。領域線は白と黒を交互にしていて背景にかかわらずみやすい。



そして Sketch.app。他に比べるとちょっとしょぼい。でもこれはソースコードがついているので実装方法を見ることができる。




今作っているキャプチャソフトで Rubber Bandの機能が欲しくなった。この機能はありそうで InterfaceBuilderなどには用意されていない。仕方ないので作ることにしよう。組み込む前にまずは単体で動くサンプルを作ってみよう。

とりあえず Sketch.appのソースコードを見てみる(続く)。

2008年4月15日火曜日

WindowList (その5)

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

前回までに作ったサンプルプログラムを使ってウィンドウリストを見ていく。

まずはシステムメニュー。SystemUIServerが管理している。これもウィンドウ扱いなのか。



メニューの中でも右にある一連のステータスバーは別ウィンドウになっている。描画上、左のメニューとは別管理になっていることがわかる。



続いて Dock。これは NSRect{0, 22, 1440, 878}となっていて一番上のメニューを除く画面領域全体を示している。つまりアイコンが表示されている所だけでなく、それ以外の画面全体で描かれる Dock関連のアニメーションなどは、普段透明なこのウィンドウに描かれるということか。



アイコンが載っている部分は "Magic Mirror"と呼ばれていて、わざわざ一つのウィンドウが割り当てられている。



次は kCGWindowListExcludeDesktopElements オプションを外してみる。すると Finder関連のウィンドウがたくさん出てくる。一つずつ見ていくと、その大半がデスクトップ上に表示されているアイコンであることがわかる。おお、アイコン1個が1つのウィンドウになっているのか。面白い。デスクトップ上のアイコンのドラックは、実はウィンドウをドラックしていたことになるのか。



続いて kCGWindowListOptionOnScreenOnly チェックを外す。すると非表示状態のウィンドウもリストに出てくる。表一番右に "Workspace" というカラムあるが、これは Speces.app で表示している画面番号を表す。この表では仮想画面の "4" や "3" にあるウィンドウが掲載されている。つまり、Speces.app は画面を切り替える時に隠れるウィンドウ群を単純に非表示にしていることがわかる(当たり前の動作だけど)。



最後に "Desktop"。これは画面全体{1440,900}を覆っていて Window Serverが管理している。これは壁紙を描くエリアだろうか?同様に Finderも画面全体を覆う(透明な)ウィンドウを確保している。レイヤーは最下層の一つとなっているのでデスクトップ上へのドラック操作などは、このFinderのウィンドウが引き受けているのかもしれない。ただ順番は Desktop(WindowServer) -> Finder となっているので、この仮説だと Desktopが先にドラッグを処理してしまいそうだが。



- - - -
こうしてみると普段見ている MacOSXの画面は基本的にウィンドウで構成されていることがわかる。Dockもしかり、Finderもしかり。中でも一番興味負深かったのがデスクトップ上のアイコン。これがウィンドウだったとは。しかし、これがウィンドウということはデスクトップに大量にアイコンがあると、それだけメモリを食うってことになるな。それにドラッグ&ドロップした時のイベントハンドリングも、たくさんアイコンがあると負荷がかかる可能性がありそうだ。今回は調査していて楽しかったけど、ウィンドウを使えば基本なんでも描画できることがわかったのが一番の収穫だった。今回までの検証成果はいずれ SimpleCapへ反映する。

2008年4月14日月曜日

WindowList (その4) - 選択したウィンドウを反転させる

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

リスト上で選択したウィンドウをハイライトするようにしてみた。



ソース:WindowList-4.zip

アルゴリズムは以前紹介したものをそのまま使っている。

2008年4月13日日曜日

WindowList (その3) - バインディング値の表示形式

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

さて NSValueTransformer、NSFormatter と検証して、その結果、バインディング値を任意の形式で表示させるには NSFormatterが良いことが分かったので、これを Windowリストへ組み込んでみる。

ソース:WindowList-3.zip

NSFormatterを組み込んだ結果、Boundsの表示が下の様だったのが


こういう風になった。


NSFormatter導入前は、NSCFDictionary の文字列表記(#description)の一行目が表示されていた。

{     <--ここだけが表示されていた
Height = 383;
Width = 740;
X = 625;
Y = 377;
}


今回 RectFormatterを導入して NSCFDictionaryに格納されている上記値をカンマ区切りで並べた文字列に変換するようにした。

RectFormatter.h
@interface RectFormatter : NSFormatter {
}
@end


RectFormatter.m
@implementation RectFormatter
- (NSString *)stringForObjectValue:(id)anObject
{
if (![anObject isKindOfClass:[NSDictionary class]]) {
return nil;
}
return [NSString stringWithFormat:@"%4d,%4d,%4d,%4d",
[[anObject objectForKey:@"X"] intValue],
[[anObject objectForKey:@"Y"] intValue],
[[anObject objectForKey:@"Width"] intValue],
[[anObject objectForKey:@"Height"] intValue]];
}
 :


メソッド内のクラスチェックは重要で、最初これをやらなかったのでクラッシュした。理由はカラム値の初期値に InterfaceBuilderで設定された "Text Cell"が入っており、これが一番最初にこのメソッドに渡されるため。その結果 NSStringのインスタンスに対して #objectForKey: を投げることになったのでプログラムがクラッシュしていた。


クラス定義後に Interface Builder でインスタンス化してやり、目的の列(NSTextFieldCell)の fomatterアウトレットに接続する。


さらに今回は桁の表示位置を合わせたかったので等幅フォントを選び、サイズも 11ptと小さくした。これを行なう為に #attributedStringForObjectValue:withDefaultAttributes: を実装する。

- (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes
{
NSString* str = [self stringForObjectValue:anObject];
NSFont *font = [NSFont userFixedPitchFontOfSize:11.0];
[attributes setValue:font forKey:NSFontAttributeName];

return [[[NSAttributedString alloc] initWithString:str
attributes:attributes] autorelease];
}



NSFormatterは初めて作ったが便利だ。アウトレットの接続だけで表示形式が追加できるとは Cocoaはよく考えられてるな。

2008年4月12日土曜日

NSFormatter その2

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

引き続き NSFormatter。

#attributedStringForObjectValue:withDefaultAttributes: をオーバーライドすると、NSAttributedStringで表現ができる。前回のサンプルに文字色を赤くするようにメソッドを追加してみた。

- (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes
{
[attributes setValue:[NSColor redColor] forKey:NSForegroundColorAttributeName];
NSAttributedString* attr_str = [[NSAttributedString alloc]
initWithString:[NSString stringWithFormat:@"%x", [anObject intValue]]
attributes:attributes];
return attr_str;
}


するとこんな感じ。


このメソッドを使うとマイナス値は赤など任意の修飾ができる。

2008年4月11日金曜日

NSFormatter

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

前回 NSValueTransformer を使ってバインディングした値の変換を行った。ただ NSValueTransformerは表現を変換するというよりは、値を別の単位や次元を変えるのが主な目的だと思われる。やりたいのは表現の変換なのでもう少し調べてみる。すると NSFormatter があった。前回と同じプログラムを今度は NSFormatterを使って実装してみた。

ソース:VTSample2.zip



まずは NSFormatterのサブクラスを作る。

HexFormatter.h

@interface HexFormatter : NSFormatter {
}
@end


HexFormatter.m
- (NSString *)stringForObjectValue:(id)anObject
{
return [NSString stringWithFormat:@"%x", [anObject intValue]];
}

- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
{
int num = 0;
int x =1;
int i = 0;
unichar ch;
for (i=[string length]-1; i >= 0; i--) {
ch = [string characterAtIndex:i];
if ('0' <= ch && ch <= '9') {
num += x * (ch - '0');
} else if ('a' <= ch && ch <= 'f') {
num += x * (ch - 'a' + 10);
} else if ('A' <= ch && ch <= 'F') {
num += x * (ch - 'A' + 10);
}
x *= 16;
}

*anObject = [NSNumber numberWithInt:num];

return YES;
}
@end


カスタムのフォーマッタを作る方法は ADCにマニュアルがある。
Creating a Custom Formatter

それによると3つのメソッドをオーバライドすべしと書いてあるが2つだけでも動作した。
まず #stringForObjectValue: は、わたされた値を希望する書式に変換するコードを書く。今回は10進数値を 16進数文字列に変換している。

次に #getObjectValue:forString:errorDescription: ではその逆に、入力された文字列を値に変換する。サンプルでは 16進数文字列を int値に変換している。

フォーマッタができたら、最後にこれを GUIのコントロールへ接続する。接続方法は InterfaceBuilder を使う方法とコードで書く2つの方法がある。サンプルでは InterfaceBuilderを使っている。



HexFormatterを InterfaceBuilderでインスタンス化しておいて、目的のコントロール(ここでは NSTextFieldCell)の アウトレット formatter へ接続する。



これで表示・入力する時にフォーマッタが適用される。


コードで接続する時はコントロールに対して setFormatter: を投げれば良い。こんな感じ。
 HexFormatter* hex_formatter = [[[HexFormatter alloc] init] autorelease];
[cell_in setFormatter:hex_formatter];
[cell_out setFormatter:hex_formatter];

2008年4月10日木曜日

NSValueTransformer

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

バインディングした値の表示形式を変えたい。Value Transformer を使ってみることにした。まずは動作を確認するためにサンプルを作ってみる。

VTSample.zip



すべて同じモデル(値)とバインディングされている。上は 10進数での入力・表示する。下は NSValueTransformerを使い同じ値を 16進数で入力・表示させる。


NSValueTransformerの使い方は ADCに書かれている。
Value Transformer Programming Guide

これに沿ってコーディングしていく。

まずはヘッダファイル。NSValueTransformerのサブクラスを作る。
HexValueTransformer.h

@interface HexValueTransformer : NSValueTransformer {

}
@end



そして実装。
HexValueTransformer.m

まず変換後のオブジェクトのクラス型を定義する。
+ (Class)transformedValueClass
{
return [NSString class];
}



そして変換コード。ここでは入力値(NSNumber)を 16進数文字列として返す。これが表示で使われる。
- (id)transformedValue:(id)value
{
NSString* hex_value = [NSString stringWithFormat:@"%x", [value intValue]];
return hex_value;
}


続いて入力された値の変換。まず #allowsReverseTransformation をオーバライドして YESを返す。
+ (BOOL)allowsReverseTransformation
{
return YES;
}


そして入力値の変換コードを書く。簡易的な16進数文字列->NSNumber 変換コードを書いてみた。
- (id)reverseTransformedValue:(id)value
{
int num = 0;
int x =1;
int i = 0;
unichar ch;
for (i=[value length]-1; i >= 0; i--) {
ch = [value characterAtIndex:i];
if ('0' <= ch && ch <= '9') {
num += x * (ch - '0');
} else if ('a' <= ch && ch <= 'f') {
num += x * (ch - 'a' + 10);
} else if ('A' <= ch && ch <= 'F') {
num += x * (ch - 'A' + 10);
}
x *= 16;
}

return [NSNumber numberWithInt:num];
}


これで準備ができた。この他に2つほど行なうことがある。

1つめは変換クラスのインスタンスの登録。ADCのドキュメントによれば、通常はアプリケーションのデリゲートクラスの初期化コードで行なうとのこと。サンプルではコントローラクラスに下記のようなコードを書いた。

MyController.m
static BOOL initialized = NO;
+(void)initialize
{
if (!initialized) {
HexValueTransformer *transformer = [[[HexValueTransformer alloc] init] autorelease];
[NSValueTransformer setValueTransformer:transformer
forName:@"HexValueTransformer"];
initialized = YES;
}
}


forName: で指定する名前はバインディングで指定する時に使う。通常はクラス名で良い。ADCによれば名前を替えることで同じクラスの複数のインスタンスを登録できるようだ。そうであれば変換クラスをいくつも用意するのではなく、一つにまとめることもできそう。


そしてもう一つはバインディングでの指定。Interface Builderを使い 16進数入出力に使うテキストフィールドとラベルにこのクラスを指定する。



これでできあがり。

- - - -
これで目的は達成できそうだが、今回のような表現変換は NSValueTransformer の想定する用途とはちょっと異なる気もする。

2008年4月9日水曜日

WindowList(その2) - 全情報表示

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

前回スクリーン上にあるウィンドウのリストを表示するプログラムを作ったが、今回全ての情報が表示できるように手を加えた。

こんな感じ。ずいぶんと横幅を取るようになったが全ての情報が一覧できるようになった。ついでに CGWindowListOptionも制御できるようにした。


ソース:WindowList-2.zip

前回は WindowItemなるクラスを用意して、わざわざCGWindowListCopyWindowInfo() から取得した情報をつめ直していた。しかしよくよく考えると、CGWindowListCopyWindowInfo()で得られる結果は CFDictionaryRef であり、CGWindow.h で定義される下記のようなキーを持っている。

/* sample
kCGWindowAlpha = 1;
kCGWindowBounds = {
Height = 439;
Width = 262;
X = 15;
Y = 22;
};
kCGWindowIsOnscreen = 1;
kCGWindowLayer = 0;
kCGWindowMemoryUsage = 510808;
kCGWindowName = "Account Information";
kCGWindowNumber = 21155;
kCGWindowOwnerName = EasyToDo;
kCGWindowOwnerPID = 5452;
kCGWindowSharingState = 1;
kCGWindowStoreType = 2;
kCGWindowWorkspace = 1;
*/


CFDictionaryRef は NSDicrionaryと互換があるし、キーバリュー値コーディングに準拠している。
ということはバインディングが使えるのでは?

そこでやってみた。まずビュー(NSTableViewのカラム)とモデル(CFDictionaryRefの配列)を仲介する NSObjectController を用意し、MyControllerのインスタンス変数 windowList へバインドする。後はすべてのカラムの Key Path にキー名を指定する(下記例では "kCGWindowOwnerName")。


するとあっさりと、できた。

個々のカラムの表示設定はGUI側の設定だけで、コーディングはいっさい行っていない。すごいな。バインディングは。


全体の関係はこんな感じ。


コードも非常に簡単になった。

-(void)reloadList
{
[windowList removeAllObjects];

UInt32 option = ...(略);

CFArrayRef list =CGWindowListCopyWindowInfo(option, [window windowNumber]);

CFDictionaryRef w;
CFIndex i;

for (i=0; i < CFArrayGetCount(list); i++) {
w = CFArrayGetValueAtIndex(list, i);
[windowList addObject:(NSDictionary*)w];
}

[arrayController rearrangeObjects];
}


カラムに関するコードがまったく無い。

- - - -
リストを眺めているといろいろと面白いことがわかる。この辺りはまた別に紹介しよう。
ただ問題があって見ての通り "Bounds" がうまく表示できていない。バインディング値を表示用に変換する仕組みが必要だな。

2008年4月8日火曜日

WindowList(その1) - しつこく CGWindowListCopyWindowInfo

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

しつこく CGWindowListCopyWindowInfoの話題を。現在表示中のウィンドウリストをNSTableViewで表示させる。
ADCのサンプルがまさにそういうアプリなのだが、作成中のアプリで必要なので自分で組み立ててみる。

こんな感じ。


ソース:WindowList.zip

バインディングを使うとあっと言う間?にできる。組み立ては下のようになる。


注意点としては NSArrayController を介している為、直接 windowList(NSMutableArray)を変更しても NSTableViewに反映されないこと。NSArrayControllerのメソッド(addObject:など)を使うか、変更後に rearrangeObjectsを呼出す必要がある。NSTableViewの reloadData や setNeedDisplay: を使ってもダメ。

メイン部分のコードを掲載しておく。

MyController.m

-(void)reloadList
{
[windowList removeAllObjects];

CFArrayRef list =CGWindowListCopyWindowInfo(
(kCGWindowListOptionAll|kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements), kCGNullWindowID);
CFDictionaryRef w;
CFIndex i;

CGWindowID windowID;
CFStringRef name;
CFStringRef owner;
CGRect rect;

for (i=0; i < CFArrayGetCount(list); i++) {
w = CFArrayGetValueAtIndex(list, i);

CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),
kCGWindowIDCFNumberType, &windowID);

name = CFDictionaryGetValue(w, kCGWindowName);
owner = CFDictionaryGetValue(w, kCGWindowOwnerName);

CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect);

WindowItem* item = [[[WindowItem alloc] init] autorelease];
[item setValue:[NSNumber numberWithInt:windowID] forKey:@"windowID"];
[item setValue:(NSString*)name forKey:@"name"];
[item setValue:(NSString*)owner forKey:@"owner"];
NSRect rect2 = NSRectFromCGRect(rect);
[item setValue:NSStringFromRect(rect2) forKey:@"frame"];
[windowList addObject:item];
}

[arrayController rearrangeObjects];
}



- - - -
作ってはみたものの、表示情報が足りないな。
全情報を表示するよう手を加えよう。