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 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 flavorsList = new ArrayList(mimeTypes.length + 1);
 230:         for (int i = 0; i < mimeTypes.length; i++)
 231:           {
 232:         try
 233:           {
 234:             if (mimeTypes[i] == GtkClipboard.stringMimeType)
 235:               {
 236:             // XXX - Fix DataFlavor.getTextPlainUnicodeFlavor()
 237:             // and also add it to the list.
 238:             flavorsList.add(DataFlavor.stringFlavor);
 239:             flavorsList.add(DataFlavor.plainTextFlavor);
 240:               }
 241:             else if (mimeTypes[i] == GtkClipboard.imageMimeType)
 242:               flavorsList.add(DataFlavor.imageFlavor);
 243:             else if (mimeTypes[i] == GtkClipboard.filesMimeType)
 244:               flavorsList.add(DataFlavor.javaFileListFlavor);
 245:             else
 246:               {
 247:             // We check the target to prevent duplicates
 248:             // of the "magic" targets above.
 249:             DataFlavor target = new DataFlavor(mimeTypes[i]);
 250:             if (! flavorsList.contains(target))
 251:               flavorsList.add(target);
 252:               }
 253:           }
 254:         catch (ClassNotFoundException cnfe)
 255:           {
 256:             cnfe.printStackTrace();
 257:           }
 258:         catch (NullPointerException npe)
 259:           {
 260:             npe.printStackTrace();
 261:           }
 262:           }
 263:         
 264:         dataFlavors = new DataFlavor[flavorsList.size()];
 265:         flavorsList.toArray(dataFlavors);
 266:       }
 267: 
 268:     mimeTypesDelivered = true;
 269:     requestLock.notifyAll();
 270:       }
 271:   }
 272: 
 273:   /**
 274:    * Gets the available data flavors for this selection and checks
 275:    * that at least one of them is equal to the given DataFlavor.
 276:    */
 277:   public boolean isDataFlavorSupported(DataFlavor flavor)
 278:   {
 279:     DataFlavor[] dfs = getTransferDataFlavors();
 280:     for (int i = 0; i < dfs.length; i++)
 281:       if (flavor.equals(dfs[i]))
 282:     return true;
 283: 
 284:     return false;
 285:   }
 286: 
 287:   /**
 288:    * Helper method that tests whether we already have the text for the
 289:    * current gtk+ selection on the clipboard and if not requests it
 290:    * and waits till it is available.
 291:    */
 292:   private String getText()
 293:   {
 294:     String result;
 295:     synchronized (requestLock)
 296:       {
 297:     // Did we request already and cache the result?
 298:     if (textDelivered)
 299:       result = text;
 300:     else
 301:       {
 302:         // Wait till there are no pending requests.
 303:         while (requestInProgress)
 304:           {
 305:         try
 306:           {
 307:             requestLock.wait();
 308:           }
 309:         catch (InterruptedException ie)
 310:           {
 311:             // ignored
 312:           }
 313:           }
 314: 
 315:         // If nobody else beat us we try ourselves to get and
 316:         // caching the result.
 317:         if (! textDelivered)
 318:           {
 319:         requestInProgress = true;
 320:         requestText(clipboard);
 321:         while (! textDelivered)
 322:           {
 323:             try
 324:               {
 325:             requestLock.wait();
 326:               }
 327:             catch (InterruptedException ie)
 328:               {
 329:             // ignored
 330:               }
 331:           }
 332:         requestInProgress = false;
 333:           }
 334:         result = text;
 335:         if (! GtkClipboard.canCache)
 336:           {
 337:         text = null;
 338:         textDelivered = false;
 339:           }
 340:         requestLock.notifyAll();
 341:       }
 342:       }
 343:     return result;
 344:   }
 345: 
 346:   /**
 347:    * Callback that sets the available text on the clipboard. Note that
 348:    * this should not call any code that could need the main gdk lock.
 349:    */
 350:   private void textAvailable(String text)
 351:   {
 352:     synchronized (requestLock)
 353:       {
 354:     this.text = text;
 355:     textDelivered = true;
 356:     requestLock.notifyAll();
 357:       }
 358:   }
 359: 
 360:   /**
 361:    * Helper method that tests whether we already have an image for the
 362:    * current gtk+ selection on the clipboard and if not requests it
 363:    * and waits till it is available.
 364:    */
 365:   private Image getImage()
 366:   {
 367:     Image result;
 368:     synchronized (requestLock)
 369:       {
 370:     // Did we request already and cache the result?
 371:     if (imageDelivered)
 372:       result = image;
 373:     else
 374:       {
 375:         // Wait till there are no pending requests.
 376:         while (requestInProgress)
 377:           {
 378:         try
 379:           {
 380:             requestLock.wait();
 381:           }
 382:         catch (InterruptedException ie)
 383:           {
 384:             // ignored
 385:           }
 386:           }
 387: 
 388:         // If nobody else beat us we try ourselves to get and
 389:         // caching the result.
 390:         if (! imageDelivered)
 391:           {
 392:         requestInProgress = true;
 393:         requestImage(clipboard);
 394:         while (! imageDelivered)
 395:           {
 396:             try
 397:               {
 398:             requestLock.wait();
 399:               }
 400:             catch (InterruptedException ie)
 401:               {
 402:             // ignored
 403:               }
 404:           }
 405:         requestInProgress = false;
 406:           }
 407:         if (imagePointer != null)
 408:           image = new GtkImage(imagePointer);
 409:         imagePointer = null;
 410:         result = image;
 411:         if (! GtkClipboard.canCache)
 412:           {
 413:         image = null;
 414:         imageDelivered = false;
 415:           }
 416:         requestLock.notifyAll();
 417:       }
 418:       }
 419:     return result;
 420:   }
 421: 
 422:   /**
 423:    * Callback that sets the available image on the clipboard. Note
 424:    * that this should not call any code that could need the main gdk
 425:    * lock. Note that we get a Pointer to a GdkPixbuf which we cannot
 426:    * turn into a real GtkImage at this point. That will be done on the
 427:    * "user thread" in getImage().
 428:    */
 429:   private void imageAvailable(Pointer pointer)
 430:   {
 431:     synchronized (requestLock)
 432:       {
 433:     this.imagePointer = pointer;
 434:     imageDelivered = true;
 435:     requestLock.notifyAll();
 436:       }
 437:   }
 438: 
 439:   /**
 440:    * Helper method that test whether we already have a list of
 441:    * URIs/Files and if not requests them and waits till they are
 442:    * available.
 443:    */
 444:   private List getURIs()
 445:   {
 446:     List result;
 447:     synchronized (requestLock)
 448:       {
 449:     // Did we request already and cache the result?
 450:     if (urisDelivered)
 451:       result = uris;
 452:     else
 453:       {
 454:         // Wait till there are no pending requests.
 455:         while (requestInProgress)
 456:           {
 457:         try
 458:           {
 459:             requestLock.wait();
 460:           }
 461:         catch (InterruptedException ie)
 462:           {
 463:             // ignored
 464:           }
 465:           }
 466: 
 467:         // If nobody else beat us we try ourselves to get and
 468:         // caching the result.
 469:         if (! urisDelivered)
 470:           {
 471:         requestInProgress = true;
 472:         requestURIs(clipboard);
 473:         while (! urisDelivered)
 474:           {
 475:             try
 476:               {
 477:             requestLock.wait();
 478:               }
 479:             catch (InterruptedException ie)
 480:               {
 481:             // ignored
 482:               }
 483:           }
 484:         requestInProgress = false;
 485:           }
 486:         result = uris;
 487:         if (! GtkClipboard.canCache)
 488:           {
 489:         uris = null;
 490:         urisDelivered = false;
 491:           }
 492:         requestLock.notifyAll();
 493:       }
 494:       }
 495:     return result;
 496:   }
 497: 
 498:   /**
 499:    * Callback that sets the available File list. Note that this should
 500:    * not call any code that could need the main gdk lock.
 501:    */
 502:   private void urisAvailable(String[] uris)
 503:   {
 504:     synchronized (requestLock)
 505:       {
 506:     if (uris != null && uris.length != 0)
 507:       {
 508:         ArrayList list = new ArrayList(uris.length);
 509:         for (int i = 0; i < uris.length; i++)
 510:           {
 511:         try
 512:           {
 513:             URI uri = new URI(uris[i]);
 514:             if (uri.getScheme().equals("file"))
 515:               list.add(new File(uri));
 516:           }
 517:         catch (URISyntaxException use)
 518:           {
 519:           }
 520:           }
 521:         this.uris = list;
 522:       }
 523: 
 524:     urisDelivered = true;
 525:     requestLock.notifyAll();
 526:       }
 527:   }
 528: 
 529:   /**
 530:    * Helper method that requests a byte[] for the given target
 531:    * mime-type flavor and waits till it is available. Note that unlike
 532:    * the other get methods this one doesn't cache the result since
 533:    * there are possibly many targets.
 534:    */
 535:   private byte[] getBytes(String target)
 536:   {
 537:     byte[] result;
 538:     synchronized (requestLock)
 539:       {
 540:     // Wait till there are no pending requests.
 541:     while (requestInProgress)
 542:       {
 543:         try
 544:           {
 545:         requestLock.wait();
 546:           }
 547:         catch (InterruptedException ie)
 548:           {
 549:         // ignored
 550:           }
 551:       }
 552: 
 553:     // Request bytes and wait till they are available.
 554:     requestInProgress = true;
 555:     requestBytes(clipboard, target);
 556:     while (! bytesDelivered)
 557:       {
 558:         try
 559:           {
 560:         requestLock.wait();
 561:           }
 562:         catch (InterruptedException ie)
 563:           {
 564:         // ignored
 565:           }
 566:       }
 567:     result = bytes;
 568:     bytes = null;
 569:     bytesDelivered = false;
 570:     requestInProgress = false;
 571:     
 572:     requestLock.notifyAll();
 573:       }
 574:     return result;
 575:   }
 576: 
 577:   /**
 578:    * Callback that sets the available byte array on the
 579:    * clipboard. Note that this should not call any code that could
 580:    * need the main gdk lock.
 581:    */
 582:   private void bytesAvailable(byte[] bytes)
 583:   {
 584:     synchronized (requestLock)
 585:       {
 586:     this.bytes = bytes;
 587:     bytesDelivered = true;
 588:     requestLock.notifyAll();
 589:       }
 590:   }
 591: 
 592:   public Object getTransferData(DataFlavor flavor)
 593:     throws UnsupportedFlavorException
 594:   {
 595:     // Note the fall throughs for the "magic targets" if they fail we
 596:     // try one more time through getBytes().
 597:     if (flavor.equals(DataFlavor.stringFlavor))
 598:       {
 599:     String text = getText();
 600:     if (text != null)
 601:       return text;
 602:       }
 603: 
 604:     if (flavor.equals(DataFlavor.plainTextFlavor))
 605:       {
 606:     String text = getText();
 607:     if (text != null)
 608:       return new StringBufferInputStream(text);
 609:       }
 610: 
 611:     if (flavor.equals(DataFlavor.imageFlavor))
 612:       {
 613:     Image image = getImage();
 614:     if (image != null)
 615:       return image;
 616:       }
 617: 
 618:     if (flavor.equals(DataFlavor.javaFileListFlavor))
 619:       {
 620:     List uris = getURIs();
 621:     if (uris != null)
 622:       return uris;
 623:       }
 624: 
 625:     byte[] bytes = getBytes(flavor.getMimeType());
 626:     if (bytes == null)
 627:       throw new UnsupportedFlavorException(flavor);
 628: 
 629:     if (flavor.isMimeTypeSerializedObject())
 630:       {
 631:     try
 632:       {
 633:         ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
 634:         ObjectInputStream ois = new ObjectInputStream(bais);
 635:         return ois.readObject();
 636:       }
 637:     catch (IOException ioe)
 638:       {
 639:         ioe.printStackTrace();
 640:       }
 641:     catch (ClassNotFoundException cnfe)
 642:       {
 643:         cnfe.printStackTrace();
 644:       }
 645:       }
 646: 
 647:     if (flavor.isRepresentationClassInputStream())
 648:       return new ByteArrayInputStream(bytes);
 649: 
 650:     // XXX, need some more conversions?
 651: 
 652:     throw new UnsupportedFlavorException(flavor);
 653:   }
 654: 
 655:   /*
 656:    * Requests text, Image or an byte[] for a particular target from the
 657:    * other application. These methods return immediately. When the
 658:    * content is available the contentLock will be notified through
 659:    * textAvailable, imageAvailable, urisAvailable or bytesAvailable and the
 660:    * appropriate field is set.
 661:    * The clipboard argument is true if we want the Clipboard, and false
 662:    * if we want the (primary) selection.
 663:    */
 664:   private native void requestText(boolean clipboard);
 665:   private native void requestImage(boolean clipboard);
 666:   private native void requestURIs(boolean clipboard);
 667:   private native void requestBytes(boolean clipboard, String target);
 668: 
 669:   /* Similar to the above but for requesting the supported targets. */
 670:   private native void requestMimeTypes(boolean clipboard);
 671: }