Skip to content

Commit 0835105

Browse files
committed
Implement filter-based table viewer via the new FilterTable class
This moves the viewer-agnostic components of the FilterTree widget into an AbstractFilteredStructuredViewer base class, which is then used to implement the FilterTable widget. The base class has been moved to org.eclipse.jface.text together with the TextMatcher, to allow it to be used together with both an E3 and E4 workbench. For the AbstractFilteredStructuredViewer, following methods have been added to support this abstraction: - isShowFilterControls() - isQuickSelectionMode() - init(int) For the FilteredTree, following fields and methods have been marked as for-removal: - filterToolBar - clearButtonControl - updateToolbar(boolean) To avoid code-duplication as a result of bug 260664, the code of clearText() has been moved to a separate doClearText() method, so that the same code can be invoked inside the listeners, without having to worry about clearText() being overridden by subclasses. This change adds a dependency from org.eclipse.jface.text to org.eclipse.ui.workbench.
1 parent 7adb4ef commit 0835105

File tree

35 files changed

+1459
-538
lines changed

35 files changed

+1459
-538
lines changed

bundles/org.eclipse.e4.ui.dialogs/META-INF/MANIFEST.MF

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %pluginName
44
Bundle-SymbolicName: org.eclipse.e4.ui.dialogs
5-
Bundle-Version: 1.5.0.qualifier
5+
Bundle-Version: 1.6.0.qualifier
66
Bundle-RequiredExecutionEnvironment: JavaSE-17
77
Bundle-Vendor: %providerName
88
Bundle-Localization: plugin
99
Export-Package: org.eclipse.e4.ui.dialogs.filteredtree,
1010
org.eclipse.e4.ui.dialogs.textbundles;x-internal:=true,
1111
org.eclipse.e4.ui.internal.dialogs.about;x-internal:=true
12+
Import-Package: org.eclipse.jface.internal.text,
13+
org.eclipse.jface.text
1214
Require-Bundle: org.eclipse.jface;bundle-version="3.11.0",
1315
org.eclipse.core.runtime;bundle-version="3.29.0"
1416
Automatic-Module-Name: org.eclipse.e4.ui.dialogs
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Patrick Ziegler 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+
* Patrick Ziegler - initial API and implementation
13+
******************************************************************************/
14+
package org.eclipse.e4.ui.dialogs.filteredtree;
15+
16+
import org.eclipse.core.runtime.IProgressMonitor;
17+
import org.eclipse.core.runtime.IStatus;
18+
import org.eclipse.core.runtime.Status;
19+
import org.eclipse.e4.ui.dialogs.textbundles.E4DialogMessages;
20+
import org.eclipse.jface.internal.text.TextMatcher;
21+
import org.eclipse.jface.text.AbstractFilteredStructuredViewer;
22+
import org.eclipse.jface.viewers.ILabelProvider;
23+
import org.eclipse.jface.viewers.ISelection;
24+
import org.eclipse.jface.viewers.TableViewer;
25+
import org.eclipse.jface.viewers.Viewer;
26+
import org.eclipse.jface.viewers.ViewerFilter;
27+
import org.eclipse.osgi.util.NLS;
28+
import org.eclipse.swt.SWT;
29+
import org.eclipse.swt.accessibility.AccessibleAdapter;
30+
import org.eclipse.swt.accessibility.AccessibleEvent;
31+
import org.eclipse.swt.events.KeyAdapter;
32+
import org.eclipse.swt.events.KeyEvent;
33+
import org.eclipse.swt.layout.GridData;
34+
import org.eclipse.swt.layout.GridLayout;
35+
import org.eclipse.swt.widgets.Composite;
36+
import org.eclipse.swt.widgets.Table;
37+
import org.eclipse.swt.widgets.TableItem;
38+
39+
/**
40+
* A simple control that provides a text widget and a table viewer. The contents
41+
* of the text widget are used to drive a TextMatcher that is on the viewer.
42+
*
43+
* @since 1.6
44+
*/
45+
public class FilteredTable extends AbstractFilteredStructuredViewer {
46+
47+
/**
48+
* Default time for refresh job delay in ms
49+
*/
50+
private static final long DEFAULT_REFRESH_TIME = 200;
51+
52+
private TableViewer tableViewer;
53+
private TextMatcher matcher;
54+
55+
public FilteredTable(Composite parent, int style) {
56+
this(parent, style, DEFAULT_REFRESH_TIME);
57+
}
58+
59+
public FilteredTable(Composite parent, int style, long refreshTime) {
60+
super(parent, style, refreshTime);
61+
init(style);
62+
}
63+
64+
@Override
65+
protected void createControl(Composite parent, int treeStyle) {
66+
super.createControl(parent, treeStyle);
67+
68+
Composite tableComposite = new Composite(this, SWT.NONE);
69+
GridLayout treeCompositeLayout = new GridLayout();
70+
treeCompositeLayout.marginHeight = 0;
71+
treeCompositeLayout.marginWidth = 0;
72+
tableComposite.setLayout(treeCompositeLayout);
73+
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
74+
tableComposite.setLayoutData(data);
75+
createTableControl(tableComposite, treeStyle);
76+
}
77+
78+
@Override
79+
protected void createFilterText(Composite parent) {
80+
super.createFilterText(parent);
81+
getFilterControl().getAccessible().addAccessibleListener(new AccessibleAdapter() {
82+
@Override
83+
public void getName(AccessibleEvent e) {
84+
String filterTextString = getFilterControl().getText();
85+
if (filterTextString.isEmpty() || filterTextString.equals(getInitialText())) {
86+
e.result = getInitialText();
87+
} else {
88+
e.result = NLS.bind(E4DialogMessages.FilteredTree_AccessibleListenerFiltered,
89+
new String[] { filterTextString, String.valueOf(getFilteredItemsCount()) });
90+
}
91+
}
92+
93+
/**
94+
* Return the number of filtered items
95+
*
96+
* @return int
97+
*/
98+
private int getFilteredItemsCount() {
99+
return getViewer().getTable().getItemCount();
100+
}
101+
});
102+
103+
getFilterControl().addKeyListener(new KeyAdapter() {
104+
@Override
105+
public void keyPressed(KeyEvent e) {
106+
// on a CR we want to transfer focus to the list
107+
boolean hasItems = getViewer().getTable().getItemCount() > 0;
108+
if (hasItems && e.keyCode == SWT.ARROW_DOWN) {
109+
getViewer().getTable().setFocus();
110+
return;
111+
}
112+
}
113+
});
114+
115+
// enter key set focus to table
116+
getFilterControl().addTraverseListener(e -> {
117+
if (e.detail == SWT.TRAVERSE_RETURN) {
118+
e.doit = false;
119+
if (getViewer().getTable().getItemCount() != 0) {
120+
// if the initial filter text hasn't changed, do not try
121+
// to match
122+
boolean hasFocus = getViewer().getTable().setFocus();
123+
boolean textChanged = !getInitialText().equals(getFilterControl().getText().trim());
124+
if (hasFocus && textChanged && getFilterControl().getText().trim().length() > 0) {
125+
Table table = getViewer().getTable();
126+
TableItem item;
127+
if (table.getSelectionCount() > 0) {
128+
item = getFirstMatchingItem(table.getSelection());
129+
} else {
130+
item = getFirstMatchingItem(table.getItems());
131+
}
132+
if (item != null) {
133+
table.setSelection(new TableItem[] { item });
134+
ISelection sel = getViewer().getSelection();
135+
getViewer().setSelection(sel, true);
136+
}
137+
}
138+
}
139+
}
140+
});
141+
}
142+
143+
/**
144+
* Return the first item in the tree that matches the filter pattern.
145+
*
146+
* @return the first matching TreeItem
147+
*/
148+
private TableItem getFirstMatchingItem(TableItem[] items) {
149+
for (TableItem item : items) {
150+
if (matcher == null) {
151+
return item;
152+
}
153+
154+
ILabelProvider labelProvider = (ILabelProvider) getViewer().getLabelProvider();
155+
if (matcher.match(labelProvider.getText(item.getData()))) {
156+
return item;
157+
}
158+
}
159+
return null;
160+
}
161+
162+
/**
163+
* Creates and set up the table and table viewer. This method calls
164+
* {@link #doCreateTableViewer(Composite, int)} to create the table viewer.
165+
* Subclasses should override {@link #doCreateTableViewer(Composite, int)}
166+
* instead of overriding this method.
167+
*
168+
* @param parent parent <code>Composite</code>
169+
* @param style SWT style bits used to create the table
170+
* @return the table
171+
*/
172+
protected Table createTableControl(Composite parent, int style) {
173+
tableViewer = doCreateTableViewer(parent, style);
174+
tableViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
175+
tableViewer.addFilter(new ViewerFilter() {
176+
@Override
177+
public boolean select(Viewer viewer, Object parentElement, Object element) {
178+
if (matcher == null) {
179+
return true;
180+
}
181+
ILabelProvider labelProvider = (ILabelProvider) tableViewer.getLabelProvider();
182+
return matcher.match(labelProvider.getText(element));
183+
}
184+
});
185+
return tableViewer.getTable();
186+
}
187+
188+
/**
189+
* Creates the table viewer. Subclasses may override.
190+
*
191+
* @param parent the parent composite
192+
* @param style SWT style bits used to create the table viewer
193+
* @return the table viewer
194+
*/
195+
protected TableViewer doCreateTableViewer(Composite parent, int style) {
196+
return new TableViewer(parent, style);
197+
}
198+
199+
@Override
200+
public TableViewer getViewer() {
201+
return tableViewer;
202+
}
203+
204+
@Override
205+
protected BasicUIJob doCreateRefreshJob() {
206+
return new BasicUIJob("Refresh Filter", getDisplay()) {//$NON-NLS-1$
207+
@Override
208+
public IStatus runInUIThread(IProgressMonitor monitor) {
209+
if (getViewer().getControl().isDisposed()) {
210+
return Status.CANCEL_STATUS;
211+
}
212+
213+
String text = getFilterString();
214+
if (text == null) {
215+
return Status.OK_STATUS;
216+
}
217+
218+
boolean initial = getInitialText() != null && getInitialText().equals(text);
219+
if (initial) {
220+
matcher = null;
221+
} else if (text != null) {
222+
matcher = new TextMatcher(text + '*', true, false);
223+
}
224+
225+
tableViewer.refresh(true);
226+
227+
return Status.OK_STATUS;
228+
}
229+
};
230+
}
231+
232+
@Override
233+
protected boolean isShowFilterControls() {
234+
return true;
235+
}
236+
}

0 commit comments

Comments
 (0)