itok's Lab

昔の開発ネタを記録として残してます

iPhotoプラグインを作る「フォト蔵編」-2 : iPhotoプラグインの作り方-2 : ひな形作成

さて、次は、サンプルソースを参考に、自前のプラグインのひな形を作ってみましょう。手順として大事なのは、ざっとこんな感じです。

  1. Cocoa Bundleプロジェクトを作成
  2. 拡張子を .iPhotoExporter に変更
  3. .nib ファイルの作成
    1. File owner として ExportPluginProtocol に従う NSObject サブクラスを作成
    2. カスタムViewに ExportPluginBoxProtocol に従う NSBox サブクラスを配置
    3. 配置した NSBox への outlet を接続 ← これが実際に表示されます
  4. 上記 File owner の実装を追加
    1. とりあえずすべての ExportPluginProtocol を空実装
    2. -(id)settingView; で上記 NSBox への outlet を返す
  5. 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プラグインを作る「フォト蔵編」-0 : はじめに

このシリーズでは、国内大手の写真共有サイト「フォト蔵」へ、iPhotoから直接写真を投稿するためのプラグインを作成する手順を紹介していきます。紹介、といっても、僕自身もiPhotoプラグイン作成は初めてのことなので、試行錯誤しながら、と。はてさて、どうなりますことやら。

とりあえず、目標を。

実行環境
  • Mac OSX 10.4, 10.5
  • iPhoto '06, '07, '08
機能
  • iPhotoで選択した写真をフォト蔵の選択したアルバムへアップロード
  • アップロード時に画像サイズの拡大縮小
  • iPhoto上でフォト蔵のアルバム作成
  • iPhotoでつけたタイトル、コメント、キーワードをフォト蔵にも反映

まあ、これだけ出来れば十分でしょうね。

さて、がんばるぞ〜!!!と、そうそう、もちろん、作ったプラグインはフリーで公開しますので、お楽しみに。

Leopard の新機能 NSCell の expansion frame を抑制する

Leopard になると、例えば NSTableView 上のとあるセルにカーソルを持ってくれば、勝手にそのセルの内容が ToolTip みたいなものとして表示されることがあります。これは、NSCell の新機能の一つである expansion frame というやつだそうです。

NSCell ではデフォルトで無効になっているんですが、そのサブクラス(例えば NSTextFieldCell)では有効らしい。

これがまた、すでに自前でその行に対して ToolTip を仕込んでいるような場合に、両方が表示されてしまい結構鬱陶しいわけです(例↓)。っていうか、勝手にそんな(ちょっとは便利?)機能を有効にしないでくださいな、と。

拙作 Ensemble2 の様子。上が expansion frame、下がもともとの ToolTip。
nscell_expansion_frame.png

で、どうすればよいかというと、該当の 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
オブジェクト
CalSenderProcessIDKey
自アプリのプロセスID
CalUserUIDKey
ユーザID
CalInsertedRecordsKey *
追加されたカレンダーのUID配列
CalUpdatedRecordsKey *
更新されたカレンダーのUID配列
CalDeletedRecordsKey *
削除されたカレンダーのUID配列
* 上記3つのうち最低でも1つを含む
CalEvents
ChangedNotification
CalCalendarStore
オブジェクト
CalSenderProcessIDKey
自アプリのプロセスID
CalUserUIDKey
ユーザID
CalInsertedRecordsKey *
追加されたイベントのUID配列
CalUpdatedRecordsKey *
更新されたイベントのUID配列
CalDeletedRecordsKey *
削除されたイベントのUID配列
* 上記3つのうち最低でも1つを含む
CalTasks
ChangedNotification
CalCalendarStore
オブジェクト
CalSenderProcessIDKey
自アプリのプロセスID
CalUserUIDKey
ユーザID
CalInsertedRecordsKey *
追加されたタスクのUID配列
CalUpdatedRecordsKey *
更新されたタスクのUID配列
CalDeletedRecordsKey *
削除されたタスクのUID配列
* 上記3つのうち最低でも1つを含む

他アプリが行った変更通知を自アプリで受けとる

通知名 object userInfo
CalCalendarsChanged
ExternallyNotification
CalCalendarStore
オブジェクト
CalSenderProcessIDKey
他アプリのプロセスID
CalUserUIDKey
ユーザID
CalInsertedRecordsKey *
追加されたカレンダーのUID配列
CalUpdatedRecordsKey *
更新されたカレンダーのUID配列
CalDeletedRecordsKey *
削除されたカレンダーのUID配列
* 上記3つのうち最低でも1つを含む
CalEventsChanged
ExternallyNotification
CalCalendarStore
オブジェクト
CalSenderProcessIDKey
他アプリのプロセスID
CalUserUIDKey
ユーザID
CalInsertedRecordsKey *
追加されたイベントのUID配列
CalUpdatedRecordsKey *
更新されたイベントのUID配列
CalDeletedRecordsKey *
削除されたイベントのUID配列
* 上記3つのうち最低でも1つを含む
CalTasksChanged
ExternallyNotification
CalCalendarStore
オブジェクト
CalSenderProcessIDKey
他アプリのプロセスID
CalUserUIDKey
ユーザID
CalInsertedRecordsKey *
追加されたタスクのUID配列
CalUpdatedRecordsKey *
更新されたタスクのUID配列
CalDeletedRecordsKey *
削除されたタスクのUID配列
* 上記3つのうち最低でも1つを含む

これらは NSNotificationCenter 経由でやり取りされるようです。(NSDistributedNotificationCenter じゃなくていいらしい)