org.eclipse.gmf.runtime.emf.ui.preferences.PathmapsPreferencePage.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gmf.runtime.emf.ui.preferences.PathmapsPreferencePage.java

Source

/******************************************************************************
 * Copyright (c) 2006, 2008 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.emf.ui.preferences;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IPathVariableManager;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.URI;
import org.eclipse.gmf.runtime.emf.core.internal.resources.PathmapManager;
import org.eclipse.gmf.runtime.emf.ui.internal.MslUIPlugin;
import org.eclipse.gmf.runtime.emf.ui.internal.l10n.EMFUIMessages;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.osgi.util.NLS;
import org.eclipse.osgi.util.TextProcessor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.dialogs.PreferenceLinkArea;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;

/**
 * Preference page for specifying the path variables that should be considered
 * for modeling.
 * <p>
 * Path variable are created on the "Linked Resources" preference page, and
 * selected for modeling using this page.
 * </p>
 * <p>
 * This class may be instantiated by clients, but is not intended to be
 * subclassed.
 * </p>
 * 
 * @author Chris McGee
 * @autor Christian W. Damus (cdamus)
 */
public class PathmapsPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {

    private static final String NAME_ATTRIBUTE = "name"; //$NON-NLS-1$

    private IPathVariableManager pathVariableManager = ResourcesPlugin.getWorkspace().getPathVariableManager();

    private Composite pathVariablesComposite;

    private CheckboxTableViewer pathVariables;

    private PathVariableContentProvider pathVariablesContent;

    private Button newVariable;

    private Button editVariable;

    private Button removeVariable;

    /** Path variable changes since last time the Apply button was pressed. */
    private Map variableChanges = new HashMap();

    private Object addedToken = new Object();

    private Object changedToken = new Object();

    private Object removedToken = new Object();

    protected void initHelp() {
        // No context-sensitive help for now.
    }

