--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/awt/datatransfer/SunClipboard.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,472 @@
+/*
+ * Copyright 1999-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.awt.datatransfer;
+
+import java.awt.EventQueue;
+
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.FlavorTable;
+import java.awt.datatransfer.SystemFlavorMap;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.ClipboardOwner;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.FlavorListener;
+import java.awt.datatransfer.FlavorEvent;
+import java.awt.datatransfer.UnsupportedFlavorException;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.HashSet;
+
+import java.io.IOException;
+
+import sun.awt.AppContext;
+import sun.awt.PeerEvent;
+import sun.awt.SunToolkit;
+import sun.awt.EventListenerAggregate;
+
+
+/**
+ * Serves as a common, helper superclass for the Win32 and X11 system
+ * Clipboards.
+ *
+ * @author Danila Sinopalnikov
+ * @author Alexander Gerasimov
+ *
+ * @since 1.3
+ */
+public abstract class SunClipboard extends Clipboard
+ implements PropertyChangeListener {
+
+ public static final FlavorTable flavorMap =
+ (FlavorTable)SystemFlavorMap.getDefaultFlavorMap();
+
+ private AppContext contentsContext = null;
+
+ private final Object CLIPBOARD_FLAVOR_LISTENER_KEY;
+
+ /**
+ * A number of <code>FlavorListener</code>s currently registered
+ * on this clipboard across all <code>AppContext</code>s.
+ */
+ private volatile int numberOfFlavorListeners = 0;
+
+ /**
+ * A set of <code>DataFlavor</code>s that is available on
+ * this clipboard. It is used for tracking changes
+ * of <code>DataFlavor</code>s available on this clipboard.
+ */
+ private volatile Set currentDataFlavors;
+
+
+ public SunClipboard(String name) {
+ super(name);
+ CLIPBOARD_FLAVOR_LISTENER_KEY = new StringBuffer(name + "_CLIPBOARD_FLAVOR_LISTENER_KEY");
+ }
+
+ public synchronized void setContents(Transferable contents,
+ ClipboardOwner owner) {
+ // 4378007 : Toolkit.getSystemClipboard().setContents(null, null)
+ // should throw NPE
+ if (contents == null) {
+ throw new NullPointerException("contents");
+ }
+
+ initContext();
+
+ final ClipboardOwner oldOwner = this.owner;
+ final Transferable oldContents = this.contents;
+
+ try {
+ this.owner = owner;
+ this.contents = new TransferableProxy(contents, true);
+
+ setContentsNative(contents);
+ } finally {
+ if (oldOwner != null && oldOwner != owner) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ oldOwner.lostOwnership(SunClipboard.this, oldContents);
+ }
+ });
+ }
+ }
+ }
+
+ private synchronized void initContext() {
+ final AppContext context = AppContext.getAppContext();
+
+ if (contentsContext != context) {
+ // Need to synchronize on the AppContext to guarantee that it cannot
+ // be disposed after the check, but before the listener is added.
+ synchronized (context) {
+ if (context.isDisposed()) {
+ throw new IllegalStateException("Can't set contents from disposed AppContext");
+ }
+ context.addPropertyChangeListener
+ (AppContext.DISPOSED_PROPERTY_NAME, this);
+ }
+ if (contentsContext != null) {
+ contentsContext.removePropertyChangeListener
+ (AppContext.DISPOSED_PROPERTY_NAME, this);
+ }
+ contentsContext = context;
+ }
+ }
+
+ public synchronized Transferable getContents(Object requestor) {
+ if (contents != null) {
+ return contents;
+ }
+ return new ClipboardTransferable(this);
+ }
+
+
+ /**
+ * @return the contents of this clipboard if it has been set from the same
+ * AppContext as it is currently retrieved or null otherwise
+ * @since 1.5
+ */
+ private synchronized Transferable getContextContents() {
+ AppContext context = AppContext.getAppContext();
+ return (context == contentsContext) ? contents : null;
+ }
+
+
+ /**
+ * @see java.awt.Clipboard#getAvailableDataFlavors
+ * @since 1.5
+ */
+ public DataFlavor[] getAvailableDataFlavors() {
+ Transferable cntnts = getContextContents();
+ if (cntnts != null) {
+ return cntnts.getTransferDataFlavors();
+ }
+
+ long[] formats = getClipboardFormatsOpenClose();
+
+ return DataTransferer.getInstance().
+ getFlavorsForFormatsAsArray(formats, flavorMap);
+ }
+
+ /**
+ * @see java.awt.Clipboard#isDataFlavorAvailable
+ * @since 1.5
+ */
+ public boolean isDataFlavorAvailable(DataFlavor flavor) {
+ if (flavor == null) {
+ throw new NullPointerException("flavor");
+ }
+
+ Transferable cntnts = getContextContents();
+ if (cntnts != null) {
+ return cntnts.isDataFlavorSupported(flavor);
+ }
+
+ long[] formats = getClipboardFormatsOpenClose();
+
+ return formatArrayAsDataFlavorSet(formats).contains(flavor);
+ }
+
+ /**
+ * @see java.awt.Clipboard#getData
+ * @since 1.5
+ */
+ public Object getData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ if (flavor == null) {
+ throw new NullPointerException("flavor");
+ }
+
+ Transferable cntnts = getContextContents();
+ if (cntnts != null) {
+ return cntnts.getTransferData(flavor);
+ }
+
+ long format = 0;
+ byte[] data = null;
+ Transferable localeTransferable = null;
+
+ try {
+ openClipboard(null);
+
+ long[] formats = getClipboardFormats();
+ Long lFormat = (Long)DataTransferer.getInstance().
+ getFlavorsForFormats(formats, flavorMap).get(flavor);
+
+ if (lFormat == null) {
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ format = lFormat.longValue();
+ data = getClipboardData(format);
+
+ if (DataTransferer.getInstance().isLocaleDependentTextFormat(format)) {
+ localeTransferable = createLocaleTransferable(formats);
+ }
+
+ } finally {
+ closeClipboard();
+ }
+
+ return DataTransferer.getInstance().
+ translateBytes(data, flavor, format, localeTransferable);
+ }
+
+ /**
+ * The clipboard must be opened.
+ *
+ * @since 1.5
+ */
+ protected Transferable createLocaleTransferable(long[] formats) throws IOException {
+ return null;
+ }
+
+ /**
+ * @throws IllegalStateException if the clipboard has not been opened
+ */
+ public void openClipboard(SunClipboard newOwner) {}
+ public void closeClipboard() {}
+
+ public abstract long getID();
+
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AppContext.DISPOSED_PROPERTY_NAME.equals(evt.getPropertyName()) &&
+ Boolean.TRUE.equals(evt.getNewValue())) {
+ final AppContext disposedContext = (AppContext)evt.getSource();
+ lostOwnershipLater(disposedContext);
+ }
+ }
+
+ protected void lostOwnershipImpl() {
+ lostOwnershipLater(null);
+ }
+
+ /**
+ * Clears the clipboard state (contents, owner and contents context) and
+ * notifies the current owner that ownership is lost. Does nothing if the
+ * argument is not <code>null</code> and is not equal to the current
+ * contents context.
+ *
+ * @param disposedContext the AppContext that is disposed or
+ * <code>null</code> if the ownership is lost because another
+ * application acquired ownership.
+ */
+ protected void lostOwnershipLater(final AppContext disposedContext) {
+ final AppContext context = this.contentsContext;
+ if (context == null) {
+ return;
+ }
+
+ final Runnable runnable = new Runnable() {
+ public void run() {
+ final SunClipboard sunClipboard = SunClipboard.this;
+ ClipboardOwner owner = null;
+ Transferable contents = null;
+
+ synchronized (sunClipboard) {
+ final AppContext context = sunClipboard.contentsContext;
+
+ if (context == null) {
+ return;
+ }
+
+ if (disposedContext == null || context == disposedContext) {
+ owner = sunClipboard.owner;
+ contents = sunClipboard.contents;
+ sunClipboard.contentsContext = null;
+ sunClipboard.owner = null;
+ sunClipboard.contents = null;
+ sunClipboard.clearNativeContext();
+ context.removePropertyChangeListener
+ (AppContext.DISPOSED_PROPERTY_NAME, sunClipboard);
+ } else {
+ return;
+ }
+ }
+ if (owner != null) {
+ owner.lostOwnership(sunClipboard, contents);
+ }
+ }
+ };
+
+ SunToolkit.postEvent(context, new PeerEvent(this, runnable,
+ PeerEvent.PRIORITY_EVENT));
+ }
+
+ protected abstract void clearNativeContext();
+
+ protected abstract void setContentsNative(Transferable contents);
+
+ /**
+ * @since 1.5
+ */
+ protected long[] getClipboardFormatsOpenClose() {
+ try {
+ openClipboard(null);
+ return getClipboardFormats();
+ } finally {
+ closeClipboard();
+ }
+ }
+
+ /**
+ * Returns zero-length array (not null) if the number of available formats is zero.
+ *
+ * @throws IllegalStateException if formats could not be retrieved
+ */
+ protected abstract long[] getClipboardFormats();
+
+ protected abstract byte[] getClipboardData(long format) throws IOException;
+
+
+ private static Set formatArrayAsDataFlavorSet(long[] formats) {
+ return (formats == null) ? null :
+ DataTransferer.getInstance().
+ getFlavorsForFormatsAsSet(formats, flavorMap);
+ }
+
+
+ public synchronized void addFlavorListener(FlavorListener listener) {
+ if (listener == null) {
+ return;
+ }
+ AppContext appContext = AppContext.getAppContext();
+ EventListenerAggregate contextFlavorListeners = (EventListenerAggregate)
+ appContext.get(CLIPBOARD_FLAVOR_LISTENER_KEY);
+ if (contextFlavorListeners == null) {
+ contextFlavorListeners = new EventListenerAggregate(FlavorListener.class);
+ appContext.put(CLIPBOARD_FLAVOR_LISTENER_KEY, contextFlavorListeners);
+ }
+ contextFlavorListeners.add(listener);
+
+ if (numberOfFlavorListeners++ == 0) {
+ long[] currentFormats = null;
+ try {
+ openClipboard(null);
+ currentFormats = getClipboardFormats();
+ } catch (IllegalStateException exc) {
+ } finally {
+ closeClipboard();
+ }
+ currentDataFlavors = formatArrayAsDataFlavorSet(currentFormats);
+
+ registerClipboardViewerChecked();
+ }
+ }
+
+ public synchronized void removeFlavorListener(FlavorListener listener) {
+ if (listener == null) {
+ return;
+ }
+ AppContext appContext = AppContext.getAppContext();
+ EventListenerAggregate contextFlavorListeners = (EventListenerAggregate)
+ appContext.get(CLIPBOARD_FLAVOR_LISTENER_KEY);
+ if (contextFlavorListeners == null){
+ //else we throw NullPointerException, but it is forbidden
+ return;
+ }
+ if (contextFlavorListeners.remove(listener) &&
+ --numberOfFlavorListeners == 0) {
+ unregisterClipboardViewerChecked();
+ currentDataFlavors = null;
+ }
+ }
+
+ public synchronized FlavorListener[] getFlavorListeners() {
+ EventListenerAggregate contextFlavorListeners = (EventListenerAggregate)
+ AppContext.getAppContext().get(CLIPBOARD_FLAVOR_LISTENER_KEY);
+ return contextFlavorListeners == null ? new FlavorListener[0] :
+ (FlavorListener[])contextFlavorListeners.getListenersCopy();
+ }
+
+ public boolean areFlavorListenersRegistered() {
+ return (numberOfFlavorListeners > 0);
+ }
+
+ protected abstract void registerClipboardViewerChecked();
+
+ protected abstract void unregisterClipboardViewerChecked();
+
+ /**
+ * Checks change of the <code>DataFlavor</code>s and, if necessary,
+ * posts notifications on <code>FlavorEvent</code>s to the
+ * AppContexts' EDTs.
+ * The parameter <code>formats</code> is null iff we have just
+ * failed to get formats available on the clipboard.
+ *
+ * @param formats data formats that have just been retrieved from
+ * this clipboard
+ */
+ public void checkChange(long[] formats) {
+ Set prevDataFlavors = currentDataFlavors;
+ currentDataFlavors = formatArrayAsDataFlavorSet(formats);
+
+ if ((prevDataFlavors != null) && (currentDataFlavors != null) &&
+ prevDataFlavors.equals(currentDataFlavors)) {
+ // we've been able to successfully get available on the clipboard
+ // DataFlavors this and previous time and they are coincident;
+ // don't notify
+ return;
+ }
+
+ class SunFlavorChangeNotifier implements Runnable {
+ private final FlavorListener flavorListener;
+
+ SunFlavorChangeNotifier(FlavorListener flavorListener) {
+ this.flavorListener = flavorListener;
+ }
+
+ public void run() {
+ if (flavorListener != null) {
+ flavorListener.flavorsChanged(new FlavorEvent(SunClipboard.this));
+ }
+ }
+ };
+
+ for (Iterator it = AppContext.getAppContexts().iterator(); it.hasNext();) {
+ AppContext appContext = (AppContext)it.next();
+ if (appContext == null || appContext.isDisposed()) {
+ continue;
+ }
+ EventListenerAggregate flavorListeners = (EventListenerAggregate)
+ appContext.get(CLIPBOARD_FLAVOR_LISTENER_KEY);
+ if (flavorListeners != null) {
+ FlavorListener[] flavorListenerArray =
+ (FlavorListener[])flavorListeners.getListenersInternal();
+ for (int i = 0; i < flavorListenerArray.length; i++) {
+ SunToolkit.postEvent(appContext, new PeerEvent(this,
+ new SunFlavorChangeNotifier(flavorListenerArray[i]),
+ PeerEvent.PRIORITY_EVENT));
+ }
+ }
+ }
+ }
+
+}