/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Jan 15, 2006 */ package org.python.pydev.ui.dialogs; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; 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.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.dialogs.ElementTreeSelectionDialog; import org.python.pydev.core.ExtensionHelper; import org.python.pydev.core.callbacks.CallbackWithListeners; import org.python.pydev.core.callbacks.ICallbackWithListeners; import org.python.pydev.ui.IViewCreatedObserver; import org.python.pydev.ui.IViewWithControls; import com.aptana.shared_core.string.StringMatcher; /** * This class extends the 'default' element tree selection dialog so that the user is able to filter the matches * on the tree (As the org.eclipse.ui.dialogs.ElementListSelectionDialog). * * @author Fabio */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class TreeSelectionDialog extends ElementTreeSelectionDialog implements IViewWithControls { private ILabelProvider labelProvider; protected DefaultFilterMatcher fFilterMatcher = new DefaultFilterMatcher(); protected ITreeContentProvider contentProvider; protected String initialFilter = ""; public final ICallbackWithListeners onControlCreated = new CallbackWithListeners(); public final ICallbackWithListeners onControlDisposed = new CallbackWithListeners(); /** * Give subclasses a chance to decide if they want to update the contents of the tree in a thread or not. */ protected boolean updateInThread = true; protected class UpdateJob extends Thread { IProgressMonitor monitor = new NullProgressMonitor(); //only thing it implements is the canceled public UpdateJob() { setPriority(Thread.MIN_PRIORITY); setName("TreeSelectionDialog: UpdateJob"); } @Override public void run() { try { sleep(300); } catch (InterruptedException e) { //ignore } if (!monitor.isCanceled()) { Display display = Display.getDefault(); display.asyncExec(new Runnable() { public void run() { if (!monitor.isCanceled()) { doFilterUpdate(monitor); } } }); } } public void cancel() { this.monitor.setCanceled(true); } } /** * Updates the current filter with the text field text. */ protected void doFilterUpdate(IProgressMonitor monitor) { if (text != null && !text.isDisposed()) { //Must check if it's disposed, as this will be run asynchronously. setFilter(text.getText(), monitor, true); onFinishUpdateJob(); } } /** * Subclasses may override to do something when the update job is finished. */ protected void onFinishUpdateJob() { } public TreeSelectionDialog(Shell parent, ILabelProvider labelProvider, ITreeContentProvider contentProvider) { super(parent, labelProvider, contentProvider); this.labelProvider = labelProvider; this.contentProvider = contentProvider; } public void setInitialFilter(String initialFilter) { this.initialFilter = initialFilter; } private int fWidth = 60; protected Text text; protected UpdateJob updateJob; protected int getDefaultMargins() { return 2; } protected int getDefaultSpacing() { return 2; } @Override protected Control createDialogArea(Composite parent) { Control composite = super.createDialogArea(parent); if (composite instanceof Composite) { updateCompositeLayout((Composite) composite); } TreeViewer treeViewer = getTreeViewer(); treeViewer.addFilter(new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { return matchItemToShowInTree(element); } }); treeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS); treeViewer.expandAll(); if (this.initialFilter.length() > 0) { this.text.setText(this.initialFilter); this.text.setSelection(this.initialFilter.length()); this.setFilter(this.initialFilter, new NullProgressMonitor(), true); } List<IViewCreatedObserver> participants = ExtensionHelper .getParticipants(ExtensionHelper.PYDEV_VIEW_CREATED_OBSERVER); for (IViewCreatedObserver iViewCreatedObserver : participants) { iViewCreatedObserver.notifyViewCreated(this); } onControlCreated.call(this.text); onControlCreated.call(this.getTreeViewer()); return composite; } @Override public int open() { try { return super.open(); } finally { onControlDisposed.call(this.text); onControlDisposed.call(this.getTreeViewer()); } } /* (non-Javadoc) * @see org.eclipse.ui.dialogs.SelectionStatusDialog#createButtonBar(org.eclipse.swt.widgets.Composite) */ @Override protected Control createButtonBar(Composite parent) { Control composite = super.createButtonBar(parent); if (composite instanceof Composite) { updateCompositeLayout((Composite) composite); } return composite; } private void updateCompositeLayout(Composite composite) { Layout l = composite.getLayout(); if (l instanceof GridLayout) { GridLayout layout = (GridLayout) l; layout.marginHeight = convertVerticalDLUsToPixels(getDefaultMargins()); layout.marginWidth = convertHorizontalDLUsToPixels(getDefaultMargins()); layout.verticalSpacing = convertVerticalDLUsToPixels(getDefaultSpacing()); layout.horizontalSpacing = convertHorizontalDLUsToPixels(getDefaultSpacing()); composite.setLayout(layout); } for (Control t : composite.getChildren()) { if (t instanceof Composite) { updateCompositeLayout((Composite) t); } } } @Override protected Label createMessageArea(Composite composite) { Label label = super.createMessageArea(composite); //ok, after the label, we have to create the edit so that the user can filter the results text = new Text(composite, SWT.BORDER); text.setFont(composite.getFont()); GridData data = new GridData(GridData.FILL_HORIZONTAL); data.widthHint = convertWidthInCharsToPixels(fWidth); text.setLayoutData(data); Listener listener = new Listener() { public void handleEvent(Event e) { if (updateInThread) { if (updateJob != null) { updateJob.cancel(); //cancel it if it was already in progress } updateJob = new UpdateJob(); updateJob.start(); } else { doFilterUpdate(new NullProgressMonitor()); } } }; text.addListener(SWT.Modify, listener); text.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.PAGE_DOWN) { Tree tree = getTreeViewer().getTree(); tree.setFocus(); updateSelectionIfNothingSelected(tree); } } public void keyReleased(KeyEvent e) { } }); return label; } private final Object lock = new Object(); //filtering things... protected void setFilter(String text, IProgressMonitor monitor, boolean updateFilterMatcher) { synchronized (lock) { if (monitor.isCanceled()) return; if (updateFilterMatcher) { //just so that subclasses may already treat it. if (fFilterMatcher.lastPattern.equals(text)) { //no actual change... return; } fFilterMatcher.setFilter(text); if (monitor.isCanceled()) return; } TreeViewer treeViewer = getTreeViewer(); Tree tree = treeViewer.getTree(); tree.setRedraw(false); tree.getParent().setRedraw(false); try { if (monitor.isCanceled()) return; treeViewer.refresh(); if (monitor.isCanceled()) return; treeViewer.expandAll(); } finally { tree.setRedraw(true); tree.getParent().setRedraw(true); } } } protected class DefaultFilterMatcher { public StringMatcher fMatcher; public String lastPattern; public DefaultFilterMatcher() { setFilter(""); } public void setFilter(String pattern) { setFilter(pattern, true, false); } private void setFilter(String pattern, boolean ignoreCase, boolean ignoreWildCards) { fMatcher = new StringMatcher(pattern + '*', ignoreCase, ignoreWildCards); this.lastPattern = pattern; } public boolean match(Object element) { boolean match = fMatcher.match(labelProvider.getText(element)); if (match) { return true; } List<Object> allChildren = getAllChildren(element); for (Object object : allChildren) { if (fMatcher.match(labelProvider.getText(object))) { return true; } } return false; } } private List<Object> getAllChildren(Object element) { ArrayList<Object> list = new ArrayList<Object>(); Object[] children = contentProvider.getChildren(element); if (children == null) { return list; } for (Object object : children) { list.add(object); list.addAll(getAllChildren(object)); } return list; } /* * @see SelectionStatusDialog#computeResult() */ protected void computeResult() { doFinalUpdateBeforeComputeResult(); IStructuredSelection selection = (IStructuredSelection) getTreeViewer().getSelection(); List list = selection.toList(); if (list.size() > 0) { setResult(list); } else { TreeItem[] items = getTreeViewer().getTree().getItems(); if (items.length == 1) { //there is only one item filtered in the tree. list = new ArrayList(); list.add(items[0].getData()); setResult(list); } } } protected void doFinalUpdateBeforeComputeResult() { if (updateInThread) { //Make sure that the selection is OK UpdateJob j = updateJob; if (j != null) { updateJob.cancel(); } doFilterUpdate(new NullProgressMonitor()); } } /** * In the default implementation, an item goes to the tree if the filter can properly match * it (but subclasses may override if their understanding of what goes into the tree is * not decided solely by that). */ protected boolean matchItemToShowInTree(Object element) { return fFilterMatcher.match(element); } protected void updateSelectionIfNothingSelected(Tree tree) { TreeItem[] sel = tree.getSelection(); if (sel == null || sel.length == 0) { TreeItem[] items = tree.getItems(); if (items != null && items.length > 0) { tree.setSelection(items[0]); } } } @Override public boolean isHelpAvailable() { return false; } public ICallbackWithListeners getOnControlCreated() { return onControlCreated; } public ICallbackWithListeners getOnControlDisposed() { return onControlDisposed; } }