| /***************************************************************************** |
| * Copyright (c) 2013, 2017 CEA LIST and others. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * CEA LIST - Initial API and implementation |
| * Christian W. Damus (CEA) - bug 429242 |
| * Christian W. Damus (CEA) - bug 433830 |
| * |
| *****************************************************************************/ |
| package org.eclipse.papyrus.cdo.internal.ui.wizards; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Queue; |
| import java.util.Set; |
| |
| import org.eclipse.emf.common.util.Diagnostic; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.resource.DeviceResourceManager; |
| import org.eclipse.jface.resource.ResourceManager; |
| import org.eclipse.jface.viewers.CheckStateChangedEvent; |
| import org.eclipse.jface.viewers.CheckboxTreeViewer; |
| import org.eclipse.jface.viewers.DecorationOverlayIcon; |
| import org.eclipse.jface.viewers.ICheckStateListener; |
| import org.eclipse.jface.viewers.ICheckable; |
| import org.eclipse.jface.viewers.IDecoration; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.papyrus.cdo.core.importer.IModelTransferConfiguration; |
| import org.eclipse.papyrus.cdo.core.importer.IModelTransferNode; |
| import org.eclipse.papyrus.cdo.internal.ui.l10n.Messages; |
| import org.eclipse.papyrus.cdo.internal.ui.providers.ModelImportNodeLabelProvider; |
| import org.eclipse.papyrus.cdo.ui.Activator; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Text; |
| |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Sets; |
| import com.google.common.eventbus.EventBus; |
| import com.google.common.eventbus.Subscribe; |
| |
| /** |
| * This is the ModelReferencesPage type. Enjoy. |
| */ |
| public class ModelReferencesPage extends ModelImportWizardPage { |
| |
| private static final String IMPORT_MESSAGE = Messages.ModelReferencesPage_0; |
| |
| private static final String EXPORT_MESSAGE = Messages.ModelReferencesPage_1; |
| |
| private static final Object[] NO_OBJECTS = {}; |
| |
| private final boolean isImport; |
| |
| private IModelTransferConfiguration importConfig; |
| |
| private TreeViewer modelsTree; |
| |
| private Text pathText; |
| |
| private Button stripSashModelContent; |
| |
| public ModelReferencesPage(EventBus bus, boolean isImport) { |
| super("references", Messages.ModelReferencesPage_3, null, bus, isImport ? IMPORT_MESSAGE : EXPORT_MESSAGE); //$NON-NLS-1$ |
| |
| this.isImport = isImport; |
| } |
| |
| @Override |
| public void createControl(Composite parent) { |
| initializeDialogUnits(parent); |
| |
| Composite result = new Composite(parent, SWT.NONE); |
| result.setLayout(new GridLayout(1, false)); |
| |
| new Label(result, SWT.NONE).setText(Messages.ModelReferencesPage_4); |
| |
| ModelImportContentProvider contents = new ModelImportContentProvider(); |
| modelsTree = new CheckboxTreeViewer(result); |
| modelsTree.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create()); |
| modelsTree.setAutoExpandLevel(2); |
| modelsTree.setContentProvider(contents); |
| modelsTree.setLabelProvider(new TreeNodeLabelProvider()); |
| |
| new Label(result, SWT.NONE).setText(Messages.ModelReferencesPage_5); |
| pathText = new Text(result, SWT.BORDER | SWT.MULTI | SWT.WRAP | SWT.READ_ONLY); |
| pathText.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).hint(SWT.DEFAULT, convertHeightInCharsToPixels(3)).create()); |
| |
| if (isImport) { |
| stripSashModelContent = new Button(result, SWT.CHECK); |
| stripSashModelContent.setText(Messages.ModelReferencesPage_2); |
| stripSashModelContent.setToolTipText(Messages.ModelReferencesPage_6); |
| |
| stripSashModelContent.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| if (getConfiguration() != null) { |
| getConfiguration().setStripSashModelContent(stripSashModelContent.getSelection()); |
| } |
| } |
| }); |
| } |
| |
| modelsTree.addSelectionChangedListener(new ISelectionChangedListener() { |
| |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| IStructuredSelection sel = (IStructuredSelection) event.getSelection(); |
| if (!sel.isEmpty()) { |
| selected(sel.getFirstElement()); |
| } else { |
| selected(null); |
| } |
| } |
| }); |
| |
| ((ICheckable) modelsTree).addCheckStateListener(contents); |
| ((ICheckable) modelsTree).addCheckStateListener(new ICheckStateListener() { |
| |
| @Override |
| public void checkStateChanged(CheckStateChangedEvent event) { |
| Display.getCurrent().asyncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| validatePage(); |
| } |
| }); |
| } |
| }); |
| |
| setControl(result); |
| |
| validatePage(); |
| } |
| |
| public IModelTransferConfiguration getConfiguration() { |
| return importConfig; |
| } |
| |
| @Subscribe |
| public void setConfiguration(IModelTransferConfiguration configuration) { |
| if (configuration != this.importConfig) { |
| if (this.importConfig != null) { |
| this.importConfig.dispose(); |
| } |
| |
| this.importConfig = configuration; |
| } |
| |
| modelsTree.setInput(configuration); |
| |
| if (configuration != null) { |
| // initialize the checkboxes |
| initializeCheckedNodes(); |
| |
| // determine enablement of strip-sash-model option |
| if (stripSashModelContent != null) { |
| stripSashModelContent.setSelection(true); |
| stripSashModelContent.setEnabled(configuration.hasSashModelContent()); |
| |
| this.importConfig.setStripSashModelContent(true); |
| } |
| } else if (stripSashModelContent != null) { |
| stripSashModelContent.setSelection(true); |
| stripSashModelContent.setEnabled(false); |
| } |
| |
| validatePage(); |
| } |
| |
| @Override |
| public void dispose() { |
| // I didn't create it, so don't dispose it |
| importConfig = null; |
| |
| super.dispose(); |
| } |
| |
| private void initializeCheckedNodes() { |
| final Collection<IModelTransferNode> initialSet = importConfig.getModelsToTransfer(); |
| final ITreeContentProvider contents = (ITreeContentProvider) modelsTree.getContentProvider(); |
| final ICheckable checkable = (ICheckable) modelsTree; |
| |
| final Set<IModelTransferNode> visited = Sets.newHashSet(); |
| final Queue<Object> queue = new java.util.ArrayDeque<Object>(Arrays.asList(contents.getElements(importConfig))); |
| |
| for (Object next = queue.poll(); next != null; next = queue.poll()) { |
| ITreeNode parent = (ITreeNode) next; |
| |
| // we must check a parent if the user initially selected it on opening the wizard |
| // or we are importing and it is a dependent of a checked node, |
| // or we are exporting and it is a dependency of a checked node, |
| // or it is a model sub-unit (required dependency) of a checked node |
| boolean mustCheck = initialSet.contains(parent.getElement()); |
| if (mustCheck) { |
| checkable.setChecked(next, true); |
| } |
| |
| if (visited.add(parent.getElement())) { |
| // recurse into the children |
| for (Object child : contents.getChildren(next)) { |
| ITreeNode treeNode = (ITreeNode) child; |
| queue.add(treeNode); |
| |
| // we must check a node if either the user initially selected it on opening the wizard, |
| // or we are importing and it is a dependent of a checked node, |
| // or we are exporting and it is a dependency of a checked node, |
| // or it is a model sub-unit (required dependency) of a checked node |
| mustCheck = initialSet.contains(treeNode.getElement()) // |
| || (isImport ? treeNode.isDependent() : treeNode.isDependency()) // |
| || (checkable.getChecked(parent) && parent.getElement().isModelSubUnit(treeNode.getElement())); |
| |
| if (mustCheck) { |
| checkable.setChecked(child, true); |
| importConfig.addModelToTransfer(treeNode.getElement().getPrimaryResourceURI()); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| void selected(Object treeNode) { |
| if (treeNode == null) { |
| pathText.setText(""); //$NON-NLS-1$ |
| } else { |
| IModelTransferNode node = ((ITreeNode) treeNode).getElement(); |
| |
| pathText.setText(node.getName()); |
| } |
| } |
| |
| @Override |
| protected Diagnostic doValidatePage() { |
| Diagnostic result = Diagnostic.CANCEL_INSTANCE; |
| |
| if (importConfig != null) { |
| if (importConfig.getModelsToTransfer().isEmpty()) { |
| result = report(Diagnostic.CANCEL, NLS.bind(Messages.ModelReferencesPage_7, isImport ? Messages.ModelReferencesPage_8 : Messages.ModelReferencesPage_9)); |
| } else { |
| result = importConfig.validate(); |
| } |
| } |
| |
| return result; |
| } |
| |
| // |
| // Nested types |
| // |
| |
| static interface ITreeNode { |
| |
| IModelTransferNode getElement(); |
| |
| boolean isDependent(); |
| |
| boolean isDependency(); |
| } |
| |
| private class ModelImportContentProvider implements ITreeContentProvider, ICheckStateListener { |
| |
| private final Multimap<IModelTransferNode, TreeNode> nodes = HashMultimap.create(); |
| |
| private IModelTransferConfiguration config; |
| |
| private Object[] elements; |
| |
| private Viewer viewer; |
| |
| @Override |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| nodes.clear(); |
| elements = null; |
| |
| config = (IModelTransferConfiguration) newInput; |
| |
| this.viewer = viewer; |
| } |
| |
| @Override |
| public Object[] getElements(Object inputElement) { |
| Object[] result = elements; |
| |
| if ((inputElement != config) || (result == null)) { |
| IModelTransferConfiguration inputConfig = (IModelTransferConfiguration) inputElement; |
| List<TreeNode> nodes = Lists.newArrayListWithCapacity(inputConfig.getModelsToTransfer().size()); |
| |
| for (IModelTransferNode next : inputConfig.getModelsToTransfer()) { |
| nodes.add(new TreeNode(next)); |
| } |
| |
| result = nodes.toArray(); |
| |
| if (inputConfig == config) { |
| // cache the result |
| elements = result; |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public boolean hasChildren(Object element) { |
| IModelTransferNode importNode = ((TreeNode) element).getElement(); |
| |
| return !(importNode.getDependencies().isEmpty() && importNode.getDependents().isEmpty()); |
| } |
| |
| @Override |
| public Object getParent(Object element) { |
| return ((TreeNode) element).getParent(); |
| } |
| |
| @Override |
| public Object[] getChildren(Object parentElement) { |
| return ((TreeNode) parentElement).getChildren(); |
| } |
| |
| @Override |
| public void dispose() { |
| nodes.clear(); |
| elements = null; |
| } |
| |
| @Override |
| public void checkStateChanged(CheckStateChangedEvent event) { |
| ITreeNode node = (ITreeNode) event.getElement(); |
| IModelTransferNode model = node.getElement(); |
| |
| // apply the check state to the model |
| if (event.getChecked()) { |
| config.addModelToTransfer(model.getPrimaryResourceURI()); |
| } else { |
| config.removeModelToTransfer(model); |
| } |
| |
| // propagate the check state to other occurrences of the same model |
| for (ITreeNode next : nodes.get(model)) { |
| event.getCheckable().setChecked(next, event.getChecked()); |
| } |
| } |
| |
| /** |
| * Need a tree-node class because {@link IModelTransferNode}s are repeated |
| * in the tree. |
| */ |
| private class TreeNode implements ITreeNode { |
| |
| private final IModelTransferNode element; |
| |
| private final TreeNode parent; |
| |
| private List<TreeNode> children; |
| |
| private final boolean dependent; |
| |
| TreeNode(IModelTransferNode element) { |
| this(null, element, false); |
| } |
| |
| TreeNode(TreeNode parent, IModelTransferNode element, boolean dependent) { |
| this.parent = parent; |
| this.element = element; |
| this.dependent = dependent; |
| |
| nodes.put(element, this); |
| } |
| |
| @Override |
| public IModelTransferNode getElement() { |
| return element; |
| } |
| |
| @Override |
| public boolean isDependent() { |
| return (getParent() != null) && dependent; |
| } |
| |
| @Override |
| public boolean isDependency() { |
| return (getParent() != null) && !dependent; |
| } |
| |
| Object getParent() { |
| return parent; |
| } |
| |
| Object[] getChildren() { |
| if (children == null) { |
| createChildren(); |
| } |
| |
| return (children == null) ? NO_OBJECTS : children.toArray(); |
| } |
| |
| void createChildren() { |
| Collection<IModelTransferNode> dependencies = element.getDependencies(); |
| Collection<IModelTransferNode> dependents = element.getDependents(); |
| |
| if (!dependencies.isEmpty() || !dependents.isEmpty()) { |
| children = Lists.newArrayListWithCapacity(dependencies.size() + dependents.size()); |
| |
| // the recommendation for importing dependencies vs. dependents is |
| // reversed for export as for import. We suggest to export dependencies |
| // of an exported model (those that it references) and import dependents |
| // of an imported model (those that reference it) |
| for (IModelTransferNode next : isImport ? dependents : dependencies) { |
| children.add(new TreeNode(this, next, isImport)); |
| } |
| |
| for (IModelTransferNode next : isImport ? dependencies : dependents) { |
| // don't show a model as both a dependent and a |
| // dependency if it both references and is |
| // referenced by the other |
| if (isImport ? !dependents.contains(next) : !dependencies.contains(next)) { |
| children.add(new TreeNode(this, next, false)); |
| } |
| } |
| |
| // initialize check state of new children from configuration |
| Display.getCurrent().asyncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| if (config != null) { |
| Collection<IModelTransferNode> imported = config.getModelsToTransfer(); |
| ICheckable checkable = (ICheckable) viewer; |
| for (ITreeNode next : children) { |
| if (imported.contains(next.getElement())) { |
| checkable.setChecked(next, true); |
| } |
| } |
| } |
| } |
| }); |
| } |
| } |
| } |
| } |
| |
| private static class TreeNodeLabelProvider extends ModelImportNodeLabelProvider { |
| |
| private final ResourceManager images = new DeviceResourceManager(Display.getCurrent()); |
| |
| @Override |
| public Image getImage(Object element) { |
| ITreeNode treeNode = (ITreeNode) element; |
| Image result = super.getImage(element); |
| |
| if ((result != null) && treeNode.isDependent()) { |
| // decorate it |
| result = (Image) images.get(new DecorationOverlayIcon(result, Activator.getIcon(Activator.ICON_DEPENDENT_OVERLAY16), IDecoration.TOP_RIGHT)); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public void dispose() { |
| images.dispose(); |
| } |
| |
| @Override |
| protected IModelTransferNode getModelImportNode(Object element) { |
| return ((ITreeNode) element).getElement(); |
| } |
| } |
| } |