ページ

2008年12月31日水曜日

以前のキャプチャ画像を見る

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

さて以前からボタンだけ追加してあった機能の実装にはいる。左向きのボタンを押すと過去のキャプチャ画像を表示していくことができる。右はその逆。


フォルダ内のファイル一覧を得やすい様に、ファイル名の形式を変える事にした。頭に "SCAP-"を付ける。


保存フォルダ内でこの接頭辞が付いているファイルのみを対象としよう。安易だが今回の用途には十分だろう。
過去のファイル(およびその逆方向)のファイル名を取得するメソッドを用意した。

FileManager.m

- (NSString*)filenameInSaveFolderWithCurrentFilename:(NSString*)filename direction:(BOOL)direction
{
filename = [filename lastPathComponent];

NSFileManager* fm = [NSFileManager defaultManager];
NSError* error;
NSArray* all_files = [fm contentsOfDirectoryAtPath:[self path]
error:&error];
NSMutableArray* files = [NSMutableArray array];

for(NSString* file in all_files) {
if ([file hasPrefix:FILENAME_PREFIX]) {
[files addObject:file];
}
}
[files sortUsingSelector:@selector(caseInsensitiveCompare:)];

int index = 0;

for (NSString*file in files) {
if ([file isEqualToString:filename]) {
break;
}
index++;
}

NSString* new_filename;

if (direction) {
index++;
} else {
index--;
}
if ([files count] > 0 && index >=0 && index < [files count]) {
new_filename = [[self path] stringByAppendingPathComponent:[files objectAtIndex:index]];
} else {
new_filename = nil;
}
return new_filename;
}


これをボタンハンドラで使い画像を表示するようにした。動かしてみる。

キャプチャ後、左矢印を押す。


一つ前にキャプチャした画像が表示された。



今は表示される画像毎にウィンドウの大きさが変わってしまう。このままでは使いづらいのである程度固定にすることにしよう。

2008年12月30日火曜日

NSWindowリサイズ時だけ画像を荒く、終了後奇麗に

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

縮小画像を奇麗に見せる件を以前取り上げた。NSGraphicsContext#setImageInerporation: に NSImageInterpolationNone を設定すれば良い。ただ縮小画像のを奇麗にする(補完する)処理は重いため、例えば QuickLookパネルではリサイズ時のみ荒くして、終了後に奇麗な画像を表示するようにしている。

この仕組みを SimpleViewでも対応することにした。最初は NSWindowの delegateメソッドである、windowWillResize:toSize:windowDidResize: を使えば容易にできるとたかをくくっていたが、やってみるとこれらでは役不足であることがわかった。

これらのメソッドはリサイズの前後のタイミングを正確に拾う事はできるのだが、実際にユーザがリサイズする場合には右下のリサイズボックスのドラッグと静止を繰り返す為、これらのメソッドが頻繁に呼ばれてしまい今回の用途では使えない(粗い設定にした直後に、奇麗な設定に戻す為、見た目は荒くならない)。理想的なのはユーザがリサイズボックスのドラッグを開始した後、マウスを放すまでの間だけ画像を荒くしたい。その為には、(1)リサイズボックスのドラッグの開始イベントと、(2)マウスを離すイベントが必要。

NSWindow にはリサイズボックスのイベントに関するメソッドは先の delegateぐらいで他に役に立ちそうなものはない。マウスを離すイベントも、リサイズボックスに関する操作は mouseUp: で受け取れない。

結局、NSWindowやNSResponderのリファレンスを眺めるもいい方法が見つからずネットを調べてみた。すると欲しかった情報そのものずばりが見つかった。

Apple Mailing Lists
Re: nswindow resize question

これによると NSView にはライブリサイズ用のイベントを扱うメソッドが用意されているとのこと。NSViewのリファレンスを見てみる。ああ、あった。
Managing Live Resize

さっそく試してみる。
SimpleViewerImageView.m

- (void)viewWillStartLiveResize
{
_interpolation = NSImageInterpolationNone;
}

- (void)viewDidEndLiveResize
{
_interpolation = NSImageInterpolationHigh;
[self setNeedsDisplay:YES];
}


NSView#viewWillStartLiveResize はユーザがリサイズボックスのドラッグを開始した時に1回だけ呼ばれる。そしてマウスを離した時に NSView#viewDidEndLiveResize が最後に1回だけ呼ばれる。まさに欲しかったイベントがずばり得られる。これらのメソッドをオーバーライドして、画質を切り替えるようにした。viewDidEndLiveResize が呼ばれた後は、再描画は行われないのでこのメソッド内で #setNeedsDisplay:YES を呼んでおく。

設定したメンバ変数 _interpolation は #drawRect: 内で使用する。
[[NSGraphicsContext currentContext] setImageInterpolation:_interpolation];


この辺りは以前のブログ「SimpleViewer(その5)画像の拡大縮小を奇麗に(2)」を参照のこと。


さて実行してみよう。

まずはキャプチャ直後に原寸大(100%)で画像を表示している状態。