    protected Control createContents(Composite parent) {
        GridData gridData = null;
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setFont(parent.getFont());

        composite.setLayout(new GridLayout(2, false));

        PreferenceLinkArea pathVariablesArea = new PreferenceLinkArea(composite, SWT.NONE,
                "org.eclipse.ui.preferencePages.LinkedResources", //$NON-NLS-1$
                EMFUIMessages.PathmapsPreferencePage_mainDescription,
                (IWorkbenchPreferenceContainer) getContainer(), null);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
        gridData.grabExcessHorizontalSpace = true;
        gridData.grabExcessVerticalSpace = false;
        gridData.horizontalSpan = 2;
        pathVariablesArea.getControl().setLayoutData(gridData);

        Label pathVariablesLabel = new Label(composite, SWT.LEFT);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
        gridData.grabExcessHorizontalSpace = true;
        gridData.grabExcessVerticalSpace = false;
        gridData.horizontalSpan = 2;
        gridData.verticalIndent = 20;
        pathVariablesLabel.setLayoutData(gridData);
        pathVariablesLabel.setText(EMFUIMessages.PathmapsPreferencePage_availablePathVariables);

        pathVariablesComposite = new Composite(composite, SWT.BORDER);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
        gridData.grabExcessHorizontalSpace = true;
        gridData.grabExcessVerticalSpace = true;
        gridData.horizontalSpan = 1;
        pathVariablesComposite.setLayoutData(gridData);
        GridLayout gridLayout = new GridLayout(1, true);
        gridLayout.marginHeight = 0;
        gridLayout.marginWidth = 0;
        gridLayout.horizontalSpacing = 0;
        gridLayout.verticalSpacing = 0;
        pathVariablesComposite.setLayout(gridLayout);

        pathVariables = CheckboxTableViewer.newCheckList(pathVariablesComposite, SWT.MULTI);

        pathVariablesContent = new PathVariableContentProvider();
        pathVariables.setContentProvider(pathVariablesContent);
        pathVariables.setLabelProvider(new PathVariableLabelProvider());
        pathVariables.setComparator(new PathVariableViewerComparator());
        gridData = new GridData(GridData.FILL_BOTH);
        gridData.grabExcessHorizontalSpace = true;
        gridData.grabExcessVerticalSpace = true;
        // These two hard coded values were borrowed from similar code in
        // org.eclipse.ui.internal.ide.dialogs.PathVariablesGroup
        gridData.heightHint = pathVariables.getTable().getItemHeight() * 7;
        gridData.widthHint = 332;
        pathVariables.getTable().setLayoutData(gridData);

        Composite buttonComposite = new Composite(composite, SWT.NONE);
        buttonComposite.setLayout(new GridLayout(1, false));
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.grabExcessHorizontalSpace = false;
        gridData.grabExcessVerticalSpace = false;
        gridData.horizontalSpan = 1;
        gridData.verticalAlignment = GridData.BEGINNING;
        buttonComposite.setLayoutData(gridData);

        newVariable = new Button(buttonComposite, SWT.CENTER);
        newVariable.setText(EMFUIMessages.PathmapsPreferencePage_newVariable);
        setButtonLayoutData(newVariable);

        editVariable = new Button(buttonComposite, SWT.CENTER);
        editVariable.setText(EMFUIMessages.PathmapsPreferencePage_editVariable);
        setButtonLayoutData(editVariable);

        removeVariable = new Button(buttonComposite, SWT.CENTER);
        removeVariable.setText(EMFUIMessages.PathmapsPreferencePage_removeVariable);
        setButtonLayoutData(removeVariable);

        pathVariables.addSelectionChangedListener(new ISelectionChangedListener() {

            public void selectionChanged(SelectionChangedEvent event) {
                pathVariableSelected(event.getSelection());
            }
        });

        pathVariables.addCheckStateListener(new ICheckStateListener() {

            public void checkStateChanged(CheckStateChangedEvent event) {
                pathVariableChecked(event, (PathVariableEntry) event.getElement());
            }
        });

        newVariable.addSelectionListener(new SelectionAdapter() {

            public void widgetSelected(SelectionEvent e) {
                addPathVariable();
            }
        });

        editVariable.addSelectionListener(new SelectionAdapter() {

            public void widgetSelected(SelectionEvent e) {
                editPathVariable(pathVariables.getSelection());
            }
        });

        removeVariable.addSelectionListener(new SelectionAdapter() {

            public void widgetSelected(SelectionEvent e) {
                removePathVariable(pathVariables.getSelection());
            }
        });

        initializeContents();

        applyDialogFont(composite);

        pathVariableSelected(pathVariables.getSelection());

        return composite;
    }

    /**
     * Responds to the user's gesture to either check or uncheck the specified
     * <code>entry</code> in the path variables list. This may, according to
     * the status of the path variable entry, result in the user's change being
     * reverted (e.g., in the case of attempting to uncheck a variable
     * registered on the extension point). This works around the inability in
     * SWT to disable the checkbox of an item in a check-table (see <a
     * href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=76509">bug 76509</a>
     * for details).
     * 
     * @param event
     *            the (un)check event
     * @param entry
     *            the path variable entry that was (un)checked
     */
    private void pathVariableChecked(CheckStateChangedEvent event, PathVariableEntry entry) {
        if (event.getChecked()) {
            // validate the check
            if (validateSelection(entry, false)) {
                entry.setSelected(true);
            } else {
                event.getCheckable().setChecked(entry, false);
            }
        } else {
            // validate the uncheck
            if (validateDeselection(entry, false)) {
                entry.setSelected(false);
            } else {
                event.getCheckable().setChecked(entry, true);
            }
        }
    }

    /**
     * Handles the selection of zero or more path variables in the list,
     * updating the enablement state of the "Edit..." and "Remove" buttons.
     * 
     * @param selection
     *            the new path variables list selection
     */
    private void pathVariableSelected(ISelection selection) {
        IStructuredSelection ssel = (IStructuredSelection) selection;

        editVariable.setEnabled(validateEdit(ssel, false));
        removeVariable.setEnabled(validateRemove(ssel, false));
    }

