2007/02/20

次のゴール

前回のゴールは達成できたので、次のゴールを設定することにする。

次のゴールは、「ファイルの出力先ディレクトリを指定できるようにする」にしよう。

Export Thumbnailsボタンを押した時に、いきなり処理を行うのでなくダイアログボックスを表示し、設定を変更した後、そのダイアログボックスにある「実行」ボタンを押してエクスポートを行うようにする。ダイアログボックスにはデフォルトのディレクトリを表示しておき、その変更は標準のファイル選択ダイアログボックスを使うようにする。

箇条書きにすると、
  • ボタンクリックでカスタムダイアログボックスの表示
  • ダイアログボックスからのファイル選択ダイアログボックスの表示
  • 前回選択したディレクトリをアプリケーション終了後もどこかに保持しておく
以上の3点だ。

Xcodeでソースコード管理: Subversionを使う

しばらくCocoaアプリケーションの開発をやってきて、そろそろソースコード管理システムなしで開発するのがおっかなくなってきた。Xcodeのメニューを見ると、SCMという文字が見えるからソースコード管理システムとの統合が実現されているように思う。実際、CVS, Subversion, Perforceといったソースコード管理システムと連動できるようになっている。

そこで、XCodeのSubversion連動機能を使えるよう設定してみることにした。設定手順については詳しくは書かないが、一筋縄ではいかないのでやってみようと思う人はがんばってほしい。

大まかな手順は以下の通り
  • apache2をインストールする。Mac OS Xインストール時に動いているのは1.3ベースのapacheなのでこれを止め、apache2をMacPortsからインストールする。
  • subversionもMacPortsから入れ直す。この時、mod_dav_svn.soがインストールされるようなオプションを付けてインストールする。
  • apache2で、mod_dav_svn.soを使うように設定。
  • subversionのリポジトリを作る。
  • XCodeのプロジェクトの複製を作り、subversionリポジトリにインポートする。
  • subversionリポジトリからプロジェクトをcheckoutする。
  • Xcodeのプロジェクト設定からSCMをenableする。subversionのバイナリの位置を指定する。が、Subversion 1.4とXcode 2.4ではNSCFArray addObject attempt to insert nilというエラーが出るので、これを直すために次の作業が必要。
  • SIMBLをインストールする。
  • Xcode+svn-1.4というパッチをインストールする。
と大変手間が掛かるが、動き出すとなかなか便利そうだ。

2007/02/18

画像縮小

目標通り、今作っているアプリケーションでボタンを押すと、現在読み込まれている画像を縮小したファイルを吐き出す機能が実装できた。

縮小の操作については動作を完全に理解できていないので、あまり参考にしない方がよいと思うが、一応以下の操作でオリジナルの20%のサイズの画像を生成できる。

  NSImage* originalImage = [[imageModel objectAtIndex:i] copy];
NSBitmapImageRep* originalImageRep = [NSBitmapImageRep imageRepWithData:[originalImage TIFFRepresentation]];

// producing a scaled image
// scale to 20%
NSSize rect = NSMakeSize([originalImageRep pixelsWide], [originalImageRep pixelsHigh]);
// [originalImageRep release];

NSSize thumbRect;
thumbRect.width = rect.width * 0.2;
thumbRect.height = rect.height * 0.2;

NSImage* image = [[[NSImage alloc] initWithSize:thumbRect] autorelease];
[image lockFocus];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[originalImage setScalesWhenResized:YES];
[originalImage setSize:thumbRect];
[originalImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
[image unlockFocus];

次に、生成した画像をJPEGファイルとして保存する手順についてメモしておく。手順としては、NSImageからNSBitmapImageRepを取得する。NSBitmapImageRepをJPEGデータとしてNSDataを生成する。これをファイルに書き出せばOKだ。

// Saving to disk
NSData* imageData = [image TIFFRepresentation];
NSBitmapImageRep* imageRep = [NSBitmapImageRep imageRepWithData: imageData];
NSDictionary* imageProps = [NSDictionary dictionaryWithObject: [NSNumber numberWithFloat: 0.9] forKey:NSImageCompressionFactor];
imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];

