/*****************************************************************************
* Copyright (c) 2011 CEA LIST.
*
* 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:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
*****************************************************************************/
package org.eclipse.papyrus.infra.widgets.providers;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.papyrus.infra.widgets.strategy.IStrategyBasedContentProvider;
import org.eclipse.papyrus.infra.widgets.strategy.ProviderBasedBrowseStrategy;
import org.eclipse.papyrus.infra.widgets.strategy.TreeBrowseStrategy;
/**
* An abstract ViewerFilter for TreeViewers.
*
* You should extend this class whenever you want to implement a filter
* for a Tree. An element is visible :
* - If the method isVisible() returns true
* - If one of its children is visible
* - Optionally, if one of its parents is visible ({@link #showIfHasVisibleParent})
*
* This class can implements a cache, which should be cleaned each time
* a parameter influencing the result of the {@link #isVisible(Viewer, Object, Object)} method is changed ({@link #clearCache()}).
*
* @author Camille Letavernier
*/
public abstract class AbstractTreeFilter extends ViewerFilter {
/**
* If set to true, the results of the filter will be cached, to improve
* performance.
*
* Implementers are responsible of cleaning the cache (by calling {@link #clearCache()} when the result of the filter on a given
* element might change.
*
* For example, a string-pattern-based filter should clear the cache when
* the pattern changes. The viewer should also be refreshed.
*/
protected boolean useCache = true;
/**
* Indicates if an element should be visible when one its parents is visible.
* This may be useful, for example, when you want to display all the contents
* of a given package, by entering a filter that will match this package.
*/
protected boolean showIfHasVisibleParent = false;
/**
* Cache
*/
protected final Map<Object, Boolean> visibleElement = new HashMap<Object, Boolean>();
/**
* Cache
*/
protected final Map<Object, Boolean> visibleParent = new HashMap<Object, Boolean>();
/**
* Cache
*/
protected final Map<Object, Boolean> visibleChild = new HashMap<Object, Boolean>();
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
TreeBrowseStrategy strategy = null;
if(viewer instanceof StructuredViewer) {
IContentProvider baseContentProvider = ((StructuredViewer)viewer).getContentProvider();
if(baseContentProvider instanceof IStrategyBasedContentProvider) {
strategy = ((IStrategyBasedContentProvider)baseContentProvider).getRevealStrategy();
}
if(strategy == null && baseContentProvider instanceof ITreeContentProvider) {
strategy = new ProviderBasedBrowseStrategy((ITreeContentProvider)baseContentProvider);
}
}
if(strategy == null) { //The contentProvider is not a TreeContentProvider
return isVisible(viewer, parentElement, element);
}
return select(viewer, parentElement, element, strategy);
}
protected boolean select(Viewer viewer, Object parentElement, Object element, TreeBrowseStrategy strategy) {
Set<Object> visitedChildren = new HashSet<Object>();
Set<Object> visitedParents = new HashSet<Object>();
if(useCache && visibleElement.containsKey(element)) {
return visibleElement.get(element);
}
boolean isVisible = isVisible(viewer, parentElement, element) || hasOneVisibleChild(viewer, element, strategy, visitedChildren);
if(showIfHasVisibleParent) {
isVisible = isVisible || hasOneVisibleParent(viewer, element, strategy, visitedParents);
}
if(useCache) {
visibleElement.put(element, isVisible);
}
return isVisible;
}
protected boolean hasOneVisibleChild(Viewer viewer, Object element, TreeBrowseStrategy strategy, Set<Object> visitedElements) {
//TODO : separate this method in -hasOneVisibleChild() and #doHasOneVisibleChild(), to handle the cache management in a private method,
//while letting the opportunity to override the method
if(useCache && visibleChild.containsKey(element)) {
return visibleChild.get(element);
}
boolean result = false;
if(!visitedElements.contains(element)) {
visitedElements.add(element);
for(Object childElement : strategy.getChildren(element)) {
if(isVisible(viewer, element, childElement) || hasOneVisibleChild(viewer, childElement, strategy, visitedElements)) {
result = true;
break;
}
}
}
if(useCache) {
visibleChild.put(element, result);
}
return result;
}
protected boolean hasOneVisibleParent(Viewer viewer, Object element, TreeBrowseStrategy strategy, Set<Object> visitedElements) {
if(useCache && visibleParent.containsKey(element)) {
return visibleParent.get(element);
}
boolean result = false;
if(!visitedElements.contains(element)) {
visitedElements.add(element);
Object parentElement = strategy.getParent(element);
if(parentElement == element || parentElement == null) {
result = isVisible(viewer, parentElement, element);
} else {
result = isVisible(viewer, null, parentElement) || hasOneVisibleParent(viewer, parentElement, strategy, visitedElements);
}
}
if(useCache) {
visibleParent.put(element, result);
}
return result;
}
protected void clearCache() {
visibleElement.clear();
visibleParent.clear();
visibleChild.clear();
}
public abstract boolean isVisible(Viewer viewer, Object parentElement, Object element);
public void setShowIfHasVisibleParent(boolean showIfHasVisibleParent) {
this.showIfHasVisibleParent = showIfHasVisibleParent;
}
}