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 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:    */
  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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 431:   private static native boolean initNativeState(GtkClipboard clipboard,
 432:                                                 GtkClipboard selection,
 433:                                                 String stringTarget,
 434:                                                 String imageTarget,
 435:                                                 String filesTarget);
 436: }