Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,31 @@

public class HopWebEntryPoint extends AbstractEntryPoint {

/**
* Shortcuts that must remain active (sent to the server) but must not be cancelled in the
* browser, so native text editing still works (e.g. Ctrl+C/V/X in input fields).
*/
private static final Set<String> NATIVE_TEXT_EDITING_SHORTCUTS =
Set.of("CTRL+C", "CTRL+V", "CTRL+X");

/**
* Bare navigation keys used for caret movement and selection in text fields (and tree widgets).
* They may stay in {@code ACTIVE_KEYS} so canvas pan shortcuts still reach the server when focus
* is on the graph, but must not be in {@code CANCEL_KEYS} or the browser never moves the caret
* (see issue #7833). Modifier combinations (CTRL+ARROW_*, ALT+ARROW_*, …) are separate RAP keys
* and remain cancelled when registered as application shortcuts.
*/
private static final Set<String> NATIVE_TEXT_NAVIGATION_KEYS =
Set.of(
"ARROW_UP",
"ARROW_DOWN",
"ARROW_LEFT",
"ARROW_RIGHT",
"HOME",
"END",
"PAGE_UP",
"PAGE_DOWN");

/** Audit group/type/name for Hop Web theme preference (per-user in audit folder). */
public static final String AUDIT_GROUP_HOP_WEB = "hop-web";

Expand Down Expand Up @@ -346,6 +368,7 @@ private String[] buildKeyboardShortcuts() {
static String[] buildCancelledKeyboardShortcuts(String[] activeShortcuts) {
return Arrays.stream(activeShortcuts)
.filter(shortcut -> !NATIVE_TEXT_EDITING_SHORTCUTS.contains(shortcut))
.filter(shortcut -> !NATIVE_TEXT_NAVIGATION_KEYS.contains(shortcut))
.distinct()
.toArray(String[]::new);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@ void preservesNativeTextEditingShortcutsAsActiveOnly() {
assertArrayEquals(new String[] {"CTRL+S"}, cancelledShortcuts);
}

@Test
void preservesBareNavigationKeysAsActiveOnly() {
// Canvas pan registers bare arrows/HOME as ACTIVE_KEYS; CANCEL would block caret movement in
// text fields (issue #7833). Modifier combos stay cancellable for app shortcuts.
String[] activeShortcuts = {
"ARROW_LEFT",
"ARROW_RIGHT",
"ARROW_UP",
"ARROW_DOWN",
"HOME",
"END",
"PAGE_UP",
"PAGE_DOWN",
"CTRL+ARROW_LEFT",
"CTRL+S"
};

String[] cancelledShortcuts = HopWebEntryPoint.buildCancelledKeyboardShortcuts(activeShortcuts);

assertArrayEquals(new String[] {"CTRL+ARROW_LEFT", "CTRL+S"}, cancelledShortcuts);
}

@Test
void removesDuplicateCancelledShortcuts() {
String[] cancelledShortcuts =
Expand Down
67 changes: 51 additions & 16 deletions ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,13 @@ public void keyPressed(KeyEvent event) {
return;
}

// Ignore shortcuts inside Text, Combo, StyledText, or CCombo widgets (including terminal).
// Do not steal keys needed for native editing / caret movement inside text-like widgets.
// StyledText is not available in RAP, so we check via reflection to avoid NoClassDefFoundError.
if (event.widget instanceof Text
|| event.widget instanceof Combo
|| event.widget instanceof CCombo
|| isStyledText(event.widget)) {
// Ignore Copy/Cut/Paste/Select all - check both keyCode and character
if ((event.stateMask & (SWT.CONTROL + SWT.COMMAND)) != 0) {
char key = Character.toLowerCase((char) event.keyCode);
if (key == 'a' || key == 'c' || key == 'v' || key == 'x') {
return;
}
}
// Ignore DEL and Backspace
if (event.keyCode == SWT.DEL || event.character == SWT.BS) {
return;
}
// Bare ARROW_*/HOME/END would otherwise match canvas pan shortcuts (DragViewZoomBase) and break
// caret navigation — especially in Hop Web where RAP CANCEL_KEYS can also block the browser
// (see issue #7833). App shortcuts with CTRL/CMD/ALT (e.g. Ctrl+S, Ctrl+Arrow) still run.
if (isTextLikeWidget(event.widget) && isNativeTextEditingKey(event)) {
return;
}

List<Object> orderedParents = getParentObjectsInContextOrder(event.widget);
Expand Down Expand Up @@ -279,6 +269,51 @@ private boolean isWidgetInControlHierarchy(Object widget, Control control) {
return false;
}

/**
* Returns true if the widget is a Text, Combo, CCombo, or StyledText. StyledText is resolved via
* reflection so it is not referenced when it is not on the classpath (e.g. in RAP/Hop Web).
*/
private static boolean isTextLikeWidget(Widget widget) {
if (widget == null) {
return false;
}
return widget instanceof Text
|| widget instanceof Combo
|| widget instanceof CCombo
|| isStyledText(widget);
}

/**
* Keys that text-like widgets must handle themselves: copy/cut/paste/select-all,
* delete/backspace, and caret / selection navigation (arrows, home/end, page up/down) without
* CTRL/CMD/ALT. Shift alone is allowed so Shift+Arrow selection stays in the widget.
*/
private static boolean isNativeTextEditingKey(KeyEvent event) {
if ((event.stateMask & (SWT.CONTROL | SWT.COMMAND)) != 0) {
char key = Character.toLowerCase((char) event.keyCode);
if (key == 'a' || key == 'c' || key == 'v' || key == 'x') {
return true;
}
}
if (event.keyCode == SWT.DEL || event.character == SWT.BS) {
return true;
}
// Caret movement / selection: leave for the widget unless an app modifier is held
boolean hasAppModifier = (event.stateMask & (SWT.CONTROL | SWT.COMMAND | SWT.ALT)) != 0;
if (!hasAppModifier) {
int keyCode = event.keyCode & SWT.KEY_MASK;
return keyCode == SWT.ARROW_LEFT
|| keyCode == SWT.ARROW_RIGHT
|| keyCode == SWT.ARROW_UP
|| keyCode == SWT.ARROW_DOWN
|| keyCode == SWT.HOME
|| keyCode == SWT.END
|| keyCode == SWT.PAGE_UP
|| keyCode == SWT.PAGE_DOWN;
}
return false;
}

/**
* Returns true if the widget is a StyledText. Uses reflection so that StyledText is not
* referenced when it is not on the classpath (e.g. in RAP/Hop Web).
Expand Down
Loading