2011年6月30日木曜日

Cocoa ファイルの非同期コピー(4)

というわけでFileCopyManagerを作成中。

こんなダイアログを表示しようと考えている。


コピーするファイルのサイズの合計、コピーしているファイルのサイズ、それぞれの進行状況、コピーしているファイル名及びアイコン、などなどを表示する。

cancelボタンも作ってみた。ということは、「複数コピーしているファイルのうちのひとつだけキャンセル」、「コピーそのもののキャンセル」といった処理も必要となる。

コピーをキャンセルということはコピー中のファイルも削除しないといけないわけで、なんとなくできるかどうか不安になるな。(^^;)

本日はinitメソッドを考えて、書いて、それでほぼ終了。
素人なので初期化ひとつとってもすらすらとはいかない。
今回のFileCopyManager、結局初期化はこんな風にした。

-(id)initWithDistURL:(NSURL*)dURL copyFileArray:(NSArray*)fArray{
    self=[super init];
    if(self){
        [NSBundle loadNibNamed:@"FileCopyDialog" owner:self];
        NSFileManager* fileManager=[NSFileManager defaultManager];
        BOOL isDir, valid;
        valid=[fileManager fileExistsAtPath:[dURL path] isDirectory:&isDir];
        if (!valid || !isDir) {
            return nil;
        }
        if([fArray count]<=0){
            return nil;
        }
        [self setDistURL:dURL];
        [self setFileArray:fArray];        
    }
    return self;
}

filemanagerを生成する必要はなかったけれど、最初はsourceURLも初期化の引数にしていたので生成している。結局、sourceURLはコピー元のフルパスがわかれば必要ないので割愛したが。

コピー先のURLが無効ならFileCopyManager自体を生成しないようにしているが、これって今考えたんだがあまりよろしくない。無効ならそのパスを作る、が正しい気がする。(ドラッグアンドドロップでのコピー操作をメインに考えているため、無効なパスへのコピー操作はそもそも発生しない、という事情もある)

distURLとfileArrayはpropatyでそれぞれ

@property (assign) NSURL*distURL;
@property (retain) NSArray* fileArray;

と宣言しているため、セッタゲッタが自動で作られるから、FileCopyManagerのオブジェクトを生成したあと、オブジェクトの外部からsetDistURLとする方法もあるわけで、この辺の使い分け方がまだよくわかっていない。

今回はコピー先やコピーファイル群が無効ならオブジェクトを生成しないでnilを返す、という方向でいいような気がしているが。

FileCopyManagerのオブジェクトがnilでないなら、[fileCopyManager startFileCopy]みたいなメソッドを呼び出せば呼び出し側の処理は終了、というふうにしようと考えている。

しかし残念ながら、明日からはいよいよ学期末モードに突入の予定。家で通知表作成の作業に入る。どうしてもExcelを使わないと済まないので、Winを起動せざるをえない。あまりプログラミングに時間がとれないなあ。悲しい。

2011年6月29日水曜日

Cocoa ファイルの非同期コピー(3)

FSCopyObjectAsyncの引数で指定したcallbackに、

FSFileOperationStage stage

というオペレーションの状態を表す変数が渡されてくるので、これがkFSOperationStageCompleteだったらダイアログを閉じるようにすればいい、と本日わかった。

現在のところ、処理すべきファイルのURLをNSArrayに入れて、そこからファイルを一つずつ取り出してコピーする、という段取りで処理している。これだと、1つのファイルを処理するたびにダイアログが現れては消える。

ま、それでも素人のプログラミングだからいいんだろうけど、ここはやはり「複数のファイルが指定されたら、相対のコピーサイズの進捗状況と単体のファイルの進捗状況」を両方表示できるようにしたい。

そんなわけでFileCopyManagerというクラスを作り始める。
ファイルのArrayを受け取って、コピーそのものとダイアログで進行状況を表示する、という働きをする予定。

しかしのっけから.nibファイルを動的に読み込む、というところでつまずいて本日は終了。(^^;)
どーなることやら。

しかし考えてみれば非同期コピーが曲がりなりにも実現できたので、かなりの前進だな。喜ぼう。

追記:
nibの動的な読み込みもやってみればどうということはなかった。NSViewControllerでinitWithNibName;するのを、nib読み込みだけ自前でやる、という感じ。

1,ダイアログのxib内のFile's Ownerを予め動的に読み込む予定のクラスにしておく
2,読み込み元クラスでIBOutletを作って、xib内でつないでおく
3、読み込み元クラスのinitあたりで、[NSBundle loadNibNamed:]でnibを読み込む

読み込んだ時点でIBOutletも有効になるようだ。案ずるより産むが易し。Cocoaはこういうことが多いかも。

2011年6月28日火曜日

Cocoa ファイルの非同期コピー(2)

ふむ、ちゃんとできるもんだ、非同期コピー。

昨日の参照サイトに書いてあったとおりにやってみたところ、見事に非同期コピーができた。
問題は、NSProgressIndicatorを置いたモードレスダイアログ。

表示して、NSProgressIndicatorをファイルのコピー状況に合わせて変化させるところまではいいとして、ファイルのコピーが終わってダイアログを閉じるところがわからない。(^^;)

callbackでコピーしたサイズがコピー元のファイルサイズを越えたところでダイアログを閉じるのかな。callbackはCの関数になるので、AppKitにアクセスするのが若干面倒なような。

もう少し研究。

2011年6月27日月曜日

Cocoa QLPreviewPanel失敗

QLPreViewPanelのコントローラーを今までと違うクラスにしようとして気軽に作業を始めて・・ハマってしまった。未だ解決できず。

順調に表示されていたPreviewが表示できなくなってストレスがたまる。さてどうしたものか。

このところやることが散漫で、十分あれこれ試すという姿勢が薄い。もともと薄いけど。もうすこし一つずつ着実に調べていくようにしよう。

追記:
QLPreviewPanelを表示する直前に

[[self window] makeFirstResponder:self];

とやると(ちなみにClassはNSViewのサブクラス)きちんと表示される。でもそんなんでいいのか?という疑問が・・・。結果オーライでいいのかなあ。

Cocoa ファイルの非同期コピー

そうか、スレッドとか使わないでも「ファイルの非同期コピー」をすればよかったのか。

「非同期コピー」という言葉も概念も知らなかったのが敗因。ま、知ることができてよかった。

日本語のの情報はこちらで。

Objective-Cで非同期ファイルコピー

サンプルプロジェクト付きの英語情報はこちらがいいかと。
Cocoa Tutorial: File Copy With Progress Indicator

特に後者のサンプルは作業進行度を表すインジケータの表示の仕方まで示した親切設計。

そろそろ在宅仕事が忙しくなるので、非同期コピーに果たして手をつけることができるのか・・。

2011年6月26日日曜日

Cocoa ファイルの種類

漫然とした1日。家族の用事やらなにやらであっという間に過ぎる。

プログラミングに対する集中力もなく、ちょっと調べては自分のプロジェクトを眺め、少し考えてはまたちょっと調べ、という感じでこれといって成果のない1日となった。ま、そんな日もあるさ。

IKImageBrowserViewでフォルダアイコンをダブルクリックしたらフォルダ内へ移動、はすぐできあがった。そりゃそうだ、NSTableViewでさんざんやったんだから。

delegateの

-(void)imageBrowser:(IKImageBrowserView*)aBrowser cellWasDoubleClickedAtIndex:(NSUInteger)index

を定義してダブルクリックの処理をしたので、フォルダでない場合は自分で書いてやらないと何も起こらない。とりあえず

[[NSWorkspace sharedWorkspace]openFile:[item fullPath]];

みたいな感じでファイルオープンだけするようにして、ここから少し考えた。ここの処理で、ファイルの種類によってあれこれやるのもおもしろいかな、と。単純にアプリケーションなら、「開く」か「実行」か、画像なら開くアプリの選択、とか。

IKImageBrowserViewで画像を自前で開けば、WindowsのVixみたいなアプリにもできるな、とか。

どこまで突き進むかわからないけれど、まずはクリックしたファイルの種類を判定するところから。

NSString* uti=[[NSWorkspace sharedWorkspace] typeOfFile:[item fullPath] error:nil];
UTIってなによ、な素人なわけだが、とにかくこれでファイルのUTIが取得できた。[item fullPath]はファイルのURLを収めたモデルクラス(のメソッド)。

NSString* loca=[[NSWorkspace sharedWorkspace] localizedDescriptionForType:uti];
これでいわゆる「ファイルの種類」を表す文字列が取得できた。ついでにEseのTableViewで表示できるようにしてみる。


