org.obeonetwork.dsl.uml2.design.api.wizards.ModelElementsSelectionDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.obeonetwork.dsl.uml2.design.api.wizards.ModelElementsSelectionDialog.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Obeo.
 * 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:
 *     Obeo - initial API and implementation
 *******************************************************************************/

package org.obeonetwork.dsl.uml2.design.api.wizards;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.provider.EcoreItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.sirius.diagram.DDiagram;
import org.eclipse.sirius.diagram.DSemanticDiagram;
import org.eclipse.sirius.diagram.description.DiagramDescription;
import org.eclipse.sirius.diagram.ui.provider.DiagramUIPlugin;
import org.eclipse.sirius.diagram.ui.tools.api.image.DiagramImagesPath;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.base.Options;
import org.eclipse.sirius.ui.tools.api.color.VisualBindingManager;
import org.eclipse.sirius.ui.tools.internal.views.common.navigator.SiriusCommonLabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.CheckedTreeSelectionDialog;
import org.eclipse.uml2.uml.Activity;
import org.eclipse.uml2.uml.Artifact;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Collaboration;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.DataType;
import org.eclipse.uml2.uml.Device;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ExecutionEnvironment;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.Node;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.StructuredClassifier;
import org.eclipse.uml2.uml.UseCase;
import org.eclipse.uml2.uml.edit.providers.UMLItemProviderAdapterFactory;
import org.obeonetwork.dsl.uml2.design.internal.listeners.UmlDesignerSessionManagerListener;
import org.obeonetwork.dsl.uml2.design.internal.services.ElementServices;
import org.obeonetwork.dsl.uml2.design.internal.services.UIServices;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
 * Dialog to select semantic model elements. Derived from @see DiagramElementsSelectionDialog.
 *
 * @author Melani Bats <a href="mailto:melanie.bats@obeo.fr">melanie.bats@obeo.fr</a>
 */
@SuppressWarnings("restriction")
public class ModelElementsSelectionDialog {

    /**
     * A customized version of CheckedTreeSelectionDialog with a combo to filter the view to show all
     * elements/only checked elements/only unchecked elements.
     */
    protected final class CustomTreeSelectionDialog extends CheckedTreeSelectionDialog {

        /**
         * A String used to identify the Text allowing user to type regular expression (can be used for
         * testing).
         */
        public static final String REGEXP_TOOL_TIP = "Expression that will be used to filer elements by name (for example 'abc', 'a?c', '*c'...)"; //$NON-NLS-1$

        /**
         * The title of the Group allowing user to type Regular Expressions and filter the selection.
         */
        private static final String REGEXP_TITLE = "Filter elements by name"; //$NON-NLS-1$

        /**
         * The String explaining to user how to use regular expressions.
         */
        private static final String REGEXP_EXPLANATIONS = "? = any character, * = any String"; //$NON-NLS-1$

        /**
         * A matcher used to determine if a given DDiagramElement is matching the regular expression typed by
         * user. It's updated each time the user modify the regular expression.
         */
        protected ModelElementsSelectionDialogPatternMatcher patternMatcher;

        /**
         * Collection of elements currently checked by user.
         */
        private final Set<Object> checkedElements = Sets.newHashSet();

        /**
         * Constructor.
         *
         * @param parent
         *            Shell
         * @param labelProvider
         *            Label provider
         * @param contentProvider
         *            Content provider
         */
        private CustomTreeSelectionDialog(Shell parent, ILabelProvider labelProvider,
                ITreeContentProvider contentProvider) {
            super(parent, labelProvider, contentProvider);
            patternMatcher = new ModelElementsSelectionDialogPatternMatcher(""); //$NON-NLS-1$
        }

        /**
         * Add button.
         *
         * @param parent
         *            Composite
         * @param toolTipText
         *            Tooltip
         * @param image
         *            Image
         * @param action
         *            Add action
         * @return Add button
         */
        private Button addButton(Composite parent, String toolTipText, Image image, SelectionListener action) {
            final Button button = new Button(parent, SWT.PUSH);
            button.setToolTipText(toolTipText);
            button.setImage(image);
            button.addSelectionListener(action);
            return button;
        }

        /**
         * Check all elements in tree viewer.
         */
        @SuppressWarnings({ "rawtypes", "unchecked" })
        private void checkAll() {
            final Collection roots = (Collection) getTreeViewer().getInput();
            setRecursiveState(roots, true);
        }

