/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.dnd; import java.awt.Component; import java.awt.Point; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.Reader; import java.io.StringWriter; import javax.swing.JComponent; import javax.swing.JViewport; import javax.swing.TransferHandler; import com.servoy.base.scripting.api.IJSEvent.EventType; import com.servoy.j2db.dataprocessing.IRecordInternal; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Utils; /** * Class used for handling drag and drop operations in smart client. * * @author gboros */ public class CompositeTransferHandler extends TransferHandler implements DropTargetListener { DropTargetDragEvent dropTargetDragEvent; DropTargetDropEvent dropTargetDropEvent; InputEvent inputEvent; private boolean canImport; private Transferable customTransferable; /** * @see javax.swing.TransferHandler#getSourceActions(JComponent) */ @Override public int getSourceActions(JComponent c) { // when dnd from portal, getSourceActions is called also before, exportAsDrag, // so, let ignore it, and wait for the inputEvent to be set if (inputEvent == null) return TransferHandler.COPY_OR_MOVE; ICompositeDragNDrop ddComp = (ICompositeDragNDrop)c; JSDNDEvent onDragEvent = createScriptEvent(EventType.onDrag, (ICompositeDragNDrop)c, inputEvent); int dragReturn = ddComp.onDrag(onDragEvent); try { customTransferable = createTransferable(onDragEvent.getData(), onDragEvent.getDataMimeType()); } catch (ClassNotFoundException ex) { Debug.error(ex); return TransferHandler.NONE; } return dragReturn == DRAGNDROP.NONE ? TransferHandler.NONE : dragReturn == DRAGNDROP.COPY ? TransferHandler.COPY : dragReturn == DRAGNDROP.MOVE ? TransferHandler.MOVE : dragReturn == DRAGNDROP.COPY + DRAGNDROP.MOVE ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE; } /** * @see javax.swing.TransferHandler#createTransferable(javax.swing.JComponent) */ @Override protected Transferable createTransferable(JComponent c) { return customTransferable; } /** * @see javax.swing.TransferHandler#exportAsDrag(JComponent, InputEvent, int) */ @Override public void exportAsDrag(JComponent comp, InputEvent e, int action) { inputEvent = e; super.exportAsDrag(comp, e, action); } /** * @see javax.swing.TransferHandler#exportDone(javax.swing.JComponent, java.awt.datatransfer.Transferable, int) */ @Override protected void exportDone(JComponent source, Transferable data, int action) { super.exportDone(source, data, action); JComponent cmp = getDragComponent(source); if (cmp instanceof ICompositeDragNDrop && data != null) { ICompositeDragNDrop ddComp = (ICompositeDragNDrop)cmp; JSDNDEvent onDragEndEvent = createScriptEvent(EventType.onDragEnd, ddComp, inputEvent); int dragResult = action == COPY ? DRAGNDROP.COPY : action == MOVE ? DRAGNDROP.MOVE : DRAGNDROP.NONE; onDragEndEvent.setDragResult(dragResult); try { DataFlavor[] transferableFlavors = data.getTransferDataFlavors(); if (transferableFlavors.length > 0) { onDragEndEvent.setDataMimeType(transferableFlavors[0].getMimeType()); if (transferableFlavors[0].isRepresentationClassInputStream() || transferableFlavors[0].isRepresentationClassReader()) onDragEndEvent.setData(null); else onDragEndEvent.setData(data.getTransferData(transferableFlavors[0])); } } catch (Exception ex) { Debug.error(ex); } ((ICompositeDragNDrop)source).onDragEnd(onDragEndEvent); } customTransferable = null; inputEvent = null; } /** * @see javax.swing.TransferHandler#canImport(JComponent, DataFlavor[]) */ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors, Transferable transferable) { JComponent cmp = getDragComponent(comp); if (cmp instanceof ICompositeDragNDrop) { ICompositeDragNDrop ddComp = (ICompositeDragNDrop)cmp; JSDNDEvent event = createScriptEvent(EventType.onDragOver, ddComp, dropTargetDragEvent); // ddComp = testTarget(ddComp, event); try { DataFlavor[] transferableFlavors = transferable.getTransferDataFlavors(); if (transferableFlavors.length > 0) { event.setDataMimeType(transferableFlavors[0].getMimeType()); if (transferableFlavors[0].isRepresentationClassInputStream() || transferableFlavors[0].isRepresentationClassReader()) event.setData(null); else event.setData(transferable.getTransferData(transferableFlavors[0])); } } catch (Exception ex) { Debug.error(ex); } return ddComp.onDragOver(event); } return false; } /** * Gets the drag component from the component parameter. * * @param comp to check for drag * * @return the drag component */ public JComponent getDragComponent(JComponent comp) { if (comp instanceof JViewport) { return (JComponent)((JViewport)comp).getView(); } return comp; } // private ISupportDragNDrop testTarget(ISupportDragNDrop ddComp, JSEvent event) // { // if (event.js_getSource() instanceof SpecialTabPanel) // { // SpecialTabPanel tabPanel = (SpecialTabPanel)event.js_getSource(); // Component selectedComponent = tabPanel.getEnclosingComponent().getSelectedComponent(); // if (selectedComponent instanceof FormLookupPanel) // { // FormController formControler = ((FormLookupPanel)selectedComponent).getFormPanel(); // event.setSource(null); // event.setFormName(formControler.getName()); // event.setElementName(null); // return (ISupportDragNDrop)formControler.getViewComponent(); // } // } // return ddComp; // } /** * @see javax.swing.TransferHandler#importData(JComponent, Transferable) */ @Override public boolean importData(JComponent comp, Transferable t) { JComponent cmp = getDragComponent(comp); if (cmp instanceof ICompositeDragNDrop) { ICompositeDragNDrop ddComp = (ICompositeDragNDrop)cmp; JSDNDEvent event = createScriptEvent(EventType.onDrop, ddComp, dropTargetDropEvent); // ddComp = testTarget(ddComp, event); try { DataFlavor[] transferableFlavors = t.getTransferDataFlavors(); if (transferableFlavors.length > 0) { event.setDataMimeType(transferableFlavors[0].getMimeType()); if (transferableFlavors[0].isRepresentationClassInputStream()) { BufferedInputStream contents = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int len; try { contents = new BufferedInputStream((InputStream)t.getTransferData(transferableFlavors[0])); while ((len = contents.read(buffer)) != -1) bos.write(buffer, 0, len); event.setData(bos.toByteArray()); } finally { Utils.closeOutputStream(bos); Utils.closeInputStream(contents); } } else if (transferableFlavors[0].isRepresentationClassReader()) { Reader contents = (Reader)t.getTransferData(transferableFlavors[0]); StringWriter stringWriter = new StringWriter(); try { Utils.readerWriterCopy(contents, stringWriter); event.setData(stringWriter.toString()); } finally { Utils.closeWriter(stringWriter); Utils.closeReader(contents); } } else event.setData(t.getTransferData(transferableFlavors[0])); } } catch (Exception ex) { Debug.error(ex); } return ddComp.onDrop(event); } return false; } protected JSDNDEvent createScriptEvent(EventType type, ICompositeDragNDrop ddComponent, Object event) { JSDNDEvent jsEvent = new JSDNDEvent(); jsEvent.setType(type); Point location = getEventXY(event); if (location != null) jsEvent.setLocation(location); jsEvent.setModifiers(getModifiersOrDropAction(event)); return jsEvent; } protected Transferable createTransferable(Object data, String mimeType) throws ClassNotFoundException { return new CompositeTransferable(data, mimeType); } protected int getModifiersOrDropAction(Object eventObject) { if (eventObject instanceof MouseEvent) { MouseEvent mouseEvent = (MouseEvent)eventObject; return mouseEvent.getModifiers(); } else if (eventObject instanceof DropTargetDragEvent) { DropTargetDragEvent event = (DropTargetDragEvent)eventObject; return event.getDropAction(); } else if (eventObject instanceof DropTargetDropEvent) { DropTargetDropEvent event = (DropTargetDropEvent)eventObject; return event.getDropAction(); } return 0; } protected Point getEventXY(Object eventObject) { if (eventObject instanceof MouseEvent) { MouseEvent mouseEvent = (MouseEvent)eventObject; return mouseEvent.getPoint(); } else if (eventObject instanceof DropTargetDragEvent) { DropTargetDragEvent event = (DropTargetDragEvent)eventObject; return event.getLocation(); } else if (eventObject instanceof DropTargetDropEvent) { DropTargetDropEvent event = (DropTargetDropEvent)eventObject; return event.getLocation(); } return null; } private boolean actionSupported(int action) { return (action & (COPY_OR_MOVE)) != NONE; } // --- DropTargetListener methods ----------------------------------- private Object lastDragSource = null; private IRecordInternal lastDragRecord = null; /** * @see java.awt.dnd.DropTargetListener#dragEnter(DropTargetDragEvent) */ public void dragEnter(DropTargetDragEvent e) { dropTargetDragEvent = e; DataFlavor[] flavors = e.getCurrentDataFlavors(); JComponent c = getDragComponent((JComponent)e.getDropTargetContext().getComponent()); if (c instanceof ICompositeDragNDrop) { ICompositeDragNDrop dropComponent = (ICompositeDragNDrop)c; Object newDragSource = dropComponent.getDragSource(getEventXY(e)); if (!canReplaceDragSource(lastDragSource, newDragSource, e)) { e.rejectDrag(); dropTargetDragEvent = null; return; // dragSource did not changed } lastDragSource = newDragSource; lastDragRecord = c instanceof IFormDataDragNDrop ? ((IFormDataDragNDrop)dropComponent).getDragRecord(getEventXY(e)) : null; //TODO: move this code out } TransferHandler importer = c.getTransferHandler(); if (importer instanceof CompositeTransferHandler && ((CompositeTransferHandler)importer).canImport(c, flavors, e.getTransferable())) { canImport = true; } else { canImport = false; } int dropAction = e.getDropAction(); if (canImport && actionSupported(dropAction)) { e.acceptDrag(dropAction); } else { e.rejectDrag(); } dropTargetDragEvent = null; } /** * @see java.awt.dnd.DropTargetListener#dragOver(DropTargetDragEvent) */ public void dragOver(DropTargetDragEvent e) { dropTargetDragEvent = e; int dropAction = e.getDropAction(); Component component = getDragComponent((JComponent)e.getDropTargetContext().getComponent()); if (actionSupported(dropAction) && component instanceof ICompositeDragNDrop) { ICompositeDragNDrop dropComponent = (ICompositeDragNDrop)component; Object dragSource = dropComponent.getDragSource(getEventXY(e)); IRecordInternal dragRecord = dropComponent instanceof IFormDataDragNDrop ? ((IFormDataDragNDrop)dropComponent).getDragRecord(getEventXY(e)) : null; // TODO: move this code out if (dragSource != lastDragSource || dragRecord != lastDragRecord) { // simulate a drag enter now dragEnter(e); } else { if (canImport) { e.acceptDrag(dropAction); } else { e.rejectDrag(); } } } else { e.rejectDrag(); } dropTargetDragEvent = null; } /** * @see java.awt.dnd.DropTargetListener#dragExit(DropTargetEvent) */ public void dragExit(DropTargetEvent e) { dropTargetDragEvent = null; } /** * @see java.awt.dnd.DropTargetListener#drop(DropTargetDropEvent) */ public void drop(DropTargetDropEvent e) { dropTargetDropEvent = e; int dropAction = e.getDropAction(); JComponent c = getDragComponent((JComponent)e.getDropTargetContext().getComponent()); TransferHandler importer = c.getTransferHandler(); if (canImport && importer != null && actionSupported(dropAction)) { e.acceptDrop(dropAction); try { Transferable t = e.getTransferable(); e.dropComplete(importer.importData(c, t)); } catch (RuntimeException re) { e.dropComplete(false); } } else { e.rejectDrop(); } dropTargetDropEvent = null; } /** * @see java.awt.dnd.DropTargetListener#dropActionChanged(DropTargetDragEvent) */ public void dropActionChanged(DropTargetDragEvent e) { dropTargetDragEvent = e; int dropAction = e.getDropAction(); if (canImport && actionSupported(dropAction)) { e.acceptDrag(dropAction); } else { e.rejectDrag(); } dropTargetDragEvent = null; } /** * Called during dragStart to check if the current drag source can be replaced by the new one * * @param currentDragSource the current drag source * @param newDragSource the new drag source * @param e the drag event * @return true if the new drag source can replace the current drag source */ protected boolean canReplaceDragSource(Object currentDragSource, Object newDragSource, DropTargetDragEvent e) { return true; } }