com.google.dart.tools.designer.editor.XmlDesignPage.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dart.tools.designer.editor.XmlDesignPage.java

Source

/*
 * Copyright (c) 2012, the Dart project authors.
 * 
 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.dart.tools.designer.editor;

import com.google.common.collect.Maps;
import com.google.dart.tools.designer.DartDesignerPlugin;
import com.google.dart.tools.designer.model.XmlObjectInfo;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.wb.core.controls.PageBook;
import org.eclipse.wb.core.editor.DesignerState;
import org.eclipse.wb.core.editor.IDesignPageSite;
import org.eclipse.wb.core.model.broadcast.EditorActivatedListener;
import org.eclipse.wb.core.model.broadcast.EditorActivatedRequest;
import org.eclipse.wb.gef.core.ICommandExceptionHandler;
import org.eclipse.wb.internal.core.DesignerPlugin;
import org.eclipse.wb.internal.core.EnvironmentUtils;
import org.eclipse.wb.internal.core.editor.DesignComposite;
import org.eclipse.wb.internal.core.editor.DesignPageSite;
import org.eclipse.wb.internal.core.editor.structure.PartListenerAdapter;
import org.eclipse.wb.internal.core.utils.Debug;
import org.eclipse.wb.internal.core.utils.exception.DesignerExceptionUtils;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

/**
 * {@link XmlEditorPage} for XML.
 * 
 * @author scheglov_ke
 * @coverage XML.editor
 */
public abstract class XmlDesignPage extends XmlEditorPage {
    protected IFile m_file;
    protected IDocument m_document;
    protected Composite m_composite;
    private PageBook m_pageBook;
    private XmlDesignComposite m_designComposite;
    private final Map<Class<?>, Composite> m_errorCompositesMap = Maps.newHashMap();
    private UndoManager m_undoManager;
    protected XmlObjectInfo m_rootObject;
    private DesignerState m_designerState = DesignerState.Undefined;
    private boolean m_forceDocumentListener;
    ////////////////////////////////////////////////////////////////////////////
    //
    // Life cycle
    //
    ////////////////////////////////////////////////////////////////////////////
    private final IPartListener m_partListener = new PartListenerAdapter() {
        @Override
        public void partActivated(IWorkbenchPart part) {
            if (part == m_editor) {
                ExecutionUtils.runAsync(new RunnableEx() {
                    @Override
                    public void run() throws Exception {
                        // TODO(scheglov)
                        //            GlobalStateXml.activate(m_rootObject);
                        if (m_active) {
                            checkDependenciesOnDesignPageActivation();
                        }
                    }
                });
            }
        }
    };

    @Override
    public void initialize(AbstractXmlEditor editor) {
        super.initialize(editor);
        m_file = ((IFileEditorInput) editor.getEditorInput()).getFile();
        m_document = editor.getDocument();
        m_undoManager = new UndoManager(this, m_document);
        m_editor.getEditorSite().getPage().addPartListener(m_partListener);
    }

    @Override
    public void dispose() {
        super.dispose();
        m_undoManager.deactivate();
        m_editor.getEditorSite().getPage().removePartListener(m_partListener);
        disposeAll(true);
    }

    /**
     * Disposes design and model.
     */
    private void disposeAll(final boolean force) {
        // dispose design
        if (!m_composite.isDisposed()) {
            dispose_beforePresentation();
            m_designComposite.disposeDesign();
        }
        // dispose model
        if (m_rootObject != null) {
            ExecutionUtils.runLog(new RunnableEx() {
                @Override
                public void run() throws Exception {
                    m_rootObject.refresh_dispose();
                    m_rootObject.getBroadcastObject().dispose();
                    disposeContext(force);
                    // TODO(scheglov)
                    //          GlobalStateXml.deactivate(m_rootObject);
                }
            });
            m_rootObject = null;
        }
    }

    /**
     * Sends notification that presentation will be disposed.
     */
    private void dispose_beforePresentation() {
        if (m_rootObject != null) {
            ExecutionUtils.runLog(new RunnableEx() {
                @Override
                public void run() throws Exception {
                    m_rootObject.getBroadcastObject().dispose_beforePresentation();
                }
            });
        }
    }

    /**
     * Disposes {@link EditorContext} of current hierarchy.
     * <p>
     * It is not guarantied that hierarchy exists, may be parsing was failed.
     * 
     * @param force is <code>true</code> if user closes editor or explicitly requests re-parsing.
     */
    protected void disposeContext(boolean force) {
        if (m_rootObject != null) {
            ExecutionUtils.runLog(new RunnableEx() {
                @Override
                public void run() throws Exception {
                    // TODO(scheglov)
                    //          m_rootObject.getContext().dispose();
                }
            });
        }
    }

