Added
Link Here
|
1 |
/******************************************************************************* |
2 |
* Copyright (c) 2006 IBM Corporation and others. |
3 |
* All rights reserved. This program and the accompanying materials |
4 |
* are made available under the terms of the Eclipse Public License v1.0 |
5 |
* which accompanies this distribution, and is available at |
6 |
* http://www.eclipse.org/legal/epl-v10.html |
7 |
* |
8 |
* Contributors: |
9 |
* IBM Corporation - initial API and implementation |
10 |
******************************************************************************/ |
11 |
|
12 |
package org.eclipse.ui.internal; |
13 |
|
14 |
import java.util.ArrayList; |
15 |
import java.util.Arrays; |
16 |
import java.util.HashMap; |
17 |
import java.util.HashSet; |
18 |
import java.util.Iterator; |
19 |
import java.util.List; |
20 |
import java.util.Map; |
21 |
import java.util.Set; |
22 |
|
23 |
import org.eclipse.core.runtime.Assert; |
24 |
import org.eclipse.core.runtime.CoreException; |
25 |
import org.eclipse.core.runtime.IProgressMonitor; |
26 |
import org.eclipse.core.runtime.ListenerList; |
27 |
import org.eclipse.core.runtime.SubProgressMonitor; |
28 |
import org.eclipse.jface.dialogs.ErrorDialog; |
29 |
import org.eclipse.jface.dialogs.IDialogConstants; |
30 |
import org.eclipse.jface.dialogs.MessageDialog; |
31 |
import org.eclipse.jface.operation.IRunnableWithProgress; |
32 |
import org.eclipse.jface.viewers.ArrayContentProvider; |
33 |
import org.eclipse.jface.viewers.ILabelProvider; |
34 |
import org.eclipse.jface.viewers.IStructuredContentProvider; |
35 |
import org.eclipse.osgi.util.NLS; |
36 |
import org.eclipse.swt.SWT; |
37 |
import org.eclipse.swt.widgets.Composite; |
38 |
import org.eclipse.swt.widgets.Shell; |
39 |
import org.eclipse.ui.IModelLifecycleListener; |
40 |
import org.eclipse.ui.ISaveableModel; |
41 |
import org.eclipse.ui.ISaveableModelManager; |
42 |
import org.eclipse.ui.ISaveableModelSource; |
43 |
import org.eclipse.ui.ISaveablePart; |
44 |
import org.eclipse.ui.ISaveablePart2; |
45 |
import org.eclipse.ui.IWorkbenchPart; |
46 |
import org.eclipse.ui.IWorkbenchWindow; |
47 |
import org.eclipse.ui.ModelLifecycleEvent; |
48 |
import org.eclipse.ui.PlatformUI; |
49 |
import org.eclipse.ui.dialogs.ListSelectionDialog; |
50 |
import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor; |
51 |
import org.eclipse.ui.model.WorkbenchPartLabelProvider; |
52 |
|
53 |
/** |
54 |
* @since 3.2 |
55 |
* |
56 |
*/ |
57 |
public class SaveableModelManager implements ISaveableModelManager { |
58 |
|
59 |
private ListenerList listeners = new ListenerList(); |
60 |
|
61 |
// event source (mostly ISaveableModelSource) -> Set of ISaveableModel |
62 |
private Map modelMap = new HashMap(); |
63 |
|
64 |
// reference counting map, ISaveableModel -> Integer |
65 |
private Map modelRefCounts = new HashMap(); |
66 |
|
67 |
public ISaveableModel[] getOpenModels() { |
68 |
return (ISaveableModel[]) modelRefCounts.keySet().toArray( |
69 |
new ISaveableModel[modelRefCounts.size()]); |
70 |
} |
71 |
|
72 |
// returns true if this model has not yet been in getModels() |
73 |
private boolean addModel(Object source, ISaveableModel model) { |
74 |
boolean result = false; |
75 |
Set modelsForSource = (Set) modelMap.get(source); |
76 |
if (modelsForSource == null) { |
77 |
modelsForSource = new HashSet(); |
78 |
modelMap.put(source, modelsForSource); |
79 |
} |
80 |
if (modelsForSource.add(model)) { |
81 |
result = incrementRefCount(modelRefCounts, model); |
82 |
} |
83 |
return result; |
84 |
} |
85 |
|
86 |
/** |
87 |
* returns true if the given key was added for the first time |
88 |
* |
89 |
* @param referenceMap |
90 |
* @param key |
91 |
* @return true if the ref count of the given key is now 1 |
92 |
*/ |
93 |
private boolean incrementRefCount(Map referenceMap, Object key) { |
94 |
boolean result = false; |
95 |
Integer refCount = (Integer) referenceMap.get(key); |
96 |
if (refCount == null) { |
97 |
result = true; |
98 |
refCount = new Integer(0); |
99 |
} |
100 |
referenceMap.put(key, new Integer(refCount.intValue() + 1)); |
101 |
return result; |
102 |
} |
103 |
|
104 |
/** |
105 |
* returns true if the given key has been removed |
106 |
* |
107 |
* @param referenceMap |
108 |
* @param key |
109 |
* @return true if the ref count of the given key was 1 |
110 |
*/ |
111 |
private boolean decrementRefCount(Map referenceMap, Object key) { |
112 |
boolean result = false; |
113 |
Integer refCount = (Integer) referenceMap.get(key); |
114 |
Assert.isTrue(refCount != null); |
115 |
if (refCount.intValue() == 1) { |
116 |
referenceMap.remove(key); |
117 |
result = true; |
118 |
} else { |
119 |
referenceMap.put(key, new Integer(refCount.intValue() - 1)); |
120 |
} |
121 |
return result; |
122 |
} |
123 |
|
124 |
// returns true if this model was removed from getModels(); |
125 |
private boolean removeModel(Object source, ISaveableModel model) { |
126 |
boolean result = false; |
127 |
Set modelsForSource = (Set) modelMap.get(source); |
128 |
if (modelsForSource == null) { |
129 |
modelsForSource = new HashSet(); |
130 |
modelMap.put(source, modelsForSource); |
131 |
} |
132 |
if (modelsForSource.remove(model)) { |
133 |
result = decrementRefCount(modelRefCounts, model); |
134 |
if (modelsForSource.isEmpty()) { |
135 |
modelMap.remove(source); |
136 |
} |
137 |
} |
138 |
return result; |
139 |
} |
140 |
|
141 |
public void handleModelLifecycleEvent(ModelLifecycleEvent event) { |
142 |
ISaveableModel[] modelArray = event.getModels(); |
143 |
switch (event.getEventType()) { |
144 |
case ModelLifecycleEvent.POST_OPEN: |
145 |
addModels(event.getSource(), modelArray); |
146 |
break; |
147 |
case ModelLifecycleEvent.PRE_CLOSE: |
148 |
ISaveableModel[] models = event.getModels(); |
149 |
Map modelsDecrementing = new HashMap(); |
150 |
Set modelsClosing = new HashSet(); |
151 |
for (int i = 0; i < models.length; i++) { |
152 |
incrementRefCount(modelsDecrementing, models[i]); |
153 |
} |
154 |
|
155 |
fillModelsClosing(modelsClosing, modelsDecrementing); |
156 |
boolean canceled = promptForSavingIfNecessary(PlatformUI |
157 |
.getWorkbench().getActiveWorkbenchWindow(), modelsClosing, |
158 |
!event.isForce()); |
159 |
if (canceled) { |
160 |
event.setVeto(true); |
161 |
} |
162 |
break; |
163 |
case ModelLifecycleEvent.POST_CLOSE: |
164 |
removeModels(event.getSource(), modelArray); |
165 |
break; |
166 |
case ModelLifecycleEvent.DIRTY_CHANGED: |
167 |
fireModelLifecycleEvent(new ModelLifecycleEvent(this, event |
168 |
.getEventType(), event.getModels(), false)); |
169 |
break; |
170 |
} |
171 |
} |
172 |
|
173 |
/** |
174 |
* @param source |
175 |
* @param modelArray |
176 |
*/ |
177 |
private void removeModels(Object source, ISaveableModel[] modelArray) { |
178 |
List removed = new ArrayList(); |
179 |
for (int i = 0; i < modelArray.length; i++) { |
180 |
ISaveableModel model = modelArray[i]; |
181 |
if (removeModel(source, model)) { |
182 |
removed.add(model); |
183 |
} |
184 |
} |
185 |
if (removed.size() > 0) { |
186 |
fireModelLifecycleEvent(new ModelLifecycleEvent(this, |
187 |
ModelLifecycleEvent.POST_OPEN, (ISaveableModel[]) removed |
188 |
.toArray(new ISaveableModel[removed.size()]), false)); |
189 |
} |
190 |
} |
191 |
|
192 |
/** |
193 |
* @param source |
194 |
* @param modelArray |
195 |
*/ |
196 |
private void addModels(Object source, ISaveableModel[] modelArray) { |
197 |
List added = new ArrayList(); |
198 |
for (int i = 0; i < modelArray.length; i++) { |
199 |
ISaveableModel model = modelArray[i]; |
200 |
if (addModel(source, model)) { |
201 |
added.add(model); |
202 |
} |
203 |
} |
204 |
if (added.size() > 0) { |
205 |
fireModelLifecycleEvent(new ModelLifecycleEvent(this, |
206 |
ModelLifecycleEvent.POST_OPEN, (ISaveableModel[]) added |
207 |
.toArray(new ISaveableModel[added.size()]), false)); |
208 |
} |
209 |
} |
210 |
|
211 |
/** |
212 |
* @param event |
213 |
*/ |
214 |
private void fireModelLifecycleEvent(ModelLifecycleEvent event) { |
215 |
Object[] listenerArray = listeners.getListeners(); |
216 |
for (int i = 0; i < listenerArray.length; i++) { |
217 |
((IModelLifecycleListener) listenerArray[i]) |
218 |
.handleModelLifecycleEvent(event); |
219 |
} |
220 |
} |
221 |
|
222 |
public void addModelLifecycleListener(IModelLifecycleListener listener) { |
223 |
listeners.add(listener); |
224 |
} |
225 |
|
226 |
public void removeModelLifecycleListener(IModelLifecycleListener listener) { |
227 |
listeners.remove(listener); |
228 |
} |
229 |
|
230 |
/** |
231 |
* @param editorsToClose |
232 |
* @param save |
233 |
* @param window |
234 |
* @return the post close info to be passed to postClose |
235 |
*/ |
236 |
public Object preCloseParts(List editorsToClose, boolean save, |
237 |
final IWorkbenchWindow window) { |
238 |
// reference count (how many occurrences of a model will go away?) |
239 |
PostCloseInfo postCloseInfo = new PostCloseInfo(); |
240 |
for (Iterator it = editorsToClose.iterator(); it.hasNext();) { |
241 |
IWorkbenchPart part = (IWorkbenchPart) it.next(); |
242 |
postCloseInfo.partsClosing.add(part); |
243 |
if (part instanceof ISaveablePart) { |
244 |
ISaveablePart saveablePart = (ISaveablePart) part; |
245 |
if (save && !saveablePart.isSaveOnCloseNeeded()) { |
246 |
// pretend for now that this part is not closing |
247 |
continue; |
248 |
} |
249 |
} |
250 |
if (save && part instanceof ISaveablePart2) { |
251 |
ISaveablePart2 saveablePart2 = (ISaveablePart2) part; |
252 |
// TODO show saveablePart2 before prompting, see |
253 |
// EditorManager.saveAll |
254 |
int response = SaveableHelper.savePart(saveablePart2, window, |
255 |
true); |
256 |
// only include this part in the following logic if it returned |
257 |
// DEFAULT |
258 |
if (response != ISaveablePart2.DEFAULT) { |
259 |
continue; |
260 |
} |
261 |
} |
262 |
ISaveableModel[] modelsFromSource = getSaveableModels(part); |
263 |
for (int i = 0; i < modelsFromSource.length; i++) { |
264 |
incrementRefCount(postCloseInfo.modelsDecrementing, |
265 |
modelsFromSource[i]); |
266 |
} |
267 |
} |
268 |
fillModelsClosing(postCloseInfo.modelsClosing, |
269 |
postCloseInfo.modelsDecrementing); |
270 |
if (save) { |
271 |
boolean canceled = promptForSavingIfNecessary(window, |
272 |
postCloseInfo.modelsClosing, true); |
273 |
if (canceled) { |
274 |
return null; |
275 |
} |
276 |
} |
277 |
return postCloseInfo; |
278 |
} |
279 |
|
280 |
/** |
281 |
* @param window |
282 |
* @param modelsClosing |
283 |
* @param canCancel |
284 |
* @return true if the user canceled |
285 |
*/ |
286 |
private boolean promptForSavingIfNecessary(final IWorkbenchWindow window, |
287 |
Set modelsClosing, boolean canCancel) { |
288 |
// TODO prompt for saving of dirty modelsDecrementing but not closing |
289 |
// (changes |
290 |
// won't be lost) |
291 |
|
292 |
List modelsToSave = new ArrayList(); |
293 |
for (Iterator it = modelsClosing.iterator(); it.hasNext();) { |
294 |
ISaveableModel modelClosing = (ISaveableModel) it.next(); |
295 |
if (modelClosing.isDirty()) { |
296 |
modelsToSave.add(modelClosing); |
297 |
} |
298 |
} |
299 |
return modelsToSave.isEmpty() ? false : promptForSaving(modelsToSave, |
300 |
window, canCancel); |
301 |
} |
302 |
|
303 |
/** |
304 |
* @param modelsClosing |
305 |
* @param modelsDecrementing |
306 |
*/ |
307 |
private void fillModelsClosing(Set modelsClosing, Map modelsDecrementing) { |
308 |
for (Iterator it = modelsDecrementing.keySet().iterator(); it.hasNext();) { |
309 |
ISaveableModel model = (ISaveableModel) it.next(); |
310 |
if (modelsDecrementing.get(model).equals(modelRefCounts.get(model))) { |
311 |
modelsClosing.add(model); |
312 |
} |
313 |
} |
314 |
} |
315 |
|
316 |
/** |
317 |
* @param modelsToSave |
318 |
* @param window |
319 |
* @param canCancel |
320 |
* @return true if the user canceled |
321 |
*/ |
322 |
private boolean promptForSaving(List modelsToSave, |
323 |
final IWorkbenchWindow window, boolean canCancel) { |
324 |
// Save parts, exit the method if cancel is pressed. |
325 |
if (modelsToSave.size() > 0) { |
326 |
if (modelsToSave.size() == 1) { |
327 |
ISaveableModel model = (ISaveableModel) modelsToSave.get(0); |
328 |
String message = NLS.bind( |
329 |
WorkbenchMessages.EditorManager_saveChangesQuestion, |
330 |
model.getName()); |
331 |
// Show a dialog. |
332 |
String[] buttons = new String[] { IDialogConstants.YES_LABEL, |
333 |
IDialogConstants.NO_LABEL, |
334 |
IDialogConstants.CANCEL_LABEL }; |
335 |
MessageDialog d = new MessageDialog(window.getShell(), |
336 |
WorkbenchMessages.Save_Resource, null, message, |
337 |
MessageDialog.QUESTION, buttons, 0); |
338 |
|
339 |
int choice = SaveableHelper.testGetAutomatedResponse(); |
340 |
if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) { |
341 |
choice = d.open(); |
342 |
} |
343 |
|
344 |
// Branch on the user choice. |
345 |
// The choice id is based on the order of button labels |
346 |
// above. |
347 |
switch (choice) { |
348 |
case ISaveablePart2.YES: // yes |
349 |
break; |
350 |
case ISaveablePart2.NO: // no |
351 |
modelsToSave.clear(); |
352 |
break; |
353 |
default: |
354 |
case ISaveablePart2.CANCEL: // cancel |
355 |
return true; |
356 |
} |
357 |
} else { |
358 |
ListSelectionDialog dlg = new MyListSelectionDialog(window |
359 |
.getShell(), modelsToSave, new ArrayContentProvider(), |
360 |
new WorkbenchPartLabelProvider(), |
361 |
EditorManager.RESOURCES_TO_SAVE_MESSAGE, canCancel); |
362 |
dlg.setInitialSelections(modelsToSave.toArray()); |
363 |
dlg.setTitle(EditorManager.SAVE_RESOURCES_TITLE); |
364 |
|
365 |
// this "if" statement aids in testing. |
366 |
if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) { |
367 |
int result = dlg.open(); |
368 |
// Just return null to prevent the operation continuing |
369 |
if (result == IDialogConstants.CANCEL_ID) |
370 |
return true; |
371 |
|
372 |
modelsToSave = Arrays.asList(dlg.getResult()); |
373 |
} |
374 |
} |
375 |
} |
376 |
// Create save block. |
377 |
final List finalModels = modelsToSave; |
378 |
IRunnableWithProgress progressOp = new IRunnableWithProgress() { |
379 |
public void run(IProgressMonitor monitor) { |
380 |
IProgressMonitor monitorWrap = new EventLoopProgressMonitor( |
381 |
monitor); |
382 |
monitorWrap.beginTask("", finalModels.size()); //$NON-NLS-1$ |
383 |
for (Iterator i = finalModels.iterator(); i.hasNext();) { |
384 |
ISaveableModel model = (ISaveableModel) i.next(); |
385 |
// handle case where this model got saved as a result of |
386 |
// saving another |
387 |
if (!model.isDirty()) { |
388 |
monitor.worked(1); |
389 |
continue; |
390 |
} |
391 |
try { |
392 |
model.doSave(new SubProgressMonitor(monitorWrap, 1)); |
393 |
} catch (CoreException e) { |
394 |
ErrorDialog.openError(window.getShell(), |
395 |
WorkbenchMessages.Error, e.getMessage(), e |
396 |
.getStatus()); |
397 |
} |
398 |
if (monitorWrap.isCanceled()) |
399 |
break; |
400 |
} |
401 |
monitorWrap.done(); |
402 |
} |
403 |
}; |
404 |
|
405 |
// Do the save. |
406 |
if (!SaveableHelper.runProgressMonitorOperation( |
407 |
WorkbenchMessages.Save_All, progressOp, window)) { |
408 |
// cancelled |
409 |
return true; |
410 |
} |
411 |
return false; |
412 |
} |
413 |
|
414 |
private static class PostCloseInfo { |
415 |
private List partsClosing = new ArrayList(); |
416 |
|
417 |
private Map modelsDecrementing = new HashMap(); |
418 |
|
419 |
private Set modelsClosing = new HashSet(); |
420 |
} |
421 |
|
422 |
/** |
423 |
* @param postCloseInfoObject |
424 |
*/ |
425 |
public void postClose(Object postCloseInfoObject) { |
426 |
PostCloseInfo postCloseInfo = (PostCloseInfo) postCloseInfoObject; |
427 |
List removed = new ArrayList(); |
428 |
for (Iterator it = postCloseInfo.partsClosing.iterator(); it.hasNext();) { |
429 |
IWorkbenchPart part = (IWorkbenchPart) it.next(); |
430 |
ISaveableModel[] modelArray = getSaveableModels(part); |
431 |
for (int i = 0; i < modelArray.length; i++) { |
432 |
ISaveableModel model = modelArray[i]; |
433 |
if (removeModel(part, model)) { |
434 |
removed.add(model); |
435 |
} |
436 |
} |
437 |
} |
438 |
if (removed.size() > 0) { |
439 |
fireModelLifecycleEvent(new ModelLifecycleEvent(this, |
440 |
ModelLifecycleEvent.POST_CLOSE, (ISaveableModel[]) removed |
441 |
.toArray(new ISaveableModel[removed.size()]), false)); |
442 |
} |
443 |
} |
444 |
|
445 |
/** |
446 |
* Returns the saveable models provided by the given part. If the part does |
447 |
* not provide any models, a default model is returned representing the |
448 |
* part. |
449 |
* |
450 |
* @param part |
451 |
* the workbench part |
452 |
* @return the saveable models |
453 |
*/ |
454 |
private ISaveableModel[] getSaveableModels(IWorkbenchPart part) { |
455 |
if (part instanceof ISaveableModelSource) { |
456 |
ISaveableModelSource source = (ISaveableModelSource) part; |
457 |
return source.getModels(); |
458 |
} else if (part instanceof ISaveablePart) { |
459 |
return new ISaveableModel[] { new DefaultSaveableModel(part) }; |
460 |
} else { |
461 |
return new ISaveableModel[0]; |
462 |
} |
463 |
} |
464 |
|
465 |
/** |
466 |
* @param actualPart |
467 |
*/ |
468 |
public void postOpen(IWorkbenchPart part) { |
469 |
addModels(part, getSaveableModels(part)); |
470 |
} |
471 |
|
472 |
/** |
473 |
* @param actualPart |
474 |
*/ |
475 |
public void dirtyChanged(IWorkbenchPart part) { |
476 |
ISaveableModel[] saveableModels = getSaveableModels(part); |
477 |
if (saveableModels.length > 0) { |
478 |
fireModelLifecycleEvent(new ModelLifecycleEvent(this, |
479 |
ModelLifecycleEvent.DIRTY_CHANGED, saveableModels, false)); |
480 |
} |
481 |
} |
482 |
|
483 |
/** |
484 |
* For testing purposes. Not to be called by clients. |
485 |
* |
486 |
* @param model |
487 |
* @return |
488 |
*/ |
489 |
public Object[] testGetSourcesForModel(ISaveableModel model) { |
490 |
List result = new ArrayList(); |
491 |
for (Iterator it = modelMap.entrySet().iterator(); it.hasNext();) { |
492 |
Map.Entry entry = (Map.Entry) it.next(); |
493 |
Set values = (Set) entry.getValue(); |
494 |
if (values.contains(model)) { |
495 |
result.add(entry.getKey()); |
496 |
} |
497 |
} |
498 |
return result.toArray(); |
499 |
} |
500 |
|
501 |
private static final class MyListSelectionDialog extends |
502 |
ListSelectionDialog { |
503 |
private final boolean canCancel; |
504 |
|
505 |
private MyListSelectionDialog(Shell shell, Object input, |
506 |
IStructuredContentProvider contentprovider, |
507 |
ILabelProvider labelProvider, String message, boolean canCancel) { |
508 |
super(shell, input, contentprovider, labelProvider, message); |
509 |
this.canCancel = canCancel; |
510 |
if (!canCancel) { |
511 |
int shellStyle = getShellStyle(); |
512 |
shellStyle &= ~SWT.CLOSE; |
513 |
setShellStyle(shellStyle); |
514 |
} |
515 |
} |
516 |
|
517 |
protected void createButtonsForButtonBar(Composite parent) { |
518 |
createButton(parent, IDialogConstants.OK_ID, |
519 |
IDialogConstants.OK_LABEL, true); |
520 |
if (canCancel) { |
521 |
createButton(parent, IDialogConstants.CANCEL_ID, |
522 |
IDialogConstants.CANCEL_LABEL, false); |
523 |
} |
524 |
} |
525 |
} |
526 |
|
527 |
} |