org.eclipse.gmf.runtime.common.ui.services.elementselection.ElementSelectionComposite.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gmf.runtime.common.ui.services.elementselection.ElementSelectionComposite.java

Source

/******************************************************************************
 * 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. */
    }
}