        /**
         * Collapse tree viewer.
         */
        private void collapseAll() {
            getTreeViewer().collapseAll();
        }

        /**
         * {@inheritDoc}
         *
         * @see org.eclipse.jface.dialogs.Dialog#createContents(org.eclipse.swt.widgets.Composite)
         */
        @Override
        protected Control createContents(Composite parent) {
            final Control result = super.createContents(parent);
            getTreeViewer().setCheckStateProvider(new ICheckStateProvider() {

                public boolean isChecked(Object element) {
                    return checkedElements.contains(element);
                }

                public boolean isGrayed(Object element) {
                    return isGrayed.apply(element);
                }
            });
            getTreeViewer().addCheckStateListener(new ICheckStateListener() {
                public void checkStateChanged(CheckStateChangedEvent event) {
                    if (!isGrayed.apply(event.getElement())) {
                        if (event.getChecked()) {
                            checkedElements.add(event.getElement());
                        } else {
                            checkedElements.remove(event.getElement());
                        }
                    }
                }
            });
            getTreeViewer().expandToLevel(2);
            return result;
        }

        /**
         * This method has been overridden to be able to insert selection buttons between the top label and
         * the tree viewer. {@inheritDoc}
         */
        @Override
        protected Label createMessageArea(Composite composite) {
            final Label createMessageArea = super.createMessageArea(composite);

            createSelectionButtonsAfterMessageArea(composite);

            // creating a text zone to allow user to type regular expressions
            createRegexpTypeZone(composite);
            return createMessageArea;
        }

        /**
         * Creates a zone in which user will be able to type a Regular Expression to filter the shown
         * elements.
         *
         * @param composite
         *            the parent composite
         */
        private void createRegexpTypeZone(Composite composite) {
            // Step 1 : create Group
            final Group expregGroup = new Group(composite, SWT.NONE);
            expregGroup.setText(REGEXP_TITLE);
            final GridLayout expregLayout = new GridLayout();
            expregGroup.setLayout(expregLayout);
            expregGroup.setFont(composite.getFont());
            expregGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

            // Step 2 : create explanations zone
            final Label explanationsLabel = new Label(expregGroup, SWT.NONE);
            explanationsLabel.setText(REGEXP_EXPLANATIONS);

            // Step 3 : create the text zone in which user will type the expreg
            final Text regularExpressionText = new Text(expregGroup, SWT.BORDER);
            regularExpressionText.setToolTipText(REGEXP_TOOL_TIP);
            regularExpressionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

            // Step 4 : add modify listener to this textZone
            regularExpressionText.addModifyListener(new ModifyListener() {

                public void modifyText(ModifyEvent e) {
                    final String typedRegex = ((Text) e.getSource()).getText();
                    // Each time the regular expression is modified, the
                    // patternMatcher is updated
                    setPatternMatcher(new ModelElementsSelectionDialogPatternMatcher(typedRegex));
                    updateFilteringMode(mode);
                }
            });
        }

        /**
         * This method has been overridden to remove the selection buttons that are generically created after
         * the tree viewer. This method should not return a null value. Otherwise, in case of empty list we
         * will have a NPE. {@inheritDoc}
         */
        @Override
        protected Composite createSelectionButtons(Composite composite) {
            final Composite buttonComposite = new Composite(composite, SWT.RIGHT) {
                /**
                 * This method has been overridden to have an "empty" size for this composite. {@inheritDoc}
                 *
                 * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
                 */
                @Override
                public Point computeSize(int wHint, int hHint, boolean b) {
                    return super.computeSize(0, 0, b);
                }
            };
            buttonComposite.setVisible(false);
            return buttonComposite;
        }

        /**
         * Creates selection buttons.
         *
         * @param composite
         *            the parent composite
         * @return the selection buttons composite
         */
        protected Composite createSelectionButtonsAfterMessageArea(Composite composite) {
            final Composite buttonComposite = new Composite(composite, SWT.RIGHT);
            final GridLayout layout = new GridLayout();
            layout.numColumns = 7;
            layout.makeColumnsEqualWidth = false;
            buttonComposite.setLayout(layout);
            buttonComposite.setFont(composite.getFont());

            GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.GRAB_HORIZONTAL);
            data.grabExcessHorizontalSpace = true;
            composite.setData(data);

