| /******************************************************************************* |
| * 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.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IProjectDescription; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| 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.SubProgressMonitor; |
| import org.eclipse.jface.dialogs.IInputValidator; |
| import org.eclipse.jface.dialogs.InputDialog; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| 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.internal.ide.IDEWorkbenchMessages; |
| import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; |
| import org.eclipse.ui.internal.ide.IIDEHelpContextIds; |
| |
| /** |
| * Standard action for renaming the selected resources. |
| * <p> |
| * This class may be instantiated; it is not intended to be subclassed. |
| * </p> |
| */ |
| 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 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 RENAMING_MESSAGE = IDEWorkbenchMessages.RenameResourceAction_progressMessage; |
| |
| /** |
| * 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 |
| */ |
| public RenameResourceAction(Shell shell) { |
| super(shell, IDEWorkbenchMessages.RenameResourceAction_text); |
| 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 |
| */ |
| public RenameResourceAction(Shell shell, Tree tree) { |
| this(shell); |
| 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() { |
| public void run() { |
| String pathName = destination.getFullPath().makeRelative() |
| .toString(); |
| result[0] = MessageDialog.openQuestion(shell, |
| RESOURCE_EXISTS_TITLE, MessageFormat.format( |
| RESOURCE_EXISTS_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 |
| if (currentResource.getResourceAttributes().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; |
| } |
| |
| /** |
| * On Mac the text widget already provides a border when it has focus, so there is no need to draw another one. |
| * The value of returned by this method is usd to control the inset we apply to the text field bound's in order to get space for drawing a border. |
| * A value of 1 means a one-pixel wide border around the text field. A negative value supresses the border. |
| * However, in M9 the system property "org.eclipse.swt.internal.carbon.noFocusRing" has been introduced |
| * as a temporary workaround for bug #28842. The existence of the property turns the native focus ring off |
| * if the widget is contained in a main window (not dialog). |
| * The check for the property should be removed after a final fix for #28842 has been provided. |
| */ |
| private static int getCellEditorInset(Control c) { |
| if ("carbon".equals(SWT.getPlatform())) { // special case for MacOS X //$NON-NLS-1$ |
| if (System |
| .getProperty("org.eclipse.swt.internal.carbon.noFocusRing") == null || c.getShell().getParent() != null) //$NON-NLS-1$ |
| return -2; // native border |
| } |
| 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) // only register for paint events if we have a border |
| textEditorParent.addListener(SWT.Paint, new Listener() { |
| 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() { |
| 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() { |
| 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() { |
| 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>) |
| */ |
| protected List getActionResources() { |
| if (inlinedResource == null) |
| return super.getActionResources(); |
| |
| List actionResources = new ArrayList(); |
| actionResources.add(inlinedResource); |
| return actionResources; |
| } |
| |
| /* (non-Javadoc) |
| * Method declared on WorkspaceAction. |
| */ |
| protected String getOperationMessage() { |
| return IDEWorkbenchMessages.RenameResourceAction_progress; |
| } |
| |
| /* (non-Javadoc) |
| * Method declared on WorkspaceAction. |
| */ |
| protected String getProblemsMessage() { |
| return IDEWorkbenchMessages.RenameResourceAction_problemMessage; |
| } |
| |
| /* (non-Javadoc) |
| * Method declared on WorkspaceAction. |
| */ |
| protected String getProblemsTitle() { |
| return IDEWorkbenchMessages.RenameResourceAction_problemTitle; |
| } |
| |
| /** |
| * Get the Tree being edited. |
| * @returnTree |
| */ |
| private Tree getTree() { |
| return this.navigatorTree; |
| } |
| |
| /* (non-Javadoc) |
| * Method declared on WorkspaceAction. |
| */ |
| protected void invokeOperation(IResource resource, IProgressMonitor monitor) |
| throws CoreException { |
| |
| monitor.beginTask(RENAMING_MESSAGE, 100); |
| IWorkspaceRoot workspaceRoot = resource.getWorkspace().getRoot(); |
| |
| IResource newResource = workspaceRoot.findMember(newPath); |
| if (newResource != null) { |
| if (checkOverwrite(getShell(), newResource)) { |
| if (resource.getType() == IResource.FILE |
| && newResource.getType() == IResource.FILE) { |
| IFile file = (IFile) resource; |
| IFile newFile = (IFile) newResource; |
| if (validateEdit(file, newFile, getShell())) { |
| IProgressMonitor subMonitor = new SubProgressMonitor( |
| monitor, 50); |
| newFile.setContents(file.getContents(), |
| IResource.KEEP_HISTORY, subMonitor); |
| file.delete(IResource.KEEP_HISTORY, subMonitor); |
| } |
| monitor.worked(100); |
| return; |
| } |
| newResource.delete(IResource.KEEP_HISTORY, |
| new SubProgressMonitor(monitor, 50)); |
| } else { |
| monitor.worked(100); |
| return; |
| } |
| } |
| if (resource.getType() == IResource.PROJECT) { |
| IProject project = (IProject) resource; |
| IProjectDescription description = project.getDescription(); |
| description.setName(newPath.segment(0)); |
| project.move(description, IResource.FORCE | IResource.SHALLOW, |
| monitor); |
| } else |
| resource.move(newPath, IResource.KEEP_HISTORY | IResource.SHALLOW, |
| new SubProgressMonitor(monitor, 50)); |
| } |
| |
| /** |
| * 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() { |
| 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); |
| dialog.open(); |
| return dialog.getValue(); |
| } |
| |
| /** |
| * 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. |
| */ |
| public void run() { |
| |
| if (this.navigatorTree == null) { |
| IResource currentResource = getCurrentResource(); |
| if (currentResource == null || !currentResource.exists()) |
| return; |
| //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() { |
| 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. |
| */ |
| 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; |
| } |
| |
| /** |
| * Validates the destination file if it is read-only and additionally |
| * the source file if both are read-only. |
| * Returns true if both files could be made writeable. |
| * |
| * @param source source file |
| * @param destination destination file |
| * @param shell ui context for the validation |
| * @return boolean <code>true</code> both files could be made writeable. |
| * <code>false</code> either one or both files were not made writeable |
| */ |
| boolean validateEdit(IFile source, IFile destination, Shell shell) { |
| if (destination.isReadOnly()) { |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| IStatus status; |
| if (source.isReadOnly()) |
| status = workspace.validateEdit(new IFile[] { source, |
| destination }, shell); |
| else |
| status = workspace.validateEdit(new IFile[] { destination }, |
| shell); |
| return status.isOK(); |
| } |
| return true; |
| } |
| |
| } |