ページ

2009年12月31日木曜日

SimpleCap - 1.1.0リリース

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

SimpleCap 1.1.0 をリリースしました。

ダウンロード - MacOSX用スクリーンキャプチャ SimpleCap





変更点は次の通りです。

追加・変更機能
 1. Mac OS X v10.6 に対応した
 2. 環境設定でホットキーが変更できるようになった
 3 ログイン時自動起動設定を追加した
 4 QuickConfig他、ボタンを押した時にメニューが出る様にした。
 5. [範囲選択キャプチャ] Undo/Redo が使えるようになった(Command+Z)
 6. [メニューキャプチャ]メニューバーを実際の幅に合わせるオプションを追加した
 7. [ウィンドウキャプチャ] 前回選択したウィンドウが次回選択済みになるようになった
 8. [SimpleViewer] アプリケーションボタンパレットを追加した
 9. [SimpleViewer] クリップボードへのコピーを追加した
10. [SimpleViewer] 画像ファイル複製を追加した
11. [SimpleViewer] 縮小保存を追加した

バグ修正
 1. 「環境設定...」で設定した「画像フォーマット」がキャプチャ時のクイック設定に反映されない現象に対応
 2. [マルチスクリーン利用時] メニューバーが存在するスクリーンのサイズが、他のスクリーンのサイズよりも小さい場合に正確にキャプチャが撮れない現象に対応
 3. [マルチスクリーン利用時] ウィンドウキャプチャが正確に撮れない現象に対応
 4. ウィンドウキャプチャでタイマーキャンセルした時に選択中のウィンドウのサイズが更新されない現象に対応
 5. [Mac OS X v10.6] スクリーンキャプチャで動作が止まる現象に対応
 6. [Mac OS X v10.6] ウィンドウキャプチャでウィンドウ選択中に落ちる現象に対応
 7. [Mac OS X v10.6.2] 保存形式がJPEGで影付きの場合に縁が黒くなる現象に対応


- - - -
バグ、改善要望などありましたら、このブログのコメントでもメールでも良いので是非お知らせください(※コメントはスパム対策で許可制になっていますが基本すぐに公開します)。

2009年12月30日水曜日

Spaces - アクティブなspaceの番号を得る(プライベート関数使用)

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

Leopard から導入された Spaces





現在表示しているアクティブな space(workspace?)をプログラム内で得る方法は今のところプライベート関数を使うしかないようだ。

detecting when the active space changes under leopard / entries / tonyarnold.com

CocoaDev: CoreGraphicsPrivate

上記を参考にしてサンプルを書いてみた。CGSGetWorkspace を使う。


typedef int             CGSConnection;
extern OSStatus CGSGetWorkspace(const CGSConnection cid, int *workspace);

@implementation ActiveSpaceAppDelegate

@synthesize window;


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
int spaceNumber = -1;
int cid=[NSApp contextID];
CGSGetWorkspace(cid, &spaceNumber);
NSLog(@"spaceNumber=%d", spaceNumber);
}


CGSGetWorkspace へ渡す CGSConnection には -[NSApplication contextID] が使える。ただこれも非公開メソッド。Xcodeでコンパイルすると Warning が出る。



実行するとログへ実行時の space番号が書き出される。

2009-12-30 07:26:00.155 ActiveSpace[2348:80f] spaceNumber=2


Workspace関係では他にこんなのがあった。
extern OSStatus CGSGetWorkspaceWindowCount(
 const CGSConnection cid, int workspaceNumber, int *outCount);
extern OSStatus CGSGetWorkspaceWindowList(
 const CGSConnection cid, int workspaceNumber, int count, 
    int* list, int* outCount);

指定した Workspace内のウィンドウの数やリストが取得できるようだ。

他にも面白そうな関数がいくつかある。プライベート関数なので使い方が難しいが機会があったら他も試してみたい。

- - - -
なお SimpleCap で使っている CGWindow 系の関数で取得できるウィンドウ情報には、そのウィンドウが表示されている Workspace も含まれる。

CGWindow.h

/* Optional: The value for this key is a CFNumberRef encoding as a kCFNumberIntType
 * the workspace the window is associated with. */
