001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.datatransfer;
003
004import java.awt.GraphicsEnvironment;
005import java.awt.HeadlessException;
006import java.awt.Toolkit;
007import java.awt.datatransfer.Clipboard;
008import java.awt.datatransfer.ClipboardOwner;
009import java.awt.datatransfer.DataFlavor;
010import java.awt.datatransfer.StringSelection;
011import java.awt.datatransfer.Transferable;
012import java.awt.datatransfer.UnsupportedFlavorException;
013import java.io.IOException;
014
015import org.openstreetmap.josm.Main;
016import org.openstreetmap.josm.gui.util.GuiHelper;
017import org.openstreetmap.josm.tools.Utils;
018
019/**
020 * This is a utility class that provides methods useful for general data transfer support.
021 *
022 * @author Michael Zangl
023 * @since 10604
024 */
025public final class ClipboardUtils {
026    private static final class DoNothingClipboardOwner implements ClipboardOwner {
027        @Override
028        public void lostOwnership(Clipboard clpbrd, Transferable t) {
029            // Do nothing
030        }
031    }
032
033    private static Clipboard clipboard;
034
035    private ClipboardUtils() {
036    }
037
038    /**
039     * This method should be used from all of JOSM to access the clipboard.
040     * <p>
041     * It will default to the system clipboard except for cases where that clipboard is not accessible.
042     * @return A clipboard.
043     * @see #getClipboardContent()
044     */
045    public static synchronized Clipboard getClipboard() {
046        // Might be unsupported in some more cases, we need a fake clipboard then.
047        if (clipboard == null) {
048            try {
049                clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
050            } catch (HeadlessException e) {
051                Main.warn("Headless. Using fake clipboard.", e);
052                clipboard = new Clipboard("fake");
053            }
054        }
055        return clipboard;
056    }
057
058    /**
059     * Gets the singleton instance of the system selection as a <code>Clipboard</code> object.
060     * This allows an application to read and modify the current, system-wide selection.
061     * @return the system selection as a <code>Clipboard</code>, or <code>null</code> if the native platform does not
062     *         support a system selection <code>Clipboard</code> or if GraphicsEnvironment.isHeadless() returns true
063     * @see Toolkit#getSystemSelection
064     */
065    public static Clipboard getSystemSelection() {
066        if (GraphicsEnvironment.isHeadless()) {
067            return null;
068        } else {
069            return Toolkit.getDefaultToolkit().getSystemSelection();
070        }
071    }
072
073    /**
074     * Gets the clipboard content as string.
075     * @return the content if available, <code>null</code> otherwise.
076     */
077    public static String getClipboardStringContent() {
078        try {
079            Transferable t = getClipboardContent();
080            if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
081                return (String) t.getTransferData(DataFlavor.stringFlavor);
082            }
083        } catch (UnsupportedFlavorException | IOException ex) {
084            Main.error(ex);
085        }
086        return null;
087    }
088
089    /**
090     * Extracts clipboard content as {@code Transferable} object. Using this method avoids some problems on some platforms.
091     * @return The content or <code>null</code> if it is not available
092     */
093    public static synchronized Transferable getClipboardContent() {
094        return getClipboardContent(getClipboard());
095    }
096
097    /**
098     * Extracts clipboard content as {@code Transferable} object. Using this method avoids some problems on some platforms.
099     * @param clipboard clipboard from which contents are retrieved
100     * @return clipboard contents if available, {@code null} otherwise.
101     */
102    public static Transferable getClipboardContent(Clipboard clipboard) {
103        Transferable t = null;
104        for (int tries = 0; t == null && tries < 10; tries++) {
105            try {
106                t = clipboard.getContents(null);
107            } catch (IllegalStateException e) {
108                // Clipboard currently unavailable.
109                // On some platforms, the system clipboard is unavailable while it is accessed by another application.
110                Main.trace("Clipboard unavailable.", e);
111                try {
112                    Thread.sleep(1);
113                } catch (InterruptedException ex) {
114                    Main.warn(ex, "InterruptedException in " + Utils.class.getSimpleName()
115                            + " while getting clipboard content");
116                }
117            } catch (NullPointerException e) {
118                // JDK-6322854: On Linux/X11, NPE can happen for unknown reasons, on all versions of Java
119                Main.error(e);
120            }
121        }
122        return t;
123    }
124
125    /**
126     * Copy the given string to the clipboard.
127     * @param s The string to copy.
128     * @return True if the  copy was successful
129     */
130    public static boolean copyString(String s) {
131        return copy(new StringSelection(s));
132    }
133
134    /**
135     * Copies the given transferable to the clipboard. Handles state problems that occur on some platforms.
136     * @param transferable The transferable to copy.
137     * @return True if the copy was successful
138     */
139    public static boolean copy(final Transferable transferable) {
140        return GuiHelper.runInEDTAndWaitAndReturn(() -> {
141            try {
142                getClipboard().setContents(transferable, new DoNothingClipboardOwner());
143                return true;
144            } catch (IllegalStateException ex) {
145                Main.error(ex);
146                return false;
147            }
148        });
149    }
150
151    /**
152     * Returns a new {@link DataFlavor} for the given class and human-readable name.
153     * @param c class
154     * @param humanPresentableName the human-readable string used to identify this flavor
155     * @return a new {@link DataFlavor} for the given class and human-readable name
156     * @since 10801
157     */
158    public static DataFlavor newDataFlavor(Class<?> c, String humanPresentableName) {
159        try {
160            return new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=" + c.getName(),
161                    humanPresentableName, c.getClassLoader());
162        } catch (ClassNotFoundException e) {
163            throw new IllegalArgumentException(e);
164        }
165    }
166}