    /**
     * Updates the map of pending path variable changes to indicate that the
     * specified variable has been added by the user.
     * 
     * @param variableName
     *            the name of the added variable
     */
    private void markAdded(String variableName) {
        Object currentChange = variableChanges.get(variableName);

        if (currentChange == removedToken) {
            // if we previously removed this variable's value, then it will
            // appear to be a change when we sync on apply
            variableChanges.put(variableName, changedToken);
        } else if (currentChange != changedToken) {
            // shouldn't have been a "changed" if we thought we were adding
            variableChanges.put(variableName, addedToken);
        }
    }

    /**
     * Queries whether the specified path variable has an add change pending, to
     * be applied when the OK/Apply button is pressed.
     * 
     * @param variableName
     *            the path variable name
     * @return <code>true</code> if the variable has a pending change that is
     *         an add; <code>false</code>, otherwise
     */
    boolean isAdded(String variableName) {
        return variableChanges.get(variableName) == addedToken;
    }

    /**
     * Updates the map of pending path variable changes to indicate that the
     * specified variable has been removed by the user.
     * 
     * @param variableName
     *            the name of the removed variable
     */
    private void markRemoved(String variableName) {
        Object currentChange = variableChanges.get(variableName);

        if (currentChange == addedToken) {
            // it was added since the last apply? Just forget about it, then
            variableChanges.remove(variableName);
        } else {
            variableChanges.put(variableName, removedToken);
        }
    }

    /**
     * Queries whether the specified path variable has a remove change pending,
     * to be applied when the OK/Apply button is pressed.
     * 
     * @param variableName
     *            the path variable name
     * @return <code>true</code> if the variable has a pending change that is
     *         a removal; <code>false</code>, otherwise
     */
    boolean isRemoved(String variableName) {
        return variableChanges.get(variableName) == removedToken;
    }

    /**
     * Updates the map of pending path variable changes to indicate that the
     * specified variable's value has been changed by the user.
     * 
     * @param variableName
     *            the name of the changed variable
     */
    private void markChanged(String variableName) {
        Object currentChange = variableChanges.get(variableName);

        if (currentChange == addedToken) {
            // do nothing in this case. If it was added, changing it doesn't
            // change the fact that it's a new variable
        } else {
            variableChanges.put(variableName, changedToken);
        }
    }

    /**
     * Queries whether the specified path variable has a change of value
     * pending, to be applied when the OK/Apply button is pressed.
     * 
     * @param variableName
     *            the path variable name
     * @return <code>true</code> if the variable has a pending change that is
     *         a value change; <code>false</code>, otherwise
     */
    boolean isChanged(String variableName) {
        return variableChanges.get(variableName) == changedToken;
    }

    /**
     * Queries whether the current pending path variables (not yet applied to
     * the workspace and GMF path map manager) has a variable referencing the
     * specified location. Note that this does not consider path variables that
     * are pending removal or others that are currently defined in the workspace
     * and/or GMF that are not visible.
     * 
     * @param location
     *            a location
     * @return <code>true</code> if any of the path variables showing in the
     *         preference page has the specified location; <code>false</code>,
     *         otherwise
     */
    boolean isLocationDefined(IPath location) {
        for (Iterator iter = pathVariablesContent.entries.iterator(); iter.hasNext();) {
            if (location.equals(((PathVariableEntry) iter.next()).getLocationPath())) {
                return true;
            }
        }

        return false;
    }

    /**
     * Handles the pushing of the "New..." button, to create a new path map
     * variable.
     */
    private void addPathVariable() {
        NewPathVariableDialog dlg = NewPathVariableDialog.openNew(this);
        if (dlg != null) {
            String name = dlg.getVariableName();
            IPath location = dlg.getVariableLocation();

            // prepare data for synchronization on apply
            markAdded(name);

            // by default, check the variable (if the user created it in this
            // pref page, assume that it should be used for GMF modeling)
            PathVariableEntry entry = new PathVariableEntry(name, location);
            entry.setSelected(true);
            pathVariablesContent.add(entry);
            pathVariables.setChecked(entry, true);

            // select the new path variable
            pathVariables.setSelection(new StructuredSelection(entry));
        }
    }

