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