右下のリサイズボックスをドラッグして縮小する。画像が荒くなる。


リサイズを終えてマウスを放す。画像が奇麗になる。


わかってみれば実装は5分もかからなかった。ここに至るまでは時間がかかったのだが。。。

2008年12月29日月曜日

Windowアニメーション(その3)NSViewAnimation

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

前回検証した NSViewAnimation クラスを使って SimpleView にフェードイン/フェードアウト効果を導入する。
パネルウィンドウのクラス SimpleViewerPanel へ show/hideメソッドを用意してこれで制御することにしよう。

SimpleViewerPanel.h

@interface SimpleViewerPanel : NSPanel {

}
- (void)show;
- (void)hide;
@end


以下、実装コード。
SimpleViewerPanel.m
#define FADE_DURATION 0.25
- (void)show
{
if (![self isVisible]) {
[self setAlphaValue:0.0];
[self orderFront:self];
}
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject:self forKey:NSViewAnimationTargetKey];
[dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
[dict setObject:[NSValue valueWithRect:[self frame]] forKey:NSViewAnimationStartFrameKey];
[dict setObject:[NSValue valueWithRect:[self frame]] forKey:NSViewAnimationEndFrameKey];

NSViewAnimation *anim = [[NSViewAnimation alloc]
initWithViewAnimations:[NSArray arrayWithObject:dict]];
[anim setDuration:FADE_DURATION];
[anim setAnimationCurve:NSAnimationEaseIn];

[anim startAnimation];
[anim release];
}


- (void)hide
{
if (![self isVisible] || [self alphaValue]==0) {
return;
}

NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject:self forKey:NSViewAnimationTargetKey];
[dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];

NSViewAnimation *anim = [[NSViewAnimation alloc]
initWithViewAnimations:[NSArray arrayWithObject:dict]];
[anim setDuration:FADE_DURATION];
[anim setAnimationCurve:NSAnimationEaseIn];

[anim startAnimation];
[anim release];
}


組み込んでみて次のことがわかった。

(1)フェードアウトは不透明度(alpha値)が 0になり、見えなくなるだけ。
   NSWindow#isVisible: YES
   NSWindow#alphaValue: 1.0=>0.0

(2)フェードインは不透明度を0.0から1.0に変えるだけ。
   NSWindow#isVisible: YES
   NSWindow#alphaValue: 0.0=>1.0

つまりいずれも不透明度を変えるだけで、ウィンドウそのものを表示したり(#orderFront:)、非表示にしたり(#orderOut:)しているわけではない。アニメーションを行うだけのクラスなので当然の動作といえばそうなのだが、気がつくまでちょと混乱してしまった。

さて上記メソッドを呼ぶ様にした。こんな感じ。


やっぱり、フェードイン/フェードアウトがあると柔らかい感じがしていい。

なお、左上の×でウィンドウを閉じた時にもフェードアウトがかかるよう、NSWindow#close: メソッドをオーバーライドしてある。
- (void)close
{
[self hide];
}


- - - -
こんな便利なクラス(NSViewAnimation)があるとは知らなかった。もっと早く気がつけば吹き出しウィンドウのフェードアウトは簡単にできたのに。。

2008年12月28日日曜日

Windowアニメーション(その2)

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

引き続き調査。いろいろと情報があったが、ウィンドウのフェードアウトは NSViewAnimationを使えばよさそうなことがわかった。

NSViewAnimation Class Reference

これをつかった解説があり、ずばりフェードアウトの例が載っていた。
Animating Views and Windows

早速試してみよう。
サンプル:WindowAnimation-2.zip

前回のサンプルを流用し、メイン部分だけ書き換えた。_windowはアウトレット。あらかじめ IBで作ったウィンドウを接続してある。
AppController.m

- (void)showWindow:(id)sender
{
[_window orderFront:nil];

NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject:_window forKey:NSViewAnimationTargetKey];
[dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];

NSViewAnimation *anim = [[NSViewAnimation alloc]
initWithViewAnimations:[NSArray arrayWithObject:dict]];
[anim setDuration:1];
[anim setAnimationCurve:NSAnimationEaseIn];

[anim startAnimation];
[anim release];
}


最初に表示されるウィンドウのボタンを押すとこのメソッドが呼ばれる。設定値を NSMutableDictionary へ入れて後は NSViewAnimationを制御するだけ。

実行してみよう。


おお。いい感じにフェードアウトしている。静止画ではわかりづらいが、下のウィンドウが徐々に薄くなって(透明度が高くなって)最後は見えなくなる。

NSViewAnimationは NSViewAnimationStartFrameKey, NSViewAnimationEndFrameKey を使えば大きさを変えた場合のZoomアニメーションも可能になる。ただ試したところ前回同様、拡大縮小は行っていないようだ(縮小時はクリッピング)。

2008年12月27日土曜日

Windowアニメーション(その1)

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

SimpleViewerのウィンドウのアニメーションを実装する。やりたいのはフェードアウト。まずは検証のため調べてみた。

