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