| /******************************************************************************* |
| * 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; |
| } |
| } |