/* * 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 } } }