2008年9月30日火曜日

波紋(その3)

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

CoocaSliedsのコードを読む。いくつかクラスがあるが、トランジション処理は SlideshowViewクラス に記述されている。
SlideshowViewはNSViewのサブクラスでメンバ変数には、表示中のイメージやトランジションのスタイル、マスク画像などが保持されている。

@interface SlideshowView : NSView
{
NSImageView *currentImageView; // an NSImageView that displays the current image, as a subview of the SlideshowView
int transitionStyle; // the style of transition to use; one of the SlideshowViewTransitionStyle values enumerated above
BOOL autoCyclesTransitionStyle; // set if we should automatically cycle our transitionStyle
CIImage *inputShadingImage; // an environment-map image that the transition filter may use in generating the transition effect
CIImage *inputMaskImage; // a mask image that the transition filter may use in generating the transition effect
}



まず awakeFromNib。ここではマスク画像など共通に使われる CIImageインスタンスを準備している。
- (void)awakeFromNib {
:
inputShadingImage = [[CIImage alloc] initWithBitmapImageRep:shadingBitmap];
:
inputMaskImage = [[CIImage alloc] initWithBitmapImageRep:maskBitmap];
}



続いて updateSubviewsTransition。ここはトランジションの切り替えで呼ばれる。
- (void)updateSubviewsTransition {
NSRect rect = [self bounds];
NSString *transitionType = nil;
CIFilter *transitionFilter = nil;
CIFilter *maskScalingFilter = nil;
CGRect maskExtent;

switch (transitionStyle) {
case SlideshowViewFadeTransitionStyle:
transitionType = kCATransitionFade;
break;
:
case SlideshowViewRippleTransitionStyle:
default:
transitionFilter = [[CIFilter filterWithName:@"CIRippleTransition"] retain];
[transitionFilter setDefaults];
[transitionFilter setValue:[CIVector vectorWithX:NSMidX(rect) Y:NSMidY(rect)] forKey:@"inputCenter"];
[transitionFilter setValue:[CIVector vectorWithX:rect.origin.x Y:rect.origin.y Z:rect.size.width W:rect.size.height] forKey:@"inputExtent"];
[transitionFilter setValue:inputShadingImage forKey:@"inputShadingImage"];
break;


CIRippleTransitionの設定が一通り行われる。続いて Core Annimation 設定。
    // Construct a new CATransition that describes the transition effect we want.
CATransition *transition = [CATransition animation];
if (transitionFilter) {
// We want to build a CIFilter-based CATransition. When an CATransition's "filter" property is set, the CATransition's "type" and "subtype" properties are ignored, so we don't need to bother setting them.
[transition setFilter:transitionFilter];
} else {
// We want to specify one of Core Animation's built-in transitions.
[transition setType:transitionType];
[transition setSubtype:kCATransitionFromLeft];
}

// Specify an explicit duration for the transition.
[transition setDuration:1.0];

// Associate the CATransition we've just built with the "subviews" key for this SlideshowView instance, so that when we swap ImageView instances in our -transitionToImage: method below (via -replaceSubview:with:).
[self setAnimations:[NSDictionary dictionaryWithObject:transition forKey:@"subviews"]];


この @"subviews"がよくわからない。



そして transitionToImage: 。このメソッドは SlidshowWindowController から呼出される。画像が次々と変わる時に transitionToImage: が呼出される。
- (void)transitionToImage:(NSImage *)newImage {
:
:
// Create a new NSImageView and swap it into the view in place of our previous NSImageView. This will trigger the transition animation we've wired up in -updateSubviewsTransition, which fires on changes in the "subviews" property.
NSImageView *newImageView = nil;
if (newImage) {
newImageView = [[NSImageView alloc] initWithFrame:[self bounds]];
[newImageView setImage:newImage];
[newImageView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
}
if (currentImageView && newImageView) {
[[self animator] replaceSubview:currentImageView with:newImageView];
} else {
if (currentImageView) [[currentImageView animator] removeFromSuperview];
if (newImageView) [[self animator] addSubview:newImageView];
}
[currentImageView release];
currentImageView = newImageView;
}


渡された画像(NSImage)を表示する NSImageViewを作成し、トランジションをかける。っと見えるのだがイマイチ理解できない。

[[self animator] replaceSubview:currentImageView with:newImageView];

この辺りがトランジションのアニメーション処理の肝だろうか。

2008年9月29日月曜日

波紋(その2)

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

ソースコードを読むつもりだったが時間がない。今日は関連情報の紹介だけ。

ADCのCore Image Programmingは日本語訳が用意されている。
Core Image プログラミングガイド

この中に波紋トランジションの記述がある。
トランジションフィルタ (Transition Filters) - 波紋トランジション


トランジションのコーディング方法も載っている。
トランジションエフェクトの使用

2008年9月28日日曜日

波紋(その1)

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

今回から波紋に取り組む。ここでいう波紋とはCoreImageのトランジションの事。SimpleCapにちょっとした視覚的効果を加えてみたくて前から気になっていた。他の実装をやりつつ並行して検証を進めてみる。

まずは調査から。
木下さんが Core Image の記事をマイコミジャーナルに書いている。
Core Imageで体験 - Mac OS Xの高速画像処理
この中で波紋(Ripple)についても書かれておりサンプルコードも掲載されていた。
Rippleトランジションにもトライ

こちらを試してみる。


画像は表示されるのだが何も動かない。?


他も探してみる。ADCにサンプルが上がっていた。これも試してみよう。
CocoaSlides

ビルドして実行すると画像の一覧が表示される。


画像にチェックを付けてスライドショーを開始すると様々なトランジションを試すことができる。


いい感じだ。まずはこのソースコードを読んでみよう。

2008年9月27日土曜日

環境設定(17)

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

今回はキャプチャに効果音を付ける。Cocoaには NSSound が用意されていて、これを使うとたった2行で音が出せる。

 NSSound *sound = [NSSound soundNamed:@"Submarine"];
[sound play];


今回はシステムに付属している /System/Library/Sounds/Submarine.aiff を鳴らしてみた。


音の有無が制御できるよう新たに環境設定に "Play Sounds"を加えた。



- - - -
コードは簡単にできたが、肝心の音が用意できない。
シャッター音はどこで手に入るのだろうか?
自分で作らないとダメだろうか。

2008年9月26日金曜日

環境設定(16)

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

角丸+影+白枠のケースを例にして処理を追ってみる。

まずCGWindowListCreateImage( ) でキャプチャ画像を用意し、NSImageに変換しておく。


次に最終画像を書き込む NSImage *output_imageを用意する。大きさは、影と白枠が付くので元画像よりも大きくなる。※説明しやすいように黄色で表現してある。


白枠用のパス NSBezierPath* frame_path を用意する。白枠が付かない場合も画像と同じ大きさで作っておく。影を描く時にこれを使う。なお今回は角が丸いので #bezierPathWithRoundedRect:xRadius:yRadius: を使う(角が丸くない場合は #bezierPathWithRect: を使う)。


output_imageのフォーカスをロックした上で、frame_pathを使って白く塗りつぶす。

 [output_image lockFocus];
[[NSColor whiteColor] set];
[frame_path fill];



塗りつぶす前に NSShadow*shadow を作り #set しておく。そうすると上記の様に塗りつぶした領域の下に影が付く。

最後に src_image を上に描く。


画像の角を丸く切り抜くには、角丸のパスを作り、これを使って描画時にクリッピングする。こんな感じ。



できあがり。



角丸、影、白枠の有無によって上記の大きさや、処理の有無を調整すればいろいろな組み合わせで加工できる。

ソースコードは SimpleCap の一部として後日公開予定。

2008年9月25日木曜日

環境設定(15)

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

実装してみた。

まずノーマル。


影付き。


白枠付き。


影+角丸。


白枠+影+角丸


解説はまた明日。

2008年9月24日水曜日

環境設定(14)

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

プレビューができたので、実際の実装に入る。手を入れるのは範囲選択処理を司る SelectionHandler。キャプチャ画像を作成するメソッドが1つにまとめられているので、ここに手を入れれば良い。

SelectionHandler.m

- (CGImageRef)capture
{
CGImageRef cgimage = CGWindowListCreateImage(NSRectToCGRect(_rect),
kCGWindowListOptionOnScreenBelowWindow,
[_capture_controller windowID],
kCGWindowImageDefault);}
return cgimage;


他のHandlerと同様、最後のファイル出力メソッドへは CGImage で渡している。一方、画像を加工するには NSImageの方が少なくとも自分にとっては都合が良い。そこで一旦、NSImageへ変換してそれを加工した後、最後にまた CGImageへ変換して返す事にする。そうすればこのメソッド以外に書き直す必要は無い。

こんな感じ。
- (CGImageRef)capture
{
// (1) キャプチャ
CGImageRef cgimage = CGWindowListCreateImage(NSRectToCGRect(_rect),
kCGWindowListOptionOnScreenBelowWindow,
[_capture_controller windowID],
kCGWindowImageDefault);}

// (2) NSImage へ変換
NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc] initWithCGImage:cgimage] autorelease];
NSImage* src_image = [[[NSImage alloc] init] autorelease];
[src_image addRepresentation:bitmap];

