Skip to content

Commit 5ccbcda

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 abstract base class, which is then used to implement the FilterTable widget.
1 parent 6c459cc commit 5ccbcda

File tree

20 files changed

+1171
-341
lines changed

20 files changed

+1171
-341
lines changed

bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %pluginName
44
Bundle-SymbolicName: org.eclipse.ui.workbench; singleton:=true
5-
Bundle-Version: 3.134.100.qualifier
5+
Bundle-Version: 3.135.0.qualifier
66
Bundle-Activator: org.eclipse.ui.internal.WorkbenchPlugin
77
Bundle-ActivationPolicy: lazy
88
Bundle-Vendor: %providerName
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
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.ui.dialogs;
15+
16+
import org.eclipse.core.runtime.jobs.Job;
17+
import org.eclipse.jface.viewers.ColumnViewer;
18+
import org.eclipse.swt.SWT;
19+
import org.eclipse.swt.events.FocusAdapter;
20+
import org.eclipse.swt.events.FocusEvent;
21+
import org.eclipse.swt.events.MouseAdapter;
22+
import org.eclipse.swt.events.MouseEvent;
23+
import org.eclipse.swt.layout.GridData;
24+
import org.eclipse.swt.layout.GridLayout;
25+
import org.eclipse.swt.widgets.Composite;
26+
import org.eclipse.swt.widgets.Display;
27+
import org.eclipse.swt.widgets.Text;
28+
import org.eclipse.ui.IWorkbenchPreferenceConstants;
29+
import org.eclipse.ui.PlatformUI;
30+
import org.eclipse.ui.internal.WorkbenchMessages;
31+
import org.eclipse.ui.progress.WorkbenchJob;
32+
33+
/**
34+
* The abstract base class for a composite containing both a column viewer and a
35+
* text widget. The text drives the filter of the viewer. Current
36+
* implementations support both table- and tree-based viewers.
37+
*
38+
* @since 3.135
39+
*/
40+
public abstract sealed class AbstractFilteredViewer extends Composite permits FilteredTable, FilteredTree {
41+
42+
/**
43+
* The filter text widget to be used by this tree. This value may be
44+
* <code>null</code> if there is no filter widget, or if the controls have not
45+
* yet been created.
46+
*/
47+
protected Text filterText;
48+
49+
/**
50+
* The Composite on which the filter controls are created. This is used to set
51+
* the background color of the filter controls to match the surrounding
52+
* controls.
53+
*/
54+
protected Composite filterComposite;
55+
56+
/**
57+
* The text to initially show in the filter text control.
58+
*/
59+
protected String initialText = ""; //$NON-NLS-1$
60+
61+
/**
62+
* The job used to refresh the tree.
63+
*/
64+
private Job refreshJob;
65+
66+
/**
67+
* The parent composite of the filtered tree.
68+
*/
69+
protected Composite parent;
70+
71+
/**
72+
* Whether or not to show the filter controls (text and clear button). The
73+
* default is to show these controls. This can be overridden by providing a
74+
* setting in the product configuration file. The setting to add to not show
75+
* these controls is:
76+
*
77+
* org.eclipse.ui/SHOW_FILTERED_TEXTS=false
78+
*/
79+
protected boolean showFilterControls;
80+
81+
/**
82+
* Tells whether this filtered tree is used to make quick selections. In this
83+
* mode the first match in the tree is automatically selected while filtering
84+
* and the 'Enter' key is not used to move the focus to the tree.
85+
*/
86+
private boolean quickSelectionMode = false;
87+
88+
/**
89+
* Time for refresh job delay in terms of expansion in long value
90+
*/
91+
private final long refreshJobDelayInMillis;
92+
93+
protected AbstractFilteredViewer(Composite parent, int style, long refreshJobDelayInMillis) {
94+
super(parent, style);
95+
this.parent = parent;
96+
this.refreshJobDelayInMillis = refreshJobDelayInMillis;
97+
}
98+
99+
/**
100+
* Create the viewer.
101+
*
102+
* @param style the style bits for the {@code viewer}.
103+
*/
104+
protected final void init(int style) {
105+
showFilterControls = PlatformUI.getPreferenceStore()
106+
.getBoolean(IWorkbenchPreferenceConstants.SHOW_FILTERED_TEXTS);
107+
createControl(parent, style);
108+
createRefreshJob();
109+
setInitialText(WorkbenchMessages.FilteredTree_FilterMessage);
110+
setFont(parent.getFont());
111+
getViewer().getControl().addDisposeListener(e -> refreshJob.cancel());
112+
}
113+
114+
/**
115+
* Create the filtered tree's controls. Subclasses should override.
116+
*
117+
* @param parent the parent
118+
* @param treeStyle SWT style bits used to create the tree
119+
*/
120+
protected void createControl(Composite parent, int treeStyle) {
121+
GridLayout layout = new GridLayout();
122+
layout.marginHeight = 0;
123+
layout.marginWidth = 0;
124+
setLayout(layout);
125+
126+
if (parent.getLayout() instanceof GridLayout) {
127+
setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
128+
}
129+
130+
if (showFilterControls) {
131+
filterComposite = new Composite(this, SWT.NONE);
132+
GridLayout filterLayout = new GridLayout();
133+
filterLayout.marginHeight = 0;
134+
filterLayout.marginWidth = 0;
135+
filterComposite.setLayout(filterLayout);
136+
filterComposite.setFont(parent.getFont());
137+
138+
createFilterControls(filterComposite);
139+
filterComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
140+
}
141+
}
142+
143+
/**
144+
* Create the filter controls. By default, a text and corresponding tool bar
145+
* button that clears the contents of the text is created. Subclasses may
146+
* override.
147+
*
148+
* @param parent parent <code>Composite</code> of the filter controls
149+
* @return the <code>Composite</code> that contains the filter controls
150+
*/
151+
protected Composite createFilterControls(Composite parent) {
152+
createFilterText(parent);
153+
return parent;
154+
}
155+
156+
/**
157+
* Create the refresh job for the receiver.
158+
*/
159+
private void createRefreshJob() {
160+
refreshJob = doCreateRefreshJob();
161+
refreshJob.setSystem(true);
162+
}
163+
164+
/**
165+
* Creates a workbench job that will refresh the tree based on the current
166+
* filter text. Subclasses may override.
167+
*
168+
* @return a workbench job that can be scheduled to refresh the tree
169+
*/
170+
protected abstract WorkbenchJob doCreateRefreshJob();
171+
172+
/**
173+
* Creates the filter text and adds listeners. This method calls
174+
* {@link #doCreateFilterText(Composite)} to create the text control. Subclasses
175+
* should override {@link #doCreateFilterText(Composite)} instead of overriding
176+
* this method.
177+
*
178+
* @param parent <code>Composite</code> of the filter text
179+
*/
180+
protected void createFilterText(Composite parent) {
181+
filterText = doCreateFilterText(parent);
182+
183+
filterText.addFocusListener(new FocusAdapter() {
184+
@Override
185+
public void focusGained(FocusEvent e) {
186+
/*
187+
* Running in an asyncExec because the selectAll() does not appear to work when
188+
* using mouse to give focus to text.
189+
*/
190+
Display display = filterText.getDisplay();
191+
display.asyncExec(() -> {
192+
if (!filterText.isDisposed()) {
193+
if (getInitialText().equals(filterText.getText().trim())) {
194+
filterText.selectAll();
195+
}
196+
}
197+
});
198+
}
199+
200+
@Override
201+
public void focusLost(FocusEvent e) {
202+
if (filterText.getText().equals(initialText)) {
203+
setFilterText(""); //$NON-NLS-1$
204+
textChanged();
205+
}
206+
}
207+
});
208+
209+
filterText.addMouseListener(new MouseAdapter() {
210+
@Override
211+
public void mouseDown(MouseEvent e) {
212+
if (filterText.getText().equals(initialText)) {
213+
// XXX: We cannot call clearText() due to
214+
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=260664
215+
setFilterText(""); //$NON-NLS-1$
216+
textChanged();
217+
}
218+
}
219+
});
220+
221+
filterText.addModifyListener(e -> textChanged());
222+
223+
GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
224+
filterText.setLayoutData(gridData);
225+
}
226+
227+
/**
228+
* Creates the text control for entering the filter text. Subclasses may
229+
* override.
230+
*
231+
* @param parent the parent composite
232+
* @return the text widget
233+
*/
234+
protected Text doCreateFilterText(Composite parent) {
235+
return new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
236+
}
237+
238+
/**
239+
* Update the receiver after the text has changed.
240+
*/
241+
protected void textChanged() {
242+
// cancel currently running job first, to prevent unnecessary redraw
243+
refreshJob.cancel();
244+
refreshJob.schedule(getRefreshJobDelay());
245+
}
246+
247+
/**
248+
* Return the time delay that should be used when scheduling the filter refresh
249+
* job. Subclasses may override.
250+
*
251+
* @return a time delay in milliseconds before the job should run
252+
*/
253+
protected long getRefreshJobDelay() {
254+
return refreshJobDelayInMillis;
255+
}
256+
257+
/**
258+
* Clears the text in the filter text widget.
259+
*/
260+
protected void clearText() {
261+
setFilterText(""); //$NON-NLS-1$
262+
textChanged();
263+
}
264+
265+
/**
266+
* Set the text in the filter control.
267+
*
268+
* @param filterText the text to set.
269+
*/
270+
protected void setFilterText(String filterText) {
271+
if (this.filterText != null) {
272+
this.filterText.setText(filterText);
273+
selectAll();
274+
}
275+
}
276+
277+
/**
278+
* Get the column viewer of the receiver.
279+
*
280+
* @return the column viewer
281+
*/
282+
public abstract ColumnViewer getViewer();
283+
284+
/**
285+
* Get the filter text for the receiver, if it was created. Otherwise return
286+
* <code>null</code>.
287+
*
288+
* @return the filter Text, or null if it was not created
289+
*/
290+
public Text getFilterControl() {
291+
return filterText;
292+
}
293+
294+
/**
295+
* Convenience method to return the text of the filter control. If the text
296+
* widget is not created, then null is returned.
297+
*
298+
* @return String in the text, or null if the text does not exist
299+
*/
300+
protected String getFilterString() {
301+
return filterText != null ? filterText.getText() : null;
302+
}
303+
304+
/**
305+
* Set the text that will be shown until the first focus. A default value is
306+
* provided, so this method only need be called if overriding the default
307+
* initial text is desired.
308+
*
309+
* @param text initial text to appear in text field
310+
*/
311+
public void setInitialText(String text) {
312+
initialText = text;
313+
if (filterText != null) {
314+
filterText.setMessage(text);
315+
if (filterText.isFocusControl()) {
316+
setFilterText(initialText);
317+
textChanged();
318+
} else {
319+
getDisplay().asyncExec(() -> {
320+
if (!filterText.isDisposed() && filterText.isFocusControl()) {
321+
setFilterText(initialText);
322+
textChanged();
323+
}
324+
});
325+
}
326+
} else {
327+
setFilterText(initialText);
328+
textChanged();
329+
}
330+
}
331+
332+
/**
333+
* Sets whether this filtered tree is used to make quick selections. In this
334+
* mode the first match in the tree is automatically selected while filtering
335+
* and the 'Enter' key is not used to move the focus to the tree.
336+
* <p>
337+
* By default, this is set to <code>false</code>.
338+
* </p>
339+
*
340+
* @param enabled <code>true</code> if this filtered tree is used to make quick
341+
* selections, <code>false</code> otherwise
342+
*/
343+
public void setQuickSelectionMode(boolean enabled) {
344+
this.quickSelectionMode = enabled;
345+
}
346+
347+
/**
348+
* Select all text in the filter text field.
349+
*/
350+
protected void selectAll() {
351+
if (filterText != null) {
352+
filterText.selectAll();
353+
}
354+
}
355+
356+
/**
357+
* Get the initial text for the receiver.
358+
*
359+
* @return String
360+
*/
361+
protected String getInitialText() {
362+
return initialText;
363+
}
364+
365+
/**
366+
* Get the quick selection mode.
367+
*
368+
* @return {@code true}, if enabled.
369+
*/
370+
protected boolean isQuickSelectionMode() {
371+
return quickSelectionMode;
372+
}
373+
}

0 commit comments

Comments
 (0)