KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > internal > SaveableHelper


1 /*******************************************************************************
2  * Copyright (c) 2004, 2007 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 package org.eclipse.ui.internal;
12
13 import java.lang.reflect.InvocationTargetException JavaDoc;
14 import java.util.ArrayList JavaDoc;
15 import java.util.Collection JavaDoc;
16 import java.util.HashSet JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Set JavaDoc;
20
21 import org.eclipse.core.runtime.CoreException;
22 import org.eclipse.core.runtime.IProgressMonitor;
23 import org.eclipse.core.runtime.IStatus;
24 import org.eclipse.core.runtime.OperationCanceledException;
25 import org.eclipse.core.runtime.Status;
26 import org.eclipse.core.runtime.SubMonitor;
27 import org.eclipse.core.runtime.SubProgressMonitor;
28 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
29 import org.eclipse.core.runtime.jobs.Job;
30 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
31 import org.eclipse.jface.dialogs.IDialogConstants;
32 import org.eclipse.jface.dialogs.MessageDialog;
33 import org.eclipse.jface.operation.IRunnableContext;
34 import org.eclipse.jface.operation.IRunnableWithProgress;
35 import org.eclipse.jface.window.IShellProvider;
36 import org.eclipse.osgi.util.NLS;
37 import org.eclipse.ui.ISaveablePart;
38 import org.eclipse.ui.ISaveablePart2;
39 import org.eclipse.ui.ISaveablesLifecycleListener;
40 import org.eclipse.ui.ISaveablesSource;
41 import org.eclipse.ui.IWorkbenchPart;
42 import org.eclipse.ui.IWorkbenchWindow;
43 import org.eclipse.ui.PlatformUI;
44 import org.eclipse.ui.Saveable;
45 import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
46 import org.eclipse.ui.internal.misc.StatusUtil;
47 import org.eclipse.ui.progress.IJobRunnable;
48 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
49 import org.eclipse.ui.statushandlers.StatusManager;
50
51 /**
52  * Helper class for prompting to save dirty views or editors.
53  *
54  * @since 3.0.1
55  */

