Frames | No Frames |
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: }