blob: 795f8d39154cc2c62bd4b2f54d2c0a078da1ce8a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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
* Benjamin Muskalla <b.muskalla@gmx.net>
* - Fix for bug 172574 - [IDE] DeleteProjectDialog inconsequent selection behavior
*******************************************************************************/
package org.eclipse.ui.actions;
import java.util.List;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
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.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.undo.DeleteResourcesOperation;
import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
import org.eclipse.ui.internal.ide.actions.LTKLauncher;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* 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());
// Add explanatory label that the action cannot be undone.
// We can't put multi-line formatted text in a radio button,
// so we have to create a separate label.
Label detailsLabel = new Label(composite, SWT.LEFT);
detailsLabel.setText(IDEWorkbenchMessages.DeleteResourceAction_deleteContentsDetails);
detailsLabel.setFont(parent.getFont());
// indent the explanatory label
GC gc = new GC(detailsLabel);
gc.setFont(detailsLabel.getParent().getFont());
FontMetrics fontMetrics = gc.getFontMetrics();
gc.dispose();
GridData data = new GridData();
data.horizontalIndent = Dialog.convertHorizontalDLUsToPixels(fontMetrics, IDialogConstants.INDENT);
detailsLabel.setLayoutData(data);
// add a listener so that clicking on the label selects the
// corresponding radio button.
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=172574
detailsLabel.addMouseListener(new MouseAdapter() {
public void mouseUp(MouseEvent e) {
deleteContent = true;
radio1.setSelection(deleteContent);
radio2.setSelection(!deleteContent);
}
});
// Add a spacer label
new Label(composite, SWT.LEFT);
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$
private IShellProvider shellProvider = null;
/**
* Whether or not we are deleting content for projects.
*/
private boolean deleteContent = 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;
private String[] modelProviderIds;
/**
* Creates a new delete resource action.
*
* @param shell
* the shell for any dialogs
* @deprecated Should take an IShellProvider, see
* {@link #DeleteResourceAction(IShellProvider)}
*/
public DeleteResourceAction(final Shell shell) {
super(IDEWorkbenchMessages.DeleteResourceAction_text);
Assert.isNotNull(shell);
initAction();
setShellProvider(new IShellProvider() {
public Shell getShell() {
return shell;
}
});
}
/**
* Creates a new delete resource action.
*
* @param provider
* the shell provider to use. Must not be <code>null</code>.
* @since 3.4
*/
public DeleteResourceAction(IShellProvider provider) {
super(IDEWorkbenchMessages.DeleteResourceAction_text);
Assert.isNotNull(provider);
initAction();
setShellProvider(provider);
}
/**
* Action initialization.
*/
private void initAction() {
setToolTipText(IDEWorkbenchMessages.DeleteResourceAction_toolTip);
PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
IIDEHelpContextIds.DELETE_RESOURCE_ACTION);
setId(ID);
}
private void setShellProvider(IShellProvider provider) {
shellProvider = provider;
}
/**
* 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;
}
/**
* 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(shellProvider.getShell(), 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(shellProvider.getShell(), resources);
dialog.setTestingMode(fTestingMode);
int code = dialog.open();
deleteContent = dialog.getDeleteContent();
return code == 0; // YES
}
/**
* 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() {
final IResource[] resources = getSelectedResourcesArray();
if (!fTestingMode) {
if (LTKLauncher.openDeleteWizard()) {
return;
}
}
// 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;
}
Job deletionCheckJob = new Job(IDEWorkbenchMessages.DeleteResourceAction_checkJobName) {
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
protected IStatus run(IProgressMonitor monitor) {
if (resources.length == 0)
return Status.CANCEL_STATUS;
scheduleDeleteJob(resources);
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);
}
};
deletionCheckJob.schedule();
}
/**
* Schedule a job to delete the resources to delete.
*
* @param resourcesToDelete
*/
private void scheduleDeleteJob(final IResource[] resourcesToDelete) {
// 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(final IProgressMonitor monitor) {
try {
final DeleteResourcesOperation op =
new DeleteResourcesOperation(resourcesToDelete, IDEWorkbenchMessages.DeleteResourceAction_operationLabel, deleteContent);
op.setModelProviderIds(getModelProviderIds());
// If we are deleting projects and their content, do not
// execute the operation in the undo history, since it cannot be
// properly restored. Just execute it directly so it won't be
// added to the undo history.
if (deleteContent && containsOnlyProjects(resourcesToDelete)) {
// We must compute the execution status first so that any user prompting
// or validation checking occurs. Do it in a syncExec because
// we are calling this from a Job.
WorkbenchJob statusJob = new WorkbenchJob("Status checking"){ //$NON-NLS-1$
/* (non-Javadoc)
* @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus runInUIThread(
IProgressMonitor monitor) {
return op.computeExecutionStatus(monitor);
}
};
statusJob.setSystem(true);
statusJob.schedule();
try {//block until the status is ready
statusJob.join();
} catch (InterruptedException e) {
//Do nothing as status will be a cancel
}
if (statusJob.getResult().isOK()) {
return op.execute(monitor, WorkspaceUndoUtil.getUIInfoAdapter(shellProvider.getShell()));
}
return statusJob.getResult();
}
return PlatformUI.getWorkbench().getOperationSupport()
.getOperationHistory().execute(op, monitor,
WorkspaceUndoUtil.getUIInfoAdapter(shellProvider.getShell()));
} catch (ExecutionException e) {
if (e.getCause() instanceof CoreException) {
return ((CoreException)e.getCause()).getStatus();
}
return new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, e.getMessage(),e);
}
}
/*
* (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.setUser(true);
deleteJob.schedule();
}
/**
* 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());
}
/**
* Returns the model provider ids that are known to the client that
* instantiated this operation.
*
* @return the model provider ids that are known to the client that
* instantiated this operation.
* @since 3.2
*/
public String[] getModelProviderIds() {
return modelProviderIds;
}
/**
* Sets the model provider ids that are known to the client that
* instantiated this operation. Any potential side effects reported by these
* models during validation will be ignored.
*
* @param modelProviderIds
* the model providers known to the client who is using this
* operation.
* @since 3.2
*/
public void setModelProviderIds(String[] modelProviderIds) {
this.modelProviderIds = modelProviderIds;
}
}