org.xmind.ui.internal.editor.MindMapEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.xmind.ui.internal.editor.MindMapEditor.java

Source

/* ******************************************************************************
 * Copyright (c) 2006-2012 XMind Ltd. and others.
 *
 * This file is a part of XMind 3. XMind releases 3 and
 * above are dual-licensed under the Eclipse Public License (EPL),
 * which is available at http://www.eclipse.org/legal/epl-v10.html
 * and the GNU Lesser General Public License (LGPL),
 * which is available at http://www.gnu.org/licenses/lgpl.html
 * See http://www.xmind.net/license.html for details.
 *
 * Contributors:
 *     XMind Ltd. - initial API and implementation
 *******************************************************************************/
package org.xmind.ui.internal.editor;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorSupportProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.ISaveablePart2;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartConstants;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.internal.util.BundleUtility;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.progress.IProgressService;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.services.IEvaluationService;
import org.eclipse.ui.statushandlers.IStatusAdapterConstants;
import org.eclipse.ui.statushandlers.StatusAdapter;
import org.eclipse.ui.statushandlers.StatusManager;
import org.xmind.core.Core;
import org.xmind.core.CoreException;
import org.xmind.core.IFileEntry;
import org.xmind.core.IManifest;
import org.xmind.core.IRelationship;
import org.xmind.core.IRelationshipEnd;
import org.xmind.core.ISheet;
import org.xmind.core.ISheetComponent;
import org.xmind.core.ISheetSettings;
import org.xmind.core.ITopic;
import org.xmind.core.ITopicComponent;
import org.xmind.core.IWorkbook;
import org.xmind.core.event.CoreEvent;
import org.xmind.core.event.CoreEventRegister;
import org.xmind.core.event.ICoreEventListener;
import org.xmind.core.event.ICoreEventRegister;
import org.xmind.core.event.ICoreEventSource2;
import org.xmind.core.event.ICoreEventSupport;
import org.xmind.core.util.FileUtils;
import org.xmind.gef.EditDomain;
import org.xmind.gef.IEditDomainListener;
import org.xmind.gef.IGraphicalViewer;
import org.xmind.gef.command.Command;
import org.xmind.gef.command.CommandStackEvent;
import org.xmind.gef.command.CompoundCommand;
import org.xmind.gef.command.ICommandStack;
import org.xmind.gef.image.ResizeConstants;
import org.xmind.gef.tool.ITool;
import org.xmind.gef.ui.actions.IActionRegistry;
import org.xmind.gef.ui.actions.RedoAction;
import org.xmind.gef.ui.actions.UndoAction;
import org.xmind.gef.ui.editor.GraphicalEditor;
import org.xmind.gef.ui.editor.IEditable;
import org.xmind.gef.ui.editor.IEditingContext;
import org.xmind.gef.ui.editor.IGraphicalEditorPage;
import org.xmind.gef.ui.editor.IInteractiveMessage;
import org.xmind.ui.IWordContextProvider;
import org.xmind.ui.actions.MindMapActionFactory;
import org.xmind.ui.blackbox.BlackBox;
import org.xmind.ui.commands.ModifyFoldedCommand;
import org.xmind.ui.commands.MoveSheetCommand;
import org.xmind.ui.editor.EditorHistoryItem;
import org.xmind.ui.editor.IEditorHistory;
import org.xmind.ui.editor.PageTitleTabFolderRenderer;
import org.xmind.ui.internal.MindMapMessages;
import org.xmind.ui.internal.MindMapUIPlugin;
import org.xmind.ui.internal.MindMapWordContextProvider;
import org.xmind.ui.internal.actions.CopySheetAction;
import org.xmind.ui.internal.actions.CreateSheetAction;
import org.xmind.ui.internal.actions.DeleteOtherSheetsAction;
import org.xmind.ui.internal.actions.DeleteSheetAction;
import org.xmind.ui.internal.actions.DuplicateSheetAction;
import org.xmind.ui.internal.actions.PasteSheetAction;
import org.xmind.ui.internal.actions.ShowPropertiesAction;
import org.xmind.ui.internal.e4handlers.SaveWorkbookAsHandler;
import org.xmind.ui.internal.findreplace.IFindReplaceOperationProvider;
import org.xmind.ui.internal.mindmap.MindMapEditDomain;
import org.xmind.ui.internal.mindmap.MindMapState;
import org.xmind.ui.mindmap.IMindMap;
import org.xmind.ui.mindmap.IMindMapImages;
import org.xmind.ui.mindmap.IMindMapViewer;
import org.xmind.ui.mindmap.IWorkbookRef;
import org.xmind.ui.mindmap.IWorkbookRefListener;
import org.xmind.ui.mindmap.MindMap;
import org.xmind.ui.mindmap.MindMapImageExporter;
import org.xmind.ui.mindmap.MindMapUI;
import org.xmind.ui.prefs.PrefConstants;
import org.xmind.ui.resources.ColorUtils;
import org.xmind.ui.resources.FontUtils;
import org.xmind.ui.tabfolder.IPageMoveListener;
import org.xmind.ui.tabfolder.PageMoveHelper;
import org.xmind.ui.util.Logger;
import org.xmind.ui.wizards.ISaveContext;