CG_EXTERN const CFStringRef kCGWindowWorkspace AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;



サンプルコードあり:GitHub からどうぞ
ActiveSpace at master from xcatsan's SampleCode - GitHub

2009年12月29日火曜日

SimpleCap - v1.1追い込み

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

v1.1 開発経過

ビューアに新メニューを追加した




「複製」するとファイル名の後ろに (1) (2) ...と連番がつく。「コピー」はクリップボードへのコピー。「現在の...」はその説明の通り。後者2つは1.0でも実装されている(隠し機能)。


ウィンドウキャプチャで前回選択したウィンドウを次回も選択されるようにした




同じウィンドウ(複数可)を連続してキャプチャしやすくなった。

- - - -
年内公開にむけて現在最終調整中..

2009年12月28日月曜日

Core Data : 複数の NSManagedObjectContext を使う - Optimistic Locking

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

複数の NSManagedObjectContext

Core Data では一つのDBに対して複数の NSManagedObjectContext を使うことができる。NSManagedObjectContext を複数使うケースとしては、例えば同じDBを扱う複数のビューを用意して編集途中の状況をお互いに影響与えないような使い方が考えられる。編集が確定した時だけ他のビューへその変更内容を反映する。


Optimistic Locking

ただしこの場合、複数のビューで同時に編集が行われるとデータに不整合が生じる恐れがある。Core Data ではこのような不整合を防ぐ為に Optimistic Locking(楽観的ロック)を採用している。Optimistic Locking では編集前に排他的ロックをかけることは行わず、編集を確定(commit)する時に不整合をチェックする。もし不整合が生じることが検出された場合は確定処理を失敗させる。Core Data では NSManagedObjectContext がデータを取得した時にバージョンも併せて保持していて、編集確定時に対象となるバージョンが一致していなければ(すなわち変更されている)不整合と判断する。


サンプルプログラム作成

このあたりの挙動をサンプルプログラムを作って確認してみよう。
(以下、Mac OS X 10.6 / Xcode 3.2 で実施)

まずテンプレートから  Cocoa Application (Use Core Data for storage)を選びプロジェクトを作成する。



AppDelegateクラスに2つの NSManagedObjectContext を追加する。

@interface CoreDataConflict_AppDelegate : NSObject
{
    NSWindow *window;
  
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
    NSManagedObjectModel *managedObjectModel;
    NSManagedObjectContext *managedObjectContext;

NSManagedObjectContext* moc1;
NSManagedObjectContext* moc2;

}
:

@property (nonatomic, retain, readonly) NSManagedObjectContext *moc1;
@property (nonatomic, retain, readonly) NSManagedObjectContext *moc2;



Getメソッドは自動生成せず自前で用意する。

- (NSManagedObjectContext *)moc1
{
if (moc1) {
return moc1;
}
NSPersistentStoreCoordinator* psc = [self persistentStoreCoordinator];
moc1 = [[NSManagedObjectContext alloc] init];
[moc1 setPersistentStoreCoordinator:psc];
return moc1;
}
- (NSManagedObjectContext *)moc2
{
if (moc2) {
return moc2;
}
NSPersistentStoreCoordinator* psc = [self persistentStoreCoordinator];
moc2 = [[NSManagedObjectContext alloc] init];
[moc2 setPersistentStoreCoordinator:psc];
return moc2;
}


データモデル用に Book クラス(NSManagedObject)を作る。

@interface Book :  NSManagedObject
{
}

@property (nonatomic, retain) NSString * author;
@property (nonatomic, retain) NSString * title;

@end



続いて Interface Builder を開き2つのウィンドウとテーブルビューを用意する。

NSArrayController をそれぞれに用意し、先ほどの NSManagedObjectContext へ接続する。

NSArrayController の接続状況(bindings)はこんな感じ。


実行

実行してみよう。起動するとテスト用データ1件が表示される。

両方の Title を変更し保存する(ctrl+S)。

すると2番目の NSManagedObjectContext(moc2)でエラーが検出された。

CoreDataConflict[11054:80f] moc2 error:
 Error Domain=NSCocoaErrorDomain Code=133020
 UserInfo=0xe37400 "Could not merge changes."

