665 lines
16 KiB
Java
665 lines
16 KiB
Java
![]() |
/* 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.
|
||
|
*
|
||
|
* XXX - should only cache when
|
||
|
* gdk_display_supports_selection_notification is true.
|
||
|
*/
|
||
|
public class GtkSelection implements Transferable
|
||
|
{
|
||
|
/**
|
||
|
* Static lock used for requests of mimetypes and contents retrieval.
|
||
|
*/
|
||
|
static private Object requestLock = new Object();
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
GtkSelection()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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();
|
||
|
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();
|
||
|
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();
|
||
|
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();
|
||
|
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(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.
|
||
|
*/
|
||
|
private native void requestText();
|
||
|
private native void requestImage();
|
||
|
private native void requestURIs();
|
||
|
private native void requestBytes(String target);
|
||
|
|
||
|
/* Similar to the above but for requesting the supported targets. */
|
||
|
private native void requestMimeTypes();
|
||
|
}
|