blob: cb7d1f59134223e65ffd0fb8a4a8769ed3fc7bea [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 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.actions;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
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.events.SelectionListener;
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.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
/**
* Standard action for deleting the currently selected resources.
* <p>
* This class may be instantiated; it is not intended to be subclassed.
* </p>
*/
public class DeleteResourceAction extends SelectionListenerAction {
static class DeleteProjectDialog extends MessageDialog {
private IResource[] projects;
private boolean deleteContent = false;
/**
* Control testing mode. In testing mode, it returns true to
* delete contents and does not pop up the dialog.
*/
private boolean fIsTesting = false;
private Button radio1;
private Button radio2;
DeleteProjectDialog(Shell parentShell, IResource[] projects) {
super(parentShell, getTitle(projects), null, // accept the default window icon
getMessage(projects), MessageDialog.QUESTION, new String[] {
IDialogConstants.YES_LABEL,
IDialogConstants.NO_LABEL }, 0); // yes is the default
this.projects = projects;
}
static String getTitle(IResource[] projects) {
if (projects.length == 1)
return IDEWorkbenchMessages.DeleteResourceAction_titleProject1;
return IDEWorkbenchMessages.DeleteResourceAction_titleProjectN;
}
static String getMessage(IResource[] projects) {
if (projects.length == 1) {
IProject project = (IProject) projects[0];
return NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_confirmProject1, project.getName());
}
return NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_confirmProjectN, new Integer(projects.length));
}
/* (non-Javadoc)
* Method declared on Window.
*/
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell,
IIDEHelpContextIds.DELETE_PROJECT_DIALOG);
}
protected Control createCustomArea(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayout(new GridLayout());
radio1 = new Button(composite, SWT.RADIO);
radio1.addSelectionListener(selectionListener);
String text1;
if (projects.length == 1) {
IProject project = (IProject) projects[0];
if (project == null || project.getLocation() == null)
text1 = IDEWorkbenchMessages.DeleteResourceAction_deleteContentsN;
else
text1 = NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_deleteContents1, project.getLocation().toOSString());
} else {
text1 = IDEWorkbenchMessages.DeleteResourceAction_deleteContentsN;
}
radio1.setText(text1);
radio1.setFont(parent.getFont());
radio2 = new Button(composite, SWT.RADIO);
radio2.addSelectionListener(selectionListener);
String text2 = IDEWorkbenchMessages.DeleteResourceAction_doNotDeleteContents;
radio2.setText(text2);
radio2.setFont(parent.getFont());
// set initial state
radio1.setSelection(deleteContent);
radio2.setSelection(!deleteContent);
return composite;
}
private SelectionListener selectionListener = new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
Button button = (Button) e.widget;
if (button.getSelection()) {
deleteContent = (button == radio1);
}
}
};
boolean getDeleteContent() {
return deleteContent;
}
/* (non-Javadoc)
* @see org.eclipse.jface.window.Window#open()
*/
public int open() {
//Override Window#open() to allow for non-interactive testing.
if (fIsTesting) {
deleteContent = true;
return Window.OK;
}
return super.open();
}
/**
* Set this delete dialog into testing mode. It won't pop up,
* and it returns true for deleteContent.
*
* @param t the testing mode
*/
void setTestingMode(boolean t) {
fIsTesting = t;
}
}
/**
* The id of this action.
*/
public static final String ID = PlatformUI.PLUGIN_ID
+ ".DeleteResourceAction";//$NON-NLS-1$
/**
* The shell in which to show any dialogs.
*/
private Shell shell;
/**
* Whether or not we are deleting content for projects.
*/
private boolean deleteContent = false;
/**
* Whether or not to automatically delete out of sync resources
*/
private boolean forceOutOfSyncDelete = false;
/**
* Flag that allows testing mode ... it won't pop up the project
* delete dialog, and will return "delete all content".
*/
protected boolean fTestingMode = false;
/**
* Creates a new delete resource action.
*
* @param shell the shell for any dialogs
*/
public DeleteResourceAction(Shell shell) {
super(IDEWorkbenchMessages.DeleteResourceAction_text);
setToolTipText(IDEWorkbenchMessages.DeleteResourceAction_toolTip);
PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
IIDEHelpContextIds.DELETE_RESOURCE_ACTION);
setId(ID);
if (shell == null) {
throw new IllegalArgumentException();
}
this.shell = shell;
}
/**
* Returns whether delete can be performed on the current selection.
*
* @param resources the selected resources
* @return <code>true</code> if the resources can be deleted, and
* <code>false</code> if the selection contains non-resources or phantom
* resources
*/
private boolean canDelete(IResource[] resources) {
// allow only projects or only non-projects to be selected;
// note that the selection may contain multiple types of resource
if (!(containsOnlyProjects(resources) || containsOnlyNonProjects(resources))) {
return false;
}
if (resources.length == 0)
return false;
// Return true if everything in the selection exists.
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource.isPhantom()) {
return false;
}
}
return true;
}
/**
* Returns whether the selection contains linked resources.
*
* @param resources the selected resources
* @return <code>true</code> if the resources contain linked
* resources, and <code>false</code> otherwise
*/
private boolean containsLinkedResource(IResource[] resources) {
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource.isLinked())
return true;
}
return false;
}
/**
* Returns whether the selection contains only non-projects.
*
* @param resources the selected resources
* @return <code>true</code> if the resources contains only non-projects, and
* <code>false</code> otherwise
*/
private boolean containsOnlyNonProjects(IResource[] resources) {
int types = getSelectedResourceTypes(resources);
// check for empty selection
if (types == 0)
return false;
// note that the selection may contain multiple types of resource
return (types & IResource.PROJECT) == 0;
}
/**
* Returns whether the selection contains only projects.
*
* @param resources the selected resources
* @return <code>true</code> if the resources contains only projects, and
* <code>false</code> otherwise
*/
private boolean containsOnlyProjects(IResource[] resources) {
int types = getSelectedResourceTypes(resources);
// note that the selection may contain multiple types of resource
return types == IResource.PROJECT;
}
/**
* Creates and returns a result status appropriate for the given list of exceptions.
* @param exceptions The list of exceptions that occurred (may be empty)
* @return The result status for the deletion
*/
private IStatus createResult(List exceptions) {
if (exceptions.isEmpty())
return Status.OK_STATUS;
final int exceptionCount = exceptions.size();
if (exceptionCount == 1) {
return ((CoreException) exceptions.get(0)).getStatus();
}
CoreException[] children = (CoreException[]) exceptions.toArray(new CoreException[exceptionCount]);
boolean outOfSync = false;
for (int i = 0; i < children.length; i++) {
if (children[i].getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
outOfSync = true;
break;
}
}
String title = outOfSync ? IDEWorkbenchMessages.DeleteResourceAction_outOfSyncError : IDEWorkbenchMessages.DeleteResourceAction_deletionExceptionMessage;
final MultiStatus multi = new MultiStatus(
IDEWorkbenchPlugin.IDE_WORKBENCH,
0,
title, null);
for (int i = 0; i < exceptionCount; i++) {
CoreException exception = children[i];
IStatus status = exception.getStatus();
multi.add(new Status(status.getSeverity(), status.getPlugin(),
status.getCode(), status.getMessage(), exception));
}
return multi;
}
/**
* Asks the user to confirm a delete operation.
*
* @param resources the selected resources
* @return <code>true</code> if the user says to go ahead, and <code>false</code>
* if the deletion should be abandoned
*/
private boolean confirmDelete(IResource[] resources) {
if (containsOnlyProjects(resources)) {
return confirmDeleteProjects(resources);
}
return confirmDeleteNonProjects(resources);
}
/**
* Asks the user to confirm a delete operation,
* where the selection contains no projects.
*
* @param resources the selected resources
* @return <code>true</code> if the user says to go ahead, and <code>false</code>
* if the deletion should be abandoned
*/
private boolean confirmDeleteNonProjects(IResource[] resources) {
String title;
String msg;
if (resources.length == 1) {
title = IDEWorkbenchMessages.DeleteResourceAction_title1;
IResource resource = resources[0];
if (resource.isLinked())
msg = NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_confirmLinkedResource1, resource.getName());
else
msg = NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_confirm1, resource.getName());
} else {
title = IDEWorkbenchMessages.DeleteResourceAction_titleN;
if (containsLinkedResource(resources))
msg = NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_confirmLinkedResourceN, new Integer(resources.length));
else
msg = NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_confirmN, new Integer(resources.length));
}
return MessageDialog.openQuestion(shell, title, msg);
}
/**
* Asks the user to confirm a delete operation,
* where the selection contains only projects.
* Also remembers whether project content should be deleted.
*
* @param resources the selected resources
* @return <code>true</code> if the user says to go ahead, and <code>false</code>
* if the deletion should be abandoned
*/
private boolean confirmDeleteProjects(IResource[] resources) {
DeleteProjectDialog dialog = new DeleteProjectDialog(shell, resources);
dialog.setTestingMode(fTestingMode);
int code = dialog.open();
deleteContent = dialog.getDeleteContent();
return code == 0; // YES
}
/**
* Deletes the given resources.
*/
private void delete(IResource[] resourcesToDelete, IProgressMonitor monitor) throws CoreException{
final List exceptions = new ArrayList();
forceOutOfSyncDelete = false;
monitor.beginTask("", resourcesToDelete.length); //$NON-NLS-1$
try {
for (int i = 0; i < resourcesToDelete.length; ++i) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
try {
delete(resourcesToDelete[i], new SubProgressMonitor(monitor, 1,
SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
} catch (CoreException e) {
exceptions.add(e);
}
}
IStatus result = createResult(exceptions);
if (!result.isOK())
throw new CoreException(result);
} finally {
monitor.done();
}
}
/**
* Deletes the given resource.
*/
private void delete(IResource resourceToDelete, IProgressMonitor monitor)
throws CoreException {
boolean force = false; // don't force deletion of out-of-sync resources
try {
if (resourceToDelete.getType() == IResource.PROJECT) {
// if it's a project, ask whether content should be deleted too
IProject project = (IProject) resourceToDelete;
project.delete(deleteContent, force, monitor);
} else {
// if it's not a project, just delete it
resourceToDelete.delete(IResource.KEEP_HISTORY, monitor);
}
} catch (CoreException exception) {
if (resourceToDelete.getType() == IResource.FILE) {
IStatus[] children = exception.getStatus().getChildren();
if (children.length == 1
&& children[0].getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
if (forceOutOfSyncDelete) {
resourceToDelete.delete(IResource.KEEP_HISTORY
| IResource.FORCE, monitor);
} else {
int result = queryDeleteOutOfSync(resourceToDelete);
if (result == IDialogConstants.YES_ID) {
resourceToDelete.delete(IResource.KEEP_HISTORY
| IResource.FORCE, monitor);
} else if (result == IDialogConstants.YES_TO_ALL_ID) {
forceOutOfSyncDelete = true;
resourceToDelete.delete(IResource.KEEP_HISTORY
| IResource.FORCE, monitor);
} else if (result == IDialogConstants.CANCEL_ID) {
throw new OperationCanceledException();
}
}
} else {
throw exception;
}
} else {
throw exception;
}
}
}
/**
* Return an array of the currently selected resources.
*
* @return the selected resources
*/
private IResource[] getSelectedResourcesArray() {
List selection = getSelectedResources();
IResource[] resources = new IResource[selection.size()];
selection.toArray(resources);
return resources;
}
/**
* Returns a bit-mask containing the types of resources in the selection.
*
* @param resources the selected resources
*/
private int getSelectedResourceTypes(IResource[] resources) {
int types = 0;
for (int i = 0; i < resources.length; i++) {
types |= resources[i].getType();
}
return types;
}
/* (non-Javadoc)
* Method declared on IAction.
*/
public void run() {
IResource[] resources = getSelectedResourcesArray();
// WARNING: do not query the selected resources more than once
// since the selection may change during the run,
// e.g. due to window activation when the prompt dialog is dismissed.
// For more details, see Bug 60606 [Navigator] (data loss) Navigator deletes/moves the wrong file
if (!confirmDelete(resources))
return;
final IResource[] resourcesToDelete = getResourcesToDelete(resources);
if (resourcesToDelete.length == 0)
return;
//use a non-workspace job with a runnable inside so we can avoid periodic updates
Job deleteJob = new Job(IDEWorkbenchMessages.DeleteResourceAction_jobName) {
public IStatus run(IProgressMonitor monitor) {
try {
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
delete(resourcesToDelete, monitor);
}
}, null, IWorkspace.AVOID_UPDATE, monitor);
} catch (CoreException e) {
return e.getStatus();
}
return Status.OK_STATUS;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
*/
public boolean belongsTo(Object family) {
if (IDEWorkbenchMessages.DeleteResourceAction_jobName
.equals(family)) {
return true;
}
return super.belongsTo(family);
}
};
deleteJob.setRule(getDeleteRule(resourcesToDelete));
deleteJob.setUser(true);
deleteJob.schedule();
}
/*
* Return the scheduling rule that encompasses the deletion of the selected resources
*/
private ISchedulingRule getDeleteRule(IResource[] resourcesToDelete) {
IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory();
ISchedulingRule combinedRule = null;
for (int i = 0; i < resourcesToDelete.length; i++) {
IResource resource = resourcesToDelete[i];
ISchedulingRule deleteRule = ruleFactory.deleteRule(resource);
if (combinedRule == null) {
combinedRule = deleteRule;
} else {
combinedRule = MultiRule.combine(combinedRule, deleteRule);
}
}
return combinedRule;
}
/**
* Returns the resources to delete based on the selection and their read-only status.
*
* @param resources the selected resources
* @return the resources to delete
*/
private IResource[] getResourcesToDelete(IResource[] resources) {
if (containsOnlyProjects(resources) && !deleteContent) {
// We can just return the selection
return resources;
}
ReadOnlyStateChecker checker = new ReadOnlyStateChecker(this.shell,
IDEWorkbenchMessages.DeleteResourceAction_title1,
IDEWorkbenchMessages.DeleteResourceAction_readOnlyQuestion);
checker.setIgnoreLinkedResources(true);
return checker.checkReadOnlyResources(resources);
}
/**
* The <code>DeleteResourceAction</code> implementation of this
* <code>SelectionListenerAction</code> method disables the action
* if the selection contains phantom resources or non-resources
*/
protected boolean updateSelection(IStructuredSelection selection) {
return super.updateSelection(selection)
&& canDelete(getSelectedResourcesArray());
}
/**
* Ask the user whether the given resource should be deleted
* despite being out of sync with the file system.
* @param resource the out of sync resource
* @return One of the IDialogConstants constants indicating which
* of the Yes, Yes to All, No, Cancel options has been selected by
* the user.
*/
private int queryDeleteOutOfSync(IResource resource) {
final MessageDialog dialog = new MessageDialog(
shell,
IDEWorkbenchMessages.DeleteResourceAction_messageTitle,
null,
NLS.bind(IDEWorkbenchMessages.DeleteResourceAction_outOfSyncQuestion, resource.getName()),
MessageDialog.QUESTION, new String[] {
IDialogConstants.YES_LABEL,
IDialogConstants.YES_TO_ALL_LABEL,
IDialogConstants.NO_LABEL,
IDialogConstants.CANCEL_LABEL }, 0);
shell.getDisplay().syncExec(new Runnable() {
public void run() {
dialog.open();
}
});
int result = dialog.getReturnCode();
if (result == 0)
return IDialogConstants.YES_ID;
if (result == 1)
return IDialogConstants.YES_TO_ALL_ID;
if (result == 2)
return IDialogConstants.NO_ID;
return IDialogConstants.CANCEL_ID;
}
}