56 public class SaveableHelper {
57     
58     /**
59      * The helper must prompt.
60      */

61     public static final int USER_RESPONSE = -1;
62     
63     private static int AutomatedResponse = USER_RESPONSE;
64     
65     /**
66      * FOR USE BY THE AUTOMATED TEST HARNESS ONLY.
67      *
68      * Sets the response to use when <code>savePart</code> is called with <code>confirm=true</code>.
69      *
70      * @param response 0 for yes, 1 for no, 2 for cancel, -1 for default (prompt)
71      */

72     public static void testSetAutomatedResponse(int response) {
73         AutomatedResponse = response;
74     }
75     
76     /**
77      * FOR USE BY THE AUTOMATED TEST HARNESS ONLY.
78      *
79      * Sets the response to use when <code>savePart</code> is called with <code>confirm=true</code>.
80      *
81      * @return 0 for yes, 1 for no, 2 for cancel, -1 for default (prompt)
82      */

83     public static int testGetAutomatedResponse() {
84         return AutomatedResponse;
85     }
86     
87     /**
88      * Saves the workbench part.
89      *
90      * @param saveable the part
91      * @param part the same part
92      * @param window the workbench window
93      * @param confirm request confirmation
94      * @return <code>true</code> for continue, <code>false</code> if the operation
95      * was canceled.
96      */

97     static boolean savePart(final ISaveablePart saveable, IWorkbenchPart part,
98             IWorkbenchWindow window, boolean confirm) {
99         // Short circuit.
100
if (!saveable.isDirty()) {
101             return true;
102         }
103
104         // If confirmation is required ..
105
if (confirm) {
106             int choice = AutomatedResponse;
107             if (choice == USER_RESPONSE) {
108                 if (saveable instanceof ISaveablePart2) {
109                     choice = ((ISaveablePart2)saveable).promptToSaveOnClose();
110                 }
111                 if (choice == USER_RESPONSE || choice == ISaveablePart2.DEFAULT) {
112                     String JavaDoc message = NLS.bind(WorkbenchMessages.EditorManager_saveChangesQuestion, part.getTitle());
113                     // Show a dialog.
114
String JavaDoc[] buttons = new String JavaDoc[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL };
115                         MessageDialog d = new MessageDialog(
116                             window.getShell(), WorkbenchMessages.Save_Resource,
117                             null, message, MessageDialog.QUESTION, buttons, 0);
118                     choice = d.open();
119                 }
120             }
121
122             // Branch on the user choice.
123
// The choice id is based on the order of button labels above.
124
switch (choice) {
125                 case ISaveablePart2.YES : //yes
126
break;
127                 case ISaveablePart2.NO : //no
128
return true;
129                 default :
130                 case ISaveablePart2.CANCEL : //cancel
131
return false;
132             }
133         }
134
135         if (saveable instanceof ISaveablesSource) {
136             return saveModels((ISaveablesSource) saveable, window, confirm);
137         }
138
139         // Create save block.
140
IRunnableWithProgress progressOp = new IRunnableWithProgress() {
141             public void run(IProgressMonitor monitor) {
142                 IProgressMonitor monitorWrap = new EventLoopProgressMonitor(monitor);
143                 saveable.doSave(monitorWrap);
144             }
145         };
146
147         // Do the save.
148
return runProgressMonitorOperation(WorkbenchMessages.Save, progressOp, window);
149     }
150     
151     /**
152      * Saves the selected dirty models from the given model source.
153      *
154      * @param modelSource the model source
155      * @param window the workbench window
156      * @param confirm
157      * @return <code>true</code> for continue, <code>false</code> if the operation
158      * was canceled or an error occurred while saving.
159      */

160     private static boolean saveModels(ISaveablesSource modelSource, final IWorkbenchWindow window, final boolean confirm) {
161         Saveable[] selectedModels = modelSource.getActiveSaveables();
162         final ArrayList JavaDoc dirtyModels = new ArrayList JavaDoc();
163         for (int i = 0; i < selectedModels.length; i++) {
164             Saveable model = selectedModels[i];
165             if (model.isDirty()) {
166                 dirtyModels.add(model);
167             }
168         }
169         if (dirtyModels.isEmpty()) {
170             return true;
171         }
172         
173         // Create save block.
174
IRunnableWithProgress progressOp = new IRunnableWithProgress() {
175             public void run(IProgressMonitor monitor) {
176                 IProgressMonitor monitorWrap = new EventLoopProgressMonitor(monitor);
177                 monitorWrap.beginTask(WorkbenchMessages.Save, dirtyModels.size());
178                 for (Iterator JavaDoc i = dirtyModels.iterator(); i.hasNext();) {
179                     Saveable model = (Saveable) i.next();
180                     // handle case where this model got saved as a result of saving another
181
if (!model.isDirty()) {
182                         monitor.worked(1);
183                         continue;
184                     }
185                     doSaveModel(model, new SubProgressMonitor(monitorWrap, 1),
186                             window, confirm);
187                     if (monitor.isCanceled()) {
188                         break;
189                     }
190                 }
191                 monitorWrap.done();
192             }
193         };
194
195         // Do the save.
196
return runProgressMonitorOperation(WorkbenchMessages.Save, progressOp, window);
197     }
198
199     /**
200      * Saves the workbench part ... this is similar to
201      * {@link SaveableHelper#savePart(ISaveablePart, IWorkbenchPart, IWorkbenchWindow, boolean) }
202      * except that the {@link ISaveablePart2#DEFAULT } case must cause the
203      * calling function to allow this part to participate in the default saving
204      * mechanism.
205      *
206      * @param saveable the part
207      * @param window the workbench window
208      * @param confirm request confirmation
209      * @return the ISaveablePart2 constant
210      */

211     static int savePart(final ISaveablePart2 saveable,
212             IWorkbenchWindow window, boolean confirm) {
213         // Short circuit.
214
if (!saveable.isDirty()) {
215             return ISaveablePart2.YES;
216         }
217
218         // If confirmation is required ..
219
if (confirm) {
220             int choice = AutomatedResponse;
221             if (choice == USER_RESPONSE) {
222                 choice = saveable.promptToSaveOnClose();
223             }
224
225             // Branch on the user choice.
226
// The choice id is based on the order of button labels above.
227
if (choice!=ISaveablePart2.YES) {
228                 return (choice==USER_RESPONSE?ISaveablePart2.DEFAULT:choice);
229             }
230         }
231
232         // Create save block.
233
IRunnableWithProgress progressOp = new IRunnableWithProgress() {
234             public void run(IProgressMonitor monitor) {
235                 IProgressMonitor monitorWrap = new EventLoopProgressMonitor(monitor);
236                 saveable.doSave(monitorWrap);
237             }
238         };
239
240         // Do the save.
241
if (!runProgressMonitorOperation(WorkbenchMessages.Save, progressOp,window)) {
242             return ISaveablePart2.CANCEL;
243         }
244         return ISaveablePart2.YES;
245     }
246     
247     /**
248      * Runs a progress monitor operation. Returns true if success, false if
249      * canceled.
250      */

251     static boolean runProgressMonitorOperation(String JavaDoc opName,
252             IRunnableWithProgress progressOp, IWorkbenchWindow window) {
253         return runProgressMonitorOperation(opName, progressOp, window, window);
254     }
255     
256     /**
257      * Runs a progress monitor operation.
258      * Returns true if success, false if canceled or an error occurred.
259      */

260     static boolean runProgressMonitorOperation(String JavaDoc opName,
261             final IRunnableWithProgress progressOp,
262             final IRunnableContext runnableContext, final IShellProvider shellProvider) {
263         final boolean[] success = new boolean[] { false };
264         IRunnableWithProgress runnable = new IRunnableWithProgress() {
265             public void run(IProgressMonitor monitor) throws InvocationTargetException JavaDoc, InterruptedException JavaDoc {
266                 progressOp.run(monitor);
267                 // Only indicate success if the monitor wasn't canceled
268
if (!monitor.isCanceled())
269                     success[0] = true;
270             }
271         };
272
273         try {
274             runnableContext.run(false, true, runnable);
275         } catch (InvocationTargetException JavaDoc e) {
276             String JavaDoc title = NLS.bind(WorkbenchMessages.EditorManager_operationFailed, opName );
277             Throwable JavaDoc targetExc = e.getTargetException();
278             WorkbenchPlugin.log(title, new Status(IStatus.WARNING,
279                     PlatformUI.PLUGIN_ID, 0, title, targetExc));
280             StatusUtil.handleStatus(title, targetExc, StatusManager.SHOW,
281                     shellProvider.getShell());
282             // Fall through to return failure
283
} catch (InterruptedException JavaDoc e) {
284             // The user pressed cancel. Fall through to return failure
285
} catch (OperationCanceledException e) {
286             // The user pressed cancel. Fall through to return failure
287
}
288         return success[0];
289     }
290
291     /**
292      * Returns whether the model source needs saving. This is true if any of
293      * the active models are dirty. This logic must correspond with
294      * {@link #saveModels} above.
295      *
296      * @param modelSource
297      * the model source
298      * @return <code>true</code> if save is required, <code>false</code>
299      * otherwise
300      * @since 3.2
301      */

302     public static boolean needsSave(ISaveablesSource modelSource) {
303         Saveable[] selectedModels = modelSource.getActiveSaveables();
304         for (int i = 0; i < selectedModels.length; i++) {
305             Saveable model = selectedModels[i];
306             if (model.isDirty() && !((InternalSaveable)model).isSavingInBackground()) {
307                 return true;
308             }
309         }
310         return false;
311     }
312
313     /**
314      * @param model
315      * @param progressMonitor
316      * @param shellProvider
317      * @param blockUntilSaved
318      */

319     public static void doSaveModel(final Saveable model,
320             IProgressMonitor progressMonitor,
321             final IShellProvider shellProvider, boolean blockUntilSaved) {
322         try {
323             Job backgroundSaveJob = ((InternalSaveable)model).getBackgroundSaveJob();
324             if (backgroundSaveJob != null) {
325                 boolean canceled = waitForBackgroundSaveJob(model);
326                 if (canceled) {
327                     progressMonitor.setCanceled(true);
328                     return;
329                 }
330                 // return early if the saveable is no longer dirty
331
if (!model.isDirty()) {
332                     return;
333                 }
334             }
335             final IJobRunnable[] backgroundSaveRunnable = new IJobRunnable[1];
336             try {
337                 SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 3);
338                 backgroundSaveRunnable[0] = model.doSave(
339                         subMonitor.newChild(2), shellProvider);
340                 if (backgroundSaveRunnable[0] == null) {
341                     // no further work needs to be done
342
return;
343                 }
344                 if (blockUntilSaved) {
345                     // for now, block on close by running the runnable in the UI
346
// thread
347
IStatus result = backgroundSaveRunnable[0].run(subMonitor
348                             .newChild(1));
349                     if (!result.isOK()) {
350                         StatusUtil.handleStatus(result, StatusManager.SHOW,
351                                 shellProvider.getShell());
352                         progressMonitor.setCanceled(true);
353                     }
354                     return;
355                 }
356                 // for the job family, we use the model object because based on
357
// the family we can display the busy state with an animated tab
358
// (see the calls to showBusyForFamily() below).
359
Job saveJob = new Job(NLS.bind(
360                         WorkbenchMessages.EditorManager_backgroundSaveJobName,
361                         model.getName())) {
362                     public boolean belongsTo(Object JavaDoc family) {
363                         if (family instanceof DynamicFamily) {
364                             return ((DynamicFamily)family).contains(model);
365                         }
366                         return family.equals(model);
367                     }
368
369                     protected IStatus run(IProgressMonitor monitor) {
370                         return backgroundSaveRunnable[0].run(monitor);
371                     }
372                 };
373                 // we will need the associated parts (for disabling their UI)
374
((InternalSaveable) model).setBackgroundSaveJob(saveJob);
375                 SaveablesList saveablesList = (SaveablesList) PlatformUI
376                         .getWorkbench().getService(
377                                 ISaveablesLifecycleListener.class);
378                 final IWorkbenchPart[] parts = saveablesList
379                         .getPartsForSaveable(model);
380
381                 // this will cause the parts tabs to show the ongoing background operation
382
for (int i = 0; i < parts.length; i++) {
383                     IWorkbenchPart workbenchPart = parts[i];
384                     IWorkbenchSiteProgressService progressService = (IWorkbenchSiteProgressService) workbenchPart
385                             .getSite().getAdapter(
386                                     IWorkbenchSiteProgressService.class);
387                     progressService.showBusyForFamily(model);
388                 }
389                 model.disableUI(parts, blockUntilSaved);
390                 // Add a listener for enabling the UI after the save job has
391
// finished, and for displaying an error dialog if
392
// necessary.
393
saveJob.addJobChangeListener(new JobChangeAdapter() {
394                     public void done(final IJobChangeEvent event) {
395                         ((InternalSaveable) model).setBackgroundSaveJob(null);
396                         shellProvider.getShell().getDisplay().asyncExec(
397                                 new Runnable JavaDoc() {
398                                     public void run() {
399                                         notifySaveAction(parts);
400                                         model.enableUI(parts);
401                                     }
402                                 });
403                     }
404                 });
405                 // Finally, we are ready to schedule the job.
406
saveJob.schedule();
407                 // the job was started - notify the save actions,
408
// this is done through the workbench windows, which
409
// we can get from the parts...
410
notifySaveAction(parts);
411             } catch (CoreException e) {
412                 StatusUtil.handleStatus(e.getStatus(), StatusManager.SHOW,
413                         shellProvider.getShell());
414                 progressMonitor.setCanceled(true);
415             }
416         } finally {
417             progressMonitor.done();
418         }
419     }
420
421     private static void notifySaveAction(final IWorkbenchPart[] parts) {
422         Set JavaDoc wwindows = new HashSet JavaDoc();
423         for (int i = 0; i < parts.length; i++) {
424             wwindows.add(parts[i].getSite().getWorkbenchWindow());
425         }
426         for (Iterator JavaDoc it = wwindows.iterator(); it.hasNext();) {
427             WorkbenchWindow wwin = (WorkbenchWindow) it.next();
428             wwin.fireBackgroundSaveStarted();
429         }
430     }
431
432     /**
433      * Waits for the background save job (if any) of the given saveable to complete.
434      * This may open a progress dialog with the option to cancel.
435      *
436      * @param modelToSave
437      * @return true if the user canceled.
438      */

