iPhotoプラグインを作る「フォト蔵編」-2 : iPhotoプラグインの作り方-2 : ひな形作成
さて、次は、サンプルソースを参考に、自前のプラグインのひな形を作ってみましょう。手順として大事なのは、ざっとこんな感じです。
- Cocoa Bundleプロジェクトを作成
- 拡張子を .iPhotoExporter に変更
- .nib ファイルの作成
- File owner として ExportPluginProtocol に従う NSObject サブクラスを作成
- カスタムViewに ExportPluginBoxProtocol に従う NSBox サブクラスを配置
- 配置した NSBox への outlet を接続 ← これが実際に表示されます
- 上記 File owner の実装を追加
- とりあえずすべての ExportPluginProtocol を空実装
-(id)settingView;
で上記 NSBox への outlet を返す
- Info.plist にて NSPrincipalClass と NSMainNibFile を指定
ざっくりと、大事なのは以上です。あとはサンプルソースを見ながらやりくりします。
これでビルドして、先ほどと同じようにプラグインを /Applications/iPhoto.app/Contents/PlugIns 以下に設置すると、iPhotoで確認できます。
iPhotoプラグインを作る「フォト蔵編」-1 : iPhotoプラグインの作り方-1 : サンプルソースを触る
まずは、iPhotoプラグインの作り方を調べます。。。と、Appleのサイトを見ても何にも載ってませんねえ。。。
いろいろ、徘徊した結果、わかったこと。iPhotoプラグインの作成なんて公式にサポートされているものじゃなさそう。いわば、みなさんがハックした結果ってこととでもいうんですかね。
そんなわけで、参考にしたのは、以前のエントリーにもあった二つの英語サイト。特に後者のほうがサンプルソースもあって読みやすいです。
で、こちらから、サンプルを落としつつ、その手順に従って、プラグインのひな形を作っていきます。
サンプルを動かす
とりあえず、サンプルプロジェクトを動かしてみましょう。
ビルドして、ExampleExport.iPhotoExpoter を iPhoto.app/Contents/PlugIns 以下にコピーします。
で、iPhotoを起動時、「書き出し」メニューを選べば、サンプルのGUIがみえるはず、、、だったんですが、どうもうまくいってません。コンソールログを見ると
Not a valid plugin: /Applications/iPhoto.app/Contents/PlugIns/
ExampleExport.iPhotoExpoter
ってでてます。これって、もしかしたら、サンプルが古いから?(テスト環境は10.5 + iPhoto'08)と思い、いったん中断。先に iPhoto の現状を確認(つまりクラスダンプ)することにしましょう。
class dumpする
これぞ、まさに第一歩。Cocoaの実行バイナリからクラス構造をダンプします。class-dump
コマンドの実行ファイルは、こちらのサイトからもらってきて、適当にパスの通った場所に設置。
で、こんな感じで実行。
# class-dump -C Export /Applications/iPhoto.app/Contents/MacOS/iPhoto > iPhoto.class
これでiPhoto内の"Export"っていうシンボルを持つクラス(プロトコル含む)のインターフェースがテキストファイルに出力されます。なんて便利。初めて使ったんですけれど、こうやってみなさんは裏口を開けていくのね、と。
ダンプしたインターフェースをヘッダへ
ダンプしたテキストファイルから必要なものをインクルードヘッダとして使います。主にはこんな感じで。
- ExportMgr → ExportMgr.h : iPhoto書き出しに使用(選択ファイルの情報などを取得)
- ExportPluginProtocol → ExportPluginProtocol.h : プラグインプロトコルそのもの(これを実装すればよい)
- ExportPluginBoxProtocol → ExportPluginBoxProtocol.h : NSBoxのサブクラスで使用
- ExportImageProtocol → ExportImageProtocol.h
ヘッダを比較
ここで、先ほどのサンプルヘッダと、ダンプして作ったヘッダを比較してみました。確かに過不足ありましたね。今回の場合は、-(BOOL)handlesMovieFiles;
が増えているようです。
なので、早速サンプルソースに空実装を追加して、再度プラグインとして組み込んでみます。
すると、確かに「書き出し」ウィンドウ内に新しいタブがあらわれてきました。なるほどなるほど、こうやって読み込まれていくのね、というわけで、「書き出し」ボタンを押してみます。。。が、それはそれで、なにやらメソッドが存在しないとかいうエラーをコンソールにはいておしまい。これまたソースを比較してみると、ExportMgr のメソッドがずいぶんと変わっているみたいです。
まあ、これはこれで、プラグイン読み込みとデータ書き出しの雰囲気がなんとなくわかったので、よしとしましょう。次は実際に自分でプラグインを書いてみます。
iPhotoプラグインメモ
Leopard の新機能 NSCell の expansion frame を抑制する
Leopard になると、例えば NSTableView 上のとあるセルにカーソルを持ってくれば、勝手にそのセルの内容が ToolTip みたいなものとして表示されることがあります。これは、NSCell の新機能の一つである expansion frame というやつだそうです。
NSCell ではデフォルトで無効になっているんですが、そのサブクラス(例えば NSTextFieldCell)では有効らしい。
これがまた、すでに自前でその行に対して ToolTip を仕込んでいるような場合に、両方が表示されてしまい結構鬱陶しいわけです(例↓)。っていうか、勝手にそんな(ちょっとは便利?)機能を有効にしないでくださいな、と。
拙作 Ensemble2 の様子。上が expansion frame、下がもともとの ToolTip。
で、どうすればよいかというと、該当の NSCell のサブクラスを作って、
// NSCell
-(NSRect) expansionFrameWithFrame:(NSRect)cellFrame inView:(NSView*)view
の結果として NSZeroRect を返してあげればOKです。
CalendarStoreを使う-3「終日予定は?」
繰り返し予定は無事に取り扱えることがわかりました。では、終日予定は、といいますと、、、
そもそも、イベントクラスである CalEvent のプロパティとして isAllDay というものがありまして、それがまさに終日予定かどうかを示しているわけですが、ここで問題なのは、数日にまたがる終日予定がどのように取り扱われているのか?ということ。
これは、調べてみれば一目瞭然でして「複数日にまたがる終日イベントは一つのイベントとして取り扱われている」ということです。
ただし、検索結果としてはマッチします。つまり、11/17スタートの2日間の終日イベントが存在する場合は、それ自体は1つのイベントオブジェクトとして存在しているのですが、11/17指定の検索でも11/18指定の検索でも取り出すことが出来るようです。まあ、当たり前といえば、当たり前ですけれど。
CalendarStoreを使う-4「データ変更を通知する」
iCalのデータっていうのは当然のことながら、同時にいろんなアプリケーションからアクセスされます。なので、データを変更した場合にはそれを他のアプリケーションに通知しなければなりませんし、また他のアプリが変更した場合もその通知を受けとる必要が出てくることでしょう。
自アプリが行った変更を他アプリへ通知する
通知名 | object | userInfo |
---|---|---|
CalCalendars ChangedNotification |
CalCalendarStore オブジェクト |
|
CalEvents ChangedNotification |
CalCalendarStore オブジェクト |
|
CalTasks ChangedNotification |
CalCalendarStore オブジェクト |
|
他アプリが行った変更通知を自アプリで受けとる
通知名 | object | userInfo |
---|---|---|
CalCalendarsChanged ExternallyNotification |
CalCalendarStore オブジェクト |
|
CalEventsChanged ExternallyNotification |
CalCalendarStore オブジェクト |
|
CalTasksChanged ExternallyNotification |
CalCalendarStore オブジェクト |
|
これらは NSNotificationCenter 経由でやり取りされるようです。(NSDistributedNotificationCenter じゃなくていいらしい)