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 6f25de691d fix keyboard shortcuts, fixes #6613 (#6618)
6f25de691d is described below
commit 6f25de691dbd4e0c17429801dc0b7d2f48e60f40
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Thu Feb 19 18:01:51 2026 +0100
fix keyboard shortcuts, fixes #6613 (#6618)
fix header
---
.../apache/hop/core/gui/plugin/GuiRegistry.java | 21 ++-
.../core/gui/plugin/key/GuiKeyboardShortcut.java | 3 +
.../gui/plugin/key/GuiOsxKeyboardShortcut.java | 3 +
.../hop/core/gui/plugin/key/KeyboardShortcut.java | 34 ++++
.../main/java/org/apache/hop/git/GitGuiPlugin.java | 4 -
.../apache/hop/imports/gui/HopImportGuiPlugin.java | 6 +-
.../org/apache/hop/ui/core/gui/BaseGuiWidgets.java | 2 +
.../org/apache/hop/ui/core/gui/GuiMenuWidgets.java | 100 ++++-------
.../apache/hop/ui/core/gui/GuiToolbarWidgets.java | 10 +-
.../hop/ui/core/gui/ShortcutDisplayUtil.java | 166 +++++++++++++++++
.../apache/hop/ui/core/vfs/HopVfsFileDialog.java | 10 +-
.../main/java/org/apache/hop/ui/hopgui/HopGui.java | 57 ++----
.../org/apache/hop/ui/hopgui/HopGuiKeyHandler.java | 143 +++++++++++----
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 45 ++---
.../delegates/HopGuiPipelineGridDelegate.java | 27 +--
.../hopgui/file/workflow/HopGuiWorkflowGraph.java | 31 +---
.../configuration/ConfigurationPerspective.java | 14 +-
.../tabs/ConfigKeyboardShortcutsTab.java | 200 +++------------------
.../execution/ExecutionPerspective.java | 4 +-
.../perspective/explorer/ExplorerPerspective.java | 15 +-
.../perspective/metadata/MetadataPerspective.java | 4 +-
.../perspective/search/HopSearchPerspective.java | 4 +-
.../hopgui/selection/HopGuiSelectionTracker.java | 107 -----------
.../ui/hopgui/terminal/HopGuiTerminalPanel.java | 76 +++++---
.../terminal/messages/messages_en_US.properties | 20 +++
25 files changed, 548 insertions(+), 558 deletions(-)
diff --git a/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java
b/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java
index 5a43478879..de98795e97 100644
--- a/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java
+++ b/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java
@@ -492,8 +492,27 @@ public class GuiRegistry {
shortcuts.add(new KeyboardShortcut(shortcut, parentMethod));
}
+ /**
+ * Returns keyboard shortcuts for the given class in a stable order: by
method name, then by
+ * isOsx() (non-OSX before OSX). Callers (menus, config panel, key handler)
then see a consistent
+ * modifier/key order and display order.
+ */
public List<KeyboardShortcut> getKeyboardShortcuts(String parentClassName) {
- return shortCutsMap.get(parentClassName);
+ List<KeyboardShortcut> list = shortCutsMap.get(parentClassName);
+ if (list == null) {
+ return null;
+ }
+ if (list.isEmpty()) {
+ return list;
+ }
+ List<KeyboardShortcut> sorted = new ArrayList<>(list);
+ sorted.sort(
+ (a, b) -> {
+ int c = a.getParentMethodName().compareTo(b.getParentMethodName());
+ if (c != 0) return c;
+ return Boolean.compare(a.isOsx(), b.isOsx());
+ });
+ return sorted;
}
/**
diff --git
a/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiKeyboardShortcut.java
b/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiKeyboardShortcut.java
index 8b5e61dda7..a1cc09d90d 100644
---
a/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiKeyboardShortcut.java
+++
b/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiKeyboardShortcut.java
@@ -39,4 +39,7 @@ public @interface GuiKeyboardShortcut {
boolean command() default false;
int key() default -1;
+
+ /** If true, shortcut works from anywhere (e.g. perspective activate, toggle
terminal). */
+ boolean global() default false;
}
diff --git
a/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiOsxKeyboardShortcut.java
b/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiOsxKeyboardShortcut.java
index bbc0465e6b..53fcbe2a3f 100644
---
a/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiOsxKeyboardShortcut.java
+++
b/core/src/main/java/org/apache/hop/core/gui/plugin/key/GuiOsxKeyboardShortcut.java
@@ -39,4 +39,7 @@ public @interface GuiOsxKeyboardShortcut {
boolean command() default false;
int key() default -1;
+
+ /** If true, shortcut works from anywhere (e.g. perspective activate, toggle
terminal). */
+ boolean global() default false;
}
diff --git
a/core/src/main/java/org/apache/hop/core/gui/plugin/key/KeyboardShortcut.java
b/core/src/main/java/org/apache/hop/core/gui/plugin/key/KeyboardShortcut.java
index 7e7a699fe3..e876b272dd 100644
---
a/core/src/main/java/org/apache/hop/core/gui/plugin/key/KeyboardShortcut.java
+++
b/core/src/main/java/org/apache/hop/core/gui/plugin/key/KeyboardShortcut.java
@@ -28,6 +28,12 @@ public class KeyboardShortcut {
private boolean command;
private int keyCode;
+ /** If true, shortcut works from anywhere (e.g. activate perspective, toggle
terminal). */
+ private boolean global;
+
+ /** Class that declares the shortcut (where it should be triggered). */
+ private String parentClassName;
+
private String parentMethodName;
public KeyboardShortcut() {
@@ -41,6 +47,8 @@ public class KeyboardShortcut {
this.shift = shortcut.shift();
this.command = shortcut.command();
this.keyCode = shortcut.key();
+ this.global = shortcut.global();
+ this.parentClassName = parentMethod.getDeclaringClass().getName();
this.parentMethodName = parentMethod.getName();
}
@@ -51,6 +59,8 @@ public class KeyboardShortcut {
this.shift = shortcut.shift();
this.command = shortcut.command();
this.keyCode = shortcut.key();
+ this.global = shortcut.global();
+ this.parentClassName = parentMethod.getDeclaringClass().getName();
this.parentMethodName = parentMethod.getName();
}
@@ -305,6 +315,22 @@ public class KeyboardShortcut {
this.keyCode = keyCode;
}
+ /**
+ * Gets parentClassName (class where this shortcut should be triggered).
+ *
+ * @return value of parentClassName
+ */
+ public String getParentClassName() {
+ return parentClassName;
+ }
+
+ /**
+ * @param parentClassName The parentClassName to set
+ */
+ public void setParentClassName(String parentClassName) {
+ this.parentClassName = parentClassName;
+ }
+
/**
* Gets parentMethodName
*
@@ -320,4 +346,12 @@ public class KeyboardShortcut {
public void setParentMethodName(String parentMethodName) {
this.parentMethodName = parentMethodName;
}
+
+ public boolean isGlobal() {
+ return global;
+ }
+
+ public void setGlobal(boolean global) {
+ this.global = global;
+ }
}
diff --git
a/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
b/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
index 1f5434bfac..5540edea47 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
@@ -32,8 +32,6 @@ import org.apache.hop.core.Const;
import org.apache.hop.core.exception.HopFileException;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.gui.plugin.callback.GuiCallback;
-import org.apache.hop.core.gui.plugin.key.GuiKeyboardShortcut;
-import org.apache.hop.core.gui.plugin.key.GuiOsxKeyboardShortcut;
import org.apache.hop.core.gui.plugin.menu.GuiMenuElement;
import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElementType;
@@ -318,8 +316,6 @@ public class GitGuiPlugin
id = TOOLBAR_ITEM_PULL,
toolTip = "i18n::GitGuiPlugin.Toolbar.Pull.Tooltip",
image = "pull.svg")
- @GuiKeyboardShortcut(control = true, key = 'T')
- @GuiOsxKeyboardShortcut(control = true, key = 'T')
public void gitPull() {
try {
if (git.pull()) {
diff --git
a/plugins/misc/import/src/main/java/org/apache/hop/imports/gui/HopImportGuiPlugin.java
b/plugins/misc/import/src/main/java/org/apache/hop/imports/gui/HopImportGuiPlugin.java
index 12784ab21c..3538c44e1a 100644
---
a/plugins/misc/import/src/main/java/org/apache/hop/imports/gui/HopImportGuiPlugin.java
+++
b/plugins/misc/import/src/main/java/org/apache/hop/imports/gui/HopImportGuiPlugin.java
@@ -26,7 +26,7 @@ import org.apache.hop.imports.kettle.KettleImportDialog;
import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.hopgui.HopGui;
-@GuiPlugin
+@GuiPlugin(name = "Import", description = "Import Kettle projects")
public class HopImportGuiPlugin {
public static final String ID_MAIN_MENU_FILE_IMPORT =
"10060-menu-tools-import";
@@ -47,8 +47,8 @@ public class HopImportGuiPlugin {
image = "kettle-logo.svg",
parentId = HopGui.ID_MAIN_MENU_FILE,
separator = true)
- @GuiKeyboardShortcut(control = true, key = 'i')
- @GuiOsxKeyboardShortcut(command = true, key = 'i')
+ @GuiKeyboardShortcut(control = true, key = 'i', global = true)
+ @GuiOsxKeyboardShortcut(command = true, key = 'i', global = true)
public void menuToolsImport() {
HopGui hopGui = HopGui.getInstance();
try {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/BaseGuiWidgets.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/BaseGuiWidgets.java
index ebe7265b4b..d6acd05542 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/BaseGuiWidgets.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/BaseGuiWidgets.java
@@ -27,6 +27,7 @@ import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.logging.LogChannel;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.HopGuiKeyHandler;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Listener;
@@ -98,6 +99,7 @@ public class BaseGuiWidgets {
GuiRegistry.getInstance()
.registerGuiPluginObject(hopGuiId, listenerClassName, instanceId,
guiPluginObject);
}
+ HopGuiKeyHandler.getInstance().addParentObjectToHandle(guiPluginObject);
return guiPluginObject;
} catch (Exception e) {
throw new HopException(
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
index 17cdd43d43..9181fe33d2 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
@@ -25,10 +25,13 @@ import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
import org.apache.hop.core.Const;
+import org.apache.hop.core.gui.plugin.GuiPluginType;
import org.apache.hop.core.gui.plugin.GuiRegistry;
import org.apache.hop.core.gui.plugin.key.KeyboardShortcut;
import org.apache.hop.core.gui.plugin.menu.GuiMenuItem;
import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.plugins.IPlugin;
+import org.apache.hop.core.plugins.PluginRegistry;
import org.apache.hop.ui.core.ConstUi;
import org.apache.hop.ui.hopgui.file.IHopFileType;
import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
@@ -52,6 +55,31 @@ public class GuiMenuWidgets extends BaseGuiWidgets {
this.menuEnabledMap = new HashMap<>();
}
+ /** Pre-create shortcut plugin instances so their shortcuts work before menu
is used. */
+ public void ensureShortcutPluginInstancesRegistered() {
+ GuiRegistry guiRegistry = GuiRegistry.getInstance();
+ PluginRegistry pluginRegistry = PluginRegistry.getInstance();
+ for (String className : guiRegistry.getShortCutsMap().keySet()) {
+ try {
+ IPlugin plugin =
+ pluginRegistry.getPlugins(GuiPluginType.class).stream()
+ .filter(p -> p.getClassMap().values().contains(className))
+ .findFirst()
+ .orElse(null);
+ if (plugin == null) {
+ continue;
+ }
+ findGuiPluginInstance(pluginRegistry.getClassLoader(plugin),
className, instanceId);
+ } catch (Exception e) {
+ LogChannel.UI.logDebug(
+ "Could not pre-register shortcut plugin instance for "
+ + className
+ + ": "
+ + e.getMessage());
+ }
+ }
+ }
+
public void createMenuWidgets(String root, Shell shell, Menu parent) {
// Find the GUI Elements for the given class...
//
@@ -183,7 +211,8 @@ public class GuiMenuWidgets extends BaseGuiWidgets {
guiMenuItem.getListenerClassName(),
guiMenuItem.getListenerMethod(), Const.isOSX());
if (shortcut != null) {
appendShortCut(menuItem, shortcut);
- menuItem.setAccelerator(getAccelerator(shortcut));
+ // Do not set menu accelerators; keyboard shortcuts are handled only by
HopGuiKeyHandler
+ // so the correct context (focus) is used. Menu still shows shortcut
text for discoverability.
shortcutMap.put(guiMenuItem.getId(), shortcut);
}
}
@@ -211,74 +240,7 @@ public class GuiMenuWidgets extends BaseGuiWidgets {
}
public static String getShortcutString(KeyboardShortcut shortcut) {
- String s = shortcut.toString();
- if (StringUtils.isEmpty(s) || s.endsWith("+")) {
- // Unknown characters from the SWT library
- // We'll handle the special cases here.
- //
- int keyCode = shortcut.getKeyCode();
- if (keyCode == SWT.BS) {
- return s + "Backspace";
- }
- if (keyCode == SWT.ESC) {
- return s + "Esc";
- }
- if (keyCode == SWT.DEL) {
- return s + "Delete";
- }
- if (keyCode == SWT.ARROW_LEFT) {
- return s + "Left";
- }
- if (keyCode == SWT.ARROW_RIGHT) {
- return s + "Right";
- }
- if (keyCode == SWT.ARROW_UP) {
- return s + "Up";
- }
- if (keyCode == SWT.ARROW_DOWN) {
- return s + "Down";
- }
- if (keyCode == SWT.HOME) {
- return s + "Home";
- }
- if (keyCode == SWT.F1) {
- return s + "F1";
- }
- if (keyCode == SWT.F2) {
- return s + "F2";
- }
- if (keyCode == SWT.F3) {
- return s + "F3";
- }
- if (keyCode == SWT.F4) {
- return s + "F4";
- }
- if (keyCode == SWT.F5) {
- return s + "F5";
- }
- if (keyCode == SWT.F6) {
- return s + "F6";
- }
- if (keyCode == SWT.F7) {
- return s + "F7";
- }
- if (keyCode == SWT.F8) {
- return s + "F8";
- }
- if (keyCode == SWT.F9) {
- return s + "F9";
- }
- if (keyCode == SWT.F10) {
- return s + "F10";
- }
- if (keyCode == SWT.F11) {
- return s + "F11";
- }
- if (keyCode == SWT.F12) {
- return s + "F12";
- }
- }
- return s;
+ return ShortcutDisplayUtil.getShortcutDisplayString(shortcut);
}
/**
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
index cde9d9150d..a9f7306cb4 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
@@ -414,7 +414,10 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
Const.isOSX());
if (shortcut != null) {
String tip = Const.NVL(guiToolbarItem.getToolTip(), "");
- composite.setToolTipText(tip + " (" + shortcut + ')');
+ String shortcutText =
ShortcutDisplayUtil.getShortcutDisplayString(shortcut);
+ if (!shortcutText.isEmpty()) {
+ composite.setToolTipText(tip + " (" + shortcutText + ')');
+ }
}
}
@@ -621,7 +624,10 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
guiToolbarItem.getListenerMethod(),
Const.isOSX());
if (shortcut != null) {
- toolItem.setToolTipText(toolItem.getToolTipText() + " (" + shortcut +
')');
+ String shortcutText =
ShortcutDisplayUtil.getShortcutDisplayString(shortcut);
+ if (!shortcutText.isEmpty()) {
+ toolItem.setToolTipText(toolItem.getToolTipText() + " (" +
shortcutText + ')');
+ }
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/gui/ShortcutDisplayUtil.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/ShortcutDisplayUtil.java
new file mode 100644
index 0000000000..9fcc70bf0b
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/ShortcutDisplayUtil.java
@@ -0,0 +1,166 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.gui.plugin.key.KeyboardShortcut;
+import org.eclipse.swt.SWT;
+
+/**
+ * Shared utility for displaying keyboard shortcuts with OS-appropriate
symbols (e.g. ⌘⇧⌥ on macOS).
+ * Used by menus, toolbars, and the keyboard shortcuts configuration tab so
display is consistent.
+ */
+public final class ShortcutDisplayUtil {
+
+ private ShortcutDisplayUtil() {}
+
+ /**
+ * Returns the full shortcut string for menus and tooltips (e.g. "⌘⇧Z" on
macOS, "Ctrl+Shift+Z" on
+ * Windows/Linux).
+ */
+ public static String getShortcutDisplayString(KeyboardShortcut shortcut) {
+ if (shortcut == null || shortcut.getKeyCode() == 0) {
+ return "";
+ }
+ List<String> parts = getModifierDisplayStrings(shortcut);
+ String keyText = getKeyDisplayText(shortcut.getKeyCode());
+ if (!keyText.isEmpty()) {
+ parts.add(keyText);
+ }
+ boolean isMacOS = Const.isOSX();
+ return isMacOS ? String.join("", parts) : String.join("+", parts);
+ }
+
+ /**
+ * Returns modifier symbols/labels in display order for menus and key
badges. Uses the same
+ * canonical order everywhere so menu bar and config panel match: Control →
Option → Shift →
+ * Command (⌃ ⌥ ⇧ ⌘ on macOS; Ctrl, Alt, Shift, Cmd on Windows/Linux). Does
not include the main
+ * key.
+ */
+ public static List<String> getModifierDisplayStrings(KeyboardShortcut
shortcut) {
+ List<String> out = new ArrayList<>();
+ boolean isMacOS = Const.isOSX();
+ // Same order on all platforms: Control, Option/Alt, Shift, Command
+ if (shortcut.isControl()) out.add(isMacOS ? "⌃" : "Ctrl");
+ if (shortcut.isAlt()) out.add(isMacOS ? "⌥" : "Alt");
+ if (shortcut.isShift()) out.add(isMacOS ? "⇧" : "Shift");
+ if (shortcut.isCommand()) out.add(isMacOS ? "⌘" : "Cmd");
+ return out;
+ }
+
+ /**
+ * Converts an SWT key code to the display string used in the configuration
panel (e.g. "⌫" for
+ * Delete on macOS, "↑" for arrow up).
+ */
+ public static String getKeyDisplayText(int keyCode) {
+ boolean isMacOS = Const.isOSX();
+
+ if (keyCode == SWT.KEYPAD_ADD) return "+";
+ if (keyCode == SWT.KEYPAD_SUBTRACT) return "-";
+ if (keyCode == SWT.KEYPAD_MULTIPLY) return "*";
+ if (keyCode == SWT.KEYPAD_DIVIDE) return "/";
+ if (keyCode == SWT.KEYPAD_EQUAL) return "=";
+ if (keyCode == SWT.KEYPAD_DECIMAL) return ".";
+ if (keyCode == SWT.KEYPAD_CR) return "↵";
+ if (keyCode >= SWT.KEYPAD_0 && keyCode <= SWT.KEYPAD_9) {
+ return String.valueOf(keyCode - SWT.KEYPAD_0);
+ }
+
+ if (keyCode == 32) return "Space";
+ if (keyCode >= 65 && keyCode <= 90) return String.valueOf((char) keyCode);
+ if (keyCode >= 97 && keyCode <= 122)
+ return String.valueOf(Character.toUpperCase((char) keyCode));
+ if (keyCode == 96) return "`";
+ if (keyCode == 127) return isMacOS ? "⌫" : "Del";
+ if ((keyCode >= 48 && keyCode <= 57) || "+-/*=".indexOf(keyCode) >= 0) {
+ return String.valueOf((char) keyCode);
+ }
+
+ if ((keyCode & (1 << 24)) != 0) {
+ switch (keyCode & (0xFFFF)) {
+ case 1:
+ return "↑";
+ case 2:
+ return "↓";
+ case 3:
+ return "←";
+ case 4:
+ return "→";
+ case 5:
+ return "PgUp";
+ case 6:
+ return "PgDn";
+ case 7:
+ return "Home";
+ case 8:
+ return "End";
+ case 9:
+ return "Ins";
+ case 10:
+ return "F1";
+ case 11:
+ return "F2";
+ case 12:
+ return "F3";
+ case 13:
+ return "F4";
+ case 14:
+ return "F5";
+ case 15:
+ return "F6";
+ case 16:
+ return "F7";
+ case 17:
+ return "F8";
+ case 18:
+ return "F9";
+ case 19:
+ return "F10";
+ case 20:
+ return "F11";
+ case 21:
+ return "F12";
+ case 22:
+ return "F13";
+ case 23:
+ return "F14";
+ case 24:
+ return "F15";
+ case 25:
+ return "F16";
+ case 26:
+ return "F17";
+ case 27:
+ return "F18";
+ case 28:
+ return "F19";
+ case 29:
+ return "F20";
+ default:
+ break;
+ }
+ }
+
+ if (keyCode == SWT.ESC) return isMacOS ? "⎋" : "Esc";
+ if (keyCode == SWT.BS) return isMacOS ? "⌫" : "Backspace";
+
+ return "";
+ }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
b/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
index 3d941ed56e..1e8b13b1b8 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
@@ -64,6 +64,7 @@ import org.apache.hop.ui.core.gui.WindowProperty;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.core.widget.TreeUtil;
import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.HopGuiKeyHandler;
import org.apache.hop.ui.hopgui.ToolbarFacade;
import org.apache.hop.ui.hopgui.file.HopFileTypePluginType;
import org.apache.hop.ui.hopgui.file.HopFileTypeRegistry;
@@ -106,7 +107,7 @@ import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
-@GuiPlugin(description = "Allows you to browse to local or VFS locations")
+@GuiPlugin(name = "File Browser", description = "Allows you to browse to local
or VFS locations")
public class HopVfsFileDialog implements IFileDialog, IDirectoryDialog {
private static final Class<?> PKG = HopVfsFileDialog.class;
@@ -580,6 +581,11 @@ public class HopVfsFileDialog implements IFileDialog,
IDirectoryDialog {
//
wFilename.setFocus();
+ // So Delete etc. work and we're tried before the active perspective
+ HopGuiKeyHandler keyHandler = HopGuiKeyHandler.getInstance();
+ keyHandler.addParentObjectToHandle(this, shell);
+ HopGui.getInstance().replaceKeyboardShortcutListeners(shell, keyHandler);
+
shell.open();
while (!shell.isDisposed()) {
@@ -1146,6 +1152,7 @@ public class HopVfsFileDialog implements IFileDialog,
IDirectoryDialog {
bookmarksToolbarWidgets.dispose();
browserToolbarWidgets.dispose();
+ HopGuiKeyHandler.getInstance().removeParentObjectToHandle(this);
shell.dispose();
}
@@ -1401,7 +1408,6 @@ public class HopVfsFileDialog implements IFileDialog,
IDirectoryDialog {
id = BROWSER_ITEM_ID_DELETE,
toolTip = "i18n::HopVfsFileDialog.DeleteFile.Tooltip.Message",
image = "ui/images/delete.svg")
- // FIXME: Keyboard don't work
@GuiKeyboardShortcut(key = SWT.DEL)
@GuiOsxKeyboardShortcut(key = SWT.DEL)
public void deleteFile() {
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 8ff9a4e887..e87f98b96f 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
@@ -160,7 +160,7 @@ import org.eclipse.swt.widgets.ToolItem;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
-@GuiPlugin(description = "The main hop graphical user interface")
+@GuiPlugin(name = "Hop Gui", description = "The main hop graphical user
interface")
@SuppressWarnings("java:S1104")
@Getter
@Setter
@@ -954,6 +954,7 @@ public class HopGui
mainMenu = new Menu(shell, SWT.BAR);
mainMenuWidgets.createMenuWidgets(ID_MAIN_MENU, shell, mainMenu);
+ mainMenuWidgets.ensureShortcutPluginInstancesRegistered();
if (EnvironmentUtils.getInstance().isWeb()) {
mainMenuWidgets.enableMenuItem(HopGui.ID_MAIN_MENU_FILE_EXIT, false);
@@ -1413,38 +1414,6 @@ public class HopGui
// Nothing is done here.
}
- @GuiMenuElement(
- root = ID_MAIN_MENU,
- id = ID_MAIN_MENU_VIEW_TERMINAL,
- label = "i18n::HopGui.Menu.View.Terminal",
- parentId = ID_MAIN_MENU_VIEW_PARENT_ID)
- @GuiKeyboardShortcut(control = true, key = '`')
- @GuiOsxKeyboardShortcut(control = true, key = '`')
- public void menuViewTerminal() {
- if (EnvironmentUtils.getInstance().isWeb()) {
- return;
- }
- if (terminalPanel != null) {
- terminalPanel.toggleTerminal();
- }
- }
-
- @GuiMenuElement(
- root = ID_MAIN_MENU,
- id = ID_MAIN_MENU_VIEW_NEW_TERMINAL,
- label = "i18n::HopGui.Menu.View.NewTerminal",
- parentId = ID_MAIN_MENU_VIEW_PARENT_ID)
- @GuiKeyboardShortcut(control = true, shift = true, key = '`')
- @GuiOsxKeyboardShortcut(control = true, shift = true, key = '`')
- public void menuViewNewTerminal() {
- if (EnvironmentUtils.getInstance().isWeb()) {
- return;
- }
- if (terminalPanel != null) {
- terminalPanel.createNewTerminal(null, null);
- }
- }
-
// ======================== Run Menu ========================
@GuiMenuElement(
@@ -1705,6 +1674,22 @@ public class HopGui
fdTerminalPanel.right = new FormAttachment(100, 0);
terminalPanel.setLayoutData(fdTerminalPanel);
+ // Register so Tools > Terminal menu items can invoke panel methods
+ String menuInstanceId = mainMenuWidgets.getInstanceId();
+ org.apache.hop.core.gui.plugin.GuiRegistry.getInstance()
+ .registerGuiPluginObject(
+ getId(),
+
org.apache.hop.ui.hopgui.terminal.HopGuiTerminalPanel.class.getName(),
+ menuInstanceId,
+ terminalPanel);
+ terminalPanel.addDisposeListener(
+ e ->
+ org.apache.hop.core.gui.plugin.GuiRegistry.getInstance()
+ .removeGuiPluginObject(
+ getId(),
+
org.apache.hop.ui.hopgui.terminal.HopGuiTerminalPanel.class.getName(),
+ menuInstanceId));
+
mainPerspectivesComposite = terminalPanel.getPerspectiveComposite();
mainPerspectivesComposite.setLayout(new StackLayout());
}
@@ -1842,11 +1827,7 @@ public class HopGui
return getActivePerspective().getActiveFileTypeHandler();
}
- /**
- * Replace the listeners based on the @{@link GuiKeyboardShortcut}
annotations
- *
- * @param parentObject The parent object containing the annotations and
methods
- */
+ /** Register parent and attach key handler to shell + children. */
public void replaceKeyboardShortcutListeners(Object parentObject) {
HopGuiKeyHandler keyHandler = HopGuiKeyHandler.getInstance();
keyHandler.addParentObjectToHandle(parentObject);
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
index e3bfa0fac8..c4580c8447 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
@@ -18,8 +18,12 @@
package org.apache.hop.ui.hopgui;
import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.apache.hop.core.Const;
import org.apache.hop.core.gui.plugin.GuiRegistry;
@@ -41,6 +45,9 @@ public class HopGuiKeyHandler extends KeyAdapter {
public Set<Object> parentObjects;
+ /** Parent -> Control (e.g. shell) so we try that parent when focus is in
its window. */
+ private final Map<Object, Control> parentToControl = new HashMap<>();
+
private KeyboardShortcut lastShortcut;
private long lastShortcutTime;
@@ -59,16 +66,21 @@ public class HopGuiKeyHandler extends KeyAdapter {
parentObjects.add(parentObject);
}
+ /** Register parent with its window control so shortcuts in that window take
precedence. */
+ public void addParentObjectToHandle(Object parentObject, Control control) {
+ parentObjects.add(parentObject);
+ if (control != null) {
+ parentToControl.put(parentObject, control);
+ }
+ }
+
public void removeParentObjectToHandle(Object parentObject) {
parentObjects.remove(parentObject);
+ parentToControl.remove(parentObject);
}
@Override
public void keyPressed(KeyEvent event) {
- // TODO: allow for keyboard shortcut priorities for certain objects.
- //
-
- // If the event has already been handled by another listener (e.g.
terminal), skip it
if (!event.doit) {
return;
}
@@ -91,53 +103,110 @@ public class HopGuiKeyHandler extends KeyAdapter {
}
}
- for (Object parentObject : parentObjects) {
+ List<Object> orderedParents = getParentObjectsInContextOrder(event.widget);
+ for (Object parentObject : orderedParents) {
List<KeyboardShortcut> shortcuts =
GuiRegistry.getInstance().getKeyboardShortcuts(parentObject.getClass().getName());
if (shortcuts != null) {
for (KeyboardShortcut shortcut : shortcuts) {
if (handleKey(parentObject, event, shortcut)) {
event.doit = false;
- return; // This key is handled.
+ return;
}
}
}
}
}
- private boolean handleKey(Object parentObject, KeyEvent event,
KeyboardShortcut shortcut) {
- // If this is a control, only handle the shortcut if the control is
visible.
- // This prevents keyboard shortcuts being applied to a workflow or
pipeline which
- // isn't visible (in another tab, for example).
- //
+ /** Order: parents whose window has focus (closest first), then active
perspectives, then rest. */
+ private List<Object> getParentObjectsInContextOrder(Object focusedWidget) {
+ List<Object> inFocus = new ArrayList<>();
+ List<Object> fallback = new ArrayList<>();
+ for (Object parent : parentObjects) {
+ Control control = parent instanceof Control c ? c :
parentToControl.get(parent);
+ if (control != null && isWidgetInControlHierarchy(focusedWidget,
control)) {
+ inFocus.add(parent);
+ } else {
+ fallback.add(parent);
+ }
+ }
+ inFocus.sort(
+ Comparator.comparingInt(
+ p -> {
+ Control c = p instanceof Control x ? x : parentToControl.get(p);
+ return c != null ? getDepthFromWidgetToControl(focusedWidget, c)
: Integer.MAX_VALUE;
+ }));
+ fallback.sort(
+ (a, b) -> {
+ boolean aActive = isActivePerspective(a);
+ boolean bActive = isActivePerspective(b);
+ if (aActive && !bActive) return -1;
+ if (!aActive && bActive) return 1;
+ return 0;
+ });
+ List<Object> result = new ArrayList<>(inFocus);
+ result.addAll(fallback);
+ return result;
+ }
+
+ private boolean isActivePerspective(Object parent) {
+ if (parent instanceof IHopPerspective perspective) {
+ try {
+ return perspective.isActive();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /** Depth from widget to control (1 = direct parent). */
+ private int getDepthFromWidgetToControl(Object widget, Control control) {
+ if (!(widget instanceof Control)) {
+ return Integer.MAX_VALUE;
+ }
+ int depth = 0;
+ Control current = (Control) widget;
+ while (current != null) {
+ if (current == control) {
+ return depth;
+ }
+ depth++;
+ try {
+ current = current.getParent();
+ } catch (Exception e) {
+ return Integer.MAX_VALUE;
+ }
+ }
+ return Integer.MAX_VALUE;
+ }
+
+ private boolean isParentInContext(
+ Object parentObject, KeyEvent event, KeyboardShortcut shortcut) {
if (parentObject instanceof Control control) {
try {
if (!control.isVisible()) {
- return false;
- }
- // Also skip if the event widget (focused widget) is NOT within this
control's hierarchy
- // This prevents pipeline/workflow shortcuts from firing when focus is
in the terminal
- if (!isWidgetInControlHierarchy(event.widget, control)) {
- return false;
+ return shortcut.isGlobal();
}
+ return shortcut.isGlobal() || isWidgetInControlHierarchy(event.widget,
control);
} catch (SWTException e) {
- // Invalid thread: none of our business, bail out
- //
return false;
}
}
- // If this is attached to a perspective, and it's not active, bail out.
- // Except if it's shortcut to activate perspective
if (parentObject instanceof IHopPerspective perspective) {
try {
- // TODO: It's not the best way to check with the method name, but it
works for now.
- if (!perspective.isActive() &&
!shortcut.getParentMethodName().equals("activate")) {
- return false;
- }
- } catch (Exception ex) {
+ return perspective.isActive() || shortcut.isGlobal();
+ } catch (Exception e) {
return false;
}
}
+ return true;
+ }
+
+ private boolean handleKey(Object parentObject, KeyEvent event,
KeyboardShortcut shortcut) {
+ if (!isParentInContext(parentObject, event, shortcut)) {
+ return false;
+ }
int keyCode = (event.keyCode & SWT.KEY_MASK);
@@ -153,16 +222,25 @@ public class HopGuiKeyHandler extends KeyAdapter {
else if (keyCode == SWT.KEYPAD_MULTIPLY) keyCode = '*';
else if (keyCode == SWT.KEYPAD_DIVIDE) keyCode = '/';
else if (keyCode == SWT.KEYPAD_EQUAL) keyCode = '=';
+ // Backtick: in SWT use event.character ('`' = 96); keyCode may be 0 or
192 (VK_OEM_3) on some
+ // platforms
+ else if (keyCode == 192) keyCode = '`';
- boolean keyMatch = keyCode == shortcut.getKeyCode();
+ int shortcutKey = shortcut.getKeyCode();
+ // Match by keyCode, or by event.character (SWT maps backtick/grave accent
to event.character)
+ boolean keyMatch =
+ keyCode == shortcutKey || (shortcutKey != 0 && event.character ==
shortcutKey);
boolean altMatch = shortcut.isAlt() == alt;
boolean shiftMatch = shortcut.isShift() == shift;
boolean controlMatch = shortcut.isControl() == control;
boolean commandMatch = shortcut.isCommand() == command;
if (matchOS && keyMatch && altMatch && shiftMatch && controlMatch &&
commandMatch) {
- // This is the key: call the method to which the original key shortcut
annotation belongs
- //
+ // Only invoke if this shortcut is linked to this class (context)
+ if (shortcut.getParentClassName() != null
+ &&
!shortcut.getParentClassName().equals(parentObject.getClass().getName())) {
+ return false;
+ }
try {
Class<?> parentClass = parentObject.getClass();
Method method = parentClass.getMethod(shortcut.getParentMethodName());
@@ -179,13 +257,6 @@ public class HopGuiKeyHandler extends KeyAdapter {
return false;
}
- /**
- * Check if a widget is within the hierarchy of a control
- *
- * @param widget The widget to check (typically the focused widget)
- * @param control The parent control to check against
- * @return true if widget is within control's hierarchy, false otherwise
- */
private boolean isWidgetInControlHierarchy(Object widget, Control control) {
if (!(widget instanceof Control)) {
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 76714bd3e0..a72b4ef0c5 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
@@ -179,7 +179,6 @@ import
org.apache.hop.ui.hopgui.file.shared.PipelineRowSamplerHelper;
import org.apache.hop.ui.hopgui.perspective.execution.ExecutionPerspective;
import org.apache.hop.ui.hopgui.perspective.execution.IExecutionViewer;
import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
-import org.apache.hop.ui.hopgui.selection.HopGuiSelectionTracker;
import org.apache.hop.ui.hopgui.shared.CanvasZoomHelper;
import org.apache.hop.ui.hopgui.shared.SwtGc;
import org.apache.hop.ui.pipeline.dialog.PipelineDialog;
@@ -771,9 +770,7 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
selectedTransforms = pipelineMeta.getSelectedTransforms();
selectedTransform = currentTransform;
- // Track that a transform was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
for (ITransformSelectionListener listener :
currentTransformListeners) {
listener.onUpdateSelection(currentTransform);
@@ -843,8 +840,7 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
selectedNotes = pipelineMeta.getSelectedNotes();
selectedNote = currentNotePad;
// Track that a note was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
Point loc = currentNotePad.getLocation();
previousNoteLocations = pipelineMeta.getSelectedNoteLocations();
@@ -1022,8 +1018,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
// Track that transforms were selected via region selection
if (!pipelineMeta.getSelectedTransforms().isEmpty()
|| !pipelineMeta.getSelectedNotes().isEmpty()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
}
updateGui();
return;
@@ -1079,8 +1074,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedTransform.flipSelected();
// Track that a transform selection changed (if it's now selected)
if (selectedTransform.isSelected()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
}
} else {
singleClick = true;
@@ -1094,8 +1088,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedTransform.setSelected(true);
}
// Track that a transform was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
}
} else {
// Find out which Transforms & Notes are selected
@@ -1103,8 +1096,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedNotes = pipelineMeta.getSelectedNotes();
// Track that transforms were selected
if (!selectedTransforms.isEmpty() || !selectedNotes.isEmpty()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
}
// We moved around some items: store undo info...
@@ -1167,8 +1159,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedNote.flipSelected();
// Track that a note selection changed (if it's now selected)
if (selectedNote.isSelected()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
}
} else {
// single click on a note: ask what needs to happen...
@@ -1184,8 +1175,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedNote.setSelected(true);
}
// Track that a note was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
}
} else {
// Find out which Transforms & Notes are selected
@@ -1193,8 +1183,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedNotes = pipelineMeta.getSelectedNotes();
// Track that notes were selected
if (!selectedTransforms.isEmpty() || !selectedNotes.isEmpty()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
}
// We moved around some items: store undo info...
@@ -1620,8 +1609,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedTransforms.add(selectedTransform);
previousTransformLocations = new Point[]
{selectedTransform.getLocation()};
// Track that a transform was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
doRedraw = true;
} else if (selectedNote != null && !selectedNote.isSelected()) {
pipelineMeta.unselectAll();
@@ -1630,8 +1618,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
selectedNotes.add(selectedNote);
previousNoteLocations = new Point[] {selectedNote.getLocation()};
// Track that a note was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
+ pipelineGridDelegate.onPipelineSelectionChanged();
doRedraw = true;
} else if (selectionRegion != null && startHopTransform == null) {
// Did we select a region...?
@@ -5440,19 +5427,13 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
@GuiOsxKeyboardShortcut(key = SWT.DEL)
@Override
public void deleteSelected() {
- // Only handle delete if a pipeline graph item was the last selected item
- // OR if there are actually selected transforms/notes in the pipeline
- HopGuiSelectionTracker selectionTracker =
HopGuiSelectionTracker.getInstance();
+ // Shortcut only fires when focus is in this graph; delete if we have
selection
boolean hasPipelineSelection =
!pipelineMeta.getSelectedTransforms().isEmpty()
|| !pipelineMeta.getSelectedNotes().isEmpty();
- boolean isLastPipelineSelection =
-
selectionTracker.isLastSelection(HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH);
-
- if (!isLastPipelineSelection || !hasPipelineSelection) {
+ if (!hasPipelineSelection) {
return;
}
-
delSelected(null);
updateGui();
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
index 988f92d402..1b19de3a9c 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
@@ -62,7 +62,6 @@ import org.apache.hop.ui.hopgui.ToolbarFacade;
import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
import org.apache.hop.ui.hopgui.file.pipeline.PipelineMetricDisplayUtil;
-import org.apache.hop.ui.hopgui.selection.HopGuiSelectionTracker;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.graphics.Color;
@@ -334,18 +333,6 @@ public class HopGuiPipelineGridDelegate {
startRefreshMetricsTimer();
pipelineGridTab.addDisposeListener(disposeEvent ->
stopRefreshMetricsTimer());
- HopGuiSelectionTracker.getInstance()
- .addSelectionListener(
- HopGuiSelectionTracker.SelectionType.PIPELINE_GRAPH,
- () -> {
- if (!showSelectedTransforms) {
- return;
- }
- if (pipelineGridView == null || pipelineGridView.isDisposed()) {
- return;
- }
- hopGui.getDisplay().asyncExec(this::refreshView);
- });
pipelineGridTab.setControl(pipelineGridComposite);
}
@@ -385,6 +372,20 @@ public class HopGuiPipelineGridDelegate {
refreshMetricsTimer = null;
}
+ /**
+ * Called by the pipeline graph when selection changes (e.g. user selects
transforms). Refreshes
+ * the grid view when "show selected" is on.
+ */
+ public void onPipelineSelectionChanged() {
+ if (!showSelectedTransforms) {
+ return;
+ }
+ if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+ return;
+ }
+ hopGui.getDisplay().asyncExec(this::refreshView);
+ }
+
/**
* When a toolbar is hit it knows the class so it will come here to ask for
the instance.
*
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 6bdf21a40b..c322f3f4d6 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
@@ -138,7 +138,6 @@ import
org.apache.hop.ui.hopgui.file.workflow.extension.HopGuiWorkflowGraphExten
import org.apache.hop.ui.hopgui.perspective.execution.ExecutionPerspective;
import org.apache.hop.ui.hopgui.perspective.execution.IExecutionViewer;
import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
-import org.apache.hop.ui.hopgui.selection.HopGuiSelectionTracker;
import org.apache.hop.ui.hopgui.shared.CanvasZoomHelper;
import org.apache.hop.ui.hopgui.shared.SwtGc;
import org.apache.hop.ui.util.EnvironmentUtils;
@@ -721,8 +720,6 @@ public class HopGuiWorkflowGraph extends HopGuiAbstractGraph
selectedNotes = workflowMeta.getSelectedNotes();
selectedNote = currentNotePad;
// Track that a note was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.WORKFLOW_GRAPH);
Point loc = currentNotePad.getLocation();
previousNoteLocations = workflowMeta.getSelectedNoteLocations();
@@ -897,12 +894,6 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
workflowMeta.unselectAll();
selectInRect(workflowMeta, selectionRegion);
selectionRegion = null;
- // Track that actions/notes were selected via region selection
- if (!workflowMeta.getSelectedActions().isEmpty()
- || !workflowMeta.getSelectedNotes().isEmpty()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.WORKFLOW_GRAPH);
- }
updateGui();
return;
}
@@ -956,19 +947,11 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
selectedAction.setSelected(true);
}
// Track that an action was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.WORKFLOW_GRAPH);
}
} else {
// Find out which Transforms & Notes are selected
selectedActions = workflowMeta.getSelectedActions();
selectedNotes = workflowMeta.getSelectedNotes();
- // Track that actions/notes were selected
- if (!selectedActions.isEmpty() || !selectedNotes.isEmpty()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.WORKFLOW_GRAPH);
- }
-
// We moved around some items: store undo info...
//
boolean also = false;
@@ -1061,18 +1044,13 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
selectedNote.setSelected(true);
}
// Track that a note was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.WORKFLOW_GRAPH);
}
} else {
// Find out which Transforms & Notes are selected
selectedActions = workflowMeta.getSelectedActions();
selectedNotes = workflowMeta.getSelectedNotes();
// Track that actions/notes were selected
- if (!selectedActions.isEmpty() || !selectedNotes.isEmpty()) {
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.WORKFLOW_GRAPH);
- }
+ if (!selectedActions.isEmpty() || !selectedNotes.isEmpty()) {}
// We moved around some items: store undo info...
boolean also = false;
@@ -2170,12 +2148,7 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
@GuiOsxKeyboardShortcut(key = SWT.DEL)
@Override
public void deleteSelected() {
- // Only handle delete if a workflow graph item was the last selected item
- HopGuiSelectionTracker selectionTracker =
HopGuiSelectionTracker.getInstance();
- if
(!selectionTracker.isLastSelection(HopGuiSelectionTracker.SelectionType.WORKFLOW_GRAPH))
{
- return;
- }
-
+ // Shortcut only fires when focus is in this graph
deleteSelected(null);
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java
index 8e9167a1cc..8bf953eb6f 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java
@@ -38,6 +38,7 @@ import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.widget.TableView;
import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.HopGuiKeyHandler;
import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
import org.apache.hop.ui.hopgui.perspective.HopPerspectivePlugin;
import org.apache.hop.ui.hopgui.perspective.IHopPerspective;
@@ -58,6 +59,7 @@ import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
@@ -107,8 +109,8 @@ public class ConfigurationPerspective implements
IHopPerspective {
return "configuration";
}
- @GuiKeyboardShortcut(control = true, shift = true, key = 'c')
- @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'c')
+ @GuiKeyboardShortcut(control = true, shift = true, key = 'c', global = true)
+ @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'c', global =
true)
@Override
public void activate() {
hopGui.setActivePerspective(this);
@@ -244,6 +246,14 @@ public class ConfigurationPerspective implements
IHopPerspective {
loadSettingCategories();
sashForm.setWeights(20, 80);
+
+ // Register with key handler so activate shortcut (Ctrl+Shift+C /
Cmd+Shift+C) works
+ HopGuiKeyHandler keyHandler = HopGuiKeyHandler.getInstance();
+ keyHandler.addParentObjectToHandle(this);
+ Shell shell = (sashForm != null && !sashForm.isDisposed()) ?
sashForm.getShell() : null;
+ if (shell != null) {
+ HopGui.getInstance().replaceKeyboardShortcutListeners(shell, keyHandler);
+ }
}
private void loadSettingCategories() {
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigKeyboardShortcutsTab.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigKeyboardShortcutsTab.java
index 38dae0cd99..3bd013a493 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigKeyboardShortcutsTab.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigKeyboardShortcutsTab.java
@@ -24,15 +24,19 @@ import java.util.List;
import java.util.Map;
import org.apache.hop.core.Const;
import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.GuiPluginType;
import org.apache.hop.core.gui.plugin.GuiRegistry;
import org.apache.hop.core.gui.plugin.key.KeyboardShortcut;
import org.apache.hop.core.gui.plugin.tab.GuiTab;
+import org.apache.hop.core.plugins.IPlugin;
+import org.apache.hop.core.plugins.PluginRegistry;
import org.apache.hop.core.util.TranslateUtil;
import org.apache.hop.core.util.Utils;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.PropsUi;
import org.apache.hop.ui.core.dialog.BaseDialog;
import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.core.gui.ShortcutDisplayUtil;
import
org.apache.hop.ui.hopgui.perspective.configuration.ConfigurationPerspective;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
@@ -101,7 +105,7 @@ public class ConfigKeyboardShortcutsTab {
monoFont = new Font(display, monoFontData);
}
- // Get all keyboard shortcuts from the registry
+ // Get all keyboard shortcuts from the registry (sorted by method name,
then OS)
GuiRegistry guiRegistry = GuiRegistry.getInstance();
Map<String, List<KeyboardShortcut>> shortcutsMap =
guiRegistry.getShortCutsMap();
@@ -114,7 +118,7 @@ public class ConfigKeyboardShortcutsTab {
Collections.sort(classNames);
for (String className : classNames) {
- List<KeyboardShortcut> allShortcuts = shortcutsMap.get(className);
+ List<KeyboardShortcut> allShortcuts =
guiRegistry.getKeyboardShortcuts(className);
if (allShortcuts == null || allShortcuts.isEmpty()) {
continue;
}
@@ -282,44 +286,12 @@ public class ConfigKeyboardShortcutsTab {
fdKeys.width = 200; // Fixed width for shortcut column
keysComposite.setLayoutData(fdKeys);
- // Add modifier keys and main key
- // Standard order for macOS: Command, Shift, Alt, Control (Apple
convention)
- // Standard order for other OS: Control, Alt, Shift
- boolean isMacOS = Const.isOSX();
+ // Add modifier keys and main key (same order and symbols as
menus/toolbars)
Control lastKey = null;
-
- if (isMacOS) {
- // macOS order: Command first, then Shift, then Alt/Option, then Control
- if (shortcut.isCommand()) {
- lastKey = createKeyBadge(keysComposite, "⌘", lastKey, margin);
- }
- if (shortcut.isShift()) {
- lastKey = createKeyBadge(keysComposite, "⇧", lastKey, margin);
- }
- if (shortcut.isAlt()) {
- lastKey = createKeyBadge(keysComposite, "⌥", lastKey, margin);
- }
- if (shortcut.isControl()) {
- lastKey = createKeyBadge(keysComposite, "⌃", lastKey, margin);
- }
- } else {
- // Windows/Linux order: Control, Alt, Shift, Command
- if (shortcut.isControl()) {
- lastKey = createKeyBadge(keysComposite, "Ctrl", lastKey, margin);
- }
- if (shortcut.isAlt()) {
- lastKey = createKeyBadge(keysComposite, "Alt", lastKey, margin);
- }
- if (shortcut.isShift()) {
- lastKey = createKeyBadge(keysComposite, "Shift", lastKey, margin);
- }
- if (shortcut.isCommand()) {
- lastKey = createKeyBadge(keysComposite, "Cmd", lastKey, margin);
- }
+ for (String modifier :
ShortcutDisplayUtil.getModifierDisplayStrings(shortcut)) {
+ lastKey = createKeyBadge(keysComposite, modifier, lastKey, margin);
}
-
- // Add the main key
- String keyText = getKeyText(shortcut.getKeyCode());
+ String keyText =
ShortcutDisplayUtil.getKeyDisplayText(shortcut.getKeyCode());
if (!Utils.isEmpty(keyText)) {
lastKey = createKeyBadge(keysComposite, keyText, lastKey, margin);
}
@@ -409,130 +381,6 @@ public class ConfigKeyboardShortcutsTab {
return 32;
}
- /**
- * Converts a key code to a readable string.
- *
- * @param keyCode The SWT key code
- * @return A readable key string
- */
- private String getKeyText(int keyCode) {
- boolean isMacOS = Const.isOSX();
-
- // Keypad key conversions (match the logic in HopGuiKeyHandler)
- if (keyCode == SWT.KEYPAD_ADD) {
- return "+";
- } else if (keyCode == SWT.KEYPAD_SUBTRACT) {
- return "-";
- } else if (keyCode == SWT.KEYPAD_MULTIPLY) {
- return "*";
- } else if (keyCode == SWT.KEYPAD_DIVIDE) {
- return "/";
- } else if (keyCode == SWT.KEYPAD_EQUAL) {
- return "=";
- } else if (keyCode == SWT.KEYPAD_DECIMAL) {
- return ".";
- } else if (keyCode == SWT.KEYPAD_CR) {
- return "↵";
- } else if (keyCode >= SWT.KEYPAD_0 && keyCode <= SWT.KEYPAD_9) {
- // Keypad 0-9
- return String.valueOf(keyCode - SWT.KEYPAD_0);
- }
-
- // Spacebar
- if (keyCode == 32) {
- return "Space";
- }
- // Character upper
- else if (keyCode >= 65 && keyCode <= 90) {
- return String.valueOf((char) keyCode);
- }
- // Character lower
- else if (keyCode >= 97 && keyCode <= 122) {
- return String.valueOf(Character.toUpperCase((char) keyCode));
- }
- // Delete key
- else if (keyCode == 127) {
- return isMacOS ? "⌫" : "Del";
- }
- // Digit and common symbols
- else if ((keyCode >= 48 && keyCode <= 57) || "+-/*=".indexOf(keyCode) >=
0) {
- return String.valueOf((char) keyCode);
- }
-
- // Special keys (SWT constants)
- if ((keyCode & (1 << 24)) != 0) {
- switch (keyCode & (0xFFFF)) {
- case 1:
- return "↑";
- case 2:
- return "↓";
- case 3:
- return "←";
- case 4:
- return "→";
- case 5:
- return "PgUp";
- case 6:
- return "PgDn";
- case 7:
- return "Home";
- case 8:
- return "End";
- case 9:
- return "Ins";
- case 10:
- return "F1";
- case 11:
- return "F2";
- case 12:
- return "F3";
- case 13:
- return "F4";
- case 14:
- return "F5";
- case 15:
- return "F6";
- case 16:
- return "F7";
- case 17:
- return "F8";
- case 18:
- return "F9";
- case 19:
- return "F10";
- case 20:
- return "F11";
- case 21:
- return "F12";
- case 22:
- return "F13";
- case 23:
- return "F14";
- case 24:
- return "F15";
- case 25:
- return "F16";
- case 26:
- return "F17";
- case 27:
- return "F18";
- case 28:
- return "F19";
- case 29:
- return "F20";
- default:
- break;
- }
- }
-
- // ESC key
- if (keyCode == SWT.ESC) {
- return isMacOS ? "⎋" : "Esc";
- }
-
- return "";
- }
-
/**
* Formats a method name to be more readable.
*
@@ -560,25 +408,31 @@ public class ConfigKeyboardShortcutsTab {
return formatted.toString();
}
- /**
- * Gets a friendly plugin name from a fully qualified class name. Checks
GuiPlugin annotation for
- * name or id, otherwise falls back to the simple class name.
- *
- * @param className The fully qualified class name
- * @return The plugin name, id, or simple class name
- */
+ /** Plugin name from class name: registry first, then @GuiPlugin, then
simple name. */
private String getPluginName(String className) {
if (Utils.isEmpty(className)) {
return "";
}
try {
- // Try to load the class and get its GuiPlugin annotation
+ String pluginName =
+ PluginRegistry.getInstance().getPlugins(GuiPluginType.class).stream()
+ .filter(p -> p.getClassMap().values().contains(className))
+ .findFirst()
+ .map(IPlugin::getName)
+ .orElse(null);
+ if (!Utils.isEmpty(pluginName)) {
+ return pluginName;
+ }
+ } catch (Exception e) {
+ // Fall through
+ }
+
+ try {
Class<?> clazz = Class.forName(className);
GuiPlugin annotation = clazz.getAnnotation(GuiPlugin.class);
if (annotation != null) {
- // First try to get the name
if (!Utils.isEmpty(annotation.name())) {
String name = TranslateUtil.translate(annotation.name(), clazz);
if (!Utils.isEmpty(name)) {
@@ -586,16 +440,14 @@ public class ConfigKeyboardShortcutsTab {
}
}
- // Fall back to id if name is empty
if (!Utils.isEmpty(annotation.id())) {
return annotation.id();
}
}
} catch (Exception e) {
- // Fall through to simple class name
+ // ignore
}
- // Fallback to simple class name
int lastDot = className.lastIndexOf('.');
if (lastDot >= 0 && lastDot < className.length() - 1) {
return className.substring(lastDot + 1);
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
index 071de715b7..0993e5f93b 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
@@ -141,8 +141,8 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
return "execution-perspective";
}
- @GuiKeyboardShortcut(control = true, shift = true, key = 'i')
- @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'i')
+ @GuiKeyboardShortcut(control = true, shift = true, key = 'i', global = true)
+ @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'i', global =
true)
@Override
public void activate() {
hopGui.setActivePerspective(this);
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
index a8109e4ff5..585b9ff258 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -99,7 +99,6 @@ import
org.apache.hop.ui.hopgui.perspective.explorer.file.ExplorerFileType;
import
org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
import org.apache.hop.ui.hopgui.perspective.explorer.file.types.FolderFileType;
import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.GenericFileType;
-import org.apache.hop.ui.hopgui.selection.HopGuiSelectionTracker;
import org.apache.hop.ui.hopgui.shared.CanvasZoomHelper;
import org.apache.hop.workflow.WorkflowMeta;
import org.apache.hop.workflow.engine.IWorkflowEngine;
@@ -248,8 +247,8 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
return "explorer-perspective";
}
- @GuiKeyboardShortcut(control = true, shift = true, key = 'd')
- @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'd')
+ @GuiKeyboardShortcut(control = true, shift = true, key = 'd', global = true)
+ @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'd', global =
true)
@Override
public void activate() {
hopGui.setActivePerspective(this);
@@ -1746,12 +1745,7 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
@GuiKeyboardShortcut(key = SWT.DEL)
@GuiOsxKeyboardShortcut(key = SWT.DEL)
public void deleteFile() {
- // Only handle delete if a file was the last selected item
- HopGuiSelectionTracker selectionTracker =
HopGuiSelectionTracker.getInstance();
- if
(!selectionTracker.isLastSelection(HopGuiSelectionTracker.SelectionType.FILE_EXPLORER))
{
- return;
- }
-
+ // Shortcut only fires when focus is in file explorer
TreeItem[] selection = tree.getSelection();
if (selection == null || selection.length == 0) {
return;
@@ -2375,9 +2369,6 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
if (tif == null) {
return;
}
- // Track that a file was selected
- HopGuiSelectionTracker.getInstance()
-
.setLastSelectionType(HopGuiSelectionTracker.SelectionType.FILE_EXPLORER);
}
boolean isFolderSelected = tif != null && tif.fileType instanceof
FolderFileType;
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
index 49afd63d25..8f274956ca 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
@@ -156,8 +156,8 @@ public class MetadataPerspective implements
IHopPerspective, TabClosable {
return "metadata-perspective";
}
- @GuiKeyboardShortcut(control = true, shift = true, key = 'm')
- @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'm')
+ @GuiKeyboardShortcut(control = true, shift = true, key = 'm', global = true)
+ @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'm', global =
true)
@Override
public void activate() {
hopGui.setActivePerspective(this);
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
index 096c3d60d4..68d15b1d06 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
@@ -97,8 +97,8 @@ public class HopSearchPerspective implements IHopPerspective {
}
@Override
- @GuiKeyboardShortcut(control = true, key = 'f')
- @GuiOsxKeyboardShortcut(command = true, key = 'f')
+ @GuiKeyboardShortcut(control = true, key = 'f', global = true)
+ @GuiOsxKeyboardShortcut(command = true, key = 'f', global = true)
public void activate() {
// Someone clicked on the search icon of used CTRL-F
//
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/selection/HopGuiSelectionTracker.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/selection/HopGuiSelectionTracker.java
deleted file mode 100644
index 151efe3e26..0000000000
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/selection/HopGuiSelectionTracker.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.selection;
-
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Tracks the last selected item type to help route keyboard shortcuts
correctly. This is needed
- * when multiple components (like file explorer and pipeline graph) share the
same keyboard
- * shortcuts but need to act on different types of items.
- *
- * <p>Listeners can register to be notified when a given selection type is set
(e.g. pipeline graph
- * selection changed).
- */
-public class HopGuiSelectionTracker {
-
- private static HopGuiSelectionTracker instance;
-
- /** The type of item that was last selected */
- public enum SelectionType {
- /** A file or folder in the file explorer */
- FILE_EXPLORER,
- /** A transform, note, or hop in a pipeline graph */
- PIPELINE_GRAPH,
- /** An action, note, or hop in a workflow graph */
- WORKFLOW_GRAPH,
- /** No specific selection */
- NONE
- }
-
- private SelectionType lastSelectionType = SelectionType.NONE;
-
- private final Map<SelectionType, List<Runnable>> selectionListeners =
- new EnumMap<>(SelectionType.class);
-
- private HopGuiSelectionTracker() {
- for (SelectionType t : SelectionType.values()) {
- selectionListeners.put(t, new ArrayList<>());
- }
- }
-
- public static HopGuiSelectionTracker getInstance() {
- if (instance == null) {
- instance = new HopGuiSelectionTracker();
- }
- return instance;
- }
-
- /**
- * Add a listener that runs when the given selection type is set.
- *
- * @param selectionType the type to listen for
- * @param listener runnable to execute when
setLastSelectionType(selectionType) is called
- */
- public void addSelectionListener(SelectionType selectionType, Runnable
listener) {
- selectionListeners.get(selectionType).add(listener);
- }
-
- /**
- * Set the last selected item type and notify any listeners for this type.
- *
- * @param selectionType The type of item that was selected
- */
- public void setLastSelectionType(SelectionType selectionType) {
- this.lastSelectionType = selectionType;
- for (Runnable listener : selectionListeners.get(selectionType)) {
- listener.run();
- }
- }
-
- /**
- * Get the last selected item type
- *
- * @return The type of item that was last selected
- */
- public SelectionType getLastSelectionType() {
- return lastSelectionType;
- }
-
- /**
- * Check if the last selection matches the given type
- *
- * @param selectionType The type to check against
- * @return true if the last selection matches the given type
- */
- public boolean isLastSelection(SelectionType selectionType) {
- return lastSelectionType == selectionType;
- }
-}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiTerminalPanel.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiTerminalPanel.java
index 2929f8c841..023eb92eac 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiTerminalPanel.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiTerminalPanel.java
@@ -17,7 +17,12 @@
package org.apache.hop.ui.hopgui.terminal;
+import lombok.Getter;
import org.apache.commons.lang.StringUtils;
+import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.key.GuiKeyboardShortcut;
+import org.apache.hop.core.gui.plugin.key.GuiOsxKeyboardShortcut;
+import org.apache.hop.core.gui.plugin.menu.GuiMenuElement;
import org.apache.hop.history.AuditList;
import org.apache.hop.history.AuditManager;
import org.apache.hop.history.AuditState;
@@ -27,8 +32,10 @@ import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.HopNamespace;
import org.apache.hop.ui.core.widget.TabFolderReorder;
import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.HopGuiKeyHandler;
import org.apache.hop.ui.hopgui.perspective.TabClosable;
import org.apache.hop.ui.hopgui.perspective.TabCloseHandler;
+import org.apache.hop.ui.util.EnvironmentUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Adapter;
@@ -50,19 +57,22 @@ import org.eclipse.swt.widgets.ToolItem;
* section and the terminal panel in the bottom section. The terminal panel
persists across
* perspective switches.
*/
+@GuiPlugin(name = "Terminal panel", description = "Terminal panel")
public class HopGuiTerminalPanel extends Composite implements TabClosable {
+ public static final String ID_MAIN_MENU_TOOLS_TERMINAL =
"40010-menu-tools-terminal";
+ public static final String ID_MAIN_MENU_TOOLS_NEW_TERMINAL =
"40020-menu-tools-new-terminal";
+
private final HopGui hopGui;
private SashForm verticalSash;
- private Composite perspectiveComposite;
+ @Getter private Composite perspectiveComposite;
private Composite bottomPanelComposite;
private Composite terminalComposite;
- private CTabFolder terminalTabs;
+ @Getter private CTabFolder terminalTabs;
private CTabItem newTerminalTab;
-
- private boolean terminalVisible = false;
- private int terminalHeightPercent = 35;
+ @Getter private boolean terminalVisible = false;
+ @Getter private int terminalHeightPercent = 35;
private boolean isClearing = false;
private int terminalCounter = 1;
@@ -106,6 +116,11 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
createBottomPanel();
verticalSash.setMaximizedControl(perspectiveComposite);
+
+ // Register with key handler so Ctrl+J / Cmd+J and Ctrl+Shift+J /
Cmd+Shift+J work in this panel
+ HopGuiKeyHandler keyHandler = HopGuiKeyHandler.getInstance();
+ keyHandler.addParentObjectToHandle(this);
+ hopGui.replaceKeyboardShortcutListeners(this, keyHandler);
}
/** Create the bottom panel with terminal */
@@ -311,7 +326,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
if (!terminalVisible) {
verticalSash.setMaximizedControl(null);
int perspectivePercent = 100 - terminalHeightPercent;
- verticalSash.setWeights(new int[] {perspectivePercent,
terminalHeightPercent});
+ verticalSash.setWeights(perspectivePercent, terminalHeightPercent);
terminalVisible = true;
if (terminalTabs.getItemCount() <= 1) {
@@ -322,11 +337,6 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
}
}
- /** Check if terminal panel is currently visible */
- public boolean isTerminalVisible() {
- return terminalVisible;
- }
-
/** Hide the terminal panel */
public void hideTerminal() {
if (terminalVisible) {
@@ -337,7 +347,17 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
}
/** Toggle terminal panel visibility */
+ @GuiMenuElement(
+ root = HopGui.ID_MAIN_MENU,
+ id = ID_MAIN_MENU_TOOLS_TERMINAL,
+ label = "i18n::HopGuiTerminalPanel.Menu.Terminal",
+ parentId = HopGui.ID_MAIN_MENU_TOOLS_PARENT_ID)
+ @GuiKeyboardShortcut(control = true, key = 'j', global = true)
+ @GuiOsxKeyboardShortcut(command = true, key = 'j', global = true)
public void toggleTerminal() {
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ return;
+ }
if (terminalVisible) {
hideTerminal();
} else {
@@ -345,6 +365,21 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
}
}
+ /** Open a new terminal tab */
+ @GuiMenuElement(
+ root = HopGui.ID_MAIN_MENU,
+ id = ID_MAIN_MENU_TOOLS_NEW_TERMINAL,
+ label = "i18n::HopGuiTerminalPanel.Menu.NewTerminal",
+ parentId = HopGui.ID_MAIN_MENU_TOOLS_PARENT_ID)
+ @GuiKeyboardShortcut(control = true, shift = true, key = 'j', global = true)
+ @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'j', global =
true)
+ public void newTerminal() {
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ return;
+ }
+ createNewTerminal(null, null);
+ }
+
/** Close a terminal tab (implements TabClosable interface) */
@Override
public void closeTab(CTabFolderEvent event, CTabItem tabItem) {
@@ -443,7 +478,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
} else {
// Restore normal split
verticalSash.setMaximizedControl(null);
- verticalSash.setWeights(new int[] {100 - terminalHeightPercent,
terminalHeightPercent});
+ verticalSash.setWeights(100 - terminalHeightPercent,
terminalHeightPercent);
maximizeItem.setImage(GuiResource.getInstance().getImageMaximizePanel());
maximizeItem.setToolTipText("Maximize terminal panel");
}
@@ -670,29 +705,14 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
}
}
- /** Get the perspective composite */
- public Composite getPerspectiveComposite() {
- return perspectiveComposite;
- }
-
- /** Get the terminal tabs folder */
- public CTabFolder getTerminalTabs() {
- return terminalTabs;
- }
-
/** Set terminal height percentage */
public void setTerminalHeightPercent(int percent) {
if (percent > 0 && percent < 100) {
this.terminalHeightPercent = percent;
if (terminalVisible) {
int perspectivePercent = 100 - terminalHeightPercent;
- verticalSash.setWeights(new int[] {perspectivePercent,
terminalHeightPercent});
+ verticalSash.setWeights(perspectivePercent, terminalHeightPercent);
}
}
}
-
- /** Get current terminal height percentage */
- public int getTerminalHeightPercent() {
- return terminalHeightPercent;
- }
}
diff --git
a/ui/src/main/resources/org/apache/hop/ui/hopgui/terminal/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/hopgui/terminal/messages/messages_en_US.properties
new file mode 100644
index 0000000000..672d4d683f
--- /dev/null
+++
b/ui/src/main/resources/org/apache/hop/ui/hopgui/terminal/messages/messages_en_US.properties
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+#
+
+HopGuiTerminalPanel.Menu.Terminal=Terminal
+HopGuiTerminalPanel.Menu.NewTerminal=New Terminal