This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 9abda5d915 [Bug]: Ctrl+C, Ctrl+V and Ctrl+X fail for graph elements in
Hop Web (#7681)
9abda5d915 is described below
commit 9abda5d915ea713bb9af4f42a08525074aac64a1
Author: Michael <[email protected]>
AuthorDate: Mon Aug 3 01:59:03 2026 -0500
[Bug]: Ctrl+C, Ctrl+V and Ctrl+X fail for graph elements in Hop Web (#7681)
* Fix Hop Web clipboard operations
* Delete PR_HOP_WEB_CLIPBOARD.md
---------
Co-authored-by: Michael <michaaels>
---
.../hop/ui/hopgui/ContentEditorFacadeImpl.java | 24 +++++++++
.../org/apache/hop/ui/hopgui/HopWebEntryPoint.java | 19 +++++--
.../org/apache/hop/ui/hopgui/monaco-editor.js | 19 ++++++-
.../apache/hop/ui/hopgui/HopWebEntryPointTest.java | 58 ++++++++++++++++++++++
.../org/apache/hop/ui/core/gui/GuiResource.java | 19 +++++++
.../org/apache/hop/ui/core/gui/WebClipboard.java | 35 +++++++++++++
.../main/java/org/apache/hop/ui/hopgui/HopGui.java | 46 ++++++-----------
.../hop/ui/hopgui/TextEditingControlUtil.java | 43 ++++++++++++++++
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 6 ++-
.../delegates/HopGuiPipelineClipboardDelegate.java | 11 ++--
.../hopgui/file/workflow/HopGuiWorkflowGraph.java | 6 ++-
.../delegates/HopGuiWorkflowClipboardDelegate.java | 11 ++--
.../apache/hop/ui/core/gui/WebClipboardTest.java | 40 +++++++++++++++
.../hop/ui/hopgui/TextEditingControlUtilTest.java | 44 ++++++++++++++++
14 files changed, 332 insertions(+), 49 deletions(-)
diff --git
a/rap/src/main/java/org/apache/hop/ui/hopgui/ContentEditorFacadeImpl.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/ContentEditorFacadeImpl.java
index dd6aac7a6f..6d02102e1f 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/ContentEditorFacadeImpl.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/ContentEditorFacadeImpl.java
@@ -80,9 +80,11 @@ public class ContentEditorFacadeImpl extends
ContentEditorFacade {
remoteObject.setHandler(widget.getOperationHandler());
remoteObject.listen("contentChanged", true);
+ remoteObject.listen("focusChanged", true);
host.addListener(
SWT.Dispose,
event -> {
+ widget.clearFocus();
try {
remoteObject.destroy();
} catch (Exception ignored) {
@@ -142,6 +144,7 @@ public class ContentEditorFacadeImpl extends
ContentEditorFacade {
private final Composite root;
private final RemoteObject remoteObject;
+ private final Display display;
private volatile String cachedContent = "";
private final java.util.List<ModifyListener> modifyListeners = new
CopyOnWriteArrayList<>();
private boolean suppressModify;
@@ -152,11 +155,16 @@ public class ContentEditorFacadeImpl extends
ContentEditorFacade {
Composite root, Composite host, RemoteObject remoteObject, String
languageId) {
this.root = root;
this.remoteObject = remoteObject;
+ this.display = host.getDisplay();
this.languageId = languageId != null ? languageId : "";
this.operationHandler =
new AbstractOperationHandler() {
@Override
public void handleNotify(String event, JsonObject properties) {
+ if ("focusChanged".equals(event) && properties.get("focused") !=
null) {
+ setFocus(properties.get("focused").asBoolean());
+ return;
+ }
if (!"contentChanged".equals(event) || properties.get("content")
== null) {
return;
}
@@ -189,6 +197,22 @@ public class ContentEditorFacadeImpl extends
ContentEditorFacade {
};
}
+ private void setFocus(boolean focused) {
+ if (display == null || display.isDisposed()) {
+ return;
+ }
+ Object focusedEditor = display.getData(HopGui.TEXT_EDITOR_FOCUS_DATA);
+ if (focused) {
+ display.setData(HopGui.TEXT_EDITOR_FOCUS_DATA, this);
+ } else if (focusedEditor == this) {
+ display.setData(HopGui.TEXT_EDITOR_FOCUS_DATA, null);
+ }
+ }
+
+ void clearFocus() {
+ setFocus(false);
+ }
+
AbstractOperationHandler getOperationHandler() {
return operationHandler;
}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
index 2119120da0..8177382d19 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
@@ -18,6 +18,7 @@
package org.apache.hop.ui.hopgui;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -50,6 +51,9 @@ import org.eclipse.swt.widgets.Display;
public class HopWebEntryPoint extends AbstractEntryPoint {
+ private static final Set<String> NATIVE_TEXT_EDITING_SHORTCUTS =
+ Set.of("CTRL+C", "CTRL+V", "CTRL+X");
+
/** Audit group/type/name for Hop Web theme preference (per-user in audit
folder). */
public static final String AUDIT_GROUP_HOP_WEB = "hop-web";
@@ -129,9 +133,9 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
// CANCEL_KEYS prevents the browser from handling these shortcuts
// Note: CTRL automatically maps to Command key on Mac
Display display = parent.getDisplay();
- String[] allShortcuts = buildKeyboardShortcuts();
- display.setData(RWT.ACTIVE_KEYS, allShortcuts);
- display.setData(RWT.CANCEL_KEYS, allShortcuts);
+ String[] activeShortcuts = buildKeyboardShortcuts();
+ display.setData(RWT.ACTIVE_KEYS, activeShortcuts);
+ display.setData(RWT.CANCEL_KEYS,
buildCancelledKeyboardShortcuts(activeShortcuts));
// Transferring Widget Data for client-side canvas drawing instructions
WidgetUtil.registerDataKeys("props");
@@ -339,6 +343,13 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
return shortcuts.toArray(new String[0]);
}
+ static String[] buildCancelledKeyboardShortcuts(String[] activeShortcuts) {
+ return Arrays.stream(activeShortcuts)
+ .filter(shortcut -> !NATIVE_TEXT_EDITING_SHORTCUTS.contains(shortcut))
+ .distinct()
+ .toArray(String[]::new);
+ }
+
/**
* Convert a KeyboardShortcut to RAP format for ACTIVE_KEYS / CANCEL_KEYS.
RAP only supports CTRL,
* ALT, SHIFT (not META), so we use CTRL+ for all command/control shortcuts;
on Mac the browser
@@ -347,7 +358,7 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
* @param shortcut The keyboard shortcut to convert
* @return RAP format string (e.g., "CTRL+C", "ALT+SHIFT+F1") or null if
invalid
*/
- private String convertToRapFormat(KeyboardShortcut shortcut) {
+ String convertToRapFormat(KeyboardShortcut shortcut) {
if (shortcut.getKeyCode() == 0) {
return null;
}
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
b/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
index 9c463327f2..3393a71772 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/monaco-editor.js
@@ -75,6 +75,7 @@
return new hop.MonacoEditor(properties);
},
destructor: function(widget) {
+ widget._notifyFocus(false);
widget._destroyed = true;
if (widget._retryId) { clearInterval(widget._retryId); widget._retryId =
null; }
if (widget._debounceId) { clearTimeout(widget._debounceId);
widget._debounceId = null; }
@@ -87,7 +88,7 @@
}
},
properties: [ "content", "language", "readOnly", "theme" ],
- events: [ "contentChanged" ]
+ events: [ "contentChanged", "focusChanged" ]
});
rwt.define("hop");
@@ -162,6 +163,17 @@
}
},
+ _notifyFocus: function(focused) {
+ try {
+ var remote = rap.getRemoteObject(this);
+ if (remote) {
+ remote.notify("focusChanged", { focused: focused });
+ }
+ } catch (e) {
+ console.warn("MonacoEditor: failed to notify focus", e);
+ }
+ },
+
_scheduleNotify: function() {
var self = this;
if (self._debounceId) clearTimeout(self._debounceId);
@@ -229,7 +241,12 @@
self._scheduleNotify();
});
+ self._editor.onDidFocusEditorWidget(function() {
+ self._notifyFocus(true);
+ });
+
self._editor.onDidBlurEditorWidget(function() {
+ self._notifyFocus(false);
if (self._debounceId) {
clearTimeout(self._debounceId);
self._debounceId = null;
diff --git
a/rap/src/test/java/org/apache/hop/ui/hopgui/HopWebEntryPointTest.java
b/rap/src/test/java/org/apache/hop/ui/hopgui/HopWebEntryPointTest.java
new file mode 100644
index 0000000000..decef4fb17
--- /dev/null
+++ b/rap/src/test/java/org/apache/hop/ui/hopgui/HopWebEntryPointTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import org.apache.hop.core.gui.plugin.key.KeyboardShortcut;
+import org.junit.jupiter.api.Test;
+
+class HopWebEntryPointTest {
+
+ @Test
+ void preservesNativeTextEditingShortcutsAsActiveOnly() {
+ String[] activeShortcuts = {"CTRL+C", "CTRL+V", "CTRL+X", "CTRL+S"};
+
+ String[] cancelledShortcuts =
HopWebEntryPoint.buildCancelledKeyboardShortcuts(activeShortcuts);
+
+ assertArrayEquals(new String[] {"CTRL+C", "CTRL+V", "CTRL+X", "CTRL+S"},
activeShortcuts);
+ assertArrayEquals(new String[] {"CTRL+S"}, cancelledShortcuts);
+ }
+
+ @Test
+ void removesDuplicateCancelledShortcuts() {
+ String[] cancelledShortcuts =
+ HopWebEntryPoint.buildCancelledKeyboardShortcuts(
+ new String[] {"CTRL+S", "CTRL+C", "CTRL+S"});
+
+ assertEquals(cancelledShortcuts.length,
Arrays.stream(cancelledShortcuts).distinct().count());
+ }
+
+ @Test
+ void mapsMacCommandShortcutToRapControlShortcut() {
+ KeyboardShortcut shortcut = mock(KeyboardShortcut.class);
+ when(shortcut.getKeyCode()).thenReturn((int) 'c');
+ when(shortcut.isCommand()).thenReturn(true);
+
+ assertEquals("CTRL+C", new
HopWebEntryPoint().convertToRapFormat(shortcut));
+ }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
index 919134384a..3e155ca999 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
@@ -39,8 +39,11 @@ import org.apache.hop.core.util.Utils;
import org.apache.hop.ui.core.ConstUi;
import org.apache.hop.ui.core.PropsUi;
import org.apache.hop.ui.core.widget.OsHelper;
+import org.apache.hop.ui.hopgui.HopWebUrlHelper;
+import org.apache.hop.ui.hopgui.IHopWebUrlUpdater;
import org.apache.hop.ui.hopgui.ISingletonProvider;
import org.apache.hop.ui.hopgui.ImplementationLoader;
+import org.apache.hop.ui.util.EnvironmentUtils;
import org.apache.hop.ui.util.SwtSvgImageUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
@@ -276,6 +279,8 @@ public class GuiResource {
*/
private Clipboard clipboard;
+ private final WebClipboard webClipboard = new WebClipboard();
+
protected GuiResource() {
this(Display.getCurrent());
}
@@ -1162,12 +1167,26 @@ public class GuiResource {
return;
}
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ IHopWebUrlUpdater urlUpdater = HopWebUrlHelper.getUrlUpdater();
+ try {
+ webClipboard.write(cliptext, urlUpdater == null ? null :
urlUpdater::copyToClipboard);
+ } catch (Exception e) {
+ log.logDebug("Unable to copy text to the browser clipboard", e);
+ }
+ return;
+ }
+
getNewClipboard();
TextTransfer tran = TextTransfer.getInstance();
clipboard.setContents(new String[] {cliptext}, new Transfer[] {tran});
}
public String fromClipboard() {
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ return webClipboard.read();
+ }
+
getNewClipboard();
TextTransfer tran = TextTransfer.getInstance();
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/WebClipboard.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/WebClipboard.java
new file mode 100644
index 0000000000..a7c4cb49f0
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/WebClipboard.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.ui.core.gui;
+
+import java.util.function.Consumer;
+
+final class WebClipboard {
+
+ private String text;
+
+ String read() {
+ return text;
+ }
+
+ void write(String text, Consumer<String> browserWriter) {
+ this.text = text;
+ if (browserWriter != null) {
+ browserWriter.accept(text);
+ }
+ }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
index db68c595f8..c58f872451 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
@@ -21,7 +21,6 @@ import static org.apache.hop.core.Const.getDocUrl;
import java.io.FileOutputStream;
import java.io.PrintStream;
-import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -164,6 +163,8 @@ public class HopGui
implements IActionContextHandlersProvider, ISearchableProvider,
IHasHopMetadataProvider {
private static final Class<?> PKG = HopGui.class;
+ public static final String TEXT_EDITOR_FOCUS_DATA = HopGui.class.getName() +
".textEditorFocus";
+
// The main Menu IDs
public static final String ID_MAIN_MENU = "HopGui-Menu";
public static final String ID_MAIN_MENU_FILE = "10000-menu-file";
@@ -1318,11 +1319,9 @@ public class HopGui
@GuiKeyboardShortcut(control = true, key = 'c')
@GuiOsxKeyboardShortcut(command = true, key = 'c')
public void menuEditCopySelected() {
- Control focusControl = display.getFocusControl();
- if (isStyledTextControl(focusControl)) {
- if (getStyledTextSelectionCount(focusControl) > 0) {
- return;
- }
+ if (TextEditingControlUtil.isTextEditingControl(display.getFocusControl())
+ || isWebTextEditorFocused()) {
+ return;
}
// Otherwise, delegate to the active file type handler (pipeline/workflow)
@@ -1338,9 +1337,8 @@ public class HopGui
@GuiKeyboardShortcut(control = true, key = 'v')
@GuiOsxKeyboardShortcut(command = true, key = 'v')
public void menuEditPaste() {
- Control focusControl = display.getFocusControl();
- if (isStyledTextControl(focusControl)) {
- // Terminal handles paste internally
+ if (TextEditingControlUtil.isTextEditingControl(display.getFocusControl())
+ || isWebTextEditorFocused()) {
return;
}
@@ -1357,32 +1355,16 @@ public class HopGui
@GuiKeyboardShortcut(control = true, key = 'x')
@GuiOsxKeyboardShortcut(command = true, key = 'x')
public void menuEditCutSelected() {
- getActiveFileTypeHandler().cutSelectedToClipboard();
- }
+ if (TextEditingControlUtil.isTextEditingControl(display.getFocusControl())
+ || isWebTextEditorFocused()) {
+ return;
+ }
- /**
- * Returns true if the control is a StyledText. Uses class name so RAP (Hop
Web) does not load
- * org.eclipse.swt.custom.StyledText, which is not available in RAP.
- */
- private static boolean isStyledTextControl(Control control) {
- return control != null
- &&
"org.eclipse.swt.custom.StyledText".equals(control.getClass().getName());
+ getActiveFileTypeHandler().cutSelectedToClipboard();
}
- /**
- * Returns getSelectionCount() for a StyledText control, or 0. Uses
reflection so StyledText is
- * not loaded on RAP.
- */
- private static int getStyledTextSelectionCount(Control control) {
- if (!isStyledTextControl(control)) {
- return 0;
- }
- try {
- Method m = control.getClass().getMethod("getSelectionCount");
- return ((Number) m.invoke(control)).intValue();
- } catch (Exception e) {
- return 0;
- }
+ private boolean isWebTextEditorFocused() {
+ return display.getData(TEXT_EDITOR_FOCUS_DATA) != null;
}
@GuiMenuElement(
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/TextEditingControlUtil.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/TextEditingControlUtil.java
new file mode 100644
index 0000000000..580eab8466
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/TextEditingControlUtil.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+
+final class TextEditingControlUtil {
+
+ private static final String STYLED_TEXT_CLASS =
"org.eclipse.swt.custom.StyledText";
+
+ private TextEditingControlUtil() {}
+
+ /** Uses the class name for StyledText so RAP does not load the desktop-only
StyledText class. */
+ static boolean isTextEditingControl(Control control) {
+ if (control instanceof Text) {
+ return true;
+ }
+ for (Class<?> type = control == null ? null : control.getClass();
+ type != null;
+ type = type.getSuperclass()) {
+ if (STYLED_TEXT_CLASS.equals(type.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
index bae08e57f9..bd8060090f 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
@@ -6095,8 +6095,10 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
@GuiOsxKeyboardShortcut(command = true, key = 'x')
@Override
public void cutSelectedToClipboard() {
- pipelineClipboardDelegate.copySelected(
- pipelineMeta, pipelineMeta.getSelectedTransforms(),
pipelineMeta.getSelectedNotes());
+ if (!pipelineClipboardDelegate.copySelected(
+ pipelineMeta, pipelineMeta.getSelectedTransforms(),
pipelineMeta.getSelectedNotes())) {
+ return;
+ }
pipelineTransformDelegate.delTransforms(pipelineMeta,
pipelineMeta.getSelectedTransforms());
notePadDelegate.deleteNotes(pipelineMeta, pipelineMeta.getSelectedNotes());
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineClipboardDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineClipboardDelegate.java
index 062c8f5c8f..8cff22ab73 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineClipboardDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineClipboardDelegate.java
@@ -64,15 +64,17 @@ public class HopGuiPipelineClipboardDelegate {
ILogChannel log = hopGui.getLog();
}
- public void toClipboard(String clipText) {
+ public boolean toClipboard(String clipText) {
try {
GuiResource.getInstance().toClipboard(clipText);
+ return true;
} catch (Exception e) {
new ErrorDialog(
hopGui.getActiveShell(),
BaseMessages.getString(PKG,
"HopGui.Dialog.ExceptionCopyToClipboard.Title"),
BaseMessages.getString(PKG,
"HopGui.Dialog.ExceptionCopyToClipboard.Message"),
e);
+ return false;
}
}
@@ -298,10 +300,10 @@ public class HopGuiPipelineClipboardDelegate {
location.y = location.y + (int) (5 *
PropsUi.getInstance().getZoomFactor());
}
- public void copySelected(
+ public boolean copySelected(
PipelineMeta pipelineMeta, List<TransformMeta> transforms,
List<NotePadMeta> notes) {
if (transforms == null || transforms.isEmpty() && notes.isEmpty()) {
- return;
+ return false;
}
StringBuilder xml = new
StringBuilder(5000).append(XmlHandler.getXmlHeader());
@@ -313,9 +315,10 @@ public class HopGuiPipelineClipboardDelegate {
serializeTransformErrorHandlingToXml(transforms, xml);
xml.append(XmlHandler.closeTag(XML_TAG_PIPELINE_TRANSFORMS)).append(Const.CR);
- toClipboard(xml.toString());
+ return toClipboard(xml.toString());
} catch (Exception ex) {
new ErrorDialog(hopGui.getActiveShell(), "Error", "Error encoding to
XML", ex);
+ return false;
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
index d49fbf1f39..bf15ca081b 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
@@ -2473,8 +2473,10 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
@GuiOsxKeyboardShortcut(command = true, key = 'x')
@Override
public void cutSelectedToClipboard() {
- workflowClipboardDelegate.copySelected(
- workflowMeta, workflowMeta.getSelectedActions(),
workflowMeta.getSelectedNotes());
+ if (!workflowClipboardDelegate.copySelected(
+ workflowMeta, workflowMeta.getSelectedActions(),
workflowMeta.getSelectedNotes())) {
+ return;
+ }
deleteSelected();
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowClipboardDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowClipboardDelegate.java
index a6990d870d..833f1af46c 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowClipboardDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowClipboardDelegate.java
@@ -62,15 +62,17 @@ public class HopGuiWorkflowClipboardDelegate {
this.log = hopGui.getLog();
}
- public void toClipboard(String clipText) {
+ public boolean toClipboard(String clipText) {
try {
GuiResource.getInstance().toClipboard(clipText);
+ return true;
} catch (Throwable e) {
new ErrorDialog(
hopGui.getActiveShell(),
BaseMessages.getString(PKG,
"HopGui.Dialog.ExceptionCopyToClipboard.Title"),
BaseMessages.getString(PKG,
"HopGui.Dialog.ExceptionCopyToClipboard.Message"),
e);
+ return false;
}
}
@@ -300,10 +302,10 @@ public class HopGuiWorkflowClipboardDelegate {
return uniqueName;
}
- public void copySelected(
+ public boolean copySelected(
WorkflowMeta workflowMeta, List<ActionMeta> actions, List<NotePadMeta>
notes) {
if (actions == null || actions.isEmpty() && notes.isEmpty()) {
- return;
+ return false;
}
StringBuilder xml = new
StringBuilder(5000).append(XmlHandler.getXmlHeader());
@@ -341,9 +343,10 @@ public class HopGuiWorkflowClipboardDelegate {
xml.append(XmlHandler.closeTag(XML_TAG_WORKFLOW_ACTIONS)).append(Const.CR);
- toClipboard(xml.toString());
+ return toClipboard(xml.toString());
} catch (Exception ex) {
new ErrorDialog(hopGui.getActiveShell(), "Error", "Error encoding to
XML", ex);
+ return false;
}
}
diff --git a/ui/src/test/java/org/apache/hop/ui/core/gui/WebClipboardTest.java
b/ui/src/test/java/org/apache/hop/ui/core/gui/WebClipboardTest.java
new file mode 100644
index 0000000000..fbe97c204e
--- /dev/null
+++ b/ui/src/test/java/org/apache/hop/ui/core/gui/WebClipboardTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.ui.core.gui;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+
+class WebClipboardTest {
+
+ @Test
+ void storesTextPerInstanceAndForwardsItToTheBrowserWriter() {
+ WebClipboard first = new WebClipboard();
+ WebClipboard second = new WebClipboard();
+ AtomicReference<String> browserText = new AtomicReference<>();
+
+ assertNull(first.read());
+ first.write("<pipeline-transforms/>", browserText::set);
+
+ assertEquals("<pipeline-transforms/>", first.read());
+ assertEquals("<pipeline-transforms/>", browserText.get());
+ assertNull(second.read());
+ }
+}
diff --git
a/ui/src/test/java/org/apache/hop/ui/hopgui/TextEditingControlUtilTest.java
b/ui/src/test/java/org/apache/hop/ui/hopgui/TextEditingControlUtilTest.java
new file mode 100644
index 0000000000..e6a40f6325
--- /dev/null
+++ b/ui/src/test/java/org/apache/hop/ui/hopgui/TextEditingControlUtilTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+import org.junit.jupiter.api.Test;
+
+class TextEditingControlUtilTest {
+
+ @Test
+ void recognizesTextEditingControls() {
+ assertTrue(TextEditingControlUtil.isTextEditingControl(mock(Text.class)));
+
assertTrue(TextEditingControlUtil.isTextEditingControl(mock(StyledText.class)));
+ }
+
+ @Test
+ void rejectsNonTextControls() {
+
assertFalse(TextEditingControlUtil.isTextEditingControl(mock(Canvas.class)));
+
assertFalse(TextEditingControlUtil.isTextEditingControl(mock(Composite.class)));
+ assertFalse(TextEditingControlUtil.isTextEditingControl(null));
+ }
+}