ページ

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];
}



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