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}