// (3) 加工
    :
    :
NSImage* output_iamge = 加工 ( src_image );

// (4) CGImageへ戻す
NSBitmapImageRep *out_bitmap = [NSBitmapImageRep imageRepWithData:[output_image TIFFRepresentation]];
CGImageRef out_image = [out_bitmap CGImage];


// (5) 結果を返す
return cgimage;
}


変換のオーバーヘッドはあるが、これで扱いやすくなった。実際の加工処理を入れていこう。

2008年9月23日火曜日

環境設定(13)

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

プレビューを実装してみた。

最初はこう。


白枠をつける。


それに影を追加。


最後に角丸。


白枠を外す。組み合わせは自由。



プレビューはこれでいいだろう。実際の機能を実装しよう。

2008年9月22日月曜日

環境設定(12)

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

環境設定は続く。次は選択範囲のオプション設定。

範囲選択で切り出した画像を出力時に加工する。白枠(White Frame)、影(Shadow)、角丸(RoundRect)の3つを用意する。


今回はプレビューの作成から入ろう。


おおっと。今日は時間切れでここまで。続きは明日。

2008年9月21日日曜日

環境設定(11)

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

プレビューの続き。


まずはマウスカーソルの表示。これは NSCursorから画像を取得して重ね合わせれば良い。

 [frame_image lockFocus];
   :
