Skip to content

Commit 4e83222

Browse files
committed
Add inline filter bar to Project Explorer
Adds a collapsible filter text field to the Project Explorer toolbar. The bar is hidden by default and toggled via a new toolbar button. When visible it uses e4 FilteredTree / PatternFilter to narrow the tree to matching file and folder names (with 200 ms debounce and a soft expansion time-limit to protect performance on large workspaces). Implementation: - ProjectExplorerFilteredTree: e4 FilteredTree subclass that overrides doCreateTreeViewer() to return a CommonViewer, so all Common Navigator content/label/filter services are preserved. - EmptyWorkspaceHelper: new setNonEmptyStackControl() overload so the StackLayout top-control can be the FilteredTree composite while the SWT.EmptinessChanged listener still tracks the inner Tree widget. - ProjectExplorerFilterActionGroup: new AS_CHECK_BOX toggle action added before the existing filter-dialog button.
1 parent 307ba92 commit 4e83222

8 files changed

Lines changed: 134 additions & 9 deletions

File tree

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/helpers/EmptyWorkspaceHelper.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public final class EmptyWorkspaceHelper {
9393
private Composite emptyArea;
9494
private StackLayout layout;
9595
private Tree control;
96+
private Control nonEmptyStackControl;
9697
private boolean controlIsEmpty;
9798
private Composite displayArea;
9899
private ArrayList<IAction> projectWizardActions;
@@ -133,6 +134,20 @@ public void setNonEmptyControl(Control control) {
133134
registerListeners();
134135
}
135136

137+
/**
138+
* Overrides the control used as the top control in the stack layout. Call this
139+
* after {@link #setNonEmptyControl(Control)} when the tree resides inside a
140+
* wrapper composite (e.g. a {@code FilteredTree}) that should be shown/hidden
141+
* as a whole instead of the bare tree widget.
142+
*
143+
* @param stackControl the direct child of the stack composite to use as the
144+
* non-empty top control
145+
*/
146+
public void setNonEmptyStackControl(Control stackControl) {
147+
this.nonEmptyStackControl = stackControl;
148+
switchTopControl();
149+
}
150+
136151
private void dispose(Listener listener) {
137152
IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
138153
if (activeWorkbenchWindow != null) {
@@ -309,7 +324,7 @@ private boolean switchTopControl() {
309324
}
310325
Control oldTop = layout.topControl;
311326
if (!controlIsEmpty) {
312-
layout.topControl = control;
327+
layout.topControl = nonEmptyStackControl != null ? nonEmptyStackControl : control;
313328
disposeEmptyArea();
314329
} else {
315330
if (emptyArea == null || emptyArea.isDisposed()) {

bundles/org.eclipse.ui.navigator.resources/META-INF/MANIFEST.MF

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ Export-Package: org.eclipse.ui.internal.navigator.resources;x-internal:=true,
1313
org.eclipse.ui.internal.navigator.resources.nested;x-internal:=true,
1414
org.eclipse.ui.internal.navigator.workingsets;x-internal:=true,
1515
org.eclipse.ui.navigator.resources
16-
Require-Bundle: org.eclipse.ui.ide;bundle-version="[3.21.0,4.0.0)",
16+
Require-Bundle: org.eclipse.e4.ui.dialogs;bundle-version="[1.4.0,2.0.0)",
17+
org.eclipse.ui.ide;bundle-version="[3.21.0,4.0.0)",
1718
org.eclipse.core.resources;bundle-version="[3.19.0,4.0.0)",
1819
org.eclipse.jface;bundle-version="[3.39.0,4.0.0)",
1920
org.eclipse.ui;bundle-version="[3.208.0,4.0.0)",

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/ProjectExplorerActionGroup.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,17 @@
2626
public class ProjectExplorerActionGroup extends CommonNavigatorActionGroup {
2727

2828
private ProjectExplorerFilterActionGroup fFilterActionGroup;
29+
private final ProjectExplorerFilteredTree fFilteredTree;
2930

3031
public ProjectExplorerActionGroup(CommonNavigator aNavigator, CommonViewer aViewer,
31-
LinkHelperService linkHelperService) {
32+
LinkHelperService linkHelperService, ProjectExplorerFilteredTree filteredTree) {
3233
super(aNavigator, aViewer, linkHelperService);
34+
this.fFilteredTree = filteredTree;
3335
}
3436

3537
@Override
3638
protected FilterActionGroup createFilterActionGroup(CommonViewer pCommonViewer) {
37-
fFilterActionGroup = new ProjectExplorerFilterActionGroup(pCommonViewer);
39+
fFilterActionGroup = new ProjectExplorerFilterActionGroup(pCommonViewer, fFilteredTree);
3840
return fFilterActionGroup;
3941
}
4042

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/ProjectExplorerFilterActionGroup.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2003, 2018 IBM Corporation and others.
2+
* Copyright (c) 2003, 2026 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -13,11 +13,14 @@
1313
*******************************************************************************/
1414
package org.eclipse.ui.internal.navigator.resources;
1515

16+
import org.eclipse.jface.action.Action;
17+
import org.eclipse.jface.action.IAction;
1618
import org.eclipse.jface.action.IToolBarManager;
1719
import org.eclipse.jface.resource.ResourceLocator;
1820
import org.eclipse.ui.IActionBars;
1921
import org.eclipse.ui.internal.navigator.filters.FilterActionGroup;
2022
import org.eclipse.ui.internal.navigator.filters.SelectFiltersAction;
23+
import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorMessages;
2124
import org.eclipse.ui.navigator.CommonViewer;
2225

2326
/**
@@ -26,20 +29,42 @@
2629
public class ProjectExplorerFilterActionGroup extends FilterActionGroup {
2730

2831
private SelectFiltersAction selectFiltersAction;
32+
private Action toggleFilterAction;
2933
private final CommonViewer commonViewer;
34+
private final ProjectExplorerFilteredTree filteredTree;
3035

31-
public ProjectExplorerFilterActionGroup(CommonViewer aCommonViewer) {
36+
public ProjectExplorerFilterActionGroup(CommonViewer aCommonViewer,
37+
ProjectExplorerFilteredTree aFilteredTree) {
3238
super(aCommonViewer);
3339
commonViewer = aCommonViewer;
40+
filteredTree = aFilteredTree;
3441
makeActions();
3542
}
3643

3744
public void makeActions() {
3845
selectFiltersAction = new SelectFiltersAction(commonViewer, this);
39-
String imageFilePath = "icons/full/elcl16/filter_ps.svg"; //$NON-NLS-1$
40-
ResourceLocator.imageDescriptorFromBundle(getClass(), imageFilePath).ifPresent(d -> {
46+
String filterImagePath = "icons/full/elcl16/filter_ps.svg"; //$NON-NLS-1$
47+
ResourceLocator.imageDescriptorFromBundle(getClass(), filterImagePath).ifPresent(d -> {
4148
selectFiltersAction.setImageDescriptor(d);
4249
});
50+
51+
toggleFilterAction = new Action(WorkbenchNavigatorMessages.ProjectExplorer_toggleFilterField,
52+
IAction.AS_CHECK_BOX) {
53+
@Override
54+
public void run() {
55+
filteredTree.setShowFilterControls(isChecked());
56+
if (isChecked()) {
57+
filteredTree.getFilterControl().setFocus();
58+
} else {
59+
filteredTree.getViewer().getControl().setFocus();
60+
}
61+
}
62+
};
63+
toggleFilterAction.setToolTipText(WorkbenchNavigatorMessages.ProjectExplorer_toggleFilterField);
64+
String searchImagePath = "icons/full/elcl16/filter_ps.svg"; //$NON-NLS-1$
65+
ResourceLocator.imageDescriptorFromBundle(getClass(), searchImagePath).ifPresent(d -> {
66+
toggleFilterAction.setImageDescriptor(d);
67+
});
4368
}
4469

4570
@Override
@@ -48,6 +73,7 @@ public void fillActionBars(IActionBars actionBars) {
4873
}
4974

5075
protected void fillToolbar(IToolBarManager toolBar) {
76+
toolBar.add(toggleFilterAction);
5177
toolBar.add(selectFiltersAction);
5278
}
5379
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 vogella GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Lars Vogel <Lars.Vogel@vogella.com> - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.ui.internal.navigator.resources;
15+
16+
import org.eclipse.e4.ui.dialogs.filteredtree.FilteredTree;
17+
import org.eclipse.e4.ui.dialogs.filteredtree.PatternFilter;
18+
import org.eclipse.jface.viewers.TreeViewer;
19+
import org.eclipse.swt.SWT;
20+
import org.eclipse.swt.widgets.Composite;
21+
import org.eclipse.ui.navigator.CommonViewer;
22+
23+
/**
24+
* A {@link FilteredTree} subclass that hosts a {@link CommonViewer} as its
25+
* embedded tree viewer, allowing the Project Explorer to use the e4
26+
* FilteredTree infrastructure while retaining full Common Navigator behaviour.
27+
*
28+
* <p>
29+
* The filter bar is hidden by default and can be shown on demand via
30+
* {@link #setShowFilterControls(boolean)}.
31+
* </p>
32+
*/
33+
public class ProjectExplorerFilteredTree extends FilteredTree {
34+
35+
private final String viewerId;
36+
37+
/**
38+
* Creates the filtered tree. Uses the protected no-arg super constructor so
39+
* that {@link #doCreateTreeViewer} is already overridden before
40+
* {@link #init} creates the controls.
41+
*
42+
* @param parent parent composite (typically the displayAreas stack composite)
43+
* @param viewerId the view-site ID passed to {@link CommonViewer}
44+
*/
45+
public ProjectExplorerFilteredTree(Composite parent, String viewerId) {
46+
super(parent); // protected ctor – does NOT call init() yet
47+
this.viewerId = viewerId;
48+
init(SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, new PatternFilter());
49+
}
50+
51+
@Override
52+
protected void init(int treeStyle, PatternFilter filter) {
53+
super.init(treeStyle, filter); // creates filterComposite and treeComposite
54+
setShowFilterControls(false); // hide the filter bar initially
55+
}
56+
57+
/**
58+
* Returns a {@link CommonViewer} instead of the default
59+
* {@link org.eclipse.e4.ui.dialogs.filteredtree.FilteredTree.NotifyingTreeViewer}.
60+
*/
61+
@Override
62+
protected TreeViewer doCreateTreeViewer(Composite parent, int style) {
63+
return new CommonViewer(viewerId, parent, style);
64+
}
65+
66+
@Override
67+
public CommonViewer getViewer() {
68+
return (CommonViewer) super.getViewer();
69+
}
70+
}

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/plugin/WorkbenchNavigatorMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public class WorkbenchNavigatorMessages extends NLS {
6666
public static String ProjectExplorer_toolTip;
6767
public static String ProjectExplorer_toolTip2;
6868
public static String ProjectExplorer_toolTip3;
69+
public static String ProjectExplorer_toggleFilterField;
6970

7071
public static String ProjectExplorerPart_workspace;
7172
public static String ProjectExplorerPart_workingSetModel;

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/plugin/messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ WorkingSetActionProvider_multipleWorkingSets=Multiple Working Sets
4242
ProjectExplorer_toolTip= Working Set: {0}
4343
ProjectExplorer_toolTip2= {0} - Working Set: {1}
4444
ProjectExplorer_toolTip3= {0}/{1}
45+
ProjectExplorer_toggleFilterField=Toggle Filter Field
4546
ProjectExplorerPart_workspace=Workspace
4647
ProjectExplorerPart_workingSetModel=Working Sets
4748
ResourceMgmtActionProvider_logTitle=Exception in {0}. run: {1}

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/navigator/resources/ProjectExplorer.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import org.eclipse.ui.internal.navigator.framelist.FrameList;
6666
import org.eclipse.ui.internal.navigator.framelist.TreeFrame;
6767
import org.eclipse.ui.internal.navigator.resources.ProjectExplorerActionGroup;
68+
import org.eclipse.ui.internal.navigator.resources.ProjectExplorerFilteredTree;
6869
import org.eclipse.ui.internal.navigator.resources.ResourceToItemsMapper;
6970
import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorMessages;
7071
import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorPlugin;
@@ -120,6 +121,7 @@ public final class ProjectExplorer extends CommonNavigator implements ISecondary
120121

121122
private List<UserFilter> userFilters;
122123
private EmptyWorkspaceHelper emptyWorkspaceHelper;
124+
private ProjectExplorerFilteredTree filteredTree;
123125

124126
@Override
125127
public void init(IViewSite site, IMemento memento) throws PartInitException {
@@ -167,7 +169,7 @@ public void createPartControl(Composite aParent) {
167169

168170
@Override
169171
protected ActionGroup createCommonActionGroup() {
170-
return new ProjectExplorerActionGroup(this, getCommonViewer(), getLinkHelperService());
172+
return new ProjectExplorerActionGroup(this, getCommonViewer(), getLinkHelperService(), filteredTree);
171173
}
172174

173175
/**
@@ -332,10 +334,17 @@ protected void handleDoubleClick(DoubleClickEvent anEvent) {
332334
super.handleDoubleClick(anEvent);
333335
}
334336

337+
@Override
338+
protected CommonViewer createCommonViewerObject(Composite aParent) {
339+
filteredTree = new ProjectExplorerFilteredTree(aParent, getViewSite().getId());
340+
return filteredTree.getViewer();
341+
}
342+
335343
@Override
336344
protected CommonViewer createCommonViewer(Composite aParent) {
337345
CommonViewer viewer = super.createCommonViewer(aParent);
338346
emptyWorkspaceHelper.setNonEmptyControl(viewer.getControl());
347+
emptyWorkspaceHelper.setNonEmptyStackControl(filteredTree);
339348
WorkbenchViewerSetup.setupViewer(viewer);
340349
return viewer;
341350
}

0 commit comments

Comments
 (0)