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; + } + } +}