/*******************************************************************************
* Copyright (c) 2011, 2012 Red Hat, Inc.
* All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
*
* @author Innar Made
******************************************************************************/
package org.eclipse.bpmn2.modeler.ui.property;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import org.eclipse.bpmn2.BaseElement;
import org.eclipse.bpmn2.Definitions;
import org.eclipse.bpmn2.modeler.core.adapters.AdapterRegistry;
import org.eclipse.bpmn2.modeler.core.adapters.ExtendedPropertiesProvider;
import org.eclipse.bpmn2.modeler.core.merrimac.clad.AbstractBpmn2PropertySection;
import org.eclipse.bpmn2.modeler.core.merrimac.clad.AbstractDetailComposite;
import org.eclipse.bpmn2.modeler.core.merrimac.clad.DefaultDetailComposite;
import org.eclipse.bpmn2.modeler.core.merrimac.clad.PropertiesCompositeFactory;
import org.eclipse.bpmn2.modeler.core.runtime.TargetRuntime;
import org.eclipse.bpmn2.modeler.core.utils.ModelUtil;
import org.eclipse.bpmn2.modeler.ui.util.PropertyUtil;
import org.eclipse.bpmn2.provider.Bpmn2ItemProviderAdapterFactory;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.impl.EStructuralFeatureImpl.BasicFeatureMapEntry;
import org.eclipse.emf.edit.command.CommandParameter;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.ui.services.GraphitiUi;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.Section;
@SuppressWarnings("unchecked")
public class AdvancedDetailComposite extends AbstractDetailComposite {
private TreeViewer treeViewer;
private AbstractDetailComposite detailsDetailComposite;
private Section treeSection;
private Section detailsSection;
private Composite detailsComposite;
private Button fullDetails;
private HashSet<Control> myChildren = new HashSet<Control>();
/**
* @param section
*/
public AdvancedDetailComposite(AbstractBpmn2PropertySection section) {
super(section);
}
public AdvancedDetailComposite(Composite parent, int style) {
super(parent,style);
}
public void initialize() {
super.initialize();
GridData data;
SashForm sashForm = new SashForm(this, SWT.BORDER);
sashForm.setSashWidth(5);
toolkit.adapt(sashForm);
toolkit.paintBordersFor(sashForm);
data = new GridData(SWT.FILL,SWT.FILL,true,true,3,1);
data.widthHint = 800;
sashForm.setLayoutData(data);
treeSection = toolkit.createSection(sashForm, ExpandableComposite.TITLE_BAR);
toolkit.paintBordersFor(treeSection);
Composite composite = toolkit.createComposite(treeSection, SWT.NONE);
toolkit.paintBordersFor(composite);
treeSection.setClient(composite);
composite.setLayout(new GridLayout(1, false));
treeViewer = new TreeViewer(composite, SWT.BORDER);
Tree tree = treeViewer.getTree();
data = new GridData(SWT.FILL, SWT.FILL, false, true, 1, 1);
// data.widthHint = 200;
tree.setLayoutData(data);
toolkit.paintBordersFor(tree);
treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
updateDetailsSection();
}
});
treeViewer.setContentProvider(new PropertyTreeContentProvider(this));
treeViewer.setLabelProvider(AdapterRegistry.getLabelProvider());
detailsSection = toolkit.createSection(sashForm, ExpandableComposite.TITLE_BAR);
toolkit.paintBordersFor(detailsSection);
detailsSection.setText(Messages.AdvancedDetailComposite_Properties_Title);
detailsComposite = toolkit.createComposite(detailsSection);
detailsComposite.setLayout(new GridLayout(1,false));
detailsSection.setClient(detailsComposite);
fullDetails = toolkit.createButton(detailsComposite, Messages.AdvancedDetailComposite_Advanced_Button, SWT.CHECK);
data = new GridData(SWT.LEFT,SWT.TOP,false,false,1,1);
data.exclude = true;
fullDetails.setLayoutData(data);
fullDetails.setVisible(false); // TODO: is the "Show advanced details" useful?
fullDetails.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
EObject obj = getSelectedBaseElement();
if (obj!=null) {
updateDetailsSection();
}
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
// don't construct the details composite yet because no selection has been
// made from the tree. We'll construct a details composite using the selected
// object's type (class) and then consult the PropertiesCompositeFactory for
// a composite to use to render this object.
// detailsDetailComposite = new DefaultDetailComposite(detailsSection, SWT.NONE);
// detailsDetailComposite.setPropertySection(propertySection);
//
// detailsSection.setClient(detailsDetailComposite);
// toolkit.adapt(detailsDetailComposite);
// toolkit.paintBordersFor(detailsDetailComposite);
sashForm.setWeights(new int[] { 1, 2 });
}
private void saveChildren(Control parent) {
myChildren.add(parent);
if (parent instanceof Composite) {
Control[] children = ((Composite)parent).getChildren();
for (Control c : children) {
myChildren.add(c);
saveChildren(c);
}
}
}
private void disposeDetailsChildren(Composite parent) {
// clean up any table widgets that may have been created by the previous
// incarnation of DefaultDetailComposite
for (Control c : parent.getChildren()) {
if (!myChildren.contains(c)) {
// argh! For some reason the Sash in a SashForm is not returned as one of its children
if (!(c instanceof Sash))
c.dispose();
}
else if (c instanceof Composite)
disposeDetailsChildren((Composite)c);
}
}
private void updateDetailsSection() {
if (myChildren.size()==0)
saveChildren(getParent());
disposeDetailsChildren(getParent());
EObject obj = getSelectedBaseElement();
if (obj==null) {
detailsSection.layout(true); // refresh the layout's clientCache!
detailsSection.setVisible(false);
}
else {
TargetRuntime rt = TargetRuntime.getRuntime(obj);
// construct a details composite based on the selected object's class
if (fullDetails.getSelection()) {
detailsDetailComposite = new DefaultDetailComposite(detailsComposite,SWT.NONE);
}
else {
detailsDetailComposite = PropertiesCompositeFactory.INSTANCE.createDetailComposite(obj.getClass(), detailsComposite, rt, SWT.NONE);
}
detailsDetailComposite.setLayoutData(new GridData(SWT.FILL,SWT.TOP,true,false,3,1));
Class cc = PropertiesCompositeFactory.findDetailCompositeClass(obj.getClass(), rt);
if (cc==null||cc==DefaultDetailComposite.class)
fullDetails.setVisible(false);
else
fullDetails.setVisible(true);
detailsComposite.layout(true);
detailsDetailComposite.setPropertySection(propertySection);
toolkit.adapt(detailsDetailComposite);
toolkit.paintBordersFor(detailsDetailComposite);
detailsSection.layout(true); // refresh the layout's clientCache!
detailsDetailComposite.setBusinessObject(obj);
String name = ExtendedPropertiesProvider.getTextValue(obj);
detailsSection.setText(name+Messages.AdvancedDetailComposite_Details_Title);
detailsSection.setVisible(true);
redrawPage();
}
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.ui.property.AbstractBpmn2DetailComposite#createBindings(org.eclipse.emf.ecore.EObject)
*/
@Override
public void createBindings(EObject be) {
}
protected void cleanBindings() {
}
@Override
public void setBusinessObject(EObject be) {
super.setBusinessObject(be);
EObject input = be;
while (input.eContainer()!=null) {
if (input instanceof Definitions)
break;
input = input.eContainer();
}
if (input!=null) {
treeViewer.setInput(input);
treeViewer.expandAll();
treeViewer.setSelection(new StructuredSelection(be),true);
}
else
treeViewer.setInput(be);
hookPropertySheetPageMenu();
treeSection.setText(ExtendedPropertiesProvider.getTextValue(be));
detailsSection.setVisible(true);
}
private void hookPropertySheetPageMenu() {
MenuManager manager = new MenuManager("#PropertiesMenu"); //$NON-NLS-1$
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager) {
AdvancedDetailComposite.this.buildMenu((MenuManager) manager);
}
});
Tree tree = treeViewer.getTree();
Menu menu = manager.createContextMenu(tree);
tree.setMenu(menu);
getDiagramEditor().getSite().registerContextMenu("#PropertiesMenu", manager, treeViewer); //$NON-NLS-1$
// PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart().getSite().registerContextMenu("#PropertiesMenu", manager, treeViewer);
// getPropertySection().getTabbedPropertySheetPage().
// getSite().registerContextMenu("#PropertiesMenu", manager, treeViewer);
}
protected void buildMenu(MenuManager manager) {
EObject selectedElem = getSelectedBaseElement();
createElementProperties(manager, selectedElem);
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
if (selectedElem != null) {
manager.add(createRemoveAction(selectedElem));
}
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
createRootProperties(manager);
}
private void createRootProperties(MenuManager menuManager) {
MenuManager manager = new MenuManager("Add Root Property"); //$NON-NLS-1$
menuManager.add(manager);
createMenuItems(manager, "", businessObject, true); //$NON-NLS-1$
}
private void createElementProperties(MenuManager manager, EObject baseElement) {
if (baseElement != null) {
createMenuItems(manager, Messages.AdvancedDetailComposite_Add_Action, baseElement, false);
}
}
private void createMenuItems(MenuManager manager, String prefix, EObject baseElement, boolean root) {
ItemProviderAdapter itemProviderAdapter = (ItemProviderAdapter) new Bpmn2ItemProviderAdapterFactory().adapt(
baseElement, ItemProviderAdapter.class);
if (itemProviderAdapter==null)
return;
Collection<CommandParameter> desc = (Collection<CommandParameter>) itemProviderAdapter.getNewChildDescriptors(
baseElement, getDiagramEditor().getEditingDomain(), null);
EList<EReference> eAllContainments = baseElement.eClass().getEAllContainments();
List<Action>items = new ArrayList<Action>();
for (CommandParameter command : desc) {
EStructuralFeature feature = (EStructuralFeature) command.feature;
EObject commandValue = null;
if (command.value instanceof BasicFeatureMapEntry) {
BasicFeatureMapEntry entry = (BasicFeatureMapEntry)command.value;
feature = entry.getEStructuralFeature();
commandValue = (EObject)entry.getValue();
}
else if (command.value instanceof EObject)
commandValue = (EObject) command.value;
if (root) {
if (eAllContainments.contains(feature) && isModelObjectEnabled(commandValue.eClass())) {
Object value = baseElement.eGet(feature);
String name = PropertyUtil.deCamelCase(commandValue.eClass().getName());
if (name.equalsIgnoreCase("SingleAssignment") || name.equalsIgnoreCase("Formal SingleAssignment")) { //$NON-NLS-1$ //$NON-NLS-2$
if (feature.getName() != null && !feature.getName().isEmpty()) {
String featureName = PropertyUtil.deCamelCase(' ' + feature.getName());
featureName = featureName.substring(0, 1).toUpperCase() + featureName.substring(1);
name = featureName + ' ' + name;
}
}
Action item = createMenuItemFor(prefix + name, baseElement, (EReference) feature, command.value);
item.setEnabled(value == null || value instanceof EList);
items.add(item);
}
} else {
if (eAllContainments.contains(feature) && isModelObjectEnabled(baseElement.eClass(), feature) &&
isModelObjectEnabled(commandValue.eClass())) {
Object value = baseElement.eGet(feature);
String name = PropertyUtil.deCamelCase(commandValue.eClass().getName());
if (name.equalsIgnoreCase("SingleAssignment") || name.equalsIgnoreCase("Formal SingleAssignment")) { //$NON-NLS-1$ //$NON-NLS-2$
if (feature.getName() != null && !feature.getName().isEmpty()) {
String featureName = PropertyUtil.deCamelCase(' ' + feature.getName());
featureName = featureName.substring(0, 1).toUpperCase() + featureName.substring(1);
name = featureName + ' ' + name;
}
}
Action item = createMenuItemFor(prefix + name, baseElement, (EReference) feature, command.value);
item.setEnabled(value == null || value instanceof EList);
items.add(item);
}
}
}
Collections.sort(items, new Comparator<Action>() {
@Override
public int compare(Action a1, Action a2) {
return a1.getText().compareToIgnoreCase(a2.getText());
}
});
for (Action a : items)
manager.add(a);
}
private Action createMenuItemFor(String prefix, final EObject baseElement, final EReference eReference,
final Object value) {
return new Action(prefix) {
@Override
public void run() {
TransactionalEditingDomain domain = getDiagramEditor().getEditingDomain();
domain.getCommandStack().execute(new RecordingCommand(domain) {
@Override
protected void doExecute() {
createNewProperty(baseElement, eReference);
}
private void createNewProperty(final EObject baseElement, final EReference eReference) {
Object eGet = baseElement.eGet(eReference);
if (value instanceof BaseElement) {
BaseElement e = ((BaseElement) value);
if (e.getId() == null) {
ModelUtil.setID(e,baseElement.eResource());
}
}
if (eGet instanceof EList) {
((EList) eGet).add(value);
} else {
baseElement.eSet(eReference, value);
}
treeViewer.refresh(true);
}
});
}
};
}
private EObject getSelectedBaseElement() {
ISelection selection = treeViewer.getSelection();
EObject baseElement = null;
if (selection instanceof IStructuredSelection) {
Object firstElement = ((IStructuredSelection) selection).getFirstElement();
if (firstElement instanceof EObject) {
baseElement = (EObject) firstElement;
}
}
return baseElement;
}
private Action createRemoveAction(final EObject baseElement) {
return new Action(Messages.AdvancedDetailComposite_Remove_Action) {
@SuppressWarnings("restriction")
@Override
public void run() {
if (baseElement == null) {
treeViewer.refresh(true);
return;
}
final EObject container = baseElement.eContainer();
final Object eGet = container.eGet(baseElement.eContainingFeature());
TransactionalEditingDomain domain = getDiagramEditor().getEditingDomain();
domain.getCommandStack().execute(new RecordingCommand(domain) {
@SuppressWarnings("rawtypes")
@Override
protected void doExecute() {
List<PictogramElement> pictogramElements = GraphitiUi.getLinkService().getPictogramElements(
getDiagramEditor().getDiagramTypeProvider().getDiagram(), baseElement);
if (eGet instanceof EList) {
((EList) eGet).remove(baseElement);
} else {
container.eUnset(baseElement.eContainingFeature());
}
for (PictogramElement pictogramElement : pictogramElements) {
// TODO: check if we need to update the diagram manually or will the autoupdate do
// it
// UpdateContext context = new UpdateContext(pictogramElement);
// IUpdateFeature updateFeature = diagramEditor.getDiagramTypeProvider()
// .getFeatureProvider().getUpdateFeature(context);
// context.putProperty("deleted", baseElement);
// if (updateFeature != null) {
// updateFeature.update(context);
// }
pictogramElement.getLink().getBusinessObjects().clear();
}
treeViewer.refresh(true);
}
});
}
};
}
}