blob: 9bf6b097217247df18fe5f8bc8f6f91b16ed5e3e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 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.eclipse.sirius.ui.editor.internal.graphicalcomponents;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IFile;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.workspace.util.WorkspaceSynchronizer;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.sirius.business.api.dialect.DialectManager;
import org.eclipse.sirius.business.api.session.Session;
import org.eclipse.sirius.business.api.session.SessionListener;
import org.eclipse.sirius.business.api.session.SessionManager;
import org.eclipse.sirius.business.api.session.SessionManagerListener;
import org.eclipse.sirius.common.ui.tools.api.util.SWTUtil;
import org.eclipse.sirius.ui.editor.Messages;
import org.eclipse.sirius.ui.tools.api.views.common.item.ProjectDependenciesItem;
import org.eclipse.sirius.ui.tools.internal.actions.analysis.AddModelDependencyAction;
import org.eclipse.sirius.ui.tools.internal.actions.analysis.RemoveSemanticResourceAction;
import org.eclipse.sirius.ui.tools.internal.views.common.item.NoDynamicProjectDependencies;
import org.eclipse.sirius.ui.tools.internal.views.common.item.ViewpointsFolderItemImpl;
import org.eclipse.sirius.ui.tools.internal.views.common.navigator.ManageSessionActionProvider;
import org.eclipse.sirius.ui.tools.internal.views.common.navigator.SiriusCommonContentProvider;
import org.eclipse.sirius.ui.tools.internal.views.common.navigator.SiriusCommonLabelProvider;
import org.eclipse.sirius.ui.tools.internal.views.common.navigator.sorter.CommonItemSorter;
import org.eclipse.sirius.ui.tools.internal.views.modelexplorer.DeleteActionHandler;
import org.eclipse.sirius.ui.tools.internal.views.modelexplorer.RenameActionHandler;
import org.eclipse.sirius.viewpoint.DAnalysisSessionEObject;
import org.eclipse.sirius.viewpoint.DRepresentation;
import org.eclipse.sirius.viewpoint.DRepresentationDescriptor;
import org.eclipse.sirius.viewpoint.DSemanticDecorator;
import org.eclipse.sirius.viewpoint.description.Viewpoint;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.NewWizardAction;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.forms.widgets.FormToolkit;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* This graphical component provides a {@link TreeViewer} showing all semantic
* models loaded in the given session. It also provides button to add or remove
* external semantic model dependency to the session. This component also reacts
* to external session changes regarding loading semantic models to update the
* viewer list.
*
* @author <a href="mailto:pierre.guilet@obeo.fr">Pierre Guilet</a>
*
*/
public class GraphicalSemanticModelsHandler implements SessionListener, SessionManagerListener {
/**
* Session from which semantic models are handled.
*/
private Session session;
/**
* The Form Toolkit to use to create & configure the controls.
*/
private FormToolkit toolkit;
/**
* Sirius content provider providing expandable models loaded by the session
* and representations.
*/
private SiriusCommonContentProvider siriusCommonContentModelProvider;
/**
* The button used to remove external semantic model reference and
* representations from the session.
*/
private Button removeSemanticModelOrRepresentationButton;
/**
* The viewer showing all semantic models loaded from the given session.
*/
private TreeViewer treeViewer;
/**
* The {@link MenuManager} for this component.
*/
private MenuManager menuManager;
/**
* The component providing actions available for selected semantic and
* representations element in its viewer.
*/
private ManageSessionActionProvider manageSessionActionProvider;
/**
* The Tree showing semantic models and representations.
*/
private FilteredTree modelTree;
/**
* Handler allowing to delete a representation.
*/
private Action deleteActionHandler;
/**
* Handler allowing to rename a representation.
*/
private Action renameActionHandler;
/**
* Initialize the component with the given session.
*
* @param theSession
* the session used by the component to handle semantic models
* lifecycle.
* @param toolkit
* the toolkit to use to create & configure the controls.
*/
public GraphicalSemanticModelsHandler(Session theSession, FormToolkit toolkit) {
this.session = theSession;
this.toolkit = toolkit;
}
/**
* Return the {@link TreeViewer} showing all semantic models.
*
* @return the {@link TreeViewer} showing all semantic models.
*/
public TreeViewer getTreeViewer() {
return treeViewer;
}
/**
* Launch a representation deletion or renaming if the right key is used and
* the selected element is a representation.
*
* @param event
*/
private void handleKeyReleased(KeyEvent event) {
if (event.stateMask != 0)
return;
int key = event.keyCode;
if (key == SWT.DEL) {
if (deleteActionHandler.isEnabled()) {
deleteActionHandler.run();
}
} else if (key == SWT.F2) {
if (renameActionHandler.isEnabled()) {
renameActionHandler.run();
}
}
}
/**
* Create the graphic composites and the {@link TreeViewer} and initialize
* it with semantic models from the session.
*
* @param parentComposite
* the composite to be attached to.
*/
public void createControl(Composite parentComposite) {
treeViewer = createModelExplorerNavigator(parentComposite);
createModelExplorerButton(parentComposite, treeViewer);
session.addListener(this);
SessionManager.INSTANCE.addSessionsListener(this);
siriusCommonContentModelProvider.addRefreshViewerTrigger(session);
treeViewer.getTree().getHorizontalBar().setSelection(0);
}
/**
* Create ModelExplorer control buttons.
*
* @param parent
* the model Explorer Composite
*/
private void createModelExplorerButton(Composite parent, final TreeViewer theTreeViewer) {
Composite subComposite = toolkit.createComposite(parent, SWT.NONE);
subComposite.setLayout(GridLayoutFactory.fillDefaults().margins(0, 0).create());
subComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
Composite buttonsComposite = toolkit.createComposite(subComposite, SWT.NONE);
FillLayout buttonsLayout = new FillLayout(SWT.BEGINNING);
buttonsLayout.spacing = 5;
buttonsComposite.setLayout(buttonsLayout);
addButton(buttonsComposite, Messages.UI_SessionEditor_new_semantic_model_action_label, () -> {
new NewWizardAction(PlatformUI.getWorkbench().getActiveWorkbenchWindow()).run();
});
addButton(buttonsComposite, Messages.UI_SessionEditor_models_button_newSemanticModel, () -> {
AddModelDependencyAction addModelDependencyAction = new AddModelDependencyAction(session);
addModelDependencyAction.run();
});
removeSemanticModelOrRepresentationButton = addButton(buttonsComposite, Messages.UI_SessionEditor_models_button_removeSemanticModel, () -> {
if (theTreeViewer != null) {
final IStructuredSelection selection = (IStructuredSelection) theTreeViewer.getSelection();
Collection<?> selectedObjects = selection.toList();
if (!selectedObjects.isEmpty()) {
RemoveSemanticResourceAction removeSemanticResourceAction = new RemoveSemanticResourceAction(getSemanticResources(selectedObjects), session);
removeSemanticResourceAction.run();
}
theTreeViewer.refresh();
}
});
removeSemanticModelOrRepresentationButton.setEnabled(false);
deleteActionHandler.setEnabled(false);
renameActionHandler.setEnabled(false);
}
/**
* Return true if the resources can be deleted. I.e it is not a controlled
* resource and no representation are based on a semantic element of the
* resources.
*
* @param toRemove
* all semantic resources to remove.
* @return true if the resources can be deleted. I.e it is not a controlled
* resource and no representation are based on a semantic element of
* the resources. False otherwise.
*/
private boolean checkResources(Collection<Resource> toRemove) {
boolean okForRemove = true;
if (session instanceof DAnalysisSessionEObject) {
// Controlled resource should be removed with uncontrol command
okForRemove = !Iterables.removeAll(toRemove, ((DAnalysisSessionEObject) session).getControlledResources());
}
if (okForRemove) {
for (final DRepresentation representation : DialectManager.INSTANCE.getAllRepresentations(session)) {
if (representation instanceof DSemanticDecorator) {
final DSemanticDecorator decorator = (DSemanticDecorator) representation;
if (decorator.getTarget() != null && toRemove.contains(decorator.getTarget().eResource())) {
okForRemove = false;
break;
}
}
}
}
return okForRemove;
}
/**
* Create Model explorer navigator.
*
* @param parent
* the model Explorer Composite
*/
private TreeViewer createModelExplorerNavigator(Composite parent) {
modelTree = SWTUtil.createFilteredTree(parent, SWT.BORDER, new org.eclipse.ui.dialogs.PatternFilter());
TreeViewer theTreeViewer = modelTree.getViewer();
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.widthHint = 300;
theTreeViewer.getControl().setLayoutData(gridData);
theTreeViewer.getTree().setHeaderVisible(false);
theTreeViewer.getTree().setLinesVisible(false);
siriusCommonContentModelProvider = new SiriusCommonContentProvider();
theTreeViewer.setContentProvider(siriusCommonContentModelProvider);
theTreeViewer.setLabelProvider(new SiriusCommonLabelProvider());
updateViewerInput(theTreeViewer);
deleteActionHandler = new DeleteActionHandler(theTreeViewer);
renameActionHandler = new RenameActionHandler(theTreeViewer);
theTreeViewer.addSelectionChangedListener((event) -> {
if (event.getSelection().isEmpty()) {
removeSemanticModelOrRepresentationButton.setEnabled(false);
deleteActionHandler.setEnabled(false);
renameActionHandler.setEnabled(false);
} else {
TreeSelection selection = (TreeSelection) event.getSelection();
// The tree allows only single selections so we pick the
// first element.
Object firstElement = selection.getFirstElement();
if (isExternalDependency(firstElement, selection) && (firstElement instanceof EObject || firstElement instanceof Resource)
&& checkResources(getSemanticResources(Lists.newArrayList(firstElement)))) {
removeSemanticModelOrRepresentationButton.setEnabled(true);
} else {
if (firstElement instanceof DRepresentationDescriptor) {
deleteActionHandler.setEnabled(true);
renameActionHandler.setEnabled(true);
} else {
deleteActionHandler.setEnabled(false);
renameActionHandler.setEnabled(false);
}
removeSemanticModelOrRepresentationButton.setEnabled(false);
}
}
});
theTreeViewer.setSorter(new CommonItemSorter());
menuManager = new MenuManager();
menuManager.addMenuListener((manager) -> {
manageSessionActionProvider.setContext(new ActionContext(theTreeViewer.getSelection()));
manageSessionActionProvider.fillContextMenu(menuManager);
});
Menu menu = menuManager.createContextMenu(theTreeViewer.getControl());
manageSessionActionProvider = new ManageSessionActionProvider();
manageSessionActionProvider.initFromViewer(theTreeViewer);
theTreeViewer.getControl().setMenu(menu);
theTreeViewer.getControl().addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent event) {
handleKeyReleased(event);
}
});
return theTreeViewer;
}
/**
* Returns true if the given element is an external dependency of the
* session.
*
* @param element
* the element from which we want to know if it is an external
* dependency.
* @param selection
* the selection from which we want to know if it is an external
* dependency.
* @return true if the given element is an external dependency of the
* session. False otherwise.
*/
private boolean isExternalDependency(Object element, TreeSelection selection) {
if (!(element instanceof ProjectDependenciesItem) && !(element instanceof DRepresentationDescriptor)) {
TreePath pathsForSelection = selection.getPathsFor(element)[0];
int segmentCount = pathsForSelection.getSegmentCount();
for (int i = 0; i < segmentCount; i++) {
Object segment = pathsForSelection.getSegment(i);
if (segment instanceof ProjectDependenciesItem) {
return true;
}
}
}
return false;
}
/**
* Update the semantic models viewer with the models currently loaded in the
* session.
*
* @param theTreeViewer
* the viewer to update.
*/
protected void updateViewerInput(TreeViewer theTreeViewer) {
Object[] children = siriusCommonContentModelProvider.getChildren(session);
List<Object> childrenList = Arrays.stream(children).collect(Collectors.toList());
Resource sessionResource = session.getSessionResource();
IFile file = WorkspaceSynchronizer.getFile(sessionResource);
ProjectDependenciesItem projectDependenciesItem = new NoDynamicProjectDependencies(file.getProject(), session);
List<Object> directChildOfProjectDependency = Arrays.asList(siriusCommonContentModelProvider.getChildren(projectDependenciesItem));
childrenList.add(projectDependenciesItem);
// We put as input only the ProjectDependenciesItemImpl and all Ecore
// resources not provided by this item.
theTreeViewer.setInput(childrenList.stream().filter(input -> !(input instanceof ViewpointsFolderItemImpl) && !directChildOfProjectDependency.contains(input)).collect(Collectors.toSet()));
theTreeViewer.expandToLevel(2);
theTreeViewer.expandToLevel(projectDependenciesItem, 2);
}
/**
* Return semantic resource form selection in treeViewer.
*
* @param selection
* the selection from treeViewer
* @return semantic resource from selection
*/
private Collection<Resource> getSemanticResources(final Collection<?> selection) {
Collection<Resource> semanticResources = new HashSet<Resource>();
if (selection != null) {
Iterator<?> iterator = selection.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
if (object instanceof Resource) {
semanticResources.add((Resource) object);
} else if (object instanceof EObject) {
EObject eObject = (EObject) object;
Resource eResource = eObject.eResource();
if (eResource != null) {
semanticResources.add(eResource);
}
}
}
}
return semanticResources;
}
/**
* Helper method to add an action button to the view.
*
* @param parent
* parent Composite where button is displayed
* @param name
* name of the button
* @param body
* action to launch when button is pushed
* @return the newly created button.
*/
protected Button addButton(Composite parent, final String name, final Runnable body) {
Button button = toolkit.createButton(parent, name, SWT.PUSH);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (body != null) {
body.run();
}
}
});
return button;
}
@Override
public void notify(int changeKind) {
switch (changeKind) {
case SessionListener.SELECTED_VIEWS_CHANGE_KIND:
case SessionListener.SEMANTIC_CHANGE:
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
updateViewerInput(treeViewer);
});
break;
default:
break;
}
}
/**
* Dispose all listeners.
*/
public void dispose() {
session.removeListener(this);
SessionManager.INSTANCE.removeSessionsListener(this);
session = null;
treeViewer = null;
manageSessionActionProvider = null;
siriusCommonContentModelProvider = null;
menuManager = null;
toolkit = null;
}
@Override
public void notifyAddSession(Session newSession) {
}
@Override
public void notifyRemoveSession(Session removedSession) {
siriusCommonContentModelProvider.removeRefreshViewerTriggers(removedSession);
}
@Override
public void viewpointSelected(Viewpoint selectedSirius) {
}
@Override
public void viewpointDeselected(Viewpoint deselectedSirius) {
}
@Override
public void notify(Session updated, int notification) {
if (session.equals(updated)) {
switch (notification) {
case SessionListener.REPRESENTATION_CHANGE:
case SessionListener.SEMANTIC_CHANGE:
case SessionListener.SELECTED_VIEWS_CHANGE_KIND:
case SessionListener.VSM_UPDATED:
case SessionListener.REPLACED:
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (!modelTree.isDisposed()) {
treeViewer.refresh();
// we reset the selection to update the button
// activated
// state if needed.
ISelection selection = treeViewer.getSelection();
treeViewer.setSelection(selection);
}
}
});
break;
case SessionListener.SYNC:
case SessionListener.DIRTY:
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (!modelTree.isDisposed()) {
treeViewer.refresh();
}
}
});
break;
case SessionListener.OPENED:
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (!modelTree.isDisposed()) {
treeViewer.refresh();
siriusCommonContentModelProvider.addRefreshViewerTrigger(updated);
}
}
});
break;
default:
// do nothing as we will be notified in other way
break;
}
}
}
}