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