えーと日本語で表示されないのは、アプリをローカライズしてないからかな?

それで以下のメソッドで「このタイプのファイル?」と問い合わせればBOOLで答えてもらえるみたい。

[[NSWorkspace sharedWorkspace] type:uti conformsToType:@"public.image"]);

上の例は画像かどうか問い合せてみた。NSLogを取ってみると、画像ならPNGでもJpegでもちゃんと「YES」を返してくれる模様。

そんなんでファイルの種類を得ることができたので、今度は

1,ファイルの種類を判定する専用のクラス
2,ファイルの種類に基づいてOpenする動作を割り当てるクラス

を細分化して書いてみようかなー、と考えてところで本日は終了。
QLPreviewPanelをちゃんと開くための作業をしようと思っていたけれど、なかなかうまく進まないものだ。

2011年6月25日土曜日

書店の風景

久しぶりに書店に行った。

木下誠氏のiOS関係の新刊書籍があれば買いたいなあ、と思っていたが、残念ながら並んでいなかった。

iOS開発におけるパターンによるオートマティズム
木下 誠
ビー・エヌ・エヌ新社
売り上げランキング: 56686

これですな。む・・今日発売?それなら並んでいるはずがないな、北海道なら。(2日遅れでありますな、なんでも)

で、書店のパソコン関連の棚を眺めてきたら・・・平台に並べているうち、3分の1がiPad関係。3分の1がスマートフォン関係。最後の3分の1が、なんとFaceBook関係の解説書。

売れるのかな、FaceBook関連。「ビジネスに活かす云々」みたいなタイトルが多かった気がする。
今だに「すぐできる〜」とかいう感じの入門書が多いものの、ネットがらみの書籍がずいぶん幅をきかせている感じがする。田舎だとプログラミング関係の本があまり手に入らなくて困る。この手の本ほど、実際に手にとってざっと中身を確かめてから買わないと危険なんだが。

Cocoa IKImageBrowserView(2)バインディングで表示する

なんのことない、すべて
こたつつきみかんさん

のこの2つのアーティクルのまんま作業したら見事に表示できた。

IKImageBrowserViewの使い方

IKImageBrowserViewの使い方 – 2

xcatsan師匠の文章と同じくらい、参考になるなあこのblogは。

以前、NSTreeControllerの実験用に作ったプロジェクトに、IKImageBrowserViewをはめこむ。で、上の2つのアーティクルどおりにバインディングでディレクトリ内のファイルを表示してみたらうまくいった。

で、以下の2点のみ自分で書き換えた。
いずれもIKImageBrowserViewItemプロトコル。
- (NSString *)imageRepresentationType
{
 return IKImageBrowserQuickLookPathRepresentationType;
}

- (id)imageRepresentation
{
 return [self fileURL];
}
こたつつきみかんさんでは

・IKImageBrowserNSImageRepresentationType

を指定してNSImageを渡すように書かれていたのを、IKImageBrowserQuickLookPathRepresentationTypeでNSURLを渡すように変更したところ、ほぼFinderライクな見た目となった。


速度も十分。以前、自分でカスタムCellを作ったときは/usr/bin内を表示させると激おそだったが、もちろんIKImageBrowserViewならそんなことはない。

いくつか不満もあって、たとえば表示用のIKImageBrowserCellのカスタマイズが簡単ではない(ように思える)こと、とか。ファイル名が長い場合、Finderなら2行に折り返しているけど、そういうカスタマイズはできないようだ、サブクラス化しても。

しかしとりあえずこれで「プレビューView」のめどはたった感じ。明日はdelegateメソッドを作ってフォルダアイコンをダブルクリックした場合は、という処理を付け加えよう。

2011年6月24日金曜日

OSXアップデート

10.6.8が来てるってことなのでアップデートしてみた。何が変わったのかは全然わかんないけど。

次はいよいよLion、だとか。

UIがけっこう変更されるということなので楽しみではある。

Cocoa IKImageBrowserView

QLPreviewPanelをTableViewのControllerで操作しているのはいくらなんでもまずいだろう(Tabごとにインスタンスが生成される)ということで、どのクラスで担当するか考える。

考えながら、そろそろTabを表示するところから全部書き直すかな、と思い始めた。

現在書いているプロジェクトはまだまだ実験用プロジェクトなわけだが、ある程度安定して動いているクラスを順次本番プロジェクトに書き写していく、というのもモチベーション維持のために必要かも。

飽きっぽいのでこのままいくと途中で放り出してしまう可能性もある。(^^;)

現在splitviewの右側のペインはTableViewで表示しているが、これをアイコン表示のViewと切り替えられるようにしたいし。

そのためのViewはひょっとしてIKImageBrowserViewがいいのかな、ということでこのクラスについて調べ始めた。以前TreeController調査用に作ったプロジェクトに組み込んで、うまく動くか試してみようと考えている。

そろそろ学期末の声が聞こえ始めて、そうそう遊びに時間をかけられなくなってくるなあ。早いところ仕事を終わらせてお楽しみに時間をかけられるようがんばろう。

2011年6月23日木曜日

追悼・元麻布春男氏

昨日急逝されたということで黙祷。

思えばずいぶん「スーパーアスキー」の記事を熱心に読んだ。

ショックだったのは、たしか氏は私と同世代のはず。出先で倒れてそのまま搬送先の病院で亡くなったようだ。明日は我が身。色即是空。諸行無常。

Cocoa QLPreviewPanel(2)

おそらく、日本語による10.6仕様QuickLook・QLPreviewPanelの解説、

Controllerと たわむれる11

とデベロッパドキュメントと、サンプルプロジェクト「QuickLookDownloader」のソースを読みながらQLPreviewPanelの勉強。

うーん、QLPreviewPanelControllerのプロトコルはNSViewControllerで定義してもだめなのか。たしかにデベロッパドキュメントでは「WindowControllerで実装するのがいいよ」みたいなことが書いてある。
QLPreviewPanelもまたシングルトンなので、Tabで複数表示されるTableViewにdatasourceやdelegateをあれこれ書いても仕方がない。

うーん、どうしよう、というところで本日は終了。

(追記)
masakihの日記
Controllerと たわむれる12

このアーティクルを読みながら、NSViewControllerをレスポンダチェーンに割り込ませてみたところ、見事QLPreviewPanelが意図通りに表示された。


やはりQLPreviewPanelControllerのプロトコルの問題だったか。

けっこうすっきりしたけど、ソースはすでにぐちゃぐちゃである。なんとかしたいなあ。

2011年6月22日水曜日

口唇ヘルペス

口唇ヘルペスという病気を発症した。といっても、ちょっとぴりぴりと痛みを感じる吹き出物が唇のそばにできただけだが、ネットでざっと調べたら抗生剤をのんだり塗り薬を塗ったりしないとけっこうひどいことになるらしい。

ということで病院へ。

ドクターからもらったパンフレットには「幼児期に感染した細菌が、体調が悪くなると引き起こす症状」とある。ふむふむ。ということは、これまでどんなに体調が悪くても発症しなかったけれど、50歳を間近にしてはげしく体力が落ちたためついに発症するようになった、と解釈するしかない。

ふー、いろいろあるもんだなあ。

本日は教室で教壇から脚を踏み外した拍子にけっこう激しくころんで膝を打って痛い思いもしたし、そういう日、ということなんだろうなあ。年を感じる日。

膝はけっこう具合が悪くて、といっても立ったり歩いたり、がんばれば走ったりもできるし日常生活に全く支障はないけれど、膝を曲げると鋭い痛みが走る。明日の朝がちょっと心配。

Cocoa QLPreviewPanel

気をとり直してQuickLook関連を調べ始める。

最初は選択したファイルをQLPreviewPanelで表示できればいい、と思っているわけだが、ドキュメントを読むとどうも「PreviewView」ともいうべき、「アイコンの代わりにPreviewがアイコン大に表示されるView」も作る方法がQuickLook関連でわかるかもしれない。わくわく。

とりあえずQLPreviewPanel。

先達のみなさまが親切なソースコード付きの解説をネットのあちこちに残してくれている。例えばいつものxcatsan師匠

Quick Look APIs(その2)

こんな感じで分かりやすく解説してくれている。しかし・・・OSX10.6以降、QuickLookの仕組みが大きく変わったらしく、解説されている方法は全然通用しない。

[[QLPreviewPanel sharedPreviewPanel] setURLs:list
currentIndex:0
preservingDisplayState:YES];

