/***************************************************************************** * Copyright (c) 2009 CEA LIST & LIFL * * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cedric Dumoulin Cedric.dumoulin@lifl.fr - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.infra.core.sasheditor.internal; import java.io.PrintWriter; import java.io.StringWriter; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.jface.window.Window; import org.eclipse.papyrus.infra.core.sasheditor.Activator; import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.IEditorModel; import org.eclipse.papyrus.infra.core.sasheditor.editor.IEditorPage; import org.eclipse.papyrus.infra.core.sasheditor.internal.eclipsecopy.IMultiPageEditorSite; import org.eclipse.papyrus.infra.core.sasheditor.internal.eclipsecopy.MultiPageEditorSite; import org.eclipse.papyrus.infra.core.sasheditor.internal.eclipsecopy.MultiPageEditorSite4x; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.internal.ErrorEditorPart; import org.eclipse.ui.internal.dnd.IDropTarget; import org.eclipse.ui.internal.misc.StatusUtil; import org.eclipse.ui.part.EditorActionBarContributor; import org.eclipse.ui.part.IWorkbenchPartOrientation; /** * This is a controler/part for an Editor. It is associated to a {@link IEditorModel}. * This Part encapsulate an Eclipse Editor implementing {@link IEditorPart}. * * @author dumoulin * @author <a href="mailto:thomas.szadel@atosorigin.com">Thomas SZADEL</a> Improve the error text (avoid NPE) */ @SuppressWarnings("restriction") public class EditorPart extends PagePart implements IEditorPage { /** * The model representing the editor. */ private IEditorModel editorModel; /** * The created Eclipse editor. */ private IEditorPart editorPart; /** * The SWT Control containning the editor's controls. */ private Composite editorControl; /** * The MultiPageContainer system. This is the manager of all tiles. */ // private SashWindowsContainer tilesContainer; /** * The manager used to access main editor properties like site, actionbars, ... */ private IMultiEditorManager multiEditorManager; /** * Parent owning this PagePart. * Can be null if the Part is orphaned. Even if it is orphaned, the Item still set. */ // protected TabFolderPart parent; /** * Listen on mouse enter event. * Try to get an event indicating that the mouse enter over the editor. * This can be used to switch the active editor. * TODO This doesn't work yet. */ private Listener mouseEnterListener = new Listener() { /** * (non-Javadoc) * * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ public void handleEvent(Event event) { // Point globalPos = new Point(event.x, event.y); // System.out.println(this.getClass().getSimpleName() + ".handleEvent(" + eventName(event.type) + ", " + globalPos + ")"); } }; /** * Listener on widget disposed event. When the widget is disposed, the associated IEditor dispose() * method is called. * */ private DisposeListener widgetDisposedListener = new DisposeListener() { /** * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) * @see SashWindowsContainer#dispose() * @param e */ public void widgetDisposed(DisposeEvent e) { // We dispose the associated editor. disposeEditorPart(); } }; // To be removed // private String eventName(int eventType) { // switch(eventType) { // case SWT.MouseEnter: // return "MouseEnter"; // case SWT.MouseDown: // return "MouseDown"; // case SWT.MouseExit: // return "MouseExit"; // case SWT.MouseHover: // return "MouseHover"; // case SWT.FocusIn: // return "FocusIn"; // case SWT.FocusOut: // return "FocusOut"; // case SWT.MouseMove: // return "MouseMove"; // case SWT.MouseUp: // return "MouseUp"; // case SWT.Activate: // return "Activate"; // default: // return Integer.toString(eventType); // } // } /** * Constructor. * * @param editorModel * The model of the editor. */ public EditorPart(TabFolderPart parent, IEditorModel editorModel, Object rawModel, IMultiEditorManager multiEditorManager) { super(parent, rawModel); this.editorModel = editorModel; this.multiEditorManager = multiEditorManager; } /** * Create the control of this part. * For a this implementations, also create the children's controls. * This method forward to {@link createPartControl(Composite)}. * * @param parent * TODO remove ? */ // public void createControl(Composite parent) { // createPartControl(parent); // } /** * Create the control of this Part, and children's controls. * * @param parent The SWT parent of this EditorPart. This is usually the {@link TabFolderPart}'s control. */ @Override public void createPartControl(Composite parent) { try { // Create the editor. editorPart = createIEditorPart(); // Initialize it and create its controls. editorControl = createEditorPartControl(parent, editorPart); attachListeners(editorControl, true); } catch (PartInitException e) { Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getLocalizedMessage(), e)); // TODO Create a fake Error Page and initialize this part with. // editorPart = new ErrorEditorPart(); // editorControl = createEditorPartControl(parent, editorPart); // editorControl = createErrorPartControl(parent, e); createErrorEditorPart(parent, e); } catch (Exception e) { Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getLocalizedMessage(), e)); // TODO Create a fake Error Page and initialize this part with. // editorControl = createErrorPartControl(parent, e); createErrorEditorPart(parent, e); } } /** * Create a Control showing the error. * * @param parent * Parent Control to which the Created Control should be attached * @param e * Exception containing the error. */ private Composite createErrorPartControl(Composite parent, Exception e) { Composite comp = new Composite(parent, SWT.NONE); comp.setLayout(new FillLayout()); // Show the stack trace StringWriter strOut = new StringWriter(); PrintWriter out = new PrintWriter(strOut); e.printStackTrace(out); out.flush(); out.close(); Text diag = new Text(comp, SWT.MULTI); diag.setSize(64, 32); diag.setText(strOut.toString()); return comp; } /** * Create an EditorPart showing the Exception. * This is used when the creation of the regular IEditorPart failed. * @param e */ private void createErrorEditorPart(Composite parent, Exception e) { try { PartInitException partInitException = new PartInitException( StatusUtil.getLocalizedMessage(e), StatusUtil.getCause(e)); editorPart = new ErrorEditorPart(partInitException.getStatus()); // Initialize it and create its controls. editorControl = createEditorPartControl(parent, editorPart); } catch (Exception ex) { // Even the ErrorEditorPart creation fail. // Use a more simple renderer. Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getLocalizedMessage(), e)); // TODO Create a fake Error Page and initialize this part with. editorControl = createErrorPartControl(parent, e); } } /** * Create the editor associated to this TabPart. * * @return * @throws PartInitException */ private IEditorPart createIEditorPart() throws PartInitException { return editorModel.createIEditorPart(); } /** * Create the controls required by the editor. * Init the editor. * * @param viewer * @param editorInput * @param model * @return * @throws PartInitException */ private Composite createEditorPartControl(Composite parentControl, IEditorPart editor) throws PartInitException { IEditorSite site = createSite(editor); // call init first so that if an exception is thrown, we have created no // new widgets editor.init(site, getIMultiEditorManager().getEditorInput()); Composite editorParent = new Composite(parentControl, getOrientation(editor)); editorParent.setLayout(new FillLayout()); // Listen to dispose event editorParent.addDisposeListener(widgetDisposedListener); // Create editor controls editor.createPartControl(editorParent); editor.addPropertyListener(new IPropertyListener() { public void propertyChanged(Object source, int propertyId) { EditorPart.this.handlePropertyChange(propertyId); } }); // TODO test to be removed // attachListeners(editorParent, false); return editorParent; } /** * Attach SWT listeners. */ private void attachListeners(Control theControl, boolean recursive) { // All following methods listen to the same event. // So use only one of them // theControl.addListener(SWT.MouseEnter, mouseEnterListener); // // theControl.addListener(SWT.FocusIn, mouseEnterListener); // theControl.addListener(SWT.MouseMove, mouseEnterListener); // theControl.addListener(SWT.MouseHover, mouseEnterListener); // theControl.addListener(SWT.MouseUp, mouseEnterListener); // theControl.addListener(SWT.MouseDown, mouseEnterListener); theControl.addListener(SWT.Activate, mouseEnterListener); // if (recursive && theControl instanceof Composite) { // Composite composite = (Composite) theControl; // Control[] children = composite.getChildren(); // // for (int i = 0; i < children.length; i++) { // Control control = children[i]; // // attachListeners(control, true); // } // } } /** * Detach SWT listeners */ private void detachListeners(Control theControl, boolean recursive) { // theControl.removeListener(SWT.MouseEnter, mouseEnterListener); // theControl.removeListener(SWT.FocusIn, mouseEnterListener); // theControl.removeListener(SWT.MouseMove, mouseEnterListener); // theControl.removeListener(SWT.MouseHover, mouseEnterListener); // theControl.removeListener(SWT.MouseUp, mouseEnterListener); // theControl.removeListener(SWT.MouseDown, mouseEnterListener); theControl.removeListener(SWT.Activate, mouseEnterListener); // if (recursive && theControl instanceof Composite) { // Composite composite = (Composite) theControl; // Control[] children = composite.getChildren(); // // for (int i = 0; i < children.length; i++) { // Control control = children[i]; // // detachListeners(control, false); // } // } } /** * Handles a property change notification from a nested editor. The default implementation simply forwards * the change to listeners on this multi-page editor by calling <code>firePropertyChange</code> with the same property id. For example, if the * dirty state of a nested * editor changes (property id <code>IEditorPart.PROP_DIRTY</code>), this method handles it * by firing a property change event for <code>IEditorPart.PROP_DIRTY</code> to property listeners on this * multi-page editor. * <p> * Subclasses may extend or reimplement this method. * </p> * * @param propertyId * the id of the property that changed */ private void handlePropertyChange(int propertyId) { getSashWindowContainer().firePropertyChange(propertyId); } /** * Creates the site for the given nested editor. The <code>MultiPageEditorPart</code> implementation * of this method creates an instance of <code>MultiPageEditorSite</code>. Subclasses may * reimplement to create more specialized sites. * * @param editor * the nested editor * @return the editor site */ protected IEditorSite createSite(IEditorPart editor) { EditorActionBarContributor contributor = createEditorActionBarContributor(); // If you got compilation errors with the following line (under 3.X), // just comment the lines. // Do not commit these change :-) // try eclipse 4.x try { return new MultiPageEditorSite4x(multiEditorManager.getEditorSite(), editor, contributor); } catch (NoClassDefFoundError e) { // Ok, will use 3.x } catch (NoSuchMethodError e) { // Ok, will use 3.x } // Use 3.x return new MultiPageEditorSite(multiEditorManager.getEditorSite(), editor, contributor); } /** * Create the EditorActionBarContributor requested by the editor. * Creation is done by delegating to the IMultiEditorNestedPartManager. * * @return */ private EditorActionBarContributor createEditorActionBarContributor() { EditorActionBarContributor contributor = editorModel.getActionBarContributor(); return contributor; } /** * Get the orientation of the editor. * * @param editor * @return int the orientation flag * @see SWT#RIGHT_TO_LEFT * @see SWT#LEFT_TO_RIGHT * @see SWT#NONE */ private int getOrientation(IEditorPart editor) { if(editor instanceof IWorkbenchPartOrientation) { return ((IWorkbenchPartOrientation)editor).getOrientation(); } return Window.getDefaultOrientation(); } /** * Get the nested part manager. * * @return */ private IMultiEditorManager getIMultiEditorManager() { return multiEditorManager; } /** /** * Dispose all resources used by this part. * <br/> * The Part should not be used after it has been disposed. */ public void dispose() { detachListeners(editorControl, true); // dispose the SWT root control // This should also trigger the disposal of associated editor. editorControl.dispose(); // Dispose the editor. // disposeEditorPart(); // clean up properties to help GC editorModel = null; // editorPart = null; rawModel = null; } /** * Dispose this part and all its children. * The method is called recursively on children of the part. * <br/> * SWT resources have already been disposed. We don't need to dispose them again. * */ @Override public void disposeThisAndChildren() { // Dispose the editor (normally this should be already done). disposeEditorPart(); // clean up properties to help GC editorModel = null; // editorPart = null; rawModel = null; } /** * Disposes the associated editor and its site. * Do not dispose it twice. * * @param part * The part to dispose; must not be <code>null</code>. * @copy copied from org.eclipse.ui.part.MultiPageEditorPart.disposePart(IWorkbenchPart) v3.8 */ private void disposeEditorPart() { // Is the editor already disposed ? if( editorPart == null ) { return; } final IWorkbenchPart part = editorPart; editorPart = null; SafeRunner.run(new ISafeRunnable() { public void run() { IWorkbenchPartSite partSite = part.getSite(); part.dispose(); if (partSite instanceof IMultiPageEditorSite) { ((IMultiPageEditorSite) partSite).dispose(); } } public void handleException(Throwable e) { // Exception has already being logged by Core. Do nothing. } }); } /** * As we are a final Tile, we should be the requested part. * Return this TilePart. * * @param toFind * @return */ public PagePart findPart(Point toFind) { return this; } /** * Locates the part that intersects the given point and that have the expected type * * @param toFind * @return */ @Override public PagePart findPartAt(Point toFind, Class<?> expectedTileType) { if(expectedTileType == this.getClass()) { return this; } // Not found !! // The tile contains the position, but the type is not found. throw new UnsupportedOperationException("Tile match the expected position '" + toFind + "' but there is no Tile of requested type '" + expectedTileType.getClass().getName() + "'"); } /** * @param control * @return */ public PagePart findPart(Object control) { if(getControl() == control) { return this; } // Not found return null; } /** * Returns the active nested editor if there is one. * <p> * Subclasses should not override this method * </p> * * @return the active nested editor, or <code>null</code> if none */ public IEditorPart getIEditorPart() { return editorPart; } /** * Get associated SWT Control. * * @return */ @Override public Composite getControl() { return editorControl; } /** * This is a container method. Not necessary in Leaf Tile. * TODO: change the interface. * * @param draggedObject * @param sourcePart * @param position * @return */ public IDropTarget getDropTarget(Object draggedObject, TabFolderPart sourcePart, Point position) { return null; } /** * @return */ @Override public GarbageState getGarbageState() { return garbageState; } /** * Is the associated editor dirty ? * Delegate to {@link IEditorPart.isDirty()} * * @return true if the associated editor is dirty. * * @unused */ public boolean isDirty() { return editorPart.isDirty(); } /** * Change the parent of the Tile. The parent is changed, and the control is * attached to the parent control. Change garbage state to {@link GarbageState.REPARENTED}. * Do not detach the Tile from its old parent. * * @param newParent * The tilePart that should be used as part parent. * @param compositeParent * The composite that should be used as parent. */ @Override public void reparent(TabFolderPart newParent) { // Change the tile parent this.parent = newParent; // Change the SWT parent. editorControl.setParent(newParent.getControl()); // Change state if(garbageState == GarbageState.UNVISITED || garbageState == GarbageState.ORPHANED) { garbageState = GarbageState.REPARENTED; } else { // Bad state, this is an internal error // TODO : log a warning ? throw new IllegalStateException("Try to change state from " + garbageState.toString() + " to REPARENTED. This is forbidden."); } } /** * Asks this part to take focus within the workbench. * Set the focus on the active nested part if the part is a container. */ @Override public void setFocus() { editorPart.setFocus(); } /** * Synchronize the Part, and its children. PartMap contains a snapshot of the available part before * the synchronization. After synchronization, unreachable parts should be marked "orphaned" (= no * parent). * Do nothing in this implementation, as we are a final leaf, and there is nothing to synchronize * with the underlying model. * * @param partMap */ public void synchronize2(PartLists partMap) { } /** * Garbage this part. * This part will be not used anymore. * The part is already marked as ORPHANED. It is not used anymore. It is already detached * from its parent. * <br> * This method is called by the sashwindows garbage mechanism after the Part has been marked as ORPHANED. * All resources associated to this part can be disposed. * */ @Override public void garbage() { dispose(); // fire appropriate life cycle event getSashWindowContainer().getLifeCycleEventProvider().firePageClosedEvent(this); } /** * Accept the provided visitor. * Call the corresponding accept method in the visitor. * * @param visitor * @return */ @Override public boolean visit(IPartVisitor visitor) { return visitor.accept(this); } /** * Visit the children of this Tile. * There is no child, so do nothing. * * @param visitor */ public boolean visitChildren(IPartVisitor visitor) { return true; } /** * Show item status. */ protected void showStatus() { // System.out.println( "EditorTile: " // + " disposed=" + editorControl.isDisposed() // + ", visible=" + editorControl.isVisible() // + ", garbState=" + garbageState // + ", '" + editorPart.getTitle() // + "', " + this); String title = (editorPart != null ? editorPart.getTitle() : "no editorPart"); System.out.printf("EditorTile: disposed=%-5b, visible=%-5b, garbState=%-10s, %s, %s\n", editorControl.isDisposed(), (editorControl.isDisposed() ? false : editorControl.isVisible()), garbageState, title, this); } /** * Get the title for this part. {@inheritDoc} */ @Override public String getPageTitle() { return editorModel.getTabTitle(); } /** * Return an icon for this part. {@inheritDoc} */ @Override public Image getPageIcon() { return editorModel.getTabIcon(); } }