すると CocoaBuilderのArchiveにいい情報が見つかった。
Re: How to implement window fade-in fade-out effects
フェードアウトというよりZoomエフェクトなのだが早速試してみた。

サンプル:WindowAnimation-1.zip

アニメーションのメインは showWindow: メソッド。

- (void)showWindow:(id)sender
{
NSRect startFrom = NSZeroRect;
NSRect endAt = [_window frame];
CGFloat duration = 10;

[_window setFrame:startFrom display:NO];
[_window orderFront:nil];

[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:duration];
[[_window animator] setFrame:endAt display:YES];

[NSAnimationContext endGrouping];
}


実行するとボタンのついたウィンドウが現れる。"Show Window"を押すと showWindow: メソッドが起動されアニメーションが開始される。


左下からウィンドウがZoomして現れ、徐々に画面中央の方へ大きくなりながら移動していく。そして最後に下記のウィンドウが表示される。


おお、簡単だ。

- - - -
とはいえ、上記サンプルは簡易でウィンドウ自体は拡大縮小ではなくクリッピングに近い。

2008年12月26日金曜日

SimpleViewer(その7)縮小率を表示

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

縮小率を右下に表示するようにした。後々の縮小保存で使うつもり。


実装では情報表示用に専用のカスタムビューを用意した。
SimpleViewerInfoView.h

@interface SimpleViewerInfoView : NSView {

CGFloat _ratio;
}

- (void)setRatio:(CGFloat)ratio;
@end


文字描画はこんな感じ。
SimpleViewerInfoView.m
#define INFORMATION_OFFSET_X 20.0
#define INFORMATION_OFFSET_Y 4.0

- (void)drawRect:(NSRect)rect {
int ratio_str = (int)(_ratio*100);
NSString *info = [NSString stringWithFormat:@"%d%%", ratio_str];
NSMutableDictionary *stringAttributes = [NSMutableDictionary dictionary];

[stringAttributes setObject:[NSFont boldSystemFontOfSize:12.0]
forKey: NSFontAttributeName];
[stringAttributes setObject:[NSColor colorWithDeviceRed:1.0 green:1.0 blue:1.0 alpha:1.00]
forKey:NSForegroundColorAttributeName];
[stringAttributes setObject:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.75]
forKey:NSStrokeColorAttributeName];
[stringAttributes setObject:[NSNumber numberWithFloat: -1.5]
forKey:NSStrokeWidthAttributeName];

NSSize size = [info sizeWithAttributes:stringAttributes];

NSRect bounds = [self bounds];
NSPoint p = NSMakePoint(bounds.size.width - size.width - INFORMATION_OFFSET_X,
INFORMATION_OFFSET_Y);

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


後はこれをパネルの conten viewへ張りつけ(addView:)てやれば良い。

2008年12月25日木曜日

SimpleViewer(その6)画像の拡大縮小を奇麗に(3)

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

検証ができたので SimpleViewerへ組み込む。NSImageViewに変えてカスタムビュー SimpleViewerImageViewクラスを用意した。
SimpleViewerImageView.h

@interface SimpleViewerImageView : NSView {

NSImage* _image;

CGFloat _reduction_ratio;
}

- (void)setImage:(NSImage*)image;
- (CGFloat)reductionRatio;

@end


メインの描画部分は前回のサンプルと同じ。

実行してみる。

適用前


適用後


良さそうだ。QuickLookではドラッグ中に画質を落としているが、思ったより負荷はかからないようだし、SimpleViewでの拡大縮小も頻度は多くないのでこのままとしよう。

2008年12月24日水曜日

SimpleViewer(その5)画像の拡大縮小を奇麗に(2)

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

拡大縮小時の画像を奇麗にするには NSGraphicsContext#setImageInterpolation: を使えばよさそうなことが分かった。

Re: NSimage NSImageInterpolationHigh-Low not working?

can a Cocoa app scale jpeg images as nicely as Preview does?


サンプルを作って試してみた。

サンプル:ShrinkImage-1.zip

カスタムビュー(ImageView)を用意し、この中でバンドル内の画像(sample.png)を描画させている。
ImageView.m

