/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * 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: * Remi Schnekenburger (CEA LIST) remi.schnekenburger@cea.fr - Initial API and implementation *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.common.service.palette; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EEnum; import org.eclipse.emf.ecore.EEnumLiteral; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.transaction.Transaction; import org.eclipse.emf.workspace.AbstractEMFOperation; import org.eclipse.gef.EditPart; import org.eclipse.gef.EditPartViewer; import org.eclipse.gef.palette.CombinedTemplateCreationEntry; import org.eclipse.gmf.runtime.common.core.command.CompositeCommand; import org.eclipse.gmf.runtime.common.core.util.StringStatics; import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart; import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer; import org.eclipse.gmf.runtime.diagram.ui.util.EditPartUtil; import org.eclipse.gmf.runtime.emf.type.core.commands.SetValueCommand; import org.eclipse.gmf.runtime.emf.type.core.requests.SetRequest; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CheckboxCellEditor; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.Viewer; import org.eclipse.papyrus.uml.diagram.common.Activator; import org.eclipse.papyrus.uml.diagram.common.part.PaletteUtil; import org.eclipse.papyrus.uml.diagram.common.service.IPapyrusPaletteConstant; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.uml2.uml.Profile; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Aspect action that modifies semantic information on the edit part newly * created */ public class SemanticPostAction extends ModelPostAction { /** list of properties to update, identified by their names */ protected Map<String, Object> propertiesToUpdate; /** list of properties defined at runtime */ protected List<String> runtimeDefinedProperties; /** entry proxy "parent" of this action when configuring the action */ protected IPaletteEntryProxy entryProxy; /** list of applied profiles */ protected List<Profile> appliedProfiles; /** viewer for the attributes to initialize */ protected TableViewer attributeViewer; /** * this attribute caches the value of the metaclass linked to the creation * entry (performance optimization) */ protected EClass metaclass = null; /** path to the checked box image */ protected final static String ICON_CHECKED = "/icons/complete_tsk.gif"; /** path to the unchecked box image */ protected final static String ICON_UNCHECKED = "/icons/incomplete_tsk.gif"; /** separator used to serialize lists */ protected static final String SEPARATOR = ",,"; /** * Constructor. */ public SemanticPostAction() { propertiesToUpdate = new HashMap<String, Object>(); runtimeDefinedProperties = new ArrayList<String>(); } /** * @{inheritDoc */ @Override public void init(Node configurationNode, IAspectActionProvider factory) { super.init(configurationNode, factory); if(configurationNode == null) { return; } NodeList childNodes = configurationNode.getChildNodes(); for(int i = 0; i < childNodes.getLength(); i++) { Node featureNode = childNodes.item(i); if(IPapyrusPaletteConstant.FEATURE_NODE_NAME.equals(featureNode.getNodeName())) { Node nameNode = featureNode.getAttributes().getNamedItem(IPapyrusPaletteConstant.NAME); Node valueNode = featureNode.getAttributes().getNamedItem(IPapyrusPaletteConstant.VALUE); Node separatorNode = featureNode.getAttributes().getNamedItem(IPapyrusPaletteConstant.SEPARATOR); if(nameNode != null && valueNode != null && separatorNode != null) { propertiesToUpdate.put(nameNode.getNodeValue(), parseValue(valueNode.getNodeValue(), separatorNode.getNodeValue())); } else { Activator.log.error("Impossible to parse the configuration node for semantic post action", null); } } else if(IPapyrusPaletteConstant.RUNTIME_FEATURE_NODE_NAME.equals(featureNode.getNodeName())) { Node nameNode = featureNode.getAttributes().getNamedItem(IPapyrusPaletteConstant.NAME); if(nameNode != null) { runtimeDefinedProperties.add(nameNode.getNodeValue()); } else { Activator.log.error("Impossible to parse the configuration node for semantic post action", null); } } } } /** * parse the value from the serialized form * * @param nodeValue * the serialized value of the element * @param separator * the separator used to serialized, in case the element is a * list * @return the value of the element */ protected Object parseValue(String nodeValue, String separator) { // check this was a list if(nodeValue.indexOf(separator) > 0) { // this is a list List<Object> values = new ArrayList<Object>(); StringTokenizer tokenizer = new StringTokenizer(nodeValue, separator); while(tokenizer.hasMoreElements()) { Object value = tokenizer.nextElement(); values.add(value); } return values; } else { // this is not a list return nodeValue; } } /** * {@inheritDoc} */ public void run(EditPart editPart) { final CompositeCommand compositeCommand = new CompositeCommand("Modify Semantic"); EObject objectToEdit = ((View)editPart.getModel()).getElement(); for(String featureName : propertiesToUpdate.keySet()) { // retrieve feature to set EStructuralFeature feature = objectToEdit.eClass().getEStructuralFeature(featureName); if(feature == null) { Activator.log.error("Impossible to find the feature " + featureName + " for element " + objectToEdit, null); return; } else { SetRequest request = new SetRequest(objectToEdit, feature, getValue(feature, propertiesToUpdate.get(featureName))); // request.getExtendedData().put(ApplyStereotypeRequest.NEW_EDIT_PART_NAME, // "NEW"); compositeCommand.compose(new SetValueCommand(request)); } } // create the command to open the dialog to set properties on runtime if(runtimeDefinedProperties.size() > 0) { DynamicConfigureRequest request = new DynamicConfigureRequest(objectToEdit, runtimeDefinedProperties); compositeCommand.compose(new SetDynamicValueCommand(request)); } compositeCommand.reduce(); if(compositeCommand.canExecute()) { boolean isActivating = true; Map<String, Boolean> options = null; // use the viewer to determine if we are still initializing the // diagram // do not use the DiagramEditPart.isActivating since // ConnectionEditPart's // parent will not be a diagram edit part EditPartViewer viewer = editPart.getViewer(); if(viewer instanceof DiagramGraphicalViewer) { isActivating = ((DiagramGraphicalViewer)viewer).isInitializing(); } if(isActivating || !EditPartUtil.isWriteTransactionInProgress((IGraphicalEditPart)editPart, false, false)) { options = Collections.singletonMap(Transaction.OPTION_UNPROTECTED, Boolean.TRUE); } AbstractEMFOperation operation = new AbstractEMFOperation(((IGraphicalEditPart)editPart).getEditingDomain(), StringStatics.BLANK, options) { protected IStatus doExecute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { compositeCommand.execute(monitor, info); return Status.OK_STATUS; } }; try { operation.execute(new NullProgressMonitor(), null); } catch (ExecutionException e) { Activator.log.error(e); } } } /** * Returns the map of properties to update * * @return the map of properties to update */ public Map<String, Object> getPropertiesToUpdate() { return propertiesToUpdate; } /** * @{inheritDoc */ public Control createConfigurationComposite(Composite parent, IPaletteEntryProxy entryProxy, List<Profile> appliedProfiles) { this.appliedProfiles = appliedProfiles; this.entryProxy = entryProxy; // retrieve tool metaclass if(entryProxy.getEntry() instanceof CombinedTemplateCreationEntry) { metaclass = PaletteUtil.getToolMetaclass((CombinedTemplateCreationEntry)entryProxy.getEntry()); } Composite mainComposite = new Composite(parent, SWT.BORDER); GridLayout layout = new GridLayout(3, false); mainComposite.setLayout(layout); Label titleLabel = new Label(mainComposite, SWT.NONE); titleLabel.setText("Values to set for the semantic element"); GridData data = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1); titleLabel.setLayoutData(data); attributeViewer = new TableViewer(mainComposite, SWT.BORDER | SWT.FULL_SELECTION); createColumns(attributeViewer); attributeViewer.setContentProvider(new AttributeContentProvider()); attributeViewer.setLabelProvider(new AttributeLabelProvider()); data = new GridData(SWT.FILL, SWT.FILL, true, true); data.horizontalSpan = 3; attributeViewer.getControl().setLayoutData(data); updateAttributesViewer(); return mainComposite; } /** * This will create the columns for the table * * @param viewer * the viewer for which column are created */ protected void createColumns(TableViewer viewer) { // name column TableViewerColumn nameColumn = new TableViewerColumn(viewer, SWT.NONE); nameColumn.getColumn().setText("Feature Name"); nameColumn.getColumn().setWidth(130); nameColumn.getColumn().setResizable(true); nameColumn.getColumn().setMoveable(false); // runtime defined property column TableViewerColumn runtimeColumn = new TableViewerColumn(viewer, SWT.NONE); runtimeColumn.getColumn().setText("Runtime"); runtimeColumn.getColumn().setWidth(70); runtimeColumn.getColumn().setResizable(true); runtimeColumn.getColumn().setMoveable(false); runtimeColumn.setEditingSupport(new DynamicFeatureEditingSupport(viewer)); // value column TableViewerColumn valueColumn = new TableViewerColumn(viewer, SWT.NONE); valueColumn.getColumn().setText("Values"); valueColumn.getColumn().setWidth(300); valueColumn.getColumn().setResizable(true); valueColumn.getColumn().setMoveable(false); valueColumn.setEditingSupport(new AttributeEditingSupport(viewer)); Table table = viewer.getTable(); table.setHeaderVisible(true); table.setLinesVisible(true); } /** * updates the stereotype viewer */ protected void updateAttributesViewer() { attributeViewer.setInput(metaclass); } /** * @{inheritDoc */ public void save(Node parentNode) { if(!(parentNode instanceof Element)) { Activator.log.error("parent node is not an Element", null); return; } else { for(String featureName : propertiesToUpdate.keySet()) { Element childElement = ((Element)parentNode).getOwnerDocument().createElement(IPapyrusPaletteConstant.FEATURE_NODE_NAME); childElement.setAttribute(IPapyrusPaletteConstant.NAME, featureName); childElement.setAttribute(IPapyrusPaletteConstant.VALUE, serializeValue(propertiesToUpdate.get(featureName), SEPARATOR)); childElement.setAttribute(IPapyrusPaletteConstant.SEPARATOR, SEPARATOR); parentNode.appendChild(childElement); } for(String featureName : runtimeDefinedProperties) { Element childElement = ((Element)parentNode).getOwnerDocument().createElement(IPapyrusPaletteConstant.RUNTIME_FEATURE_NODE_NAME); childElement.setAttribute(IPapyrusPaletteConstant.NAME, featureName); parentNode.appendChild(childElement); } } } /** * Serialize the value of the element * * @param object * the object to serialize * @return the string corresponding to the serialize value */ @SuppressWarnings("unchecked") protected String serializeValue(Object object, String separator) { if(object instanceof List<?>) { StringBuffer result = new StringBuffer(); Iterator<Object> it = ((List<Object>)object).iterator(); while(it.hasNext()) { Object o = (Object)it.next(); result.append(o); if(it.hasNext()) { result.append(separator); } } return result.toString(); } return object.toString(); } /** * Returns <code>true</code> if the value of this feature is defined at * runtime * * @param feature * the feature to check * @return <code>true</code> if the value of this feature is defined at * runtime, else <code>false</code> */ protected boolean isRuntimeDefined(EStructuralFeature feature) { return runtimeDefinedProperties.contains(feature.getName()); } /** * Content provider for the attribute viewer */ protected class AttributeContentProvider implements IStructuredContentProvider { /** current edited metaclass */ protected EClass currentEClass; /** * @{inheritDoc */ public Object[] getElements(Object inputElement) { if(inputElement instanceof EClass) { currentEClass = (EClass)inputElement; List<EAttribute> attributes = new ArrayList<EAttribute>(); // create a new list with only non derived attributes for(EAttribute attribute : currentEClass.getEAllAttributes()) { if(!attribute.isDerived() && attribute.isChangeable()) { attributes.add(attribute); } } return attributes.toArray(); } return new Object[0]; } /** * @{inheritDoc */ public void dispose() { } /** * @{inheritDoc */ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } /** * Class giving cell editors for statically defined values */ protected class AttributeEditingSupport extends EditingSupport { /** proposals for boolean */ protected final String[] booleanProposals = new String[]{ "", "true", "false" }; /** * Constructor. * * @param viewer * viewer in which editors will open */ public AttributeEditingSupport(ColumnViewer viewer) { super(viewer); } /** * @{inheritDoc */ @Override protected boolean canEdit(Object element) { return true; } /** * @{inheritDoc */ @Override protected CellEditor getCellEditor(Object element) { EStructuralFeature feature = (EStructuralFeature)element; EClassifier eType = feature.getEType(); if(eType instanceof EEnum) { return createEnumerationEditor(feature); } String instanceTypeName = eType.getInstanceClassName(); if(instanceTypeName.equals(String.class.getCanonicalName())) { return new TextCellEditor(((TableViewer)getViewer()).getTable()); } else if(instanceTypeName.equals(Integer.class.getCanonicalName())) { return new TextCellEditor(((TableViewer)getViewer()).getTable()); } else if(instanceTypeName.equals("boolean")) { return createBooleanEditor(feature); } return new TextCellEditor(((TableViewer)getViewer()).getTable()); } /** * Creates and return a combobox cell editor for a boolean type * * @param feature * the feature to edit * @return the newly created combo box cell editor */ protected CellEditor createBooleanEditor(EStructuralFeature feature) { return new ComboBoxCellEditor(((TableViewer)getViewer()).getTable(), booleanProposals, SWT.READ_ONLY); } /** * Creates and return a combobox cell editor for an enumeration type * * @param feature * the feature to edit * @return the newly created combo box cell editor */ protected CellEditor createEnumerationEditor(EStructuralFeature feature) { EEnum eType = (EEnum)feature.getEType(); List<EEnumLiteral> literals = eType.getELiterals(); String[] proposals = new String[literals.size() + 1]; proposals[0] = ""; for(int i = 0; i < literals.size(); i++) { // i+1 because there is already the "" string proposals[i + 1] = literals.get(i).getLiteral(); } return new ComboBoxCellEditor(((TableViewer)getViewer()).getTable(), proposals, SWT.READ_ONLY); } /** * @{inheritDoc */ @Override protected Object getValue(Object element) { EStructuralFeature feature = (EStructuralFeature)element; EClassifier eType = feature.getEType(); if(eType instanceof EEnum) { return getEnumerationValue(feature); } else { String instanceTypeName = eType.getInstanceClassName(); if(instanceTypeName.equals("boolean")) { return getBooleanValue(feature); } return (propertiesToUpdate.get(feature.getName()) != null) ? propertiesToUpdate.get(feature.getName()) : ""; } } /** * Returns the value from the feature * * @param feature * the feature to edit * @return the boolean value */ protected Object getEnumerationValue(EStructuralFeature feature) { EEnum eType = (EEnum)feature.getEType(); List<EEnumLiteral> literals = eType.getELiterals(); List<String> proposals = new ArrayList<String>(); proposals.add(""); for(int i = 0; i < literals.size(); i++) { // i+1 because there is already the "" string proposals.add(i + 1, literals.get(i).getLiteral()); } Object value = propertiesToUpdate.get(feature.getName()); if(value == null) { return 0; } else { return proposals.indexOf(value); } } /** * Returns the value from the feature * * @param feature * the feature to edit * @return the boolean value */ protected Object getBooleanValue(EStructuralFeature feature) { List<String> booleans = Arrays.asList(booleanProposals); Object value = propertiesToUpdate.get(feature.getName()); if(value == null || value.equals("")) { return 0; } else { return booleans.indexOf(value); } } /** * @{inheritDoc */ @Override protected void setValue(Object element, Object value) { EStructuralFeature feature = (EStructuralFeature)element; String featureName = ((EStructuralFeature)element).getName(); EClassifier eType = feature.getEType(); if(eType instanceof EEnum) { setEnumerationValue(element, value); } else { String instanceTypeName = eType.getInstanceClassName(); if(instanceTypeName.equals("boolean")) { setBooleanValue(element, value); } else { if(value == null || value.equals("")) { propertiesToUpdate.remove(featureName); } else { propertiesToUpdate.put(featureName, value); } } } getViewer().update(element, null); } /** * Sets the new boolean value on the given element. * * @param element * the model element * @param value * the new value */ protected void setEnumerationValue(Object element, Object value) { EStructuralFeature feature = (EStructuralFeature)element; EEnum eType = (EEnum)feature.getEType(); List<EEnumLiteral> literals = eType.getELiterals(); List<String> proposals = new ArrayList<String>(); proposals.add(""); for(int i = 0; i < literals.size(); i++) { // i+1 because there is already the "" string proposals.add(i + 1, literals.get(i).getLiteral()); } String featureName = feature.getName(); if(value == null || value.equals(0)) { propertiesToUpdate.remove(featureName); } else { // retrieve the index of the current value in the list int index = (Integer)value; propertiesToUpdate.put(featureName, proposals.get(index)); } } /** * Sets the new boolean value on the given element. * * @param element * the model element * @param value * the new value */ protected void setBooleanValue(Object element, Object value) { EStructuralFeature feature = (EStructuralFeature)element; String featureName = feature.getName(); if(value == null || value.equals(0)) { propertiesToUpdate.remove(featureName); } else if(value.equals(1)) { propertiesToUpdate.put(featureName, booleanProposals[1]); } else if(value.equals(2)) { propertiesToUpdate.put(featureName, booleanProposals[2]); } else { Activator.log.error("impossible to set boolean value " + value, null); } } } /** * Class giving cell editors for statically defined values */ protected class DynamicFeatureEditingSupport extends EditingSupport { /** editor */ private CellEditor editor; /** * Constructor. * * @param viewer * viewer in which editors will open */ public DynamicFeatureEditingSupport(ColumnViewer viewer) { super(viewer); editor = new CheckboxCellEditor(null, SWT.NONE); } /** * @{inheritDoc */ @Override protected boolean canEdit(Object element) { return true; } /** * @{inheritDoc */ @Override protected CellEditor getCellEditor(Object element) { return editor; } /** * @{inheritDoc */ @Override protected Object getValue(Object element) { EStructuralFeature feature = (EStructuralFeature)element; return runtimeDefinedProperties.contains(feature.getName()); } /** * @{inheritDoc */ @Override protected void setValue(Object element, Object value) { String featureName = ((EStructuralFeature)element).getName(); // it should be in the list if(!runtimeDefinedProperties.contains(featureName)) { runtimeDefinedProperties.add(featureName); } else { runtimeDefinedProperties.remove(featureName); } getViewer().update(element, null); } } /** * Label provider for the attribute viewer */ protected class AttributeLabelProvider implements ITableLabelProvider { /** * @{inheritDoc */ public Image getColumnImage(Object element, int columnIndex) { if(columnIndex == 1) { EStructuralFeature feature = (EStructuralFeature)element; // check if it is defined at runtime, using a popup dialog if(isRuntimeDefined(feature)) { return Activator.getPluginIconImage(Activator.ID, ICON_CHECKED); } else { return Activator.getPluginIconImage(Activator.ID, ICON_UNCHECKED); } } return null; } /** * @{inheritDoc */ public String getColumnText(Object element, int columnIndex) { String text = ""; EStructuralFeature feature = (EStructuralFeature)element; switch(columnIndex) { case 0: text = feature.getName(); break; case 1: break; case 2: // retrieve current given values Object value = propertiesToUpdate.get(feature.getName()); text = (value != null) ? value.toString() : ""; default: break; } return text; } /** * @{inheritDoc */ public void addListener(ILabelProviderListener listener) { } /** * @{inheritDoc */ public void dispose() { } /** * @{inheritDoc */ public boolean isLabelProperty(Object element, String property) { return false; } /** * @{inheritDoc */ public void removeListener(ILabelProviderListener listener) { } } }