    /**
     * Handles the pushing of the "Edit..." button, to edit the path variable
     * contained in the specified <code>selection</code>.
     * 
     * @param selection
     *            the current selection in the path variables list (should
     *            contain a single {@link PathVariableEntry})
     */
    private void editPathVariable(ISelection selection) {
        PathVariableEntry entry = null;

        if (selection instanceof IStructuredSelection) {
            IStructuredSelection ssel = (IStructuredSelection) selection;

            if (!ssel.isEmpty()) {
                entry = (PathVariableEntry) ssel.getFirstElement();
            }
        }

        if (entry != null) {
            String oldName = entry.getName();
            NewPathVariableDialog dlg = NewPathVariableDialog.openEdit(this, oldName, entry.getLocation());

            if (dlg != null) {
                String newName = dlg.getVariableName();
                IPath newLocation = dlg.getVariableLocation();
                boolean nameChanged = !oldName.equals(newName);

                if (nameChanged) {
                    // changing the name is like removing the old name
                    // and adding the new name

                    // prepare data for synchronization on apply
                    markAdded(newName);
                    markRemoved(oldName);
                } else {
                    // prepare data for synchronization on apply
                    markChanged(oldName);
                }

                entry.setName(newName);
                entry.setLocation(newLocation);

                pathVariables.update(entry, nameChanged ? new String[] { NAME_ATTRIBUTE } : null);
            }
        }
    }

    /**
     * Handles the pushing of the "Remove" button, to remove the path
     * variable(s) contained in the specified <code>selection</code>.
     * 
     * @param selection
     *            the current selection in the path variables list (should
     *            contain one or more {@link PathVariableEntry}s of which none
     *            is registered on the extension point)
     */
    private void removePathVariable(ISelection selection) {
        Iterator entries = null;

        if (selection instanceof IStructuredSelection) {
            IStructuredSelection ssel = (IStructuredSelection) selection;

            if (!ssel.isEmpty()) {
                entries = ssel.iterator();
            }
        }

        if (entries != null) {
            while (entries.hasNext()) {
                PathVariableEntry entry = (PathVariableEntry) entries.next();
                String name = entry.getName();

                // prepare data for synchronization on apply
                markRemoved(name);

                pathVariablesContent.remove(entry);
            }
        }
    }

    /**
     * Validates an attempt to check a previously unchecked path variable in the
     * list, optionally showing an error explaining the reason why this is not
     * permitted.
     * 
     * @param entry
     *            a path variable that the user attempted to check
     * @param showError
     *            whether to show any potential error message in the title area
     * @return whether the checking of this variable is permitted
     */
    private boolean validateSelection(PathVariableEntry entry, boolean showError) {
        String name = entry.getName();

        if (!PathmapManager.isCompatiblePathVariable(name)) {
            if (showError) {
                setMessage(EMFUIMessages.PathmapsPreferencePage_incompatiblePathVariableErrorMessage, ERROR);
            }
            return false;
        }

        if (PathmapManager.isRegisteredPathVariable(name)) {
            if (showError) {
                setMessage(EMFUIMessages.PathmapsPreferencePage_registeredPathVariableErrorMessage, ERROR);
            }
            return false;
        }

        return true;
    }

    /**
     * Validates an attempt to uncheck a previously checked path variable in the
     * list, optionally showing an error explaining the reason why this is not
     * permitted.
     * 
     * @param entry
     *            a path variable that the user attempted to uncheck
     * @param showError
     *            whether to show any potential error message in the title area
     * @return whether the unchecking of this variable is permitted
     */
    private boolean validateDeselection(PathVariableEntry entry, boolean showError) {
        if (entry.isRequired()) {
            if (showError) {
                setMessage(EMFUIMessages.PathmapsPreferencePage_registeredPathVariableErrorMessage, ERROR);
            }
            return false;
        }

        return true;
    }