public class MindMapEditor extends GraphicalEditor implements ISaveablePart2, ICoreEventListener, IPageMoveListener,
        IPropertyChangeListener, IMindMapPreviewGenerator, ISaveContext, IWorkbookRefListener {

    private static class EditorStatus {

        private static EditorStatus instance = null;

        private int activeIndex;

        private List<Double> zooms;

        private List<Point> scrollPositions;

        private List<String> indexPaths;

        private List<List<String>> topicTypeChains;

        private EditorStatus() {
        }

        public void saveStatus(IWorkbookRef workbookRef, int activeIndex, IGraphicalEditorPage[] pages) {
            init();
            if (workbookRef != null) {
                IWorkbook workbook = workbookRef.getWorkbook();
                if (workbook != null && pages != null && (pages.length != 0)) {
                    this.activeIndex = activeIndex;
                    for (IGraphicalEditorPage page : pages) {
                        IGraphicalViewer viewer = page.getViewer();
                        if (zooms == null)
                            zooms = new ArrayList<Double>();
                        double zoom = viewer.getZoomManager().getScale();
                        zooms.add(zoom);

                        if (scrollPositions == null)
                            scrollPositions = new ArrayList<Point>();
                        Point scrollPosition = viewer.getScrollPosition();
                        scrollPositions.add(scrollPosition);

                        if (indexPaths == null)
                            indexPaths = new ArrayList<String>();
                        if (topicTypeChains == null)
                            topicTypeChains = new ArrayList<List<String>>();
                        Object focused = viewer.getFocused();
                        if (focused == null || !(focused instanceof ITopic)) {
                            indexPaths.add(""); //$NON-NLS-1$
                            topicTypeChains.add(Collections.<String>emptyList());
                        } else {
                            List<String> typeChain = new ArrayList<String>();
                            String indexPath = getTopicIndexPath((ITopic) focused, "", typeChain); //$NON-NLS-1$
                            indexPaths.add(indexPath);
                            Collections.reverse(typeChain);
                            topicTypeChains.add(typeChain);
                        }
                    }
                }
            }
        }

        private void init() {
            activeIndex = 0;

            if (zooms != null)
                zooms.clear();

            if (scrollPositions != null)
                scrollPositions.clear();

            if (indexPaths != null)
                indexPaths.clear();

            if (topicTypeChains != null)
                topicTypeChains.clear();
        }

        private String getTopicIndexPath(ITopic topic, String path, List<String> typeChain) {
            path = topic.getIndex() + "/" + path; //$NON-NLS-1$
            typeChain.add(topic.getType());
            if (topic.getParent() != null && topic.getParent() instanceof ITopic)
                return getTopicIndexPath(topic.getParent(), path, typeChain);
            return path;
        }

        public int getActiveIndex() {
            return activeIndex;
        }

        public List<Double> getZooms() {
            return zooms;
        }

        public List<Point> getScrollPositions() {
            return scrollPositions;
        }

        public List<String> getIndexPaths() {
            return indexPaths;
        }

        public List<List<String>> getTopicTypeChains() {
            return topicTypeChains;
        }

        public static EditorStatus getInstance() {
            if (instance == null) {
                synchronized (EditorStatus.class) {
                    if (instance == null)
                        instance = new EditorStatus();
                }
            }
            return instance;
        }
    }

    private class MindMapEditorSelectionProvider extends MultiPageSelectionProvider {

        /*
         * (non-Javadoc)
         * @see
         * org.xmind.ui.tabfolder.DelegatedSelectionProvider#setSelection(org
         * .eclipse.jface.viewers.ISelection)
         */
        @Override
        public void setSelection(ISelection selection) {
            if (selection instanceof IStructuredSelection) {
                for (Object element : ((IStructuredSelection) selection).toList()) {
                    if (element instanceof ITopicComponent) {
                        setSelectionAndUnfold(element);
                    } else if (element instanceof IRelationship) {
                        IRelationship r = (IRelationship) element;
                        IRelationshipEnd e1 = r.getEnd1();
                        IRelationshipEnd e2 = r.getEnd2();
                        if (e1 instanceof ITopicComponent) {
                            setSelectionAndUnfold(e1);
                        }
                        if (e2 instanceof ITopicComponent) {
                            setSelectionAndUnfold(e2);

                        }
                    }
                }
            }

            super.setSelection(selection);
        }
    }

    private IWorkbookRef workbookRef = null;

    private ICoreEventRegister workbookEventRegister = null;

    private ICoreEventRegister globalEventRegister = null;

    private MindMapPageTitleEditor pageTitleEditor = null;

    private PageMoveHelper pageMoveHelper = null;

    private MindMapFindReplaceOperationProvider findReplaceOperationProvider = null;

    private Composite messageContainer = null;

    private PageBook pageBook = null;

    private Composite pageContainer = null;

    private IEditingContext editingContext = new IEditingContext() {

        @Override
        public <T> T getAdapter(Class<T> adapter) {
            MindMapEditor editor = MindMapEditor.this;
            T result;

            result = editor.getSite().getService(adapter);
            if (result != null)
                return result;

            result = editor.getAdapter(adapter);
            if (result != null)
                return result;

            result = Platform.getAdapterManager().getAdapter(editor, adapter);
            if (result != null)
                return result;

            return null;
        }
    };

    private IWordContextProvider wordContextProvider = null;

    private boolean skipNextPreviewImage = false;

    private IContextActivation contextActivation = null;

    private IEditorHistory editorHistory = null;

    private boolean passwordTried = false;

    private IWindowListener windowListener;

    private ISelectionListener selectionListener;

    private boolean ignoreFileChanged = false;

    private IEditorLayoutManager layoutManager = null;

    private IContextActivation toolContextActivation = null;

    private IContextService contextService = null;

    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
        this.editorHistory = site.getService(IEditorHistory.class);
        this.workbookRef = createWorkbookRefFromInput(input);
        if (this.workbookRef == null)
            throw new PartInitException("Failed to obtain workbook reference from editor input '" //$NON-NLS-1$
                    + input.toString() + "'"); //$NON-NLS-1$

        super.init(site, input);
        setMiniBarContributor(new MindMapMiniBarContributor());
    }

    private IWorkbookRef createWorkbookRefFromInput(IEditorInput input) {
        IWorkbookRef workbookRef = input.getAdapter(IWorkbookRef.class);
        if (workbookRef != null)
            return workbookRef;

        URI uri = input.getAdapter(URI.class);
        if (uri != null) {
            return MindMapUIPlugin.getDefault().getWorkbookRefFactory().createWorkbookRef(uri, null);
        }

        return null;
    }

    protected ISelectionProvider createSelectionProvider() {
        return new MindMapEditorSelectionProvider();
    }

    protected ICommandStack createCommandStack() {
        if (workbookRef != null)
            return workbookRef.getCommandStack();
        return super.createCommandStack();
    }

    protected void disposeCommandStack(ICommandStack commandStack) {
        // No need to dispose command stack here, because the workbook reference
        // manager will dispose unused command stacks automatically.
    }

    private void disposeData() {
        if (workbookRef != null && workbookRef.getURI() != null) {
            URI uri = workbookRef.getURI();
            if (URIUtil.isFileURI(uri)) {
                BlackBox.removeSavedMap(URIUtil.toFile(uri).getAbsolutePath());
            }
        }

        final IWorkbookRef theWorkbookRef = this.workbookRef;
        if (theWorkbookRef != null) {
            theWorkbookRef.removePropertyChangeListener(this);
            theWorkbookRef.removeWorkbookRefListener(this);
            MindMapState.getInstance().saveState(theWorkbookRef, getPages());
            if (theWorkbookRef.isDirty()) {
                theWorkbookRef.discardChanges();
            }
            safeRun(null, false, new IRunnableWithProgress() {

                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    theWorkbookRef.close(monitor);
                }
            });
            IEditingContext activeContext = theWorkbookRef.getActiveContext();
            if (activeContext == this.editingContext) {
                theWorkbookRef.setActiveContext(null);
            }
        }

        deactivateToolContext();

        uninstallModelListener();
    }

    public void dispose() {
        if (contextActivation != null) {
            IContextService cs = getSite().getService(IContextService.class);
            cs.deactivateContext(contextActivation);
        }
        disposeData();
        super.dispose();
        deactivateFileNotifier();
        workbookEventRegister = null;
        globalEventRegister = null;
        pageTitleEditor = null;
        pageMoveHelper = null;
        findReplaceOperationProvider = null;
        workbookRef = null;
        pageBook = null;
        pageContainer = null;

        // release reference to the workbook reference object
        this.workbookRef = null;
        layoutManager = null;
    }

    @Override
    public void createPartControl(Composite parent) {
        super.createPartControl(parent);

        Composite container = getContainer();
        if (container instanceof CTabFolder) {
            ((CTabFolder) container).setRenderer(new PageTitleTabFolderRenderer((CTabFolder) container, this));
        }

        activateFileNotifier();
    }

    protected Composite createContainerParent(Composite parent) {
        FormLayout formLayout = new FormLayout();
        parent.setLayout(formLayout);

        messageContainer = new Composite(parent, SWT.BORDER);
        FormData messageFormData = new FormData();
        //no correct size data to set form data
        messageContainer.setLayoutData(messageFormData);
        messageContainer.setVisible(false);

        GridLayout messageLayout = new GridLayout(1, false);
        messageLayout.marginWidth = 0;
        messageLayout.marginHeight = 0;
        messageLayout.horizontalSpacing = 0;
        messageLayout.verticalSpacing = 0;
        messageContainer.setLayout(messageLayout);

        updateMessages(false);

        pageBook = new PageBook(parent, SWT.NONE);
        FormData pageBookData = new FormData();
        pageBookData.left = new FormAttachment(0, 0);
        pageBookData.top = new FormAttachment(0, 0);
        pageBookData.bottom = new FormAttachment(100, 0);
        pageBookData.right = new FormAttachment(100, 0);
        pageBook.setLayoutData(pageBookData);

        pageContainer = new Composite(pageBook, SWT.NONE);
        layoutManager = new EditorLayoutManager(pageContainer);
        layoutManager.setActiveLayout(new DefaultEditorLayout());
        IContextService cs = getSite().getService(IContextService.class);
        contextActivation = cs.activateContext(MindMapUI.CONTEXT_MINDMAP);
        return pageContainer;
    }

    @Override
    protected void createEditorContents() {
        super.createEditorContents();

        if (workbookRef != null) {
            workbookRef.setActiveContext(editingContext);
            workbookRef.addPropertyChangeListener(this);
            workbookRef.addWorkbookRefListener(this);
        }

        // Make editor actions:
        createActions(getActionRegistry());

        // Update editor pane title:
        updateNames();

        // Add helpers to handle moving pages, editing page title, showing
        // page popup preview, creating new page, etc.:
        if (getContainer() instanceof CTabFolder) {
            final CTabFolder tabFolder = (CTabFolder) getContainer();
            pageMoveHelper = new PageMoveHelper(tabFolder);
            pageMoveHelper.addListener(this);
            pageTitleEditor = new MindMapPageTitleEditor(tabFolder, this);
            //            pageTitleEditor.addPageTitleChangedListener(this);
            //            pageTitleEditor.setContextId(getSite(),
            //                    MindMapUI.CONTEXT_PAGETITLE_EDIT);
            new MindMapEditorPagePopupPreviewHelper(this, tabFolder);

        }

        // Let 3rd-party plugins configure this editor:
        MindMapEditorConfigurerManager.getInstance().configureEditor(this);

        // Try loading workbook:
        if (getWorkbook() != null) {
            workbookLoaded();
        } else if (workbookRef != null) {
            final IWorkbookRef theWorkbookRef = workbookRef;
            Display.getCurrent().asyncExec(new Runnable() {
                @Override
                public void run() {
                    loadWorkbook(theWorkbookRef, 0);
                }
            });
        }
    }

    private void loadWorkbook(final IWorkbookRef workbookRef, int times) {
        IEncryptable encryptable = workbookRef.getAdapter(IEncryptable.class);
        if (encryptable != null && !encryptable.hasPassword()) {
            /// make sure setPassword is called to prevent the default password dialog
            encryptable.setPassword(null);
        }

        IWorkbenchSiteProgressService context = getSite().getService(IWorkbenchSiteProgressService.class);
        Assert.isTrue(context != null);
        try {
            context.run(true, true, new IRunnableWithProgress() {

                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    workbookRef.open(monitor);
                }
            });
        } catch (final InvocationTargetException e) {
            CoreException coreEx = getCoreException(e);
            if (coreEx != null) {
                int errType = coreEx.getType();
                if (errType == Core.ERROR_WRONG_PASSWORD) {
                    // password error
                    String message = passwordTried ? MindMapMessages.MindMapEditor_passwordPrompt_message1
                            : MindMapMessages.MindMapEditor_passwordPrompt_message2;
                    openDecryptionDialog(workbookRef, message, times);
                    passwordTried = true;
                    return;
                }
            }

            // failed
            Throwable cause = e.getTargetException();
            if (cause == null)
                cause = e;

            if (cause instanceof FileNotFoundException) {
                Display.getDefault().asyncExec(new Runnable() {
                    public void run() {
                        MessageDialog.openWarning(Display.getDefault().getActiveShell(),
                                MindMapMessages.MindMapEditor_Warning_FileNotFoundDialog_title,
                                MindMapMessages.WorkbookHistoryItem_FileMissingMessage);
                    }
                });
                asyncClose();
            } else {
                showError(cause, workbookRef.getAdapter(ErrorSupportProvider.class));
            }
            return;
        } catch (InterruptedException e) {
            // canceled
            asyncClose();
            return;
        }

        workbookLoaded();
    }

    private void openDecryptionDialog(final IWorkbookRef workbookRef, String message, int times) {
        final int nextTime = times + 1;
        final IEncryptable encryptable = workbookRef.getAdapter(IEncryptable.class);

        new DecryptionDialog(Display.getCurrent().getActiveShell(), workbookRef.getName(),
                encryptable.getPasswordHint(), times) {
            protected void okPressed() {
                super.okPressed();

                encryptable.setPassword(getPassword());
                loadWorkbook(workbookRef, nextTime);
            };

            protected void handleShellCloseEvent() {
                super.handleShellCloseEvent();
                asyncClose();
            };

            protected void cancelPressed() {
                super.cancelPressed();
                asyncClose();
            };
        }.open();
    }

    private CoreException getCoreException(Throwable e) {
        if (e == null)
            return null;
        if (e instanceof CoreException)
            return (CoreException) e;
        return getCoreException(e.getCause());
    }

    private void asyncClose() {
        Display.getCurrent().asyncExec(new Runnable() {

            @Override
            public void run() {
                closeEditor();
            }
        });
    }

    private void closeEditor() {
        getSite().getPage().closeEditor(this, false);
    }

    private void showError(Throwable exception, ErrorSupportProvider supportProvider) {
        StatusAdapter statusAdapter = new StatusAdapter(new Status(IStatus.ERROR, MindMapUIPlugin.PLUGIN_ID,
                MindMapMessages.LoadWorkbookJob_errorDialog_title, exception));
        statusAdapter.setProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY,
                Long.valueOf(System.currentTimeMillis()));
        ErrorDialog errorDialog = new ErrorDialog(Display.getCurrent().getActiveShell(), statusAdapter,
                supportProvider);
        errorDialog.setCloseCallback(new Runnable() {
            @Override
            public void run() {
                asyncClose();
            }
        });
        errorDialog.open();
    }

    protected void createActions(IActionRegistry actionRegistry) {
        UndoAction undoAction = new UndoAction(this);
        actionRegistry.addAction(undoAction);
        addCommandStackAction(undoAction);

        RedoAction redoAction = new RedoAction(this);
        actionRegistry.addAction(redoAction);
        addCommandStackAction(redoAction);

        CreateSheetAction createSheetAction = new CreateSheetAction(this);
        actionRegistry.addAction(createSheetAction);

        DeleteSheetAction deleteSheetAction = new DeleteSheetAction(this);
        actionRegistry.addAction(deleteSheetAction);

        DeleteOtherSheetsAction deleteOtherSheetAction = new DeleteOtherSheetsAction(this);
        actionRegistry.addAction(deleteOtherSheetAction);

        DuplicateSheetAction duplicateSheetAction = new DuplicateSheetAction(this);
        actionRegistry.addAction(duplicateSheetAction);

        CopySheetAction copySheetAction = new CopySheetAction(this);
        actionRegistry.addAction(copySheetAction);

        PasteSheetAction pasteSheetAction = new PasteSheetAction(this);
        actionRegistry.addAction(pasteSheetAction);

        ShowPropertiesAction showPropertiesAction = new ShowPropertiesAction(getSite().getWorkbenchWindow());
        actionRegistry.addAction(showPropertiesAction);
    }

    private void configurePage(IGraphicalEditorPage page) {
        MindMapEditorConfigurerManager.getInstance().configurePage(page);
    }

    protected void createPages() {
        if (getWorkbook() == null)
            return;

        for (ISheet sheet : getWorkbook().getSheets()) {
            IGraphicalEditorPage page = createSheetPage(sheet, -1);
            configurePage(page);
        }
        if (getPageCount() > 0) {
            setActivePage(0);
        }
    }

    protected IGraphicalEditorPage createSheetPage(ISheet sheet, int index) {
        IGraphicalEditorPage page = new MindMapEditorPage();
        page.init(this, sheet);
        addPage(page);
        if (index >= 0 && index < getPageCount()) {
            movePageTo(findPage(page), index);
        }
        index = findPage(page);
        if (getActivePage() != index) {
            setActivePage(index);
        }
        page.updatePageTitle();
        MindMapState.getInstance().loadState(getWorkbookRef(), page);
        return page;
    }

    protected EditDomain createEditDomain(IGraphicalEditorPage page) {
        MindMapEditDomain domain = new MindMapEditDomain();
        domain.addEditDomainListener(new IEditDomainListener() {

            @Override
            public void activeToolChanged(ITool oldTool, ITool newTool) {
                changeContext(newTool);
            }
        });
        domain.setCommandStack(getCommandStack());
        return domain;
    }

    protected void changeContext(ITool newTool) {
        deactivateToolContext();
        activateContext(newTool == null ? null : newTool.getContextId());
    }

    protected void changeContext(String contextId) {
        deactivateToolContext();
        activateContext(contextId);
    }

    private void activateContext(String contextId) {
        if (contextId == null)
            return;
        contextService = getSite().getService(IContextService.class);
        if (contextService != null) {
            toolContextActivation = contextService.activateContext(contextId);
        }
    }

    private void deactivateToolContext() {
        if (contextService != null && toolContextActivation != null) {
            contextService.deactivateContext(toolContextActivation);
        }
        contextService = null;
        toolContextActivation = null;
    }

    private void updateMessages(boolean layout) {
        for (Control messageItem : messageContainer.getChildren()) {
            messageItem.dispose();
        }
        List<IInteractiveMessage> messages = workbookRef.getMessages();
        if (messages != null && !messages.isEmpty()) {
            for (IInteractiveMessage message : new ArrayList<IInteractiveMessage>(messages)) {

                Image icon;
                Color lineColor;
                Color textColor;
                int type = message.getMessageType();
                if (type == IInteractiveMessage.WARNING) {
                    ///TODO use a dedicated icon
                    icon = null;
                    lineColor = (Color) JFaceResources.getResources().get(ColorUtils.toDescriptor("#fff6ae")); //$NON-NLS-1$
                    textColor = (Color) JFaceResources.getResources().get(ColorUtils.toDescriptor("#1e1e1e")); //$NON-NLS-1$
                } else if (type == IInteractiveMessage.ERROR) {
                    ///TODO use a dedicated icon
                    icon = null;
                    lineColor = (Color) JFaceResources.getResources().get(ColorUtils.toDescriptor("#fff6ae")); //$NON-NLS-1$
                    textColor = (Color) JFaceResources.getResources().get(ColorUtils.toDescriptor("#1e1e1e")); //$NON-NLS-1$
                } else {
                    ///TODO use a dedicated icon
                    icon = Display.getCurrent().getSystemImage(SWT.ICON_INFORMATION);
                    lineColor = (Color) JFaceResources.getResources().get(ColorUtils.toDescriptor("#fff6ae")); //$NON-NLS-1$
                    textColor = (Color) JFaceResources.getResources().get(ColorUtils.toDescriptor("#1e1e1e")); //$NON-NLS-1$
                }

                String text = message.getMessage();
                if (text == null) {
                    text = ""; //$NON-NLS-1$
                }

                List<IAction> actions = message.getActions();

                final Composite line = new Composite(messageContainer, SWT.NONE);
                line.setBackground(lineColor);
                GridData lineData = new GridData(SWT.CENTER, SWT.FILL, true, false);
                lineData.widthHint = 500;
                line.setLayoutData(lineData);
                line.setLayout(new GridLayout(2, false));

                Composite leftComposite = new Composite(line, SWT.NONE);
                leftComposite.setBackground(line.getBackground());
                GridData lineBodyData = new GridData(SWT.CENTER, SWT.FILL, true, false);
                leftComposite.setLayoutData(lineBodyData);
                int numColumns = 0;
                GridLayout leftCompositeLayout = new GridLayout(numColumns, false);
                leftCompositeLayout.marginLeft = 16;
                leftCompositeLayout.marginHeight = 5;
                leftCompositeLayout.horizontalSpacing = 3;
                leftCompositeLayout.verticalSpacing = 0;
                leftComposite.setLayout(leftCompositeLayout);

                if (icon != null) {
                    numColumns++;
                    Label iconLabel = new Label(leftComposite, SWT.NONE);
                    iconLabel.setBackground(line.getBackground());
                    iconLabel.setImage(icon);
                    iconLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, true));
                }

                numColumns++;
                Label textLabel = new Label(leftComposite, SWT.WRAP | SWT.CENTER);
                textLabel.setText(text);
                textLabel.setBackground(line.getBackground());
                textLabel.setForeground(textColor);
                textLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
                Font textFont = textLabel.getFont();
                if (textFont != null) {
                    FontData[] fontData = textFont.getFontData();
                    FontData[] newFontData = FontUtils.newHeight(fontData, Util.isMac() ? 12 : 10);
                    textLabel.setFont(
                            (Font) JFaceResources.getResources().get(FontDescriptor.createFrom(newFontData)));
                }

                if (!actions.isEmpty()) {
                    numColumns++;
                    Composite actionBar = new Composite(leftComposite, SWT.NONE);
                    actionBar.setBackground(line.getBackground());
                    actionBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
                    GridLayout actionLayout = new GridLayout(actions.size(), false);
                    actionLayout.marginWidth = 0;
                    actionLayout.marginHeight = 0;
                    actionLayout.horizontalSpacing = 3;
                    actionLayout.verticalSpacing = 0;
                    actionBar.setLayout(actionLayout);

                    for (final IAction action : actions) {
                        final Button actionButton = new Button(actionBar, SWT.PUSH);
                        actionButton.setBackground(actionBar.getBackground());
                        actionButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true));
                        String actionText = action.getText();
                        if (actionText == null) {
                            actionButton.setText(actionText);
                        } else {
                            ImageDescriptor actionImageDescriptor = action.getImageDescriptor();
                            if (actionImageDescriptor != null) {
                                final Image actionImage = actionImageDescriptor.createImage(Display.getCurrent());
                                actionButton.addDisposeListener(new DisposeListener() {

                                    @Override
                                    public void widgetDisposed(DisposeEvent e) {
                                        actionImage.dispose();
                                    }
                                });
                                actionButton.setImage(actionImage);
                            }
                        }
                        actionButton.setToolTipText(action.getToolTipText());
                        actionButton.addListener(SWT.Selection, new Listener() {

                            @Override
                            public void handleEvent(Event event) {
                                action.runWithEvent(event);
                            }
                        });
                    }
                }

                leftCompositeLayout.numColumns = numColumns;

                final Label closeImageLabel = new Label(line, SWT.NONE);
                closeImageLabel.setBackground(line.getBackground());
                closeImageLabel.setImage(
                        (Image) JFaceResources.getResources().get(MindMapUI.getImages().get("close.png", true))); //$NON-NLS-1$
                closeImageLabel.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false));
                closeImageLabel.addMouseListener(new MouseAdapter() {

                    @Override
                    public void mouseDown(MouseEvent e) {
                        if (messageContainer != null && !messageContainer.isDisposed()) {
                            if (line != null && !line.isDisposed()) {
                                line.dispose();
                            }
                            Control[] children = messageContainer.getChildren();
                            if (children == null || children.length == 0) {
                                messageContainer.setVisible(false);
                            }
                            messageContainer.getParent().layout(true);
                        }
                    }
                });
                closeImageLabel.addMouseTrackListener(new MouseTrackListener() {

                    @Override
                    public void mouseHover(MouseEvent e) {
                        closeImageLabel.setCursor(Display.getDefault().getSystemCursor(SWT.CURSOR_HAND));
                    }

                    @Override
                    public void mouseExit(MouseEvent e) {
                        closeImageLabel.setCursor(null);
                    }

                    @Override
                    public void mouseEnter(MouseEvent e) {
                        closeImageLabel.setCursor(Display.getDefault().getSystemCursor(SWT.CURSOR_HAND));
                    }
                });
            }
            org.eclipse.swt.graphics.Point size = messageContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT);
            FormData messageFormData = new FormData();
            messageFormData.left = new FormAttachment(50, -size.x / 2);
            messageFormData.right = new FormAttachment(50, size.x / 2);
            messageFormData.top = new FormAttachment(0, 0);
            //can't set bottom
            //            messageFormData.bottom = new FormAttachment(100, -size.y);
            messageContainer.setLayoutData(messageFormData);
            messageContainer.setVisible(true);
        }

        if (layout) {
            messageContainer.getParent().layout(true, true);
        }
    }

    protected void updateNames() {
        setPartName(getEditorInput().getName());
        setTitleToolTip(getEditorInput().getToolTipText());
    }

    public IWorkbookRef getWorkbookRef() {
        return workbookRef;
    }

    public IWorkbook getWorkbook() {
        if (workbookRef == null)
            return null;
        return workbookRef.getWorkbook();
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getAdapter(Class<T> adapter) {
        if (adapter == IEditorLayoutManager.class)
            return (T) layoutManager;
        return super.getAdapter(adapter);
    }

    @Override
    protected <T> T getEditorAdapter(Class<T> adapter) {
        if (workbookRef != null) {
            T result = workbookRef.getAdapter(adapter);
            if (result != null)
                return result;
        }

        if (adapter == IWorkbookRef.class) {
            return adapter.cast(getWorkbookRef());
        } else if (adapter == IWorkbook.class) {
            return adapter.cast(getWorkbook());
        } else if (adapter == MindMapPageTitleEditor.class) {
            return adapter.cast(pageTitleEditor);
        } else if (adapter == PageMoveHelper.class) {
            return adapter.cast(pageMoveHelper);
        } else if (adapter == IFindReplaceOperationProvider.class) {
            if (findReplaceOperationProvider == null) {
                findReplaceOperationProvider = new MindMapFindReplaceOperationProvider(this);
            }
            return adapter.cast(findReplaceOperationProvider);
        } else if (adapter == IWordContextProvider.class) {
            if (wordContextProvider == null) {
                wordContextProvider = new MindMapWordContextProvider(this);
            }
            return adapter.cast(wordContextProvider);
        } else if (adapter == IDialogPaneContainer.class) {
            //            return adapter.cast(backCover);
        }
        return super.getEditorAdapter(adapter);
    }

    protected void installModelListener() {
        IWorkbook workbook = getWorkbook();
        workbookEventRegister = new CoreEventRegister(workbook, this);
        workbookEventRegister.register(Core.SheetAdd);
        workbookEventRegister.register(Core.SheetRemove);
        workbookEventRegister.register(Core.SheetMove);
        workbookEventRegister.register(Core.PasswordChange);
        workbookEventRegister.register(Core.WorkbookPreSaveOnce);

        globalEventRegister = new CoreEventRegister(workbook.getAdapter(ICoreEventSupport.class), this);
        globalEventRegister.register(Core.SheetSettings);
    }

    protected void uninstallModelListener() {
        if (workbookEventRegister != null) {
            workbookEventRegister.unregisterAll();
            workbookEventRegister = null;
        }
        if (globalEventRegister != null) {
            globalEventRegister.unregisterAll();
            globalEventRegister = null;
        }
    }

    public void handleCoreEvent(final CoreEvent event) {
        if (pageBook == null || pageBook.isDisposed() || workbookRef == null)
            return;

        pageBook.getDisplay().syncExec(new Runnable() {

            public void run() {
                String type = event.getType();
                if (Core.WorkbookPreSaveOnce.equals(type)) {
                    firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);
                    firePropertyChange(PROP_INPUT);
                } else if (Core.SheetAdd.equals(type)) {
                    ISheet sheet = (ISheet) event.getTarget();
                    int index = event.getIndex();
                    IGraphicalEditorPage page = createSheetPage(sheet, index);
                    configurePage(page);
                } else if (Core.SheetRemove.equals(type)) {
                    ISheet sheet = (ISheet) event.getTarget();
                    IGraphicalEditorPage page = findPage(sheet);
                    if (page != null) {
                        removePage(page);
                    }
                } else if (Core.SheetMove.equals(type)) {
                    int oldIndex = event.getIndex();
                    int newIndex = ((ISheet) event.getTarget()).getIndex();
                    movePageTo(oldIndex, newIndex);
                } else if (Core.PasswordChange.equals(type)) {
                    IWorkbook workbook = getWorkbook();
                    if (workbook instanceof ICoreEventSource2) {
                        ((ICoreEventSource2) workbook).registerOnceCoreEventListener(Core.WorkbookPreSaveOnce,
                                ICoreEventListener.NULL);
                    }
                }

                //update sheet tab colors
                if (Core.SheetSettings.equals(type) && ISheetSettings.ATTR_RGB.equals(event.getTarget())) {
                    updateSheetTabColors();
                }
            }
        });
    }

    private void updateSheetTabColors() {
        getContainer().redraw();
    }

    protected void saveAndRun(Command command) {
        ICommandStack cs = getCommandStack();
        if (cs != null)
            cs.execute(command);
    }

    public void pageMoved(int fromIndex, int toIndex) {
        IWorkbook workbook = getWorkbook();
        MoveSheetCommand command = new MoveSheetCommand(workbook, fromIndex, toIndex);
        command.setLabel(""); //$NON-NLS-1$
        saveAndRun(command);
    }

    //    public void pageTitleChanged(int pageIndex, String newValue) {
    //        /// forbid changing sheet title to empty string
    //        /// preventing saving errors (e.g. cloud syncing)
    //        if (newValue == null || "".equals(newValue)) //$NON-NLS-1$
    //            return;
    //
    //        IGraphicalEditorPage page = getPage(pageIndex);
    //        if (page != null) {
    //            Object pageInput = page.getInput();
    //            if (pageInput instanceof ISheet) {
    //                ModifyTitleTextCommand command = new ModifyTitleTextCommand(
    //                        (ISheet) pageInput, newValue);
    //                command.setLabel(""); //$NON-NLS-1$
    //                saveAndRun(command);
    //            }
    //        }
    //    }

    protected void createSheet() {
        IAction action = getActionRegistry().getAction(MindMapActionFactory.NEW_SHEET.getId());
        if (action != null && action.isEnabled()) {
            action.run();
        }
    }

    @Override
    public void setFocus() {
        //        if (workbookRef != null) {
        //            workbookRef.setPrimaryReferrer(this);
        //        }
        if (workbookRef != null) {
            workbookRef.setActiveContext(editingContext);
        }
        //        if (backCover != null && backCover.isOpen()) {
        //            backCover.setFocus();
        //        } else {
        super.setFocus();
        //        }
    }

    public void openEncryptionDialog() {
        if (pageBook == null || pageBook.isDisposed())
            return;

        final IWorkbookRef theWorkbookRef = workbookRef;

        new EncryptionDialog(Display.getCurrent().getActiveShell()) {

            @Override
            protected boolean hasPassword() {
                IEncryptable encryptable = theWorkbookRef.getAdapter(IEncryptable.class);
                return encryptable != null && encryptable.hasPassword();
            }

            @Override
            protected boolean testsPassword(String password) {
                IEncryptable encryptable = theWorkbookRef.getAdapter(IEncryptable.class);
                return encryptable != null && encryptable.testsPassword(password);
            }

            @Override
            protected void okPressed() {
                boolean verified = verify();

                super.okPressed();

                if (verified) {
                    IEncryptable encryptable = theWorkbookRef.getAdapter(IEncryptable.class);
                    if (encryptable != null) {
                        encryptable.setPassword(getPassword());
                        encryptable.setPasswordHint(getHintMessage());
                    }
                }
            }
        }.open();
    }

    public ISelectionProvider getSelectionProvider() {
        return getSite().getSelectionProvider();
    }

    public void reveal() {
        getSite().getPage().activate(this);
        setFocus();
    }

    /**
     * @param monitor
     * @deprecated
     */
    @Deprecated
    public void postSave(final IProgressMonitor monitor) {
        getSite().getShell().getDisplay().syncExec(new Runnable() {

            public void run() {
                postSave();
            }
        });
    }

    public void setSelection(ISelection selection, boolean reveal, boolean forceFocus) {
        ISelectionProvider selectionProvider = getSite().getSelectionProvider();
        if (selectionProvider != null) {
            selectionProvider.setSelection(selection);
        }
        if (forceFocus) {
            getSite().getPage().activate(this);
            Shell shell = getSite().getShell();
            if (shell != null && !shell.isDisposed()) {
                shell.setActive();
            }
        } else if (reveal) {
            getSite().getPage().bringToTop(this);
        }
    }

    public IGraphicalEditorPage findPage(Object input) {
        if (input instanceof IMindMap) {
            input = ((IMindMap) input).getSheet();
        }
        return super.findPage(input);
    }

    private void workbookLoaded() {
        passwordTried = false;
        if (pageBook == null || pageBook.isDisposed())
            return;

        // TODO
        pageBook.showPage(pageContainer);
        if (isEditorActive())
            setFocus();

        Assert.isTrue(getWorkbook() != null);
        createPages();
        if (isEditorActive()) {
            setFocus();
        }
        installModelListener();
        firePropertyChange(PROP_INPUT);
        firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);

        recordEditorHistory();
    }

    private boolean isEditorActive() {
        return getSite().getPage().getActiveEditor() == this;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.xmind.gef.ui.editor.GraphicalEditor#findOwnedInput(org.eclipse.jface
     * .viewers.ISelection)
     */
    @Override
    protected Object findOwnedInput(ISelection selection) {
        if (selection instanceof IStructuredSelection) {
            Object[] elements = ((IStructuredSelection) selection).toArray();
            for (Object element : elements) {
                if (element instanceof ISheetComponent)
                    return ((ISheetComponent) element).getOwnedSheet();
                if (element instanceof ISheet)
                    return (ISheet) element;
            }
        }
        return super.findOwnedInput(selection);
    }

    public void skipNextPreviewImage() {
        this.skipNextPreviewImage = true;
    }

    private void setSelectionAndUnfold(Object element) {
        List<Command> showElementsCommands = new ArrayList<Command>(1);
        ITopic parent = ((ITopicComponent) element).getParent();
        while (parent != null) {
            if (parent.isFolded()) {
                showElementsCommands.add(new ModifyFoldedCommand(parent, false));
            }
            parent = parent.getParent();
        }
        if (!showElementsCommands.isEmpty()) {
            Command command = new CompoundCommand(showElementsCommands.get(0).getLabel(), showElementsCommands);
            saveAndRun(command);
        }
    }

    @Override
    public boolean isDirty() {
        return workbookRef != null && workbookRef.isDirty();
    }

    @Override
    public void doSave(IProgressMonitor monitor) {
        if (workbookRef == null)
            return;

        if (!workbookRef.canSave()) {
            doSaveAs();
            return;
        }

        safeRun(monitor, true, new IRunnableWithProgress() {

            @Override
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                workbookRef.save(monitor);
                if (MindMapUIPlugin.getDefault().getPreferenceStore()
                        .getBoolean(PrefConstants.AUTO_BACKUP_ENABLE)) {
                    URI uri = workbookRef.getURI();
                    if (URIUtil.isFileURI(uri)) {
                        BlackBox.doBackup(URIUtil.toFile(uri).getAbsolutePath());
                    }
                }
                postSave();
                recordEditorHistory();
            }
        });
    }

    private void recordEditorHistory() {

        if (workbookRef.getURI() == null || workbookRef.getName() == null)
            return;
        URI uri = workbookRef.getURI();

        //only local file or seawind file can record it history.
        if (!uri.getScheme().equalsIgnoreCase("file") //$NON-NLS-1$
                && !uri.getScheme().equalsIgnoreCase("seawind")) //$NON-NLS-1$
            return;

        InputStream input = null;
        try {
            try {
                if (uri.getScheme().equalsIgnoreCase("file")) { //$NON-NLS-1$
                    input = getThumbnailStreamForLocal();
                } else
                    input = getThumbnailStreamFor(workbookRef);
                if (input == null)
                    return;
                editorHistory.add(workbookRef.getURI(),
                        new EditorHistoryItem(workbookRef.getName(), System.currentTimeMillis()));
                editorHistory.saveThumbnailData(workbookRef.getURI(), input);
            } finally {
                if (input != null)
                    input.close();
            }
        } catch (IOException e) {
            MindMapUIPlugin.getDefault().getLog()
                    .log(new Status(IStatus.ERROR, MindMapUIPlugin.PLUGIN_ID,
                            "Failed to record preview image for editor history", //$NON-NLS-1$
                            e));
        }
    }

    private InputStream getThumbnailStreamForLocal() throws IOException {
        IManifest manifest = getWorkbook().getManifest();
        if (manifest != null) {
            IFileEntry thumbnailEntry = manifest.getFileEntry("Thumbnails/thumbnail.png"); //$NON-NLS-1$
            if (thumbnailEntry != null)
                return thumbnailEntry.openInputStream();
        }
        return null;
    }

    private InputStream getThumbnailStreamFor(IWorkbookRef workbookRef) {
        if (workbookRef == null)
            return null;

        List<ISheet> sheets = workbookRef.getWorkbook().getSheets();
        if (sheets.isEmpty())
            return null;

        ISheet sheet = sheets.get(0);
        try {
            return workbookRef.getPreviewImageData(sheet.getId(), null);
        } catch (IOException e) {
            MindMapUIPlugin.log(e, String.format("Failed to load preview image for sheet 'workbooks/%s/sheets/%s'", //$NON-NLS-1$
                    sheet.getParent().toString(), sheet.getId()));
        } catch (SWTException e) {
            MindMapUIPlugin.log(e, String.format("Failed to load preview image for sheet 'workbooks/%s/sheets/%s'", //$NON-NLS-1$
                    sheet.getParent().toString(), sheet.getId()));
        }

        return null;
    }

    public void doSaveAs() {
        doSaveAs(false);
    }

    public void doSaveAs(boolean onlyToLocal) {
        final IWorkbookRef oldWorkbookRef = this.workbookRef;
        if (oldWorkbookRef == null || oldWorkbookRef.isInState(IWorkbookRef.CLOSED | IWorkbookRef.CLOSING))
            throw new IllegalStateException("This mind map editor is already closed."); //$NON-NLS-1$

        IProgressService runner = getSite().getService(IProgressService.class);
        final IWorkbookRef newWorkbookRef;
        try {
            newWorkbookRef = SaveWorkbookAsHandler.saveWorkbookAs(this, oldWorkbookRef, runner, null, onlyToLocal);
            if (newWorkbookRef == null)
                // canceled
                return;
            if (newWorkbookRef.equals(oldWorkbookRef)) {
                // saved to old location
                postSave();
                recordEditorHistory();
                return;
            }

            try {
                runner.run(true, true, new IRunnableWithProgress() {

                    @Override
                    public void run(IProgressMonitor monitor)
                            throws InvocationTargetException, InterruptedException {
                        newWorkbookRef.open(monitor);
                        newWorkbookRef.save(monitor);
                    }
                });
            } catch (InterruptedException e) {
                // canceled
                return;
            }
        } catch (Exception e) {
            StatusManager.getManager().handle(
                    new Status(IStatus.ERROR, MindMapUIPlugin.PLUGIN_ID, e.getMessage(), e), StatusManager.SHOW);
            return;
        }

        // TODO save editor state before changing editor input
        EditorStatus editorStatus = EditorStatus.getInstance();
        editorStatus.saveStatus(oldWorkbookRef, getActivePage(), getPages());

        disposeData();
        while (getPageCount() > 0) {
            removePage(0);
        }

        this.workbookRef = newWorkbookRef;
        if (newWorkbookRef != null) {
            newWorkbookRef.addPropertyChangeListener(this);
            newWorkbookRef.addWorkbookRefListener(this);
        }
        setInput(MindMapUI.getEditorInputFactory().createEditorInput(newWorkbookRef));
        setCommandStack(newWorkbookRef.getCommandStack());
        updateNames();

        createPages();
        installModelListener();

        firePropertyChange(PROP_INPUT);
        postSave();

        // TODO restore editor state after changing editor input
        setActivePage(editorStatus.getActiveIndex());
        IGraphicalEditorPage[] pages = getPages();
        for (int index = 0; index < pages.length; index++) {
            IGraphicalViewer viewer = pages[index].getViewer();
            viewer.getProperties().set(IMindMapViewer.VIEWER_SELECT_CENTRALTOPIC, Boolean.FALSE);
            Double scale = editorStatus.getZooms().get(index);
            if (scale != null)
                viewer.getZoomManager().setScale(scale);

            String indexPath = index < editorStatus.getIndexPaths().size() ? editorStatus.getIndexPaths().get(index)
                    : ""; //$NON-NLS-1$
            List<String> topicTypeChain = index < editorStatus.getTopicTypeChains().size()
                    ? editorStatus.getTopicTypeChains().get(index)
                    : Collections.<String>emptyList();
            if (indexPath != null) {
                if ("".equals(indexPath)) //$NON-NLS-1$
                    viewer.setSelection(StructuredSelection.EMPTY, true);
                else {
                    Object input = viewer.getInput();
                    if (input instanceof IMindMap) {
                        ITopic topic = ((IMindMap) input).getCentralTopic();
                        String[] indexes = indexPath.split("/"); //$NON-NLS-1$
                        for (int i = 1; i < indexes.length; i++) {
                            String type = i < topicTypeChain.size() ? topicTypeChain.get(i) : ITopic.ATTACHED;
                            if (topic.getChildren(type).size() <= Integer.parseInt(indexes[i])) {
                                continue;
                            }
                            topic = topic.getChildren(type).get(Integer.parseInt(indexes[i]));
                        }
                        viewer.setSelection(new StructuredSelection(topic), true);
                    }

                }
            }

            Point scrollPosition = editorStatus.getScrollPositions().get(index);
            if (scrollPosition != null)
                viewer.scrollTo(scrollPosition);
        }

        recordEditorHistory();

        //Force update IEclipseContext
        IEclipseContext partContext = getSite().getService(IEclipseContext.class);
        partContext.deactivate();
        partContext.activateBranch();
    }

    private void reload() {
        IWorkbookRef oldWorkbookRef = this.workbookRef;
        if (oldWorkbookRef == null || oldWorkbookRef.isInState(IWorkbookRef.CLOSED | IWorkbookRef.CLOSING)) {
            throw new IllegalStateException("This mind map editor is already closed."); //$NON-NLS-1$
        }

        // save editor state before changing editor input
        EditorStatus editorStatus = EditorStatus.getInstance();
        editorStatus.saveStatus(oldWorkbookRef, getActivePage(), getPages());

        IEditingContext context = oldWorkbookRef.getActiveContext();
        disposeData();
        while (getPageCount() > 0) {
            removePage(0);
        }

        final IWorkbookRef newWorkbookRef = MindMapUIPlugin.getDefault().getWorkbookRefFactory()
                .createWorkbookRef(oldWorkbookRef.getURI(), null);
        if (newWorkbookRef == null) {
            return;
        }

        try {
            IProgressService runner = getSite().getService(IProgressService.class);
            runner.run(true, true, new IRunnableWithProgress() {

                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    newWorkbookRef.open(monitor);
                }
            });
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            return;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        }

        this.workbookRef = newWorkbookRef;
        if (newWorkbookRef != null) {
            newWorkbookRef.setActiveContext(context);
            newWorkbookRef.addPropertyChangeListener(this);
            newWorkbookRef.addWorkbookRefListener(this);
        }
        setInput(MindMapUI.getEditorInputFactory().createEditorInput(newWorkbookRef));
        setCommandStack(newWorkbookRef.getCommandStack());
        updateNames();

        createPages();
        installModelListener();

        firePropertyChange(PROP_INPUT);
        firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);

        // restore editor state after changing editor input
        setActivePage(editorStatus.getActiveIndex());
        IGraphicalEditorPage[] pages = getPages();
        for (int index = 0; index < pages.length; index++) {
            IGraphicalViewer viewer = pages[index].getViewer();
            viewer.getProperties().set(IMindMapViewer.VIEWER_SELECT_CENTRALTOPIC, Boolean.FALSE);
            Double scale = editorStatus.getZooms().size() > index ? editorStatus.getZooms().get(index) : null;
            if (scale != null)
                viewer.getZoomManager().setScale(scale);

            String indexPath = editorStatus.getIndexPaths().size() > index ? editorStatus.getIndexPaths().get(index)
                    : ""; //$NON-NLS-1$
            List<String> topicTypeChain = editorStatus.getTopicTypeChains().size() > index
                    ? editorStatus.getTopicTypeChains().get(index)
                    : Collections.<String>emptyList();
            if (indexPath != null) {
                if ("".equals(indexPath)) //$NON-NLS-1$
                    viewer.setSelection(StructuredSelection.EMPTY, true);
                else {
                    Object input = viewer.getInput();
                    if (input instanceof IMindMap) {
                        ITopic topic = ((IMindMap) input).getCentralTopic();
                        String[] indexes = indexPath.split("/"); //$NON-NLS-1$
                        for (int i = 1; i < indexes.length; i++) {
                            String type = i < topicTypeChain.size() ? topicTypeChain.get(i) : ITopic.ATTACHED;
                            if (topic.getChildren(type).size() <= Integer.parseInt(indexes[i])) {
                                continue;
                            }
                            topic = topic.getChildren(type).get(Integer.parseInt(indexes[i]));
                        }
                        viewer.setSelection(new StructuredSelection(topic), true);
                    }

                }
            }

            Point scrollPosition = editorStatus.getScrollPositions().size() > index
                    ? editorStatus.getScrollPositions().get(index)
                    : null;
            if (scrollPosition != null)
                viewer.scrollTo(scrollPosition);
        }
    }

    private boolean safeRun(final IProgressMonitor monitor, final boolean promptError,
            final IRunnableWithProgress runnable) {
        final boolean[] successful = new boolean[1];
        successful[0] = false;
        SafeRunner.run(new SafeRunnable() {

            @Override
            public void run() throws Exception {
                if (monitor != null) {
                    runnable.run(monitor);
                } else {
                    IWorkbenchSiteProgressService context = getSite()
                            .getService(IWorkbenchSiteProgressService.class);
                    Assert.isTrue(context != null);
                    context.run(true, true, runnable);
                }
                successful[0] = true;
            }

            @Override
            public void handleException(Throwable e) {
                if (e instanceof InterruptedException)
                    // canceled, no error
                    return;

                if (e instanceof InvocationTargetException) {
                    Throwable cause = ((InvocationTargetException) e).getTargetException();
                    if (cause != null)
                        e = cause;
                }

                if (!promptError) {
                    // log only
                    Logger.log(e);
                    return;
                }

                super.handleException(e);
            }
        });

        return successful[0];
    }

    @Override
    public boolean isSaveAsAllowed() {
        return getWorkbook() != null;
    }

    public int promptToSaveOnClose() {
        //TODO
        //        if (workbookRef != null && workbookRef.canSave()) {
        //            NullProgressMonitor monitor = new NullProgressMonitor();
        //            doSave(monitor);
        //            if (monitor.isCanceled())
        //                return CANCEL;
        //            return NO;
        //        }
        return DEFAULT;
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        if (event.getSource() == workbookRef) {
            workbookRefPropertyChange(event);
        }
    }

    protected void workbookRefPropertyChange(PropertyChangeEvent event) {
        if (IEditable.PROP_NAME.equals(event.getProperty())
                || IEditable.PROP_DESCRIPTION.equals(event.getProperty())) {
            runInUI(new Runnable() {

                @Override
                public void run() {
                    updateNames();
                }
            });
        } else if (IEditable.PROP_DIRTY.equals(event.getProperty())) {
            runInUI(new Runnable() {

                @Override
                public void run() {
                    firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);
                }
            });
        } else if (IEditable.PROP_MESSAGES.equals(event.getProperty())) {
            runInUI(new Runnable() {

                @Override
                public void run() {
                    updateMessages(true);
                }
            });
        }

    }

    private void runInUI(final Runnable runnable) {
        final Control control = pageContainer;
        if (control == null || control.isDisposed())
            return;
        final Display display = control.getDisplay();
        if (display == null || display.isDisposed())
            return;
        display.asyncExec(runnable);
    }

    @Override
    public void handleCommandStackEvent(CommandStackEvent event) {
        // override to do nothing
    }

    /*
     * (non-Javadoc)
     * @see org.xmind.ui.internal.editor.IMindMapPreviewGenerator#
     * generateMindMapPreview(org.xmind.ui.mindmap.IWorkbookRef,
     * java.io.OutputStream, org.xmind.ui.internal.editor.MindMapPreviewOptions)
     */
    @Override
    public Properties generateMindMapPreview(IWorkbookRef workbookRef, final ISheet sheet,
            final OutputStream output, MindMapPreviewOptions options) throws IOException {
        Assert.isLegal(output != null);

        final Properties properties = new Properties();
        if (sheet == null
                || MindMapUIPlugin.getDefault().getPreferenceStore().getBoolean(PrefConstants.PREVIEW_SKIPPED)
                || skipNextPreviewImage) {
            URL url = BundleUtility.find(MindMapUI.PLUGIN_ID, IMindMapImages.DEFAULT_THUMBNAIL);
            if (url != null) {
                InputStream input = url.openStream();
                try {
                    FileUtils.transfer(input, output, false);
                } finally {
                    input.close();
                }
            }
            skipNextPreviewImage = false;
            return properties;
        }

        IEncryptable encryptable = workbookRef.getAdapter(IEncryptable.class);
        if (encryptable != null && encryptable.hasPassword()) {
            URL url = BundleUtility.find(MindMapUI.PLUGIN_ID, IMindMapImages.ENCRYPTED_THUMBNAIL);
            if (url != null) {
                InputStream input = url.openStream();
                try {
                    FileUtils.transfer(input, output, false);
                } finally {
                    input.close();
                }
            }
            return properties;
        }

        Shell parentShell = getSite().getShell();
        final Display display = parentShell.getDisplay();
        final MindMapImageExporter exporter = new MindMapImageExporter(display);
        final Exception[] error = new Exception[1];
        display.syncExec(new Runnable() {

            public void run() {
                IGraphicalEditorPage page = findPage(sheet);
                if (page != null) {
                    IGraphicalViewer sourceViewer = page.getViewer();
                    IMindMap map = (IMindMap) sourceViewer.getAdapter(IMindMap.class);
                    if (map == null || map.getCentralTopic().equals(map.getSheet().getRootTopic())) {
                        exporter.setSourceViewer(sourceViewer, null, null,
                                new Insets(MindMapUI.DEFAULT_EXPORT_MARGIN));
                    } else {
                        exporter.setSource(new MindMap(sheet), null, new Insets(MindMapUI.DEFAULT_EXPORT_MARGIN));
                    }
                } else {
                    exporter.setSource(new MindMap(sheet), null, new Insets(MindMapUI.DEFAULT_EXPORT_MARGIN));
                }
                exporter.setResize(ResizeConstants.RESIZE_MAXPIXELS, 1280, 1024);
                exporter.setTargetStream(output);

                try {
                    exporter.export();
                } catch (SWTException e) {
                    error[0] = e;
                    return;
                }

                org.eclipse.draw2d.geometry.Point origin = exporter.calcRelativeOrigin();

                properties.put(PREVIEW_ORIGIN_X, String.valueOf(origin.x));
                properties.put(PREVIEW_ORIGIN_Y, String.valueOf(origin.y));
                properties.put(PREVIEW_BACKGROUND, exporter.getBackgroundColor());
            }
        });

        if (error[0] != null) {
            throw new IOException(error[0]);
        }

        return properties;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.xmind.ui.wizards.ISaveContext#getContextVariable(java.lang.Class)
     */
    @Override
    public <T> T getContextVariable(Class<T> key) {
        return getSite().getService(key);
    }

    /*
     * (non-Javadoc)
     * @see
     * org.xmind.ui.wizards.ISaveContext#getContextVariable(java.lang.String)
     */
    @Override
    public Object getContextVariable(String key) {
        IEvaluationService service = getSite().getService(IEvaluationService.class);
        Assert.isNotNull(service);
        Object variable = service.getCurrentState().getVariable(key);
        return variable == IEvaluationContext.UNDEFINED_VARIABLE ? null : variable;
    }

    private void activateFileNotifier() {
        getEditorSite().getWorkbenchWindow().getWorkbench().addWindowListener(getWindowListener());
        getEditorSite().getPage().addSelectionListener(getEditorSite().getId(), getSelectionListener());
    }

    private void deactivateFileNotifier() {
        getEditorSite().getWorkbenchWindow().getWorkbench().removeWindowListener(getWindowListener());
        getEditorSite().getPage().removeSelectionListener(getEditorSite().getId(), getSelectionListener());
    }

    private IWindowListener getWindowListener() {
        if (windowListener == null) {
            windowListener = new IWindowListener() {

                @Override
                public void windowOpened(IWorkbenchWindow window) {
                }

                @Override
                public void windowDeactivated(IWorkbenchWindow window) {
                }

                @Override
                public void windowClosed(IWorkbenchWindow window) {
                }

                @Override
                public void windowActivated(IWorkbenchWindow window) {
                    IEditorPart activeEditor = getEditorSite().getPage().getActiveEditor();
                    if (activeEditor == MindMapEditor.this) {
                        if (workbookRef != null) {
                            workbookRef.activateNotifier();
                        }
                    }
                }
            };
        }
        return windowListener;
    }

    private ISelectionListener getSelectionListener() {
        if (selectionListener == null) {
            selectionListener = new ISelectionListener() {

                @Override
                public void selectionChanged(IWorkbenchPart part, ISelection selection) {
                    if (workbookRef != null) {
                        workbookRef.activateNotifier();
                    }
                }
            };
        }
        return selectionListener;
    }

    @Override
    public void fileChanged(final String title, final String message, final String[] buttons) {
        if (workbookRef.getState() != IWorkbookRef.NORMAL) {
            return;
        }

        if (ignoreFileChanged) {
            return;
        }

        Display.getDefault().asyncExec(new Runnable() {

            @Override
            public void run() {
                deactivateFileNotifier();
                getEditorSite().getPage().activate(MindMapEditor.this);
                if (!isDirty()) {
                    reload();
                } else {
                    MessageDialog dialog = new MessageDialog(null, title, null,
                            NLS.bind(MindMapMessages.MindMapEditor_fileChangedDialog_message_prefix + message,
                                    workbookRef.getName()),
                            MessageDialog.CONFIRM, buttons, 0);

                    int code = dialog.open();
                    if (code == 0) {
                        reload();
                    } else if (code == 1 || code == -1) {
                        ignoreFileChanged = true;
                    }
                }

                activateFileNotifier();
            }
        });
    }

    @Override
    public void fileRemoved(final String title, final String message, final String[] buttons, boolean forceQuit) {
        if (workbookRef.getState() != IWorkbookRef.NORMAL) {
            return;
        }

        if (forceQuit) {
            Display.getDefault().asyncExec(new Runnable() {

                @Override
                public void run() {
                    closeEditor();
                }
            });
            return;
        }

        Display.getDefault().asyncExec(new Runnable() {

            @Override
            public void run() {
                deactivateFileNotifier();
                getEditorSite().getPage().activate(MindMapEditor.this);
                if (!isDirty()) {
                    closeEditor();
                } else {
                    MessageDialog dialog = new MessageDialog(null, title, null,
                            NLS.bind(MindMapMessages.MindMapEditor_fileRemovedDialog_message_prefix + message,
                                    workbookRef.getName()),
                            MessageDialog.CONFIRM, buttons, 0);

                    int code = dialog.open();
                    if (code == 0) {
                        doSaveAs();
                    } else if (code == 1 || code == -1) {
                        closeEditor();
                    }

                    activateFileNotifier();
                }
            }
        });
    }

}