From 5ccbcdae95281fdcdaa7375f1de23c9f9ef06318 Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Mon, 2 Dec 2024 19:02:49 +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 abstract base class, which is then used to implement the FilterTable widget. --- .../META-INF/MANIFEST.MF | 2 +- .../ui/dialogs/AbstractFilteredViewer.java | 373 ++++++++++++++++++ .../org/eclipse/ui/dialogs/FilteredTable.java | 240 +++++++++++ .../org/eclipse/ui/dialogs/FilteredTree.java | 362 ++--------------- .../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 + .../filteredtree/FilteredTableTests.java | 129 ++++++ 20 files changed, 1171 insertions(+), 341 deletions(-) create mode 100644 bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/AbstractFilteredViewer.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 create mode 100644 tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTableTests.java diff --git a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF index c0d826a9b67..1b0b5062145 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 diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/AbstractFilteredViewer.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/AbstractFilteredViewer.java new file mode 100644 index 00000000000..54dd4d13823 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/AbstractFilteredViewer.java @@ -0,0 +1,373 @@ +/******************************************************************************* + * 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.jobs.Job; +import org.eclipse.jface.viewers.ColumnViewer; +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.ui.IWorkbenchPreferenceConstants; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.progress.WorkbenchJob; + +/** + * The abstract base class for a composite containing both a column viewer and a + * text widget. The text drives the filter of the viewer. Current + * implementations support both table- and tree-based viewers. + * + * @since 3.135 + */ +public abstract sealed class AbstractFilteredViewer extends Composite permits FilteredTable, FilteredTree { + + /** + * 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; + + /** + * 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 tree. + */ + private Job refreshJob; + + /** + * The parent composite of the filtered tree. + */ + 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; + + /** + * 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. + */ + private boolean quickSelectionMode = false; + + /** + * Time for refresh job delay in terms of expansion in long value + */ + private final long refreshJobDelayInMillis; + + protected AbstractFilteredViewer(Composite parent, int style, long refreshJobDelayInMillis) { + super(parent, style); + this.parent = parent; + this.refreshJobDelayInMillis = refreshJobDelayInMillis; + } + + /** + * Create the viewer. + * + * @param style the style bits for the {@code viewer}. + */ + protected final void init(int style) { + showFilterControls = PlatformUI.getPreferenceStore() + .getBoolean(IWorkbenchPreferenceConstants.SHOW_FILTERED_TEXTS); + createControl(parent, style); + createRefreshJob(); + setInitialText(WorkbenchMessages.FilteredTree_FilterMessage); + setFont(parent.getFont()); + getViewer().getControl().addDisposeListener(e -> refreshJob.cancel()); + } + + /** + * Create the filtered tree's controls. Subclasses should override. + * + * @param parent the parent + * @param treeStyle SWT style bits used to create the tree + */ + 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)); + } + } + + /** + * 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; + } + + /** + * 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. + * + * @return a workbench job that can be scheduled to refresh the tree + */ + protected abstract WorkbenchJob 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 parent Composite of the filter text + */ + protected void createFilterText(Composite parent) { + filterText = doCreateFilterText(parent); + + 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.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 parent the parent composite + * @return the text widget + */ + protected Text doCreateFilterText(Composite parent) { + return new Text(parent, 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() { + 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 column viewer of the receiver. + * + * @return the column viewer + */ + public abstract ColumnViewer getViewer(); + + /** + * 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 + */ + 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; + } + + /** + * Get the quick selection mode. + * + * @return {@code true}, if enabled. + */ + protected boolean isQuickSelectionMode() { + return quickSelectionMode; + } +} 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..470ff207b35 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredTable.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * 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.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.internal.WorkbenchMessages; +import org.eclipse.ui.internal.misc.TextMatcher; +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 non-sealed class FilteredTable extends AbstractFilteredViewer { + + /** + * 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 + public TableViewer getViewer() { + return tableViewer; + } + + @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; + } + }; + } +} 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..49d1601bd66 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,7 +19,6 @@ 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.viewers.IContentProvider; @@ -29,24 +28,16 @@ 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; -import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.WorkbenchMessages; import org.eclipse.ui.progress.WorkbenchJob; @@ -57,20 +48,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 non-sealed class FilteredTree extends AbstractFilteredViewer { /** *

* 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 +66,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 +77,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 +120,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 +164,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 +194,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,43 +282,12 @@ 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(); - setInitialText(WorkbenchMessages.FilteredTree_FilterMessage); - setFont(parent.getFont()); - + init(treeStyle); } - /** - * Create the filtered tree's controls. Subclasses should override. - * - * @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(); treeCompositeLayout.marginHeight = 0; @@ -394,19 +298,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 +312,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,22 +351,7 @@ 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. - * - * @return a workbench job that can be scheduled to refresh the tree - * - * @since 3.4 - */ + @Override protected WorkbenchJob doCreateRefreshJob() { return new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$ @Override @@ -536,7 +411,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,21 +458,16 @@ private boolean recursiveExpand(TreeItem[] items, IProgressMonitor monitor, long * override. * * @param visible boolean + * @deprecated As of 4.13 not used anymore */ + @Deprecated(forRemoval = true, since = "2025-03") protected void updateToolbar(boolean visible) { // nothing to do } - /** - * 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 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 +511,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 +525,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 +533,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,76 +567,17 @@ 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; - /** - * 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(); } /** @@ -821,97 +589,11 @@ public final PatternFilter getPatternFilter() { return patternFilter; } - /** - * Get the tree viewer of the receiver. - * - * @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/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.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; + } + } +}