From 763212e6d27ec17b3389330de3b5c63136d85d23 Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Tue, 3 Dec 2024 21:38:09 +0100 Subject: [PATCH] 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. --- .../META-INF/MANIFEST.MF | 4 +- .../jface/internal/text}/TextMatcher.java | 2 +- .../AbstractFilteredStructuredViewer.java | 368 ++++++++++++++++++ .../META-INF/MANIFEST.MF | 4 +- .../org/eclipse/ui/dialogs/FilteredList.java | 2 +- .../org/eclipse/ui/dialogs/FilteredTable.java | 248 ++++++++++++ .../org/eclipse/ui/dialogs/FilteredTree.java | 332 ++-------------- .../org/eclipse/ui/dialogs/PatternFilter.java | 2 +- .../org/eclipse/ui/dialogs/SearchPattern.java | 2 +- .../ui/internal/about/AboutPluginsPage.java | 2 +- .../org.eclipse.ui.examples.filter/.classpath | 11 + .../org.eclipse.ui.examples.filter/.project | 34 ++ .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 11 + .../META-INF/MANIFEST.MF | 12 + .../org.eclipse.ui.examples.filter/README.TXT | 7 + .../org.eclipse.ui.examples.filter/about.html | 36 ++ .../build.properties | 8 + .../plugin.properties | 7 + .../org.eclipse.ui.examples.filter/plugin.xml | 26 ++ .../ui/examples/filter/FilteredTableView.java | 61 +++ .../ui/examples/filter/FilteredTreeView.java | 84 ++++ .../filter/FilteredVirtualTableView.java | 74 ++++ .../eclipse/ui/examples/filter/Messages.java | 30 ++ .../ui/examples/filter/messages.properties | 3 + .../jface/text/tests/JFaceTextTestSuite.java | 3 +- .../jface/text/tests}/TextMatcherTest.java | 4 +- .../org/eclipse/ui/tests/UiTestSuite.java | 4 +- .../filteredtree/FilteredTableTests.java | 129 ++++++ 29 files changed, 1194 insertions(+), 318 deletions(-) rename bundles/{org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/misc => org.eclipse.jface.text/src/org/eclipse/jface/internal/text}/TextMatcher.java (99%) create mode 100644 bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractFilteredStructuredViewer.java create mode 100644 bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTable.java create mode 100644 examples/org.eclipse.ui.examples.filter/.classpath create mode 100644 examples/org.eclipse.ui.examples.filter/.project create mode 100644 examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.core.resources.prefs create mode 100644 examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.jdt.core.prefs create mode 100644 examples/org.eclipse.ui.examples.filter/META-INF/MANIFEST.MF create mode 100644 examples/org.eclipse.ui.examples.filter/README.TXT create mode 100644 examples/org.eclipse.ui.examples.filter/about.html create mode 100644 examples/org.eclipse.ui.examples.filter/build.properties create mode 100644 examples/org.eclipse.ui.examples.filter/plugin.properties create mode 100644 examples/org.eclipse.ui.examples.filter/plugin.xml create mode 100644 examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTableView.java create mode 100644 examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTreeView.java create mode 100644 examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredVirtualTableView.java create mode 100644 examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/Messages.java create mode 100644 examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/messages.properties rename tests/{org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree => org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests}/TextMatcherTest.java (98%) create mode 100644 tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTableTests.java diff --git a/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF index 146f03a2a43..6fbefbc2f46 100644 --- a/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF @@ -2,13 +2,13 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jface.text -Bundle-Version: 3.26.100.qualifier +Bundle-Version: 3.27.0.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: org.eclipse.jface.contentassist, org.eclipse.jface.contentassist.images, - org.eclipse.jface.internal.text;x-internal:=true, + org.eclipse.jface.internal.text;x-friends:="org.eclipse.ui.workbench", org.eclipse.jface.internal.text.codemining;x-internal:=true, 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", org.eclipse.jface.internal.text.link.contentassist;x-internal:=true, diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/misc/TextMatcher.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/TextMatcher.java similarity index 99% rename from bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/misc/TextMatcher.java rename to bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/TextMatcher.java index 6b07140a2f1..59bb8e849aa 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/misc/TextMatcher.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/TextMatcher.java @@ -8,7 +8,7 @@ * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ -package org.eclipse.ui.internal.misc; +package org.eclipse.jface.internal.text; import java.util.ArrayList; import java.util.Arrays; diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractFilteredStructuredViewer.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractFilteredStructuredViewer.java new file mode 100644 index 00000000000..b9b8fbb1985 --- /dev/null +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractFilteredStructuredViewer.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 2024 Patrick Ziegler and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + ******************************************************************************/ +package org.eclipse.jface.text; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; + +import org.eclipse.core.runtime.jobs.Job; + +import org.eclipse.jface.viewers.StructuredViewer; + +/** + * The abstract base class for a composite containing both a column viewer and a text widget. The + * text drives the filter of the viewer. + * + * @since 3.27 + */ +public abstract class AbstractFilteredStructuredViewer extends Composite { + + /** + * The filter text widget to be used by this viewer. This value may be {@code null} if there is + * no filter widget, or if the controls have not yet been created. + */ + protected Text filterText; + + /** + * The Composite on which the filter controls are created. This is used to set the background + * color of the filter controls to match the surrounding controls. + */ + protected Composite filterComposite; + + /** + * The text to initially show in the filter text control. + */ + protected String initialText= ""; //$NON-NLS-1$ + + /** + * The job used to refresh the viewer. + */ + private Job refreshJob; + + /** + * The parent composite of the filtered viewer. + */ + protected Composite parent; + + /** + * Whether or not to show the filter controls (text and clear button). + */ + protected boolean showFilterControls; + + /** + * Tells whether this filtered viewer is used to make quick selections. In this mode the first + * match in the viewer is automatically selected while filtering and the 'Enter' key is not used + * to move the focus to the viewer. + */ + private boolean quickSelectionMode= false; + + /** + * Time for refresh job delay in terms of expansion in long value + */ + private final long refreshJobDelayInMillis; + + protected AbstractFilteredStructuredViewer(Composite parent, int style, long refreshJobDelayInMillis) { + super(parent, style); + this.parent= parent; + this.refreshJobDelayInMillis= refreshJobDelayInMillis; + } + + /** + * Create the filtered viewer. + * + * @param style the style bits for the {@link StructuredViewer}. + */ + protected void init(int style) { + showFilterControls= isShowFilterControls(); + createControl(parent, style); + createRefreshJob(); + setFont(parent.getFont()); + getViewer().getControl().addDisposeListener(e -> refreshJob.cancel()); + } + + /** + * Indicates whether the filter controls (text and clear button) should be shown. + * + * @return {@code true}, if the filter controls should be shown, otherwise {@code false}. + */ + protected abstract boolean isShowFilterControls(); + + /** + * Get the quick selection mode. + * + * @return {@code true}, if enabled otherwise {@code false}. + */ + protected boolean isQuickSelectionMode() { + return quickSelectionMode; + } + + /** + * Create the filtered viewer's controls. Subclasses should override. + * + * @param parentComposite the parent + * @param style SWT style bits used to create the viewer + */ + protected void createControl(Composite parentComposite, int style) { + GridLayout layout= new GridLayout(); + layout.marginHeight= 0; + layout.marginWidth= 0; + setLayout(layout); + + if (parentComposite.getLayout() instanceof GridLayout) { + setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + + if (showFilterControls) { + filterComposite= new Composite(this, SWT.NONE); + GridLayout filterLayout= new GridLayout(); + filterLayout.marginHeight= 0; + filterLayout.marginWidth= 0; + filterComposite.setLayout(filterLayout); + filterComposite.setFont(parentComposite.getFont()); + + createFilterControls(filterComposite); + filterComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); + } + } + + /** + * Create the filter controls. By default, a text and corresponding tool bar button that clears + * the contents of the text is created. Subclasses may override. + * + * @param parentComposite parent {@link Composite} of the filter controls + * @return the {@link Composite} that contains the filter controls + */ + protected Composite createFilterControls(Composite parentComposite) { + createFilterText(parentComposite); + return parentComposite; + } + + /** + * Create the refresh job for the receiver. + */ + private void createRefreshJob() { + refreshJob= doCreateRefreshJob(); + refreshJob.setSystem(true); + } + + /** + * Creates a workbench job that will refresh the viewer based on the current filter text. + * Subclasses may override. + * + * @return a workbench job that can be scheduled to refresh the viewer + */ + protected abstract Job doCreateRefreshJob(); + + /** + * Creates the filter text and adds listeners. This method calls + * {@link #doCreateFilterText(Composite)} to create the text control. Subclasses should override + * {@link #doCreateFilterText(Composite)} instead of overriding this method. + * + * @param parentComposite {@link Composite} of the filter text + */ + protected void createFilterText(Composite parentComposite) { + filterText= doCreateFilterText(parentComposite); + + filterText.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + /* + * Running in an asyncExec because the selectAll() does not appear to work when + * using mouse to give focus to text. + */ + Display display= filterText.getDisplay(); + display.asyncExec(() -> { + if (!filterText.isDisposed()) { + if (getInitialText().equals(filterText.getText().trim())) { + filterText.selectAll(); + } + } + }); + } + + @Override + public void focusLost(FocusEvent e) { + if (filterText.getText().equals(initialText)) { + //We cannot call clearText() due to + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=260664 + doClearText(); + } + } + }); + + filterText.addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent e) { + if (filterText.getText().equals(initialText)) { + //We cannot call clearText() due to + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=260664 + doClearText(); + } + } + }); + + filterText.addModifyListener(e -> textChanged()); + + GridData gridData= new GridData(SWT.FILL, SWT.CENTER, true, false); + filterText.setLayoutData(gridData); + } + + /** + * Creates the text control for entering the filter text. Subclasses may override. + * + * @param parentComposite the parent composite + * @return the text widget + */ + protected Text doCreateFilterText(Composite parentComposite) { + return new Text(parentComposite, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL); + } + + /** + * Update the receiver after the text has changed. + */ + protected void textChanged() { + // cancel currently running job first, to prevent unnecessary redraw + refreshJob.cancel(); + refreshJob.schedule(getRefreshJobDelay()); + } + + /** + * Return the time delay that should be used when scheduling the filter refresh job. Subclasses + * may override. + * + * @return a time delay in milliseconds before the job should run + */ + protected long getRefreshJobDelay() { + return refreshJobDelayInMillis; + } + + /** + * Clears the text in the filter text widget. + */ + protected void clearText() { + doClearText(); + } + + private void doClearText() { + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=260664 + setFilterText(""); //$NON-NLS-1$ + textChanged(); + } + + /** + * Set the text in the filter control. + * + * @param filterText the text to set. + */ + protected void setFilterText(String filterText) { + if (this.filterText != null) { + this.filterText.setText(filterText); + selectAll(); + } + } + + /** + * Get the structured viewer of the receiver. + * + * @return the structured viewer + */ + public abstract StructuredViewer getViewer(); + + /** + * Get the filter text for the receiver, if it was created. Otherwise return {@code null}. + * + * @return the filter Text, or null if it was not created + */ + public Text getFilterControl() { + return filterText; + } + + /** + * Convenience method to return the text of the filter control. If the text widget is not + * created, then null is returned. + * + * @return String in the text, or null if the text does not exist + */ + protected String getFilterString() { + return filterText != null ? filterText.getText() : null; + } + + /** + * Set the text that will be shown until the first focus. A default value is provided, so this + * method only need be called if overriding the default initial text is desired. + * + * @param text initial text to appear in text field + */ + public void setInitialText(String text) { + initialText= text; + if (filterText != null) { + filterText.setMessage(text); + if (filterText.isFocusControl()) { + setFilterText(initialText); + textChanged(); + } else { + getDisplay().asyncExec(() -> { + if (!filterText.isDisposed() && filterText.isFocusControl()) { + setFilterText(initialText); + textChanged(); + } + }); + } + } else { + setFilterText(initialText); + textChanged(); + } + } + + /** + * Sets whether this filtered viewer is used to make quick selections. In this mode the first + * match in the viewer is automatically selected while filtering and the 'Enter' key is not used + * to move the focus to the viewer. + *