    /**
     * Queries whether it is permitted to edit the specified
     * <code>selection</code> of path variables. Editing is only permitted for
     * a single selection that is not a registered path variable.
     * 
     * @param selection
     *            the current selection in the path variables list
     * @param showError
     *            whether to show any potential error message in the title area
     * @return whether the editing of this selection is permitted
     */
    private boolean validateEdit(IStructuredSelection selection, boolean showError) {
        if (selection.isEmpty() || (selection.size() > 1)) {
            return false;
        }

        String name = ((PathVariableEntry) selection.getFirstElement()).getName();

        if (PathmapManager.isRegisteredPathVariable(name)) {
            if (showError) {
                setMessage(EMFUIMessages.PathmapsPreferencePage_registeredPathVariableErrorMessage, ERROR);
            }

            return false;
        }

        return true;
    }

    /**
     * Queries whether it is permitted to remove the specified
     * <code>selection</code> of path variables. Removal is only permitted
     * when the selection is not empty and does not contain any registered path
     * variable.
     * 
     * @param selection
     *            the current selection in the path variables list
     * @param showError
     *            whether to show any potential error message in the title area
     * @return whether the editing of this selection is permitted
     */
    private boolean validateRemove(IStructuredSelection selection, boolean showError) {
        if (selection.isEmpty()) {
            return false;
        }

        for (Iterator iter = selection.iterator(); iter.hasNext();) {
            String name = ((PathVariableEntry) iter.next()).getName();

            if (PathmapManager.isRegisteredPathVariable(name)) {
                if (showError) {
                    setMessage(EMFUIMessages.PathmapsPreferencePage_registeredPathVariableErrorMessage, ERROR);
                }

                return false;
            }
        }

        return true;
    }

    /**
     * Loads the contents of the Path Variables list, additionally setting the
     * check state of each variable.
     */
    private void initializeContents() {
        setMessage(null);

        variableChanges.clear();

        Set currentVariables = PathmapManager.getPathVariableReferences();

        Set allVariables = new HashSet();
        Set checkedVariables = new HashSet();

        Set pathVariableNames = new HashSet();
        pathVariableNames.addAll(Arrays.asList(pathVariableManager.getPathVariableNames()));
        pathVariableNames.addAll(PathmapManager.getAllPathVariables());

        for (Iterator iter = pathVariableNames.iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            PathVariableEntry entry;

            if (PathmapManager.isRegisteredPathVariable(name)) {
                String value = PathmapManager.getRegisteredValue(name);

                try {
                    URI uri = URI.createURI(value);

                    if (uri.isFile()) {
                        // show the user a familiar file system path instead
                        // of a URI
                        value = uri.toFileString();
                    }
                } catch (RuntimeException e) {
                    // the value is not a valid URI. Nothing for us to
                    // do; that is a problem for the plug-in developer
                    // who registered this path map. We'll show the
                    // value as is
                }

                entry = new PathVariableEntry(name, value);
                checkedVariables.add(entry);
                allVariables.add(entry);
            } else if (PathmapManager.isCompatiblePathVariable(name)) {

                entry = new PathVariableEntry(name, pathVariableManager.getValue(name));

                if (currentVariables.contains(entry.getName())) {
                    checkedVariables.add(entry);
                    entry.setSelected(true);
                }

                allVariables.add(entry);
            }
        }

        pathVariables.setInput(allVariables);
        pathVariables.setCheckedElements(checkedVariables.toArray());
    }

    public void init(IWorkbench workbench) {
        // No initialization is necessary.
    }

    protected void performDefaults() {
        initializeContents();
        super.performDefaults();
    }