    @Override
    public void setActive(boolean active) {
        super.setActive(active);
        if (active) {
            m_undoManager.activate();
            m_designComposite.onActivate();
            checkDependenciesOnDesignPageActivation();
        } else {
            if (!m_forceDocumentListener) {
                m_undoManager.deactivate();
            }
            m_designComposite.onDeActivate();
        }
    }

    /**
     * This editor and its "Design" page are activated. Check if some external dependencies are
     * changed so that reparse or refresh should be performed.
     */
    private void checkDependenciesOnDesignPageActivation() {
        if (m_rootObject != null) {
            ExecutionUtils.runLog(new RunnableEx() {
                @Override
                public void run() throws Exception {
                    EditorActivatedRequest request = new EditorActivatedRequest();
                    m_rootObject.getBroadcast(EditorActivatedListener.class).invoke(request);
                    if (request.isReparseRequested()) {
                        refreshGEF();
                    } else if (request.isRefreshRequested()) {
                        m_rootObject.refresh();
                    }
                }
            });
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Access
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return the internal {@link DesignComposite}.
     */
    public final DesignComposite getDesignComposite() {
        return m_designComposite;
    }

    /**
     * @return the current {@link DesignerState} of editor.
     */
    public DesignerState getDesignerState() {
        return m_designerState;
    }

    /**
     * @return the {@link UndoManager} of this editor.
     */
    public UndoManager getUndoManager() {
        return m_undoManager;
    }

    /**
     * Ensure that page always listens for {@link IDocument} changes, even if it is not active. We
     * need this for "split mode", when updates on "Source" page should cause delayed UI refresh.
     */
    public void forceDocumentListener() {
        m_forceDocumentListener = true;
    }

    /**
     * Sets {@link IRefreshStrategy} to respond to {@link IDocument} changes.
     */
    public void setRefreshStrategy(IRefreshStrategy refreshStrategy) {
        m_undoManager.setRefreshStrategy(refreshStrategy);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Control
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public Control createControl(Composite parent) {
        m_composite = new Composite(parent, SWT.NONE);
        m_composite.setLayout(new FillLayout());
        // page book
        m_pageBook = new PageBook(m_composite, SWT.NONE);
        // design composite
        ICommandExceptionHandler exceptionHandler = new ICommandExceptionHandler() {
            @Override
            public void handleException(Throwable exception) {
                handleDesignException(exception);
            }
        };
        m_designComposite = createDesignComposite(m_pageBook, exceptionHandler);
        // show "design" initially
        m_pageBook.showPage(m_designComposite);
        return m_composite;
    }

    /**
     * @return the toolkit specific {@link XmlDesignComposite} instance.
     */
    protected XmlDesignComposite createDesignComposite(Composite parent,
            ICommandExceptionHandler exceptionHandler) {
        return new XmlDesignComposite(parent, SWT.NONE, m_editor, exceptionHandler);
    }

    @Override
    public Control getControl() {
        return m_composite;
    }

    /**
     * Creates and caches the composites for displaying some error/warning messages.
     */
    @SuppressWarnings("unchecked")
    private <T extends Composite> T getErrorComposite(Class<T> compositeClass) throws Exception {
        T composite = (T) m_errorCompositesMap.get(compositeClass);
        if (composite == null) {
            Constructor<T> constructor = compositeClass.getConstructor(Composite.class, int.class);
            composite = constructor.newInstance(m_pageBook, SWT.NONE);
            m_errorCompositesMap.put(compositeClass, composite);
        }
        return composite;
    }

    /**
     * Handles any exception happened on "Design" page, such as exceptions in GEF commands, property
     * table, components tree.
     */
    private void handleDesignException(Throwable e) {
        // at first, try to make post-mortem screenshot
        Image screenshot;
        try {
            screenshot = DesignerExceptionUtils.makeScreenshot();
        } catch (Throwable ex) {
            screenshot = null;
        }
        // dispose current state to prevent any further exceptions
        disposeAll(true);
        // show exception
        if (EnvironmentUtils.isTestingTime()) {
            e.printStackTrace();
        }
        showExceptionOnDesignPane(e, screenshot);
    }

    /**
     * Makes this page disabled (during refresh) and again enabled.
     */
    private void setEnabled(boolean enabled) {
        m_composite.setRedraw(enabled);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Presentation
    //
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public String getName() {
        return "Design";
    }

    @Override
    public Image getImage() {
        return DartDesignerPlugin.getImage("editor_page_design.png");
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Render
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return <code>true</code> if parsing operation is slow, so should be performed with progress.
     */
    protected boolean shouldShowProgress() {
        return false;
    }

    /**
     * Performs toolkit specific parsing.
     */
    protected abstract XmlObjectInfo parse() throws Exception;

    /**
     * Disposes context and {@link #updateGEF()}.
     */
    public void refreshGEF() {
        disposeContext(true);
        updateGEF();
    }

    /**
     * Parses XML and displays it in GEF.
     */
    void updateGEF() {
        m_undoManager.refreshDesignerEditor();
    }

    /**
     * Parses {@link ICompilationUnit} and displays it in GEF.
     * 
     * @return <code>true</code> if parsing was successful.
     */
    boolean internal_refreshGEF() {
        // if "split mode", then try to parse, but expect that if may fail
        if (m_forceDocumentListener) {
            m_designComposite.setEnabled(false);
            try {
                parse();
            } catch (Throwable e) {
                return false;
            }
            m_designComposite.setEnabled(true);
        }
        // OK, do real parsing
        setEnabled(false);
        try {
            m_designerState = DesignerState.Parsing;
            disposeAll(false);
            // do parse
            if (shouldShowProgress()) {
                internal_refreshGEF_withProgress();
            } else {
                internal_refreshGEF(new NullProgressMonitor());
            }
            // success, show Design
            m_pageBook.showPage(m_designComposite);
            m_designerState = DesignerState.Successful;
            return true;
        } catch (Throwable e) {
            // show exception in editor
            showExceptionOnDesignPane(e, null);
            // failure
            return false;
        } finally {
            setEnabled(true);
        }
    }

    private void internal_refreshGEF_withProgress() throws Exception {
        final Display display = Display.getCurrent();
        IRunnableWithProgress runnable = new IRunnableWithProgress() {
            @Override
            public void run(final IProgressMonitor monitor) {
                monitor.beginTask("Opening Design page.", 6);
                //
                try {
                    DesignPageSite.setProgressMonitor(monitor);
                    display.syncExec(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                internal_refreshGEF(monitor);
                            } catch (Throwable e) {
                                ReflectionUtils.propagate(e);
                            }
                        }
                    });
                } catch (Throwable e) {
                    ReflectionUtils.propagate(e);
                } finally {
                    DesignPageSite.setProgressMonitor(null);
                }
                // done progress monitor
                monitor.subTask(null);
                ExecutionUtils.waitEventLoop(100);
                monitor.done();
            }
        };
        try {
            new ProgressMonitorDialog(DesignerPlugin.getShell()).run(false, false, runnable);
        } catch (InvocationTargetException e) {
            ReflectionUtils.propagate(e.getCause());
        } catch (Throwable e) {
            ReflectionUtils.propagate(e);
        }
    }

    private void internal_refreshGEF(IProgressMonitor monitor) throws Exception {
        monitor.subTask("Initializing...");
        monitor.worked(1);
        // do parse
        {
            long start = System.currentTimeMillis();
            monitor.subTask("Parsing...");
            Debug.print("Parsing...");
            m_rootObject = parse();
            monitor.worked(1);
            Debug.println("done: " + (System.currentTimeMillis() - start));
        }
        // refresh model (create GUI)
        {
            long start = System.currentTimeMillis();
            monitor.subTask("Refreshing...");
            m_rootObject.refresh();
            monitor.worked(1);
            Debug.println("refresh: " + (System.currentTimeMillis() - start));
        }
        // site
        installDesignPageSite();
        // refresh design
        m_designComposite.refresh(m_rootObject, monitor);
        // configure helpers
        m_undoManager.setRoot(m_rootObject);
    }

    private void installDesignPageSite() {
        IDesignPageSite designPageSite = new DesignPageSite() {
            @Override
            public void showSourcePosition(int position) {
                m_editor.showSourcePosition(position);
            }

            @Override
            public void openSourcePosition(int position) {
                m_editor.showSourcePosition(position);
                m_editor.showSource();
            }

            @Override
            public void handleException(Throwable e) {
                handleDesignException(e);
            }

            @Override
            public void reparse() {
                refreshGEF();
            }
        };
        DesignPageSite.Helper.setSite(m_rootObject, designPageSite);
    }

    /**
     * Displays the error information on Design Pane.
     * 
     * @param e the {@link Throwable} to display.
     * @param screenshot the {@link Image} of entire shell just before error. Can be <code>null</code>
     *          in case of parse error when no screenshot needed.
     */
    private void showExceptionOnDesignPane(Throwable e, Image screenshot) {
        m_designerState = DesignerState.Error;
        // dispose context, because it may be already allocated some resources before parsing failed
        disposeContext(true);
        // show Throwable
        try {
            e = DesignerExceptionUtils.rewriteException(e);
            if (DesignerExceptionUtils.isWarning(e)) {
                XmlWarningComposite composite = getErrorComposite(XmlWarningComposite.class);
                composite.setException(e);
                m_pageBook.showPage(composite);
            } else {
                DesignerPlugin.log(e);
                XmlExceptionComposite composite = getErrorComposite(XmlExceptionComposite.class);
                composite.setException(e, screenshot, m_file, m_document);
                m_pageBook.showPage(composite);
            }
        } catch (Throwable ex) {
            // ignore, prevent error while showing the error
        }
    }
}