1 /* GtkClipboard.java
2    Copyright (C) 1999, 2005, 2006 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package gnu.java.awt.peer.gtk;
40 
41 import gnu.java.lang.CPStringBuilder;
42 
43 import java.awt.Image;
44 
45 import java.awt.datatransfer.Clipboard;
46 import java.awt.datatransfer.ClipboardOwner;
47 import java.awt.datatransfer.DataFlavor;
48 import java.awt.datatransfer.StringSelection;
49 import java.awt.datatransfer.Transferable;
50 import java.awt.datatransfer.UnsupportedFlavorException;
51 
52 import java.io.ByteArrayOutputStream;
53 import java.io.File;
54 import java.io.InputStream;
55 import java.io.IOException;
56 import java.io.ObjectOutputStream;
57 import java.io.Reader;
58 import java.io.Serializable;
59 import java.io.UnsupportedEncodingException;
60 
61 import java.util.List;
62 import java.util.Iterator;
63 
64 public class GtkClipboard extends Clipboard
65 {
66   /**
67    * The one and only gtk+ clipboard instance for the CLIPBOARD selection.
68    */
69   final static GtkClipboard clipboard = new GtkClipboard("System Clipboard");
70 
71   /**
72    * The one and only gtk+ clipboard instance for the PRIMARY selection.
73    */
74   final static GtkClipboard selection = new GtkClipboard("System Selection");
75 
76   // Given to the native side so it can signal special targets that
77   // can be converted to one of the special predefined DataFlavors.
78   static final String stringMimeType
79     = DataFlavor.stringFlavor.getMimeType();
80   static final String imageMimeType
81     = DataFlavor.imageFlavor.getMimeType();
82   static final String filesMimeType
83     = DataFlavor.javaFileListFlavor.getMimeType();
84 
85   // Indicates whether the results of the clipboard selection can be
86   // cached by GtkSelection. True if
87   // gdk_display_supports_selection_notification.
88   static final boolean canCache = initNativeState(clipboard, selection,
89                                                   stringMimeType,
90                                                   imageMimeType,
91                                                   filesMimeType);
92 
93   /**
94    * Creates the clipboard and sets the initial contents to the
95    * current gtk+ selection.
96    */
GtkClipboard(String name)97   private GtkClipboard(String name)
98   {
99     super(name);
100     setContents(new GtkSelection(this), null);
101   }
102 
103   /**
104    * Returns the one and only GtkClipboard instance for the CLIPBOARD
105    * selection.
106    */
getClipboardInstance()107   static GtkClipboard getClipboardInstance()
108   {
109     return clipboard;
110   }
111 
112   /**
113    * Returns the one and only GtkClipboard instance for the PRIMARY
114    * selection.
115    */
getSelectionInstance()116   static GtkClipboard getSelectionInstance()
117   {
118     return selection;
119   }
120 
121   /**
122    * Sets the GtkSelection facade as new contents of the clipboard.
123    * Called from gtk+ when another application grabs the clipboard and
124    * we loose ownership.
125    *
126    * @param cleared If true this is a clear event (someone takes the
127    * clipboard from us) otherwise it is an owner changed event.
128    */
setSystemContents(boolean cleared)129   private synchronized void setSystemContents(boolean cleared)
130   {
131     // We need to notify clipboard owner listeners when we were the
132     // owner (the selection was explictly set) and someone takes the
133     // clipboard away from us and asks us the clear any held storage,
134     // or if we weren't the owner of the clipboard to begin with, but
135     // the clipboard contents changed. We could refine this and check
136     // whether the actual available formats did in fact change, but we
137     // assume listeners will check for that anyway (and if possible we
138     // ask to cache the available formats so even if multiple
139     // listeners check after a notification the overhead should be
140     // minimal).
141     boolean owner = ! (contents instanceof GtkSelection);
142     boolean needNotification = (cleared && owner) || (! cleared && ! owner);
143     if (needNotification)
144       GtkClipboardNotifier.announce(this);
145   }
146 
147   /**
148    * Sets the new contents and advertises the available flavors to the
149    * gtk+ clipboard.
150    */
setContents(Transferable contents, ClipboardOwner owner)151   public synchronized void setContents(Transferable contents,
152                                        ClipboardOwner owner)
153   {
154     super.setContents(contents, owner);
155 
156     if (contents == null)
157       {
158         advertiseContent(null, false, false, false);
159         return;
160       }
161 
162     // We don't need to do anything for a GtkSelection facade.
163     if (contents instanceof GtkSelection)
164       return;
165 
166     boolean text = false;
167     boolean images = false;
168     boolean files = false;
169 
170     if (contents instanceof StringSelection
171         || contents.isDataFlavorSupported(DataFlavor.stringFlavor)
172         || contents.isDataFlavorSupported(DataFlavor.plainTextFlavor)
173         || contents.isDataFlavorSupported(DataFlavor.getTextPlainUnicodeFlavor()))
174       text = true;
175 
176     DataFlavor[] flavors = contents.getTransferDataFlavors();
177     String[] mimeTargets = new String[flavors.length];
178     for (int i = 0; i < flavors.length; i++)
179       {
180         DataFlavor flavor = flavors[i];
181         String mimeType = flavor.getMimeType();
182         mimeTargets[i] = mimeType;
183 
184         if (! text)
185           if ("text".equals(flavor.getPrimaryType())
186               || flavor.isRepresentationClassReader())
187             text = true;
188 
189         if (! images && flavors[i].equals(DataFlavor.imageFlavor))
190           {
191             try
192               {
193                 Object o = contents.getTransferData(DataFlavor.imageFlavor);
194                 if (o instanceof Image)
195                   images = true;
196               }
197             catch (UnsupportedFlavorException ufe)
198               {
199               }
200             catch (IOException ioe)
201               {
202               }
203             catch (ClassCastException cce)
204               {
205               }
206           }
207 
208         if (flavors[i].equals(DataFlavor.javaFileListFlavor))
209           files = true;
210       }
211 
212     advertiseContent(mimeTargets, text, images, files);
213   }
214 
215   /**
216    * Advertises new contents to the gtk+ clipboard given a string
217    * array of (mime-type) targets. When the boolean flags text, images
218    * and/or files are set then gtk+ is asked to also advertise the
219    * availability of any text, image or uri/file content types it
220    * supports. If targets is null (and all flags false) then the
221    * selection has explicitly been erased.
222    */
advertiseContent(String[] targets, boolean text, boolean images, boolean files)223   private native void advertiseContent(String[] targets,
224                                        boolean text,
225                                        boolean images,
226                                        boolean files);
227 
228   /**
229    * Called by the gtk+ clipboard when an application has requested
230    * text.  Return a string representing the current clipboard
231    * contents or null when no text can be provided.
232    */
provideText()233   private String provideText()
234   {
235     Transferable contents = this.contents;
236     if (contents == null || contents instanceof GtkSelection)
237       return null;
238 
239     // Handle StringSelection special since that is just pure text.
240     if (contents instanceof StringSelection)
241       {
242         try
243           {
244             return (String) contents.getTransferData(DataFlavor.stringFlavor);
245           }
246         catch (UnsupportedFlavorException ufe)
247           {
248           }
249         catch (IOException ioe)
250           {
251           }
252         catch (ClassCastException cce)
253           {
254           }
255       }
256 
257     // Try to get a plain text reader for the current contents and
258     // turn the result into a string.
259     try
260       {
261         DataFlavor plainText = DataFlavor.getTextPlainUnicodeFlavor();
262         Reader r = plainText.getReaderForText(contents);
263         if (r != null)
264           {
265             CPStringBuilder sb = new CPStringBuilder();
266             char[] cs = new char[1024];
267             int l = r.read(cs);
268             while (l != -1)
269               {
270                 sb.append(cs, 0, l);
271                 l = r.read(cs);
272               }
273             return sb.toString();
274           }
275       }
276     catch (IllegalArgumentException iae)
277       {
278       }
279     catch (UnsupportedEncodingException iee)
280       {
281       }
282     catch (UnsupportedFlavorException ufe)
283       {
284       }
285     catch (IOException ioe)
286       {
287       }
288 
289     return null;
290   }
291 
292   /**
293    * Called by the gtk+ clipboard when an application has requested an
294    * image.  Returns a GtkImage representing the current clipboard
295    * contents or null when no image can be provided.
296    */
provideImage()297   private GtkImage provideImage()
298   {
299     Transferable contents = this.contents;
300     if (contents == null || contents instanceof GtkSelection)
301       return null;
302 
303     try
304       {
305         Object o = contents.getTransferData(DataFlavor.imageFlavor);
306         if( o instanceof GtkImage )
307           return (GtkImage) o;
308         else
309           return new GtkImage(((Image)o).getSource());
310       }
311     catch (UnsupportedFlavorException ufe)
312       {
313       }
314     catch (IOException ioe)
315       {
316       }
317     catch (ClassCastException cce)
318       {
319       }
320 
321     return null;
322   }
323 
324   /**
325    * Called by the gtk+ clipboard when an application has requested a
326    * uri-list.  Return a string array containing the URIs representing
327    * the current clipboard contents or null when no URIs can be
328    * provided.
329    */
provideURIs()330   private String[] provideURIs()
331   {
332     Transferable contents = this.contents;
333     if (contents == null || contents instanceof GtkSelection)
334       return null;
335 
336     try
337       {
338         List list = (List) contents.getTransferData(DataFlavor.javaFileListFlavor);
339         String[] uris = new String[list.size()];
340         int u = 0;
341         Iterator it = list.iterator();
342         while (it.hasNext())
343           uris[u++] = ((File) it.next()).toURI().toString();
344         return uris;
345       }
346     catch (UnsupportedFlavorException ufe)
347       {
348       }
349     catch (IOException ioe)
350       {
351       }
352     catch (ClassCastException cce)
353       {
354       }
355 
356     return null;
357   }
358 
359   /**
360    * Called by gtk+ clipboard when an application requests the given
361    * target mime-type. Returns a byte array containing the requested
362    * data, or null when the contents cannot be provided in the
363    * requested target mime-type. Only called after any explicit text,
364    * image or file/uri requests have been handled earlier and failed.
365    */
provideContent(String target)366   private byte[] provideContent(String target)
367   {
368     // Sanity check. The callback could be triggered just after we
369     // changed the clipboard.
370     Transferable contents = this.contents;
371     if (contents == null || contents instanceof GtkSelection)
372       return null;
373 
374     // XXX - We are being called from a gtk+ callback. Which means we
375     // should return as soon as possible and not call arbitrary code
376     // that could deadlock or go bonkers. But we don't really know
377     // what DataTransfer contents object we are dealing with. Same for
378     // the other provideXXX() methods.
379     try
380       {
381         DataFlavor flavor = new DataFlavor(target);
382         Object o = contents.getTransferData(flavor);
383 
384         if (o instanceof byte[])
385           return (byte[]) o;
386 
387         if (o instanceof InputStream)
388           {
389             InputStream is = (InputStream) o;
390             ByteArrayOutputStream baos = new ByteArrayOutputStream();
391             byte[] bs = new byte[1024];
392             int l = is.read(bs);
393             while (l != -1)
394               {
395                 baos.write(bs, 0, l);
396                 l = is.read(bs);
397               }
398             return baos.toByteArray();
399           }
400 
401         if (o instanceof Serializable)
402           {
403             ByteArrayOutputStream baos = new ByteArrayOutputStream();
404             ObjectOutputStream oos = new ObjectOutputStream(baos);
405             oos.writeObject(o);
406             oos.close();
407             return baos.toByteArray();
408           }
409       }
410     catch (ClassNotFoundException cnfe)
411       {
412       }
413     catch (UnsupportedFlavorException ufe)
414       {
415       }
416     catch (IOException ioe)
417       {
418       }
419     catch (ClassCastException cce)
420       {
421       }
422 
423     return null;
424   }
425 
426   /**
427    * Initializes the gtk+ clipboards and caches any native side
428    * structures needed. Returns whether or not the contents of the
429    * Clipboard can be cached (gdk_display_supports_selection_notification).
430    */
initNativeState(GtkClipboard clipboard, GtkClipboard selection, String stringTarget, String imageTarget, String filesTarget)431   private static native boolean initNativeState(GtkClipboard clipboard,
432                                                 GtkClipboard selection,
433                                                 String stringTarget,
434                                                 String imageTarget,
435                                                 String filesTarget);
436 }
437