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
 159:                       .getTextPlainUnicodeFlavor()))
 160:       text = true;
 161: 
 162:     DataFlavor[] flavors = contents.getTransferDataFlavors();
 163:     String[] mimeTargets = new String[flavors.length];
 164:     for (int i = 0; i < flavors.length; i++)
 165:       {
 166:     DataFlavor flavor = flavors[i];
 167:     String mimeType = flavor.getMimeType();
 168:     mimeTargets[i] = mimeType;
 169: 
 170:     if (! text)
 171:       if ("text".equals(flavor.getPrimaryType())
 172:           || flavor.isRepresentationClassReader())
 173:         text = true;
 174: 
 175:     if (! images && flavors[i].equals(DataFlavor.imageFlavor))
 176:       {
 177:         try
 178:           {
 179:         Object o = contents.getTransferData(DataFlavor.imageFlavor);
 180:         if (o instanceof Image)
 181:           images = true;
 182:           }
 183:         catch (UnsupportedFlavorException ufe)
 184:           {
 185:           }
 186:         catch (IOException ioe)
 187:           {
 188:           }
 189:         catch (ClassCastException cce)
 190:           {
 191:           }
 192:       }
 193: 
 194:     if (flavors[i].equals(DataFlavor.javaFileListFlavor))
 195:       files = true;
 196:       }
 197: 
 198:     advertiseContent(mimeTargets, text, images, files);
 199:   }
 200: 
 201:   /**
 202:    * Advertises new contents to the gtk+ clipboard given a string
 203:    * array of (mime-type) targets. When the boolean flags text, images
 204:    * and/or files are set then gtk+ is asked to also advertise the
 205:    * availability of any text, image or uri/file content types it
 206:    * supports. If targets is null (and all flags false) then the
 207:    * selection has explicitly been erased.
 208:    */
 209:   private native void advertiseContent(String[] targets,
 210:                        boolean text,
 211:                        boolean images,
 212:                        boolean files);
 213:   
 214:   /**
 215:    * Called by the gtk+ clipboard when an application has requested
 216:    * text.  Return a string representing the current clipboard
 217:    * contents or null when no text can be provided.
 218:    */
 219:   private String provideText()
 220:   {
 221:     Transferable contents = this.contents;
 222:     if (contents == null || contents instanceof GtkSelection)
 223:       return null;
 224: 
 225:     // Handle StringSelection special since that is just pure text.
 226:     if (contents instanceof StringSelection)
 227:       {
 228:         try
 229:           {
 230:             return (String) contents.getTransferData(DataFlavor.stringFlavor);
 231:       }
 232:         catch (UnsupportedFlavorException ufe)
 233:           {
 234:           }
 235:         catch (IOException ioe)
 236:           {
 237:           }
 238:         catch (ClassCastException cce)
 239:           {
 240:           }
 241:       }
 242: 
 243:     // Try to get a plain text reader for the current contents and
 244:     // turn the result into a string.
 245:     try
 246:       {
 247:     DataFlavor plainText = DataFlavor.getTextPlainUnicodeFlavor();
 248:     Reader r = plainText.getReaderForText(contents);
 249:     if (r != null)
 250:       {
 251:         StringBuffer sb = new StringBuffer();
 252:         char[] cs = new char[1024];
 253:         int l = r.read(cs);
 254:         while (l != -1)
 255:           {
 256:         sb.append(cs, 0, l);
 257:         l = r.read(cs);
 258:           }
 259:         return sb.toString();
 260:       }
 261:       }
 262:     catch (IllegalArgumentException iae)
 263:       {
 264:       }
 265:     catch (UnsupportedEncodingException iee)
 266:       {
 267:       }
 268:     catch (UnsupportedFlavorException ufe)
 269:       {
 270:       }
 271:     catch (IOException ioe)
 272:       {
 273:       }
 274: 
 275:     return null;
 276:   }
 277: 
 278:   /**
 279:    * Called by the gtk+ clipboard when an application has requested an
 280:    * image.  Returns a GtkImage representing the current clipboard
 281:    * contents or null when no image can be provided.
 282:    */
 283:   private GtkImage provideImage()
 284:   {
 285:     Transferable contents = this.contents;
 286:     if (contents == null || contents instanceof GtkSelection)
 287:       return null;
 288: 
 289:     try
 290:       {
 291:     Object o = contents.getTransferData(DataFlavor.imageFlavor);
 292:     if( o instanceof GtkImage )
 293:       return (GtkImage) o;
 294:     else
 295:       return new GtkImage(((Image)o).getSource());
 296:       }
 297:     catch (UnsupportedFlavorException ufe)
 298:       {
 299:       }
 300:     catch (IOException ioe)
 301:       {
 302:       }
 303:     catch (ClassCastException cce)
 304:       {
 305:       }
 306: 
 307:     return null;
 308:   }
 309: 
 310:   /**
 311:    * Called by the gtk+ clipboard when an application has requested a
 312:    * uri-list.  Return a string array containing the URIs representing
 313:    * the current clipboard contents or null when no URIs can be
 314:    * provided.
 315:    */
 316:   private String[] provideURIs()
 317:   {
 318:     Transferable contents = this.contents;
 319:     if (contents == null || contents instanceof GtkSelection)
 320:       return null;
 321: 
 322:     try
 323:       {
 324:     List list = (List) contents.getTransferData
 325:       (DataFlavor.javaFileListFlavor);
 326:     String[] uris = new String[list.size()];
 327:     int u = 0;
 328:     Iterator it = list.iterator();
 329:     while (it.hasNext())
 330:       uris[u++] = ((File) it.next()).toURI().toString();
 331:     return uris;
 332:       }
 333:     catch (UnsupportedFlavorException ufe)
 334:       {
 335:       }
 336:     catch (IOException ioe)
 337:       {
 338:       }
 339:     catch (ClassCastException cce)
 340:       {
 341:       }
 342: 
 343:     return null;
 344:   }
 345: 
 346:   /**
 347:    * Called by gtk+ clipboard when an application requests the given
 348:    * target mime-type. Returns a byte array containing the requested
 349:    * data, or null when the contents cannot be provided in the
 350:    * requested target mime-type. Only called after any explicit text,
 351:    * image or file/uri requests have been handled earlier and failed.
 352:    */
 353:   private byte[] provideContent(String target)
 354:   {
 355:     // Sanity check. The callback could be triggered just after we
 356:     // changed the clipboard.
 357:     Transferable contents = this.contents;
 358:     if (contents == null || contents instanceof GtkSelection)
 359:       return null;
 360: 
 361:     // XXX - We are being called from a gtk+ callback. Which means we
 362:     // should return as soon as possible and not call arbitrary code
 363:     // that could deadlock or go bonkers. But we don't really know
 364:     // what DataTransfer contents object we are dealing with. Same for
 365:     // the other provideXXX() methods.
 366:     try
 367:       {
 368:     DataFlavor flavor = new DataFlavor(target);
 369:     Object o = contents.getTransferData(flavor);
 370: 
 371:     if (o instanceof byte[])
 372:       return (byte[]) o;
 373: 
 374:     if (o instanceof InputStream)
 375:       {
 376:         InputStream is = (InputStream) o;
 377:         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 378:         byte[] bs = new byte[1024];
 379:         int l = is.read(bs);
 380:         while (l != -1)
 381:           {
 382:         baos.write(bs, 0, l);
 383:         l = is.read(bs);
 384:           }
 385:         return baos.toByteArray();
 386:       }
 387: 
 388:     if (o instanceof Serializable)
 389:       {
 390:         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 391:         ObjectOutputStream oos = new ObjectOutputStream(baos);
 392:         oos.writeObject(o);
 393:         oos.close();
 394:         return baos.toByteArray();
 395:       }
 396:       }
 397:     catch (ClassNotFoundException cnfe)
 398:       {
 399:       }
 400:     catch (UnsupportedFlavorException ufe)
 401:       {
 402:       }
 403:     catch (IOException ioe)
 404:       {
 405:       }
 406:     catch (ClassCastException cce)
 407:       {
 408:       }
 409: 
 410:     return null;
 411:   }
 412: 
 413:   /**
 414:    * Initializes the gtk+ clipboards and caches any native side
 415:    * structures needed. Returns whether or not the contents of the
 416:    * Clipboard can be cached (gdk_display_supports_selection_notification).
 417:    */
 418:   private static native boolean initNativeState(GtkClipboard clipboard,
 419:                         GtkClipboard selection,
 420:                         String stringTarget,
 421:                         String imageTarget,
 422:                         String filesTarget);
 423: }