NSString* outputFilename = [NSString stringWithFormat:@"/tmp/output%d.jpg", i];
[imageData writeToFile:outputFilename atomically:NO];

2007/02/15

Exposeの設定

Exposeを使いたいシチュエーションが増えてきたので設定を調整してみた。


ExposeはCtrl-右ボタンとしてみた。

画面右下にマウスを移動するのは意図的にやる場合以外ないので、それでDashboardを起動する。一応、キーボードでも起動できるようにはしたが、多分使わないだろう。

追記(2007/02/20): 右下にマウスを動かすのは意図的な場合と書いたものの、実は誤って起動されてしまうことが何度かあった。現在Dashboardの起動は左下に割り当ててみた。

Happy Hacking Keyboard Lite 2を使っていて、右コマンドキーはほぼ使わないので、これをアプリケーションウィンドウに割り当ててみた。

2007/02/14

画像サイズの取得: NSImageのサイズがおかしい

画像を読み込んで縮小するコードを書いている時におかしな現象に出くわした。

ある2592 x 1944ピクセルのJPEG画像を読み込んで画像のサイズをNSImage:sizeで取得すると、なぜか1036.80005 x 777.599976になってしまう。別の画像だとこの現象は起こらず、2592 x 1944となる。

おかしい原因はプレビューアプリケーションで、2つの画像を比べてみることで分かった。サイズがおかしくなる画像は、180 dpi (ドット/インチ)で、うまくいく方は72 dpiだった。どうやらdpiに応じた処理が行われているらしい。

180 dpiにおける2592ピクセルは、14.4インチ。これは72 dpiでは1036.8ピクセルとなる。というわけでsizeで採れる値と一致する。つまり、NSImage:sizeが返す値は、画像を72 dpiで表示したときのサイズなわけだ。

ではどうやって画像のピクセルサイズを取得するか?

これはNSImageからビットマップオブジェクト(NSBitmapImageRep)を生成し、その寸法を取得すれば良い。

NSImage* image = ...
NSBitmapImageRep* imageRep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
NSSize aSize = NSMakeSize([imageRep pixelsWide], [imageRep pixelsHigh]);
こんな感じでめでたくピクセル数がとれた。

2007/02/13

warning: 'MyController' may not respond to '-loadImageFile:'

ドラッグアンドドロップの実装のためのコードを書いていて次のようなエラーが出た。

warning: 'MyController' may not respond to '-loadImageFile:'
warning: (Messages without a matching method signature will be assumed to return 'id' and accept '...' as arguments.)

実際にソースコード(MyController.m) には、-(BOOL)loadImageFile:(NSString*) filenameというメソッドがある。

この警告が出る理由は、ヘッダファイルにこのメソッドの宣言が書かれていないからだ。コンパイラが出したのは警告であってエラーではないのでコンパイルは終了しコードは動くのだが、ヘッダは書いた方が良い。特に2行目のエラーは、「宣言がないので適当に計らいまっせ」と言っている。

その警告を直し、ドラッグアンドドロップの動作を確認してみる。うまく出来ているようでちゃんと画像をドラッグしたらサムネイルが表示されるようになった。


これで2月2日に書いたゴールを達成。ちょっと当初の目的とはずれてしまうが、[サムネイル生成]ボタンを追加し、それをクリックしたら適当なディレクトリにサムネイルファイルを生成するようにしてみよう。考えられる作業は次の通り。
  1. ボタンの追加
  2. ボタンからControllerへのアクションの追加
  3. NSImageあたりを使って画像の操作(画像サイズ取得と画像縮小)
  4. ファイルの書き出し
  5. ダイアログボックスを表示して、作業の完了をお知らせする

ドラッグアンドドロップの実装

Cocoaアプリケーションにドラッグアンドドロップを実装するのは比較的簡単だ。

今回はドラッグ対象をファイル名とし、そのコピー操作をドラッグアンドドロップで受け付けることにする。

