/******************************************************************************* * Copyright (c) 2000, 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 * Stefan Xenos, IBM; Chris Torrence, ITT Visual Information Solutions - bug 51580 *******************************************************************************/ package org.eclipse.ui.internal; import java.util.BitSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; 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.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.IWorkbenchPartSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.misc.UIListenerLogging; import org.eclipse.ui.internal.util.Util; /** * */ 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; /** * Current state of the reference. Used to detect recursive creation errors, disposed * references, etc. */ private int state = STATE_LAZY; protected IWorkbenchPart part; private String id; protected PartPane pane; private boolean pinned = false; private String title; private String tooltip; /** * Stores the current Image for this part reference. Lazily created. Null if not allocated. */ private Image image = null; private ImageDescriptor defaultImageDescriptor; /** * Stores the current image descriptor for the part. */ private ImageDescriptor imageDescriptor; /** * API listener list */ private ListenerList 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 internalPropChangeListeners = new ListenerList(); private ListenerList partChangeListeners = new ListenerList(); private String partName; private String contentDescription; protected Map propertyCache = new HashMap(); /** * Used to remember which events have been queued. */ private BitSet queuedEvents = new BitSet(); private boolean queueEvents = false; private static DisposeListener prematureDisposeListener = new DisposeListener() { public void widgetDisposed(DisposeEvent e) { WorkbenchPlugin.log(new RuntimeException("Widget disposed too early!")); //$NON-NLS-1$ } }; private IPropertyListener propertyChangeListener = new IPropertyListener() { /* (non-Javadoc) * @see org.eclipse.ui.IPropertyListener#propertyChanged(java.lang.Object, int) */ public void propertyChanged(Object source, int propId) { partPropertyChanged(source, propId); } }; private IPropertyChangeListener partPropertyChangeListener = new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { partPropertyChanged(event); } }; public WorkbenchPartReference() { //no-op } public boolean isDisposed() { return state == STATE_DISPOSED; } protected void checkReference() { if (state == STATE_DISPOSED) { throw new RuntimeException("Error: IWorkbenchPartReference disposed"); //$NON-NLS-1$ } } /** * Calling this with deferEvents(true) will queue all property change events until a subsequent * call to deferEvents(false). This should be used at the beginning of a batch of related changes * to prevent duplicate property change events from being sent. * * @param shouldQueue */ private void deferEvents(boolean shouldQueue) { queueEvents = shouldQueue; if (queueEvents == false) { // do not use nextSetBit, to allow compilation against JCL Foundation (bug 80053) for (int i = 0, n = queuedEvents.size(); i < n; ++i) { if (queuedEvents.get(i)) { firePropertyChange(i); queuedEvents.clear(i); } } } } protected void setTitle(String newTitle) { if (Util.equals(title, newTitle)) { return; } title = newTitle; firePropertyChange(IWorkbenchPartConstants.PROP_TITLE); } protected void setPartName(String newPartName) { if (Util.equals(partName, newPartName)) { return; } partName = newPartName; firePropertyChange(IWorkbenchPartConstants.PROP_PART_NAME); } protected void setContentDescription(String newContentDescription) { if (Util.equals(contentDescription, newContentDescription)) { return; } contentDescription = newContentDescription; firePropertyChange(IWorkbenchPartConstants.PROP_CONTENT_DESCRIPTION); } protected void setImageDescriptor(ImageDescriptor descriptor) { if (Util.equals(imageDescriptor, descriptor)) { return; } Image oldImage = image; ImageDescriptor oldDescriptor = imageDescriptor; image = null; imageDescriptor = descriptor; // Don't queue events triggered by image changes. We'll dispose the image // immediately after firing the event, so we need to fire it right away. immediateFirePropertyChange(IWorkbenchPartConstants.PROP_TITLE); if (queueEvents) { // If there's a PROP_TITLE event queued, remove it from the queue because // we've just fired it. queuedEvents.clear(IWorkbenchPartConstants.PROP_TITLE); } // If we had allocated the old image, deallocate it now (AFTER we fire the property change // -- listeners may need to clean up references to the old image) if (oldImage != null) { JFaceResources.getResources().destroy(oldDescriptor); } } protected void setToolTip(String newToolTip) { if (Util.equals(tooltip, newToolTip)) { return; } tooltip = newToolTip; firePropertyChange(IWorkbenchPartConstants.PROP_TITLE); } protected void partPropertyChanged(Object source, int propId) { // We handle these properties directly (some of them may be transformed // before firing events to workbench listeners) if (propId == IWorkbenchPartConstants.PROP_CONTENT_DESCRIPTION || propId == IWorkbenchPartConstants.PROP_PART_NAME || propId == IWorkbenchPartConstants.PROP_TITLE) { refreshFromPart(); } else { // Any other properties are just reported to listeners verbatim 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); } /** * Refreshes all cached values with the values from the real part */ protected void refreshFromPart() { deferEvents(true); setPartName(computePartName()); setTitle(computeTitle()); setContentDescription(computeContentDescription()); setToolTip(getRawToolTip()); setImageDescriptor(computeImageDescriptor()); deferEvents(false); } protected ImageDescriptor computeImageDescriptor() { if (part != null) { return ImageDescriptor.createFromImage(part.getTitleImage(), Display.getCurrent()); } return defaultImageDescriptor; } public void init(String id, String title, String tooltip, ImageDescriptor desc, String paneName, String contentDescription) { Assert.isNotNull(id); Assert.isNotNull(title); Assert.isNotNull(tooltip); Assert.isNotNull(desc); Assert.isNotNull(paneName); Assert.isNotNull(contentDescription); this.id = id; this.title = title; this.tooltip = tooltip; this.partName = paneName; this.contentDescription = contentDescription; this.defaultImageDescriptor = desc; this.imageDescriptor = computeImageDescriptor(); } /** * 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) { Object listeners[] = internalPropChangeListeners.getListeners(); for (int i = 0; i < listeners.length; i++) { ((IPropertyListener) listeners[i]).propertyChanged(this, id); } } /** * @see IWorkbenchPart */ 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 */ 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); } public final String getId() { if (part != null) { IWorkbenchPartSite site = part.getSite(); if (site != null) { return site.getId(); } } return Util.safeString(id); } public String getTitleToolTip() { return Util.safeString(tooltip); } protected final String getRawToolTip() { return Util.safeString(part.getTitleToolTip()); } /** * Returns the pane name for the part * * @return the pane name for the part */ public String getPartName() { return Util.safeString(partName); } /** * Gets the part name directly from the associated workbench part, * or the empty string if none. * * @return */ protected final String getRawPartName() { String result = ""; //$NON-NLS-1$ if (part instanceof IWorkbenchPart2) { IWorkbenchPart2 part2 = (IWorkbenchPart2) part; result = Util.safeString(part2.getPartName()); } return result; } protected String computePartName() { return getRawPartName(); } /** * Returns the content description for this part. * * @return the pane name for the part */ public String getContentDescription() { return Util.safeString(contentDescription); } /** * Computes a new content description for the part. Subclasses may override to change the * default behavior * * @return the new content description for the part */ protected String computeContentDescription() { return getRawContentDescription(); } /** * Returns the content description as set directly by the part, or the empty string if none * * @return the unmodified content description from the part (or the empty string if none) */ protected final String getRawContentDescription() { if (part instanceof IWorkbenchPart2) { IWorkbenchPart2 part2 = (IWorkbenchPart2) part; return part2.getContentDescription(); } return ""; //$NON-NLS-1$ } public boolean isDirty() { if (!(part instanceof ISaveablePart)) { return false; } return ((ISaveablePart) part).isDirty(); } public String getTitle() { return Util.safeString(title); } /** * 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(part.getTitle()); } public final Image getTitleImage() { if (isDisposed()) { return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_DEF_VIEW); } if (image == null) { image = JFaceResources.getResources().createImageWithDefault(imageDescriptor); } return image; } public ImageDescriptor getTitleImageDescriptor() { if (isDisposed()) { return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_DEF_VIEW); } return imageDescriptor; } /* package */ void fireVisibilityChange() { fireInternalPropertyChange(INTERNAL_PROPERTY_VISIBLE); } /* package */ void fireZoomChange() { fireInternalPropertyChange(INTERNAL_PROPERTY_ZOOMED); } public boolean getVisible() { if (isDisposed()) { return false; } return getPane().getVisible(); } public void setVisible(boolean isVisible) { if (isDisposed()) { return; } getPane().setVisible(isVisible); } protected void firePropertyChange(int id) { if (queueEvents) { queuedEvents.set(id); return; } immediateFirePropertyChange(id); } private void immediateFirePropertyChange(int id) { UIListenerLogging.logPartReferencePropertyChange(this, id); Object listeners[] = propChangeListeners.getListeners(); for (int i = 0; i < listeners.length; i++) { ((IPropertyListener) listeners[i]).propertyChanged(part, id); } fireInternalPropertyChange(id); } public final IWorkbenchPart getPart(boolean restore) { if (isDisposed()) { return null; } if (part == null && restore) { if (state == STATE_CREATION_IN_PROGRESS) { IStatus result = WorkbenchPlugin.getStatus( new PartInitException(NLS.bind("Warning: Detected recursive attempt by part {0} to create itself (this is probably, but not necessarily, a bug)", //$NON-NLS-1$ getId()))); WorkbenchPlugin.log(result); return null; } try { state = STATE_CREATION_IN_PROGRESS; IWorkbenchPart newPart = createPart(); if (newPart != null) { part = newPart; // 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. getPane().getControl().addDisposeListener(prematureDisposeListener); part.addPropertyListener(propertyChangeListener); if (part instanceof IWorkbenchPart3) { ((IWorkbenchPart3)part).addPartPropertyListener(partPropertyChangeListener); } refreshFromPart(); releaseReferences(); fireInternalPropertyChange(INTERNAL_PROPERTY_OPENED); ISizeProvider sizeProvider = (ISizeProvider) Util.getAdapter(part, ISizeProvider.class); if (sizeProvider != null) { // If this part has a preferred size, indicate that the preferred size may have changed at this point if (sizeProvider.getSizeFlags(true) != 0 || sizeProvider.getSizeFlags(false) != 0) { fireInternalPropertyChange(IWorkbenchPartConstants.PROP_PREFERRED_SIZE); } } } } finally { state = STATE_CREATED; } } return part; } protected abstract IWorkbenchPart createPart(); protected abstract PartPane createPane(); /** * Returns the part pane for this part reference. Does not return null. Should not be called * if the reference has been disposed. * * TODO: clean up all code that has any possibility of calling this on a disposed reference * and make this method throw an exception if anyone else attempts to do so. * * @return */ public final PartPane getPane() { // Note: we should never call this if the reference has already been disposed, since it // may cause a PartPane to be created and leaked. if (pane == null) { pane = createPane(); } return pane; } public final void dispose() { if (isDisposed()) { return; } // Store the current title, tooltip, etc. so that anyone that they can be returned to // anyone that held on to the disposed reference. partName = getPartName(); contentDescription = getContentDescription(); tooltip = getTitleToolTip(); title = getTitle(); if (state == STATE_CREATION_IN_PROGRESS) { IStatus result = WorkbenchPlugin.getStatus( new PartInitException(NLS.bind("Warning: Blocked recursive attempt by part {0} to dispose itself during creation", //$NON-NLS-1$ getId()))); WorkbenchPlugin.log(result); return; } // Disposing the pane disposes the part's widgets. The part's widgets need to be disposed before the part itself. if (pane != null) { // Remove the dispose listener since this is the correct place for the widgets to get disposed Control targetControl = getPane().getControl(); if (targetControl != null) { targetControl.removeDisposeListener(prematureDisposeListener); } pane.dispose(); } doDisposePart(); if (pane != null) { pane.removeContributions(); } clearListenerList(internalPropChangeListeners); clearListenerList(partChangeListeners); Image oldImage = image; ImageDescriptor oldDescriptor = imageDescriptor; image = null; state = STATE_DISPOSED; imageDescriptor = ImageDescriptor.getMissingImageDescriptor(); defaultImageDescriptor = ImageDescriptor.getMissingImageDescriptor(); immediateFirePropertyChange(IWorkbenchPartConstants.PROP_TITLE); clearListenerList(propChangeListeners); if (oldImage != null) { JFaceResources.getResources().destroy(oldDescriptor); } } /** * Clears all of the listeners in a listener list. TODO Bug 117519 Remove * this method when fixed. * * @param list * The list to be clear; must not be <code>null</code>. */ private final void clearListenerList(final ListenerList list) { final Object[] listeners = list.getListeners(); for (int i = 0; i < listeners.length; i++) { list.remove(listeners[i]); } } /** * */ protected void doDisposePart() { if (part != null) { fireInternalPropertyChange(INTERNAL_PROPERTY_CLOSED); // Don't let exceptions in client code bring us down. Log them and continue. try { part.removePropertyListener(propertyChangeListener); if (part instanceof IWorkbenchPart3) { ((IWorkbenchPart3)part).removePartPropertyListener(partPropertyChangeListener); } part.dispose(); } catch (Exception e) { WorkbenchPlugin.log(e); } part = null; } } public void setPinned(boolean newPinned) { if (isDisposed()) { return; } if (newPinned == pinned) { return; } pinned = newPinned; setImageDescriptor(computeImageDescriptor()); fireInternalPropertyChange(INTERNAL_PROPERTY_PINNED); } public boolean isPinned() { return pinned; } /* (non-Javadoc) * @see org.eclipse.ui.IWorkbenchPartReference#getPartProperty(java.lang.String) */ public String getPartProperty(String key) { if (part != null) { if (part instanceof IWorkbenchPart3) { return ((IWorkbenchPart3) part).getPartProperty(key); } } else { return (String)propertyCache.get(key); } return null; } /* (non-Javadoc) * @see org.eclipse.ui.IWorkbenchPartReference#addPartPropertyListener(org.eclipse.jface.util.IPropertyChangeListener) */ public void addPartPropertyListener(IPropertyChangeListener listener) { if (isDisposed()) { return; } partChangeListeners.add(listener); } /* (non-Javadoc) * @see org.eclipse.ui.IWorkbenchPartReference#removePartPropertyListener(org.eclipse.jface.util.IPropertyChangeListener) */ public void removePartPropertyListener(IPropertyChangeListener listener) { if (isDisposed()) { return; } partChangeListeners.remove(listener); } protected void firePartPropertyChange(PropertyChangeEvent event) { Object[] l = partChangeListeners.getListeners(); for (int i = 0; i < l.length; i++) { ((IPropertyChangeListener) l[i]).propertyChange(event); } } protected void createPartProperties(IWorkbenchPart3 workbenchPart) { Iterator i = propertyCache.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = (Map.Entry) i.next(); workbenchPart.setPartProperty((String) e.getKey(), (String) e.getValue()); } } /* (non-Javadoc) * @see org.eclipse.ui.ISizeProvider#computePreferredSize(boolean, int, int, int) */ public int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredResult) { ISizeProvider sizeProvider = (ISizeProvider) Util.getAdapter(part, ISizeProvider.class); if (sizeProvider != null) { return sizeProvider.computePreferredSize(width, availableParallel, availablePerpendicular, preferredResult); } return preferredResult; } /* (non-Javadoc) * @see org.eclipse.ui.ISizeProvider#getSizeFlags(boolean) */ public int getSizeFlags(boolean width) { ISizeProvider sizeProvider = (ISizeProvider) Util.getAdapter(part, ISizeProvider.class); if (sizeProvider != null) { return sizeProvider.getSizeFlags(width); } return 0; } }