blob: 6bf43aef0387ba966b942fd08a0a1f03461a1f9e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.actions;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
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.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.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.help.WorkbenchHelp;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.IHelpContextIds;
/**
* 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 List projects;
private boolean deleteContent = false;
private Button radio1;
private Button radio2;
DeleteProjectDialog(Shell parentShell, List 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(List projects) {
if (projects.size() == 1)
return IDEWorkbenchMessages.getString("DeleteResourceAction.titleProject1"); //$NON-NLS-1$
else
return IDEWorkbenchMessages.getString("DeleteResourceAction.titleProjectN"); //$NON-NLS-1$
}
static String getMessage(List projects) {
if (projects.size() == 1) {
IProject project = (IProject) projects.get(0);
return IDEWorkbenchMessages.format("DeleteResourceAction.confirmProject1", new Object[] { project.getName() }); //$NON-NLS-1$
}
else {
return IDEWorkbenchMessages.format("DeleteResourceAction.confirmProjectN", new Object[] { new Integer(projects.size()) }); //$NON-NLS-1$
}
}
/* (non-Javadoc)
* Method declared on Window.
*/
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
WorkbenchHelp.setHelp(newShell, IHelpContextIds.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.size() == 1) {
IProject project = (IProject) projects.get(0);
if(project == null || project.getLocation() == null)
text1 = IDEWorkbenchMessages.getString("DeleteResourceAction.deleteContentsN"); //$NON-NLS-1$
else
text1 = IDEWorkbenchMessages.format("DeleteResourceAction.deleteContents1", new Object[] { project.getLocation().toOSString() }); //$NON-NLS-1$
} else {
text1 = IDEWorkbenchMessages.getString("DeleteResourceAction.deleteContentsN"); //$NON-NLS-1$
}
radio1.setText(text1);
radio1.setFont(parent.getFont());
radio2 = new Button(composite, SWT.RADIO);
radio2.addSelectionListener(selectionListener);
String text2 = IDEWorkbenchMessages.getString("DeleteResourceAction.doNotDeleteContents"); //$NON-NLS-1$
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);
}
}
};
public boolean getDeleteContent() {
return deleteContent;
}
}
/**
* 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;
/**
* Creates a new delete resource action.
*
* @param shell the shell for any dialogs
*/
public DeleteResourceAction(Shell shell) {
super(IDEWorkbenchMessages.getString("DeleteResourceAction.text")); //$NON-NLS-1$
setToolTipText(IDEWorkbenchMessages.getString("DeleteResourceAction.toolTip")); //$NON-NLS-1$
WorkbenchHelp.setHelp(this, IHelpContextIds.DELETE_RESOURCE_ACTION);
setId(ID);
if (shell == null) {
throw new IllegalArgumentException();
}
this.shell = shell;
}
/**
* Returns whether delete can be performed on the current selection.
*
* @return <code>true</code> if the resources can be deleted, and
* <code>false</code> if the selection contains non-resources or phantom
* resources
*/
boolean canDelete() {
// allow only projects or only non-projects to be selected;
// note that the selection may contain multiple types of resource
if (!(containsOnlyProjects() || containsOnlyNonProjects())) {
return false;
}
List resources = getSelectedResources();
if (resources.size() == 0) return false;
// Return true if everything in the selection exists.
for (Iterator e = resources.iterator(); e.hasNext();) {
IResource next = (IResource)e.next();
if (next.isPhantom()) {
return false;
}
}
return true;
}
/**
* Returns whether the selection contains linked resources.
*
* @return <code>true</code> if the resources contain linked
* resources, and <code>false</code> otherwise
*/
boolean containsLinkedResource() {
Iterator iterator = getSelectedResources().iterator();
while (iterator.hasNext()) {
IResource resource = (IResource) iterator.next();
if (resource.isLinked()) return true;
}
return false;
}
/**
* Returns whether the selection contains only non-projects.
*
* @return <code>true</code> if the resources contains only non-projects, and
* <code>false</code> otherwise
*/
boolean containsOnlyNonProjects() {
if (getSelectedNonResources().size() > 0) return false;
int types = getSelectedResourceTypes();
// 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.
*
* @return <code>true</code> if the resources contains only projects, and
* <code>false</code> otherwise
*/
boolean containsOnlyProjects() {
if (getSelectedNonResources().size() > 0) return false;
int types = getSelectedResourceTypes();
// note that the selection may contain multiple types of resource
return types == IResource.PROJECT;
}
/**
* Asks the user to confirm a delete operation.
*
* @return <code>true</code> if the user says to go ahead, and <code>false</code>
* if the deletion should be abandoned
*/
boolean confirmDelete() {
if (containsOnlyProjects()) {
return confirmDeleteProjects();
}
else {
return confirmDeleteNonProjects();
}
}
/**
* Asks the user to confirm a delete operation,
* where the selection contains no projects.
*
* @return <code>true</code> if the user says to go ahead, and <code>false</code>
* if the deletion should be abandoned
*/
boolean confirmDeleteNonProjects() {
List resources = getSelectedResources();
String title;
String msg;
if (resources.size() == 1) {
title = IDEWorkbenchMessages.getString("DeleteResourceAction.title1"); //$NON-NLS-1$
IResource resource = (IResource) resources.get(0);
if (resource.isLinked())
msg = IDEWorkbenchMessages.format("DeleteResourceAction.confirmLinkedResource1", new Object[] { resource.getName() }); //$NON-NLS-1$
else
msg = IDEWorkbenchMessages.format("DeleteResourceAction.confirm1", new Object[] { resource.getName() }); //$NON-NLS-1$
}
else {
title = IDEWorkbenchMessages.getString("DeleteResourceAction.titleN"); //$NON-NLS-1$
if (containsLinkedResource())
msg = IDEWorkbenchMessages.format("DeleteResourceAction.confirmLinkedResourceN", new Object[] { new Integer(resources.size()) }); //$NON-NLS-1$
else
msg = IDEWorkbenchMessages.format("DeleteResourceAction.confirmN", new Object[] { new Integer(resources.size()) }); //$NON-NLS-1$
}
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.
*
* @return <code>true</code> if the user says to go ahead, and <code>false</code>
* if the deletion should be abandoned
*/
boolean confirmDeleteProjects() {
List resources = getSelectedResources();
DeleteProjectDialog dialog = new DeleteProjectDialog(shell, resources);
int code = dialog.open();
deleteContent = dialog.getDeleteContent();
return code == 0; // YES
}
/**
* Deletes the given resources.
*/
void delete(IResource[] resourcesToDelete, IProgressMonitor monitor) throws CoreException {
final List exceptions = new ArrayList();
forceOutOfSyncDelete = false;
monitor.beginTask("", resourcesToDelete.length); //$NON-NLS-1$
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);
}
}
// Check to see if any problems occurred during processing.
final int exceptionCount = exceptions.size();
if (exceptionCount == 1) {
throw (CoreException) exceptions.get(0);
} else if (exceptionCount > 1) {
final MultiStatus multi = new MultiStatus(IDEWorkbenchPlugin.IDE_WORKBENCH, 0, IDEWorkbenchMessages.getString("DeleteResourceAction.deletionExceptionMessage"), new Exception()); //$NON-NLS-1$
for (int i = 0; i < exceptionCount; i++) {
CoreException exception = (CoreException) exceptions.get(0);
IStatus status = exception.getStatus();
multi.add(new Status(status.getSeverity(), status.getPlugin(), status.getCode(), status.getMessage(), exception));
}
throw new CoreException(multi);
}
// Signal that the job has completed successfully.
monitor.done();
}
/**
* Deletes the given resource.
*/
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 list of selected resources
*/
IResource[] getSelectedResourcesArray() {
List selection = getSelectedResources();
IResource[] resources = new IResource[selection.size()];
int i = 0;
for (Iterator e = selection.iterator(); e.hasNext();)
resources[i++] = (IResource) e.next();
return resources;
}
/**
* Returns a bit-mask containing the types of resources in the selection.
*/
int getSelectedResourceTypes() {
int types = 0;
for (Iterator i = getSelectedResources().iterator(); i.hasNext();) {
IResource r = (IResource) i.next();
types |= r.getType();
}
return types;
}
/* (non-Javadoc)
* Method declared on IAction.
*/
public void run() {
if (!confirmDelete())
return;
final IResource[] resourcesToDelete = getResourcesToDelete();
if (resourcesToDelete.length == 0)
return;
try {
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
protected void execute(IProgressMonitor monitor) throws CoreException {
delete(resourcesToDelete, monitor);
}
};
new ProgressMonitorDialog(shell).run(true, true, op);
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof CoreException) {
CoreException exception = (CoreException) t;
IStatus status = exception.getStatus();
IStatus[] children = status.getChildren();
boolean outOfSyncError = false;
for (int i = 0; i < children.length; i++) {
if (children[i].getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
outOfSyncError = true;
break;
}
}
IDEWorkbenchPlugin.log(MessageFormat.format("Exception in {0}.run: {1}", new Object[] {getClass().getName(), t}), status);//$NON-NLS-1$
if (outOfSyncError) {
ErrorDialog.openError(
shell,
IDEWorkbenchMessages.getString("DeleteResourceAction.errorTitle"), //$NON-NLS-1$
IDEWorkbenchMessages.getString("DeleteResourceAction.outOfSyncError"), //$NON-NLS-1$
status);
}
else {
ErrorDialog.openError(
shell,
IDEWorkbenchMessages.getString("DeleteResourceAction.errorTitle"), // no special message //$NON-NLS-1$
null, status);
}
}
else {
// CoreExceptions are collected above, but unexpected runtime exceptions and errors may still occur.
IDEWorkbenchPlugin.log(MessageFormat.format("Exception in {0}.run: {1}", new Object[] {getClass().getName(), t}));//$NON-NLS-1$
MessageDialog.openError(
shell,
IDEWorkbenchMessages.getString("DeleteResourceAction.messageTitle"), //$NON-NLS-1$
IDEWorkbenchMessages.format("DeleteResourceAction.internalError", new Object[] {t.getMessage()})); //$NON-NLS-1$
}
} catch (InterruptedException e) {
// just return
}
}
/**
* Returns the resources to delete based on the selection and their read-only status.
*
* @return the resources to delete
*/
IResource[] getResourcesToDelete() {
IResource[] selectedResources = getSelectedResourcesArray();
if (containsOnlyProjects() && !deleteContent) {
// We can just return the selection
return selectedResources;
}
ReadOnlyStateChecker checker =
new ReadOnlyStateChecker(
this.shell,
IDEWorkbenchMessages.getString("DeleteResourceAction.title1"), //$NON-NLS-1$
IDEWorkbenchMessages.getString("DeleteResourceAction.readOnlyQuestion")); //$NON-NLS-1$
return checker.checkReadOnlyResources(selectedResources);
}
/**
* 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();
}
/**
* 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.getString("DeleteResourceAction.messageTitle"), //$NON-NLS-1$
null,
IDEWorkbenchMessages.format("DeleteResourceAction.outOfSyncQuestion", new Object[] {resource.getName()}), //$NON-NLS-1$
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;
}
}