ドラッグアンドドロップ操作を受け付けるには最低2つのメソッドを実装する必要がある。1つ目は、-(NSDragOperation)draggingEntered:(id )sender、もう1つは-(BOOL)performDragOperation:(id )senderだ。

draggingEnteredはウィンドウやビューにドラッグされたオブジェクトが重なった時に呼び出され、それを受け付けるかどうか応答するメソッドだ。ドラッグされてきたオブジェクトが何なのかは、NSPasteboard型のペーストボードから取得できる。このメソッドで、NSDragOperationCopyを返せばコピー操作を受け付けることになり、ドラッグされてきたオブジェクトの画像の+サインがつく。

ドラッグアンドドロップでオブジェクトが離された時にドラッグアンドドロップの操作が実行されるが、それを司るのがperformDragOperationメソッドだ。こちらもペーストボードを参照し、ドラッグされたもの、その操作に応じた処理を行う。処理に応じてYES/NOのBOOL値を返す。

ということで、この2つを実装し、ドラッグされて来たものが離された時にファイル名をログに出力するところまで実装した。

2007/02/02

リサイズイベントへの反応とコントローラーへのoutletの設定

前回ゴールとしたもののうち、1つ残っていたのがウィンドウのリサイズに応じてNSMatrixのセルの配置を修正することだ。例えば、画像を10枚表示したい時、横に4枚しか並べられないなら4x3で表示するが、このウィンドウの幅がさらに狭くなって横に3枚しか並べられないなら3x4で表示するようにしたい。



ウィンドウのリサイズイベントは、viewDidEndLiveResizeメソッドで拾うことができる。MyMatrixの表示状態が変わるのだから、MyMatrixクラスのviewDidEndLiveResizeメソッドをオーバーライドしてみた。

- (void)viewDidEndLiveResize
{
NSLog(@"viewDidEndLiveResize");
[self maybeJustExpand];
[controller assignImageToCell];
[self sizeToCells];
[self setNeedsDisplay:YES];
[super viewDidEndLiveResize];
}
ここで1つ問題があった。縦横のセルの数が変わる際、それらのセルに対して画像を再設定してあげないといけないのだが、この画像の情報はモデルクラスに持たせてある。そこで、このビュークラスは、コントローラークラスに依頼してその情報を設定してもらうと良さそうだ。

MyMatrixからMyControllerを参照したいので、これはアウトレット(outlet)の出番だ。Interface BuilderでMyMatrixからMyControllerにControl+ドラッグできればいい。次のスクリーンショットみたいに設定したいのだが、これがなかなかうまく行かない。

結局うまく出来なかった。ではどうやるのかというと、nibファイルを見るウィンドウのところの右端にあるリスト表示ボタンを押す。

すると、画面が次のように替わる。今まで謎だったが、アイコン表示ではWindowと表示されていたものは、実は階層構造になっていて、ウィンドウに配置したコントロールにアクセスできる。

ここで、MyMatrixまで辿ったあと、MyMatrixの行をControl+ドラッグし、MyControllerで離すと意図していたoutletの設定が出来た。

これでMyMatrixはメンバ変数controllerでMyControllerのインスタンスにアクセスできるようになった。ここで、(void)MyController:assignImageToCellというメソッドを書いて、モデルとビューをつないでやることで無事リサイズ時の再配置を実装できた。

これで最低限の表示はできるようになった。次は一歩進めてドラッグアンドドロップを実装してみようと思う。ゴールは、ファインダ等外部から画像ファイルをこのウィンドウにドラッグアンドドロップした際、その画像をモデルクラスに追加し、セルに表示することとする。

2007/01/31

セル配置の修正

NSMatrixのセル幅計算がうまく行かなかった問題を調査した。やはり頼るべきはReference manualだ。

まずセル幅計算が微妙に狂う理由は、セルとセルの間にある余白が原因であることが分かった。これは、(NSSize)NSMatrix: intercellSpacing で取得でき、このアプリの場合は(2,2)であった。このピクセル数とセル幅を足すとめでたく64ピクセルになって正しい表示ができる。