if ([[UserDefaults valueForKey:UDKEY_MOUSE_CURSOR] boolValue]) {
NSPoint cursor_point = NSMakePoint(50, 60);
[[[NSCursor arrowCursor] image] compositeToPoint:cursor_point operation:NSCompositeSourceOver];
}


マウスカーソルを描きたい NSViewのフォーカスをロックし(lockFocus)、いつものごとく#compositeToPoint:operation:で合成する。

するとこうなる。



次は影付け。NSShadow を使えば簡単にできる。
[NSGraphicsContext saveGraphicsState];          // (*0)

// Window Shadow
if ([[UserDefaults valueForKey:UDKEY_WINDOW_SHADOW] boolValue]) {

NSShadow* shadow = [[NSShadow alloc] init]; // (*1)
[shadow setShadowOffset:NSMakeSize(10.0, -10.0)];
[shadow setShadowBlurRadius:10.0];
[shadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.3]];
[shadow set]; // (*2)

}
// Compsite Image
[example_image compositeToPoint:draw_point operation:NSCompositeSourceOver]; // (*3)

[NSGraphicsContext restoreGraphicsState]; // (*4)


NSShadowのインスタンスを作成した後(*1)、set メッセージを投げてやる(*2)。するとこの後の描画には影が自動的に付く様になる。サンプル画像を描画する(*3)。ポイントはNSGraphicsContextの保持(*0)と復帰(*4)を前後に挟むこと。これらが無いと、この後の描画全てに影が付いてしまうことになる。

結果はこんな感じ。


なおサンプルの画像は次のように左上に余白があるもの使う。これは余白が無いと右上と左下の影が切れてしまう為。



最後に背景。下のような画像を用意して合成するだけ。


