結果的に、自分でもビックリするくらいニッチなネタになってしまったのですが、調べた時間が勿体無いので、無意味に記事として書き残しておきます。
さて、と。
えぇとですね、ドラッグ&ドロップってあるじゃないですか?
とあるアプリケーションで選択したファイルや何かを、別のアプリケーションまでドラッグして、ポイッとドロップするマウスジェスチャーのことですね。
Java でも標準で API が用意されているので、割りと簡単に実装できるのですが、あちらこちらでよく紹介されている書き方だと、一部の Linux で上手く機能しないことがあったりします。
何故か、これを解決する日本語の情報があまり見当たらなかったので、適当に書いてみようかというのが今回の趣旨だったのですが......
いえね、最初はディストリビューションか、もしくはデスクトップマネージャに依るのかなーと思ってたんですよ。
ところがどっこい、記事にする為に軽く調査したところ、どうやらそうではなくて、Java のバージョンに依るらしいんですね、これが。
具体的に言うと、Linux にインストールした Java のバージョンが 6 以前の場合のみ、問題が発生するようなのです。
えぇ~......
Java 9 の足音も聞こえてこようかというこの時期に、Java 6 の、しかも Linux でのみ発生する GUI の問題なんて、いったい誰が気にするんだ......
いや、多分、Mac の Java 6 でも再現していたんじゃないかなーという気がするのですが、今日び Apple が用意していた時代の Java 6 が乗ってる Mac なんて、この世にほとんど存在しないでしょー......
こんなどうでもいい情報を必要としている人なんて、世界中を見渡しても、余裕で数えられるくらいしかいないよ、きっと......そりゃ、情報が見当たらないのも納得ですね(笑)
えぇと、気を取り直しまして――
今回見ていくのは、ファイラーからドラッグしたファイル(複数可)を Java のウィンドウにドロップする方法なので、正確に言うならば、実装的にはドロップだけになります。
自分でもちょっと引くくらい、恐ろしくニッチなネタですが、運悪く開いてしまった方は、折角なので興味が無くてもお付き合いください(強制)
読み込み中です。少々お待ち下さい
まずは、一般的な書き方から
まずは、上で触れた問題が発生してしまう、よく見かける書き方を以下に示します。
ちなみに、Java 6(JDK/JRE 1.6)以降対応です。
DragAndDropSample.java
- import java.awt.BorderLayout;
- import java.awt.Font;
- import java.awt.datatransfer.DataFlavor;
- import java.io.File;
- import java.util.List;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.SwingConstants;
- import javax.swing.TransferHandler;
- public class DragAndDropSample extends JFrame {
- @Override protected void frameInit() {
- super.frameInit();
- this.setSize(480, 320);
- this.setLocationByPlatform(true);
- this.setTitle(this.getClass().getName());
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- JLabel label = new JLabel("Drop files here.", SwingConstants.CENTER);
- label.setFont(new Font(Font.DIALOG, Font.BOLD, 48));
- this.add(label, BorderLayout.CENTER);
- this.setTransferHandler(new TransferHandler() {
- @Override public boolean importData(TransferSupport support) {
- try {
- if (canImport(support)) {
- if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
- Object list = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
- if (list instanceof List<?>) {
- for (Object o : ((List<?>)list)) {
- if (o instanceof File) {
- System.out.println(((File)o).getAbsolutePath());
- }
- }
- }
- return true;
- }
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- return false;
- }
- @Override public boolean canImport(TransferSupport support) {
- return (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor));
- }
- });
- }
-
- public static void main(String... args) {
- (new DragAndDropSample()).setVisible(true);
- }
- }
23 行目からはじまる setTransferHandler 以外の部分はどうでもいいので、背景色を変えてあります。
まー、普通だったら、こんな風に書いたコードを実行して、ウィンドウにファイルをドロップすれば、そのパスが標準出力に出力される訳です。
ところが、Linux の Java 6 だと、ドロップが上手く処理されません(つまり、標準出力に何も出力されません)。
これは何故かといいますと、実はイベント自体が発生していない訳ではなく、TransferSupport の isDataFlavorSupported が DataFlavor.javaFileListFlavor を引数に与えると、Linux の Java 6 環境では false を返すからです。
ならば、どうするかというと、引数に DataFlavor.stringFlavor を指定してあげると良いでしょう(Mime-Type は text/url-list。ちなみに、DataFlavor.javaFileListFlavor の Mime-Type は application/x-java-file-list です)。
Linux の Java 6 対応(Swing 版)
つまり、こんな風に書き直します。
DragAndDropSwingSample.java
- import java.awt.BorderLayout;
- import java.awt.Font;
- import java.awt.datatransfer.DataFlavor;
- import java.io.File;
- import java.net.URL;
- import java.net.URLDecoder;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.StringTokenizer;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.SwingConstants;
- import javax.swing.TransferHandler;
- public class DragAndDropSwingSample extends JFrame {
- @Override protected void frameInit() {
- super.frameInit();
- this.setSize(480, 320);
- this.setLocationByPlatform(true);
- this.setTitle(this.getClass().getName());
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- JLabel label = new JLabel("Drop files here.", SwingConstants.CENTER);
- label.setFont(new Font(Font.DIALOG, Font.BOLD, 48));
- this.add(label, BorderLayout.CENTER);
- this.setTransferHandler(new TransferHandler() {
- @Override public boolean importData(TransferSupport support) {
- try {
- if (canImport(support)) {
- List<File> files = new LinkedList<File>();
- if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
- Object list = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
- if (list instanceof List<?>) {
- for (Object o : ((List<?>)list)) {
- if (o instanceof File) {
- files.add((File)o);
- }
- }
- }
- } else if (support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
- // for Linux (Java 6 or lower)
- StringTokenizer tokens = new StringTokenizer((String)support.getTransferable().getTransferData(DataFlavor.stringFlavor));
- while (tokens.hasMoreTokens()) {
- try {
- files.add(new File(URLDecoder.decode((new URL(tokens.nextToken()).getFile()), "UTF-8")));
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- }
- if (files.size() > 0) {
- for (File file : files) {
- System.out.println(file.getAbsolutePath());
- }
- return true;
- }
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- return false;
- }
- @Override public boolean canImport(TransferSupport support) {
- if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
- return true;
- }
- // for Linux (Java 6 or lower)
- boolean importable = false;
- if (support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
- for (DataFlavor flavor : support.getDataFlavors()) {
- if (flavor.getSubType().equals("uri-list")) {
- importable = true;
- break;
- }
- }
- }
- return importable;
- }
- });
- }
-
- public static void main(String... args) {
- (new DragAndDropSwingSample()).setVisible(true);
- }
- }
DataFlavor.stringFlavor なので、SubType の url-list だけで条件判断していますが、気になる方は getPrimaryType も併用するか、若しくは getMimeType を使ってください。
で、こんな風に書くと、以下のように Linux の Java 6 でも、ドロップを処理できるようになります。
Linux の Java 6 対応(AWT 版)
世に出回っている D&D サンプルコードのほとんどが Swing のような気がするので、AWT 版も置いておきます(より一層、誰も必要としていない気がしますが)。
DragAndDropAWTSample.java
- import java.awt.BorderLayout;
- import java.awt.Font;
- import java.awt.Frame;
- import java.awt.Label;
- import java.awt.datatransfer.DataFlavor;
- import java.awt.dnd.DnDConstants;
- import java.awt.dnd.DropTarget;
- import java.awt.dnd.DropTargetDropEvent;
- import java.awt.event.WindowEvent;
- import java.awt.event.WindowListener;
- import java.io.File;
- import java.net.URL;
- import java.net.URLDecoder;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.StringTokenizer;
- public class DragAndDropAWTSample extends Frame {
- public DragAndDropAWTSample() {
- this.setSize(480, 320);
- this.setLocationByPlatform(true);
- this.setTitle(this.getClass().getName());
- Label label = new Label("Drop files here.", Label.CENTER);
- label.setFont(new Font(Font.DIALOG, Font.BOLD, 48));
- this.add(label, BorderLayout.CENTER);
- this.setDropTarget(new DropTarget() {
- @Override public void drop(DropTargetDropEvent e) {
- e.acceptDrop(DnDConstants.ACTION_REFERENCE);
- try {
- List<File> files = new LinkedList<File>();
- if (e.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
- Object list = e.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
- if (list instanceof List<?>) {
- for (Object o : ((List<?>)list)) {
- if (o instanceof File) {
- files.add((File)o);
- }
- }
- }
- } else if (e.isDataFlavorSupported(DataFlavor.stringFlavor)) {
- // for Linux (Java 6 or lower)
- for (DataFlavor flavor : e.getTransferable().getTransferDataFlavors()) {
- if (flavor.getSubType().equals("uri-list")) {
- StringTokenizer tokens = new StringTokenizer((String)e.getTransferable().getTransferData(DataFlavor.stringFlavor));
- while (tokens.hasMoreTokens()) {
- try {
- files.add(new File(URLDecoder.decode((new URL(tokens.nextToken()).getFile()), "UTF-8")));
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- break;
- }
- }
- }
- if (files.size() > 0) {
- for (File file : files) {
- System.out.println(file.getAbsolutePath());
- }
- }
- } catch (Throwable t) {
- t.printStackTrace();
- }
- }
- });
- final Frame frame = this;
- this.addWindowListener(new WindowListener() {
- @Override public void windowActivated(WindowEvent e) { }
- @Override public void windowClosed(WindowEvent e) { System.exit(0); }
- @Override public void windowClosing(WindowEvent e) { frame.dispose(); }
- @Override public void windowDeactivated(WindowEvent e) { }
- @Override public void windowDeiconified(WindowEvent e) { }
- @Override public void windowIconified(WindowEvent e) { }
- @Override public void windowOpened(WindowEvent e) { }
- });
- }
-
- public static void main(String... args) {
- (new DragAndDropAWTSample()).setVisible(true);
- }
- }
おわりに
ヤケクソ気味に、Stream API とラムダ式を使った Java 8 っぽいサンプルも用意しようかと思ったのですが、書いてみたらあまりにも無意味過ぎたので、危うく思いとどまりました。
ホントに、いったい誰得なんだ、この記事......
ちなみに、上で動作確認している Linux は、Debian 8.0 "jessie" になります。
もしかしたら、Java 6 でも一番目のコードが問題無く動作する Linux 環境もあるかも知れないことはお断りしておきます。
ますます、誰得なんだ......