1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.glass.ui.swt;
  26 
  27 import com.sun.glass.ui.Pixels;
  28 import com.sun.glass.ui.Clipboard;
  29 import com.sun.glass.ui.SystemClipboard;
  30 
  31 import java.nio.IntBuffer;
  32 import java.util.HashMap;
  33 import java.util.Set;
  34 
  35 import org.eclipse.swt.*;
  36 import org.eclipse.swt.dnd.*;
  37 import org.eclipse.swt.graphics.*;
  38 import org.eclipse.swt.widgets.*;
  39 
  40 class SWTClipboard extends SystemClipboard {
  41     org.eclipse.swt.dnd.Clipboard clipboard;
  42     static final String CLIPBOARD_KEY = "SWTClipboard";
  43     
  44     //TODO - temporary code to enable multiple transfers on Windows only
  45     static final boolean MULTIPLE_TRANSFERS = SWT.getPlatform().equals("win32");
  46     
  47     // Define local constants to avoid name conflicts
  48     static final int DROP_NONE = org.eclipse.swt.dnd.DND.DROP_NONE;
  49     static final int DROP_COPY = org.eclipse.swt.dnd.DND.DROP_COPY;
  50     static final int DROP_MOVE = org.eclipse.swt.dnd.DND.DROP_MOVE;
  51     static final int DROP_LINK = org.eclipse.swt.dnd.DND.DROP_LINK;
  52 
  53     private static final boolean MUTIPLE_TRANSFERS = false;
  54     
  55     // Define standard transfer types including custom transfers
  56     static Transfer [] StandardTransfers = new Transfer [] {
  57         TextTransfer.getInstance(),
  58         RTFTransfer.getInstance(),
  59         HTMLTransfer.getInstance(),
  60         URLTransfer.getInstance(),
  61         ImageTransfer.getInstance(),
  62         FileTransfer.getInstance(),
  63     };
  64     static Transfer [] CustomTransfers = new Transfer [0];
  65 
  66     public SWTClipboard(String name) {
  67         super(name);
  68         //TODO - implement selection clipboard for Linux
  69         if (name.equals(SYSTEM)) {
  70             Display display = Display.getDefault();
  71             clipboard = (org.eclipse.swt.dnd.Clipboard) display.getData(CLIPBOARD_KEY);
  72             if (clipboard == null) {
  73                 clipboard = new org.eclipse.swt.dnd.Clipboard(display);
  74                 display.setData(CLIPBOARD_KEY, clipboard);
  75                 display.disposeExec(() -> clipboard.dispose());
  76             }
  77         }
  78     }
  79     
  80     static Transfer [] getAllTransfers () {
  81         Transfer [] transfers = new Transfer[StandardTransfers.length + CustomTransfers.length];
  82         System.arraycopy(StandardTransfers, 0, transfers, 0, StandardTransfers.length);
  83         System.arraycopy(CustomTransfers, 0, transfers, StandardTransfers.length, CustomTransfers.length);
  84         return transfers;
  85     }
  86     
  87     static Transfer getCustomTransfer(String mime) {
  88         for (int i=0; i<CustomTransfers.length; i++) {
  89             if (((CustomTransfer)CustomTransfers[i]).getMime().equals(mime)) {
  90                 return CustomTransfers[i];
  91             }
  92         }
  93         Transfer transfer = new CustomTransfer (mime, mime);
  94         Transfer [] newCustom = new Transfer [CustomTransfers.length + 1];
  95         System.arraycopy(CustomTransfers, 0, newCustom, 0, CustomTransfers.length);
  96         newCustom[CustomTransfers.length] = transfer;
  97         CustomTransfers = newCustom;
  98         return transfer;
  99     }
 100     
 101     static Transfer [] getTransferTypes(String [] mimeTypes) {
 102         int count= 0;
 103         Transfer [] transfers = new Transfer [mimeTypes.length];
 104         for (int i=0; i<mimeTypes.length; i++) {
 105             Transfer transfer = getTransferType(mimeTypes[i]);
 106             if (transfer != null) transfers [count++] = transfer;
 107         }
 108         if (count != mimeTypes.length) {
 109             Transfer [] newTransfers = new Transfer[count];
 110             System.arraycopy(transfers, 0, newTransfers, 0, count);
 111             transfers = newTransfers;
 112         }
 113         return transfers;
 114     }
 115     
 116     //TODO - make this a lookup table
 117     static String getMime(TransferData data) {
 118         if (TextTransfer.getInstance().isSupportedType(data)) return TEXT_TYPE;
 119         if (RTFTransfer.getInstance().isSupportedType(data)) return RTF_TYPE;
 120         if (HTMLTransfer.getInstance().isSupportedType(data)) return HTML_TYPE;
 121         if (URLTransfer.getInstance().isSupportedType(data)) return URI_TYPE;
 122         if (ImageTransfer.getInstance().isSupportedType(data)) return RAW_IMAGE_TYPE;
 123         if (FileTransfer.getInstance().isSupportedType(data)) return FILE_LIST_TYPE;
 124         for (int i=0; i<CustomTransfers.length; i++) {
 125             if (CustomTransfers[i].isSupportedType(data)){
 126                 return ((CustomTransfer)CustomTransfers[i]).getMime();
 127             }
 128         }
 129         return null;
 130     }
 131 
 132     //TODO - make this a lookup table
 133     static String getMime(Transfer transfer) {
 134         if (transfer.equals(TextTransfer.getInstance())) return TEXT_TYPE;
 135         if (transfer.equals(RTFTransfer.getInstance())) return RTF_TYPE; ;
 136         if (transfer.equals( HTMLTransfer.getInstance())) return HTML_TYPE;
 137         if (transfer.equals(URLTransfer.getInstance())) return URI_TYPE;
 138         if (transfer.equals( ImageTransfer.getInstance())) return RAW_IMAGE_TYPE;
 139         if (transfer.equals(FileTransfer.getInstance())) return FILE_LIST_TYPE;
 140         //if (mime.equals(FileTransfer.getInstance()) return "java.file-list";
 141         if (transfer instanceof CustomTransfer) {
 142             return ((CustomTransfer)transfer).getMime();
 143         }
 144         return null;
 145     }
 146     
 147     static String [] getMimes(TransferData [] transfers) {
 148         int count= 0;
 149         String [] result = new String [transfers.length];
 150         for (int i=0; i<transfers.length; i++) {
 151             result [count++] = getMime (transfers [i]);
 152         }
 153         if (count != result.length) {
 154             String [] newResult = new String[count];
 155             System.arraycopy(result, 0, newResult, 0, count);
 156             result = newResult;
 157         }
 158         return result;
 159     }
 160     
 161     static String [] getMimes(Transfer [] transfers, TransferData data) {
 162         int count= 0;
 163         String [] result = new String [transfers.length];
 164         for (int i=0; i<transfers.length; i++) {
 165             if (transfers[i].isSupportedType(data)) {
 166                 result [count++] = getMime (transfers [i]);
 167             }
 168         }
 169         if (count != result.length) {
 170             String [] newResult = new String[count];
 171             System.arraycopy(result, 0, newResult, 0, count);
 172             result = newResult;
 173         }
 174         return result;
 175     }
 176 
 177     //TODO - make this a lookup table
 178     static Transfer getTransferType(String mime) {
 179         if (mime.equals(TEXT_TYPE)) return TextTransfer.getInstance();
 180         if (mime.equals(RTF_TYPE)) return RTFTransfer.getInstance();
 181         if (mime.equals(HTML_TYPE)) return HTMLTransfer.getInstance();
 182         if (mime.equals(URI_TYPE)) return URLTransfer.getInstance();
 183         if (mime.equals(RAW_IMAGE_TYPE)) return ImageTransfer.getInstance();
 184         if (mime.equals(FILE_LIST_TYPE)) {// || mime.equals("java.file-list")) {
 185             return FileTransfer.getInstance();
 186         }
 187         return getCustomTransfer(mime);
 188     }
 189     
 190     //TODO - make this a lookup table
 191     static Object getData(String mime, TransferData data) {
 192         if (mime.equals(TEXT_TYPE)) return TextTransfer.getInstance().nativeToJava(data);
 193         if (mime.equals(RTF_TYPE)) return RTFTransfer.getInstance().nativeToJava(data);
 194         if (mime.equals(HTML_TYPE)) return HTMLTransfer.getInstance().nativeToJava(data);
 195         if (mime.equals(URI_TYPE)) return URLTransfer.getInstance().nativeToJava(data);
 196         if (mime.equals(RAW_IMAGE_TYPE)) return ImageTransfer.getInstance().nativeToJava(data);
 197         if (mime.equals(FILE_LIST_TYPE)) {// || mime.equals("java.file-list")) {
 198             return FileTransfer.getInstance().nativeToJava(data);
 199         }
 200         Transfer transfer = getCustomTransfer(mime);
 201         if (transfer != null) return ((CustomTransfer)transfer).nativeToJava(data);
 202         return null;
 203     }
 204 
 205     @Override
 206     protected boolean isOwner() {
 207         return MUTIPLE_TRANSFERS;
 208     }
 209     
 210     static int getSWTAction(int actions) {
 211         int result = ACTION_NONE;
 212         if ((actions & ACTION_COPY) != 0) result |= DROP_COPY;
 213         if ((actions & ACTION_MOVE) != 0) result |= DROP_MOVE;
 214         if ((actions & ACTION_REFERENCE) != 0) result |=DROP_LINK;
 215         return result;
 216     }
 217     
 218     //TODO - better name that indicates it is the invers of getDragActions()
 219     static int getFXAction(int actions) {
 220         int result = DROP_NONE;
 221         if ((actions & DROP_COPY) != 0) result |= ACTION_COPY;
 222         if ((actions & DROP_MOVE) != 0) result |= ACTION_MOVE;
 223         if ((actions & DROP_LINK) != 0) result |= ACTION_REFERENCE;
 224         return result;
 225     }
 226     
 227     @Override
 228     protected void pushToSystem(HashMap<String, Object> data, int supportedActions) {
 229         int count = 0;
 230         ImageData imageData = null;
 231         Set<String> keys = data.keySet();
 232         Object [] objects = new Object [keys.size()];
 233         Transfer[] transfers = new Transfer[keys.size()];
 234         for (String key : keys) {
 235             Transfer transfer = getTransferType(key);
 236             if (transfer != null) {
 237                 //TODO - image not done, format wrong, alpha wrong
 238                 if (transfer instanceof ImageTransfer && data.get(key) instanceof Pixels) {
 239                     Pixels pixels = (Pixels) data.get(key);
 240                     objects[count] = imageData = SWTApplication.createImageData(pixels);
 241                 } else {
 242                     objects[count] = data.get(key);
 243                 }
 244                 transfers [count] = transfer;
 245                 count++;
 246             }
 247         }
 248         if (count == 0) return;
 249         if (count != objects.length) {
 250             Object [] newObjects = new Object [objects.length];
 251             System.arraycopy(objects, 0, newObjects, 0, objects.length);
 252             objects = newObjects;
 253             Transfer [] newTransfers = new Transfer [transfers.length];
 254             System.arraycopy(transfers, 0, newTransfers, 0, transfers.length);
 255             transfers = newTransfers;
 256         }
 257         if (clipboard != null) {
 258             //TODO - setting and empty string to the clipboard causes an exception
 259             //TODO - clear the contents instead (what about multiple objects?)
 260             for (int i=0; i<transfers.length; i++) {
 261                 if (transfers[i] instanceof TextTransfer && objects[i] instanceof String) {
 262                     if (((String)objects[i]).length() == 0) {
 263                         clipboard.clearContents();
 264                         return;
 265                     }
 266                 }
 267             }
 268             clipboard.setContents(objects, transfers);
 269         } else {
 270             //TODO - does setting an empty string fail for drag and drop like the clipboard
 271             final Control control = Display.getDefault().getFocusControl();
 272             if (control != null && control.getData() instanceof SWTView) {
 273                 final SWTView view = (SWTView) control.getData();
 274                 int dragOperation = getSWTAction(supportedActions);
 275                 final DragSource dragSource = new DragSource(control, dragOperation);
 276                 dragSource.setTransfer(transfers);
 277                 dragSource.setData("objects", objects);
 278                 dragSource.setData("imageData", imageData);
 279                 dragSource.addDragListener(new DragSourceListener() {
 280                     Image image = null;
 281                     public void dragFinished(DragSourceEvent event) {
 282                         if (image != null) {
 283                             image.dispose();
 284                             image = null;
 285                         }
 286                         dragSource.setData("objects", null);
 287                         dragSource.setData("imageData", null);
 288                         dragSource.dispose();
 289                         view.notifyDragEnd(getFXAction(event.detail));
 290                     }
 291                     public void dragSetData(DragSourceEvent event) {
 292                         Object [] objects = (Object []) dragSource.getData("objects");
 293                         Transfer [] transfers = dragSource.getTransfer();
 294                         for (int i=0; i<transfers.length; i++) {
 295                             if (transfers[i].isSupportedType(event.dataType)) {
 296                                 String mime = getMime(transfers[i]);
 297                                 if (mime != null) {
 298                                     event.doit = true;
 299                                     event.data = objects [i];
 300                                     return;
 301                                 }
 302                             }
 303                             event.doit = false;
 304                         }
 305                     }
 306                     public void dragStart(DragSourceEvent event) {
 307                         ImageData imageData = (ImageData) dragSource.getData("imageData");
 308                         if (imageData != null) {
 309                             event.image = image = new Image(event.display, imageData);
 310                             event.offsetX = imageData.width / 2;
 311                             event.offsetY = imageData.height / 2;
 312                         }
 313                         Point point = control.toDisplay(event.x, event.y);
 314                         //TODO - button number is hard coded
 315                         view.notifyDragStart(1, event.x, event.y, point.x, point.y);
 316                     }
 317                 });
 318                 //TODO - not sure, why do we need to set the drop target transfers when drag starts?
 319                 //TODO - is there another place that makes more sense to make sure they are up to date
 320                 if (view.dropTarget != null) view.dropTarget.setTransfer(getAllTransfers());
 321                 control.notifyListeners(SWT.DragDetect, null);
 322             }
 323         }
 324     }
 325 
 326     @Override
 327     protected void pushTargetActionToSystem(int actionDone) {
 328         //TODO - what is the correct implementation for this method?
 329         //System.out.println("SWTClipboard.pushTargetActionToSystem");
 330     }
 331 
 332     @Override
 333     protected Object popFromSystem(String mimeType) {
 334         Transfer transfer = getTransferType(mimeType);
 335         if (transfer != null) {
 336             //TODO - image not done, format wrong, alpha wrong
 337             Object data = null;
 338             if (clipboard != null) {
 339                 data = clipboard.getContents(transfer);
 340             } else {
 341                 if (MULTIPLE_TRANSFERS) {
 342                     for (int i=0; i<transferData.length; i++) {
 343                         if (transfer.isSupportedType(transferData[i])) {
 344                             data = getData(mimeType, transferData[i]);
 345                             break;
 346                         }
 347                     }
 348                 } else {
 349                     data = currentData;
 350                 }
 351             }
 352             if (data instanceof ImageData) {
 353                 return SWTApplication.createPixels((ImageData) data);
 354             }
 355             return data;
 356         }
 357         return null;
 358     }
 359 
 360     //TODO - glass API should include the idea that a control takes part in DND
 361     //TODO - don't use statics, they are never cleared, shared state is not correct etc.
 362     static int operations = DROP_NONE;
 363     static TransferData currentTransferData;
 364     static TransferData [] transferData;
 365     static Object currentData;
 366     
 367     @Override
 368     protected int supportedSourceActionsFromSystem() {
 369         if (clipboard != null)  return Clipboard.ACTION_COPY;
 370         return getFXAction(operations);
 371     }
 372 
 373     static DropTarget createDropTarget(final Control control) {
 374         final SWTView view = (SWTView) control.getData();
 375         final DropTarget dropTarget = new DropTarget(control, DROP_COPY | DROP_LINK | DROP_MOVE);
 376         dropTarget.setTransfer(getAllTransfers());
 377         dropTarget.addDropListener(new DropTargetListener() {
 378             //Object currentData;
 379             //TransferData [] transferData;
 380             //TransferData currentTransferData;
 381             int detail = DROP_NONE;
 382             //int operations = DROP_NONE;
 383             public void dragEnter(DropTargetEvent event) {
 384                 dropTarget.setTransfer(getAllTransfers());
 385                 detail = event.detail;
 386                 operations = event.operations;
 387                 dragOver (event, true, detail);
 388             }
 389             public void dragLeave(DropTargetEvent event) {
 390                 detail = operations = DROP_NONE;
 391                 currentData = null;
 392                 transferData = null;
 393                 currentTransferData = null;
 394                 view.notifyDragLeave();
 395             }
 396             public void dragOperationChanged(DropTargetEvent event) {
 397                 detail = event.detail;
 398                 operations = event.operations;
 399                 dragOver(event, false, detail);
 400             }
 401             public void dragOver(DropTargetEvent event) {
 402                 operations = event.operations;
 403                 dragOver (event, false, detail);
 404             }
 405             public void dragOver(DropTargetEvent event, boolean enter, int detail) {
 406                 transferData = event.dataTypes;
 407                 currentTransferData = event.currentDataType;
 408                 Point pt = control.toControl(event.x, event.y);
 409                 if (detail == DROP_NONE) detail = DROP_COPY;
 410                 int action = getFXAction(detail), acceptAction;
 411                 if (enter) {
 412                     acceptAction = view.notifyDragEnter(pt.x, pt.y, event.x, event.y, action);
 413                 } else {
 414                     acceptAction = view.notifyDragOver(pt.x, pt.y, event.x, event.y, action);
 415                 }
 416                 event.detail = getSWTAction(acceptAction);
 417                 //TODO - FX should set the transfer type that is desired for the drop
 418                 //currentTransferData = event.currentDataType;
 419             }
 420             public void drop(DropTargetEvent event) {
 421                 detail = event.detail;
 422                 operations = event.operations;
 423                 currentData = event.data;
 424                 transferData = event.dataTypes;
 425                 currentTransferData = event.currentDataType;
 426                 Point pt = control.toControl(event.x, event.y);
 427                 int action = getFXAction(event.detail);
 428                 int acceptAction = view.notifyDragDrop(pt.x, pt.y, event.x, event.y, action);
 429                 event.detail = getSWTAction(acceptAction);
 430                 currentData = null;
 431                 transferData = null;
 432                 currentTransferData = null;
 433             }
 434             public void dropAccept(DropTargetEvent event) {
 435             }
 436         });
 437         return dropTarget;
 438     }
 439     
 440     @Override
 441     protected String[] mimesFromSystem() {
 442         //TODO - return non-standard clipboard/drag and drop mimes
 443         if (clipboard != null) {
 444             int count= 0;
 445             TransferData[] data = clipboard.getAvailableTypes();
 446             String [] result = new String [data.length];
 447             for (int i=0; i<data.length; i++) {
 448                 String mime = getMime(data[i]);
 449                 if (mime != null) result [count++] = mime;
 450             }
 451             if (count == result.length) return result;
 452             String [] newResult = new String [count];
 453             System.arraycopy(result,  0, newResult, 0, count);
 454             return newResult;
 455         } else {
 456             if (MULTIPLE_TRANSFERS) {
 457                 // Gets the mime type for the available objects
 458                 return getMimes(transferData);
 459             } else {
 460                 // Gets the mime type for the dropped object
 461                 if (currentTransferData == null) return new String [0];
 462                 return getMimes(getAllTransfers(), currentTransferData);
 463             }
 464         }
 465     }
 466 }