org.eclipse.ui.internal.SaveableHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of workbench Show documentation
Show all versions of workbench Show documentation
This plug-in contains the bulk of the Workbench implementation, and depends on JFace, SWT, and Core Runtime. It cannot be used independently from org.eclipse.ui. Workbench client plug-ins should not depend directly on this plug-in.
The newest version!
/*******************************************************************************
* Copyright (c) 2004, 2007 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.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.osgi.util.NLS;
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.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.progress.IJobRunnable;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.statushandlers.StatusManager;
/**
* Helper class for prompting to save dirty views or editors.
*
* @since 3.0.1
*/
public class SaveableHelper {
/**
* The helper must prompt.
*/
public static final int USER_RESPONSE = -1;
private static int AutomatedResponse = USER_RESPONSE;
/**
* FOR USE BY THE AUTOMATED TEST HARNESS ONLY.
*
* Sets the response to use when savePart
is called with confirm=true
.
*
* @param response 0 for yes, 1 for no, 2 for cancel, -1 for default (prompt)
*/
public static void testSetAutomatedResponse(int response) {
AutomatedResponse = response;
}
/**
* FOR USE BY THE AUTOMATED TEST HARNESS ONLY.
*
* Sets the response to use when savePart
is called with confirm=true
.
*
* @return 0 for yes, 1 for no, 2 for cancel, -1 for default (prompt)
*/
public static int testGetAutomatedResponse() {
return AutomatedResponse;
}
/**
* Saves the workbench part.
*
* @param saveable the part
* @param part the same part
* @param window the workbench window
* @param confirm request confirmation
* @return true
for continue, false
if the operation
* was canceled.
*/
static boolean savePart(final ISaveablePart saveable, IWorkbenchPart part,
IWorkbenchWindow window, boolean confirm) {
// Short circuit.
if (!saveable.isDirty()) {
return true;
}
// If confirmation is required ..
if (confirm) {
int choice = AutomatedResponse;
if (choice == USER_RESPONSE) {
if (saveable instanceof ISaveablePart2) {
choice = ((ISaveablePart2)saveable).promptToSaveOnClose();
}
if (choice == USER_RESPONSE || choice == ISaveablePart2.DEFAULT) {
String message = NLS.bind(WorkbenchMessages.EditorManager_saveChangesQuestion, part.getTitle());
// Show a dialog.
String[] buttons = new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL };
MessageDialog d = new MessageDialog(
window.getShell(), WorkbenchMessages.Save_Resource,
null, message, MessageDialog.QUESTION, buttons, 0);
choice = d.open();
}
}
// Branch on the user choice.
// The choice id is based on the order of button labels above.
switch (choice) {
case ISaveablePart2.YES : //yes
break;
case ISaveablePart2.NO : //no
return true;
default :
case ISaveablePart2.CANCEL : //cancel
return false;
}
}
if (saveable instanceof ISaveablesSource) {
return saveModels((ISaveablesSource) saveable, window, confirm);
}
// Create save block.
IRunnableWithProgress progressOp = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) {
IProgressMonitor monitorWrap = new EventLoopProgressMonitor(monitor);
saveable.doSave(monitorWrap);
}
};
// Do the save.
return runProgressMonitorOperation(WorkbenchMessages.Save, progressOp, window);
}
/**
* Saves the selected dirty models from the given model source.
*
* @param modelSource the model source
* @param window the workbench window
* @param confirm
* @return true
for continue, false
if the operation
* was canceled or an error occurred while saving.
*/
private static boolean saveModels(ISaveablesSource modelSource, final IWorkbenchWindow window, final boolean confirm) {
Saveable[] selectedModels = modelSource.getActiveSaveables();
final ArrayList dirtyModels = new ArrayList();
for (int i = 0; i < selectedModels.length; i++) {
Saveable model = selectedModels[i];
if (model.isDirty()) {
dirtyModels.add(model);
}
}
if (dirtyModels.isEmpty()) {
return true;
}
// Create save block.
IRunnableWithProgress progressOp = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) {
IProgressMonitor monitorWrap = new EventLoopProgressMonitor(monitor);
monitorWrap.beginTask(WorkbenchMessages.Save, dirtyModels.size());
for (Iterator i = dirtyModels.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;
}
doSaveModel(model, new SubProgressMonitor(monitorWrap, 1),
window, confirm);
if (monitor.isCanceled()) {
break;
}
}
monitorWrap.done();
}
};
// Do the save.
return runProgressMonitorOperation(WorkbenchMessages.Save, progressOp, window);
}
/**
* Saves the workbench part ... this is similar to
* {@link SaveableHelper#savePart(ISaveablePart, IWorkbenchPart, IWorkbenchWindow, boolean) }
* except that the {@link ISaveablePart2#DEFAULT } case must cause the
* calling function to allow this part to participate in the default saving
* mechanism.
*
* @param saveable the part
* @param window the workbench window
* @param confirm request confirmation
* @return the ISaveablePart2 constant
*/
static int savePart(final ISaveablePart2 saveable,
IWorkbenchWindow window, boolean confirm) {
// Short circuit.
if (!saveable.isDirty()) {
return ISaveablePart2.YES;
}
// If confirmation is required ..
if (confirm) {
int choice = AutomatedResponse;
if (choice == USER_RESPONSE) {
choice = saveable.promptToSaveOnClose();
}
// Branch on the user choice.
// The choice id is based on the order of button labels above.
if (choice!=ISaveablePart2.YES) {
return (choice==USER_RESPONSE?ISaveablePart2.DEFAULT:choice);
}
}
// Create save block.
IRunnableWithProgress progressOp = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) {
IProgressMonitor monitorWrap = new EventLoopProgressMonitor(monitor);
saveable.doSave(monitorWrap);
}
};
// Do the save.
if (!runProgressMonitorOperation(WorkbenchMessages.Save, progressOp,window)) {
return ISaveablePart2.CANCEL;
}
return ISaveablePart2.YES;
}
/**
* Runs a progress monitor operation. Returns true if success, false if
* canceled.
*/
static boolean runProgressMonitorOperation(String opName,
IRunnableWithProgress progressOp, IWorkbenchWindow window) {
return runProgressMonitorOperation(opName, progressOp, window, window);
}
/**
* Runs a progress monitor operation.
* Returns true if success, false if canceled or an error occurred.
*/
static boolean runProgressMonitorOperation(String opName,
final IRunnableWithProgress progressOp,
final IRunnableContext runnableContext, final IShellProvider shellProvider) {
final boolean[] success = new boolean[] { false };
IRunnableWithProgress runnable = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
progressOp.run(monitor);
// Only indicate success if the monitor wasn't canceled
if (!monitor.isCanceled())
success[0] = true;
}
};
try {
runnableContext.run(false, true, runnable);
} catch (InvocationTargetException e) {
String title = NLS.bind(WorkbenchMessages.EditorManager_operationFailed, opName );
Throwable targetExc = e.getTargetException();
WorkbenchPlugin.log(title, new Status(IStatus.WARNING,
PlatformUI.PLUGIN_ID, 0, title, targetExc));
StatusUtil.handleStatus(title, targetExc, StatusManager.SHOW,
shellProvider.getShell());
// Fall through to return failure
} catch (InterruptedException e) {
// The user pressed cancel. Fall through to return failure
} catch (OperationCanceledException e) {
// The user pressed cancel. Fall through to return failure
}
return success[0];
}
/**
* Returns whether the model source needs saving. This is true if any of
* the active models are dirty. This logic must correspond with
* {@link #saveModels} above.
*
* @param modelSource
* the model source
* @return true
if save is required, false
* otherwise
* @since 3.2
*/
public static boolean needsSave(ISaveablesSource modelSource) {
Saveable[] selectedModels = modelSource.getActiveSaveables();
for (int i = 0; i < selectedModels.length; i++) {
Saveable model = selectedModels[i];
if (model.isDirty() && !((InternalSaveable)model).isSavingInBackground()) {
return true;
}
}
return false;
}
/**
* @param model
* @param progressMonitor
* @param shellProvider
* @param blockUntilSaved
*/
public static void doSaveModel(final Saveable model,
IProgressMonitor progressMonitor,
final IShellProvider shellProvider, boolean blockUntilSaved) {
try {
Job backgroundSaveJob = ((InternalSaveable)model).getBackgroundSaveJob();
if (backgroundSaveJob != null) {
boolean canceled = waitForBackgroundSaveJob(model);
if (canceled) {
progressMonitor.setCanceled(true);
return;
}
// return early if the saveable is no longer dirty
if (!model.isDirty()) {
return;
}
}
final IJobRunnable[] backgroundSaveRunnable = new IJobRunnable[1];
try {
SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 3);
backgroundSaveRunnable[0] = model.doSave(
subMonitor.newChild(2), shellProvider);
if (backgroundSaveRunnable[0] == null) {
// no further work needs to be done
return;
}
if (blockUntilSaved) {
// for now, block on close by running the runnable in the UI
// thread
IStatus result = backgroundSaveRunnable[0].run(subMonitor
.newChild(1));
if (!result.isOK()) {
StatusUtil.handleStatus(result, StatusManager.SHOW,
shellProvider.getShell());
progressMonitor.setCanceled(true);
}
return;
}
// for the job family, we use the model object because based on
// the family we can display the busy state with an animated tab
// (see the calls to showBusyForFamily() below).
Job saveJob = new Job(NLS.bind(
WorkbenchMessages.EditorManager_backgroundSaveJobName,
model.getName())) {
public boolean belongsTo(Object family) {
if (family instanceof DynamicFamily) {
return ((DynamicFamily)family).contains(model);
}
return family.equals(model);
}
protected IStatus run(IProgressMonitor monitor) {
return backgroundSaveRunnable[0].run(monitor);
}
};
// we will need the associated parts (for disabling their UI)
((InternalSaveable) model).setBackgroundSaveJob(saveJob);
SaveablesList saveablesList = (SaveablesList) PlatformUI
.getWorkbench().getService(
ISaveablesLifecycleListener.class);
final IWorkbenchPart[] parts = saveablesList
.getPartsForSaveable(model);
// this will cause the parts tabs to show the ongoing background operation
for (int i = 0; i < parts.length; i++) {
IWorkbenchPart workbenchPart = parts[i];
IWorkbenchSiteProgressService progressService = (IWorkbenchSiteProgressService) workbenchPart
.getSite().getAdapter(
IWorkbenchSiteProgressService.class);
progressService.showBusyForFamily(model);
}
model.disableUI(parts, blockUntilSaved);
// Add a listener for enabling the UI after the save job has
// finished, and for displaying an error dialog if
// necessary.
saveJob.addJobChangeListener(new JobChangeAdapter() {
public void done(final IJobChangeEvent event) {
((InternalSaveable) model).setBackgroundSaveJob(null);
shellProvider.getShell().getDisplay().asyncExec(
new Runnable() {
public void run() {
notifySaveAction(parts);
model.enableUI(parts);
}
});
}
});
// Finally, we are ready to schedule the job.
saveJob.schedule();
// the job was started - notify the save actions,
// this is done through the workbench windows, which
// we can get from the parts...
notifySaveAction(parts);
} catch (CoreException e) {
StatusUtil.handleStatus(e.getStatus(), StatusManager.SHOW,
shellProvider.getShell());
progressMonitor.setCanceled(true);
}
} finally {
progressMonitor.done();
}
}
private static void notifySaveAction(final IWorkbenchPart[] parts) {
Set wwindows = new HashSet();
for (int i = 0; i < parts.length; i++) {
wwindows.add(parts[i].getSite().getWorkbenchWindow());
}
for (Iterator it = wwindows.iterator(); it.hasNext();) {
WorkbenchWindow wwin = (WorkbenchWindow) it.next();
wwin.fireBackgroundSaveStarted();
}
}
/**
* Waits for the background save job (if any) of the given saveable to complete.
* This may open a progress dialog with the option to cancel.
*
* @param modelToSave
* @return true if the user canceled.
*/
private static boolean waitForBackgroundSaveJob(final Saveable model) {
List models = new ArrayList();
models.add(model);
return waitForBackgroundSaveJobs(models);
}
/**
* Waits for the background save jobs (if any) of the given saveables to complete.
* This may open a progress dialog with the option to cancel.
*
* @param modelsToSave
* @return true if the user canceled.
*/
public static boolean waitForBackgroundSaveJobs(final List modelsToSave) {
// block if any of the saveables is still saving in the background
try {
PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InterruptedException {
Job.getJobManager().join(new DynamicFamily(modelsToSave), monitor);
}
});
} catch (InvocationTargetException e) {
StatusUtil.handleStatus(e, StatusManager.SHOW | StatusManager.LOG);
} catch (InterruptedException e) {
return true;
}
// remove saveables that are no longer dirty from the list
for (Iterator it = modelsToSave.iterator(); it.hasNext();) {
Saveable model = (Saveable) it.next();
if (!model.isDirty()) {
it.remove();
}
}
return false;
}
private static class DynamicFamily extends HashSet {
private static final long serialVersionUID = 1L;
public DynamicFamily(Collection collection) {
super(collection);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy