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());
});
}