if ([[UserDefaults valueForKey:UDKEY_BACKGROUND] boolValue]) {
[[NSImage imageNamed:@"output_background"] compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
}



するとこうなる。


いい感じだ。

2008年9月20日土曜日

環境設定(10)

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

今回はプレビューを作る。前回までで、影、マウスカーソル、背景とオプション設定ができるようになったが、これを直感的に分かる様に例を表示したい(プレビュー)。

まずは InterfaceBuilderで NSImageView を配置する。


PreferenceContoller にアウトレットを用意し、配置したNSImageViewへ接続する。

PreferenceController.h

@interface PreferenceController : NSObject {
   :
IBOutlet NSImageView* _image_view;
   :
}





つづいて例で使う画像を用意する。これは手作業で加工して(地道に?)作る。ファイル名は output_example.tiff



最後にコーディング。まずは表示確認を目的として描画してみる。下記のメソッドを呼出して描画する。

PreferenceController.m
- (void)drawOutputExample
{
NSSize frame_size = NSMakeSize(120, 120);
NSImage* frame_image = [[NSImage alloc] initWithSize:frame_size];
NSImage* example_image = [NSImage imageNamed:@"output_example"];
NSPoint draw_point = NSMakePoint(0, frame_size.height - [example_image size].height);

[frame_image lockFocus];
[example_image compositeToPoint:draw_point operation:NSCompositeSourceOver];
[frame_image unlockFocus];
[_image_view setImage:frame_image];
}


120x120ピクセルの NSImageを作り、そこへ output_example.tiff を合成し NSImageViewで表示する。


実行するとこんな感じ。



- - - -
この画像に影を付けたり、マウスカーソルを重ねたりしてプレビューの役割をまかせる。

2008年9月18日木曜日

環境設定(9)

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

前回の続き。

SimpleCapではキャプチャ処理をハンドラの基底クラスにあたる HandlerBase で行っている。

※クラス構成は過去のブログをどうぞ:SimpleCap (2) - 基本機能実装


具体的には各ハンドラはキャプチャ実行時に、Windowクラスのリスト(CGWindowIDを含む)と矩形領域(CGRect)を HandlerBase#cgimageWihWindowList:cgrectへ渡して、CGImageを取得する。

HandlerBase.m

- (CGImageRef)cgimageWithWindowList:(NSArray*)list cgrect:(CGRect)cgrect
{
   :


この中で画像キャプチャを行う CGWindowListCreateImageFromArray( ) を呼出している。つまりここを押さえれば出力の画像を自由にできる。今回はここに背景画像の合成処理を加えた。といって背景(壁紙)の CGWindowID を CGWindowListCreateImageFromArray( )に渡す配列に加えるだけで簡単にできる。

   :
BOOL is_background = [[UserDefaults valueForKey:UDKEY_BACKGROUND] boolValue];   // (1)
   :
if (is_background) {
windowIDs[widx++] = [Window desktopWindowID];      // (2)
}
   :
CFArrayRef windowIDsArray = CFArrayCreate(kCFAllocatorDefault, (const void**)windowIDs, widx, NULL);
CGImageRef cgimage = CGWindowListCreateImageFromArray(cgrect, windowIDsArray, option);
free(windowIDs);


UserDefaultsから設定を取り出し、それによって背景のCGWindowIDを Window#desktopWindowID から取得して、CGWindowListCreateImageFromArray( )へ渡す配列へ加えている(1)(2)。

背景のCGWindowIDは WindowList(自作ソフト)を使って調べると"DeskTop"という名前がついていて、"Window Server"という名前のプロセスによって作成されているのが分かる。


Window#desktopWindowID は安易にこの名前を使って CGWindowIDを決定する(もっといい方法があれば是非教えて下さい)。

Window.m
static _desktop_windowID = -1;
+ (CGWindowID)desktopWindowID
{
if (_desktop_windowID != -1) {
return _desktop_windowID;
}

CFArrayRef ar = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
CFDictionaryRef window;
CFIndex i;

_desktop_windowID = kCGNullWindowID;
for (i=0; i < CFArrayGetCount(ar); i++) {
window = CFArrayGetValueAtIndex(ar, i);
NSString* name = (NSString*)CFDictionaryGetValue(window, kCGWindowName);
NSString* owner_name = (NSString*)CFDictionaryGetValue(window, kCGWindowOwnerName);
if ([name isEqualToString:@"Desktop"] && [owner_name isEqualToString:@"Window Server"]) {
CFNumberGetValue(CFDictionaryGetValue(window, kCGWindowNumber),
kCGWindowIDCFNumberType, &_desktop_windowID);
break;
}
}
return _desktop_windowID;
}


これで、できあがり。

これが。


こうなる。


- - - -
ほ。今日もブログはぎりぎり間に合った。
最近は仕事が忙しくてかなわない。

環境設定(8)

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

今回は "Background"オプション。背景(壁紙)をキャプチャに含める。
これは初実装。

いつもはこんな感じ。


オプションをONにするとこうなる。


メニューもこの通り。


時間が無いので解説はまた明日。

おお、なんとか今日に間に合った?

2008年9月17日水曜日

環境設定(7)

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

次はマウスカーソル。



マウスカーソルはファイルへ書き出す前の最後の段階で合成している。ここを抑えればいいだろう。その責務は CaputreControllerクラスが担っている。

CaptureContoller.m

- (void)saveImage:(CGImageRef)cgimage withMouseCursorInRect:(NSRect)rect
{
:
int is_mouse_cursor = [[UserDefaults valueForKey:UDKEY_MOUSE_CURSOR] intValue];

if (is_mouse_cursor && ...
// マウスカーソル画像の合成処理
:


マウスカーソルあり。


マウスカーソルなし。



よし。

2008年9月16日火曜日

環境設定(6)

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

今度はツールバーを増やし出力オプションを追加する。



まずはウィンドウの影の制御(Winow Shadow)から。

これは SimpleCap内部で使っている CGWindowListCreateImageFromArray( ) 関数の第三引数で制御できる。

CGWindow.h

enum {
kCGWindowImageDefault = 0, /* Default behavior: If a rect of CGRectNull is used
* bounds computation includes the framing effects, such as a shadow. */
kCGWindowImageBoundsIgnoreFraming = (1 << 0), /* If a rect of CGRectNull is used,
* ignore framing effects for bounds computation */
kCGWindowImageShouldBeOpaque = (1 << 1), /* The captured image should be
* opaque. Empty areas are white */
kCGWindowImageOnlyShadows = (1 << 2)
};


オプションの種類は全部で4つ。挙動は以前ブログで紹介したことがある。
CGWindowListCreateImage のオプション

今回はこのうちの kCGWindowImageDefault(影あり)とkCGWindowImageBoundsIgnoreFraming(影無し)を使おう。以前リファクタリングした時に CGWindowListCreateImageFromArray( ) の利用はハンドラのベースクラス HandlerBase#cgimageWithWindowList:cgrect へまとめてある。ここだけを変更すれば良い。

HandlerBase.m
- (CGImageRef)cgimageWithWindowList:(NSArray*)list cgrect:(CGRect)cgrect
{ CGWindowImageOption option;

if ([[UserDefaults valueForKey:UDKEY_WINDOW_SHADOW] intValue] == 1) {
option = kCGWindowImageDefault;
} else {
option = kCGWindowImageBoundsIgnoreFraming;
}
:
CGImageRef cgimage = CGWindowListCreateImageFromArray(cgrect, windowIDsArray, option);
:


実行してみる。

影あり


影無し



良さそうだ、

2008年9月15日月曜日

環境設定(5)

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

次はファイルの保管場所。


"Choose..."を押すとダイアログが開いて保存先を選べる。



ダイアログのオープンには NSPanel#runModalForDirectory:file:types: を使う。

PreferenceController.m

- (void)chooseImageLocation:(id)sender
{
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO];
[openPanel setCanChooseDirectories:YES];
[openPanel setCanCreateDirectories:YES];
[openPanel setAllowsMultipleSelection:NO];

int result = [openPanel runModalForDirectory:NSHomeDirectory()
file:nil
types:nil];

if (result == NSOKButton) {
NSArray* filenames = [openPanel filenames];
if ([filenames count] > 0) {
NSString* new_path = [filenames objectAtIndex:0];
[UserDefaults setValue:new_path forKey:UDKEY_IMAGE_LOCATION];
}
}
}


上記の UserDefaults は NSDefaultsController を初期化したりするラッパー。パネルでフォルダが選択されたらユーザでフォルトへ書き込む。

NSDefaultsController 経由の書き込みは #values で取り出したオブジェクトに対して #setValue:forKey: を投げる。するとNSDefaultsControllerとバインドされたビュー上のオブジェクトも値が更新される。

2008年9月14日日曜日

環境設定(4)

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

試しに一つ設定を追加してみる。


InterfaceBuilderへラベルとポップアップボタンを配置し、NSUserDefaultsControllerへ接続するたけでユーザインターフェイスはできあがり。

コードでは SimpleCap内でファイル保存を担当する FileManagerクラスに手を入れる。

FileManager.m

- (void)saveImage:(NSBitmapImageRep*)bitmap_rep
{
int image_format_tag = [[[[NSUserDefaultsController
sharedUserDefaultsController] values]
valueForKey:@"image_format"] intValue];
NSData* data;

switch (image_format_tag) {
case 1:
// GIF
data = [bitmap_rep representationUsingType:NSGIFFileType
properties:[NSDictionary dictionary]];
break;

case 2:
// JPEG
data = [bitmap_rep representationUsingType:NSJPEGFileType
properties:[NSDictionary dictionary]];
break;

case 0:
defaults:
// PNG
data = [bitmap_rep representationUsingType:NSPNGFileType
properties:[NSDictionary dictionary]];
break;

}
[data writeToFile:[self nextFilename] atomically:YES];
}


"image_format" という名前で NSUserDefaultsから値を取り出して利用する。


PNG形式。ブログの画像では分かりづらいが影の部分はアルファチャネルが保存されいる。


JPEG形式。背景が白色となっている。


GIF形式。影の部分が真っ黒になってしまった。


- - - -
GIFの問題はあるが、環境設定はこんな感じで使っていけることがわかった。
どしどし追加していこう。

2008年9月13日土曜日

環境設定(3)

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

動作確認ができたので一つづつ作り込んでいく。まずは一般のタブから。




- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
NSToolbarItem *toolbar_item = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];

if ([itemIdentifier isEqualToString:TOOLBAR_GENERAL]) {
[toolbar_item setLabel:NSLocalizedString(@"General", @"")];
[toolbar_item setImage:[NSImage imageNamed:NSImageNamePreferencesGeneral]];
[toolbar_item setTarget:self];
[toolbar_item setAction:@selector(click:)];
} else {
toolbar_item = nil;
}

return toolbar_item;
}


文字列は多言語対応できるようにリソースに定義したものを使う。

初期状態はべた書き。
- (void)awakeFromNib
{
_toolbar = [[NSToolbar alloc] initWithIdentifier:PREFERENCE_TOOLBAR];
[_toolbar setDelegate:self];
[_window setToolbar:_toolbar];
[_toolbar setSelectedItemIdentifier:TOOLBAR_GENERAL];
[_window setTitle:NSLocalizedString(@"General", @"")];
}


Mail.app や Safari では選択したツールバーの名称をウィンドウのタイトルにしていたので、それに見習う。

2008年9月12日金曜日

環境設定(2)

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

ツールバーを付けてみよう。新規に PreferenceConroller クラスを用意し、Nib経由でインスタンス化する。環境設定ウィンドウの delegate先であり、逆にアウトレットの接続もある。


以前のコードを流用して、まずは動作確認。


よし。

2008年9月11日木曜日

環境設定(1)

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

一通り検証が済んだので SimpleCap へ戻って実装に入る。
今回は空のウィンドウを用意し、メニューの "Prefereces.."を選択して開くところまで実装する。

まずは InterfaceBuilderで空の NSWindowを一つつくり、AppControllerのアウトレットへ接続する。




ウィンドウの設定はデフォルトから下記項目を変更しておく。
Release When Closed => OFF
Visible At Launch => OFF



こうすることでインスタンスは作成されるが、アプリ起動時には表示されない。また閉じても解放されない。


次にメニューのターゲットを AppControllerとし用意したメソッドへアクションを接続する。



このメソッドで先ほどのウィンドウを開く。

- (IBAction)openPereferecesWindow:(id)sender
{
[_preferences_window makeKeyAndOrderFront:self];
}



さあ実行してみよう。実行直後はウィンドウは表示されない。



メニューから "Preferences.."を選ぶ。



ウィンドウが表示された。

2008年9月10日水曜日

ツールバー(その12)

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

さらにユーザーデフォルトは続く。

前回のマニュアルを参考に NSUserDefaults#registerDefaults を使う。

例に従って .plist ファイルを作成する。Xcodeだと標準のエディタで編集できる。




前回のコードに追加する。

+(void)initialize
{
NSDictionary* user_defaults =
[NSDictionary dictionaryWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:@"UserDefaults" ofType:@"plist"]];

[[NSUserDefaults standardUserDefaults] registerDefaults:user_defaults];

NSDictionary* initial_values =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:1], @"check1",
[NSNumber numberWithInt:1], @"check2",
[NSNumber numberWithFloat:50.0], @"slider",
nil];
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:initial_values];
}



