ページ

ラベル スクラップブック の投稿を表示しています。 すべての投稿を表示
ラベル スクラップブック の投稿を表示しています。 すべての投稿を表示

2008年2月24日日曜日

スクラップブックその12 - 画像の拡大縮小

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

画像を拡大縮小できるようにしてみる。



ソースコード:sp4-12.zip


拡大縮小はよくある四方に□をつけて行うやつではなく、ウィンドウのように右下だけで行うようにする。
<=このアイコンを画像の右下に表示する。この画像は OSについていた resize.gifを拝借している。

リサイズアイコンは画像が選択された時だけ表示して機能するようにする。こんな感じ。




機能実装のポイントは次のとおり。
 (1) リサイズアイコンの表示
 (2) リサイズアイコンドラッグ時の処理(拡大縮小)


(1) リサイズアイコンの表示
以前、導入した ItemIcon クラスを改良して使うことにする。初期化時に画像の4角(左上、右上、左下、右下)のどこへ表示するか指定し、位置決めの為に以前の setOrigin: をやめて、紐づける画像のサイズを渡す setBaseRect: に変更した。
その後、WorkViewに新たに ItemIconのインスタンス変数を追加し、表示処理を加えた。

(2) リサイズアイコンドラッグ時の処理(拡大縮小)
mouseDown: の中でリサイズアイコンのヒットテストを行い、押されているようなら新設のメソッド resizeIcon: を呼ぶ。

WorkView.m

-(void)resizeItem:(Item *)item
{
NSPoint current_point;
NSEvent* theEvent;

while (1) {
theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
current_point = [self convertPoint:[theEvent locationInWindow]
fromView:nil];

[self autoscroll:theEvent];

[item setSize:NSMakeSize(current_point.x - [item.x floatValue],
current_point.y - [item.y floatValue])];
[_icon setBaseRect:[item rect]];
[_resize_icon setBaseRect:[item rect]];
[self setNeedsDisplay:YES];
if ([theEvent type] == NSLeftMouseUp) {
break;
}
}
}


ここでイベントループを作り、マウスボタンが離されるまで画像サイズの拡大縮小計算を行う。ドラッグの時にやっていた移動量のチェックは今回は行っていない。

画像をビューへ描画するのに以前 compositeToPoint:operation: を使っていたが、これでは拡大縮小表示ができないので別のメソッド NSView#drawInRect:fromRect:operation:fraction: を使う。

fromRect: に NSZeroRectを指定すると画像のサイズが使われ、これが drawInRect: で指定した領域へ写像される(拡大縮小が行われる)。



忘れずに dragImage: に渡す画像作成も compsiteToPoint: から drawInRect: へ切り替えておく。


なお compsiteToPoint: を drawInRect: へ切り替えただけだと画像が上下反転して表示されてしまう。



これは drawInRect: の場合はビューの flip状態を見て描画する為。スクラップブックでは描画先のビューである WorkView の isFlipped で YESを返すようにしている。この為、drawInRect: を使うと図のように上下反転してしまう。そこで画像も反転表示するように setFlipped: を使い flip属性を変えてしまう。最終的な WorkView#drawRect: は次のようになる。

- (void)drawRect:(NSRect)rect {
[[NSColor whiteColor] set];
NSRectFill(rect);

for (Item* item in [_controller items]) {
[item.image setFlipped:YES];
[item.image drawInRect:[item rect]
fromRect:NSZeroRect operation:NSCompositeSourceOver
fraction:1.0f];
[item.image setFlipped:NO];

if (_selected_item == item) {
// draw selection
[[[NSColor grayColor] colorWithAlphaComponent:0.3f] set];
[NSBezierPath fillRect:[item rect]];
[_icon draw];
[_resize_icon draw];
}
}
}


これで画像が正しく表示されるようになった。

- - - -
選択すると灰色になるがちょっと見づらい。枠に戻すか、別の表現の方がよさそう。

2008年2月23日土曜日

スクラップブックその11 - アイコン表示の変更ほか

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