- (void)drawRect:(NSRect)rect {

NSImage* image = [NSImage imageNamed:@"sample"];
NSSize image_size = [image size];
NSRect bounds = [self bounds];

CGFloat ratio_w = bounds.size.width / image_size.width;
CGFloat ratio_h = bounds.size.height / image_size.height;
CGFloat ratio = fminf(ratio_w, ratio_h);

NSSize new_image_size;
new_image_size.width = (int)(image_size.width * ratio);
new_image_size.height = (int)(image_size.height * ratio);

NSRect image_rect = NSMakeRect(0.0, 0.0, image_size.width, image_size.height);
NSRect new_image_rect =
NSMakeRect(0.0, 0.0, new_image_size.width, new_image_size.height);

new_image_rect.origin.x = (int)((bounds.size.width - new_image_size.width)/2.0);
new_image_rect.origin.y = (int)((bounds.size.height - new_image_size.height)/2.0);

[NSGraphicsContext saveGraphicsState];
[[NSGraphicsContext currentContext] setImageInterpolation:_inter_polation];
[image drawInRect:new_image_rect
fromRect:image_rect
operation:NSCompositeSourceOver
fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
}


前半は比率計算で、最後の数行が画像描画コード。NSGraphicsContext#setImageInterporlation: で画質レベルを指定している。



さて実行してみよう。4種類ある NSImageInterpolation をラジオボタンで選択して試すことができる。リサイズも可能。
まず NSImageInterpolationDefault。NSImageViewで見たのはこんな感じか。

http://www.blogger.com/img/blank.gif

次に NSImageInterpolationNone。これはひどい。


NSImageInterpolationLow。良い感じだ。


NSImageInterpolationHigh。これもいい。


NSImageInterpolationLow と NSImageInterpolationHigh は並べれば違いが分かる程度で、NSImageInterpolationLowでも十分いい。

2008年12月23日火曜日

SimpleViewer(その4)画像の拡大縮小を奇麗に

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

QuickLookパネル(左)と、自前で作ったSimpleViewer(右)を比べると画質の差がよくわかる。

SimpleViewer は画像表示を NSImageView まかせにしているが、これではちょっと役不足のようだ。

QuickLookパネルの挙動を調べてみると次のことが分かった。
リサイズ途中は QuickLookパネルでも画像が粗い。



マウスドラッグを離しリサイズをやめると、この瞬間に画像がスムーズになる。


なるほど。画像補正は重い処理なのでこんな工夫がしてあるのか。
これは良い方法なので SimpleViewerも真似して同じ動きにしよう。

ところで画像をスムーズに縮小(拡大)するにはどうしたら良いものか?
NSImageView をやめてカスタムビューを作って CoreImageなどで自前描画が必要だろうか。うーむ。

2008年12月22日月曜日

SimpleViewer(その3)ボタン実装

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

表示がざっくりできたのでボタンの実装に入る。といっても従来使っていた QuickLookControllerを新しく作った SimpleViewerControllerへ差し替えて少し修正を加えるだけ。すぐにできた。



※左右の矢印はアイコン表示のみで実装はまだ。これは後でフォルダ内のファイルをめくる機能として実装する。

- - - -
これで機能的には QuickLookパネルと同等のものができた。ただ視覚エフェクトが物足りない。QuickLookパネルとの違いは2つある。

(1) 拡大・縮小画像が荒い(QuickLookパネルでは補正がかかっていて縮小しても奇麗にみえる)
(2) 出現時のズームアニメーションが無い。消える時にフェードアウト効果が無い。


ズームアニメーションはともかく、拡大縮小時の画像補正とフェードアウトは欲しい。これは実装していこう。

2008年12月21日日曜日

SimpleViewer(その2)表示位置とサイズ決め

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

表示位置とサイズを決める。順序としては、(1)画像の大きさに合わせてサイズを決め、(2)サイズを元に表示位置を決める、といった流れになる。

(1) サイズ決め
まずサイズから。パネルの大きさは画像の表示サイズによって決まるので、画像のサイズについて考える。

キャプチャ画像は大きさが様々なため、大きくて画面に収まらないケースなどが考えられるためルールを決める必要がある。以下、ルールを考えてみた。

 a. 最小サイズを設ける。QuickLookパネルを参考に、最小画像表示サイズは 320 x180ピクセルとする
 b. 初期表示のみ最大サイズを設ける。最大サイズに収まらない場合は縮小する。スクリーンサイズの 80%としよう。
 c. 最小と最大サイズに収まる場合は、画像を原寸大で表示する。

(2) 位置決め
サイズが決まったら、それを元にスクリーンの中央位置に表示されるように調整する。


制約適用のトリガーは2つある。(1)aを適用するタイミングは画像の初期表示。このタイミングでサイズを決定する情報は、画像サイズになる。(1)bを適用するタイミングは NSPanelのリサイズ時。サイズを決定する情報は(ユーザがドラッグして決める)パネルのサイズ。それぞれ決定情報が異なる。

 (1)a サイズ決定要素:画像サイズ  タイミング:画像表示時
 (2)b サイズ決定要素:パネルサイズ タイミング:リサイズ時


(1)a を実現するには NSPanelのリサイズを制御する。delegateメソッド NSWindow#windowWillResize:toSize: を使う。

- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
{
NSRect rect = [sender frame];
rect.size = frameSize;
rect = [sender contentRectForFrameRect:rect];
rect.size.height -= BUTTON_BAR_HEIGHT;
rect.size = [self adjustContentSize:rect.size];
rect.size.height += BUTTON_BAR_HEIGHT;
rect = [sender frameRectForContentRect:rect];

return rect.size;
}

リサイズ時にこのメソッドがコールバックされる。渡されたサイズが最小サイズを下回る場合は調整して( #adjustContentSize:)戻りちとして返す。BUTTON_BAR_HEIGHTは下部にあるボタンの高さ。NSPanelと内部のコンテントビューの大きさは NSWindow#contentRectForFrameRect:rect: と NSWindow#frameRectForContentRect:rect でそれぞれ変換ができる。

(1)b は、画像を初期表示する時に調整する。
 NSSize max_size = [self maximumContentSize];

CGFloat ratio_w = max_size.width / content_size.width;
CGFloat ratio_h = max_size.height / content_size.height;
if (ratio_w < 1.0 || ratio_h < 1.0) {
CGFloat ratio = fminf(ratio_w, ratio_h);
content_size.width *= ratio;
content_size.height *= ratio;
}

幅と高さでより小さくなる方の比率を採用してサイズを作る。こうしないとNSimageViewで表示した時、左右(もしくは上下)に余白ができてしまう。


サイズが決まったら、最後に位置合わせ。スクリーンのサイズを取得すれば位置は簡単に決められる。
 NSRect screen_frame = [[NSScreen mainScreen] visibleFrame];
NSRect panel_frame = [_viewer_panel frame];
NSPoint panel_origin;
panel_origin.x = (screen_frame.size.width - panel_frame.size.width) / 2.0;
panel_origin.y = (screen_frame.size.height - panel_frame.size.height) / 2.0;
panel_origin.y += screen_frame.origin.y; // for Dock Height
[_viewer_panel setFrameOrigin:panel_origin];

Dock の高さを補正してある。これは NSScreen#visibleFrameの origin.y が相当する。



いい感じだ。

2008年12月20日土曜日

SimpleViewer(その1)

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

パネルの ContentViewへ ボタンバー(アイコン)とNSImageViewを張りつけてみる。
コントローラでパネル(NSWindowPanel)と各種ビュー(NSImageView, ButtonBar)を作成し、addSubView: で追加する。

_viewer_panel = [[SimpleViewerPanel alloc] init];

_image_view = [[[NSImageView alloc] initWithFrame:[_viewer_panel frame]] autorelease];
_button_bar = [[[ThinButtonBar alloc] initWithFrame:NSZeroRect] autorelease];
  :(アイコン追加)
[[_viewer_panel contentView] addSubview:_image_view];
[[_viewer_panel contentView] addSubview:_button_bar];


パネルのリサイズに合わせて NSImageViewの大きさも変える為、NSPanelへdelegateを設定して #windowDidResize: メッセージを処理する。

-(id)init {
 :
[_viewer_panel setDelegate:self];
 :
}
- (void)windowDidResize:(NSNotification *)notification
{
[_image_view setFrame:[[_viewer_panel contentView] frame]];
}



キャプチャ後にファイル名を受け取って表示する。これは NSImageを作成して NSImageViewへ渡すだけ。
- (void)showFile:(NSString*)filename
{
[_image_view setImage:[[[NSImage alloc] initWithContentsOfFile:filename] autorelease]];
[_viewer_panel makeKeyAndOrderFront:self];
}


こんな感じ。


それっぽくなってきた。
NSImageViewの画像が荒い気がする。またボタンバーの高さを考慮していないので縦位置が若干不自然。

2008年12月19日金曜日

QuickLookパネルをやめる

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

QuickLookパネルの利用をやめることにした。


理由は2つ。
1. キャプチャ後のナビゲーションの要となり、拡張性が必要とされるが、QuickLookパネルでは対応できない
2. 非公開APIの利用を避ける

1. の方が強い理由だが、2.も気にはなっていた。


その代わりに独自のビューアを実装する。面倒ではあるが、一旦作成できれば拡張性は QuickLookパネルよりも飛躍的に高まるので長い目で見ればこちらがいいだろう。

で、さっそく開発にとりかかる。まずは HUD形態の NSPanelを作ってみた。


専用のクラス群を作っていく。いつものようにウィンドウ(パネル)とコントローラーを用意する。
・SimpleViewerController
・SimpleViewerPanel

名前はSimpleCapにちなんで SimpleViewer。できるだけ SimpleCapから独立させて後々再利用可能な形で作っていく。
HUDパネルの生成はこんな感じ。

- (id)init
{
NSRect frame = NSMakeRect(100, 100, 300, 200);
self = [super initWithContentRect:frame
styleMask:NSResizableWindowMask|NSHUDWindowMask|NSUtilityWindowMask|NSClosableWindowMask
backing:NSBackingStoreBuffered
defer:NO];

if (self) {
[self setReleasedWhenClosed:YES];
[self setDisplaysWhenScreenProfileChanges:YES];
[self setLevel:NSScreenSaverWindowLevel];
[self setHidesOnDeactivate:NO];
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
}
return self;
}


- - - -
ゆくゆくは画像の縮小とフォルダ内ファイルの閲覧機能を持たせたい。

2008年12月18日木曜日

QuickLookパネルにアプリ起動アイコン追加

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

今度はアプリ起動アイコンを追加。


アイコンを押すとプリファレンスで設定したアプリケーションが起動する。下はプレビューを起動させたところ。


- - - -
当初の思惑とは別にQuikLookパネルがキャプチャ後のハブとなりつつある。
オプションの扱いだったが、これを中心として操作の流れを見直した方が良さそう。

2008年12月17日水曜日

QuickLookパネルにリテイクアイコン追加

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

リテイク(撮り直し)アイコンを追加した。


このアイコンを押すと、直前のキャプチャ画像をゴミ箱へ移動した後、直前のキャプチャモードを再起動する。


- - - -
コーディングよりもアイコン作成に時間がかかった。。

2008年12月16日火曜日

QuickLookパネルに設定アイコン追加

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

こんな感じ。


歯車?のアイコンを押すと環境設定ウィンドウを開く。

- - - -
歯車の作り方は下記のページが参考になった。目から鱗。
Illustrator(イラストレーター)で歯車(ねじ)の作成

2008年12月15日月曜日

指定のプリファレンス設定を開く

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

昨日の続き。
Selection のコンテキストメニューでプリファレンスウィンドウを開いたら、選択範囲のタブを表示させたい。


まず NSMenuItem にどのタブを開くか仕込む。この目的には NSMenuItem#setRepresentedObject: が使える。こんな感じ。

NSMenuItem* item;
 :
[item setRepresentedObject:[NSNumber numberWithInt:2]];

ここでは NSNumberにタブのIndexとして2を持たせている。

メニューが選択するとターゲットのアクションが呼出される。アクションにはこの時の引数 senderに先ほどの NSMenuItemがわたってくるので、representedObject でタブのIndexを取り出せば良い。SimpleCapの実装ではそれをプリファレンスのコントローラへ渡して、プリファレンスウィンドウを表示させる。
- (IBAction)openPereferecesWindow:(id)sender
{
NSInteger tab_index = -1;
if ([sender isKindOfClass:[NSMenuItem class]]) {
id rep_obj = [sender representedObject];
if (rep_obj) {
tab_index = [rep_obj intValue];
}
}
[_preference_controller openAtTabIndex:tab_index];
   :


プリファレンスコントローラは渡されたインデックスのタブを表示させウィンドウを表示する。
- (void)openAtTabIndex:(NSInteger)tab_index
{
switch (tab_index) {
case 0:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_GENERAL];
break;

case 1:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_WINDOWS];
break;

case 2:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_SELECTION];
break;

case 3:
[_tab_ivew selectTabViewItemAtIndex:tab_index];
[_toolbar setSelectedItemIdentifier:TOOLBAR_ADVANCED];
break;

default:
break;
}
[_window makeKeyAndOrderFront:self];
}