    /**
     * Applies the current check state of every path variable to the GMF
     * {@link PathmapManager}'s list of path variable references and saves the
     * preference store.
     */
    public boolean performOk() {
        Set currentVariables = PathmapManager.getPathVariableReferences();

        try {
            // first, process the removed workspace path variables
            for (Iterator iter = variableChanges.keySet().iterator(); iter.hasNext();) {
                String name = (String) iter.next();

                if (isRemoved(name)) {
                    if (pathVariableManager.isDefined(name)) {
                        pathVariableManager.setValue(name, null);
                    }

                    PathmapManager.removePathVariableReference(name);

                    iter.remove(); // successfully processed this change
                }
            }

            // next, process the current set of path variable references to
            // add/remove them according to the user's preferences
            Object[] variables = pathVariablesContent.getElements(null);
            for (int i = 0; i < variables.length; i++) {
                PathVariableEntry entry = (PathVariableEntry) variables[i];
                String name = entry.getName();

                if (isChanged(name) || isAdded(name) && !pathVariableManager.isDefined(name)) {
                    // set the workspace path variable's new value, now
                    pathVariableManager.setValue(name, new Path(entry.getLocation()));

                    // successfully processed this change
                    variableChanges.remove(name);
                }

                if (entry.isSelected() && !currentVariables.contains(name)) {
                    PathmapManager.addPathVariableReference(name);
                } else if (!entry.isSelected() && currentVariables.contains(name)) {
                    PathmapManager.removePathVariableReference(name);
                }
            }

            PathmapManager.updatePreferenceStore();

            return true;
        } catch (CoreException e) {
            ErrorDialog.openError(getShell(), EMFUIMessages.PathmapsPreferencePage_promptTitle,
                    EMFUIMessages.PathmapsPreferencePage_updateFailed, e.getStatus());
            return false;
        }
    }

    /**
     * A content provider for the Path Variables list.
     */
    private static class PathVariableContentProvider implements IStructuredContentProvider {

        private Set entries;

        private TableViewer table;

        PathVariableContentProvider() {
            entries = new HashSet();
        }

        /**
         * Adds a path variable to the list.
         * 
         * @param entry
         *            the new path variable
         */
        void add(PathVariableEntry entry) {
            if (!entries.contains(entry)) {
                entries.add(entry);
                table.add(entry);
            }
        }

        /**
         * Removes a path variable from the list.
         * 
         * @param entry
         *            the path variable to remove
         */
        void remove(PathVariableEntry entry) {
            if (entries.contains(entry)) {
                entries.remove(entry);
                table.remove(entry);
            }
        }

        public Object[] getElements(Object inputElement) {
            return entries.toArray();
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            entries = (Set) newInput;
            table = (TableViewer) viewer;
        }

        public void dispose() {
            // nothing to clean up
        }
    }

    /**
     * A label provider for the Path Variables list.
     */
    private static class PathVariableLabelProvider implements ITableLabelProvider, IColorProvider {

        private Image lockImage = null;

        PathVariableLabelProvider() {
            super();
        }

        /**
         * Shows a lock icon for registered path variables.
         */
        public Image getColumnImage(Object element, int columnIndex) {
            PathVariableEntry entry = (PathVariableEntry) element;
            String name = entry.getName();

            if (PathmapManager.isRegisteredPathVariable(name)) {
                return getLockImage();
            } else if (!isDirectory(entry.getLocation())) {
                return MslUIPlugin.getDefault().getWorkbench().getSharedImages()
                        .getImage(ISharedImages.IMG_OBJS_WARN_TSK);
            }

            return null;
        }

        /**
         * Queries whether the specified location references a directory that
         * exists.
         * 
         * @param location
         *            a location
         * @return <code>true</code> if the location exists in the filesystem
         *         and is a directory
         */
        private boolean isDirectory(String location) {
            File file = new File(location);

            return file.exists() && file.isDirectory();
        }

        /**
         * Obtains the lazily-initialized lock image.
         * 
         * @return the lock image
         */
        private Image getLockImage() {
            if (lockImage == null) {
                lockImage = MslUIPlugin.imageDescriptorFromPlugin(MslUIPlugin.getPluginId(), "/icons/full/lock.gif") //$NON-NLS-1$
                        .createImage();
            }

            return lockImage;
        }

        /**
         * Path variables are displayed in the same way as in the Linked
         * Resources preference page.
         */
        public String getColumnText(Object element, int columnIndex) {
            if (columnIndex != 0) {
                return null;
            }

            PathVariableEntry entry = (PathVariableEntry) element;

            // use the TextProcessor's default separators for file paths
            // if the entry is not required, because only if it is, will
            // it possibly be a URI
            String pathString = entry.isRequired()
                    ? TextProcessor.process(entry.getLocation(), MslUIPlugin.URI_BIDI_SEPARATORS)
                    : TextProcessor.process(entry.getLocation());

            return NLS.bind(EMFUIMessages.PathmapsPreferencePage_variablePattern, entry.getName(), pathString);
        }