マウスオーバーでアイコンの表示・非表示を制御するとちらちらして少々うっとおしい。そこで選択された時のみアイコンを表示するようにする。

mouseMoved: の中でマウス下の画像をチェックしていたがそれが不要になった。画像が選択されている (_selected_item != nil)の時だけマウスの位置をチェックするようにする。

WorkView.m

- (void)mouseMoved:(NSEvent *)theEvent
{
if (_selected_item == nil) {
return;
}

NSPoint cp = [self convertPoint:[theEvent locationInWindow] fromView:nil];

if ([_icon inRectAtPoint:cp]) {
[_icon setState:2];
} else {
[_icon setState:1];
}

[self setNeedsDisplayInRect:[_icon frame]];
[self setNeedsDisplay:YES];
}


アイコンの上にマウスがあるかを ItemIcon#inRectAtPoint で判断するだけの処理となる。すっきりした。

これに合わせて mouseDown: の処理も少し書き換える。具体的には、(1)画像選択時にアイコンを左上に表示する (2)画像選択時にアイコンを消す (3)画像をドラッグ中にアイコンの位置も合わせて移動させる。


ソースコード:sp4-11.zip



- - - -

WorkView.m の initWithFrame: 内にあった ItemIconの作成を今回 awakeFromNibへ移した。XCode上の実行では問題なかったが、アプリケーションとしてビルドし、別のマシンで実行したところアイコン表示が出ていないことに気がついたので。

2008年2月22日金曜日

スクラップブックその10 - 画像をファインダへドラッグ&ドロップする

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

スクラップブック上の画像をファインダへドラッグ&ドロップできるようにする。こんな感じ↓



をつかみファインダ上でドロップするとその画像のファイルがコピーされる。

ソースコード:sp4-10.zip


他アプリへのドラッグ&ドロップは NSView#dragImage:at:offset:event:pasteboard:source:slideBack: を使えば容易に実現できる。方法は以前の投稿で紹介した(「Finderへドロップ」)。

実装はまず最初にアイコンが押された(ドラッグに入った)かどうかの判定から入る。

WorkView.m

- (void)mouseDown:(NSEvent *)theEvent
{
   :
   :
if ([_icon inRectAtPoint:start_point]) {
   // 他アプリへのドラッグ&ドロップ処理
  }
   :
  // 通常の画像のドラッグ処理
}


アイコンが押されたらペースとボードとドラッグ用の画像を用意して dragImage:... を呼出す。
まず新規に NSImage を作成し、これにコピー元となる画像を半透明で描画する。

  NSImage *dragged_image = [[[NSImage alloc] initWithSize:[item image].size] autorelease];
[dragged_image lockFocus];
[[item image] compositeToPoint:NSZeroPoint
operation:NSCompositeSourceOver
fraction:0.8f];
[dragged_image unlockFocus];


