単語リストプログラム その9

今回は、ファイルリストテーブルへのファイルのドラッグアンドドロップを扱う。詳しいことは、ドラッグアンドドロップにまとめてあるのでそちらを参照してください。

テーブルにファイルをドラッグアンドドロップできるようにするには、まず、その処理を書くサブクラスのインスタンスに対して、テーブルの delegate と dataSource が設定されている必要がある。ここまで順番にたどってきていれば設定してあるはずだけど、そうでない場合は設定してください。そして、そのテーブルに対して registerForDraggedTypes で扱える pasteboard のタイプを指定して、 setDraggingSourceOperationMask_forLocal でドラッグアンドドロップのタイプとアプリケーション内だけで行うかアプリケーション外へも行うかを設定する。

このサンプルでは、とりあえずファイルリストテーブルへのファイルのドラッグアンドドロップをできるようにしたいので、awakeFromNib で次のような設定をする。

def awakeFromNib

@table1.registerForDraggedTypes([NSFilenamesPboardType])

@table1.setDraggingSourceOperationMask_forLocal(NSDragOperationEvery,true)

@table2.registerForDraggedTypes([NSFilenamesPboardType])

@table2.setDraggingSourceOperationMask_forLocal(NSDragOperationEvery,true)

end

実際にドラッグアンドドロップを処理するには、tableView_validateDrop_proposedRow_proposedDropOperation と tableView_acceptDrop_row_dropOperation を定義する。前者はドラッグアンドドロップする先が有効であるかをチェックして、後者でドロップされたときの処理を記述する。

tableView_validateDrop_proposedRow_proposedDropOperation(table,info,row,operation) で、operation にテーブルにある項目にドロップするか(0)、項目の間にドロップするか(1)の情報が入る。テーブルにある項目にドロップできるようにする場合は、その項目がハイライトされる。項目の間に線が表示される。このサンプルでは、ファイルが追加されると並べ替えを行うように処理しているので、関係ないと言えばないんだけど。まあ、項目がハイライトされると、それと置き換わるように見えるので、その場合は処理を始めない(ハイライトされない)ようにするために、operation が 0 の時は何も反応しない(NSDragOperationNone)ようにする。項目間の場合は、マウスカーソルにプラスの記号が現れるようにしたいので NSDragOperationCopy を指定する。スクリプトにするとこんな感じ。

def tableView_validateDrop_proposedRow_proposedDropOperation(table,info,row,operation)

if operation == 1

return NSDragOperationCopy

else

return NSDragOperationNone

end

end

これで、テーブルにファイルがドラッグされるとマウスカーソルにプラスがついてドロップできますよ、というのがわかるようになる。

次に、tableView_acceptDrop_row_dropOperation(tableView,info,row,operation) を定義して実際の処理に入る。この info に NSDraggingInfo オブジェクトとして情報が入っているので、draggingPasteboard でそこに保持されている pasterboard 情報を得る(NSPasteboard オブジェクト)。さらにそれから propertyListForType で NSFilenamesPboardType で保持されているファイルパスを配列で得る(NSArray オブジェクト)。あと、tableView_acceptDrop_row_dropOperation で true を返すと、ドロップ処理が受け付けられてファイルがそこで消える。何も返さないか false だとファイルが元の場所に戻るような動作になる。

def tableView_acceptDrop_row_dropOperation(tableView,info,row,operation)

pasteBoard = info.draggingPasteboard

p files = pasteBoard.propertyListForType(NSFilenamesPboardType)

true

end

これで実行すると、コンソールにドロップしたファイル/フォルダのパスが NSArray オブジェクトとして表示される。

ここで、テーブルにファイルを追加する処理をするために定義した addFilesToTable(aryID,encode,files) を使う。aryID は tableView にドロップ処理が行われたテーブルが入っているので、@tableID[tableView.to_s] で得られる。encode は指定するところがないので、デフォルトで UTF-8 ということにして 0 にする。files は上の処理で得た files をそのまま使う。そうすると、次のようになる。

def tableView_acceptDrop_row_dropOperation(tableView,info,row,operation)

pasteBoard = info.draggingPasteboard

files = pasteBoard.propertyListForType(NSFilenamesPboardType)

addFilesToTable(@tableID[tableView.to_s],0,files)

true

end

これで、ファイルをファイルリストテーブルにドラッグアンドドロップする処理は完成。

これだけでもいいんだけど、どうせだからテーブル間でドラッグアンドドロップができるようにしてみる。このページの最初でテーブルがファイルパスを受け付ける様に設定した(NSFilenamesPboardType)。でも、テーブルからの情報はファイルパスではなく Array Controller のオブジェクトなので、NSDictionary オブジェクトまたは NSDictonary オブジェクトを要素とする NSArray (もしくは Ruby の Array)オブジェクトになる。ただ、これに対応する PasteBoard タイプがない。なければ作ってしまえということで、TableContentType というのを定数として作ってしまって、それを受け付けるようにする。