タブは NSTabView#selectTabViewItemAtIndex: で変更できる。またタブだけでなくツールバーも忘れずに NSToolBar#setSelectedItemIdentifier: で目的のものに変更しておく。

2008年12月14日日曜日

SimpleCapバグ取りその他

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

α版リリース直後に気がついたバグを修正した。

・吹き出しのファイル名が更新されない
・キャンセルした時でも吹き出しに「キャプチャ対象がありません」と表示されていた


Selectionのコンテキストメニューにプリファレンス呼出しのメニューを追加してみた。

少しの間試してみる。

2008年12月13日土曜日

SimpleCap α4リリース

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

MacOSX10.5用スクリーンキャプチャ SimpleCap α版の最終リリース。


α3からの追加・変更は次の通り。
・ボタンの色を変更
・ホットキー追加
・キー操作追加
・QuickLookパネル追加
・サイズ設定追加
・吹き出しウィンドウ追加

SimpleCap-a4.zip

次は1月にβ版リリース、そして本リリース。
機能はあらかたできたので残りはブラッシュアップにバグ取り、そしてホームページ作成など。

2008年12月12日金曜日

吹き出しウィンドウ(9)

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

吹き出しウィンドウ最終回。三角を中心に調整し、キャプチャ対象が無い場合のメッセージを表示させるようにした。


