38 |
|
|
39 |
package gnu.java.awt.peer.gtk; |
package gnu.java.awt.peer.gtk; |
40 |
|
|
41 |
import java.awt.datatransfer.Clipboard; |
import java.awt.Image; |
42 |
import java.awt.datatransfer.ClipboardOwner; |
import java.awt.datatransfer.*; |
43 |
import java.awt.datatransfer.DataFlavor; |
|
44 |
import java.awt.datatransfer.StringSelection; |
import java.io.*; |
45 |
import java.awt.datatransfer.Transferable; |
|
46 |
import java.io.IOException; |
import java.util.List; |
47 |
import java.io.InputStream; |
import java.util.Iterator; |
|
import java.io.InputStreamReader; |
|
48 |
|
|
49 |
public class GtkClipboard extends Clipboard |
public class GtkClipboard extends Clipboard |
50 |
{ |
{ |
|
/* the number of milliseconds that we'll wait around for the |
|
|
owner of the GDK_SELECTION_PRIMARY selection to convert |
|
|
the requested data */ |
|
|
static final int SELECTION_RECEIVED_TIMEOUT = 5000; |
|
|
|
|
|
/* We currently only support transferring of text between applications */ |
|
|
static String selection; |
|
|
static Object selectionLock = new Object (); |
|
51 |
|
|
52 |
static boolean hasSelection = false; |
// Given to the native side so it can signal special targets that |
53 |
|
// can be converted to one of the special predefined DataFlavors. |
54 |
|
static final String stringMimeType; |
55 |
|
static final String imageMimeType; |
56 |
|
static final String filesMimeType; |
57 |
|
|
58 |
|
// Indicates whether the results of the clipboard selection can be |
59 |
|
// cached by GtkSelection. True if |
60 |
|
// gdk_display_supports_selection_notification. |
61 |
|
static final boolean canCache; |
62 |
|
|
63 |
|
static |
64 |
|
{ |
65 |
|
stringMimeType = DataFlavor.stringFlavor.getMimeType(); |
66 |
|
imageMimeType = DataFlavor.imageFlavor.getMimeType(); |
67 |
|
filesMimeType = DataFlavor.javaFileListFlavor.getMimeType(); |
68 |
|
|
69 |
|
canCache = initNativeState(stringMimeType, imageMimeType, filesMimeType); |
70 |
|
} |
71 |
|
|
72 |
protected GtkClipboard() |
/** |
73 |
|
* The one and only gtk+ clipboard instance. |
74 |
|
*/ |
75 |
|
private static GtkClipboard instance = new GtkClipboard(); |
76 |
|
|
77 |
|
/** |
78 |
|
* Creates the clipboard and sets the initial contents to the |
79 |
|
* current gtk+ selection. |
80 |
|
*/ |
81 |
|
private GtkClipboard() |
82 |
{ |
{ |
83 |
super("System Clipboard"); |
super("System Clipboard"); |
84 |
initNativeState(); |
setContents(new GtkSelection(), null); |
85 |
} |
} |
86 |
|
|
87 |
public Transferable getContents(Object requestor) |
/** |
88 |
|
* Returns the one and only GtkClipboard instance. |
89 |
|
*/ |
90 |
|
|
91 |
|
static GtkClipboard getInstance() |
92 |
{ |
{ |
93 |
synchronized (this) |
return instance; |
94 |
|
} |
95 |
|
|
96 |
|
/** |
97 |
|
* Sets the GtkSelection facade as new contents of the clipboard. |
98 |
|
* Called from gtk+ when another application grabs the clipboard and |
99 |
|
* we loose ownership. |
100 |
|
*/ |
101 |
|
private static void setSystemContents() |
102 |
|
{ |
103 |
|
GtkClipboardNotifier.announce(); |
104 |
|
} |
105 |
|
|
106 |
|
/** |
107 |
|
* Sets the new contents and advertises the available flavors to the |
108 |
|
* gtk+ clipboard. |
109 |
|
*/ |
110 |
|
public synchronized void setContents(Transferable contents, |
111 |
|
ClipboardOwner owner) |
112 |
|
{ |
113 |
|
super.setContents(contents, owner); |
114 |
|
|
115 |
|
if (contents == null) |
116 |
{ |
{ |
117 |
if (hasSelection) |
advertiseContent(null, false, false, false); |
118 |
return contents; |
return; |
119 |
} |
} |
120 |
|
|
121 |
/* Java doesn't own the selection, so we need to ask X11 */ |
// We don't need to do anything for a GtkSelection facade. |
122 |
// XXX: Does this hold with Swing too ? |
if (contents instanceof GtkSelection) |
123 |
synchronized (selectionLock) |
return; |
124 |
|
|
125 |
|
boolean text = false; |
126 |
|
boolean images = false; |
127 |
|
boolean files = false; |
128 |
|
|
129 |
|
if (contents instanceof StringSelection |
130 |
|
|| contents.isDataFlavorSupported(DataFlavor.stringFlavor) |
131 |
|
|| contents.isDataFlavorSupported(DataFlavor.plainTextFlavor) |
132 |
|
|| contents.isDataFlavorSupported(DataFlavor |
133 |
|
.getTextPlainUnicodeFlavor())) |
134 |
|
text = true; |
135 |
|
|
136 |
|
DataFlavor[] flavors = contents.getTransferDataFlavors(); |
137 |
|
String[] mimeTargets = new String[flavors.length]; |
138 |
|
for (int i = 0; i < flavors.length; i++) |
139 |
{ |
{ |
140 |
requestStringConversion(); |
DataFlavor flavor = flavors[i]; |
141 |
|
String mimeType = flavor.getMimeType(); |
142 |
try |
mimeTargets[i] = mimeType; |
143 |
|
|
144 |
|
if (! text) |
145 |
|
if ("text".equals(flavor.getPrimaryType()) |
146 |
|
|| flavor.isRepresentationClassReader()) |
147 |
|
text = true; |
148 |
|
|
149 |
|
// XXX - We only support automatic image conversion for |
150 |
|
// GtkImages at the moment. So explicitly check that we have |
151 |
|
// one. |
152 |
|
if (! images && flavors[i].equals(DataFlavor.imageFlavor)) |
153 |
{ |
{ |
154 |
selectionLock.wait(SELECTION_RECEIVED_TIMEOUT); |
try |
155 |
} |
{ |
156 |
catch (InterruptedException e) |
Object o = contents.getTransferData(DataFlavor.imageFlavor); |
157 |
{ |
if (o instanceof GtkImage) |
158 |
return null; |
images = true; |
159 |
|
} |
160 |
|
catch (UnsupportedFlavorException ufe) |
161 |
|
{ |
162 |
|
} |
163 |
|
catch (IOException ioe) |
164 |
|
{ |
165 |
|
} |
166 |
|
catch (ClassCastException cce) |
167 |
|
{ |
168 |
|
} |
169 |
} |
} |
170 |
|
|
171 |
return selection == null ? null : new StringSelection(selection); |
if (flavors[i].equals(DataFlavor.javaFileListFlavor)) |
172 |
|
files = true; |
173 |
} |
} |
174 |
|
|
175 |
|
advertiseContent(mimeTargets, text, images, files); |
176 |
} |
} |
177 |
|
|
178 |
void stringSelectionReceived(String newSelection) |
/** |
179 |
|
* Advertises new contents to the gtk+ clipboard given a string |
180 |
|
* array of (mime-type) targets. When the boolean flags text, images |
181 |
|
* and/or files are set then gtk+ is asked to also advertise the |
182 |
|
* availability of any text, image or uri/file content types it |
183 |
|
* supports. If targets is null (and all flags false) then the |
184 |
|
* selection has explicitly been erased. |
185 |
|
*/ |
186 |
|
private native void advertiseContent(String[] targets, |
187 |
|
boolean text, |
188 |
|
boolean images, |
189 |
|
boolean files); |
190 |
|
|
191 |
|
/** |
192 |
|
* Called by the gtk+ clipboard when an application has requested |
193 |
|
* text. Return a string representing the current clipboard |
194 |
|
* contents or null when no text can be provided. |
195 |
|
*/ |
196 |
|
private String provideText() |
197 |
{ |
{ |
198 |
synchronized (selectionLock) |
Transferable contents = this.contents; |
199 |
|
if (contents == null || contents instanceof GtkSelection) |
200 |
|
return null; |
201 |
|
|
202 |
|
// Handle StringSelection special since that is just pure text. |
203 |
|
if (contents instanceof StringSelection) |
204 |
{ |
{ |
205 |
selection = newSelection; |
try |
206 |
selectionLock.notify(); |
{ |
207 |
|
return (String) contents.getTransferData(DataFlavor.stringFlavor); |
208 |
|
} |
209 |
|
catch (UnsupportedFlavorException ufe) |
210 |
|
{ |
211 |
|
} |
212 |
|
catch (IOException ioe) |
213 |
|
{ |
214 |
|
} |
215 |
|
catch (ClassCastException cce) |
216 |
|
{ |
217 |
|
} |
218 |
} |
} |
|
} |
|
|
|
|
|
/* convert Java clipboard data into a String suitable for sending |
|
|
to another application */ |
|
|
synchronized String stringSelectionHandler() throws IOException |
|
|
{ |
|
|
String selection = null; |
|
219 |
|
|
220 |
|
// Try to get a plain text reader for the current contents and |
221 |
|
// turn the result into a string. |
222 |
try |
try |
223 |
{ |
{ |
224 |
if (contents.isDataFlavorSupported(DataFlavor.stringFlavor)) |
DataFlavor plainText = DataFlavor.getTextPlainUnicodeFlavor(); |
225 |
selection = (String)contents.getTransferData(DataFlavor.stringFlavor); |
Reader r = plainText.getReaderForText(contents); |
226 |
else if (contents.isDataFlavorSupported(DataFlavor.plainTextFlavor)) |
if (r != null) |
227 |
{ |
{ |
228 |
StringBuffer sbuf = new StringBuffer(); |
StringBuffer sb = new StringBuffer(); |
229 |
InputStreamReader reader; |
char[] cs = new char[1024]; |
230 |
char readBuf[] = new char[512]; |
int l = r.read(cs); |
231 |
int numChars; |
while (l != -1) |
232 |
|
{ |
233 |
reader = new InputStreamReader |
sb.append(cs, 0, l); |
234 |
((InputStream) |
l = r.read(cs); |
|
contents.getTransferData(DataFlavor.plainTextFlavor), "UNICODE"); |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
numChars = reader.read(readBuf); |
|
|
if (numChars == -1) |
|
|
break; |
|
|
sbuf.append(readBuf, 0, numChars); |
|
235 |
} |
} |
236 |
|
return sb.toString(); |
|
selection = new String(sbuf); |
|
237 |
} |
} |
238 |
} |
} |
239 |
catch (Exception e) |
catch (IllegalArgumentException iae) |
240 |
|
{ |
241 |
|
} |
242 |
|
catch (UnsupportedEncodingException iee) |
243 |
|
{ |
244 |
|
} |
245 |
|
catch (UnsupportedFlavorException ufe) |
246 |
{ |
{ |
247 |
} |
} |
248 |
|
catch (IOException ioe) |
249 |
return selection; |
{ |
250 |
|
} |
251 |
|
|
252 |
|
return null; |
253 |
} |
} |
254 |
|
|
255 |
public synchronized void setContents(Transferable contents, |
/** |
256 |
ClipboardOwner owner) |
* Called by the gtk+ clipboard when an application has requested an |
257 |
|
* image. Returns a GtkImage representing the current clipboard |
258 |
|
* contents or null when no image can be provided. |
259 |
|
*/ |
260 |
|
private GtkImage provideImage() |
261 |
{ |
{ |
262 |
selectionGet(); |
Transferable contents = this.contents; |
263 |
|
if (contents == null || contents instanceof GtkSelection) |
264 |
|
return null; |
265 |
|
|
266 |
this.contents = contents; |
try |
267 |
this.owner = owner; |
{ |
268 |
|
return (GtkImage) contents.getTransferData(DataFlavor.imageFlavor); |
269 |
|
} |
270 |
|
catch (UnsupportedFlavorException ufe) |
271 |
|
{ |
272 |
|
} |
273 |
|
catch (IOException ioe) |
274 |
|
{ |
275 |
|
} |
276 |
|
catch (ClassCastException cce) |
277 |
|
{ |
278 |
|
} |
279 |
|
|
280 |
hasSelection = true; |
return null; |
281 |
} |
} |
282 |
|
|
283 |
synchronized void selectionClear() |
/** |
284 |
|
* Called by the gtk+ clipboard when an application has requested a |
285 |
|
* uri-list. Return a string array containing the URIs representing |
286 |
|
* the current clipboard contents or null when no URIs can be |
287 |
|
* provided. |
288 |
|
*/ |
289 |
|
private String[] provideURIs() |
290 |
{ |
{ |
291 |
hasSelection = false; |
Transferable contents = this.contents; |
292 |
|
if (contents == null || contents instanceof GtkSelection) |
293 |
|
return null; |
294 |
|
|
295 |
if (owner != null) |
try |
296 |
{ |
{ |
297 |
owner.lostOwnership(this, contents); |
List list = (List) contents.getTransferData |
298 |
owner = null; |
(DataFlavor.javaFileListFlavor); |
299 |
contents = null; |
String[] uris = new String[list.size()]; |
300 |
|
int u = 0; |
301 |
|
Iterator it = list.iterator(); |
302 |
|
while (it.hasNext()) |
303 |
|
uris[u++] = ((File) it.next()).toURI().toString(); |
304 |
|
return uris; |
305 |
} |
} |
306 |
|
catch (UnsupportedFlavorException ufe) |
307 |
|
{ |
308 |
|
} |
309 |
|
catch (IOException ioe) |
310 |
|
{ |
311 |
|
} |
312 |
|
catch (ClassCastException cce) |
313 |
|
{ |
314 |
|
} |
315 |
|
|
316 |
|
return null; |
317 |
|
} |
318 |
|
|
319 |
|
/** |
320 |
|
* Called by gtk+ clipboard when an application requests the given |
321 |
|
* target mime-type. Returns a byte array containing the requested |
322 |
|
* data, or null when the contents cannot be provided in the |
323 |
|
* requested target mime-type. Only called after any explicit text, |
324 |
|
* image or file/uri requests have been handled earlier and failed. |
325 |
|
*/ |
326 |
|
private byte[] provideContent(String target) |
327 |
|
{ |
328 |
|
// Sanity check. The callback could be triggered just after we |
329 |
|
// changed the clipboard. |
330 |
|
Transferable contents = this.contents; |
331 |
|
if (contents == null || contents instanceof GtkSelection) |
332 |
|
return null; |
333 |
|
|
334 |
|
// XXX - We are being called from a gtk+ callback. Which means we |
335 |
|
// should return as soon as possible and not call arbitrary code |
336 |
|
// that could deadlock or go bonkers. But we don't really know |
337 |
|
// what DataTransfer contents object we are dealing with. Same for |
338 |
|
// the other provideXXX() methods. |
339 |
|
try |
340 |
|
{ |
341 |
|
DataFlavor flavor = new DataFlavor(target); |
342 |
|
Object o = contents.getTransferData(flavor); |
343 |
|
|
344 |
|
if (o instanceof byte[]) |
345 |
|
return (byte[]) o; |
346 |
|
|
347 |
|
if (o instanceof InputStream) |
348 |
|
{ |
349 |
|
InputStream is = (InputStream) o; |
350 |
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
351 |
|
byte[] bs = new byte[1024]; |
352 |
|
int l = is.read(bs); |
353 |
|
while (l != -1) |
354 |
|
{ |
355 |
|
baos.write(bs, 0, l); |
356 |
|
l = is.read(bs); |
357 |
|
} |
358 |
|
return baos.toByteArray(); |
359 |
|
} |
360 |
|
|
361 |
|
if (o instanceof Serializable) |
362 |
|
{ |
363 |
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
364 |
|
ObjectOutputStream oos = new ObjectOutputStream(baos); |
365 |
|
oos.writeObject(o); |
366 |
|
oos.close(); |
367 |
|
return baos.toByteArray(); |
368 |
|
} |
369 |
|
} |
370 |
|
catch (ClassNotFoundException cnfe) |
371 |
|
{ |
372 |
|
} |
373 |
|
catch (UnsupportedFlavorException ufe) |
374 |
|
{ |
375 |
|
} |
376 |
|
catch (IOException ioe) |
377 |
|
{ |
378 |
|
} |
379 |
|
catch (ClassCastException cce) |
380 |
|
{ |
381 |
|
} |
382 |
|
|
383 |
|
return null; |
384 |
} |
} |
385 |
|
|
386 |
native void initNativeState(); |
/** |
387 |
static native void requestStringConversion(); |
* Initializes the gtk+ clipboard and caches any native side |
388 |
static native void selectionGet(); |
* structures needed. Returns whether or not the contents of the |
389 |
|
* Clipboard can be cached (gdk_display_supports_selection_notification). |
390 |
|
*/ |
391 |
|
private static native boolean initNativeState(String stringTarget, |
392 |
|
String imageTarget, |
393 |
|
String filesTarget); |
394 |
} |
} |