/******************************************************************************* * Copyright (c) 2006, 2007 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui.internal; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IEditorRegistry; import org.eclipse.ui.IElementFactory; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPersistableEditor; import org.eclipse.ui.IPersistableElement; import org.eclipse.ui.IReusableEditor; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPart2; import org.eclipse.ui.IWorkbenchPart3; import org.eclipse.ui.IWorkbenchPartConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.editorsupport.ComponentSupport; import org.eclipse.ui.internal.misc.StatusUtil; import org.eclipse.ui.internal.misc.UIStats; import org.eclipse.ui.internal.part.NullEditorInput; import org.eclipse.ui.internal.registry.EditorDescriptor; import org.eclipse.ui.internal.registry.EditorRegistry; import org.eclipse.ui.internal.tweaklets.TabBehaviour; import org.eclipse.ui.internal.tweaklets.Tweaklets; import org.eclipse.ui.internal.util.Util; import org.eclipse.ui.part.IWorkbenchPartOrientation; import org.eclipse.ui.part.MultiEditor; import org.eclipse.ui.part.MultiEditorInput; import org.eclipse.ui.statushandlers.StatusManager; public class EditorReference extends WorkbenchPartReference implements IEditorReference { /** * */ private final EditorManager manager; private IMemento editorMemento; private IMemento editorState = null; /** * Flag that lets us detect malfunctioning editors that don't fire PROP_INPUT events. * It is never needed for a correctly-functioning */ private boolean expectingInputChange = false; /** * Flag that determines whether we've already reported that this editor is malfunctioning. * This prevents us from spamming the event log if we repeatedly detect the same error in * a particular editor. If we ever detect an editor is violating its public contract in * a way we can recover from (such as a missing property change event), we report the error * once and then silently ignore errors from the same editor. */ private boolean reportedMalfunctioningEditor = false; /** * User-readable name of the editor's input */ String name; String factoryId; IEditorInput restoredInput; /** * If the reference is instantiated as a MultiEditor, we need to dispose the * inner references correctly. */ private IEditorReference[] multiEditorChildren = null; /** * @param manager * The editor manager for this reference * @param input * our input * @param desc * the descriptor from the declaration */ public EditorReference(EditorManager manager, IEditorInput input, EditorDescriptor desc) { this(manager, input, desc, null); } /** * @param manager * The editor manager for this reference * @param input * our input * @param desc * the descriptor from the declaration * @param editorState * propogate state from another editor. Can be <code>null</code>. */ public EditorReference(EditorManager manager, IEditorInput input, EditorDescriptor desc, IMemento editorState) { this.manager = manager; initListenersAndHandlers(); restoredInput = input; this.editorState = editorState; init(desc.getId(), desc.getLabel(), "", desc.getImageDescriptor(), desc.getLabel(), ""); //$NON-NLS-1$//$NON-NLS-2$ } /** * Constructs a new editor reference for use by editors being restored from * a memento. */ EditorReference(EditorManager manager, IMemento memento) { this.manager = manager; initListenersAndHandlers(); this.editorMemento = memento; if (EditorManager.useIPersistableEditor()) { editorState = editorMemento.getChild(IWorkbenchConstants.TAG_EDITOR_STATE); } else { editorState = null; } String id = memento.getString(IWorkbenchConstants.TAG_ID); String title = memento.getString(IWorkbenchConstants.TAG_TITLE); String tooltip = Util.safeString(memento .getString(IWorkbenchConstants.TAG_TOOLTIP)); String partName = memento .getString(IWorkbenchConstants.TAG_PART_NAME); IMemento propBag = memento.getChild(IWorkbenchConstants.TAG_PROPERTIES); if (propBag != null) { IMemento[] props = propBag .getChildren(IWorkbenchConstants.TAG_PROPERTY); for (int i = 0; i < props.length; i++) { propertyCache.put(props[i].getID(), props[i].getTextData()); } } // For compatibility set the part name to the title if not found if (partName == null) { partName = title; } // Get the editor descriptor. EditorDescriptor desc = null; if (id != null) { desc = getDescriptor(id); } // desc may be null if id is null or desc is not found, but findImage below handles this String location = memento.getString(IWorkbenchConstants.TAG_PATH); IPath path = location == null ? null : new Path(location); ImageDescriptor iDesc = this.manager.findImage(desc, path); this.name = memento.getString(IWorkbenchConstants.TAG_NAME); if (this.name == null) { this.name = title; } setPinned("true".equals(memento.getString(IWorkbenchConstants.TAG_PINNED))); //$NON-NLS-1$ IMemento inputMem = memento.getChild(IWorkbenchConstants.TAG_INPUT); if (inputMem != null) { this.factoryId = inputMem .getString(IWorkbenchConstants.TAG_FACTORY_ID); } init(id, title, tooltip, iDesc, partName, ""); //$NON-NLS-1$ } public EditorDescriptor getDescriptor() { return getDescriptor(getId()); } /** * @since 3.1 * * @param id the id * @return the editor descriptor */ private EditorDescriptor getDescriptor(String id) { EditorDescriptor desc; IEditorRegistry reg = WorkbenchPlugin.getDefault() .getEditorRegistry(); desc = (EditorDescriptor) reg.findEditor(id); return desc; } /** * Initializes the necessary editor listeners and handlers */ private void initListenersAndHandlers() { // Create a property change listener to track the "close editors automatically" // preference and show/remove the pin icon on editors // Only 1 listener will be created in the EditorManager when necessary this.manager.checkCreateEditorPropListener(); // Create a keyboard shortcut handler for pinning editors // Only 1 handler will be created in the EditorManager when necessary this.manager.checkCreatePinEditorShortcutKeyHandler(); } protected PartPane createPane() { return new EditorPane(this, this.manager.page, this.manager.editorPresentation.getActiveWorkbook()); } /** * This method is called when there should be a change in the editor pin * status (added or removed) so that it will ask its presentable part * to fire a PROP_TITLE event in order for the presentation to request * the new icon for this editor */ public void pinStatusUpdated() { firePropertyChange(IWorkbenchPart.PROP_TITLE); } public String getFactoryId() { IEditorPart editor = getEditor(false); if (editor != null) { IPersistableElement persistable = editor.getEditorInput() .getPersistable(); if (persistable != null) { return persistable.getFactoryId(); } return null; } return factoryId; } protected String computePartName() { if (part instanceof IWorkbenchPart2) { return super.computePartName(); } return getRawTitle(); } public String getName() { if (part != null) { return getEditor(false).getEditorInput().getName(); } return name; } public IEditorPart getEditor(boolean restore) { return (IEditorPart)getPart(restore); } protected void releaseReferences() { super.releaseReferences(); editorMemento = null; editorState = null; name = null; factoryId = null; restoredInput = null; } void setName(String name) { this.name = name; } public IMemento getMemento() { return editorMemento; } public IWorkbenchPage getPage() { return this.manager.page; } protected void doDisposePart() { if (multiEditorChildren!=null) { for (int i=0; i<multiEditorChildren.length; ++i) { EditorReference ref = (EditorReference)multiEditorChildren[i]; if (ref!=null) { ref.dispose(); } } multiEditorChildren = null; } IEditorPart editor = (IEditorPart) part; super.doDisposePart(); if (editor != null) { EditorSite site = (EditorSite) editor.getEditorSite(); manager.disposeEditorActionBars((EditorActionBars) site.getActionBars()); site.dispose(); } this.manager.checkDeleteEditorResources(); editorMemento = null; editorState = null; restoredInput = new NullEditorInput(); } public IEditorInput getEditorInput() throws PartInitException { if (isDisposed()) { if (!(restoredInput instanceof NullEditorInput)) { restoredInput = new NullEditorInput(); } return restoredInput; } IEditorPart part = getEditor(false); if (part != null) { return part.getEditorInput(); } return getRestoredInput(); } private IEditorInput getRestoredInput() throws PartInitException { if (restoredInput != null) { return restoredInput; } // Get the input factory. IMemento editorMem = getMemento(); if (editorMem == null) { throw new PartInitException(NLS.bind(WorkbenchMessages.EditorManager_no_persisted_state, getId(), getName())); } IMemento inputMem = editorMem .getChild(IWorkbenchConstants.TAG_INPUT); String factoryID = null; if (inputMem != null) { factoryID = inputMem .getString(IWorkbenchConstants.TAG_FACTORY_ID); } if (factoryID == null) { throw new PartInitException(NLS.bind(WorkbenchMessages.EditorManager_no_input_factory_ID, getId(), getName())); } IAdaptable input = null; String label = null; // debugging only if (UIStats.isDebugging(UIStats.CREATE_PART_INPUT)) { label = getName() != null ? getName() : factoryID; } try { UIStats.start(UIStats.CREATE_PART_INPUT, label); IElementFactory factory = PlatformUI.getWorkbench() .getElementFactory(factoryID); if (factory == null) { throw new PartInitException(NLS.bind(WorkbenchMessages.EditorManager_bad_element_factory, new Object[] { factoryID, getId(), getName() })); } // Get the input element. input = factory.createElement(inputMem); if (input == null) { throw new PartInitException(NLS.bind(WorkbenchMessages.EditorManager_create_element_returned_null, new Object[] { factoryID, getId(), getName() })); } } finally { UIStats.end(UIStats.CREATE_PART_INPUT, input, label); } if (!(input instanceof IEditorInput)) { throw new PartInitException(NLS.bind(WorkbenchMessages.EditorManager_wrong_createElement_result, new Object[] { factoryID, getId(), getName() })); } restoredInput = (IEditorInput) input; return restoredInput; } /* (non-Javadoc) * @see org.eclipse.ui.IWorkbenchPartReference#getTitleImage() * This method will append a pin to the icon of the editor * if the "automatically close editors" option in the * preferences is enabled and the editor has been pinned. */ public ImageDescriptor computeImageDescriptor() { ImageDescriptor descriptor = super.computeImageDescriptor(); if (!isPinned()) { return descriptor; } // Check if the pinned preference is set IPreferenceStore prefStore = WorkbenchPlugin.getDefault() .getPreferenceStore(); boolean bUsePin = prefStore .getBoolean(IPreferenceConstants.REUSE_EDITORS_BOOLEAN) || ((TabBehaviour)Tweaklets.get(TabBehaviour.KEY)).alwaysShowPinAction(); if (!bUsePin) { return descriptor; } ImageDescriptor pinDesc = this.manager.getEditorPinImageDesc(); if (pinDesc == null) { return descriptor; } return new OverlayIcon(descriptor, pinDesc, new Point(16, 16)); } /** * Wrapper for restoring the editor. First, this delegates to busyRestoreEditorHelper * to do the real work of restoring the view. If unable to restore the editor, this * method tries to substitute an error part and return success. * * @return the created part */ protected IWorkbenchPart createPart() { if (EditorRegistry.EMPTY_EDITOR_ID.equals(getId())) { return getEmptyEditor(getDescriptor()); } PartInitException exception = null; IWorkbenchPart result = null; // Try to restore the editor -- this does the real work of restoring the editor // try { result = createPartHelper(); } catch (PartInitException e) { exception = e; } // If unable to create the part, create an error part instead // and pass the error to the status handling facility if (exception != null) { IStatus originalStatus = exception.getStatus(); IStatus logStatus = StatusUtil.newStatus(originalStatus, NLS.bind("Unable to create editor ID {0}: {1}", //$NON-NLS-1$ getId(), originalStatus.getMessage())); IStatus displayStatus = StatusUtil.newStatus(originalStatus, NLS.bind(WorkbenchMessages.EditorManager_unableToCreateEditor, originalStatus.getMessage())); // Pass the error to the status handling facility StatusManager.getManager().handle(logStatus); ErrorEditorPart part = new ErrorEditorPart(displayStatus); IEditorInput input; try { input = getEditorInput(); } catch (PartInitException e1) { input = new NullEditorInput(this); } EditorPane pane = (EditorPane)getPane(); pane.createControl((Composite) manager.page.getEditorPresentation().getLayoutPart().getControl()); EditorDescriptor descr = getDescriptor(); EditorSite site = new EditorSite(this, part, manager.page, descr); site.setActionBars(new EditorActionBars(manager.page, site.getWorkbenchWindow(), getId())); part.init(site, input); Composite parent = (Composite)pane.getControl(); Composite content = new Composite(parent, SWT.NONE); content.setLayout(new FillLayout()); try { part.createPartControl(content); } catch (Exception e) { content.dispose(); StatusUtil.handleStatus(e, StatusManager.SHOW | StatusManager.LOG); return null; } result = part; } return result; } protected void partPropertyChanged(Object source, int propId) { // Detect badly behaved editors that don't fire PROP_INPUT events // when they're supposed to. This branch is only needed to handle // malfunctioning editors. if (propId == IWorkbenchPartConstants.PROP_INPUT) { expectingInputChange = false; } super.partPropertyChanged(source, propId); } /** * Attempts to set the input of the editor to the given input. Note that the input * can't always be changed for an editor. Editors that don't implement IReusableEditor * can't have their input changed once they've been materialized. * * @since 3.1 * * @param input new input * @return true iff the input was actually changed */ public boolean setInput(IEditorInput input) { if (part != null) { if (part instanceof IReusableEditor) { IReusableEditor editor = (IReusableEditor) part; expectingInputChange = true; editor.setInput(input); // If the editor never fired a PROP_INPUT event, log the fact that we've discovered // a buggy editor and fire the event for free. Firing the event for free isn't required // and cannot be relied on (it only works if the input change was triggered by this // method, and there are definitely other cases where events will still be lost), // but older versions of the workbench did this so we fire it here in the spirit // of playing nice. if (expectingInputChange) { // Log the fact that this editor is broken reportMalfunction("Editor is not firing a PROP_INPUT event in response to IReusableEditor.setInput(...)"); //$NON-NLS-1$ // Fire the property for free (can't be relied on since there are other ways the input // can change, but we do it here to be consistent with older versions of the workbench) firePropertyChange(IWorkbenchPartConstants.PROP_INPUT); } return editor.getEditorInput() == input; } // Can't change the input if the editor already exists and isn't an IReusableEditor return false; } // Changing the input is trivial and always succeeds if the editor doesn't exist yet if (input != restoredInput) { restoredInput = input; firePropertyChange(IWorkbenchPartConstants.PROP_INPUT); } return true; } /** * Reports a recoverable malfunction in the system log. A recoverable malfunction would be * something like failure to fire an expected property change. Only the first malfunction is * recorded to avoid spamming the system log with repeated failures in the same editor. * * @since 3.1 * * @param string */ private void reportMalfunction(String string) { if (!reportedMalfunctioningEditor) { reportedMalfunctioningEditor = true; String errorMessage = "Problem detected with part " + getId(); //$NON-NLS-1$ if (part != null) { errorMessage += " (class = " + part.getClass().getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } errorMessage += ": " + string; //$NON-NLS-1$ StatusManager.getManager().handle( StatusUtil.newStatus(getDescriptor().getPluginId(), errorMessage, null)); } } private IEditorPart createPartHelper() throws PartInitException { // Things that will need to be disposed if an exception occurs (listed // in the order they // need to be disposed, and set to null if they haven't been created yet) Composite content = null; IEditorPart part = null; EditorActionBars actionBars = null; EditorSite site = null; try { IEditorInput editorInput = getEditorInput(); // Get the editor descriptor. String editorID = getId(); EditorDescriptor desc = getDescriptor(); if (desc == null) { throw new PartInitException(NLS.bind(WorkbenchMessages.EditorManager_missing_editor_descriptor, editorID)); } if (desc.isInternal()) { // Create an editor instance. try { UIStats.start(UIStats.CREATE_PART, editorID); part = manager.createPart(desc); if (part != null && part instanceof MultiEditor) { multiEditorChildren = manager.openMultiEditor(this, (MultiEditor) part, (MultiEditorInput) editorInput); } if (part instanceof IWorkbenchPart3) { createPartProperties((IWorkbenchPart3)part); } } finally { UIStats.end(UIStats.CREATE_PART, this, editorID); } } else if (desc.getId().equals( IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID)) { part = ComponentSupport.getSystemInPlaceEditor(); if (part == null) { throw new PartInitException(WorkbenchMessages.EditorManager_no_in_place_support); } } else { throw new PartInitException(NLS.bind(WorkbenchMessages.EditorManager_invalid_editor_descriptor, editorID)); } // Create a pane for this part PartPane pane = getPane(); pane.createControl((Composite) manager.page.getEditorPresentation().getLayoutPart().getControl()); // Create controls int style = SWT.NONE; if(part instanceof IWorkbenchPartOrientation){ style = ((IWorkbenchPartOrientation) part).getOrientation(); } // Link everything up to the part reference (the part reference itself should not have // been modified until this point) site = manager.createSite(this, part, desc, editorInput); // if there is saved state that's appropriate, pass it on if (part instanceof IPersistableEditor && editorState != null) { ((IPersistableEditor) part).restoreState(editorState); } // Remember the site and the action bars (now that we've created them, we'll need to dispose // them if an exception occurs) actionBars = (EditorActionBars) site.getActionBars(); Composite parent = (Composite)pane.getControl(); content = new Composite(parent, style); content.setLayout(new FillLayout()); try { UIStats.start(UIStats.CREATE_PART_CONTROL, editorID); part.createPartControl(content); parent.layout(true); } finally { UIStats.end(UIStats.CREATE_PART_CONTROL, part, editorID); } // The editor should now be fully created. Exercise its public interface, and sanity-check // it wherever possible. If it's going to throw exceptions or behave badly, it's much better // that it does so now while we can still cancel creation of the part. PartTester.testEditor(part); return part; } catch (Exception e) { // Dispose anything which we allocated in the try block if (content != null) { try { content.dispose(); } catch (RuntimeException re) { StatusManager.getManager().handle( StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH, re)); } } if (part != null) { try { part.dispose(); } catch (RuntimeException re) { StatusManager.getManager().handle( StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH, re)); } } if (actionBars != null) { try { manager.disposeEditorActionBars(actionBars); } catch (RuntimeException re) { StatusManager.getManager().handle( StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH, re)); } } if (site != null) { try { site.dispose(); } catch (RuntimeException re) { StatusManager.getManager().handle( StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH, re)); } } throw new PartInitException(StatusUtil.getLocalizedMessage(e), StatusUtil.getCause(e)); } } /** * A quick way of finding out if this reference points to a MultiEditor. * It depends on the fact that a MultiEditor does not lazily * instantiate it's child editors. * * @return true if it has inner editor reference or the input is * MultiEditorInput. */ public boolean isMultiReference() { return multiEditorChildren!=null || restoredInput instanceof MultiEditorInput; } /** * Creates and returns an empty editor (<code>ErrorEditorPart</code>). * * @param descr the editor descriptor * @return the empty editor part or <code>null</code> in case of an exception */ public IEditorPart getEmptyEditor(EditorDescriptor descr) { ErrorEditorPart part = new ErrorEditorPart(); IEditorInput input; try { input = getEditorInput(); } catch (PartInitException e1) { input = new NullEditorInput(this); } EditorPane pane = (EditorPane)getPane(); pane.createControl((Composite) manager.page.getEditorPresentation().getLayoutPart().getControl()); EditorSite site = new EditorSite(this, part, manager.page, descr); site.setActionBars(new EditorActionBars(manager.page, site.getWorkbenchWindow(), getId())); part.init(site, input); Composite parent = (Composite)pane.getControl(); Composite content = new Composite(parent, SWT.NONE); content.setLayout(new FillLayout()); try { part.createPartControl(content); } catch (Exception e) { content.dispose(); StatusManager.getManager().handle( StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH, e)); return null; } this.part = part; // Add a dispose listener to the part. This dispose listener does nothing but log an exception // if the part's widgets get disposed unexpectedly. The workbench part reference is the only // object that should dispose this control, and it will remove the listener before it does so. part.setPartName("(Empty)"); //$NON-NLS-1$ refreshFromPart(); releaseReferences(); if (((WorkbenchPage)getPage()).getActiveEditorReference()!=this) { fireInternalPropertyChange(INTERNAL_PROPERTY_OPENED); } return part; } }