+ * By default, this is set to {@code false}. + *

+ * + * @param enabled {@code true} if this filtered viewer is used to make quick selections, + * {@code false} otherwise + */ + public void setQuickSelectionMode(boolean enabled) { + this.quickSelectionMode= enabled; + } + + /** + * Select all text in the filter text field. + */ + protected void selectAll() { + if (filterText != null) { + filterText.selectAll(); + } + } + + /** + * Get the initial text for the receiver. + * + * @return String + */ + protected String getInitialText() { + return initialText; + } +} \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF index c0d826a9b67..fbef539e118 100644 --- a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.workbench; singleton:=true -Bundle-Version: 3.134.100.qualifier +Bundle-Version: 3.135.0.qualifier Bundle-Activator: org.eclipse.ui.internal.WorkbenchPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName @@ -128,6 +128,8 @@ Import-Package: com.ibm.icu.util, org.eclipse.e4.ui.workbench, org.eclipse.e4.ui.workbench.modeling, org.eclipse.e4.ui.workbench.renderers.swt, + org.eclipse.jface.internal.text, + org.eclipse.jface.text, org.osgi.service.event;version="[1.2.0,2.0.0)", org.osgi.service.event.propertytypes;version="[1.4.0,2.0.0)", org.w3c.dom, diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java index 56e69c48c5f..ae69574e5fa 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java @@ -23,6 +23,7 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.jface.internal.text.TextMatcher; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; @@ -36,7 +37,6 @@ import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.internal.WorkbenchMessages; -import org.eclipse.ui.internal.misc.TextMatcher; import org.eclipse.ui.internal.util.Util; import org.eclipse.ui.progress.WorkbenchJob; diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTable.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTable.java new file mode 100644 index 00000000000..1cab82fda22 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTable.java @@ -0,0 +1,248 @@ +/******************************************************************************* + * Copyright (c) 2024 Patrick Ziegler and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + ******************************************************************************/ +package org.eclipse.ui.dialogs; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.internal.text.TextMatcher; +import org.eclipse.jface.text.AbstractFilteredStructuredViewer; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.ui.IWorkbenchPreferenceConstants; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.progress.WorkbenchJob; + +/** + * A simple control that provides a text widget and a table viewer. The contents + * of the text widget are used to drive a TextMatcher that is on the viewer. + * + * @since 3.135 + */ +public class FilteredTable extends AbstractFilteredStructuredViewer { + + /** + * Default time for refresh job delay in ms + */ + private static final long DEFAULT_REFRESH_TIME = 200; + + private TableViewer tableViewer; + private TextMatcher matcher; + + public FilteredTable(Composite parent, int style) { + this(parent, style, DEFAULT_REFRESH_TIME); + } + + public FilteredTable(Composite parent, int style, long refreshTime) { + super(parent, style, refreshTime); + init(style); + } + + @Override + protected void createControl(Composite parent, int treeStyle) { + super.createControl(parent, treeStyle); + + Composite tableComposite = new Composite(this, SWT.NONE); + GridLayout treeCompositeLayout = new GridLayout(); + treeCompositeLayout.marginHeight = 0; + treeCompositeLayout.marginWidth = 0; + tableComposite.setLayout(treeCompositeLayout); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + tableComposite.setLayoutData(data); + createTableControl(tableComposite, treeStyle); + } + + @Override + protected void createFilterText(Composite parent) { + super.createFilterText(parent); + filterText.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + String filterTextString = filterText.getText(); + if (filterTextString.isEmpty() || filterTextString.equals(initialText)) { + e.result = initialText; + } else { + e.result = NLS.bind(WorkbenchMessages.FilteredTree_AccessibleListenerFiltered, + new String[] { filterTextString, String.valueOf(getFilteredItemsCount()) }); + } + } + + private int getFilteredItemsCount() { + return getViewer().getTable().getItemCount(); + } + }); + + filterText.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + // on a CR we want to transfer focus to the list + boolean hasItems = getViewer().getTable().getItemCount() > 0; + if (hasItems && e.keyCode == SWT.ARROW_DOWN) { + getViewer().getTable().setFocus(); + return; + } + } + }); + + // enter key set focus to tree + filterText.addTraverseListener(e -> { + if (isQuickSelectionMode()) { + return; + } + if (e.detail == SWT.TRAVERSE_RETURN) { + e.doit = false; + updateTableSelection(true); + } + }); + } + + /** + * Updates the selection in the tree, based on the filter text. + * + * @param setFocus {@code true} if the focus should be set on the tree, + * {@code false} otherwise + */ + private void updateTableSelection(boolean setFocus) { + Table table = tableViewer.getTable(); + if (table.getItemCount() != 0) { + // if the initial filter text hasn't changed, do not try + // to match + boolean hasFocus = setFocus ? table.setFocus() : true; + boolean textChanged = !getInitialText().equals(filterText.getText().trim()); + if (hasFocus && textChanged && filterText.getText().trim().length() > 0) { + TableItem item; + if (table.getSelectionCount() > 0) { + item = getFirstMatchingItem(table.getSelection()); + } else { + item = getFirstMatchingItem(table.getItems()); + } + if (item != null) { + table.setSelection(new TableItem[] { item }); + tableViewer.setSelection(tableViewer.getSelection(), true); + } + } + } + } + + /** + * Return the first item in the tree that matches the filter pattern. + * + * @return the first matching TreeItem + */ + private TableItem getFirstMatchingItem(TableItem[] items) { + for (TableItem item : items) { + if (matcher == null) { + return item; + } + + ILabelProvider labelProvider = (ILabelProvider) getViewer().getLabelProvider(); + if (matcher.match(labelProvider.getText(item.getData()))) { + return item; + } + } + return null; + } + + /** + * Creates and set up the table and table viewer. This method calls + * {@link #doCreateTableViewer(Composite, int)} to create the table viewer. + * Subclasses should override {@link #doCreateTableViewer(Composite, int)} + * instead of overriding this method. + * + * @param parent parent Composite + * @param style SWT style bits used to create the table + * @return the table + */ + protected Table createTableControl(Composite parent, int style) { + tableViewer = doCreateTableViewer(parent, style); + tableViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + tableViewer.addFilter(new ViewerFilter() { + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (matcher == null) { + return true; + } + ILabelProvider labelProvider = (ILabelProvider) tableViewer.getLabelProvider(); + return matcher.match(labelProvider.getText(element)); + } + }); + return tableViewer.getTable(); + } + + /** + * Creates the table viewer. Subclasses may override. + * + * @param parent the parent composite + * @param style SWT style bits used to create the table viewer + * @return the table viewer + */ + protected TableViewer doCreateTableViewer(Composite parent, int style) { + return new TableViewer(parent, style); + } + + @Override + protected WorkbenchJob doCreateRefreshJob() { + return new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + if (getViewer().getControl().isDisposed()) { + return Status.CANCEL_STATUS; + } + + String text = getFilterString(); + if (text == null) { + return Status.OK_STATUS; + } + + boolean initial = initialText != null && initialText.equals(text); + if (initial) { + matcher = null; + } else if (text != null) { + matcher = new TextMatcher(text + '*', true, false); + } + + tableViewer.refresh(true); + + if (isQuickSelectionMode()) { + updateTableSelection(false); + } + return Status.OK_STATUS; + } + }; + } + + @Override + protected boolean isShowFilterControls() { + return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.SHOW_FILTERED_TEXTS); + } + + @Override + public TableViewer getViewer() { + return tableViewer; + } +} diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTree.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTree.java index 24e2d884217..30c71b3a0f8 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTree.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTree.java @@ -19,9 +19,9 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.AbstractFilteredStructuredViewer; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.TreeViewer; @@ -29,20 +29,14 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.accessibility.AccessibleAdapter; import org.eclipse.swt.accessibility.AccessibleEvent; -import org.eclipse.swt.events.FocusAdapter; -import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IWorkbenchPreferenceConstants; @@ -57,20 +51,16 @@ * @see org.eclipse.ui.dialogs.PatternFilter * @since 3.2 */ -public class FilteredTree extends Composite { - - /** - * The filter text widget to be used by this tree. This value may be - * null if there is no filter widget, or if the controls have not - * yet been created. - */ - protected Text filterText; +public class FilteredTree extends AbstractFilteredStructuredViewer { /** *

