| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Stefan Xenos, IBM; Chris Torrence, ITT Visual Information Solutions - bug 51580 |
| * Nikolay Botev - bug 240651 |
| * Andrey Loskutov <loskutov@gmx.de> - Bug 372799 |
| * Daniel Kruegler <daniel.kruegler@gmail.com> - Bug 495940 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import org.eclipse.core.runtime.Adapters; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.e4.core.contexts.IEclipseContext; |
| import org.eclipse.e4.core.services.events.IEventBroker; |
| import org.eclipse.e4.ui.model.application.ui.basic.MPart; |
| import org.eclipse.e4.ui.workbench.IPresentationEngine; |
| import org.eclipse.e4.ui.workbench.UIEvents; |
| import org.eclipse.e4.ui.workbench.modeling.EPartService; |
| import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; |
| import org.eclipse.e4.ui.workbench.renderers.swt.SWTPartRenderer; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.ui.IPropertyListener; |
| import org.eclipse.ui.ISaveablePart; |
| import org.eclipse.ui.ISaveablesLifecycleListener; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.ISizeProvider; |
| 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.IWorkbenchPartReference; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.internal.e4.compatibility.CompatibilityPart; |
| import org.eclipse.ui.internal.misc.UIListenerLogging; |
| import org.eclipse.ui.internal.util.Util; |
| import org.osgi.service.event.Event; |
| import org.osgi.service.event.EventHandler; |
| |
| /** |
| * |
| */ |
| public abstract class WorkbenchPartReference implements IWorkbenchPartReference, ISizeProvider { |
| |
| /** |
| * Internal property ID: Indicates that the underlying part was created |
| */ |
| public static final int INTERNAL_PROPERTY_OPENED = 0x211; |
| |
| /** |
| * Internal property ID: Indicates that the underlying part was destroyed |
| */ |
| public static final int INTERNAL_PROPERTY_CLOSED = 0x212; |
| |
| /** |
| * Internal property ID: Indicates that the result of |
| * IEditorReference.isPinned() |
| */ |
| public static final int INTERNAL_PROPERTY_PINNED = 0x213; |
| |
| /** |
| * Internal property ID: Indicates that the result of getVisible() has changed |
| */ |
| public static final int INTERNAL_PROPERTY_VISIBLE = 0x214; |
| |
| /** |
| * Internal property ID: Indicates that the result of isZoomed() has changed |
| */ |
| public static final int INTERNAL_PROPERTY_ZOOMED = 0x215; |
| |
| /** |
| * Internal property ID: Indicates that the part has an active child and the |
| * active child has changed. (fired by PartStack) |
| */ |
| public static final int INTERNAL_PROPERTY_ACTIVE_CHILD_CHANGED = 0x216; |
| |
| /** |
| * Internal property ID: Indicates that changed in the min / max state has |
| * changed |
| */ |
| public static final int INTERNAL_PROPERTY_MAXIMIZED = 0x217; |
| |
| // State constants ////////////////////////////// |
| |
| /** |
| * State constant indicating that the part is not created yet |
| */ |
| public static int STATE_LAZY = 0; |
| |
| /** |
| * State constant indicating that the part is in the process of being created |
| */ |
| public static int STATE_CREATION_IN_PROGRESS = 1; |
| |
| /** |
| * State constant indicating that the part has been created |
| */ |
| public static int STATE_CREATED = 2; |
| |
| /** |
| * State constant indicating that the reference has been disposed (the reference |
| * shouldn't be used anymore) |
| */ |
| public static int STATE_DISPOSED = 3; |
| |
| static String MEMENTO_KEY = "memento"; //$NON-NLS-1$ |
| |
| /** |
| * Current state of the reference. Used to detect recursive creation errors, |
| * disposed references, etc. |
| */ |
| private int state = STATE_LAZY; |
| |
| protected IWorkbenchPart legacyPart; |
| private boolean pinned = false; |
| |
| /** |
| * API listener list |
| */ |
| private ListenerList<IPropertyListener> propChangeListeners = new ListenerList<>(); |
| |
| /** |
| * Internal listener list. Listens to the INTERNAL_PROPERTY_* property change |
| * events that are not yet API. TODO: Make these properties API in 3.2 |
| */ |
| private ListenerList<IPropertyListener> internalPropChangeListeners = new ListenerList<>(); |
| |
| private ListenerList<IPropertyChangeListener> partChangeListeners = new ListenerList<>(); |
| |
| protected Map<String, String> propertyCache = new HashMap<>(); |
| |
| private IPropertyListener propertyChangeListener = this::partPropertyChanged; |
| |
| private IPropertyChangeListener partPropertyChangeListener = this::partPropertyChanged; |
| |
| private IWorkbenchPage page; |
| |
| private MPart part; |
| |
| private IEclipseContext windowContext; |
| |
| private EventHandler contextEventHandler; |
| |
| public WorkbenchPartReference(IEclipseContext windowContext, IWorkbenchPage page, MPart part) { |
| this.windowContext = windowContext; |
| this.page = page; |
| this.part = part; |
| |
| // cache the reference in the MPart's transientData |
| if (part != null) { |
| part.getTransientData().put(IWorkbenchPartReference.class.getName(), this); |
| } |
| } |
| |
| private EventHandler createContextEventHandler() { |
| if (contextEventHandler == null) { |
| contextEventHandler = this::handleContextSet; |
| } |
| return contextEventHandler; |
| } |
| |
| private void handleContextSet(Event event) { |
| Object element = event.getProperty(UIEvents.EventTags.ELEMENT); |
| MPart part = getModel(); |
| if (element == part) { |
| IEclipseContext context = part.getContext(); |
| if (context != null) { |
| context.set(getClass().getName(), this); |
| unsubscribe(); |
| } |
| } |
| } |
| |
| public void subscribe() { |
| IEventBroker broker = windowContext.get(IEventBroker.class); |
| broker.subscribe(UIEvents.Context.TOPIC_CONTEXT, createContextEventHandler()); |
| } |
| |
| public void unsubscribe() { |
| if (contextEventHandler != null) { |
| IEventBroker broker = windowContext.get(IEventBroker.class); |
| broker.unsubscribe(contextEventHandler); |
| contextEventHandler = null; |
| } |
| } |
| |
| public boolean isDisposed() { |
| return state == STATE_DISPOSED; |
| } |
| |
| protected void checkReference() { |
| if (state == STATE_DISPOSED) { |
| throw new RuntimeException("Error: IWorkbenchPartReference disposed"); //$NON-NLS-1$ |
| } |
| } |
| |
| public MPart getModel() { |
| return part; |
| } |
| |
| protected void partPropertyChanged(Object source, int propId) { |
| firePropertyChange(propId); |
| |
| // Let the model manager know as well |
| if (propId == IWorkbenchPartConstants.PROP_DIRTY) { |
| IWorkbenchPart actualPart = getPart(false); |
| if (actualPart != null) { |
| SaveablesList modelManager = (SaveablesList) actualPart.getSite() |
| .getService(ISaveablesLifecycleListener.class); |
| modelManager.dirtyChanged(actualPart); |
| } |
| } |
| } |
| |
| protected void partPropertyChanged(PropertyChangeEvent event) { |
| firePartPropertyChange(event); |
| } |
| |
| /** |
| * Releases any references maintained by this part reference when its actual |
| * part becomes known (not called when it is disposed). |
| */ |
| protected void releaseReferences() { |
| |
| } |
| |
| /* package */ void addInternalPropertyListener(IPropertyListener listener) { |
| internalPropChangeListeners.add(listener); |
| } |
| |
| /* package */ void removeInternalPropertyListener(IPropertyListener listener) { |
| internalPropChangeListeners.remove(listener); |
| } |
| |
| protected void fireInternalPropertyChange(int id) { |
| for (IPropertyListener listener : internalPropChangeListeners) { |
| listener.propertyChanged(this, id); |
| } |
| } |
| |
| /** |
| * @see IWorkbenchPart |
| */ |
| @Override |
| public void addPropertyListener(IPropertyListener listener) { |
| // The properties of a disposed reference will never change, so don't |
| // add listeners |
| if (isDisposed()) { |
| return; |
| } |
| |
| propChangeListeners.add(listener); |
| } |
| |
| /** |
| * @see IWorkbenchPart |
| */ |
| @Override |
| public void removePropertyListener(IPropertyListener listener) { |
| // Currently I'm not calling checkReference here for fear of breaking things |
| // late in 3.1, but it may |
| // make sense to do so later. For now we just turn it into a NOP if the |
| // reference is disposed. |
| if (isDisposed()) { |
| return; |
| } |
| propChangeListeners.remove(listener); |
| } |
| |
| @Override |
| public String getTitle() { |
| String title = legacyPart == null ? part.getLocalizedLabel() : legacyPart.getTitle(); |
| return Util.safeString(title); |
| } |
| |
| @Override |
| public String getTitleToolTip() { |
| String toolTip = (String) part.getTransientData().get(IPresentationEngine.OVERRIDE_TITLE_TOOL_TIP_KEY); |
| if (toolTip == null || toolTip.isEmpty()) |
| toolTip = part.getLocalizedTooltip(); |
| return Util.safeString(toolTip); |
| } |
| |
| @Override |
| public String getId() { |
| String id = part.getElementId(); |
| |
| // Only return the descriptor id |
| int colonIndex = id.indexOf(':'); |
| return colonIndex == -1 ? id : id.substring(0, colonIndex); |
| } |
| |
| /** |
| * Computes a new title for the part. Subclasses may override to change the |
| * default behavior. |
| * |
| * @return the title for the part |
| */ |
| protected String computeTitle() { |
| return getRawTitle(); |
| } |
| |
| /** |
| * Returns the unmodified title for the part, or the empty string if none |
| * |
| * @return the unmodified title, as set by the IWorkbenchPart. Returns the empty |
| * string if none. |
| */ |
| protected final String getRawTitle() { |
| return Util.safeString(legacyPart.getTitle()); |
| } |
| |
| @Override |
| public final Image getTitleImage() { |
| if (isDisposed()) { |
| return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_DEF_VIEW); |
| } |
| |
| WorkbenchWindow wbw = (WorkbenchWindow) PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| if (part != null && wbw.getModel().getRenderer() instanceof SWTPartRenderer) { |
| SWTPartRenderer r = (SWTPartRenderer) wbw.getModel().getRenderer(); |
| Image image = r.getImage(part); |
| if (image != null) { |
| return image; |
| } |
| } |
| |
| return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_DEF_VIEW); |
| } |
| |
| /* package */ void fireVisibilityChange() { |
| fireInternalPropertyChange(INTERNAL_PROPERTY_VISIBLE); |
| } |
| |
| /* package */ void fireZoomChange() { |
| fireInternalPropertyChange(INTERNAL_PROPERTY_ZOOMED); |
| } |
| |
| protected void firePropertyChange(int id) { |
| immediateFirePropertyChange(id); |
| } |
| |
| private void immediateFirePropertyChange(int id) { |
| UIListenerLogging.logPartReferencePropertyChange(this, id); |
| for (IPropertyListener listener : propChangeListeners) { |
| listener.propertyChanged(legacyPart, id); |
| } |
| |
| fireInternalPropertyChange(id); |
| } |
| |
| public abstract PartSite getSite(); |
| |
| public abstract void initialize(IWorkbenchPart part) throws PartInitException; |
| |
| void addPropertyListeners() { |
| IWorkbenchPart workbenchPart = getPart(false); |
| if (workbenchPart != null) { |
| workbenchPart.addPropertyListener(propertyChangeListener); |
| |
| if (workbenchPart instanceof IWorkbenchPart3) { |
| ((IWorkbenchPart3) workbenchPart).addPartPropertyListener(partPropertyChangeListener); |
| |
| } |
| } |
| } |
| |
| @Override |
| public final IWorkbenchPart getPart(boolean restore) { |
| if (isDisposed()) { |
| return null; |
| } |
| |
| if (legacyPart == null) { |
| if (restore && part.getWidget() == null) { |
| // create the underlying client object backed by the part model |
| // with the rendering engine |
| EPartService partService = windowContext.get(EPartService.class); |
| partService.showPart(part, PartState.CREATE); |
| } |
| |
| // check if we were actually created, it is insufficient to check |
| // whether the 'object' feature is valid or not because it is one of |
| // the last things to be unset during the teardown process, this |
| // means we may return a valid workbench part even if it is actually |
| // in the process of being destroyed, see bug 328944 |
| if (part.getObject() instanceof CompatibilityPart) { |
| CompatibilityPart compatibilityPart = (CompatibilityPart) part.getObject(); |
| if (compatibilityPart != null) { |
| legacyPart = compatibilityPart.getPart(); |
| } |
| } else if (part.getObject() != null) { |
| if (part.getTransientData().get(E4PartWrapper.E4_WRAPPER_KEY) instanceof E4PartWrapper) { |
| return (IWorkbenchPart) part.getTransientData().get(E4PartWrapper.E4_WRAPPER_KEY); |
| } |
| } |
| } |
| |
| return legacyPart; |
| } |
| |
| public abstract IWorkbenchPart createPart() throws PartInitException; |
| |
| abstract IWorkbenchPart createErrorPart(); |
| |
| public abstract IWorkbenchPart createErrorPart(IStatus status); |
| |
| protected void doDisposeNestedParts() { |
| // To be implemented by subclasses |
| } |
| |
| /** |
| * |
| */ |
| private void doDisposePart() { |
| if (legacyPart != null) { |
| fireInternalPropertyChange(INTERNAL_PROPERTY_CLOSED); |
| // Don't let exceptions in client code bring us down. Log them and continue. |
| try { |
| legacyPart.removePropertyListener(propertyChangeListener); |
| if (legacyPart instanceof IWorkbenchPart3) { |
| ((IWorkbenchPart3) legacyPart).removePartPropertyListener(partPropertyChangeListener); |
| } |
| } catch (Exception e) { |
| WorkbenchPlugin.log(e); |
| } |
| legacyPart = null; |
| } |
| } |
| |
| public void setPinned(boolean newPinned) { |
| if (isDisposed()) { |
| return; |
| } |
| |
| if (newPinned == pinned) { |
| return; |
| } |
| |
| pinned = newPinned; |
| |
| immediateFirePropertyChange(IWorkbenchPartConstants.PROP_TITLE); |
| if (pinned) |
| part.getTags().add(IPresentationEngine.ADORNMENT_PIN); |
| else |
| part.getTags().remove(IPresentationEngine.ADORNMENT_PIN); |
| |
| fireInternalPropertyChange(INTERNAL_PROPERTY_PINNED); |
| } |
| |
| public boolean isPinned() { |
| return pinned; |
| } |
| |
| @Override |
| public String getPartProperty(String key) { |
| if (legacyPart != null) { |
| if (legacyPart instanceof IWorkbenchPart3) { |
| return ((IWorkbenchPart3) legacyPart).getPartProperty(key); |
| } |
| } else { |
| return propertyCache.get(key); |
| } |
| return null; |
| } |
| |
| @Override |
| public void addPartPropertyListener(IPropertyChangeListener listener) { |
| if (isDisposed()) { |
| return; |
| } |
| partChangeListeners.add(listener); |
| } |
| |
| @Override |
| public void removePartPropertyListener(IPropertyChangeListener listener) { |
| if (isDisposed()) { |
| return; |
| } |
| partChangeListeners.remove(listener); |
| } |
| |
| protected void firePartPropertyChange(PropertyChangeEvent event) { |
| for (IPropertyChangeListener l : partChangeListeners) { |
| l.propertyChange(event); |
| } |
| } |
| |
| protected void createPartProperties(IWorkbenchPart3 workbenchPart) { |
| Iterator<Entry<String, String>> i = propertyCache.entrySet().iterator(); |
| while (i.hasNext()) { |
| Entry<String, String> e = i.next(); |
| workbenchPart.setPartProperty(e.getKey(), e.getValue()); |
| } |
| } |
| |
| @Override |
| public int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, |
| int preferredResult) { |
| |
| ISizeProvider sizeProvider = Adapters.adapt(legacyPart, ISizeProvider.class); |
| if (sizeProvider != null) { |
| return sizeProvider.computePreferredSize(width, availableParallel, availablePerpendicular, preferredResult); |
| } |
| |
| return preferredResult; |
| } |
| |
| @Override |
| public int getSizeFlags(boolean width) { |
| ISizeProvider sizeProvider = Adapters.adapt(legacyPart, ISizeProvider.class); |
| if (sizeProvider != null) { |
| return sizeProvider.getSizeFlags(width); |
| } |
| return 0; |
| } |
| |
| @Override |
| public IWorkbenchPage getPage() { |
| return page; |
| } |
| |
| public void setPage(IWorkbenchPage newPage) { |
| page = newPage; |
| } |
| |
| @Override |
| public String getPartName() { |
| return part.getLocalizedLabel(); |
| } |
| |
| @Override |
| public String getContentDescription() { |
| IWorkbenchPart workbenchPart = getPart(false); |
| if (workbenchPart instanceof IWorkbenchPart2) { |
| return ((IWorkbenchPart2) workbenchPart).getContentDescription(); |
| } |
| return workbenchPart.getTitle(); |
| } |
| |
| @Override |
| public boolean isDirty() { |
| IWorkbenchPart part = getPart(false); |
| ISaveablePart saveable = SaveableHelper.getSaveable(part); |
| if (saveable != null) { |
| return saveable.isDirty(); |
| } |
| return false; |
| } |
| |
| public void invalidate() { |
| doDisposePart(); |
| } |
| |
| public final PartPane getPane() { |
| return new PartPane() { |
| @Override |
| public Control getControl() { |
| return part == null ? null : (Control) part.getWidget(); |
| } |
| }; |
| } |
| } |