詳しい情報が UserInfo に入っているので見てみる。

CoreDataConflict[11054:80f] UserInfo: {
    conflictList =     (
                {
            cachedRow =             {
                author = "Stephen G. Kochan";
                title = "TEST TITLE 1";
            };
            newVersion = 2;
            object = " (entity: Book; id: 0xe11750 
                <x-coredata://071C66C0-BFAA-44AE-8C84-52E2B92C5FCB/Book/p1> ;
                data: {\n    author = \"Stephen G. Kochan\";
                       \n    title = \"TEST TITLE 2\";\n})";
            oldVersion = 1;
            snapshot =             {
                author = "Stephen G. Kochan";
                title = "Programming in Objective-C 2.0";
            };
        }
    );
}


問題となっているレコードが表示されてバージョンが異なっているのがわかる。


ソースコード

github からどうぞ
CoreDataConflict at SD0001 from xcatsan's SampleCode - GitHub


参考

Mac Dev Center: Core Data Programming Guide: Change Management

今回の検証のネタもと。複数 NSManagedObjectContext や Optimistic Locking について説明されている。

2009年12月27日日曜日

今週のCocoa情報(12/27) - 今週気になった Cocoaプログラミング情報の紹介

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

How to simulate a low level keypress on os x? - Stack Overflow

キー押しイベントをプログラムから擬似的に発生させる方法。

関連情報
openframeworks.cc • View topic - simulating keystrokes with CGEventCreateKeyboardEvent

how can i send a keyboard event to the app through pressing a button? - Stack Overflow



Creating a ZIP archive from a Cocoa application - Stack Overflow

ZIP ライブラリの話題。この質問は定期的に出る?下記が紹介されていた。

zip-framework - Project Hosting on Google Code

CocoaDev: ZipArchiveLibraryForCocoa

kolpanic / ZipKit / wiki / Home — bitbucket.org



What tool or technology does Apple use to create its documentation? - Stack Overflow

Apple のドキュメント作成に使われている技術は?という話題。下記が紹介されていた。

Doxygen

Mac Dev Center: HeaderDoc User Guide: Introduction

XEP Engine < Products < XML to PDF, PostScript, AFP, Print - RenderX(商用)

この中で事例として紹介されていた正規表現ライブラリが気になった。ちょっと試してみたい。
RegexKit Documentation
RegexKitLite



drawing in other processes windows on macosx? - Stack Overflow

他プロセスのウィンドウに描画ができるか?という話題。

MAC OS v10.6 から導入された IOSurface を使うとうまく実装できるらしい。

関連話題
Mac OS X: Can one process render to another process's window? - Stack Overflow

Need help with IOSurface & OpenGL for max os x snow leopard application - Stack Overflow

CocoAdHoc: Hidden Gems of Snow Leopard: IOSurface



How to debug Core Data crash on fetch request - Stack Overflow

CoreData のデバッグに関する話題。Mac Dev Center にトラブルシュートのドキュメントがある。
Mac Dev Center: Core Data Programming Guide: Troubleshooting Core Data



How to convert nib/xib to objective C code? - Stack Overflow

nib/xib から Objective-C コードを生成したいという話題。

nib2objc なるツールがあるらしい。ほー。ちょっと試してみたい。
Open Kosmaczewski - nib2objc (Objective-C, 2009)



How do I cede focus to the previous application in Cocoa? - Stack Overflow

自分のアプリを一時的に前面に出して使った後、元のアプリに戻してフォーカスをあてたい、という話題。

2009年12月26日土曜日

Pixelmator 1.5 購入(割引クーポン適用)

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

以前紹介したことのある Pixelmator を購入。

Pixelmator




このソフトは Photoshop のようなフォトレタッチソフト。ブラシで絵を書いたり写真などの加工が行える。ソフト開発のアイコン作成などに使うつもり。今までは Illustrator を使っていたが、重いのとドロー系だけではいろいろと不便だった。フリーのソフトも見てきたがなかなか良いものがなく購入に踏み切った。

ホームページからクレジットカードを使ってオンライン購入した。
購入画面にクーポン入力欄があったのでネットで調べてみると、クーポン情報共有サイトがあってそこに 20% Off の COUPON CODE が見つかった。
Pixelmator Coupon Codes - all coupons, discounts and promo codes for pixelmator.com



これを試したところ 20% Off に成功、日本円で4千円台で買うことができた。40% Off の CODEも試してみたが残念ながら通らなかった。それでも20%割引は大きい。決済は日本円で行えて JCBカードを使うこともできた。購入を考えている人はトライしてみるといいかも(自己責任で)。


ソフトは自体は、非力な PowerBook G4 でも起動が速く動きも軽快で結構うれしい。これから少しづつ使っていきたい。

なお購入ページは英語だが、ソフト自体は日本語化されている(ヘルプも日本語がある)。

2009年12月25日金曜日

NSView の表示順序と setWantsLayer:

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

(layer-backed view の覚え書き)

NSView が複数のサブビューを持っている場合、後から追加したものが上に表示される。ところがサブビューの一つが -[NSView setWantsLayer:YES] を呼んで CoreAnimation layer を使う宣言をすると、追加順序が無視されて一番上に表示される。

Mac Dev Center: NSView Class Reference


以下、SimpleCap での例



サブビューの追加順序からすると右のボタンの方が上に表示されるのだが、左の画像を表示しているビューは -[setWantsLayer:YES]を呼んでいるために上に表示されてしまった。SimpleCapの場合、画像の載っているビューは CoreAnimationのトランジションを使う為に layer-backed view にしてある。

これを解決するには右のボタンを表示するビューも -[setWantsLayer:YES] を使ってlayer-backed view にすれば良い。



- - - -
なお -[setWantsLayer:YES] を使うとそのビューでの -[NSView convertPointFromBase:] の挙動が変わる。イベントから取得したウィンドウ内の座標をビューのローカル座標に変換するには -[NSView convertPoint:FromView:] を使う。これで少々ハマってしまった。。



2009年12月24日木曜日

Plug-in開発で検証・テスト用環境を作る〜 Xcodeで複数のターゲットを設定する

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

今回開発する BlogAssistant はSafari のプラグインを予定しているが、開発途中の動作確認をいちいち Safariに読み込ませて行うのは非効率このうえない。とはいっても別途検証用のアプリを作るとソースコードの管理が面倒になる。1つのソースコードでプラグインと検証用アプリの2つをビルドできたら便利だ。実はXcodeはそれができる。今回はこの話題を扱う。

ターゲット

Xcode ではソフトウェア開発の最終成果物のことをターゲットと呼んでいる。このターゲットは新規にプロジェクトを作成する時に用意されているテンプレートを使うと標準で1つ用意される。

(例)




このターゲットは固定的なものではなく、開発目的に応じて変更したり、複数作成することができる。

今回は1つのプロジェクトでSafari用プラグインと通常のアプリケーションの2つのターゲットを作成してみる。


※以下、Mac OS X v10.5 / Xcode 3.1 での説明。


ひな形の作成(Safariプラグイン用ターゲット)

Xcodeを起動して新規プロジェクトを作成する。テンプレートは WebKit Plug-in を指定し、プロジェクト名を BlogAssistant とする。

動作確認だけしたいのでビルドして Safari へ登録する。build 配下の BologAssistant.webplugin を ~/Library/Internet Plug-Ins/ へコピーする。



Safari を再起動してヘルプメニューの「インストール済みプラグイン」を選ぶ。



表示された。ひな形がちゃんと組み込まれたのがわかる。ビルドは問題なし。


検証用ターゲット

続いて通常のアプリケーションをビルドする為のターゲットを用意する。

まずメニューから「新規ターゲット...」を選ぶ。

テンプレートから Application を選ぶ。

ターゲット名は "BlogAssistant Test App" とした。

完了すると、ターゲットリストに表示される。


*.plist もきちんと用意される。


この時点でビルドメニューにも BlogAssistant Test App が現れる。

ここまででターゲットのひな形ができた。

次は最低限アプリケーションが動く為の設定を行う。

まず main.m を作成する。これは他のアプリケーションからコピーしてくれば良い。


情報を開き、ターゲットのタブ内で BlogAssistant Test App へチェックをつけておく。一方 BlogAssistant(プラグイン用ターゲット)からはチェックを外しておく。こうするとビルド時に適切に処理される。



続いて XIB の追加。動作確認を兼ねてウィンドウを表示させたいので用意しておく。

名前を MainMenu.xib とする。こうすると起動時に自動的に読み込まれる(このファイル名は *.plist の "Main nib file base name" エントリに記載されている)。またターゲットの BlogAssistant Test App へチェックを入れておき、一方プラグイン用ターゲットの方からは外しておく。

ウィンドウを一つ追加する。

ここまででターゲットのリストを見ると BlogAssistant Test App の方にも数字が出てくる。これはビルド時に各フェーズで処理対象となるファイルの数。先ほどターゲットのチェックボックスの状態がここへ反映される。クリックすると対象となるファイルを確認することができる。



さてビルドしてみよう。アクティブターゲットに BlogAssistant Test App を指定してビルドを実行。

そして実行すると..

出た。

- - - -
これで試行錯誤しつつプラグイン開発が進められそうだ。
※プラグイン自体のデバッグはまた別の話題

2009年12月23日水曜日

NSDrawThreePartImage と NSDrawNinePartImage

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

Mac OS X V10.5 から Application Kit Functions に NSDrawThreePartImage と NSDrawNinePartImage という関数が用意された。今回はこれを試してみた。

NSDrawThreePartImage

関数のシグネチャは次の通り。

void NSDrawThreePartImage(NSRect frame,
   NSImage *startCap,
   NSImage *centerFill,
   NSImage *endCap,
   BOOL vertical,
   NSCompositingOperation op,
   CGFloat alphaFraction,
   BOOL flipped
);

(参照)Mac Dev Center: Application Kit Functions Reference

この関数は単一方向に伸縮するような絵を描くのに使うようだ。左端、右端、そしてその間の画像を用意すると任意の幅の絵を描く。伸縮するボタンやスライダ、スクロールバーなどに使えそうだ。

(イメージ)
+--------+--------------------+-------+
|startCAp|    centerFill      |endCap |
|        |    ← 伸縮 →      |       |
+--------+--------------------+-------+

vertical フラグが YES の場合は垂直方向、NO の場合は水平方向で描画される。


サンプルを作ってみた。

画像は Safari4 の Resources に入っているものを参照した(※サンプルソースコードには付属していない)。




コードは、カスタムビューに描画コードを載せて、デフォルトのウィンドウのContentViewへ貼り付けた。

CustomView.m
- (void)drawRect:(NSRect)dirtyRect {

    // Drawing code here.

if (!startCap) {
NSString* safariPath = @"/Applications/Safari.app/Contents/Resources";
startCap = [[NSImage alloc] initWithContentsOfFile:[safariPath stringByAppendingPathComponent:@"PurplePopUpLeft.png"]];
centerFill = [[NSImage alloc] initWithContentsOfFile:[safariPath stringByAppendingPathComponent:@"PurplePopUpFill.png"]];
endCap = [[NSImage alloc] initWithContentsOfFile:[safariPath stringByAppendingPathComponent:@"PurplePopUpRight.png"]];
}

CGFloat margin = 20.0;

NSRect frame = [self frame];
frame.origin.x += margin;
frame.origin.y += margin;
frame.size.width -= margin*2.0;
frame.size.height = [centerFill size].height;

NSDrawThreePartImage(frame, startCap, centerFill, endCap, NO, NSCompositeSourceOver, 1.0, NO);
}


まず実行直後。

短くしてみる。

伸ばしてみる。

これは便利。Safari4 の Resources ファルダを見るとこの形態の画像は多いのでこの関数が使われているのかもしれない。

frame を画像サイズより小さくするとどうなるのか?

縮小してちゃんと描いてくれる。では大きくするとどうなるのか?

出た。
画質はともかくとして縮小、拡大しても可能な限り形態を保ったまま描画してくれるようだ。


NSDrawNinePartImage

名前から察するにこれは先程の関数の縦横両方向バージョンのようだ。引数もその分多い。
void NSDrawNinePartImage(NSRect frame,
   NSImage *topLeftCorner,
   NSImage *topEdgeFill,
   NSImage *topRightCorner,
   NSImage *leftEdgeFill,
   NSImage *centerFill,
   NSImage *rightEdgeFill,
   NSImage *bottomLeftCorner,
   NSImage *bottomEdgeFill,
   NSImage *bottomRightCorner,
   NSCompositingOperation op,
   CGFloat alphaFraction,
   BOOL flipped
);

(参照)Mac Dev Center: Application Kit Functions Reference

これもサンプルを作ってみた。

適当な画像がみつからなかったので Bitmemo の画像を使ってみた(※サンプルソースコードにコピーを添付)

CutomView.m

- (BOOL)isFlipped
{
return YES;
}
- (void)drawRect:(NSRect)dirtyRect {
    // Drawing code here.
[[NSColor lightGrayColor] set];
NSRectFill(dirtyRect);

if (!topLeftCorner) {
topLeftCorner     = [[NSImage imageNamed:@"frame_top_left"] retain];
topEdgeFill       = [[NSImage imageNamed:@"frame_top_mid"] retain];
topRightCorner    = [[NSImage imageNamed:@"frame_top_right"] retain];
leftEdgeFill      = [[NSImage imageNamed:@"frame_mid_left"] retain];
centerFill        = [[NSImage imageNamed:@"frame_mid_mid"] retain];
rightEdgeFill     = [[NSImage imageNamed:@"frame_mid_right"] retain];
bottomLeftCorner  = [[NSImage imageNamed:@"frame_bottom_left"] retain];
bottomEdgeFill    = [[NSImage imageNamed:@"frame_bottom_mid"] retain];
bottomRightCorner = [[NSImage imageNamed:@"frame_bottom_right"] retain];
NSLog(@"%@", bottomRightCorner);
}

CGFloat margin = 20.0;

NSRect frame = [self frame];

frame.origin.x += margin;
frame.origin.y += margin;
frame.size.width -= margin*2.0;
frame.size.height -= margin*2.0;
NSDrawNinePartImage(frame, topLeftCorner, topEdgeFill, topRightCorner, leftEdgeFill, centerFill, rightEdgeFill, bottomLeftCorner, bottomEdgeFill, bottomRightCorner, NSCompositeSourceOver, 1.0, YES);
}


実行するとこんな感じ。



出た。ウィンドウの拡大縮小に合わせて描画される。

縮小しすぎると表示が崩れる。一定以上に小さくならないように制限が必要。



なお top-left と bottom-right corner image は端の大きさを決めるの使われているので、例えば bottomRightCorner が nil だったりすると右と下の画像はまったく表示されない。

画像ファイルをスペルミスして気がついた。

(関連情報)
NSDrawNinePartImage
あらかじめ枠となる画像を1枚用意しておき、それを9分割して NSDrawNinePartImage で使う方法が書かれていた。面白い。


Re: NSDrawNinePartImage draws slowly in CALayer
CALayer で使うと遅いという話題。シンプルな NSView で使っている限りでは遅くはなかった(v10.6)。


ソースコード

GitHubからどうぞ。

NSDrawNinePartImageStudy at master from xcatsan's SampleCode - GitHub

NSDrawThreePartImageStudy at master from xcatsan's SampleCode - GitHub

2009年12月22日火曜日

SimpleCap - ファイル更新反映

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

(前回)Cocoaの日々: FSEvent - フォルダを監視する

SimpleCap へフォルダ監視の仕組みを導入して他のアプリでの変更を表示に反映するようにした。


例えば、まずキャプチャを行う。


プレビューを開き、画像を加工する。ここではグレースケール化して保存する。

数秒後に SimpleCap が変更を検出してメッセージを表示する。

表示中の画像も最新のものに置き換わる。


アプリ実装時の注意点など


プリファレンスで保存先が変更になった場合
KVOなどを使って検出し、一旦以前の FSEvent を破棄した後、再登録する。
破棄例:

if (_fsevent_stream) {
FSEventStreamStop(_fsevent_stream);
FSEventStreamInvalidate(_fsevent_stream);
}


ファイルが更新されたどうかの確認
FSEvent はフォルダ単位での更新しかわからないので、表示中のファイルが更新されたかどうかは自分で調べる必要がある。SimpleCap では表示中の画像の最終更新日時をとっておき、FSEvent が発生した時に比較している。

他の操作との整合性(新規作成、削除、名前変更など)
フォルダ更新は、上記のファイル更新のほかキャプチャ後の新規作成や、削除、名前更新などさまざまな操作によって通知される。これらの操作と目的のファイル更新時の処理とが矛盾なく動作するように調整する必要がある。今回は先の最終更新日時の比較だけでうまく動いている。

Nib経由で NSUserDefaultsを取得する場合
FSEvent へ登録するパスを Nib内のNSUserDefaultsController から取得している場合、FSEvent登録タイミングに Nibの読み込みが終わっていないとパスが nilとなり登録に失敗する(あたりまえだが)。
SimpleCap[28181] (CarbonCore.framework) FSEventStreamCreate: _FSEventStreamCreate: ERROR: could not allocate 0 bytes for array of path strings
SimpleCap[28181] (CarbonCore.framework) FSEventStreamScheduleWithRunLoop(): failed assertion 'streamRef != NULL'
  
SimpleCap[28181] (CarbonCore.framework) FSEventStreamStart(): failed assertion 'streamRef != NULL'

この場合は確実に NSUsesrDefaultsController から値が取得できるように、awakeFromNib 内で FSEvent への登録を行う。

2009年12月21日月曜日

NSTableView にカスタムセルを表示する (14) ボタンをつける〜ボタンの表示とイベント処理

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

(前回)Cocoaの日々: NSTableView にカスタムセルを表示する (13) ボタンをつける〜マウスイベントの結果をセルで使う

ようやくボタンの作成まできた。先に出来上がりのイメージを掲載しておく。

起動直後のウィンドウ


ボタンの上にマウスを載せる




ボタンを押す(分かりやすい様に目立つ色にしてある)




実装

前回からも色々手を入れたのでソースコードを見てもらうのが一番わかりやすいと思う。ここではポイントだけ解説する。

CustomCellControl.h

#import

enum CONTROL_STATE
{
CONTROL_STATE_OFF,
CONTROL_STATE_ON,
CONTROL_STATE_OVER
};
@interface CustomCellControl : NSObject {

NSRect frame;
NSString* title;

id target;
SEL action;
}
@property (retain) NSString* title;
@property (retain) id target;
@property (assign) SEL action;

- (id)initWithOrigin:(NSPoint)origin;
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView state:(NSInteger)buttonState;
- (BOOL)hitTestAtPoint:(NSPoint)testPoint inFrame:(NSRect)cellFrame;




カスタムセルに載せるコントロールクラスを定義した。ボタンクラスの親にあたりイベント処理など動作上必要な基本的な実装をここに書く。

CustomCellButton.h

@interface CustomCellButton : CustomCellControl {

}
- (id)initWithTitle:(NSString*)aTitle at:(NSPoint)aPoint;

@end


描画メソッドをオーバーライドしてボタンを描いている。


CustomCell.h

@interface CustomCell : NSCell {
 :


NSMutableArray* controls;
}



CustomCell にコントロール間利用の配列を追加した。

CustomTableView からイベントが渡ってきたら、イベント処理対象のコントロールを探し出してこれを取っておく。
CustomCell.m

-(void)handleMouseEntered:(NSEvent*)theEvent frame:(NSRect)cellFrame inView:(NSView*)controlView
{
self.handlingValue = [self objectValue];
self.handlingEvent = theEvent;
self.handlingControl = [self controlOnMouse:theEvent frame:cellFrame inView:controlView];
}


そして描画時にその情報を使ってボタンの表示を描き分ける。

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
   :

NSInteger controlState;

for (CustomCellControl* cellControl in controls) {
if (handlingControl == cellControl) {
if (isMouseDown) {
controlState = CONTROL_STATE_ON;
} else {
controlState = CONTROL_STATE_OVER;
}
} else {
controlState = CONTROL_STATE_OFF;
}
[cellControl drawWithFrame:cellFrame
  inView:controlView
state:controlState];
}
}

