This is an automated email from the ASF dual-hosted git repository.
mattcasters 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 555d8b3ebd Fix blank rows when expanding folders in File Explorer
after scoped refresh (#7526)
555d8b3ebd is described below
commit 555d8b3ebdfc330b3e11b5da68feed65fc359dd3
Author: Lance <[email protected]>
AuthorDate: Tue Jul 14 23:23:03 2026 +0800
Fix blank rows when expanding folders in File Explorer after scoped refresh
(#7526)
Signed-off-by: lance <[email protected]>
---
.../perspective/explorer/ExplorerPerspective.java | 128 ++++++++++++---------
1 file changed, 75 insertions(+), 53 deletions(-)
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
index 4c943ddeac..3f968513f6 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -914,15 +914,48 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
// Which folder is being expanded?
//
TreeItem item = (TreeItem) event.item;
- TreeItemFolder treeItemFolder = (TreeItemFolder) item.getData();
- if (treeItemFolder != null && !treeItemFolder.loaded) {
- BusyIndicator.showWhile(
- hopGui.getDisplay(),
- () -> {
- refreshFolder(item, treeItemFolder.path, treeItemFolder.depth + 1);
- treeItemFolder.loaded = true;
- });
+ ensureFolderLoaded(item);
+ }
+
+ /**
+ * True when the folder still has only the lazy-loading placeholder child
(empty text, no data).
+ * Expanding such a node would show a blank row in the tree.
+ */
+ private static boolean hasOnlyLazyDummyChild(TreeItem item) {
+ if (item == null || item.isDisposed() || item.getItemCount() != 1) {
+ return false;
+ }
+ TreeItem firstChild = item.getItem(0);
+ return firstChild.getData() == null && Utils.isEmpty(firstChild.getText());
+ }
+
+ /** Depth passed to {@link #refreshFolder} when loading children of {@code
tif}. */
+ private int childDepthForFolder(TreeItemFolder tif) {
+ return tif.path.equals(rootFolder) ? 0 : tif.depth + 1;
+ }
+
+ /**
+ * Loads folder contents when still unloaded or stuck with a lazy
placeholder. Safe to call before
+ * any programmatic {@code setExpanded(true)}.
+ */
+ private void ensureFolderLoaded(TreeItem item) {
+ if (item == null || item.isDisposed()) {
+ return;
+ }
+ TreeItemFolder tif = (TreeItemFolder) item.getData();
+ if (tif == null || !tif.folder) {
+ return;
+ }
+ if (tif.loaded && !hasOnlyLazyDummyChild(item)) {
+ return;
}
+
+ BusyIndicator.showWhile(
+ hopGui.getDisplay(),
+ () -> {
+ refreshFolder(item, tif.path, childDepthForFolder(tif));
+ tif.loaded = true;
+ });
}
private void openFile(Event event) {
@@ -950,6 +983,9 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
// Expand the folder
//
boolean expanded = !item.getExpanded();
+ if (expanded) {
+ ensureFolderLoaded(item);
+ }
item.setExpanded(expanded);
TreeMemory.getInstance().storeExpanded(FILE_EXPLORER_TREE, item,
expanded);
} else {
@@ -2422,14 +2458,7 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
return true;
}
if (tif != null && tif.folder && isDescendant(tif.path, filename)) {
- if (!tif.loaded) {
- BusyIndicator.showWhile(
- hopGui.getDisplay(),
- () -> {
- refreshFolder(item, tif.path, tif.depth + 1);
- tif.loaded = true;
- });
- }
+ ensureFolderLoaded(item);
item.setExpanded(true);
TreeMemory.getInstance().storeExpanded(FILE_EXPLORER_TREE, item, true);
for (TreeItem child : item.getItems()) {
@@ -2712,15 +2741,7 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
if (selection == null || selection.length == 0) {
expandCollapse(tree.getTopItem(), true);
} else {
- TreeItemFolder treeItemFolder = (TreeItemFolder) selection[0].getData();
- if (treeItemFolder != null && !treeItemFolder.loaded) {
- BusyIndicator.showWhile(
- hopGui.getDisplay(),
- () -> {
- refreshFolder(selection[0], treeItemFolder.path,
treeItemFolder.depth + 1);
- treeItemFolder.loaded = true;
- });
- }
+ // expandCollapse → ensureFolderLoaded covers lazy folders under the
selection
expandCollapse(selection[0], true);
}
tree.setRedraw(true);
@@ -2749,12 +2770,21 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
}
public void expandCollapse(TreeItem item, boolean expand) {
- TreeItem[] items = item.getItems();
+ if (item == null || item.isDisposed()) {
+ return;
+ }
+ if (expand) {
+ // Expand All used to setExpanded(true) on lazy folders that still only
had a dummy child,
+ // which showed blank rows. Load real children first.
+ ensureFolderLoaded(item);
+ }
item.setExpanded(expand);
TreeMemory.getInstance().storeExpanded(FILE_EXPLORER_TREE, item, expand);
- for (TreeItem childItem : items) {
- if (childItem.getItemCount() >= 1) {
+ for (TreeItem childItem : item.getItems()) {
+ TreeItemFolder childTif = (TreeItemFolder) childItem.getData();
+
+ if ((childTif != null && childTif.folder) || childItem.getItemCount() >
1) {
expandCollapse(childItem, expand);
}
}
@@ -3030,8 +3060,17 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
return;
}
- int childDepth = tif.path.equals(rootFolder) ? 0 : tif.depth + 1;
- refreshFolder(folderItem, tif.path, childDepth);
+ // Dispose children while collapsed so SWT does not leave ghost blank rows
under an
+ // expanded parent (common after scoped refresh + later expand/collapse).
+ String[] treePath = ConstUi.getTreeStrings(folderItem);
+ boolean wasExpanded = folderItem.getExpanded();
+ if (wasExpanded) {
+ folderItem.setExpanded(false);
+ // Collapse listener clears TreeMemory; put the remembered expand state
back.
+ TreeMemory.getInstance().storeExpanded(FILE_EXPLORER_TREE, treePath,
true);
+ }
+
+ refreshFolder(folderItem, tif.path, childDepthForFolder(tif));
tif.loaded = true;
restoreTreeItemExpandedFromMemory(folderItem);
}
@@ -3436,13 +3475,8 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
if (tif != null) {
boolean expanded =
TreeMemory.getInstance().isExpanded(FILE_EXPLORER_TREE,
ConstUi.getTreeStrings(item));
- if (expanded && !tif.loaded && item.getItemCount() == 1) {
- TreeItem firstChild = item.getItem(0);
- if (firstChild.getData() == null) {
- int childDepth = tif.path.equals(rootFolder) ? 0 : tif.depth + 1;
- refreshFolder(item, tif.path, childDepth);
- tif.loaded = true;
- }
+ if (expanded) {
+ ensureFolderLoaded(item);
}
item.setExpanded(expanded);
}
@@ -3462,14 +3496,9 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
if (tif != null && tif.path != null &&
treeStateBeforeFilter.containsKey(tif.path)) {
boolean wasExpanded = treeStateBeforeFilter.get(tif.path);
- // If it should be expanded but has a dummy child, load it first
- if (wasExpanded && !tif.loaded && item.getItemCount() == 1) {
- TreeItem firstChild = item.getItem(0);
- if (firstChild.getData() == null) {
- // This is a dummy item, load the folder contents
- refreshFolder(item, tif.path, tif.depth + 1);
- tif.loaded = true;
- }
+ // If it should be expanded but still has a lazy placeholder, load real
children first
+ if (wasExpanded) {
+ ensureFolderLoaded(item);
}
item.setExpanded(wasExpanded);
@@ -3733,14 +3762,7 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable, IFileD
}
if (tif != null && tif.folder && isDescendant(tif.path, path)) {
- if (!tif.loaded) {
- BusyIndicator.showWhile(
- hopGui.getDisplay(),
- () -> {
- refreshFolder(item, tif.path, tif.depth + 1);
- tif.loaded = true;
- });
- }
+ ensureFolderLoaded(item);
item.setExpanded(true);
TreeMemory.getInstance().storeExpanded(FILE_EXPLORER_TREE, item, true);
for (TreeItem child : item.getItems()) {