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 4d3bdd02a7 improve about dialog, fixes #2699 (#7628)
4d3bdd02a7 is described below

commit 4d3bdd02a7271dc3c6966d16933806011db08613
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Jul 24 18:30:17 2026 +0200

    improve about dialog, fixes #2699 (#7628)
---
 .../apache/hop/ui/hopgui/dialog/AboutDialog.java   | 356 +++++++++++++++++----
 .../dialog/messages/messages_en_US.properties      |  15 +
 2 files changed, 309 insertions(+), 62 deletions(-)

diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/AboutDialog.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/AboutDialog.java
index 1fbcf65e71..7ffab0eb35 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/AboutDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/AboutDialog.java
@@ -17,13 +17,19 @@
 
 package org.apache.hop.ui.hopgui.dialog;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
+import java.util.TreeSet;
+import org.apache.hop.core.Const;
 import org.apache.hop.core.HopVersionProvider;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.core.variables.VariableRegistry;
 import org.apache.hop.core.variables.VariableScope;
 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.dialog.ErrorDialog;
 import org.apache.hop.ui.core.gui.GuiResource;
 import org.apache.hop.ui.hopgui.HopGui;
@@ -42,15 +48,21 @@ import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Dialog;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.ExpandBar;
+import org.eclipse.swt.widgets.ExpandItem;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Link;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Text;
 