というメソッドがない、とすぐに弾かれてこれに気づいた。
デベロッパドキュメントの中を漁ると、まずはサンプルプロジェクトの「QuickLookDownloader」を調べるといいらしいことがわかる。

そうすると、
・Quick Look panel data source
・Quick Look panel delegate
のメソッドを定義した上、QLPreviewPanelに表示するデータクラスをQLPreviewItemプロトコル準拠にしないといけないようだ。

で、本日はここまで。

QLPreviewPanelを表示することはできたが、おそらくdelegateのpreviewPanel:sourceFrameOnScreenForPreviewItem:とpreviewPanel:transitionImageForPreviewItem:contentRect:を定義していないため、肝心のプレビューが表示されていないと思われる。

おもしろいのでさらに調べていこう。

2011年6月21日火曜日

Cocoa FrameWork

ファイルのコピーを別スレッド、あるいはGCDでやって見る方法を調べ始めて・・・こりゃ現在の知識と技術では全然理解出来ない、と判断した。

ちょいと調べて、というのは、究極の拡張版FinderというべきPath Finderの作者さんが公開しているライブラリ、というかFrameWork

Path Finder SDK / Open Source

をダウンロードして中身を覗いてみたり、

The Omni Group

がやはりオープンソースで提供している

omnigroup / OmniGroup

を覗いてみたりしたわけで、ファイルの操作をするだけでこれだけのclassが必要なのか、とがっくりする。Cocoaは比較的読みやすいソースコードになるのでここはじっくり勉強させてもらうことにして、当面は「高度なファイル操作」の実装はあきらめた。もっと勉強が必要だ。Windowsの「まめFile」みたいなインターフェイスでファイル操作をしたい、という要望は個人的に強いので、「すぐに作る」ことはあきらめるが作ることをあきらめたわけではない、と自分に言い聞かせてみる。あきらめたらそこで試合終了ですよ。

もともと.appディレクトリの中のリソースを手軽にプレビューするためにつくり始めたファイルマネージャだから、今後はそっち方面の研究をしていくことにしよう。

手軽にファイル操作をさせてもらえるFrameWorkってないのかな、とググったりして、世の中にはたくさんのCocoa用FrameWorkが存在することを知る。

ObjectLibrary

個人のページではここ

Uli's Web Site

が印象に残った。

2011年6月20日月曜日

Cocoa ファイルのコピー

大きなファイルのコピーを試す。
といっても手元にある大型ファイルといえばXcode3のインストールDVDイメージくらいだ。これは4Gあるのでとてもではないが繰り返しコピーして試す気にならない。膨大な時間がかかるに決まっている。

そこでubuntuの日本語RemixのCDイメージをダウンロード(^^;)。ごめんなさいこんなことに使って。およそ680Mなので、まあ耐えられる大きさだろうと予測する。

コピーしてみたところ、やはりファイルコピー用のSheetを表示したまま、長い時間待たされた。これはどう考えても、作業進捗状況をしめすインジケータ付きのダイアログを表示するのがマナーというものだな。

もちろんコピー中は他の作業ができなくなるわけなので、いよいよGrand Central Dispatch(GCD)について本腰を入れて調べる必要がある。本当に使いこなせるのか、激しく自信がないがやってみるしかないだろう。

ついでにFinderでファイルのコピーをしてみて、ダイアログの動作を観察。・・・どうやったら「ファイルをコピーしているスピード」とか「残り時間」を計算できるんだ?という疑問ばかりどんどんふくらむ。プログラミングをすればするほど、勉強することが山ほど増えていく不思議。

今日から「PTA広報づくり」という帰宅後の仕事が発生したので、さらに時間がないのが悩みだな。

2011年6月19日日曜日

Cocoa FSEvent と SCEvents(3)

アプリ起動時にひとつだけ生成されるTab管理のControllerでSCEventsのインスタンスを生成する。
Tabにはこんなふうに


表示しているディレクトリのフルパスを保存してあるので、それをNSMUtableArrayにしてSCEventsに渡し、

[events startWatchingPaths:tabTitles];

てな具合でファイルシステム監視の開始。これで無事動いている。
試しにFinder上でファイル操作をしてみたら、自作アプリでもちゃんとファイルの移動などを感知できていた。便利じゃのお。

ただ、Tabを削除してみたら見事にアプリが落ちた。(^^;)Tabを削除する前に

if([events isWatchingPaths]) [events stopWatchingPaths];

をしないといけなかった。
アプリが落ちたときに気づいたのだが、監視するパスの数だけスレッドが分かれるらしい。うーむ、なんだかすごいことになるな。

さて、次は・・・いよいよ大きなサイズのファイルのコピーかな。

2011年6月18日土曜日

アンダー・ザ・ドーム読了

アンダー・ザ・ドーム 下
スティーヴン・キング
文藝春秋
売り上げランキング: 8607

朝から晩までずっとこの本を読んで、10時半近くに読了。めずらしくプログラミングをひとつもしない日になったけれど、それでも満足。

物語のスケールだけからいうとかなり小さい。なんせ小さな町の中だけで終始する。当然敵役も「この程度でどうよ?」と疑問を感じながら読んだけど・・・ラストは納得。いい話だった。

時間的・空間的スケールという点で「スタンド」は偉大な作品だった。自分としては「スタンド」がキングのベスト1だ。

その後は・・・「シャイニング」かなあ。怖さという点では「ペットセマタリー」がかなり上位。
「アンダー・ザ・ドーム」は・・・どうだろう、「骨の袋」よりは下、「トミーノッカーズ」よりかなり上、というところ。

でも巻置く能わざる、という言葉にはぴったり。えぐい展開にページをめくるスピードが加速するというもんですよ。

2011年6月17日金曜日

Cocoa FSEvent と SCEvents(2)

SCEventsをさっそく使ってみる。

NSTableViewのControllerでSCEventsのインスタンスを保持して、自身が表示しているpathを監視させるようにしてみたところ・・・がーん、よく見たらSCEventsってシングルトンだった。いくら生成しても同じsharedPathWatcherを返しているから、複数のインスタンスで複数のpathを監視、は無理だった。

FSEventを使うのにしても同じらしく、アプリでひとつインスタンスを保持して、監視するpathをNSMutableArrayで渡すしかないらしい。

それならやっぱりSCEvents使わせてもらうのが楽かも。

というわけで、どのクラスでSCEventsのインスタンスを保持するか考えているうちに夜が更けてきて読書タイム。

1学期中は、このお休みがおそらく心を落ち着いてプログラミングにむかえる最後の土日となりそうなのでがんばろう。

2011年6月16日木曜日

アンダー・ザ・ドーム下巻突入

アンダー・ザ・ドーム 下
スティーヴン・キング
文藝春秋
売り上げランキング: 4138

はい、下巻にやっとたどり着いた。なんとか今度の土日で読みきってしまわないと、繁忙期に入ったら読み切れないで返却、なんて事態になってしまうと困る。

しかし、ストーリーテラーとしてのキングってやっぱりすごい。これに比べると(比べるのは酷だけど)「ダイナミックフィギュア」の筋立ては浪花節に思えてくる。

すごく不幸を予感させながら、あっと驚く酷薄さで予想を上回る不幸な事態を平然と書きこむ、そのあざとさに脱帽中。キングはおもしろいなあ。

Cocoa FSEvent と SCEvents

「Cocoa ファイルシステム監視」くらいでググると、やっぱりxcatsan師匠のこのアーティクルに行き着いた。師匠、扱う範囲の幅が広い。
FSEvent - フォルダを監視する

そういえば以前、このアーティクルに目を通したおぼえがあって、へー、こんな便利な仕組みのあるのね、と考えたはず。

で、さらにFSEventでググってたどり着いたのがここ。

FSEvents Objective-C Wrapper
CベースのFSEventにObj-Cのラッパーをかぶせたクラスらしい。ありがたやー。

さっそくサンプルプロジェクトを開いてみる。
基本的にこんな感じでファイルシステムの監視ができるようだ。

