ドラッグ&ドロップのスクリプティング

 ドラッグ&ドロップがちとやっかいだったので、練習と実益を兼ねてサンプルを作ってみました。スタックウィンドウ内の指定フィールドにフォルダをドロップすると、そのフォルダ内の「空のフォルダ」を削除します。スクリプトは全てフィールドスクリプトにあります。スクリプトはここに載せてありますが、是非実際のスタックの動作を見ながら解説を読んで下さい。
http://udimac.web.fc2.com/RunRev/deleteEmptyFolders.rev.hqx

−−−−

 まず「何かがドラッグされてきた」時に dragEnter ハンドラが実行されます。このハンドラでやるべきは、ドラッグされたものを受け付けるか否かを決め、そのレスポンスをユーザーに返すことです。ドラッグされたものを受け取るなら set the acceptdrop to true を実行して、システムに「このオブジェクトは受付可能である」旨を知らせます。 OSX ではこの時カーソルの形が '+' になり、ドロップが可能であることを示します。

 もしそのオブジェクトを受け取らないなら、the acceptdrop をセットせずにそのまま終了します。これはシステムに「このオブジェクトは受け取らない」と返事をしたことになります。OS によって異なるかも知れませんが、MacOS ではこの状態でユーザーがマウスのボタンを離すと、「ドラッグしたオブジェクトがドラッグ元へ帰っていく」アニメーションが表示されます。これによってユーザーに「そのタイプのオブジェクトは受け取らない」という意思表示が出来ます。

 このサンプルではフォルダだけを受け取りたいので、the keys of the dragData に "files" が含まれていない時は exit しています。
 ここで the dragData プロパティはドラッグされてきたオブジェクトの配列です。その配列のキーである the keys を調べることによって、ドラッグしてきたオブジェクトの種類を知ることが出来ます。( テキストなら "text"、グラフィックデータなら"image"、ファイルなら中身に関わらず "files" です。ディスクやフォルダも "files" になります)
 さらに the dragData の中身をひとつづつ(複数の場合もあるので)調べて、そこにフォルダが無ければ exit します。このチェック処理は checkContainsFolder ハンドラで行っています。

 以上のチェックを通ったら(つまりフォルダがドラッグされてきているのなら)、ドラッグを受け取るオブジェクトのフォーカスをハイライトさせます。
 focusOn ハンドラでは set the traversalOn of me to true を実行して、オブジェクトの「フォーカス可能」プロパティをオンにしています。オブジェクトにもよりますが、ドロップ専用のフィールドは普段はフォーカス不可にしておいた方が都合がよいので、この処理が必要になります。(ドロップ処理が済んだら再度 false に戻しておきます) そして focus on me で実際にフォーカスリングをハイライトします。

 これによって、ユーザーがオブジェクトの上にフォルダをドラッグすると、そのオブジェクトがハイライトするようになります。これでユーザーは「ドロップを受け付けてくれる」と判断出来ます。ドラッグの途中でドラッグ先を変えるなど、ユーザーがドラッグ処理を中止した場合は、dragLeave メッセージが送られて来ます。ハイライトさせたオブジェクトはここでハイライトを解除して、そのオブジェクトに対するドラッグ処理が中断されたことを明確にします。つまりこれら一連の「ドラッグ」処理は、dragEnter ハンドラと dragLeave ハンドラを基点に行っています。

--2018.7.1 修正 15年も経って何を言ってるのかと言われそうだけど、間違いを見つけたので直しておきます orz
ファインダーアイテム(ファイル、フォルダ、ディスクなど)がドラッグされてきた時、the dragData には files が入っているものと思っていましたが、これは MacOS 9 の話でした。 MacOSX と Linux では files と text の両方が入っています。つまりこの下のスクリプトで「 files でなかったら」 if ( the keys of the dragData ) is not "files" then としてあるところは、「 files が入っていなかったら」 if NOT( ( the keys of the dragData ) contains "files" ) then としないと、何もドロップ出来なくなります。当時は OSX のテスト環境が無かったためのミスでした。申し訳ない。サンプルスタックは修正前のものをそのまま置いておきます。修正前と後で動作がどう変わるか見てみて下さい。


global gSaveDefaultFolder

on dragEnter -- 何かがドラッグされてきた
  -- 2018.7  if ( the keys of the dragData ) is not "files" then
  if NOT ( ( the keys of the dragData ) contains "files" ) then
    exit dragEnter -- ファインダーアイテムがない「そんなデータは知らない」
  end if
  if checkContainsFolder( the dragData ) is false then
    exit dragEnter -- フォルダが含まれていない「そんなデータは知らない」
  end if
  set the acceptdrop to true -- ドロップ可
  focusOn -- フォーカスを得る「ドロップ出来るよ」
end dragEnter

on dragLeave -- ドラッグ中にマウスがオブジェクトから外れた
  fucousOff -- フォーカスを解除「ドラッグやめたよ」
end dragLeave