これで一段落。

- - - -

今週末に最後のα版(4)をリリースする予定。

2008年12月11日木曜日

吹き出しウィンドウ(8)

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

ウィンドウリストからステータスバーの位置を割り出すことにした。NSProcessInfo#processIdentifierで自分のPIDを取得して、これとレイヤー(kCGStatusWindowLevel==25)の2つの条件からステータスバーのウィンドウを割り出す。

+ (Window*)statusBarWindow
{
CFArrayRef ar = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
CFDictionaryRef window_ref;
CFIndex i;
int status_bar_pid = [[NSProcessInfo processInfo] processIdentifier];
int owner_pid;
int layer;

for (i=0; i < CFArrayGetCount(ar); i++) {
window_ref = CFArrayGetValueAtIndex(ar, i);
CFNumberGetValue(CFDictionaryGetValue(window_ref, kCGWindowOwnerPID),
kCFNumberIntType, &owner_pid);
CFNumberGetValue(CFDictionaryGetValue(window_ref, kCGWindowLayer),
kCFNumberIntType, &layer);
if (owner_pid == status_bar_pid && layer == kCGStatusWindowLevel) {
break;
}
}
Window* window = [[[Window alloc] initWithWindowDictionaryRef:window_ref] autorelease];
CFRelease(ar);

return window;
}