前回Interface Builderで見た値を決め打ちで使っていたセルの幅も(NSSize)NSMatrix:cellSizeで取得できる。

2007/01/23

NSMatrixのセルの動的配置

前回書いた通り、NSMatrixへのセルの配置をちゃんとやってみることにした。

まず決め打ちしていた横のセル数は親の横幅を取得し、そこから算出すればよいはずと考えそのように書いてみたところほぼうまく行った。

ここで使っているNSMatrixはNSViewを継承していて親はNSView:superviewで取得できる。NSViewでは、NSView:frameを使えば縦横の大きさが取得できる。

// Get parent view's width.
NSView* parent = [self superview];
NSRect rect = [parent frame];
int cols_per_line = rect.size.width / 62;

最後の62という値はInterface Builderのプロパティとして表示されていたものを決め打ち。本当はセルの横幅を取らなければいけない。

もう1つ前回ちゃんと動いていなかった画像が追加されるセルが間違っていた問題は、画像モデルの配列要素数をNSMatrixのセルインデックスに使えばよいのに、NSMatrixのセルの個数を使っていたという単純な誤りだった。

この2つを直したところほぼ思った通りに動いてくれた。


そして、セルをどんどん追加していくとこんな感じ。


最後のセルが微妙にずれていて、一部隠れているのが気になるところ。どうも決め打ちした62という値が悪いような気がする。62ではなく何らかの幅が足されて実際は64ずつ増えているとすると、ほぼこの現象が説明できる。セル間の余白か何かがあるのだろうか?

今日はここまで。次のゴールは以下の3つ:
  1. ウィンドウをリサイズした時にも並べ直ししなければならないけれど今はそれに反応しない。ウィンドウのリサイズイベントを拾って再配置するコードを追加すれば良いだろう。
  2. セルのサイズをちゃんと取得しよう。NSMatrixにはセルのクラスを取得するメソッドがあるので、それを使えばよいような気がするのだが、セルそれぞれの大きさが違うことはあるのか。
  3. Interface Builderで得られる62ピクセルという幅のセルを配置するとなぜか計算が微妙にずれる問題の原因を突き止めよう。上記の通り、セルの余白が怪しいと思う。

2007/01/15

NSMatrixで新しいセルが表示されない

Cocoaプログラミングの練習で、サムネイルの一覧を表示するような簡単なアプリケーションを作ってみようと思っている。ウィンドウ上にボタンをクリックすると画像サムネイルが1つ追加されるというだけのものが今日のゴールとしてみた。

新しいセルの追加はNSMatrix:renewRos:columnsで出来たが、新しく追加したセルがウィンドウに表示されなかった。しばらくネットを探したり試行錯誤したりした結果、NSMatrix:setToCellsを呼ばないといけないことが分かった。

ネットの情報は断片的なので、今回も結局はAppleのreferenceが解決のきっかけを与えてくれた。やはりまずはリファレンスを当たるのが重要だ。

次のゴール
現在の実装には2つ問題があるのでそれを直そう。
  1. 横一列表示されるサムネイル表示セルの数が決め打ちで4つになっているので、それをウィンドウのサイズとセルのサイズから計算してギリギリまで詰め込む。
  2. 画像を一番後ろのセルに表示させているので、セル表示が1段増えた時、一番右のセルに画像が入ってしまうバグがあるのでこれを直す。
関連ページ
NSMatrixに追加した新しいセルが表示されない場合

2007/01/05

Undefined symbols: .objc_class_name_NSObject

簡単なObjective-Cプログラムを書いてみて、コマンドラインgccコンパイラでコンパイルした時次のようなエラーが出た。

kvant:~/src/objc gaku$ make
gcc -o frac Fraction.m main.m
/usr/bin/ld: Undefined symbols:
.objc_class_name_NSObject
_objc_msgSend
collect2: ld returned 1 exit status
make: *** [frac] Error


これは、NSObjectが入っているライブラリがリンクされていないために起こる。-framework Cocoaをつければ解消する。