/*******************************************************************************
* 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.editor;
import java.io.File;
import java.util.Arrays;
import org.eclipse.bpmn2.modeler.core.builder.BPMN2Builder;
import org.eclipse.bpmn2.modeler.core.merrimac.dialogs.ObjectEditingDialog;
import org.eclipse.bpmn2.modeler.core.model.ModelHandlerLocator;
import org.eclipse.bpmn2.modeler.core.preferences.Bpmn2Preferences;
import org.eclipse.bpmn2.modeler.core.utils.ErrorUtils;
import org.eclipse.bpmn2.modeler.core.utils.FileUtils;
import org.eclipse.bpmn2.modeler.core.utils.MarkerUtils;
import org.eclipse.bpmn2.modeler.core.utils.ModelUtil;
import org.eclipse.bpmn2.modeler.core.validation.BPMN2ProjectValidator;
import org.eclipse.bpmn2.modeler.core.validation.BPMN2ValidationStatusLoader;
import org.eclipse.bpmn2.modeler.ui.Activator;
import org.eclipse.bpmn2.modeler.ui.Bpmn2DiagramEditorInput;
import org.eclipse.bpmn2.modeler.ui.util.PropertyUtil;
import org.eclipse.bpmn2.util.Bpmn2ResourceImpl;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.ui.editor.DiagramBehavior;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
public class BPMN2Editor extends DefaultBPMN2Editor implements IGotoMarker {
public static final String EDITOR_ID = "org.eclipse.bpmn2.modeler.ui.bpmn2editor"; //$NON-NLS-1$
private static BPMN2Editor activeEditor;
protected BPMN2MultiPageEditor multipageEditor;
private IPartListener2 selectionListener;
private IWorkbenchListener workbenchListener;
private IResourceChangeListener markerChangeListener;
private boolean workbenchShutdown = false;
public BPMN2Editor(BPMN2MultiPageEditor mpe) {
multipageEditor = mpe;
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
if (input instanceof IFileEditorInput) {
BPMN2Builder.INSTANCE.loadExtensions( ((IFileEditorInput)input).getFile().getProject() );
}
// This needs to happen very early because setActiveEditor will try to
// determine the TargetRuntime from the EditorInput.
setActiveEditor(this);
if (this.getDiagramBehavior()==null) {
super.init(site, input);
// add a listener so we get notified if the workbench is shutting down.
// in this case we don't want to delete the temp file!
addWorkbenchListener();
addSelectionListener();
addMarkerChangeListener();
}
else if (input instanceof Bpmn2DiagramEditorInput) {
bpmnDiagram = ((Bpmn2DiagramEditorInput)input).getBpmnDiagram();
if (bpmnDiagram!=null) {
setBpmnDiagram(bpmnDiagram);
}
}
}
@Override
protected EditorInputHelper getInputHelper() {
return new ExtendedEditorInputHelper();
}
protected void loadMarkers() {
if (getModelFile()!=null) {
// read in the markers
BPMN2ValidationStatusLoader vsl = new BPMN2ValidationStatusLoader(this);
try {
vsl.load(Arrays.asList(getModelFile().findMarkers(null, true, IResource.DEPTH_ZERO)));
} catch (CoreException e) {
Activator.logStatus(e.getStatus());
}
}
}
@Override
protected DiagramBehavior createDiagramBehavior() {
DiagramBehavior diagramBehavior = new BPMN2EditorDiagramBehavior(this);
return diagramBehavior;
}
public BPMN2MultiPageEditor getMultipageEditor() {
return multipageEditor;
}
public static BPMN2Editor getActiveEditor() {
return activeEditor;
}
protected void setActiveEditor(BPMN2Editor editor) {
activeEditor = editor;
if (activeEditor!=null) {
Bpmn2Preferences.setActiveProject(activeEditor.getProject());
}
}
@SuppressWarnings("rawtypes")
@Override
public Object getAdapter(Class required) {
if (required == StructuredTextEditor.class) {
// ugly hack to disable selection in Property Viewer while source viewer is active
if (multipageEditor.getActiveEditor() == multipageEditor.getSourceViewer())
return multipageEditor.getSourceViewer();
}
return super.getAdapter(required);
}
@Override
public void gotoMarker(IMarker marker) {
ResourceSet rs = getEditingDomain().getResourceSet();
EObject target = MarkerUtils.getTargetObject(rs, marker);
if (target == null) {
return;
}
IFeatureProvider fp = getDiagramTypeProvider().getFeatureProvider();
PictogramElement pe = MarkerUtils.getContainerShape(fp, marker);
if (pe!=null)
selectPictogramElements(new PictogramElement[] {pe});
if (pe == null || PropertyUtil.getPropertySheetView() == null) {
ObjectEditingDialog dialog = new ObjectEditingDialog(this, target);
ObjectEditingDialog.openWithTransaction(dialog);
}
}
@Override
public void doSave(IProgressMonitor monitor) {
super.doSave(monitor);
Resource resource = getResourceSet().getResource(modelUri, false);
BPMN2ProjectValidator.validateOnSave(resource, monitor);
}
@Override
public void doSaveAs() {
IFile oldFile = getModelFile();
SaveAsDialog saveAsDialog = new SaveAsDialog(getSite().getShell());
saveAsDialog.setOriginalFile(oldFile);
saveAsDialog.create();
if (saveAsDialog.open() == SaveAsDialog.CANCEL) {
return;
}
IPath newFilePath = saveAsDialog.getResult();
if (newFilePath == null){
return;
}
IFile newFile = ResourcesPlugin.getWorkspace().getRoot().getFile(newFilePath);
IWorkbenchPage page = getSite().getPage();
try {
// if new file exists, close its editor (if open) and delete the existing file
if (newFile.exists()) {
IEditorPart editorPart = ResourceUtil.findEditor(page, newFile);
if (editorPart!=null)
page.closeEditor(editorPart, false);
newFile.delete(true, null);
}
// make a copy
oldFile.copy(newFilePath, true, null);
} catch (CoreException e) {
showErrorDialogWithLogging(e);
return;
}
// change the Resource URI and save it to the new file
URI newURI = URI.createPlatformResourceURI(newFile.getFullPath().toString(), true);
handleResourceMoved(bpmnResource,newURI);
doSave(null);
}
public void closeEditor() {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
boolean closed = getSite().getPage().closeEditor(BPMN2Editor.this, false);
if (!closed){
// If close editor fails, try again with explicit editorpart
// of the old file
IFile oldFile = ResourcesPlugin.getWorkspace().getRoot().getFile(getModelPath());
IEditorPart editorPart = ResourceUtil.findEditor(getSite().getPage(), oldFile);
closed = getSite().getPage().closeEditor(editorPart, false);
}
}
});
}
// Show error dialog and log the error
private void showErrorDialogWithLogging(Exception e) {
Status status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e);
ErrorUtils.showErrorWithLogging(status);
}
private void addWorkbenchListener() {
if (workbenchListener==null) {
workbenchListener = new IWorkbenchListener() {
@Override
public boolean preShutdown(IWorkbench workbench, boolean forced) {
workbenchShutdown = true;
return true;
}
@Override
public void postShutdown(IWorkbench workbench) {
}
};
PlatformUI.getWorkbench().addWorkbenchListener(workbenchListener);
}
}
private void removeWorkbenchListener() {
if (workbenchListener!=null) {
PlatformUI.getWorkbench().removeWorkbenchListener(workbenchListener);
workbenchListener = null;
}
}
private void addSelectionListener() {
if (selectionListener == null) {
IWorkbenchPage page = getSite().getPage();
selectionListener = new IPartListener2() {
public void partActivated(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if (part instanceof BPMN2MultiPageEditor) {
BPMN2MultiPageEditor mpe = (BPMN2MultiPageEditor)part;
setActiveEditor(mpe.getDesignEditor());
}
}
@Override
public void partBroughtToTop(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if (part instanceof BPMN2MultiPageEditor) {
BPMN2MultiPageEditor mpe = (BPMN2MultiPageEditor)part;
setActiveEditor(mpe.getDesignEditor());
}
}
@Override
public void partClosed(IWorkbenchPartReference partRef) {
}
@Override
public void partDeactivated(IWorkbenchPartReference partRef) {
}
@Override
public void partOpened(IWorkbenchPartReference partRef) {
}
@Override
public void partHidden(IWorkbenchPartReference partRef) {
}
@Override
public void partVisible(IWorkbenchPartReference partRef) {
}
@Override
public void partInputChanged(IWorkbenchPartReference partRef) {
}
};
page.addPartListener(selectionListener);
}
}
private void removeSelectionListener() {
if (selectionListener!=null) {
getSite().getPage().removePartListener(selectionListener);
selectionListener = null;
}
}
private void addMarkerChangeListener() {
if (getModelFile()!=null) {
if (markerChangeListener==null) {
markerChangeListener = new BPMN2MarkerChangeListener(this);
getModelFile().getWorkspace().addResourceChangeListener(markerChangeListener, IResourceChangeEvent.POST_BUILD);
}
}
}
private void removeMarkerChangeListener() {
if (markerChangeListener!=null) {
getModelFile().getWorkspace().removeResourceChangeListener(markerChangeListener);
markerChangeListener = null;
}
}
////////////////////////////////////////////////////////////////////////////////
// WorkspaceSynchronizer handlers called from delegate
////////////////////////////////////////////////////////////////////////////////
public boolean handleResourceChanged(Resource resource) {
if (resource==bpmnResource) {
URI newURI = resource.getURI();
URI modelUri = getModelUri();
Bpmn2Preferences preferences = getPreferences();
if (!modelUri.equals(newURI)) {
ModelHandlerLocator.remove(modelUri);
modelUri = newURI;
if (preferences!=null) {
preferences.removePreferenceChangeListener(this);
preferences.dispose();
preferences = null;
}
targetRuntime = null;
modelHandler = ModelHandlerLocator.createModelHandler(modelUri, (Bpmn2ResourceImpl)resource);
ModelHandlerLocator.put(diagramUri, modelHandler);
Bpmn2DiagramEditorInput input = (Bpmn2DiagramEditorInput)getEditorInput();
input.updateUri(newURI);
multipageEditor.setInput(input);
}
}
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (getEditorInput()!=null) {
updateDirtyState();
refreshTitle();
}
}
});
return true;
}
public boolean handleResourceDeleted(Resource resource) {
closeEditor();
return true;
}
public boolean handleResourceMoved(Resource resource, URI newURI) {
URI oldURI = resource.getURI();
// The XML loader uses a lazy reference loading: references to internal objects
// are initialized as proxies until first accessed (with eGet()).
// Before we change the URI, make sure all references are resolved
// otherwise the proxy URI (of unresolved references) will still be the old one.
TreeIterator<EObject> iter = resource.getAllContents();
while (iter.hasNext()) {
EObject o = iter.next();
for (EReference r : o.eClass().getEAllReferences()) {
// the eGet() will handle proxy resolving
o.eGet(r);
}
}
resource.setURI(newURI);
if (resource == bpmnResource) {
ModelHandlerLocator.remove(modelUri);
modelUri = newURI;
if (preferences!=null) {
preferences.removePreferenceChangeListener(this);
preferences.dispose();
preferences = null;
}
targetRuntime = null;
modelHandler = ModelHandlerLocator.createModelHandler(modelUri, (Bpmn2ResourceImpl)resource);
ModelHandlerLocator.put(diagramUri, modelHandler);
Bpmn2DiagramEditorInput input = (Bpmn2DiagramEditorInput)getEditorInput();
input.updateUri(newURI);
multipageEditor.setInput(input);
handleResourceChanged(resource);
}
else if (diagramUri.equals(oldURI)) {
ModelHandlerLocator.remove(diagramUri);
diagramUri = newURI;
ModelHandlerLocator.put(diagramUri, modelHandler);
}
return true;
}
@Override
public void dispose() {
// clear ID mapping tables if no more instances of editor are active
int instances = 0;
IWorkbenchPage[] pages = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getPages();
for (IWorkbenchPage p : pages) {
IEditorReference[] refs = p.findEditors(null, EDITOR_ID, IWorkbenchPage.MATCH_ID);
for (IEditorReference r : refs) {
if (r.getEditor(false) instanceof BPMN2MultiPageEditor) {
if (((BPMN2MultiPageEditor)r.getEditor(false)).designEditor != this)
++instances;
}
}
}
BPMN2Editor otherEditor = findOpenEditor(this, getEditorInput());
if (otherEditor==null && diagramUri != null) {
// we can delete the Graphiti Diagram file if there are no other
// editor windows open for this BPMN2 file.
File diagramFile = new File(diagramUri.toFileString());
if (diagramFile.exists()) {
try {
diagramFile.delete();
}
catch (Exception e) {
}
}
}
if (modelHandler != null) {
// TODO have to clear IDs in BasicBPMN2Editor?
ModelUtil.clearIDs(modelHandler.getResource(), instances==0);
}
removeSelectionListener();
if (instances==0)
setActiveEditor(null);
super.dispose();
// get rid of temp files and folders, but NOT if the workbench is being shut down.
// when the workbench is restarted, we need to have those temp files around!
if (modelUri != null && !workbenchShutdown) {
if (FileUtils.isTempFile(modelUri)) {
FileUtils.deleteTempFile(modelUri);
}
}
removeWorkbenchListener();
removeMarkerChangeListener();
}
public static BPMN2Editor findOpenEditor(IEditorPart newEditor, IEditorInput newInput) {
if (newEditor!=null && newInput!=null) {
IWorkbenchPage[] pages = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getPages();
for (IWorkbenchPage page : pages) {
IEditorReference[] otherEditors = page.findEditors(newInput, null, IWorkbenchPage.MATCH_INPUT);
for (IEditorReference ref : otherEditors) {
IEditorPart part = ref.getEditor(true);
if (part instanceof BPMN2MultiPageEditor) {
BPMN2Editor otherEditor = ((BPMN2MultiPageEditor)part).getDesignEditor();
if (otherEditor!=newEditor) {
return otherEditor;
}
}
else if (part instanceof BPMN2Editor) {
BPMN2Editor otherEditor = (BPMN2Editor)part;
if (otherEditor!=newEditor) {
return otherEditor;
}
}
}
}
}
return null;
}
/**
* input helper class for the BPMN2Editor, handles IDE specific functionality
*
* @author Flavio Donz�
*/
public class ExtendedEditorInputHelper extends EditorInputHelper {
/** current open BPMNEditor with the same editor input */
private BPMN2Editor otherEditor;
@Override
public void preSetInput(IEditorInput input, DefaultBPMN2Editor editor) {
input = recreateInput(input, editor);
// Check if this is a New Editor Window for an already open editor
otherEditor = BPMN2Editor.findOpenEditor(editor,input);
ResourceSet resourceSet = initializeResourceSet(input, editor);
// Now create the BPMN2 model resource, or reuse the one from the already open editor.
if (otherEditor==null) {
editor.bpmnResource = createBPMN2Resource(editor, resourceSet);
}
else {
editor.bpmnResource = otherEditor.bpmnResource;
}
}
@Override
public void postSetInput(IEditorInput input, DefaultBPMN2Editor editor) {
super.postSetInput(input, editor);
// Load error markers
((BPMN2Editor) editor).loadMarkers();
}
@Override
protected void importDiagram(IEditorInput input, DefaultBPMN2Editor editor) {
if (otherEditor==null) {
super.importDiagram(input, editor);
}
}
}
}