| /* GtkClipboard.java - Class representing gtk+ clipboard selection. |
| Copyright (C) 2005 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.java.awt.peer.gtk; |
| |
| import gnu.classpath.Pointer; |
| |
| import java.awt.datatransfer.*; |
| |
| import java.io.*; |
| import java.net.*; |
| import java.util.*; |
| |
| import java.awt.Image; |
| |
| /** |
| * Class representing the gtk+ clipboard selection. This is used when |
| * another program owns the clipboard. Whenever the system clipboard |
| * selection changes we create a new instance to notify the program |
| * that the available flavors might have changed. When requested it |
| * (lazily) caches the targets, and (text, image, or files/uris) |
| * clipboard contents. |
| */ |
| public class GtkSelection implements Transferable |
| { |
| /** |
| * Static lock used for requests of mimetypes and contents retrieval. |
| */ |
| static private Object requestLock = new Object(); |
| |
| /** |
| * Whether we belong to the Clipboard (true) or to the Primary selection. |
| */ |
| private final boolean clipboard; |
| |
| /** |
| * Whether a request for mimetypes, text, images, uris or byte[] is |
| * currently in progress. Should only be tested or set with |
| * requestLock held. When true no other requests should be made till |
| * it is false again. |
| */ |
| private boolean requestInProgress; |
| |
| /** |
| * Indicates a requestMimeTypes() call was made and the |
| * corresponding mimeTypesAvailable() callback was triggered. |
| */ |
| private boolean mimeTypesDelivered; |
| |
| /** |
| * Set and returned by getTransferDataFlavors. Only valid when |
| * mimeTypesDelivered is true. |
| */ |
| private DataFlavor[] dataFlavors; |
| |
| /** |
| * Indicates a requestText() call was made and the corresponding |
| * textAvailable() callback was triggered. |
| */ |
| private boolean textDelivered; |
| |
| /** |
| * Set as response to a requestText() call and possibly returned by |
| * getTransferData() for text targets. Only valid when textDelivered |
| * is true. |
| */ |
| private String text; |
| |
| /** |
| * Indicates a requestImage() call was made and the corresponding |
| * imageAvailable() callback was triggered. |
| */ |
| private boolean imageDelivered; |
| |
| /** |
| * Set as response to a requestImage() call and possibly returned by |
| * getTransferData() for image targets. Only valid when |
| * imageDelivered is true and image is null. |
| */ |
| private Pointer imagePointer; |
| |
| /** |
| * Cached image value. Only valid when imageDelivered is |
| * true. Created from imagePointer. |
| */ |
| private Image image; |
| |
| /** |
| * Indicates a requestUris() call was made and the corresponding |
| * urisAvailable() callback was triggered. |
| */ |
| private boolean urisDelivered; |
| |
| /** |
| * Set as response to a requestURIs() call. Only valid when |
| * urisDelivered is true |
| */ |
| private List uris; |
| |
| /** |
| * Indicates a requestBytes(String) call was made and the |
| * corresponding bytesAvailable() callback was triggered. |
| */ |
| private boolean bytesDelivered; |
| |
| /** |
| * Set as response to a requestBytes(String) call. Only valid when |
| * bytesDelivered is true. |
| */ |
| private byte[] bytes; |
| |
| /** |
| * Should only be created by the GtkClipboard class. The clipboard |
| * should be either GtkClipboard.clipboard or |
| * GtkClipboard.selection. |
| */ |
| GtkSelection(GtkClipboard clipboard) |
| { |
| this.clipboard = (clipboard == GtkClipboard.clipboard); |
| } |
| |
| /** |
| * Gets an array of mime-type strings from the gtk+ clipboard and |
| * transforms them into an array of DataFlavors. |
| */ |
| public DataFlavor[] getTransferDataFlavors() |
| { |
| DataFlavor[] result; |
| synchronized (requestLock) |
| { |
| // Did we request already and cache the result? |
| if (mimeTypesDelivered) |
| result = (DataFlavor[]) dataFlavors.clone(); |
| else |
| { |
| // Wait till there are no pending requests. |
| while (requestInProgress) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| |
| // If nobody else beat us and cached the result we try |
| // ourselves to get it. |
| if (! mimeTypesDelivered) |
| { |
| requestInProgress = true; |
| requestMimeTypes(clipboard); |
| while (! mimeTypesDelivered) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| requestInProgress = false; |
| } |
| result = dataFlavors; |
| if (! GtkClipboard.canCache) |
| { |
| dataFlavors = null; |
| mimeTypesDelivered = false; |
| } |
| requestLock.notifyAll(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Callback that sets the available DataFlavors[]. Note that this |
| * should not call any code that could need the main gdk lock. |
| */ |
| private void mimeTypesAvailable(String[] mimeTypes) |
| { |
| synchronized (requestLock) |
| { |
| if (mimeTypes == null) |
| dataFlavors = new DataFlavor[0]; |
| else |
| { |
| // Most likely the mimeTypes include text in which case we add an |
| // extra element. |
| ArrayList flavorsList = new ArrayList(mimeTypes.length + 1); |
| for (int i = 0; i < mimeTypes.length; i++) |
| { |
| try |
| { |
| if (mimeTypes[i] == GtkClipboard.stringMimeType) |
| { |
| // XXX - Fix DataFlavor.getTextPlainUnicodeFlavor() |
| // and also add it to the list. |
| flavorsList.add(DataFlavor.stringFlavor); |
| flavorsList.add(DataFlavor.plainTextFlavor); |
| } |
| else if (mimeTypes[i] == GtkClipboard.imageMimeType) |
| flavorsList.add(DataFlavor.imageFlavor); |
| else if (mimeTypes[i] == GtkClipboard.filesMimeType) |
| flavorsList.add(DataFlavor.javaFileListFlavor); |
| else |
| { |
| // We check the target to prevent duplicates |
| // of the "magic" targets above. |
| DataFlavor target = new DataFlavor(mimeTypes[i]); |
| if (! flavorsList.contains(target)) |
| flavorsList.add(target); |
| } |
| } |
| catch (ClassNotFoundException cnfe) |
| { |
| cnfe.printStackTrace(); |
| } |
| catch (NullPointerException npe) |
| { |
| npe.printStackTrace(); |
| } |
| } |
| |
| dataFlavors = new DataFlavor[flavorsList.size()]; |
| flavorsList.toArray(dataFlavors); |
| } |
| |
| mimeTypesDelivered = true; |
| requestLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * Gets the available data flavors for this selection and checks |
| * that at least one of them is equal to the given DataFlavor. |
| */ |
| public boolean isDataFlavorSupported(DataFlavor flavor) |
| { |
| DataFlavor[] dfs = getTransferDataFlavors(); |
| for (int i = 0; i < dfs.length; i++) |
| if (flavor.equals(dfs[i])) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * Helper method that tests whether we already have the text for the |
| * current gtk+ selection on the clipboard and if not requests it |
| * and waits till it is available. |
| */ |
| private String getText() |
| { |
| String result; |
| synchronized (requestLock) |
| { |
| // Did we request already and cache the result? |
| if (textDelivered) |
| result = text; |
| else |
| { |
| // Wait till there are no pending requests. |
| while (requestInProgress) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| |
| // If nobody else beat us we try ourselves to get and |
| // caching the result. |
| if (! textDelivered) |
| { |
| requestInProgress = true; |
| requestText(clipboard); |
| while (! textDelivered) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| requestInProgress = false; |
| } |
| result = text; |
| if (! GtkClipboard.canCache) |
| { |
| text = null; |
| textDelivered = false; |
| } |
| requestLock.notifyAll(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Callback that sets the available text on the clipboard. Note that |
| * this should not call any code that could need the main gdk lock. |
| */ |
| private void textAvailable(String text) |
| { |
| synchronized (requestLock) |
| { |
| this.text = text; |
| textDelivered = true; |
| requestLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * Helper method that tests whether we already have an image for the |
| * current gtk+ selection on the clipboard and if not requests it |
| * and waits till it is available. |
| */ |
| private Image getImage() |
| { |
| Image result; |
| synchronized (requestLock) |
| { |
| // Did we request already and cache the result? |
| if (imageDelivered) |
| result = image; |
| else |
| { |
| // Wait till there are no pending requests. |
| while (requestInProgress) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| |
| // If nobody else beat us we try ourselves to get and |
| // caching the result. |
| if (! imageDelivered) |
| { |
| requestInProgress = true; |
| requestImage(clipboard); |
| while (! imageDelivered) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| requestInProgress = false; |
| } |
| if (imagePointer != null) |
| image = new GtkImage(imagePointer); |
| imagePointer = null; |
| result = image; |
| if (! GtkClipboard.canCache) |
| { |
| image = null; |
| imageDelivered = false; |
| } |
| requestLock.notifyAll(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Callback that sets the available image on the clipboard. Note |
| * that this should not call any code that could need the main gdk |
| * lock. Note that we get a Pointer to a GdkPixbuf which we cannot |
| * turn into a real GtkImage at this point. That will be done on the |
| * "user thread" in getImage(). |
| */ |
| private void imageAvailable(Pointer pointer) |
| { |
| synchronized (requestLock) |
| { |
| this.imagePointer = pointer; |
| imageDelivered = true; |
| requestLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * Helper method that test whether we already have a list of |
| * URIs/Files and if not requests them and waits till they are |
| * available. |
| */ |
| private List getURIs() |
| { |
| List result; |
| synchronized (requestLock) |
| { |
| // Did we request already and cache the result? |
| if (urisDelivered) |
| result = uris; |
| else |
| { |
| // Wait till there are no pending requests. |
| while (requestInProgress) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| |
| // If nobody else beat us we try ourselves to get and |
| // caching the result. |
| if (! urisDelivered) |
| { |
| requestInProgress = true; |
| requestURIs(clipboard); |
| while (! urisDelivered) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| requestInProgress = false; |
| } |
| result = uris; |
| if (! GtkClipboard.canCache) |
| { |
| uris = null; |
| urisDelivered = false; |
| } |
| requestLock.notifyAll(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Callback that sets the available File list. Note that this should |
| * not call any code that could need the main gdk lock. |
| */ |
| private void urisAvailable(String[] uris) |
| { |
| synchronized (requestLock) |
| { |
| if (uris != null && uris.length != 0) |
| { |
| ArrayList list = new ArrayList(uris.length); |
| for (int i = 0; i < uris.length; i++) |
| { |
| try |
| { |
| URI uri = new URI(uris[i]); |
| if (uri.getScheme().equals("file")) |
| list.add(new File(uri)); |
| } |
| catch (URISyntaxException use) |
| { |
| } |
| } |
| this.uris = list; |
| } |
| |
| urisDelivered = true; |
| requestLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * Helper method that requests a byte[] for the given target |
| * mime-type flavor and waits till it is available. Note that unlike |
| * the other get methods this one doesn't cache the result since |
| * there are possibly many targets. |
| */ |
| private byte[] getBytes(String target) |
| { |
| byte[] result; |
| synchronized (requestLock) |
| { |
| // Wait till there are no pending requests. |
| while (requestInProgress) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| |
| // Request bytes and wait till they are available. |
| requestInProgress = true; |
| requestBytes(clipboard, target); |
| while (! bytesDelivered) |
| { |
| try |
| { |
| requestLock.wait(); |
| } |
| catch (InterruptedException ie) |
| { |
| // ignored |
| } |
| } |
| result = bytes; |
| bytes = null; |
| bytesDelivered = false; |
| requestInProgress = false; |
| |
| requestLock.notifyAll(); |
| } |
| return result; |
| } |
| |
| /** |
| * Callback that sets the available byte array on the |
| * clipboard. Note that this should not call any code that could |
| * need the main gdk lock. |
| */ |
| private void bytesAvailable(byte[] bytes) |
| { |
| synchronized (requestLock) |
| { |
| this.bytes = bytes; |
| bytesDelivered = true; |
| requestLock.notifyAll(); |
| } |
| } |
| |
| public Object getTransferData(DataFlavor flavor) |
| throws UnsupportedFlavorException |
| { |
| // Note the fall throughs for the "magic targets" if they fail we |
| // try one more time through getBytes(). |
| if (flavor.equals(DataFlavor.stringFlavor)) |
| { |
| String text = getText(); |
| if (text != null) |
| return text; |
| } |
| |
| if (flavor.equals(DataFlavor.plainTextFlavor)) |
| { |
| String text = getText(); |
| if (text != null) |
| return new StringBufferInputStream(text); |
| } |
| |
| if (flavor.equals(DataFlavor.imageFlavor)) |
| { |
| Image image = getImage(); |
| if (image != null) |
| return image; |
| } |
| |
| if (flavor.equals(DataFlavor.javaFileListFlavor)) |
| { |
| List uris = getURIs(); |
| if (uris != null) |
| return uris; |
| } |
| |
| byte[] bytes = getBytes(flavor.getMimeType()); |
| if (bytes == null) |
| throw new UnsupportedFlavorException(flavor); |
| |
| if (flavor.isMimeTypeSerializedObject()) |
| { |
| try |
| { |
| ByteArrayInputStream bais = new ByteArrayInputStream(bytes); |
| ObjectInputStream ois = new ObjectInputStream(bais); |
| return ois.readObject(); |
| } |
| catch (IOException ioe) |
| { |
| ioe.printStackTrace(); |
| } |
| catch (ClassNotFoundException cnfe) |
| { |
| cnfe.printStackTrace(); |
| } |
| } |
| |
| if (flavor.isRepresentationClassInputStream()) |
| return new ByteArrayInputStream(bytes); |
| |
| // XXX, need some more conversions? |
| |
| throw new UnsupportedFlavorException(flavor); |
| } |
| |
| /* |
| * Requests text, Image or an byte[] for a particular target from the |
| * other application. These methods return immediately. When the |
| * content is available the contentLock will be notified through |
| * textAvailable, imageAvailable, urisAvailable or bytesAvailable and the |
| * appropriate field is set. |
| * The clipboard argument is true if we want the Clipboard, and false |
| * if we want the (primary) selection. |
| */ |
| private native void requestText(boolean clipboard); |
| private native void requestImage(boolean clipboard); |
| private native void requestURIs(boolean clipboard); |
| private native void requestBytes(boolean clipboard, String target); |
| |
| /* Similar to the above but for requesting the supported targets. */ |
| private native void requestMimeTypes(boolean clipboard); |
| } |