- (void)setupEventListener
{
    SCEvents *events = [SCEvents sharedPathWatcher];
    
    [events setDelegate:self];
    
    NSMutableArray *paths = [NSMutableArray arrayWithObject:NSHomeDirectory()];
    NSMutableArray *excludePaths = [NSMutableArray arrayWithObject:[NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"]];
    
 // Set the paths to be excluded
 [events setExcludedPaths:excludePaths];
    
 
 // Start receiving events
 [events startWatchingPaths:paths];
    
 // Display a description of the stream
 NSLog(@"%@", [events streamDescription]); 
}

/**
 * This is the only method to be implemented to conform to the SCEventListenerProtocol.
 * As this is only an example the event received is simply printed to the console.
 */
- (void)pathWatcher:(SCEvents *)pathWatcher eventOccurred:(SCEvent *)event
{
    NSLog(@"%@", [event description]);
}

SCEventを扱う場合はというプロトコルに準拠する必要があり、それには- (void)pathWatcher:(SCEvents *)pathWatcher eventOccurred:(SCEvent *)eventを書くだけでいいらしい。

NSMutableArray *paths = [NSMutableArray arrayWithObject:NSHomeDirectory()];
[events startWatchingPaths:paths];

基本、この2行でいいようだ。ふーむ、生のFSEvent使うより、素人にはこっちのほうがいいかも。

ただファイルマネージャなので頻繁に表示するディレクトリを切り替えることが想定される。ディレクトリが変わったら監視対象も変えないといけないが、そういう乱暴な用途に向くかどうか、明日以降やってみよう。

2011年6月15日水曜日

Cocoa ゴミ箱へのDrop

Dragされたファイルがゴミ箱にDropされたかどうかは、

-(void)draggedImage:(NSImage *)image endedAt:(NSPoint)screenPoint operation:(NSDragOperation)operation{

を書けばわかる、ということがわかった。本当はControllerに書きたいところだけどサブクラス化したNSTableViewに書かないといけない。プロトコルの関係で。

このメソッドの中で

-(void)draggedImage:(NSImage *)image endedAt:(NSPoint)screenPoint operation:(NSDragOperation)operation{
   if(operation==NSDragOperationDelete){
      //何らかの処理
   }

}

と書けばいい。ただしNSDragOperationしか渡ってこないので、
NSPasteboard* pboard=[NSPasteboard pasteboardWithName:NSDragPboard];
でPasteBoardを取得して、そこから得たURLを操作する必要がある。

ふー、っと。これでURLをrecycleURLs:で処理すれば無事ゴミ箱へのDropができるわけだが、今のところの問題点は2つ。

1,ゴミ箱に入れたファイルを戻せない。NSUndoManagerとやりとりする必要がある、のか?

2,ゴミ箱に入れたファイルは当然TableViewからも消えるようにしないといけない。いちいちNSURLをデータ用のArryaから検索するより、「ファイルシステムの変更を通知してもらう」形にしたほうが汎用性が高い。NSWorkspaceDidPerformFileOperationNotificationを使う、あるいはNSWorkSpace noteFileSystemChanged:を使うのか・・・

というわけで以降は明日。

2011年6月14日火曜日

Cocoa NSTableView ドラッグアンドドロップ(8)

現在作成中のファイルマネージャ実験プロジェクト、EseのTableViewからゴミ箱へファイルをドロップしてみた。

・・・なにも起きない。がっくり。

このあたりを調べ始めたら、いろいろ実装出来ていない部分がみつかったのでひとつずつつぶしていく。

TableViewにファイルがDropされた後の処理をちゃんと書いてなかったので、ファイルのコピーはちゃんとされているんだけど、当該TableViewが更新されていなかったのを修正。ファイルのコピーを別のClassの仕事にしてしまった関係でNotificationで通知する仕様にした。

すなわち、
1,Dropされた・・・「コピーが始まるみたいNotification」発行
2,「コピーが始まるみたいNotification」をうけてFileOperation担当クラスが「本当にコピーする?」Sheetを表示
3,「本当に実行」ボタンが押されたら、「本当にコピーされるぞNotification」発行
4,「本当にコピーされるぞNotification」にはファイルのURLがuserinfoとして添付されるので、TableViewはそのURLと自分の現在のURLを比べる
5,どうやら自分だったら、ファイルを収めているArrayにコピーされたファイルをaddObjectする

なんだか迂遠だなあ。

ただ、ここまで書いたコードでFinderからのDropもちゃんと動作しているので一応よし、としておく。

しかしゴミ箱へのDrag&Dropはまだわからない。
「アンダー・ザ・ドーム」は無茶苦茶おもしろいし、仕事はそろそろ学期末モードが近づいているし、忙しくなんだか楽しい。

2011年6月13日月曜日

Cocoa NSTableView ファイルをゴミ箱へ

右クリックからコンテキストメニューを表示して、NSTableViewで表示中のファイルをゴミ箱へ移動できるようにする。

メニューはとりあえず.xibに置いて、NSTableViewのmenuとつないだ。サブクラス化したNSTableViewの-(NSMenu*)menuForEvent:(NSEvent *)eventで動的に生成する方法もやってみたが、これだとクリックされた行が「半選択状態」にならないため(自前で処理する必要があるんでしょうな)却下。

「半選択状態」とはすなわち

Finderでいう上の画像のような状態。クリックによって選択された行がいくつあっても、右クリックした行が四角で囲まれたハイライト状態になる。この状態で「ゴミ箱へ移動する」を選択したら、当然「半選択状態」の行に対する処理になる。

自分でこれを実装しようとして、はたと考え込んでしまった。「半選択状態」ってSelectionでもhighlightでもないようなんだけど・・・。

NSTableViewで選択されている行は、[arrayController selectionIndexes]とか[tableView selectedRowIndexes]とかでNSIndexSetで取得できる。でも半選択状態の行はこのindexには含まれていない。

デベロッパドキュメントを読み返すと、[tableView clickedRow]っていうメソッドがちゃんとあった。これが「現在クリックされた行」のindexを返す。というわけで、このindexが選択行のNSIndexSetに含まれていなければ、右クリックされた「半選択状態」だとわかるので、以下のように書いてみた。

- (IBAction)moveToTrash:(id)sender {
    NSUInteger clickedIndex=[tableView clickedRow];
    NSIndexSet *indexes=nil;
    if([[arrayController selectionIndexes] containsIndex:clickedIndex]){
        indexes=[arrayController selectionIndexes];
    }
    else{
        indexes=[NSIndexSet indexSetWithIndex:clickedIndex];
    }
    NSArray* selectFile=[fileArray objectsAtIndexes:indexes];
    NSMutableArray* selectURL=[NSMutableArray arrayWithCapacity:[selectFile count]];
    for(DirectoryFile* item in selectFile){
        [selectURL addObject:[item fileURL]];
    }
    [[NSWorkspace sharedWorkspace] recycleURLs:selectURL completionHandler:^(NSDictionary *newURLs, NSError *error) {
        if (error != nil) {
            NSLog(@"error: %@", error);
        } else {
            NSLog(@"newURLs: %@", newURLs);
 
        }
    }];
    [fileArray removeObjectsAtIndexes:indexes];
    [arrayController rearrangeObjects];
  
    
}
[[NSWorkspace sharedWorkspace] recycleURLsの使い方はxcatsan師匠このアーティクルからのいただき。いつものことなんだけど、CocoaのAPIがらみでググるとだいたいxcatsan師匠の過去記事に行き着いてそれが正解、ということが多いなあ。

2011年6月12日日曜日

アンダー・ザ・ドーム読み継ぐ

アンダー・ザ・ドーム 上
スティーヴン・キング
文藝春秋
売り上げランキング: 2314

上巻半分まで読んだ。
キング独特のえぐい展開で楽しく読める。

小さな町全体を包みこんでしまった「ドーム」、という、日本人のSF者なら「物体O」や「アメリカの壁」、そして「首都消失」という小松左京の一連の思考実験作品をすぐに思い出すわけだが、キングはこの現象にどうやって納得のいくオチをつけるのか興味津々。小松作品の場合は結局すべてがなぞのままで終わる。その辺でいちばんよくできていたのが一番古い「物体O」だったような。

Cocoa NSTableView サブクラス化

デフォルトのNSTableViewだと、Drag時に選択行の全部がDragImageになる。ファイル名、ファイルサイズ、更新日時が1行になるので、やたらと横に長いDragImageになってしまう。

Finderの場合はファイル名のみがDragImageになる。Finder並の動作にするためには、NSTableViewをサブクラス化するしかないようだ。delegateではできない動作となる。

どうせrightMouseDownも書きたいところだったので、本格的にサブクラス化することにした。本格的に、というのは、以前TBBOY'S ROOMさんのSimpleLyricsPlayer その31(NSTableViewでひとつのセルだけをD&Dする#2)のコードを拝借して、ファイル名のみのDragImageを作るようにしていたからだが、「ひとつのセルだけ」というタイトルからもわかるとおり、複数行を選択してもカーソル直下の1行分だけをDragImageにする方法だったわけで、これを改造するところから始める。

TBBOY'S ROOMさんのコードをよく読むと、NSImage上にNSCellのdrawInteriorWithFrameで描画をしているらしい。これをカーソル直下の1行分から、

- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent *)dragEvent offset:(NSPointPointer)dragImageOffset

で渡ってくる(NSIndexSet *)dragRows分描画するようにすればいいようだ、と見当がつく。

で、いろいろ試行錯誤の上に書いたコードがこれ。

- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent *)dragEvent offset:(NSPointPointer)dragImageOffset
{
   
    NSInteger col =0;

    if ([dragRows count]<1) {
        return nil;
    }
    NSUInteger index=[dragRows firstIndex];

    NSRect cellFrame = [self frameOfCellAtColumn:col row:index];
    
    CGFloat cellWidth=[[self tableColumnWithIdentifier:@"fileName"] width];
    CGFloat cellHeight=[self rowHeight];
    
    //NSLog(@"image size h:%f w:%f",cellHeight,cellWidth);
    
    NSSize imageSize=NSMakeSize(cellWidth,cellHeight*[dragRows count]);
  
    NSImage *dragImage = [[[NSImage alloc] initWithSize:imageSize] autorelease];
    //NSLog(@"image size w:%f h%f",[dragImage size].width,[dragImage size].height);
    [dragImage lockFocus];
    
    NSRect waku = cellFrame;
    waku.origin.x=0;
    waku.origin.y=imageSize.height-cellHeight;
    
    while (index!=NSNotFound) {
        NSCell *dragCell = [self preparedCellAtColumn:col row:index];
        [dragCell drawInteriorWithFrame:waku inView:self];
        //NSLog(@"waku origin x:%f y:%f",waku.origin.x,waku.origin.y);
        
        waku.origin.y-=cellHeight;
        index=[dragRows indexGreaterThanIndex: index];
    }
       
    [dragImage unlockFocus];
    
    *dragImageOffset=NSZeroPoint;
    //NSLog(@"offset %f %f",dragImageOffset->x,dragImageOffset->y);
    return dragImage;
}
一番調べるの時間がかかったのがNSIndexSetから、どうやってindexを順次取り出すか、というところ。これはデベロッパドキュメントにちゃんと載っていた。


で、無事DragImageが思った通りに描画された。

次は右クリックのコンテキストメニューでファイルの削除(「ごみ箱に移動」)を実装する予定。とか書いても、その日の思いつきであれこれやるので未定ではあるな。

2011年6月11日土曜日

アンダー・ザ・ドーム借りる

アンダー・ザ・ドーム 上
スティーヴン・キング
文藝春秋
売り上げランキング: 3641

久しぶりに予約してまで借りた。

しかしねー、キングの新刊さえ図書館から借りるような貧乏性になるとは・・ちょっと(いや、かなり)情けない感じがする。

上下2冊、2段組、各巻700ページ近いという大長編。果たしてプログラミングしながら2週間で読みきれるのか・・・。って必ず読むけど。

Cocoa NSTableView ドラッグアンドドロップ(7)

ファイル操作を担当するControllerをアプリのメインコントローラー側に移し、ファイル操作の開始はNotificationで通知するようにした。

で、そろそろ始めようか、ということで実際にファイルのコピーをやってみた。
NSFileManageのcopyItemAtURL: toURL: error:でNSURLを使うパターン。書いたコードはこんなところ。
-(IBAction)startFileOperation:(id)sender{
    NSError* error;
    for (NSURL *item in operationFileArray){
        NSURL* dstURL=[NSURL fileURLWithPath:[destLabel objectValue]];
        dstURL=[dstURL URLByAppendingPathComponent:[item lastPathComponent]];
        NSLog(@"srcURL %@",[item path]);
        NSLog(@"dstURL %@",[dstURL path]);
        if(![[NSFileManager defaultManager] copyItemAtURL:item toURL:dstURL error:&error]){
            NSLog(@"sometithg wroth %@",error);
        }
        
    }

    [self sheetClose:self];
}
これで実際にファイルのコピーはできる。こうなると今度は、「ファイルをゴミ箱に」機能をつけたくなる。これは明日以降。

さらに、エラーが出た場合、どうやって処理をするかを考える。ファイル操作とユーザ権限は切っても切れにない関係にあるので、そのあたりも勉強が必要。

大きなサイズのファイルのコピーとかもやってみないといけないし、やるべきことがいっぱいで楽しい。

2011年6月10日金曜日

Cocoa NSTableView ドラッグアンドドロップ(6)

Sheetに参照元とコピー/移動先のディレクトリを表示したところで本日は終了。


このSheet、なんとなく作り始めものだから、NSTableViewの付属品みたいな感じの場所においてあるが、実はアプリ全体で共有したほうが便利なのではないか、と考えている。せっかくだからNSFileManagerでのファイル操作もこのSheetのControllerでやってしまえば、たとえばNSOutlineViewにファイルをドロップした時も使えるのではないか、とか。

ってことはドラッグアンドドロップが発生したらNotificationを通知して、とやったほうがいいのか。

などと考えているうちに本日の自由時間がなくなった。明日以降。

2011年6月9日木曜日

Cocoa NSTableView ドラッグアンドドロップ(5)

きょうはここまで。


選択元ディレクトリ、コピー/移動先ディレクトリ、コピー/移動させたいファイル一覧、といった表示。

む、ということはあとはこれらのデータをNSFilemanagerに渡せばコピー/移動の処理はできる、ということか。

Sheet中のNSTableViewの表示には、NSArrayControllerとbindingを使っている。一度慣れてしまうとdetasourceやdelegateを使うよりはるかに便利だ、と思われる。プログラムサイズが両者でどれくらい違うかはわかっていない。便利な場合は大抵メモリやディスク上のサイズと引換だからなあ。

NSTableViewのbindingはこんな感じ。


ArrayControllerにはNSURLが収まっているので、bindingのModelKeyPathにはNSURLのインスタンスメソッドであるlastPathComponentを直接記述すればいい、と。そうすればファイル名だけが表示される。便利だなあ。かなりコード量が減る。

2011年6月8日水曜日

Cocoa NSTableView ドラッグアンドドロップ(4)

ドラッグアンドドロップされたファイル群は、CopyなのかMoveなのかを決めるダイアログを作る。

作りながら思ったのは「Cnacel」ボタンがけっこう肝かもしれない。いつでも動作を中断できる安心感。

とりあえず本日はここまで。


Sheetを作るのも初めて。Radioボタンつけるのも初めて。おもしろいなあ、Cocoa。
Sheetを出すのに、直接NSTableViewのControllerにあれこれ記述していくか、Controllerを分けて作るかちょっと考える。考えるまでもなく、とりあえず作ってみればいい。というわけでFileOperationSheetControllerなる長ーい名前のクラスを作ってみた。

Sheetには確認用にドロップされたファイルの一覧、コピー元ディレクトリ、コピー先ディレクトリを表示してみる。まめFileの場合、その上「速度優先」などといった動作に関するオプションも指定できるけれど、コピーも移動もNSFileManagerまかせになるのでその手のオプションはきっと無理と思われる。

Cocoaの日々: [iOS][Mac] Xcode4 : ドラッグ&ドロップで IBOutlet コードを生成する

Cocoaの日々: [iOS][Mac] Xcode4 : ドラッグ&ドロップで IBOutlet コードを生成する: "

xcatsan師匠の教え。初めて知った。

早速自分でもIBOutletでやってみた。


今つくっているFileOperation用のSheet。NSButtonを.hにつなげてみる。


こう聞かれて適当に入力すると


IBOutletが記入されてしまった。便利だなあ。

読むべきはドキュメント。またひとつ勉強になりましたxcatsan師匠

2011年6月7日火曜日

WWDC

本日出勤して自分のノートPCを起動し、いつものようにGoogleReaderでざっとニュースをチェックして・・・飛び上がってしまった。WWDCで今日の早朝だったか。明日の早朝だとばっかり思っていた。

心の準備をしていなかったため思わずじっくり読んでしまう。うーん、朝はけっこう学級の1日の準備で忙しいんだけど。

そんなんでプール当番(週に1度、学校のプールの浄水機を始動及び終了をさせる当番)を忘れるしテストの採点は始業時間ギリギリになるし、調子が狂ってしまった。

今回の発表で唯一激しく興味があるのはLionが提供する新しいAPIなわけだけど、そんな情報はどこを探してない。

やっぱりデベロッパプログラムに1万円を献上するべきかなあ。

Cocoa NSTableView ドラッグアンドドロップ(3)

NSTableViewの、Drop受け入れ処理を少し書く。具体的には

-(NSDragOperation)tableView:(NSTableView*)aTableView validateDrop:(id)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation

ですな。
    DirectoryFile* item=[fileArray objectAtIndex:row];
    if([item isDirectory]) [aTableView setDropRow:row dropOperation:NSTableViewDropOn];
    else [aTableView setDropRow:row dropOperation:NSTableViewDropAbove];
    if ([info draggingSource] == tableView) {

        return NSDragOperationMove;
    }
    else{
         return NSDragOperationCopy;
    }
    
    return NSDragOperationEvery;

プロポーズされた行がディレクトリならNSTableViewDropOn、そうでないならNSTableViewDropAboveとしてやるだけで、

NSTableViewDropOn

NSTableViewDropAbove


ついでに同じディレクトリ内のDropならNSDragOperationMove、他のディレクトリならNSDragOperationCopyとしておくと、Copyの時はカーソルのわきに緑の「+」マークがつく。さすがにSimpleCapでもキャプチャできなかったけど。

ただ、コピーか移動かの選択はいちいちダイアログを出して確認する仕様にするつもりなので、見た目の派手さをとって全部NSDragOperationCopyにしておくという手もあるな。

いちいちダイアログ、というのは面倒なように見える。が、マウス派にとってにはけっこう重宝する手法で、移動は⌘キーだっけoptionキーだっけ、とキーボード上で指を彷徨わせる必要がなくなる。(^^;)

なによりも、とりあえずDropしてから移動かコピーを選ぶというのは安心感がある。何をかくそう、Windows上で長年愛用しているファイラ「まめFile」がこの仕様。このソフトのおかげでエクスプローラは全く使わなくて済んでいる。OSXではFinderがよくできているためか、手になじむFileManagerがなかなかみつからない。よさそうなのはたいていシェアウェアのようだし。

というわけで自分用FileManagerは当然「まめFie」の動作を一部まねしている。本当にまねしたいのはXFaceの「Thunar」なんだけど。

2011年6月6日月曜日

404 Blog Not Found:ubuntu - Intel MacもブートできるSDカードを作ってみた

404 Blog Not Found:ubuntu - Intel MacもブートできるSDカードを作ってみた

記事の中身を読むまで、SDカード上にOSXをインストールして起動できる方法、だとばかり思ってドキドキした。さすがハッカーはちがうな、と。(^^;)
「Intel Macも」より、「Intel Macでも」にしたほうがより主旨に沿うかも。

それにしても中身はすごく参考になった。なるほどSDカードにUbuntu入れて持ち歩くのか。これなら学校のPCでもUbuntuで遊べるな。

Cocoa NSTableView ドラッグアンドドロップ(2)

デベロッパドキュメントその他を読みながらドラッグアンドドロップの処理の勉強。
とりあえず本日は

-(NSDragOperation)tableView:(NSTableView*)aTableView validateDrop:(id)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation

-(BOOL)tableView:(NSTableView *)tableView acceptDrop:(id)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation
を付け加えて、自分がdropを受け付ける状態にしてみた。dropされたら

    NSPasteboard* pboard=[info draggingPasteboard];
    NSArray *classes = [[NSArray alloc] initWithObjects:[NSURL class], nil];
    
    NSDictionary *options = [NSDictionary dictionary];
    
    NSArray *copiedItems = [pboard readObjectsForClasses:classes options:options];
    if(copiedItems !=nil){
         NSLog(@"%@",[[copiedItems objectAtIndex:0] path]);
        
    }
   
    return YES;

こんな感じでデベロッパドキュメントの通りに記述してみて、無事URLが渡ってきているのを確かめた。
Dragについては昨日書いた
-(BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard{

    NSLog(@"Drag index %@",rowIndexes);
    [arrayController setSelectionIndexes:rowIndexes];
    NSArray *dataArray=[arrayController selectedObjects];
    NSMutableArray *itemArray=[NSMutableArray arrayWithCapacity:[dataArray count]];
    
    for(DirectoryFile* item in dataArray){
        [itemArray addObject:[item fileURL]];
    }
    [pboard writeObjects:itemArray];  
    return YES;
}

これだけでFinderにファイルがドロップできてしまう。いささかびっくり。PasteBoardに書きだす、というのはそういう働きをするのか。

ただvalidateDrop:についてはいろいろ書かなくてはならないことがたくさんあるようだ。それに現在はNSTableViewを素で使っているが、Drag中の画像をカスタマイズしようとするとNSTableViewからのサブクラス化も避けられない模様。

この場合はFinderならどういう動作をするのかな、という感じでFinderをいじってみることが最近多い。そうすると、極めて納得のいく動き方をする。さすがだなあ。

ま、Finderの代替アプリを作りたいわけではないので、同じ動作をする必要はないのだろうけど。Finderがあえてユーザーから隠しているフォルダなどを手軽にのぞくためのToolができたらいいなあ。挫折しないようにがんばろう。

2011年6月5日日曜日

読了・ダイナミックフィギュア


うー、幸せな1週間だった。久しぶりに楽しいSFを読めた。
ちゃんと上下巻で完結して立派だった。そうか、遊星はYOU SAYだったのか。

Cocoa NSTableView ドラッグアンドドロップ(1)

NSTreeControllerをEseに組み込んで、不具合をつぶして1日が過ぎる。ほぼ組み込む前と同じ動作をするようになったので、いよいよファイルのドラッグアンドドロップによるコピー、移動、削除その他について調べる。Eseそのものもまだまだ実験用プロジェクトなのでどんどんいろいろやってみることにしている。

ヒレガス本に「ドラッグアンドドロップは壮大なカットアンドペーストだ」という記述があったはず。それならカットアンドペーストから実装すべきだったか。(^^;)ま、いいや。

ドラッグアンドドロップについてはネット上のあちこちにたくさん情報がある。とりあえず木下誠氏のこのページあたりを出発点に、xcatsan師匠の教えを乞うたり。

NSTabViewのdatasourceのクラスで、awakeFromNibに
NSArray* array = [NSArray arrayWithObject:NSFilenamesPboardType];
[tableView registerForDraggedTypes:array];
[tableView setDraggingSourceOperationMask:NSDragOperationAll forLocal:NO];
と記述するところから出発する。
これであとは
-(BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard

を書けばいいらしい。試しに「return YES;」だけのこのメソッドを書いてみたら、あらまあなんと、ドラッグができるようになった。


NSArrayControllerに格納しているモデルのクラスは、そのままだとpboardに書き出せないので、オブジェクトからファイルのURLだけ抜き出してpboardにwriteしてみる。
    NSArray *dataArray=[arrayController selectedObjects];
    NSMutableArray *itemArray=[NSMutableArray arrayWithCapacity:[dataArray count]];
    
    for(DirectoryFile* item in dataArray){
        [itemArray addObject:[item fileURL]];
    }
    [pboard writeObjects:itemArray];
これで無事に「コピー」まではできたみたい。簡単だなあ。

次にペースト、だけどこれはかなり手がかかると思われる。本日は、ドラッグされてきたファイルがある、とTabが認識するところまで。
-(NSUInteger)draggingEntered:(id)sender{
   
    [[NSColor redColor] set];
    NSFrameRectWithWidth([self frame],2.0);

    [self displayIfNeeded];

    return NSDragOperationNone;
}

TabBarにドラッグがやってきたら、自分を赤い線で囲んでみる。

カーソルの下のTabを割り出して、そのTabをアクティブにして、それからペースト、という手順になる。
果たしてできるか・・・。

ファイルのコピーや移動になかなかとりかからなかったのは、実はこの機能が実装できなくてファイルマネージャづくりをあきらめることになるかも、という恐れがあるから。サイズの大きなファイルのコピーなどでは、マルチスレッドにしないと実用にならないだろう、と考えていて、スレッドなんていじったことも勉強したこともないから自分には無理かも、と今も考えている。

ただ、荻原本によると「グランドセントラル・ディスパッチ」を使えばけっこう簡単にマルチスレッド状態を実装できる、とあったので、ま、とにかくやってみよう、と重い腰をあげてみた。さーて、どうなるかな。

2011年6月4日土曜日

Cocoa NSTreeControllerとNSOutlineView・まとめ



NSTreeControllerを使ってNSOutlineViewにファイルシステムを表示するサンプルが、ほぼ目的の動作を確認できるところまでできた。ここらでまとめをしておく。

後々の自分のための備忘録なので、特にすぐわからなくなるbindingあたりも画像付きで記録をのこしておくことにする。

プロジェクト
プロジェクトのファイル構成はこうなっている。


上からFileSystemItemはファイルシステムを表現するモデルクラス。具体的には1つのファイルのフルパス、表示用タイトル、親ディレクトリ、自分の子ディレクトリを所持している。
デベロッパドキュメント「Introduction to Outline Views」からのいただき。

ヘッダ。
#import 


@interface FileSystemItem : NSObject {
    NSString *fullPath;
    NSString *displayName;
    FileSystemItem *parent;
    NSMutableArray *children;
    
}
@property (readwrite, copy) NSString* displayName;
@property (readwrite, copy) NSString* fullPath;

+ (FileSystemItem *)rootItem;
- (id)initWithPath:(NSString *)path parent:(FileSystemItem *)parentItem; 
- (NSArray *)children;
- (NSInteger)numberOfChildren;// Returns -1 for leaf nodes

- (FileSystemItem *)childAtIndex:(NSUInteger)n; // Invalid to call on leaf nodes
- (NSString *)relativePath;
- (NSInteger) childIndexAtFullpath:(NSString*)path;
- (FileSystemItem*)childItemAtFullPath:(NSString*)path;
- (BOOL)isLeaf;
- (id)copyWithZone:(NSZone *)zone;

@end

もともとは「)relativePath」と「parent」を組み合わせてfullPathを返す仕様だったが、所々の理由でfullPathそのものを保持するように変更してある。
childIndexAtFullpath:とchildItemAtFullPath:、copyWithZone、isLeafは自分で書き足している。

このオブジェクトの配列をNSTreeControllerに格納している。

MainMenu.xib
NSTreeControllerはnibからロードしている。サブクラス化はしていない。
NSTreeControllerの設定は.xibファイルでほぼ済ませてしまえるようで、アトリビュートの設定の中でFileSystemItemのメソッドを指定することができる。

.xib全体はこんな感じ。


Controllerはメインのコントローラーで、ヘッダはこうなっている。
#import 


@interface Controller : NSObject {
    IBOutlet NSView* view;
    IBOutlet NSTreeController *treeController;
    IBOutlet NSOutlineView* outlineView;
    IBOutlet NSButton* button;
    NSMutableArray* volumes;
@private
    
}
@property (readwrite,assign) NSMutableArray* volumes;
-(IBAction)buttoPushed:(id)sender;
-(IBAction)butoonPushed2:(id)sender;
@end

NSMutableArray* volumes;がFileSystemItemを格納する配列。この配列をNSTreeControllerのContentArrayにbindingしている。
bindingなどはこうなった。



NSOutlineViewのValueもこの配列にbindingしている。


ModelKeyPathは空白にしておくとFileSystemItemのオブジェクトが渡ってくるはずなのだが、それが今回うまくいかず、「self」を入れたらOKだった。ここでFileSystemItemのオブジェクトそのものが渡ってくるようにしておけば、NSOutlineViewにカスタムセルを乗せてアイコンなどを表示することができるようになる。

NSOutlineView
NSOutlineViewも全くカスタマイズしていない。表示用のカスタムセルを載せているくらい。カスタムセルの設定はControllerのawakeFromNibで以下のようにしている。
  NSTableColumn* tableColumn;
    IconedCell*  iconedCell;
    tableColumn = [outlineView outlineTableColumn];
    iconedCell = [[[IconedCell alloc] init] autorelease];
    [tableColumn setDataCell:iconedCell];

カスタムセルである「IconedCell」は確か木下誠氏のサンプルからいただいたもの。ほとんど描画だけである。

#import "IconedCell.h"
#import "FileSystemItem.h"


@implementation IconedCell


- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
        //[self setInteriorBackgroundStyle:NSBackgroundStyleDark];
    }
    
    return self;
}

- (void)dealloc
{
    [super dealloc];
}
static int ICON_SIZE_WIDTH = 16;
static int ICON_SIZE_HEIGHT = 16;
static int MARGIN_X = 3;



- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView
{
    NSString* path;
    NSRect pathRect;
    
    NSImage* iconImage;
    NSSize iconSize;
    NSPoint iconPoint;
    FileSystemItem *item=[self objectValue];
    
    // Draw Image
  
    iconImage=[[NSWorkspace sharedWorkspace] iconForFile:[item fullPath]];
    iconSize = NSZeroSize;
    iconPoint.x = cellFrame.origin.x;
    iconPoint.y = cellFrame.origin.y;
    
    if(iconImage) {
        iconSize.width = ICON_SIZE_WIDTH;
        iconSize.height = ICON_SIZE_HEIGHT;
        iconPoint.x += MARGIN_X;
        
        if([controlView isFlipped]) {
            iconPoint.y += iconSize.height;
        }
        
        [iconImage setSize:iconSize];
        [iconImage compositeToPoint:iconPoint operation:NSCompositeSourceOver];
    }
    
    // Draw text
    path = [item displayName];
    pathRect.origin.x = cellFrame.origin.x + MARGIN_X;
    if(iconSize.width > 0) {
        pathRect.origin.x += iconSize.width + MARGIN_X;
    }
    pathRect.origin.y = cellFrame.origin.y;
    pathRect.size.width = cellFrame.size.width - (pathRect.origin.x - cellFrame.origin.x);
    pathRect.size.height = cellFrame.size.height;
    
    if(path) {
        [path drawAtPoint:pathRect.origin withAttributes:nil];
    }
}
@end

iconImageをモデル側に持たせず、カスタムセル中で iconImage=[[NSWorkspace sharedWorkspace] iconForFile:[item fullPath]];と取得して使い捨てるようにしてみた。

datasourceもdelegateも一切必要がなく、やはりNSTreeControllerと組み合わせた方が使いやすいようだ。

Controller
コントローラーの仕事は大きく分けて3つ。

1,起動時点でマウントされているVolumeの取得とセット
これはこんな感じ。
-(void)setVolume{
    NSArray* mountedVols=[[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:nil options:NSVolumeEnumerationSkipHiddenVolumes];
    NSMutableArray *roots=[NSMutableArray array];
    if ([mountedVols count] > 0){
  for (NSURL *element in mountedVols){
            FileSystemItem* item,*parent;
            if([[element path ]isEqualToString:@"/"]){
                item=[[FileSystemItem alloc] initWithPath:[element path] parent:nil];
            }
            else{
                parent=[[FileSystemItem alloc]initWithPath:[[element path] stringByDeletingLastPathComponent] parent:nil];
                item=[[FileSystemItem alloc] initWithPath:[element path] parent:parent];
                [parent release];
                
            }
            [roots addObject:item];
            [item release];
            
        }
  [self setVolumes:roots]; 
 }    
}

NSTreeControllerのContentになる配列にaddObjectすると、そのアイテムはいわゆるルートアイテムになる。アイテムであるモデルクラスでchildrenを取得するようにしておけば、ルートアイテムを追加するだけで木構造をコントロールしてもらえる。便利。モデルクラスでchildrenを管理できない場合はNSTreeNodeを使えばいいようだ。近いうちに「よく使うディレクトリのBookMark」を表示するNSOutlineViewを作ってみるつもりなので、その時はきっとNSTreeNodeを研究することになるだろう。

2、リムーバブルメディアのマウントとアンマウントの処理
マウントとアンマウントはNotificationで処理する。
awakeFromNibで
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(didMount:) name:NSWorkspaceDidMountNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(didUnmount:) name:NSWorkspaceDidUnmountNotification object:nil];
とaddObserverしておき、
-(void)didMount:(id)sender{
    NSLog(@"Mount! %@",sender);
    NSDictionary* info=[sender userInfo];
    NSURL* url=[info objectForKey:NSWorkspaceVolumeURLKey];
    FileSystemItem* parent=[[FileSystemItem alloc]initWithPath:[[url path] stringByDeletingLastPathComponent] parent:nil];
    FileSystemItem* item=[[FileSystemItem alloc] initWithPath:[url path] parent:parent];
    [parent release];
    [volumes addObject:item];
    [item release];
    [treeController rearrangeObjects];
  
    
}
-(void)didUnmount:(id)sender{
    NSLog(@"UnMount! %@",sender);
    NSDictionary* info=[sender userInfo];
    NSURL* url=[info objectForKey:NSWorkspaceVolumeURLKey];
    NSUInteger deleteNo;
    for (NSInteger i=0; i<[volumes count]; i++) {
        FileSystemItem* item=[volumes objectAtIndex:i];
        if ([[item fullPath] isEqualToString:[url path]]) {
           deleteNo=i;
        }
    }
    [volumes removeObjectAtIndex:deleteNo];
    [treeController rearrangeObjects];

}
もう少しスマートでクールなコードを書けるようになりたいものだが、確実に動く、自分で理解しやすいことを第一にしているのでこんなものだろう。 3,NSIndexPathを作る 特定のディレクトリを選択状態にしてNSOutlineViewを展開したことがある。そのためにはNSTreeControllerを使う場合はNSIndexPathの操作が不可避である。 デベロッパドキュメントのNSIndexPathのリファレンスで使われている概念図。
NSOutlineViewの特定の行を一撃で選択・展開できるので非常に便利。自分でNSIndexPathを作成する場合、例えば「@"/Applications/Chess.app/Contents"」というパスをNSIndexPathで表現するためには、ルートアイテムの「/」直下のディレクトリ群で「Applications」は何番目のindexか、さらに「Chess.app」は「/Applications」直下のディレクトリで何番目のindexか、というふうに順番に調べていかないといけない。 ルートアイテムが「/」だけなら話は簡単なのだが、リムーバブルメディアの場合は「/Volumes」が必ず頭につくことになるのでやっかいだ。ルートアイテムだからNSIndexPathの1段目にindexが入っているものの、「/」「Volumes」「うんたら」とpathComponent上では3段目まで使っていることになる。 そこで今回は、まず頭に「/Volumes」が付いていたら
-(FileSystemItem*)makeRootNodeItem:(NSString*)path{
    NSArray* pathArray=[path pathComponents];
    NSMutableString* pathString=[NSMutableString string];
    [pathString appendString:[pathArray objectAtIndex:0]];
    
    [pathString appendString:[pathArray objectAtIndex:1]];
    [pathString appendString:@"/"];
    FileSystemItem* parent=[[FileSystemItem alloc]initWithPath:pathString parent:nil];
    [pathString appendString:[pathArray objectAtIndex:2]];
    FileSystemItem* item=[[FileSystemItem alloc] initWithPath:pathString  parent:parent];
    [parent release];

    return [item autorelease];
    
}
というふうにFileSystemItemにしてしまうことにした。すごく冗長なのは自分でも目をつぶっている。 特定のパスをNSIndexPathに変換してNSTreeControllerのSelectionをセットするメソッドは次のようになる。
-(void)setSelectionByFullpath:(NSString*)path{
   
    NSInteger counter;
    FileSystemItem* item;
    
    BOOL isDir, valid;
    valid = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir] ;
    if (!isDir || !valid) {
        path=NSHomeDirectory();
    }
     NSArray *pathArray=[path pathComponents];   
    if ([[pathArray objectAtIndex:1] isEqualToString:@"Volumes"]) {
        item=[self makeRootNodeItem:path]; 
        counter=3;
    }
    else{
        item=[[FileSystemItem alloc]initWithPath:[pathArray objectAtIndex:0] parent:nil];
        counter=1;
    }
    NSIndexPath* indexPath;
    for(NSInteger i=0;i<[volumes count];i++){
        if ([[item fullPath] isEqualToString:[[volumes objectAtIndex:i] fullPath]]) {
            indexPath=[NSIndexPath indexPathWithIndex:i];
            
        }
    }
    FileSystemItem *newItem;
    NSInteger newIndex;
    NSMutableString* newPath=[[NSMutableString alloc]initWithString:[item fullPath]];
    for (NSInteger i=counter; i<[pathArray count]; i++) {
        if(i==1) [newPath appendString:[pathArray objectAtIndex:i]];
        else [newPath appendFormat:@"/%@",[pathArray objectAtIndex:i]];
       
        newItem=[item childItemAtFullPath:newPath];
        newIndex=[item childIndexAtFullpath:newPath];
        indexPath=[indexPath indexPathByAddingIndex:newIndex];
        item=newItem;
        
    }
  
    [treeController setSelectionIndexPath:indexPath];
   [item release];
    
}
以上で
-(IBAction)buttoPushed:(id)sender{

    NSString *path=[NSString stringWithString:@"/Applications/Chess.app/Contents"];
  
    [self setSelectionByFullpath:path];
}