Windowクラスは CGWindowListCopyWindowInfo から取得した情報を格納するObjective-Cのラッパークラス。

これで位置が取れた。キャプチャ後にファイル名を表示させてみる。


おお、出た。

2008年12月10日水曜日

吹き出しウィンドウ(7)

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

ステータスバー(NSStatusBar)内の位置を取得したい。NSStatusBar や NSStatusItem のAPIには用意されていない。ネットを調べても決定的な方法を見つけられなかった。

CocoaBuilderではカスタムビューを NSStatusBarへセットし、そこから座標を得る方法が検討されていた。
Cocoabuilder - Position of NSStatusItem


WindowListで調べてみるとステータスバー上のアイコンが一つのウィンドウとして割り当てられているのが分かる。下の図では一番上がステータスバー上の SimpleCapのアイコン領域を表している。


このウィンドウが取得できれば位置が得られる。


正攻法でウィンドウを取る手段がみつからないとなると、ウィンドウリストから得るしかないか。
例えば、自分と同じ PID を持っていて origin.y == 0 のウィンドウ、といった方法で狙い撃ちする。
場当たり的な感じもするが、これで十分かも。

2008年12月9日火曜日

吹き出しウィンドウ(6)

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

一通り実装できたので SimpleCapへ組み込む。
試しにキャプチャ完了時にファイル名を表示させてみた。




さて、このメッセージをステータスバーのメニュー下に表示させたいが、メニューの位置はどうやったら取得できるのだろうか?

2008年12月8日月曜日

吹き出しウィンドウ(5)

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

フェードアウト効果を追加する。その為に FukidashiControllerに状態変数を追加する。

 状態(0) FUKIDASHI_STATE_HIDE  非表示状態 ※初期状態
 状態(1) FUKIDASHI_STATE_SHOW メッセージ表示状態
 状態(2) FUKIDASHI_STATE_FADEOUT フェードアウト途中の状態

クライアントコードから showMessage: が送られると状態(0)から状態(1)へ遷移する。しばらくメッセージを表示した後、タイマーによって状態(1)から状態(2)への遷移が起きる。この状態ではウィンドウの透明度が徐々に上がり(すなわちフェードアウトが進み)、最後に何も表示されなくなり状態(0)へ戻る。

下記は状態遷移用メソッド。状態(1) FUKIDASHI_STATE_SHOWへの遷移で NSTimerを起動している。