* Note: As of 4.13 not used anymore *

+ * + * @deprecated As of 4.13 not used anymore. */ + @Deprecated(forRemoval = true, since = "2025-03") protected ToolBarManager filterToolBar; /** @@ -79,7 +69,9 @@ public class FilteredTree extends Composite { *

* * @since 3.5 + * @deprecated As of 4.13 not used anymore. */ + @Deprecated(forRemoval = true, since = "2025-03") protected Control clearButtonControl; /** @@ -88,70 +80,22 @@ public class FilteredTree extends Composite { */ protected TreeViewer treeViewer; - /** - * The Composite on which the filter controls are created. This is used to set - * the background color of the filter controls to match the surrounding - * controls. - */ - protected Composite filterComposite; - /** * The pattern filter for the tree. This value must not be null. */ private PatternFilter patternFilter; - /** - * The text to initially show in the filter text control. - */ - protected String initialText = ""; //$NON-NLS-1$ - - /** - * The job used to refresh the tree. - */ - private Job refreshJob; - - /** - * The parent composite of the filtered tree. - * - * @since 3.3 - */ - protected Composite parent; - - /** - * Whether or not to show the filter controls (text and clear button). The - * default is to show these controls. This can be overridden by providing a - * setting in the product configuration file. The setting to add to not show - * these controls is: - * - * org.eclipse.ui/SHOW_FILTERED_TEXTS=false - */ - protected boolean showFilterControls; - /** * @since 3.3 */ protected Composite treeComposite; - /** - * Tells whether this filtered tree is used to make quick selections. In this - * mode the first match in the tree is automatically selected while filtering - * and the 'Enter' key is not used to move the focus to the tree. - * - * @since 3.105 - */ - private boolean quickSelectionMode = false; - /** * Maximum time spent expanding the tree after the filter text has been updated * (this is only used if we were able to at least expand the visible nodes) */ private static final long SOFT_MAX_EXPAND_TIME = 200; - /** - * Time for refresh job delay in terms of expansion in long value - */ - private final long refreshJobDelayInMillis; - /** * Default time for refresh job delay in ms */ @@ -179,9 +123,7 @@ public class FilteredTree extends Composite { * @since 3.116 */ public FilteredTree(Composite parent, boolean useNewLook, boolean useFastHashLookup) { - super(parent, SWT.NONE); - this.parent = parent; - this.refreshJobDelayInMillis = DEFAULT_REFRESH_TIME; + super(parent, SWT.NONE, DEFAULT_REFRESH_TIME); if (treeViewer != null) { treeViewer.setUseHashlookup(useFastHashLookup); } @@ -225,9 +167,7 @@ public FilteredTree(Composite parent, int treeStyle, PatternFilter filter, boole */ public FilteredTree(Composite parent, int treeStyle, PatternFilter filter, boolean useNewLook, boolean useFastHashLookup, long refreshJobDelayInMillis) { - super(parent, SWT.NONE); - this.parent = parent; - this.refreshJobDelayInMillis = refreshJobDelayInMillis; + super(parent, SWT.NONE, refreshJobDelayInMillis); init(treeStyle, filter); if (treeViewer != null) { treeViewer.setUseHashlookup(useFastHashLookup); @@ -257,9 +197,7 @@ public FilteredTree(Composite parent, int treeStyle, PatternFilter filter, boole */ @Deprecated protected FilteredTree(Composite parent) { - super(parent, SWT.NONE); - this.refreshJobDelayInMillis = DEFAULT_REFRESH_TIME; - this.parent = parent; + super(parent, SWT.NONE, DEFAULT_REFRESH_TIME); } /** @@ -347,13 +285,13 @@ public FilteredTree(Composite parent, int treeStyle, PatternFilter filter, boole */ protected void init(int treeStyle, PatternFilter filter) { patternFilter = filter; - showFilterControls = PlatformUI.getPreferenceStore() - .getBoolean(IWorkbenchPreferenceConstants.SHOW_FILTERED_TEXTS); - createControl(parent, treeStyle); - createRefreshJob(); + super.init(treeStyle); setInitialText(WorkbenchMessages.FilteredTree_FilterMessage); - setFont(parent.getFont()); + } + @Override + protected boolean isShowFilterControls() { + return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.SHOW_FILTERED_TEXTS); } /** @@ -362,27 +300,9 @@ protected void init(int treeStyle, PatternFilter filter) { * @param parent the parent * @param treeStyle SWT style bits used to create the tree */ + @Override protected void createControl(Composite parent, int treeStyle) { - GridLayout layout = new GridLayout(); - layout.marginHeight = 0; - layout.marginWidth = 0; - setLayout(layout); - - if (parent.getLayout() instanceof GridLayout) { - setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - } - - if (showFilterControls) { - filterComposite = new Composite(this, SWT.NONE); - GridLayout filterLayout = new GridLayout(); - filterLayout.marginHeight = 0; - filterLayout.marginWidth = 0; - filterComposite.setLayout(filterLayout); - filterComposite.setFont(parent.getFont()); - - createFilterControls(filterComposite); - filterComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); - } + super.createControl(parent, treeStyle); treeComposite = new Composite(this, SWT.NONE); GridLayout treeCompositeLayout = new GridLayout(); @@ -394,19 +314,6 @@ protected void createControl(Composite parent, int treeStyle) { createTreeControl(treeComposite, treeStyle); } - /** - * Create the filter controls. By default, a text and corresponding tool bar - * button that clears the contents of the text is created. Subclasses may - * override. - * - * @param parent parent Composite of the filter controls - * @return the Composite that contains the filter controls - */ - protected Composite createFilterControls(Composite parent) { - createFilterText(parent); - return parent; - } - /** * Creates and set up the tree and tree viewer. This method calls * {@link #doCreateTreeViewer(Composite, int)} to create the tree viewer. @@ -421,7 +328,6 @@ protected Control createTreeControl(Composite parent, int style) { treeViewer = doCreateTreeViewer(parent, style); GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); treeViewer.getControl().setLayoutData(data); - treeViewer.getControl().addDisposeListener(e -> refreshJob.cancel()); if (treeViewer instanceof NotifyingTreeViewer) { patternFilter.setUseCache(true); } @@ -461,14 +367,6 @@ private TreeItem getFirstMatchingItem(TreeItem[] items) { return null; } - /** - * Create the refresh job for the receiver. - */ - private void createRefreshJob() { - refreshJob = doCreateRefreshJob(); - refreshJob.setSystem(true); - } - /** * Creates a workbench job that will refresh the tree based on the current * filter text. Subclasses may override. @@ -477,6 +375,7 @@ private void createRefreshJob() { * * @since 3.4 */ + @Override protected WorkbenchJob doCreateRefreshJob() { return new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$ @Override @@ -536,7 +435,7 @@ && recursiveExpand(items, monitor, stopTime, new int[] { numVisibleItems })) { if (items.length > 0 && getViewer().getTree().getSelectionCount() == 0) { treeViewer.getTree().setTopItem(items[0]); } - if (quickSelectionMode) + if (isQuickSelectionMode()) updateTreeSelection(false); redrawFalseControl.setRedraw(true); } @@ -583,7 +482,9 @@ private boolean recursiveExpand(TreeItem[] items, IProgressMonitor monitor, long * override. * * @param visible boolean + * @deprecated Not used since 4.13 */ + @Deprecated(forRemoval = true, since = "2025-03") protected void updateToolbar(boolean visible) { // nothing to do } @@ -596,8 +497,9 @@ protected void updateToolbar(boolean visible) { * * @param parent Composite of the filter text */ + @Override protected void createFilterText(Composite parent) { - filterText = doCreateFilterText(parent); + super.createFilterText(parent); filterText.getAccessible().addAccessibleListener(new AccessibleAdapter() { @Override public void getName(AccessibleEvent e) { @@ -641,44 +543,6 @@ private int itemCount(TreeItem treeItem) { } }); - filterText.addFocusListener(new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - /* - * Running in an asyncExec because the selectAll() does not appear to work when - * using mouse to give focus to text. - */ - Display display = filterText.getDisplay(); - display.asyncExec(() -> { - if (!filterText.isDisposed()) { - if (getInitialText().equals(filterText.getText().trim())) { - filterText.selectAll(); - } - } - }); - } - - @Override - public void focusLost(FocusEvent e) { - if (filterText.getText().equals(initialText)) { - setFilterText(""); //$NON-NLS-1$ - textChanged(); - } - } - }); - - filterText.addMouseListener(new MouseAdapter() { - @Override - public void mouseDown(MouseEvent e) { - if (filterText.getText().equals(initialText)) { - // XXX: We cannot call clearText() due to - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=260664 - setFilterText(""); //$NON-NLS-1$ - textChanged(); - } - } - }); - filterText.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { @@ -693,7 +557,7 @@ public void keyPressed(KeyEvent e) { // enter key set focus to tree filterText.addTraverseListener(e -> { - if (quickSelectionMode) { + if (isQuickSelectionMode()) { return; } if (e.detail == SWT.TRAVERSE_RETURN) { @@ -701,11 +565,6 @@ public void keyPressed(KeyEvent e) { updateTreeSelection(true); } }); - - filterText.addModifyListener(e -> textChanged()); - - GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false); - filterText.setLayoutData(gridData); } /** @@ -740,19 +599,6 @@ protected void updateTreeSelection(boolean setFocus) { } } - /** - * Creates the text control for entering the filter text. Subclasses may - * override. - * - * @param parent the parent composite - * @return the text widget - * - * @since 3.3 - */ - protected Text doCreateFilterText(Composite parent) { - return new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL); - } - private String previousFilterText; private boolean narrowingDown; @@ -760,56 +606,13 @@ protected Text doCreateFilterText(Composite parent) { /** * Update the receiver after the text has changed. */ + @Override protected void textChanged() { narrowingDown = previousFilterText == null || previousFilterText.equals(WorkbenchMessages.FilteredTree_FilterMessage) || getFilterString().startsWith(previousFilterText); previousFilterText = getFilterString(); - // cancel currently running job first, to prevent unnecessary redraw - refreshJob.cancel(); - refreshJob.schedule(getRefreshJobDelay()); - } - - /** - * Return the time delay that should be used when scheduling the filter refresh - * job. Subclasses may override. - * - * @return a time delay in milliseconds before the job should run - * - * @since 3.5 - */ - protected long getRefreshJobDelay() { - return refreshJobDelayInMillis; - } - - /** - * Set the background for the widgets that support the filter text area. - * - * @param background background Color to set - */ - @Override - public void setBackground(Color background) { - super.setBackground(background); - } - - /** - * Clears the text in the filter text widget. - */ - protected void clearText() { - setFilterText(""); //$NON-NLS-1$ - textChanged(); - } - - /** - * Set the text in the filter control. - * - * @param filterText the text to set. - */ - protected void setFilterText(String filterText) { - if (this.filterText != null) { - this.filterText.setText(filterText); - selectAll(); - } + super.textChanged(); } /** @@ -826,92 +629,11 @@ public final PatternFilter getPatternFilter() { * * @return the tree viewer */ + @Override public TreeViewer getViewer() { return treeViewer; } - /** - * Get the filter text for the receiver, if it was created. Otherwise return - * null. - * - * @return the filter Text, or null if it was not created - */ - public Text getFilterControl() { - return filterText; - } - - /** - * Convenience method to return the text of the filter control. If the text - * widget is not created, then null is returned. - * - * @return String in the text, or null if the text does not exist - */ - protected String getFilterString() { - return filterText != null ? filterText.getText() : null; - } - - /** - * Set the text that will be shown until the first focus. A default value is - * provided, so this method only need be called if overriding the default - * initial text is desired. - * - * @param text initial text to appear in text field - */ - public void setInitialText(String text) { - initialText = text; - if (filterText != null) { - filterText.setMessage(text); - if (filterText.isFocusControl()) { - setFilterText(initialText); - textChanged(); - } else { - getDisplay().asyncExec(() -> { - if (!filterText.isDisposed() && filterText.isFocusControl()) { - setFilterText(initialText); - textChanged(); - } - }); - } - } else { - setFilterText(initialText); - textChanged(); - } - } - - /** - * Sets whether this filtered tree is used to make quick selections. In this - * mode the first match in the tree is automatically selected while filtering - * and the 'Enter' key is not used to move the focus to the tree. - *

