### Eclipse Workspace Patch 1.0 #P org.eclipse.ui.workbench Index: Eclipse UI/org/eclipse/ui/internal/PartList.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java,v retrieving revision 1.4 diff -u -r1.4 PartList.java --- Eclipse UI/org/eclipse/ui/internal/PartList.java 2 Mar 2006 14:44:48 -0000 1.4 +++ Eclipse UI/org/eclipse/ui/internal/PartList.java 27 Mar 2006 20:22:31 -0000 @@ -14,6 +14,7 @@ import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IPropertyListener; +import org.eclipse.ui.ISaveableModelManager; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartConstants; import org.eclipse.ui.IWorkbenchPartReference; @@ -201,6 +202,10 @@ // open event was fired or that a closed editor was somehow activated) Assert.isTrue(activeEditorReference != ref); + SaveableModelManager modelManager = (SaveableModelManager) actualPart + .getSite().getService(ISaveableModelManager.class); + modelManager.postOpen(actualPart); + // Fire the "part opened" event firePartOpened(ref); } Index: Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java,v retrieving revision 1.256 diff -u -r1.256 WorkbenchPage.java --- Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java 2 Mar 2006 14:44:48 -0000 1.256 +++ Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java 27 Mar 2006 20:22:32 -0000 @@ -69,6 +69,7 @@ import org.eclipse.ui.IPerspectiveDescriptor; import org.eclipse.ui.IPerspectiveRegistry; import org.eclipse.ui.IReusableEditor; +import org.eclipse.ui.ISaveableModelManager; import org.eclipse.ui.ISaveablePart; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IShowEditorInput; @@ -1217,30 +1218,24 @@ IEditorReference[] editorRefs = (IEditorReference[]) toClose.toArray(new IEditorReference[toClose.size()]); - if (save) { - // Intersect the dirty editors with the editors that are closing - IEditorPart[] dirty = getDirtyEditors(); - List intersect = new ArrayList(); - for (int i = 0; i < editorRefs.length; i++) { - IEditorReference reference = editorRefs[i]; - IEditorPart refPart = reference.getEditor(false); - if (refPart != null) { - for (int j = 0; j < dirty.length; j++) { - if (refPart.equals(dirty[j]) && refPart.isSaveOnCloseNeeded()) { - intersect.add(refPart); - break; - } - } - } - } - // Save parts, exit the method if cancel is pressed. - if (intersect.size() > 0) { - if (!EditorManager.saveAll(intersect, true, true, - getWorkbenchWindow())) { - return false; - } + // notify the model manager before the close + List partsToClose = new ArrayList(); + for (int i = 0; i < editorRefs.length; i++) { + IEditorPart refPart = editorRefs[i].getEditor(false); + if (refPart != null) { + partsToClose.add(refPart); } } + SaveableModelManager modelManager = null; + Object postCloseInfo = null; + if(partsToClose.size()>0) { + modelManager = (SaveableModelManager) getWorkbenchWindow().getService(ISaveableModelManager.class); + // this may prompt for saving and return null if the user canceled: + postCloseInfo = modelManager.preCloseParts(partsToClose, save, getWorkbenchWindow()); + if (postCloseInfo==null) { + return false; + } + } // Fire pre-removal changes for (int i = 0; i < editorRefs.length; i++) { @@ -1270,6 +1265,10 @@ // Notify interested listeners after the close window.firePerspectiveChanged(this, getPerspective(), CHANGE_EDITOR_CLOSE); + + if(modelManager!=null) { + modelManager.postClose(postCloseInfo); + } // Return true on success. return true; @@ -2113,6 +2112,24 @@ } } + int refCount = getViewFactory().getReferenceCount(ref); + SaveableModelManager saveableModelManager = null; + Object postCloseInfo = null; + if (refCount == 1) { + IWorkbenchPart actualPart = ref.getPart(false); + if (actualPart != null) { + saveableModelManager = (SaveableModelManager) actualPart + .getSite().getService(ISaveableModelManager.class); + postCloseInfo = saveableModelManager.preCloseParts(Collections + .singletonList(actualPart), true, this + .getWorkbenchWindow()); + if (postCloseInfo==null) { + // cancel + return; + } + } + } + // Notify interested listeners before the hide window.firePerspectiveChanged(this, persp.getDesc(), ref, CHANGE_VIEW_HIDE); @@ -2127,6 +2144,10 @@ // Notify interested listeners after the hide window.firePerspectiveChanged(this, getPerspective(), CHANGE_VIEW_HIDE); + + if (saveableModelManager != null) { + saveableModelManager.postClose(postCloseInfo); + } } /* package */void refreshActiveView() { Index: Eclipse UI/org/eclipse/ui/internal/ViewFactory.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java,v retrieving revision 1.57 diff -u -r1.57 ViewFactory.java --- Eclipse UI/org/eclipse/ui/internal/ViewFactory.java 24 Feb 2006 18:36:12 -0000 1.57 +++ Eclipse UI/org/eclipse/ui/internal/ViewFactory.java 27 Mar 2006 20:22:31 -0000 @@ -225,6 +225,17 @@ } /** + * + * @param viewRef + * @return the current reference count for the given view + */ + public int getReferenceCount(IViewReference viewRef) { + String key = getKey(viewRef); + IViewReference ref = (IViewReference) counter.get(key); + return ref==null ? 0 : counter.getRef(key); + } + + /** * Releases an instance of a view. * * This factory does reference counting. For more info see Index: Eclipse UI/org/eclipse/ui/internal/Workbench.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java,v retrieving revision 1.390 diff -u -r1.390 Workbench.java --- Eclipse UI/org/eclipse/ui/internal/Workbench.java 7 Mar 2006 12:18:20 -0000 1.390 +++ Eclipse UI/org/eclipse/ui/internal/Workbench.java 27 Mar 2006 20:22:31 -0000 @@ -85,6 +85,7 @@ import org.eclipse.ui.IMemento; import org.eclipse.ui.IPerspectiveDescriptor; import org.eclipse.ui.IPerspectiveRegistry; +import org.eclipse.ui.ISaveableModelManager; import org.eclipse.ui.ISaveablePart; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWindowListener; @@ -1210,10 +1211,14 @@ } /** - * Initializes all of the default command-based services for the workbench. - * This also parses the registry and hooks up all the required listeners. + * Initializes all of the default services for the workbench. For + * initializing the command-based services, this also parses the registry + * and hooks up all the required listeners. */ private final void initializeDefaultServices() { + + serviceLocator.registerService(ISaveableModelManager.class, new SaveableModelManager()); + /* * Phase 1 of the initialization of commands. When this phase completes, * all the services and managers will exist, and be accessible via the Index: Eclipse UI/org/eclipse/ui/internal/EditorManager.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java,v retrieving revision 1.106 diff -u -r1.106 EditorManager.java --- Eclipse UI/org/eclipse/ui/internal/EditorManager.java 2 Mar 2006 14:44:48 -0000 1.106 +++ Eclipse UI/org/eclipse/ui/internal/EditorManager.java 27 Mar 2006 20:22:31 -0000 @@ -129,9 +129,9 @@ // Handler for the pin editor keyboard shortcut private IHandlerActivation pinEditorHandlerActivation = null; - private static final String RESOURCES_TO_SAVE_MESSAGE = WorkbenchMessages.EditorManager_saveResourcesMessage; + static final String RESOURCES_TO_SAVE_MESSAGE = WorkbenchMessages.EditorManager_saveResourcesMessage; - private static final String SAVE_RESOURCES_TITLE = WorkbenchMessages.EditorManager_saveResourcesTitle; + static final String SAVE_RESOURCES_TITLE = WorkbenchMessages.EditorManager_saveResourcesTitle; /** * EditorManager constructor comment. Index: Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java,v retrieving revision 1.2 diff -u -r1.2 DefaultSaveableModel.java --- Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java 24 Feb 2006 18:36:12 -0000 1.2 +++ Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java 27 Mar 2006 20:22:31 -0000 @@ -97,4 +97,30 @@ return false; } + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return part.hashCode(); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final DefaultSaveableModel other = (DefaultSaveableModel) obj; + if (part == null) { + if (other.part != null) + return false; + } else if (!part.equals(other.part)) + return false; + return true; + } + } Index: Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java,v retrieving revision 1.38 diff -u -r1.38 WorkbenchPartReference.java --- Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java 2 Mar 2006 14:44:48 -0000 1.38 +++ Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java 27 Mar 2006 20:22:32 -0000 @@ -24,6 +24,7 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IPropertyListener; +import org.eclipse.ui.ISaveableModelManager; import org.eclipse.ui.ISaveablePart; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchPart; @@ -272,7 +273,15 @@ // 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) { + SaveableModelManager modelManager = (SaveableModelManager) actualPart.getSite().getService(ISaveableModelManager.class); + modelManager.dirtyChanged(actualPart); + } + } } /** Index: Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java,v retrieving revision 1.7 diff -u -r1.7 ReferenceCounter.java --- Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java 24 Feb 2006 18:36:12 -0000 1.7 +++ Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java 27 Mar 2006 20:22:31 -0000 @@ -127,6 +127,18 @@ } /** + * @param id is a unique ID for the object. + * @return the current ref count + */ + public int getRef(Object id) { + RefRec rec = (RefRec) mapIdToRec.get(id); + if (rec == null) { + return 0; + } + return rec.refCount; + } + + /** * Removes one reference from an object in the counter. * If the ref count drops to 0 the object is removed from * the counter completely. @@ -135,17 +147,17 @@ * @return the new ref count */ public int removeRef(Object id) { - RefRec rec = (RefRec) mapIdToRec.get(id); - if (rec == null) { - return 0; - } - int newCount = rec.removeRef(); - if (newCount <= 0) { - mapIdToRec.remove(id); - } - return newCount; + RefRec rec = (RefRec) mapIdToRec.get(id); + if (rec == null) { + return 0; + } + int newCount = rec.removeRef(); + if (newCount <= 0) { + mapIdToRec.remove(id); + } + return newCount; } - + /** * Returns a complete list of the values in the counter. * Index: Eclipse UI/org/eclipse/ui/ISaveableModelSource.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java,v retrieving revision 1.1 diff -u -r1.1 ISaveableModelSource.java --- Eclipse UI/org/eclipse/ui/ISaveableModelSource.java 31 Jan 2006 14:31:16 -0000 1.1 +++ Eclipse UI/org/eclipse/ui/ISaveableModelSource.java 27 Mar 2006 20:22:31 -0000 @@ -21,9 +21,18 @@ public interface ISaveableModelSource { /** - * Returns the saveable models presented by the workbench part. + * Returns the saveable models presented by the workbench part. If the + * return value of this method changes during the lifetime of this part, the + * model manager must be notified about these changes by calling + * {@link ISaveableModelManager#handleModelLifecycleEvent(ModelLifecycleEvent)}. + *