続いて NSPasteboardの準備。NSFilesPromisePboardType を登録しておく。登録しておくとドロップ時に namesOfPromisedFilesDroppedAtDestination: がコールバックされる。

  NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[pboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
[pboard setPropertyList:[NSArray arrayWithObject:[item.filename pathExtension]]
forType:NSFilesPromisePboardType];


最後に drawImage:... を呼出す。これでドラッグ操作が開始される。

  [self dragImage:dragged_image
at:NSMakePoint([item.x floatValue], [item.y floatValue]+[item.height floatValue])
offset:NSZeroSize
event:theEvent
pasteboard:pboard
source:self
slideBack:YES];



ペーストボードには NSFilesPromisePboardType が登録されているので、ファインダへドロップした時に namesOfPromisedFilesDroppedAtDestination: が呼出される。ここで画像のコピー処理を書いてやれば良い。

- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
{
id dst_path = [[dropDestination relativePath] stringByAppendingPathComponent:_dragged_filename];
id src_path = [[_controller pathToSave] stringByAppendingPathComponent:_dragged_filename];

[[NSFileManager defaultManager] copyPath:src_path
toPath:dst_path
handler:nil];
return [NSArray arrayWithObject:_dragged_filename];
}


ドラッグしている画像のファイル名はこのメソッドだけでは得られないので、ドラック開始前にあらかじめメンバ変数 _dragged_filename へ取っておく。



なおこのままだと自分自身へもドロップができてしまう。この場合、実体の画像ファイルが1つなのに画面上は2つの同じ画像が表示される状態になる。これはこれで使い道もあるのだが、現状では削除した場合に矛盾が生じてしまうので禁止することにする。draggingEntered: と performDragOperation: に自分自身へのドロップかどうかのチェックを入れておく。

- (NSDragOperation)draggingEntered:(id )sender
{
if (self == [sender draggingSource]) {
return NSDragOperationMove;
}
return NSDragOperationCopy;
}

- (BOOL)performDragOperation:(id )sender {

if (self == [sender draggingSource]) {
return NO;
}
 :


NSDraggingInfo#draggingSource にはドラッグ元のオブジェクト(id)が入る(ドラッグ元が他のアプリケーションの場合は nilとなっていた)。



ここまでで簡単なスクラップブックの機能が実装できた。振り返って見ると改めて cocoaの強力さを感じた。ところどころ良くできているのに感心し、それとともに流れが分かってくると結構楽しかった。
なお、検証目的でとにかく早く作っていったので、メモリ確保の後始末(release)や描画の効率化、エラー処理の実装などは手を抜いてきた。きちんと作るとこの辺りで結局コード量は膨らむと思う。


- - - -
スクラップブックはこの後も検証目的ですこしずつ改良を加えていくつもりだが、そろそろちゃんとしたアプリの制作にとりかかろうと思う。最終目的はスクラップブックに近いものだがその前に小さなツールをいくつか作ってみたい。今後はそのあたりの開発過程もこのブログで紹介していきたいと思う。

2008年2月21日木曜日

スクラップブックその9 - 画像上にアイコンを表示する

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

マウスカーソルが画像の上に来たら、左上にアイコンを表示するようにする。



ソースコード:sp4-9.zip

このアイコンは他のアプリケーション(ファインダなど)へドラッグ&ドロップして画像をコピーする時につかむ位置を表している。ドラッグ処理は次回へ取っておいて、今回は表示する処理を追加する。


アイコンを管理する為に新しいクラス ItemIcon を追加した。

ItemIcon.h

@interface ItemIcon : NSObject {

NSImage *_image;
int _state;
NSRect _frame;
}
- (id)initWithImage:(NSImage*)image;
- (void)setOrigin:(NSPoint)point;
- (BOOL)inRectAtPoint:(NSPoint)point;
- (void)setState:(int)state; // 0=off/1=on/2=pushed
- (void)draw;
- (NSRect)frame;


ItemIconはアイコン画像と表示位置を保持する。これらの情報を使いマウスポインタがアイコン上にあるかどうかのチェックや、アイコン画像の描画処理を行う。3種類の状態(0=無効、1=画像上にマウスあり、2=アイコン上にマウスあり)を持ち、マウスの位置によって表示の有無や、透明度を変えている。


WorkView はこのインスタンスを変数として持ち、マウスポインタの位置などによって状態を制御する。

WorkView
- (void)mouseMoved:(NSEvent *)theEvent
{
   :
   :
[_icon setOrigin:[_mouseon_item rect].origin];
if ([_icon inRectAtPoint:cp]) {
[_icon setState:2];    // 2: アイコンの上にマウスがある
} else if (_mouseon_item) {
[_icon setState:1];    // 1: 画像の上にマウスがある(しかしアイコンの上には無い)
} else {
[_icon setState:0];    // 0: 画像の上にマウスが無い
}

[self setNeedsDisplayInRect:[_mouseon_item rect]];
[self setNeedsDisplay:YES];
}


マウス移動のイベントハンドラ(mouseMoved:)内でマウスポインタをチェックし、状態を決めている。そして drawRect: 内で描画する。これは ItemIcon#draw を呼び出すだけで良い。


アイコン画像はバンドル内に配置し、アプリ起動時に MyController がこれを読み込み NSImageクラスのインスタンスとして作成する。

MyController.m
- (void)awakeFromNib
{
   :
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSArray* paths = [[NSBundle mainBundle] pathsForResourcesOfType:@"png"
inDirectory:nil];
for (NSString* file in paths) {
NSImage *image = [[[NSImage alloc] initWithContentsOfFile:file] autorelease];
[dict setObject:image forKey:[[file lastPathComponent] stringByDeletingPathExtension]];
}
_icons = [dict retain];
}


NSDictionary へ名前付きで入れておき、WorkViewが ItemIconを作成する時に取り出して使う。

- - - -

次回はこのアイコンをドラッグして別アプリ(ファインダなど)へコピーする処理を追加する。

2008年2月20日水曜日

スクラップブックその8 - マウスカーソル下の画像を反転させる

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

マウストラッキングの仕組みが分かったのでスクラップブックで使ってみる。まずはマウスカーソルが画像の上に乗ったら色を変える処理を加えてみよう。こんな感じ。



画像の上にマウスカーソルが乗ると赤くなる。

サンプル:sp4-8.zip


マウストラッキングには NSTrackingArea を使う。

WorkView.m

  _tracking_area = [[NSTrackingArea alloc]
initWithRect:[self bounds]
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect| NSTrackingEnabledDuringMouseDrag)
owner:self
userInfo:nil];
[self addTrackingArea:_tracking_area];


NSScrollViewを使う場合は、トラッキングオプションに NSTrackingInVisibleRect を加えてやるとスクロールバーがトラッキング領域から外れるので便利。

これで mouseMoved: メッセージが送られてくるようになるので、マウスカーソル下に画像があるかチェックする。
- (void)mouseMoved:(NSEvent *)theEvent
{
NSPoint cp = [self convertPoint:[theEvent locationInWindow] fromView:nil];
_mouseon_item = nil;
for (Item* item_test in [[_controller items] reverseObjectEnumerator]) {
if (NSPointInRect(cp, [item_test rect])) { // ヒットテスト
_mouseon_item = item_test;
break;
}
}
if (_mouseon_item) {
[self setNeedsDisplayInRect:[_mouseon_item rect]];
}
[self setNeedsDisplay:YES];
}


画像があったら _mouseon_item へ入れて再描画する。

- (void)drawRect:(NSRect)rect {
 :
if (_mouseon_item == item) {
[[[NSColor redColor] colorWithAlphaComponent:0.3f] set];
[NSBezierPath fillRect:[item rect]];
}


これでマウスカーソル下の画像が赤くなる。

画像の数が多くなると上記のヒットテストは無駄が多い。mouseMoved:が呼ばれる頻度を考えるともっと負荷をかけない方法が必要。

- - - -

仕組みができたので次はこれを利用して画像にアイコンを貼付ける。

2008年2月14日木曜日

スクラップブックその7 - 選択の表現を替える

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

画像が選択されていた時には枠線を表示していたが、グレーの半透明色で塗りつぶすように変更した。



WorkView.m

- (void)drawRect:(NSRect)rect {
[[NSColor whiteColor] set];
NSRectFill(rect);

for (Item* item in [_controller items]) {
[item.image compositeToPoint:NSMakePoint([item.x floatValue],
[item.y floatValue]+[item.height floatValue])
operation:NSCompositeSourceOver];

if (_selected_item == item) {
// draw selection
[[[NSColor grayColor] colorWithAlphaComponent:0.3f] set];
[NSBezierPath fillRect:[item rect]];
}
}
}

画像を描画した上に半透明色で上書きする。NSColor の colorWithAlphaComponentを使うと半透明色で描画ができる。


それと画像以外の個所をクリックした時に選択を外すようにした。現状ではメンバ変数 _selected_iem = nil だけで良い。

2008年2月13日水曜日

スクラップブックその6 - サイズ固定と自動スクロール

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

前回のスクラップブックでドラッグにあわせて Viewのサイズを変えたが、使いづらいのでサイズを固定することにした。
mouseDown: の中で呼出していた resizeWithRect: をコメントアウトし、InterfaceBuilderで WorkViewのサイズを(1000,1000)、Windowの最大サイズを (1000,1000)にした。




Viewが広くなったのでスクロールしていろいろな場所に画像を配置することができる。どうせなら画像をドラッグして画面の端へ移動させた時に自動的にスクロールするようにしたい。



Viewにはこれを簡単に行うことのできる便利なメソッドが用意されている。

[NSView autoscroll:]

このメッセージを NSViewへ送ると、外側の NSScrollView がこれを適切に処理してくれる。

Supporting Automatic Scrolling

ドラッグ処理に1行加える。

WorkView.m
- (void)mouseDown:(NSEvent *)theEvent
  :
while (1) {
theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
current_point = [self convertPoint:[theEvent locationInWindow]
fromView:nil];

[self autoscroll:theEvent];  // <--追加
  :




Cocoaは良くできている...感心。

2008年2月10日日曜日

スクラップブックその5 - スクロールバー追加

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

今度はスクロールバーを追加する。



Interface Builder で WorkViewを選択後、メニューから Layout->Embed Objects In->Scroll View を選ぶ。
これだけだと画像が WorkViewの範囲外にはみ出てもスクロールできない。はみ出た分だけ WorkViewの frameサイズを大きくする必要がある。

そこでドラッグ処理内で範囲外かどうかを判断して、その場合は frameサイズを拡大する処理を追加する。

WorkView.m


- (void)mouseDown:(NSEvent *)theEvent {
  :
 [self resizeWithRect:[item rect]]; // item ...ドラッグしている画像
  :
}

- (void)resizeWithRect:(NSRect)rect
{
float x = rect.origin.x + rect.size.width;
float y = rect.origin.y + rect.size.height;
NSSize view_size = [self bounds].size;
BOOL changed = NO;

if (view_size.width < x) {
view_size.width = x;
changed = YES;
}
if (view_size.height < y) {
view_size.height = y;
changed = YES;
}
if (changed) {
[self setFrameSize:view_size];
}
}


これでドラッグ中に WorkViewをはみ出すとスクロールバーが出るようになる。

なお、スクロールバー追加にあわせて WorkViewの y座標を反転(flip)させている。
WorkView.m

- (BOOL)isFlipped
{
return YES;
}


NSViewの座標系はデフォルトでは左下が (0,0)となるが、isFlippedをオーバーライドして YESを返すとそのViewは左上が (0,0)の座標系となる。
ただこのままでは画像が正しい位置に表示されないので、描画時に compositeToPoint:operation の位置を補正している。

[item.image compositeToPoint:NSMakePoint([item.x floatValue],
[item.y floatValue]+[item.height floatValue])
operation:NSCompositeSourceOver];


Flipped Coordinate Systems の Drawing Content in a Flipped Coordinate System > Drawing Images によると、compositeToPoint:fromRect:operation: を使った描画では、描画開始位置のみが isFlippedの影響を受けて、NSImage自体の座標系は変わらない(左下が (0,0)として右上方向に描画される)。このあたりの動きは「Figure 3-9 Compositing an image to a flipped view」の図を見ると一目で分かる。

NSImageの座標系も setFlipped: を使い Y座標を反転させることができるが、実際これは(感覚的に意図したようには)働かない。使って見ると下のように上下反転した表示になる。



この辺りは下記に記述がある。
Image Coordinate Systems

2008年2月8日金曜日

スクラップブックその4 - メニューとの連携

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

前回、deleteキーで削除を行ったが、メニューから削除を行えるようにする。
メニューにある削除(Delete)はデフォルトで First Responderに接続されている。
前回 acceptsFirstResponder を実装したので delete: メソッドを実装するだけでメニューが有効になる。

WorkView.m


-(IBAction)delete:(id)sender
{
if (_selected_item) {
[_controller deleteItem:_selected_item];
_selected_item = nil;
[self setNeedsDisplay:YES];
}
}


ここが Cocoaの面白いところで、実行時に FirstResponderが deleteメソッドを実装しているか判断してメニューの有効・無効の制御している。

メニューにはキーを割り当てることができるので deleteキーを割り当てる。Interface Builderでメニューから Deleteを選び属性の Key Equiv. に設定する。



すると前回実装した keyUp:(NSEvent *)theEventは不要となる。これは削除しておこう。


ただ、このままでは画像が選択されていない時もメニューが有効になってしまう。選択されていない時にはメニューを無効にしたい。cocoaはメニューの有効・無効を判断するのにメソッドの実装の他、そのメニューのターゲットとなるオブジェクト(今回は WorkView)に対してメッセージ validateMenuItem: を送り、その戻り値(BOOL)で判断を行っている。そこで WorkViewにこのメソッドを実装する。


-(BOOL)validateMenuItem:(NSMenuItem*)menuItem
{
if ([menuItem action] == @selector(delete:)) {
return (_selected_item != nil);
} else {
return YES;
}
}


これで選択されていない時はメニューが無効になる。

- - - - -
今回もヒレガス本に世話になりました。
米国では新版が出るとの噂を聞いた。日本訳も出てくれると良いのだが。

2008年2月7日木曜日

スクラップブックその3 - 削除

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

選択した画像を削除する。削除はDeleteキーで行う。

まずビューでキー入力をハンドリングする。

WorkView.m


- (BOOL)acceptsFirstResponder
{
return YES;
}
- (void)keyDown:(NSEvent *)theEvent
{
}
- (void)keyUp:(NSEvent *)theEvent
{
if ([theEvent keyCode] == 117 && _selected_item) {
[_controller deleteItem:_selected_item];
_selected_item = nil;
[self setNeedsDisplay:YES];
}
}

キーボード・イベントを取り扱う場合には acceptsFirstResponder をオーバライドし YESを返す(ヒレガス本参考)。
次にdeleteキーが押されたかを keyUp: で判定する。keyDown: で処理する場合は、押しっぱなしだと何度も呼び出されるので1回だけ呼出される keyUp:を使う。MyController#deleteItemを呼び出してモデルを削除した後、選択を解除し再描画する。


以下は MyController内でのモデル削除の処理コード。

MyController.m

-(void)deleteItem:(Item*)item
{
NSString *filepath = [_path_to_save stringByAppendingPathComponent:item.filename];
[_items removeObject:item];                         // (1)
NSManagedObjectContext* moc = [_app_delegate managedObjectContext];
[moc deleteObject:item];                           // (2)

NSFileManager *fm = [NSFileManager defaultManager];
[fm removeFileAtPath:filepath handler:nil];                  // (3)
}

処理は3つ。(1)配列から削除、(2)CoreDataから削除、(3)画像ファイルの削除。
(本当はちゃんとエラー処理を入れてトランザクションを形成すべし)。

2008年2月6日水曜日

スクラップブックその2 - 選択枠を描く

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

選択された時に画像の周りに灰色の枠線を描く処理を入れてみる。

まず選択画像を表すメンバ変数をビューへ追加する。

WorkView.h


@interface WorkView : NSView {
   :
Item* _selected_item;
}

その上でマウスで画像が選択された時に上記変数へ入れておく。


drawRect: で選択画像の周りに枠線を描画する。
WorkView.m

- (void)drawRect:(NSRect)rect {
[[NSColor whiteColor] set];
NSRectFill(rect);

for (Item* item in [_controller items]) {
[item.image compositeToPoint:NSMakePoint([item.x floatValue], [item.y floatValue])
operation:NSCompositeSourceOver];

if (_selected_item == item) {
// draw selection
[[NSColor lightGrayColor] set];
NSRect rect = [item rect];
[NSBezierPath setDefaultLineWidth:1.5];
[NSBezierPath strokeRect:rect];
}
}
}


こんな感じ。

2008年2月5日火曜日

スクラップブックその1 (4)ビュー

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

WorkView はドラッグ&ドロップの受け入れと、画像の表示を行う。
ドロップされた画像の保存は以前紹介した Pasteboard Managerを使って行っている。それ以外は MyControllerから image(NSManagedObject)のインスタンスを取得し、必要な設定を行う。


- (BOOL)performDragOperation:(id )sender {

  :

NSImage *image = [[NSImage alloc] initByReferencingURL:[NSURL URLWithString:filepath]];
NSSize size = [image size];
Item *item = [_controller createItem];

item.image = image;
item.x = [NSNumber numberWithFloat:(point.x - size.width / 2)];
item.y = [NSNumber numberWithFloat:(point.y - size.height / 2)];
item.width = [NSNumber numberWithFloat:size.width];
item.height = [NSNumber numberWithFloat:size.height];
item.filename = [filepath lastPathComponent];
  :
}



貼付けられた画像のドラッグ処理は mouseDown: 内で行う。
ADCのドキュメント Handling Mouse Dragging Operations によればドラッグ処理は2つのアプローチがあるとのこと。

 The Three-Method Approach
 The Mouse-Tracking Loop Approach

前者は mouseDown: で開始、mouseDragged: でドラッグ処理、mouseUp: で終了と、その名のとおり3つのメソッドで処理を記述する。この方法の良いところはそれぞれのイベントハンドリングを適切なメソッドで書けるところ。イベント毎の責務が分かれている為、見た目も比較的すっきりする。ただ3つのイベントを連携させる必要があるためケースによっては複雑になるかもしれない。

後者は mouseDown: 内に自前でイベントループを作る。見た目は昔っぽい?が、マウス押しから、ドラッグ、離すまでの処理が一カ所にまとまっているので分かりやすいし、一連の動きを制御しやすい。

説明が長くなったがサンプルでは後者のアプローチを採用している。コードそのものは(忘れてしまったが)どこかのソースコードを参考にして過去に書いたものを流用している。


- (void)mouseDown:(NSEvent *)theEvent
 :
 :
while (1) {
theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
current_point = [self convertPoint:[theEvent locationInWindow]
fromView:nil];

if (!is_moving &&
((fabs(current_point.x - last_point.x) >= 2.0) ||
(fabs(current_point.y - last_point.y) >= 2.0))) {
is_moving = YES;
}

if (is_moving) {
_selected_item = nil;
moving_vector = NSMakePoint(current_point.x - last_point.x,
current_point.y - last_point.y);
[item moveWith:moving_vector];
[self setNeedsDisplay:YES];
}
last_point = current_point;

if ([theEvent type] == NSLeftMouseUp) {
break;
}
}
while (1) {
theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
current_point = [self convertPoint:[theEvent locationInWindow]
fromView:nil];

if (!is_moving &&
((fabs(current_point.x - last_point.x) >= 2.0) ||
(fabs(current_point.y - last_point.y) >= 2.0))) {
is_moving = YES;
}

if (is_moving) {
_selected_item = nil;
moving_vector = NSMakePoint(current_point.x - last_point.x,
current_point.y - last_point.y);
[item moveWith:moving_vector];
[self setNeedsDisplay:YES];
}
last_point = current_point;

if ([theEvent type] == NSLeftMouseUp) {
break;
}
}
 :


点の移動量が 2.0以上の場合をドラッグとみなし、移動量をドラッグ対象の画像の位置に反映させて再描画させている。ドラッグ対象の画像は同じ mouseDown: の先頭でヒットテストを行い決定する。

- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint start_point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
Item* item = nil;
// hit test
for (Item* item_test in [[_controller items] reverseObjectEnumerator]) {
if (NSPointInRect(start_point, [item_test rect])) {
item = item_test;
break;
}
}
 :
 :
(この後、イベントループ)

2008年2月4日月曜日

スクラップブックその1 (3)コントローラ

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

コントローラ MyControllerは、ビュー(WorkView)とモデル(Item、CoreData)との間を取り持つ。

i. 起動時

プログラム起動時にCoreDataから Itemの配列を取り出して配列 _items へ格納する。
また同時に itemsフォルダ内の画像ファイルをそれぞれ Item.image へ格納していく。
WorkViewが itemsを元に画像を表示する。


- (void)awakeFromNib
{
_path_to_save = [[_app_delegate applicationSupportFolder] retain];
NSFileManager *file_manager = [NSFileManager defaultManager];
[file_manager createDirectoryAtPath:_path_to_save
attributes:nil];

NSManagedObjectContext *moc = [_app_delegate managedObjectContext];
NSEntityDescription *entity_descriptin = [NSEntityDescription entityForName:@"Item"
inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity_descriptin];

NSError *error;
_items = [[moc executeFetchRequest:request error:&error] retain];
if (_items == nil) {
_items = [[NSMutableArray alloc] init];
}

[_items makeObjectsPerformSelector:@selector(willAccessValueForKey:) withObject:nil];

for (Item* item in _items) {
item.image = [[NSImage alloc]
initWithContentsOfFile:[_path_to_save stringByAppendingPathComponent:item.filename]];
}

}



画像がドラッグ&ドロップされた時、WorkViewが新しい imageを要求する(createItem)。これを受けてMyControllerでは、sp4_AppDelegateから NSManagedObjectContext を取得し、新しい image(NSManagedObject)を作成して返す。

-(Item*)createItem
{
Item *item = [NSEntityDescription insertNewObjectForEntityForName:@"Item"
inManagedObjectContext:[_app_delegate managedObjectContext]];
[_items addObject:item];
return item;
}



WorkViewはitemを使って画像を表示するが、その生成(と今後実装予定の削除など)については MyControllerが中を取り持つ形となる。

2008年2月3日日曜日

スクラップブックその1 (2)アーキテクチャ

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

サンプルアプリは Xcodeで新規プロジェクトを作成する時に「Core Data Application」テンプレートを使った。
イメージを下に示す。



テンプレートによって作成された sp4_AppDelegate はほとんど触らず、別途 MyControllerを作成してこれに Viewとの相手をさせる。画像(Item)の管理は NSMutableArray で行っている。Itemは NSManagedObjectであり CoreDataで管理されているのだが、Viewで扱う場合はNSArrayの方が何かと扱いやすい。NSArrayControllerを作りこれを今回の WorkViewとつなげられれば良いのだが、自前でバインディングを導入する方法がわからないため今回は断念(そもそもそんなことができるのだろうか)。

ドラッグされた画像の保存は以前紹介した Pasteboard Managerを使っている。コードも以前紹介したサンプルのまんま。

2008年2月2日土曜日

スクラップブックその1 (1)モデル

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


画像の表示と保存に必要な情報が定義されている。位置情報は float、imageは Transformable(変換可能)でかつ「一時」的なデータとしている。imageの実態は別途ファイルで管理しているので CoreData上ではメモリ上にあるだけで良い。


モデリングツールで定義した後、このモデルのソースコードを作成する。モデリングツールを開いたままで、メニューから「新規ファイル」を選び、一番したの「設計 - 管理オブジェクトクラス」を選ぶと続いてアシスタントが表示される。



ソースコードを作らなくとも CoreDataは使えるが、モデルに関する特別な処理(値変換など)を記述する場合にはあった方がカスタマイズしやすい。

サンプルではドラッグ&ドロップ処理用のメソッドをいくつか定義している。


- (NSRect)rect
{
return NSMakeRect([self.x floatValue], [self.y floatValue], [self.width floatValue], [self.height floatValue]);
}

- (void)moveWith:(NSPoint)moving_vector
{
self.x = [NSNumber numberWithFloat:([self.x floatValue] + moving_vector.x)];
self.y = [NSNumber numberWithFloat:([self.y floatValue] + moving_vector.y)];
}


CoreDataでは数値型がすべて NSNumberになるのが難点。NSRectなどの構造体がそのまま扱えると便利なのだが。

2008年2月1日金曜日

スクラップブックその1

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

スクラップブック(のプロトタイプ)を作ってみた。



上はアプリに Safari上の画像をドラッグ&ドロップして配置したもの。配置した画像はドラッグして移動できる。画像はデスクトップの itemsフォルダに保存される。




画像の配置情報は CoreDataで管理している。




アプリを立ち上げ直すと、前回終了時の画像が表示される。

今日はここまで。
コードの解説と改良はまた明日。


サンプル:sp4.zip