| /******************************************************************************* |
| * Copyright (c) 2006, 2012 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.AssertionFailedException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.jface.dialogs.IDialogConstants; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.dialogs.MessageDialogWithToggle; |
| import org.eclipse.jface.operation.IRunnableContext; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.viewers.ArrayContentProvider; |
| import org.eclipse.jface.viewers.ILabelProvider; |
| import org.eclipse.jface.viewers.IStructuredContentProvider; |
| import org.eclipse.jface.window.IShellProvider; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.ISaveablePart; |
| import org.eclipse.ui.ISaveablePart2; |
| import org.eclipse.ui.ISaveablesLifecycleListener; |
| import org.eclipse.ui.ISaveablesSource; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchPreferenceConstants; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.Saveable; |
| import org.eclipse.ui.SaveablesLifecycleEvent; |
| import org.eclipse.ui.dialogs.ListSelectionDialog; |
| import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor; |
| import org.eclipse.ui.internal.misc.StatusUtil; |
| import org.eclipse.ui.internal.util.PrefUtil; |
| import org.eclipse.ui.model.WorkbenchPartLabelProvider; |
| |
| /** |
| * The model manager maintains a list of open saveable models. |
| * |
| * @see Saveable |
| * @see ISaveablesSource |
| * |
| * @since 3.2 |
| */ |
| public class SaveablesList implements ISaveablesLifecycleListener { |
| |
| private ListenerList listeners = new ListenerList(); |
| |
| // event source (mostly ISaveablesSource) -> Set of Saveable |
| private Map modelMap = new HashMap(); |
| |
| // reference counting map, Saveable -> Integer |
| private Map modelRefCounts = new HashMap(); |
| |
| private Set nonPartSources = new HashSet(); |
| |
| /** |
| * Returns the list of open models managed by this model manager. |
| * |
| * @return a list of models |
| */ |
| public Saveable[] getOpenModels() { |
| Set allDistinctModels = new HashSet(); |
| Iterator saveables = modelMap.values().iterator(); |
| while (saveables.hasNext()) |
| allDistinctModels.addAll((Set)saveables.next()); |
| |
| return (Saveable[]) allDistinctModels.toArray( |
| new Saveable[allDistinctModels.size()]); |
| } |
| |
| // returns true if this model has not yet been in getModels() |
| private boolean addModel(Object source, Saveable model) { |
| if (model == null) { |
| logWarning( |
| "Ignored attempt to add invalid saveable", source, model); //$NON-NLS-1$ |
| return false; |
| } |
| 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); |
| } else { |
| logWarning( |
| "Ignored attempt to add saveable that was already registered", source, model); //$NON-NLS-1$ |
| } |
| 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); |
| if (refCount == null) |
| Assert.isTrue(false, key + ": " + ((Saveable) key).getName()); //$NON-NLS-1$ |
| 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, Saveable model) { |
| boolean result = false; |
| Set modelsForSource = (Set) modelMap.get(source); |
| if (modelsForSource == null) { |
| logWarning( |
| "Ignored attempt to remove a saveable when no saveables were known", source, model); //$NON-NLS-1$ |
| } else { |
| if (modelsForSource.remove(model)) { |
| result = decrementRefCount(modelRefCounts, model); |
| if (modelsForSource.isEmpty()) { |
| modelMap.remove(source); |
| } |
| } else { |
| logWarning( |
| "Ignored attempt to remove a saveable that was not registered", source, model); //$NON-NLS-1$ |
| } |
| } |
| return result; |
| } |
| |
| private void logWarning(String message, Object source, Saveable model) { |
| // create a new exception |
| AssertionFailedException assertionFailedException = new AssertionFailedException("unknown saveable: " + model //$NON-NLS-1$ |
| + " from part: " + source); //$NON-NLS-1$ |
| // record the current stack trace to help with debugging |
| assertionFailedException.fillInStackTrace(); |
| WorkbenchPlugin.log(StatusUtil.newStatus(IStatus.WARNING, message, |
| assertionFailedException)); |
| } |
| |
| /** |
| * This implementation of handleModelLifecycleEvent must be called by |
| * implementers of ISaveablesSource whenever the list of models of the model |
| * source changes, or when the dirty state of models changes. The |
| * ISaveablesSource 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 handleLifecycleEvent(SaveablesLifecycleEvent event) { |
| if (!(event.getSource() instanceof IWorkbenchPart)) { |
| // just update the set of non-part sources. No prompting necessary. |
| // See bug 139004. |
| updateNonPartSource((ISaveablesSource) event.getSource()); |
| return; |
| } |
| Saveable[] modelArray = event.getSaveables(); |
| switch (event.getEventType()) { |
| case SaveablesLifecycleEvent.POST_OPEN: |
| addModels(event.getSource(), modelArray); |
| break; |
| case SaveablesLifecycleEvent.PRE_CLOSE: |
| Saveable[] models = event.getSaveables(); |
| 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, modelsDecrementing, |
| !event.isForce()); |
| if (canceled) { |
| event.setVeto(true); |
| } |
| break; |
| case SaveablesLifecycleEvent.POST_CLOSE: |
| removeModels(event.getSource(), modelArray); |
| break; |
| case SaveablesLifecycleEvent.DIRTY_CHANGED: |
| fireModelLifecycleEvent(new SaveablesLifecycleEvent(this, event |
| .getEventType(), event.getSaveables(), false)); |
| break; |
| } |
| } |
| |
| /** |
| * Updates the set of non-part saveables sources. |
| * @param source |
| */ |
| private void updateNonPartSource(ISaveablesSource source) { |
| Saveable[] saveables = source.getSaveables(); |
| if (saveables.length == 0) { |
| nonPartSources.remove(source); |
| } else { |
| nonPartSources.add(source); |
| } |
| } |
| |
| /** |
| * @param source |
| * @param modelArray |
| */ |
| private void removeModels(Object source, Saveable[] modelArray) { |
| List removed = new ArrayList(); |
| for (int i = 0; i < modelArray.length; i++) { |
| Saveable model = modelArray[i]; |
| if (removeModel(source, model)) { |
| removed.add(model); |
| } |
| } |
| if (removed.size() > 0) { |
| fireModelLifecycleEvent(new SaveablesLifecycleEvent(this, |
| SaveablesLifecycleEvent.POST_OPEN, (Saveable[]) removed |
| .toArray(new Saveable[removed.size()]), false)); |
| } |
| } |
| |
| /** |
| * @param source |
| * @param modelArray |
| */ |
| private void addModels(Object source, Saveable[] modelArray) { |
| List added = new ArrayList(); |
| for (int i = 0; i < modelArray.length; i++) { |
| Saveable model = modelArray[i]; |
| if (addModel(source, model)) { |
| added.add(model); |
| } |
| } |
| if (added.size() > 0) { |
| fireModelLifecycleEvent(new SaveablesLifecycleEvent(this, |
| SaveablesLifecycleEvent.POST_OPEN, (Saveable[]) added |
| .toArray(new Saveable[added.size()]), false)); |
| } |
| } |
| |
| /** |
| * @param event |
| */ |
| private void fireModelLifecycleEvent(SaveablesLifecycleEvent event) { |
| Object[] listenerArray = listeners.getListeners(); |
| for (int i = 0; i < listenerArray.length; i++) { |
| ((ISaveablesLifecycleListener) listenerArray[i]) |
| .handleLifecycleEvent(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(ISaveablesLifecycleListener listener) { |
| listeners.add(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(ISaveablesLifecycleListener listener) { |
| listeners.remove(listener); |
| } |
| |
| /** |
| * @param partsToClose |
| * @param save |
| * @param window |
| * @return the post close info to be passed to postClose |
| */ |
| public Object preCloseParts(List partsToClose, boolean save, |
| final IWorkbenchWindow window) { |
| return preCloseParts(partsToClose, save, window, window); |
| } |
| |
| public Object preCloseParts(List partsToClose, boolean save, IShellProvider shellProvider, |
| final IWorkbenchWindow window) { |
| // reference count (how many occurrences of a model will go away?) |
| PostCloseInfo postCloseInfo = new PostCloseInfo(); |
| for (Iterator it = partsToClose.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); |
| if (response == ISaveablePart2.CANCEL) { |
| // user canceled |
| return null; |
| } else if (response != ISaveablePart2.DEFAULT) { |
| // only include this part in the following logic if it returned |
| // DEFAULT |
| continue; |
| } |
| } |
| Saveable[] modelsFromSource = getSaveables(part); |
| for (int i = 0; i < modelsFromSource.length; i++) { |
| incrementRefCount(postCloseInfo.modelsDecrementing, |
| modelsFromSource[i]); |
| } |
| } |
| fillModelsClosing(postCloseInfo.modelsClosing, |
| postCloseInfo.modelsDecrementing); |
| if (save) { |
| boolean canceled = promptForSavingIfNecessary(shellProvider, window, |
| postCloseInfo.modelsClosing, postCloseInfo.modelsDecrementing, 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, Map modelsDecrementing, boolean canCancel) { |
| return promptForSavingIfNecessary(window, window, modelsClosing, modelsDecrementing, |
| canCancel); |
| } |
| |
| private boolean promptForSavingIfNecessary(IShellProvider shellProvider, |
| IWorkbenchWindow window, Set modelsClosing, Map modelsDecrementing, boolean canCancel) { |
| List modelsToOptionallySave = new ArrayList(); |
| for (Iterator it = modelsDecrementing.keySet().iterator(); it.hasNext();) { |
| Saveable modelDecrementing = (Saveable) it.next(); |
| if (modelDecrementing.isDirty() && !modelsClosing.contains(modelDecrementing)) { |
| modelsToOptionallySave.add(modelDecrementing); |
| } |
| } |
| |
| boolean shouldCancel = modelsToOptionallySave.isEmpty() ? false : promptForSaving( |
| modelsToOptionallySave, shellProvider, window, canCancel, true); |
| |
| if (shouldCancel) { |
| return true; |
| } |
| |
| List modelsToSave = new ArrayList(); |
| for (Iterator it = modelsClosing.iterator(); it.hasNext();) { |
| Saveable modelClosing = (Saveable) it.next(); |
| if (modelClosing.isDirty()) { |
| modelsToSave.add(modelClosing); |
| } |
| } |
| return modelsToSave.isEmpty() ? false : promptForSaving(modelsToSave, shellProvider, |
| window, canCancel, false); |
| } |
| |
| /** |
| * @param modelsClosing |
| * @param modelsDecrementing |
| */ |
| private void fillModelsClosing(Set modelsClosing, Map modelsDecrementing) { |
| for (Iterator it = modelsDecrementing.keySet().iterator(); it.hasNext();) { |
| Saveable model = (Saveable) it.next(); |
| if (modelsDecrementing.get(model).equals(modelRefCounts.get(model))) { |
| modelsClosing.add(model); |
| } |
| } |
| } |
| |
| /** |
| * Prompt the user to save the given saveables. |
| * @param modelsToSave the saveables to be saved |
| * @param shellProvider the provider used to obtain a shell in prompting is |
| * required. Clients can use a workbench window for this. |
| * @param runnableContext a runnable context that will be used to provide a |
| * progress monitor while the save is taking place. Clients can |
| * use a workbench window for this. |
| * @param canCancel whether the operation can be canceled |
| * @param stillOpenElsewhere whether the models are referenced by open parts |
| * @return true if the user canceled |
| */ |
| public boolean promptForSaving(List modelsToSave, |
| final IShellProvider shellProvider, IRunnableContext runnableContext, final boolean canCancel, boolean stillOpenElsewhere) { |
| // Save parts, exit the method if cancel is pressed. |
| if (modelsToSave.size() > 0) { |
| boolean canceled = SaveableHelper.waitForBackgroundSaveJobs(modelsToSave); |
| if (canceled) { |
| return true; |
| } |
| |
| IPreferenceStore apiPreferenceStore = PrefUtil.getAPIPreferenceStore(); |
| boolean dontPrompt = stillOpenElsewhere && !apiPreferenceStore.getBoolean(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN); |
| |
| if (dontPrompt) { |
| modelsToSave.clear(); |
| return false; |
| } else if (modelsToSave.size() == 1) { |
| Saveable model = (Saveable) modelsToSave.get(0); |
| // Show a dialog. |
| String[] buttons; |
| if(canCancel) { |
| buttons = new String[] { IDialogConstants.YES_LABEL, |
| IDialogConstants.NO_LABEL, |
| IDialogConstants.CANCEL_LABEL }; |
| } else { |
| buttons = new String[] { IDialogConstants.YES_LABEL, |
| IDialogConstants.NO_LABEL}; |
| } |
| |
| // don't save if we don't prompt |
| int choice = ISaveablePart2.NO; |
| |
| MessageDialog dialog; |
| if (stillOpenElsewhere) { |
| String message = NLS |
| .bind( |
| WorkbenchMessages.EditorManager_saveChangesOptionallyQuestion, |
| model.getName()); |
| MessageDialogWithToggle dialogWithToggle = new MessageDialogWithToggle(shellProvider.getShell(), |
| WorkbenchMessages.Save_Resource, null, message, |
| MessageDialog.QUESTION, buttons, 0, WorkbenchMessages.EditorManager_closeWithoutPromptingOption, false) { |
| protected int getShellStyle() { |
| return (canCancel ? SWT.CLOSE : SWT.NONE) |
| | SWT.TITLE | SWT.BORDER |
| | SWT.APPLICATION_MODAL | SWT.SHEET |
| | getDefaultOrientation(); |
| } |
| }; |
| dialog = dialogWithToggle; |
| } else { |
| String message = NLS |
| .bind( |
| WorkbenchMessages.EditorManager_saveChangesQuestion, |
| model.getName()); |
| dialog = new MessageDialog(shellProvider.getShell(), |
| WorkbenchMessages.Save_Resource, null, message, |
| MessageDialog.QUESTION, buttons, 0) { |
| protected int getShellStyle() { |
| return (canCancel ? SWT.CLOSE : SWT.NONE) |
| | SWT.TITLE | SWT.BORDER |
| | SWT.APPLICATION_MODAL | SWT.SHEET |
| | getDefaultOrientation(); |
| } |
| }; |
| } |
| |
| choice = SaveableHelper.testGetAutomatedResponse(); |
| if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) { |
| choice = dialog.open(); |
| |
| if(stillOpenElsewhere) { |
| // map value of choice back to ISaveablePart2 values |
| switch (choice) { |
| case IDialogConstants.YES_ID: |
| choice = ISaveablePart2.YES; |
| break; |
| case IDialogConstants.NO_ID: |
| choice = ISaveablePart2.NO; |
| break; |
| case IDialogConstants.CANCEL_ID: |
| choice = ISaveablePart2.CANCEL; |
| break; |
| default: |
| break; |
| } |
| MessageDialogWithToggle dialogWithToggle = (MessageDialogWithToggle) dialog; |
| if (choice != ISaveablePart2.CANCEL && dialogWithToggle.getToggleState()) { |
| apiPreferenceStore.setValue(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN, false); |
| } |
| } |
| } |
| |
| // 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 { |
| MyListSelectionDialog dlg = new MyListSelectionDialog( |
| shellProvider.getShell(), |
| modelsToSave, |
| new ArrayContentProvider(), |
| new WorkbenchPartLabelProvider(), |
| stillOpenElsewhere ? WorkbenchMessages.EditorManager_saveResourcesOptionallyMessage |
| : WorkbenchMessages.EditorManager_saveResourcesMessage, |
| canCancel, stillOpenElsewhere); |
| 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; |
| |
| if (dlg.getDontPromptSelection()) { |
| apiPreferenceStore.setValue(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN, false); |
| } |
| |
| modelsToSave = Arrays.asList(dlg.getResult()); |
| } |
| } |
| } |
| // Create save block. |
| return saveModels(modelsToSave, shellProvider, runnableContext); |
| } |
| |
| /** |
| * Save the given models. |
| * @param finalModels the list of models to be saved |
| * @param shellProvider the provider used to obtain a shell in prompting is |
| * required. Clients can use a workbench window for this. |
| * @param runnableContext a runnable context that will be used to provide a |
| * progress monitor while the save is taking place. Clients can |
| * use a workbench window for this. |
| * @return <code>true</code> if the operation was canceled |
| */ |
| public boolean saveModels(final List finalModels, final IShellProvider shellProvider, IRunnableContext runnableContext) { |
| IRunnableWithProgress progressOp = new IRunnableWithProgress() { |
| public void run(IProgressMonitor monitor) { |
| IProgressMonitor monitorWrap = new EventLoopProgressMonitor( |
| monitor); |
| monitorWrap.beginTask(WorkbenchMessages.Saving_Modifications, finalModels.size()); |
| for (Iterator i = finalModels.iterator(); i.hasNext();) { |
| Saveable model = (Saveable) i.next(); |
| // handle case where this model got saved as a result of |
| // saving another |
| if (!model.isDirty()) { |
| monitor.worked(1); |
| continue; |
| } |
| SaveableHelper.doSaveModel(model, new SubProgressMonitor(monitorWrap, 1), shellProvider, true); |
| if (monitorWrap.isCanceled()) |
| break; |
| } |
| monitorWrap.done(); |
| } |
| }; |
| |
| // Do the save. |
| return !SaveableHelper.runProgressMonitorOperation( |
| WorkbenchMessages.Save_All, progressOp, runnableContext, |
| shellProvider); |
| } |
| |
| 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(); |
| Set saveables = (Set) modelMap.get(part); |
| if (saveables != null) { |
| // make a copy to avoid a ConcurrentModificationException - we |
| // will remove from the original set as we iterate |
| saveables = new HashSet(saveables); |
| for (Iterator it2 = saveables.iterator(); it2.hasNext();) { |
| Saveable saveable = (Saveable) it2.next(); |
| if (removeModel(part, saveable)) { |
| removed.add(saveable); |
| } |
| } |
| } |
| } |
| if (removed.size() > 0) { |
| fireModelLifecycleEvent(new SaveablesLifecycleEvent(this, |
| SaveablesLifecycleEvent.POST_CLOSE, (Saveable[]) removed |
| .toArray(new Saveable[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 Saveable[] getSaveables(IWorkbenchPart part) { |
| if (part instanceof ISaveablesSource) { |
| ISaveablesSource source = (ISaveablesSource) part; |
| return source.getSaveables(); |
| } else if (part instanceof ISaveablePart) { |
| return new Saveable[] { new DefaultSaveable(part) }; |
| } else { |
| return new Saveable[0]; |
| } |
| } |
| |
| /** |
| * @param actualPart |
| */ |
| public void postOpen(IWorkbenchPart part) { |
| addModels(part, getSaveables(part)); |
| } |
| |
| /** |
| * @param actualPart |
| */ |
| public void dirtyChanged(IWorkbenchPart part) { |
| Saveable[] saveables = getSaveables(part); |
| if (saveables.length > 0) { |
| fireModelLifecycleEvent(new SaveablesLifecycleEvent(this, |
| SaveablesLifecycleEvent.DIRTY_CHANGED, saveables, false)); |
| } |
| } |
| |
| /** |
| * For testing purposes. Not to be called by clients. |
| * |
| * @param model |
| * @return |
| */ |
| public Object[] testGetSourcesForModel(Saveable 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 Button checkbox; |
| private boolean dontPromptSelection; |
| private boolean stillOpenElsewhere; |
| |
| private MyListSelectionDialog(Shell shell, Object input, |
| IStructuredContentProvider contentprovider, |
| ILabelProvider labelProvider, String message, boolean canCancel, boolean stillOpenElsewhere) { |
| super(shell, input, contentprovider, labelProvider, message); |
| this.canCancel = canCancel; |
| this.stillOpenElsewhere = stillOpenElsewhere; |
| int shellStyle = getShellStyle(); |
| if (!canCancel) { |
| shellStyle &= ~SWT.CLOSE; |
| } |
| setShellStyle(shellStyle | SWT.SHEET); |
| } |
| |
| /** |
| * @return |
| */ |
| public boolean getDontPromptSelection() { |
| return dontPromptSelection; |
| } |
| |
| 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); |
| } |
| } |
| |
| protected Control createDialogArea(Composite parent) { |
| Composite dialogAreaComposite = (Composite) super.createDialogArea(parent); |
| |
| if (stillOpenElsewhere) { |
| Composite checkboxComposite = new Composite(dialogAreaComposite, SWT.NONE); |
| checkboxComposite.setLayout(new GridLayout(2, false)); |
| |
| checkbox = new Button(checkboxComposite, SWT.CHECK); |
| checkbox.addSelectionListener(new SelectionAdapter() { |
| public void widgetSelected(SelectionEvent e) { |
| dontPromptSelection = checkbox.getSelection(); |
| } |
| }); |
| GridData gd = new GridData(); |
| gd.horizontalAlignment = SWT.BEGINNING; |
| checkbox.setLayoutData(gd); |
| |
| Label label = new Label(checkboxComposite, SWT.NONE); |
| label.setText(WorkbenchMessages.EditorManager_closeWithoutPromptingOption); |
| gd = new GridData(); |
| gd.grabExcessHorizontalSpace = true; |
| gd.horizontalAlignment = SWT.BEGINNING; |
| } |
| |
| return dialogAreaComposite; |
| } |
| } |
| |
| /** |
| * @return a list of ISaveablesSource objects registered with this saveables |
| * list which are not workbench parts. |
| */ |
| public ISaveablesSource[] getNonPartSources() { |
| return (ISaveablesSource[]) nonPartSources |
| .toArray(new ISaveablesSource[nonPartSources.size()]); |
| } |
| |
| /** |
| * @param model |
| */ |
| public IWorkbenchPart[] getPartsForSaveable(Saveable 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) && entry.getKey() instanceof IWorkbenchPart) { |
| result.add(entry.getKey()); |
| } |
| } |
| return (IWorkbenchPart[]) result.toArray(new IWorkbenchPart[result.size()]); |
| } |
| |
| } |