// @formatter:off /* * Copyright (c) 2012, 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.embed.swt; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.IntBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javafx.application.Platform; import javafx.beans.NamedArg; import javafx.scene.Scene; import javafx.scene.input.TransferMode; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.DropTargetListener; import org.eclipse.swt.dnd.FileTransfer; import org.eclipse.swt.dnd.HTMLTransfer; import org.eclipse.swt.dnd.ImageTransfer; import org.eclipse.swt.dnd.RTFTransfer; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TransferData; import org.eclipse.swt.dnd.URLTransfer; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import com.sun.glass.ui.Application; import com.sun.javafx.application.PlatformImpl; import com.sun.javafx.cursor.CursorFrame; import com.sun.javafx.cursor.CursorType; import com.sun.javafx.embed.AbstractEvents; import com.sun.javafx.embed.EmbeddedSceneDSInterface; import com.sun.javafx.embed.EmbeddedSceneDTInterface; import com.sun.javafx.embed.EmbeddedSceneInterface; import com.sun.javafx.embed.EmbeddedStageInterface; import com.sun.javafx.embed.HostInterface; import com.sun.javafx.stage.EmbeddedWindow; /** * {@code FXCanvas} is a component to embed JavaFX content into * SWT applications. The content to be displayed is specified * with the {@link #setScene} method that accepts an instance of * JavaFX {@code Scene}. After the scene is assigned, it gets * repainted automatically. All the input and focus events are * forwarded to the scene transparently to the developer. * <p> * Here is a typical pattern how {@code FXCanvas} can used: * <pre> * public class Test { * private static Scene createScene() { * Group group = new Group(); * Scene scene = new Scene(group); * Button button = new Button("JFX Button"); * group.getChildren().add(button); * return scene; * } * * public static void main(String[] args) { * Display display = new Display(); * Shell shell = new Shell(display); * shell.setLayout(new FillLayout()); * FXCanvas canvas = new FXCanvas(shell, SWT.NONE); * Scene scene = createScene(); * canvas.setScene(scene); * shell.open(); * while (!shell.isDisposed()) { * if (!display.readAndDispatch()) display.sleep(); * } * display.dispose(); * } * } * </pre> * * <ul> * <li>修正DnD錯誤(<a href="https://javafx-jira.kenai.com/browse/RT-39997">JavaFX-JIRA RT-39997</a>)</li> * </ul> * * @since JavaFX 2.0 */ @SuppressWarnings("all") public class FXCanvas extends Canvas { private HostContainer hostContainer; private volatile EmbeddedWindow stage; private volatile Scene scene; private EmbeddedStageInterface stagePeer; private EmbeddedSceneInterface scenePeer; private int pWidth = 0; private int pHeight = 0; private volatile int pPreferredWidth = -1; private volatile int pPreferredHeight = -1; private IntBuffer pixelsBuf = null; // This filter runs when any widget is moved Listener moveFilter = event -> { // If a parent has moved, send a move event to FX Control control = FXCanvas.this; while (control != null) { if (control == event.widget) { this.sendMoveEventToFX(); break; } control = control.getParent(); }; }; private double getScaleFactor() { if (SWT.getPlatform().equals("cocoa")) { if (windowField == null || screenMethod == null || backingScaleFactorMethod == null) { return 1.0; } try { Object nsWindow = windowField.get(this.getShell()); Object nsScreen = screenMethod.invoke(nsWindow); Object bsFactor = backingScaleFactorMethod.invoke(nsScreen); return ((Double) bsFactor).doubleValue(); } catch (Exception e) { // FAIL silently should the reflection fail } } return 1.0; } private DropTarget dropTarget; static Transfer [] StandardTransfers = new Transfer [] { TextTransfer.getInstance(), RTFTransfer.getInstance(), HTMLTransfer.getInstance(), URLTransfer.getInstance(), ImageTransfer.getInstance(), FileTransfer.getInstance(), }; static Transfer [] CustomTransfers = new Transfer [0]; static Transfer [] getAllTransfers () { Transfer [] transfers = new Transfer[StandardTransfers.length + CustomTransfers.length]; System.arraycopy(StandardTransfers, 0, transfers, 0, StandardTransfers.length); System.arraycopy(CustomTransfers, 0, transfers, StandardTransfers.length, CustomTransfers.length); return transfers; } static Transfer getCustomTransfer(String mime) { for (int i=0; i<CustomTransfers.length; i++) { if (((CustomTransfer)CustomTransfers[i]).getMime().equals(mime)) { return CustomTransfers[i]; } } Transfer transfer = new CustomTransfer (mime, mime); Transfer [] newCustom = new Transfer [CustomTransfers.length + 1]; System.arraycopy(CustomTransfers, 0, newCustom, 0, CustomTransfers.length); newCustom[CustomTransfers.length] = transfer; CustomTransfers = newCustom; return transfer; } private static Field windowField; private static Method windowMethod; private static Method screenMethod; private static Method backingScaleFactorMethod; static { if (SWT.getPlatform().equals("cocoa")) { try { windowField = Shell.class.getDeclaredField("window"); windowField.setAccessible(true); Class nsViewClass = Class.forName("org.eclipse.swt.internal.cocoa.NSView"); windowMethod = nsViewClass.getDeclaredMethod("window"); windowMethod.setAccessible(true); Class nsWindowClass = Class.forName("org.eclipse.swt.internal.cocoa.NSWindow"); screenMethod = nsWindowClass.getDeclaredMethod("screen"); screenMethod.setAccessible(true); Class nsScreenClass = Class.forName("org.eclipse.swt.internal.cocoa.NSScreen"); backingScaleFactorMethod = nsScreenClass.getDeclaredMethod("backingScaleFactor"); backingScaleFactorMethod.setAccessible(true); } catch (Exception e) { //Fail silently. If we can't get the methods, then the current version of SWT has no retina support } } } /** * @inheritDoc * @deprecated use {@link FXCanvas2} instead */ @Deprecated public FXCanvas(@NamedArg("parent") Composite parent, @NamedArg("style") int style) { super(parent, style | SWT.NO_BACKGROUND); initFx(); this.hostContainer = new HostContainer(); this.registerEventListeners(); Display display = parent.getDisplay(); display.addFilter(SWT.Move, this.moveFilter); } private static void initFx() { AccessController.doPrivileged((PrivilegedAction<Void>) () -> { System.setProperty("javafx.embed.isEventThread", "true"); return null; }); Map map = Application.getDeviceDetails(); if (map == null) { Application.setDeviceDetails(map = new HashMap()); } if (map.get("javafx.embed.eventProc") == null) { long eventProc = 0; try { Field field = Display.class.getDeclaredField("eventProc"); field.setAccessible(true); if (field.getType() == int.class) { eventProc = field.getLong(Display.getDefault()); } else { if (field.getType() == long.class) { eventProc = field.getLong(Display.getDefault()); } } } catch (Throwable th) { //Fail silently } map.put("javafx.embed.eventProc", eventProc); } // Note that calling PlatformImpl.startup more than once is OK PlatformImpl.startup(() -> { Application.GetApplication().setName(Display.getAppName()); }); } static ArrayList<DropTarget> targets = new ArrayList<>(); DropTarget getDropTarget() { return this.dropTarget; } void setDropTarget(DropTarget newTarget) { if (this.dropTarget != null) { targets.remove(this.dropTarget); this.dropTarget.dispose(); } this.dropTarget = newTarget; if (this.dropTarget != null) { targets.add(this.dropTarget); } } static void updateDropTarget() { // Update all drop targets rather than just this target // // In order for a drop target to recognise a custom format, // the format must be registered and the transfer type added // to the list of transfers that the target accepts. This // must happen before the drag and drop operations starts // or the drop target will not accept the format. Therefore, // set all transfers for all targets before any drag and drop // operation starts // for (DropTarget target : targets) { target.setTransfer(getAllTransfers()); } } /** * {@inheritDoc} */ @Override public Point computeSize (int wHint, int hHint, boolean changed) { this.checkWidget(); if (wHint == -1 && hHint == -1) { if (this.pPreferredWidth != -1 && this.pPreferredHeight != -1) { return new Point (this.pPreferredWidth, this.pPreferredHeight); } } return super.computeSize(wHint, hHint, changed); } /** * Returns the JavaFX scene attached to this {@code FXCanvas}. * * @return the {@code Scene} attached to this {@code FXCanvas} */ public Scene getScene() { this.checkWidget(); return this.scene; } /** * Attaches a {@code Scene} object to display in this {@code * FXCanvas}. This method must called either on the JavaFX * JavaFX application thread (which is the same as the SWT * event dispatch thread). * * @param newScene a scene to display in this {@code FXCanvas} * * @see javafx.application.Platform#isFxApplicationThread() */ public void setScene(final Scene newScene) { this.checkWidget(); if ((this.stage == null) && (newScene != null)) { this.stage = new EmbeddedWindow(this.hostContainer); this.stage.show(); } this.scene = newScene; if (this.stage != null) { this.stage.setScene(newScene); } if ((this.stage != null) && (newScene == null)) { this.stage.hide(); this.stage = null; } } // Note that removing the listeners is unnecessary private void registerEventListeners() { this.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent de) { Display display = FXCanvas.this.getDisplay(); display.removeFilter(SWT.Move, FXCanvas.this.moveFilter); FXCanvas.this.widgetDisposed(de); } }); this.addPaintListener(pe -> { FXCanvas.this.paintControl(pe); }); this.addMouseListener(new MouseListener() { @Override public void mouseDoubleClick(MouseEvent me) { // Clicks and double-clicks are handled in FX } @Override public void mouseDown(MouseEvent me) { // FX only supports 3 buttons so don't send the event for other buttons if (me.button > 3) return; FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_PRESSED); } @Override public void mouseUp(MouseEvent me) { // FX only supports 3 buttons so don't send the event for other buttons if (me.button > 3) return; FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_RELEASED); } }); this.addMouseMoveListener(me -> { if ((me.stateMask & SWT.BUTTON_MASK) != 0) { // FX only supports 3 buttons so don't send the event for other buttons if ((me.stateMask & (SWT.BUTTON1 | SWT.BUTTON2 | SWT.BUTTON3)) != 0) { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_DRAGGED); } else { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED); } } else { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED); } }); this.addMouseWheelListener(me -> { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_WHEEL); }); this.addMouseTrackListener(new MouseTrackListener() { @Override public void mouseEnter(MouseEvent me) { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_ENTERED); } @Override public void mouseExit(MouseEvent me) { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_EXITED); } @Override public void mouseHover(MouseEvent me) { // No mouse hovering in FX } }); this.addControlListener(new ControlListener() { @Override public void controlMoved(ControlEvent ce) { FXCanvas.this.sendMoveEventToFX(); } @Override public void controlResized(ControlEvent ce) { FXCanvas.this.sendResizeEventToFX(); } }); this.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent fe) { FXCanvas.this.sendFocusEventToFX(fe, true); } @Override public void focusLost(FocusEvent fe) { FXCanvas.this.sendFocusEventToFX(fe, false); } }); this.addKeyListener(new KeyListener() { @Override public void keyPressed(KeyEvent e) { FXCanvas.this.sendKeyEventToFX(e, SWT.KeyDown); } @Override public void keyReleased(KeyEvent e) { FXCanvas.this.sendKeyEventToFX(e, SWT.KeyUp); } }); this.addMenuDetectListener(e -> { Runnable r = () -> { if (this.isDisposed()) return; FXCanvas.this.sendMenuEventToFX(e); }; // In SWT, MenuDetect comes before the equivalent mouse event // On Mac, the order is MenuDetect, MouseDown, MouseUp. FX // does not expect this order and when it gets the MouseDown, // it closes the menu. The fix is to defer the MenuDetect // notification until after the MouseDown is sent to FX. if ("cocoa".equals(SWT.getPlatform())) { this.getDisplay().asyncExec(r); } else { r.run(); } }); } private void widgetDisposed(DisposeEvent de) { this.setDropTarget(null); if (this.stage != null) { this.stage.hide(); } } double lastScaleFactor = 1.0; int lastWidth, lastHeight; IntBuffer lastPixelsBuf = null; private void paintControl(PaintEvent pe) { if ((this.scenePeer == null) || (this.pixelsBuf == null)) { return; } double scaleFactor = this.getScaleFactor(); if (this.lastScaleFactor != scaleFactor) { this.resizePixelBuffer(scaleFactor); this.lastScaleFactor = scaleFactor; this.scenePeer.setPixelScaleFactor((float)scaleFactor); } // if we can't get the pixels, draw the bits that were there before IntBuffer buffer = this.pixelsBuf; int width = this.pWidth, height = this.pHeight; if (this.scenePeer.getPixels(this.pixelsBuf, this.pWidth, this.pHeight)) { width = this.lastWidth = this.pWidth; height = this.lastHeight = this.pHeight; buffer = this.lastPixelsBuf = this.pixelsBuf; } else { if (this.lastPixelsBuf == null) return; width = this.lastWidth; height = this.lastHeight; buffer = this.lastPixelsBuf; } width = (int)Math.round(width * scaleFactor); height = (int)Math.round(height * scaleFactor); // Consider optimizing this ImageData imageData = null; if ("win32".equals(SWT.getPlatform())) { PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000); int scanline = width * 4; byte[] dstData = new byte[scanline * height]; int[] srcData = buffer.array(); int dp = 0, sp = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int p = srcData[sp++]; dstData[dp++] = (byte) (p & 0xFF); //dst:blue dstData[dp++] = (byte)((p >> 8) & 0xFF); //dst:green dstData[dp++] = (byte)((p >> 16) & 0xFF); //dst:green dstData[dp++] = (byte)0x00; //alpha } } /*ImageData*/ imageData = new ImageData(width, height, 32, palette, 4, dstData); } else { if (width * height > buffer.array().length) { // We shouldn't be here... System.err.println("FXCanvas.paintControl: scale mismatch!"); return; } PaletteData palette = new PaletteData(0x00ff0000, 0x0000ff00, 0x000000ff); /*ImageData*/ imageData = new ImageData(width, height, 32, palette); imageData.setPixels(0, 0,width * height, buffer.array(), 0); } Image image = new Image(Display.getDefault(), imageData); pe.gc.drawImage(image, 0, 0, width, height, 0, 0, this.pWidth, this.pHeight); image.dispose(); } private void sendMoveEventToFX() { if ((this.stagePeer == null) /*|| !isShowing()*/) { return; } Rectangle rect = this.getClientArea(); Point los = this.toDisplay(rect.x, rect.y); this.stagePeer.setLocation(los.x, los.y); } private void sendMouseEventToFX(MouseEvent me, int embedMouseType) { if (this.scenePeer == null) { return; } Point los = this.toDisplay(me.x, me.y); boolean primaryBtnDown = (me.stateMask & SWT.BUTTON1) != 0; boolean middleBtnDown = (me.stateMask & SWT.BUTTON2) != 0; boolean secondaryBtnDown = (me.stateMask & SWT.BUTTON3) != 0; boolean shift = (me.stateMask & SWT.SHIFT) != 0; boolean control = (me.stateMask & SWT.CONTROL) != 0; boolean alt = (me.stateMask & SWT.ALT) != 0; boolean meta = (me.stateMask & SWT.COMMAND) != 0; switch (embedMouseType) { case AbstractEvents.MOUSEEVENT_PRESSED: primaryBtnDown |= me.button == 1; middleBtnDown |= me.button == 2; secondaryBtnDown |= me.button == 3; break; case AbstractEvents.MOUSEEVENT_RELEASED: primaryBtnDown &= me.button != 1; middleBtnDown &= me.button != 2; secondaryBtnDown &= me.button != 3; break; case AbstractEvents.MOUSEEVENT_CLICKED: // Don't send click events to FX, as they are generated in Scene return; default: break; } this.scenePeer.mouseEvent( embedMouseType, SWTEvents.mouseButtonToEmbedMouseButton(me.button, me.stateMask), primaryBtnDown, middleBtnDown, secondaryBtnDown, me.x, me.y, los.x, los.y, shift, control, alt, meta, SWTEvents.getWheelRotation(me, embedMouseType), false); // RT-32990: popup trigger not implemented } private void sendKeyEventToFX(final KeyEvent e, int type) { if (this.scenePeer == null /*|| !isFxEnabled()*/) { return; } int stateMask = e.stateMask; if (type == SWT.KeyDown) { if (e.keyCode == SWT.SHIFT) stateMask |= SWT.SHIFT; if (e.keyCode == SWT.CONTROL) stateMask |= SWT.CONTROL; if (e.keyCode == SWT.ALT) stateMask |= SWT.ALT; if (e.keyCode == SWT.COMMAND) stateMask |= SWT.COMMAND; } else { if (e.keyCode == SWT.SHIFT) stateMask &= ~SWT.SHIFT; if (e.keyCode == SWT.CONTROL) stateMask &= ~SWT.CONTROL; if (e.keyCode == SWT.ALT) stateMask &= ~SWT.ALT; if (e.keyCode == SWT.COMMAND) stateMask &= ~SWT.COMMAND; } int keyCode = SWTEvents.keyCodeToEmbedKeyCode(e.keyCode); this.scenePeer.keyEvent( SWTEvents.keyIDToEmbedKeyType(type), keyCode, new char[0], SWTEvents.keyModifiersToEmbedKeyModifiers(stateMask)); if (e.character != '\0' && type == SWT.KeyDown) { char[] chars = new char[] { e.character }; this.scenePeer.keyEvent( AbstractEvents.KEYEVENT_TYPED, e.keyCode, chars, SWTEvents.keyModifiersToEmbedKeyModifiers(stateMask)); } } private void sendMenuEventToFX(MenuDetectEvent me) { if (this.scenePeer == null /*|| !isFxEnabled()*/) { return; } Point pt = this.toControl(me.x, me.y); this.scenePeer.menuEvent(pt.x, pt.y, me.x, me.y, false); } private void sendResizeEventToFX() { // force the panel to draw right away (avoid black rectangle) this.redraw(); this.update(); this.pWidth = this.getClientArea().width; this.pHeight = this.getClientArea().height; this.resizePixelBuffer(this.lastScaleFactor); if (this.scenePeer == null) { return; } this.stagePeer.setSize(this.pWidth, this.pHeight); this.scenePeer.setSize(this.pWidth, this.pHeight); } private void resizePixelBuffer(double newScaleFactor) { this.lastPixelsBuf = null; if ((this.pWidth <= 0) || (this.pHeight <= 0)) { this.pixelsBuf = null; } else { this.pixelsBuf = IntBuffer.allocate((int)Math.round(this.pWidth * newScaleFactor) * (int)Math.round(this.pHeight * newScaleFactor)); // The bg color may show through on resize. See RT-34380. RGB rgb = this.getBackground().getRGB(); Arrays.fill(this.pixelsBuf.array(), rgb.red << 16 | rgb.green << 8 | rgb.blue); } } private void sendFocusEventToFX(FocusEvent fe, boolean focused) { if ((this.stage == null) || (this.stagePeer == null)) { return; } int focusCause = (focused ? AbstractEvents.FOCUSEVENT_ACTIVATED : AbstractEvents.FOCUSEVENT_DEACTIVATED); this.stagePeer.setFocused(focused, focusCause); } private class HostContainer implements HostInterface { @Override public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) { FXCanvas.this.stagePeer = embeddedStage; if (FXCanvas.this.stagePeer == null) { return; } if (FXCanvas.this.pWidth > 0 && FXCanvas.this.pHeight > 0) { FXCanvas.this.stagePeer.setSize(FXCanvas.this.pWidth, FXCanvas.this.pHeight); } if (FXCanvas.this.isFocusControl()) { FXCanvas.this.stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED); } FXCanvas.this.sendMoveEventToFX(); FXCanvas.this.sendResizeEventToFX(); } TransferMode getTransferMode(int bits) { switch (bits) { case DND.DROP_COPY: return TransferMode.COPY; case DND.DROP_MOVE: case DND.DROP_TARGET_MOVE: return TransferMode.MOVE; case DND.DROP_LINK: return TransferMode.LINK; default: return null; } } Set<TransferMode> getTransferModes(int bits) { Set<TransferMode> set = new HashSet<TransferMode>(); if ((bits & DND.DROP_COPY) != 0) set.add(TransferMode.COPY); if ((bits & DND.DROP_MOVE) != 0) set.add(TransferMode.MOVE); if ((bits & DND.DROP_TARGET_MOVE) != 0) set.add(TransferMode.MOVE); if ((bits & DND.DROP_LINK) != 0) set.add(TransferMode.LINK); return set; } // Consider using dragAction private DragSource createDragSource(final EmbeddedSceneDSInterface fxDragSource, TransferMode dragAction) { Transfer [] transfers = this.getTransferTypes(fxDragSource.getMimeTypes()); if (transfers.length == 0) return null; int dragOperation = this.getDragActions(fxDragSource.getSupportedActions()); final DragSource dragSource = new DragSource(FXCanvas.this, dragOperation); dragSource.setTransfer(transfers); dragSource.addDragListener(new DragSourceListener() { @Override public void dragFinished(org.eclipse.swt.dnd.DragSourceEvent event) { dragSource.dispose(); fxDragSource.dragDropEnd(HostContainer.this.getTransferMode(event.detail)); } @Override public void dragSetData(org.eclipse.swt.dnd.DragSourceEvent event) { Transfer [] transfers = dragSource.getTransfer(); for (int i=0; i<transfers.length; i++) { if (transfers[i].isSupportedType(event.dataType)) { String mime = HostContainer.this.getMime(transfers[i]); if (mime != null) { event.doit = true; event.data = fxDragSource.getData(mime); return; } } event.doit = false; } } @Override public void dragStart(org.eclipse.swt.dnd.DragSourceEvent event) { } }); return dragSource; } int getDragAction(TransferMode tm) { if (tm == null) return DND.DROP_NONE; switch (tm) { case COPY: return DND.DROP_COPY; case MOVE: return DND.DROP_MOVE; case LINK: return DND.DROP_LINK; default: throw new IllegalArgumentException("Invalid transfer mode"); } } int getDragActions(Set<TransferMode> set) { int result = 0; for (TransferMode mode : set) { result |= this.getDragAction(mode); } return result; } Transfer getTransferType(String mime) { if (mime.equals("text/plain")) return TextTransfer.getInstance(); if (mime.equals("text/rtf")) return RTFTransfer.getInstance(); if (mime.equals("text/html")) return HTMLTransfer.getInstance(); if (mime.equals("text/uri-list")) return URLTransfer.getInstance(); if (mime.equals("application/x-java-rawimage")) return ImageTransfer.getInstance(); if (mime.equals("application/x-java-file-list") || mime.equals("java.file-list")) { return FileTransfer.getInstance(); } return getCustomTransfer(mime); } Transfer [] getTransferTypes(String [] mimeTypes) { int count= 0; Transfer [] transfers = new Transfer [mimeTypes.length]; for (int i=0; i<mimeTypes.length; i++) { Transfer transfer = this.getTransferType(mimeTypes[i]); if (transfer != null) transfers [count++] = transfer; } if (count != mimeTypes.length) { Transfer [] newTransfers = new Transfer[count]; System.arraycopy(transfers, 0, newTransfers, 0, count); transfers = newTransfers; } return transfers; } String getMime(Transfer transfer) { if (transfer.equals(TextTransfer.getInstance())) return "text/plain"; if (transfer.equals(RTFTransfer.getInstance())) return "text/rtf"; ; if (transfer.equals( HTMLTransfer.getInstance())) return "text/html"; if (transfer.equals(URLTransfer.getInstance())) return "text/uri-list"; if (transfer.equals( ImageTransfer.getInstance())) return "application/x-java-rawimage"; if (transfer.equals(FileTransfer.getInstance())) return "application/x-java-file-list"; if (transfer instanceof CustomTransfer) return ((CustomTransfer)transfer).getMime(); return null; } String [] getMimes(Transfer [] transfers, TransferData data) { int count= 0; String [] result = new String [transfers.length]; for (int i=0; i<transfers.length; i++) { if (transfers[i].isSupportedType(data)) { result [count++] = this.getMime (transfers [i]); } } if (count != result.length) { String [] newResult = new String[count]; System.arraycopy(result, 0, newResult, 0, count); result = newResult; } return result; } DropTarget createDropTarget(EmbeddedSceneInterface embeddedScene) { final DropTarget dropTarget = new DropTarget(FXCanvas.this, DND.DROP_COPY | DND.DROP_LINK | DND.DROP_MOVE); /* // DnD Remove final EmbeddedSceneDTInterface fxDropTarget = embeddedScene.createDropTarget(); */ dropTarget.setTransfer(getAllTransfers()); dropTarget.addDropListener(new DropTargetListener() { EmbeddedSceneDTInterface fxDropTarget; // DnD Add Object data; // In SWT, the list of available types that the source can provide // is part of the event. FX queries this directly from the operating // system and bypasses SWT. This variable is commented out to remind // us of this potential inconsistency. // //TransferData [] transferData; TransferData currentTransferData; boolean ignoreLeave; int detail = DND.DROP_NONE, operations = DND.DROP_NONE; EmbeddedSceneDSInterface fxDragSource = new EmbeddedSceneDSInterface() { @Override public Set<TransferMode> getSupportedActions() { return HostContainer.this.getTransferModes(operations); } @Override public Object getData(String mimeType) { // NOTE: get the data for the requested mime type, not the default data return data; } @Override public String[] getMimeTypes() { if (currentTransferData == null) return new String [0]; return HostContainer.this.getMimes(getAllTransfers(), currentTransferData); } @Override public boolean isMimeTypeAvailable(String mimeType) { String [] mimes = this.getMimeTypes(); for (int i=0; i<mimes.length; i++) { if (mimes[i].equals(mimeType)) return true; } return false; } @Override public void dragDropEnd(TransferMode performedAction) { data = null; //transferData = null; currentTransferData = null; } }; @Override public void dragEnter(DropTargetEvent event) { this.fxDropTarget = embeddedScene.createDropTarget(); // DnD Add this.ignoreLeave = false; // DnD Add this.currentTransferData = null; // DnD Add dropTarget.setTransfer(getAllTransfers()); this.detail = event.detail; this.operations = event.operations; this.dragOver (event, true, this.detail); } @Override public void dragLeave(DropTargetEvent event) { this.detail = this.operations = DND.DROP_NONE; this.data = null; //transferData = null; this.currentTransferData = null; FXCanvas.this.getDisplay().asyncExec(() -> { /* // DnD Remove if (this.ignoreLeave) return; fxDropTarget.handleDragLeave(); */ if (!this.ignoreLeave) // DnD Add this.fxDropTarget.handleDragLeave(); // DnD Add this.fxDropTarget = null; // DnD Add this.ignoreLeave = false; // DnD Add }); } @Override public void dragOperationChanged(DropTargetEvent event) { this.detail = event.detail; this.operations = event.operations; this.dragOver(event, false, this.detail); } @Override public void dragOver(DropTargetEvent event) { this.operations = event.operations; this.dragOver (event, false, this.detail); } public void dragOver(DropTargetEvent event, boolean enter, int detail) { //transferData = event.dataTypes; this.currentTransferData = event.currentDataType; Point pt = FXCanvas.this.toControl(event.x, event.y); if (detail == DND.DROP_NONE) detail = DND.DROP_COPY; TransferMode acceptedMode, recommendedMode = HostContainer.this.getTransferMode(detail); if (enter) { acceptedMode = this.fxDropTarget.handleDragEnter(pt.x, pt.y, event.x, event.y, recommendedMode, this.fxDragSource); } else { acceptedMode = this.fxDropTarget.handleDragOver(pt.x, pt.y, event.x, event.y, recommendedMode); } event.detail = HostContainer.this.getDragAction(acceptedMode); } @Override public void drop(DropTargetEvent event) { this.detail = event.detail; this.operations = event.operations; this.data = event.data; //transferData = event.dataTypes; this.currentTransferData = event.currentDataType; Point pt = FXCanvas.this.toControl(event.x, event.y); TransferMode recommendedDropAction = HostContainer.this.getTransferMode(event.detail); TransferMode acceptedMode = this.fxDropTarget.handleDragDrop(pt.x, pt.y, event.x, event.y, recommendedDropAction); event.detail = HostContainer.this.getDragAction(acceptedMode); this.data = null; //transferData = null; this.currentTransferData = null; } @Override public void dropAccept(DropTargetEvent event) { this.ignoreLeave = true; } }); return dropTarget; } @Override public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) { FXCanvas.this.scenePeer = embeddedScene; if (FXCanvas.this.scenePeer == null) { return; } if (FXCanvas.this.pWidth > 0 && FXCanvas.this.pHeight > 0) { FXCanvas.this.scenePeer.setSize(FXCanvas.this.pWidth, FXCanvas.this.pHeight); } double scaleFactor = FXCanvas.this.getScaleFactor(); FXCanvas.this.resizePixelBuffer(scaleFactor); FXCanvas.this.lastScaleFactor = scaleFactor; FXCanvas.this.scenePeer.setPixelScaleFactor((float)scaleFactor); FXCanvas.this.scenePeer.setDragStartListener((fxDragSource, dragAction) -> { Platform.runLater(() -> { DragSource dragSource = this.createDragSource(fxDragSource, dragAction); if (dragSource == null) { fxDragSource.dragDropEnd(null); } else { updateDropTarget(); FXCanvas.this.notifyListeners(SWT.DragDetect, null); } }); }); //Force the old drop target to be disposed before creating a new one FXCanvas.this.setDropTarget(null); FXCanvas.this.setDropTarget(this.createDropTarget(embeddedScene)); } @Override public boolean requestFocus() { Display.getDefault().asyncExec(() -> { if (FXCanvas.this.isDisposed()) return; FXCanvas.this.forceFocus(); }); return true; } @Override public boolean traverseFocusOut(boolean bln) { // RT-18085: not implemented return true; } Object lock = new Object(); boolean queued = false; @Override public void repaint() { synchronized (this.lock) { if (this.queued) return; this.queued = true; Display.getDefault().asyncExec(() -> { try { if (FXCanvas.this.isDisposed()) return; FXCanvas.this.redraw(); } finally { synchronized (this.lock) { this.queued = false; } } }); } } @Override public void setPreferredSize(int width, int height) { FXCanvas.this.pPreferredWidth = width; FXCanvas.this.pPreferredHeight = height; //FXCanvas.this.getShell().layout(new Control []{FXCanvas.this}, SWT.DEFER); } @Override public void setEnabled(boolean bln) { FXCanvas.this.setEnabled(bln); } @Override public void setCursor(CursorFrame cursorFrame) { FXCanvas.this.setCursor(this.getPlatformCursor(cursorFrame)); } private org.eclipse.swt.graphics.Cursor getPlatformCursor(final CursorFrame cursorFrame) { /* * On the Mac, setting the cursor during drag and drop clears the move * and link indicators. The fix is to set the default cursor for the * control (which is null) when the FX explicitly requests the default * cursor. This will preserve the drag and drop indicators. */ if (cursorFrame.getCursorType() == CursorType.DEFAULT) { return null; } final org.eclipse.swt.graphics.Cursor cachedPlatformCursor = cursorFrame.getPlatformCursor(org.eclipse.swt.graphics.Cursor.class); if (cachedPlatformCursor != null) { // platform cursor already cached return cachedPlatformCursor; } // platform cursor not cached yet final org.eclipse.swt.graphics.Cursor platformCursor = SWTCursors.embedCursorToCursor(cursorFrame); cursorFrame.setPlatforCursor(org.eclipse.swt.graphics.Cursor.class, platformCursor); return platformCursor; } @Override public boolean grabFocus() { // RT-27949: not implemented return true; } @Override public void ungrabFocus() { // RT-27949: not implemented } } }