blob: 341117ed712f6477e873a813e52621a322d6e261 [file] [log] [blame]
/**
* Copyright (c) 2012 Eclipse contributors and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*/
package org.eclipse.emf.ecore.xcore.ui.editor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.codegen.ecore.genmodel.GenBase;
import org.eclipse.emf.codegen.ecore.genmodel.GenFeature;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelFactory;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
import org.eclipse.emf.codegen.ecore.genmodel.provider.GenModelItemProvider;
import org.eclipse.emf.codegen.ecore.genmodel.provider.GenModelItemProviderAdapterFactory;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xcore.XAnnotation;
import org.eclipse.emf.ecore.xcore.XNamedElement;
import org.eclipse.emf.ecore.xcore.XPackage;
import org.eclipse.emf.ecore.xcore.XcorePackage;
import org.eclipse.emf.ecore.xcore.mappings.ToXcoreMapping;
import org.eclipse.emf.ecore.xcore.mappings.XcoreMapper;
import org.eclipse.emf.ecore.xcore.services.XcoreGrammarAccess;
import org.eclipse.emf.ecore.xcore.ui.quickfix.XcoreQuickfixProvider;
import org.eclipse.emf.ecore.xcore.util.XcoreGenModelInitializer;
import org.eclipse.emf.ecore.xcore.validation.XcoreResourceValidator;
import org.eclipse.emf.ecore.xml.type.XMLTypeFactory;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.edit.provider.AdapterFactoryItemDelegator;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.IItemPropertySource;
import org.eclipse.emf.edit.provider.ItemPropertyDescriptorDecorator;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.PropertyDescriptor;
import org.eclipse.emf.edit.ui.provider.PropertySource;
import org.eclipse.emf.edit.ui.provider.DiagnosticDecorator.DiagnosticDecoratorAdapter;
import org.eclipse.emf.edit.ui.view.ExtendedPropertySheetPage;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.editors.text.TextEditorActionContributor;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.PropertySheetPage;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.conversion.IValueConverterService;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.outline.impl.EObjectNode;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import com.google.inject.Inject;
/**
* A derived {@link XtextEditor} that supports a properties view for the Xcore resource's {@link GenModel}.
*/
public class XcoreEditor extends XtextEditor
{
@Inject
private XcoreMapper mapper;
@Inject
private IValueConverterService valueConverterService;
@Inject XcoreGrammarAccess xcoreGrammarAccess;
@Inject XcoreGenModelInitializer genModelInitializer;
protected List<PropertySheetPage> propertySheetPages = new ArrayList<PropertySheetPage>();
protected ComposedAdapterFactory adapterFactory;
@Override
@SuppressWarnings("all")
public Object getAdapter(Class type)
{
if (type.equals(IPropertySheetPage.class))
{
return getPropertySheetPage();
}
else
{
return super.getAdapter(type);
}
}
public IPropertySheetPage getPropertySheetPage()
{
// Create an adapter factory that uses registered item provider adapter factories, but specializes the one for the GenModel
//
adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
final AdapterFactoryItemDelegator itemDelegator = new AdapterFactoryItemDelegator(adapterFactory);
adapterFactory.addAdapterFactory
(new GenModelItemProviderAdapterFactory()
{
@Override
public Adapter createGenModelAdapter()
{
// Create a new adapter each time.
//
return
new GenModelItemProvider(this)
{
@Override
public List<IItemPropertyDescriptor> getPropertyDescriptors(Object object)
{
if (itemPropertyDescriptors == null)
{
super.getPropertyDescriptors(object);
// Merge in the decorated property descriptors for the one GenPackage in the model.
//
GenModel genModel = (GenModel)object;
EList<GenPackage> genPackages = genModel.getGenPackages();
if (!genPackages.isEmpty())
{
GenPackage genPackage = genPackages.get(0);
List<IItemPropertyDescriptor> genPackagePropertyDescriptors = itemDelegator.getPropertyDescriptors(genPackage);
for (IItemPropertyDescriptor genPackagePropertyDescriptor : genPackagePropertyDescriptors)
{
itemPropertyDescriptors.add(new ItemPropertyDescriptorDecorator(genPackage, genPackagePropertyDescriptor));
}
}
}
return itemPropertyDescriptors;
}
};
}
});
// Cache the editor's document and resource set.
//
final IXtextDocument document = getDocument();
final ResourceSet resourceSet =
document.readOnly
(new IUnitOfWork<ResourceSet, XtextResource>()
{
public ResourceSet exec(final XtextResource xtextResource) throws Exception
{
return xtextResource.getResourceSet();
}
});
// Create a specialized command stack.
//
BasicCommandStack commandStack =
new BasicCommandStack()
{
protected boolean isDefault(final GenModel genModel, EObject eObject, EStructuralFeature eStructuralFeature)
{
// Create a freshly initialized GenModel for the original GenModel's EPackage.
//
Resource fakeResource = new ResourceImpl(genModel.eResource().getURI());
final GenModel clonedGenModel = GenModelFactory.eINSTANCE.createGenModel();
EList<GenPackage> genPackages = genModel.getGenPackages();
if (!genPackages.isEmpty())
{
clonedGenModel.initialize(Collections.singleton(genPackages.get(0).getEcorePackage()));
}
genModelInitializer.initialize(clonedGenModel, false);
fakeResource.getContents().add(clonedGenModel);
// Traverse the EObject to find its clone in the cloned GenModel.
//
EObject clonedEObject =
new Object()
{
EObject traverse(EObject eObject)
{
EObject eContainer = eObject.eContainer();
if (eContainer == null)
{
return clonedGenModel;
}
else
{
EReference eContainmentFeature = eObject.eContainmentFeature();
EObject clonedEObject = traverse(eContainer);
Object value = clonedEObject.eGet(eContainmentFeature);
if (eContainmentFeature.isMany())
{
@SuppressWarnings("unchecked")
List<EObject> values = (List<EObject>)value;
List<?> originalValues = (List<?>)eContainer.eGet(eContainmentFeature);
return values.get(originalValues.indexOf(eObject));
}
else
{
return (EObject)value;
}
}
}
}.traverse(eObject);
// Test whether the feature's value is the same as the default.
//
Object newValue = eObject.eGet(eStructuralFeature);
Object defaultValue = clonedEObject.eGet(eStructuralFeature);
return
newValue == null ?
defaultValue == null :
newValue instanceof GenFeature && defaultValue instanceof GenFeature ?
((GenFeature)newValue).getEcoreFeature() == ((GenFeature)defaultValue).getEcoreFeature() :
newValue.equals(defaultValue);
}
protected String getValue(EObject eObject, EStructuralFeature eStructuralFeature)
{
EClassifier eType = eStructuralFeature.getEType();
Object value = eObject.eGet(eStructuralFeature);
@SuppressWarnings("unchecked")
String literal =
eType instanceof EDataType ?
eStructuralFeature.isMany() ?
XMLTypeFactory.eINSTANCE.convertENTITIESBase((List<String>)value) :
EcoreUtil.convertToString((EDataType)eType, value) :
((GenFeature)value).getName();
return valueConverterService.toString(literal, "STRING");
}
@Override
public void execute(Command command)
{
// Before executing the command, add a content adapter to the GenModel to be notified of whatever feature changes.
//
final Resource resource = resourceSet.getResources().get(0);
final GenModel genModel = (GenModel)EcoreUtil.getObjectByType(resource.getContents(), GenModelPackage.Literals.GEN_MODEL);
final List<Notification> notifications = new ArrayList<Notification>();
final EContentAdapter eContentAdatper =
new EContentAdapter()
{
@Override
public void notifyChanged(final Notification notification)
{
super.notifyChanged(notification);
int eventType = notification.getEventType();
if (eventType == Notification.REMOVING_ADAPTER)
{
// If we are removing the adapters from the GenModel, because it's unloaded when a new GenModel is inferred...
//
if (notification.getNotifier() instanceof GenModel)
{
// Defer producing a new selection changed event to update the properties view for the the new inferred selection.
//
getEditorSite().getShell().getDisplay().asyncExec
(new Runnable()
{
public void run()
{
document.readOnly
(new IUnitOfWork.Void<XtextResource>()
{
@Override
public void process(final XtextResource xtextResource) throws Exception
{
ISelection selection = getSourceViewer().getSelectionProvider().getSelection();
for (Iterator<PropertySheetPage> i = propertySheetPages.iterator(); i.hasNext(); )
{
PropertySheetPage propertySheetPage = i.next();
if (propertySheetPage.getControl() == null || propertySheetPage.getControl().isDisposed())
{
i.remove();
}
else
{
propertySheetPage.selectionChanged(XcoreEditor.this, selection);
}
}
}
});
}
});
}
}
else if (!notification.isTouch() && notification.getNotifier() instanceof EObject)
{
// Record the notifications.
//
notifications.add(0, notification);
}
}
};
genModel.eAdapters().add(eContentAdatper);
// Execute the command, recording notifications so they can be processed after the command is complete.
//
super.execute(command);
// Process the deferred notifications.
//
if (!notifications.isEmpty())
{
final Notification notification = notifications.get(0);
// For the feature of the object that's changed, process the new contents of the feature.
//
document.modify
(new IUnitOfWork.Void<XtextResource>()
{
@Override
public void process(XtextResource state) throws Exception
{
// Determine the object and feature that are changed.
//
EObject eObject = (EObject)notification.getNotifier();
EStructuralFeature eStructuralFeature = (EStructuralFeature)notification.getFeature();
String name = eStructuralFeature.getName();
// Determine the affected Xcore element.
//
ToXcoreMapping xcoreMapping = mapper.getToXcoreMapping(eObject);
XNamedElement xNamedElement = xcoreMapping.getXcoreElement();
if (xNamedElement == null && eObject instanceof GenModel)
{
xNamedElement = (XPackage)resource.getContents().get(0);
}
if (xNamedElement != null)
{
// Determine the nodes affected for the element, i.e.,
// the node for the element as a whole,
// the node for the annotation,
// the node for the detail entry,
// and the node for the value in the detail entry.
//
ICompositeNode elementNode = NodeModelUtils.getNode(xNamedElement);
ICompositeNode annotationNode = null;
ICompositeNode detailNode = null;
List<INode> valueNodes = null;
// Determine if there is already an annotation for the GenModel's annotation URI.
//
XAnnotation xAnnotation = xNamedElement.getAnnotation(GenModelPackage.eNS_URI);
if (xAnnotation != null)
{
// If there is, get the node for that.
//
annotationNode = NodeModelUtils.getNode(xAnnotation);
// Determine if there is a detail entry for the affected feature.
//
for (Map.Entry<String, String> detail : xAnnotation.getDetails())
{
if (name.equals(detail.getKey()))
{
// If there is a matching key, determine the overall node for it and the node for the value.
//
detailNode = NodeModelUtils.findActualNodeFor((EObject)detail);
valueNodes = NodeModelUtils.findNodesForFeature((EObject)detail, XcorePackage.Literals.XSTRING_TO_STRING_MAP_ENTRY__VALUE);
break;
}
}
}
// If we found a node for the element...
//
if (elementNode != null)
{
// If there doesn't yet exist an annotation node.
//
if (annotationNode == null)
{
// Insert a new annotation with the key/value mapping on a new line before the element node.
//
int offset = elementNode.getOffset();
// Match the indentation of the element.
//
int line = document.getLineOfOffset(offset);
String lineDelimiter = document.getLineDelimiter(line);
int lineOffset = document.getLineOffset(line);
String indentation = document.get(lineOffset, offset - lineOffset);
int length = indentation.length();
StringBuilder newIndentation = new StringBuilder(length);
for (int i = 0; i < length; ++i)
{
int codePoint = indentation.codePointAt(i);
newIndentation.appendCodePoint(Character.isSpaceChar(codePoint) ? codePoint : ' ');
}
document.replace(lineOffset, 0, newIndentation + "@GenModel(" + name + "=" + getValue(eObject, eStructuralFeature) + ")" + lineDelimiter);
}
// If there is a node for the old value...
//
else if (valueNodes != null)
{
// If the feature isn't set to the default, and there is a node for the detail entry, we want to remove the node...
//
if (detailNode != null && isDefault(genModel, eObject, eStructuralFeature))
{
// Cache the grammar rules we'll need to match.
//
Keyword comma = xcoreGrammarAccess.getXAnnotationAccess().getCommaKeyword_2_2_0();
Keyword leftParenthesis = xcoreGrammarAccess.getXAnnotationAccess().getLeftParenthesisKeyword_2_0();
Keyword rightParenthesis = xcoreGrammarAccess.getXAnnotationAccess().getRightParenthesisKeyword_2_3();
// Compute the locations of the surrounding mark-up, i.e.,
// the left parenthesis of the annotation,
// the comma before the detail entry,
// the start of the next entry,
// and the right parenthesis.
//
int leftParenthesisOffset = -1;
int commaOffset = -1;
int nextDetailNodeOffset = -1;
int rightParenthesisOffset = -1;
// This is set to true once we've iterated past the detail entry.
//
boolean matched = false;
for (INode child : detailNode.getParent().getChildren())
{
EObject grammarElement = child.getGrammarElement();
if (matched)
{
if (grammarElement == rightParenthesis)
{
rightParenthesisOffset = child.getOffset();
break;
}
else if (NodeModelUtils.findActualSemanticObjectFor(child) instanceof Map.Entry)
{
nextDetailNodeOffset = child.getOffset();
break;
}
}
else if (child == detailNode)
{
matched = true;
}
else if (grammarElement == leftParenthesis)
{
leftParenthesisOffset = child.getOffset();
}
else if (grammarElement == comma)
{
commaOffset = child.getOffset();
}
}
if (commaOffset != -1)
{
if (rightParenthesisOffset != -1)
{
// @GenModel(a="x", b="y", c="z")
// ^ ^
//
document.replace(commaOffset, rightParenthesisOffset - commaOffset, "");
}
else // if (nextDetailNodeOffset != -1)
{
// @GenModel(a="x", b="y", c="z")
// ^ ^
//
document.replace(commaOffset + 1, nextDetailNodeOffset - commaOffset - 1, " ");
}
}
else // if (leftParenthesisOffset != -1)
{
if (rightParenthesisOffset != -1)
{
// @GenModel(a="x")
// ^ ^
//
// document.replace(leftParenthesisOffset, rightParenthesisOffset - leftParenthesisOffset + 1, "");
XcoreQuickfixProvider.RemovalRegion removalRegion = new XcoreQuickfixProvider.RemovalRegion(document, xAnnotation);
document.replace(removalRegion.getDeleteBegin(), removalRegion.getDeleteEnd() - removalRegion.getDeleteBegin(), "");
}
else // if (nextDetailNodeOffset != -1)
{
// @GenModel(a="x", b="y", c="z")
// ^ ^
//
document.replace(leftParenthesisOffset + 1, nextDetailNodeOffset - leftParenthesisOffset - 1, "");
}
}
}
else
{
// Replace the old value with the new value.
// @GenModel(a="x")
// ^ ^
//
INode valueNode = valueNodes.get(0);
document.replace(valueNode.getOffset(), valueNode.getLength(), getValue(eObject, eStructuralFeature));
}
}
// If this is the first detail entry...
//
else if (xAnnotation != null && xAnnotation.getDetails().isEmpty())
{
// Add the key/value mapping with new parentheses.
// @GenModel
// ^
//
int offset = annotationNode.getOffset() + annotationNode.getLength();
document.replace(offset, 0, "(" + name + "=" + getValue(eObject, eStructuralFeature) + ")");
}
// Otherwise, we just need to add a new key/value mapping to the end of the list.
// @GenModel(a="x")
// ^
//
else
{
int offset = annotationNode.getOffset() + annotationNode.getLength() - 1;
document.replace(offset, 0, ", " + name + "=" + getValue(eObject, eStructuralFeature));
}
}
}
}
});
}
}
};
// Create the editing domain with a special command stack.
// Be sure that only objects in the main resource are modifiable.
//
final AdapterFactoryEditingDomain editingDomain =
new AdapterFactoryEditingDomain(adapterFactory, commandStack, resourceSet)
{
@Override
public boolean isReadOnly(Resource resource)
{
return super.isReadOnly(resource) || getResourceSet().getResources().indexOf(resource) != 0;
}
};
// Ensure that the editing domain for the resource set can be determined.
//
class EditingDomainProvider extends AdapterImpl implements IEditingDomainProvider
{
public EditingDomain getEditingDomain()
{
return editingDomain;
}
@Override
public boolean isAdapterForType(Object type)
{
return IEditingDomainProvider.class.equals(type);
}
}
resourceSet.eAdapters().add(new EditingDomainProvider());
// The property sheet does manual validation, but the resource validation is done automatically.
// Inform the diagnostic decorator adapter about the results of the resource validation.
resourceSet.eAdapters().add(new XcoreResourceValidator.ValidationAdapter()
{
@Override
public void update(Diagnostic diagnostic)
{
DiagnosticDecoratorAdapter.update(resourceSet, diagnostic);
}
});
// Create a specialized property sheet page.
//
final PropertySheetPage propertySheetPage =
new ExtendedPropertySheetPage(editingDomain, ExtendedPropertySheetPage.Decoration.MANUAL)
{
protected DelayedProcessor<Control, ITextSelection> delayedProcessor;
@Override
public void createControl(Composite parent)
{
super.createControl(parent);
delayedProcessor =
new DelayedProcessor<Control, ITextSelection>(getControl(), 1000)
{
protected long lastModification;
{
getSourceViewer().getTextWidget().addModifyListener
(new ModifyListener()
{
public void modifyText(ModifyEvent modifyEvent)
{
// Keep track of the last time the text was modified.
// This way we can make the delayed response of the properties view kick in only if there is a recent modification.
//
lastModification = System.currentTimeMillis();
}
});
}
@Override
protected boolean isStale(ITextSelection newValue)
{
return newValue.getOffset() != getSourceViewer().getTextWidget().getSelection().x;
}
@Override
public void delayedProcess(ITextSelection value)
{
// Don't delay processing if the text has not recently been modified.
//
if (runnable == null && (System.currentTimeMillis() - lastModification) > 2000)
{
process(value);
}
else
{
super.delayedProcess(value);
}
}
@Override
protected void process(final ITextSelection textSelection)
{
IXtextDocument document = getDocument();
document.readOnly
(new IUnitOfWork.Void<XtextResource>()
{
@Override
public void process(XtextResource xtextResource) throws Exception
{
IParseResult parseResult = xtextResource.getParseResult();
if (parseResult != null)
{
ICompositeNode rootNode = parseResult.getRootNode();
if (rootNode != null)
{
ILeafNode node = NodeModelUtils.findLeafNodeAtOffset(rootNode, textSelection.getOffset());
if (node != null)
{
// Determine the EObject and process that instead.
//
EObject eObject = NodeModelUtils.findActualSemanticObjectFor(node);
if (!selectionChanged(XcoreEditor.this, eObject))
{
if (AdapterFactoryEditingDomain.isStale(getInput()))
{
selectionChanged(XcoreEditor.this, StructuredSelection.EMPTY);
}
}
}
}
}
}
});
}
};
}
@Override
public void setSelectionToViewer(List<?> selection)
{
XcoreEditor.this.setFocus();
}
@Override
public void setActionBars(IActionBars actionBars)
{
// Ensure that the undo/redo actions are hooked up.
//
super.setActionBars(actionBars);
TextEditorActionContributor actionBarContributor = (TextEditorActionContributor)getEditorSite().getActionBarContributor();
IActionBars editorActionBars = actionBarContributor.getActionBars();
actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(), editorActionBars.getGlobalActionHandler((ActionFactory.UNDO.getId())));
actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(), editorActionBars.getGlobalActionHandler((ActionFactory.REDO.getId())));
}
@Override
public void selectionChanged(final IWorkbenchPart part, ISelection selection)
{
if (selection instanceof IStructuredSelection)
{
// If the first element is an EObjectNode from the outline view...
//
Object element = ((IStructuredSelection)selection).getFirstElement();
if (element instanceof EObjectNode)
{
final EObjectNode eObjectNode = (EObjectNode)element;
IXtextDocument document = getDocument();
if (!document.readOnly
(new IUnitOfWork<Boolean, XtextResource>()
{
public Boolean exec(XtextResource xtextResource) throws Exception
{
// Determine the EObject and process that instead.
//
EObject eObject = eObjectNode.getEObject(xtextResource);
return selectionChanged(part, eObject);
}
}))
{
if (AdapterFactoryEditingDomain.isStale(getInput()))
{
super.selectionChanged(part, StructuredSelection.EMPTY);
}
}
}
else
{
super.selectionChanged(part, selection);
}
}
else if (selection instanceof ITextSelection)
{
// Map the selection to a model object...
//
final ITextSelection textSelection = (ITextSelection)selection;
delayedProcessor.delayedProcess(textSelection);
}
else
{
super.selectionChanged(part, selection);
}
}
/**
* A helper utility for processing an EObject to determine the appropriate GenModel element to select.
* Returns whether candidate was successfully determined.
*/
protected boolean selectionChanged(IWorkbenchPart part, EObject eObject)
{
if (eObject instanceof XNamedElement)
{
GenBase genBase = mapper.getGen((XNamedElement)eObject);
if (genBase instanceof GenPackage)
{
genBase = ((GenPackage)genBase).getGenModel();
}
if (genBase != null)
{
selectionChanged(part, new StructuredSelection(genBase));
return true;
}
}
else if (eObject instanceof GenBase)
{
selectionChanged(part, new StructuredSelection(eObject));
return true;
}
else if (eObject != null)
{
return selectionChanged(part, eObject.eContainer());
}
return false;
}
};
// Set the content provider.
//
final AdapterFactoryContentProvider contentProvider = new AdapterFactoryContentProvider(adapterFactory)
{
@Override
protected IPropertySource createPropertySource(Object object, IItemPropertySource itemPropertySource)
{
return
new PropertySource(object, itemPropertySource)
{
@Override
protected IPropertyDescriptor createPropertyDescriptor(IItemPropertyDescriptor itemPropertyDescriptor)
{
return
new PropertyDescriptor(object, itemPropertyDescriptor)
{
@Override
public CellEditor createPropertyEditor(Composite composite)
{
if (object instanceof IItemPropertySource)
{
Object value = ((IItemPropertySource) object).getEditableValue(object);
if (value instanceof EObject && ((EObject)value).eClass().getEPackage() == EcorePackage.eINSTANCE)
{
// Ensure that Ecore properties are read only.
//
return null;
}
}
return super.createPropertyEditor(composite);
}
};
}
};
}
};
propertySheetPage.setPropertySourceProvider(contentProvider);
// Set the initial selection.
//
getEditorSite().getShell().getDisplay().asyncExec
(new Runnable()
{
public void run()
{
propertySheetPage.selectionChanged(XcoreEditor.this, getSourceViewer().getSelectionProvider().getSelection());
}
});
propertySheetPages.add(propertySheetPage);
return propertySheetPage;
}
@Override
public void dispose()
{
super.dispose();
if (adapterFactory != null)
{
adapterFactory.dispose();
}
for (PropertySheetPage propertySheetPage : propertySheetPages)
{
propertySheetPage.dispose();
}
}
public static abstract class DelayedProcessor<C extends Control, V>
{
protected Runnable runnable;
protected int delay;
protected C control;
protected V value;
public DelayedProcessor(C control, int delay)
{
this.control = control;
this.delay = delay;
}
public void delayedProcess(V value)
{
if (isChanged(this.value, value) && !isStale(value))
{
this.value = value;
runnable =
new Runnable()
{
public void run()
{
if (runnable == this && !control.isDisposed() && !isStale(DelayedProcessor.this.value))
{
runnable = null;
process(DelayedProcessor.this.value);
}
}
};
control.getDisplay().timerExec(delay, runnable);
}
}
protected boolean isChanged(V oldValue, V newValue)
{
return oldValue == null ? newValue != null : !oldValue.equals(newValue);
}
protected boolean isStale(V newValue)
{
return false;
}
protected abstract void process(V value);
}
}