439     private static boolean waitForBackgroundSaveJob(final Saveable model) {
440         List JavaDoc models = new ArrayList JavaDoc();
441         models.add(model);
442         return waitForBackgroundSaveJobs(models);
443     }
444     
445     /**
446      * Waits for the background save jobs (if any) of the given saveables to complete.
447      * This may open a progress dialog with the option to cancel.
448      *
449      * @param modelsToSave
450      * @return true if the user canceled.
451      */

452     public static boolean waitForBackgroundSaveJobs(final List JavaDoc modelsToSave) {
453         // block if any of the saveables is still saving in the background
454
try {
455             PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {
456                 public void run(IProgressMonitor monitor) throws InterruptedException JavaDoc {
457                     Job.getJobManager().join(new DynamicFamily(modelsToSave), monitor);
458                 }
459             });
460         } catch (InvocationTargetException JavaDoc e) {
461             StatusUtil.handleStatus(e, StatusManager.SHOW | StatusManager.LOG);
462         } catch (InterruptedException JavaDoc e) {
463             return true;
464         }
465         // remove saveables that are no longer dirty from the list
466
for (Iterator JavaDoc it = modelsToSave.iterator(); it.hasNext();) {
467             Saveable model = (Saveable) it.next();
468             if (!model.isDirty()) {
469                 it.remove();
470             }
471         }
472         return false;
473     }
474     
475     private static class DynamicFamily extends HashSet JavaDoc {
476         private static final long serialVersionUID = 1L;
477         public DynamicFamily(Collection JavaDoc collection) {
478             super(collection);
479         }
480     }
481
482 }
483
Popular Tags