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: * 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: }