        public void dispose() {
            if (lockImage != null) {
                lockImage.dispose();
                lockImage = null;
            }
        }

        public boolean isLabelProperty(Object element, String property) {
            return false;
        }

        public void addListener(ILabelProviderListener listener) {
            // not using listeners
        }

        public void removeListener(ILabelProviderListener listener) {
            // not using listeners
        }

        public Color getBackground(Object element) {
            return null;
        }

        public Color getForeground(Object element) {
            return null;
        }
    }

    /**
     * A sorter for the Path Variables list. All registered path maps sort to
     * the bottom of the list to keep them out of the user's way.
     */
    private static class PathVariableViewerComparator extends ViewerComparator {

        PathVariableViewerComparator() {
            super();
        }

        /**
         * We sort by <code>name</code>.
         */
        public boolean isSorterProperty(Object element, String property) {
            return NAME_ATTRIBUTE.equals(property);
        }

        /**
         * Registered variables are in a higher category than user variables.
         */
        public int category(Object element) {
            // sort statically-registered variables to the end of the list
            return PathmapManager.isRegisteredPathVariable(((PathVariableEntry) element).getName()) ? 1 : 0;
        }
    }

    /**
     * Data model for a path variable in the Path Variables list.
     */
    private static final class PathVariableEntry {

        private String name;

        private String location;

        private IPath locationPath;

        private final boolean required;

        private boolean selected;

        /**
         * Initializes a user-defined path variable with the name and location
         * path.
         * 
         * @param name
         *            the variable name
         * @param location
         *            the location
         */
        PathVariableEntry(String name, IPath location) {
            this(name, location.toPortableString(), false);

            this.locationPath = location;
        }

        /**
         * Initializes a registered path variable with the name and location
         * derived from the URI.
         * 
         * @param name
         *            the variable name
         * @param location
         *            the location URI
         */
        PathVariableEntry(String name, String location) {
            this(name, location, true);
        }

        private PathVariableEntry(String name, String location, boolean required) {
            this.name = name;
            this.location = location;
            this.required = required;
            selected = required;
        }

        /**
         * Queries whether this path variable is required (a registered path
         * variable that the user cannot edit, remove, or uncheck).
         * 
         * @return whether I am required
         */
        boolean isRequired() {
            return required;
        }

        /**
         * Obtains the path variable name.
         * 
         * @return my name
         */
        String getName() {
            return name;
        }

        /**
         * Sets the path variable name, if it is editable.
         * 
         * @param name
         *            the new name
         */
        void setName(String name) {
            if (!isRequired()) {
                this.name = name;
            }
        }

        /**
         * Obtains the path variable location.
         * 
         * @return my location
         */
        String getLocation() {
            return location;
        }

        /**
         * Obtains the path variable location, as an {@link IPath}.
         * 
         * @return my location
         */
        IPath getLocationPath() {
            return locationPath;
        }

        /**
         * Sets the path variable name, if it is editable.
         * 
         * @param location
         *            the new location
         */
        void setLocation(IPath location) {
            if (!isRequired()) {
                this.locationPath = location;
                this.location = location.toPortableString();
            }
        }

        /**
         * Queries whether the path variable is checked. Required (registered)
         * path variables are always checked.
         * 
         * @return whether I am checked
         */
        boolean isSelected() {
            return selected;
        }

        /**
         * Sets whether the path variable is checked, if it is not registered.
         * 
         * @param selected
         *            whether I am checked
         */
        void setSelected(boolean selected) {
            if (!isRequired()) {
                this.selected = selected;
            }
        }

        /**
         * Displays path variable's debug string.
         */
        public String toString() {
            return getName() + " - " + getLocation(); //$NON-NLS-1$
        }
    }
}