/*******************************************************************************
* Copyright (c) 2010-2015 Henshin developers. 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:
* TU Berlin, University of Luxembourg, SES S.A.
*******************************************************************************/
package de.tub.tfs.muvitor.ui;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
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.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartFactory;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.KeyStroke;
import org.eclipse.gef.TreeEditPart;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CommandStackListener;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.actions.DeleteAction;
import org.eclipse.gef.ui.actions.DirectEditAction;
import org.eclipse.gef.ui.actions.GEFActionConstants;
import org.eclipse.gef.ui.actions.PrintAction;
import org.eclipse.gef.ui.actions.RedoAction;
import org.eclipse.gef.ui.actions.SaveAction;
import org.eclipse.gef.ui.actions.SelectionAction;
import org.eclipse.gef.ui.actions.UndoAction;
import org.eclipse.gef.ui.actions.UpdateAction;
import org.eclipse.gef.ui.actions.WorkbenchPartAction;
import org.eclipse.gef.ui.parts.SelectionSynchronizer;
import org.eclipse.gef.ui.parts.TreeViewer;
import org.eclipse.gef.ui.properties.UndoablePropertySheetEntry;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IActionBars;
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.IMemento;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IPersistableEditor;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.IPerspectiveFactory;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.presentations.IStackPresentationSite;
import org.eclipse.ui.views.properties.IPropertySheetEntry;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.PropertySheetPage;
import org.eclipse.ui.views.properties.PropertySheetSorter;
import de.tub.tfs.muvitor.actions.ExportViewerImageAction;
import de.tub.tfs.muvitor.actions.GenericGraphLayoutAction;
import de.tub.tfs.muvitor.actions.GenericGraphLayoutActionZEST;
import de.tub.tfs.muvitor.actions.MoveNodeAction;
import de.tub.tfs.muvitor.actions.MuvitorActionBarContributor;
import de.tub.tfs.muvitor.actions.MuvitorAlignmentAction;
import de.tub.tfs.muvitor.actions.MuvitorToggleGridAction;
import de.tub.tfs.muvitor.actions.MuvitorToggleRulerVisibilityAction;
import de.tub.tfs.muvitor.actions.RevertAction;
import de.tub.tfs.muvitor.actions.TrimViewerAction;
import de.tub.tfs.muvitor.ui.MuvitorPage.MultiViewerPageViewer;
import de.tub.tfs.muvitor.ui.utils.EMFModelManager;
import de.tub.tfs.muvitor.ui.utils.MuvitorNotifierService;
import de.tub.tfs.muvitor.ui.utils.MuvitorPerspective;
import de.tub.tfs.muvitor.ui.utils.PartListenerAdapter;
import de.tub.tfs.muvitor.ui.utils.SWTResourceManager;
import de.tub.tfs.muvitor.ui.utils.ViewRegistry;
import de.tub.tfs.muvitor.ui.utils.test.EditorJob;
/**
* <p>
* This is a rich-featured abstract implementation of an {@link EditorPart} with
* a GEF {@link TreeViewer}. Its purpose is to provide many generic features
* available to GEF editors, or at least to make them easy to use by editor
* programmers. Programmers only need to configure the editor by passing some
* information by implementing the abstract methods.
* </p>
*
* <p>
* Prerequisites:
* <ul>
* <li>As extensions in the plugin.xml there have to be defined <b>exactly</b>
* one of each:
* <ul>
* <li>"org.eclipse.ui.editors", must be pointing to the concrete subclass. This
* must have a unique file extension, which will be used for loading from and
* saving to EMF files.
* <li>"org.eclipse.ui.perspectives", must be pointing to a
* {@link IPerspectiveFactory} that describes the perspective for this editor.
* </ul>
* </ul>
* </p>
*
* <p>
* When instantiated (and in {@link #createPartControl(Composite)}) this class
* does the following:
* <ol>
* <li>It creates a {@link DefaultEditDomain} and sets itself as the
* {@link EditorPart}.
* <li>Looks up the perspective defined for the plugin and opens it.
* <li>A {@link TreeViewer} is created.
* <li>The {@link TreeViewer}'s control is registered as resource user in
* {@link SWTResourceManager} so that we can use this to manage color, fonts,
* and images. These will then be automatically disposed when the editor gets
* closed.
* <li>Sets the TreeViewer in the EditDomain and listens to selection changes on
* the workbench page. The TreeViewer is set as selection provider for this
* editor.
* <li>Installs a {@link SelectionSynchronizer} on the {@link TreeViewer}.
* <li> {@link TreeViewer} is set as {@link ISelectionProvider} for this
* {@link IEditorSite}.
* <li>Sets the edit part factory, contents, and context menu for the TreeViewer
* determined by abstract methods.
* <li>Sets a standard {@link KeyHandler} for the {@link TreeViewer}.
* </ol>
* </p>
*
* <p>
* Additionally it provides the following features:
* <ul>
* <li>Errors and Exceptions should be logged for the plugin using
* {@link MuvitorActivator#logError(String, Exception)}.
* <li>Some default GEF actions (also as RetargetAction handlers) and useful
* generic Muvitor actions are created and registered by default. See
* {@link #createActions()} for details.
* <li>The {@link CommandStack} of the {@link EditDomain} is watched. When it
* changes all actions will be updated. <br>
* <li>The {@link TreeViewer} is watched for selection changes. When such a
* change occurs all actions are updated. <br>
* <li>When {@link #firePropertyChange(int)} occurs all actions are updated. <br>
* By default, this is done for {@link SaveAction}, as it is intended to react
* on firing of {@link IEditorPart#PROP_DIRTY}.
* <li>When the {@link CommandStack} is changed the editor checks its own dirty
* state and fires {@link IEditorPart#PROP_DIRTY} if it changes.
* <li>When closed, the editor closes the editor's perspective if no other
* editor with the ID declared in plugin.xml is active.
* <li>With {@link IDUtil#getIDForModel(EObject)} the passed model will be
* registered with an unique ID that is used with {@link MuvitorPageBookView}s
* to tell them with their secondary ID which model to show. These views make
* use of {@link IDUtil} to resolve the ID to the model. <br>
* It is expected that {@link MuvitorPageBookView}s are opened from within
* {@link EditPart}s or actions via a call
* {@link MuvitorTreeEditor#showView(EObject)}. See
* {@link #registerViewID(EClass, String)}.
* </ul>
* <li>The editor will hide a {@link MuvitorPageBookView} automatically when its
* model has been deleted (i.e. model.eResource() == <code>null</code>).
* <li>When being disposed the editor stores information (IDs) about the opened
* views to the plugin's preferences to reopen them when the editor is started
* again. </ul>
* </p>
*
* <p>
* This class offers several static methods to open and close
* {@link MuvitorPageBookView}s: {@link #showView(EObject)} and
* {@link #closeViewsShowing(EObject)} for closing views showing a specific
* model and {@link #closeViews(MuvitorTreeEditor)} for closing all views
* showing a model whose root container is this editor's model root.
* </p>
*
* <p>
* The following methods have to be implemented by subclasses. See their
* documentation for useful hints.
* <ul>
* <li> {@link #createDefaultModel()}: The default model that will be used if
* loading from a file fails.
* <li> {@link #createCustomActions()}: Register your own actions here.
* <li> {@link #createContextMenuProvider(TreeViewer)}: The
* {@link ContextMenuProviderWithActionRegistry} for the {@link TreeViewer}. By
* default, it conveniently populates the context menu with many standard
* actions but is meant to be subclassed.
* <li> {@link #createTreeEditPartFactory()}: A suitable {@link EditPartFactory}
* for the model elements.
* <li>{@link #setupKeyHandler(KeyHandler)}: Define additional key shortcuts for
* actions here. May do nothing.
* </ul>
* </p>
*
* @author Tony Modica
*/
public abstract class MuvitorTreeEditor extends EditorPart implements
CommandStackListener, ISelectionListener, IPersistableEditor,
IGotoMarker {
/**
* This special TreeViewer is needed to allow the {@link EditPart}s to
* access the editor.
*/
public final class EditorTreeViewer extends TreeViewer {
/**
* @return The editor that hosts this tree viewer.
*/
public MuvitorTreeEditor getEditor() {
return MuvitorTreeEditor.this;
}
}
/**
* The file extension this editor reacts on as specified in plugin.xml
*/
static public final String fileExtension = MuvitorActivator
.getUniqueExtensionAttributeValue("org.eclipse.ui.editors",
"extensions");
/**
* Some key constants for the memento.
*/
private static final String MODELURIFRAGMENT_KEY = "uriFragment_key";;
private static final String RESOURCE_URI = "resourceURI";
/**
* Similar to {@link #closeViewShowing(EObject)} this method closes all
* views showing an EObject that belongs to the specified editor, according
* to {@link IDUtil#getHostEditor(EObject)}.
*
* @param editor
* the editor whose views should be closed
* @return a list of the EObjects that were shown in the closed views
*/
static public final ArrayList<EObject> closeViews(
final MuvitorTreeEditor editor) {
final ArrayList<EObject> models = new ArrayList<EObject>();
if (PlatformUI.getWorkbench().getActiveWorkbenchWindow() != null) {
final IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
if (page != null) {
// for (final IViewReference viewRef : page.getViewReferences())
// {
// final IViewPart view = viewRef.getView(false);
for (IViewReference viewRef : page.getViewReferences()) {
IViewPart view = viewRef.getView(false);
if (view instanceof MuvitorPageBookView) {
final EObject model = ((MuvitorPageBookView) view)
.getModel();
if (IDUtil.getHostEditor(model) == editor) {
models.add(model);
//page.hideView(viewRef);
}
}
}
}
}
return models;
}
/**
* Similar to {@link #closeViewShowing(EObject)} this method closes all
* views showing an EObject that belongs to the specified editor, according
* to {@link IDUtil#getHostEditor(EObject)}.
*
* @param editor
* the editor whose views should be closed
* @return a list of the EObjects that were shown in the closed views
*/
public final void cleanUp() {
final ArrayList<EObject> models = new ArrayList<EObject>();
if (PlatformUI.getWorkbench().getActiveWorkbenchWindow() != null) {
final IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
if (page != null) {
// for (final IViewReference viewRef : page.getViewReferences())
// {
// final IViewPart view = viewRef.getView(false);
for (IViewReference viewRef : page.getViewReferences()) {
IViewPart view = viewRef.getView(false);
if (view instanceof MuvitorPageBookView) {
page.hideView(viewRef);
}
}
}
}
modelManager.cleanUp();
this.modelManager = EMFModelManager.createModelManager(fileExtension);
}
/**
* This is Muvitor's main method for closing a view in the workbench showing
* a specific EObject. This is the view that has been opened with
* {@link #showView(EObject)} for this model.
*/
static public final void closeViewShowing(final EObject model) {
final IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
if (page != null) {
final IViewReference[] viewRefs = page.getViewReferences();
for (final IViewReference viewRef : viewRefs) {
final IViewPart view = viewRef.getView(false);
if (view instanceof MuvitorPageBookView) {
final MuvitorPageBookView mpbview = (MuvitorPageBookView) view;
if (mpbview.getModel() == model) {
page.hideView(viewRef);
return;
}
}
}
}
}
/**
* Associate a class of models with the ID of a {@link MuvitorPageBookView}
* that has been registered in plugin.xml.
*
* @param modelClass
* a class of EObjects
* @param viewID
* the ID of a view to show the class of EObjects
* @see #showView(EObject)
*/
static public final void registerViewID(final EClass eClass,
final String viewID) {
ViewRegistry.registerViewID(eClass, viewID);
}
/**
* This method does the actual work of opening a {@link MuvitorPageBookView}
* . An unique ID for the passed model is being created and set as secondary
* ID of the new view. The model for this ID will be resolved y the view via
* {@link IDUtil}.
* <p>
* If a view can be opened successfully it will be returned.
* <p>
* In the special case that Eclipse has just been started and there is not
* an active {@link IWorkbenchPage} the opening of the view will be delayed
* to when the {@link IWorkbenchWindow} is being activated. So, this method
* will return null, even if the view might be opened successfully later.
*
* @param viewId
* The primary ID of the view to be opened. This must correspond
* to an org.eclipse.ui.views entry in plugin.xml
* @param model
* The model to be shown in the view
* @return the view part that has been opened or <code>null</code>
*/
static protected final IViewPart showView(final String viewId,
final EObject model) {
try {
final IWorkbench workbench = PlatformUI.getWorkbench();
final IWorkbenchPage page = workbench.getActiveWorkbenchWindow()
.getActivePage();
// can't open views without active workbench window!
if (page != null) {
/*
* Unmaximize a possible maximized view before opening a new
* view, otherwise Eclipse would not know about the active
* editor
*/
final IWorkbenchPartReference activePartRef = page
.getActivePartReference();
if (activePartRef != null){
final int state = page.getPartState(activePartRef);
if (state == IStackPresentationSite.STATE_MAXIMIZED) {
page.toggleZoom(activePartRef);
}
}
if (model.eResource() == null)
// case: resource of model in not available, e.g.: another
// editor is using it
return null;
return page.showView(viewId, IDUtil.getIDForModel(model),
IWorkbenchPage.VIEW_ACTIVATE);
}
/*
* this happens if Eclipse has been closed with an open editor,
* register a listener on the workbench and show the views when it
* has been opened
*/
workbench.addWindowListener(new IWindowListener() {
@Override
public final void windowActivated(final IWorkbenchWindow window) {
try {
workbench
.getActiveWorkbenchWindow()
.getActivePage()
.showView(viewId, IDUtil.getIDForModel(model),
IWorkbenchPage.VIEW_ACTIVATE);
} catch (final PartInitException e) {
final String message = "View with ID " + viewId
+ " could not be initialized belatedly!";
MuvitorActivator.logError(message, e);
e.printStackTrace();
}
workbench.removeWindowListener(this);
}
@Override
public final void windowClosed(final IWorkbenchWindow window) {
}
@Override
public final void windowDeactivated(
final IWorkbenchWindow window) {
}
@Override
public final void windowOpened(final IWorkbenchWindow window) {
}
});
} catch (final PartInitException e) {
final String message = "View with ID " + viewId
+ " could not be initialized!";
MuvitorActivator.logError(message, e);
}
return null;
}
protected String perspectiveID = null;
/**
* The {@link ActionRegistry} containing the actions created by this editor.
*/
private ActionRegistry actionRegistry;
/**
* The {@link EditDomain} of this editor.
*/
private final DefaultEditDomain editDomain;
/**
* The key handler of this editor, enabling key shortcuts for actions and
* configures the {@link #keyHandler} with standard key strokes like DEL
* (for {@link DeleteAction}) and F2 (for direct edit).
*/
private KeyHandler keyHandler;
/**
* The {@link EMFModelManager} for model persistence operations, using file
* extension specified in plugin.xml.
*/
private EMFModelManager modelManager = EMFModelManager.createModelManager(fileExtension);
/**
* The root element of the model.
*/
protected List<EObject> modelRoots = null;
/**
* When several editor instances are running at teh same time, each has its
* own action instances. This listener takes care of cleaning and
* repopulating the toolbar with this editor instance's actions that have
* been registered via {@link #registerActionOnToolBar(IAction)}.
*/
final private IPartListener2 partListener = new PartListenerAdapter() {
@Override
public final void partActivated(final IWorkbenchPartReference partRef) {
final IWorkbenchPart activatedPart = partRef.getPart(false);
if (!(activatedPart instanceof MuvitorTreeEditor)) {
return;
}
final IToolBarManager toolbarmgr = getEditorSite().getActionBars()
.getToolBarManager();
if (activatedPart == MuvitorTreeEditor.this
&& toolbarActions.size() > 0
&& !contains(toolbarmgr, toolbarActions.get(0))) {
/*
* this editor has been activated or one of its views (which
* brought the editor to top) and this editor's toolbar actions
* are not present in the toolbar, repopulate the toolbar
*/
for (final IAction action : toolbarActions) {
toolbarmgr.add(action);
}
toolbarmgr.update(true);
} else if (activatedPart != MuvitorTreeEditor.this
&& contains(toolbarmgr, toolbarActions.get(0))) {
/*
* another editor has been activated or has been brought to top,
* clean up the toolbar
*/
for (final IAction action : toolbarActions) {
toolbarmgr.remove(action.getId());
}
toolbarmgr.update(true);
}
}
@Override
public final void partBroughtToTop(final IWorkbenchPartReference partRef) {
// a Muvitor view might have brought this editor to top
partActivated(partRef);
}
private final boolean contains(final IToolBarManager toolbarmgr,
final IAction action) {
// find candidate for equality with same ID
final ActionContributionItem item = (ActionContributionItem) toolbarmgr
.find(action.getId());
// test actual object equality
return item != null && item.getAction() == action;
}
};
/**
* A helper flag to decide if the save action should be enabled.
*/
private boolean previousDirtyState;
/**
* The {@link SelectionSynchronizer} managing the selections for this editor
* and the views it can open. The synchronizer relates the selection of 2 or
* more EditPartViewers. This is a special synchronizer for EObjects: if the
* model itself has no counterpart edit part in the viewer, the synchronizer
* traverses the containment hierarchy upwards to find an edit part for some
* parent.
*/
private final SelectionSynchronizer selectionSynchronizer = new SelectionSynchronizer() {
/**
* @param viewer
* the viewer being mapped to
* @param model
* the model element of a part from another viewer
* @return An edit part of an (in)direct parent of the model in the
* viewer or <code>null</code>
*/
private final EditPart convertEObject(final EditPartViewer viewer,
final EObject model) {
if (model == null) {
return null;
}
final Object temp = viewer.getEditPartRegistry().get(model);
if (temp != null) {
return (EditPart) temp;
}
return convertEObject(viewer, model.eContainer());
}
@Override
protected final EditPart convert(final EditPartViewer viewer,
final EditPart part) {
// We assume that we have an EMF model
final Object model = part.getModel();
if (model instanceof EObject) {
return convertEObject(viewer, (EObject) model);
}
/*
* else look if the (possibly existing) parent has an EObject as
* model
*/
return convert(viewer, part.getParent());
}
};
/**
* The {@link TreeViewer} in this editor.
*/
private final TreeViewer treeViewer = new EditorTreeViewer();
/**
* A list to keep track of the actions that are put on the tool bar via
* {@link #registerActionOnToolBar(IAction)}.
*/
final List<IAction> toolbarActions = new ArrayList<IAction>();
protected String markerID = IMarker.PROBLEM;
/**
* The standard constructor creates a {@link DefaultEditDomain} and
* registers itself as a {@link CommandStackListener} on the domains
* {@link CommandStack}.
*
* @throws PartInitException
*/
protected MuvitorTreeEditor() {
editDomain = new DefaultEditDomain(this);
getCommandStack().addCommandStackListener(this);
previousDirtyState = false;
}
/**
* When the command stack changes, the actions are updated. Additionally the
* editor checks if itself has changed its dirty state by comparing its
* <i>previous</i> dirty state with the actual command stack state.
*
* @param event
* the change event
*/
/*
* (non-Javadoc)
*
* @see
* org.eclipse.gef.commands.CommandStackListener#commandStackChanged(java
* .util.EventObject)
*/
@Override
public final void commandStackChanged(final EventObject event) {
if (isDirty() != previousDirtyState) {
previousDirtyState = isDirty();
firePropertyChange(IEditorPart.PROP_DIRTY);
} else {
updateActions();
}
}
/**
* Create an eclipse error marker for the currently edited file on given
* location with specified message. An EObject model's ID will be stored
* with in this marker, allowing {@link MuvitorTreeEditor} to "jump" to the
* error-causing model via {@link #gotoMarker(IMarker)}.
*
* @param type
* specifies the severity of the marker
* @param model
* an EObject model as the problem cause
*
* @param location
* the location of the problem
* @param message
* a message describing the problem
* @return the newly created marker for setting further attributes
*
* @see #gotoMarker(IMarker)
*/
public final void clearAllMarker() {
final IResource resource = ((IFileEditorInput) getEditorInput())
.getFile();
try {
resource.deleteMarkers(markerID, true, IResource.DEPTH_INFINITE);
} catch (final CoreException e) {
throw new RuntimeException(e);
}
}
/**
* Create an eclipse error marker for the currently edited file on given
* location with specified message. An EObject model's ID will be stored
* with in this marker, allowing {@link MuvitorTreeEditor} to "jump" to the
* error-causing model via {@link #gotoMarker(IMarker)}.
*
* @param type
* specifies the severity of the marker
* @param model
* an EObject model as the problem cause
*
* @param location
* the location of the problem
* @param message
* a message describing the problem
* @return the newly created marker for setting further attributes
*
* @see #gotoMarker(IMarker)
*/
public final IMarker createErrorMarker(final int severity,
final EObject model, final String location, final String message) {
final IResource resource = ((IFileEditorInput) getEditorInput())
.getFile();
try {
final IMarker marker = resource.createMarker(markerID);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
marker.setAttribute(IMarker.LOCATION, location);
final XMLResource res = (XMLResource) model.eResource();
marker.setAttribute(IMarker.SOURCE_ID, res.getID(model));
return marker;
} catch (final CoreException e) {
throw new RuntimeException(e);
}
}
/**
* Create an eclipse error marker for the currently edited file on given
* location with specified message. An EObject model's ID will be stored
* with in this marker, allowing {@link MuvitorTreeEditor} to "jump" to the
* error-causing model via {@link #gotoMarker(IMarker)}.
*
* @param model
* an EObject model as the problem cause
*
* @param location
* the location of the problem
* @param message
* a message describing the problem
* @return the newly created marker for setting further attributes
*
* @see #gotoMarker(IMarker)
*/
public final IMarker createErrorMarker(final EObject model,
final String location, final String message) {
final IResource resource = ((IFileEditorInput) getEditorInput())
.getFile();
try {
final IMarker marker = resource.createMarker(markerID);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.LOCATION, location);
final XMLResource res = (XMLResource) model.eResource();
marker.setAttribute(IMarker.SOURCE_ID, res.getID(model));
return marker;
} catch (final CoreException e) {
throw new RuntimeException(e);
}
}
/**
* Realizes the editor by creating its {@link Control}. The tree viewer is
* added to the SelectionSynchronizer, which can be used to keep 2 or more
* EditPartViewers in sync. The viewer is also registered as the
* ISelectionProvider for the Editor's PartSite. The editor again registers
* itself as a listener to the global selection service to react on changes
* in this plugin's {@link MuvitorPageBookView}s.
*
* <P>
* WARNING: This method may or may not be called by the workbench prior to
* {@link #dispose()}.
*
* @param parent
* the parent composite
*
* @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets
* .Composite)
*/
@Override
public final void createPartControl(final Composite parent) {
createTreeViewer(parent);
selectionSynchronizer.addViewer(getTreeViewer());
getSite().setSelectionProvider(getTreeViewer());
getEditDomain().addViewer(getTreeViewer());
getSite().getPage().addSelectionListener(this);
// final Tree c = (Tree) treeViewer.getControl();
//
// // // Create the drag source on the tree
// DragSource ds = new DragSource(c, DND.DROP_MOVE);
// ds.setTransfer(new Transfer[] { TextTransfer.getInstance() });
// ds.addDragListener(new DragSourceAdapter() {
// public void dragSetData(DragSourceEvent event) {
// // Set the data to be the first selected item's text
// event.data = c.getSelection()[0].getText();
// }
// });
}
/**
* Tries to close views for all models of the resource that have possibly
* been opened. Stops listening to the command stack and selection service.
* Closes the perspective if the last editor instance is being closed.
* Subclasses may override but must call super implementation!
*/
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.WorkbenchPart#dispose()
*/
@Override
public void dispose() {
MuvitorNotifierService.clear(this);
closeViews(this);
getCommandStack().removeCommandStackListener(this);
// FIXED do not listen only to tree viewer, but to general
// selection service itself!
getSite().getPage().removeSelectionListener(this);
getSite().getPage().removePartListener(partListener);
getEditDomain().setActiveTool(null);
getActionRegistry().dispose();
getCommandStack().dispose();
// close perspective if the last editor instance is being closed
if (!isAnotherEditorActive()) {
// final IWorkbenchPage page = getSite().getPage();
// final IPerspectiveDescriptor perspective = page.getPerspective();
IWorkbenchPage page = getSite().getPage();
IPerspectiveDescriptor perspective = page.getPerspective();
if (perspective.getId().equals(perspectiveID))
page.closePerspective(perspective, true, false);
String pid = MuvitorActivator.getUniqueExtensionAttributeValue(
"org.eclipse.ui.perspectives", "id");
if (!pid.equals(perspectiveID) && perspective.getId().equals(pid)) {
try {
page.closePerspective(perspective, true, true);
} catch (Exception ex){
try {
page.closePerspective(perspective, false, true);
} catch (Exception ex1){
}
}
}
}
EditorJob.cancelAll();
IDUtil.deregisterEditor(this);
super.dispose();
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.
* IProgressMonitor)
*/
@Override
public final void doSave(final IProgressMonitor monitor) {
try {
final IFile file = ((IFileEditorInput) getEditorInput()).getFile();
if (!file.exists()) {
file.create(new ByteArrayInputStream(new byte[0]), true,
new SubProgressMonitor(monitor, 5));
}
save(file, monitor);
getCommandStack().markSaveLocation();
} catch (final CoreException e) {
ErrorDialog.openError(getSite().getShell(), "Error during save",
"The current model could not be saved!", e.getStatus());
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.EditorPart#doSaveAs()
*/
@Override
public final void doSaveAs() {
final SaveAsDialog dialog = new SaveAsDialog(getSite().getShell());
dialog.setTitle("Save as...");
dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
dialog.open();
final IPath path = dialog.getResult();
if (dialog.getReturnCode() == Window.CANCEL || path == null) {
return;
}
final IFile newFile = ResourcesPlugin.getWorkspace().getRoot()
.getFile(path);
final Job job = new Job("Saving editor model to file") {
@Override
protected IStatus run(final IProgressMonitor monitor) {
try {
monitor.beginTask("Save model to file", 1);
/*
* FIXED: When saving in a different file set new IDs to all
* EObjects in the model! Otherwise, opening several
* "saved as"-files at the same time will confuse the Editor
* due to equal IDs for EObjects in different files.
*/
for (final EObject modelroot : getModelRoots()) {
IDUtil.refreshIDs(modelroot);
}
save(newFile, monitor);
/*
* FIXED: model URI presumably changed, so reregister editor
*/
IDUtil.registerEditor(MuvitorTreeEditor.this);
return Status.OK_STATUS;
} catch (final CoreException e) {
ErrorDialog.openError(getSite().getShell(),
"Error during save",
"The current model could not be saved!",
e.getStatus());
return Status.CANCEL_STATUS;
}
}
};
job.schedule();
try {
job.join();
} catch (final InterruptedException e) {
}
if (job.getResult().isOK()) {
// setInput(new FileEditorInput(newFile))
// do not call the whole setInput with model loading,
// setting the input is enough
getCommandStack().markSaveLocation();
final FileEditorInput newInput = new FileEditorInput(newFile);
super.setInput(newInput);
setPartName(newInput.getFile().getName());
}
}
/**
* Convenience method that collects all problem markers like
* {@link #findProblemMarkers(String, Object)} for which the attribute has
* been set with any value.
*
* @param attrName
* some custom attribute that has been set to a marker
* @return a list of problem markers for the currently opened file for which
* the attribute has been set with any value
*/
public final ArrayList<IMarker> findProblemMarkers(final String attrName) {
return findProblemMarkers(attrName, null);
}
/**
* Helper method to find problem markers with specific attribute values. The
* main purpose is to to allow checking facilities to manage (especially
* delete) problem markers they have created.
*
* @param attrName
* some custom attribute that has been set to a marker
* @param attrValue
* a value for the attrName attribute to be matched; will be
* ignored if <code>null</code>
* @return a list of problem markers for the currently opened file with the
* specified attribute values
*/
public final ArrayList<IMarker> findProblemMarkers(final String attrName,
final Object attrValue) {
final ArrayList<IMarker> markers = new ArrayList<IMarker>();
final IResource resource = ((IFileEditorInput) getEditorInput())
.getFile();
try {
for (final IMarker marker : resource.findMarkers(IMarker.PROBLEM,
false, 1)) {
final Object actualAttrValue = marker.getAttribute(attrName);
// is attribute defined for marker?
if (actualAttrValue != null) {
if (attrValue == null || attrValue.equals(actualAttrValue)) {
markers.add(marker);
}
}
}
} catch (final CoreException e) {
e.printStackTrace();
}
return markers;
}
/**
* <em>IMPORTANT</em> certain requests, such as the property sheet, may be
* made before or after {@link #createPartControl(Composite)} is called. The
* order is unspecified by the Workbench. Subclasses may override but must
* call super implementation!
*
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
@Override
public Object getAdapter(@SuppressWarnings("rawtypes") final Class type) {
if (type == IPropertySheetPage.class) {
/*
* The PropertySheetPage is extended so that a PropertySheetSorter
* that does nothing (compares everything as equal) is being set.
* This way the properties will be displayed in the order they are
* returned by the property sources instead of being sorted
* alphabetically.
*/
final PropertySheetPage page = new PropertySheetPage() {
@Override
public final void createControl(final Composite parent) {
setSorter(new PropertySheetSorter() {
@Override
public int compare(final IPropertySheetEntry entryA,
final IPropertySheetEntry entryB) {
return 0;
}
});
super.createControl(parent);
}
};
// set the PropertySheetPage to use our command stack
page.setRootEntry(new UndoablePropertySheetEntry(getCommandStack()));
return page;
}
if (type == CommandStack.class) {
return getCommandStack();
}
if (type == ActionRegistry.class) {
return getActionRegistry();
}
if (type == EditPart.class && getTreeViewer() != null) {
return getTreeViewer().getRootEditPart();
}
if (type == Widget.class && getTreeViewer() != null) {
return ((TreeEditPart) getTreeViewer().getRootEditPart())
.getWidget();
}
if (type == KeyHandler.class) {
return keyHandler;
}
if (type == SelectionSynchronizer.class) {
return selectionSynchronizer;
}
if (type == IEditorInput.class) {
return getEditorInput();
}
return super.getAdapter(type);
}
/**
* Returns the list of {@link EObject} models that have been loaded by the
* {@link EMFModelManager}. The first of this (see
* {@link #getPrimaryModelRoot()}) will be set as contents for the
* TreeViewer.
*
* @return the modelRoots of this editor
*/
public final List<EObject> getModelRoots() {
return modelRoots;
}
/**
* Returns by default the first model in the list of {@link EObject} models
* that have been loaded by the {@link EMFModelManager}. This will be set as
* contents for the TreeViewer. Subclasses may override.
*
* @return the element of the model roots to be set as the treeviewer's
* contents
* @see #getModelRoots()
*/
public EObject getPrimaryModelRoot() {
return modelRoots.get(0);
}
/**
* This can be used to display messages in this page's status line.
*
* @return The {@link IStatusLineManager} from the {@link IActionBars} of
* this page's {@link IPageSite}
*/
public final IStatusLineManager getStatusLineManager() {
return getEditorSite().getActionBars().getStatusLineManager();
}
/**
* @return The {@link EditorTreeViewer} in this editor.
*/
public final TreeViewer getTreeViewer() {
// ensure that the view IDs are up to date
registerViewIDs();
return treeViewer;
}
/**
* This is Muvitor's main method for opening a {@link MuvitorPageBookView}
* in the workbench displaying a model. It will check if the {@link EClass}
* of the passed model or of one of its ancestors has been registered with a
* view ID via {@link #registerViewID(EClass, String)} and will open a view
* of this type showing the corresponding model. *
* <p>
* If a view can be opened successfully it will be returned.
*
* @param model
* The model to be shown in the view
* @return the view part that has been opened or <code>null</code>
* @see #showView(String, EObject)
*/
static public final IViewPart showView(final EObject model) {
final String viewID = ViewRegistry.getViewID(model.eClass());
if (viewID != null) {
return showView(viewID, model);
}
final EObject parent = model.eContainer();
if (parent != null) {
return showView(parent);
}
final String message = "No view for " + model.eClass().getName()
+ " or indirect container type could be found!";
//MuvitorActivator.logError(message, new IllegalArgumentException());
return null;
}
/**
* This default implementation of {@link IGotoMarker} tries to resolve the
* marker's {@link IMarker#SOURCE_ID} attribute to an EObject model. If such
* a model exists it will be opened via {@link #showView(EObject)} (if
* possible) and set as the selection in the corresponding viewer.
*
* @see org.eclipse.ui.ide.IGotoMarker#gotoMarker(org.eclipse.core.resources.
* IMarker)
*
* @see #createErrorMarker(EObject, String, String)
*/
@Override
public void gotoMarker(final IMarker marker) {
try {
final String sourceID = (String) marker
.getAttribute(IMarker.SOURCE_ID);
final EObject model = ((XMLResource) getPrimaryModelRoot()
.eResource()).getEObject(sourceID);
if (model != null) {
final IViewPart viewPart = showView(model);
if (viewPart != null && viewPart instanceof MuvitorPageBookView) {
final MuvitorPage page = (MuvitorPage) ((MuvitorPageBookView) viewPart)
.getCurrentPage();
for (final MultiViewerPageViewer viewer : page.getViewers()) {
final EditPart part = (EditPart) viewer
.getEditPartRegistry().get(model);
if (part != null) {
viewer.setSelection(new StructuredSelection(part));
return;
}
}
}
}
} catch (final CoreException e) {
MuvitorActivator.logError("Error while accessing problem marker!",
e);
}
}
/**
* Sets the site and input for this editor and opens the unique perspective
* defined in plugin.xml. Subclasses may extend this method but must call
* super implementation!
*
* @see org.eclipse.ui.IEditorPart#init(IEditorSite, IEditorInput)
*/
@Override
public void init(final IEditorSite site, final IEditorInput input) {
setSite(site);
registerViewIDs();
setPerspective();
site.getPage().addPartListener(partListener);
setInput(input);
/*
* Using the active page of the workbench is a workaround for that the
* page layout is corrupted if the perspective is set on
* getSite().getPage()
*/
final IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
if (null != page) {
// open editor perspective
String perspectiveID2;
if (perspectiveID != null) {
perspectiveID2 = perspectiveID;
} else
perspectiveID2 = MuvitorActivator
.getUniqueExtensionAttributeValue(
"org.eclipse.ui.perspectives", "id");
if (null != perspectiveID2) {
final IPerspectiveDescriptor editorPerspective = PlatformUI
.getWorkbench().getPerspectiveRegistry()
.findPerspectiveWithId(perspectiveID2);
if (page.getPerspective() != editorPerspective) {
page.setPerspective(editorPerspective);
}
}
}
}
// to be overwritten by instantiated editors
protected void setPerspective() {
// perspective = new ... (perspective of editor)
return;
}
// method to be overwritten
// register the viewer IDs
protected void registerViewIDs() {
}
/**
* Returns <code>true</code> if the command stack is dirty
*
* @see org.eclipse.ui.ISaveablePart#isDirty()
*/
@Override
public final boolean isDirty() {
return getCommandStack().isDirty();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
*/
@Override
public final boolean isSaveAsAllowed() {
return true;
}
/**
* Restores the views that were opened when Eclipse was closed during an
* editor session if the editor is still able to work on the same file.
* Subclasses may extend but must call super implementation!
*
* @see #saveState(IMemento)
* @see org.eclipse.ui.IPersistableEditor#restoreState(org.eclipse.ui.IMemento)
*/
@Override
public void restoreState(final IMemento memento) {
final XMLResource res = (XMLResource) getPrimaryModelRoot().eResource();
// test if the model opened by this editor instance is the one whose
// element ID has been stored to the memento
if (!res.getURI().toString().equals(memento.getString(RESOURCE_URI))) {
return;
}
}
/**
* This method implements support for the Muvitor's {@link RevertAction}.
* Subclasses may extend but must call super implementation!
*
* Closes all views, resets the actual input, and updates all actions.
*/
public void revertToLastSaved() {
closeViews(this);
getCommandStack().flush();
setInput(getEditorInput());
getTreeViewer().setContents(getPrimaryModelRoot());
updateActions();
}
/**
* Closes all views of this editor. The model URIs for the currently open
* views are stored to the editor's {@link IMemento}. <br>
* To be called, when the editor is being closed. Subclasses may extend but
* must call super implementation!
*
* @see #restoreState(IMemento)
* @see org.eclipse.ui.IPersistable#saveState(org.eclipse.ui.IMemento)
*/
@Override
public void saveState(final IMemento memento) {
// store URI of the model root's resource and of the models whose views
// have been closed
final XMLResource res = (XMLResource) getPrimaryModelRoot().eResource();
if(res == null)
return;
memento.putString(RESOURCE_URI, res.getURI().toString());
for (final EObject model : closeViews(this)) {
// get the real uriFragment, not the unique id
final String uriFragment = IDUtil.getRealURIFragment(model);
memento.createChild(MODELURIFRAGMENT_KEY, uriFragment);
}
}
@Override
public final void selectionChanged(final IWorkbenchPart part,
final ISelection selection) {
updateActions();
/*
* FIXED: a little hack to keep the actions of this editor (possibly)
* enabled when some element is selected in another viewer, the
* workbench will respect the editor actions' calculateEnabled state
* instead of just disabling them just because the editor is not the
* active part any more.
*/
try {
/*
* simulate ((EditorSite) getEditorSite()).activateActionBars(true);
* via reflection because this method is not public API.
*/
getEditorSite().getClass()
.getMethod("activateActionBars", boolean.class)
.invoke(getEditorSite(), Boolean.TRUE);
} catch (final Exception e) {
MuvitorActivator
.logError(
"Reflective method invocation failed due to possible change of internal class 'EditorSite'!",
e);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IWorkbenchPart#setFocus()
*/
@Override
public final void setFocus() {
if (getTreeViewer().getControl() != null)
getTreeViewer().getControl().setFocus();
// I suppose this is needed for "focus follow mouse" as in Linux
updateActions();
getEditorSite().getActionBars().updateActionBars();
}
/**
* Creates and registers some standard GEF actions and some generic Muvitor
* actions for this editor's {@link ActionRegistry}. See the code below for
* details. Instances of universal actions like DeleteAction are shared with
* the graphical {@link MuvitorPage}s of this editor. See there for more
* information about sharing action instances.
*
* <p>
* Standard RetargetActions are declared in the MuvitorActionBarContributor
* for all views of this editor. You may subclass this in the rare case that
* you need own RetargetActions. There is no need to explicitly set handler
* actions that you create here (which will be used if the TreeEditor is the
* active part) for the RetargetActions here because this will be done by
* default in
* {@link MuvitorActionBarContributor#setActiveEditor(IEditorPart)}. This
* will work as long as the handle action has the same ID as the
* RetargetAction it is meant to handle.
* </p>
*
* <p>
* <b>Keep in mind that RetargetAction handles for {@link MuvitorPage}s must
* be set explicitly!</b> See there for more information about registering
* (shared) handles for RetargetActions.
* </p>
*
* @see #createCustomActions()
* @see MuvitorActionBarContributor
* @see MuvitorPage#createCustomActions()
*/
private final void createActions() {
// standard GEF and Eclipse actions
registerAction(new SaveAction(this));
registerAction(new UndoAction(this));
registerAction(new RedoAction(this));
registerAction(new PrintAction(this));
registerAction(new DirectEditAction((IWorkbenchPart) this));
registerAction(new DeleteAction((IWorkbenchPart) this));
registerAction(new RevertAction(this));
// GEF alignment actions
registerActionOnToolBar(new MuvitorAlignmentAction((IWorkbenchPart) this,
PositionConstants.LEFT));
registerActionOnToolBar(new MuvitorAlignmentAction((IWorkbenchPart) this,
PositionConstants.RIGHT));
registerActionOnToolBar(new MuvitorAlignmentAction((IWorkbenchPart) this,
PositionConstants.TOP));
registerActionOnToolBar(new MuvitorAlignmentAction((IWorkbenchPart) this,
PositionConstants.BOTTOM));
registerActionOnToolBar(new MuvitorAlignmentAction((IWorkbenchPart) this,
PositionConstants.CENTER));
registerActionOnToolBar(new MuvitorAlignmentAction((IWorkbenchPart) this,
PositionConstants.MIDDLE));
// some special shared actions for graphical sub views
registerAction(new ExportViewerImageAction(this));
registerAction(new TrimViewerAction(this));
registerActionOnToolBar(new GenericGraphLayoutAction(this));
registerActionOnToolBar(new GenericGraphLayoutActionZEST(this));
registerActionOnToolBar(new MuvitorToggleRulerVisibilityAction(this));
registerActionOnToolBar(new MuvitorToggleGridAction(this));
// shared move node actions for graphical viewers,
// the keystrokes are defined in MuvitorPage.getSharedKeyHandler()
registerAction(new MoveNodeAction(this, MoveNodeAction.LEFT));
registerAction(new MoveNodeAction(this, MoveNodeAction.RIGHT));
registerAction(new MoveNodeAction(this, MoveNodeAction.UP));
registerAction(new MoveNodeAction(this, MoveNodeAction.DOWN));
registerAction(new MoveNodeAction(this, MoveNodeAction.PREC_LEFT));
registerAction(new MoveNodeAction(this, MoveNodeAction.PREC_RIGHT));
registerAction(new MoveNodeAction(this, MoveNodeAction.PREC_UP));
registerAction(new MoveNodeAction(this, MoveNodeAction.PREC_DOWN));
// registerActionOnToolBar(new ToggleAnimationAction());
// create custom actions
createCustomActions();
}
/**
* Creates the tree viewer on the specified <code>Composite</code>. The
* various abstract methods are used to configure the viewer. The model
* provided by {@link #getPrimaryModelRoot()} will be set as contents.
*
* @param parent
* the parent composite
*
* @see #createTreeEditPartFactory()
* @see #createContextMenuProvider(TreeViewer)
* @see #getKeyHandler()
*/
private final void createTreeViewer(final Composite parent) {
getTreeViewer().createControl(parent);
getTreeViewer().getControl().setBackground(
parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
getTreeViewer().getControl().setForeground(
parent.getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND));
// set the contents, edit part factory and context menu for the
// TreeViewer
getTreeViewer().setEditPartFactory(createTreeEditPartFactory());
getTreeViewer().setContents(getPrimaryModelRoot());
final ContextMenuProviderWithActionRegistry provider = createContextMenuProvider(getTreeViewer());
provider.setActionRegistry(getActionRegistry());
getTreeViewer().setContextMenu(provider);
if (isContextMenuRegistered()) {
getEditorSite().registerContextMenu(provider, getTreeViewer());
}
// configure the key handler
getTreeViewer().setKeyHandler(getKeyHandler());
/*
* register the tree viewer's control to SWTResourceManager so that we
* can use it to manage colors, font etc. and to let the manager dispose
* them when the editor gets closed.
*/
SWTResourceManager.registerResourceUser(getTreeViewer().getControl());
}
/**
* A helper method that looks for other active instances of this editor in
* the site's page.
*
* @return <code>true</code> if another instance of this editor is active
*/
private final boolean isAnotherEditorActive() {
final IEditorReference[] editorRefs = getSite().getPage()
.getEditorReferences();
final String editorID = MuvitorActivator
.getUniqueExtensionAttributeValue("org.eclipse.ui.editors",
"id");
for (final IEditorReference editorRef : editorRefs) {
if (editorRef.getId().equals(editorID)) {
return true;
}
}
return false;
}
/**
* Subclasses must implement to specify the
* {@link ContextMenuProviderWithActionRegistry} that should be used by this
* editor's {@link TreeViewer} and is responsible to show the created
* actions in the context menu.
*
* @param viewer
* The graphical viewer for context menu provider
* @return The {@link ContextMenuProviderWithActionRegistry} for the
* specified {@link TreeViewer}.
*
* @see #createTreeViewer(Composite)
*/
abstract protected ContextMenuProviderWithActionRegistry createContextMenuProvider(
TreeViewer viewer);
/**
* To be implemented by subclasses.
*
* <p>
* To be available for the context menu, tool bar, key binding, or as shared
* action instance for this editor's {@link MuvitorPage}s (see there for
* information about sharing action instances), custom actions have to
* registered here via {@link #registerAction(IAction)}. Alternatively, you
* may use the convenient method {@link #registerActionOnToolBar(IAction)}
* to put an action on a tool bar. <br>
* With this, you will rarely need to create RetargetActions. But if you do,
* use (subclass) {@link MuvitorActionBarContributor} for this purpose and
* check it and {@link #createActions()} for existing actions before.
* </p>
* <p>
* {@link WorkbenchPartAction}s and {@link SelectionAction}s created here
* should get the editor (<code>this</code>) as workbench part in their
* constructor. <br>
* </p>
*
* <p>
* There is no need to set handles that you create here (which will be used
* if the TreeEditor is the active part) for the RetargetActions here
* because this will be done by default in
* {@link MuvitorActionBarContributor#setActiveEditor(IEditorPart)}. This
* will work as long as the handle action has the same ID as the
* RetargetAction it is meant to handle.
* </p>
*
* <p>
* <b>Keep in mind that RetargetAction handles for {@link MuvitorPage}s must
* be set explicitly!</b> See there for more information about registering
* (shared) handles for RetargetActions.
* </p>
*
* @see #createActions
* @see #createContextMenuProvider(TreeViewer)
* @see MuvitorActionBarContributor
*/
abstract protected void createCustomActions();
/**
* Subclasses have to generate a default model in this method. This will be
* called if loading from a file fails. For separate model roots override
* {@link #createDefaultModels()} instead and leave this method
* implementation empty.
*
* @return the default model
*
* @see #setInput(IEditorInput)
*/
abstract protected EObject createDefaultModel();
/**
* By default, MuvitorTreeEditor uses a single model root, which will be
* created in {@link #createDefaultModel()}. But it is capable to handle
* multiple roots
*
* @return
*/
protected List<EObject> createDefaultModels() {
return Collections.singletonList(createDefaultModel());
}
/**
* Subclasses must implement this to specify the {@link EditPartFactory}
* that should be used by this editor's {@link TreeViewer}.
*
* @return The {@link EditPartFactory} for the GEF {@link TreeViewer}.
*/
abstract protected EditPartFactory createTreeEditPartFactory();
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.WorkbenchPart#firePropertyChange(int)
*/
@Override
protected final void firePropertyChange(final int property) {
super.firePropertyChange(property);
updateActions();
}
/**
* Lazily creates, initializes and returns the action registry. The
* keyhandler will be configured with custom key bindings here.
*
* @return the action registry
*
* @see #createActions()
* @see #updateActions()
*/
protected final ActionRegistry getActionRegistry() {
if (actionRegistry == null) {
actionRegistry = new ActionRegistry();
createActions();
// now that actions have been created, configure keyhandler
// add key strokes defined by concrete subclasses
setupKeyHandler(getKeyHandler());
updateActions();
}
return actionRegistry;
}
/**
* @return this editor's command stack
*/
protected final CommandStack getCommandStack() {
return getEditDomain().getCommandStack();
}
/**
* @return this editor's edit domain
*/
protected final DefaultEditDomain getEditDomain() {
return editDomain;
}
/**
* Need to create key handler lazily because accessing the action registry
* creates the actions which needs the editor site etc.
*
* @return the lazily created keyhandler with default key strokes
*/
protected final KeyHandler getKeyHandler() {
if (keyHandler == null) {
keyHandler = new KeyHandler();
keyHandler
.put(KeyStroke.getPressed(SWT.DEL, 127, 0),
getActionRegistry().getAction(
ActionFactory.DELETE.getId()));
keyHandler.put(KeyStroke.getPressed(SWT.F2, 0), getActionRegistry()
.getAction(GEFActionConstants.DIRECT_EDIT));
// HOME/END/PGUP/PGDN run the correspondent MoveNodeActions
keyHandler.put(KeyStroke.getPressed(SWT.HOME, 0),
getActionRegistry().getAction(MoveNodeAction.LEFT));
keyHandler.put(KeyStroke.getPressed(SWT.END, 0),
getActionRegistry().getAction(MoveNodeAction.RIGHT));
keyHandler.put(KeyStroke.getPressed(SWT.PAGE_UP, 0),
getActionRegistry().getAction(MoveNodeAction.UP));
keyHandler.put(KeyStroke.getPressed(SWT.PAGE_DOWN, 0),
getActionRegistry().getAction(MoveNodeAction.DOWN));
// SHIFT + HOME/END/PGUP/PGDN run the correspondent precise
// MoveNodeActions
keyHandler.put(KeyStroke.getPressed(SWT.HOME, SWT.SHIFT),
getActionRegistry().getAction(MoveNodeAction.PREC_LEFT));
keyHandler.put(KeyStroke.getPressed(SWT.END, SWT.SHIFT),
getActionRegistry().getAction(MoveNodeAction.PREC_RIGHT));
keyHandler.put(KeyStroke.getPressed(SWT.PAGE_UP, SWT.SHIFT),
getActionRegistry().getAction(MoveNodeAction.PREC_UP));
keyHandler.put(KeyStroke.getPressed(SWT.PAGE_DOWN, SWT.SHIFT),
getActionRegistry().getAction(MoveNodeAction.PREC_DOWN));
// // run the MoveNodeActions with keypad numbers
// keyHandler.put(KeyStroke.getPressed('4', SWT.KEYPAD_4, 0),
// getActionRegistry().getAction(MoveNodeAction.LEFT));
// keyHandler.put(KeyStroke.getPressed('6', SWT.KEYPAD_6, 0),
// getActionRegistry().getAction(MoveNodeAction.RIGHT));
// keyHandler.put(KeyStroke.getPressed('8', SWT.KEYPAD_8, 0),
// getActionRegistry().getAction(MoveNodeAction.UP));
// keyHandler.put(KeyStroke.getPressed('2', SWT.KEYPAD_2, 0),
// getActionRegistry().getAction(MoveNodeAction.DOWN));
//
// //run the precise MoveNodeActions with SHIFT + keypad numbers
// keyHandler.put(KeyStroke.getPressed('4', SWT.KEYPAD_4, SWT.CTRL),
// getActionRegistry().getAction(MoveNodeAction.PREC_LEFT));
// keyHandler.put(KeyStroke.getPressed('6', SWT.KEYPAD_6, SWT.CTRL),
// getActionRegistry().getAction(MoveNodeAction.PREC_RIGHT));
// keyHandler.put(KeyStroke.getPressed('8', SWT.KEYPAD_8, SWT.CTRL),
// getActionRegistry().getAction(MoveNodeAction.PREC_UP));
// keyHandler.put(KeyStroke.getPressed('2', SWT.KEYPAD_2, SWT.CTRL),
// getActionRegistry().getAction(MoveNodeAction.PREC_DOWN));
}
return keyHandler;
}
/**
* If the context menu should be registered with the workbench override this
* method and return <code>true</code>.
*
* @return Defaults to <code>false</code>.
* @author Winzent Fischer <winzent.fischer@berlin.de>
*/
protected boolean isContextMenuRegistered() {
return false;
}
/**
* Registers the action with the editor's action registry.
*
* <p>
* There is no need to set handles that you register here (which will be
* used if the TreeEditor is the active part) for the RetargetActions here
* because this will be done by default in
* {@link MuvitorActionBarContributor#setActiveEditor(IEditorPart)}. This
* will work as long as the handle action has the same ID as the
* RetargetAction it is meant to handle.
* </p>
*
* <p>
* <b>Keep in mind that RetargetAction handles for {@link MuvitorPage}s must
* be set explicitly!</b> See there for more information about registering
* (shared) handles for RetargetActions.
* </p>
*
* @param action
* the action to be registered in this editor's
* {@link ActionRegistry}
*/
protected final IAction registerAction(final IAction action) {
getActionRegistry().registerAction(action);
return action;
}
/**
* Registers the action with the editor's action registry and puts it on
* this editor's tool bar.
*
* <p>
* With this, you will rarely need to create RetargetActions. But if you do,
* use (subclass) {@link MuvitorActionBarContributor} for this purpose and
* check it and {@link #createActions()} for existing actions before.
* </p>
*
* @param action
* the action to be registered in this editor's
* {@link ActionRegistry} and to be put on the editors tool bar.
*
* @see #registerAction(IAction)
*/
protected final IAction registerActionOnToolBar(final IAction action) {
registerAction(action);
toolbarActions.add(action);
getEditorSite().getActionBars().getToolBarManager().add(action);
return action;
}
/**
* This method saves the model to a file using the {@link EMFModelManager}.
* Subclasses may extend but must call super implementation!
*
* @param file
* The {@link IFile} to save the model to.
* @param progressMonitor
* A progress monitor that could be used to show the saving
* status.
* @throws CoreException
* This exception indicates that something went wrong during
* saving.
*/
protected void save(final IFile file, final IProgressMonitor monitor)
throws CoreException {
try {
modelManager.save(file.getFullPath(),getModelRoots().get(0));
monitor.worked(1);
file.refreshLocal(IResource.DEPTH_ZERO, new SubProgressMonitor(
monitor, 1));
} catch (final FileNotFoundException e) {
MuvitorActivator.logError("Error writing file.", e);
} catch (final IOException e) {
MuvitorActivator.logError("Error writing file.", e);
}
}
/**
* This method will create an {@link EMFModelManager} that handles loading
* and saving to the file the passed {@link IEditorInput} relies on. If
* loading fails (possibly because we created a file with the creation
* wizard) we create a new default model and put it to the file resource.
*
* @see #createDefaultModel()
* @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
*/
@Override
protected void setInput(final IEditorInput input) {
super.setInput(input);
final IFile file = ((IFileEditorInput) input).getFile();
setPartName(file.getName());
setContentDescription(file.getName());
/*
* This must be called before trying to load the model, so that the EMF
* package has been initialized.
*/
final List<EObject> defaultModels = createDefaultModels();
for (final EObject defaultModel : defaultModels) {
defaultModel.eClass().getEPackage();
}
ProgressMonitorDialog dialog = new ProgressMonitorDialog(Display.getCurrent().getActiveShell());
try {
dialog.run(false, true, new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException,
InterruptedException {
int lines = 0;
try {
BufferedReader r = new BufferedReader(new FileReader(file.getRawLocation().toFile()));
while (r.ready()){
r.readLine();
lines++;
}
} catch (FileNotFoundException e) {
lines = -1;
} catch (IOException e) {
lines = -1;
}
modelManager.setMonitor(monitor);
monitor.beginTask("loading Ecore Model", lines);
Thread t = new Thread(){
@Override
public void run() {
modelRoots = new ArrayList<EObject>(modelManager.load(
file.getFullPath(), defaultModels));
super.run();
}
};
t.start();
while (t.isAlive()){
if (!Display.getCurrent().readAndDispatch())
Thread.sleep(20);
}
if (modelRoots == null || modelRoots.isEmpty()) {
MuvitorActivator
.logError(
"The loaded or created model is corrupt and no default model could be created!",
null);
}
// register the root model ID with the editor in the IDUtil
IDUtil.registerEditor(MuvitorTreeEditor.this);
monitor.done();
}
});
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Subclasses must implement to associate {@link KeyStroke}s with Actions in
* the passed {@link KeyHandler} which is the one used for the
* {@link TreeViewer}.
*
* @see #getKeyHandler()
*/
abstract protected void setupKeyHandler(KeyHandler kh);
/**
* Updates all {@link UpdateAction}s registered in the
* {@link #actionRegistry}.
*/
protected final void updateActions() {
for (@SuppressWarnings("unchecked")
final Iterator<IAction> iter = getActionRegistry().getActions(); iter
.hasNext();) {
final IAction action = iter.next();
if (action instanceof UpdateAction) {
((UpdateAction) action).update();
}
}
}
}