function checkContainsFolder dragList -- ドラッグリストにフォルダがあるか?
  repeat for each line aFolder in dragList -- オブジェクトをひとつづつ調べる
    if ( there is a folder aFolder ) then -- それはフォルダか?
      return true -- ドロップリストにはフォルダが含まれている
    end if
  end repeat
  return false -- ドロップリストにはフォルダが含まれていない
end checkContainsFolder

 次に、ドロップされたオブジェクトを dragdrop ハンドラで処理します。このハンドラが実行される時は、既に「ドラッグ」処理が済んいます。つまりマウスボタンが離されて、オブジェクトに何かが「ドロップ」された時です。
 データ種別のチェックは dragEnter で済んでいますから、ここでは「フォルダかそうでないか」だけを考えます。

 まずドロップしたオブジェクトの数だけループさせます。もしフォルダでないものが混じっていたら、それはスキップします。

 そしてドロップされたフォルダ内のアイテムリストを得ます。 the defaultFolder プロパティに目的のフォルダのパスをセットして、the folders プロパティ(デフォルトフォルダ内のアイテム名リスト)を読みます。 the folders で得られるリストの第1行目は必ず ".." ( Unix でカレントディレクトリを示す記号)になっているので、「 line 2 to -1 of 」(第2行目から最後の行まで、の指定)を付けています。
 デフォルトフォルダを変更する前に、現在のデフォルトフォルダをグローバル変数に保存しています。あとでこのフォルダをデフォルトにセットしてやれば、このスクリプトの実行前後でデフォルトが変わらずに済むわけです。

 これで「ドロップしたフォルダ内のアイテムリスト」が変数 folderList に得られました。このリストに含まれているフォルダのうち、中身が空のものについて delete folder を実行します。
 the number of lines of the folders が 1 だった時に削除しているのは、このリストが必ず ".." を含んでいるためです。つまりリストが ".." の1行のみだった場合は、このフォルダは空であると判断しています。( MacOS ではフォルダのアイコンはファイルの形で保存されています。そのため、このスクリプトではアイコン付きのフォルダは「空」とはみなされません)
 実際にフォルダを削除する前にデフォルトフォルダを元に戻しているのは、デフォルト指定されているフォルダは削除出来ないからです。

 「ドロップされたフォルダのリスト」と「そのフォルダの中のアイテムリスト」で二重のループになっていることに気を付けて下さい。注意深く読まないと混乱するかも知れません(^^;) 最後にフォーカスを解除して、デフォルトフォルダを戻して終わりです。

on dragDrop
  put the dragData into dragList
  put the defaultFolder into saveDefaultFolder -- 現在のデフォルトフォルダを保存
  repeat for each line aFolder in dragList -- ドロップしたオブジェクトの数だけループ
    if ( there is not a folder aFolder ) then
      next repeat -- ドロップしたアイテムがフォルダでなかったらスキップ
    end if
    set the defaultFolder to aFolder
    put line 2 to -1 of the folders into folderList -- ドロップしたフォルダ内のアイテム名リスト
    repeat for each line tgFolder in folderList
      set the cursor to busy
      put ( aFolder & "/" & tgFolder ) into tgFolderPath -- 親フォルダのパスと合成
      if ( there is not a folder tgFolderPath ) then
        next repeat -- アイテムがフォルダでなかったらスキップ
      end if
      set the defaultFolder to tgFolderPath -- ドロップしたフォルダ内のフォルダの中のアイテム名リスト
      if ( the number of lines of the folders )  = 1 then -- リストが空( ".." のみ)なら
        set the defaultFolder to saveDefaultFolder -- デフォルトフォルダを戻してから
        delete folder tgFolderPath -- そのフォルダを削除
      end if
    end repeat
  end repeat
  fucousOff
  set the defaultFolder to gSaveDefaultFolder
end dragDrop

on focusOn -- 自分をフォーカスする
  set the traversalOn of me to true
  focus on me
end focusOn

on fucousOff -- 自分のフォーカスを外す
  select empty
  set the traversalOn of me to false
end fucousOff

 このスクリプトは1つの階層しか見ていませんが、うまく再帰を使うとフォルダの中の深い階層まで調べることが出来るようになります。その時は必ず「奥の階層」から調べます。浅い階層から先に処理してしまうと、奥の階層を処理した結果空になったフォルダを見逃してしまうからです。

 なおこのスタックは RuntimeRevolution 2.0.1 MacOS Classic 版で作っていますが、dragEnter ハンドラや dragDrop ハンドラ内でスクリプトエラーが起きると、システムごと飛んでしまって困りました。改造して遊ぶ時はご注意下さい。

2003.06.18 *訂正* the folders はデフォルトフォルダ内の「フォルダの」リストでした。つまり dragDrop ハンドラの 12行目で行っている「フォルダでなかったら」のチェックは不要です。混乱した人がいたらごめんなさい(^^;)


UDI
2003.06.10
2003.06.18
2018.07.01

inserted by FC2 system