Add Clipboard Support
Clipboard support (read and write) is only available in Chromium-based
browsers (Chrome, Opera, new Edge). It's only possible to transfer text
content. Current implementation requires SWT_COMPATIBILITY operational
mode as it's a synchronous operation.
Bug 360790: Add Clipboard Support to RAP
https://bugs.eclipse.org/bugs/show_bug.cgi?id=360790
Change-Id: I6b4e779c3da5b7086331e583c7c6c8afb070ef52
diff --git a/bundles/org.eclipse.rap.rwt/js/rwt/client/Clipboard.js b/bundles/org.eclipse.rap.rwt/js/rwt/client/Clipboard.js
new file mode 100644
index 0000000..1d4456e
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt/js/rwt/client/Clipboard.js
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2020 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+
+namespace( "rwt.client" );
+
+(function() {
+
+ rwt.client.Clipboard = {
+
+ writeText: function( properties ) {
+ var remoteObject = rap.getRemoteObject( this );
+ if( navigator.clipboard && navigator.clipboard.writeText ) {
+ navigator.clipboard.writeText( properties.text ).then( function() {
+ remoteObject.call( "operationSucceeded", {
+ "operation" : "writeText",
+ "result" : ""
+ } );
+ }, function( error ) {
+ remoteObject.call( "operationFailed", {
+ "operation" : "writeText",
+ "errorMessage" : error.message
+ } );
+ } );
+ } else {
+ remoteObject.call( "operationFailed", {
+ "operation" : "writeText",
+ "errorMessage" : "Clipboard operation writeText is not supported."
+ } );
+ }
+ },
+
+ readText: function() {
+ var remoteObject = rap.getRemoteObject( this );
+ if( navigator.clipboard && navigator.clipboard.readText ) {
+ navigator.clipboard.readText().then( function( text ) {
+ remoteObject.call( "operationSucceeded", {
+ "operation" : "readText",
+ "result" : text
+ } );
+ }, function( error ) {
+ remoteObject.call( "operationFailed", {
+ "operation" : "readText",
+ "errorMessage" : error.message
+ } );
+ } );
+ } else {
+ remoteObject.call( "operationFailed", {
+ "operation" : "readText",
+ "errorMessage" : "Clipboard operation readText is not supported."
+ } );
+ }
+ }
+
+ };
+
+})();
diff --git a/bundles/org.eclipse.rap.rwt/js/rwt/remote/handler/ClipboardHandler.js b/bundles/org.eclipse.rap.rwt/js/rwt/remote/handler/ClipboardHandler.js
new file mode 100644
index 0000000..ad26b82
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt/js/rwt/remote/handler/ClipboardHandler.js
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2020 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+
+rwt.remote.HandlerRegistry.add( "rwt.client.Clipboard", {
+
+ factory : function() {
+ return rwt.client.Clipboard;
+ },
+
+ methods : [ "writeText", "readText" ]
+
+} );
diff --git a/bundles/org.eclipse.rap.rwt/resources/client.files b/bundles/org.eclipse.rap.rwt/resources/client.files
index 8a67db4..2339c57 100644
--- a/bundles/org.eclipse.rap.rwt/resources/client.files
+++ b/bundles/org.eclipse.rap.rwt/resources/client.files
@@ -265,3 +265,5 @@
rwt/widgets/DropDown.js
rwt/widgets/util/DropDownSynchronizer.js
rwt/remote/handler/DropDownHandler.js
+rwt/client/Clipboard.js
+rwt/remote/handler/ClipboardHandler.js
diff --git a/bundles/org.eclipse.rap.rwt/src/org/eclipse/swt/dnd/Clipboard.java b/bundles/org.eclipse.rap.rwt/src/org/eclipse/swt/dnd/Clipboard.java
new file mode 100644
index 0000000..6de317a
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt/src/org/eclipse/swt/dnd/Clipboard.java
@@ -0,0 +1,450 @@
+/*******************************************************************************
+ * Copyright (c) 2020 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.swt.dnd;
+
+import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+
+import org.eclipse.rap.json.JsonObject;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.internal.lifecycle.ProcessActionRunner;
+import org.eclipse.rap.rwt.internal.lifecycle.SimpleLifeCycle;
+import org.eclipse.rap.rwt.remote.AbstractOperationHandler;
+import org.eclipse.rap.rwt.remote.RemoteObject;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * The <code>Clipboard</code> provides a mechanism for transferring data from one
+ * application to another or within an application.
+ *
+ * <p>IMPORTANT: This class is <em>not</em> intended to be subclassed.</p>
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @since 3.14
+ */
+public class Clipboard {
+
+ private static final String REMOTE_TYPE = "rwt.client.Clipboard";
+ private static final String PROP_TEXT = "text";
+ private static final String PROP_OPERATION = "operation";
+ private static final String PROP_RESULT = "result";
+ private static final String PROP_ERROR_MESSAGE = "errorMessage";
+ private static final String METHOD_OPERATION_SUCCEEDED = "operationSucceeded";
+ private static final String METHOD_OPERATION_FAILED = "operationFailed";
+ private static final String METHOD_READ_TEXT = "readText";
+ private static final String METHOD_WRITE_TEXT = "writeText";
+
+ enum ClipboardOperation {
+ WRITE_TEXT,
+ READ_TEXT
+ }
+
+ private Display display;
+ private final RemoteObject remoteObject;
+ private ClipboardListener listener;
+ private boolean operationPending;
+ private Object operationResult;
+
+ /**
+ * Constructs a new instance of this class.
+ *
+ * @param display the display on which to allocate the clipboard
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
+ * </ul>
+ *
+ * @see Clipboard#checkSubclass
+ */
+ public Clipboard( Display display ) {
+ checkSubclass();
+ this.display = display;
+ if( this.display == null ) {
+ this.display = Display.getCurrent();
+ if( this.display == null ) {
+ this.display = Display.getDefault();
+ }
+ }
+ if( this.display.getThread() != Thread.currentThread() ) {
+ DND.error( SWT.ERROR_THREAD_INVALID_ACCESS );
+ }
+ remoteObject = RWT.getUISession().getConnection().createRemoteObject( REMOTE_TYPE );
+ remoteObject.setHandler( new ClipboardOperationHandler() );
+ }
+
+ /**
+ * Sets the listener who will be notified when clipboard operation is performed, by sending
+ * it one of the messages defined in the <code>ClipboardListener</code> interface.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see ClipboardListener
+ */
+ void setClipboardListener( ClipboardListener listener ) {
+ checkWidget();
+ if( listener == null ) {
+ DND.error( SWT.ERROR_NULL_ARGUMENT );
+ }
+ this.listener = listener;
+ }
+
+ /**
+ * Place text data in the client clipboard.
+ *
+ * @param text the text to placed in the clipboard
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+ void writeText( String text ) {
+ checkWidget();
+ if( text == null ) {
+ DND.error( SWT.ERROR_NULL_ARGUMENT );
+ }
+ remoteObject.call( METHOD_WRITE_TEXT, new JsonObject().add( PROP_TEXT, text ) );
+ }
+
+ /**
+ * Ask the client to get the text data from the clipboard.
+ *
+ * To get the actual data you have to add ClipboardListener before calling this method.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see ClipboardListener
+ */
+ void readText() {
+ checkWidget();
+ remoteObject.call( METHOD_READ_TEXT, null );
+ }
+
+ /**
+ * Retrieve the data of the specified type currently available on the system
+ * clipboard. Refer to the specific subclass of <code>Transfer</code> to
+ * determine the type of object returned. Only <code>TextTransfer</code> is currently supported.
+ *
+ * <p>The following snippet shows text being retrieved from the
+ * clipboard:</p>
+ *
+ * <code><pre>
+ * Clipboard clipboard = new Clipboard(display);
+ * TextTransfer textTransfer = TextTransfer.getInstance();
+ * String textData = (String)clipboard.getContents(textTransfer);
+ * if (textData != null) System.out.println("Text is "+textData);
+ * </code></pre>
+ *
+ * @param transfer the transfer agent for the type of data being requested
+ * @return the data obtained from the clipboard or null if no data of this type is available
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if transfer is null</li>
+ * </ul>
+ *
+ * @exception UnsupportedOperationException when running the application in JEE_COMPATIBILITY mode
+ * @exception IllegalStateException when clipboard content is already requested.
+ *
+ * @see org.eclipse.rap.rwt.application.Application.OperationMode
+ * @see Transfer
+ */
+ public Object getContents( Transfer transfer ) {
+ checkOperationMode();
+ checkWidget();
+ if( transfer == null ) {
+ DND.error( SWT.ERROR_NULL_ARGUMENT );
+ }
+ if( !( transfer instanceof TextTransfer ) ) {
+ DND.error( SWT.ERROR_INVALID_ARGUMENT );
+ }
+ if( operationPending ) {
+ throw new IllegalStateException( "Another clipboard operation is already pending" );
+ }
+ operationPending = true;
+ operationResult = null;
+ readText();
+ while( operationResult == null ) {
+ if( !display.readAndDispatch() ) {
+ display.sleep();
+ }
+ }
+ operationPending = false;
+ return operationResult instanceof SWTException ? null : operationResult;
+ }
+
+ /**
+ * Place data of the specified type on the system clipboard. Only one type
+ * of data can be placed on the system clipboard at the same time. Setting the
+ * data clears any previous data from the system clipboard, regardless of type.
+ * Only <code>TextTransfer</code> is currently supported.
+ *
+ * <p>The following snippet shows text being set on the copy/paste clipboard:
+ * </p>
+ *
+ * <code><pre>
+ * Clipboard clipboard = new Clipboard(display);
+ * String textData = "Hello World";
+ * TextTransfer textTransfer = TextTransfer.getInstance();
+ * Transfer[] transfers = new Transfer[]{textTransfer};
+ * Object[] data = new Object[]{textData};
+ * clipboard.setContents(data, transfers);
+ * </code></pre>
+ *
+ * @param data the data to be set in the clipboard
+ * @param dataTypes the transfer agents that will convert the data to its
+ * platform specific format; each entry in the data array must have a
+ * corresponding dataType
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT - if data is null or datatypes is null
+ * or the length of data is not the same as the length of dataTypes</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception SWTError <ul>
+ * <li>ERROR_CANNOT_SET_CLIPBOARD - if the clipboard is locked or otherwise unavailable</li>
+ * </ul>
+ *
+ * @exception UnsupportedOperationException when running the application in JEE_COMPATIBILITY mode
+ * @exception IllegalStateException when clipboard content is already requested.
+ *
+ * @see org.eclipse.rap.rwt.application.Application.OperationMode
+ */
+ public void setContents( Object[] data, Transfer[] dataTypes ) {
+ checkOperationMode();
+ checkWidget();
+ if( data == null || dataTypes == null || data.length != dataTypes.length || data.length != 1 ) {
+ DND.error( SWT.ERROR_INVALID_ARGUMENT );
+ }
+ for( int i = 0; i < data.length; i++ ) {
+ if( data[ i ] == null
+ || dataTypes[ i ] == null
+ || !( dataTypes[ i ] instanceof TextTransfer )
+ || !dataTypes[ i ].validate( data[ i ] ) )
+ {
+ DND.error( SWT.ERROR_INVALID_ARGUMENT );
+ }
+ }
+ if( operationPending ) {
+ throw new IllegalStateException( "Another clipboard operation is already pending" );
+ }
+ operationPending = true;
+ operationResult = null;
+ writeText( ( String )data[ 0 ] );
+ while( operationResult == null ) {
+ if( !display.readAndDispatch() ) {
+ display.sleep();
+ }
+ }
+ operationPending = false;
+ if( operationResult instanceof SWTException ) {
+ DND.error( DND.ERROR_CANNOT_SET_CLIPBOARD );
+ }
+ }
+
+ /**
+ * Disposes of the operating system resources associated with the clipboard.
+ * The data will still be available on the system clipboard after the dispose
+ * method is called.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ */
+ public void dispose() {
+ if( isDisposed() ) {
+ return;
+ }
+ if( display.getThread() != Thread.currentThread() ) {
+ DND.error( SWT.ERROR_THREAD_INVALID_ACCESS );
+ }
+ display = null;
+ remoteObject.destroy();
+ }
+
+ /**
+ * Returns <code>true</code> if the clipboard has been disposed,
+ * and <code>false</code> otherwise.
+ * <p>
+ * This method gets the dispose state for the clipboard.
+ * When a clipboard has been disposed, it is an error to
+ * invoke any other method using the clipboard.
+ * </p>
+ *
+ * @return <code>true</code> when the widget is disposed and <code>false</code> otherwise
+ */
+ public boolean isDisposed() {
+ return display == null;
+ }
+
+ /**
+ * Throws an <code>SWTException</code> if the receiver can not
+ * be accessed by the caller. This may include both checks on
+ * the state of the receiver and more generally on the entire
+ * execution context. This method <em>should</em> be called by
+ * widget implementors to enforce the standard SWT invariants.
+ * <p>
+ * Currently, it is an error to invoke any method (other than
+ * <code>isDisposed()</code>) on a widget that has had its
+ * <code>dispose()</code> method called. It is also an error
+ * to call widget methods from any thread that is different
+ * from the thread that created the widget.
+ * </p><p>
+ * In future releases of SWT, there may be more or fewer error
+ * checks and exceptions may be thrown for different reasons.
+ * </p>
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+ protected void checkWidget() {
+ if( display == null ) {
+ DND.error( SWT.ERROR_WIDGET_DISPOSED );
+ }
+ if( display.getThread() != Thread.currentThread() ) {
+ DND.error( SWT.ERROR_THREAD_INVALID_ACCESS );
+ }
+ if( display.isDisposed() ) {
+ DND.error( SWT.ERROR_WIDGET_DISPOSED );
+ }
+ }
+
+ /**
+ * Checks that this class can be subclassed.
+ * <p>
+ * The SWT class library is intended to be subclassed
+ * only at specific, controlled points. This method enforces this
+ * rule unless it is overridden.
+ * </p><p>
+ * <em>IMPORTANT:</em> By providing an implementation of this
+ * method that allows a subclass of a class which does not
+ * normally allow subclassing to be created, the implementer
+ * agrees to be fully responsible for the fact that any such
+ * subclass will likely fail between SWT releases and will be
+ * strongly platform specific. No support is provided for
+ * user-written classes which are implemented in this fashion.
+ * </p><p>
+ * The ability to subclass outside of the allowed SWT classes
+ * is intended purely to enable those not on the SWT development
+ * team to implement patches in order to get around specific
+ * limitations in advance of when those limitations can be
+ * addressed by the team. Subclassing should not be attempted
+ * without an intimate and detailed understanding of the hierarchy.
+ * </p>
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
+ * </ul>
+ */
+ protected void checkSubclass() {
+ String name = getClass().getName();
+ String validName = Clipboard.class.getName();
+ if( !validName.equals( name ) ) {
+ DND.error( SWT.ERROR_INVALID_SUBCLASS );
+ }
+ }
+
+ private static void checkOperationMode() {
+ if( getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof SimpleLifeCycle ) {
+ throw new UnsupportedOperationException( "Method not supported in JEE_COMPATIBILITY mode." );
+ }
+ }
+
+ /**
+ * This listener interface is used to inform application code that the result of clipboard
+ * operation execution is available.
+ *
+ * @see Clipboard#readText()
+ */
+ private interface ClipboardListener {
+
+ void operationSucceeded( ClipboardOperation operation, String result );
+
+ void operationFailed( ClipboardOperation operation, String errorMessage );
+
+ }
+
+ private class ClipboardOperationHandler extends AbstractOperationHandler {
+
+ @Override
+ public void handleCall( String methodName, JsonObject properties ) {
+ if( METHOD_OPERATION_SUCCEEDED.equals( methodName ) ) {
+ final ClipboardOperation operation = getOperation( properties );
+ final String result = properties.get( PROP_RESULT ).asString();
+ operationResult = result;
+ if( listener != null ) {
+ ProcessActionRunner.add( new Runnable() {
+ @Override
+ public void run() {
+ listener.operationSucceeded( operation, result );
+ }
+ } );
+ }
+ } else if( METHOD_OPERATION_FAILED.equals( methodName ) ) {
+ final ClipboardOperation operation = getOperation( properties );
+ final String errorMessage = properties.get( PROP_ERROR_MESSAGE ).asString();
+ if( ClipboardOperation.WRITE_TEXT.equals( operation ) ) {
+ operationResult = new SWTException( SWT.ERROR_CANNOT_SET_TEXT, errorMessage );
+ } else if( ClipboardOperation.READ_TEXT.equals( operation ) ) {
+ operationResult = new SWTException( SWT.ERROR_CANNOT_GET_TEXT, errorMessage );
+ } else {
+ operationResult = new SWTException( errorMessage );
+ }
+ if( listener != null ) {
+ ProcessActionRunner.add( new Runnable() {
+ @Override
+ public void run() {
+ listener.operationFailed( operation, errorMessage );
+ }
+ } );
+ }
+ }
+ }
+
+ private ClipboardOperation getOperation( JsonObject properties ) {
+ String operation = properties.get( PROP_OPERATION ).asString();
+ if( METHOD_WRITE_TEXT.equals( operation ) ) {
+ return ClipboardOperation.WRITE_TEXT;
+ } else if( METHOD_READ_TEXT.equals( operation ) ) {
+ return ClipboardOperation.READ_TEXT;
+ } else {
+ return null;
+ }
+ }
+
+ }
+
+}
diff --git a/bundles/org.eclipse.rap.rwt/src/org/eclipse/swt/dnd/DND.java b/bundles/org.eclipse.rap.rwt/src/org/eclipse/swt/dnd/DND.java
index a629800..ebbdc92 100644
--- a/bundles/org.eclipse.rap.rwt/src/org/eclipse/swt/dnd/DND.java
+++ b/bundles/org.eclipse.rap.rwt/src/org/eclipse/swt/dnd/DND.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2009 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -26,15 +26,17 @@
/**
* The transfer mechanism for data that is being cut
* and then pasted or copied and then pasted (value is 1).
+ *
+ * @see Clipboard
*/
-// * @see Clipboard
public final static int CLIPBOARD = 1 << 0;
/**
* The transfer mechanism for clients that use the selection
* mechanism (value is 2).
+ *
+ * @see Clipboard
*/
-// * @see Clipboard
public final static int SELECTION_CLIPBOARD = 1 << 1;
/**
diff --git a/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/ClipboardTab.java b/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/ClipboardTab.java
new file mode 100644
index 0000000..8dd76a9
--- /dev/null
+++ b/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/ClipboardTab.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2020 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.demo.controls;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+
+
+public class ClipboardTab extends ExampleTab {
+
+ private Clipboard clipboard;
+ private Text copyText;
+ private Text pasteText;
+
+ public ClipboardTab() {
+ super( "Clipboard" );
+ setHorizontalSashFormWeights( new int[] { 100, 0 } );
+ }
+
+ @Override
+ protected void createStyleControls( Composite parent ) {
+ }
+
+ @Override
+ protected void createExampleControls( Composite parent ) {
+ clipboard = new Clipboard( parent.getDisplay() );
+
+ configureDisplay( parent.getDisplay() );
+
+ parent.setLayout( new GridLayout( 2, false ) );
+
+ copyText = new Text( parent, SWT.SINGLE | SWT.BORDER );
+ copyText.setLayoutData( new GridData( SWT.FILL, SWT.CENTER, true, false ) );
+ Button copyButton = new Button( parent, SWT.PUSH );
+ copyButton.setText( "Copy" );
+ copyButton.addListener( SWT.Selection, new Listener() {
+ @Override
+ public void handleEvent( Event event ) {
+ String text = copyText.getText();
+ if( !text.isEmpty() ) {
+ setClipboardData( text );
+ }
+ }
+ } );
+
+ pasteText = new Text( parent, SWT.SINGLE | SWT.BORDER );
+ pasteText.setLayoutData( new GridData( SWT.FILL, SWT.CENTER, true, false ) );
+ Button pasteButton = new Button( parent, SWT.PUSH );
+ pasteButton.setText( "Paste" );
+ pasteButton.addListener( SWT.Selection, new Listener() {
+ @Override
+ public void handleEvent( Event event ) {
+ String text = getClipboardData();
+ pasteText.setText( text );
+ }
+ } );
+
+ }
+
+ private void configureDisplay( Display display ) {
+ display.setData( RWT.ACTIVE_KEYS, new String[] { "CTRL+C", "CTRL+V" } );
+ display.setData( RWT.CANCEL_KEYS, new String[] { "CTRL+C", "CTRL+V" } );
+ display.addFilter( SWT.KeyDown, new Listener() {
+ @Override
+ public void handleEvent( Event event ) {
+ if( event.character == 'c' ) {
+ String text = copyText.getText();
+ if( !text.isEmpty() ) {
+ setClipboardData( text );
+ }
+ } else if( event.character == 'v' ) {
+ String text = getClipboardData();
+ pasteText.setText( text );
+ }
+ }
+ } );
+ }
+
+ private void setClipboardData( String textData ) {
+ try {
+ Transfer[] transfers = new Transfer[]{ TextTransfer.getInstance() };
+ Object[] data = new Object[]{ textData };
+ clipboard.setContents( data, transfers );
+ } catch( SWTError error ) {
+ String message = "Unable to set client clipboard data!";
+ MessageDialog.openError( getShell(), "Clipboard Error", message );
+ }
+ }
+
+ private String getClipboardData() {
+ String textData = ( String )clipboard.getContents( TextTransfer.getInstance() );
+ if( textData == null ) {
+ String message = "Unable to get client clipboard data!";
+ MessageDialog.openError( getShell(), "Clipboard Error", message );
+ return "";
+ }
+ return textData;
+ }
+
+}
diff --git a/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/ControlsDemo.java b/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/ControlsDemo.java
index d80895f..6995736 100644
--- a/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/ControlsDemo.java
+++ b/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/ControlsDemo.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2007, 2016 Innoopract Informationssysteme GmbH and others.
+ * Copyright (c) 2007, 2020 Innoopract Informationssysteme GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -175,6 +175,7 @@
new ZOrderTab(),
new VariantsTab(),
new ControlDecorationTab(),
+ new ClipboardTab(),
new ErrorHandlingTab(),
new ClientServicesTab(),
new NLSTab(),
diff --git a/tests/org.eclipse.rap.rwt.test/src/org/eclipse/swt/dnd/Clipboard_Test.java b/tests/org.eclipse.rap.rwt.test/src/org/eclipse/swt/dnd/Clipboard_Test.java
new file mode 100644
index 0000000..863b7c4
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.test/src/org/eclipse/swt/dnd/Clipboard_Test.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2020 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.swt.dnd;
+
+import static org.eclipse.rap.rwt.application.Application.OperationMode.JEE_COMPATIBILITY;
+import static org.eclipse.rap.rwt.application.Application.OperationMode.SWT_COMPATIBILITY;
+import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.eclipse.rap.json.JsonObject;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.internal.lifecycle.LifeCycleFactory;
+import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle;
+import org.eclipse.rap.rwt.remote.Connection;
+import org.eclipse.rap.rwt.remote.RemoteObject;
+import org.eclipse.rap.rwt.testfixture.TestContext;
+import org.eclipse.swt.widgets.Display;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class Clipboard_Test {
+
+ private static final String REMOTE_TYPE = "rwt.client.Clipboard";
+ private Connection connection;
+ private RemoteObject remoteObject;
+
+ @Rule
+ public TestContext context = new TestContext();
+
+ private Clipboard clipboard;
+
+ @Before
+ public void setUp() {
+ Display display = new Display();
+ connection = mock( Connection.class );
+ remoteObject = mock( RemoteObject.class );
+ when( connection.createRemoteObject( anyString() ) ).thenReturn( remoteObject );
+ context.replaceConnection( connection );
+ clipboard = new Clipboard( display );
+ }
+
+ @Test
+ public void testConstructor_createsRemoteObject() {
+ verify( connection ).createRemoteObject( eq( REMOTE_TYPE ) );
+ }
+
+ @Test
+ public void testWriteText() {
+ clipboard.writeText( "foo" );
+
+ verify( remoteObject ).call( "writeText", new JsonObject().add( "text", "foo" ) );
+ }
+
+ @Test
+ public void testReadText() {
+ clipboard.readText();
+
+ verify( remoteObject ).call( "readText", null );
+ }
+
+ @Test
+ public void testSetContents_JEE_COMPATIBILITY() {
+ ensureOperationMode( JEE_COMPATIBILITY );
+ try {
+ Object[] data = new Object[] { "foo" };
+ Transfer[] transfers = new Transfer[] { TextTransfer.getInstance() };
+ clipboard.setContents( data, transfers );
+ fail();
+ } catch( UnsupportedOperationException expected ) {
+ assertEquals( "Method not supported in JEE_COMPATIBILITY mode.", expected.getMessage() );
+ }
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void testSetContents_nonTextTransfer() {
+ ensureOperationMode( SWT_COMPATIBILITY );
+
+ Object[] data = new Object[] { "foo" };
+ Transfer[] transfers = new Transfer[] { ImageTransfer.getInstance() };
+ clipboard.setContents( data, transfers );
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void testSetContents_multipleTransfers() {
+ ensureOperationMode( SWT_COMPATIBILITY );
+
+ Object[] data = new Object[] { "foo", "bar" };
+ Transfer[] transfers = new Transfer[] {
+ TextTransfer.getInstance(),
+ TextTransfer.getInstance()
+ };
+ clipboard.setContents( data, transfers );
+ }
+
+ @Test
+ public void testGetContents_JEE_COMPATIBILITY() {
+ ensureOperationMode( JEE_COMPATIBILITY );
+ try {
+ clipboard.getContents( TextTransfer.getInstance() );
+ fail();
+ } catch( UnsupportedOperationException expected ) {
+ assertEquals( "Method not supported in JEE_COMPATIBILITY mode.", expected.getMessage() );
+ }
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void testGetContents_nonTextTransfer() {
+ ensureOperationMode( SWT_COMPATIBILITY );
+
+ clipboard.getContents( ImageTransfer.getInstance() );
+ }
+
+ @Test
+ public void testDispose() {
+ clipboard.dispose();
+
+ assertTrue( clipboard.isDisposed() );
+ }
+
+ private static void ensureOperationMode( OperationMode operationMode ) {
+ LifeCycleFactory lifeCycleFactory = getApplicationContext().getLifeCycleFactory();
+ lifeCycleFactory.deactivate();
+ if( SWT_COMPATIBILITY.equals( operationMode ) ) {
+ lifeCycleFactory.configure( RWTLifeCycle.class );
+ }
+ lifeCycleFactory.activate();
+ }
+
+}