/* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.draganddrop; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.factory.ControlsFactory; import javafx.factory.NodeFactory; import javafx.factory.Panes; import javafx.factory.Shapes; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.scene.input.DataFormat; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.MouseEvent; import javafx.scene.input.TransferMode; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.stage.Stage; import javafx.util.Pair; import static org.junit.Assert.*; import test.javaclient.shared.InteroperabilityApp; import static test.javaclient.shared.TestUtil.isEmbedded; import test.javaclient.shared.Utils; public class DragDropWithControls extends InteroperabilityApp { final public static String PARAMETER_ONLY_SOURCE_STAGE = "onlySource"; final public static String PARAMETER_ONLY_TARGET_STAGE = "onlyTarget"; final static String TITLE_TARGET_STAGE = "Target"; final static String TITLE_SOURCE_STAGE = "Source"; final static String ID_NODE_CHOOSER = "nodeChooser"; final static String ID_DRAG_SOURCE = "from"; final static String ID_DRAG_TARGET = "to"; final static String ID_TO_CLIPBOARD_BUTTON = "toClipboardButton"; final static String ID_FROM_CLIPBOARD_BUTTON = "fromClipboardButton"; final static String ID_PLAIN_TEXT = "PLAIN_TEXT"; final static String ID_HTML = "HTML"; final static String ID_IMAGE = "IMAGE"; final static String ID_RTF = "RTF"; final static String ID_URL = "URL"; final static String ID_FILES = "Files"; final static String ID_CUSTOM_BYTES = "Custom (bytes)"; final static String ID_CUSTOM_STRING = "Custom (string)"; final static String ID_CUSTOM_CLASS = "Custom (class)"; final static String ID_RECEIVED_IMAGE = "ReceivedImage"; final static String ID_SRC_IMAGE = "SrcImage"; public final static DataFormat DF_CUSTOM_BYTES = new DataFormat("dndwithcontrols.custom.bytes"); public final static DataFormat DF_CUSTOM_STRING = new DataFormat("dndwithcontrols.custom.string"); public final static DataFormat DF_CUSTOM_CLASS = new DataFormat("dndwithcontrols.custom.class"); final static String CONTENT_PLAIN_TEXT = "Hello!!!"; final static String CONTENT_URL = "http://www.oracle.com"; private static Image CONTENT_IMAGE; final static String CONTENT_HTML = "" + "" + "" + "Hello!" + ""; final static String CONTENT_RTF = "{\\rtf1 This is some {\\b bold} text.\\par}"; final static String CONTENT_CUSTOM_STRING = "Hello Custom String!"; final static List CONTENT_FILES = new LinkedList(); private Scene leftScene; private Scene rightScene; static { CONTENT_FILES.add(new File("./content/index.html")); System.setProperty("prism.lcdtext", "false"); } final static byte[] CONTENT_CUSTOM_BYTES = new byte[]{1, 2, 3, 4}; final static SerializableClass CONTENT_CUSTOM_CLASS = new SerializableClass(new SerializableClass(100, 100.9), 10, 10.9); final static Map receivedContent = new ConcurrentHashMap(); public final static Map> dataFormatToCheckBoxID = new HashMap>(10); static { dataFormatToCheckBoxID.put(DataFormat.PLAIN_TEXT, new Pair(ID_PLAIN_TEXT, CONTENT_PLAIN_TEXT)); dataFormatToCheckBoxID.put(DataFormat.HTML, new Pair(ID_HTML, CONTENT_HTML)); dataFormatToCheckBoxID.put(DataFormat.RTF, new Pair(ID_RTF, CONTENT_RTF)); dataFormatToCheckBoxID.put(DataFormat.URL, new Pair(ID_URL, CONTENT_URL)); dataFormatToCheckBoxID.put(DataFormat.FILES, new Pair(ID_FILES, CONTENT_FILES)); dataFormatToCheckBoxID.put(DF_CUSTOM_BYTES, new Pair(ID_CUSTOM_BYTES, CONTENT_CUSTOM_BYTES)); dataFormatToCheckBoxID.put(DF_CUSTOM_STRING, new Pair(ID_CUSTOM_STRING, CONTENT_CUSTOM_STRING)); dataFormatToCheckBoxID.put(DF_CUSTOM_CLASS, new Pair(ID_CUSTOM_CLASS, CONTENT_CUSTOM_CLASS)); InteroperabilityApp.isEmbeddedFullScreenMode = false; } @Override protected Scene getScene() { if (null == CONTENT_IMAGE) { CONTENT_IMAGE = new Image(DragDropWithControls.class.getResource("JavaFX.png").toExternalForm()); dataFormatToCheckBoxID.put(DataFormat.IMAGE, new Pair(ID_IMAGE, CONTENT_IMAGE)); } Parameters params = getParameters(); parameters = params == null ? Collections.emptyList() : params.getRaw(); if (parameters.size() > 1 && parameters.get(1).equals(PARAMETER_ONLY_SOURCE_STAGE)) { leftScene = prepareSourceStage(stage); return leftScene; } else if (parameters.size() > 1 && parameters.get(1).equals(PARAMETER_ONLY_TARGET_STAGE)) { rightScene = prepareTargetStage(secondaryStage); return rightScene; } else { leftScene = prepareSourceStage(stage); secondaryStage = new Stage(); rightScene = prepareTargetStage(secondaryStage); return leftScene; } } @Override protected String getFirstStageName() { return TITLE_SOURCE_STAGE; } @Override protected StageInfo getSecondaryScene() { return new StageInfo(new Callable() { public Scene call() throws Exception { return rightScene; } }, isEmbedded() ? 510 : 670 + (Utils.isLinux() ? 70 : 0), 30, TITLE_TARGET_STAGE); } @Override protected boolean hasSecondaryScene() { return leftScene != null && rightScene != null; } private static class SerializableClass implements Serializable { public SerializableClass() { } public SerializableClass(SerializableClass clazz, int i, double d) { this(i, d); this.clazz = clazz; } public SerializableClass(int i, double d) { this.i = i; this.d = d; } SerializableClass clazz; int i; double d; @Override public String toString() { return "Class{" + " clazz=" + clazz + ", i=" + i + ", d=" + d + "}"; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final SerializableClass other = (SerializableClass) obj; if (this.clazz != other.clazz && (this.clazz == null || !this.clazz.equals(other.clazz))) { return false; } if (this.i != other.i) { return false; } if (Double.doubleToLongBits(this.d) != Double.doubleToLongBits(other.d)) { return false; } return true; } @Override public int hashCode() { int hash = 5; hash = 17 * hash + (this.clazz != null ? this.clazz.hashCode() : 0); hash = 17 * hash + this.i; hash = 17 * hash + (int) (Double.doubleToLongBits(this.d) ^ (Double.doubleToLongBits(this.d) >>> 32)); return hash; } } Pane sourceControlPane = new StackPane() { { setStyle("-fx-border-color:green;-fx-border-width: 2px;"); } }; Pane targetControlPane = new StackPane() { { setStyle("-fx-border-color:red;-fx-border-width: 2px;"); } }; Pane transferedContentPane = new VBox(); public Set sourceModes = EnumSet.noneOf(TransferMode.class); public Set targetModes = EnumSet.noneOf(TransferMode.class); Set sourceFormats = new HashSet(); Set targetFormats = new HashSet(); static List eventList = new ArrayList(); Text log = new Text(); CheckBox useCustomViewCB; List messages = new LinkedList(); List parameters; private Scene prepareTargetStage(Stage stageTarget) { final Scene localScene = new Scene(createRightPane(), isEmbedded() ? 420 : 630, 700); localScene.getRoot().setId(TITLE_TARGET_STAGE); if (stageTarget != null) { stageTarget.setTitle(TITLE_TARGET_STAGE); stageTarget.setScene(localScene); stageTarget.setX(680); stageTarget.setY(30); } return localScene; } private Scene prepareSourceStage(final Stage stageSource) { final Scene localScene = new Scene(createLeftPane(), isEmbedded() ? 420 : 580, 700); localScene.getRoot().setId(TITLE_SOURCE_STAGE); if (stageSource != null) { stageSource.setTitle(TITLE_SOURCE_STAGE); stageSource.setScene(localScene); stageSource.setX(0); stageSource.setY(30); } return localScene; } private Parent createLeftPane() { VBox lbox = new VBox(10); lbox.getChildren().addAll(sourceControlPane, new Separator(), new Text("Source control type:"), createControlCombo(sourceControlPane, true), new Text("Source transfer modes:"), createTMSelect(sourceModes)); VBox rbox = new VBox(10); Button b = new Button("Put to clipboard"); b.setId(ID_TO_CLIPBOARD_BUTTON); b.setOnAction(new EventHandler() { public void handle(ActionEvent t) { Clipboard.getSystemClipboard().setContent(prepareClipboardContent()); } }); rbox.getChildren().addAll(new Text("Data formats:"), createFormatSelect(sourceFormats), b); HBox hbox = new HBox(10); hbox.getChildren().addAll(lbox, new Separator(Orientation.VERTICAL), rbox); final Text fileHdr = new Text("Files to drag (1):"); final Text fileNames = new Text(); showFileNames(fileNames); final TextField tb = new TextField("Put full path here"); final Button add = new Button("Add"); add.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { File f = new File(tb.getText()); if (f.exists()) { CONTENT_FILES.add(f); tb.setText(""); fileHdr.setText("Files to drag (" + CONTENT_FILES.size() + ")"); showFileNames(fileNames); log("Added file " + f.getPath()); } else { log("File doesn't exist: " + f.getPath()); } } }); final Button clear = new Button("Clear"); clear.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { CONTENT_FILES.clear(); fileHdr.setText("Files to drag (0)"); log("File list cleared"); } }); HBox btns = new HBox(); btns.getChildren().addAll(add, clear); useCustomViewCB = new CheckBox("Use custom drag view."); VBox box = new VBox(10); ImageView i = new ImageView(); i.setImage(CONTENT_IMAGE); i.setId(ID_SRC_IMAGE); box.getChildren().addAll(hbox, new Separator(), fileHdr, fileNames, tb, btns, useCustomViewCB, new Text("Image: "), i, log); if (parameters.size() > 0) { box.setStyle("-fx-background-color: " + parameters.get(0) + ";"); } return box; } private void showFileNames(Text text) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < CONTENT_FILES.size(); i++) { File file = CONTENT_FILES.get(i); sb.append(i + 1).append(": ").append(file.toURI()).append("\n"); } text.setText(sb.toString()); } private Parent createRightPane() { HBox hbox = new HBox(10); VBox lbox = new VBox(10); lbox.getChildren().addAll(targetControlPane, new Separator(), new Text("Target control type:"), createControlCombo(targetControlPane, false), new Text("Target transfer modes:"), createTMSelect(targetModes)); VBox rbox = new VBox(10); Button b = new Button("paste from clipboard"); b.setId(ID_FROM_CLIPBOARD_BUTTON); b.setOnAction(new EventHandler() { public void handle(ActionEvent t) { getDataFromClipboard(Clipboard.getSystemClipboard()); } }); rbox.getChildren().addAll(new Text("Data formats:"), createFormatSelect(targetFormats), b); VBox content = new VBox(10); content.getChildren().addAll(new Text("Transfered content:"), transferedContentPane); hbox.getChildren().addAll(lbox, new Separator(Orientation.VERTICAL), rbox, new Separator(Orientation.VERTICAL), content); if (parameters.size() > 0) { hbox.setStyle("-fx-background-color: " + parameters.get(0) + ";"); } return hbox; } private Node createControlCombo(final Pane sourceControlPane, final boolean source) { ChoiceBox cb = new ChoiceBox(); cb.setId(ID_NODE_CHOOSER); cb.getItems().addAll(ControlsFactory.filteredValues()); cb.getItems().addAll(Shapes.values()); cb.getItems().addAll(Panes.values()); cb.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, NodeFactory t, NodeFactory t1) { Node ctrl = null; ctrl = t1.createNode(); if (ctrl instanceof ImageView) { ctrl.setPickOnBounds(true); } if (source) { ctrl.setId(ID_DRAG_SOURCE); } else { ctrl.setId(ID_DRAG_TARGET); } eventList.clear(); sourceControlPane.getChildren().clear(); sourceControlPane.getChildren().add(ctrl); final Node control = ctrl; if (source) { System.out.println("Source control : " + control); control.setOnDragDetected(new EventHandler() { @Override public void handle(MouseEvent event) { eventList.add(DragEvents.DRAG_DETECTED); System.out.println("DragDetected"); Dragboard db = control.startDragAndDrop(sourceModes.toArray(new TransferMode[sourceModes.size()])); if (db == null) { log("Cannot start drag and drop."); return; } System.out.println("db " + db); final ClipboardContent prepareClipboardContent = prepareClipboardContent(); System.out.println("prepareClipboardContent " + prepareClipboardContent); db.setContent(prepareClipboardContent); if (useCustomViewCB.isSelected()) { final int const1 = 100; final int const2 = 100; db.setDragView(CONTENT_IMAGE, const1, const2); //Assertions here normally prevent DnD, and don't crash the app. assertEquals("OffsetX equals", db.getDragViewOffsetX(), const1, 0); assertEquals("OffsetY equals", db.getDragViewOffsetY(), const2, 0); assertEquals("Image correct get", db.getDragView(), CONTENT_IMAGE); } event.consume(); } }); control.setOnDragDone(new EventHandler() { @Override public void handle(DragEvent event) { System.out.println("DragDone"); if (event.getTransferMode() == null) { eventList.add(DragEvents.EMPTY_DRAG_DONE); } eventList.add(DragEvents.DRAG_DONE); log("Transfer done: " + event.getTransferMode()); log(""); } }); } else { System.out.println("Target Control : " + control); control.setOnDragEntered(new EventHandler() { public void handle(DragEvent t) { System.out.println("DragEntered"); eventList.add(DragEvents.DRAG_ENTER); } }); control.setOnDragOver(new EventHandler() { @Override public void handle(DragEvent event) { System.out.println("DragOver"); eventList.add(DragEvents.DRAG_OVER); Dragboard db = event.getDragboard(); for (DataFormat df : targetFormats) { if (db.hasContent(df)) { event.acceptTransferModes( targetModes.toArray(new TransferMode[targetModes.size()])); return; } } } }); control.setOnDragDropped(new EventHandler() { @Override public void handle(DragEvent event) { System.out.println("DragDropped"); eventList.add(DragEvents.DRAG_DROPPED); Dragboard db = event.getDragboard(); boolean gotData = getDataFromClipboard(db); event.setDropCompleted(gotData); } }); } } }); cb.getSelectionModel().select(0); return cb; } private ClipboardContent prepareClipboardContent() { ClipboardContent content = new ClipboardContent(); if (sourceFormats.contains(DataFormat.PLAIN_TEXT)) { log("Source is putting string on dragboard"); content.putString(CONTENT_PLAIN_TEXT); } if (sourceFormats.contains(DataFormat.URL)) { log("Source is putting URL on dragboard"); content.putUrl(CONTENT_URL); } if (sourceFormats.contains(DataFormat.IMAGE)) { log("Source is putting image on dragboard"); content.putImage(CONTENT_IMAGE); } if (sourceFormats.contains(DataFormat.HTML)) { log("Source is putting HTML on dragboard"); content.putHtml(CONTENT_HTML); } if (sourceFormats.contains(DataFormat.RTF)) { log("Source is putting RTF on dragboard"); content.putRtf(CONTENT_RTF); } if (sourceFormats.contains(DF_CUSTOM_BYTES)) { log("Source is putting custom four bytes on dragboard"); content.put(DF_CUSTOM_BYTES, CONTENT_CUSTOM_BYTES); } if (sourceFormats.contains(DF_CUSTOM_STRING)) { log("Source is putting custom four bytes on dragboard"); content.put(DF_CUSTOM_STRING, CONTENT_CUSTOM_STRING); } if (sourceFormats.contains(DF_CUSTOM_CLASS)) { log("Source is putting custom class on dragboard"); content.put(DF_CUSTOM_CLASS, CONTENT_CUSTOM_CLASS); } if (sourceFormats.contains(DataFormat.FILES)) { log("Source is putting two files on dragboard"); content.putFiles(CONTENT_FILES); } return content; } private boolean getDataFromClipboard(Clipboard cb) { boolean gotData = false; receivedContent.clear(); transferedContentPane.getChildren().clear(); if (targetFormats.contains(DataFormat.PLAIN_TEXT) && cb.hasString()) { receivedContent.put(DataFormat.PLAIN_TEXT, cb.getString()); transferedContentPane.getChildren().addAll(new WrappedLabel("String: " + cb.getString())); log("Dropped string: " + cb.getString()); gotData = true; } if (targetFormats.contains(DataFormat.HTML) && cb.hasHtml()) { receivedContent.put(DataFormat.HTML, cb.getHtml()); transferedContentPane.getChildren().addAll(new WrappedLabel("Html: " + cb.getHtml())); log("Dropped HTML: " + cb.getHtml()); gotData = true; } if (targetFormats.contains(DataFormat.RTF) && cb.hasRtf()) { receivedContent.put(DataFormat.RTF, cb.getRtf()); transferedContentPane.getChildren().addAll(new WrappedLabel("Rtf: " + cb.getRtf())); log("Dropped RTF: " + cb.getRtf()); gotData = true; } if (targetFormats.contains(DataFormat.URL) && cb.hasUrl()) { receivedContent.put(DataFormat.URL, cb.getUrl()); transferedContentPane.getChildren().addAll(new WrappedLabel("Url: " + cb.getUrl())); log("Dropped URL: " + cb.getUrl()); gotData = true; } if (targetFormats.contains(DataFormat.IMAGE) && cb.hasImage()) { receivedContent.put(DataFormat.IMAGE, cb.getImage()); ImageView i = new ImageView(cb.getImage()); i.setId(ID_RECEIVED_IMAGE); transferedContentPane.getChildren().addAll(new Text("Image: "), i); log("Dropped image: " + cb.getImage()); gotData = true; } if (targetFormats.contains(DataFormat.FILES) && cb.hasFiles()) { log("Dropped files:"); receivedContent.put(DataFormat.FILES, cb.getFiles()); transferedContentPane.getChildren().addAll(new WrappedLabel("Files: ")); for (File f : cb.getFiles()) { transferedContentPane.getChildren().addAll(new Label("File: " + f) { { setWrapText(true); } }); log(" " + f.getPath()); } transferedContentPane.getChildren().addAll(new Separator()); gotData = true; } if (targetFormats.contains(DF_CUSTOM_BYTES) && cb.hasContent(DF_CUSTOM_BYTES)) { byte[] b = (byte[]) cb.getContent(DF_CUSTOM_BYTES); receivedContent.put(DF_CUSTOM_BYTES, b); transferedContentPane.getChildren().addAll(new WrappedLabel("bytes: " + b[0] + ", " + b[1] + ", " + b[2] + ", " + b[3])); log("Dropped custom bytes: " + b[0] + ", " + b[1] + ", " + b[2] + ", " + b[3]); gotData = true; } if (targetFormats.contains(DF_CUSTOM_STRING) && cb.hasContent(DF_CUSTOM_STRING)) { receivedContent.put(DF_CUSTOM_STRING, cb.getContent(DF_CUSTOM_STRING)); String s = (String) cb.getContent(DF_CUSTOM_STRING); transferedContentPane.getChildren().addAll(new WrappedLabel("customString: " + s)); log("Dropped custom string: " + s); gotData = true; } if (targetFormats.contains(DF_CUSTOM_CLASS) && cb.hasContent(DF_CUSTOM_CLASS)) { receivedContent.put(DF_CUSTOM_CLASS, cb.getContent(DF_CUSTOM_CLASS)); String s = cb.getContent(DF_CUSTOM_CLASS).toString(); transferedContentPane.getChildren().addAll(new WrappedLabel("customClass: " + s)); log("Dropped custom class: " + s); gotData = true; } return gotData; } private Node createTMSelect(final Set tms) { VBox box = new VBox(); for (final TransferMode tm : TransferMode.values()) { CheckBox cb = new CheckBox(tm.toString()); cb.selectedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, Boolean t, Boolean t1) { if (t1) { tms.add(tm); } else { tms.remove(tm); } } }); if (tm == TransferMode.COPY) { cb.selectedProperty().set(true); } box.getChildren().add(cb); } return box; } private Node createFormatSelect(final Set dataFormats) { VBox box = new VBox(); for (final Map.Entry> df : dataFormatToCheckBoxID.entrySet()) { CheckBox cb = new CheckBox(df.getValue().getKey()); cb.selectedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, Boolean t, Boolean t1) { if (t1) { dataFormats.add(df.getKey()); } else { dataFormats.remove(df.getKey()); } } }); box.getChildren().add(cb); } ((CheckBox) box.getChildren().get(0)).selectedProperty().set(true); return box; } private void log(String text) { System.out.println(text); messages.add(text); if (messages.size() > 15) { messages.remove(0); } StringBuilder sb = new StringBuilder(); for (String msg : messages) { sb.append(msg).append("\n"); } log.setText(sb.toString()); } public static void main(String[] args) { Utils.launch(DragDropWithControls.class, args); } private static class WrappedLabel extends VBox { public WrappedLabel(String string) { getChildren().add(new Label(string) { { setWrapText(true); } }); getChildren().add(new Separator()); } } }