- (void)changeState:(int)state
{
switch (state) {
case FUKIDASHI_STATE_HIDE:
[_window orderOut:self];
break;

case FUKIDASHI_STATE_SHOW:
if (_state != FUKIDASHI_STATE_HIDE && [_timer isValid]) {
[_timer invalidate];
}
_count = 0;
[_window setContentSize:[_view areaSize]];
[_window setAlphaValue:1.0];
[_window makeKeyAndOrderFront:self];
_timer = [NSTimer timerWithTimeInterval:FUKIDASHI_TIME_INTERVAL
target:self
selector:@selector(callBack:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
break;

case FUKIDASHI_STATE_FADEOUT:
_count = 0;
break;
default:
break;
}
_state = state;
}


タイマーの処理はこんな感じ。
- (void)callBack:(NSTimer*)timer
{
_count++;
switch (_state) {
case FUKIDASHI_STATE_SHOW:
if (_count >= (int)((float)_showtime/FUKIDASHI_TIME_INTERVAL)) {
[self changeState:FUKIDASHI_STATE_FADEOUT];
}
break;
case FUKIDASHI_STATE_FADEOUT:
if (_count >= (int)(FUKIDASHI_FADEOUT_TIME/FUKIDASHI_TIME_INTERVAL)) {
[self changeState:FUKIDASHI_STATE_HIDE];
[timer invalidate];
} else {
[_window setAlphaValue:(1.0-_count*FUKIDASHI_TIME_INTERVAL/FUKIDASHI_FADEOUT_TIME)];
}
break;
default:
break;
}
}




サンプル:Fukidashi-4.zip

2008年12月7日日曜日

吹き出しウィンドウ(4)

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

吹き出しの尖った三角を追加した。



三角形は位置決めして NSBezierPathで描く。

 NSPoint p = NSMakePoint(OFFSET_X, background_rect.size.height+TRIANGLE_HEIGHT+SHADOW_OFFSET);
[triangle_path moveToPoint:p];
p.x += TRIANGLE_HEIGHT/1.5;
p.y -= TRIANGLE_HEIGHT;
[triangle_path lineToPoint:p];
p.x -= (TRIANGLE_HEIGHT/1.5)*2;
[triangle_path lineToPoint:p];
[triangle_path closePath];
[triangle_path fill];



サンプル:Fukidashi-3.zip

2008年12月6日土曜日

吹き出しウィンドウ(3)

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

吹き出しの続き。
描画の目処が立ったので、アプリへ組み込める様にクラスを整備する。ビューに加えて新たに2つのクラスを追加した。

FukidashiView
FukidashiWindow
FukidashiController


FukidashiWindowはビュー(FukidashiView)を載せるウィンドウ。FukidashiControllerは名前の通りコントローラで、この機能を使うクライアントコードと、吹き出し表示の間を調整する。

(イメージ)
 <<クライアント>>
    |
    |表示指示
    ↓
 FukidashiContoroller
    |
    |表示設定・大きさ調整
    ↓
 FukidashiWindow
 FukidashiView


位置決め後回しとして今回は渡された文字列をこの仕組みで表示するところまで実装した。コントローラの実装はこんな感じ。
FukidashiController.m
- (id)init
{
self = [super init];
if (self) {
_window = [[FukidashiWindow alloc] init];
_view = [[FukidashiView alloc] init];
[_window setContentView:_view];
}
return self;
}
- (void)showMessage:(NSString*)message
{
[_view setMessage:message];
NSSize window_size = [_view areaSize];
[_window setContentSize:window_size];
[_window makeKeyAndOrderFront:self];
}


#initでウィンドウとビューのインスタンスを作り紐づける。クライアントコードから showMessageメッセージが来たらビューに文字列を渡した後、表示サイズを計算させウィンドウサイズに設定する。

実行結果はこうなる。



サンプル:Fukidashi-2.zip

2008年12月5日金曜日

吹き出しウィンドウ(2)

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

まずは見た目から。カスタムビューを用意してウィンドウへ貼付けて描画してみた。

こんな感じ。


ビューは渡された文字列を元に黒い背景の大きさを計算して描画している。


サンプル:Fukidashi.zip

2008年12月4日木曜日

吹き出しウィンドウ(1)

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

SimpleCapでユーザに情報を伝えたいときがある。例えばタイマー実行時に対象のウィンドウが閉じられてしまい、何もキャプチャできなかった場合など。普通のダイアログはデザインがSimpleCapに合わないし、OKボタンを押さなければならないのは煩わしい。欲しいのは一定時間表示され、しばらくするとフェードアウトして消えるようなもの。見た目は Dockの吹き出しのようなデザインがいい。



そんなカスタムウィンドウを作ってみる。名付けて「吹き出しウィンドウ」。

2008年12月3日水曜日

SimpleCap ウィンドウ選択をキーボードで行う

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

キー周り最後の仕上げ。ウィンドウ選択時に、矢印キーでウィンドウ選択切り替える機能を追加する。例えば、上矢印キーを押すと現在選択されているウィンドウの一つ上に選択状態が移る。




これを実現するには画面に表示されているウィンドウのリストを座標でソートする必要がある。SimpleCapではウィンドウ一覧(配列)を取得するメソッドを元々用意していたのでこれを元にソート処理を実装する。配列のソート方法は NSArray#sortedArrayUsingSelector: を使ってオブジェクト自身にソートロジックを実装することが多い。今回はこれを使わず NSArray#sortedArrayUsingFunction:context: を使ってみた。今回のケースでは矢印キーの方向によってソート対象を X座標、Y座標と切り替える必要があったのがその理由。

ソート処理を行う関数はこんな感じ。

HandlerBase.m

NSInteger window_comparator(Window* window1, Window* window2, void *context)
{
NSRect rect1 = [window1 rect];
NSRect rect2 = [window2 rect];

CGFloat v1, v2;

if ([(NSNumber*)context boolValue]) {
v1 = rect1.origin.x;
v2 = rect2.origin.x;
} else {
v1 = rect1.origin.y;
v2 = rect2.origin.y;
}

if (v1 < v2) {
return NSOrderedAscending;
} else if (v1 > v2) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}



ソート済みのウィンドウリストを作る部分。
- (NSArray*)getSortedWindowListDirection:(BOOL)direction
{
NSArray* list = [self getWindowList];
NSArray* sorted_list =
[list sortedArrayUsingFunction:window_comparator
context:[NSNumber numberWithBool:direction]];

return sorted_list;
}


後は押された矢印キーによってこのリスト上で前後のウィンドウを取得し選択状態にすれば良い。

2008年12月2日火曜日

キー割当

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

ただいまブラッシュアップ中。
キーを下記のように割り当てた。

キャプチャ:Command+R
コピー:Command+C
タイマー:Command+T

2008年12月1日月曜日

バインディングメモ(EnabledとHidden)

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

Cocoa Bindings には値のバインディングの他にコントロールの状態(Availability)を制御できるものが用意されている。それが EnabledとHidden。下図は InterfaceBuilderで NSMenuIitemの Hidden項目を見たところ。



ここで変数やメソッドなどにバインディングすると、その内容によってそのNSMenuItemを隠したり、ディゼーブルにすることができる。

これを使っていて気がついたことがある。
"Enabled" のバインディングはメニューを表示するたびに値が評価されるのだが、Hiddenは初回のみので表示毎には評価してくれない。つまりある条件によって、そのNSMenuItemを表示したり、隠したりすることは初回表示時のみ可能で、それ以降の表示では制御できない。

それだけ。