+ * The model manager is available as a service from the part site, by + * calling partSite.getService(ISaveableModelManager.class). + *

* * @return the saveable models presented by the workbench part + * + * @see ISaveableModelManager */ ISaveableModel[] getModels(); @@ -31,7 +40,8 @@ * Returns the saveable models currently active in the workbench part. *

* Certain workbench actions, such as Save, target only the active models in - * the active part. + * the active part. For example, the active saveable models could be + * determined based on the current selection in the part. *

* * @return the saveable models currently active in the workbench part Index: Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java =================================================================== RCS file: Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java diff -N Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2006 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; + +import java.util.EventObject; + + +/** + * Event object describing a change to a set of ISaveableModel objects. + * + * @since 3.2 + */ +public class ModelLifecycleEvent extends EventObject { + + /** + * Serial version UID for this class. + *

+ * Note: This class is not intended to be serialized. + *

+ */ + private static final long serialVersionUID = -3530773637989046452L; + + /** + * Event type constant specifying that the given models have been opened. + */ + public static final int POST_OPEN = 1; + + /** + * Event type constant specifying that the given models are about to be + * closed. Listeners may veto the closing if isForce() is false. + */ + public static final int PRE_CLOSE = 2; + + /** + * Event type constant specifying that the given models have been closed. + */ + public static final int POST_CLOSE = 3; + + /** + * Event type constant specifying that the dirty state of the given models + * has changed. + */ + public static final int DIRTY_CHANGED = 4; + + private int eventType; + + private ISaveableModel[] models; + + private boolean force; + + private boolean veto = false; + + /** + * Creates a new ModelLifecycleEvent. + * + * @param source + * The source of the event. If an ISaveableModelSource notifies + * about changes to the models returned by + * {@link ISaveableModelSource#getModels()}, the source must be + * the ISaveableModelSource object. + * @param eventType + * the event type, currently one of POST_OPEN, PRE_CLOSE, + * POST_CLOSE, DIRTY_CHANGED + * @param models + * The affected models + * @param force + * true if the event type is PRE_CLOSE and this is a closed force + * that cannot be canceled. + */ + public ModelLifecycleEvent(Object source, int eventType, + ISaveableModel[] models, boolean force) { + super(source); + this.eventType = eventType; + this.models = models; + this.force = force; + } + + /** + * Returns the eventType, currently one of POST_OPEN, PRE_CLOSE, POST_CLOSE, + * DIRTY_CHANGED. Listeners should silently ignore unknown event types since + * new event types might be added in the future. + * + * @return the eventType + */ + public int getEventType() { + return eventType; + } + + /** + * Returns the affected models. + * + * @return the models + */ + public ISaveableModel[] getModels() { + return models; + } + + /** + * Returns the veto. This value is ignored for POST_OPEN,POST_CLOSE, and + * DIRTY_CHANGED. + * + * @return Returns the veto. + */ + public boolean isVeto() { + return veto; + } + + /** + * @param veto + * The veto to set. + */ + public void setVeto(boolean veto) { + this.veto = veto; + } + + /** + * Sets the veto. This value is ignored for POST_OPEN, POST_CLOSE, and + * DIRTY_CHANGED. + * + * @return Returns the force. + */ + public boolean isForce() { + return force; + } + +} Index: Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java =================================================================== RCS file: Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java diff -N Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,527 @@ +/******************************************************************************* + * Copyright (c) 2006 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IModelLifecycleListener; +import org.eclipse.ui.ISaveableModel; +import org.eclipse.ui.ISaveableModelManager; +import org.eclipse.ui.ISaveableModelSource; +import org.eclipse.ui.ISaveablePart; +import org.eclipse.ui.ISaveablePart2; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.ModelLifecycleEvent; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.ListSelectionDialog; +import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor; +import org.eclipse.ui.model.WorkbenchPartLabelProvider; + +/** + * @since 3.2 + * + */ +public class SaveableModelManager implements ISaveableModelManager { + + private ListenerList listeners = new ListenerList(); + + // event source (mostly ISaveableModelSource) -> Set of ISaveableModel + private Map modelMap = new HashMap(); + + // reference counting map, ISaveableModel -> Integer + private Map modelRefCounts = new HashMap(); + + public ISaveableModel[] getOpenModels() { + return (ISaveableModel[]) modelRefCounts.keySet().toArray( + new ISaveableModel[modelRefCounts.size()]); + } + + // returns true if this model has not yet been in getModels() + private boolean addModel(Object source, ISaveableModel model) { + boolean result = false; + Set modelsForSource = (Set) modelMap.get(source); + if (modelsForSource == null) { + modelsForSource = new HashSet(); + modelMap.put(source, modelsForSource); + } + if (modelsForSource.add(model)) { + result = incrementRefCount(modelRefCounts, model); + } + return result; + } + + /** + * returns true if the given key was added for the first time + * + * @param referenceMap + * @param key + * @return true if the ref count of the given key is now 1 + */ + private boolean incrementRefCount(Map referenceMap, Object key) { + boolean result = false; + Integer refCount = (Integer) referenceMap.get(key); + if (refCount == null) { + result = true; + refCount = new Integer(0); + } + referenceMap.put(key, new Integer(refCount.intValue() + 1)); + return result; + } + + /** + * returns true if the given key has been removed + * + * @param referenceMap + * @param key + * @return true if the ref count of the given key was 1 + */ + private boolean decrementRefCount(Map referenceMap, Object key) { + boolean result = false; + Integer refCount = (Integer) referenceMap.get(key); + Assert.isTrue(refCount != null); + if (refCount.intValue() == 1) { + referenceMap.remove(key); + result = true; + } else { + referenceMap.put(key, new Integer(refCount.intValue() - 1)); + } + return result; + } + + // returns true if this model was removed from getModels(); + private boolean removeModel(Object source, ISaveableModel model) { + boolean result = false; + Set modelsForSource = (Set) modelMap.get(source); + if (modelsForSource == null) { + modelsForSource = new HashSet(); + modelMap.put(source, modelsForSource); + } + if (modelsForSource.remove(model)) { + result = decrementRefCount(modelRefCounts, model); + if (modelsForSource.isEmpty()) { + modelMap.remove(source); + } + } + return result; + } + + public void handleModelLifecycleEvent(ModelLifecycleEvent event) { + ISaveableModel[] modelArray = event.getModels(); + switch (event.getEventType()) { + case ModelLifecycleEvent.POST_OPEN: + addModels(event.getSource(), modelArray); + break; + case ModelLifecycleEvent.PRE_CLOSE: + ISaveableModel[] models = event.getModels(); + Map modelsDecrementing = new HashMap(); + Set modelsClosing = new HashSet(); + for (int i = 0; i < models.length; i++) { + incrementRefCount(modelsDecrementing, models[i]); + } + + fillModelsClosing(modelsClosing, modelsDecrementing); + boolean canceled = promptForSavingIfNecessary(PlatformUI + .getWorkbench().getActiveWorkbenchWindow(), modelsClosing, + !event.isForce()); + if (canceled) { + event.setVeto(true); + } + break; + case ModelLifecycleEvent.POST_CLOSE: + removeModels(event.getSource(), modelArray); + break; + case ModelLifecycleEvent.DIRTY_CHANGED: + fireModelLifecycleEvent(new ModelLifecycleEvent(this, event + .getEventType(), event.getModels(), false)); + break; + } + } + + /** + * @param source + * @param modelArray + */ + private void removeModels(Object source, ISaveableModel[] modelArray) { + List removed = new ArrayList(); + for (int i = 0; i < modelArray.length; i++) { + ISaveableModel model = modelArray[i]; + if (removeModel(source, model)) { + removed.add(model); + } + } + if (removed.size() > 0) { + fireModelLifecycleEvent(new ModelLifecycleEvent(this, + ModelLifecycleEvent.POST_OPEN, (ISaveableModel[]) removed + .toArray(new ISaveableModel[removed.size()]), false)); + } + } + + /** + * @param source + * @param modelArray + */ + private void addModels(Object source, ISaveableModel[] modelArray) { + List added = new ArrayList(); + for (int i = 0; i < modelArray.length; i++) { + ISaveableModel model = modelArray[i]; + if (addModel(source, model)) { + added.add(model); + } + } + if (added.size() > 0) { + fireModelLifecycleEvent(new ModelLifecycleEvent(this, + ModelLifecycleEvent.POST_OPEN, (ISaveableModel[]) added + .toArray(new ISaveableModel[added.size()]), false)); + } + } + + /** + * @param event + */ + private void fireModelLifecycleEvent(ModelLifecycleEvent event) { + Object[] listenerArray = listeners.getListeners(); + for (int i = 0; i < listenerArray.length; i++) { + ((IModelLifecycleListener) listenerArray[i]) + .handleModelLifecycleEvent(event); + } + } + + public void addModelLifecycleListener(IModelLifecycleListener listener) { + listeners.add(listener); + } + + public void removeModelLifecycleListener(IModelLifecycleListener listener) { + listeners.remove(listener); + } + + /** + * @param editorsToClose + * @param save + * @param window + * @return the post close info to be passed to postClose + */ + public Object preCloseParts(List editorsToClose, boolean save, + final IWorkbenchWindow window) { + // reference count (how many occurrences of a model will go away?) + PostCloseInfo postCloseInfo = new PostCloseInfo(); + for (Iterator it = editorsToClose.iterator(); it.hasNext();) { + IWorkbenchPart part = (IWorkbenchPart) it.next(); + postCloseInfo.partsClosing.add(part); + if (part instanceof ISaveablePart) { + ISaveablePart saveablePart = (ISaveablePart) part; + if (save && !saveablePart.isSaveOnCloseNeeded()) { + // pretend for now that this part is not closing + continue; + } + } + if (save && part instanceof ISaveablePart2) { + ISaveablePart2 saveablePart2 = (ISaveablePart2) part; + // TODO show saveablePart2 before prompting, see + // EditorManager.saveAll + int response = SaveableHelper.savePart(saveablePart2, window, + true); + // only include this part in the following logic if it returned + // DEFAULT + if (response != ISaveablePart2.DEFAULT) { + continue; + } + } + ISaveableModel[] modelsFromSource = getSaveableModels(part); + for (int i = 0; i < modelsFromSource.length; i++) { + incrementRefCount(postCloseInfo.modelsDecrementing, + modelsFromSource[i]); + } + } + fillModelsClosing(postCloseInfo.modelsClosing, + postCloseInfo.modelsDecrementing); + if (save) { + boolean canceled = promptForSavingIfNecessary(window, + postCloseInfo.modelsClosing, true); + if (canceled) { + return null; + } + } + return postCloseInfo; + } + + /** + * @param window + * @param modelsClosing + * @param canCancel + * @return true if the user canceled + */ + private boolean promptForSavingIfNecessary(final IWorkbenchWindow window, + Set modelsClosing, boolean canCancel) { + // TODO prompt for saving of dirty modelsDecrementing but not closing + // (changes + // won't be lost) + + List modelsToSave = new ArrayList(); + for (Iterator it = modelsClosing.iterator(); it.hasNext();) { + ISaveableModel modelClosing = (ISaveableModel) it.next(); + if (modelClosing.isDirty()) { + modelsToSave.add(modelClosing); + } + } + return modelsToSave.isEmpty() ? false : promptForSaving(modelsToSave, + window, canCancel); + } + + /** + * @param modelsClosing + * @param modelsDecrementing + */ + private void fillModelsClosing(Set modelsClosing, Map modelsDecrementing) { + for (Iterator it = modelsDecrementing.keySet().iterator(); it.hasNext();) { + ISaveableModel model = (ISaveableModel) it.next(); + if (modelsDecrementing.get(model).equals(modelRefCounts.get(model))) { + modelsClosing.add(model); + } + } + } + + /** + * @param modelsToSave + * @param window + * @param canCancel + * @return true if the user canceled + */ + private boolean promptForSaving(List modelsToSave, + final IWorkbenchWindow window, boolean canCancel) { + // Save parts, exit the method if cancel is pressed. + if (modelsToSave.size() > 0) { + if (modelsToSave.size() == 1) { + ISaveableModel model = (ISaveableModel) modelsToSave.get(0); + String message = NLS.bind( + WorkbenchMessages.EditorManager_saveChangesQuestion, + model.getName()); + // Show a dialog. + String[] buttons = new String[] { IDialogConstants.YES_LABEL, + IDialogConstants.NO_LABEL, + IDialogConstants.CANCEL_LABEL }; + MessageDialog d = new MessageDialog(window.getShell(), + WorkbenchMessages.Save_Resource, null, message, + MessageDialog.QUESTION, buttons, 0); + + int choice = SaveableHelper.testGetAutomatedResponse(); + if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) { + choice = d.open(); + } + + // Branch on the user choice. + // The choice id is based on the order of button labels + // above. + switch (choice) { + case ISaveablePart2.YES: // yes + break; + case ISaveablePart2.NO: // no + modelsToSave.clear(); + break; + default: + case ISaveablePart2.CANCEL: // cancel + return true; + } + } else { + ListSelectionDialog dlg = new MyListSelectionDialog(window + .getShell(), modelsToSave, new ArrayContentProvider(), + new WorkbenchPartLabelProvider(), + EditorManager.RESOURCES_TO_SAVE_MESSAGE, canCancel); + dlg.setInitialSelections(modelsToSave.toArray()); + dlg.setTitle(EditorManager.SAVE_RESOURCES_TITLE); + + // this "if" statement aids in testing. + if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) { + int result = dlg.open(); + // Just return null to prevent the operation continuing + if (result == IDialogConstants.CANCEL_ID) + return true; + + modelsToSave = Arrays.asList(dlg.getResult()); + } + } + } + // Create save block. + final List finalModels = modelsToSave; + IRunnableWithProgress progressOp = new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) { + IProgressMonitor monitorWrap = new EventLoopProgressMonitor( + monitor); + monitorWrap.beginTask("", finalModels.size()); //$NON-NLS-1$ + for (Iterator i = finalModels.iterator(); i.hasNext();) { + ISaveableModel model = (ISaveableModel) i.next(); + // handle case where this model got saved as a result of + // saving another + if (!model.isDirty()) { + monitor.worked(1); + continue; + } + try { + model.doSave(new SubProgressMonitor(monitorWrap, 1)); + } catch (CoreException e) { + ErrorDialog.openError(window.getShell(), + WorkbenchMessages.Error, e.getMessage(), e + .getStatus()); + } + if (monitorWrap.isCanceled()) + break; + } + monitorWrap.done(); + } + }; + + // Do the save. + if (!SaveableHelper.runProgressMonitorOperation( + WorkbenchMessages.Save_All, progressOp, window)) { + // cancelled + return true; + } + return false; + } + + private static class PostCloseInfo { + private List partsClosing = new ArrayList(); + + private Map modelsDecrementing = new HashMap(); + + private Set modelsClosing = new HashSet(); + } + + /** + * @param postCloseInfoObject + */ + public void postClose(Object postCloseInfoObject) { + PostCloseInfo postCloseInfo = (PostCloseInfo) postCloseInfoObject; + List removed = new ArrayList(); + for (Iterator it = postCloseInfo.partsClosing.iterator(); it.hasNext();) { + IWorkbenchPart part = (IWorkbenchPart) it.next(); + ISaveableModel[] modelArray = getSaveableModels(part); + for (int i = 0; i < modelArray.length; i++) { + ISaveableModel model = modelArray[i]; + if (removeModel(part, model)) { + removed.add(model); + } + } + } + if (removed.size() > 0) { + fireModelLifecycleEvent(new ModelLifecycleEvent(this, + ModelLifecycleEvent.POST_CLOSE, (ISaveableModel[]) removed + .toArray(new ISaveableModel[removed.size()]), false)); + } + } + + /** + * Returns the saveable models provided by the given part. If the part does + * not provide any models, a default model is returned representing the + * part. + * + * @param part + * the workbench part + * @return the saveable models + */ + private ISaveableModel[] getSaveableModels(IWorkbenchPart part) { + if (part instanceof ISaveableModelSource) { + ISaveableModelSource source = (ISaveableModelSource) part; + return source.getModels(); + } else if (part instanceof ISaveablePart) { + return new ISaveableModel[] { new DefaultSaveableModel(part) }; + } else { + return new ISaveableModel[0]; + } + } + + /** + * @param actualPart + */ + public void postOpen(IWorkbenchPart part) { + addModels(part, getSaveableModels(part)); + } + + /** + * @param actualPart + */ + public void dirtyChanged(IWorkbenchPart part) { + ISaveableModel[] saveableModels = getSaveableModels(part); + if (saveableModels.length > 0) { + fireModelLifecycleEvent(new ModelLifecycleEvent(this, + ModelLifecycleEvent.DIRTY_CHANGED, saveableModels, false)); + } + } + + /** + * For testing purposes. Not to be called by clients. + * + * @param model + * @return + */ + public Object[] testGetSourcesForModel(ISaveableModel model) { + List result = new ArrayList(); + for (Iterator it = modelMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Set values = (Set) entry.getValue(); + if (values.contains(model)) { + result.add(entry.getKey()); + } + } + return result.toArray(); + } + + private static final class MyListSelectionDialog extends + ListSelectionDialog { + private final boolean canCancel; + + private MyListSelectionDialog(Shell shell, Object input, + IStructuredContentProvider contentprovider, + ILabelProvider labelProvider, String message, boolean canCancel) { + super(shell, input, contentprovider, labelProvider, message); + this.canCancel = canCancel; + if (!canCancel) { + int shellStyle = getShellStyle(); + shellStyle &= ~SWT.CLOSE; + setShellStyle(shellStyle); + } + } + + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.OK_ID, + IDialogConstants.OK_LABEL, true); + if (canCancel) { + createButton(parent, IDialogConstants.CANCEL_ID, + IDialogConstants.CANCEL_LABEL, false); + } + } + } + +} Index: Eclipse UI/org/eclipse/ui/ISaveableModelManager.java =================================================================== RCS file: Eclipse UI/org/eclipse/ui/ISaveableModelManager.java diff -N Eclipse UI/org/eclipse/ui/ISaveableModelManager.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Eclipse UI/org/eclipse/ui/ISaveableModelManager.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2006 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; + + +/** + * The model manager maintains a list of open saveable models. + * + * @see SaveableModel + * @see ISaveableModelSource + * + * @since 3.2 + */ +public interface ISaveableModelManager extends IModelLifecycleListener { + + /** + * Returns the list of open models managed by this model manager. + * + * @return a list of models + */ + public ISaveableModel[] getOpenModels(); + + /** + * This implementation of handleModelLifecycleEvent must be called by + * implementers of ISaveableModelSource whenever the list of models of the + * model source changes, or when the dirty state of models changes. The + * ISaveableModelSource instance must be passed as the source of the event + * object. + *

+ * This method may also be called by objects that hold on to models but are + * not workbench parts. In this case, the event source must be set to an + * object that is not an instanceof IWorkbenchPart. + *

+ *

+ * Corresponding open and close events must originate from the same + * (identical) event source. + *

+ *

+ * This method must be called on the UI thread. + *

+ */ + public void handleModelLifecycleEvent(ModelLifecycleEvent event); + + /** + * Adds the given listener to the list of listeners. Has no effect if the + * same (identical) listener has already been added. The listener will be + * notified about changes to the models managed by this model manager. Event + * types include:
+ * POST_OPEN when models were added to the list of models
+ * POST_CLOSE when models were removed from the list of models
+ * DIRTY_CHANGED when the dirty state of models changed + *

+ * Listeners should ignore all other event types, including PRE_CLOSE. There + * is no guarantee that listeners are notified before models are closed. + * + * @param listener + */ + public void addModelLifecycleListener(IModelLifecycleListener listener); + + /** + * Removes the given listener from the list of listeners. Has no effect if + * the given listener is not contained in the list. + * + * @param listener + */ + public void removeModelLifecycleListener(IModelLifecycleListener listener); + +} Index: Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java =================================================================== RCS file: Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java diff -N Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2006 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; + + + +/** + * Listener for model lifecycle events. + * + * @since 3.2 + */ +public interface IModelLifecycleListener { + + /** + * Handle the given model lifecycle event. + * @param event + */ + public void handleModelLifecycleEvent(ModelLifecycleEvent event); + +}