Skip to content

Commit 763212e

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 98ff2aa commit 763212e

File tree

29 files changed

+1194
-318
lines changed

29 files changed

+1194
-318
lines changed

bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %pluginName
44
Bundle-SymbolicName: org.eclipse.jface.text
5-
Bundle-Version: 3.26.100.qualifier
5+
Bundle-Version: 3.27.0.qualifier
66
Bundle-Vendor: %providerName
77
Bundle-Localization: plugin
88
Export-Package:
99
org.eclipse.jface.contentassist,
1010
org.eclipse.jface.contentassist.images,
11-
org.eclipse.jface.internal.text;x-internal:=true,
11+
org.eclipse.jface.internal.text;x-friends:="org.eclipse.ui.workbench",
1212
org.eclipse.jface.internal.text.codemining;x-internal:=true,
1313
org.eclipse.jface.internal.text.html;x-friends:="org.eclipse.ant.ui, org.eclipse.jdt.ui, org.eclipse.ltk.ui.refactoring, org.eclipse.pde.ui, org.eclipse.ui.editors, org.eclipse.xtext.ui",
1414
org.eclipse.jface.internal.text.link.contentassist;x-internal:=true,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
* SPDX-License-Identifier: EPL-2.0
1010
*******************************************************************************/
11-
package org.eclipse.ui.internal.misc;
11+
package org.eclipse.jface.internal.text;
1212

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

0 commit comments

Comments
 (0)