といった処理が簡単にできるようになった。これでEse本体への組み込みのめどがついたようだ。

2011年6月3日金曜日

Cocoa NSTreeControllerを調べる(7)NSIndexPathを作る

特定のディレクトリへのパスから、NSIndexPathを作る。
buttonは「Applications/Chess.app/Contents」からNSIndexPathを作成する。

button2はリムーバブルHDDの「/data」へのパスからNSIndexPathを作成する。

というわけでNSTreeControllerに移行する準備は整ったと思ったのだけれど、カスタムセルでアイコンを描画する、というところでつまずいた。(^^;)

OutlineViewのdatasourceを使えなくなったので、bindingで直接カスタムセルにモデルのオブジェクトを渡す、という以前一度やった方法を試しているが、なぜかこれが失敗する。

うーむ、いちいち多難だ。

Cocoa 備忘録 NSHomeDirectory()

ユーザーのホームディレクトリを取得する。

NSString * NSHomeDirectory()

Foundation Functions Referenceにのっていた。すぐ忘れるんだよなあ。

2011年6月2日木曜日

Cocoa NSTreeControllerを調べる(6)NSIndexPath

NStreeControllerのSlectionを操ろうとすると、NSIndexPathを作ってそれをsetSelectionIndexPathでセットしなければいけない。

