| /******************************************************************************* |
| * Copyright (c) 2000, 2008 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.commands.ExecutionException; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourceAttributes; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.dialogs.IInputValidator; |
| import org.eclipse.jface.dialogs.InputDialog; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.window.IShellProvider; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.TreeEditor; |
| import org.eclipse.swt.events.FocusAdapter; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeItem; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.ide.undo.MoveResourcesOperation; |
| 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 com.ibm.icu.text.MessageFormat; |
| |
| /** |
| * Standard action for renaming the selected resources. |
| * <p> |
| * This class may be instantiated; it is not intended to be subclassed. |
| * </p> |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class RenameResourceAction extends WorkspaceAction { |
| |
| /* |
| * The tree editing widgets. If treeEditor is null then edit using the |
| * dialog. We keep the editorText around so that we can close it if a new |
| * selection is made. |
| */ |
| private TreeEditor treeEditor; |
| |
| private Tree navigatorTree; |
| |
| private Text textEditor; |
| |
| private Composite textEditorParent; |
| |
| private TextActionHandler textActionHandler; |
| |
| // The resource being edited if this is being done inline |
| private IResource inlinedResource; |
| |
| private boolean saving = false; |
| |
| /** |
| * The id of this action. |
| */ |
| public static final String ID = PlatformUI.PLUGIN_ID |
| + ".RenameResourceAction";//$NON-NLS-1$ |
| |
| /** |
| * The new path. |
| */ |
| private IPath newPath; |
| |
| private String[] modelProviderIds; |
| |
| private static final String CHECK_RENAME_TITLE = IDEWorkbenchMessages.RenameResourceAction_checkTitle; |
| |
| private static final String CHECK_RENAME_MESSAGE = IDEWorkbenchMessages.RenameResourceAction_readOnlyCheck; |
| |
| private static String RESOURCE_EXISTS_TITLE = IDEWorkbenchMessages.RenameResourceAction_resourceExists; |
| |
| private static String RESOURCE_EXISTS_MESSAGE = IDEWorkbenchMessages.RenameResourceAction_overwriteQuestion; |
| |
| private static String PROJECT_EXISTS_MESSAGE = IDEWorkbenchMessages.RenameResourceAction_overwriteProjectQuestion; |
| |
| private static String PROJECT_EXISTS_TITLE = IDEWorkbenchMessages.RenameResourceAction_projectExists; |
| |
| /** |
| * Creates a new action. Using this constructor directly will rename using a |
| * dialog rather than the inline editor of a ResourceNavigator. |
| * |
| * @param shell |
| * the shell for any dialogs |
| * @deprecated see {@link #RenameResourceAction(IShellProvider)} |
| */ |
| @Deprecated |
| public RenameResourceAction(Shell shell) { |
| super(shell, IDEWorkbenchMessages.RenameResourceAction_text); |
| initAction(); |
| } |
| |
| /** |
| * Creates a new action. Using this constructor directly will rename using a |
| * dialog rather than the inline editor of a ResourceNavigator. |
| * |
| * @param provider |
| * the IShellProvider for any dialogs |
| * @since 3.4 |
| */ |
| public RenameResourceAction(IShellProvider provider){ |
| super(provider, IDEWorkbenchMessages.RenameResourceAction_text); |
| initAction(); |
| } |
| |
| private void initAction(){ |
| setToolTipText(IDEWorkbenchMessages.RenameResourceAction_toolTip); |
| setId(ID); |
| PlatformUI.getWorkbench().getHelpSystem().setHelp(this, |
| IIDEHelpContextIds.RENAME_RESOURCE_ACTION); |
| } |
| /** |
| * Creates a new action. |
| * |
| * @param shell |
| * the shell for any dialogs |
| * @param tree |
| * the tree |
| * @deprecated see {@link #RenameResourceAction(IShellProvider, Tree)} |
| */ |
| @Deprecated |
| public RenameResourceAction(Shell shell, Tree tree) { |
| this(shell); |
| this.navigatorTree = tree; |
| this.treeEditor = new TreeEditor(tree); |
| } |
| |
| /** |
| * Creates a new action. |
| * |
| * @param provider |
| * the shell provider for any dialogs |
| * @param tree |
| * the tree |
| * @since 3.4 |
| */ |
| public RenameResourceAction(IShellProvider provider, Tree tree) { |
| this(provider); |
| this.navigatorTree = tree; |
| this.treeEditor = new TreeEditor(tree); |
| } |
| |
| /** |
| * Check if the user wishes to overwrite the supplied resource |
| * |
| * @returns true if there is no collision or delete was successful |
| * @param shell |
| * the shell to create the dialog in |
| * @param destination - |
| * the resource to be overwritten |
| */ |
| private boolean checkOverwrite(final Shell shell, |
| final IResource destination) { |
| |
| final boolean[] result = new boolean[1]; |
| |
| // Run it inside of a runnable to make sure we get to parent off of the |
| // shell as we are not in the UI thread. |
| |
| Runnable query = new Runnable() { |
| @Override |
| public void run() { |
| String pathName = destination.getFullPath().makeRelative() |
| .toString(); |
| String message = RESOURCE_EXISTS_MESSAGE; |
| String title = RESOURCE_EXISTS_TITLE; |
| if (destination.getType() == IResource.PROJECT) { |
| message = PROJECT_EXISTS_MESSAGE; |
| title = PROJECT_EXISTS_TITLE; |
| } |
| result[0] = MessageDialog.openQuestion(shell, |
| title, MessageFormat.format(message, |
| new Object[] { pathName })); |
| } |
| |
| }; |
| |
| shell.getDisplay().syncExec(query); |
| return result[0]; |
| } |
| |
| /** |
| * Check if the supplied resource is read only or null. If it is then ask |
| * the user if they want to continue. Return true if the resource is not |
| * read only or if the user has given permission. |
| * |
| * @return boolean |
| */ |
| private boolean checkReadOnlyAndNull(IResource currentResource) { |
| // Do a quick read only and null check |
| if (currentResource == null) { |
| return false; |
| } |
| |
| // Do a quick read only check |
| final ResourceAttributes attributes = currentResource |
| .getResourceAttributes(); |
| if (attributes != null && attributes.isReadOnly()) { |
| return MessageDialog.openQuestion(getShell(), CHECK_RENAME_TITLE, |
| MessageFormat.format(CHECK_RENAME_MESSAGE, |
| new Object[] { currentResource.getName() })); |
| } |
| |
| return true; |
| } |
| |
| Composite createParent() { |
| Tree tree = getTree(); |
| Composite result = new Composite(tree, SWT.NONE); |
| TreeItem[] selectedItems = tree.getSelection(); |
| treeEditor.horizontalAlignment = SWT.LEFT; |
| treeEditor.grabHorizontal = true; |
| treeEditor.setEditor(result, selectedItems[0]); |
| return result; |
| } |
| |
| /** |
| * Get the inset used for cell editors |
| * @param c the Control |
| * @return int |
| */ |
| private static int getCellEditorInset(Control c) { |
| return 1; // one pixel wide black border |
| } |
| |
| /** |
| * Create the text editor widget. |
| * |
| * @param resource |
| * the resource to rename |
| */ |
| private void createTextEditor(final IResource resource) { |
| // Create text editor parent. This draws a nice bounding rect. |
| textEditorParent = createParent(); |
| textEditorParent.setVisible(false); |
| final int inset = getCellEditorInset(textEditorParent); |
| if (inset > 0) { |
| textEditorParent.addListener(SWT.Paint, new Listener() { |
| @Override |
| public void handleEvent(Event e) { |
| Point textSize = textEditor.getSize(); |
| Point parentSize = textEditorParent.getSize(); |
| e.gc.drawRectangle(0, 0, Math.min(textSize.x + 4, |
| parentSize.x - 1), parentSize.y - 1); |
| } |
| }); |
| } |
| // Create inner text editor. |
| textEditor = new Text(textEditorParent, SWT.NONE); |
| textEditor.setFont(navigatorTree.getFont()); |
| textEditorParent.setBackground(textEditor.getBackground()); |
| textEditor.addListener(SWT.Modify, new Listener() { |
| @Override |
| public void handleEvent(Event e) { |
| Point textSize = textEditor.computeSize(SWT.DEFAULT, |
| SWT.DEFAULT); |
| textSize.x += textSize.y; // Add extra space for new |
| // characters. |
| Point parentSize = textEditorParent.getSize(); |
| textEditor.setBounds(2, inset, Math.min(textSize.x, |
| parentSize.x - 4), parentSize.y - 2 * inset); |
| textEditorParent.redraw(); |
| } |
| }); |
| textEditor.addListener(SWT.Traverse, new Listener() { |
| @Override |
| public void handleEvent(Event event) { |
| |
| // Workaround for Bug 20214 due to extra |
| // traverse events |
| switch (event.detail) { |
| case SWT.TRAVERSE_ESCAPE: |
| // Do nothing in this case |
| disposeTextWidget(); |
| event.doit = true; |
| event.detail = SWT.TRAVERSE_NONE; |
| break; |
| case SWT.TRAVERSE_RETURN: |
| saveChangesAndDispose(resource); |
| event.doit = true; |
| event.detail = SWT.TRAVERSE_NONE; |
| break; |
| } |
| } |
| }); |
| textEditor.addFocusListener(new FocusAdapter() { |
| @Override |
| public void focusLost(FocusEvent fe) { |
| saveChangesAndDispose(resource); |
| } |
| }); |
| |
| if (textActionHandler != null) { |
| textActionHandler.addText(textEditor); |
| } |
| } |
| |
| /** |
| * Close the text widget and reset the editorText field. |
| */ |
| private void disposeTextWidget() { |
| if (textActionHandler != null) { |
| textActionHandler.removeText(textEditor); |
| } |
| |
| if (textEditorParent != null) { |
| textEditorParent.dispose(); |
| textEditorParent = null; |
| textEditor = null; |
| treeEditor.setEditor(null, null); |
| } |
| } |
| |
| /** |
| * Returns the elements that the action is to be performed on. Return the |
| * resource cached by the action as we cannot rely on the selection being |
| * correct for inlined text. |
| * |
| * @return list of resource elements (element type: <code>IResource</code>) |
| */ |
| @Override |
| protected List getActionResources() { |
| if (inlinedResource == null) { |
| return super.getActionResources(); |
| } |
| |
| List actionResources = new ArrayList(); |
| actionResources.add(inlinedResource); |
| return actionResources; |
| } |
| |
| /* |
| * (non-Javadoc) Method declared on WorkspaceAction. |
| */ |
| @Override |
| protected String getOperationMessage() { |
| return IDEWorkbenchMessages.RenameResourceAction_progress; |
| } |
| |
| /* |
| * (non-Javadoc) Method declared on WorkspaceAction. |
| */ |
| @Override |
| protected String getProblemsMessage() { |
| return IDEWorkbenchMessages.RenameResourceAction_problemMessage; |
| } |
| |
| /* |
| * (non-Javadoc) Method declared on WorkspaceAction. |
| */ |
| @Override |
| protected String getProblemsTitle() { |
| return IDEWorkbenchMessages.RenameResourceAction_problemTitle; |
| } |
| |
| /** |
| * Get the Tree being edited. |
| * |
| * @returnTree |
| */ |
| private Tree getTree() { |
| return this.navigatorTree; |
| } |
| |
| /** |
| * Return the new name to be given to the target resource. |
| * |
| * @return java.lang.String |
| * @param resource |
| * the resource to query status on |
| */ |
| protected String queryNewResourceName(final IResource resource) { |
| final IWorkspace workspace = IDEWorkbenchPlugin.getPluginWorkspace(); |
| final IPath prefix = resource.getFullPath().removeLastSegments(1); |
| IInputValidator validator = new IInputValidator() { |
| @Override |
| public String isValid(String string) { |
| if (resource.getName().equals(string)) { |
| return IDEWorkbenchMessages.RenameResourceAction_nameMustBeDifferent; |
| } |
| IStatus status = workspace.validateName(string, resource |
| .getType()); |
| if (!status.isOK()) { |
| return status.getMessage(); |
| } |
| if (workspace.getRoot().exists(prefix.append(string))) { |
| return IDEWorkbenchMessages.RenameResourceAction_nameExists; |
| } |
| return null; |
| } |
| }; |
| |
| InputDialog dialog = new InputDialog(getShell(), |
| IDEWorkbenchMessages.RenameResourceAction_inputDialogTitle, |
| IDEWorkbenchMessages.RenameResourceAction_inputDialogMessage, |
| resource.getName(), validator); |
| dialog.setBlockOnOpen(true); |
| int result = dialog.open(); |
| if (result == Window.OK) |
| return dialog.getValue(); |
| return null; |
| } |
| |
| /** |
| * Return the new name to be given to the target resource or |
| * <code>null<code> |
| * if the query was canceled. Rename the currently selected resource using the table editor. |
| * Continue the action when the user is done. |
| * |
| * @param resource the resource to rename |
| */ |
| private void queryNewResourceNameInline(final IResource resource) { |
| // Make sure text editor is created only once. Simply reset text |
| // editor when action is executed more than once. Fixes bug 22269. |
| if (textEditorParent == null) { |
| createTextEditor(resource); |
| } |
| textEditor.setText(resource.getName()); |
| |
| // Open text editor with initial size. |
| textEditorParent.setVisible(true); |
| Point textSize = textEditor.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| textSize.x += textSize.y; // Add extra space for new characters. |
| Point parentSize = textEditorParent.getSize(); |
| int inset = getCellEditorInset(textEditorParent); |
| textEditor.setBounds(2, inset, Math.min(textSize.x, parentSize.x - 4), |
| parentSize.y - 2 * inset); |
| textEditorParent.redraw(); |
| textEditor.selectAll(); |
| textEditor.setFocus(); |
| } |
| |
| /* |
| * (non-Javadoc) Method declared on IAction; overrides method on |
| * WorkspaceAction. |
| */ |
| @Override |
| public void run() { |
| IResource currentResource = getCurrentResource(); |
| if (currentResource == null || !currentResource.exists()) { |
| return; |
| } |
| if (LTKLauncher.openRenameWizard(getStructuredSelection())) { |
| return; |
| } |
| if (this.navigatorTree == null) { |
| // Do a quick read only and null check |
| if (!checkReadOnlyAndNull(currentResource)) { |
| return; |
| } |
| String newName = queryNewResourceName(currentResource); |
| if (newName == null || newName.equals("")) { //$NON-NLS-1$ |
| return; |
| } |
| newPath = currentResource.getFullPath().removeLastSegments(1) |
| .append(newName); |
| super.run(); |
| } else { |
| runWithInlineEditor(); |
| } |
| } |
| |
| /* |
| * Run the receiver using an inline editor from the supplied navigator. The |
| * navigator will tell the action when the path is ready to run. |
| */ |
| private void runWithInlineEditor() { |
| IResource currentResource = getCurrentResource(); |
| if (!checkReadOnlyAndNull(currentResource)) { |
| return; |
| } |
| queryNewResourceNameInline(currentResource); |
| } |
| |
| /** |
| * Return the currently selected resource. Only return an IResouce if there |
| * is one and only one resource selected. |
| * |
| * @return IResource or <code>null</code> if there is zero or more than |
| * one resources selected. |
| */ |
| private IResource getCurrentResource() { |
| List resources = getSelectedResources(); |
| if (resources.size() == 1) { |
| return (IResource) resources.get(0); |
| } |
| return null; |
| |
| } |
| |
| /** |
| * @param path |
| * the path |
| * @param resource |
| * the resource |
| */ |
| protected void runWithNewPath(IPath path, IResource resource) { |
| this.newPath = path; |
| super.run(); |
| } |
| |
| /** |
| * Save the changes and dispose of the text widget. |
| * |
| * @param resource - |
| * the resource to move. |
| */ |
| private void saveChangesAndDispose(IResource resource) { |
| if (saving == true) { |
| return; |
| } |
| |
| saving = true; |
| // Cache the resource to avoid selection loss since a selection of |
| // another item can trigger this method |
| inlinedResource = resource; |
| final String newName = textEditor.getText(); |
| // Run this in an async to make sure that the operation that triggered |
| // this action is completed. Otherwise this leads to problems when the |
| // icon of the item being renamed is clicked (i.e., which causes the |
| // rename |
| // text widget to lose focus and trigger this method). |
| Runnable query = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| if (!newName.equals(inlinedResource.getName())) { |
| IWorkspace workspace = IDEWorkbenchPlugin |
| .getPluginWorkspace(); |
| IStatus status = workspace.validateName(newName, |
| inlinedResource.getType()); |
| if (!status.isOK()) { |
| displayError(status.getMessage()); |
| } else { |
| IPath newPath = inlinedResource.getFullPath() |
| .removeLastSegments(1).append(newName); |
| runWithNewPath(newPath, inlinedResource); |
| } |
| } |
| inlinedResource = null; |
| // Dispose the text widget regardless |
| disposeTextWidget(); |
| // Ensure the Navigator tree has focus, which it may not if |
| // the |
| // text widget previously had focus. |
| if (navigatorTree != null && !navigatorTree.isDisposed()) { |
| navigatorTree.setFocus(); |
| } |
| } finally { |
| saving = false; |
| } |
| } |
| }; |
| getTree().getShell().getDisplay().asyncExec(query); |
| } |
| |
| /** |
| * The <code>RenameResourceAction</code> implementation of this |
| * <code>SelectionListenerAction</code> method ensures that this action is |
| * disabled if any of the selections are not resources or resources that are |
| * not local. |
| */ |
| @Override |
| protected boolean updateSelection(IStructuredSelection selection) { |
| disposeTextWidget(); |
| |
| if (selection.size() > 1) { |
| return false; |
| } |
| if (!super.updateSelection(selection)) { |
| return false; |
| } |
| |
| IResource currentResource = getCurrentResource(); |
| if (currentResource == null || !currentResource.exists()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Set the text action handler. |
| * |
| * @param actionHandler |
| * the action handler |
| */ |
| public void setTextActionHandler(TextActionHandler actionHandler) { |
| textActionHandler = actionHandler; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.actions.WorkspaceAction#createOperation(org.eclipse.core.runtime.IStatus[]) |
| * |
| * Overridden to create and execute an undoable operation that performs the |
| * rename. |
| * @since 3.3 |
| */ |
| @Override |
| protected IRunnableWithProgress createOperation(final IStatus[] errorStatus) { |
| return new IRunnableWithProgress() { |
| @Override |
| public void run(IProgressMonitor monitor) { |
| IResource[] resources = (IResource[]) getActionResources() |
| .toArray(new IResource[getActionResources().size()]); |
| // Rename is only valid for a single resource. This has already |
| // been validated. |
| if (resources.length == 1) { |
| // check for overwrite |
| IWorkspaceRoot workspaceRoot = resources[0].getWorkspace() |
| .getRoot(); |
| IResource newResource = workspaceRoot.findMember(newPath); |
| boolean go = true; |
| if (newResource != null) { |
| go = checkOverwrite(getShell(), newResource); |
| } |
| if (go) { |
| MoveResourcesOperation op = new MoveResourcesOperation( |
| resources[0], |
| newPath, |
| IDEWorkbenchMessages.RenameResourceAction_operationTitle); |
| op.setModelProviderIds(getModelProviderIds()); |
| try { |
| PlatformUI |
| .getWorkbench() |
| .getOperationSupport() |
| .getOperationHistory() |
| .execute( |
| op, |
| monitor, |
| WorkspaceUndoUtil |
| .getUIInfoAdapter(getShell())); |
| } catch (ExecutionException e) { |
| if (e.getCause() instanceof CoreException) { |
| errorStatus[0] = ((CoreException) e.getCause()) |
| .getStatus(); |
| } else { |
| errorStatus[0] = new Status(IStatus.ERROR, |
| PlatformUI.PLUGIN_ID, |
| getProblemsMessage(), e); |
| } |
| } |
| } |
| } |
| } |
| }; |
| } |
| } |