見た目はたくさんあるボタンだが、インスタンスは1つだけなのでこうやって(ボタンの)外部から状態を渡して、描き分けさせる必要がある。


同様に CustomCellLabel, CustomCellImage を作り、描画はそれらにおこわなせるようにした。


ソースコード

GitHubからどうぞ。
CustomCellWithCoredata at 20091221b from xcatsan's SampleCode - GitHub


- - - - -
作っているうちに結局NSCell内に NSCellを作るような形となってしまい、一時はいっそのこと NSCellのコンテナを作ってしまった方が良い気もした。そうすれば Cocoaで提供されているすべてのコントロール(セル)が使える。ただ一方でオリジナルの実装は低機能だがしがらみが無い分好きにできる。低機能ではあるが自前のコントロールを作るのは結構楽しかった。

これでカスタムセルでどこまでできるのかが大体掴めた。汎用的に仕上げる誘惑は残るが、このままではいつまでたっても開発が進まないので後はアプリ(BlogAssistant)を作る過程で必要に応じて改良していくことにする。

2009年12月20日日曜日

今週のCocoa情報(12/20) - 今週気になった Cocoaプログラミング情報の紹介

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

Create a Map with "Struct" in NSDictionary in Objective-c? - Stack Overflow
 Struct(Cの構造体) を NSDictionary へ入れる話。NSValue でラップしてやる。


