001: /*******************************************************************************
002: * Copyright (c) 2006, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.HashMap;
015: import java.util.HashSet;
016: import java.util.Iterator;
017: import java.util.List;
018: import java.util.Map;
019: import java.util.Set;
020:
021: import org.eclipse.core.runtime.Assert;
022: import org.eclipse.core.runtime.AssertionFailedException;
023: import org.eclipse.core.runtime.IProgressMonitor;
024: import org.eclipse.core.runtime.IStatus;
025: import org.eclipse.core.runtime.ListenerList;
026: import org.eclipse.core.runtime.SubProgressMonitor;
027: import org.eclipse.jface.dialogs.IDialogConstants;
028: import org.eclipse.jface.dialogs.MessageDialog;
029: import org.eclipse.jface.dialogs.MessageDialogWithToggle;
030: import org.eclipse.jface.operation.IRunnableContext;
031: import org.eclipse.jface.operation.IRunnableWithProgress;
032: import org.eclipse.jface.preference.IPreferenceStore;
033: import org.eclipse.jface.viewers.ArrayContentProvider;
034: import org.eclipse.jface.viewers.ILabelProvider;
035: import org.eclipse.jface.viewers.IStructuredContentProvider;
036: import org.eclipse.jface.window.IShellProvider;
037: import org.eclipse.osgi.util.NLS;
038: import org.eclipse.swt.SWT;
039: import org.eclipse.swt.events.SelectionAdapter;
040: import org.eclipse.swt.events.SelectionEvent;
041: import org.eclipse.swt.layout.GridData;
042: import org.eclipse.swt.layout.GridLayout;
043: import org.eclipse.swt.widgets.Button;
044: import org.eclipse.swt.widgets.Composite;
045: import org.eclipse.swt.widgets.Control;
046: import org.eclipse.swt.widgets.Label;
047: import org.eclipse.swt.widgets.Shell;
048: import org.eclipse.ui.ISaveablePart;
049: import org.eclipse.ui.ISaveablePart2;
050: import org.eclipse.ui.ISaveablesLifecycleListener;
051: import org.eclipse.ui.ISaveablesSource;
052: import org.eclipse.ui.IWorkbenchPart;
053: import org.eclipse.ui.IWorkbenchPreferenceConstants;
054: import org.eclipse.ui.IWorkbenchWindow;
055: import org.eclipse.ui.PlatformUI;
056: import org.eclipse.ui.Saveable;
057: import org.eclipse.ui.SaveablesLifecycleEvent;
058: import org.eclipse.ui.dialogs.ListSelectionDialog;
059: import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
060: import org.eclipse.ui.internal.misc.StatusUtil;
061: import org.eclipse.ui.internal.util.PrefUtil;
062: import org.eclipse.ui.model.WorkbenchPartLabelProvider;
063:
064: /**
065: * The model manager maintains a list of open saveable models.
066: *
067: * @see Saveable
068: * @see ISaveablesSource
069: *
070: * @since 3.2
071: */
072: public class SaveablesList implements ISaveablesLifecycleListener {
073:
074: private ListenerList listeners = new ListenerList();
075:
076: // event source (mostly ISaveablesSource) -> Set of Saveable
077: private Map modelMap = new HashMap();
078:
079: // reference counting map, Saveable -> Integer
080: private Map modelRefCounts = new HashMap();
081:
082: private Set nonPartSources = new HashSet();
083:
084: /**
085: * Returns the list of open models managed by this model manager.
086: *
087: * @return a list of models
088: */
089: public Saveable[] getOpenModels() {
090: Set allDistinctModels = new HashSet();
091: Iterator saveables = modelMap.values().iterator();
092: while (saveables.hasNext())
093: allDistinctModels.addAll((Set) saveables.next());
094:
095: return (Saveable[]) allDistinctModels
096: .toArray(new Saveable[allDistinctModels.size()]);
097: }
098:
099: // returns true if this model has not yet been in getModels()
100: private boolean addModel(Object source, Saveable model) {
101: boolean result = false;
102: Set modelsForSource = (Set) modelMap.get(source);
103: if (modelsForSource == null) {
104: modelsForSource = new HashSet();
105: modelMap.put(source, modelsForSource);
106: }
107: if (modelsForSource.add(model)) {
108: result = incrementRefCount(modelRefCounts, model);
109: } else {
110: logWarning(
111: "Ignored attempt to add saveable that was already registered", source, model); //$NON-NLS-1$
112: }
113: return result;
114: }
115:
116: /**
117: * returns true if the given key was added for the first time
118: *
119: * @param referenceMap
120: * @param key
121: * @return true if the ref count of the given key is now 1
122: */
123: private boolean incrementRefCount(Map referenceMap, Object key) {
124: boolean result = false;
125: Integer refCount = (Integer) referenceMap.get(key);
126: if (refCount == null) {
127: result = true;
128: refCount = new Integer(0);
129: }
130: referenceMap.put(key, new Integer(refCount.intValue() + 1));
131: return result;
132: }
133:
134: /**
135: * returns true if the given key has been removed
136: *
137: * @param referenceMap
138: * @param key
139: * @return true if the ref count of the given key was 1
140: */
141: private boolean decrementRefCount(Map referenceMap, Object key) {
142: boolean result = false;
143: Integer refCount = (Integer) referenceMap.get(key);
144: Assert.isTrue(refCount != null);
145: if (refCount.intValue() == 1) {
146: referenceMap.remove(key);
147: result = true;
148: } else {
149: referenceMap.put(key, new Integer(refCount.intValue() - 1));
150: }
151: return result;
152: }
153:
154: // returns true if this model was removed from getModels();
155: private boolean removeModel(Object source, Saveable model) {
156: boolean result = false;
157: Set modelsForSource = (Set) modelMap.get(source);
158: if (modelsForSource == null) {
159: logWarning(
160: "Ignored attempt to remove a saveable when no saveables were known", source, model); //$NON-NLS-1$
161: } else {
162: if (modelsForSource.remove(model)) {
163: result = decrementRefCount(modelRefCounts, model);
164: if (modelsForSource.isEmpty()) {
165: modelMap.remove(source);
166: }
167: } else {
168: logWarning(
169: "Ignored attempt to remove a saveable that was not registered", source, model); //$NON-NLS-1$
170: }
171: }
172: return result;
173: }
174:
175: private void logWarning(String message, Object source,
176: Saveable model) {
177: // create a new exception
178: AssertionFailedException assertionFailedException = new AssertionFailedException(
179: "unknown saveable: " + model //$NON-NLS-1$
180: + " from part: " + source); //$NON-NLS-1$
181: // record the current stack trace to help with debugging
182: assertionFailedException.fillInStackTrace();
183: WorkbenchPlugin.log(StatusUtil.newStatus(IStatus.WARNING,
184: message, assertionFailedException));
185: }
186:
187: /**
188: * This implementation of handleModelLifecycleEvent must be called by
189: * implementers of ISaveablesSource whenever the list of models of the model
190: * source changes, or when the dirty state of models changes. The
191: * ISaveablesSource instance must be passed as the source of the event
192: * object.
193: * <p>
194: * This method may also be called by objects that hold on to models but are
195: * not workbench parts. In this case, the event source must be set to an
196: * object that is not an instanceof IWorkbenchPart.
197: * </p>
198: * <p>
199: * Corresponding open and close events must originate from the same
200: * (identical) event source.
201: * </p>
202: * <p>
203: * This method must be called on the UI thread.
204: * </p>
205: */
206: public void handleLifecycleEvent(SaveablesLifecycleEvent event) {
207: if (!(event.getSource() instanceof IWorkbenchPart)) {
208: // just update the set of non-part sources. No prompting necessary.
209: // See bug 139004.
210: updateNonPartSource((ISaveablesSource) event.getSource());
211: return;
212: }
213: Saveable[] modelArray = event.getSaveables();
214: switch (event.getEventType()) {
215: case SaveablesLifecycleEvent.POST_OPEN:
216: addModels(event.getSource(), modelArray);
217: break;
218: case SaveablesLifecycleEvent.PRE_CLOSE:
219: Saveable[] models = event.getSaveables();
220: Map modelsDecrementing = new HashMap();
221: Set modelsClosing = new HashSet();
222: for (int i = 0; i < models.length; i++) {
223: incrementRefCount(modelsDecrementing, models[i]);
224: }
225:
226: fillModelsClosing(modelsClosing, modelsDecrementing);
227: boolean canceled = promptForSavingIfNecessary(PlatformUI
228: .getWorkbench().getActiveWorkbenchWindow(),
229: modelsClosing, modelsDecrementing, !event.isForce());
230: if (canceled) {
231: event.setVeto(true);
232: }
233: break;
234: case SaveablesLifecycleEvent.POST_CLOSE:
235: removeModels(event.getSource(), modelArray);
236: break;
237: case SaveablesLifecycleEvent.DIRTY_CHANGED:
238: fireModelLifecycleEvent(new SaveablesLifecycleEvent(this ,
239: event.getEventType(), event.getSaveables(), false));
240: break;
241: }
242: }
243:
244: /**
245: * Updates the set of non-part saveables sources.
246: * @param source
247: */
248: private void updateNonPartSource(ISaveablesSource source) {
249: Saveable[] saveables = source.getSaveables();
250: if (saveables.length == 0) {
251: nonPartSources.remove(source);
252: } else {
253: nonPartSources.add(source);
254: }
255: }
256:
257: /**
258: * @param source
259: * @param modelArray
260: */
261: private void removeModels(Object source, Saveable[] modelArray) {
262: List removed = new ArrayList();
263: for (int i = 0; i < modelArray.length; i++) {
264: Saveable model = modelArray[i];
265: if (removeModel(source, model)) {
266: removed.add(model);
267: }
268: }
269: if (removed.size() > 0) {
270: fireModelLifecycleEvent(new SaveablesLifecycleEvent(this ,
271: SaveablesLifecycleEvent.POST_OPEN,
272: (Saveable[]) removed.toArray(new Saveable[removed
273: .size()]), false));
274: }
275: }
276:
277: /**
278: * @param source
279: * @param modelArray
280: */
281: private void addModels(Object source, Saveable[] modelArray) {
282: List added = new ArrayList();
283: for (int i = 0; i < modelArray.length; i++) {
284: Saveable model = modelArray[i];
285: if (addModel(source, model)) {
286: added.add(model);
287: }
288: }
289: if (added.size() > 0) {
290: fireModelLifecycleEvent(new SaveablesLifecycleEvent(this ,
291: SaveablesLifecycleEvent.POST_OPEN,
292: (Saveable[]) added.toArray(new Saveable[added
293: .size()]), false));
294: }
295: }
296:
297: /**
298: * @param event
299: */
300: private void fireModelLifecycleEvent(SaveablesLifecycleEvent event) {
301: Object[] listenerArray = listeners.getListeners();
302: for (int i = 0; i < listenerArray.length; i++) {
303: ((ISaveablesLifecycleListener) listenerArray[i])
304: .handleLifecycleEvent(event);
305: }
306: }
307:
308: /**
309: * Adds the given listener to the list of listeners. Has no effect if the
310: * same (identical) listener has already been added. The listener will be
311: * notified about changes to the models managed by this model manager. Event
312: * types include: <br>
313: * POST_OPEN when models were added to the list of models <br>
314: * POST_CLOSE when models were removed from the list of models <br>
315: * DIRTY_CHANGED when the dirty state of models changed
316: * <p>
317: * Listeners should ignore all other event types, including PRE_CLOSE. There
318: * is no guarantee that listeners are notified before models are closed.
319: *
320: * @param listener
321: */
322: public void addModelLifecycleListener(
323: ISaveablesLifecycleListener listener) {
324: listeners.add(listener);
325: }
326:
327: /**
328: * Removes the given listener from the list of listeners. Has no effect if
329: * the given listener is not contained in the list.
330: *
331: * @param listener
332: */
333: public void removeModelLifecycleListener(
334: ISaveablesLifecycleListener listener) {
335: listeners.remove(listener);
336: }
337:
338: /**
339: * @param partsToClose
340: * @param save
341: * @param window
342: * @return the post close info to be passed to postClose
343: */
344: public Object preCloseParts(List partsToClose, boolean save,
345: final IWorkbenchWindow window) {
346: // reference count (how many occurrences of a model will go away?)
347: PostCloseInfo postCloseInfo = new PostCloseInfo();
348: for (Iterator it = partsToClose.iterator(); it.hasNext();) {
349: IWorkbenchPart part = (IWorkbenchPart) it.next();
350: postCloseInfo.partsClosing.add(part);
351: if (part instanceof ISaveablePart) {
352: ISaveablePart saveablePart = (ISaveablePart) part;
353: if (save && !saveablePart.isSaveOnCloseNeeded()) {
354: // pretend for now that this part is not closing
355: continue;
356: }
357: }
358: if (save && part instanceof ISaveablePart2) {
359: ISaveablePart2 saveablePart2 = (ISaveablePart2) part;
360: // TODO show saveablePart2 before prompting, see
361: // EditorManager.saveAll
362: int response = SaveableHelper.savePart(saveablePart2,
363: window, true);
364: if (response == ISaveablePart2.CANCEL) {
365: // user canceled
366: return null;
367: } else if (response != ISaveablePart2.DEFAULT) {
368: // only include this part in the following logic if it returned
369: // DEFAULT
370: continue;
371: }
372: }
373: Saveable[] modelsFromSource = getSaveables(part);
374: for (int i = 0; i < modelsFromSource.length; i++) {
375: incrementRefCount(postCloseInfo.modelsDecrementing,
376: modelsFromSource[i]);
377: }
378: }
379: fillModelsClosing(postCloseInfo.modelsClosing,
380: postCloseInfo.modelsDecrementing);
381: if (save) {
382: boolean canceled = promptForSavingIfNecessary(window,
383: postCloseInfo.modelsClosing,
384: postCloseInfo.modelsDecrementing, true);
385: if (canceled) {
386: return null;
387: }
388: }
389: return postCloseInfo;
390: }
391:
392: /**
393: * @param window
394: * @param modelsClosing
395: * @param canCancel
396: * @return true if the user canceled
397: */
398: private boolean promptForSavingIfNecessary(
399: final IWorkbenchWindow window, Set modelsClosing,
400: Map modelsDecrementing, boolean canCancel) {
401: List modelsToOptionallySave = new ArrayList();
402: for (Iterator it = modelsDecrementing.keySet().iterator(); it
403: .hasNext();) {
404: Saveable modelDecrementing = (Saveable) it.next();
405: if (modelDecrementing.isDirty()
406: && !modelsClosing.contains(modelDecrementing)) {
407: modelsToOptionallySave.add(modelDecrementing);
408: }
409: }
410:
411: boolean shouldCancel = modelsToOptionallySave.isEmpty() ? false
412: : promptForSaving(modelsToOptionallySave, window,
413: window, canCancel, true);
414:
415: if (shouldCancel) {
416: return true;
417: }
418:
419: List modelsToSave = new ArrayList();
420: for (Iterator it = modelsClosing.iterator(); it.hasNext();) {
421: Saveable modelClosing = (Saveable) it.next();
422: if (modelClosing.isDirty()) {
423: modelsToSave.add(modelClosing);
424: }
425: }
426: return modelsToSave.isEmpty() ? false : promptForSaving(
427: modelsToSave, window, window, canCancel, false);
428: }
429:
430: /**
431: * @param modelsClosing
432: * @param modelsDecrementing
433: */
434: private void fillModelsClosing(Set modelsClosing,
435: Map modelsDecrementing) {
436: for (Iterator it = modelsDecrementing.keySet().iterator(); it
437: .hasNext();) {
438: Saveable model = (Saveable) it.next();
439: if (modelsDecrementing.get(model).equals(
440: modelRefCounts.get(model))) {
441: modelsClosing.add(model);
442: }
443: }
444: }
445:
446: /**
447: * Prompt the user to save the given saveables.
448: * @param modelsToSave the saveables to be saved
449: * @param shellProvider the provider used to obtain a shell in prompting is
450: * required. Clients can use a workbench window for this.
451: * @param runnableContext a runnable context that will be used to provide a
452: * progress monitor while the save is taking place. Clients can
453: * use a workbench window for this.
454: * @param canCancel whether the operation can be canceled
455: * @param stillOpenElsewhere whether the models are referenced by open parts
456: * @return true if the user canceled
457: */
458: public boolean promptForSaving(List modelsToSave,
459: final IShellProvider shellProvider,
460: IRunnableContext runnableContext, final boolean canCancel,
461: boolean stillOpenElsewhere) {
462: // Save parts, exit the method if cancel is pressed.
463: if (modelsToSave.size() > 0) {
464: boolean canceled = SaveableHelper
465: .waitForBackgroundSaveJobs(modelsToSave);
466: if (canceled) {
467: return true;
468: }
469:
470: IPreferenceStore apiPreferenceStore = PrefUtil
471: .getAPIPreferenceStore();
472: boolean dontPrompt = stillOpenElsewhere
473: && !apiPreferenceStore
474: .getBoolean(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN);
475:
476: if (dontPrompt) {
477: modelsToSave.clear();
478: return false;
479: } else if (modelsToSave.size() == 1) {
480: Saveable model = (Saveable) modelsToSave.get(0);
481: // Show a dialog.
482: String[] buttons;
483: if (canCancel) {
484: buttons = new String[] {
485: IDialogConstants.YES_LABEL,
486: IDialogConstants.NO_LABEL,
487: IDialogConstants.CANCEL_LABEL };
488: } else {
489: buttons = new String[] {
490: IDialogConstants.YES_LABEL,
491: IDialogConstants.NO_LABEL };
492: }
493:
494: // don't save if we don't prompt
495: int choice = ISaveablePart2.NO;
496:
497: MessageDialog dialog;
498: if (stillOpenElsewhere) {
499: String message = NLS
500: .bind(
501: WorkbenchMessages.EditorManager_saveChangesOptionallyQuestion,
502: model.getName());
503: MessageDialogWithToggle dialogWithToggle = new MessageDialogWithToggle(
504: shellProvider.getShell(),
505: WorkbenchMessages.Save_Resource,
506: null,
507: message,
508: MessageDialog.QUESTION,
509: buttons,
510: 0,
511: WorkbenchMessages.EditorManager_closeWithoutPromptingOption,
512: false) {
513: protected int getShellStyle() {
514: return (canCancel ? SWT.CLOSE : SWT.NONE)
515: | SWT.TITLE | SWT.BORDER
516: | SWT.APPLICATION_MODAL
517: | getDefaultOrientation();
518: }
519: };
520: dialog = dialogWithToggle;
521: } else {
522: String message = NLS
523: .bind(
524: WorkbenchMessages.EditorManager_saveChangesQuestion,
525: model.getName());
526: dialog = new MessageDialog(
527: shellProvider.getShell(),
528: WorkbenchMessages.Save_Resource, null,
529: message, MessageDialog.QUESTION, buttons, 0) {
530: protected int getShellStyle() {
531: return (canCancel ? SWT.CLOSE : SWT.NONE)
532: | SWT.TITLE | SWT.BORDER
533: | SWT.APPLICATION_MODAL
534: | getDefaultOrientation();
535: }
536: };
537: }
538:
539: choice = SaveableHelper.testGetAutomatedResponse();
540: if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) {
541: choice = dialog.open();
542:
543: if (stillOpenElsewhere) {
544: // map value of choice back to ISaveablePart2 values
545: switch (choice) {
546: case IDialogConstants.YES_ID:
547: choice = ISaveablePart2.YES;
548: break;
549: case IDialogConstants.NO_ID:
550: choice = ISaveablePart2.NO;
551: break;
552: case IDialogConstants.CANCEL_ID:
553: choice = ISaveablePart2.CANCEL;
554: break;
555: default:
556: break;
557: }
558: MessageDialogWithToggle dialogWithToggle = (MessageDialogWithToggle) dialog;
559: if (choice != ISaveablePart2.CANCEL
560: && dialogWithToggle.getToggleState()) {
561: apiPreferenceStore
562: .setValue(
563: IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN,
564: false);
565: }
566: }
567: }
568:
569: // Branch on the user choice.
570: // The choice id is based on the order of button labels
571: // above.
572: switch (choice) {
573: case ISaveablePart2.YES: // yes
574: break;
575: case ISaveablePart2.NO: // no
576: modelsToSave.clear();
577: break;
578: default:
579: case ISaveablePart2.CANCEL: // cancel
580: return true;
581: }
582: } else {
583: MyListSelectionDialog dlg = new MyListSelectionDialog(
584: shellProvider.getShell(),
585: modelsToSave,
586: new ArrayContentProvider(),
587: new WorkbenchPartLabelProvider(),
588: stillOpenElsewhere ? WorkbenchMessages.EditorManager_saveResourcesOptionallyMessage
589: : WorkbenchMessages.EditorManager_saveResourcesMessage,
590: canCancel, stillOpenElsewhere);
591: dlg.setInitialSelections(modelsToSave.toArray());
592: dlg.setTitle(EditorManager.SAVE_RESOURCES_TITLE);
593:
594: // this "if" statement aids in testing.
595: if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) {
596: int result = dlg.open();
597: // Just return null to prevent the operation continuing
598: if (result == IDialogConstants.CANCEL_ID)
599: return true;
600:
601: if (dlg.getDontPromptSelection()) {
602: apiPreferenceStore
603: .setValue(
604: IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN,
605: false);
606: }
607:
608: modelsToSave = Arrays.asList(dlg.getResult());
609: }
610: }
611: }
612: // Create save block.
613: return saveModels(modelsToSave, shellProvider, runnableContext);
614: }
615:
616: /**
617: * Save the given models.
618: * @param finalModels the list of models to be saved
619: * @param shellProvider the provider used to obtain a shell in prompting is
620: * required. Clients can use a workbench window for this.
621: * @param runnableContext a runnable context that will be used to provide a
622: * progress monitor while the save is taking place. Clients can
623: * use a workbench window for this.
624: * @return <code>true</code> if the operation was canceled
625: */
626: public boolean saveModels(final List finalModels,
627: final IShellProvider shellProvider,
628: IRunnableContext runnableContext) {
629: IRunnableWithProgress progressOp = new IRunnableWithProgress() {
630: public void run(IProgressMonitor monitor) {
631: IProgressMonitor monitorWrap = new EventLoopProgressMonitor(
632: monitor);
633: monitorWrap.beginTask("", finalModels.size()); //$NON-NLS-1$
634: for (Iterator i = finalModels.iterator(); i.hasNext();) {
635: Saveable model = (Saveable) i.next();
636: // handle case where this model got saved as a result of
637: // saving another
638: if (!model.isDirty()) {
639: monitor.worked(1);
640: continue;
641: }
642: SaveableHelper.doSaveModel(model,
643: new SubProgressMonitor(monitorWrap, 1),
644: shellProvider, true);
645: if (monitorWrap.isCanceled())
646: break;
647: }
648: monitorWrap.done();
649: }
650: };
651:
652: // Do the save.
653: return !SaveableHelper.runProgressMonitorOperation(
654: WorkbenchMessages.Save_All, progressOp,
655: runnableContext, shellProvider);
656: }
657:
658: private static class PostCloseInfo {
659: private List partsClosing = new ArrayList();
660:
661: private Map modelsDecrementing = new HashMap();
662:
663: private Set modelsClosing = new HashSet();
664: }
665:
666: /**
667: * @param postCloseInfoObject
668: */
669: public void postClose(Object postCloseInfoObject) {
670: PostCloseInfo postCloseInfo = (PostCloseInfo) postCloseInfoObject;
671: List removed = new ArrayList();
672: for (Iterator it = postCloseInfo.partsClosing.iterator(); it
673: .hasNext();) {
674: IWorkbenchPart part = (IWorkbenchPart) it.next();
675: Set saveables = (Set) modelMap.get(part);
676: if (saveables != null) {
677: // make a copy to avoid a ConcurrentModificationException - we
678: // will remove from the original set as we iterate
679: saveables = new HashSet(saveables);
680: for (Iterator it2 = saveables.iterator(); it2.hasNext();) {
681: Saveable saveable = (Saveable) it2.next();
682: if (removeModel(part, saveable)) {
683: removed.add(saveable);
684: }
685: }
686: }
687: }
688: if (removed.size() > 0) {
689: fireModelLifecycleEvent(new SaveablesLifecycleEvent(this ,
690: SaveablesLifecycleEvent.POST_CLOSE,
691: (Saveable[]) removed.toArray(new Saveable[removed
692: .size()]), false));
693: }
694: }
695:
696: /**
697: * Returns the saveable models provided by the given part. If the part does
698: * not provide any models, a default model is returned representing the
699: * part.
700: *
701: * @param part
702: * the workbench part
703: * @return the saveable models
704: */
705: private Saveable[] getSaveables(IWorkbenchPart part) {
706: if (part instanceof ISaveablesSource) {
707: ISaveablesSource source = (ISaveablesSource) part;
708: return source.getSaveables();
709: } else if (part instanceof ISaveablePart) {
710: return new Saveable[] { new DefaultSaveable(part) };
711: } else {
712: return new Saveable[0];
713: }
714: }
715:
716: /**
717: * @param actualPart
718: */
719: public void postOpen(IWorkbenchPart part) {
720: addModels(part, getSaveables(part));
721: }
722:
723: /**
724: * @param actualPart
725: */
726: public void dirtyChanged(IWorkbenchPart part) {
727: Saveable[] saveables = getSaveables(part);
728: if (saveables.length > 0) {
729: fireModelLifecycleEvent(new SaveablesLifecycleEvent(this ,
730: SaveablesLifecycleEvent.DIRTY_CHANGED, saveables,
731: false));
732: }
733: }
734:
735: /**
736: * For testing purposes. Not to be called by clients.
737: *
738: * @param model
739: * @return
740: */
741: public Object[] testGetSourcesForModel(Saveable model) {
742: List result = new ArrayList();
743: for (Iterator it = modelMap.entrySet().iterator(); it.hasNext();) {
744: Map.Entry entry = (Map.Entry) it.next();
745: Set values = (Set) entry.getValue();
746: if (values.contains(model)) {
747: result.add(entry.getKey());
748: }
749: }
750: return result.toArray();
751: }
752:
753: private static final class MyListSelectionDialog extends
754: ListSelectionDialog {
755: private final boolean canCancel;
756: private Button checkbox;
757: private boolean dontPromptSelection;
758: private boolean stillOpenElsewhere;
759:
760: private MyListSelectionDialog(Shell shell, Object input,
761: IStructuredContentProvider contentprovider,
762: ILabelProvider labelProvider, String message,
763: boolean canCancel, boolean stillOpenElsewhere) {
764: super (shell, input, contentprovider, labelProvider, message);
765: this .canCancel = canCancel;
766: this .stillOpenElsewhere = stillOpenElsewhere;
767: if (!canCancel) {
768: int shellStyle = getShellStyle();
769: shellStyle &= ~SWT.CLOSE;
770: setShellStyle(shellStyle);
771: }
772: }
773:
774: /**
775: * @return
776: */
777: public boolean getDontPromptSelection() {
778: return dontPromptSelection;
779: }
780:
781: protected void createButtonsForButtonBar(Composite parent) {
782: createButton(parent, IDialogConstants.OK_ID,
783: IDialogConstants.OK_LABEL, true);
784: if (canCancel) {
785: createButton(parent, IDialogConstants.CANCEL_ID,
786: IDialogConstants.CANCEL_LABEL, false);
787: }
788: }
789:
790: protected Control createDialogArea(Composite parent) {
791: Composite dialogAreaComposite = (Composite) super
792: .createDialogArea(parent);
793:
794: if (stillOpenElsewhere) {
795: Composite checkboxComposite = new Composite(
796: dialogAreaComposite, SWT.NONE);
797: checkboxComposite.setLayout(new GridLayout(2, false));
798:
799: checkbox = new Button(checkboxComposite, SWT.CHECK);
800: checkbox.addSelectionListener(new SelectionAdapter() {
801: public void widgetSelected(SelectionEvent e) {
802: dontPromptSelection = checkbox.getSelection();
803: }
804: });
805: GridData gd = new GridData();
806: gd.horizontalAlignment = SWT.BEGINNING;
807: checkbox.setLayoutData(gd);
808:
809: Label label = new Label(checkboxComposite, SWT.NONE);
810: label
811: .setText(WorkbenchMessages.EditorManager_closeWithoutPromptingOption);
812: gd = new GridData();
813: gd.grabExcessHorizontalSpace = true;
814: gd.horizontalAlignment = SWT.BEGINNING;
815: }
816:
817: return dialogAreaComposite;
818: }
819: }
820:
821: /**
822: * @return a list of ISaveablesSource objects registered with this saveables
823: * list which are not workbench parts.
824: */
825: public ISaveablesSource[] getNonPartSources() {
826: return (ISaveablesSource[]) nonPartSources
827: .toArray(new ISaveablesSource[nonPartSources.size()]);
828: }
829:
830: /**
831: * @param model
832: */
833: public IWorkbenchPart[] getPartsForSaveable(Saveable model) {
834: List result = new ArrayList();
835: for (Iterator it = modelMap.entrySet().iterator(); it.hasNext();) {
836: Map.Entry entry = (Map.Entry) it.next();
837: Set values = (Set) entry.getValue();
838: if (values.contains(model)
839: && entry.getKey() instanceof IWorkbenchPart) {
840: result.add(entry.getKey());
841: }
842: }
843: return (IWorkbenchPart[]) result
844: .toArray(new IWorkbenchPart[result.size()]);
845: }
846:
847: }
|