/*******************************************************************************
* Copyright (c) 2007, 2010 Association for Decentralized Information Management
* in Industry THTH ry.
* 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:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.browsing.ui.swt;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleControlAdapter;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.simantics.browsing.ui.GraphExplorer;
import org.simantics.browsing.ui.NodeContext;
import org.simantics.browsing.ui.common.processors.FilterSelectionRequestQueryProcessor;
import org.simantics.browsing.ui.common.views.IFilterArea;
import org.simantics.browsing.ui.common.views.IFilterAreaProvider;
import org.simantics.browsing.ui.common.views.IFocusable;
import org.simantics.db.layer0.SelectionHints;
import org.simantics.utils.threads.SWTThread;
import org.simantics.utils.threads.ThreadUtils;
import org.simantics.utils.ui.ISelectionUtils;
@SuppressWarnings("restriction")
public class FilterArea extends Composite implements IFocusable, IFilterArea, IFilterAreaProvider {
protected static final Integer FILTER_DELAY = 500;
private final LocalResourceManager resourceManager;
protected GraphExplorer explorer;
protected FilterSelectionRequestQueryProcessor queryProcessor;
private Text filterText;
private NodeContext currentContext;
/**
* The control representing the clear button for the filter text entry. This
* value may be null
if no such button exists, or if the
* controls have not yet been created.
*/
protected Control clearButtonControl;
/**
* Construct the filter area UI component.
*
* @param explorer
* @param queryProcessor
* @param parent
* @param style
*/
public FilterArea(final GraphExplorer explorer, final FilterSelectionRequestQueryProcessor queryProcessor, Composite parent, int style) {
super(parent, style | SWT.BORDER);
this.explorer = explorer;
this.queryProcessor = queryProcessor;
resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()), this);
GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(this);
GridLayoutFactory.fillDefaults().margins(0, 0).spacing(0, 0).numColumns(2).equalWidth(false).applyTo(this);
createFilterText(this);
createFilterCancelIcon(this);
this.setBackground(filterText.getBackground());
addTextModifyListener();
addExplorerSelectionListener();
}
private void createFilterText(FilterArea filterArea) {
//filterText = new Text(this, SWT.SINGLE | SWT.FLAT | SWT.SEARCH | SWT.ICON_CANCEL);
filterText = new Text(this, SWT.SINGLE | SWT.ICON_CANCEL);
GridDataFactory.fillDefaults().grab(true, false).applyTo(filterText);
filterText.setFont(resourceManager.createFont(FontDescriptor.createFrom(filterText.getFont()).increaseHeight(-1)));
// if we're using a field with built in cancel we need to listen for
// default selection changes (which tell us the cancel button has been
// pressed)
if ((filterText.getStyle() & SWT.ICON_CANCEL) != 0) {
filterText.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
if (e.detail == SWT.ICON_CANCEL)
clearText();
}
});
}
GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
// if the text widget supported cancel then it will have it's own
// integrated button. We can take all of the space.
if ((filterText.getStyle() & SWT.ICON_CANCEL) != 0)
gridData.horizontalSpan = 2;
filterText.setLayoutData(gridData);
}
private void createFilterCancelIcon(Composite parent) {
// only create the button if the text widget doesn't support one
// natively
if ((filterText.getStyle() & SWT.ICON_CANCEL) == 0) {
final Image inactiveImage = WorkbenchImages.getImage(ISharedImages.IMG_ETOOL_CLEAR_DISABLED);
final Image activeImage = WorkbenchImages.getImage(ISharedImages.IMG_ETOOL_CLEAR);
final Image pressedImage = (Image) resourceManager.get( ImageDescriptor.createWithFlags( ImageDescriptor.createFromImage( activeImage ), SWT.IMAGE_GRAY ) );
final Label clearButton= new Label(parent, SWT.NONE);
clearButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
clearButton.setImage(inactiveImage);
clearButton.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
clearButton.setToolTipText(WorkbenchMessages.FilteredTree_ClearToolTip);
clearButton.addMouseListener(new MouseAdapter() {
private MouseMoveListener fMoveListener;
@Override
public void mouseDown(MouseEvent e) {
clearButton.setImage(pressedImage);
fMoveListener= new MouseMoveListener() {
private boolean fMouseInButton= true;
@Override
public void mouseMove(MouseEvent e) {
boolean mouseInButton= isMouseInButton(e);
if (mouseInButton != fMouseInButton) {
fMouseInButton= mouseInButton;
clearButton.setImage(mouseInButton ? pressedImage : inactiveImage);
}
}
};
clearButton.addMouseMoveListener(fMoveListener);
}
@Override
public void mouseUp(MouseEvent e) {
if (fMoveListener != null) {
clearButton.removeMouseMoveListener(fMoveListener);
fMoveListener= null;
boolean mouseInButton= isMouseInButton(e);
clearButton.setImage(mouseInButton ? activeImage : inactiveImage);
if (mouseInButton) {
clearText();
filterText.setFocus();
}
}
}
private boolean isMouseInButton(MouseEvent e) {
Point buttonSize = clearButton.getSize();
return 0 <= e.x && e.x < buttonSize.x && 0 <= e.y && e.y < buttonSize.y;
}
});
clearButton.addMouseTrackListener(new MouseTrackListener() {
@Override
public void mouseEnter(MouseEvent e) {
clearButton.setImage(activeImage);
}
@Override
public void mouseExit(MouseEvent e) {
clearButton.setImage(inactiveImage);
}
@Override
public void mouseHover(MouseEvent e) {
}
});
clearButton.getAccessible().addAccessibleListener(
new AccessibleAdapter() {
@Override
public void getName(AccessibleEvent e) {
e.result= WorkbenchMessages.FilteredTree_AccessibleListenerClearButton;
}
});
clearButton.getAccessible().addAccessibleControlListener(
new AccessibleControlAdapter() {
@Override
public void getRole(AccessibleControlEvent e) {
e.detail= ACC.ROLE_PUSHBUTTON;
}
});
this.clearButtonControl = clearButton;
}
}
/**
* Clears the text in the filter text widget.
*/
protected void clearText() {
//System.out.println("clearText");
filterText.setText(""); //$NON-NLS-1$
// TODO: HACK: this can be used to access filter for root context
explorer.select(null);
//textChanged();
}
protected void addTextModifyListener() {
filterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
final String filter = filterText.getText();
//System.out.println("Scheduling setFilter(" + context + ", " + filter + ")");
setFilter(filter, false);
}
});
}
private Map modCount = new HashMap();
public void setFilter(String filter) {
setFilter(filter, true);
}
protected void setFilter(String filter, boolean updateUI) {
final NodeContext context = getFilteredNode();
if (context == null)
return;
AtomicInteger i = modCount.get(context);
if (i == null)
modCount.put(context, new AtomicInteger());
final AtomicInteger counter = modCount.get(context);
final int count = counter.incrementAndGet();
ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {
@Override
public void run() {
int newCount = counter.get();
if (newCount != count)
return;
//System.out.println("schedule setFilter(" + context + ", " + filter + ")");
modCount.remove(context);
if (isDisposed())
return;
ThreadUtils.asyncExec(SWTThread.getThreadAccess(getDisplay()), new Runnable() {
@Override
public void run() {
if (isDisposed())
return;
applyFilter(context, filter, updateUI);
}
});
}
}, FILTER_DELAY, TimeUnit.MILLISECONDS);
}
protected void applyFilter(NodeContext context, String filter, boolean updateUI) {
if (updateUI) {
String current = filterText.getText();
if (!current.equals(filter))
filterText.setText(filter);
}
//System.out.println("queryProcessor.setFilter(" + context + ", " + filter + ")");
queryProcessor.setFilter(context, filter.isEmpty() ? null : filter);
}
protected void addExplorerSelectionListener() {
IPostSelectionProvider selectionProvider = (IPostSelectionProvider)explorer.getAdapter(IPostSelectionProvider.class);
selectionProvider.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
NodeContext context = ISelectionUtils.getSinglePossibleKey(selection, SelectionHints.KEY_MAIN, NodeContext.class);
if(context == null) {
context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
}
if (context == null) {
context = explorer.getRoot();
}
assert (context != null);
String filter = queryProcessor.getFilter(context);
if (filter == null)
filter = "";
//filterText.setData(null);
//System.out.println("selection changed, new filter: " + filter + " for context " + context + ", currentContext=" + currentContext);
currentContext = context;
filterText.setText(filter);
//filterText.setData("context", context);
}
});
}
protected NodeContext getFilteredNode() {
if(currentContext != null) return currentContext;
else return explorer.getRoot();
// return currentContext;
// NodeContext context = (NodeContext)filterText.getData("context");
// if (context == null)
// context = explorer.getRoot();
// return context;
}
@Override
public void focus() {
if (filterText.isDisposed())
return;
Display d = filterText.getDisplay();
if (d.getThread() == Thread.currentThread()) {
doSetFocus();
} else {
d.asyncExec(new Runnable() {
@Override
public void run() {
if (filterText.isDisposed())
return;
doSetFocus();
}
});
}
}
@Override
public void dispose() {
if (isDisposed())
return;
resourceManager.dispose();
explorer = null;
queryProcessor = null;
filterText = null;
currentContext = null;
clearButtonControl = null;
super.dispose();
}
protected void doSetFocus() {
filterText.selectAll();
filterText.setFocus();
}
@Override
public IFilterArea getFilterArea() {
return this;
}
}