MAAttachedWindow NSWindow subclass » Matt Legend Gemmell
 Snow Leopard でドックを右クリックすると出てくる吹き出し風ウィンドウを表示するライブラリ。


drawing NSImage tiles - Stack Overflow
 NSDrawThreePartImage( ) という関数があるらしい(初めて知った)。今度試してみよう。
 Mac Dev Center: Application Kit Functions Reference


What is the best scripting language to embed in Mac OS X 10.6 applications? - Stack Overflow
 アプリ組み込みのスクリプティング環境は何が良いか?という話題。
 AppleScript、F-Script(Smalltalk系)、JSTalk(知らなかった!)、
 Nu(知らなかった、LISP系?)、ブリッジ経由で Perl, Python, Ruby、
 それとRubyCooca,にPyObjC,CamelBones(for Perl)、MacRuby などが上がっていた。


Getting an array of properties for an object in Objective-C - Stack Overflow
 プロパティをダンプする方法。class_copyPropertyList( ) が使えるらしい。
 Mac Dev Center: Objective-C Runtime Programming Guide: Declared Properties


Coffee & Cocoa » Blog Archive » NSCell Image and Text Sample
 NSTableView でカスタムセルを表示するサンプルプログラム。iPhone3.0のテーブル表示っぽい。


How does -performSelector:withObject:afterDelay: work? - Stack Overflow
 メソッドを遅延実行できるとは知らなかった。

