From 51eb713ea2283a732d58c64229a7d67e9d8ae894 Mon Sep 17 00:00:00 2001 From: Elsa Zacharia Date: Sun, 30 Aug 2026 23:27:47 +0530 Subject: [PATCH 1/2] Add expand/collapse hover option in Search Results tree view When hovering over a top-level project node in the tree layout, an option appears showing expand or collapse based on the node's current state. This is to individually expand or collapse tree parent items. --- .../ui/text/AbstractTextSearchViewPage.java | 152 ++++++++++++++++++ .../search2/internal/ui/SearchMessages.java | 2 + .../internal/ui/SearchMessages.properties | 2 + 3 files changed, 156 insertions(+) diff --git a/bundles/org.eclipse.search/newsearch/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java b/bundles/org.eclipse.search/newsearch/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java index 3d0a2bb2646..7a159086e55 100644 --- a/bundles/org.eclipse.search/newsearch/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java +++ b/bundles/org.eclipse.search/newsearch/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java @@ -26,14 +26,24 @@ import org.osgi.framework.FrameworkUtil; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseTrackAdapter; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; 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.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; @@ -54,6 +64,7 @@ import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.util.OpenStrategy; +import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.DecoratingLabelProvider; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IOpenListener; @@ -279,6 +290,8 @@ public void selectionChanged(SelectionChangedEvent event) { public static final int FLAG_LAYOUT_TREE = 2; private OpenAndLinkWithEditorHelper openAndLinkWithEditorHelper; + private Shell fHoverShell; + private TreeItem fHoveredItem; /** * This constructor must be passed a combination of layout flags combined @@ -709,6 +722,8 @@ public void setLayout(int layout) { } private void disposeViewer() { + disposeHoverShell(); + fHoveredItem = null; fViewer.removeSelectionChangedListener(fViewerAdapter); fViewer.getControl().dispose(); fViewer = null; @@ -746,6 +761,7 @@ private void createViewer(Composite parent, int layout) { configureTreeViewer(viewer); fCollapseAllAction.setViewer(viewer); fExpandAllAction.setViewer(viewer); + installHoverExpandSupport(viewer); } fCopyToClipboardAction.setViewer(fViewer); @@ -1559,5 +1575,141 @@ public Integer getElementLimit() { return fElementLimit; } + private void installHoverExpandSupport(TreeViewer viewer) { + Tree tree = viewer.getTree(); + + tree.addMouseMoveListener(e -> { + TreeItem item = tree.getItem(new Point(e.x, e.y)); + + boolean overShell = fHoverShell != null && !fHoverShell.isDisposed() + && fHoverShell.getBounds().contains(tree.toDisplay(e.x, e.y)); + if (item == null || item.getParentItem() != null) { + if (!overShell) { + disposeHoverShell(); + fHoveredItem = null; + } + return; + } + Rectangle rowBounds = item.getBounds(); + Rectangle extRowBounds = new Rectangle(rowBounds.x, rowBounds.y, + rowBounds.width + 20, rowBounds.height); + boolean overRow = extRowBounds.contains(e.x, e.y); + if (!overRow) { + if (!overShell) { + disposeHoverShell(); + fHoveredItem = null; + } + return; + } + if (item == fHoveredItem) { + return; + } + disposeHoverShell(); + fHoveredItem = item; + + Object element = item.getData(); + if (element == null) { + return; + } + boolean expanded = viewer.getExpandedState(element); + showHoverShell(tree, item, element, viewer, expanded); + }); + + tree.addMouseTrackListener(new MouseTrackAdapter() { + @Override + public void mouseExit(org.eclipse.swt.events.MouseEvent e) { + if (fHoverShell == null || fHoverShell.isDisposed()) { + fHoveredItem = null; + } + } + }); + } + + /** + * Creates and displays an option to expand or collapse any single item + * + * @param expanded + * whether the parent is currently expanded + */ + private void showHoverShell(Tree tree, TreeItem item, Object element, TreeViewer viewer, boolean expanded) { + org.eclipse.jface.resource.ImageDescriptor desc = expanded + ? fCollapseAllAction.getImageDescriptor() + : fExpandAllAction.getImageDescriptor(); + if (desc == null) { + return; + } + Image actionImage = desc.createImage(false); + if (actionImage == null) { + return; + } + + Shell shell = new Shell(tree.getShell(), SWT.TOOL | SWT.ON_TOP | SWT.NO_FOCUS | SWT.NO_TRIM); + shell.setBackground(tree.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + ToolBar tb = new ToolBar(shell, SWT.FLAT); + ToolItem ti = new ToolItem(tb, SWT.PUSH); + ti.setImage(actionImage); + ti.setToolTipText(expanded ? SearchMessages.CollapseItemAction_tooltip : SearchMessages.ExpandItemAction_tooltip); + + shell.addListener(SWT.Dispose, e -> actionImage.dispose()); + shell.addMouseTrackListener(new MouseTrackAdapter() { + @Override + public void mouseExit(org.eclipse.swt.events.MouseEvent e) { + disposeHoverShell(); + fHoveredItem = null; + } + }); + + ti.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() { + @Override + public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) { + if (!viewer.getControl().isDisposed()) { + viewer.getControl().setRedraw(false); + try { + if (expanded) { + viewer.collapseToLevel(element, AbstractTreeViewer.ALL_LEVELS); + } else { + viewer.expandToLevel(element, AbstractTreeViewer.ALL_LEVELS); + } + } finally { + viewer.getControl().setRedraw(true); + } + } + shell.dispose(); + fHoverShell = null; + fHoveredItem = null; + } + }); + + tb.pack(); + shell.pack(); + + Rectangle textBounds = item.getTextBounds(0); + int shellHeight = shell.getSize().y; + int shellWidth = shell.getSize().x; + int centredY = textBounds.y + (textBounds.height - shellHeight) / 2; + GC gc = new GC(tree); + int textWidth; + try { + textWidth = gc.textExtent(item.getText()).x; + } finally { + gc.dispose(); + } + int desiredX = textBounds.x + textWidth + 2; + Rectangle treeDisplayBounds = tree.getDisplay().map(tree, null, tree.getClientArea()); + int maxX = treeDisplayBounds.x + treeDisplayBounds.width - shellWidth - 2; + Point desiredDisplay = tree.toDisplay(desiredX, centredY); + int finalX = Math.min(desiredDisplay.x, maxX); + shell.setLocation(finalX, desiredDisplay.y); + shell.setVisible(true); + + fHoverShell = shell; + } + + private void disposeHoverShell() { + if (fHoverShell != null && !fHoverShell.isDisposed()) { + fHoverShell.dispose(); + } + fHoverShell = null; + } } diff --git a/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.java b/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.java index 75adbe4b6a2..62edc7749da 100644 --- a/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.java +++ b/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.java @@ -84,6 +84,8 @@ private SearchMessages() { public static String CollapseAllAction_1; public static String ExpandAllAction_label; public static String ExpandAllAction_tooltip; + public static String ExpandItemAction_tooltip; + public static String CollapseItemAction_tooltip; public static String SearchView_error_noResultPage; public static String InternalSearchUI_error_unexpected; public static String NewSearchUI_error_title; diff --git a/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.properties b/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.properties index 0973f594f8a..dfc4a623359 100644 --- a/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.properties +++ b/bundles/org.eclipse.search/newsearch/org/eclipse/search2/internal/ui/SearchMessages.properties @@ -57,6 +57,8 @@ CollapseAllAction_0=Collapse All CollapseAllAction_1=Collapse All ExpandAllAction_label=Expand All ExpandAllAction_tooltip=Expand All +ExpandItemAction_tooltip=Expand item +CollapseItemAction_tooltip=Collapse item SearchView_error_noResultPage=No search result page found for search result class {0} SearchView_empty_search_label=No search results available. Start a search from the search dialog... InternalSearchUI_error_unexpected=An unexpected exception occurred during search From ff395586bf1aae2af89475ad8ec55109ee73309c Mon Sep 17 00:00:00 2001 From: Eclipse Platform Bot Date: Mon, 31 Aug 2026 20:00:07 +0000 Subject: [PATCH 2/2] Version bump(s) for 4.42 stream --- bundles/org.eclipse.search/META-INF/MANIFEST.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.eclipse.search/META-INF/MANIFEST.MF b/bundles/org.eclipse.search/META-INF/MANIFEST.MF index 726e86f7caf..969c9033d31 100644 --- a/bundles/org.eclipse.search/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.search/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.search; singleton:=true -Bundle-Version: 3.19.0.qualifier +Bundle-Version: 3.19.100.qualifier Bundle-Activator: org.eclipse.search.internal.ui.SearchPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName