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

   1: /* GtkClipboard.java - Class representing gtk+ clipboard selection.
   2:    Copyright (C) 2005 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.classpath.Pointer;
  42: 
  43: import java.awt.datatransfer.*;
  44: 
  45: import java.io.*;
  46: import java.net.*;
  47: import java.util.*;
  48: 
  49: import java.awt.Image;
  50: 
  51: /**
  52:  * Class representing the gtk+ clipboard selection. This is used when
  53:  * another program owns the clipboard. Whenever the system clipboard
  54:  * selection changes we create a new instance to notify the program
  55:  * that the available flavors might have changed. When requested it
  56:  * (lazily) caches the targets, and (text, image, or files/uris)
  57:  * clipboard contents.
  58:  */
  59: public class GtkSelection implements Transferable
  60: {
  61:   /**
  62:    * Static lock used for requests of mimetypes and contents retrieval.
  63:    */
  64:   static private Object requestLock = new Object();
  65: 
  66:   /**
  67:    * Whether we belong to the Clipboard (true) or to the Primary selection.
  68:    */
  69:   private final boolean clipboard;
  70: 
  71:   /**
  72:    * Whether a request for mimetypes, text, images, uris or byte[] is
  73:    * currently in progress. Should only be tested or set with
  74:    * requestLock held. When true no other requests should be made till
  75:    * it is false again.
  76:    */
  77:   private boolean requestInProgress;
  78: 
  79:   /**
  80:    * Indicates a requestMimeTypes() call was made and the
  81:    * corresponding mimeTypesAvailable() callback was triggered.
  82:    */
  83:   private boolean mimeTypesDelivered;
  84: 
  85:   /**
  86:    * Set and returned by getTransferDataFlavors. Only valid when
  87:    * mimeTypesDelivered is true.
  88:    */
  89:   private DataFlavor[] dataFlavors;
  90: 
  91:   /**
  92:    * Indicates a requestText() call was made and the corresponding
  93:    * textAvailable() callback was triggered.
  94:    */
  95:   private boolean textDelivered;
  96: 
  97:   /**
  98:    * Set as response to a requestText() call and possibly returned by
  99:    * getTransferData() for text targets. Only valid when textDelivered
 100:    * is true.
 101:    */
 102:   private String text;
 103: 
 104:   /**
 105:    * Indicates a requestImage() call was made and the corresponding
 106:    * imageAvailable() callback was triggered.
 107:    */
 108:   private boolean imageDelivered;
 109: 
 110:   /**
 111:    * Set as response to a requestImage() call and possibly returned by
 112:    * getTransferData() for image targets. Only valid when
 113:    * imageDelivered is true and image is null.
 114:    */
 115:   private Pointer imagePointer;
 116: 
 117:   /**
 118:    * Cached image value. Only valid when imageDelivered is
 119:    * true. Created from imagePointer.
 120:    */
 121:   private Image image;
 122: 
 123:   /**
 124:    * Indicates a requestUris() call was made and the corresponding
 125:    * urisAvailable() callback was triggered.
 126:    */
 127:   private boolean urisDelivered;
 128: 
 129:   /**
 130:    * Set as response to a requestURIs() call. Only valid when
 131:    * urisDelivered is true
 132:    */
 133:   private List<File> uris;
 134: 
 135:   /**
 136:    * Indicates a requestBytes(String) call was made and the
 137:    * corresponding bytesAvailable() callback was triggered.
 138:    */
 139:   private boolean bytesDelivered;
 140: 
 141:   /**
 142:    * Set as response to a requestBytes(String) call. Only valid when
 143:    * bytesDelivered is true.
 144:    */
 145:   private byte[] bytes;
 146: 
 147:   /**
 148:    * Should only be created by the GtkClipboard class. The clipboard
 149:    * should be either GtkClipboard.clipboard or
 150:    * GtkClipboard.selection.
 151:    */
 152:   GtkSelection(GtkClipboard clipboard)
 153:   {
 154:     this.clipboard = (clipboard == GtkClipboard.clipboard);
 155:   }
 156: 
 157:   /**
 158:    * Gets an array of mime-type strings from the gtk+ clipboard and
 159:    * transforms them into an array of DataFlavors.
 160:    */
 161:   public DataFlavor[] getTransferDataFlavors()
 162:   {
 163:     DataFlavor[] result;
 164:     synchronized (requestLock)
 165:       {
 166:         // Did we request already and cache the result?
 167:         if (mimeTypesDelivered)
 168:           result = (DataFlavor[]) dataFlavors.clone();
 169:         else
 170:           {
 171:             // Wait till there are no pending requests.
 172:             while (requestInProgress)
 173:               {
 174:                 try
 175:                   {
 176:                     requestLock.wait();
 177:                   }
 178:                 catch (InterruptedException ie)
 179:                   {
 180:                     // ignored
 181:                   }
 182:               }
 183: 
 184:             // If nobody else beat us and cached the result we try
 185:             // ourselves to get it.
 186:             if (! mimeTypesDelivered)
 187:               {
 188:                 requestInProgress = true;
 189:                 requestMimeTypes(clipboard);
 190:                 while (! mimeTypesDelivered)
 191:                   {
 192:                     try
 193:                       {
 194:                         requestLock.wait();
 195:                       }
 196:                     catch (InterruptedException ie)
 197:                       {
 198:                         // ignored
 199:                       }
 200:                   }
 201:                 requestInProgress = false;
 202:               }
 203:             result = dataFlavors;
 204:             if (! GtkClipboard.canCache)
 205:               {
 206:                 dataFlavors = null;
 207:                 mimeTypesDelivered = false;
 208:               }
 209:             requestLock.notifyAll();
 210:           }
 211:       }
 212:     return result;
 213:   }
 214: 
 215:   /**
 216:    * Callback that sets the available DataFlavors[]. Note that this
 217:    * should not call any code that could need the main gdk lock.
 218:    */
 219:   private void mimeTypesAvailable(String[] mimeTypes)
 220:   {
 221:     synchronized (requestLock)
 222:       {
 223:         if (mimeTypes == null)
 224:           dataFlavors = new DataFlavor[0];
 225:         else
 226:           {
 227:             // Most likely the mimeTypes include text in which case we add an
 228:             // extra element.
 229:             ArrayList<DataFlavor> flavorsList =
 230:               new ArrayList<DataFlavor>(mimeTypes.length + 1);
 231: 
 232:             for (int i = 0; i < mimeTypes.length; i++)
 233:               {
 234:                 try
 235:                   {
 236:                     if (mimeTypes[i] == GtkClipboard.stringMimeType)
 237:                       {
 238:                         // XXX - Fix DataFlavor.getTextPlainUnicodeFlavor()
 239:                         // and also add it to the list.
 240:                         flavorsList.add(DataFlavor.stringFlavor);
 241:                         flavorsList.add(DataFlavor.plainTextFlavor);
 242:                       }
 243:                     else if (mimeTypes[i] == GtkClipboard.imageMimeType)
 244:                       flavorsList.add(DataFlavor.imageFlavor);
 245:                     else if (mimeTypes[i] == GtkClipboard.filesMimeType)
 246:                       flavorsList.add(DataFlavor.javaFileListFlavor);
 247:                     else
 248:                       {
 249:                         // We check the target to prevent duplicates
 250:                         // of the "magic" targets above.
 251:                         DataFlavor target = new DataFlavor(mimeTypes[i]);
 252:                         if (! flavorsList.contains(target))
 253:                           flavorsList.add(target);
 254:                       }
 255:                   }
 256:                 catch (ClassNotFoundException cnfe)
 257:                   {
 258:                     cnfe.printStackTrace();
 259:                   }
 260:                 catch (NullPointerException npe)
 261:                   {
 262:                     npe.printStackTrace();
 263:                   }
 264:               }
 265: 
 266:             dataFlavors = new DataFlavor[flavorsList.size()];
 267:             flavorsList.toArray(dataFlavors);
 268:           }
 269: 
 270:         mimeTypesDelivered = true;
 271:         requestLock.notifyAll();
 272:       }
 273:   }
 274: 
 275:   /**
 276:    * Gets the available data flavors for this selection and checks
 277:    * that at least one of them is equal to the given DataFlavor.
 278:    */
 279:   public boolean isDataFlavorSupported(DataFlavor flavor)
 280:   {
 281:     DataFlavor[] dfs = getTransferDataFlavors();
 282:     for (int i = 0; i < dfs.length; i++)
 283:       if (flavor.equals(dfs[i]))
 284:         return true;
 285: 
 286:     return false;
 287:   }
 288: 
 289:   /**
 290:    * Helper method that tests whether we already have the text for the
 291:    * current gtk+ selection on the clipboard and if not requests it
 292:    * and waits till it is available.
 293:    */
 294:   private String getText()
 295:   {
 296:     String result;
 297:     synchronized (requestLock)
 298:       {
 299:         // Did we request already and cache the result?
 300:         if (textDelivered)
 301:           result = text;
 302:         else
 303:           {
 304:             // Wait till there are no pending requests.
 305:             while (requestInProgress)
 306:               {
 307:                 try
 308:                   {
 309:                     requestLock.wait();
 310:                   }
 311:                 catch (InterruptedException ie)
 312:                   {
 313:                     // ignored
 314:                   }
 315:               }
 316: 
 317:             // If nobody else beat us we try ourselves to get and
 318:             // caching the result.
 319:             if (! textDelivered)
 320:               {
 321:                 requestInProgress = true;
 322:                 requestText(clipboard);
 323:                 while (! textDelivered)
 324:                   {
 325:                     try
 326:                       {
 327:                         requestLock.wait();
 328:                       }
 329:                     catch (InterruptedException ie)
 330:                       {
 331:                         // ignored
 332:                       }
 333:                   }
 334:                 requestInProgress = false;
 335:               }
 336:             result = text;
 337:             if (! GtkClipboard.canCache)
 338:               {
 339:                 text = null;
 340:                 textDelivered = false;
 341:               }
 342:             requestLock.notifyAll();
 343:           }
 344:       }
 345:     return result;
 346:   }
 347: 
 348:   /**
 349:    * Callback that sets the available text on the clipboard. Note that
 350:    * this should not call any code that could need the main gdk lock.
 351:    */
 352:   private void textAvailable(String text)
 353:   {
 354:     synchronized (requestLock)
 355:       {
 356:         this.text = text;
 357:         textDelivered = true;
 358:         requestLock.notifyAll();
 359:       }
 360:   }
 361: 
 362:   /**
 363:    * Helper method that tests whether we already have an image for the
 364:    * current gtk+ selection on the clipboard and if not requests it
 365:    * and waits till it is available.
 366:    */
 367:   private Image getImage()
 368:   {
 369:     Image result;
 370:     synchronized (requestLock)
 371:       {
 372:         // Did we request already and cache the result?
 373:         if (imageDelivered)
 374:           result = image;
 375:         else
 376:           {
 377:             // Wait till there are no pending requests.
 378:             while (requestInProgress)
 379:               {
 380:                 try
 381:                   {
 382:                     requestLock.wait();
 383:                   }
 384:                 catch (InterruptedException ie)
 385:                   {
 386:                     // ignored
 387:                   }
 388:               }
 389: 
 390:             // If nobody else beat us we try ourselves to get and
 391:             // caching the result.
 392:             if (! imageDelivered)
 393:               {
 394:                 requestInProgress = true;
 395:                 requestImage(clipboard);
 396:                 while (! imageDelivered)
 397:                   {
 398:                     try
 399:                       {
 400:                         requestLock.wait();
 401:                       }
 402:                     catch (InterruptedException ie)
 403:                       {
 404:                         // ignored
 405:                       }
 406:                   }
 407:                 requestInProgress = false;
 408:               }
 409: 
 410:             if (imagePointer != null)
 411:               image = new GtkImage(imagePointer);
 412: 
 413:             imagePointer = null;
 414:             result = image;
 415:             if (! GtkClipboard.canCache)
 416:               {
 417:                 image = null;
 418:                 imageDelivered = false;
 419:               }
 420:             requestLock.notifyAll();
 421:           }
 422:       }
 423:     return result;
 424:   }
 425: 
 426:   /**
 427:    * Callback that sets the available image on the clipboard. Note
 428:    * that this should not call any code that could need the main gdk
 429:    * lock. Note that we get a Pointer to a GdkPixbuf which we cannot
 430:    * turn into a real GtkImage at this point. That will be done on the
 431:    * "user thread" in getImage().
 432:    */
 433:   private void imageAvailable(Pointer pointer)
 434:   {
 435:     synchronized (requestLock)
 436:       {
 437:         this.imagePointer = pointer;
 438:         imageDelivered = true;
 439:         requestLock.notifyAll();
 440:       }
 441:   }
 442: 
 443:   /**
 444:    * Helper method that test whether we already have a list of
 445:    * URIs/Files and if not requests them and waits till they are
 446:    * available.
 447:    */
 448:   private List<File> getURIs()
 449:   {
 450:     List<File> result;
 451:     synchronized (requestLock)
 452:       {
 453:         // Did we request already and cache the result?
 454:         if (urisDelivered)
 455:           result = uris;
 456:         else
 457:           {
 458:             // Wait till there are no pending requests.
 459:             while (requestInProgress)
 460:               {
 461:                 try
 462:                   {
 463:                     requestLock.wait();
 464:                   }
 465:                 catch (InterruptedException ie)
 466:                   {
 467:                     // ignored
 468:                   }
 469:               }
 470: 
 471:             // If nobody else beat us we try ourselves to get and
 472:             // caching the result.
 473:             if (! urisDelivered)
 474:               {
 475:                 requestInProgress = true;
 476:                 requestURIs(clipboard);
 477:                 while (! urisDelivered)
 478:                   {
 479:                     try
 480:                       {
 481:                         requestLock.wait();
 482:                       }
 483:                     catch (InterruptedException ie)
 484:                       {
 485:                         // ignored
 486:                       }
 487:                   }
 488:                 requestInProgress = false;
 489:               }
 490:             result = uris;
 491:             if (! GtkClipboard.canCache)
 492:               {
 493:                 uris = null;
 494:                 urisDelivered = false;
 495:               }
 496:             requestLock.notifyAll();
 497:           }
 498:       }
 499:     return result;
 500:   }
 501: 
 502:   /**
 503:    * Callback that sets the available File list. Note that this should
 504:    * not call any code that could need the main gdk lock.
 505:    */
 506:   private void urisAvailable(String[] uris)
 507:   {
 508:     synchronized (requestLock)
 509:       {
 510:         if (uris != null && uris.length != 0)
 511:           {
 512:             ArrayList<File> list = new ArrayList<File>(uris.length);
 513:             for (int i = 0; i < uris.length; i++)
 514:               {
 515:                 try
 516:                   {
 517:                     URI uri = new URI(uris[i]);
 518:                     if (uri.getScheme().equals("file"))
 519:                       list.add(new File(uri));
 520:                   }
 521:                 catch (URISyntaxException use)
 522:                   {
 523:                   }
 524:               }
 525:             this.uris = list;
 526:           }
 527: 
 528:         urisDelivered = true;
 529:         requestLock.notifyAll();
 530:       }
 531:   }
 532: 
 533:   /**
 534:    * Helper method that requests a byte[] for the given target
 535:    * mime-type flavor and waits till it is available. Note that unlike
 536:    * the other get methods this one doesn't cache the result since
 537:    * there are possibly many targets.
 538:    */
 539:   private byte[] getBytes(String target)
 540:   {
 541:     byte[] result;
 542:     synchronized (requestLock)
 543:       {
 544:         // Wait till there are no pending requests.
 545:         while (requestInProgress)
 546:           {
 547:             try
 548:               {
 549:                 requestLock.wait();
 550:               }
 551:             catch (InterruptedException ie)
 552:               {
 553:                 // ignored
 554:               }
 555:           }
 556: 
 557:         // Request bytes and wait till they are available.
 558:         requestInProgress = true;
 559:         requestBytes(clipboard, target);
 560:         while (! bytesDelivered)
 561:           {
 562:             try
 563:               {
 564:                 requestLock.wait();
 565:               }
 566:             catch (InterruptedException ie)
 567:               {
 568:                 // ignored
 569:               }
 570:           }
 571:         result = bytes;
 572:         bytes = null;
 573:         bytesDelivered = false;
 574:         requestInProgress = false;
 575: 
 576:         requestLock.notifyAll();
 577:       }
 578:     return result;
 579:   }
 580: 
 581:   /**
 582:    * Callback that sets the available byte array on the
 583:    * clipboard. Note that this should not call any code that could
 584:    * need the main gdk lock.
 585:    */
 586:   private void bytesAvailable(byte[] bytes)
 587:   {
 588:     synchronized (requestLock)
 589:       {
 590:         this.bytes = bytes;
 591:         bytesDelivered = true;
 592:         requestLock.notifyAll();
 593:       }
 594:   }
 595: 
 596:   public Object getTransferData(DataFlavor flavor)
 597:     throws UnsupportedFlavorException
 598:   {
 599:     // Note the fall throughs for the "magic targets" if they fail we
 600:     // try one more time through getBytes().
 601:     if (flavor.equals(DataFlavor.stringFlavor))
 602:       {
 603:         String text = getText();
 604:         if (text != null)
 605:           return text;
 606:       }
 607: 
 608:     if (flavor.equals(DataFlavor.plainTextFlavor))
 609:       {
 610:         String text = getText();
 611:         if (text != null)
 612:           return new StringBufferInputStream(text);
 613:       }
 614: 
 615:     if (flavor.equals(DataFlavor.imageFlavor))
 616:       {
 617:         Image image = getImage();
 618:         if (image != null)
 619:           return image;
 620:       }
 621: 
 622:     if (flavor.equals(DataFlavor.javaFileListFlavor))
 623:       {
 624:         List<File> uris = getURIs();
 625:         if (uris != null)
 626:           return uris;
 627:       }
 628: 
 629:     byte[] bytes = getBytes(flavor.getMimeType());
 630:     if (bytes == null)
 631:       throw new UnsupportedFlavorException(flavor);
 632: 
 633:     if (flavor.isMimeTypeSerializedObject())
 634:       {
 635:         try
 636:           {
 637:             ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
 638:             ObjectInputStream ois = new ObjectInputStream(bais);
 639:             return ois.readObject();
 640:           }
 641:         catch (IOException ioe)
 642:           {
 643:             ioe.printStackTrace();
 644:           }
 645:         catch (ClassNotFoundException cnfe)
 646:           {
 647:             cnfe.printStackTrace();
 648:           }
 649:       }
 650: 
 651:     if (flavor.isRepresentationClassInputStream())
 652:       return new ByteArrayInputStream(bytes);
 653: 
 654:     // XXX, need some more conversions?
 655: 
 656:     throw new UnsupportedFlavorException(flavor);
 657:   }
 658: 
 659:   /*
 660:    * Requests text, Image or an byte[] for a particular target from the
 661:    * other application. These methods return immediately. When the
 662:    * content is available the contentLock will be notified through
 663:    * textAvailable, imageAvailable, urisAvailable or bytesAvailable and the
 664:    * appropriate field is set.
 665:    * The clipboard argument is true if we want the Clipboard, and false
 666:    * if we want the (primary) selection.
 667:    */
 668:   private native void requestText(boolean clipboard);
 669:   private native void requestImage(boolean clipboard);
 670:   private native void requestURIs(boolean clipboard);
 671:   private native void requestBytes(boolean clipboard, String target);
 672: 
 673:   /* Similar to the above but for requesting the supported targets. */
 674:   private native void requestMimeTypes(boolean clipboard);
 675: }