NSIndexPathを作るのは簡単で、とりあえず[NSIndexPath indexPathWithIndex:0]とクラスメソッドを使えば「最初のindexが0のNSIndexPath」を作ることができる。内蔵HDDのことだけを考えればこれでいいわけだが、リムーバブルメディアがあるのでそう簡単にはいきそうにない。

indexPathの追加も簡単。indexPath=[indexPath indexPathByAddingIndex:newIndex];てな感じでどんどん階層を深くしていくことができる。これで試しに{0,4,1}みたいなindexPathを作ってtreeControllerにセットすると、ちゃんとOutlineViewで望みのディレクトリを選択して、表示できるまでスクロールまでしてくれる。

そこで選択したいディレクトリのフルパスから、indexPathを作成していく作業に移る。
ところが、内蔵HDDを表すモデルの一番根元のフルパスは、もちろん「/」。しかしリムーバブルメディアの場合は、必ず「/volumes」以下にマウントされるので、「/Volumes/なんとか」が一番根本となる。だから単純にフルパスのpathComponentsを再結合していくわけにはもいかない。

このあたりをすっきりと説明する言葉を持っていないのが残念だ。自分用備忘録なので仕方ないけど。とにかく本日は、pathComponentsで分解したフルパスのobjectAtIndex:1に「Volumes」がある時は特別な処理をする、というところまで。