TableContentType = "TableContentType"

def awakeFromNib

@table1.registerForDraggedTypes([NSFilenamesPboardType,TableContentType])

@table1.setDraggingSourceOperationMask_forLocal(NSDragOperationEvery,true)

@table2.registerForDraggedTypes([NSFilenamesPboardType,TableContentType])

@table2.setDraggingSourceOperationMask_forLocal(NSDragOperationEvery,true)

end

これで準備ができた。

テーブルからドラッグ処理を始めるには tableView_writeRowsWithIndexes_toPasteboard(tableView,idx,pasteboard) を使う。この pasteboard を使ってデータを受け渡す。ここで行う処理は declareTypes_owner と setPropertyList_forType。前者で受け渡す PasteBoard を準備して、後者で受け渡すデータを PasteBoard に書き込む。

まず、TableContentType を受け渡すデータのタイプとして使うように宣言して、draggingItems というオブジェクトを作って、それを受け渡すようにする。

pasteboard.declareTypes_owner([TableContentType],self)

pasteboard.setPropertyList_forType(draggingItems,TableContentType)

draggingItems には、ドラッグする NSArrayController の要素を入れる Array オブジェクトにする。idx は NSIndexSet で to_a でインデックスの配列にできるので、それをブロックで処理して、draggingItems に受け渡す要素を入れていく。

draggingItems = Array.new

idx.to_a.each do |index|

draggingItems << @fileAryCtl[@tableID[tableView.to_s]].arrangedObjects[index]

end

これをまとめると、次のようになる。

def tableView_writeRowsWithIndexes_toPasteboard(tableView,idx,pasteboard)

pasteboard.declareTypes_owner([TableContentType],self)

draggingItems = Array.new

idx.to_a.each do |index|

draggingItems << @fileAryCtl[@tableID[tableView.to_s]].arrangedObjects[index]

end

pasteboard.setPropertyList_forType(draggingItems,TableContentType)

true

end

さて、テーブル間のドラッグアンドドロップを完成させるには、tableView_acceptDrop_row_dropOperation も TableContentType のデータを受け入れて処理できるようにしなければならない。TableContentType を受け付けたときと NSFilenamesPboardType を受け付けたときで処理を分けるには、次のようにする。どちらのテーブルでの処理か判断する @tableID を入力の簡略化のために aryID に入れている。

def tableView_acceptDrop_row_dropOperation(tableView,info,row,operation)

pasteBoard = info.draggingPasteboard

aryID = @tableID[tableView.to_s]

if pasteBoard.propertyListForType(TableContentType)


elsif pasteBoard.propertyListForType(NSFilenamesPboardType)


end

end

この順番が逆だと、URL を探したけどなかった、とおこられるので、TableContentType かどうかを先に判断するようにした。

TableContentType だった場合には、移動先の Array Controller に要素を追加して並べ替える処理をして、移動元のファイルを消さなくてはいけないのでその処理も入れる。

この処理は、前々回作った addFilesToTable というメソッドと共通な部分があるので、そこをまた別のメソッドにしよう、と思ったんだけど、どうせなら、NSArrayController にファイルを追加して重複しているファイルを消して並べ替える、というメソッドを追加する手でいってみる。

まず、これが、addFilesTTable。

def addFilesToTable(aryID,encode,files)

fileAry = Array.new

files.each do |dir|

Find.find(dir.to_s) do |path|

if @fileExt =~ File.extname(path)

currentEncode = File.extname(path).downcase == ".txt" ? encode : -1

fileAry << {"fileName" => File.basename(path),"encoding" => currentEncode,"fullPath" => path}

end

end

end

@fileAryCtl[aryID].addObjects(fileAry)

uniqFiles = NSCountedSet.alloc.initWithArray(@fileAryCtl[aryID].arrangedObjects).allObjects

@fileAryCtl[aryID].setSelectionIndexes(NSIndexSet.indexSetWithIndexesInRange([0,@fileAryCtl[aryID].arrangedObjects.length]))

@fileAryCtl[aryID].addObjects(uniqFiles)

descriptor = [NSSortDescriptor.alloc.initWithKey_ascending_selector("fileName",true,"caseInsensitiveCompare:")]

@fileAryCtl[aryID].setSortDescriptors(descriptor)

@fileAryCtl[aryID].rearrangeObjects

end

これで、最後の部分が共通になっているので、それを取り出して NSArrayController クラスに追加するメソッドを作る。メソッド名は addUniqFiles としておく。

class NSArrayController

def addUniqFiles(fileAry)

self.addObjects(fileAry)

uniqFiles = NSCountedSet.alloc.initWithArray(self.arrangedObjects).allObjects