-/** A dialog to display version information. */
+/** A dialog to display version and system information. */
 public class AboutDialog extends Dialog {
   private static final Class<?> PKG = AboutDialog.class;
 
+  private static final String HOP_URL = "https://hop.apache.org";;
+  private static final long MB = 1024L * 1024L;
+
+  /** Full set of Java properties shown, sorted, in the collapsible "Advanced" 
section. */
   private static final String[] JAVA_PROPERTIES =
       new String[] {
         "os.name",
@@ -64,6 +76,10 @@ public class AboutDialog extends Dialog {
       };
 
   private Shell shell;
+  private ExpandBar expandBar;
+  private ExpandItem advanced;
+  private Button wOk;
+  private int collapsedHeight;
 
   public AboutDialog(Shell parent) {
     super(parent, SWT.NONE);
@@ -72,83 +88,153 @@ public class AboutDialog extends Dialog {
   public void open() {
     Shell parent = getParent();
     Display display = parent.getDisplay();
-
-    shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | 
SWT.SHEET | SWT.RESIZE);
+    shell = new Shell(parent, BaseDialog.getDefaultDialogStyle());
     shell.setText(BaseMessages.getString(PKG, "AboutDialog.Title"));
     shell.setImage(GuiResource.getInstance().getImageHopUi());
-    shell.setSize(700, 500);
-    shell.setMinimumSize(450, 300);
 
+    int margin = PropsUi.getMargin();
     FormLayout formLayout = new FormLayout();
     formLayout.marginWidth = PropsUi.getFormMargin();
     formLayout.marginHeight = PropsUi.getFormMargin();
     shell.setLayout(formLayout);
     PropsUi.setLook(shell);
 
-    // Composite for logo, app name & version, and centering link
-    Composite composite = new Composite(shell, SWT.NONE);
-    FormData fdLink = new FormData();
-    fdLink.top = new FormAttachment(0, 0);
-    fdLink.left = new FormAttachment(0, 0);
-    fdLink.right = new FormAttachment(100, 0);
-    composite.setLayoutData(fdLink);
-    GridLayout gridLayout = new GridLayout(2, false);
-    gridLayout.marginWidth = 0;
-    composite.setLayout(gridLayout);
-    PropsUi.setLook(composite);
-
-    // Widget application logo
-    Label wLogo = new Label(composite, SWT.CENTER);
+    // ----- Header: logo on the left, name / version / link stacked on the 
right -----
+    Composite header = new Composite(shell, SWT.NONE);
+    PropsUi.setLook(header);
+    GridLayout headerLayout = new GridLayout(2, false);
+    headerLayout.marginWidth = 0;
+    headerLayout.marginHeight = 0;
+    headerLayout.horizontalSpacing = 2 * margin;
+    header.setLayout(headerLayout);
+    FormData fdHeader = new FormData();
+    fdHeader.top = new FormAttachment(0, 0);
+    fdHeader.left = new FormAttachment(0, 0);
+    fdHeader.right = new FormAttachment(100, 0);
+    header.setLayoutData(fdHeader);
+
+    Label wLogo = new Label(header, SWT.NONE);
     wLogo.setImage(
         SwtSvgImageUtil.getImageAsResource(display, "ui/images/logo_hop.svg")
-            .getAsBitmapForSize(display, 100, 100));
-    wLogo.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 
3));
+            .getAsBitmapForSize(display, 90, 90));
+    wLogo.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
+
+    Composite titleBox = new Composite(header, SWT.NONE);
+    PropsUi.setLook(titleBox);
+    GridLayout titleLayout = new GridLayout(1, false);
+    titleLayout.marginWidth = 0;
+    titleLayout.marginHeight = 0;
+    titleBox.setLayout(titleLayout);
+    titleBox.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
 
-    // Widget application name
-    Label wName = new Label(composite, SWT.CENTER);
+    Label wName = new Label(titleBox, SWT.LEFT);
     wName.setText("Apache Hop");
     wName.setFont(GuiResource.getInstance().getFontBold());
-    wName.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+    wName.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
     PropsUi.setLook(wName);
 
-    // Widget application version
-    Text wVersion = new Text(composite, SWT.READ_ONLY | SWT.CENTER);
-    wVersion.setText(this.getVersion());
-    wVersion.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+    Label wVersion = new Label(titleBox, SWT.LEFT);
+    wVersion.setText(getVersionLabel());
+    wVersion.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
     PropsUi.setLook(wVersion);
 
-    Link wLink = new Link(composite, SWT.WRAP | SWT.MULTI);
-    wLink.setText("<a href=\"https://hop.apache.org\";>hop.apache.org</a>");
-    wLink.addListener(
-        SWT.Selection,
-        e -> {
-          try {
-            EnvironmentUtils.getInstance().openUrl("https://hop.apache.org";);
-          } catch (Exception ex) {
-            new ErrorDialog(shell, "Error", "Error opening URL", ex);
-          }
-        });
-    wLink.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+    Link wLink = new Link(titleBox, SWT.NONE);
+    wLink.setText("<a href=\"" + HOP_URL + "\">hop.apache.org</a>");
+    wLink.addListener(SWT.Selection, e -> openUrl());
+    wLink.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
     PropsUi.setLook(wLink);
 
-    // Buttons
-    Button wOk = new Button(shell, SWT.PUSH);
+    // ----- Separator -----
+    Label separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL);
+    FormData fdSeparator = new FormData();
+    fdSeparator.top = new FormAttachment(header, 2 * margin);
+    fdSeparator.left = new FormAttachment(0, 0);
+    fdSeparator.right = new FormAttachment(100, 0);
+    separator.setLayoutData(fdSeparator);
+
+    // ----- "System information" heading with a Copy button on the right -----
+    Button wCopy = new Button(shell, SWT.PUSH);
+    wCopy.setText(BaseMessages.getString(PKG, "AboutDialog.Button.Copy"));
+    wCopy.setToolTipText(BaseMessages.getString(PKG, 
"AboutDialog.Button.Copy.Tooltip"));
+    wCopy.addListener(SWT.Selection, e -> copyToClipboard(wCopy));
+    FormData fdCopy = new FormData();
+    fdCopy.top = new FormAttachment(separator, margin);
+    fdCopy.right = new FormAttachment(100, 0);
+    wCopy.setLayoutData(fdCopy);
+
+    Label wSysInfo = new Label(shell, SWT.LEFT);
+    wSysInfo.setText(BaseMessages.getString(PKG, 
"AboutDialog.SystemInformation"));
+    wSysInfo.setFont(GuiResource.getInstance().getFontBold());
+    PropsUi.setLook(wSysInfo);
+    FormData fdSysInfo = new FormData();
+    fdSysInfo.top = new FormAttachment(wCopy, 0, SWT.CENTER);
+    fdSysInfo.left = new FormAttachment(0, 0);
+    wSysInfo.setLayoutData(fdSysInfo);
+
+    // ----- The essentials: a compact aligned key / value block -----
+    Composite info = new Composite(shell, SWT.NONE);
+    PropsUi.setLook(info);
+    GridLayout infoLayout = new GridLayout(2, false);
+    infoLayout.marginWidth = 0;
+    infoLayout.marginHeight = 0;
+    infoLayout.horizontalSpacing = 3 * margin;
+    infoLayout.verticalSpacing = Math.max(2, margin / 2);
+    info.setLayout(infoLayout);
+    FormData fdInfo = new FormData();
+    fdInfo.top = new FormAttachment(wCopy, margin);
+    fdInfo.left = new FormAttachment(0, 0);
+    fdInfo.right = new FormAttachment(100, 0);
+    info.setLayoutData(fdInfo);
+
+    for (String[] row : getEssentials()) {
+      Label wKey = new Label(info, SWT.LEFT);
+      wKey.setText(row[0]);
+      wKey.setFont(GuiResource.getInstance().getFontBold());
+      GridData gdKey = new GridData(SWT.LEFT, SWT.TOP, false, false);
+      gdKey.widthHint = (int) (140 * PropsUi.getInstance().getZoomFactor());
+      wKey.setLayoutData(gdKey);
+      PropsUi.setLook(wKey);
+
+      Label wValue = new Label(info, SWT.LEFT);
+      wValue.setText(row[1]);
+      wValue.setToolTipText(row[1]);
+      wValue.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+      PropsUi.setLook(wValue);
+    }
+
+    // ----- OK button, anchored at the bottom of the (resizable) dialog -----
+    wOk = new Button(shell, SWT.PUSH);
     wOk.setText(BaseMessages.getString(PKG, "System.Button.OK"));
     wOk.addListener(SWT.Selection, e -> ok());
