Fix for Bug 112225 [WorkbenchParts] Need consistent save lifecycle when multiple parts share the same model
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java
new file mode 100644
index 0000000..e17db1a
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java
@@ -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);
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelManager.java
new file mode 100644
index 0000000..d43c0f9
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelManager.java
@@ -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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * Corresponding open and close events must originate from the same
+ * (identical) event source.
+ * </p>
+ * <p>
+ * This method must be called on the UI thread.
+ * </p>
+ */
+ 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: <br>
+ * POST_OPEN when models were added to the list of models <br>
+ * POST_CLOSE when models were removed from the list of models <br>
+ * DIRTY_CHANGED when the dirty state of models changed
+ * <p>
+ * 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);
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java
index 522da34..7ef1b3f 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java
@@ -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)}.
+ * <p>
+ * The model manager is available as a service from the part site, by
+ * calling <code>partSite.getService(ISaveableModelManager.class)</code>.
+ * </p>
*
* @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.
* <p>
* 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.
* </p>
*
* @return the saveable models currently active in the workbench part
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java
new file mode 100644
index 0000000..c14a196
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java
@@ -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.
+ * <p>
+ * Note: This class is not intended to be serialized.
+ * </p>
+ */
+ 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;
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java
index a2f682e..bbf0bc3 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java
@@ -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;
+ }
+
}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
index d235162..392d3be 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
@@ -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.
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java
index f695071..9394c0e 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java
@@ -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);
}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java
index a8182d8..c197217 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java
@@ -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.
*
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java
new file mode 100644
index 0000000..701083f
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java
@@ -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);
+ }
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java
index 98f519a..7f10892 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java
@@ -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
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java
index deab59b..b6b775e 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java
@@ -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
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
index 7e18e3a..4e59fcc 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
@@ -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,29 +1218,23 @@
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;
- }
- }
- }
+ // 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);
}
- // Save parts, exit the method if cancel is pressed.
- if (intersect.size() > 0) {
- if (!EditorManager.saveAll(intersect, true, true,
- getWorkbenchWindow())) {
- return false;
- }
- }
+ }
+ 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
@@ -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() {
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java
index 5ceb525..e461137 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java
@@ -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);
+ }
+ }
}
/**