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 a1a09f0c74 Issue #8147 : Stop metrics grid flicker and add column 
resize controls (#8174)
a1a09f0c74 is described below

commit a1a09f0c7430a7a9c679de7844540079b4faad42
Author: Matt Casters <[email protected]>
AuthorDate: Sat Aug 29 20:27:51 2026 +0200

    Issue #8147 : Stop metrics grid flicker and add column resize controls 
(#8174)
    
    Win32 was packing and restoring column widths on every 1s metrics refresh
    (and TableView.optWidth applied setWidth twice). Timer ticks now grow
    auto-sized columns only; user-dragged widths stick.
    
    View menu and GUI options gain Dynamic column resizing (default on) and
    Fit columns to content so users can freeze the layout on Windows 11.
---
 .../hop/ui/core/widget/TableViewOptWidthTest.java  | 215 ++++++++++++++
 .../main/java/org/apache/hop/ui/core/PropsUi.java  |  14 +
 .../org/apache/hop/ui/core/widget/TableView.java   | 263 +++++++++++------
 .../delegates/HopGuiPipelineGridDelegate.java      | 314 +++++++++++++++------
 .../configuration/tabs/ConfigGuiOptionsTab.java    |  19 ++
 .../core/dialog/messages/messages_en_US.properties |   2 +
 .../ui/hopgui/messages/messages_en_US.properties   |   6 +-
 7 files changed, 657 insertions(+), 176 deletions(-)

diff --git 
a/rcp/src/test/java/org/apache/hop/ui/core/widget/TableViewOptWidthTest.java 
b/rcp/src/test/java/org/apache/hop/ui/core/widget/TableViewOptWidthTest.java
new file mode 100644
index 0000000000..60baf47bc7
--- /dev/null
+++ b/rcp/src/test/java/org/apache/hop/ui/core/widget/TableViewOptWidthTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.widget;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.testing.SwtBotTestBase;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swtbot.swt.finder.SWTBot;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Width-stability contracts for {@link TableView#optWidth}. Live metrics 
refresh used to set each
+ * column twice (and bounce pack-then-restore); these cases pin down the 
behaviour Windows 11 needs
+ * even though this agent runs under GTK.
+ */
+@Tag("uitest")
+class TableViewOptWidthTest extends SwtBotTestBase {
+
+  private static final int NAME_COLUMN = 1;
+  private static final int VALUE_COLUMN = 2;
+  private static final int PREFERRED_WIDTH = 250;
+
+  @Test
+  void optWidthTwiceWithUnchangedContentLeavesColumnWidthsAlone() {
+    withTableView(
+        (view, bot) -> {
+          // Pack after the shell is open: GTK stretches the last column on 
first layout.
+          onUi(
+              () -> {
+                view.optWidth(true);
+                return null;
+              });
+          int[] first = snapshotWidths(view);
+          onUi(
+              () -> {
+                view.optWidth(true);
+                return null;
+              });
+          int[] second = snapshotWidths(view);
+          assertEquals(first.length, second.length);
+          for (int i = 0; i < first.length; i++) {
+            assertEquals(first[i], second[i], "column " + i + " must not move 
on a no-op pack");
+          }
+        });
+  }
+
+  @Test
+  void optWidthHonorsExplicitColumnWidth() {
+    withTableView(
+        (view, bot) -> {
+          onUi(
+              () -> {
+                view.setPreferredColumnWidth(VALUE_COLUMN, PREFERRED_WIDTH);
+                view.optWidth(true);
+                return null;
+              });
+          assertEquals(PREFERRED_WIDTH, columnWidth(view, VALUE_COLUMN));
+          onUi(
+              () -> {
+                view.optWidth(true);
+                return null;
+              });
+          assertEquals(
+              PREFERRED_WIDTH,
+              columnWidth(view, VALUE_COLUMN),
+              "a second pack must not override the preferred width");
+        });
+  }
+
+  @Test
+  void growOnlyWidensForLongerTextButNeverShrinks() {
+    withTableView(
+        (view, bot) -> {
+          onUi(
+              () -> {
+                view.table.getItem(0).setText(VALUE_COLUMN, "1");
+                view.optWidth(true);
+                return null;
+              });
+          int packedShort = columnWidth(view, VALUE_COLUMN);
+
+          onUi(
+              () -> {
+                view.table.getItem(0).setText(VALUE_COLUMN, 
"1,234,567,890,123");
+                view.optWidth(true, 0, true);
+                return null;
+              });
+          int afterGrow = columnWidth(view, VALUE_COLUMN);
+          assertTrue(
+              afterGrow > packedShort,
+              "grow-only should widen for a much longer value (short="
+                  + packedShort
+                  + ", grown="
+                  + afterGrow
+                  + ")");
+
+          onUi(
+              () -> {
+                view.table.getItem(0).setText(VALUE_COLUMN, "1");
+                view.optWidth(true, 0, true);
+                return null;
+              });
+          assertEquals(
+              afterGrow,
+              columnWidth(view, VALUE_COLUMN),
+              "grow-only must not shrink when the cell text gets shorter");
+        });
+  }
+
+  @Test
+  void growOnlyDoesNotTouchUserSizedColumns() {
+    withTableView(
+        (view, bot) -> {
+          onUi(
+              () -> {
+                view.setPreferredColumnWidth(VALUE_COLUMN, PREFERRED_WIDTH);
+                view.optWidth(true);
+                view.table.getItem(0).setText(VALUE_COLUMN, 
"1,234,567,890,123");
+                view.optWidth(true, 0, true);
+                return null;
+              });
+          assertEquals(
+              PREFERRED_WIDTH,
+              columnWidth(view, VALUE_COLUMN),
+              "grow-only must leave a user-sized column alone");
+        });
+  }
+
+  private void withTableView(BiConsumer<TableView, SWTBot> body) {
+    AtomicReference<TableView> viewRef = new AtomicReference<>();
+    withScene(
+        shell -> {
+          shell.setLayout(new FillLayout());
+          shell.setSize(700, 280);
+          ColumnInfo[] columns = {
+            new ColumnInfo("Name", ColumnInfo.COLUMN_TYPE_TEXT, false, true),
+            new ColumnInfo("Value", ColumnInfo.COLUMN_TYPE_TEXT, true, true),
+          };
+          TableView view =
+              new TableView(
+                  new Variables(),
+                  shell,
+                  SWT.BORDER | SWT.FULL_SELECTION,
+                  columns,
+                  1,
+                  true,
+                  null,
+                  PropsUi.getInstance());
+          TableItem row = view.table.getItem(0);
+          row.setText(NAME_COLUMN, "rows");
+          row.setText(VALUE_COLUMN, "1");
+          view.optWidth(true);
+          viewRef.set(view);
+        },
+        bot -> body.accept(viewRef.get(), bot));
+  }
+
+  private static int[] snapshotWidths(TableView view) {
+    return onUi(
+        () -> {
+          int n = view.table.getColumnCount();
+          int[] widths = new int[n];
+          for (int i = 0; i < n; i++) {
+            widths[i] = view.table.getColumn(i).getWidth();
+          }
+          return widths;
+        });
+  }
+
+  private static int columnWidth(TableView view, int index) {
+    return onUi(() -> view.table.getColumn(index).getWidth());
+  }
+
+  private static <T> T onUi(Supplier<T> supplier) {
+    AtomicReference<T> result = new AtomicReference<>();
+    AtomicReference<RuntimeException> failure = new AtomicReference<>();
+    display.syncExec(
+        () -> {
+          try {
+            result.set(supplier.get());
+          } catch (RuntimeException e) {
+            failure.set(e);
+          }
+        });
+    if (failure.get() != null) {
+      throw failure.get();
+    }
+    return result.get();
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java 
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index 9c86b941e9..8202d76abb 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -116,6 +116,8 @@ public class PropsUi extends Props {
   private static final String METRICS_PANEL_SHOW_DATA_VOLUME = 
"MetricsPanel.ShowDataVolume";
   private static final String METRICS_PANEL_SHOW_DATA_VOLUME_IN = 
"MetricsPanel.ShowDataVolumeIn";
   private static final String METRICS_PANEL_SHOW_DATA_VOLUME_OUT = 
"MetricsPanel.ShowDataVolumeOut";
+  private static final String METRICS_PANEL_DYNAMIC_COLUMN_RESIZE =
+      "MetricsPanel.DynamicColumnResize";
 
   public static final int DEFAULT_MAX_EXECUTION_LOGGING_TEXT_SIZE = 2000000;
   private Map<RGB, RGB> contrastingColors;
@@ -740,6 +742,18 @@ public class PropsUi extends Props {
     setProperty(METRICS_PANEL_SHOW_DATA_VOLUME_OUT, show ? YES : NO);
   }
 
+  /**
+   * When true (default), auto-sized metrics columns grow during execution as 
values get wider. When
+   * false, widths stay where they were after the last pack or user drag.
+   */
+  public boolean isMetricsPanelDynamicColumnResize() {
+    return 
YES.equalsIgnoreCase(getProperty(METRICS_PANEL_DYNAMIC_COLUMN_RESIZE, YES));
+  }
+
+  public void setMetricsPanelDynamicColumnResize(boolean dynamic) {
+    setProperty(METRICS_PANEL_DYNAMIC_COLUMN_RESIZE, dynamic ? YES : NO);
+  }
+
   public static void setLook(Widget widget) {
     int style = WIDGET_STYLE_DEFAULT;
     if (widget instanceof Table) {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/widget/TableView.java 
b/ui/src/main/java/org/apache/hop/ui/core/widget/TableView.java
index 9acd521206..b692d763e0 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/TableView.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/TableView.java
@@ -267,6 +267,13 @@ public class TableView extends Composite {
   private final Set<Integer> hiddenDataColumns = new HashSet<>();
   private int[] rememberedWidths;
 
+  /**
+   * Last width we asked each native column to take during {@link #optWidth}. 
Win32/DPI often
+   * returns a slightly different {@code getWidth()} than the value passed to 
{@code setWidth}, so
+   * grow-only mode uses this to avoid applying the same target again every 
refresh.
+   */
+  private int[] lastOptWidthApplied;
+
   /**
    * Create a table to add to a dialog
    *
@@ -3683,6 +3690,21 @@ public class TableView extends Composite {
   }
 
   public void optWidth(boolean header, int nrLines) {
+    optWidth(header, nrLines, false);
+  }
+
+  /**
+   * Size columns to their content.
+   *
+   * @param header include header text in the packed size
+   * @param nrLines max rows to measure, or {@code <= 0} for all rows
+   * @param growOnly when true, never shrink a column and never touch columns 
with an explicit
+   *     {@link ColumnInfo} width (user-sized). Used by live grids that 
refresh often.
+   */
+  public void optWidth(boolean header, int nrLines, boolean growOnly) {
+    if (table == null || table.isDisposed()) {
+      return;
+    }
 
     int extraForMargin;
     if (Const.isWindows()) {
@@ -3692,117 +3714,130 @@ public class TableView extends Composite {
     }
     extraForMargin += EXTRA_COLUMN_WIDTH_MARGIN;
 
-    for (int c = 0; c < table.getColumnCount(); c++) {
-      TableColumn tc = table.getColumn(c);
-      if (c > 0 && hiddenDataColumns.contains(c - 1)) {
-        if (tc.getWidth() != 0) {
-          tc.setWidth(0);
-        }
-        continue;
-      }
-      int max = 0;
-      if (header) {
-        max = TextSizeUtilFacade.textExtent(tc.getText()).x + extraForMargin;
-        if (tc.getImage() != null) {
-          max += tc.getImage().getBounds().width;
+    boolean widthChanged = false;
+    table.setRedraw(false);
+    try {
+      for (int c = 0; c < table.getColumnCount(); c++) {
+        TableColumn tc = table.getColumn(c);
+        if (c > 0 && hiddenDataColumns.contains(c - 1)) {
+          if (tc.getWidth() != 0) {
+            tc.setWidth(0);
+            rememberAppliedColumnWidth(c, 0);
+            widthChanged = true;
+          }
+          continue;
         }
+        int max = 0;
+        if (header) {
+          max = TextSizeUtilFacade.textExtent(tc.getText()).x + extraForMargin;
+          if (tc.getImage() != null) {
+            max += tc.getImage().getBounds().width;
+          }
 
-        // Check if the column has a sorted mark set. In that case, we need the
-        // header to be a bit wider...
-        //
-        if (c == sortField && sortable) {
-          max += ConstUi.SMALL_ICON_SIZE + extraForMargin;
+          // Check if the column has a sorted mark set. In that case, we need 
the
+          // header to be a bit wider...
+          //
+          if (c == sortField && sortable) {
+            max += ConstUi.SMALL_ICON_SIZE + extraForMargin;
+          }
         }
-      }
-      Set<String> columnStrings = new HashSet<>();
-
-      boolean haveToGetTexts = false;
-      if (c > 0) {
-        final ColumnInfo column = columns[c - 1];
-        if (column != null) {
-          switch (column.getType()) {
-            case ColumnInfo.COLUMN_TYPE_TEXT_BUTTON, 
ColumnInfo.COLUMN_TYPE_TEXT:
-              haveToGetTexts = true;
-              break;
-            case ColumnInfo.COLUMN_TYPE_CCOMBO, ColumnInfo.COLUMN_TYPE_FORMAT:
-              haveToGetTexts = true;
-              if (column.getComboValues() != null) {
-                for (String comboValue : columns[c - 1].getComboValues()) {
-                  columnStrings.add(comboValue);
+        Set<String> columnStrings = new HashSet<>();
+
+        boolean haveToGetTexts = false;
+        if (c > 0) {
+          final ColumnInfo column = columns[c - 1];
+          if (column != null) {
+            switch (column.getType()) {
+              case ColumnInfo.COLUMN_TYPE_TEXT_BUTTON, 
ColumnInfo.COLUMN_TYPE_TEXT:
+                haveToGetTexts = true;
+                break;
+              case ColumnInfo.COLUMN_TYPE_CCOMBO, 
ColumnInfo.COLUMN_TYPE_FORMAT:
+                haveToGetTexts = true;
+                if (column.getComboValues() != null) {
+                  for (String comboValue : columns[c - 1].getComboValues()) {
+                    columnStrings.add(comboValue);
+                  }
                 }
-              }
-              break;
-            case ColumnInfo.COLUMN_TYPE_BUTTON:
-              columnStrings.add(column.getButtonText());
-              break;
-            default:
-              break;
+                break;
+              case ColumnInfo.COLUMN_TYPE_BUTTON:
+                columnStrings.add(column.getButtonText());
+                break;
+              default:
+                break;
+            }
           }
+        } else {
+          haveToGetTexts = true;
         }
-      } else {
-        haveToGetTexts = true;
-      }
 
-      if (haveToGetTexts) {
-        for (int r = 0; r < table.getItemCount() && (r < nrLines || nrLines <= 
0); r++) {
-          TableItem ti = table.getItem(r);
-          if (ti != null) {
-            // Size on what is actually drawn: a long or multi-line value is 
shown shortened, so
-            // measuring the full value would stretch the column for text 
nobody sees.
-            String display = customCellText(ti, c);
-            columnStrings.add(display != null ? display : ti.getText(c));
+        if (haveToGetTexts) {
+          for (int r = 0; r < table.getItemCount() && (r < nrLines || nrLines 
<= 0); r++) {
+            TableItem ti = table.getItem(r);
+            if (ti != null) {
+              // Size on what is actually drawn: a long or multi-line value is 
shown shortened, so
+              // measuring the full value would stretch the column for text 
nobody sees.
+              String display = customCellText(ti, c);
+              columnStrings.add(display != null ? display : ti.getText(c));
+            }
           }
         }
-      }
 
-      for (String str : columnStrings) {
-        int len = TextSizeUtilFacade.textExtent(str == null ? "" : str).x;
-        if (len > max) {
-          max = len;
+        for (String str : columnStrings) {
+          int len = TextSizeUtilFacade.textExtent(str == null ? "" : str).x;
+          if (len > max) {
+            max = len;
+          }
         }
-      }
 
-      try {
-        max += extraForMargin;
-        if (c > 0) {
-          max += extraForMargin; // margins on both sides of the column
-        }
-        if (Const.isWindows() || Const.isLinux()) {
+        try {
           max += extraForMargin;
-        }
+          if (c > 0) {
+            max += extraForMargin; // margins on both sides of the column
+          }
+          if (Const.isWindows() || Const.isLinux()) {
+            max += extraForMargin;
+          }
 
-        // The line number column
-        //
-        if (c == 0) {
-          if (tc.getWidth() != max) {
-            tc.setWidth(max);
+          int desiredWidth = preferredColumnWidth(c);
+          int target;
+          if (c == 0) {
+            // Line-number column: always pack unless the caller marked a 
preferred width.
+            target = desiredWidth > 0 ? desiredWidth : max;
+          } else if (desiredWidth > 0) {
+            target = desiredWidth;
+          } else {
+            target = max;
           }
-        } else {
-          int desiredWidth = columns[c - 1].getWidth();
-          if (desiredWidth > 0) {
-            if (tc.getWidth() != desiredWidth) {
-              tc.setWidth(desiredWidth);
+
+          if (growOnly) {
+            if (desiredWidth > 0) {
+              continue;
             }
-          } else {
-            if (tc.getWidth() != max) {
-              tc.setWidth(max);
+            int baseline = Math.max(tc.getWidth(), lastAppliedColumnWidth(c));
+            if (max <= baseline) {
+              continue;
             }
+            target = max;
           }
-        }
 
-        if (tc.getWidth() != max) {
-          if (c > 0 && columns[c - 1].getWidth() > 0) {
-            tc.setWidth(columns[c - 1].getWidth());
+          if (tc.getWidth() != target) {
+            tc.setWidth(target);
+            rememberAppliedColumnWidth(c, target);
+            widthChanged = true;
           } else {
-            tc.setWidth(max);
+            rememberAppliedColumnWidth(c, target);
           }
+        } catch (Exception e) {
+          // Ignore errors
+          LogChannel.UI.logError("error in TableView", e);
         }
-      } catch (Exception e) {
-        // Ignore errors
-        LogChannel.UI.logError("error in TableView", e);
+      }
+    } finally {
+      if (!table.isDisposed()) {
+        table.setRedraw(true);
       }
     }
-    if (table.isListening(SWT.Resize)) {
+    if (widthChanged && table.isListening(SWT.Resize)) {
       Event resizeEvent = new Event();
       resizeEvent.widget = table;
       resizeEvent.type = SWT.Resize;
@@ -3813,6 +3848,60 @@ public class TableView extends Composite {
     unEdit();
   }
 
+  /**
+   * Records a preferred width for a table column ({@code 0} is the "#" index 
column). Later {@link
+   * #optWidth} calls honor this instead of packing, and grow-only mode will 
not change it.
+   *
+   * @param tableColumnIndex native table column index
+   * @param width pixel width
+   */
+  public void setPreferredColumnWidth(int tableColumnIndex, int width) {
+    if (tableColumnIndex == 0) {
+      if (numberColumn != null) {
+        numberColumn.setWidth(width);
+      }
+      return;
+    }
+    int dataIndex = tableColumnIndex - 1;
+    if (dataIndex >= 0 && dataIndex < columns.length) {
+      columns[dataIndex].setWidth(width);
+    }
+  }
+
+  private int preferredColumnWidth(int tableColumnIndex) {
+    if (tableColumnIndex == 0) {
+      return numberColumn != null ? numberColumn.getWidth() : -1;
+    }
+    int dataIndex = tableColumnIndex - 1;
+    if (dataIndex >= 0 && dataIndex < columns.length && columns[dataIndex] != 
null) {
+      return columns[dataIndex].getWidth();
+    }
+    return -1;
+  }
+
+  private int lastAppliedColumnWidth(int tableColumnIndex) {
+    if (lastOptWidthApplied == null || tableColumnIndex >= 
lastOptWidthApplied.length) {
+      return -1;
+    }
+    return lastOptWidthApplied[tableColumnIndex];
+  }
+
+  private void rememberAppliedColumnWidth(int tableColumnIndex, int width) {
+    int count = table.getColumnCount();
+    if (lastOptWidthApplied == null || lastOptWidthApplied.length != count) {
+      int[] next = new int[count];
+      Arrays.fill(next, -1);
+      if (lastOptWidthApplied != null) {
+        System.arraycopy(
+            lastOptWidthApplied, 0, next, 0, 
Math.min(lastOptWidthApplied.length, count));
+      }
+      lastOptWidthApplied = next;
+    }
+    if (tableColumnIndex >= 0 && tableColumnIndex < 
lastOptWidthApplied.length) {
+      lastOptWidthApplied[tableColumnIndex] = width;
+    }
+  }
+
   public void optimizeTableView() {
     removeEmptyRows();
     setRowNums();
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 1c0bdf8404..43ab143b95 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
@@ -20,6 +20,7 @@ package org.apache.hop.ui.hopgui.file.pipeline.delegates;
 import java.text.DecimalFormat;
 import java.text.NumberFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -74,7 +75,6 @@ import org.eclipse.swt.layout.FormLayout;
 import org.eclipse.swt.layout.RowData;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.MenuItem;
 import org.eclipse.swt.widgets.Table;
@@ -104,6 +104,8 @@ public class HopGuiPipelineGridDelegate {
 
   public static final long UPDATE_TIME_VIEW = 1000L;
 
+  private static final int METRICS_TABLE_STYLE = SWT.BORDER | 
SWT.FULL_SELECTION | SWT.MULTI;
+
   private final HopGui hopGui;
   private final HopGuiPipelineGraph pipelineGraph;
 
@@ -156,11 +158,17 @@ public class HopGuiPipelineGridDelegate {
   private final Map<String, Integer> metricsColumnWidths = new HashMap<>();
 
   /**
-   * True while we are programmatically restoring column widths. Resize 
listeners must not persist
+   * True while we are programmatically changing column widths. Resize 
listeners must not persist
    * those changes or they overwrite the user's saved widths.
    */
   private boolean restoringColumnWidths = false;
 
+  /**
+   * When true, the next {@link #refreshView()} fully packs columns (View menu 
toggled units or
+   * visibility). Steady-state timer ticks only grow auto-sized columns.
+   */
+  private boolean packColumnsOnNextRefresh = false;
+
   /**
    * @param hopGui Hop GUI instance
    * @param pipelineGraph the pipeline graph that owns this delegate
@@ -196,6 +204,7 @@ public class HopGuiPipelineGridDelegate {
         gridSortColumn = 0;
         gridSortDescending = false;
         lastEngineMetrics = null;
+        packColumnsOnNextRefresh = true;
         startRefreshMetricsTimer();
         return;
       }
@@ -304,33 +313,12 @@ public class HopGuiPipelineGridDelegate {
     columns[12].setAlignment(SWT.RIGHT);
     columns[13].setAlignment(SWT.RIGHT);
 
-    pipelineGridView =
-        new TableView(
-            pipelineGraph.getVariables(),
-            pipelineGridComposite,
-            SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI,
-            columns,
-            1,
-            true, // readonly
-            null,
-            hopGui.getProps(),
-            true,
-            null,
-            false,
-            false); // no TableView toolbar; copy/filter are on our toolbar
-    FormData fdView = new FormData();
-    fdView.left = new FormAttachment(0, 0);
-    fdView.right = new FormAttachment(100, 0);
-    fdView.top = new FormAttachment(toolbar, 0);
-    fdView.bottom = new FormAttachment(100, 0);
-    pipelineGridView.setLayoutData(fdView);
-    pipelineGridView.setSortable(true);
+    pipelineGridView = createMetricsTableView(columns, 1);
+    metricsColumnKeys = placeholderColumnKeys();
+    attachColumnWidthListeners();
     attachMetricsTableListeners(pipelineGridView);
-
-    ColumnInfo numberColumn = pipelineGridView.getNumberColumn();
-    IValueMeta numberColumnValueMeta =
-        new ValueMetaString("#", 
HopGuiPipelineGridDelegate::subTransformCompare);
-    numberColumn.setValueMeta(numberColumnValueMeta);
+    applyNumberColumnComparator();
+    previousRefreshColumns = Arrays.asList(columns);
 
     startRefreshMetricsTimer();
     pipelineGridTab.addDisposeListener(disposeEvent -> 
stopRefreshMetricsTimer());
@@ -423,9 +411,10 @@ public class HopGuiPipelineGridDelegate {
   }
 
   /**
-   * Add a "View" dropdown to the toolbar with checkable items for metrics 
panel options (hide
-   * units, hide columns). Syncs with PropsUi so options dialog and toolbar 
stay in sync. Uses a
-   * CLabel with SWT.CENTER so the text is centered consistently across 
platforms (e.g. Linux).
+   * Add a "View" dropdown to the toolbar with checkable items for metrics 
panel options (units,
+   * column resizing, hide columns). Syncs with PropsUi so options dialog and 
toolbar stay in sync.
+   * Uses a CLabel with SWT.CENTER so the text is centered consistently across 
platforms (e.g.
+   * Linux).
    */
   private void addMetricsViewDropdown(Control toolbarControl) {
     if (!(toolbarControl instanceof ToolBar tb)) {
@@ -465,6 +454,30 @@ public class HopGuiPipelineGridDelegate {
             setMetricsOptionAndRefresh(
                 () -> 
props.setMetricsPanelShowUnits(showUnitsItem.getSelection())));
 
+    MenuItem dynamicResizeItem = new MenuItem(menu, SWT.CHECK);
+    dynamicResizeItem.setText(
+        BaseMessages.getString(PKG, 
"PipelineLog.MetricsView.DynamicColumnResize"));
+    dynamicResizeItem.setToolTipText(
+        BaseMessages.getString(PKG, 
"PipelineLog.MetricsView.DynamicColumnResize.Tooltip"));
+    dynamicResizeItem.setSelection(props.isMetricsPanelDynamicColumnResize());
+    dynamicResizeItem.addListener(
+        SWT.Selection,
+        e ->
+            setMetricsOptionAndRefresh(
+                () -> 
props.setMetricsPanelDynamicColumnResize(dynamicResizeItem.getSelection()),
+                false));
+
+    MenuItem fitColumnsItem = new MenuItem(menu, SWT.PUSH);
+    fitColumnsItem.setText(BaseMessages.getString(PKG, 
"PipelineLog.MetricsView.FitColumns"));
+    fitColumnsItem.setToolTipText(
+        BaseMessages.getString(PKG, 
"PipelineLog.MetricsView.FitColumns.Tooltip"));
+    fitColumnsItem.addListener(
+        SWT.Selection,
+        e -> {
+          packColumnsOnNextRefresh = true;
+          refreshView();
+        });
+
     new MenuItem(menu, SWT.SEPARATOR);
 
     addMetricsViewMenuItem(
@@ -546,12 +559,17 @@ public class HopGuiPipelineGridDelegate {
   }
 
   private void setMetricsOptionAndRefresh(Runnable setOption) {
+    setMetricsOptionAndRefresh(setOption, true);
+  }
+
+  private void setMetricsOptionAndRefresh(Runnable setOption, boolean 
packColumns) {
     setOption.run();
     try {
       HopConfig.getInstance().saveToFile();
     } catch (Exception ignored) {
       // best-effort persist
     }
+    packColumnsOnNextRefresh = packColumns;
     refreshView();
   }
 
@@ -678,21 +696,37 @@ public class HopGuiPipelineGridDelegate {
       List<List<String>> componentStringsList =
           buildComponentStringsList(engineMetrics, shownComponents, 
usedMetrics);
 
-      recreateTableIfColumnsChanged(columns, shownComponents.size(), 
usedMetrics);
+      boolean recreated =
+          recreateTableIfColumnsChanged(columns, shownComponents.size(), 
usedMetrics);
+      boolean packColumns = packColumnsOnNextRefresh;
+      packColumnsOnNextRefresh = false;
 
       sortComponentStringsByColumn(componentStringsList, gridSortColumn, 
gridSortDescending);
 
+      Table table = pipelineGridView.table;
+      int topIndex = table.getTopIndex();
+      int[] selection = table.getSelectionIndices();
+
       int errorCol = indexOfMetric(usedMetrics, Pipeline.METRIC_ERROR);
       fillTableRows(componentStringsList, errorCol);
 
       setSortIndicator();
 
-      // Ignore resize events from optWidth and restoreColumnWidths so we 
don't overwrite saved
-      // widths
+      // Ignore resize events from optWidth / restore so we don't treat them 
as user drags.
       restoringColumnWidths = true;
-      pipelineGridView.optWidth(true);
-      restoreColumnWidths(usedMetrics);
+      try {
+        if (packColumns && !recreated) {
+          pipelineGridView.optWidth(true);
+          restoreColumnWidths(usedMetrics);
+        } else if (PropsUi.getInstance().isMetricsPanelDynamicColumnResize()) {
+          // Recreate already packed in the constructor; timer ticks only grow 
auto-sized columns.
+          pipelineGridView.optWidth(true, 0, true);
+        }
+      } finally {
+        restoringColumnWidths = false;
+      }
       previousRefreshColumns = columns;
+      restoreTableViewport(table, topIndex, selection);
       updateEditButtonState();
     } finally {
       refreshViewLock.unlock();
@@ -918,39 +952,100 @@ public class HopGuiPipelineGridDelegate {
     return componentStringsList;
   }
 
-  private void recreateTableIfColumnsChanged(
+  /**
+   * Rebuilds the metrics {@link TableView} when the column set changes. 
Returns true when a new
+   * table was created (constructor already packed columns).
+   */
+  private boolean recreateTableIfColumnsChanged(
       List<ColumnInfo> columns, int rowCount, List<IEngineMetric> usedMetrics) 
{
     if (!haveColumnsChanged(columns)) {
-      return;
+      return false;
     }
     saveColumnWidths();
     pipelineGridView.dispose();
-    pipelineGridView =
+    pipelineGridView = createMetricsTableView(columns.toArray(new 
ColumnInfo[0]), rowCount);
+    metricsColumnKeys = getColumnKeys(usedMetrics);
+    applyNumberColumnComparator();
+    attachColumnWidthListeners();
+    attachMetricsTableListeners(pipelineGridView);
+    pipelineGridComposite.layout(true, true);
+    restoreColumnWidths(usedMetrics);
+    return true;
+  }
+
+  private TableView createMetricsTableView(ColumnInfo[] columns, int rowCount) 
{
+    TableView view =
         new TableView(
             pipelineGraph.getVariables(),
             pipelineGridComposite,
-            SWT.NONE,
-            columns.toArray(new ColumnInfo[0]),
-            rowCount,
+            METRICS_TABLE_STYLE,
+            columns,
+            Math.max(rowCount, 1),
             true,
             null,
-            PropsUi.getInstance(),
+            hopGui.getProps(),
             true,
             null,
             false,
             false); // no TableView toolbar; copy/filter are on our toolbar
-    pipelineGridView.setSortable(true);
-    metricsColumnKeys = getColumnKeys(usedMetrics);
-    attachColumnWidthListeners();
-    attachMetricsTableListeners(pipelineGridView);
+    view.setSortable(true);
     FormData fdView = new FormData();
     fdView.left = new FormAttachment(0, 0);
     fdView.right = new FormAttachment(100, 0);
     fdView.top = new FormAttachment(toolbar, 0);
     fdView.bottom = new FormAttachment(100, 0);
-    pipelineGridView.setLayoutData(fdView);
-    pipelineGridComposite.layout(true, true);
-    restoreColumnWidths(usedMetrics);
+    view.setLayoutData(fdView);
+    return view;
+  }
+
+  private void applyNumberColumnComparator() {
+    if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+      return;
+    }
+    ColumnInfo numberColumn = pipelineGridView.getNumberColumn();
+    IValueMeta numberColumnValueMeta =
+        new ValueMetaString("#", 
HopGuiPipelineGridDelegate::subTransformCompare);
+    numberColumn.setValueMeta(numberColumnValueMeta);
+  }
+
+  /**
+   * Column keys used by the placeholder table created in {@link 
#addPipelineGrid()} before the
+   * first engine-metrics refresh rebuilds the real column set.
+   */
+  private static List<String> placeholderColumnKeys() {
+    List<String> keys = new ArrayList<>();
+    keys.add("#");
+    keys.add("TransformName");
+    keys.add("Copy");
+    keys.add(Pipeline.METRIC_NAME_INPUT);
+    keys.add(Pipeline.METRIC_NAME_READ);
+    keys.add(Pipeline.METRIC_NAME_WRITTEN);
+    keys.add(Pipeline.METRIC_NAME_OUTPUT);
+    keys.add(Pipeline.METRIC_NAME_UPDATED);
+    keys.add(Pipeline.METRIC_NAME_REJECTED);
+    keys.add(Pipeline.METRIC_NAME_ERROR);
+    keys.add(Pipeline.METRIC_NAME_BUFFER_IN);
+    keys.add(Pipeline.METRIC_NAME_BUFFER_OUT);
+    keys.add("duration");
+    keys.add("speed");
+    keys.add("status");
+    return keys;
+  }
+
+  private void restoreTableViewport(Table table, int topIndex, int[] 
selection) {
+    if (table == null || table.isDisposed()) {
+      return;
+    }
+    int itemCount = table.getItemCount();
+    if (selection != null && selection.length > 0) {
+      int[] valid = Arrays.stream(selection).filter(i -> i >= 0 && i < 
itemCount).toArray();
+      if (valid.length > 0) {
+        table.setSelection(valid);
+      }
+    }
+    if (topIndex >= 0 && topIndex < itemCount) {
+      table.setTopIndex(topIndex);
+    }
   }
 
   /**
@@ -971,54 +1066,65 @@ public class HopGuiPipelineGridDelegate {
     return keys;
   }
 
-  /** Saves current column widths to the session map so they can be restored 
after refresh. */
+  /**
+   * Refreshes native widths for columns the user has already resized. 
Auto-sized columns are not
+   * stored so grow-only packing can still widen them as counts grow.
+   */
   private void saveColumnWidths() {
     if (pipelineGridView == null || pipelineGridView.isDisposed()) {
       return;
     }
-    if (metricsColumnKeys == null || metricsColumnKeys.size() == 0) {
+    if (metricsColumnKeys == null || metricsColumnKeys.isEmpty() || 
metricsColumnWidths.isEmpty()) {
       return;
     }
-    org.eclipse.swt.widgets.Table table = pipelineGridView.table;
+    Table table = pipelineGridView.table;
     int n = Math.min(table.getColumnCount(), metricsColumnKeys.size());
     for (int i = 0; i < n; i++) {
+      String key = metricsColumnKeys.get(i);
+      if (!metricsColumnWidths.containsKey(key)) {
+        continue;
+      }
       int w = table.getColumn(i).getWidth();
       if (w > 0) {
-        metricsColumnWidths.put(metricsColumnKeys.get(i), w);
+        metricsColumnWidths.put(key, w);
       }
     }
   }
 
   /**
-   * Restores column widths from the session map onto the table. Needed for 
the "#" column (no
-   * ColumnInfo) and as a fallback so widths are applied after optWidth.
+   * Restores user-resized column widths from the session map onto the table. 
Needed for the "#"
+   * column (no data {@link ColumnInfo}) and after a table recreate.
    */
   private void restoreColumnWidths(List<IEngineMetric> usedMetrics) {
     if (pipelineGridView == null || pipelineGridView.isDisposed()) {
       return;
     }
+    if (metricsColumnWidths.isEmpty()) {
+      return;
+    }
+    boolean nested = restoringColumnWidths;
     restoringColumnWidths = true;
+    Table table = pipelineGridView.table;
+    table.setRedraw(false);
     try {
       List<String> keys = getColumnKeys(usedMetrics);
-      Table table = pipelineGridView.table;
       int n = Math.min(table.getColumnCount(), keys.size());
       for (int i = 0; i < n; i++) {
         String key = keys.get(i);
         Integer w = metricsColumnWidths.get(key);
         if (w != null && w > 0) {
           TableColumn tc = table.getColumn(i);
-          tc.setWidth(w);
+          if (tc.getWidth() != w) {
+            tc.setWidth(w);
+          }
+          pipelineGridView.setPreferredColumnWidth(i, w);
         }
       }
     } finally {
-      // Clear flag asynchronously so any Resize events queued by setWidth() 
are still ignored
-      Display display =
-          pipelineGridView != null && !pipelineGridView.isDisposed()
-              ? pipelineGridView.table.getDisplay()
-              : null;
-      if (display != null && !display.isDisposed()) {
-        display.asyncExec(() -> restoringColumnWidths = false);
-      } else {
+      if (!table.isDisposed()) {
+        table.setRedraw(true);
+      }
+      if (!nested) {
         restoringColumnWidths = false;
       }
     }
@@ -1029,12 +1135,13 @@ public class HopGuiPipelineGridDelegate {
     if (pipelineGridView == null || pipelineGridView.isDisposed()) {
       return;
     }
-    if (metricsColumnKeys == null || metricsColumnKeys.size() == 0) {
+    if (metricsColumnKeys == null || metricsColumnKeys.isEmpty()) {
       return;
     }
     Table table = pipelineGridView.table;
     for (int i = 0; i < table.getColumnCount() && i < 
metricsColumnKeys.size(); i++) {
       final String key = metricsColumnKeys.get(i);
+      final int columnIndex = i;
       TableColumn col = table.getColumn(i);
       col.addListener(
           SWT.Resize,
@@ -1043,32 +1150,56 @@ public class HopGuiPipelineGridDelegate {
               return;
             }
             if (!col.isDisposed()) {
-              metricsColumnWidths.put(key, col.getWidth());
+              int w = col.getWidth();
+              if (w > 0) {
+                metricsColumnWidths.put(key, w);
+                pipelineGridView.setPreferredColumnWidth(columnIndex, w);
+              }
             }
           });
     }
   }
 
   private void fillTableRows(List<List<String>> componentStringsList, int 
errorColumnIndex) {
-    while (pipelineGridView.table.getItemCount() > 
componentStringsList.size()) {
-      pipelineGridView.table.remove(pipelineGridView.table.getItemCount() - 1);
-    }
-    int errorsCol = 3 + errorColumnIndex; // row has #, name, copy, then 
metrics
-    Color errorBg = GuiResource.getInstance().getColorLightRed();
-    for (int row = 0; row < componentStringsList.size(); row++) {
-      List<String> componentStrings = componentStringsList.get(row);
-      TableItem item;
-      if (row < pipelineGridView.table.getItemCount()) {
-        item = pipelineGridView.table.getItem(row);
-      } else {
-        item = new TableItem(pipelineGridView.table, SWT.NONE);
+    Table table = pipelineGridView.table;
+    table.setRedraw(false);
+    try {
+      while (table.getItemCount() > componentStringsList.size()) {
+        table.remove(table.getItemCount() - 1);
+      }
+      int errorsCol = 3 + errorColumnIndex; // row has #, name, copy, then 
metrics
+      Color errorBg = GuiResource.getInstance().getColorLightRed();
+      for (int row = 0; row < componentStringsList.size(); row++) {
+        List<String> componentStrings = componentStringsList.get(row);
+        TableItem item;
+        if (row < table.getItemCount()) {
+          item = table.getItem(row);
+        } else {
+          item = new TableItem(table, SWT.NONE);
+        }
+        applyRowTextsIfChanged(item, componentStrings);
+        if (errorColumnIndex >= 0 && errorsCol < componentStrings.size()) {
+          long err = parseFormattedLong(componentStrings.get(errorsCol));
+          boolean wantError = err > 0;
+          boolean hasError = errorBg.equals(item.getBackground());
+          if (wantError != hasError) {
+            item.setBackground(wantError ? errorBg : null);
+          }
+        }
       }
-      for (int col = 0; col < componentStrings.size(); col++) {
-        item.setText(col, componentStrings.get(col));
+    } finally {
+      if (!table.isDisposed()) {
+        table.setRedraw(true);
       }
-      if (errorColumnIndex >= 0 && errorsCol < componentStrings.size()) {
-        long err = parseFormattedLong(componentStrings.get(errorsCol));
-        item.setBackground(err > 0 ? errorBg : null);
+    }
+  }
+
+  /** Anti-flicker: only push cell text that actually changed. */
+  private static void applyRowTextsIfChanged(TableItem item, List<String> 
texts) {
+    for (int col = 0; col < texts.size(); col++) {
+      String next = Const.NVL(texts.get(col), "");
+      if (!next.equals(item.getText(col))) {
+        item.setText(col, next);
       }
     }
   }
@@ -1247,9 +1378,16 @@ public class HopGuiPipelineGridDelegate {
       return;
     }
     Table table = pipelineGridView.table;
-    if (gridSortColumn >= 0 && gridSortColumn < table.getColumnCount()) {
-      table.setSortColumn(table.getColumn(gridSortColumn));
-      table.setSortDirection(gridSortDescending ? SWT.DOWN : SWT.UP);
+    if (gridSortColumn < 0 || gridSortColumn >= table.getColumnCount()) {
+      return;
+    }
+    TableColumn wanted = table.getColumn(gridSortColumn);
+    int direction = gridSortDescending ? SWT.DOWN : SWT.UP;
+    if (table.getSortColumn() != wanted) {
+      table.setSortColumn(wanted);
+    }
+    if (table.getSortDirection() != direction) {
+      table.setSortDirection(direction);
     }
   }
 
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
index a69c6f5483..4bfdca989c 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
@@ -115,6 +115,7 @@ public class ConfigGuiOptionsTab {
   private Text wMaxPreviewCellLength;
   private Button wShowPreviewLineBreaks;
   private Button wMetricsPanelShowUnits;
+  private Button wMetricsPanelDynamicColumnResize;
   private Button wMetricsPanelShowInput;
   private Button wMetricsPanelShowRead;
   private Button wMetricsPanelShowOutput;
@@ -215,6 +216,10 @@ public class ConfigGuiOptionsTab {
       }
       if (wMetricsPanelShowUnits != null && 
!wMetricsPanelShowUnits.isDisposed()) {
         wMetricsPanelShowUnits.setSelection(props.isMetricsPanelShowUnits());
+        if (wMetricsPanelDynamicColumnResize != null
+            && !wMetricsPanelDynamicColumnResize.isDisposed()) {
+          
wMetricsPanelDynamicColumnResize.setSelection(props.isMetricsPanelDynamicColumnResize());
+        }
         wMetricsPanelShowInput.setSelection(props.isMetricsPanelShowInput());
         wMetricsPanelShowRead.setSelection(props.isMetricsPanelShowRead());
         wMetricsPanelShowOutput.setSelection(props.isMetricsPanelShowOutput());
@@ -1024,6 +1029,16 @@ public class ConfigGuiOptionsTab {
             margin);
     lastMetricsPanelControl = wMetricsPanelShowUnits;
 
+    wMetricsPanelDynamicColumnResize =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.DynamicColumnResize.Label",
+            "EnterOptionsDialog.MetricsPanel.DynamicColumnResize.ToolTip",
+            props.isMetricsPanelDynamicColumnResize(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelDynamicColumnResize;
+
     wMetricsPanelShowInput =
         createCheckbox(
             metricsPanelContent,
@@ -1336,6 +1351,10 @@ public class ConfigGuiOptionsTab {
         Const.toInt(wMaxPreviewCellLength.getText(), 
props.getMaxPreviewCellLength()));
     
props.setShowPreviewLineBreaksAsSymbols(wShowPreviewLineBreaks.getSelection());
     props.setMetricsPanelShowUnits(wMetricsPanelShowUnits.getSelection());
+    if (wMetricsPanelDynamicColumnResize != null
+        && !wMetricsPanelDynamicColumnResize.isDisposed()) {
+      
props.setMetricsPanelDynamicColumnResize(wMetricsPanelDynamicColumnResize.getSelection());
+    }
     props.setMetricsPanelShowInput(wMetricsPanelShowInput.getSelection());
     props.setMetricsPanelShowRead(wMetricsPanelShowRead.getSelection());
     props.setMetricsPanelShowOutput(wMetricsPanelShowOutput.getSelection());
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
index 45936bfa78..addd19e1b8 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
@@ -110,6 +110,8 @@ EnterOptionsDialog.AutoLayout.MoveNotes.ToolTip=When 
arranging, move each note a
 EnterOptionsDialog.Section.MetricsPanel=Metrics panel
 EnterOptionsDialog.MetricsPanel.ShowUnits.Label=Show units in cells
 EnterOptionsDialog.MetricsPanel.ShowUnits.ToolTip=When enabled, units (e.g. r, 
h:m:s, rows/s) are shown in the pipeline metrics grid cells.
+EnterOptionsDialog.MetricsPanel.DynamicColumnResize.Label=Dynamic column 
resizing
+EnterOptionsDialog.MetricsPanel.DynamicColumnResize.ToolTip=When enabled, 
metrics columns grow during execution as values get wider. Turn this off to 
keep the widths you set. Use View > Fit columns to content to pack once.
 EnterOptionsDialog.MetricsPanel.ShowInput.Label=Show Input
 EnterOptionsDialog.MetricsPanel.ShowRead.Label=Show Read
 EnterOptionsDialog.MetricsPanel.ShowOutput.Label=Show Output
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
index 30c6e7ab85..8d474ca9e9 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
@@ -256,8 +256,12 @@ PipelineLog.Button.ShowOnlyActiveTransforms=Hide inactive
 PipelineLog.Button.ShowOnlySelectedTransforms=Show only selected transforms
 PipelineLog.Search.TransformName.Placeholder=Filter by transform name...
 PipelineLog.MetricsView.View=View
-PipelineLog.MetricsView.View.Tooltip=Show or hide columns and units in the 
metrics grid
+PipelineLog.MetricsView.View.Tooltip=Show or hide columns, units, and column 
resizing in the metrics grid
 PipelineLog.MetricsView.ShowUnits=Show units in cells
+PipelineLog.MetricsView.DynamicColumnResize=Dynamic column resizing
+PipelineLog.MetricsView.DynamicColumnResize.Tooltip=When enabled, auto-sized 
columns grow during execution as values get wider. Turn this off to keep the 
widths you set.
+PipelineLog.MetricsView.FitColumns=Fit columns to content
+PipelineLog.MetricsView.FitColumns.Tooltip=Pack columns once to the current 
cell and header text. Columns you resized by dragging are left as they are.
 PipelineLog.MetricsView.ShowInput=Show Input
 PipelineLog.MetricsView.ShowRead=Show Read
 PipelineLog.MetricsView.ShowOutput=Show Output

Reply via email to