単語リストプログラム その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 - 単語リストテーブルへのパーツの配置