- * By default, this is set to false. - *

- * - * @param enabled true if this filtered tree is used to make quick - * selections, false otherwise - * @since 3.105 - */ - public void setQuickSelectionMode(boolean enabled) { - this.quickSelectionMode = enabled; - } - - /** - * Select all text in the filter text field. - */ - protected void selectAll() { - if (filterText != null) { - filterText.selectAll(); - } - } - - /** - * Get the initial text for the receiver. - * - * @return String - */ - protected String getInitialText() { - return initialText; - } - /** * Return a bold font if the given element matches the given pattern. Clients * can opt to call this method from a Viewer's label provider to get a bold font diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java index b27c7d89b43..be0c5f73493 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java @@ -17,13 +17,13 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.jface.internal.text.TextMatcher; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ContentViewer; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; -import org.eclipse.ui.internal.misc.TextMatcher; /** * A filter used in conjunction with FilteredTree. In order to diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java index 0b219cab6f1..c783d16dc5c 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java @@ -13,8 +13,8 @@ *******************************************************************************/ package org.eclipse.ui.dialogs; +import org.eclipse.jface.internal.text.TextMatcher; import org.eclipse.jface.util.Util; -import org.eclipse.ui.internal.misc.TextMatcher; /** * A search pattern defines how search results are found. diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java index f39c445e9b1..a1f3c531071 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java @@ -40,6 +40,7 @@ import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.internal.text.TextMatcher; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.util.ConfigureColumns; import org.eclipse.jface.viewers.ArrayContentProvider; @@ -73,7 +74,6 @@ import org.eclipse.ui.internal.WorkbenchMessages; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.misc.StatusUtil; -import org.eclipse.ui.internal.misc.TextMatcher; import org.eclipse.ui.internal.util.BundleUtility; import org.eclipse.ui.progress.WorkbenchJob; import org.eclipse.ui.statushandlers.StatusManager; diff --git a/examples/org.eclipse.ui.examples.filter/.classpath b/examples/org.eclipse.ui.examples.filter/.classpath new file mode 100644 index 00000000000..c5932f42c7e --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/org.eclipse.ui.examples.filter/.project b/examples/org.eclipse.ui.examples.filter/.project new file mode 100644 index 00000000000..7186350c037 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/.project @@ -0,0 +1,34 @@ + + + org.eclipse.ui.examples.filter + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + org.eclipse.m2e.core.maven2Nature + + diff --git a/examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.core.resources.prefs b/examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.jdt.core.prefs b/examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..3d1fdb619b3 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/examples/org.eclipse.ui.examples.filter/META-INF/MANIFEST.MF b/examples/org.eclipse.ui.examples.filter/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..3f6d27484b7 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/META-INF/MANIFEST.MF @@ -0,0 +1,12 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name +Bundle-SymbolicName: org.eclipse.ui.examples.filter;singleton:=true +Bundle-Vendor: %Bundle-Vendor +Bundle-Version: 1.0.0.qualifier +Require-Bundle: org.eclipse.ui;bundle-version="3.207.0", + org.eclipse.core.runtime;bundle-version="3.32.100" +Bundle-RequiredExecutionEnvironment: JavaSE-17 +Automatic-Module-Name: org.eclipse.ui.examples.filter +Bundle-Localization: plugin +Bundle-ActivationPolicy: lazy diff --git a/examples/org.eclipse.ui.examples.filter/README.TXT b/examples/org.eclipse.ui.examples.filter/README.TXT new file mode 100644 index 00000000000..a641f19b8c0 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/README.TXT @@ -0,0 +1,7 @@ +This examples plug-in demonstrates the following features: + +- Creating a tree viewer with a simple filter +- Creating a table viewer with a simple filter +- Creating a lazy table viewer with a simple filter + +The examples can be seen by opening the "Filter Tree View", "Filter Table View" and "Filter (Virtual) Table View, respectively. \ No newline at end of file diff --git a/examples/org.eclipse.ui.examples.filter/about.html b/examples/org.eclipse.ui.examples.filter/about.html new file mode 100644 index 00000000000..164f781a8fd --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/about.html @@ -0,0 +1,36 @@ + + + + +About + + +

About This Content

+ +

November 30, 2017

+

License

+ +

+ The Eclipse Foundation makes available all content in this plug-in + ("Content"). Unless otherwise indicated below, the Content + is provided to you under the terms and conditions of the Eclipse + Public License Version 2.0 ("EPL"). A copy of the EPL is + available at http://www.eclipse.org/legal/epl-2.0. + For purposes of the EPL, "Program" will mean the Content. +

+ +

+ If you did not receive this Content directly from the Eclipse + Foundation, the Content is being redistributed by another party + ("Redistributor") and different terms and conditions may + apply to your use of any object code in the Content. Check the + Redistributor's license that was provided with the Content. If no such + license exists, contact the Redistributor. Unless otherwise indicated + below, the terms and conditions of the EPL still apply to any source + code in the Content and such source code may be obtained at http://www.eclipse.org. +

+ + + \ No newline at end of file diff --git a/examples/org.eclipse.ui.examples.filter/build.properties b/examples/org.eclipse.ui.examples.filter/build.properties new file mode 100644 index 00000000000..adac29f50d5 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/build.properties @@ -0,0 +1,8 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + plugin.properties,\ + plugin.xml,\ + about.html,\ + . +src.includes = about.html diff --git a/examples/org.eclipse.ui.examples.filter/plugin.properties b/examples/org.eclipse.ui.examples.filter/plugin.properties new file mode 100644 index 00000000000..9fa8ad8205c --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/plugin.properties @@ -0,0 +1,7 @@ +#Properties file for org.eclipse.ui.examples.filter +Bundle-Vendor = Eclipse.org +Bundle-Name = Eclipse Filtered Viewer + +view.tree.name = Filtered Tree View +view.table.name = Filtered Table View +view.table.virtual.name = Filtered (Virtual) Table View \ No newline at end of file diff --git a/examples/org.eclipse.ui.examples.filter/plugin.xml b/examples/org.eclipse.ui.examples.filter/plugin.xml new file mode 100644 index 00000000000..451dd217b11 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/plugin.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTableView.java b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTableView.java new file mode 100644 index 00000000000..980fdab9f72 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTableView.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2024 Patrick Ziegler and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + ******************************************************************************/ +package org.eclipse.ui.examples.filter; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.dialogs.FilteredTable; +import org.eclipse.ui.part.ViewPart; + +public class FilteredTableView extends ViewPart { + protected FilteredTable filter; + protected TableViewer viewer; + protected List input; + + @Override + public void createPartControl(Composite parent) { + input = generateInput(9); + filter = createFilteredTable(parent); + viewer = filter.getViewer(); + viewer.setContentProvider(createContentProvider()); + viewer.setInput(input); + } + + @Override + public void setFocus() { + viewer.getControl().setFocus(); + } + + protected FilteredTable createFilteredTable(Composite parent) { + return new FilteredTable(parent, SWT.NONE, 500); + } + + protected IContentProvider createContentProvider() { + return ArrayContentProvider.getInstance(); + } + + private List generateInput(int size) { + List input = new ArrayList<>(); + for (int i = 1; i <= size; ++i) { + input.add(Messages.bind(Messages.FilteredTableView_Element, i)); + } + return input; + } +} diff --git a/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTreeView.java b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTreeView.java new file mode 100644 index 00000000000..0cb7affe913 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredTreeView.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2024 Patrick Ziegler and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + ******************************************************************************/ +package org.eclipse.ui.examples.filter; + +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.dialogs.FilteredTree; +import org.eclipse.ui.dialogs.PatternFilter; +import org.eclipse.ui.part.ViewPart; + +public class FilteredTreeView extends ViewPart { + private FilteredTree filter; + private TreeViewer viewer; + + @Override + public void createPartControl(Composite parent) { + filter = new FilteredTree(parent, SWT.NONE, new PatternFilter(), true, true, 500); + viewer = filter.getViewer(); + viewer.setContentProvider(new TreeContentProvider(9)); + viewer.setInput(new Object()); + viewer.expandAll(); + } + + @Override + public void setFocus() { + viewer.getControl().setFocus(); + } + + private static class TreeContentProvider implements ITreeContentProvider { + private Map elements = new TreeMap<>(); + + public TreeContentProvider(int size) { + for (int i = 1; i <= size; ++i) { + String key = Messages.bind(Messages.FilteredTreeView_Parent, i); + String value = Messages.bind(Messages.FilteredTreeView_Child, i); + elements.put(key, value); + } + } + + @Override + public Object[] getElements(Object inputElement) { + return elements.keySet().toArray(); + } + + @Override + public Object[] getChildren(Object parentElement) { + if (!hasChildren(parentElement)) { + return new Object[0]; + } + return new Object[] { elements.get(parentElement) }; + } + + @Override + public Object getParent(Object element) { + for (Map.Entry entry : elements.entrySet()) { + if (entry.getValue().equals(element)) { + return entry.getKey(); + } + } + return null; + } + + @Override + public boolean hasChildren(Object element) { + return elements.containsKey(element); + } +} +} diff --git a/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredVirtualTableView.java b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredVirtualTableView.java new file mode 100644 index 00000000000..069b720fcd2 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/FilteredVirtualTableView.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2024 Patrick Ziegler and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + ******************************************************************************/ +package org.eclipse.ui.examples.filter; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.dialogs.FilteredTable; +import org.eclipse.ui.progress.WorkbenchJob; + +public class FilteredVirtualTableView extends FilteredTableView { + protected FilteredTable createFilteredTable(Composite parent) { + return new FilteredTable(parent, SWT.VIRTUAL, 500) { + @Override + protected WorkbenchJob doCreateRefreshJob() { + WorkbenchJob job = super.doCreateRefreshJob(); + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + if (viewer.getControl().isDisposed()) { + return; + } + ViewerFilter filter = viewer.getFilters()[0]; + Object[] newInput = filter.filter(viewer, (Object) null, input.toArray()); + viewer.setInput(Arrays.asList(newInput)); + } + }); + return job; + } + }; + } + + protected IContentProvider createContentProvider() { + return new ContentProvider(); + } + + private static class ContentProvider implements ILazyContentProvider { + private List input; + private TableViewer viewer; + + @Override + public void updateElement(int index) { + viewer.replace(input.get(index), index); + } + + @Override + @SuppressWarnings("unchecked") + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + this.input = (List) newInput; + this.viewer = (TableViewer) viewer; + this.viewer.setItemCount(input.size()); + } + } +} diff --git a/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/Messages.java b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/Messages.java new file mode 100644 index 00000000000..8e8661dbaf7 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/Messages.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2024 Patrick Ziegler and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + ******************************************************************************/ +package org.eclipse.ui.examples.filter; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = Messages.class.getPackageName() + ".messages"; //$NON-NLS-1$ + static { + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + public static String FilteredTableView_Element; + public static String FilteredTreeView_Child; + public static String FilteredTreeView_Parent; + + private Messages() { + } +} diff --git a/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/messages.properties b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/messages.properties new file mode 100644 index 00000000000..7b03719c9e1 --- /dev/null +++ b/examples/org.eclipse.ui.examples.filter/src/org/eclipse/ui/examples/filter/messages.properties @@ -0,0 +1,3 @@ +FilteredTableView_Element=Element {0} +FilteredTreeView_Child=Child {0} +FilteredTreeView_Parent=Parent {0} diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java index cbba13b9774..b649415b565 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java @@ -81,7 +81,8 @@ MultiSelectionTest.class, FindReplaceDocumentAdapterContentProposalProviderTest.class, ProjectionViewerTest.class, - TestWhitespaceCharacterPainter.class + TestWhitespaceCharacterPainter.class, + TextMatcherTest.class }) public class JFaceTextTestSuite { // see @SuiteClasses diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/TextMatcherTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextMatcherTest.java similarity index 98% rename from tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/TextMatcherTest.java rename to tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextMatcherTest.java index 7f817e79951..f21e87f8e3e 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/TextMatcherTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextMatcherTest.java @@ -8,12 +8,12 @@ * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ -package org.eclipse.ui.tests.filteredtree; +package org.eclipse.jface.text.tests; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.eclipse.ui.internal.misc.TextMatcher; +import org.eclipse.jface.internal.text.TextMatcher; import org.junit.Test; /** diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java index 8664529313e..26c69354451 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java @@ -35,9 +35,9 @@ import org.eclipse.ui.tests.dynamicplugins.DynamicPluginsTestSuite; import org.eclipse.ui.tests.encoding.EncodingTestSuite; import org.eclipse.ui.tests.fieldassist.FieldAssistTestSuite; +import org.eclipse.ui.tests.filteredtree.FilteredTableTests; import org.eclipse.ui.tests.filteredtree.FilteredTreeTests; import org.eclipse.ui.tests.filteredtree.PatternFilterTest; -import org.eclipse.ui.tests.filteredtree.TextMatcherTest; import org.eclipse.ui.tests.internal.InternalTestSuite; import org.eclipse.ui.tests.intro.IntroTestSuite; import org.eclipse.ui.tests.keys.KeysTestSuite; @@ -89,9 +89,9 @@ CommandsTestSuite.class, ContextsTestSuite.class, ConcurrencyTestSuite.class, + FilteredTableTests.class, FilteredTreeTests.class, PatternFilterTest.class, - TextMatcherTest.class, StatusHandlingTestSuite.class, MenusTestSuite.class, QuickAccessTestSuite.class, diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTableTests.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTableTests.java new file mode 100644 index 00000000000..b0c031a6429 --- /dev/null +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTableTests.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2024 Patrick Ziegler and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + ******************************************************************************/ +package org.eclipse.ui.tests.filteredtree; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.tests.viewers.TestElement; +import org.eclipse.jface.tests.viewers.TestModelContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.FilteredTable; +import org.eclipse.ui.progress.WorkbenchJob; +import org.junit.Before; +import org.junit.Test; + +public class FilteredTableTests { + private final static int NUM_ITEMS = 8000; + private TestFilteredTable tableViewer; + private TestElement rootElement; + + @Before + public void setUp() { + rootElement = TestElement.createModel(1, NUM_ITEMS); + } + + @Test + public void testAddAndRemovePattern() { + Dialog dialog = new FilteredTableDialog(null, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); + dialog.create(); + + assertNotNull(tableViewer); + assertEquals(tableViewer.getItemCount(), NUM_ITEMS); + + tableViewer.applyPattern("0-0 name-"); + assertEquals(tableViewer.getItemCount(), 1); + + tableViewer.applyPattern("0-0 name unknownWord"); + assertEquals(tableViewer.getItemCount(), 0); + + tableViewer.applyPattern(""); + assertEquals(tableViewer.getItemCount(), NUM_ITEMS); + + dialog.close(); + } + + private class TestFilteredTable extends FilteredTable { + private boolean jobScheduled; + + public TestFilteredTable(Composite parent, int style) { + super(parent, style, 0); + } + + public int getItemCount() { + return getViewer().getTable().getItemCount(); + } + + public void applyPattern(String pattern) { + setFilterText(pattern); + textChanged(); + + while (jobScheduled) { + getDisplay().readAndDispatch(); + } + } + + @Override + protected WorkbenchJob doCreateRefreshJob() { + WorkbenchJob job = super.doCreateRefreshJob(); + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void scheduled(IJobChangeEvent event) { + jobScheduled = true; + } + + @Override + public void done(IJobChangeEvent event) { + if (event.getResult().isOK()) { + jobScheduled = false; + } + } + }); + return job; + } + } + + + private class FilteredTableDialog extends Dialog { + private final int style; + + public FilteredTableDialog(Shell shell, int tableStyle) { + super(shell); + style = tableStyle; + } + + @Override + protected Control createContents(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout()); + + tableViewer = new TestFilteredTable(composite, style); + tableViewer.setLayoutData(GridDataFactory.fillDefaults().hint(400, 500).create()); + tableViewer.getViewer().setContentProvider(new TestModelContentProvider()); + tableViewer.getViewer().setLabelProvider(new LabelProvider()); + tableViewer.getViewer().setUseHashlookup(true); + tableViewer.getViewer().setInput(rootElement); + return parent; + } + } +}