/*******************************************************************************
* Copyright (c) 2007, 2009 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:
* Anton Leherbauer (Wind River Systems) - initial API and implementation
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.browser.opentype;
import java.util.Arrays;
import java.util.HashSet;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.swt.SWT;
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.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.browser.ITypeInfo;
import org.eclipse.cdt.core.browser.IndexTypeInfo;
import org.eclipse.cdt.core.browser.QualifiedTypeName;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.index.IIndexMacro;
import org.eclipse.cdt.core.index.IndexFilter;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.browser.typeinfo.TypeSelectionDialog;
import org.eclipse.cdt.internal.core.browser.IndexModelUtil;
import org.eclipse.cdt.internal.ui.ICHelpContextIds;
/**
* A dialog to select an element from a filterable list of elements.
*
* @since 4.0
* @noextend This class is not intended to be subclassed by clients.
*/
public class ElementSelectionDialog extends TypeSelectionDialog {
/**
* Job to update the element list in the background.
*/
private class UpdateElementsJob extends Job {
/**
* The last used prefix to query the index. <code>null</code> means
* the query result should be empty.
*/
private volatile char[] fCurrentPrefix = null;
public UpdateElementsJob(String name) {
super(name);
setSystem(true);
setUser(false);
setPriority(Job.LONG);
}
public char[] getCurrentPrefix() {
return fCurrentPrefix;
}
public void scheduleQuery(char[] prefix) {
fCurrentPrefix= prefix;
int delay = fCurrentPrefix == null ? 0 : (fCurrentPrefix.length < 5 ? 400 : 200);
schedule(delay);
}
@Override
public IStatus run(final IProgressMonitor monitor) {
monitor.beginTask(OpenTypeMessages.ElementSelectionDialog_UpdateElementsJob_inProgress, IProgressMonitor.UNKNOWN);
final ITypeInfo[] elements= getElementsByPrefix(fCurrentPrefix, monitor);
if (elements != null && !monitor.isCanceled()) {
final Shell shell= getShell();
if (shell != null && !shell.isDisposed()) {
Runnable update= new Runnable() {
public void run() {
if (!shell.isDisposed() && !monitor.isCanceled()) {
setListElements(elements);
updateOkState();
}
}};
shell.getDisplay().asyncExec(update);
monitor.done();
return Status.OK_STATUS;
}
}
return Status.CANCEL_STATUS;
}
}
/**
* A job listener for simple job status reporting.
*/
private final class UpdateJobListener extends JobChangeAdapter {
boolean fDone;
private IProgressMonitor fMonitor;
private UpdateJobListener(IProgressMonitor monitor) {
fMonitor= monitor;
}
@Override
public void done(IJobChangeEvent event) {
fDone= true;
final Shell shell= getShell();
if (shell != null && !shell.isDisposed()) {
Runnable update= new Runnable() {
public void run() {
if (!shell.isDisposed() && fDone) {
fMonitor.done();
}
}};
shell.getDisplay().asyncExec(update);
}
}
@Override
public void running(final IJobChangeEvent event) {
fDone= false;
final Shell shell= getShell();
if (shell != null && !shell.isDisposed()) {
Runnable update= new Runnable() {
public void run() {
if (!shell.isDisposed() && !fDone) {
fMonitor.beginTask(OpenTypeMessages.ElementSelectionDialog_UpdateElementsJob_inProgress, IProgressMonitor.UNKNOWN);
}
}};
shell.getDisplay().asyncExec(update);
}
}
}
private static final ISchedulingRule SINGLE_INSTANCE_RULE = new ISchedulingRule() {
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}};
private UpdateElementsJob fUpdateJob;
private boolean fAllowEmptyPrefix= true;
private boolean fAllowEmptyString= true;
private ProgressMonitorPart fProgressMonitorPart;
private String fHelpContextId;
/**
* Constructs an instance of <code>OpenTypeDialog</code>.
* @param parent the parent shell.
*/
public ElementSelectionDialog(Shell parent) {
super(parent);
setHelpContextId(ICHelpContextIds.OPEN_ELEMENT_DIALOG);
setMatchEmptyString(false);
fUpdateJob= new UpdateElementsJob(OpenTypeMessages.ElementSelectionDialog_UpdateElementsJob_name);
fUpdateJob.setRule(SINGLE_INSTANCE_RULE);
}
/*
* @see org.eclipse.cdt.ui.browser.typeinfo.TypeSelectionDialog#create()
*/
@Override
public void create() {
super.create();
// trigger initial query
scheduleUpdate(getFilter());
}
/*
* @see org.eclipse.cdt.ui.browser.typeinfo.TypeSelectionDialog#close()
*/
@Override
public boolean close() {
fUpdateJob.cancel();
return super.close();
}
/**
* Configure the help context id for this dialog.
*
* @param helpContextId
*/
public void setHelpContextId(String helpContextId) {
fHelpContextId= helpContextId;
setHelpAvailable(fHelpContextId != null);
}
/*
* @see org.eclipse.ui.dialogs.AbstractElementListSelectionDialog#setMatchEmptyString(boolean)
*/
@Override
public void setMatchEmptyString(boolean matchEmptyString) {
super.setMatchEmptyString(matchEmptyString);
fAllowEmptyString= matchEmptyString;
if (matchEmptyString) {
setAllowEmptyPrefix(true);
}
}
/**
* Set whether an empty prefix should be allowed for queries.
*
* @param allowEmptyPrefix
*/
public void setAllowEmptyPrefix(boolean allowEmptyPrefix) {
fAllowEmptyPrefix = allowEmptyPrefix;
}
/*
* @see org.eclipse.cdt.ui.browser.typeinfo.TypeSelectionDialog#showLowLevelFilter()
*/
@Override
protected boolean showLowLevelFilter() {
// the low-level filter is useless for us
return false;
}
/*
* @see org.eclipse.ui.dialogs.TwoPaneElementSelector#createDialogArea(org.eclipse.swt.widgets.Composite)
*/
@Override
public Control createDialogArea(Composite parent) {
PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, fHelpContextId);
return super.createDialogArea(parent);
}
/*
* @see org.eclipse.ui.dialogs.TwoPaneElementSelector#createLowerList(org.eclipse.swt.widgets.Composite)
*/
@Override
protected Table createLowerList(Composite parent) {
Table table= super.createLowerList(parent);
createProgressMonitorPart(parent);
return table;
}
/**
* Create the control for progress reporting.
* @param parent
*/
private void createProgressMonitorPart(Composite parent) {
fProgressMonitorPart= new ProgressMonitorPart(parent, new GridLayout(2, false));
GridData gridData= new GridData(GridData.FILL_HORIZONTAL);
gridData.horizontalIndent= 0;
gridData.verticalAlignment= GridData.BEGINNING;
fProgressMonitorPart.setLayoutData(gridData);
Label separator= new Label(parent.getParent(), SWT.SEPARATOR | SWT.HORIZONTAL);
separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fUpdateJob.addJobChangeListener(new UpdateJobListener(fProgressMonitorPart));
}
/**
* Query the elements for the given prefix.
*
* @param prefix
* @param monitor
*/
protected ITypeInfo[] getElementsByPrefix(char[] prefix, IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return null;
}
HashSet<IndexTypeInfo> types = new HashSet<IndexTypeInfo>();
if (prefix != null) {
final IndexFilter filter= new IndexFilter() {
@Override
public boolean acceptBinding(IBinding binding) throws CoreException {
if (isVisibleType(IndexModelUtil.getElementType(binding))) {
return IndexFilter.ALL_DECLARED.acceptBinding(binding);
}
return false;
}
};
try {
IIndex index = CCorePlugin.getIndexManager().getIndex(CoreModel.getDefault().getCModel().getCProjects());
index.acquireReadLock();
try {
IIndexBinding[] bindings= index.findBindingsForPrefix(prefix, false, filter, monitor);
for (int i= 0; i < bindings.length; i++) {
if (i % 0x1000 == 0 && monitor.isCanceled()) {
return null;
}
final IndexTypeInfo typeinfo = IndexTypeInfo.create(index, bindings[i]);
types.add(typeinfo);
}
if (isVisibleType(ICElement.C_MACRO)) {
IIndexMacro[] macros= index.findMacrosForPrefix(prefix, IndexFilter.ALL_DECLARED, monitor);
for (int i= 0; i < macros.length; i++) {
if (i % 0x1000 == 0 && monitor.isCanceled()) {
return null;
}
final IndexTypeInfo typeinfo = IndexTypeInfo.create(index, macros[i]);
types.add(typeinfo);
}
}
} finally {
index.releaseReadLock();
}
} catch (CoreException e) {
CUIPlugin.log(e);
} catch (InterruptedException e) {
CUIPlugin.log(e);
}
}
return types.toArray(new ITypeInfo[types.size()]);
}
@Override
protected final void setListElements(Object[] elements) {
super.setListElements(elements);
}
/**
* @deprecated Unsupported
*/
@Deprecated
@Override
public void setElements(Object[] elements) {
throw new UnsupportedOperationException();
}
@Override
protected void handleEmptyList() {
updateOkState();
}
@Override
protected Text createFilterText(Composite parent) {
final Text result = super.createFilterText(parent);
Listener listener = new Listener() {
public void handleEvent(Event e) {
scheduleUpdate(result.getText());
}
};
result.addListener(SWT.Modify, listener);
return result;
}
protected void scheduleUpdate(String filterText) {
char[] newPrefix= toPrefix(filterText);
final char[] currentPrefix= fUpdateJob.getCurrentPrefix();
final boolean equivalentPrefix= isEquivalentPrefix(currentPrefix, newPrefix);
boolean emptyQuery= newPrefix.length == 0 && !fAllowEmptyPrefix || filterText.length() == 0 && !fAllowEmptyString;
final int jobState = fUpdateJob.getState();
boolean needQuery= !equivalentPrefix || (currentPrefix.length < newPrefix.length && currentPrefix.length < 5 && jobState == Job.RUNNING);
if (emptyQuery) {
newPrefix= null;
needQuery= needQuery || currentPrefix != null;
}
if (needQuery || jobState == Job.WAITING || jobState == Job.SLEEPING) {
fUpdateJob.cancel();
fUpdateJob.scheduleQuery(newPrefix);
}
}
private char[] toPrefix(String userFilter) {
QualifiedTypeName qualifiedName= new QualifiedTypeName(userFilter);
if (qualifiedName.segmentCount() > 1) {
userFilter= qualifiedName.lastSegment();
}
if (userFilter.endsWith("<")) { //$NON-NLS-1$
userFilter= userFilter.substring(0, userFilter.length() - 1);
}
int asterisk= userFilter.indexOf("*"); //$NON-NLS-1$
int questionMark= userFilter.indexOf("?"); //$NON-NLS-1$
int prefixEnd = asterisk < 0 ? questionMark :
questionMark < 0 ? asterisk : Math.min(asterisk, questionMark);
return (prefixEnd == -1 ? userFilter : userFilter.substring(0, prefixEnd)).toCharArray();
}
private boolean isEquivalentPrefix(char[] currentPrefix, char[] newPrefix) {
if (currentPrefix == null || currentPrefix.length > newPrefix.length) {
return false;
} else if (newPrefix.length == currentPrefix.length) {
return Arrays.equals(currentPrefix, newPrefix);
}
return new String(currentPrefix).equals(new String(newPrefix, 0, currentPrefix.length));
}
}