SimpleCap のソースコードをGitHubへ公開しました
2012年10月11日木曜日 | Published in SimpleCap | 0 コメント
・SimpleCap-1.2 は現在公開中のバージョンです。
・SimpleCap-1.3 は開発中の未公開バージョンです(範囲ヒストリなどがあります)
技術的な情報は旧Cocoaの日々にたくさん書いてあるのでそちらを参照してみてください。
旧Cocoaの日々
SimpleCapの開発を始めたところから記載があります。あのころはブログを毎日書いていたので、これを読むとSimpleCapの一つ一つの実装風景を見ることができます(長いけど)。
SimpleCapとは...
「Mac用のスクリーンショットツール。画面上の好きなエリアを簡単にイメージ保存できます。メニューバー常駐型なのですぐに使える。」
紹介記事など
- - -
このアプリを開発していた数年の間に子供3人が生まれたので個人的には思い出深いソフトです。通院中の妻を待ちながら喫茶店でプログラミング、なんてこともありました。まあ双子の出産が開発を止めた最大の理由でもあるのですが。
いつかはソースを公開しようと考えていましたが忙しさにかまけて放置状態にしていました。先日ソースを見せて欲しいと依頼が来たので、これをいい機会だと思い Githubへ公開しました。はっきり言って数年前のMac/Cocoa用コードなので、今のモダンなiOS/Cocoaコードに比べるとかなり古臭いです。それでも何かの役に立てればと思い公開しておきます。好きに使ってください。引き継いでのバージョンアップ大歓迎です ^^。ちなみにアプリは自分自身が毎日使っているヘビーユーザです。
なお今となってはほとんど忘れてしまったのですが、ネット上のコードをコピペしている箇所があるかもしれません。もし気がつかれた方がいたらご指摘下さい。クレジット表記を追加します。
PR: 今は iOSアプリをメインで開発しています。良かったらどうぞ。
※こちらはソース公開してませんのであしからず。
SimpleCap - 1.3開発〜途中経過[2]
2011年1月21日金曜日 | Published in SimpleCap, 情報 | 0 コメント
昨年リリースするつもりだったのがまだ完成していません。。
導入を検討していた SHE(Selection History Expose)は見送り。
見た目はいいのだが実用的じゃないというのがその理由。
代わりに Selection History List パネルを追加。
範囲プリセットもここで管理するようにした。
Selection History List は、過去の範囲を選択するとその時に保存した時画像がチラッと表示される。
来月中にはリリースしたい。
SimpleCap - 途中経過
2010年11月21日日曜日 | Published in SimpleCap | 0 コメント
こんなのとか
こんなの
これらは従来だと範囲選択キャプチャでしかできなかった。
SHE(Selection History Expose)とか。
最初はどうかと思ったが何気に重宝する。もう少し何かが足りない気もするが。
範囲サイズの直接入力。
その他、SimpleViewのアイコン見直し、アプリ自由登録など。
現在は範囲選択のプリセット、プリファレンス構成の見直しなどやっている。
SimpleCap - Selection History Expose [13] 再考
2010年11月12日金曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
一旦考えを整理することにした。
そもそも履歴が多くてゴチャゴチャする状況は履歴が多いからこうなる。当たり前のようだが、履歴が少なければゴチャゴチャになる可能性は低い。例えば5つくらい。多くても7〜10個。だったら何故履歴を多くしてしまうのか?それは過去使った範囲をできるだけ再利用したいから。履歴が多く残っていればそれだけ再利用の可能性が高まる。ただその分探すのに時間がかかる。再利用の為の時間がかかったら新しく範囲を定義し直した方がいいという本末転倒なことになる。検索性が上がればいいのか?そうでもない。あまり意識せずに使った過去の範囲の再利用というのは直近数個なら記憶があるので意味があるが、それよりも多く、それよりも過去(昨日〜)のものは残っていてもゴミでしかない。前回試行錯誤していた利用回数によるフィルタはこのゴミを取り去ることだが、それでも完全ではない。このアプローチでの自動化ではユーザが求めるものを提供するのは難しいだろう。昔の履歴で再利用したいものはむしろユーザに選ばせる方が自然だろう。
そこで履歴を次の2種類のものがあると捉えてみた。
(1)ユーザが明示的に指定するもの
(2)自動的に残るもの
(1)は範囲選択キャプチャで、ユーザが残しておきたいと判断したらボタンなどを押して明示的に記録しておく。そしてユーザからの指示でこれを呼び出して再利用できるようにする。削除もユーザ自身に行わせる。これはルーチンワークなど定期的・長期にわたって使用する目的で使用する。
一方 (2)はキャプチャの度に自動的に記録され、必要に応じて再利用ができる。これは今までやってきた Selection History Expose にあたる。これは直近に使った履歴を再利用したい場合に使う。時間が過ぎればあまり使うことはなくなる(使い捨て)。履歴は5つ、多くても 7〜10あれば十分。
これまでのこの2種類の履歴を1つの方法でやろうとしていたから無理が出てきた。この2種類を別々の性質と捉え、ユーザインターフェイスを再考してみることにしよう。
(1)(2) を別々のモードとして捉え、記録の方法、再利用の方法、記録場所、それぞれを分けてしまう。ベタだがこのほうが実用的。なお (2)はもともとプリファレンス画面で5つまでプリセット範囲として登録することができた。このままでもいいのだが、せっかくなのでこれを改良して画面上で登録できるようにする。そうすると使い勝手が上がると思う(今のプリファレンス形態だとあまり使われていないと思う)。
仮で範囲選択のイメージを考えてみた。
左下のアイコンおよびサイズ表示部を押すと SHE発動〜(1)の履歴モード。(2)のユーザ範囲モードは右上に新たにアイコンを追加してそこから起動させる。ユーザ範囲の保存はどうやるか。だんだんごちゃごちゃしてきた。悪いサインだ。
- - - -
再考は続く...
とりあえず他の事も並行してやっていこう。
SimpleCap - Selection History Expose [12] アイディア
2010年11月11日木曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
もっと良い見せ方はないのか?アイディアを出してみた。
なんか大げさになってきた。うーむ。
SimpleCap - Selection History Expose [11] 2回以上使ったものだけ表示する
2010年11月10日水曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
前回導入した利用カウンタを使用して2回以上使った履歴のみ表示するようにしてみる。
filteredArrayUsingPredicate:
履歴は内部的には NSArray で管理している。
この配列を取得する時に使うメソッドに -[NSArray filteredArrayUsingPredicate:]を使い、カウンタが2以上のもののみ返すようにする。
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"count >= %d", 2]; return [_historyArray filteredArrayUsingPredicate:predicate];
するとこうだったのが
こうなった。
悪くない。
ただ根本的な問題解決になっていない。
どうるすか。
SimpleCap - Selection History Expose [10] モデルにカウンタを追加
2010年11月9日火曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
SHE (Selection History Expose) を試しに使っていてわかったのは、1度きりしか使わない範囲を表示すると画面が範囲だらけになって本来使いまわしたい範囲がわかりづらいこと。
使用回数を保持する
よく使う範囲と1回しか使わない範囲を区別する必要があるな。とりあえず使った回数を保持しておくことにする。SHEの履歴は SelectionHistoryクラスで管理している。
@interface SelectionHistory : NSObject {
NSMutableArray* _historyArray;
NSArray* _cachedArrayOrderBySize;
NSArray* _cachedArrayOrderBySizeDesc;
}
+ (SelectionHistory*)sharedSelectionHistory;
- (void)addHistoryRect:(NSRect)frame;
- (NSArray*)array;
- (NSArray*)arrayOrderBySize;
- (NSArray*)arrayOrderBySizeDesc;
- (void)clearAll;
@end配列 _historyArray には NSRect を NSValue でラップしていれていた。これをやめる。新たに SelectionHistoryEntryクラスを導入して、これで履歴1つ分の範囲矩形と使用回数を管理する。@interface SelectionHistoryEntry : NSObject
{
NSRect _rect;
NSInteger _count;
}
@property (nonatomic, assign) NSRect rect;
@property (nonatomic, assign) NSInteger count;
@end保存は以前と同様に NSUserDefaultsに行う。SelectionHistoryEntryのシリアライズが必要だが今回は安易に NSMutableDictionaryに @"count", @"rect" をキーにして値を入れてそれを保存した(NSMutableDictionaryは標準で NSUserDefaultsに保存できる)。- (void)save
{
NSMutableArray* array = [NSMutableArray array];
for (SelectionHistoryEntry* entry in _historyArray) {
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject:NSStringFromRect(entry.rect) forKey:@"rect"];
[dict setObject:[NSNumber numberWithInteger:entry.count]
forKey:@"count"];
[array addObject:dict];
}
[UserDefaults setValue:array forKey:UDKEY_SELECTION_HISTORY];
[UserDefaults save];
}試しに実行して save時の履歴をログへ出してみよう。SimpleCap[9099:a0f] SAVE: (
{
count = 1;
rect = "{{870, 135}, {254, 163}}";
},
{
count = 3;
rect = "{{208, 203}, {400, 250}}";
}
)よさそうだ。サイズの小さい順にソート
なお SelectionHistory クラスでは arrayOrderBySize というメソッドを用意していて、これを使うと履歴をサイズの小さい順で得ることができる。サイズの比較は今回は矩形の面積で判断するようにした。
NSInteger sizeSort(id obj1, id obj2, void* context) {
NSRect rect1 = [obj1 rect];
NSRect rect2 = [obj2 rect];
CGFloat area1 = rect1.size.width * rect1.size.height;
CGFloat area2 = rect2.size.width * rect2.size.height;
if (area1 < area2) {
return NSOrderedAscending;
} else if (area1 > area2) {
return NSOrderedDescending;
} else {
CGFloat len1 = rect1.origin.x*rect1.origin.x + rect1.origin.y*rect1.origin.y;
CGFloat len2 = rect2.origin.x*rect2.origin.x + rect2.origin.y*rect2.origin.y;
if (len1 < len2) {
return NSOrderedAscending;
} else if (len1 > len2) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}
}またソート結果はマウスオーバーの判断でよく使うのでキャッシュしてある。履歴の内容が変わらない限りはこのキャッシュを使う。- (NSArray*)arrayOrderBySize
{
if (!_cachedArrayOrderBySize) {
_cachedArrayOrderBySize = [[_historyArray sortedArrayUsingFunction:sizeSort
context:NULL] retain];
}
return _cachedArrayOrderBySize;
}- - - -
使用回数が取れるようになったので次回はこれを活用してみよう。
SimpleCap - Selection History Expose [9] マウスカーソルが上に載った時に色を変える
2010年11月8日月曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
こんな感じ。
イベントハンドリング
Core Animation の CALayer は 表示専用クラスでマウスイベントの受付はできない。どちらかといえばアニメーションに必要な値やルールを保持するモデルに近い(たしかリファレンスでもそう解説されていた)。イベントの受付は CALayer をホストしている NSView で行う。
SimpleCap の範囲選択キャプチャの場合、画面を全体を覆う NSView があって、そこへ CALayer を並べることで今回のアニメーションを表示している。こんなイメージ。
なので各 CALayer の上にマウスカーソルが載ったかどうかの判定は CALayerではなくそれらをホストする NSView で行う。
流れはこんな感じ。
1. ユーザがマウス移動 ↓ 2. ホストNSView の mouseMoved: が呼ばれる SelectionHistoryLayer のメソッドをマウス位置を引数にして呼び出す ↓ 3. すべての CALayer についてテストを行う。 該当した場合、そのCALayerの背景色を変える。 また直前の CALayer の背景色を元に戻テストは CALayer にもメソッドが用意されているが、それを使わず自前でベタにNSPointInRectを使って行っている。こんな感じ。
for (NSValue* selectionValue in [selectionHistory arrayOrderBySize]) {
NSRect rect = [selectionValue rectValue];
if (NSPointInRect(cp, rect)) {
[self setHoveredLayer:[self layerForRect:rect]];
hit = YES;
break;
}
}当然ながらマウス移動だけでなくクリックされた時の処理も最初の受付は NSView が行う。
SimpleCap - Selection History Expose [8] CALayer周りの構成を変更
2010年11月7日日曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
前回までは SelectionHistoryLayer という CALayer のサブクラスを作り、これを SelectionHandler と呼ぶ範囲選択クラスでこの管理を行っていた。しかしただでさえコード量の多い肥大化ぎみのクラス SelectionHandler に CALayer 関連の処理が入るとメンテナンスがやりづらい。そこで新規にクラスを作り、SHE(Selection History Epose)に関する主要処理の責務をそこに持たせることにした。そのクラスが SelectionHistoryLayeManager。中身はこんな感じ。
@interface SelectionHistoryLayerManager : NSObject {
CALayer* _baseLayer;
id _delegate;
BOOL _endAfterAnimation;
}
@property (nonatomic, assign) id delegate;
- (void)addLayerToView:(NSView*)view currentRect:(NSRect)currentRect;
- (void)animateStart:(NSRect)rect;
- (void)animateSelect:(NSRect)rect;
- (void)animateCancel:(NSRect)rect;
@end SHE 発動時に addLayerToView:currentRect: が呼ばれ、この時に SelectionHistory(履歴管理モデル)から履歴の矩形を取得して CALayerを生成する。そして animate* 系メソッドでアニメーションの制御が行われる。デリゲートはアニメーションが終わった時に SelectionHandler をコールバックするのに使う(SHEから通常モードへ復帰させる)。
SimpleCap - Selection History Expose [6] 座標系変換(flippedからnon-flippedへ)
2010年10月31日日曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
今回は座標系変換について。
flippedから non-flippedへ
SimpleCap のメインとなるビューの CaptureView は、NSViewのサブクラスなので通常は座標系は左下原点だが、フリップ(flipped)させているので左上原点となっている。
一方、Core Animation(CALayer)の座標系は左下が原点となっている。
この為、CaptureView の座標を使って CALayer へ描画する際は flippedな座標系から、non-flippedな座標系への変換が必要になる。
変換
ここでは矩形(NSRect or CGRect)を変換対象と考えてみる。NSRect flippedRect から nonFlippedRect へ変換する場合、スクリーンが1つの場合は簡単でこんな感じで求められる。
NSRect screenFrame = [[NSScreen mainScreen] frame]; nonFlippedRect = flippedRect; nonFlippedRect.origin.y = screenFrame.size.height - flippedRect.origin.y - flippedRect.size.height;
やっかいなのはマルチスクリーンの場合。上記コードのmainScreen以外も考慮に入れる必要がある。
以前調査したようにスクリーンの配置の仕方によって各スクリーンが置かれる座標が異なる。
[参考情報] (旧) Cocoaの日々: β版バグ修正 - マルチスクリーン
今回は下記のケースはこの時の「b. SimpleCapのメインビューのローカル座標系」から「a. スクリーン座標系」への変換に該当する。
まず X座標については変換は不要。flippedの有無は Y座標のみ関係していて、それを除けば基本的に同じ座標系だから。
Y座標についてはサブ画面の位置によって次の2つのケースを考える。
[a] サブ画面が上に載るケース
[b] サブ画面が下に載るケース
変換の考え方は原点を中心に考えるとわかりやすい。
[参考情報] (旧) Cocoaの日々: β版バグ修正 - マルチスクリーン(その2)
そのアプローチで考えた数式は次の通り。
cx1 = lx1; cy1 = sy2 - ly1 - lh1;・スクリーン座標系上での全画面範囲(左下)-(右上):(sx1, sy1)-(sx2, sy2)
・fliipedな座標系内の NSRect:(lx1, ly1),(lw1, lh1)
・(non-flippedな)変換後の座標:(cx1,cy1)
スクリーン1つの場合と似ているが、その時のスクリーンの高さを起点にしていたのに対してマルチスクリーンの場合はそこにスクリーン全体の座標で補正している点。
確認
シングルスクリーンでは問題なし。マルチスクリーンは手元に無いため未確認...。
SimpleCap - Selection History Expose [5] Core Animation で表示
2010年10月30日土曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
前回は履歴の矩形を NSView に直接描画していた。今回はこれを Core Animation に切り替え、各矩形がアニメーションできるようにお膳立てする。
カスタムレイヤー
CALayer をサブクラス化して履歴矩形1つを表すレイヤークラスを用意した。
@interface SelectionHistoryLayer : CATextLayer {
}
+ (SelectionHistoryLayer*)layerWithFrame:(NSRect)frame;
@end初期化コード- (id)initWithFrame:(NSRect)frame
{
if (self = [super init]) {
self.frame = NSRectToCGRect(frame); // TODO: convert coordination
CGColorRef colorRef;
colorRef= CGColorCreateGenericGray(0.0f, 0.3f);
self.backgroundColor = colorRef;
CGColorRelease(colorRef);
colorRef= CGColorCreateGenericGray(0.0f, 0.5f);
self.borderColor = colorRef;
CGColorRelease(colorRef);
self.cornerRadius = 7.0f;
self.layoutManager = [CAConstraintLayoutManager layoutManager];
// text
CATextLayer* textLayer = [CATextLayer layer];
colorRef= CGColorCreateGenericGray(1.0f, 1.0f);
textLayer.string = [NSString stringWithFormat:@"%d x %d",
(int)frame.size.width, (int)frame.size.height];
textLayer.font = @"Lucida-Grande";
textLayer.fontSize = 13.0;
textLayer.foregroundColor = colorRef;
[self addSublayer:textLayer];
CGColorRelease(colorRef);
[textLayer addConstraint:[CAConstraint
constraintWithAttribute:kCAConstraintMidX
relativeTo:@"superlayer"
attribute:kCAConstraintMidX]];
[textLayer addConstraint:[CAConstraint
constraintWithAttribute:kCAConstraintMidY
relativeTo:@"superlayer"
attribute:kCAConstraintMidY]];
}
return self;
}背景色を黒の半透明にして角を丸くする。さらに CATextLayer を追加してサイズを表示する。この時 addConstraint を使うと CATextLayer を中心位置にレイアウトすることができる。表示はこんな感じ。
履歴矩形の表示
カスタムレイヤーを SimpleCap に組み込んで使う。こんな感じ。
- (void)setupSelectionHistoryLayers
{
CaptureView *view = [_capture_controller view];
// clean up
[view.layer removeFromSuperlayer];
// [1] background layer
// TODO:multi screen
CALayer* backgroundLayer = [CALayer layer];
backgroundLayer.frame = NSRectToCGRect([view frame]);
CGColorRef backgroundColorRef = CGColorCreateGenericGray(0.0f, 0.25f);
backgroundLayer.backgroundColor = backgroundColorRef;
CGColorRelease(backgroundColorRef);
view.layer = backgroundLayer;
// [2] history selection layers
BOOL currentHasExisted = NO;
for (NSValue* historyValue in
[[SelectionHistory sharedSelectionHistory] array]) {
NSRect historyRect = [historyValue rectValue];
SelectionHistoryLayer* layer =
[SelectionHistoryLayer layerWithFrame:historyRect];
[backgroundLayer addSublayer:layer];
if (NSEqualRects(_rect, historyRect)) {
currentHasExisted = YES;
}
}
// [3] current selection layer
if (!currentHasExisted) {
SelectionHistoryLayer* currentLayer =
[SelectionHistoryLayer layerWithFrame:_rect];
[backgroundLayer addSublayer:currentLayer];
}
}背景用に1つ backgroundLayer を用意してそこへカスタムレイヤーを追加する。[3]で現在表示中の範囲選択も表示しておく。履歴とダブらないようなチェックも入れてある。実行するとこんな感じ。
おーなんかいい感じだ。
CALayer は毎回作り直している。無駄な気もするが軽いクラスとの触れ込みなので今は気にせずこのままいこう。
文字の明滅
フィルタとアニメーションを組み合わせると文字(レイヤー)を明滅させる効果をつけられる。
コードはこんな感じ。
CIFilter *filter = [CIFilter filterWithName:@"CIBloom"]; [filter setDefaults]; [filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"]; [filter setName:@"pulseFilter"]; [textLayer setFilters:[NSArray arrayWithObject:filter]]; CABasicAnimation* pulseAnimation = [CABasicAnimation animation]; pulseAnimation.keyPath = @"filters.pulseFilter.inputIntensity"; pulseAnimation.fromValue = [NSNumber numberWithFloat: 0.0]; pulseAnimation.toValue = [NSNumber numberWithFloat: 2.0]; pulseAnimation.duration = 1.0; pulseAnimation.repeatCount = 1e100f; pulseAnimation.autoreverses = YES; pulseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [textLayer addAnimation:pulseAnimation forKey:@"pulseAnimation"];
実行例(静止画なのでわかりずらいが文字の周りが明滅している)
参考情報
【特集】Leopardのアニメーションを簡単実装! Core Animationを使いこなす (1) アニメーションプログラミングを身に付けるには"とにかく動かせ" | エンタープライズ | マイコミジャーナル
わかりやすくてとても参考になった。
Core Animationプログラミングガイド: レイヤスタイルプロパティ
レイヤに適用できる視覚効果がわかる。
Core Animationプログラミングガイド: サンプル:Core Animation Menuアプリケーション
Core Animation を使ったサンプルアプリ。レイアウトの取り方や、フィルタのかけかたなどが参考になった。文字の明滅はここからコードを拝借した。
- - - -
SimpleCap内ではflippedしているために座標系がCALayerと異なる。この変換が必要。
SimpleCap - Selection History Expose [4] 仮表示と選択
2010年10月29日金曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
モデルができたのでいよいよ表示にとりかかる。なお修正を加えるクラスは SelectionHandler と呼ぶ範囲選択を処理するクラス。GoFでいう Commandパターン辺りに該当するのだろうか。
interface SelectionHandler : HandlerBaseHandlerBase は NSObject の派生クラス(なので NSView 系統ではない)。{ NSRect _rect; : }
Selection History Expose表示ボタン
まずは範囲選択にボタンを追加する。自作のボタンバークラスへボタンの追加処理を加える。
実行するとこんな感じ。アイコンは後でちゃんと作る。
[参考情報] cocoaの日々 ThinButton - Google 検索
仮表示
まずはとりあえず履歴領域を白く塗りつぶしてみよう。
- (void)drawRect:(NSRect)rect {
:
case STATE_HISTORY:
[self drawBackground:rect];
[self drawAllHistorySelection];
break;
:先程のボタンが押されたら半透明の黒で塗りつぶし(drawBackgroud)、その後履歴の描画にとりかかる(drawAllHistorySelection)。履歴の描画に加えて現在表示中の領域も表示するようにした。こうすると履歴がひとつもない場合でも最低は1つ表示されるので特別な処理(履歴表示を禁じるとかメッセージを出すとか)が不要になる。
- (void)drawAllHistorySelections
{
NSLog(@"TODO: %s", __PRETTY_FUNCTION__);
// [1] draw current selection
[self drawHistorySelection:_rect];
// [2] draw history selection
for (NSValue* historyValue in
[[SelectionHistory sharedSelectionHistory] array]) {
[self drawHistorySelection:[historyValue rectValue]];
}
}仮描画のコード。白く塗りつぶすだけ。- (void)drawHistorySelection:(NSRect)rect
{
[[NSColor whiteColor] set];
NSRectFill(rect);
}さて実行してみよう。
出た。
履歴選択
白い部分をクリックした時にその履歴の領域(位置+サイズ)を採用するように修正する。まずはクリック判定用のメソッドを書く。
- (NSRect)rectOfhistorySelectionsAt:(NSPoint)cp
{
NSRect rect = NSZeroRect;
for (NSValue* historyValue in
[[SelectionHistory sharedSelectionHistory] array]) {
NSRect historyRect = [historyValue rectValue];
if (NSPointInRect(cp, historyRect)) {
rect = historyRect;
break;
}
}
return rect;
}戻り値にクリックされた領域を返す。該当領域がない場合は NSZeroRect が返る。これを使って mouseDown: を実装する。
- (void)mouseDown:(NSEvent *)theEvent
{
:
case STATE_HISTORY:
historyRect = [self rectOfhistorySelectionsAt:cp];
if (!NSIsEmptyRect(historyRect)) {
[self setFrame:historyRect];
}
[self changeState:STATE_RUBBERBAND];
return;
// ** not reached **
:白い部分がクリックされた場合は現在の範囲選択の位置とサイズを変更した後、元の範囲選択画面へ戻る。それ以外をクリックした場合は何もせずに戻る。位置とサイズの変更はアンドゥ対象となるため setFrame: メソッドを用意し、そこで必要な手当を行っておく。
-(void)setFrame:(NSRect)frame
{
CaptureView* view = [_capture_controller view];
NSUndoManager* undoManager = [view undoManager];
[[undoManager prepareWithInvocationTarget:self]
setRubberBandFrame:_rect];
[undoManager setActionName:NSLocalizedString(@"UndoResizeSelection", @"")];
[self setRubberBandFrame:frame];
}実行結果
初期状態
履歴を表示し適当な領域を選択すると...
出た。アンドゥもちゃんと働く。って静止画じゃわかりずらいが..
考察
履歴の表示は重なったり小さくて見にくいケースが想定されるので工夫が必要。また(200x200のような)サイズ表示があるとよさそうだ。それと選択された時に点滅するとか何らかのアニメーションを起こしてユーザにフィードバックすることも必要だな。
- - - -
次回も引き続き表示。
SimpleCap - Selection History Expose [3] モデル実装
2010年10月28日木曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
まずモデルから実装する。
モデルクラス - SelectionHistory
前回定義した仕様でモデルに関係するところをピックアップすると次の通り。
- 範囲選択でキャプチャ実行した時の位置と大きさを履歴として記録する
- 同じ大きさが履歴にある場合は位置のみ更新する(常に履歴内のすべてのサイズは異なる)
- 過去の履歴は最大10件までとする
- 履歴は次回起動時にも利用できる
- 履歴件数はプリファレンスで変更できる
- 履歴はプリファレンスで削除できる
モデルはこのロジックを実装すればいい。
こんな感じで書いてみた。
インターフェイス
@interface SelectionHistory : NSObject {
NSMutableArray* historyArray_;
}
+ (SelectionHistory*)sharedSelectionHistory;
- (void)addHistoryRect:(NSRect)frame;
- (NSArray*)array;
- (NSRect)rectAtIndex:(int)index;
- (void)clearAll;
@endシングルトンとして実装し +shareSelectionHistory でインスタンスを取得する。
static SelectionHistory* selectionHistory_;
+ (SelectionHistory*)sharedSelectionHistory
{
if (selectionHistory_ == nil) {
selectionHistory_ = [[SelectionHistory alloc] init];
}
return selectionHistory_;
}NSRect を受け取り追加する。
- (void)addHistoryRect:(NSRect)rect
{
NSInteger index;
for (index=0; index < [historyArray_ count]; index++) {
NSValue* historyValue = [historyArray_ objectAtIndex:index];
NSRect historyRect = [historyValue rectValue];
if (NSEqualSizes(historyRect.size, rect.size)) {
break;
}
}
if (index < [historyArray_ count]) {
// found it
[historyArray_ removeObjectAtIndex:index];
}
[historyArray_ addObject:[NSValue valueWithRect:rect]];
while ([historyArray_ count] >
[[UserDefaults valueForKey:UDKEY_SELECTION_HISTORY_MAX] intValue]) {
[historyArray_ removeObjectAtIndex:0];
}
[self save];
}追加にあたっては履歴中に同じサイズのものがある場合には、それを削除し新たに追加しなおす。つまり同サイズは最近のものだけを残すようにする。また履歴の最大数を越えた場合は履歴の数を調整する。追加の最後に NSUserDefaults へ保存する。
- (void)save
{
NSMutableArray* rectArray = [NSMutableArray array];
for (NSValue* rectValue in historyArray_) {
NSString* rectString = NSStringFromRect([rectValue rectValue]);
[rectArray addObject:rectString];
}
[UserDefaults setValue:rectArray forKey:UDKEY_SELECTION_HISTORY];
[UserDefaults save];
}キャプチャ時に履歴追加
モデルができたのでキャプチャ時に記録するようコードを追加した。
:
case TAG_RECORD:
[_capture_controller setContinouslyFlag:NO];
[_capture_controller saveImage:[self capture] imageFrame:_rect];
[[SelectionHistory sharedSelectionHistory] addHistoryRect:_rect];
[_capture_controller exit];
break;
:試しに実行して試してみよう。履歴の中身をデバッグコンソールへ出力した。
NSRect: {{155, 180}, {400, 250}}
NSRect: {{155, 180}, {298, 343}}
NSRect: {{776, 71}, {210, 128}}よさそうだ。
- - - -
次回は表示。
SimpleCap - Selection History Expose [2] 仕様
2010年10月27日水曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
Selection History Expose開発について。
今回は仕様決め。
仕様
こんな感じ。
- 範囲選択内のボタンから起動する(その他の起動も用意する。ダブルクリックなど)
- 範囲選択でキャプチャ実行した時の位置と大きさを履歴として記録する
- 同じ大きさが履歴にある場合は位置のみ更新する(常に履歴内のすべてのサイズは異なる)
- 過去の履歴は最大10件までとする
- 履歴は次回起動時にも利用できる
- 履歴件数はプリファレンスで変更できる
- 履歴はプリファレンスで削除できる
- マルチスクリーンに対応する(後日...)
- 表示する時には視覚効果(アニメーション)を使う
開発アプローチ
こんな感じで進めていく。
- モデル作成
- キャプチャ時追加処理
- 表示処理
- プリファレンス設定
次から実装に入る。
SimpleCap - Selection History Expose [1] アイディア
2010年10月26日火曜日 | Published in Mac OS X 10.6, SimpleCap | 0 コメント
というわけで(?)久々に SimpleCap の開発を再開。
Selection History Expose
Selection History Expose(仮称〜範囲選択履歴エクスポゼ)、とは Mac OS X の Exposeよろしく過去に使った範囲を画面に表示するというもの。
こんなイメージ。
ブログの画像作成に範囲選択をよく使うのだが、同じ範囲を何度も使いまわすことが多い。範囲はプリセット設定できるし、Cmd+Z で Undo できるのだが使い勝手はあまりよくない。また以前から過去の履歴を使いたいとの要望を受けていたし、実際初期バージョンでは履歴機能を盛り込んだこともあった。ただ履歴を単純にリストで表示しても分かりづらく、使いやすさに欠けていることからリリースは見送ってきた。そうしてずいぶん長い間放っておいたのだが昨日「Expose風に過去の範囲を見せて、それをユーザに選択させたらどうだろう?」と突然思いついた。思いついたら居ても立ってもいられなくて?iPhone開発を中断してこれにとりかかることにした。思いつきが本当に使いやすいか?とにかく実装してみよう。
次回から設計に入る。
(続く)
[参考情報] 過去の検討など
(旧) Cocoaの日々: 範囲選択履歴(その1)
(旧) Cocoaの日々: 範囲選択履歴(その2)
(旧) Cocoaの日々: 範囲選択履歴(その3)
(旧) Cocoaの日々: 範囲選択履歴(その4)ポップアップメニュー
(旧) Cocoaの日々: 範囲選択履歴(その5)ポップアップメニューのハンドリング
(旧) Cocoaの日々: 範囲選択履歴(その6)モデル作成
(旧) Cocoaの日々: 範囲選択履歴(その7)履歴の追加
(旧) Cocoaの日々: 範囲選択履歴(その8)履歴の選択
(旧) Cocoaの日々: 範囲選択履歴(その9)履歴の保存
人気の投稿(過去 30日間)
-
先日投稿した記事のソースコードに @griffin_stewie さんから pull request をいただいた。 Cocoaの日々: CAGradientLayer を使ったグラデーション付きボタンの試作 GradientButton at 2011-10-22 f...
-
iOS3.2 からメニューのカスタマイズが可能になった。少し試してみた。 UIMenuController メニューは UIMenuController を使ってカスタマイズする。 UIMenuController Class Reference 使い方は次の通り。...
-
NSUserDefaultsにはアプリケーション出荷時設定を登録することができる。 -アプリケーション出荷時設定 -[NSUserDefaults registerDefaults:] を使う。 User Defaults Programming Topics: Usi...
-
locationInView: の引数に安易に nil を渡していたら画面回転時に痛い目にあったのでメモしておく。 タッチ位置の座標系 -[UITouch locationInView:] の引数を nil とすると Windowの座標系でタッチ位置が取得できる。 [参考...
-
[関連] Cocoaの日々: UIKit コントロールの View構成と retainCount (追記)前提条件が抜けていました。UIViewController での話です。 (追記 6/29)assign はやめて retain にしました。下記を参照のこと。 C...



































