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