From 40b7b61473d3228bfc89923e2a3802bcd51115bc Mon Sep 17 00:00:00 2001 From: Generoso Pagano Date: Wed, 27 Aug 2014 15:31:10 +0200 Subject: [PATCH] TMF: added support for process filter in the TimeGraphFilterDialog. On top of the filter dialog a text field enables filtering over the tree of process names. A node of the tree is shown if: - the node matches with the search pattern - one of the children of the node matches with the search pattern - one of the parents of the node matches with the search pattern The buttons check-all and check-subtree check only the visible items. The button uncheck-subtree unchecks all the children (the hidden children too, since it makes no sense a checked child with an unchecked parent). The button uncheck-all unchecks everything, except the hidden roots (and corresponding subtrees). Change-Id: I517f3333bb7b6da9fd14eaaac90e7b914671479e Signed-off-by: Generoso Pagano Reviewed-on: https://git.eclipse.org/r/32404 Tested-by: Hudson CI Reviewed-by: Patrick Tasse Tested-by: Patrick Tasse --- .../timegraph/AbstractTimeGraphView.java | 20 +- .../ui/widgets/timegraph/TimeGraphCombo.java | 4 +- .../dialogs/FilteredCheckboxTree.java | 193 ++++++++++++++++++ .../dialogs/TimeGraphFilterDialog.java | 89 ++++---- .../timegraph/dialogs/TreePatternFilter.java | 61 ++++++ 5 files changed, 324 insertions(+), 43 deletions(-) create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/FilteredCheckboxTree.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TreePatternFilter.java diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/timegraph/AbstractTimeGraphView.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/timegraph/AbstractTimeGraphView.java index e48dbd5e2f..63ea76a77a 100644 --- a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/timegraph/AbstractTimeGraphView.java +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/timegraph/AbstractTimeGraphView.java @@ -33,6 +33,7 @@ import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.Separator; +import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.ITableLabelProvider; @@ -389,7 +390,7 @@ public abstract class AbstractTimeGraphView extends TmfView { * this class typically need to override the getColumnText method if they * have more than one column to display */ - protected static class TreeLabelProvider implements ITableLabelProvider { + protected static class TreeLabelProvider implements ITableLabelProvider, ILabelProvider { @Override public void addListener(ILabelProviderListener listener) { @@ -422,6 +423,23 @@ public abstract class AbstractTimeGraphView extends TmfView { return new String(); } + /** + * @since 3.1 + */ + @Override + public Image getImage(Object element) { + return null; + } + + /** + * @since 3.1 + */ + @Override + public String getText(Object element) { + TimeGraphEntry entry = (TimeGraphEntry) element; + return entry.getName(); + } + } private class BuildThread extends Thread { diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/TimeGraphCombo.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/TimeGraphCombo.java index ecffa3e8db..3b772fd33f 100644 --- a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/TimeGraphCombo.java +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/TimeGraphCombo.java @@ -681,9 +681,9 @@ public class TimeGraphCombo extends Composite { fTimeGraphViewer.refresh(); fInhibitTreeSelection = false; alignTreeItems(true); - // Reset selection to first entry + // Reset selection if (fFilterDialog.getResult().length > 0) { - setSelection((ITimeGraphEntry) fFilterDialog.getResult()[0]); + setSelection(null); } } } diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/FilteredCheckboxTree.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/FilteredCheckboxTree.java new file mode 100644 index 0000000000..23c097a59b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/FilteredCheckboxTree.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2014 Inria + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Generoso Pagano, Inria - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.linuxtools.tmf.ui.widgets.timegraph.dialogs; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.ICheckable; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.dialogs.FilteredTree; +import org.eclipse.ui.dialogs.PatternFilter; +import org.eclipse.ui.progress.WorkbenchJob; + +/** + * A FilteredTree wrapping a CheckboxTreeViewer. + * + * This tree keeps the check state of the nodes in sync, regardless of the fact + * that a node is filtered or not. This way, even if an node is filtered (not + * visible), the caller can get and set the check state. + * + * Note that all the "uncheck" operations act only on what is not filtered and + * what is child of something not filtered (even if such a child is filtered). + * On the contrary, all the "check" operations act only on what is not filtered. + * + * @author "Generoso Pagano " + * @since 3.1 + */ +public class FilteredCheckboxTree extends FilteredTree implements ICheckable { + + /** + * Set containing only the tree items that are checked + */ + private Set fObjects = new HashSet<>(); + + /** + * Handle to the tree viewer + */ + private CheckboxTreeViewer fCheckboxTreeViewer; + + /** + * Create a new instance of the receiver. + * + * @param parent + * the parent Composite + * @param treeStyle + * the style bits for the Tree + * @param filter + * the filter to be used + * @param useNewLook + * true if the new FilteredTree look + * should be used + */ + public FilteredCheckboxTree(Composite parent, int treeStyle, PatternFilter filter, + boolean useNewLook) { + super(parent, treeStyle, filter, useNewLook); + } + + @Override + protected TreeViewer doCreateTreeViewer(Composite parentComposite, int style) { + fCheckboxTreeViewer = new CheckboxTreeViewer(parentComposite, style); + fCheckboxTreeViewer.addCheckStateListener(new ICheckStateListener() { + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + if (event.getChecked()) { + fObjects.add(event.getElement()); + } else { + fObjects.remove(event.getElement()); + } + } + }); + return fCheckboxTreeViewer; + } + + @Override + protected WorkbenchJob doCreateRefreshJob() { + WorkbenchJob job = super.doCreateRefreshJob(); + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + fCheckboxTreeViewer.expandAll(); + fCheckboxTreeViewer.setCheckedElements(getCheckedElements()); + } + }); + return job; + } + + @Override + public boolean getChecked(Object element) { + return fObjects.contains(element); + } + + @Override + public boolean setChecked(Object element, boolean state) { + boolean checkable = fCheckboxTreeViewer.setChecked(element, state); + if (!state) { + fObjects.remove(element); + } else if (checkable) { + fObjects.add(element); + } + return checkable; + } + + @Override + public void addCheckStateListener(ICheckStateListener listener) { + fCheckboxTreeViewer.addCheckStateListener(listener); + } + + @Override + public void removeCheckStateListener(ICheckStateListener listener) { + fCheckboxTreeViewer.addCheckStateListener(listener); + } + + /** + * Returns all the checked elements of this tree, either visible or not. + * + * @return an array containing all the checked elements + */ + public Object[] getCheckedElements() { + return fObjects.toArray(); + } + + /** + * Checks all the passed elements and unchecks all the other. + * + * @param elements + * the elements to check + */ + public void setCheckedElements(Object[] elements) { + fObjects = new HashSet<>(); + for (Object element : elements) { + fObjects.add(element); + } + fCheckboxTreeViewer.setCheckedElements(elements); + } + + /** + * Sets the check state for the given element and its children in this + * viewer. The unchecked state is always set, while the checked state is set + * only on visible elements. + * + * @param element + * the element + * @param state + * the check state to set + * @return true if the check state could be set, and + * false otherwise + */ + public boolean setSubtreeChecked(Object element, boolean state) { + checkSubtree(element, state); + return fCheckboxTreeViewer.setSubtreeChecked(element, state); + } + + /** + * Recursively sets the check state on an element and its children, using + * the politic specified in {@link #setSubtreeChecked(Object, boolean)} + * documentation. + * + * @param element + * the element + * @param state + * the check state to set + */ + private void checkSubtree(Object element, boolean state) { + if (!state || (fCheckboxTreeViewer.testFindItem(element) != null)) { + if (state) { + fObjects.add(element); + } else { + fObjects.remove(element); + } + for (Object o : ((ITreeContentProvider) fCheckboxTreeViewer.getContentProvider()).getChildren(element)) { + checkSubtree(o, state); + } + } + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TimeGraphFilterDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TimeGraphFilterDialog.java index b2b1c8d3a8..76465cd071 100644 --- a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TimeGraphFilterDialog.java +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TimeGraphFilterDialog.java @@ -14,6 +14,7 @@ * align the selection buttons to the right * François Rajotte - Support for multiple columns + selection control * Patrick Tasse - Fix Sonar warnings + * Generoso Pagano - Add tree filter *******************************************************************************/ package org.eclipse.linuxtools.tmf.ui.widgets.timegraph.dialogs; @@ -50,12 +51,13 @@ import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ISelectionStatusValidator; +import org.eclipse.ui.dialogs.PatternFilter; import org.eclipse.ui.dialogs.SelectionStatusDialog; /** - * Filter dialog for the time graphs - * This class is derived from the CheckedTreeSelectionDialog - * It was necessary to develop this similar dialog to allow multiple columns + * Filter dialog for the time graphs This class is derived from the + * CheckedTreeSelectionDialog It was necessary to develop this similar dialog to + * allow multiple columns * * @version 1.0 * @since 2.0 @@ -70,7 +72,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { private static final int DEFAULT_WIDTH = 60; private static final int DEFAULT_HEIGHT = 18; - private CheckboxTreeViewer fViewer; + private FilteredCheckboxTree fTree; private IBaseLabelProvider fLabelProvider; @@ -205,21 +207,24 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { } /** - * @param contentProvider The content provider for the table + * @param contentProvider + * The content provider for the table */ public void setContentProvider(ITreeContentProvider contentProvider) { fContentProvider = contentProvider; } /** - * @param labelProvider The label provider for the table + * @param labelProvider + * The label provider for the table */ public void setLabelProvider(IBaseLabelProvider labelProvider) { fLabelProvider = labelProvider; } /** - * @param columnNames An array of column names to display + * @param columnNames + * An array of column names to display */ public void setColumnNames(String[] columnNames) { if (columnNames != null) { @@ -236,7 +241,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { protected void updateOKStatus() { if (!fIsEmpty) { if (fValidator != null) { - fCurrStatus = fValidator.validate(fViewer.getCheckedElements()); + fCurrStatus = fValidator.validate(fTree.getCheckedElements()); updateStatus(fCurrStatus); } else if (!fCurrStatus.isOK()) { fCurrStatus = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, @@ -265,7 +270,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { @Override protected void computeResult() { - setResult(Arrays.asList(fViewer.getCheckedElements())); + setResult(Arrays.asList(fTree.getCheckedElements())); } @Override @@ -274,10 +279,10 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { @Override public void run() { TimeGraphFilterDialog.super.create(); - fViewer.setCheckedElements(getInitialElementSelections() + fTree.setCheckedElements(getInitialElementSelections() .toArray()); if (fExpandedElements != null) { - fViewer.setExpandedElements(fExpandedElements); + fTree.getViewer().setExpandedElements(fExpandedElements); } updateOKStatus(); } @@ -312,9 +317,11 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { * @return the tree viewer */ protected CheckboxTreeViewer createTreeViewer(Composite parent) { - fViewer = new CheckboxTreeViewer(parent, SWT.BORDER | SWT.MULTI); + PatternFilter filter = new TreePatternFilter(); + filter.setIncludeLeadingWildcard(true); + fTree = new FilteredCheckboxTree(parent, SWT.BORDER | SWT.MULTI, filter, true); - Tree tree = fViewer.getTree(); + Tree tree = fTree.getViewer().getTree(); tree.setHeaderVisible(true); for (String columnName : fColumnNames) { TreeColumn column = new TreeColumn(tree, SWT.LEFT); @@ -322,28 +329,22 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { column.pack(); } - fViewer.setContentProvider(fContentProvider); - fViewer.setLabelProvider(fLabelProvider); - fViewer.addCheckStateListener(new CheckStateListener()); - fViewer.addCheckStateListener(new ICheckStateListener() { - @Override - public void checkStateChanged(CheckStateChangedEvent event) { - updateOKStatus(); - } - }); - fViewer.setComparator(fComparator); + fTree.getViewer().setContentProvider(fContentProvider); + fTree.getViewer().setLabelProvider(fLabelProvider); + fTree.addCheckStateListener(new CheckStateListener()); + fTree.getViewer().setComparator(fComparator); if (fFilters != null) { for (int i = 0; i != fFilters.size(); i++) { - fViewer.addFilter(fFilters.get(i)); + fTree.getViewer().addFilter(fFilters.get(i)); } } - fViewer.setInput(fInput); + fTree.getViewer().setInput(fInput); - //pack the columns again for a nice view... + // pack the columns again for a nice view... for (TreeColumn column : tree.getColumns()) { column.pack(); } - return fViewer; + return (CheckboxTreeViewer) fTree.getViewer(); } /** @@ -352,7 +353,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { * @return the tree viewer */ protected CheckboxTreeViewer getTreeViewer() { - return fViewer; + return (CheckboxTreeViewer) fTree.getViewer(); } /** @@ -395,7 +396,6 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { IDialogConstants.DESELECT_ALL_ID, Messages.TmfTimeFilterDialog_UNCHECK_ALL, false); - /* * Apply the layout again after creating the buttons to override * createButton messing with the columns @@ -407,7 +407,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { checkSelectedButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - TreeSelection selection = (TreeSelection) fViewer.getSelection(); + TreeSelection selection = (TreeSelection) fTree.getViewer().getSelection(); for (Object element : selection.toArray()) { checkElement(element); @@ -420,7 +420,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { checkSubtreeButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - TreeSelection selection = (TreeSelection) fViewer.getSelection(); + TreeSelection selection = (TreeSelection) fTree.getViewer().getSelection(); for (Object element : selection.toArray()) { checkElementAndSubtree(element); @@ -434,7 +434,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { Object[] viewerElements = fContentProvider.getElements(fInput); for (int i = 0; i < viewerElements.length; i++) { - fViewer.setSubtreeChecked(viewerElements[i], true); + fTree.setSubtreeChecked(viewerElements[i], true); } updateOKStatus(); @@ -444,7 +444,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { uncheckSelectedButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - TreeSelection selection = (TreeSelection) fViewer.getSelection(); + TreeSelection selection = (TreeSelection) fTree.getViewer().getSelection(); for (Object element : selection.toArray()) { uncheckElement(element); @@ -457,7 +457,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { uncheckSubtreeButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - TreeSelection selection = (TreeSelection) fViewer.getSelection(); + TreeSelection selection = (TreeSelection) fTree.getViewer().getSelection(); for (Object element : selection.toArray()) { uncheckElement(element); @@ -470,7 +470,13 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { uncheckAllButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - fViewer.setCheckedElements(new Object[0]); + Object[] viewerElements = fContentProvider.getElements(fInput); + for (Object element : viewerElements) { + if (fTree.getViewer().testFindItem(element) != null) { + // uncheck only visible roots and their children + uncheckElement(element); + } + } updateOKStatus(); } }); @@ -485,11 +491,11 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { * The element to check. */ private void checkElement(Object element) { - fViewer.setChecked(element, true); + fTree.setChecked(element, true); Object parent = fContentProvider.getParent(element); - if (parent != null) { + if (parent != null && !fTree.getChecked(parent)) { checkElement(parent); } } @@ -515,7 +521,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { * The element to uncheck. */ private void uncheckElement(Object element) { - fViewer.setChecked(element, false); + fTree.setChecked(element, false); for (Object child : fContentProvider.getChildren(element)) { uncheckElement(child); @@ -527,7 +533,7 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { if (elements.length > 0 && fFilters != null) { for (int i = 0; i < fFilters.size(); i++) { ViewerFilter curr = fFilters.get(i); - elements = curr.filter(fViewer, input, elements); + elements = curr.filter(fTree.getViewer(), input, elements); } } return elements.length == 0; @@ -554,7 +560,10 @@ public class TimeGraphFilterDialog extends SelectionStatusDialog { } } catch (ClassCastException e) { return; + } finally { + updateOKStatus(); } } + } -} +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TreePatternFilter.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TreePatternFilter.java new file mode 100644 index 0000000000..14277eb082 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/dialogs/TreePatternFilter.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2014 Inria + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Generoso Pagano, Inria - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.linuxtools.tmf.ui.widgets.timegraph.dialogs; + +import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.dialogs.PatternFilter; + +/** + * A filter extending the org.eclipse.ui.dialogs.PatternFilter. + * + * It redefines the {@link #isElementVisible(Viewer, Object)}} method in order + * to have a match on a node if: the node matches or one of the children matches + * or one of the parents matches. + * + * @author "Generoso Pagano " + * @since 3.1 + */ +public class TreePatternFilter extends PatternFilter { + + @Override + public boolean isElementVisible(Viewer viewer, Object element) { + return super.isElementVisible(viewer, element) || isChildMatch(viewer, element); + } + + /** + * Check if at least one of the parents of this element is a match with the + * filter text. + * + * @param viewer + * the viewer that contains the element + * @param element + * the tree element to check + * @return true if the given element has a parent that matches the filter + * text + */ + private boolean isChildMatch(Viewer viewer, Object element) { + Object parent = ((ITreeContentProvider) ((AbstractTreeViewer) viewer).getContentProvider()) + .getParent(element); + while (parent != null) { + if (isLeafMatch(viewer, parent)) { + return true; + } + parent = ((ITreeContentProvider) ((AbstractTreeViewer) viewer).getContentProvider()) + .getParent(parent); + } + return false; + } + +} -- 2.34.1