            new Label(buttonComposite, SWT.LEAD).setText("Show"); //$NON-NLS-1$
            final Combo choices = new Combo(buttonComposite, SWT.READ_ONLY);
            choices.add(FilteringMode.SHOW_ONLY_DIRECT_CHILDREN.getName());
            choices.add(FilteringMode.SHOW_ONLY_UNDISPLAYED_ELEMENTS.getName());
            choices.add(FilteringMode.SHOW_ALL.getName());
            choices.select(0);
            choices.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    switch (choices.getSelectionIndex()) {
                    case 0:
                        updateFilteringMode(FilteringMode.SHOW_ONLY_DIRECT_CHILDREN);
                        break;
                    case 1:
                        updateFilteringMode(FilteringMode.SHOW_ONLY_UNDISPLAYED_ELEMENTS);
                        break;
                    case 2:
                        updateFilteringMode(FilteringMode.SHOW_ALL);
                        break;
                    default:
                        throw new RuntimeException();
                    }
                }
            });

            data = new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.GRAB_HORIZONTAL);
            data.grabExcessHorizontalSpace = true;
            data.horizontalSpan = 2;
            choices.setLayoutData(data);

            data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
            data.grabExcessHorizontalSpace = true;
            addButton(buttonComposite, "Check All", //$NON-NLS-1$
                    DiagramUIPlugin.getPlugin().getBundledImage(DiagramImagesPath.CHECK_ALL_ICON),
                    new SelectionAdapter() {
                        @Override
                        public void widgetSelected(SelectionEvent e) {
                            checkAll();
                        }
                    }).setLayoutData(data);

            addButton(buttonComposite, "Uncheck All", //$NON-NLS-1$
                    DiagramUIPlugin.getPlugin().getBundledImage(DiagramImagesPath.UNCHECK_ALL_ICON),
                    new SelectionAdapter() {
                        @Override
                        public void widgetSelected(SelectionEvent e) {
                            uncheckAll();
                        }
                    }).setLayoutData(data);

            addButton(buttonComposite, "Expand All", //$NON-NLS-1$
                    DiagramUIPlugin.getPlugin().getBundledImage(DiagramImagesPath.EXPAND_ALL_ICON),
                    new SelectionAdapter() {
                        @Override
                        public void widgetSelected(SelectionEvent e) {
                            expandAll();
                        }
                    }).setLayoutData(data);

            addButton(buttonComposite, "Collapse All", //$NON-NLS-1$
                    DiagramUIPlugin.getPlugin().getBundledImage(DiagramImagesPath.COLLAPSE_ALL_ICON),
                    new SelectionAdapter() {
                        @Override
                        public void widgetSelected(SelectionEvent e) {
                            collapseAll();
                        }
                    }).setLayoutData(data);

            return buttonComposite;
        }

        private void expandAll() {
            getTreeViewer().expandAll();
        }

        /**
         * Returns the elements currently checked by user.
         *
         * @return the elements currently checked by user
         */
        public Set<Object> getCheckedElements() {
            return checkedElements;
        }

        /**
         * Returns a Predicate indicating if an object is matching the Regular Expression currently typed by
         * user.
         *
         * @return a Predicate indicating if an object is matching the Regular Expression currently typed by
         *         user
         */
        public Predicate<Object> getRegexpMatchPredicate() {
            return patternMatcher.getMatchPredicate();
        }

        /**
         * Indicates if the given element is matching the currently typed regular expression.
         *
         * @param element
         *            the element to test
         * @return true if the given element is matching the currently typed regular expression, false
         *         otherwise.
         */
        public boolean isMatchingExpregOrHasMatchingExpregDescendantsAllMode(Object element) {
            return isOrHasDescendant(element, getRegexpMatchPredicate());
        }

        /**
         * Indicates if the given element is checked <b>AND</b> is matching the currently typed regular
         * expression.
         *
         * @param element
         *            the element to test
         * @return true if the given element is checked <b>AND</b> is matching the currently typed regular
         *         expression, false otherwise.
         */
        public boolean isMatchingExpregOrHasMatchingExpregDescendantsCheckedMode(Object element) {
            final Predicate<Object> isCheckedElementPredicate = Predicates.in(checkedElements);
            final Predicate<Object> isMatchinExpregPredicate = getRegexpMatchPredicate();
            return isOrHasDescendant(element, Predicates.and(isCheckedElementPredicate, isMatchinExpregPredicate));
        }

        /**
         * Indicates if the given element is checked <b>AND</b> is matching the currently typed regular
         * expression.
         *
         * @param element
         *            the element to test
         * @return true if the given element is checked <b>AND</b> is matching the currently typed regular
         *         expression, false otherwise.
         */
        @SuppressWarnings("unchecked")
        public boolean isMatchingExpregOrHasMatchingExpregDescendantsSubMode(Object element) {
            if (element instanceof EObject) {
                @SuppressWarnings("rawtypes")
                final List subElements = Lists.newArrayList();
                Iterators.addAll(subElements,
                        Iterators.filter(eObject.eAllContents(), Predicates.instanceOf(Element.class)));
                final Predicate<Object> isSubElementPredicate = Predicates.in(subElements);
                final Predicate<Object> isMatchinExpregPredicate = getRegexpMatchPredicate();
                return isOrHasDescendant(element, Predicates.and(isSubElementPredicate, isMatchinExpregPredicate));
            }
            return false;
        }

        /**
         * Indicates if the given element is unchecked <b>AND</b> is matching the currently typed regular
         * expression.
         *
         * @param element
         *            the element to test
         * @return true if the given element is unchecked <b>AND</b> is matching the currently typed regular
         *         expression, false otherwise.
         */
        public boolean isMatchingExpregOrHasMatchingExpregDescendantsUncheckedMode(Object element) {
            final Predicate<Object> isUncheckedElementPredicate = Predicates.not(Predicates.in(checkedElements));
            final Predicate<Object> isMatchinExpregPredicate = getRegexpMatchPredicate();
            return isOrHasDescendant(element,
                    Predicates.and(isUncheckedElementPredicate, isMatchinExpregPredicate));
        }

        /**
         * Indicates if the given element is checked <b>AND</b> is matching the currently typed regular
         * expression.
         *
         * @param element
         *            the element to test
         * @return true if the given element is checked <b>AND</b> is matching the currently typed regular
         *         expression, false otherwise.
         */
        public boolean isMatchingExpregOrHasMatchingExpregDescendantsUnrepresentedMode(Object element) {
            if (element instanceof EObject) {
                @SuppressWarnings("rawtypes")
                final Collection existingElementsOnDiagram = UIServices.INSTANCE.getDisplayedNodes(diagram);
                @SuppressWarnings("unchecked")
                final Predicate<Object> isSubElementPredicate = Predicates
                        .not(Predicates.in(existingElementsOnDiagram));
                final Predicate<Object> isMatchinExpregPredicate = getRegexpMatchPredicate();
                return isOrHasDescendant(element, Predicates.and(isSubElementPredicate, isMatchinExpregPredicate));
            }
            return false;
        }

        /**
         * Indicates if the given element or at least one of its children checks the given predicate.
         *
         * @param element
         *            the element to check
         * @param pred
         *            the predicate to use
         * @return true if the given element or at least one of its children checks the given predicate, false
         *         otherwise
         */
        private boolean isOrHasDescendant(Object element, final Predicate<Object> pred) {
            final boolean matches = pred.apply(element);
            if (matches) {
                return true;
            }
            return Iterables.any(Arrays.asList(contentProvider.getChildren(element)), new Predicate<Object>() {
                public boolean apply(Object input) {
                    return isOrHasDescendant(input, pred);
                }
            });
        }

        /**
         * Refreshes this dialog's viewer.
         */
        public void refresh() {
            getTreeViewer().refresh();
        }

        @Override
        public void setInitialElementSelections(@SuppressWarnings("rawtypes") List selectedElements) {
            @SuppressWarnings("unchecked")
            final List<Object> filteredSeletection = Lists
                    .newArrayList(Iterables.filter(selectedElements, Predicates.not(isGrayed)));
            checkedElements.addAll(filteredSeletection);
            super.setInitialElementSelections(filteredSeletection);
        }

        @Override
        public void setInitialSelections(Object[] selectedElements) {
            setInitialElementSelections(Lists.newArrayList(selectedElements));
        }

        /**
         * Sets the matcher used to determine if a given DDiagramElement is matching the regular expression
         * typed by user.
         *
         * @param patternMatcher
         *            the patternMatcher to set
         */
        public void setPatternMatcher(ModelElementsSelectionDialogPatternMatcher patternMatcher) {
            this.patternMatcher = patternMatcher;
        }

        /**
         * Check / Uncheck elements recursively.
         *
         * @param elements
         *            Elements
         * @param state
         *            State check/uncheck
         */
        private void setRecursiveState(Collection<Object> elements, boolean state) {
            for (final Object element : elements) {
                setRecursiveState(element, state);
            }
        }

        private void setRecursiveState(final Object element, final boolean state) {
            getTreeViewer().setChecked(element, state);
            if (!isGrayed.apply(element)) {
                if (state) {
                    checkedElements.add(element);
                } else {
                    checkedElements.remove(element);
                }
            }
            for (final Object child : contentProvider.getChildren(element)) {
                setRecursiveState(child, state);
            }
        }

        /**
         * Uncheck all elements in tree viewer.
         */
        @SuppressWarnings("unchecked")
        private void uncheckAll() {
            @SuppressWarnings("rawtypes")
            final Collection roots = (Collection) getTreeViewer().getInput();
            setRecursiveState(roots, false);
        }

        /**
         * Updates the treeViewer after a change in the filteringMode or the typed regular expression.
         *
         * @param filteringMode
         *            the new filtering mode
         */
        @SuppressWarnings("deprecation")
        public void updateFilteringMode(FilteringMode filteringMode) {
            mode = filteringMode;
            refresh();
            // We expand the tree so that all elements matching the regular
            // expression (i.e. all visible leafs) are correctly shown
            getTreeViewer().expandToLevel(2);
            getTreeViewer().setAllChecked(false);
            for (final Object element : checkedElements) {
                getTreeViewer().setChecked(element, true);
            }
        }
    }

    /**
     * Represents the various kinds of filtering supported by the tree viewer.
     *
     * @author pcdavid
     */
    protected enum FilteringMode {
        /**
         * Filtering mode in which all elements are considered.
         */
        SHOW_ALL("all elements"), //$NON-NLS-1$
        /**
         * Filtering mode in which all elements are considered.
         */
        SHOW_ONLY_DIRECT_CHILDREN("only direct children"), //$NON-NLS-1$
        /**
         * Filtering mode in which only elements which are not already displayed on diagram are considered.
         */
        SHOW_ONLY_UNDISPLAYED_ELEMENTS("only undisplayed elements"); //$NON-NLS-1$

        private final String name;

        private FilteringMode(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    private class ModeFilter extends ViewerFilter {

        @Override
        public boolean select(Viewer viewer, Object parentElement, Object element) {
            boolean show = true;
            // Step 1: only showing check/unchecked element if required
            switch (mode) {
            case SHOW_ALL:
                show = dialog.isMatchingExpregOrHasMatchingExpregDescendantsAllMode(element);
                break;
            case SHOW_ONLY_DIRECT_CHILDREN:
                show = dialog.isMatchingExpregOrHasMatchingExpregDescendantsSubMode(element);
                break;
            case SHOW_ONLY_UNDISPLAYED_ELEMENTS:
                show = dialog.isMatchingExpregOrHasMatchingExpregDescendantsUnrepresentedMode(element);
                break;
            default:
                show = true;
                break;
            }

            return show;
        }
    }

    private class SelectionDialogLabelProvider extends SiriusCommonLabelProvider {

        /**
         * {@inheritDoc}
         */
        @Override
        public Color getBackground(Object element) {
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Color getForeground(final Object element) {

            Color foreground = null;
            if (isGrayed.apply(element)) {
                foreground = VisualBindingManager.getDefault().getColorFromName("light_gray"); //$NON-NLS-1$
            }
            return foreground;
        }
    }

    final Map<EObject, Boolean> isOrHasDescendantCache = UmlDesignerSessionManagerListener.getDescendantCache();

    private static final Function<Object, Void> DO_NOTHING = new Function<Object, Void>() {
        public Void apply(Object from) {
            return null;
        }
    };

    /**
     * The internal dialog used by this dialog.
     */
    protected CustomTreeSelectionDialog dialog;

    /**
     * The eObject associated to this dialog.
     */
    protected EObject eObject;

    /**
     * The filtering mode currently associated to the tree viewer. Can be :
     * <ul>
     * <li>All elements</li>
     * <li>Only direct children</li>
     * <li>Only undisplayed elements</li>
     * </ul>
     */
    protected FilteringMode mode = FilteringMode.SHOW_ONLY_DIRECT_CHILDREN;

    private final String title;

    private final String message;

    // Grayed elements will not be selectable.
    private Predicate<Object> isGrayed = Predicates.alwaysFalse();

    private Function<Object, Void> selectedAction = DO_NOTHING;

    private ModelFilteredTreeContentProvider contentProvider;

    private DDiagram diagram;

    /**
     * Constructor.
     *
     * @param title
     *            the title of the dialog window.
     * @param message
     *            the message for the dialog.
     */
    public ModelElementsSelectionDialog(String title, String message) {
        this.title = title;
        this.message = message;
    }

    /**
     * Updates the status of the elements according to the user request.
     *
     * @param selectedBefore
     *            all (and only) the elements in the diagram which were actually pinned before the action.
     * @param selectedAfter
     *            all (and only) the elements in the diagram which should be pinned as requested by the user.
     */
    protected void applyRequestedChanges(Set<Object> selectedBefore, Set<Object> selectedAfter) {
        for (final Object dde : Sets.difference(selectedAfter, selectedBefore)) {
            selectedAction.apply(dde);
        }
    }

    /**
     * Asks the user to edit the set of elements which should be selected/match the criterion being edited.
     *
     * @param parent
     *            the parent shell to use if user interaction requires opening new windows.
     * @param initialSelection
     *            the set of elements to display as checked on dialog opening.
     * @return the new set of all the elements in the diagram which were selected by the user, or
     *         <code>Options.newNone()</code> if the user canceled the operation.
     */
    protected Option<Set<Object>> askUserForNewSelection(Shell parent, Set<Object> initialSelection) {
        setupDialog(parent, initialSelection);
        final int result = dialog.open();
        if (result == Window.OK) {
            final Set<Object> selectedAfter = getElementsSelectedAfter();
            return Options.newSome(selectedAfter);
        }
        return Options.newNone();
    }

    /**
     * Returns the adapter factory used by this viewer.
     *
     * @return The adapter factory used by this viewer.
     */
    private AdapterFactory getAdapterFactory() {
        final List<AdapterFactory> factories = new ArrayList<AdapterFactory>();
        factories.add(new UMLItemProviderAdapterFactory());
        factories.add(new ResourceItemProviderAdapterFactory());
        factories.add(new EcoreItemProviderAdapterFactory());
        factories.add(new ReflectiveItemProviderAdapterFactory());
        return new ComposedAdapterFactory(factories);
    }

    /**
     * @param parent
     * @return
     */
    private Set<Object> getAllChildren(Object parent) {
        final Set<Object> result = new HashSet<Object>();
        final Object[] children = contentProvider.getChildren(parent);
        for (final Object element : children) {
            result.add(element);
            result.addAll(getAllChildren(element));
        }
        return result;
    }

    /**
     * Return all selected elements of the diagram that are display in the tree.
     *
     * @return All selected elements of the diagram that are display in the tree.
     */
    protected Set<Object> getAllSelectedElements() {
        final Set<Object> treeElements = getAllChildren(eObject);
        return Sets.newHashSet(Iterators.filter(treeElements.iterator(), Predicates.not(isGrayed)));
    }

    /**
     * Get displayable elements according to diagram kind.
     *
     * @param selectedContainer
     *            Selected container
     * @param diagram
     *            Diagram
     * @return Predicate to select only displayable element according to diagram kind
     */
    private Predicate<Object> getDisplayablePredicate() {
        return new Predicate<Object>() {
            public boolean apply(Object input) {
                if (input instanceof Element) {
                    return isOrHasDescendant((Element) input, isValidForDiagram());
                }
                return false;
            }
        };
    }

    /**
     * Returns the elements selected when the dialog is about to close.
     *
     * @return the elements selected when the dialog is about to close
     */
    protected Set<Object> getElementsSelectedAfter() {
        final Set<Object> selectedElements = Sets.newHashSet();
        for (final Object obj : dialog.checkedElements) {
            selectedElements.add(obj);
        }
        return selectedElements;
    }

    private Predicate<Object> getValidsForClassDiagram() {
        final Predicate<Object> validForClassDiagram = new Predicate<Object>() {

            public boolean apply(Object input) {
                return input instanceof Package || input instanceof Interface || input instanceof DataType
                        || "Class".equals(((EObject) input).eClass().getName()) //$NON-NLS-1$
                        || "Component".equals(((EObject) input).eClass().getName()); //$NON-NLS-1$
            }
        };
        return validForClassDiagram;
    }

    private Predicate<Object> getValidsForComponentDiagram() {
        final Predicate<Object> validForComponentDiagram = new Predicate<Object>() {

            public boolean apply(Object input) {
                return input instanceof Package || input instanceof Interface
                        || "Class".equals(((EObject) input).eClass().getName()) //$NON-NLS-1$
                        || "Component".equals(((EObject) input).eClass().getName()); //$NON-NLS-1$
            }
        };
        return validForComponentDiagram;
    }

    private Predicate<Object> getValidsForCompositeDiagram() {
        final Predicate<Object> validForCompositeDiagram = new Predicate<Object>() {

            public boolean apply(Object input) {
                if (input instanceof StructuredClassifier) {
                    return !(input instanceof Interaction || input instanceof StateMachine
                            || input instanceof Activity);
                }
                return input instanceof Package || input instanceof Interface
                        || "Port".equals(((EObject) input).eClass().getName()) //$NON-NLS-1$
                        || "Property".equals(((EObject) input).eClass().getName()); //$NON-NLS-1$

            }
        };
        return validForCompositeDiagram;
    }

    private Predicate<Object> getValidsForCompositeStructureDiagram(final EObject container) {
        final Predicate<Object> validForCompositeDiagram = new Predicate<Object>() {

            public boolean apply(Object input) {
                return "Property".equals(((EObject) input).eClass().getName()) //$NON-NLS-1$
                        && container.equals(((EObject) input).eContainer());

            }
        };
        return validForCompositeDiagram;
    }

    private Predicate<Object> getValidsForDeploymentDiagram() {
        final Predicate<Object> validForDeploymentDiagram = new Predicate<Object>() {

            public boolean apply(Object input) {
                return input instanceof Package && !(input instanceof Profile)
                        || input instanceof ExecutionEnvironment || input instanceof Node
                        || input instanceof Artifact || input instanceof Device || input instanceof Component;
            }
        };
        return validForDeploymentDiagram;
    }

    /**
     * Get valid elements for a diagram.
     *
     * @param cur
     *            Element
     * @return List of valid elements for the current representation
     */
    private Predicate<Object> getValidsForPackageDiagram() {
        final Predicate<Object> validForPackageDiagram = new Predicate<Object>() {

            public boolean apply(Object input) {
                return input instanceof Package;
            }
        };
        return validForPackageDiagram;
    }

    private Predicate<Object> getValidsForUseCaseDiagram() {
        final Predicate<Object> validForUseCaseDiagram = new Predicate<Object>() {

            public boolean apply(Object input) {
                return input instanceof Package || input instanceof Class || input instanceof Component
                        || input instanceof Artifact || input instanceof DataType || input instanceof Interface
                        || input instanceof Collaboration || input instanceof UseCase;
            }
        };
        return validForUseCaseDiagram;
    }

    /**
     * Init the content provider.
     *
     * @param includeNodeLabel
     *            include node label (if there are on border) in the tree content
     */
    protected void initContentProvider() {
        final AdapterFactory adapterFactory = getAdapterFactory();
        contentProvider = new ModelFilteredTreeContentProvider(adapterFactory, getDisplayablePredicate());
    }

    /**
     * Indicates if the given element or at least one of its children checks the given predicate.
     *
     * @param input
     *            the element to check
     * @param predicate
     *            the predicate to use
     * @return true if the given element or at least one of its children checks the given predicate, false
     *         otherwise
     */
    @SuppressWarnings("boxing")
    private boolean isOrHasDescendant(Element element, final Predicate<Object> predicate) {
        if (isOrHasDescendantCache.containsKey(element)) {
            return isOrHasDescendantCache.get(element);
        }

        if (predicate.apply(element)) {
            isOrHasDescendantCache.put(element, true);
            return true;
        }

        final List<Element> elements = element.getOwnedElements();
        for (final Element ownedElement : elements) {
            if (predicate.apply(ownedElement)) {
                isOrHasDescendantCache.put(element, true);
                return true;
            }
            isOrHasDescendantCache.put(element, false);
        }

        return false;
    }

    /**
     * Get valid elements for a diagram.
     *
     * @param element
     *            Element
     * @param containerView
     *            Container view
     * @return List of valid elements for the current representation
     */
    private Predicate<Object> isValidForDiagram() {
        Predicate<Object> results = Predicates.alwaysTrue();
        if (diagram instanceof DSemanticDiagram) {
            final DiagramDescription description = ((DSemanticDiagram) diagram).getDescription();

            if ("Class Diagram".equals(description.getName()) //$NON-NLS-1$
                    || "Profile Diagram".equals(description.getName())) { //$NON-NLS-1$
                results = getValidsForClassDiagram();
            } else if ("Component Diagram".equals(description.getName())) { //$NON-NLS-1$
                results = getValidsForComponentDiagram();
            } else if ("Composite Diagram".equals(description.getName())) { //$NON-NLS-1$
                results = getValidsForCompositeDiagram();
            } else if ("Composite Structure Diagram".equals(description.getName())) { //$NON-NLS-1$
                results = getValidsForCompositeStructureDiagram(((DSemanticDiagram) diagram).getTarget());
            } else if ("Deployment Diagram".equals(description.getName())) { //$NON-NLS-1$
                results = getValidsForDeploymentDiagram();
            } else if ("Package Hierarchy".equals(description.getName())) { //$NON-NLS-1$
                results = getValidsForPackageDiagram();
            } else if ("Use Case Diagram".equals(description.getName())) { //$NON-NLS-1$
                results = getValidsForUseCaseDiagram();
            }
        }

        return results;
    }

    /**
     * Asks the end-user for a list of elements to select/de-select, and applies the corresponding changes.
     *
     * @param parent
     *            the shell to use to interact with the user, if required.
     * @param selectedContainer
     *            Selected semantic container
     * @param ddiagram
     *            the diagram whose elements to edit.
     * @param includeNodeLabel
     *            include node label (if there are on border) in the tree content
     * @return <code>true</code> if the operation was correctly executed, <code>false</code> if it was
     *         canceled by the user.
     */
    public List<Object> open(Shell parent, EObject selectedContainer, DDiagram ddiagram, boolean includeNodeLabel) {
        final List<Object> result = Lists.newArrayList();
        eObject = selectedContainer;
        diagram = ddiagram;

        initContentProvider();

        final Set<Object> allSelectedElements = Collections.unmodifiableSet(getAllSelectedElements());
        final Option<Set<Object>> response = askUserForNewSelection(parent, allSelectedElements);
        if (response.some()) {
            final Set<Object> selectedAfter = response.get();
            result.addAll(selectedAfter);
        }
        eObject = null;
        dialog = null;
        contentProvider = null;
        return result;
    }

    /**
     * Sets the predicate to use to detect which elements of the diagram are selected, in the sense of the
     * criterion to be edited.
     *
     * @param isGrayedPredicate
     *            the predicate to used to detect selected elements of the diagram.
     */
    @SuppressWarnings("unchecked")
    public void setGrayedPredicate(Predicate<EObject> isGrayedPredicate) {
        isGrayed = Predicates.and((Predicate<? super Object>) (isGrayedPredicate != null ? isGrayedPredicate
                : Predicates.alwaysFalse()), isValidForDiagram());
    }

    /**
     * Sets the operation to be applied on elements which are newly selected by the user.
     *
     * @param selectedAction
     *            the operation to apply to newly selected elements.
     */
    public void setSelectedAction(Function<Object, Void> selectedAction) {
        this.selectedAction = selectedAction;
    }

    /**
     * Create an configure a selection dialog which allows the user to select a sub-set of the elements in the
     * diagram.
     *
     * @param parent
     *            the parent shell.
     * @param initialSelection
     *            the set of elements to display as checked on dialog opening.
     */
    protected void setupDialog(Shell parent, Set<Object> initialSelection) {
        dialog = new CustomTreeSelectionDialog(parent, new SelectionDialogLabelProvider(), contentProvider);
        dialog.setTitle(title);

        String msg = message;
        if (!Predicates.alwaysFalse().equals(isGrayed)) {
            final StringBuilder sb = new StringBuilder(message);
            sb.append("\n"); //$NON-NLS-1$
            sb.append("The wizard will have no effect on grayed elements."); //$NON-NLS-1$
            msg = sb.toString();
        }
        dialog.setMessage(msg);
        final Collection<Element> roots = ElementServices.INSTANCE.getAllRootsInSession(eObject);

        dialog.setInput(roots);
        dialog.addFilter(new ModeFilter());
        dialog.setInitialElementSelections(Lists.newArrayList(initialSelection));
    }
}