/******************************************************************************* * Copyright (c) 2011, 2013 Wind River Systems, Inc. and others. 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.ui.utils; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.eclipse.tcf.te.ui.activator.UIPlugin; import org.eclipse.tcf.te.ui.jface.images.AbstractImageDescriptor; import org.eclipse.tcf.te.ui.search.FilteringImageDescriptor; import org.eclipse.tcf.te.ui.search.QuickFilter; import org.eclipse.tcf.te.ui.search.TreeViewerSearchDialog; import org.eclipse.ui.PlatformUI; /** * The utilities to search and filter a tree viewer. */ public class TreeViewerUtil { // The method to access AbstractTreeViewer#getSortedChildren in order to the children visually on the tree. static volatile Method methodGetSortedChildren; static { SafeRunner.run(new ISafeRunnable(){ @Override public void handleException(Throwable exception) { // Ignore on purpose. } @Override public void run() throws Exception { // Initialize the method object. methodGetSortedChildren = AbstractTreeViewer.class.getDeclaredMethod("getSortedChildren", new Class[]{Object.class}); //$NON-NLS-1$ // Because "getSortedChildren" is a protected method, we need to make it accessible. AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { methodGetSortedChildren.setAccessible(true); return null; } }); } }); } /** * Decorate the image using the filtering image. * * @param image * @param viewer * @param path * @return */ public static Image getDecoratedImage(Image image, TreeViewer viewer, TreePath path) { AbstractImageDescriptor descriptor = new FilteringImageDescriptor(UIPlugin.getDefault().getImageRegistry(), image); return UIPlugin.getSharedImage(descriptor); } /** * Decorate the text using the filter text. * * @param text * @param viewer * @param path * @return */ public static String getDecoratedText(String text, TreeViewer viewer, TreePath path) { String pattern = TreeViewerUtil.getFilteringString(viewer, path); if (pattern != null) { return text + " Filtered (" + pattern + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } return text; } /** * Reset the viewer to the original view. * * @param viewer The viewer to be reset. */ public static void doReset(TreeViewer viewer) { ViewerFilter[] vFilters = viewer.getFilters(); @SuppressWarnings("unchecked") Map<TreePath, QuickFilter> filters = (Map<TreePath, QuickFilter>) viewer.getData("quick.filter"); //$NON-NLS-1$ if (filters != null && vFilters != null && !filters.isEmpty() && vFilters.length > 0) { List<ViewerFilter> filterList = new ArrayList<ViewerFilter>(Arrays.asList(vFilters)); for(Map.Entry<TreePath, QuickFilter> entry : filters.entrySet()) { QuickFilter quickFilter = entry.getValue(); filterList.remove(quickFilter); quickFilter.setPattern(null); } vFilters = filterList.toArray(new ViewerFilter[filterList.size()]); viewer.setFilters(vFilters); } viewer.setData("quick.filter", null); //$NON-NLS-1$ } /** * Provide a pop up to enter filter to filter the tree viewer. * * @param viewer The tree viewer to be filtered. */ public static void doCommonViewerFilter(TreeViewer viewer) { TreePath rootPath = getSelectedPath(viewer); TreePath root = getViewFilterRoot(viewer, rootPath); if (root != null && (root.getSegmentCount() == 0 || viewer.getExpandedState(root))) { @SuppressWarnings("unchecked") Map<TreePath, QuickFilter> filters = (Map<TreePath, QuickFilter>) viewer.getData("quick.filter"); //$NON-NLS-1$ if (filters == null) { filters = new HashMap<TreePath, QuickFilter>(); viewer.setData("quick.filter", filters); //$NON-NLS-1$ } QuickFilter filter = filters.get(root); if(filter == null) { filter = new QuickFilter(viewer, root); filters.put(root, filter); } filter.showFilterPopup(false); } } /** * Provide a pop up to enter filter to filter the tree viewer. * * @param viewer The tree viewer to be filtered. */ public static void doEditorFilter(TreeViewer viewer) { TreePath rootPath = getSelectedPath(viewer); TreePath root = getEditorFilterRoot(viewer, rootPath); if (root != null && (root.getSegmentCount() == 0 || viewer.getExpandedState(root))) { @SuppressWarnings("unchecked") Map<TreePath, QuickFilter> filters = (Map<TreePath, QuickFilter>) viewer.getData("quick.filter"); //$NON-NLS-1$ if (filters == null) { filters = new HashMap<TreePath, QuickFilter>(); viewer.setData("quick.filter", filters); //$NON-NLS-1$ } QuickFilter filter = filters.get(root); if(filter == null) { filter = new QuickFilter(viewer, root); filters.put(root, filter); } filter.showFilterPopup(root.getSegmentCount() == 0); } } /** * Search a tree viewer for specified name specified in the pop up dialog. * * @param viewer The tree viewer to be searched. */ public static void doSearch(TreeViewer viewer) { TreePath rootPath = getSelectedPath(viewer); rootPath = getSearchRoot(viewer, rootPath); Assert.isNotNull(rootPath); TreeViewerSearchDialog dialog = new TreeViewerSearchDialog(viewer, rootPath); dialog.open(); } /** * If the specified element is being filtered. * * @param path * @return */ public static boolean isFiltering(TreeViewer viewer, TreePath path) { if(path != null) { @SuppressWarnings("unchecked") Map<TreePath, QuickFilter> filters = (Map<TreePath, QuickFilter>) viewer.getData("quick.filter"); //$NON-NLS-1$ if (filters != null) { QuickFilter filter = filters.get(path); if(filter != null) { return filter.isFiltering(path); } } } return false; } /** * Test if the specified tree viewer is being filtered. * * @param viewer The tree viewer to be tested. * @return true if there's at least a filter. */ public static boolean isFiltering(TreeViewer viewer) { @SuppressWarnings("unchecked") Map<TreePath, QuickFilter> filters = (Map<TreePath, QuickFilter>) viewer.getData("quick.filter"); //$NON-NLS-1$ if (filters != null && !filters.isEmpty()) { for(Map.Entry<TreePath, QuickFilter> entry : filters.entrySet()) { TreePath path = entry.getKey(); QuickFilter filter = entry.getValue(); if (filter!= null && filter.isFiltering(path)) return true; } } return false; } /** * Get the filter root for the viewer based on the root path. * * @param viewer The tree viewer. * @param rootPath The root path of the filter. * @return An adjust filter. */ private static TreePath getViewFilterRoot(TreeViewer viewer, TreePath rootPath) { if (rootPath != null) { if (!isEligibleRoot(rootPath, viewer)) { return null; } if (rootPath.getSegmentCount() == 0) { viewer.setSelection(StructuredSelection.EMPTY); return TreePath.EMPTY; } return rootPath; } viewer.setSelection(StructuredSelection.EMPTY); return TreePath.EMPTY; } /** * Get the filter root for the viewer based on the root path. * * @param viewer The tree viewer. * @param rootPath The root path of the filter. * @return An adjust filter. */ private static TreePath getEditorFilterRoot(TreeViewer viewer, TreePath rootPath) { if (rootPath != null) { if (!isEligibleRoot(rootPath, viewer)) { return TreePath.EMPTY; } if (rootPath.getSegmentCount() == 0) { return TreePath.EMPTY; } return rootPath; } return TreePath.EMPTY; } /** * Get the current visible/sorted children under the specified parent element or path * by invoking the reflective method. This method is UI thread-safe. * * @param viewer the viewer to get the children from. * @param parentElementOrTreePath The parent element or path. * @return The current visible/sorted children of the parent path/element. */ public static Object[] getSortedChildren(final TreeViewer viewer, final Object parentElementOrTreePath) { if (Display.getCurrent() != null) { try { if (methodGetSortedChildren != null) { return (Object[]) methodGetSortedChildren.invoke(viewer, parentElementOrTreePath); } } catch (Exception e) { } return new Object[0]; } final Object[][] result = new Object[1][]; PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { result[0] = getSortedChildren(viewer, parentElementOrTreePath); } }); return result[0]; } /** * Reposition the starting search path. */ private static TreePath getSearchRoot(TreeViewer viewer, TreePath rootPath) { if (rootPath != null) { if (!hasChildren(rootPath, viewer)) { rootPath = rootPath.getParentPath(); } if (rootPath.getSegmentCount() == 0) { return new TreePath(new Object[] { viewer.getInput() }); } return rootPath; } return new TreePath(new Object[] { viewer.getInput() }); } /** * Test if the root for the tree viewer is eligible as a root path * of a quick filter. * * @param root The root path to be tested. * @param viewer The tree viewer to be filtered. * @return true if it is eligible as a root path or else false. */ private static boolean isEligibleRoot(TreePath root, TreeViewer viewer) { if (viewer.getExpandedState(root)) { return hasChildren(root, viewer); } return false; } /** * Judges if the specified root has children nodes. */ private static boolean hasChildren(TreePath root, TreeViewer viewer) { ITreeContentProvider contentProvider = (ITreeContentProvider) viewer.getContentProvider(); Object rootElement = root.getLastSegment(); Object[] children = contentProvider.getChildren(rootElement); if (children != null && children.length > 0) { ViewerFilter[] filters = viewer.getFilters(); if (filters != null && filters.length > 0) { for (ViewerFilter filter : filters) { if (!(filter instanceof QuickFilter)) { children = filter.filter(viewer, rootElement, children); } if (children == null || children.length == 0) break; } } return children != null && children.length > 0; } return false; } /** * Get the selected path of the viewer. * * @param viewer The tree viewer to get the selected path from. * @return the first selected path or null if no selected path. */ private static TreePath getSelectedPath(TreeViewer viewer) { ISelection selection = viewer.getSelection(); if (selection instanceof TreeSelection) { TreeSelection treeSelection = (TreeSelection) selection; TreePath[] paths = treeSelection.getPaths(); if (paths != null && paths.length > 0) { return paths[0]; } } return null; } /** * Get the filtering text of the filter attached to the specified tree viewer at the specified path. * * @param viewer The tree viewer. * @param path The path at which the filter applies to. * @return The filter text. */ public static String getFilteringString(TreeViewer viewer, TreePath path) { @SuppressWarnings("unchecked") Map<TreePath, QuickFilter> filters = (Map<TreePath, QuickFilter>) viewer.getData("quick.filter"); //$NON-NLS-1$ if (filters != null) { QuickFilter filter = filters.get(path); if (filter != null) { return filter.getFilterText(); } } return null; } }