diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java index 98265d833cc..da83f012e05 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java @@ -223,7 +223,9 @@ public static Comparator imageDataComparator() { .thenComparing((ImageData firstData, ImageData secondData) -> { for (int x = 0; x < firstData.width; x++) { for (int y = 0; y < firstData.height; y++) { - if (firstData.getPixel(x, y) != secondData.getPixel(x, y)) { + RGB first = firstData.palette.getRGB(firstData.getPixel(x, y)); + RGB second = secondData.palette.getRGB(secondData.getPixel(x, y)); + if (!first.equals(second)) { return -1; } if (firstData.getAlpha(x, y) != secondData.getAlpha(x, y)) { diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java index 52bc3d6a4d1..642edfd9bd8 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java @@ -43,7 +43,14 @@ Test_org_eclipse_swt_accessibility_AccessibleControlEvent.class, // Test_org_eclipse_swt_accessibility_AccessibleEvent.class, // Test_org_eclipse_swt_accessibility_AccessibleTextEvent.class, // - Test_org_eclipse_swt_dnd_Clipboard.class, + Test_org_eclipse_swt_dnd_ByteArrayTransfer.class, // + Test_org_eclipse_swt_dnd_Clipboard.class, // + Test_org_eclipse_swt_dnd_FileTransfer.class, // + Test_org_eclipse_swt_dnd_HTMLTransfer.class, // + Test_org_eclipse_swt_dnd_ImageTransfer.class, // + Test_org_eclipse_swt_dnd_RTFTransfer.class, // + Test_org_eclipse_swt_dnd_TextTransfer.class, // + Test_org_eclipse_swt_dnd_URLTransfer.class, // Test_org_eclipse_swt_events_ArmEvent.class, // Test_org_eclipse_swt_events_ControlEvent.class, // Test_org_eclipse_swt_events_DisposeEvent.class, // diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java index 815f090065f..d1d247d21cf 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java @@ -31,6 +31,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import clipboard.ClipboardTest; + /** * Base class for tests that test clipboard and transfer types */ @@ -168,6 +170,7 @@ public void tearDown() throws Exception { } } finally { if (clipboard != null) { + supportedClipboardIds().forEach(clipboard::clearContents); clipboard.dispose(); } if (shell != null) { @@ -177,6 +180,28 @@ public void tearDown() throws Exception { } } + protected String addTrailingNulCharacter(String result) { + return result + '\0'; + } + + /** + * Trim trailing nul character - some transfer types require a trailing nul when + * copied to the clipboard, so use this method to remove it for tests that + * obtain bytes from the {@link ClipboardTest} app. + * + * @param result to trim terminating nul character from + * @return string with the nul character trimmed, or null if result was null. + */ + protected String trimTrailingNulCharacter(String result) { + if (result == null) { + return null; + } + if (result.charAt(result.length() - 1) == '\0') { + result = result.substring(0, result.length() - 1); + } + return result; + } + /** * Make sure to always copy/paste unique strings - this ensures that tests run * under {@link RepeatedTest}s don't false pass because of clipboard value on diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java index 6c2875e0931..b2a1c13bc12 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java @@ -35,56 +35,19 @@ import clipboard.ClipboardCommands; public class RemoteClipboard implements ClipboardCommands { + /** + * Set to true to manually launch the ClipboardTest. This is useful so you can + * debug the Swing side. + */ + private static boolean DEBUG_REMOTE = false; private ClipboardCommands remote; private Process remoteClipboardProcess; private Path remoteClipboardTempDir; public void start() throws Exception { assertNull(remote, "Create a new instance to restart"); - /* - * The below copy using getPath may be redundant (i.e. it may be possible to run - * the class files from where they currently reside in the bin folder or the - * jar), but this method of setting up the class files is very simple and is - * done the same way that other files are extracted for tests. - * - * If the ClipboardTest starts to get more complicated, or other tests want to - * replicate this design element, then refactoring this is an option. - */ - remoteClipboardTempDir = Files.createTempDirectory("swt-test-Clipboard"); - List.of( // - "ClipboardTest", // - "ClipboardCommands", // - "ClipboardCommandsImpl", // - "ClipboardTest$LocalHostOnlySocketFactory" // - ).forEach((f) -> { - // extract the files and put them in the temp directory - SwtTestUtil.copyFile("/clipboard/" + f + ".class", - remoteClipboardTempDir.resolve("clipboard/" + f + ".class")); - }); - - String javaHome = System.getProperty("java.home"); - String javaExe = javaHome + "/bin/java" + (SwtTestUtil.isWindowsOS ? ".exe" : ""); - assertTrue(Files.exists(Path.of(javaExe))); - - ProcessBuilder pb = new ProcessBuilder(javaExe, "clipboard.ClipboardTest") - .directory(remoteClipboardTempDir.toFile()); - pb.inheritIO(); - pb.redirectOutput(Redirect.PIPE); - remoteClipboardProcess = pb.start(); - // Read server output to find the port - int port = SwtTestUtil.runOperationInThread(() -> { - BufferedReader reader = new BufferedReader(new InputStreamReader(remoteClipboardProcess.getInputStream())); - String line; - while ((line = reader.readLine()) != null) { - if (line.startsWith(ClipboardCommands.PORT_MESSAGE)) { - String[] parts = line.split(":"); - return Integer.parseInt(parts[1].trim()); - } - } - throw new RuntimeException("Failed to get port"); - }); - assertNotEquals(0, port); + int port = DEBUG_REMOTE ? ClipboardCommands.DEFAULT_PORT : launchRemote(); try { Registry reg = LocateRegistry.getRegistry("127.0.0.1", port); long stopTime = System.currentTimeMillis() + 10000; @@ -128,8 +91,65 @@ public void start() throws Exception { remote.waitUntilReady(); } + private int launchRemote() throws IOException { + /* + * The below copy using getPath may be redundant (i.e. it may be possible to run + * the class files from where they currently reside in the bin folder or the + * jar), but this method of setting up the class files is very simple and is + * done the same way that other files are extracted for tests. + * + * If the ClipboardTest starts to get more complicated, or other tests want to + * replicate this design element, then refactoring this is an option. + */ + remoteClipboardTempDir = Files.createTempDirectory("swt-test-Clipboard"); + List.of( // + "ClipboardTest", // + "ClipboardCommands", // + "ClipboardCommandsImpl", // + "ClipboardTest$LocalHostOnlySocketFactory", // + "FileListSelection", // + "HtmlSelection", // + "ImageSelection", // + "MyTypeSelection", // + "RtfSelection", // + "UrlSelection" // + ).forEach((f) -> { + // extract the files and put them in the temp directory + SwtTestUtil.copyFile("/clipboard/" + f + ".class", + remoteClipboardTempDir.resolve("clipboard/" + f + ".class")); + }); + + String javaHome = System.getProperty("java.home"); + String javaExe = javaHome + "/bin/java" + (SwtTestUtil.isWindowsOS ? ".exe" : ""); + assertTrue(Files.exists(Path.of(javaExe))); + + ProcessBuilder pb = new ProcessBuilder(javaExe, "clipboard.ClipboardTest", "-autoport") + .directory(remoteClipboardTempDir.toFile()); + pb.inheritIO(); + pb.redirectOutput(Redirect.PIPE); + remoteClipboardProcess = pb.start(); + + // Read server output to find the port + int port = SwtTestUtil.runOperationInThread(() -> { + BufferedReader reader = new BufferedReader(new InputStreamReader(remoteClipboardProcess.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith(ClipboardCommands.PORT_MESSAGE)) { + String[] parts = line.split(":"); + return Integer.parseInt(parts[1].trim()); + } + } + throw new RuntimeException("Failed to get port"); + }); + assertNotEquals(0, port); + return port; + } + @Override public void stop() throws RemoteException { + if (DEBUG_REMOTE) { + return; + } try { stopProcess(); } catch (InterruptedException e) { @@ -186,6 +206,11 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } } + @Override + public void setContents(String string) throws RemoteException { + setContents(string, CLIPBOARD); + } + @Override public void setContents(String string, int clipboardId) throws RemoteException { remote.setContents(string, clipboardId); @@ -196,6 +221,11 @@ public void setFocus() throws RemoteException { remote.setFocus(); } + @Override + public String getStringContents() throws RemoteException { + return getStringContents(CLIPBOARD); + } + @Override public String getStringContents(int clipboardId) throws RemoteException { return remote.getStringContents(clipboardId); @@ -207,7 +237,68 @@ public void waitUntilReady() throws RemoteException { } @Override - public void waitForButtonPress() throws RemoteException { + public void waitForButtonPress() throws RemoteException { remote.waitForButtonPress(); } -} \ No newline at end of file + + @Override + public void setRtfContents(String test) throws RemoteException { + remote.setRtfContents(test); + } + + @Override + public String getRtfContents() throws RemoteException { + return remote.getRtfContents(); + } + + @Override + public void setHtmlContents(String test) throws RemoteException { + remote.setHtmlContents(test); + } + + @Override + public String getHtmlContents() throws RemoteException { + return remote.getHtmlContents(); + } + + @Override + public void setUrlContents(byte[] test) throws RemoteException { + remote.setUrlContents(test); + } + + @Override + public byte[] getUrlContents() throws RemoteException { + return remote.getUrlContents(); + } + + @Override + public void setImageContents(byte[] imageContents) throws RemoteException { + remote.setImageContents(imageContents); + } + + @Override + public byte[] getImageContents() throws RemoteException { + return remote.getImageContents(); + } + + @Override + public void setFileListContents(String[] fileList) throws RemoteException { + remote.setFileListContents(fileList); + } + + @Override + public String[] getFileListContents() throws RemoteException { + return remote.getFileListContents(); + } + + @Override + public void setMyTypeContents(byte[] bytes) throws RemoteException { + remote.setMyTypeContents(bytes); + } + + @Override + public byte[] getMyTypeContents() throws RemoteException { + return remote.getMyTypeContents(); + } + +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ByteArrayTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ByteArrayTransfer.java new file mode 100644 index 00000000000..e04b408cdf2 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ByteArrayTransfer.java @@ -0,0 +1,296 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.ByteArrayTransfer; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * Automated Test Suite for class {@link ByteArrayTransfer} + * + * ByteArrayTransfer is abstract, so we test it via a custom subclass based on + * the javadoc of ByteArrayTransfer, the inner classes below {@link MyType} and + * {@link MyTypeTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_ByteArrayTransfer extends ClipboardBase { + private MyTypeTransfer myTypeTransfer; + + @BeforeEach + public void localSetup() { + myTypeTransfer = MyTypeTransfer.getInstance(); + } + + private MyType getMyTypeInstance() { + MyType myType = new MyType(); + myType.fileName = getUniqueTestString(); + myType.fileLength = 1234; + myType.lastModified = 5678; + return myType; + } + + /** + * Duplicates what is in + * {@link MyTypeTransfer#javaToNative(Object, TransferData)} + * + * It is duplicated so the MyTypeTransfer can stay matching what is in + * {@link ByteArrayTransfer} javadoc. + */ + private byte[] serializeMyType(MyType myType) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (DataOutputStream writeOut = new DataOutputStream(out)) { + byte[] fileNameBytes = myType.fileName.getBytes(StandardCharsets.UTF_8); + writeOut.writeInt(fileNameBytes.length); + writeOut.write(fileNameBytes); + writeOut.writeLong(myType.fileLength); + writeOut.writeLong(myType.lastModified); + return out.toByteArray(); + } + } + + /** + * Duplicates what is in {@link MyTypeTransfer#nativeToJava(TransferData)} + * + * It is duplicated so the MyTypeTransfer can stay matching what is in + * {@link ByteArrayTransfer} javadoc. + */ + private MyType deserializeMyType(byte[] buffer) throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(buffer); + try (DataInputStream readIn = new DataInputStream(in)) { + MyType myType = new MyType(); + int size = readIn.readInt(); + byte[] name = new byte[size]; + readIn.read(name); + myType.fileName = new String(name); + myType.fileLength = readIn.readLong(); + myType.lastModified = readIn.readLong(); + return myType; + } + } + + private void assertMyTypeEquals(MyType expected, MyType actual) { + assertEquals(expected.fileName, actual.fileName); + assertEquals(expected.fileLength, actual.fileLength); + assertEquals(expected.lastModified, actual.lastModified); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, myTypeTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private MyType getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(myTypeTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(MyType.class, o); + return (MyType) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + MyType test = getMyTypeInstance(); + setContents(test); + assertMyTypeEquals(test, getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents(getUniqueTestString())); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getMyTypeInstance()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(myTypeTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(myTypeTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + MyType test = getMyTypeInstance(); + + openAndFocusShell(false); + + setContents(test); + MyType contents = getContents(); + assertMyTypeEquals(test, contents); + } + + @Test + public void test_nativeToJava() throws Exception { + MyType test = getMyTypeInstance(); + + openAndFocusRemote(); + remote.setMyTypeContents(serializeMyType(test)); + openAndFocusShell(false); + assertMyTypeEquals(test, getContents()); + } + + @Order(1) + @Test + @DisabledOnOs(value = OS.MAC, disabledReason = """ + remote.getMyTypeContents doesn't work properly on macOS, this is + probably a test only issue on macOS + """) + public void test_javaToNative() throws Exception { + MyType test = getMyTypeInstance(); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + byte[] resultBytes = SwtTestUtil.runOperationInThread(() -> remote.getMyTypeContents()); + MyType result = deserializeMyType(resultBytes); + assertMyTypeEquals(test, result); + } + + public static class MyType { + public String fileName; + public long fileLength; + public long lastModified; + } + + public static class MyTypeTransfer extends ByteArrayTransfer { + + private static final String MYTYPENAME = "my_type_name"; + private static final int MYTYPEID = registerType(MYTYPENAME); + private static MyTypeTransfer _instance = new MyTypeTransfer(); + + private MyTypeTransfer() { + } + + public static MyTypeTransfer getInstance() { + return _instance; + } + + @Override + public void javaToNative(Object object, TransferData transferData) { + if (!checkMyType(object) || !isSupportedType(transferData)) { + DND.error(DND.ERROR_INVALID_DATA); + } + + MyType myType = (MyType) object; + // write data to a byte array and then ask super to convert to native + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (DataOutputStream writeOut = new DataOutputStream(out)) { + byte[] fileNameBytes = myType.fileName.getBytes(StandardCharsets.UTF_8); + writeOut.writeInt(fileNameBytes.length); + writeOut.write(fileNameBytes); + writeOut.writeLong(myType.fileLength); + writeOut.writeLong(myType.lastModified); + super.javaToNative(out.toByteArray(), transferData); + } catch (IOException e) { + } + } + + @Override + public Object nativeToJava(TransferData transferData) { + if (!isSupportedType(transferData)) { + return null; + } + + byte[] buffer = (byte[]) super.nativeToJava(transferData); + if (buffer == null) { + return null; + } + + ByteArrayInputStream in = new ByteArrayInputStream(buffer); + try (DataInputStream readIn = new DataInputStream(in)) { + MyType myType = new MyType(); + int size = readIn.readInt(); + byte[] name = new byte[size]; + readIn.read(name); + myType.fileName = new String(name); + myType.fileLength = readIn.readLong(); + myType.lastModified = readIn.readLong(); + return myType; + } catch (IOException ex) { + } + return null; + } + + @Override + protected String[] getTypeNames() { + return new String[] { MYTYPENAME }; + } + + @Override + protected int[] getTypeIds() { + return new int[] { MYTYPEID }; + } + + private boolean checkMyType(Object object) { + return (object instanceof MyType); + } + + @Override + protected boolean validate(Object object) { + return checkMyType(object); + } + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_FileTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_FileTransfer.java new file mode 100644 index 00000000000..a960536ed27 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_FileTransfer.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.RTFTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * Automated Test Suite for {@link FileTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_FileTransfer extends ClipboardBase { + /** + * FileTransfer is slightly mis-named as it takes a list. + */ + private FileTransfer fileTransfer; + + private List tmpFilesToDelete = new ArrayList<>(); + + @BeforeEach + public void localSetup() { + fileTransfer = FileTransfer.getInstance(); + } + + /** + * Return a new name of a tempfile and clean it up at the end of the test + * + * This is probably more heavy weight than needed, but it is to ensure the file + * list has real paths suitable to the platform we are running on. + */ + private String tempFile() throws IOException { + Path tempFile = Files.createTempFile("swt-test", ""); + tmpFilesToDelete.add(tempFile); + return tempFile.toString(); + } + + @AfterEach + public void deleteTempFiles() throws IOException { + for (Path path : tmpFilesToDelete) { + Files.delete(path); + } + } + + private String[] getFileList() throws IOException { + return new String[] { tempFile(), tempFile(), tempFile() }; + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, fileTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String[] getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(fileTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String[].class, o); + return (String[]) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + + String[] fileList = getFileList(); + setContents(fileList); + assertArrayEquals(fileList, getContents()); + + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(Arrays.asList(getFileList()))); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[0])); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { null })); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { "" })); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { tempFile(), null })); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { tempFile(), "" })); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getFileList()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(fileTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(getUniqueTestString(), RTFTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(fileTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String[] fileList = getFileList(); + + openAndFocusShell(false); + // Put a type on the clipboard and ensure we don't match it + setContents(fileList); + String[] contents = getContents(); + assertArrayEquals(fileList, contents); + } + + @Test + @DisabledOnOs(value = { OS.WINDOWS, OS.MAC }, disabledReason = """ + AWT's DataFlavor.javaFileListFlavor used to test this method is not quite + compatible with FileTransfer on Windows or macOS. + + Or the implementation of remote.setFileListContents is incorrect. + + On Windows DataFlavor.javaFileListFlavor does manipulate the contents of the + strings by replacing long versions of folder names with 8.3 versions, so + a different test method is needed to make this test stable + """) + public void test_nativeToJava() throws Exception { + + String[] fileList = getFileList(); + + openAndFocusRemote(); + remote.setFileListContents(fileList); + openAndFocusShell(false); + + assertArrayEquals(fileList, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String[] fileList = getFileList(); + + openAndFocusShell(true); + setContents(fileList); + openAndFocusRemote(); + String[] contents = SwtTestUtil.runOperationInThread(() -> remote.getFileListContents()); + assertArrayEquals(fileList, contents); + + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_HTMLTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_HTMLTransfer.java new file mode 100644 index 00000000000..344a10d8008 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_HTMLTransfer.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.HTMLTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Automated Test Suite for {@link HTMLTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_HTMLTransfer extends ClipboardBase { + private HTMLTransfer htmlTransfer; + + @BeforeEach + public void localSetup() { + htmlTransfer = HTMLTransfer.getInstance(); + } + + private String toHtml(String s) { + return "" + s + ""; + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, htmlTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(htmlTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String helloWorld = toHtml(getUniqueTestString()); + setContents(helloWorld); + assertEquals(helloWorld, getContents()); + setContents(" "); // whitespace only - HTML restrictions, at least within SWT are very mild + assertEquals(" ", getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(toHtml(getUniqueTestString())); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(htmlTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(htmlTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String test = toHtml(getUniqueTestString()); + + openAndFocusShell(false); + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + static Stream testData() { + return Stream.of(// + Arguments.of("hello_world", "Hello, World!") // + ); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_nativeToJava(String name, String test) throws Exception { + openAndFocusRemote(); + remote.setHtmlContents(test); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String test = toHtml(getUniqueTestString()); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + String result = SwtTestUtil.runOperationInThread(() -> remote.getHtmlContents()); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ImageTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ImageTransfer.java new file mode 100644 index 00000000000..158e1f0a8ea --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ImageTransfer.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.eclipse.swt.tests.graphics.ImageDataTestHelper.imageDataComparator; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.ImageTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Automated Test Suite for class {@link ImageTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_ImageTransfer extends ClipboardBase { + private ImageTransfer imageTransfer; + + @BeforeEach + public void localSetup() { + imageTransfer = ImageTransfer.getInstance(); + } + + private InputStream getImageAsInputStream() { + return SwtTestUtil.class.getResourceAsStream(SwtTestUtil.imageFilenames[0] + "." + SwtTestUtil.imageFormats[0]); + } + + private ImageData getImageData() throws IOException { + try (InputStream stream = getImageAsInputStream()) { + return new ImageData(stream); + } + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, imageTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private ImageData getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(imageTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(ImageData.class, o); + return (ImageData) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + + ImageData imageData = getImageData(); + setContents(imageData); + assertEquals(0, imageDataComparator().compare(imageData, getContents())); + + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Image(display, imageData))); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getImageData()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(imageTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(imageTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + ImageData imageData = getImageData(); + + openAndFocusShell(false); + // Put a type on the clipboard and ensure we don't match it + setContents(imageData); + ImageData contents = getContents(); + assertEquals(0, imageDataComparator().compare(imageData, contents)); + } + + @Test + public void test_nativeToJava() throws Exception { + + byte[] fileContents; + try (InputStream stream = getImageAsInputStream()) { + fileContents = stream.readAllBytes(); + } + + openAndFocusRemote(); + remote.setImageContents(fileContents); + openAndFocusShell(false); + ImageData expected = getImageData(); + assertEquals(0, imageDataComparator().compare(expected, getContents())); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + ImageData expected = getImageData(); + + openAndFocusShell(true); + setContents(expected); + openAndFocusRemote(); + byte[] fileContents = SwtTestUtil.runOperationInThread(() -> remote.getImageContents()); + ImageData result = new ImageData(new ByteArrayInputStream(fileContents)); + assertEquals(0, imageDataComparator().compare(expected, result)); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_RTFTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_RTFTransfer.java new file mode 100644 index 00000000000..6c53170cb90 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_RTFTransfer.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.RTFTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Automated Test Suite for {@link RTFTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_RTFTransfer extends ClipboardBase { + private RTFTransfer rtfTransfer; + + @BeforeEach + public void localSetup() { + rtfTransfer = RTFTransfer.getInstance(); + } + + private String toRtf(String s) { + return "{\\rtf1\\b\\i " + s + "}"; + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, rtfTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(rtfTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String helloWorld = toRtf(getUniqueTestString()); + setContents(helloWorld); + assertEquals(helloWorld, getContents()); + setContents(" "); // whitespace only - RTF restrictions, at least within SWT are very mild + assertEquals(" ", getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(toRtf(getUniqueTestString())); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(rtfTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(rtfTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String test = toRtf(getUniqueTestString()); + + openAndFocusShell(false); + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + @Test + public void test_nativeToJava() throws Exception { + String test = toRtf(getUniqueTestString()); + + openAndFocusRemote(); + String trailingNulCharacter = test; + if (SwtTestUtil.isWindows) { + // Windows require nul trailing character, but + // other Platforms don't want that + trailingNulCharacter = addTrailingNulCharacter(test); + } + + remote.setRtfContents(trailingNulCharacter); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String test = toRtf(getUniqueTestString()); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + String result = SwtTestUtil.runOperationInThread(() -> remote.getRtfContents()); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_TextTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_TextTransfer.java new file mode 100644 index 00000000000..04478f1134f --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_TextTransfer.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Automated Test Suite for {@link TextTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_TextTransfer extends ClipboardBase { + private TextTransfer textTransfer; + + @BeforeEach + public void localSetup() { + textTransfer = TextTransfer.getInstance(); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, textTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(textTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String helloWorld = getUniqueTestString(); + setContents(helloWorld); + assertEquals(helloWorld, getContents()); + setContents(" "); // whitespace only + assertEquals(" ", getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getUniqueTestString()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(textTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(textTransfer::isSupportedType)); + } + + static Stream testData() { + return Stream.of(// + Arguments.of("hello_world", "Hello, World!"), // + Arguments.of("blank_non_empty", " "), // + Arguments.of("emoji_grinning_face", "\uD83D\uDE00") // + ); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_internalClone(String name, String test) throws Exception { + openAndFocusShell(false); + + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_nativeToJava(String name, String test) throws Exception { + openAndFocusRemote(); + remote.setContents(test); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_javaToNative(String name, String test) throws Exception { + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + String result = SwtTestUtil.runOperationInThread(() -> remote.getStringContents()); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_URLTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_URLTransfer.java new file mode 100644 index 00000000000..73ffdde14d6 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_URLTransfer.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.dnd.URLTransfer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * Automated Test Suite for {@link URLTransfer} + */ +@Tag("clipboard") +@DisabledOnOs(value=OS.MAC, disabledReason = """ + URLTransfer doesn't seem to work properly on mac, perhaps + this is a side effect of mac's version using a deprecated + transfer type. See https://github.com/eclipse-platform/eclipse.platform.swt/pull/2669 + """) +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_URLTransfer extends ClipboardBase { + private URLTransfer urlTransfer; + + @BeforeEach + public void localSetup() { + urlTransfer = URLTransfer.getInstance(); + } + + /** + * Returns a string matching text/x-moz-url according to + * https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store + */ + private String getURLTransferString() { + return "https://eclipse.dev/eclipse/swt/" + "\n" + getUniqueTestString(); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, urlTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(urlTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String mozUrl = getURLTransferString(); + setContents(mozUrl); + assertEquals(mozUrl, getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getURLTransferString()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(urlTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(urlTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String test = getURLTransferString(); + + openAndFocusShell(false); + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + @Test + public void test_nativeToJava() throws Exception { + String test = getURLTransferString(); + + openAndFocusRemote(); + + byte[] bytes = test.getBytes(StandardCharsets.UTF_16LE); + remote.setUrlContents(bytes); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String test = getURLTransferString(); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + byte[] bytes = SwtTestUtil.runOperationInThread(() -> remote.getUrlContents()); + + // This is an attempt to recreate the same bytestream that Firefox provides when + // doing text/x-moz-url which appears to be this encoding (specifically no BOM) + String result = new String(bytes, StandardCharsets.UTF_16LE); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java index 56741a7673a..7007706f09d 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java @@ -26,23 +26,59 @@ public interface ClipboardCommands extends Remote { */ int SELECTION_CLIPBOARD = 1 << 1; + /** + * The port the RMI will listen on by default + */ + int DEFAULT_PORT = 3456; void stop() throws RemoteException; + void setFocus() throws RemoteException; + + void waitUntilReady() throws RemoteException; + + void waitForButtonPress() throws RemoteException; + + /** + * @param string string to set as contents on {@link #CLIPBOARD} + */ + void setContents(String string) throws RemoteException; + /** - * @param string string to set as contents + * @param string string to set as contents * @param clipboardId {@link #CLIPBOARD} or {@value #SELECTION_CLIPBOARD} */ void setContents(String string, int clipboardId) throws RemoteException; - void setFocus() throws RemoteException; + void setRtfContents(String string) throws RemoteException; + + String getRtfContents() throws RemoteException; + + void setHtmlContents(String string) throws RemoteException; + + String getHtmlContents() throws RemoteException; + + void setUrlContents(byte[] string) throws RemoteException; + + byte[] getUrlContents() throws RemoteException; + + String getStringContents() throws RemoteException; /** * @param clipboardId {@link #CLIPBOARD} or {@value #SELECTION_CLIPBOARD} */ String getStringContents(int clipboardId) throws RemoteException; - void waitUntilReady() throws RemoteException; + void setImageContents(byte[] imageContents) throws RemoteException; + + byte[] getImageContents() throws RemoteException; + + void setFileListContents(String[] fileList) throws RemoteException; + + String[] getFileListContents() throws RemoteException; + + void setMyTypeContents(byte[] bytes) throws RemoteException; + + byte[] getMyTypeContents() throws RemoteException; - void waitForButtonPress() throws RemoteException; } diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java index 9a6c7877207..0f1e366f29f 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java @@ -10,17 +10,28 @@ *******************************************************************************/ package clipboard; +import java.awt.Image; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; +import java.awt.image.BufferedImage; +import java.awt.image.MultiResolutionImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import javax.imageio.ImageIO; import javax.swing.SwingUtilities; public class ClipboardCommandsImpl extends UnicastRemoteObject implements ClipboardCommands { @@ -60,6 +71,11 @@ public void stop() throws RemoteException { }); } + @Override + public void setContents(String string) throws RemoteException { + setContents(string, CLIPBOARD); + } + @Override public void setContents(String text, int clipboardId) throws RemoteException { invokeAndWait(() -> { @@ -67,10 +83,153 @@ public void setContents(String text, int clipboardId) throws RemoteException { clipboardTest.log("setContents(" + display + ", " + clipboardId + ")"); StringSelection selection = new StringSelection(text); getClipboard(clipboardId).setContents(selection, null); + }); + } + + @Override + public void setRtfContents(String text) throws RemoteException { + invokeAndWait(() -> { + String display = text == null ? "null" : ("\"" + text + "\""); + clipboardTest.log("setRtfContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new RtfSelection(text.getBytes(StandardCharsets.UTF_8)), null); + }); + } + + @Override + public String getRtfContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getRtfContents", RtfSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + byte[] allBytes = stream.readAllBytes(); + return new String(allBytes, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to String", e); + } + } + + @Override + public void setHtmlContents(String text) throws RemoteException { + invokeAndWait(() -> { + String display = text == null ? "null" : ("\"" + text + "\""); + clipboardTest.log("setHtmlContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new HtmlSelection(text.getBytes(StandardCharsets.UTF_8)), null); + }); + } + + @Override + public String getHtmlContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getHtmlContents", HtmlSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + byte[] allBytes = stream.readAllBytes(); + return new String(allBytes, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to String", e); + } + } + + @Override + public void setUrlContents(byte[] bytes) throws RemoteException { + invokeAndWait(() -> { + String display = bytes == null ? "null" : ("bytes"); + clipboardTest.log("setUrlContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new UrlSelection(bytes), null); + }); + } + + @Override + public byte[] getUrlContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getUrlContents", UrlSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + return stream.readAllBytes(); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to byte[]", e); + } + } + @Override + public void setMyTypeContents(byte[] bytes) throws RemoteException { + invokeAndWait(() -> { + String display = bytes == null ? "null" : ("bytes"); + clipboardTest.log("setMyTypeContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new MyTypeSelection(bytes), null); }); } + @Override + public byte[] getMyTypeContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getMyTypeContents", MyTypeSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + return stream.readAllBytes(); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to byte[]", e); + } + } + + @Override + public void setImageContents(byte[] imageContents) throws RemoteException { + invokeAndWait(() -> { + BufferedImage img; + try { + img = ImageIO.read(new ByteArrayInputStream(imageContents)); + if (img == null) { + throw new IOException("ImageIO.read unable to convert bytes to image file"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + getClipboard(CLIPBOARD).setContents(new ImageSelection(img), null); + }); + } + + @Override + public byte[] getImageContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getImageContents", DataFlavor.imageFlavor); + if (!(contents instanceof Image image)) { + return null; + } + if (contents instanceof MultiResolutionImage mri) { + image = mri.getResolutionVariant(image.getWidth(null), image.getHeight(null)); + } + if (!(image instanceof BufferedImage bufferedImage)) { + return null; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufferedImage, "png", baos); + } catch (IOException e) { + throw new RemoteException("Failed to convert image to png byte array", e); + } + return baos.toByteArray(); + } + + @Override + public void setFileListContents(String[] fileList) throws RemoteException { + invokeAndWait(() -> { + getClipboard(CLIPBOARD).setContents(new FileListSelection(Arrays.asList(fileList)), null); + }); + } + + @Override + public String[] getFileListContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getFileListContents", DataFlavor.javaFileListFlavor); + if (!(contents instanceof List fileList)) { + return null; + } + + return fileList.stream().map(f -> (File) f).map(File::toString).toArray(String[]::new); + } + private Clipboard getClipboard(int clipboardId) { Clipboard clipboard; if (clipboardId == SELECTION_CLIPBOARD) { @@ -83,18 +242,27 @@ private Clipboard getClipboard(int clipboardId) { return clipboard; } + @Override + public String getStringContents() throws RemoteException { + return getStringContents(CLIPBOARD); + } + @Override public String getStringContents(int clipboardId) throws RemoteException { - String[] data = new String[] { null }; + return (String) getContents(clipboardId, "getStringContents", DataFlavor.stringFlavor); + } + + private Object getContents(int clipboardId, String methodName, DataFlavor flavor) throws RemoteException { + Object[] data = new Object[] { null }; invokeAndWait(() -> { Clipboard clipboard = getClipboard(clipboardId); try { - data[0] = (String) clipboard.getData(DataFlavor.stringFlavor); - clipboardTest.log("getStringContents(" + clipboardId + ") returned " + data[0]); + data[0] = clipboard.getData(flavor); + clipboardTest.log(methodName + "(" + clipboardId + ") returned " + data[0]); } catch (Exception e) { data[0] = null; DataFlavor[] availableDataFlavors = clipboard.getAvailableDataFlavors(); - clipboardTest.log("getStringContents(" + clipboardId + ") threw " + e.toString() + clipboardTest.log(methodName + "(" + clipboardId + ") threw " + e.toString() + " and returned null. The clipboard had availableDataFlavors = " + Arrays.asList(availableDataFlavors)); } diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java index 41ddce44d03..a15cf655b90 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java @@ -19,13 +19,14 @@ import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; -import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.RMISocketFactory; +import java.util.Arrays; import javax.swing.JButton; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -58,18 +59,26 @@ public Socket createSocket(String host, int port) throws IOException { private JTextArea textArea; private ClipboardCommandsImpl commands; - public ClipboardTest() throws RemoteException { + public ClipboardTest() throws Exception { super("ClipboardTest"); + registerSelectionTypes(); commands = new ClipboardCommandsImpl(this); rmiRegistry.rebind(ClipboardCommands.ID, commands); + JLabel label = new JLabel(""" + This window is a AWT/Swing window used to test the SWT Clipboard
+ This window will be opened and closed repeatedly while running
+ clipboard JUnit tests. Do not close this window (unless the JUnit
+ tests have completed)
+
+ If the Press Button to Continue Test becomes enabled, press it. + """); textArea = new JTextArea(10, 40); JScrollPane scrollPane = new JScrollPane(textArea); JButton copyButton = new JButton("Copy"); JButton pasteButton = new JButton("Paste"); JButton pressToContinue = new JButton("Press To Continue Test"); - copyButton.addActionListener(e -> { String text = textArea.getSelectedText(); if (text != null) { @@ -89,6 +98,7 @@ public ClipboardTest() throws RemoteException { } }); + pressToContinue.setEnabled(false); pressToContinue.addActionListener(e -> { commands.buttonPressed.countDown(); }); @@ -98,6 +108,7 @@ public ClipboardTest() throws RemoteException { buttonPanel.add(pasteButton); buttonPanel.add(pressToContinue); + add(label, BorderLayout.NORTH); add(scrollPane, BorderLayout.CENTER); add(buttonPanel, BorderLayout.SOUTH); @@ -117,16 +128,18 @@ public void log(String log) { public static void main(String[] args) throws IOException { System.setProperty("java.rmi.server.hostname", "127.0.0.1"); + boolean autoPort = Arrays.asList(args).contains("-autoport"); + // Make sure RMI is localhost only RMISocketFactory.setSocketFactory(new LocalHostOnlySocketFactory()); - int chosenPort = getAvailablePort(); + int chosenPort = autoPort ? getAvailablePort() : ClipboardCommands.DEFAULT_PORT; rmiRegistry = LocateRegistry.createRegistry(chosenPort); System.out.println(ClipboardCommands.PORT_MESSAGE + chosenPort); SwingUtilities.invokeLater(() -> { try { new ClipboardTest(); - } catch (RemoteException e) { + } catch (Exception e) { System.err.println("Failed to start ClipboardTest"); e.printStackTrace(); System.exit(1); @@ -143,4 +156,11 @@ private static int getAvailablePort() throws IOException { return ss.getLocalPort(); } } + + static void registerSelectionTypes() throws Exception { + RtfSelection.register(); + HtmlSelection.register(); + UrlSelection.register(); + MyTypeSelection.register(); + } } diff --git a/tests/org.eclipse.swt.tests/data/clipboard/FileListSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/FileListSelection.java new file mode 100644 index 00000000000..b602f9d60ca --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/FileListSelection.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.util.List; + +final class FileListSelection implements Transferable { + private final List files; + + FileListSelection(List files) { + this.files = files; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { DataFlavor.javaFileListFlavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return DataFlavor.javaFileListFlavor.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) + throw new UnsupportedFlavorException(flavor); + return files; + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/HtmlSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/HtmlSelection.java new file mode 100644 index 00000000000..0be9b955050 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/HtmlSelection.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; +import java.util.List; + +class HtmlSelection implements Transferable { + + static DataFlavor flavor; + + static void register() throws ClassNotFoundException { + flavor = new DataFlavor("text/html;class=java.io.InputStream"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + + String os = System.getProperty("os.name", "").toLowerCase(); + if (os.contains("win")) { + String nativeFmt = "HTML Format"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else if (os.contains("mac")) { + String nativeFmt = "public.html"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else { + // X11/Wayland + for (String nativeFmt : List.of("text/html")) { + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } + } + } + + private final byte[] html; + + HtmlSelection(byte[] html) { + this.html = html; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor f) { + return f.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor f) throws UnsupportedFlavorException { + if (f.equals(flavor)) + return new ByteArrayInputStream(html); + throw new UnsupportedFlavorException(f); + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ImageSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/ImageSelection.java new file mode 100644 index 00000000000..ba7610f1717 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/ImageSelection.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package clipboard; + +import java.awt.Image; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; + +final class ImageSelection implements Transferable { + private final Image image; + + ImageSelection(Image image) { + this.image = image; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { DataFlavor.imageFlavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return DataFlavor.imageFlavor.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) + throw new UnsupportedFlavorException(flavor); + return image; + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/MyTypeSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/MyTypeSelection.java new file mode 100644 index 00000000000..69c8ecc0b2e --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/MyTypeSelection.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; + +public final class MyTypeSelection implements Transferable { + + static DataFlavor flavor; + + static void register() throws ClassNotFoundException { + flavor = new DataFlavor("application/x-my_type_name;class=java.io.InputStream", "my_type_name"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + map.addUnencodedNativeForFlavor(flavor, "my_type_name"); + map.addFlavorForUnencodedNative("my_type_name", flavor); + } + + private final byte[] bytes; + + MyTypeSelection(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) + throw new UnsupportedFlavorException(flavor); + return new ByteArrayInputStream(bytes); + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/RtfSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/RtfSelection.java new file mode 100644 index 00000000000..7272b226602 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/RtfSelection.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; +import java.util.List; + +class RtfSelection implements Transferable { + static DataFlavor flavor; + + static void register() throws Exception { + flavor = new DataFlavor("text/rtf;class=java.io.InputStream"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + + String os = System.getProperty("os.name", "").toLowerCase(); + if (os.contains("win")) { + String nativeFmt = "Rich Text Format"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else if (os.contains("mac")) { + String nativeFmt = "public.rtf"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else { + // X11/Wayland + for (String nativeFmt : List.of("text/rtf", "application/rtf")) { + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } + } + } + + private final byte[] rtf; + + RtfSelection(byte[] rtf) { + this.rtf = rtf; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor f) { + return f.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor f) throws UnsupportedFlavorException { + if (f.equals(flavor)) + return new ByteArrayInputStream(rtf); + throw new UnsupportedFlavorException(f); + } + +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/UrlSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/UrlSelection.java new file mode 100644 index 00000000000..ccb170cda96 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/UrlSelection.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; + +class UrlSelection implements Transferable { + + static DataFlavor flavor; + + static void register() throws ClassNotFoundException { + flavor = new DataFlavor("application/x-uniform-resourcelocatorw;class=java.io.InputStream"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + + String os = System.getProperty("os.name", "").toLowerCase(); + if (os.contains("win")) { + String nativeFmt = "UniformResourceLocatorW"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else if (os.contains("mac")) { + String nativeFmt = "public.url"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else { + // X11/Wayland + String nativeFmt = "text/x-moz-url"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + + } + } + + private final byte[] url; + + UrlSelection(byte[] url) { + this.url = url; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor f) { + return f.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor f) throws UnsupportedFlavorException { + if (f.equals(flavor)) + return new ByteArrayInputStream(url); + throw new UnsupportedFlavorException(f); + } + +} \ No newline at end of file