実行してみる。

NSUserDefaults#registerDefault: の値が使われている。NSUserDefaultsController#setInitialValues: よりも優先されている。




ここで "revertToInitialValues"ボタンを押す。するとNSUserDefaultsController#setInitialValues: の値が使われる。



この後、値を変更して save を押して終了させる。そして再び起動すると、最後に saveされた値が表示される。

- - - - - - - - - -

一連の検証をまとめると初期値の扱いは次の順番で使われることが分かった。

1. ~/Library/Preferences/*.plist に保存された値
2. NSUserDefaults#registerDefault: で設定した値
3. NSUserDefaultsController#setInitialValues: で設定した値

1. が無ければ 2.が使われ、2.が無ければ 3.が使われるという動きになる。

また、NSUserDefaultsController#revertToInitialValuesを呼ぶと 3. の値が使われる。3.の設定がなければ何も起らない。

2008年9月9日火曜日

ツールバー(その11)

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

(ツールバーというか、NSUserDefaultsControllerの話題ばっかりだが)

引き続き setInitialValues: の話題。

マニュアルをもう少し読むと使い方がだんだんわかってきた。
Cocoaバインディング:ユーザデフォルトおよびバインディング(日本語があった)

このドキュメントによれば、デフォルト値の検索順序は次のようになっている。

1. values に含まれる、対応するキーの値
2. NSUserDefaultsController の defaults メソッドが返す NSUserDefaults インスタンスに含まれる、対応するキーの値
3. 初期値辞書に含まれる、対応するキーの値


1. は NSUserDefaultsController が内部的に持っている値で場合によっては保存されていない。2. は NSUserDefaults(ファイル)に保存されている値。そして 3. は initialValues: で設定した値だと思われる。


またドキュメントによれば initialalues: をクラス初期メソッド (initialize)で使うのが良いらしい。

やってみる。
+(void)initialize
{
NSLog(@"initialized");
NSDictionary* initial_values =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:1], @"check1",
[NSNumber numberWithInt:1], @"check2",
[NSNumber numberWithFloat:50.0], @"slider",
nil];
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:initial_values];
}


NSUserDefaultsController は #sharedUserDefaultsControllerで取得できるので、わざわざアウトレットで接続して awakeFromNibまで利用を待たなくても良いことがわかった。


実行する。

初めて起動したときの初期状態が initializeで設定した値になった。一旦 saveすると、それ以降は値がファイルに保存され起動時に使われる。

2008年9月8日月曜日

ツールバー(その10)

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

NSUserDefaultController には #revertInitialValues というメソッドもある。これはどう働くのか?

まずはボタンを接続して呼出してみる。


何も起らない。当たり前か。




マニュアルを読むと編集中の値を破棄して initialValues の値に置き換えるとある。

NSUserDefaultsController#revertToInitialValues:

この initialValues は #setInitialValues: で設定することができる。
試しに awakeFromNibの中で設定してみる。

- (void)awakeFromNib
{
    :
NSDictionary* initial_values =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:1], @"check1",
[NSNumber numberWithInt:1], @"check2",
[NSNumber numberWithFloat:50.0], @"slider",
nil];
[_user_defaults_controller setInitialValues:initial_values];

}


Key名で使っているのはバインディングの "Model Key Path" で使った名前。





できた。ボタンを押すと setInitialValues: で指定した値に設定される。


サンプル:
ToolBar-07.zip

2008年9月7日日曜日

ツールバー(その9)

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

NSUserDefautlsController には #save: と #revert: というメソッドが用意されている。これを試してみよう。



InterfaceBuilder でボタンを追加し、ボタンの Targetに save: と revert:を接続するだけ。


実行してみる。

。。。。。ボタンを押しても何もおこらない。

ドキュメントを調べてみると #appliesImmediately:, #setAppliesImmediately というメソッドが見つかる。
NSUserDefaultsController#appliesImmediately

デフォルトでは appliesImmediatelyは YES を返すようになっている。すなわち、コントロールが変更されると即時にファイルへ書き込まれる(正確には NSUserDefaultsへの書き込み)ようになっている。このため revert: を送っても、既に変更が確定した後であるため何も起らなかった。

そこで ApplicationController にアウトレットを用意し、"Shared User Defaults Controller"へ接続する。


@interface AppController : NSObject {
   :
IBOutlet NSUserDefaultsController* _user_defaults_controller;
}


その上で起動時に setAppliesImmediately:YES を送る。
- (void)awakeFromNib
{
   :
[_user_defaults_controller setAppliesImmediately:NO];
}



実行してみよう。

今度は save を押さない限り、状態は保存されなくなった。その代わり、saveを押すまでの間 revert: を押すとそれまでの変更が破棄されて直前に saveを押した時の状態に戻るようになった。


サンプル:
ToolBar-06.zip

2008年9月6日土曜日

ツールバー(その8)

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

ツールバーのアイコンをどうするか?システムで使える物がないか探してみる。

するとあった。Leopardでシステム内のアイコンが簡単に使えるようになっていた。


NSView Class Reference


"Toolbar Named Images" のアイコンを使ってみる。


サンプル:
ToolBar-05.zip

とりあえずはこれを使わせてもらおう。

2008年9月5日金曜日

ツールバー(その7)

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

Shared User Defaults Controller にバインドして設定結果を自動保存(復帰)する。

やり方は以前紹介した。
PreferencesでBindingを使う

タブへコントロールを配置した後、Bindingsを設定する。"Model Key Path"には必ず名前を入れておく(空だとアプリの起動に失敗する)。



"Bind to"をチェックすると Shared User Defaults Controller が自動的に作られる。




実行してみる。







設定後に一旦アプリを終了し、再起動すると前回の値が自動的に再現される。

サンプル:
ToolBar-04.zip

今回はコーディングはいっさい無し。

2008年9月4日木曜日

ツールバー(その6)

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

さてツールバーの使い方が分かってきたので、今度は NSTabView の切り替えに使ってみる。
わかりやすいように上にタブを付けたまま InterfaceBuilderでデザインした。



これをツールバー上のアイコンを押すことで切り替えてみる。

NSTabViewの制御も簡単で Outletで接続しておき、ボタンが押されたら #selectTabViewItemAtIndex: でタブのインデックスを指定するだけ。

AppController.h

@interface AppController : NSObject {

IBOutlet NSWindow* _window;
IBOutlet NSTabView* _tab;
}
@end


AppController.m
- (void)hello:(id)sender
{
NSString *itemIdentifier = [sender itemIdentifier];
if ([itemIdentifier isEqual:@"HELLO"]) {
[_tab selectTabViewItemAtIndex:0];
} else if ([itemIdentifier isEqual:@"JAPAN"]) {
[_tab selectTabViewItemAtIndex:1];
} else if ([itemIdentifier isEqual:@"USA"]) {
[_tab selectTabViewItemAtIndex:2];
}
}



サンプル:
ToolBar-03.zip


- - - -
見た目の基本は大体つかめた。次はモデルとの連携をやってみよう。

2008年9月3日水曜日

ツールバー(その5)

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

アイコンを増やしてみる。


画像を用意し、識別子を定義し、そして toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: 内で NSToolbarItem を定義すれば簡単に追加できる。

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];

if ([itemIdentifier isEqual:@"HELLO"]) {
[toolbarItem setLabel:@"Hello"];
[toolbarItem setImage:[NSImage imageNamed:@"hello"]];

[toolbarItem setTarget:self];
[toolbarItem setAction:@selector(hello:)];

} else if ([itemIdentifier isEqual:@"USA"]) {
[toolbarItem setLabel:@"USA"];
[toolbarItem setImage:[NSImage imageNamed:@"USA"]];

[toolbarItem setTarget:self];
[toolbarItem setAction:@selector(hello:)];


} else if ([itemIdentifier isEqual:@"JAPAN"]) {
[toolbarItem setLabel:@"Japan"];
[toolbarItem setImage:[NSImage imageNamed:@"Japan"]];

[toolbarItem setTarget:self];
[toolbarItem setAction:@selector(hello:)];


} else {
toolbarItem = nil;
}
return toolbarItem;
}




プリファレンスダイアログでよくあるように選択状態にすることもできる。




Delegateメソッド toolbarSelectableItemIdentifiers: を実装すればこれも簡単にできる。
-(NSArray*)toolbarSelectableItemIdentifiers:(NSToolbar*)toolbar
{
return [NSArray arrayWithObjects:@"HELLO", @"JAPAN", @"USA", nil];
}



selectedItemIdentifier: を使えば、現在どの ToolbarItemが選択されているのかが分かる。



サンプル:
ToolBar-02.zip

2008年9月2日火曜日

ツールバー(その4)

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

今回はコード解説。

AppController.m を用意し、ターゲットの NSWindowをアウトレットに接続しておく。

@interface AppController : NSObject {

IBOutlet NSWindow* _window;
}



まず awakeFromNib でツールバーのインスタンスを作成し、NSWindowへ設定する。Delegateは自身(AppController)とする。
- (void)awakeFromNib
{
NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier:@"TOOLBAR"] autorelease];

[toolbar setDelegate:self];

[_window setToolbar:toolbar];
}


その上で必要な Delegateメソッドを実装していく。

まずは toolbarDefaultItemIdentifiers: から。
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
{
return [NSArray arrayWithObjects:@"HELLO", nil];
}


デフォルトで表示するツールバーアイテム(NSToolbarItem)の識別子(ItemIdentifier)のリストを返す。フレームワーク(cocoa)がこれを元にツールバーを構成していく。

同じく toolbarAllowedItemIdentifiers: も実装しておく。
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
{
return [NSArray arrayWithObjects:@"HELLO", nil];
}



そして toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
引数で渡された ItemIdentifier を元にして必要なツールバーアイテムのインスタンスを作成し、これを返す。

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];

if ([itemIdentifier isEqual:@"HELLO"]) {
[toolbarItem setLabel:@"Hello"];
[toolbarItem setImage:[NSImage imageNamed:@"hello"]];

[toolbarItem setTarget:self];
[toolbarItem setAction:@selector(hello:)];

} else {
toolbarItem = nil;
}
return toolbarItem;
}



最後にツールバーアイテムがクリックされた時のアクションターゲット hello:
- (void)hello:(id)sender
{
NSLog(@"hello");
}


サンプルではログに "hello" と表示している。

2008年9月1日月曜日

ツールバー(その3)

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

まずは最低限に動くものを作る。



ToolBar-1.zip