| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.openide.util.datatransfer; |
| |
| import java.awt.datatransfer.DataFlavor; |
| import java.awt.datatransfer.Transferable; |
| import java.awt.datatransfer.UnsupportedFlavorException; |
| import java.io.IOException; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import javax.swing.event.EventListenerList; |
| import org.openide.util.NbBundle; |
| |
| /** Provides additional operations on |
| * a transferable. |
| * |
| * @author Jaroslav Tulach |
| */ |
| public class ExTransferable extends Object implements Transferable { |
| /** An implementation of <code>Transferable</code> that contains no data. */ |
| public static final Transferable EMPTY = new Empty(); |
| |
| /** Flavor for transfer of multiple objects. |
| */ |
| public static final DataFlavor multiFlavor; |
| static { |
| try { |
| multiFlavor = new DataFlavor( |
| "application/x-java-openide-multinode;class=org.openide.util.datatransfer.MultiTransferObject", // NOI18N |
| NbBundle.getBundle(ExTransferable.class).getString("transferFlavorsMultiFlavorName"), |
| MultiTransferObject.class.getClassLoader()); |
| } catch (ClassNotFoundException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** hash map that assigns objects to dataflavors (DataFlavor, Single) */ |
| private LinkedHashMap<DataFlavor,Single> map; |
| |
| /** listeners */ |
| private EventListenerList listeners; |
| |
| /** Creates new support. |
| * @param t transferable to to copy values from |
| * @param o clipobard owner (or null) |
| */ |
| private ExTransferable(final Transferable t) { |
| map = new LinkedHashMap<DataFlavor,Single>(); |
| |
| final DataFlavor[] df = t.getTransferDataFlavors(); |
| |
| if (df != null) { |
| for (int i = 0; i < df.length; i++) { |
| try { |
| final int fi = i; |
| map.put( |
| df[i], |
| new Single(df[i]) { |
| public Object getData() throws IOException, UnsupportedFlavorException { |
| return t.getTransferData(df[fi]); |
| } |
| } |
| ); |
| } catch (Exception ex) { |
| // ignore if the data cannot be retrived |
| } |
| } |
| } |
| } |
| |
| /** Add a new flavor with its data. |
| * @param single the single transferable to use |
| */ |
| public void put(Single single) { |
| map.put(single.flavor, single); |
| } |
| |
| /** Remove a flavor from the supported set. |
| * @param flavor the flavor to remove |
| */ |
| public void remove(DataFlavor flavor) { |
| map.remove(flavor); |
| } |
| |
| /* Get supported flavors. |
| * @return the flavors |
| */ |
| public DataFlavor[] getTransferDataFlavors() { |
| return map.keySet().toArray(new DataFlavor[0]); |
| } |
| |
| /* Is this flavor supported? |
| * @param flavor flavor to test |
| * @return <code>true</code> if this flavor is supported |
| */ |
| public boolean isDataFlavorSupported(DataFlavor flavor) { |
| return map.containsKey(flavor); |
| } |
| |
| /* Get the transferable data for this flavor. |
| * @param flavor the flavor |
| * @return the data |
| * @throws IOException currently not thrown |
| * @throws UnsupportedFlavorException if that flavor is not supported |
| */ |
| public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { |
| Single o = map.get(flavor); |
| |
| if (o == null) { |
| throw new UnsupportedFlavorException(flavor); |
| } |
| |
| return o.getTransferData(flavor); |
| } |
| |
| /** Method to create a new extended transferable from a plain transferable. |
| * If the given transferable is already <code>ExTransferable</code>, then it |
| * is returned as is. |
| * Otherwise the data is copied. |
| * |
| * @param t transferable to create support for |
| * @return extended transferable |
| */ |
| public static ExTransferable create(Transferable t) { |
| // [PENDING] check should probably be: if (t.getClass() == ExTransferable.class) |
| // (in case for some weird reason someone subclasses ExTransferable) |
| if (t instanceof ExTransferable) { |
| return (ExTransferable) t; |
| } |
| |
| return new ExTransferable(t); |
| } |
| |
| /** Adds a listener to watch the life-cycle of this object. |
| * |
| * @param l the listener |
| */ |
| public final synchronized void addTransferListener(TransferListener l) { |
| if (listeners == null) { |
| listeners = new EventListenerList(); |
| } |
| |
| listeners.add(TransferListener.class, l); |
| } |
| |
| /** Removes a listener. |
| */ |
| public final synchronized void removeTransferListener(TransferListener l) { |
| if (listeners != null) { |
| listeners.remove(TransferListener.class, l); |
| } |
| } |
| |
| /** Fires notification to all listeners about |
| * accepting the drag. |
| * @param action one of java.awt.dnd.DnDConstants.ACTION_* |
| */ |
| final void fireAccepted(int action) { |
| if (listeners == null) { |
| return; |
| } |
| |
| Object[] arr = listeners.getListenerList(); |
| |
| for (int i = arr.length - 1; i >= 0; i -= 2) { |
| ((TransferListener) arr[i]).accepted(action); |
| } |
| } |
| |
| /** Fires notification to all listeners about |
| * accepting the drag. |
| */ |
| final void fireRejected() { |
| if (listeners == null) { |
| return; |
| } |
| |
| Object[] arr = listeners.getListenerList(); |
| |
| for (int i = arr.length - 1; i >= 0; i -= 2) { |
| ((TransferListener) arr[i]).rejected(); |
| } |
| } |
| |
| /** Fires notification to all listeners about |
| * accepting the drag. |
| */ |
| final void fireOwnershipLost() { |
| if (listeners == null) { |
| return; |
| } |
| |
| Object[] arr = listeners.getListenerList(); |
| |
| for (int i = arr.length - 1; i >= 0; i -= 2) { |
| ((TransferListener) arr[i]).ownershipLost(); |
| } |
| } |
| |
| /** Support for transferable owner with only one data flavor. |
| * Subclasses need only implement {@link #getData}. |
| */ |
| public abstract static class Single extends Object implements Transferable { |
| /** the supported data flavor */ |
| private DataFlavor flavor; |
| |
| /** Constructor. |
| * @param flavor flavor of the data |
| */ |
| public Single(DataFlavor flavor) { |
| this.flavor = flavor; |
| } |
| |
| /* Flavors that are supported. |
| * @return array with <CODE>contextFlavor</CODE> |
| * @see TransferFlavors.contextFlavor |
| */ |
| public DataFlavor[] getTransferDataFlavors() { |
| return new DataFlavor[] { flavor }; |
| } |
| |
| /* Is the flavor supported? |
| * @param flavor flavor to test |
| * @return true if this flavor is supported |
| */ |
| public boolean isDataFlavorSupported(DataFlavor flavor) { |
| return this.flavor.equals(flavor); |
| } |
| |
| /* Creates transferable data for this flavor. |
| */ |
| public Object getTransferData(DataFlavor flavor) |
| throws UnsupportedFlavorException, IOException { |
| if (!this.flavor.equals(flavor)) { |
| throw new UnsupportedFlavorException(flavor); |
| } |
| |
| return getData(); |
| } |
| |
| /** Abstract method to override to provide the right data for this |
| * transferable. |
| * |
| * @return the data |
| * @throws IOException when an I/O error occurs |
| * @throws UnsupportedFlavorException if the flavor is not supported |
| */ |
| protected abstract Object getData() throws IOException, UnsupportedFlavorException; |
| } |
| |
| /** Transferable object for multiple transfer. |
| * It allows several types of data |
| * to be combined into one clipboard element. |
| * |
| * @author Jaroslav Tulach |
| */ |
| public static class Multi extends Object implements Transferable { |
| /** supported flavors list */ |
| private static final DataFlavor[] flavorList = { multiFlavor }; |
| |
| /** object that is about to be return as result of transfer */ |
| private MultiTransferObject transferObject; |
| |
| /** Constructor taking a list of <code>Transferable</code> objects. |
| * |
| * @param trans array of transferable objects |
| */ |
| public Multi(Transferable[] trans) { |
| transferObject = new TransferObjectImpl(trans); |
| } |
| |
| /** Get supported flavors. |
| * @return only one flavor, {@link #multiFlavor} |
| */ |
| public DataFlavor[] getTransferDataFlavors() { |
| return flavorList; |
| } |
| |
| /** Is this flavor supported? |
| * @param flavor the flavor |
| * @return <code>true</code> only if the flavor is {@link #multiFlavor} |
| */ |
| public boolean isDataFlavorSupported(DataFlavor flavor) { |
| return flavor.equals(multiFlavor); |
| } |
| |
| /** Get transfer data. |
| * @param flavor the flavor ({@link #multiFlavor}) |
| * @return {@link MultiTransferObject} that represents data in this object |
| * @exception UnsupportedFlavorException when the flavor is not supported |
| * @exception IOException when it is not possible to read data |
| */ |
| public Object getTransferData(DataFlavor flavor) |
| throws UnsupportedFlavorException, IOException { |
| if (!isDataFlavorSupported(flavor)) { |
| throw new UnsupportedFlavorException(flavor); |
| } |
| |
| return transferObject; |
| } |
| |
| /** Class implementing MultiTransferObject interface. */ |
| static class TransferObjectImpl implements MultiTransferObject { |
| /** transferable objects */ |
| private Transferable[] trans; |
| |
| /** Creates new object from transferable objects. |
| * @param trans array of transferable objects |
| */ |
| public TransferObjectImpl(Transferable[] trans) { |
| this.trans = trans; |
| } |
| |
| /** Number of transfered elements. |
| * @return number of elements |
| */ |
| public int getCount() { |
| return trans.length; |
| } |
| |
| /** @return Transferable at the specific index */ |
| public Transferable getTransferableAt(int index) { |
| return trans[index]; |
| } |
| |
| /** Test whether data flavor is supported by index-th item. |
| * |
| * @param index the index of |
| * @param flavor flavor to test |
| * @return <CODE>true</CODE> if flavor is supported by all elements |
| */ |
| public boolean isDataFlavorSupported(int index, DataFlavor flavor) { |
| try { |
| return trans[index].isDataFlavorSupported(flavor); |
| } catch (Exception e) { |
| return false; |
| |
| // patch to get the Netbeans start under Solaris |
| // [PENDINGbeta] |
| } |
| } |
| |
| /** Test whether each transfered item supports at least one of these |
| * flavors. Each item can support different flavor. |
| * @param array array of flavors |
| */ |
| public boolean areDataFlavorsSupported(DataFlavor[] array) { |
| HashSet<DataFlavor> flav = new HashSet<DataFlavor>(); |
| |
| for (int i = 0; i < array.length; i++) { |
| flav.add(array[i]); |
| } |
| |
| |
| // cycles through all transferable objects and scans their content |
| // to find out if each supports at least one requested flavor |
| outer: |
| for (int i = 0; i < trans.length; i++) { |
| // insert all flavors of the first object into array |
| DataFlavor[] flavors = trans[i].getTransferDataFlavors(); |
| |
| if (flavors == null) { |
| return false; |
| } |
| |
| // loop through rest of Transferable objects |
| for (int j = 0; j < flavors.length; j++) { |
| if (flav.contains(flavors[j])) { |
| // this flavor is supported |
| continue outer; |
| } |
| } |
| |
| // for this transferable no flavor is supported |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** Gets list of all supported flavors for i-th element. |
| * @param i the element to find flavors for |
| * @return array of supported flavors |
| */ |
| public DataFlavor[] getTransferDataFlavors(int i) { |
| return trans[i].getTransferDataFlavors(); |
| } |
| |
| /** |
| * @param indx index of element to work with |
| * @param flavor one needs to obtain |
| * @return object for the flavor of the i-th element |
| */ |
| public Object getTransferData(int indx, DataFlavor flavor) |
| throws UnsupportedFlavorException, IOException { |
| return trans[indx].getTransferData(flavor); |
| } |
| |
| /** Compute common flavors. |
| * @param t array of transferable objects |
| * @return array of common flavors |
| * / |
| private static DataFlavor[] computeCommonFlavors (Transferable[] t) { |
| if (t.length == 0) { |
| // no flavor is supported => return empty array |
| return new DataFlavor[] { }; |
| } |
| |
| // insert all flavors of the first object into array |
| DataFlavor[] flavors = t[0].getTransferDataFlavors (); |
| // number of non null elements in flavors array |
| int flavorsCount = (flavors == null)? 0 : flavors.length; |
| int flavorsLength = flavorsCount; // non-changing length of the original flavors array |
| |
| // loop through rest of Transferable objects |
| for (int i = 1; i < t.length; i++) { |
| // loop through array |
| for (int j = 0; j < flavorsLength; j++) { |
| // if the flavor is not supported |
| boolean supported = false; |
| try { |
| supported = t[i].isDataFlavorSupported (flavors[j]); |
| } catch (Exception e) { |
| // patch to get the Netbeans start under Solaris |
| // [PENDINGbeta] |
| } |
| if (flavors[j] != null && !supported) { |
| // then clear it |
| flavors[j] = null; |
| flavorsCount--; |
| } |
| } |
| } |
| |
| // create resulting array |
| DataFlavor[] result = new DataFlavor[flavorsLength]; |
| for (int i = 0, j = 0; i < flavorsLength; i++) { |
| if (flavors[i] != null) { |
| // add it to the result |
| result[j++] = flavors[i]; |
| } |
| } |
| |
| return result; |
| } |
| */ |
| } |
| } |
| |
| /** TransferableOwnerEmpty is TransferableOwner that contains no data. |
| * |
| * @author Jaroslav Tulach |
| */ |
| private static class Empty extends Object implements Transferable { |
| /** Package private constructor to allow only one instance from TransferableOwner. |
| */ |
| Empty() { |
| } |
| |
| /** Flavors that are supported. |
| * @return empty array |
| */ |
| public DataFlavor[] getTransferDataFlavors() { |
| return new DataFlavor[] { }; |
| } |
| |
| /** Does not support any flavor |
| * @param flavor flavor to test |
| * @return false |
| */ |
| public boolean isDataFlavorSupported(DataFlavor flavor) { |
| return false; |
| } |
| |
| /** Creates transferable data for this flavor. |
| * @exception UnsupportedFlavorException does not support any flavor |
| */ |
| public Object getTransferData(DataFlavor flavor) |
| throws UnsupportedFlavorException, IOException { |
| throw new UnsupportedFlavorException(flavor); |
| } |
| } |
| } |