2011年6月1日水曜日

Cocoa NSTreeControllerを調べる(5)

OutlineViewを直接使っている状態から、NSTreeControllerを経由する方法に変更しようとしている。

delegateのoutlineViewSelectionDidChange:(NSNotification *)notificationを定義して、選択行が変更されると何が起きるかNSLogで出力してみる。

NSLog(@"%@",[outlineView itemAtRow:[outlineView selectedRow]]);で出力されるのは
NSTreeControllerTreeNode: 0x100647eb0, child nodes {}
やはりOutlineViewは単なる「見た目」だけの存在となるようだ。

FileSystemItem *item=[[treeController selectedObjects] objectAtIndex:0]とやれば、modelのオブジェクトに行き着くことができる。

modelに行き着くことができるならNSTreeControllerをEseに組み込んでもほぼ大丈夫と思われるが、ひとつだけ考え込んでいるのが

pathを与えられたら、OutlineViewの所定の行を選択状態にする

方法。前回終了時に表示していたディレクトリを復帰させる、とか、TableViewでディレクトリのダブルクリックでそのディレクトリの内容を表示、OutlineViewも連動する、といった場合に必要となる。

OutlineViewの場合はNSStringで探せばよかったわけだが、NSTreeControllerだと「所定の行を選択状態にする」ためにはNSIndexPathを使わないといけない。「/Applications/Chess.app/Contents」といった文字列から、「0,4,6,1」というNSIndexPathをどうやって作るか・・・それを考えているうちに時間切れとあいなった。明日以降。