-    BaseTransformDialog.positionBottomButtons(shell, new Button[] {wOk}, 
PropsUi.getMargin(), null);
+    BaseTransformDialog.positionBottomButtons(shell, new Button[] {wOk}, 
margin, null);
+
+    // ----- Collapsible "Advanced" section with the full property / classpath 
dump -----
+    expandBar = new ExpandBar(shell, SWT.V_SCROLL);
+    PropsUi.setLook(expandBar);
+    FormData fdBar = new FormData();
+    fdBar.top = new FormAttachment(info, 2 * margin);
+    fdBar.left = new FormAttachment(0, 0);
+    fdBar.right = new FormAttachment(100, 0);
+    fdBar.bottom = new FormAttachment(wOk, -margin);
+    expandBar.setLayoutData(fdBar);
 
-    // Widget system properties
-    Text wText =
+    Text wProperties =
         new Text(
-            shell, SWT.READ_ONLY | SWT.WRAP | SWT.MULTI | SWT.BORDER | 
SWT.V_SCROLL | SWT.H_SCROLL);
-    wText.setText(getProperties());
-    FormData fdText = new FormData();
-    fdText.top = new FormAttachment(composite, PropsUi.getMargin());
-    fdText.left = new FormAttachment(0, 0);
-    fdText.right = new FormAttachment(100, 0);
-    fdText.bottom = new FormAttachment(wOk, -PropsUi.getMargin());
-    wText.setLayoutData(fdText);
-    PropsUi.setLook(wText);
+            expandBar,
+            SWT.READ_ONLY | SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | 
SWT.H_SCROLL);
+    wProperties.setText(getProperties());
+    PropsUi.setLook(wProperties);
+
+    advanced = new ExpandItem(expandBar, SWT.NONE);
+    advanced.setText(BaseMessages.getString(PKG, "AboutDialog.Advanced"));
+    advanced.setControl(wProperties);
+    advanced.setHeight((int) (240 * PropsUi.getInstance().getZoomFactor()));
+    advanced.setExpanded(false);
+
+    // Keep the properties view filling the available height and grow / shrink 
on toggle.
+    shell.addListener(SWT.Resize, e -> fillAdvanced());
+    expandBar.addListener(SWT.Expand, e -> display.asyncExec(this::onExpand));
+    expandBar.addListener(SWT.Collapse, e -> 
display.asyncExec(this::onCollapse));
 
     // Detect [X] or ALT-F4 or something that kills this window...
     shell.addShellListener(
@@ -159,6 +245,17 @@ public class AboutDialog extends Dialog {
           }
         });
 
