-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
20 changed files
with
1,171 additions
and
341 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
373 changes: 373 additions & 0 deletions
373
...les/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/AbstractFilteredViewer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
* <code>null</code> 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 <code>Composite</code> of the filter controls | ||
* @return the <code>Composite</code> 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 <code>Composite</code> 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 | ||
* <code>null</code>. | ||
* | ||
* @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. | ||
* <p> | ||
* By default, this is set to <code>false</code>. | ||
* </p> | ||
* | ||
* @param enabled <code>true</code> if this filtered tree is used to make quick | ||
* selections, <code>false</code> 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; | ||
} | ||
} |
Oops, something went wrong.