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 f187a89e7b issue #7832 : re-enable data preview sorting and column 
metadata tooltips (#7852)
f187a89e7b is described below

commit f187a89e7be4373a1b0ae737da80368269e2a36c
Author: Matt Casters <[email protected]>
AuthorDate: Mon Aug 10 14:18:26 2026 +0200

    issue #7832 : re-enable data preview sorting and column metadata tooltips 
(#7852)
    
    ShowRowsDialog can sort columns again while keeping full cell-value lookup
    working via preserved buffer indexes. Selected and hovered cells show name,
    type, length, precision, and origin tooltips.
---
 .../dialog/ShowRowsDialogCellSelectionTest.java    |  40 ++++++
 .../apache/hop/ui/core/dialog/ShowRowsDialog.java  | 147 +++++++++++++++++----
 .../org/apache/hop/ui/core/widget/TableView.java   |  27 +++-
 .../core/dialog/messages/messages_en_US.properties |   5 +
 .../ui/core/dialog/ShowRowsDialogTooltipTest.java  |  89 +++++++++++++
 5 files changed, 276 insertions(+), 32 deletions(-)

diff --git 
a/rcp/src/test/java/org/apache/hop/ui/core/dialog/ShowRowsDialogCellSelectionTest.java
 
b/rcp/src/test/java/org/apache/hop/ui/core/dialog/ShowRowsDialogCellSelectionTest.java
index 202bac6b54..b5c3b85ce5 100644
--- 
a/rcp/src/test/java/org/apache/hop/ui/core/dialog/ShowRowsDialogCellSelectionTest.java
+++ 
b/rcp/src/test/java/org/apache/hop/ui/core/dialog/ShowRowsDialogCellSelectionTest.java
@@ -88,6 +88,46 @@ class ShowRowsDialogCellSelectionTest extends SwtBotTestBase 
{
               LONG_VALUE,
               onUi(editor::getText),
               "the editor must expose the full, untruncated cell value for 
copying");
+          String tooltip = onUi(editor::getToolTipText);
+          assertNotNull(tooltip, "selected cell should show a column-metadata 
tooltip");
+          assertTrue(
+              tooltip.contains("text"),
+              "cell tooltip should include the column name; was: " + tooltip);
+        });
+  }
+
+  @Test
+  void sortingKeepsFullValueLookupForTheSelectedCell() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("text"));
+    List<Object[]> rows = new ArrayList<>();
+    rows.add(new Object[] {"zebra-" + LONG_VALUE});
+    rows.add(new Object[] {"alpha-" + LONG_VALUE});
+
+    withDialog(
+        parent ->
+            new ShowRowsDialog(parent, new Variables(), "Preview", "Output 
rows", rowMeta, rows)
+                .open(),
+        bot -> {
+          Table table = awaitTable(bot);
+          assertNotNull(table, "the ShowRowsDialog table should open");
+
+          // Sort by the data column (index 1; 0 is the row-number column).
+          onUi(
+              () -> {
+                table.getColumn(1).notifyListeners(SWT.Selection, new Event());
+                return null;
+              });
+          bot.sleep(100);
+
+          // After ascending sort, "alpha-..." should be first. Click it and 
verify full value.
+          clickFirstDataCell(table);
+          Text editor = awaitCellEditor(bot, table);
+          assertNotNull(editor, "cell editor should open after sort");
+          assertEquals(
+              "alpha-" + LONG_VALUE,
+              onUi(editor::getText),
+              "after sorting, the full buffer value for the clicked row must 
still be resolved");
         });
   }
 
diff --git a/ui/src/main/java/org/apache/hop/ui/core/dialog/ShowRowsDialog.java 
b/ui/src/main/java/org/apache/hop/ui/core/dialog/ShowRowsDialog.java
index b6dab857de..b62dd882e9 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/dialog/ShowRowsDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/dialog/ShowRowsDialog.java
@@ -18,6 +18,7 @@
 package org.apache.hop.ui.core.dialog;
 
 import java.util.List;
+import java.util.Objects;
 import org.apache.commons.codec.binary.Hex;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.config.HopConfig;
@@ -53,6 +54,10 @@ import org.eclipse.swt.widgets.Text;
  * <p>Use this when the caller already has rows in memory and only needs to 
display them. For
  * transform preview (streaming, "get more rows", pause/stop, logging text), 
use {@link
  * PreviewRowsDialog} instead.
+ *
+ * <p>Column headers are sortable. Each table item stores its original buffer 
index via {@link
+ * TableItem#setData(Object)} so cell selection and full-value lookup still 
work after a sort
+ * ({@link TableView} preserves unkeyed item data when rebuilding rows).
  */
 public final class ShowRowsDialog {
 
@@ -155,7 +160,7 @@ public final class ShowRowsDialog {
       IValueMeta valueMeta = rowMeta.getValueMeta(i);
       columns[i] =
           new ColumnInfo(valueMeta.getName(), ColumnInfo.COLUMN_TYPE_TEXT, 
valueMeta.isNumeric());
-      columns[i].setToolTip(valueMeta.toStringMeta());
+      columns[i].setToolTip(formatColumnMetaTooltip(valueMeta));
       columns[i].setValueMeta(valueMeta);
       columns[i].setImage(GuiResource.getInstance().getImage(valueMeta));
       columns[i].setReadOnly(true);
@@ -171,10 +176,9 @@ public final class ShowRowsDialog {
             null,
             PropsUi.getInstance());
     view.setShowingBlueNullValues(true);
-    // Rows are kept in load order so a cell's visual position maps straight 
back to the buffer that
-    // holds its full value (see getFullCellString). Sorting rebuilds the 
table items from their
-    // truncated display text, breaking that mapping, so it is disabled here.
-    view.setSortable(false);
+    // Column sorting is enabled; each item stores its original buffer index 
so full-value lookup
+    // still works after the table is reordered (see resolveBufferIndex).
+    view.setSortable(true);
 
     FormData fdTable = new FormData();
     fdTable.left = new FormAttachment(0, 0);
@@ -194,18 +198,21 @@ public final class ShowRowsDialog {
       } else {
         item = new TableItem(tableView.table, SWT.NONE);
       }
-      fillRow(item, rows.get(i));
+      fillRow(item, rows.get(i), i);
     }
     if (!tableView.isDisposed()) {
       tableView.optWidth(true, 200);
     }
   }
 
-  private void fillRow(TableItem item, Object[] row) {
+  private void fillRow(TableItem item, Object[] row, int bufferIndex) {
     if (row == null) {
       return;
     }
 
+    // Unkeyed data: TableView preserves getData()/setData(Object) across 
column sorts.
+    item.setData(bufferIndex);
+
     lineNr++;
     String rowNumber;
     try {
@@ -264,7 +271,8 @@ public final class ShowRowsDialog {
    * TableView#formatCellValueForDisplay}). Clicking a cell drops a read-only 
text field on it (like
    * the inline editor of an editable grid) so its full value can be selected 
and copied in place;
    * double-clicking a cell shows its full, original content in a floating box 
(handy for long
-   * strings, JSON and multi-line values).
+   * strings, JSON and multi-line values). Hovering (or selecting) a cell 
shows column metadata in a
+   * tooltip: name, type, length, precision.
    */
   private void setupCellSelection() {
     cellEditor = new TableEditor(tableView.table);
@@ -280,12 +288,61 @@ public final class ShowRowsDialog {
         });
     tableView.table.addListener(
         SWT.MouseDoubleClick, event -> showFullCellValue(new Point(event.x, 
event.y)));
+    // Dynamic cell tooltip with column metadata (name, type, length, 
precision, ...).
+    tableView.table.addListener(
+        SWT.MouseMove,
+        event -> {
+          CellRef ref = cellAt(new Point(event.x, event.y));
+          String tip =
+              ref == null ? null : 
formatColumnMetaTooltip(rowMeta.getValueMeta(ref.dataColumn));
+          if (!Objects.equals(tip, tableView.table.getToolTipText())) {
+            tableView.table.setToolTipText(tip);
+          }
+        });
+  }
+
+  /**
+   * Build a multi-line tooltip describing a column: name, type, and optional 
length / precision /
+   * origin.
+   */
+  static String formatColumnMetaTooltip(IValueMeta valueMeta) {
+    if (valueMeta == null) {
+      return null;
+    }
+    StringBuilder tip = new StringBuilder();
+    tip.append(
+        BaseMessages.getString(
+            PKG, "ShowRowsDialog.CellTooltip.Name", 
Const.NVL(valueMeta.getName(), "")));
+    tip.append(Const.CR);
+    tip.append(
+        BaseMessages.getString(
+            PKG, "ShowRowsDialog.CellTooltip.Type", 
Const.NVL(valueMeta.getTypeDesc(), "")));
+    if (valueMeta.getLength() > 0) {
+      tip.append(Const.CR);
+      tip.append(
+          BaseMessages.getString(
+              PKG, "ShowRowsDialog.CellTooltip.Length", 
Integer.toString(valueMeta.getLength())));
+    }
+    if (valueMeta.getPrecision() > 0) {
+      tip.append(Const.CR);
+      tip.append(
+          BaseMessages.getString(
+              PKG,
+              "ShowRowsDialog.CellTooltip.Precision",
+              Integer.toString(valueMeta.getPrecision())));
+    }
+    if (!Utils.isEmpty(valueMeta.getOrigin())) {
+      tip.append(Const.CR);
+      tip.append(
+          BaseMessages.getString(PKG, "ShowRowsDialog.CellTooltip.Origin", 
valueMeta.getOrigin()));
+    }
+    return tip.toString();
   }
 
   private void showFullCellValue(Point point) {
     CellRef ref = cellAt(point);
     if (ref != null) {
-      expandCell(ref.bounds, ref.rowIndex, ref.columnIndex);
+      expandCell(ref.bounds, ref.bufferIndex, ref.tableColumn);
     }
   }
 
@@ -299,11 +356,11 @@ public final class ShowRowsDialog {
     if (ref == null) {
       return;
     }
-    String full = getFullCellString(ref.rowIndex, ref.columnIndex - 1);
+    String full = getFullCellString(ref.bufferIndex, ref.dataColumn);
     if (full == null) {
       return;
     }
-    TableItem item = tableView.table.getItem(ref.rowIndex);
+    TableItem item = tableView.table.getItem(ref.tableRowIndex);
 
     if (cellEditorText != null && !cellEditorText.isDisposed()) {
       cellEditorText.dispose();
@@ -312,6 +369,7 @@ public final class ShowRowsDialog {
     final Text field = new Text(tableView.table, SWT.SINGLE | SWT.READ_ONLY);
     PropsUi.setLook(field);
     field.setText(full);
+    
field.setToolTipText(formatColumnMetaTooltip(rowMeta.getValueMeta(ref.dataColumn)));
     cellEditorText = field;
     final long openedAt = System.currentTimeMillis();
 
@@ -330,25 +388,25 @@ public final class ShowRowsDialog {
     // 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));
+    final int bufferIndex = ref.bufferIndex;
+    final int tableColumn = ref.tableColumn;
+    field.addListener(SWT.MouseDoubleClick, e -> expandCell(cellBounds, 
bufferIndex, tableColumn));
     field.addListener(
         SWT.MouseDown,
         e -> {
           if (System.currentTimeMillis() - openedAt
               <= tableView.getDisplay().getDoubleClickTime()) {
-            expandCell(cellBounds, rowIndex, columnIndex);
+            expandCell(cellBounds, bufferIndex, tableColumn);
           }
         });
 
-    cellEditor.setEditor(field, item, columnIndex);
+    cellEditor.setEditor(field, item, tableColumn);
     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) 
{
+  private void expandCell(Rectangle cellBounds, int bufferIndex, int 
tableColumn) {
     if (cellEditorText != null && !cellEditorText.isDisposed()) {
       cellEditorText.dispose();
     }
@@ -357,7 +415,7 @@ public final class ShowRowsDialog {
     if (valueOverlay != null && !valueOverlay.isDisposed()) {
       return;
     }
-    String full = getFullCellString(rowIndex, columnIndex - 1);
+    String full = getFullCellString(bufferIndex, tableColumn - 1);
     if (full != null) {
       showValueOverlay(cellBounds, full);
     }
@@ -375,31 +433,66 @@ public final class ShowRowsDialog {
     if (item == null) {
       return null;
     }
-    int rowIndex = tableView.table.indexOf(item);
-    if (rowIndex < 0 || rowIndex >= rows.size()) {
+    int tableRowIndex = tableView.table.indexOf(item);
+    if (tableRowIndex < 0) {
+      return null;
+    }
+    int bufferIndex = resolveBufferIndex(item);
+    if (bufferIndex < 0 || bufferIndex >= rows.size()) {
       return null;
     }
     // Column 0 is the row-number column; data columns start at 1. Find the 
one under the pointer.
     for (int i = 1; i < tableView.table.getColumnCount(); i++) {
       Rectangle b = item.getBounds(i);
       if (b.contains(point)) {
-        return i - 1 < rowMeta.size() ? new CellRef(rowIndex, i, b) : null;
+        int dataColumn = i - 1;
+        return dataColumn < rowMeta.size()
+            ? new CellRef(bufferIndex, tableRowIndex, i, dataColumn, b)
+            : null;
       }
     }
     return null;
   }
 
   /**
-   * A located data cell: its row index into the buffer, its 1-based table 
column, and its bounds.
+   * Map a visual table item back to its original {@link #rows} index. Prefers 
the value stored at
+   * fill time via {@link TableItem#setData(Object)}; falls back to the 
1-based line number in
+   * column 0, which also moves with the row when the table is sorted.
+   */
+  private int resolveBufferIndex(TableItem item) {
+    Object data = item.getData();
+    if (data instanceof Integer index) {
+      return index;
+    }
+    // Fallback: original line number text in the # column (1-based).
+    try {
+      String text = item.getText(0);
+      if (Utils.isEmpty(text)) {
+        return -1;
+      }
+      return Integer.parseInt(text.trim()) - 1;
+    } catch (NumberFormatException e) {
+      return -1;
+    }
+  }
+
+  /**
+   * A located data cell: buffer index into {@link #rows}, visual table row, 
1-based table column,
+   * 0-based data column, and cell bounds.
    */
   private static final class CellRef {
-    private final int rowIndex;
-    private final int columnIndex;
+    private final int bufferIndex;
+    private final int tableRowIndex;
+    private final int tableColumn;
+    private final int dataColumn;
     private final Rectangle bounds;
 
-    private CellRef(int rowIndex, int columnIndex, Rectangle bounds) {
-      this.rowIndex = rowIndex;
-      this.columnIndex = columnIndex;
+    private CellRef(
+        int bufferIndex, int tableRowIndex, int tableColumn, int dataColumn, 
Rectangle bounds) {
+      this.bufferIndex = bufferIndex;
+      this.tableRowIndex = tableRowIndex;
+      this.tableColumn = tableColumn;
+      this.dataColumn = dataColumn;
       this.bounds = bounds;
     }
   }
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 47fbc5d709..3564dbd8fb 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
@@ -1712,13 +1712,25 @@ public class TableView extends Composite {
       final IRowMeta sourceRowMeta = buildTableSourceRowMeta(rowMeta, 
conversionRowMeta);
       List<Object[]> v = getTableItemsAsRows(items, sourceRowMeta);
 
+      // Keep TableItem#getData() across the rebuild so callers (e.g. 
ShowRowsDialog) can map a
+      // visual row back to an external buffer index after the user sorts a 
column.
+      final Object[] preservedData = new Object[items.length];
+      for (int i = 0; i < items.length; i++) {
+        preservedData[i] = items[i].getData();
+      }
+
       final int[] sortIndex = new int[] {sortField + 2};
 
-      // Sort the vector!
-      v.sort(
-          (r1, r2) -> {
+      // Sort indices so each row stays paired with its preserved widget data.
+      Integer[] order = new Integer[v.size()];
+      for (int i = 0; i < order.length; i++) {
+        order[i] = i;
+      }
+      Arrays.sort(
+          order,
+          (i1, i2) -> {
             try {
-              return conversionRowMeta.compare(r1, r2, sortIndex);
+              return conversionRowMeta.compare(v.get(i1), v.get(i2), 
sortIndex);
             } catch (HopValueException e) {
               throw new HopRuntimeException("Error comparing rows", e);
             }
@@ -1728,7 +1740,8 @@ public class TableView extends Composite {
       table.removeAll();
 
       // Refill the table
-      for (Object[] r : v) {
+      for (int origIdx : order) {
+        Object[] r = v.get(origIdx);
         TableItem item = new TableItem(table, SWT.NONE);
 
         String colorName = (String) r[0];
@@ -1755,6 +1768,10 @@ public class TableView extends Composite {
             item.setText(j - 2, string);
           }
         }
+
+        if (preservedData[origIdx] != null) {
+          item.setData(preservedData[origIdx]);
+        }
       }
       table.setSortColumn(table.getColumn(this.sortField));
       table.setSortDirection(sortingDescending ? SWT.DOWN : SWT.UP);
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 58fd859c8e..4589d50e52 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
@@ -299,6 +299,11 @@ PreviewRowsDialog.ShowLogging.Title=Logging text
 PreviewRowsDialog.Title=Examine preview data
 ShowRowsDialog.NoRows.Message=No rows to display.
 ShowRowsDialog.NrRows=({0} rows)
+ShowRowsDialog.CellTooltip.Name=Name: {0}
+ShowRowsDialog.CellTooltip.Type=Type: {0}
+ShowRowsDialog.CellTooltip.Length=Length: {0}
+ShowRowsDialog.CellTooltip.Precision=Precision: {0}
+ShowRowsDialog.CellTooltip.Origin=Origin: {0}
 ProgressMonitorDialog.InitialTaskLabel=Apache Hop is handling a long running 
task
 ProgressMonitorDialog.Shell.Title=Progress...
 ProgressMonitorDialog.InitialSubTaskLabel=\ 
diff --git 
a/ui/src/test/java/org/apache/hop/ui/core/dialog/ShowRowsDialogTooltipTest.java 
b/ui/src/test/java/org/apache/hop/ui/core/dialog/ShowRowsDialogTooltipTest.java
new file mode 100644
index 0000000000..c687e30d1a
--- /dev/null
+++ 
b/ui/src/test/java/org/apache/hop/ui/core/dialog/ShowRowsDialogTooltipTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.dialog;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.row.value.ValueMetaNumber;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Unit tests for {@link ShowRowsDialog#formatColumnMetaTooltip}: cell 
tooltips must expose column
+ * metadata (name, type, length, precision) as requested in issue #7832.
+ */
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
+class ShowRowsDialogTooltipTest {
+
+  @BeforeAll
+  static void initHop() throws Exception {
+    HopEnvironment.init();
+  }
+
+  @Test
+  void nullValueMetaReturnsNull() {
+    assertNull(ShowRowsDialog.formatColumnMetaTooltip(null));
+  }
+
+  @Test
+  void stringFieldTooltipIncludesNameTypeAndLength() {
+    ValueMetaString meta = new ValueMetaString("customer_name");
+    meta.setLength(100);
+    meta.setOrigin("Customers");
+
+    String tip = ShowRowsDialog.formatColumnMetaTooltip(meta);
+    assertNotNull(tip);
+    assertTrue(tip.contains("customer_name"), "tooltip should include the 
field name");
+    assertTrue(tip.contains("String") || tip.contains("string"), "tooltip 
should include the type");
+    assertTrue(tip.contains("100"), "tooltip should include the length");
+    assertTrue(tip.contains("Customers"), "tooltip should include the origin 
when set");
+  }
+
+  @Test
+  void numberFieldTooltipIncludesPrecision() {
+    ValueMetaNumber meta = new ValueMetaNumber("amount");
+    meta.setLength(12);
+    meta.setPrecision(2);
+
+    String tip = ShowRowsDialog.formatColumnMetaTooltip(meta);
+    assertNotNull(tip);
+    assertTrue(tip.contains("amount"), "tooltip should include the field 
name");
+    assertTrue(tip.contains("12"), "tooltip should include the length");
+    assertTrue(tip.contains("2"), "tooltip should include the precision");
+  }
+
+  @Test
+  void omitsUnsetLengthAndPrecision() {
+    ValueMetaString meta = new ValueMetaString("notes");
+    // length/precision left at defaults (-1)
+
+    String tip = ShowRowsDialog.formatColumnMetaTooltip(meta);
+    assertNotNull(tip);
+    assertTrue(tip.contains("notes"));
+    // No dedicated length/precision lines when values are not positive
+    assertFalse(tip.toLowerCase().contains("length:"), tip);
+    assertFalse(tip.toLowerCase().contains("precision:"), tip);
+  }
+}

Reply via email to