+    // Size the dialog: compact by default, with the Advanced section 
collapsed.
+    double zoom = PropsUi.getInstance().getZoomFactor();
+    int width =
+        Math.clamp(
+            shell.computeSize(SWT.DEFAULT, SWT.DEFAULT).x, (int) (520 * zoom), 
(int) (780 * zoom));
+    shell.setSize(width, (int) (600 * zoom));
+    shell.layout(true, true);
+    collapsedHeight = computeCollapsedHeight(margin);
+    shell.setSize(width, collapsedHeight);
+    shell.setMinimumSize(width, collapsedHeight);
+
     shell.open();
     while (!shell.isDisposed()) {
       if (!display.readAndDispatch()) {
@@ -167,26 +264,161 @@ public class AboutDialog extends Dialog {
     }
   }
 
+  /** Compact height with the Advanced section collapsed, including the window 
trim. */
+  private int computeCollapsedHeight(int margin) {
+    int clientHeight =
+        expandBar.getLocation().y
+            + advanced.getHeaderHeight()
+            + 2 * expandBar.getSpacing()
+            + margin
+            + wOk.getSize().y
+            + PropsUi.getFormMargin();
+    return clientHeight + (shell.getSize().y - shell.getClientArea().height);
+  }
+
+  private void onExpand() {
+    if (shell.isDisposed()) {
+      return;
+    }
+    int grow = (int) (260 * PropsUi.getInstance().getZoomFactor());
+    shell.setSize(shell.getSize().x, Math.max(shell.getSize().y, 
collapsedHeight + grow));
+    shell.layout(true, true);
+    fillAdvanced();
+  }
+
+  private void onCollapse() {
+    if (shell.isDisposed()) {
+      return;
+    }
+    shell.setSize(shell.getSize().x, collapsedHeight);
+    shell.layout(true, true);
+  }
+
+  /** When expanded, size the properties view to fill the space above the OK 
button. */
+  private void fillAdvanced() {
+    if (shell == null || shell.isDisposed() || advanced == null || 
!advanced.getExpanded()) {
+      return;
+    }
+    int height =
+        expandBar.getClientArea().height - advanced.getHeaderHeight() - 2 * 
expandBar.getSpacing();
+    int minHeight = (int) (60 * PropsUi.getInstance().getZoomFactor());
+    if (height < minHeight) {
+      height = minHeight;
+    }
+    if (height != advanced.getHeight()) {
+      advanced.setHeight(height);
+    }
+  }
+
+  /** Copy the full system report to the clipboard and briefly acknowledge on 
the button. */
+  private void copyToClipboard(Button button) {
+    GuiResource.getInstance().toClipboard(getReport());
+    String original = button.getText();
+    button.setText(BaseMessages.getString(PKG, "AboutDialog.Button.Copied"));
+    button
+        .getDisplay()
+        .timerExec(
+            1500,
+            () -> {
+              if (!button.isDisposed()) {
+                button.setText(original);
+              }
+            });
+  }
+
+  private void openUrl() {
+    try {
+      EnvironmentUtils.getInstance().openUrl(HOP_URL);
+    } catch (Exception ex) {
+      new ErrorDialog(shell, "Error", "Error opening URL", ex);
+    }
+  }
+
   protected String getVersion() {
     HopVersionProvider versionProvider = new HopVersionProvider();
     return versionProvider.getVersion()[0];
   }
 
-  private String getProperties() {
-    Set<String> names = 
VariableRegistry.getInstance().getVariableNames(VariableScope.SYSTEM);
-    for (String name : JAVA_PROPERTIES) {
-      names.add(name);
+  private String getVersionLabel() {
+    String version = getVersion();
+    if (version == null || version.isBlank()) {
+      return BaseMessages.getString(PKG, "AboutDialog.DevelopmentBuild");
+    }
+    return version;
+  }
+
+  /** The handful of values most people open this dialog for, shown at the 
top. */
+  private List<String[]> getEssentials() {
+    IVariables variables = HopGui.getInstance().getVariables();
+    List<String[]> rows = new ArrayList<>();
+    rows.add(essential("HopVersion", getVersionLabel()));
+    rows.add(
+        essential(
+            "OperatingSystem",
+            prop(variables, "os.name")
+                + " "
+                + prop(variables, "os.version")
+                + " ("
+                + prop(variables, "os.arch")
+                + ")"));
+    rows.add(
+        essential(
+            "Java",
+            prop(variables, "java.version") + " (" + prop(variables, 
"java.vm.vendor") + ")"));
+    rows.add(essential("JavaSpecification", prop(variables, 
"java.specification.version")));
+    rows.add(essential("FileEncoding", prop(variables, "file.encoding")));
+    rows.add(essential("Memory", getMemory()));
+    rows.add(essential("Processors", 
Integer.toString(Runtime.getRuntime().availableProcessors())));
+    rows.add(essential("ConfigFolder", Const.HOP_CONFIG_FOLDER));
+    return rows;
+  }
+
+  private String[] essential(String key, String value) {
+    return new String[] {
+      BaseMessages.getString(PKG, "AboutDialog.Label." + key), value == null ? 
"" : value
+    };
+  }
+
+  private String getMemory() {
+    Runtime runtime = Runtime.getRuntime();
+    long usedMb = (runtime.totalMemory() - runtime.freeMemory()) / MB;
+    long max = runtime.maxMemory();
+    String maxLabel =
+        max == Long.MAX_VALUE
+            ? BaseMessages.getString(PKG, "AboutDialog.Memory.NoLimit")
+            : (max / MB) + " MB";
+    return usedMb + " MB / " + maxLabel;
+  }
+
+  private String prop(IVariables variables, String name) {
+    String value = variables.getVariable(name);
+    if (value == null || value.isEmpty()) {
+      value = System.getProperty(name, "");
     }
+    return value;
+  }
+
+  /** The complete, sorted list of system properties shown in the Advanced 
section. */
+  private String getProperties() {
+    Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+    
names.addAll(VariableRegistry.getInstance().getVariableNames(VariableScope.SYSTEM));
+    Collections.addAll(names, JAVA_PROPERTIES);
 
     IVariables variables = HopGui.getInstance().getVariables();
     StringBuilder builder = new StringBuilder();
     for (String name : names) {
-      builder.append(name);
-      builder.append('=');
-      builder.append(variables.getVariable(name, ""));
-      builder.append('\n');
+      builder.append(name).append('=').append(variables.getVariable(name, 
"")).append('\n');
     }
+    return builder.toString();
+  }
 
+  /** Essentials followed by the full property dump — what the Copy button 
puts on the clipboard. */
+  private String getReport() {
+    StringBuilder builder = new StringBuilder();
+    for (String[] row : getEssentials()) {
+      builder.append(row[0]).append(": ").append(row[1]).append('\n');
+    }
+    builder.append('\n').append(getProperties());
     return builder.toString();
   }
 
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/dialog/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/dialog/messages/messages_en_US.properties
index 522a535810..edafd0e107 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/dialog/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/dialog/messages/messages_en_US.properties
@@ -16,6 +16,21 @@
 # limitations under the License.
 #
 
+AboutDialog.Advanced=Advanced - all system properties and classpath
+AboutDialog.Button.Copied=Copied
+AboutDialog.Button.Copy=Copy
+AboutDialog.Button.Copy.Tooltip=Copy the system information to the clipboard
+AboutDialog.DevelopmentBuild=development build
+AboutDialog.Label.ConfigFolder=Configuration folder
+AboutDialog.Label.FileEncoding=File encoding
+AboutDialog.Label.HopVersion=Hop version
+AboutDialog.Label.Java=Java
+AboutDialog.Label.JavaSpecification=Java specification
+AboutDialog.Label.Memory=Memory
+AboutDialog.Label.OperatingSystem=Operating system
+AboutDialog.Label.Processors=Processors
+AboutDialog.Memory.NoLimit=no limit
+AboutDialog.SystemInformation=System information
 AboutDialog.Title=About
 AnalyseImpactProgressDialog.Dialog.UnableToAnalyzeImpact.Messages=Unable to 
analyze database impact because of an error\:
 AnalyseImpactProgressDialog.Dialog.UnableToAnalyzeImpact.Title=Error checking 
pipeline

Reply via email to