self.setSelectionIndexes(NSIndexSet.indexSetWithIndexesInRange([0,self.arrangedObjects.length]))

self.addObjects(uniqFiles)

descriptor = [NSSortDescriptor.alloc.initWithKey("fileName",ascending:true,selector:"caseInsensitiveCompare:")]

self.setSortDescriptors(descriptor)

self.rearrangeObjects

end

end

こうすることで、addFilesToTable は次のようになる。

def addFilesToTable(aryID,encode,files)

fileAry = Array.new

files.each do |dir|

Find.find(dir.to_s) do |path|

if @fileExt =~ File.extname(path)

currentEncode = File.extname(path).downcase == ".txt" ? encode : -1

fileAry << {"fileName" => File.basename(path),"encoding" => currentEncode,"fullPath" => path}

end

end

end

@fileAryCtl[aryID].addUniqFiles(fileAry)

end

ここで、本題のドラッグアンドドロップ処理に戻ると、pasteBoard.propertyListForType(TableContentType) に移動元の Array Controller から取り出した配列の要素が入っているので、それを addUniqFiles で移動先の Array Controller に追加する。

@fileAryCtl[aryID].addUniqFiles(pasteBoard.propertyListForType(TableContentType))

それから、移動した要素を移動元の Array Controller から消す。

origAryID = aryID == 0 ? 1 : 0

@fileAryCtl[origAryID].removeObjects(pasteBoard.propertyListForType(TableContentType))

これをまとめると、こんな感じになる。

def tableView_acceptDrop_row_dropOperation(tableView,info,row,operation)

pasteBoard = info.draggingPasteboard

aryID = @tableID[tableView.to_s]

if pasteBoard.propertyListForType(TableContentType)

@fileAryCtl[aryID].addUniqFiles(pasteBoard.propertyListForType(TableContentType))

origAryID = aryID == 0 ? 1 : 0

@fileAryCtl[origAryID].removeObjects(pasteBoard.propertyListForType(TableContentType))

true

elsif pasteBoard.propertyListForType(NSFilenamesPboardType)

files = pasteBoard.propertyListForType(NSFilenamesPboardType)

addFilesToTable(aryID,0,files)

true

else

false

end

end

最後に、今回のスクリプトをまとめてみる。

TableContentType = "TableContentType"

def awakeFromNib

@table1.registerForDraggedTypes([NSFilenamesPboardType,TableContentType])

@table1.setDraggingSourceOperationMask_forLocal(NSDragOperationEvery,true)

@table2.registerForDraggedTypes([NSFilenamesPboardType,TableContentType])

@table2.setDraggingSourceOperationMask_forLocal(NSDragOperationEvery,true)

end

def tableView_validateDrop_proposedRow_proposedDropOperation(table,info,row,operation)

if operation == 1

return NSDragOperationCopy

else

return NSDragOperationNone

end

end

def tableView_acceptDrop_row_dropOperation(tableView,info,row,operation)

pasteBoard = info.draggingPasteboard

aryID = @tableID[tableView.to_s]

if pasteBoard.propertyListForType(TableContentType)

@fileAryCtl[aryID].addUniqFiles(pasteBoard.propertyListForType(TableContentType))

origAryID = aryID == 0 ? 1 : 0

@fileAryCtl[origAryID].removeObjects(pasteBoard.propertyListForType(TableContentType))

true

elsif pasteBoard.propertyListForType(NSFilenamesPboardType)

files = pasteBoard.propertyListForType(NSFilenamesPboardType)

addFilesToTable(aryID,0,files)

true

else

false

end

end

def tableView_writeRowsWithIndexes_toPasteboard(tableView,idx,pasteboard)

pasteboard.declareTypes_owner([TableContentType],self)

draggingItems = Array.new

idx.to_a.each do |index|

draggingItems << @fileAryCtl[@tableID[tableView.to_s]].arrangedObjects[index]

end

pasteboard.setPropertyList_forType(draggingItems,TableContentType)

true

end

これとは別に NSArrayController にメソッドを追加しておく。

class NSArrayController

include OSX

def addUniqFiles(fileAry)

self.addObjects(fileAry)

uniqFiles = NSCountedSet.alloc.initWithArray(self.arrangedObjects).allObjects

self.setSelectionIndexes(NSIndexSet.indexSetWithIndexesInRange([0,self.arrangedObjects.length]))

self.addObjects(uniqFiles)

descriptor = [NSSortDescriptor.alloc.initWithKey("fileName",ascending:true,selector:"caseInsensitiveCompare:")]

self.setSortDescriptors(descriptor)

self.rearrangeObjects

end

end

これでファイルビューはとりあえずの完成にして、次回からは、単語リストビューに移る。

単語リストプログラム その10 - 単語リストテーブルへのパーツの配置