/****************************************************************************** * Copyright (c) 2005, 2006 IBM Corporation 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: * IBM Corporation - initial API and implementation ****************************************************************************/ package org.eclipse.gmf.runtime.common.ui.services.elementselection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.gmf.runtime.common.core.util.StringStatics; import org.eclipse.gmf.runtime.common.ui.services.internal.l10n.CommonUIServicesMessages; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.jface.wizard.ProgressMonitorPart; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Text; /** * The element selection composite. The composite functional similar to the JDT * select type dialog. There is a filter field and a table containing a list of * elements to select from. * <p> * The element selection composite requires an IElementSelectionInput as input * for the element selection service. * <p> * Subclasses must override the {@link #isValidSelection}and * {@link #handleSelection(boolean)} to provide custom validation. * * @author Anthony Hunter */ public abstract class ElementSelectionComposite implements IElementSelectionListener { /** * The title to display at the top of the element selection composite. */ private final String title; /** * The elements that have been selected by the user. */ private final List selectedElements = new ArrayList(); /** * Text control to display the filter text. */ private Text filterText = null; /** * The table viewer to display list of matching objects. */ private TableViewer tableViewer = null; /** * The progress bar when searching for matching objects. */ private ProgressMonitorPart progressBar; /** * The input for the element selection service. */ private AbstractElementSelectionInput input; /** * The job running the element selection service. */ private ElementSelectionServiceJob job; /** * The element selection service to use to search for elements. */ private final ElementSelectionService elementSelectionService; /** * Control character for the filter. * <p> * When the user enters the first character into the filterText, element * selection service is called. When the user enters the second character * after the first, we can use the existing results returned by the service. * If the user enters text such that the first character has been changed, * we need to query the service again. * <p> * For example, if the user enters "a" then "ab", we can use the existing * results from "a". If the user enters "a" then "b", then we must query a * second time. * <p> * We also must remember if the service has already been called. If the user * enters "a" and then "b", we must cancel "a" and wait before calling the * service for "b". */ private char firstCharacter = Character.MIN_VALUE; private String lastSearchedFor = StringStatics.BLANK; private int lastScopeSearchedFor = 0; /** * matching objects from the element selection service. */ private List matchingObjects = new ArrayList(); /** * Pattern for the input filter. */ private Pattern pattern; /** * Constructs a new instance that will create the new composite. I will use * the default {@linkplain ElementSelectionService#getInstance() selection service} * to process the <tt>input</tt>. * * @param title * the dialog title * @param input * the element selection input. */ public ElementSelectionComposite(String title, AbstractElementSelectionInput input) { this(title, input, ElementSelectionService.getInstance()); } /** * Constructs a new instance that will create the new composite. * * @param title the dialog title * @param input the element selection input * @param elementSelectionService the selection service to use to process the * <tt>input</tt> */ public ElementSelectionComposite(String title, AbstractElementSelectionInput input, ElementSelectionService elementSelectionService) { super(); this.title = title; this.input = input; this.elementSelectionService = elementSelectionService; this.lastScopeSearchedFor = input.getScope().intValue(); } /** * Determines if the selected elements are a valid selection. * * @param currentSelectedElements * the selected list of Elements * @return <code>true</code> if the selected elements are a valid * selection */ abstract protected boolean isValidSelection(List currentSelectedElements); /** * Handle a selection change, where the validity of the new selection is * encoded in <code>isValid</code>. * * @param isValid * <code>true</code> if the new selection is valid, * <code>false</code> otherwise. */ protected abstract void handleSelection(boolean isValid); /** * Creates the composite. * * @param parent * the parent composite * @return the new composite */ public Composite createComposite(Composite parent) { Composite result = new Composite(parent, SWT.NONE); result.setLayout(new GridLayout()); result.setLayoutData(new GridData(GridData.FILL_BOTH)); // Add the selection title label Label label = new Label(result, SWT.NONE); label.setText(title); // Add the element selection text widget filterText = new Text(result, SWT.SINGLE | SWT.BORDER); filterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); filterText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { handleFilterChange(); } }); // Add the table viewer int selectStyle = SWT.SINGLE; tableViewer = new TableViewer(result, selectStyle | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); tableViewer.setUseHashlookup(true); Table table = tableViewer.getTable(); GridData gridData = new GridData(GridData.FILL_BOTH); GC gc = new GC(result); gc.setFont(JFaceResources.getDefaultFont()); FontMetrics fontMetrics = gc.getFontMetrics(); gc.dispose(); gridData.widthHint = Dialog .convertWidthInCharsToPixels(fontMetrics, 80); gridData.heightHint = table.getItemHeight() * 15; table.setLayoutData(gridData); table.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { handleSelectionChange(); } public void widgetDefaultSelected(SelectionEvent e) { handleWidgetDefaultSelected(); } }); progressBar = new ProgressMonitorPart(result, new GridLayout()); progressBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); progressBar.setVisible(false); tableViewer.setLabelProvider(new LabelProvider() { public Image getImage(Object element) { assert element instanceof AbstractMatchingObject; return ((AbstractMatchingObject) element).getImage(); } public String getText(Object element) { assert element instanceof AbstractMatchingObject; return ((AbstractMatchingObject) element).getDisplayName(); } }); tableViewer.setSorter(new ViewerSorter() { public int compare(Viewer viewer, Object e1, Object e2) { if (e1 instanceof IMatchingObject && e2 instanceof IMatchingObject) return ((IMatchingObject)e1).getName().toLowerCase().compareTo( ((IMatchingObject) e2).getName().toLowerCase()); return super.compare(viewer, e1, e2); } }); createCompositeAdditions(result); return result; } /** * The method is provided so that clients can add additional fields to the * bottom of the selection composite. For example, clients may want to a * checkbox button to the bottom of the composite. * * @param parent * the parent composite */ protected void createCompositeAdditions(Composite parent) { /* clients are expected to override this method */ } /** * Handles a filter change. */ public void handleFilterChange() { if (filterText.getText().equals(StringStatics.BLANK)) { /* no filter, no results */ cancel(); matchingObjects.clear(); tableViewer.getTable().removeAll(); firstCharacter = Character.MIN_VALUE; return; } String filter = validatePattern(filterText.getText()); pattern = Pattern.compile(filter); if (firstCharacter != filterText.getText().charAt(0) || this.input.getScope().intValue() != this.lastScopeSearchedFor || !filterText.getText().startsWith(lastSearchedFor)) { // scope changes, start from scratch... cancel(); matchingObjects.clear(); tableViewer.getTable().removeAll(); firstCharacter = filterText.getText().charAt(0); this.lastScopeSearchedFor = this.input.getScope().intValue(); startElementSelectionService(); } else { /* * clear the existing matches in the table and refilter results we have * received */ tableViewer.getTable().removeAll(); for (Iterator i = matchingObjects.iterator(); i.hasNext();) { IMatchingObject matchingObject = (IMatchingObject) i.next(); Matcher matcher = pattern.matcher(matchingObject.getName() .toLowerCase()); if (matcher.matches()) { tableViewer.add(matchingObject); setSelection(); } } } } /** * Fill the table viewer with results from the element selection service. */ private void startElementSelectionService() { /* * Initialize all possible matching objects from the select element * service. */ input.setInput(filterText.getText()); lastSearchedFor = filterText.getText(); progressBar.setVisible(true); progressBar.beginTask( CommonUIServicesMessages.ElementSelectionService_ProgressName, IProgressMonitor.UNKNOWN); job = elementSelectionService.getMatchingObjects(input, this); } /** * Handles a selection change and validates the new selection. */ private void handleSelectionChange() { StructuredSelection selection = (StructuredSelection) tableViewer .getSelection(); if (selection.size() == 0) { // nothing selected selectedElements.clear(); handleSelection(false); return; } List selectionList = selection.toList(); // get the current selected elements List currentSelectedElements = new ArrayList(); for (Iterator iter = selectionList.iterator(); iter.hasNext();) { AbstractMatchingObject matchingObject = (AbstractMatchingObject) iter .next(); currentSelectedElements.add(matchingObject); } // validate selection boolean isValidSelection = isValidSelection(currentSelectedElements); // store the selection selectedElements.clear(); if (isValidSelection) { selectedElements.addAll(currentSelectedElements); } // update UI based on selection handleSelection(isValidSelection); } /** * Gets the user selected elements. * * @return the user selected elements */ public List getSelectedElements() { List result = new ArrayList(); for (Iterator iter = selectedElements.iterator(); iter.hasNext();) { IMatchingObject matchingObject = (IMatchingObject) iter.next(); IElementSelectionProvider provider = matchingObject.getProvider(); Object object = provider.resolve(matchingObject); result.add(object); } return result; } public void matchingObjectEvent(IMatchingObjectEvent matchingObjectEvent) { if (!progressBar.isDisposed()) { if (matchingObjectEvent.getEventType() == MatchingObjectEventType.END_OF_MATCHES) { progressBar.done(); progressBar.setVisible(false); job = null; } else { IMatchingObject matchingObject = matchingObjectEvent .getMatchingObject(); progressBar.worked(1); progressBar.subTask(matchingObject.getName()); matchingObjects.add(matchingObject); Matcher matcher = pattern.matcher(matchingObject.getName() .toLowerCase()); if (matcher.matches()) { tableViewer.add(matchingObject); setSelection(); } } } } /** * Cancel the job running the element selection service. */ public void cancel() { if (job != null) { elementSelectionService.cancelJob(job); job = null; progressBar.done(); progressBar.setVisible(false); } } /** * Convert the UNIX style pattern entered by the user to a Java regex * pattern (? = any character, * = any string). * * @param string * the UNIX style pattern. * @return a Java regex pattern. */ private String validatePattern(String string) { if (string.equals(StringStatics.BLANK)) { return string; } StringBuffer result = new StringBuffer(); for (int i = 0; i < string.length(); i++) { char c = Character.toLowerCase(string.charAt(i)); if (c == '?') { result.append('.'); } else if (c == '*') { result.append(".*"); //$NON-NLS-1$ } else { result.append(c); } } result.append(".*"); //$NON-NLS-1$ return result.toString(); } /** * If there is no selection in the composite, set the selection to the * provided MatchingObject. * * @param matchingObject * the MatchingObject to select. */ protected void setSelection() { StructuredSelection selection = (StructuredSelection) tableViewer .getSelection(); if (selection.isEmpty()) { tableViewer.getTable().setSelection(0); handleSelectionChange(); } } /** * Retreive the filter text field. * * @return the filter text field. */ public Text getFilterText() { return filterText; } /** * Retreive the element selection service job. * * @return the element selection service job. */ public ElementSelectionServiceJob getSelectionServiceJob() { return job; } /** * Handle the double click of a selection in the table viewer. */ protected void handleWidgetDefaultSelected() { /** Default behavior is to do nothing. Subclasses can override. */ } }