de.tub.tfs.muvitor.ui.MuvitorTreeEditor.java Source code

Java tutorial

Introduction

Here is the source code for de.tub.tfs.muvitor.ui.MuvitorTreeEditor.java

Source

/*******************************************************************************
 * 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();
            }
        }
    }

}