/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.dialogs;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IElementComparer;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
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.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.swt.widgets.Widget;
import org.eclipse.ui.internal.misc.StringMatcher;
import org.xmind.ui.viewers.SWTUtils;
public class PopupFilteredList extends PopupDialog {
/**
* An element creator creates an element using a user-input text. This class
* is used when the popup filtered list permits unprovided values and need
* to convert the user-input text into a proper value.
*
* @author Frank Shaka
*/
public static interface IElementCreator {
/**
* Creates an element according to the user-input text.
*
* @param text
* a string inputted by user
* @return an element created according to the specified text
*/
Object createElement(String text);
}
public static class PatternFilter extends ViewerFilter {
private String patternText = null;
private StringMatcher matcher = null;
private boolean ignoreCase;
private boolean useWildCards;
private boolean useWhiteSpacesAsWildCards;
private boolean useWildCardOnStart;
public PatternFilter() {
this(true, true, true, true);
}
public PatternFilter(boolean ignoreCase, boolean useWildCards,
boolean useWhiteSpacesAsWildCards, boolean useWildCardOnStart) {
this.ignoreCase = ignoreCase;
this.useWildCards = useWildCards;
this.useWhiteSpacesAsWildCards = useWhiteSpacesAsWildCards;
this.useWildCardOnStart = useWildCardOnStart;
}
public String getPatternText() {
return patternText;
}
private void setPatternText(String pattern) {
this.patternText = pattern;
updateMatcher();
}
protected void updateMatcher() {
if (patternText == null || patternText.length() == 0) {
matcher = null;
} else {
String pattern = patternText;
if (usesWhiteSpacesAsWildCards()) {
pattern = pattern.replaceAll(" ", "*"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (usesWildCardOnStart()) {
if (!pattern.startsWith("*")) //$NON-NLS-1$
pattern = "*" + pattern; //$NON-NLS-1$
}
if (!pattern.endsWith("*")) //$NON-NLS-1$
pattern = pattern + "*"; //$NON-NLS-1$
matcher = new StringMatcher(pattern, ignoresCase(),
!usesWildCards());
}
}
public boolean ignoresCase() {
return ignoreCase;
}
public boolean usesWildCardOnStart() {
return useWildCardOnStart;
}
public boolean usesWhiteSpacesAsWildCards() {
return useWhiteSpacesAsWildCards;
}
public boolean usesWildCards() {
return useWildCards;
}
public void setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
updateMatcher();
}
public void setUseWildCardOnStart(boolean useWildCardOnStart) {
this.useWildCardOnStart = useWildCardOnStart;
updateMatcher();
}
public void setUseWhiteSpacesAsWildCards(
boolean usesWhiteSpacesAsWildCards) {
this.useWhiteSpacesAsWildCards = usesWhiteSpacesAsWildCards;
updateMatcher();
}
public void setUseWildCards(boolean usesWildCards) {
this.useWildCards = usesWildCards;
updateMatcher();
}
public boolean select(Viewer viewer, Object parentElement,
Object element) {
if (matcher == null)
return true;
if (isMatch(viewer, element))
return true;
return isLeafMatch(viewer, element);
}
private boolean isMatch(Viewer viewer, Object element) {
String elementName = ((ILabelProvider) ((ContentViewer) viewer)
.getLabelProvider()).getText(element);
if (elementName == null)
return false;
return matcher.match(elementName);
}
private boolean isLeafMatch(Viewer viewer, Object element) {
if (isMatch(viewer, element))
return true;
Object[] children = ((ITreeContentProvider) ((AbstractTreeViewer) viewer)
.getContentProvider()).getChildren(element);
if ((children != null) && (children.length > 0)) {
for (Object ele : children) {
if (isLeafMatch(viewer, ele))
return true;
}
}
return false;
}
}
private static class DelegatingTreeContentProvider implements
ITreeContentProvider {
private IContentProvider delegate;
public DelegatingTreeContentProvider(IContentProvider delegate) {
this.delegate = delegate;
}
public Object[] getChildren(Object parentElement) {
if (delegate instanceof ITreeContentProvider) {
return ((ITreeContentProvider) delegate)
.getChildren(parentElement);
}
return new Object[0];
}
public Object getParent(Object element) {
if (delegate instanceof ITreeContentProvider) {
return ((ITreeContentProvider) delegate).getParent(element);
}
return null;
}
public boolean hasChildren(Object element) {
if (delegate instanceof ITreeContentProvider) {
return ((ITreeContentProvider) delegate).hasChildren(element);
}
return false;
}
public Object[] getElements(Object inputElement) {
if (delegate instanceof IStructuredContentProvider) {
return ((IStructuredContentProvider) delegate)
.getElements(inputElement);
} else if (delegate instanceof ITreeContentProvider) {
return ((ITreeContentProvider) delegate)
.getElements(inputElement);
}
return new Object[0];
}
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
private class DelegatingLabelProvider extends LabelProvider implements
IFontProvider, ILabelProviderListener {
private IBaseLabelProvider delegate;
public DelegatingLabelProvider(IBaseLabelProvider delegate) {
this.delegate = delegate;
if (delegate != null) {
delegate.addListener(this);
}
}
public String getText(Object element) {
if (delegate instanceof ILabelProvider) {
return ((ILabelProvider) delegate).getText(element);
}
return super.getText(element);
}
public Image getImage(Object element) {
if (delegate instanceof ILabelProvider) {
return ((ILabelProvider) delegate).getImage(element);
}
return super.getImage(element);
}
public Font getFont(Object element) {
if (element == defaultSelection
|| (element != null && element.equals(defaultSelection)))
return JFaceResources.getFontRegistry().getBold(
JFaceResources.DEFAULT_FONT);
return null;
}
public void labelProviderChanged(LabelProviderChangedEvent event) {
LabelProviderChangedEvent e = new LabelProviderChangedEvent(this,
event.getElements());
fireLabelProviderChanged(e);
}
}
private Text filterText;
private TreeViewer treeViewer;
private List<IOpenListener> openListeners = null;
private PatternFilter patternFilter = null;
private Object input = null;
private IContentProvider contentProvider = null;
private IBaseLabelProvider labelProvider = null;
private ViewerFilter[] filters = null;
private ViewerSorter sorter = null;
private ViewerComparator comparator = null;
private IElementComparer comparer = null;
private Object defaultSelection = null;
private Rectangle boundsReference = null;
private boolean permitsUnprovidedElement = false;
private IElementCreator elementCreator = null;
public PopupFilteredList(Shell parent) {
this(parent, SWT.ON_TOP | SWT.RESIZE);
}
public PopupFilteredList(Shell parent, String titleText, String infoText) {
super(parent, SWT.ON_TOP | SWT.RESIZE, true, false, false, false,
false, titleText, infoText);
}
public PopupFilteredList(Shell parent, int shellStyle) {
super(parent, shellStyle, true, false, false, false, false, null, null);
}
protected Control createDialogArea(Composite parent) {
Display display = parent.getDisplay();
Composite composite = (Composite) super.createDialogArea(parent);
composite.setBackground(display
.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
GridLayout layout2 = (GridLayout) composite.getLayout();
layout2.verticalSpacing = 3;
layout2.marginWidth = 3;
layout2.marginTop = 4;
filterText = createFilterText(composite);
filterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
treeViewer = createTreeViewer(composite);
treeViewer.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
configureFilterText(filterText);
configureTreeViewer(treeViewer);
return composite;
}
protected Text createFilterText(Composite parent) {
return new Text(parent, SWT.SINGLE);
}
protected void configureFilterText(final Text filterText) {
filterText.setText(""); //$NON-NLS-1$
filterText.addFocusListener(new FocusAdapter() {
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 = getFilterText().getDisplay();
display.asyncExec(new Runnable() {
public void run() {
if (!getFilterText().isDisposed()) {
getFilterText().selectAll();
}
}
});
}
});
filterText.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
// on a Arrow Down we want to transfer focus to the list
boolean hasItems = getViewer().getTree().getItemCount() > 0;
if (hasItems && e.keyCode == SWT.ARROW_DOWN) {
treeViewer.getTree().setFocus();
}
}
});
// enter key set focus to tree
filterText.addTraverseListener(new TraverseListener() {
public void keyTraversed(TraverseEvent e) {
if (e.detail == SWT.TRAVERSE_RETURN) {
e.doit = false;
fireOpen();
}
}
});
filterText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
filterChanged();
}
});
}
protected TreeViewer createTreeViewer(Composite parent) {
return new TreeViewer(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL
| SWT.FULL_SELECTION);
}
protected void configureTreeViewer(TreeViewer treeViewer) {
final Tree tree = treeViewer.getTree();
Listener listener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.KeyDown:
if (SWTUtils.matchKey(event.stateMask, event.keyCode, 0,
SWT.ESC)) {
close();
}
break;
case SWT.DefaultSelection:
fireOpen();
break;
case SWT.MouseUp:
handleMouseUp(event);
break;
}
}
protected void handleMouseUp(Event e) {
if (tree.getSelectionCount() < 1)
return;
if (e.button == 1) {
if (tree.equals(e.widget)) {
Widget o = tree.getItem(new Point(e.x, e.y));
TreeItem selection = tree.getSelection()[0];
if (selection.equals(o)) {
fireOpen(selection.getData());
}
}
}
}
};
tree.addListener(SWT.KeyDown, listener);
tree.addListener(SWT.DefaultSelection, listener);
tree.addListener(SWT.MouseUp, listener);
tree.addMouseMoveListener(new MouseMoveListener() {
final int ignoreEventCount = "gtk".equals(SWT.getPlatform()) ? 4 : 1; //$NON-NLS-1$
TreeItem fLastItem = null;
int lastY = 0;
int itemHeightdiv4 = tree.getItemHeight() / 4;
int tableHeight = tree.getBounds().height;
Point tableLoc = tree.toDisplay(0, 0);
int divCount = 0;
public void mouseMove(MouseEvent e) {
if (divCount == ignoreEventCount) {
divCount = 0;
}
if (tree.equals(e.getSource()) & ++divCount == ignoreEventCount) {
Widget item = tree.getItem(new Point(e.x, e.y));
if (item instanceof TreeItem && lastY != e.y) {
lastY = e.y;
if (!item.equals(fLastItem)) {
fLastItem = (TreeItem) item;
tree.setSelection(new TreeItem[] { fLastItem });
} else if (e.y < itemHeightdiv4) {
// Scroll up
item = getViewer().scrollUp(e.x + tableLoc.x,
e.y + tableLoc.y);
if (item instanceof TreeItem) {
fLastItem = (TreeItem) item;
tree.setSelection(new TreeItem[] { fLastItem });
}
} else if (e.y > tableHeight - itemHeightdiv4) {
// Scroll down
item = getViewer().scrollDown(e.x + tableLoc.x,
e.y + tableLoc.y);
if (item instanceof TreeItem) {
fLastItem = (TreeItem) item;
tree.setSelection(new TreeItem[] { fLastItem });
}
}
}
}
}
});
treeViewer.setContentProvider(new DelegatingTreeContentProvider(
getContentProvider()));
treeViewer.setLabelProvider(new DelegatingLabelProvider(
getLabelProvider()));
if (getFilters() != null)
treeViewer.setFilters(getFilters());
getPatternFilter().setPatternText(null);
treeViewer.addFilter(getPatternFilter());
treeViewer.setSorter(getSorter());
treeViewer.setComparator(getComparator());
treeViewer.setComparer(getComparer());
treeViewer.setInput(getInput());
treeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
if (defaultSelection != null) {
treeViewer.setSelection(new StructuredSelection(defaultSelection));
} else if (tree.getItemCount() > 0) {
tree.setSelection(tree.getItem(0));
}
}
protected void filterChanged() {
Control control = getViewer().getControl();
control.setRedraw(false);
getPatternFilter().setPatternText(getFilterText().getText());
getViewer().refresh();
control.setRedraw(true);
}
public Text getFilterText() {
return filterText;
}
public TreeViewer getViewer() {
return treeViewer;
}
public void addOpenListener(IOpenListener listener) {
if (openListeners == null)
openListeners = new ArrayList<IOpenListener>();
openListeners.add(listener);
}
public void removeOpenListener(IOpenListener listener) {
if (openListeners == null)
return;
openListeners.remove(listener);
}
protected void fireOpen() {
ISelection selection = getViewer().getSelection();
if (selection.isEmpty() && permitsUnprovidedElement()) {
selection = new StructuredSelection(createElement(getFilterText()
.getText()));
}
fireOpenEvent(new OpenEvent(getViewer(), selection));
}
private Object createElement(String text) {
if (elementCreator != null)
return elementCreator.createElement(text);
return text;
}
protected void fireOpen(Object element) {
fireOpenEvent(new OpenEvent(getViewer(), new StructuredSelection(
element)));
}
protected void fireOpenEvent(final OpenEvent e) {
// if (input instanceof List<?>) {
// TreeViewer viewer = getViewer();
// ISelection selection = viewer.getSelection();
// if (selection instanceof StructuredSelection) {
// Object element = ((StructuredSelection) selection)
// .getFirstElement();
// boolean unAction = FontUtils.LOADED_FONTS.equals(element)
// || FontUtils.RECOMMENDED_FONTS.equals(element)
// || FontUtils.SYSTEM_FONTS.equals(element);
// if (unAction) {
// if (viewer.getExpandedState(element))
// viewer.collapseToLevel(element, 2);
// else
// viewer.expandToLevel(element, 2);
// return;
// }
// }
// }
close();
if (openListeners == null || openListeners.isEmpty())
return;
for (final Object l : openListeners.toArray()) {
SafeRunner.run(new SafeRunnable() {
public void run() throws Exception {
((IOpenListener) l).open(e);
}
});
}
}
protected PatternFilter getPatternFilter() {
if (patternFilter == null)
patternFilter = new PatternFilter();
return patternFilter;
}
public Object getInput() {
return input;
}
public void setInput(Object input) {
this.input = input;
}
public IContentProvider getContentProvider() {
return contentProvider;
}
public void setContentProvider(IContentProvider contentProvider) {
this.contentProvider = contentProvider;
}
public IBaseLabelProvider getLabelProvider() {
return labelProvider;
}
public void setLabelProvider(IBaseLabelProvider labelProvider) {
this.labelProvider = labelProvider;
}
public ViewerSorter getSorter() {
return sorter;
}
public void setSorter(ViewerSorter sorter) {
this.sorter = sorter;
}
public ViewerComparator getComparator() {
return comparator;
}
public void setComparator(ViewerComparator comparator) {
this.comparator = comparator;
}
public IElementComparer getComparer() {
return comparer;
}
public void setComparer(IElementComparer comparer) {
this.comparer = comparer;
}
public void setFilters(ViewerFilter[] filters) {
this.filters = filters;
}
public ViewerFilter[] getFilters() {
return filters;
}
public Object getDefaultSelection() {
return defaultSelection;
}
public void setDefaultSelection(Object defaultSelection) {
this.defaultSelection = defaultSelection;
}
public void setBoundsReference(Rectangle reference) {
this.boundsReference = reference;
}
// @Override
// protected Point getInitialSize() {
// Point initialSize = super.getInitialSize();
// initialSize.x = initialSize.x + 35;
// return initialSize;
// }
protected Point getInitialLocation(Point initialSize) {
Rectangle r = boundsReference;
if (r != null) {
return new Point(r.x, r.y + r.height);
}
return super.getInitialLocation(initialSize);
}
public void setPatternFilter(PatternFilter patternFilter) {
if (patternFilter == this.patternFilter)
return;
this.patternFilter = patternFilter;
if (getViewer() != null && !getViewer().getControl().isDisposed()) {
filterChanged();
}
}
public boolean permitsUnprovidedElement() {
return permitsUnprovidedElement;
}
public void setPermitsUnprovidedElement(boolean permitsUnprovidedElement) {
this.permitsUnprovidedElement = permitsUnprovidedElement;
}
public void setElementCreator(IElementCreator elementCreator) {
this.elementCreator = elementCreator;
}
public IElementCreator getElementCreator() {
return elementCreator;
}
}