Source for gnu.java.awt.peer.gtk.GtkClipboard

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