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 b601afb26a minor improvement on the tableview, fixes #7444 (#7445)
b601afb26a is described below

commit b601afb26a41a94f2c54f9587d0d8f28bbc09772
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Jul 7 11:04:45 2026 +0200

    minor improvement on the tableview, fixes #7444 (#7445)
---
 .../hop/ui/core/dialog/PreviewRowsDialog.java      | 215 ++++++++++++++++-----
 .../org/apache/hop/ui/core/widget/TableView.java   |  22 ++-
 2 files changed, 187 insertions(+), 50 deletions(-)

diff --git 
a/ui/src/main/java/org/apache/hop/ui/core/dialog/PreviewRowsDialog.java 
b/ui/src/main/java/org/apache/hop/ui/core/dialog/PreviewRowsDialog.java
index 399ec41aad..ead736ba20 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/dialog/PreviewRowsDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/dialog/PreviewRowsDialog.java
@@ -40,6 +40,7 @@ import org.apache.hop.ui.core.widget.TableView;
 import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.TableEditor;
 import org.eclipse.swt.events.KeyAdapter;
 import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.events.KeyListener;
@@ -65,6 +66,11 @@ public class PreviewRowsDialog {
       Const.toBoolean(
           
HopConfig.readStringVariable(Const.HOP_BINARY_FIELDS_AVOID_HEX_PREVIEW, 
"false"));
 
+  /** Caps for the floating full-value box: it grows to fit the value, then 
scrolls beyond these. */
+  private static final int MAX_OVERLAY_WIDTH = 600;
+
+  private static final int MAX_OVERLAY_HEIGHT = 400;
+
   private String transformName;
 
   private Label wlFields;
@@ -76,6 +82,11 @@ public class PreviewRowsDialog {
   /** Lightweight floating box showing the full value of an overflowing cell 
(Ctrl+Space style). */
   private Shell valueOverlay;
 
+  /** Read-only text field overlaid on the clicked cell so its value can be 
selected in place. */
+  private TableEditor cellEditor;
+
+  private Text cellEditorText;
+
   private final List<Object[]> buffer;
 
   private String title;
@@ -295,8 +306,9 @@ public class PreviewRowsDialog {
       columns[i].setToolTip(valueMeta.toStringMeta());
       columns[i].setValueMeta(valueMeta);
       columns[i].setImage(GuiResource.getInstance().getImage(valueMeta));
-      // A preview is a viewer: don't open an inline editor on click. This 
also frees double-click
-      // to pop up the full, untruncated value of a cell.
+      // A preview is a viewer: the grid's own editable inline editor must 
stay off. We drop our own
+      // read-only field on the cell instead (see the MouseDown handler 
below), which also frees
+      // double-click to pop up the full, untruncated value of a cell.
       columns[i].setReadOnly(true);
     }
 
@@ -309,8 +321,21 @@ public class PreviewRowsDialog {
     // breaking that mapping, so it is disabled here.
     wFields.setSortable(false);
 
-    // Cells only show a truncated, single-lined value for performance. 
Double-click a cell to see
-    // its full, original content (handy for long strings, JSON and multi-line 
values).
+    // Cells only show a truncated, single-lined value for performance. Click 
a cell to drop a
+    // read-only text field on it (like the inline editor of an editable grid) 
so its value can be
+    // selected and copied in place; double-click a cell to see its full, 
original content in a
+    // floating box (handy for long strings, JSON and multi-line values).
+    cellEditor = new TableEditor(wFields.table);
+    cellEditor.grabHorizontal = true;
+    cellEditor.horizontalAlignment = SWT.LEFT;
+    wFields.table.addListener(
+        SWT.MouseDown,
+        event -> {
+          // Leave Shift/Ctrl clicks to the table so row range/toggle 
selection keeps working.
+          if (event.button == 1 && (event.stateMask & (SWT.SHIFT | SWT.MOD1)) 
== 0) {
+            openCellEditor(new Point(event.x, event.y));
+          }
+        });
     wFields.table.addListener(
         SWT.MouseDoubleClick, event -> showFullCellValue(new Point(event.x, 
event.y)));
 
@@ -329,6 +354,9 @@ public class PreviewRowsDialog {
   }
 
   public void dispose() {
+    if (cellEditor != null) {
+      cellEditor.dispose();
+    }
     PropsUi.getInstance().setScreen(new WindowProperty(shell));
     bounds = shell.getBounds();
     hscroll = wFields.getHorizontalBar().getSelection();
@@ -428,52 +456,128 @@ public class PreviewRowsDialog {
   }
 
   /**
-   * When a cell whose value overflows the grid display is double-clicked, 
expand it in a
-   * lightweight inline multi-line box anchored to the cell. Does nothing for 
cells that aren't data
-   * cells or whose value already fits.
+   * When a data cell is double-clicked, show its full value in a lightweight 
box anchored to the
+   * cell so it can be selected and copied. Overflowing values (long / 
multi-line) get a larger,
+   * scrollable box; a short single value gets a small one. Does nothing for 
non-data cells.
    */
   private void showFullCellValue(Point point) {
-    if (wFields == null || wFields.isDisposed() || buffer == null || rowMeta 
== null) {
+    CellRef ref = cellAt(point);
+    if (ref != null) {
+      expandCell(ref.bounds, ref.rowIndex, ref.columnIndex);
+    }
+  }
+
+  /**
+   * Single-click handler: drop a read-only text field on the clicked cell — 
the same cell-fitting
+   * effect as an editable grid's inline editor — holding the full, 
untruncated value so it can be
+   * selected and copied in place. Double-clicking the field expands it to the 
full-value box.
+   */
+  private void openCellEditor(Point point) {
+    CellRef ref = cellAt(point);
+    if (ref == null) {
+      return;
+    }
+    String full = getFullCellString(ref.rowIndex, ref.columnIndex - 1);
+    if (full == null) {
+      return;
+    }
+    TableItem item = wFields.table.getItem(ref.rowIndex);
+
+    if (cellEditorText != null && !cellEditorText.isDisposed()) {
+      cellEditorText.dispose();
+    }
+
+    final Text field = new Text(wFields.table, SWT.SINGLE | SWT.READ_ONLY);
+    PropsUi.setLook(field);
+    field.setText(full);
+    cellEditorText = field;
+    final long openedAt = System.currentTimeMillis();
+
+    // Escape or losing focus removes the field again.
+    field.addListener(
+        SWT.KeyDown,
+        e -> {
+          if (e.keyCode == SWT.ESC) {
+            field.dispose();
+          }
+        });
+    field.addListener(SWT.FocusOut, e -> field.dispose());
+
+    // Double-clicking the field expands to the full-value box. The field is 
created on the first
+    // click, so on some platforms the second click of a double-click lands on 
this fresh field as a
+    // plain MouseDown; treat a click within the OS double-click time of it 
opening as that second
+    // click too. Coordinates are captured so the box anchors to the same cell.
+    final Rectangle cellBounds = ref.bounds;
+    final int rowIndex = ref.rowIndex;
+    final int columnIndex = ref.columnIndex;
+    field.addListener(SWT.MouseDoubleClick, e -> expandCell(cellBounds, 
rowIndex, columnIndex));
+    field.addListener(
+        SWT.MouseDown,
+        e -> {
+          if (System.currentTimeMillis() - openedAt <= 
wFields.getDisplay().getDoubleClickTime()) {
+            expandCell(cellBounds, rowIndex, columnIndex);
+          }
+        });
+
+    cellEditor.setEditor(field, item, columnIndex);
+    field.setFocus();
+    field.selectAll();
+  }
+
+  /** Expand the given cell's full value into the floating, selectable value 
box. */
+  private void expandCell(Rectangle cellBounds, int rowIndex, int columnIndex) 
{
+    if (cellEditorText != null && !cellEditorText.isDisposed()) {
+      cellEditorText.dispose();
+    }
+    // A double-click fires both a MouseDown (caught within the double-click 
window) and a
+    // MouseDoubleClick; ignore the second while the box for this click is 
still up.
+    if (valueOverlay != null && !valueOverlay.isDisposed()) {
       return;
     }
+    String full = getFullCellString(rowIndex, columnIndex - 1);
+    if (full != null) {
+      showValueOverlay(cellBounds, full);
+    }
+  }
+
+  /**
+   * Resolve the data cell under a table-relative point, or null when the 
point isn't over a data
+   * cell (the row-number column, empty space, or a row/column outside the 
backing buffer).
+   */
+  private CellRef cellAt(Point point) {
+    if (wFields == null || wFields.isDisposed() || buffer == null || rowMeta 
== null) {
+      return null;
+    }
     TableItem item = wFields.table.getItem(point);
     if (item == null) {
-      return;
+      return null;
     }
     int rowIndex = wFields.table.indexOf(item);
-
+    if (rowIndex < 0 || rowIndex >= buffer.size()) {
+      return null;
+    }
     // Column 0 is the row-number column; data columns start at 1. Find the 
one under the pointer.
-    int columnIndex = -1;
-    Rectangle cellBounds = null;
     for (int i = 1; i < wFields.table.getColumnCount(); i++) {
       Rectangle b = item.getBounds(i);
       if (b.contains(point)) {
-        columnIndex = i;
-        cellBounds = b;
-        break;
+        return i - 1 < rowMeta.size() ? new CellRef(rowIndex, i, b) : null;
       }
     }
-    if (columnIndex < 1 || cellBounds == null) {
-      return;
-    }
-    int column = columnIndex - 1;
-    if (rowIndex < 0 || rowIndex >= buffer.size() || column >= rowMeta.size()) 
{
-      return;
-    }
+    return null;
+  }
 
-    String full = getFullCellString(rowIndex, column);
-    if (full == null) {
-      return;
-    }
-    // Only expand cells whose value actually overflows what the grid shows.
-    int maxLength = PropsUi.getInstance().getMaxPreviewCellLength();
-    boolean overflow =
-        (maxLength > 0 && full.length() > maxLength)
-            || full.indexOf('\n') >= 0
-            || full.indexOf('\r') >= 0
-            || full.indexOf('\t') >= 0;
-    if (overflow) {
-      showValueOverlay(cellBounds, full);
+  /**
+   * A located data cell: its row index into the buffer, its 1-based table 
column, and its bounds.
+   */
+  private static final class CellRef {
+    private final int rowIndex;
+    private final int columnIndex;
+    private final Rectangle bounds;
+
+    private CellRef(int rowIndex, int columnIndex, Rectangle bounds) {
+      this.rowIndex = rowIndex;
+      this.columnIndex = columnIndex;
+      this.bounds = bounds;
     }
   }
 
@@ -489,7 +593,9 @@ public class PreviewRowsDialog {
 
     Point location = wFields.table.toDisplay(cellBounds.x, cellBounds.y);
 
-    final Shell overlay = new Shell(shell, SWT.NONE);
+    // A resizable (but title-less) floating shell: light like the variable 
helper, yet the user can
+    // drag its edges to make it bigger for a long value.
+    final Shell overlay = new Shell(shell, SWT.RESIZE);
     overlay.setLayout(new FillLayout());
 
     final Text text =
@@ -497,11 +603,22 @@ public class PreviewRowsDialog {
     PropsUi.setLook(text);
     text.setText(value);
 
-    overlay.setSize(Math.max(cellBounds.width, 400), 200);
+    // Size the box to its content. Width comes from the value's natural 
(unwrapped) width, at least
+    // the cell width and capped so it never sprawls across the screen; height 
is then measured
+    // after
+    // the value wraps to that width (minus the border + scrollbar gutter), so 
a value that spans
+    // several lines gets a taller box instead of a scrollbar — we only scroll 
past the height cap.
+    Point natural = text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+    int contentWidth = Math.min(natural.x + 20, MAX_OVERLAY_WIDTH);
+    int width = Math.max(cellBounds.width, contentWidth);
+    int wrapWidth = Math.max(50, width - 24);
+    Point wrapped = text.computeSize(wrapWidth, SWT.DEFAULT);
+    int contentHeight = Math.min(wrapped.y + 8, MAX_OVERLAY_HEIGHT);
+    int height = Math.max(cellBounds.height + 4, contentHeight);
+    overlay.setSize(width, height);
     overlay.setLocation(location.x, location.y);
 
-    // Dismiss on Escape or when focus leaves the box (mirrors the variable 
helper).
-    text.addListener(SWT.FocusOut, e -> overlay.dispose());
+    // Dismiss on Escape.
     text.addListener(
         SWT.KeyDown,
         e -> {
@@ -514,15 +631,27 @@ public class PreviewRowsDialog {
     valueOverlay = overlay;
 
     // Grab focus after the current (double-click) event settles, so a 
trailing table focus event
-    // can't immediately close the box.
+    // can't immediately close the box. Pre-select the whole value so it's 
ready to copy. Only after
+    // focus is settled inside the box do we arm click-away dismissal — via 
shell deactivation
+    // rather
+    // than a text focus-out, so grabbing a resize edge (which keeps the shell 
active) doesn't close
+    // it.
     overlay
         .getDisplay()
         .asyncExec(
             () -> {
-              if (!text.isDisposed()) {
-                text.setFocus();
-                text.setSelection(0, 0);
+              if (text.isDisposed()) {
+                return;
               }
+              text.setFocus();
+              text.selectAll();
+              overlay.addListener(
+                  SWT.Deactivate,
+                  e -> {
+                    if (!overlay.isDisposed()) {
+                      overlay.dispose();
+                    }
+                  });
             });
   }
 
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 b9bf484f2e..5ad738b493 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
@@ -1998,7 +1998,9 @@ public class TableView extends Composite {
     Rectangle cellBounds = row.getBounds(colNr);
     Point location = table.toDisplay(cellBounds.x, cellBounds.y);
 
-    final Shell popup = new Shell(getShell(), SWT.NONE);
+    // Resizable (but title-less) floating shell so the user can drag its 
edges to make a long value
+    // bigger, matching the read-only value viewer.
+    final Shell popup = new Shell(getShell(), SWT.RESIZE);
     multilineShell = popup;
     popup.addListener(
         SWT.Dispose,
@@ -2059,8 +2061,9 @@ public class TableView extends Composite {
           }
         };
 
-    // Commit when focus leaves the box; Ctrl+Enter also commits; Escape 
cancels.
-    multi.addListener(SWT.FocusOut, e -> commit.run());
+    // Ctrl+Enter commits; Escape cancels. (Clicking away commits too — armed 
below via shell
+    // deactivation rather than a text focus-out, so grabbing a resize edge 
doesn't
+    // commit-and-close.)
     multi.addListener(
         SWT.KeyDown,
         e -> {
@@ -2073,14 +2076,19 @@ public class TableView extends Composite {
         });
 
     popup.open();
-    // Grab focus after the double-click settles so a trailing focus event 
can't self-close it.
+    // Grab focus after the double-click settles so a trailing focus event 
can't self-close it. Only
+    // once focus is settled inside the box do we arm click-away commit, so an 
early stray event or
+    // a
+    // resize-edge grab (which keeps the shell active) doesn't 
commit-and-close it.
     getDisplay()
         .asyncExec(
             () -> {
-              if (!multi.isDisposed()) {
-                multi.setFocus();
-                multi.setSelection(multi.getText().length());
+              if (multi.isDisposed()) {
+                return;
               }
+              multi.setFocus();
+              multi.setSelection(multi.getText().length());
+              popup.addListener(SWT.Deactivate, e -> commit.run());
             });
   }
 

Reply via email to