2009年12月19日土曜日

NSCell の内容をポップアップ表示する

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

NSCell には面白いメソッドが用意されていて、マウスをセルの上に持っていった時にポップアップでセルの内容を表示することができる。





expansionFrameWithFrame:inView:

Returns the expansion cell frame for the receiver.
- (NSRect)expansionFrameWithFrame:(NSRect)cellFrame inView:(NSView *)view


Mac Dev Center: NSCell Class Reference


サブクラスでオーバーライドしてポップアップ表示するセルのフレームを返すと、後は自動的にポップアップが表示される。

(例)少しづらした位置にセルよりも大きいサイズでポップアップを表示する。


- (NSRect)expansionFrameWithFrame:(NSRect)cellFrame inView:(NSView *)view
{
NSRect frame = cellFrame;
frame.origin.x += 20;
frame.origin.y -= 20;
frame.size.width = 440;
frame.size.height = 100;
return frame;
}


セルは小さいが、マウスを置いて数秒待つと。。



指定した位置、サイズでポップアップが表示される。




ポップアップ表示する内容は -[drawWithExpansionFrame:inView:] を使ってカスタマイズすることができる。


drawWithFrame:inView:

Draws the receiver’s border and then draws the interior of the cell.
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView


Mac Dev Center: NSCell Class Reference

デフォルトではこのメソッドから -[drawWithFrame:inView:] が呼び出される。