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 eccb6bfb47 UI fixes, fixes #6662, #6616, #6633, #6657 (#6663)
eccb6bfb47 is described below
commit eccb6bfb4777255d66a0505aa6508970bc1d1e98
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Thu Feb 26 16:20:48 2026 +0100
UI fixes, fixes #6662, #6616, #6633, #6657 (#6663)
---
.../apache/hop/projects/gui/ProjectsGuiPlugin.java | 5 +
.../org/apache/hop/ui/core/gui/GuiResource.java | 12 +-
.../main/java/org/apache/hop/ui/hopgui/HopGui.java | 231 ++++++++++++++++++---
.../ui/hopgui/SidebarToolbarItemDescriptor.java | 15 +-
.../ui/hopgui/delegates/HopGuiFileDelegate.java | 8 +
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 2 +
.../hopgui/file/workflow/HopGuiWorkflowGraph.java | 2 +
.../perspective/explorer/ExplorerPerspective.java | 85 +++++++-
.../ui/hopgui/terminal/HopGuiTerminalPanel.java | 193 +++++++++++++++--
.../hop/ui/hopgui/terminal/ITerminalWidget.java | 11 +
.../hop/ui/hopgui/terminal/JediTerminalWidget.java | 74 ++++++-
.../terminal/messages/messages_en_US.properties | 12 ++
12 files changed, 576 insertions(+), 74 deletions(-)
diff --git
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
index a61fccfbf9..50b08d74b7 100644
---
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
+++
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
@@ -168,6 +168,11 @@ public class ProjectsGuiPlugin {
hopGui.getTerminalPanel().clearAllTerminals();
}
+ // Save explorer perspective state for the current project before
switching namespace
+ //
+
org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective.getInstance()
+ .saveExplorerStateOnShutdown();
+
// This is called only in Hop GUI so we want to start with a new set of
variables
// It avoids variables from one project showing up in another
//
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 64964b5fec..3146930e99 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
@@ -1628,14 +1628,12 @@ public class GuiResource {
}
public Color getWidgetBackGroundColor() {
+ if (PropsUi.getInstance().isDarkMode()) {
+ return display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+ }
if (OsHelper.isMac()) {
- if (PropsUi.getInstance().isDarkMode()) {
- return display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
- } else {
- return colorDemoGray;
- }
- } else {
- return colorWhite;
+ return colorDemoGray;
}
+ return colorWhite;
}
}
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 5c7d919cb9..c1961154fd 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
@@ -274,7 +274,7 @@ public class HopGui
private GuiToolbarWidgets statusToolbarWidgets;
private Composite perspectivesSidebar;
- private ToolBar bottomToolbar;
+ private Composite bottomToolbar;
private final java.util.List<SidebarToolbarItemDescriptor>
sidebarToolbarDescriptors =
new java.util.ArrayList<>();
private java.util.List<SidebarButton> sidebarButtons = new
java.util.ArrayList<>();
@@ -498,6 +498,11 @@ public class HopGui
// Terminal restoration is handled by the Projects plugin
+ // Restore explorer perspective state (file explorer panel
visibility) for current
+ // namespace (default or project set by extension point).
+ //
+ ExplorerPerspective.getInstance().applyRestoredState();
+
// We need to start tracking file history again.
//
reOpeningFiles = false;
@@ -1551,12 +1556,16 @@ public class HopGui
fdPerspectivesContainer.right = new FormAttachment(100, 0);
perspectivesContainer.setLayoutData(fdPerspectivesContainer);
- bottomToolbar = new ToolBar(perspectivesSidebar, SWT.WRAP | SWT.RIGHT |
SWT.VERTICAL);
- PropsUi.setLook(bottomToolbar, Props.WIDGET_STYLE_TOOLBAR);
+ bottomToolbar = new Composite(perspectivesSidebar, SWT.NONE);
+ GridLayout bottomLayout = new GridLayout(1, true);
+ bottomLayout.marginWidth = 0;
+ bottomLayout.marginHeight = 0;
+ bottomLayout.verticalSpacing = 1;
+ bottomToolbar.setLayout(bottomLayout);
+
bottomToolbar.setBackground(GuiResource.getInstance().getWidgetBackGroundColor());
FormData fdBottomToolbar = new FormData();
fdBottomToolbar.left = new FormAttachment(0, 0);
fdBottomToolbar.right = new FormAttachment(100, 0);
- // Add small margin at bottom to create visual separation from
project/environment dropdowns
fdBottomToolbar.bottom = new FormAttachment(100, -4);
bottomToolbar.setLayoutData(fdBottomToolbar);
@@ -1576,6 +1585,7 @@ public class HopGui
terminalPanel.toggleTerminal();
}
})
+ .selectedSupplier(() -> terminalPanel != null &&
terminalPanel.isTerminalVisible())
.available(!EnvironmentUtils.getInstance().isWeb())
.build());
sidebarToolbarDescriptors.add(
@@ -1583,9 +1593,18 @@ public class HopGui
.id(SIDEBAR_TOOLBAR_ITEM_EXECUTION_RESULTS)
.visibleForPerspectiveIds(Set.of(PERSPECTIVE_ID_EXPLORER))
.imagePath("ui/images/show-results.svg")
+ .activeImagePath("ui/images/hide-results.svg")
.imageSize(sidebarIconSize)
.tooltip("Toggle Execution Results (Logging/Metrics/Problems)")
.onSelect(this::toggleExecutionResults)
+ .selectedSupplier(
+ () -> {
+ HopGuiPipelineGraph pg = getActivePipelineGraph();
+ if (pg != null) return pg.isExecutionResultsPaneVisible();
+ HopGuiWorkflowGraph wg = getActiveWorkflowGraph();
+ if (wg != null) return wg.isExecutionResultsPaneVisible();
+ return false;
+ })
.available(true)
.build());
@@ -1868,6 +1887,7 @@ public class HopGui
perspectiveManager.notifyPerspectiveActivated(perspective);
+ updateSidebarButtonSelection(perspective);
refreshBottomToolbarItems();
}
@@ -1888,9 +1908,8 @@ public class HopGui
/**
* Refresh the bottom sidebar toolbar so only items visible for the current
perspective are shown.
* Items are added in reverse descriptor order so that the second, third,
etc. buttons appear
- * above the first (SWT vertical toolbar lays out first-added at top). This
avoids overlapping and
- * keeps perspective-specific buttons (e.g. execution) above the
always-visible ones (e.g.
- * terminal).
+ * above the first (GridLayout lays out first-added at top). This avoids
overlapping and keeps
+ * perspective-specific buttons (e.g. execution) above the always-visible
ones (e.g. terminal).
*/
public void refreshBottomToolbarItems() {
if (bottomToolbar == null || bottomToolbar.isDisposed()) {
@@ -1917,34 +1936,190 @@ public class HopGui
}
}
- // Dispose existing items
- for (ToolItem item : bottomToolbar.getItems()) {
- item.dispose();
+ // Dispose existing children
+ for (Control child : bottomToolbar.getChildren()) {
+ child.dispose();
}
- // Add in reverse order: last in list becomes first (top) in toolbar so we
don't overlap
+ Color normalBg = GuiResource.getInstance().getWidgetBackGroundColor();
+ Color selectionBg = GuiResource.getInstance().getColorLightBlue();
+ Color hoverBg = GuiResource.getInstance().getColorGray();
+ int buttonSize = (int) (34 * PropsUi.getNativeZoomFactor());
+
+ // Add in reverse order: last in list becomes first (top) in toolbar
for (int i = visible.size() - 1; i >= 0; i--) {
SidebarToolbarItemDescriptor d = visible.get(i);
- ToolItem item = new ToolItem(bottomToolbar, SWT.PUSH);
- Image img =
- GuiResource.getInstance().getImage(d.getImagePath(),
d.getImageSize(), d.getImageSize());
- item.setImage(img);
- item.setToolTipText(d.getTooltip());
- item.setData("descriptor", d);
- item.addListener(
- SWT.Selection,
- event -> {
- SidebarToolbarItemDescriptor desc =
- (SidebarToolbarItemDescriptor) item.getData("descriptor");
- if (desc != null && desc.getOnSelect() != null) {
- desc.getOnSelect().run();
- }
- });
+
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ // RAP: use Composite + Label (same pattern as SidebarButton)
+ Composite comp = new Composite(bottomToolbar, SWT.NONE);
+ comp.setToolTipText(d.getTooltip());
+ comp.setBackground(normalBg);
+ comp.setData("descriptor", d);
+ comp.setData("org.eclipse.rap.rwt.customVariant", "sidebarButton");
+
+ GridLayout gl = new GridLayout(1, false);
+ gl.marginWidth = 0;
+ gl.marginHeight = 0;
+ comp.setLayout(gl);
+
+ Label imgLabel = new Label(comp, SWT.NONE);
+ imgLabel.setImage(resolveButtonImage(d));
+ imgLabel.setBackground(normalBg);
+ imgLabel.setToolTipText(d.getTooltip());
+ imgLabel.setData("org.eclipse.rap.rwt.customVariant", "sidebarButton");
+ imgLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true,
true));
+
+ GridData compGd = new GridData();
+ compGd.widthHint = buttonSize;
+ compGd.heightHint = buttonSize;
+ comp.setLayoutData(compGd);
+
+ Runnable updateVisual =
+ () -> {
+ boolean sel = d.getSelectedSupplier().getAsBoolean();
+ boolean hov = Boolean.TRUE.equals(comp.getData("hovered"));
+ Color bg = sel ? selectionBg : hov ? hoverBg : normalBg;
+ comp.setBackground(bg);
+ imgLabel.setBackground(bg);
+ imgLabel.setImage(resolveButtonImage(d));
+ comp.redraw();
+ };
+ comp.setData("updateVisual", updateVisual);
+
+ comp.addListener(
+ SWT.MouseEnter,
+ e -> {
+ comp.setData("hovered", true);
+ updateVisual.run();
+ });
+ comp.addListener(
+ SWT.MouseExit,
+ e -> {
+ comp.setData("hovered", false);
+ updateVisual.run();
+ });
+ comp.addListener(
+ SWT.MouseDown,
+ e -> {
+ if (d.getOnSelect() != null) d.getOnSelect().run();
+ updateVisual.run();
+ });
+ imgLabel.addListener(
+ SWT.MouseEnter,
+ e -> {
+ comp.setData("hovered", true);
+ updateVisual.run();
+ });
+ imgLabel.addListener(
+ SWT.MouseExit,
+ e -> {
+ comp.setData("hovered", false);
+ updateVisual.run();
+ });
+ imgLabel.addListener(
+ SWT.MouseDown,
+ e -> {
+ if (d.getOnSelect() != null) d.getOnSelect().run();
+ updateVisual.run();
+ });
+
+ updateVisual.run();
+ } else {
+ // Desktop SWT: Canvas with custom painting (matches SidebarButton
style)
+ Canvas canvas = new Canvas(bottomToolbar, SWT.NONE);
+ canvas.setToolTipText(d.getTooltip());
+ canvas.setBackground(normalBg);
+ canvas.setData("descriptor", d);
+ canvas.setData("selected", d.getSelectedSupplier().getAsBoolean());
+ canvas.setData("hovered", false);
+
+ GridData gd = new GridData();
+ gd.widthHint = buttonSize;
+ gd.heightHint = buttonSize;
+ canvas.setLayoutData(gd);
+
+ canvas.addPaintListener(
+ e -> {
+ GC gc = e.gc;
+ Point size = canvas.getSize();
+ gc.setAntialias(SWT.ON);
+
+ boolean sel = Boolean.TRUE.equals(canvas.getData("selected"));
+ boolean hov = Boolean.TRUE.equals(canvas.getData("hovered"));
+ gc.setBackground(sel ? selectionBg : hov ? hoverBg : normalBg);
+ gc.fillRoundRectangle(4, 4, size.x - 8, size.y - 8, 8, 8);
+
+ Image currentImg = resolveButtonImage(d);
+ if (currentImg != null && !currentImg.isDisposed()) {
+ Rectangle imgBounds = currentImg.getBounds();
+ int x = (size.x - imgBounds.width) / 2;
+ int y = (size.y - imgBounds.height) / 2;
+ gc.drawImage(currentImg, x, y);
+ }
+ });
+
+ canvas.addListener(
+ SWT.MouseEnter,
+ e -> {
+ canvas.setData("hovered", true);
+ canvas.redraw();
+ });
+ canvas.addListener(
+ SWT.MouseExit,
+ e -> {
+ canvas.setData("hovered", false);
+ canvas.redraw();
+ });
+ canvas.addListener(
+ SWT.MouseDown,
+ e -> {
+ if (d.getOnSelect() != null) {
+ d.getOnSelect().run();
+ }
+ canvas.setData("selected",
d.getSelectedSupplier().getAsBoolean());
+ canvas.redraw();
+ });
+ }
}
- bottomToolbar.pack();
+ bottomToolbar.layout(true, true);
bottomToolbar.getParent().layout(true, true);
}
+ /**
+ * Update the visual selected/active state of all bottom sidebar toolbar
buttons. Call this after
+ * an action that changes the state externally (e.g. terminal toggled via
keyboard shortcut).
+ */
+ public void refreshSidebarToolbarButtonStates() {
+ if (bottomToolbar == null || bottomToolbar.isDisposed()) {
+ return;
+ }
+ for (Control child : bottomToolbar.getChildren()) {
+ SidebarToolbarItemDescriptor desc =
+ (SidebarToolbarItemDescriptor) child.getData("descriptor");
+ if (desc != null) {
+ boolean selected = desc.getSelectedSupplier().getAsBoolean();
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ Runnable updateVisual = (Runnable) child.getData("updateVisual");
+ if (updateVisual != null) {
+ updateVisual.run();
+ }
+ } else {
+ child.setData("selected", selected);
+ child.redraw();
+ }
+ }
+ }
+ }
+
+ /** Resolve the correct image for a sidebar toolbar button based on its
current selected state. */
+ private Image resolveButtonImage(SidebarToolbarItemDescriptor d) {
+ boolean active = d.getSelectedSupplier().getAsBoolean();
+ String path =
+ active && !d.getActiveImagePath().isEmpty() ? d.getActiveImagePath() :
d.getImagePath();
+ return GuiResource.getInstance().getImage(path, d.getImageSize(),
d.getImageSize());
+ }
+
public boolean isActivePerspective(IHopPerspective perspective) {
return activePerspective != null && activePerspective.equals(perspective);
}
@@ -2046,12 +2221,14 @@ public class HopGui
HopGuiPipelineGraph pipelineGraph = getActivePipelineGraph();
if (pipelineGraph != null) {
pipelineGraph.showExecutionResults();
+ refreshSidebarToolbarButtonStates();
return;
}
HopGuiWorkflowGraph workflowGraph = getActiveWorkflowGraph();
if (workflowGraph != null) {
workflowGraph.showExecutionResults();
+ refreshSidebarToolbarButtonStates();
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java
index 9815ae8ac4..d499593de5 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java
@@ -19,6 +19,7 @@ package org.apache.hop.ui.hopgui;
import java.util.Collections;
import java.util.Set;
+import java.util.function.BooleanSupplier;
import lombok.Builder;
import lombok.Value;
@@ -57,9 +58,15 @@ public class SidebarToolbarItemDescriptor {
*/
@Builder.Default Set<String> hiddenForPerspectiveIds =
Collections.emptySet();
- /** Image path (e.g. "ui/images/show-results.svg"). */
+ /** Image path used when the item is in its default (inactive) state. */
String imagePath;
+ /**
+ * Optional image path used when the item is in its active/selected state
(e.g.
+ * "ui/images/hide-results.svg"). When empty, {@link #imagePath} is used for
both states.
+ */
+ @Builder.Default String activeImagePath = "";
+
/** Icon size in pixels. */
int imageSize;
@@ -69,6 +76,12 @@ public class SidebarToolbarItemDescriptor {
/** Called when the button is selected. */
Runnable onSelect;
+ /**
+ * Supplies the current "selected/active" state of this toolbar item. Used
to render the button
+ * with a highlight when its associated panel is visible (e.g. terminal
panel open).
+ */
+ @Builder.Default BooleanSupplier selectedSupplier = () -> false;
+
/** Whether this item is available (e.g. terminal only when not in web). */
@Builder.Default boolean available = true;
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
index f4ea9e5e79..2a949cd6cd 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
@@ -56,6 +56,7 @@ import
org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
import org.apache.hop.ui.hopgui.file.workflow.HopGuiWorkflowGraph;
import org.apache.hop.ui.hopgui.perspective.IHopPerspective;
import org.apache.hop.ui.hopgui.perspective.TabItemHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
import org.apache.hop.ui.util.EnvironmentUtils;
import org.apache.hop.workflow.WorkflowMeta;
import org.apache.hop.workflow.WorkflowSvgPainter;
@@ -324,6 +325,13 @@ public class HopGuiFileDelegate {
hopGui.getTerminalPanel().saveTerminalsOnShutdown();
}
+ // Save explorer perspective state (file explorer panel visibility)
+ //
+ ExplorerPerspective explorerPerspective =
ExplorerPerspective.getInstance();
+ if (explorerPerspective != null) {
+ explorerPerspective.saveExplorerStateOnShutdown();
+ }
+
return true;
}
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 d1a8953666..d9b1b6d06e 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
@@ -4257,6 +4257,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
BaseMessages.getString(PKG, "HopGui.Tooltip.ShowExecutionResults"));
toolBarWidgets.setToolbarItemImage(
TOOLBAR_ITEM_SHOW_EXECUTION_RESULTS, "ui/images/show-results.svg");
+ hopGui.refreshSidebarToolbarButtonStates();
}
private void minMaxExtraView() {
@@ -4553,6 +4554,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
toolBarWidgets.setToolbarItemToolTip(
TOOLBAR_ITEM_SHOW_EXECUTION_RESULTS,
BaseMessages.getString(PKG, "HopGui.Tooltip.HideExecutionResults"));
+ hopGui.refreshSidebarToolbarButtonStates();
}
public synchronized void debug(
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 c322f3f4d6..bd205fa48e 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
@@ -3703,6 +3703,7 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
"i18n:org.apache.hop.ui.hopgui:HopGui.Tooltip.ShowExecutionResults", PKG));
toolBarWidgets.setToolbarItemImage(
TOOLBAR_ITEM_SHOW_EXECUTION_RESULTS, "ui/images/show-results.svg");
+ hopGui.refreshSidebarToolbarButtonStates();
}
private void minMaxExtraView() {
@@ -3758,6 +3759,7 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
"i18n:org.apache.hop.ui.hopgui:HopGui.Tooltip.HideExecutionResults", PKG));
toolBarWidgets.setToolbarItemImage(
TOOLBAR_ITEM_SHOW_EXECUTION_RESULTS, "ui/images/hide-results.svg");
+ hopGui.refreshSidebarToolbarButtonStates();
}
@Override
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 5d23092799..535eea6d2b 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
@@ -60,6 +60,9 @@ import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.variables.Variables;
import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.history.AuditManager;
+import org.apache.hop.history.AuditState;
+import org.apache.hop.history.AuditStateMap;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.engine.IPipelineEngine;
@@ -72,6 +75,7 @@ import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.gui.GuiMenuWidgets;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.GuiToolbarWidgets;
+import org.apache.hop.ui.core.gui.HopNamespace;
import org.apache.hop.ui.core.gui.IToolbarContainer;
import org.apache.hop.ui.core.widget.TreeMemory;
import org.apache.hop.ui.hopgui.HopGui;
@@ -189,6 +193,9 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
"ExplorerPerspective-ContextMenu-10401-CopyPath";
public static final String CONTEXT_MENU_DELETE =
"ExplorerPerspective-ContextMenu-90000-Delete";
private static final String FILE_EXPLORER_TREE = "File explorer tree";
+ private static final String EXPLORER_AUDIT_TYPE =
"explorer-perspective-state";
+ private static final String STATE_PANEL_VISIBLE_KEY = "panel-visible";
+ private static final String STATE_PANEL_VISIBLE_PROP = "visible";
private static ExplorerPerspective instance;
@Getter private GuiToolbarWidgets toolBarWidgets;
@@ -290,14 +297,14 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
sash.setWeights(20, 80);
- // Set initial file explorer panel visibility from configuration
+ // Set initial file explorer panel visibility from configuration only.
+ // Saved state is applied later in applyRestoredState() after startup (and
project) so
+ // the correct namespace is set (see HopGui open() async block and
ProjectActivated).
Boolean visibleByDefault =
ExplorerPerspectiveConfigSingleton.getConfig().getFileExplorerVisibleByDefault();
- if (visibleByDefault != null && !visibleByDefault) {
- fileExplorerPanelVisible = false;
+ fileExplorerPanelVisible = visibleByDefault == null || visibleByDefault;
+ if (!fileExplorerPanelVisible) {
sash.setMaximizedControl(tabFolder);
- } else {
- fileExplorerPanelVisible = true;
}
// Refresh the file explorer when a project is activated or updated.
@@ -306,7 +313,11 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
.getEventsHandler()
.addEventListener(
getClass().getName() + "ProjectActivated",
- e -> refresh(),
+ e -> {
+ refresh();
+ // Defer so namespace and UI are fully updated after project
switch
+ hopGui.getDisplay().asyncExec(() -> applyRestoredState());
+ },
HopGuiEvents.ProjectActivated.name());
hopGui
@@ -2574,6 +2585,68 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
return fileExplorerPanelVisible;
}
+ /** Save the file explorer panel visibility state so it persists across
restarts. */
+ public void saveExplorerStateOnShutdown() {
+ try {
+ AuditStateMap stateMap = new AuditStateMap();
+ stateMap.add(
+ new AuditState(
+ STATE_PANEL_VISIBLE_KEY,
+ Map.of(STATE_PANEL_VISIBLE_PROP,
Boolean.valueOf(fileExplorerPanelVisible))));
+ AuditManager.getActive()
+ .saveAuditStateMap(HopNamespace.getNamespace(), EXPLORER_AUDIT_TYPE,
stateMap);
+ } catch (Exception e) {
+ hopGui.getLog().logError("Error saving explorer perspective state", e);
+ }
+ }
+
+ /**
+ * Restore file explorer panel visibility from saved audit state.
+ *
+ * @return the saved visibility, or null if no saved state exists
+ */
+ private Boolean restoreFileExplorerPanelVisibility() {
+ try {
+ AuditStateMap stateMap =
+ AuditManager.getActive()
+ .loadAuditStateMap(HopNamespace.getNamespace(),
EXPLORER_AUDIT_TYPE);
+ AuditState state = stateMap.get(STATE_PANEL_VISIBLE_KEY);
+ if (state != null) {
+ Object visible = state.getStateMap().get(STATE_PANEL_VISIBLE_PROP);
+ if (visible instanceof Boolean) {
+ return (Boolean) visible;
+ }
+ if (visible != null) {
+ return Boolean.parseBoolean(visible.toString());
+ }
+ }
+ } catch (Exception e) {
+ hopGui.getLog().logError("Error restoring explorer perspective state",
e);
+ }
+ return null;
+ }
+
+ /**
+ * Load saved file explorer panel visibility for the current namespace and
apply it. Call this
+ * after startup (e.g. from HopGui open() async block) and when the project
changes
+ * (ProjectActivated), so the correct namespace is in effect.
+ */
+ public void applyRestoredState() {
+ if (sash == null || sash.isDisposed()) {
+ return;
+ }
+ Boolean savedVisibility = restoreFileExplorerPanelVisibility();
+ if (savedVisibility != null) {
+ fileExplorerPanelVisible = savedVisibility;
+ if (fileExplorerPanelVisible) {
+ sash.setMaximizedControl(null);
+ sash.setWeights(20, 80);
+ } else {
+ sash.setMaximizedControl(tabFolder);
+ }
+ }
+ }
+
public static class DetermineRootFolderExtension {
public HopGui hopGui;
public String rootFolder;
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 023eb92eac..f0f91b3ebe 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
@@ -19,6 +19,7 @@ package org.apache.hop.ui.hopgui.terminal;
import lombok.Getter;
import org.apache.commons.lang.StringUtils;
+import org.apache.hop.core.Const;
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;
@@ -27,6 +28,7 @@ import org.apache.hop.history.AuditList;
import org.apache.hop.history.AuditManager;
import org.apache.hop.history.AuditState;
import org.apache.hop.history.AuditStateMap;
+import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.PropsUi;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.HopNamespace;
@@ -42,6 +44,8 @@ import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
@@ -60,6 +64,8 @@ import org.eclipse.swt.widgets.ToolItem;
@GuiPlugin(name = "Terminal panel", description = "Terminal panel")
public class HopGuiTerminalPanel extends Composite implements TabClosable {
+ private static final Class<?> PKG = HopGuiTerminalPanel.class;
+
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";
@@ -76,8 +82,19 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
private boolean isClearing = false;
private int terminalCounter = 1;
+ /** Font size scale for all terminal tabs (100 = 100%). Persisted and
applied to new tabs. */
+ private int terminalFontSizePercent = 100;
+
private static final String TERMINAL_AUDIT_TYPE = "terminal";
+ /** Reserved state key for panel visibility (minimized vs visible). Not a
terminal tab. */
+ private static final String STATE_PANEL_VISIBLE_KEY = "terminalPanelVisible";
+
+ private static final String STATE_PANEL_VISIBLE_PROP = "visible";
+
+ /** Reserved state key for terminal font size percent (e.g. 100 = 100%). */
+ private static final String STATE_TERMINAL_FONT_SIZE_PERCENT_KEY =
"terminalFontSizePercent";
+
// State map keys
private static final String STATE_TAB_NAME = "tabName";
private static final String STATE_SHELL_PATH = "shellPath";
@@ -154,7 +171,8 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
newTerminalTab = new CTabItem(terminalTabs, SWT.NONE);
newTerminalTab.setText("+");
- newTerminalTab.setToolTipText("Create a new terminal");
+ newTerminalTab.setToolTipText(
+ BaseMessages.getString(PKG, "HopGuiTerminalPanel.NewTab.Tooltip"));
Composite newTerminalPlaceholder = new Composite(terminalTabs, SWT.NONE);
newTerminalTab.setControl(newTerminalPlaceholder);
@@ -166,13 +184,15 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
SWT.Selection,
event -> {
CTabItem item = terminalTabs.getSelection();
- if (item == newTerminalTab) {
- if (isClearing || isClosingTab[0]) {
- return;
- }
+ // When only the "+" tab exists, getSelection() can be null; create
a new terminal.
+ if (item == null && terminalTabs.getItemCount() == 1 && !isClearing
&& !isClosingTab[0]) {
createNewTerminal(null, null);
return;
}
+ if (item == newTerminalTab) {
+ // Creation is handled by MouseDown so we don't double-create when
both fire
+ return;
+ }
if (item != null) {
ITerminalWidget widget = (ITerminalWidget)
item.getData("terminalWidget");
@@ -185,6 +205,17 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
}
});
+ // Ensure + tab click always creates a terminal (e.g. when it's the only
tab and
+ // Selection doesn't fire because selection doesn't change)
+ terminalTabs.addListener(
+ SWT.MouseDown,
+ event -> {
+ CTabItem item = terminalTabs.getItem(new Point(event.x, event.y));
+ if (item == newTerminalTab && !isClearing && !isClosingTab[0]) {
+ createNewTerminal(null, null);
+ }
+ });
+
terminalTabs.addCTabFolder2Listener(
new CTabFolder2Adapter() {
@Override
@@ -240,7 +271,9 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
terminalTab.setText(shellName + " (" + (terminalCounter - 1) + ")");
}
terminalTab.setImage(GuiResource.getInstance().getImageTerminal());
- terminalTab.setToolTipText("Terminal: " + shellPath + " in " +
workingDirectory);
+ terminalTab.setToolTipText(
+ BaseMessages.getString(
+ PKG, "HopGuiTerminalPanel.Tab.Tooltip", shellPath,
workingDirectory));
terminalTab.setData("terminalId", terminalId);
terminalTab.setData("workingDirectory", workingDirectory);
@@ -251,7 +284,8 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
terminalTab.setControl(terminalWidgetComposite);
ITerminalWidget terminalWidget =
- new JediTerminalWidget(terminalWidgetComposite, shellPath,
workingDirectory);
+ new JediTerminalWidget(
+ terminalWidgetComposite, shellPath, workingDirectory,
getTerminalFontSizePercent());
terminalTab.setData("terminalWidget", terminalWidget);
@@ -285,7 +319,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
/** Extract shell name from full path (e.g., "/bin/bash" -> "bash") */
private String extractShellName(String shellPath) {
if (shellPath == null || shellPath.isEmpty()) {
- return "Terminal";
+ return BaseMessages.getString(PKG,
"HopGuiTerminalPanel.ShellName.Default");
}
// Handle Windows paths
@@ -334,6 +368,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
}
layout(true, true);
+ hopGui.refreshSidebarToolbarButtonStates();
}
}
@@ -343,6 +378,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
terminalVisible = false;
verticalSash.setMaximizedControl(perspectiveComposite);
layout(true, true);
+ hopGui.refreshSidebarToolbarButtonStates();
}
}
@@ -450,7 +486,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
return items;
}
- /** Create toolbar with panel controls (maximize/minimize, close) */
+ /** Create toolbar with font size controls and panel controls
(maximize/minimize, close) */
private void createTerminalToolbar() {
ToolBar toolBar = new ToolBar(terminalTabs, SWT.FLAT);
terminalTabs.setTopRight(toolBar, SWT.RIGHT);
@@ -463,10 +499,34 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
toolBar.setBackground(terminalTabs.getBackground());
}
+ // Font size: increase
+ ToolItem increaseFontItem = new ToolItem(toolBar, SWT.PUSH);
+
increaseFontItem.setImage(GuiResource.getInstance().getImage("ui/images/zoom-in.svg",
16, 16));
+ increaseFontItem.setToolTipText(
+ BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Toolbar.IncreaseFont"));
+ increaseFontItem.addListener(SWT.Selection, e ->
increaseTerminalFontSize());
+
+ // Font size: decrease
+ ToolItem decreaseFontItem = new ToolItem(toolBar, SWT.PUSH);
+
decreaseFontItem.setImage(GuiResource.getInstance().getImage("ui/images/zoom-out.svg",
16, 16));
+ decreaseFontItem.setToolTipText(
+ BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Toolbar.DecreaseFont"));
+ decreaseFontItem.addListener(SWT.Selection, e ->
decreaseTerminalFontSize());
+
+ // Font size: reset to 100%
+ ToolItem resetFontItem = new ToolItem(toolBar, SWT.PUSH);
+
resetFontItem.setImage(GuiResource.getInstance().getImage("ui/images/zoom-100.svg",
16, 16));
+ resetFontItem.setToolTipText(
+ BaseMessages.getString(PKG, "HopGuiTerminalPanel.Toolbar.ResetFont"));
+ resetFontItem.addListener(SWT.Selection, e -> resetTerminalFontSize());
+
+ new ToolItem(toolBar, SWT.SEPARATOR);
+
// Maximize/Minimize button
final ToolItem maximizeItem = new ToolItem(toolBar, SWT.PUSH);
maximizeItem.setImage(GuiResource.getInstance().getImageMaximizePanel());
- maximizeItem.setToolTipText("Maximize terminal panel");
+ maximizeItem.setToolTipText(
+ BaseMessages.getString(PKG, "HopGuiTerminalPanel.Toolbar.Maximize"));
maximizeItem.addListener(
SWT.Selection,
e -> {
@@ -474,26 +534,63 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
// Maximize terminal panel
verticalSash.setMaximizedControl(bottomPanelComposite);
maximizeItem.setImage(GuiResource.getInstance().getImageMinimizePanel());
- maximizeItem.setToolTipText("Restore terminal panel");
+ maximizeItem.setToolTipText(
+ BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Toolbar.Restore"));
} else {
// Restore normal split
verticalSash.setMaximizedControl(null);
verticalSash.setWeights(100 - terminalHeightPercent,
terminalHeightPercent);
maximizeItem.setImage(GuiResource.getInstance().getImageMaximizePanel());
- maximizeItem.setToolTipText("Maximize terminal panel");
+ maximizeItem.setToolTipText(
+ BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Toolbar.Maximize"));
}
});
// Close button
final ToolItem closeItem = new ToolItem(toolBar, SWT.PUSH);
closeItem.setImage(GuiResource.getInstance().getImageClose());
- closeItem.setToolTipText("Close terminal panel");
+ closeItem.setToolTipText(BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Toolbar.Close"));
closeItem.addListener(SWT.Selection, e -> hideTerminal());
int height = toolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
terminalTabs.setTabHeight(Math.max(height, terminalTabs.getTabHeight()));
}
+ private void increaseTerminalFontSize() {
+ terminalFontSizePercent = Math.min(200, terminalFontSizePercent + 10);
+ applyFontSizeToAllTerminals();
+ saveOpenTerminals();
+ }
+
+ private void decreaseTerminalFontSize() {
+ terminalFontSizePercent = Math.max(50, terminalFontSizePercent - 10);
+ applyFontSizeToAllTerminals();
+ saveOpenTerminals();
+ }
+
+ private void resetTerminalFontSize() {
+ terminalFontSizePercent = 100;
+ applyFontSizeToAllTerminals();
+ saveOpenTerminals();
+ }
+
+ /** Apply current terminal font size percent to all open terminal tabs. */
+ private void applyFontSizeToAllTerminals() {
+ for (CTabItem item : terminalTabs.getItems()) {
+ if (item == newTerminalTab) {
+ continue;
+ }
+ ITerminalWidget widget = (ITerminalWidget)
item.getData("terminalWidget");
+ if (widget != null) {
+ widget.setFontScalePercent(terminalFontSizePercent);
+ }
+ }
+ }
+
+ private int getTerminalFontSizePercent() {
+ return terminalFontSizePercent;
+ }
+
/** Rename a terminal tab via dialog */
private void renameTerminalTab(CTabItem item) {
if (item == null || item == newTerminalTab) {
@@ -503,7 +600,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
final Text text = new Text(terminalTabs, SWT.BORDER);
text.setText(item.getText());
- org.eclipse.swt.graphics.Rectangle bounds = item.getBounds();
+ Rectangle bounds = item.getBounds();
text.setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
text.moveAbove(null);
@@ -546,7 +643,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
saveOpenTerminals();
}
- /** Save all open terminals */
+ /** Save all open terminals and panel visibility */
private void saveOpenTerminals() {
try {
java.util.List<String> terminalIds = new java.util.ArrayList<>();
@@ -569,6 +666,17 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
}
}
+ // Persist panel visibility so we don't reopen when user had minimized
the terminal
+ stateMap.add(
+ new AuditState(
+ STATE_PANEL_VISIBLE_KEY,
+ java.util.Map.of(STATE_PANEL_VISIBLE_PROP,
Boolean.valueOf(terminalVisible))));
+
+ stateMap.add(
+ new AuditState(
+ STATE_TERMINAL_FONT_SIZE_PERCENT_KEY,
+ java.util.Map.of("value",
Integer.valueOf(terminalFontSizePercent))));
+
AuditList auditList = new AuditList(terminalIds);
AuditManager.getActive()
.storeList(HopNamespace.getNamespace(), TERMINAL_AUDIT_TYPE,
auditList);
@@ -580,7 +688,9 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
.getLog()
.logDebug("Saved " + terminalIds.size() + " open terminal(s) for
current project");
} catch (Exception e) {
- hopGui.getLog().logError("Error saving open terminals", e);
+ hopGui
+ .getLog()
+ .logError(BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Error.SavingTerminals"), e);
}
}
@@ -645,7 +755,7 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
return System.getProperty("user.home");
}
- /** Restore terminals from previous session */
+ /** Restore terminals from previous session; respects saved panel visibility
(minimized state). */
public void restoreTerminals() {
try {
String namespace = HopNamespace.getNamespace();
@@ -663,21 +773,51 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
AuditList auditList = AuditManager.getActive().retrieveList(namespace,
TERMINAL_AUDIT_TYPE);
- if (auditList.getNames().isEmpty()) {
- return;
- }
-
AuditStateMap stateMap;
try {
stateMap =
AuditManager.getActive()
.loadAuditStateMap(HopNamespace.getNamespace(),
TERMINAL_AUDIT_TYPE);
} catch (Exception e) {
- hopGui.getLog().logError("Error loading terminal state map", e);
+ hopGui
+ .getLog()
+ .logError(BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Error.LoadingStateMap"), e);
stateMap = new AuditStateMap();
}
+ // Restore panel visibility: if user had minimized (hidden) the
terminal, keep it hidden
+ boolean savedPanelVisible = true;
+ AuditState panelVisibleState = stateMap.get(STATE_PANEL_VISIBLE_KEY);
+ if (panelVisibleState != null
+ && panelVisibleState.getStateMap() != null
+ && panelVisibleState.getStateMap().get(STATE_PANEL_VISIBLE_PROP)
instanceof Boolean) {
+ savedPanelVisible =
+
Boolean.TRUE.equals(panelVisibleState.getStateMap().get(STATE_PANEL_VISIBLE_PROP));
+ }
+
+ // Restore terminal font size percent
+ AuditState fontSizeState =
stateMap.get(STATE_TERMINAL_FONT_SIZE_PERCENT_KEY);
+ if (fontSizeState != null
+ && fontSizeState.getStateMap() != null
+ && fontSizeState.getStateMap().get("value") != null) {
+ int saved =
Const.toInt(fontSizeState.getStateMap().get("value").toString(), 100);
+ terminalFontSizePercent = Math.max(50, Math.min(200, saved));
+ }
+
+ if (auditList.getNames().isEmpty()) {
+ return;
+ }
+
+ // If panel was hidden when saved, create terminals without showing the
panel
+ boolean wasVisible = terminalVisible;
+ if (!savedPanelVisible) {
+ terminalVisible = true; // prevent createNewTerminal from calling
showTerminal()
+ }
+
for (String terminalId : auditList.getNames()) {
+ if (STATE_PANEL_VISIBLE_KEY.equals(terminalId)) {
+ continue;
+ }
String customTabName = null;
String workingDir = null;
String shellPath = null;
@@ -700,8 +840,15 @@ public class HopGuiTerminalPanel extends Composite
implements TabClosable {
createNewTerminal(workingDir, shellPath, customTabName);
}
+
+ if (!savedPanelVisible) {
+ terminalVisible = wasVisible;
+ hideTerminal();
+ }
} catch (Exception e) {
- hopGui.getLog().logError("Error restoring terminals", e);
+ hopGui
+ .getLog()
+ .logError(BaseMessages.getString(PKG,
"HopGuiTerminalPanel.Error.RestoringTerminals"), e);
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/ITerminalWidget.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/ITerminalWidget.java
index 042615b213..39504b1e35 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/ITerminalWidget.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/ITerminalWidget.java
@@ -67,4 +67,15 @@ public interface ITerminalWidget {
* @return Terminal type string
*/
String getTerminalType();
+
+ /**
+ * Set the terminal font scale as a percentage of the base font size (e.g.
100 = 100%, 120 =
+ * 120%). Applied to all terminal tabs when changed from the toolbar.
Default implementation does
+ * nothing.
+ *
+ * @param percent font scale percent (typically 50–200)
+ */
+ default void setFontScalePercent(int percent) {
+ // no-op for implementations that don't support dynamic font scaling
+ }
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/JediTerminalWidget.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/JediTerminalWidget.java
index 57e19633be..6a3cbc4e5b 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/JediTerminalWidget.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/JediTerminalWidget.java
@@ -21,6 +21,7 @@ import com.jediterm.terminal.TerminalColor;
import com.jediterm.terminal.TextStyle;
import com.jediterm.terminal.TtyConnector;
import com.jediterm.terminal.ui.JediTermWidget;
+import com.jediterm.terminal.ui.TerminalPanel;
import com.jediterm.terminal.ui.settings.DefaultSettingsProvider;
import com.pty4j.PtyProcess;
import com.pty4j.PtyProcessBuilder;
@@ -29,12 +30,15 @@ import java.awt.Frame;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.hop.core.Const;
import org.apache.hop.core.logging.LogChannel;
import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.gui.GuiResource;
import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.custom.StyledText;
@@ -42,6 +46,7 @@ import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
@@ -57,6 +62,9 @@ public class JediTerminalWidget implements ITerminalWidget {
private final String shellPath;
private final String workingDirectory;
+ /** Font scale percent (100 = base size). Mutable so toolbar +/- can update
all tabs. */
+ private final AtomicInteger fontScalePercent = new AtomicInteger(100);
+
private Composite bridgeComposite;
private Frame awtFrame;
private JediTermWidget jediTermWidget;
@@ -64,8 +72,14 @@ public class JediTerminalWidget implements ITerminalWidget {
private Pty4JTtyConnector ttyConnector;
public JediTerminalWidget(Composite parent, String shellPath, String
workingDirectory) {
+ this(parent, shellPath, workingDirectory, 100);
+ }
+
+ public JediTerminalWidget(
+ Composite parent, String shellPath, String workingDirectory, int
fontScalePercent) {
this.shellPath = shellPath;
this.workingDirectory = workingDirectory;
+ this.fontScalePercent.set(Math.max(50, Math.min(200, fontScalePercent)));
createWidget(parent, parent.getDisplay());
@@ -131,7 +145,7 @@ public class JediTerminalWidget implements ITerminalWidget {
// Defer focus until after terminal initialization
bridgeComposite.addListener(
- org.eclipse.swt.SWT.FocusIn,
+ SWT.FocusIn,
event -> {
display.asyncExec(
() -> {
@@ -199,14 +213,18 @@ public class JediTerminalWidget implements
ITerminalWidget {
}
}
- /** Create SettingsProvider with Hop theme and font scaling */
+ /**
+ * Create SettingsProvider with Hop theme and fixed-width font; font size
uses fontScalePercent.
+ */
private DefaultSettingsProvider createHopSettingsProvider(
final boolean isDarkMode, final Display display) {
- // Get the SWT system font size as base
- FontData systemFontData = display.getSystemFont().getFontData()[0];
- int swtFontSize = systemFontData.getHeight();
+ // Hop's fixed-width font (name, style, base size from preferences)
+ Font swtFixedFont = GuiResource.getInstance().getFontFixed();
+ FontData[] fixedFontData = swtFixedFont.getFontData();
+ final String fontName = fixedFontData.length > 0 ?
fixedFontData[0].getName() : "Monospaced";
+ final int baseFontHeight = fixedFontData.length > 0 ?
fixedFontData[0].getHeight() : 12;
+ int swtStyle = fixedFontData.length > 0 ? fixedFontData[0].getStyle() :
SWT.NORMAL;
- // Get font scale from system property, default 1.0
float fontScale = 1.0f;
String manualScale = System.getProperty("JediTerm.fontScale");
if (manualScale != null && !manualScale.isEmpty()) {
@@ -217,11 +235,22 @@ public class JediTerminalWidget implements
ITerminalWidget {
log.logError("JediTerm: Invalid JediTerm.fontScale value: " +
manualScale, e);
}
}
+ final float systemPropScale = fontScale;
- float nativeZoomFactor = (float) PropsUi.getNativeZoomFactor();
- final int targetFontSize = Math.round(swtFontSize * fontScale *
nativeZoomFactor);
+ int awtStyle = java.awt.Font.PLAIN;
+ if ((swtStyle & SWT.BOLD) != 0) awtStyle |= java.awt.Font.BOLD;
+ if ((swtStyle & SWT.ITALIC) != 0) awtStyle |= java.awt.Font.ITALIC;
+ final int finalAwtStyle = awtStyle;
return new DefaultSettingsProvider() {
+ private int computeFontSize() {
+ // baseFontHeight already includes Hop's globalZoomFactor (applied in
GuiResource).
+ // Both SWT and AWT interpret point sizes the same way and apply
screen DPI
+ // internally, so no additional nativeZoomFactor or DPI correction is
needed.
+ int percent = fontScalePercent.get();
+ return Math.max(6, Math.round(baseFontHeight * (percent / 100f) *
systemPropScale));
+ }
+
@Override
public TerminalColor getDefaultBackground() {
if (Const.isWindows() && !isDarkMode) {
@@ -272,16 +301,41 @@ public class JediTerminalWidget implements
ITerminalWidget {
@Override
public java.awt.Font getTerminalFont() {
- return new java.awt.Font("Monospaced", java.awt.Font.PLAIN,
targetFontSize);
+ return new java.awt.Font(fontName, finalAwtStyle, computeFontSize());
}
@Override
public float getTerminalFontSize() {
- return (float) targetFontSize;
+ return (float) computeFontSize();
}
};
}
+ @Override
+ public void setFontScalePercent(int percent) {
+ int clamped = Math.max(50, Math.min(200, percent));
+ fontScalePercent.set(clamped);
+ if (jediTermWidget == null) {
+ return;
+ }
+ // The DefaultSettingsProvider already reads fontScalePercent via
computeFontSize(),
+ // so updating the AtomicInteger is enough for the provider. We now need
to tell
+ // TerminalPanel to re-read the font from the provider and recalculate its
character
+ // grid. TerminalPanel.reinitFontAndResize() does exactly that but is
protected,
+ // so we call it via reflection.
+ java.awt.EventQueue.invokeLater(
+ () -> {
+ try {
+ TerminalPanel panel = jediTermWidget.getTerminalPanel();
+ Method reinit =
TerminalPanel.class.getDeclaredMethod("reinitFontAndResize");
+ reinit.setAccessible(true);
+ reinit.invoke(panel);
+ } catch (Exception e) {
+ log.logDebug("Terminal font reinit: " + e.getMessage());
+ }
+ });
+ }
+
public void startShellProcess() {
try {
// Build PTY process
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
index 672d4d683f..75b9b89cfc 100644
---
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
@@ -18,3 +18,15 @@
HopGuiTerminalPanel.Menu.Terminal=Terminal
HopGuiTerminalPanel.Menu.NewTerminal=New Terminal
+HopGuiTerminalPanel.Toolbar.IncreaseFont=Increase font size
+HopGuiTerminalPanel.Toolbar.DecreaseFont=Decrease font size
+HopGuiTerminalPanel.Toolbar.ResetFont=Reset font to 100%
+HopGuiTerminalPanel.NewTab.Tooltip=Create a new terminal
+HopGuiTerminalPanel.Tab.Tooltip=Terminal: {0} in {1}
+HopGuiTerminalPanel.ShellName.Default=Terminal
+HopGuiTerminalPanel.Toolbar.Maximize=Maximize terminal panel
+HopGuiTerminalPanel.Toolbar.Restore=Restore terminal panel
+HopGuiTerminalPanel.Toolbar.Close=Close terminal panel
+HopGuiTerminalPanel.Error.SavingTerminals=Error saving open terminals
+HopGuiTerminalPanel.Error.LoadingStateMap=Error loading terminal state map
+HopGuiTerminalPanel.Error.RestoringTerminals=Error restoring terminals