001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.actions;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.core.commands.ExecutionException;
016: import org.eclipse.core.resources.IResource;
017: import org.eclipse.core.resources.IWorkspace;
018: import org.eclipse.core.resources.IWorkspaceRoot;
019: import org.eclipse.core.resources.ResourceAttributes;
020: import org.eclipse.core.runtime.CoreException;
021: import org.eclipse.core.runtime.IPath;
022: import org.eclipse.core.runtime.IProgressMonitor;
023: import org.eclipse.core.runtime.IStatus;
024: import org.eclipse.core.runtime.Status;
025: import org.eclipse.jface.dialogs.IInputValidator;
026: import org.eclipse.jface.dialogs.InputDialog;
027: import org.eclipse.jface.dialogs.MessageDialog;
028: import org.eclipse.jface.operation.IRunnableWithProgress;
029: import org.eclipse.jface.viewers.IStructuredSelection;
030: import org.eclipse.jface.window.Window;
031: import org.eclipse.swt.SWT;
032: import org.eclipse.swt.custom.TreeEditor;
033: import org.eclipse.swt.events.FocusAdapter;
034: import org.eclipse.swt.events.FocusEvent;
035: import org.eclipse.swt.graphics.Point;
036: import org.eclipse.swt.widgets.Composite;
037: import org.eclipse.swt.widgets.Control;
038: import org.eclipse.swt.widgets.Event;
039: import org.eclipse.swt.widgets.Listener;
040: import org.eclipse.swt.widgets.Shell;
041: import org.eclipse.swt.widgets.Text;
042: import org.eclipse.swt.widgets.Tree;
043: import org.eclipse.swt.widgets.TreeItem;
044: import org.eclipse.ui.PlatformUI;
045: import org.eclipse.ui.ide.undo.MoveResourcesOperation;
046: import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
047: import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
048: import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
049: import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
050:
051: import com.ibm.icu.text.MessageFormat;
052:
053: /**
054: * Standard action for renaming the selected resources.
055: * <p>
056: * This class may be instantiated; it is not intended to be subclassed.
057: * </p>
058: */
059: public class RenameResourceAction extends WorkspaceAction {
060:
061: /*
062: * The tree editing widgets. If treeEditor is null then edit using the
063: * dialog. We keep the editorText around so that we can close it if a new
064: * selection is made.
065: */
066: private TreeEditor treeEditor;
067:
068: private Tree navigatorTree;
069:
070: private Text textEditor;
071:
072: private Composite textEditorParent;
073:
074: private TextActionHandler textActionHandler;
075:
076: // The resource being edited if this is being done inline
077: private IResource inlinedResource;
078:
079: private boolean saving = false;
080:
081: /**
082: * The id of this action.
083: */
084: public static final String ID = PlatformUI.PLUGIN_ID
085: + ".RenameResourceAction";//$NON-NLS-1$
086:
087: /**
088: * The new path.
089: */
090: private IPath newPath;
091:
092: private String[] modelProviderIds;
093:
094: private static final String CHECK_RENAME_TITLE = IDEWorkbenchMessages.RenameResourceAction_checkTitle;
095:
096: private static final String CHECK_RENAME_MESSAGE = IDEWorkbenchMessages.RenameResourceAction_readOnlyCheck;
097:
098: private static String RESOURCE_EXISTS_TITLE = IDEWorkbenchMessages.RenameResourceAction_resourceExists;
099:
100: private static String RESOURCE_EXISTS_MESSAGE = IDEWorkbenchMessages.RenameResourceAction_overwriteQuestion;
101:
102: private static String PROJECT_EXISTS_MESSAGE = IDEWorkbenchMessages.RenameResourceAction_overwriteProjectQuestion;
103:
104: private static String PROJECT_EXISTS_TITLE = IDEWorkbenchMessages.RenameResourceAction_projectExists;
105:
106: /**
107: * Creates a new action. Using this constructor directly will rename using a
108: * dialog rather than the inline editor of a ResourceNavigator.
109: *
110: * @param shell
111: * the shell for any dialogs
112: */
113: public RenameResourceAction(Shell shell) {
114: super (shell, IDEWorkbenchMessages.RenameResourceAction_text);
115: setToolTipText(IDEWorkbenchMessages.RenameResourceAction_toolTip);
116: setId(ID);
117: PlatformUI.getWorkbench().getHelpSystem().setHelp(this ,
118: IIDEHelpContextIds.RENAME_RESOURCE_ACTION);
119: }
120:
121: /**
122: * Creates a new action.
123: *
124: * @param shell
125: * the shell for any dialogs
126: * @param tree
127: * the tree
128: */
129: public RenameResourceAction(Shell shell, Tree tree) {
130: this (shell);
131: this .navigatorTree = tree;
132: this .treeEditor = new TreeEditor(tree);
133: }
134:
135: /**
136: * Check if the user wishes to overwrite the supplied resource
137: *
138: * @returns true if there is no collision or delete was successful
139: * @param shell
140: * the shell to create the dialog in
141: * @param destination -
142: * the resource to be overwritten
143: */
144: private boolean checkOverwrite(final Shell shell,
145: final IResource destination) {
146:
147: final boolean[] result = new boolean[1];
148:
149: // Run it inside of a runnable to make sure we get to parent off of the
150: // shell as we are not in the UI thread.
151:
152: Runnable query = new Runnable() {
153: public void run() {
154: String pathName = destination.getFullPath()
155: .makeRelative().toString();
156: String message = RESOURCE_EXISTS_MESSAGE;
157: String title = RESOURCE_EXISTS_TITLE;
158: if (destination.getType() == IResource.PROJECT) {
159: message = PROJECT_EXISTS_MESSAGE;
160: title = PROJECT_EXISTS_TITLE;
161: }
162: result[0] = MessageDialog.openQuestion(shell, title,
163: MessageFormat.format(message,
164: new Object[] { pathName }));
165: }
166:
167: };
168:
169: shell.getDisplay().syncExec(query);
170: return result[0];
171: }
172:
173: /**
174: * Check if the supplied resource is read only or null. If it is then ask
175: * the user if they want to continue. Return true if the resource is not
176: * read only or if the user has given permission.
177: *
178: * @return boolean
179: */
180: private boolean checkReadOnlyAndNull(IResource currentResource) {
181: // Do a quick read only and null check
182: if (currentResource == null) {
183: return false;
184: }
185:
186: // Do a quick read only check
187: final ResourceAttributes attributes = currentResource
188: .getResourceAttributes();
189: if (attributes != null && attributes.isReadOnly()) {
190: return MessageDialog
191: .openQuestion(getShell(), CHECK_RENAME_TITLE,
192: MessageFormat.format(CHECK_RENAME_MESSAGE,
193: new Object[] { currentResource
194: .getName() }));
195: }
196:
197: return true;
198: }
199:
200: Composite createParent() {
201: Tree tree = getTree();
202: Composite result = new Composite(tree, SWT.NONE);
203: TreeItem[] selectedItems = tree.getSelection();
204: treeEditor.horizontalAlignment = SWT.LEFT;
205: treeEditor.grabHorizontal = true;
206: treeEditor.setEditor(result, selectedItems[0]);
207: return result;
208: }
209:
210: /**
211: * Get the inset used for cell editors
212: * @param c the Control
213: * @return int
214: */
215: private static int getCellEditorInset(Control c) {
216: return 1; // one pixel wide black border
217: }
218:
219: /**
220: * Create the text editor widget.
221: *
222: * @param resource
223: * the resource to rename
224: */
225: private void createTextEditor(final IResource resource) {
226: // Create text editor parent. This draws a nice bounding rect.
227: textEditorParent = createParent();
228: textEditorParent.setVisible(false);
229: final int inset = getCellEditorInset(textEditorParent);
230: if (inset > 0) {
231: textEditorParent.addListener(SWT.Paint, new Listener() {
232: public void handleEvent(Event e) {
233: Point textSize = textEditor.getSize();
234: Point parentSize = textEditorParent.getSize();
235: e.gc.drawRectangle(0, 0, Math.min(textSize.x + 4,
236: parentSize.x - 1), parentSize.y - 1);
237: }
238: });
239: }
240: // Create inner text editor.
241: textEditor = new Text(textEditorParent, SWT.NONE);
242: textEditor.setFont(navigatorTree.getFont());
243: textEditorParent.setBackground(textEditor.getBackground());
244: textEditor.addListener(SWT.Modify, new Listener() {
245: public void handleEvent(Event e) {
246: Point textSize = textEditor.computeSize(SWT.DEFAULT,
247: SWT.DEFAULT);
248: textSize.x += textSize.y; // Add extra space for new
249: // characters.
250: Point parentSize = textEditorParent.getSize();
251: textEditor.setBounds(2, inset, Math.min(textSize.x,
252: parentSize.x - 4), parentSize.y - 2 * inset);
253: textEditorParent.redraw();
254: }
255: });
256: textEditor.addListener(SWT.Traverse, new Listener() {
257: public void handleEvent(Event event) {
258:
259: // Workaround for Bug 20214 due to extra
260: // traverse events
261: switch (event.detail) {
262: case SWT.TRAVERSE_ESCAPE:
263: // Do nothing in this case
264: disposeTextWidget();
265: event.doit = true;
266: event.detail = SWT.TRAVERSE_NONE;
267: break;
268: case SWT.TRAVERSE_RETURN:
269: saveChangesAndDispose(resource);
270: event.doit = true;
271: event.detail = SWT.TRAVERSE_NONE;
272: break;
273: }
274: }
275: });
276: textEditor.addFocusListener(new FocusAdapter() {
277: public void focusLost(FocusEvent fe) {
278: saveChangesAndDispose(resource);
279: }
280: });
281:
282: if (textActionHandler != null) {
283: textActionHandler.addText(textEditor);
284: }
285: }
286:
287: /**
288: * Close the text widget and reset the editorText field.
289: */
290: private void disposeTextWidget() {
291: if (textActionHandler != null) {
292: textActionHandler.removeText(textEditor);
293: }
294:
295: if (textEditorParent != null) {
296: textEditorParent.dispose();
297: textEditorParent = null;
298: textEditor = null;
299: treeEditor.setEditor(null, null);
300: }
301: }
302:
303: /**
304: * Returns the elements that the action is to be performed on. Return the
305: * resource cached by the action as we cannot rely on the selection being
306: * correct for inlined text.
307: *
308: * @return list of resource elements (element type: <code>IResource</code>)
309: */
310: protected List getActionResources() {
311: if (inlinedResource == null) {
312: return super .getActionResources();
313: }
314:
315: List actionResources = new ArrayList();
316: actionResources.add(inlinedResource);
317: return actionResources;
318: }
319:
320: /*
321: * (non-Javadoc) Method declared on WorkspaceAction.
322: */
323: protected String getOperationMessage() {
324: return IDEWorkbenchMessages.RenameResourceAction_progress;
325: }
326:
327: /*
328: * (non-Javadoc) Method declared on WorkspaceAction.
329: */
330: protected String getProblemsMessage() {
331: return IDEWorkbenchMessages.RenameResourceAction_problemMessage;
332: }
333:
334: /*
335: * (non-Javadoc) Method declared on WorkspaceAction.
336: */
337: protected String getProblemsTitle() {
338: return IDEWorkbenchMessages.RenameResourceAction_problemTitle;
339: }
340:
341: /**
342: * Get the Tree being edited.
343: *
344: * @returnTree
345: */
346: private Tree getTree() {
347: return this .navigatorTree;
348: }
349:
350: /*
351: * (non-Javadoc) Method declared on WorkspaceAction. Since 3.3, this method
352: * is not used, but an implementation is still provided for compatibility.
353: * All work is now done in the operation created in
354: * createOperation(IStatus[]).
355: */
356: protected void invokeOperation(IResource resource,
357: IProgressMonitor monitor) {
358: }
359:
360: /**
361: * Return the new name to be given to the target resource.
362: *
363: * @return java.lang.String
364: * @param resource
365: * the resource to query status on
366: */
367: protected String queryNewResourceName(final IResource resource) {
368: final IWorkspace workspace = IDEWorkbenchPlugin
369: .getPluginWorkspace();
370: final IPath prefix = resource.getFullPath().removeLastSegments(
371: 1);
372: IInputValidator validator = new IInputValidator() {
373: public String isValid(String string) {
374: if (resource.getName().equals(string)) {
375: return IDEWorkbenchMessages.RenameResourceAction_nameMustBeDifferent;
376: }
377: IStatus status = workspace.validateName(string,
378: resource.getType());
379: if (!status.isOK()) {
380: return status.getMessage();
381: }
382: if (workspace.getRoot().exists(prefix.append(string))) {
383: return IDEWorkbenchMessages.RenameResourceAction_nameExists;
384: }
385: return null;
386: }
387: };
388:
389: InputDialog dialog = new InputDialog(
390: getShell(),
391: IDEWorkbenchMessages.RenameResourceAction_inputDialogTitle,
392: IDEWorkbenchMessages.RenameResourceAction_inputDialogMessage,
393: resource.getName(), validator);
394: dialog.setBlockOnOpen(true);
395: int result = dialog.open();
396: if (result == Window.OK)
397: return dialog.getValue();
398: return null;
399: }
400:
401: /**
402: * Return the new name to be given to the target resource or
403: * <code>null<code>
404: * if the query was canceled. Rename the currently selected resource using the table editor.
405: * Continue the action when the user is done.
406: *
407: * @param resource the resource to rename
408: */
409: private void queryNewResourceNameInline(final IResource resource) {
410: // Make sure text editor is created only once. Simply reset text
411: // editor when action is executed more than once. Fixes bug 22269.
412: if (textEditorParent == null) {
413: createTextEditor(resource);
414: }
415: textEditor.setText(resource.getName());
416:
417: // Open text editor with initial size.
418: textEditorParent.setVisible(true);
419: Point textSize = textEditor.computeSize(SWT.DEFAULT,
420: SWT.DEFAULT);
421: textSize.x += textSize.y; // Add extra space for new characters.
422: Point parentSize = textEditorParent.getSize();
423: int inset = getCellEditorInset(textEditorParent);
424: textEditor.setBounds(2, inset, Math.min(textSize.x,
425: parentSize.x - 4), parentSize.y - 2 * inset);
426: textEditorParent.redraw();
427: textEditor.selectAll();
428: textEditor.setFocus();
429: }
430:
431: /*
432: * (non-Javadoc) Method declared on IAction; overrides method on
433: * WorkspaceAction.
434: */
435: public void run() {
436:
437: if (this .navigatorTree == null) {
438: IResource currentResource = getCurrentResource();
439: if (currentResource == null || !currentResource.exists()) {
440: return;
441: }
442: // Do a quick read only and null check
443: if (!checkReadOnlyAndNull(currentResource)) {
444: return;
445: }
446: String newName = queryNewResourceName(currentResource);
447: if (newName == null || newName.equals("")) { //$NON-NLS-1$
448: return;
449: }
450: newPath = currentResource.getFullPath().removeLastSegments(
451: 1).append(newName);
452: super .run();
453: } else {
454: runWithInlineEditor();
455: }
456: }
457:
458: /*
459: * Run the receiver using an inline editor from the supplied navigator. The
460: * navigator will tell the action when the path is ready to run.
461: */
462: private void runWithInlineEditor() {
463: IResource currentResource = getCurrentResource();
464: if (!checkReadOnlyAndNull(currentResource)) {
465: return;
466: }
467: queryNewResourceNameInline(currentResource);
468: }
469:
470: /**
471: * Return the currently selected resource. Only return an IResouce if there
472: * is one and only one resource selected.
473: *
474: * @return IResource or <code>null</code> if there is zero or more than
475: * one resources selected.
476: */
477: private IResource getCurrentResource() {
478: List resources = getSelectedResources();
479: if (resources.size() == 1) {
480: return (IResource) resources.get(0);
481: }
482: return null;
483:
484: }
485:
486: /**
487: * @param path
488: * the path
489: * @param resource
490: * the resource
491: */
492: protected void runWithNewPath(IPath path, IResource resource) {
493: this .newPath = path;
494: super .run();
495: }
496:
497: /**
498: * Save the changes and dispose of the text widget.
499: *
500: * @param resource -
501: * the resource to move.
502: */
503: private void saveChangesAndDispose(IResource resource) {
504: if (saving == true) {
505: return;
506: }
507:
508: saving = true;
509: // Cache the resource to avoid selection loss since a selection of
510: // another item can trigger this method
511: inlinedResource = resource;
512: final String newName = textEditor.getText();
513: // Run this in an async to make sure that the operation that triggered
514: // this action is completed. Otherwise this leads to problems when the
515: // icon of the item being renamed is clicked (i.e., which causes the
516: // rename
517: // text widget to lose focus and trigger this method).
518: Runnable query = new Runnable() {
519: public void run() {
520: try {
521: if (!newName.equals(inlinedResource.getName())) {
522: IWorkspace workspace = IDEWorkbenchPlugin
523: .getPluginWorkspace();
524: IStatus status = workspace.validateName(
525: newName, inlinedResource.getType());
526: if (!status.isOK()) {
527: displayError(status.getMessage());
528: } else {
529: IPath newPath = inlinedResource
530: .getFullPath()
531: .removeLastSegments(1).append(
532: newName);
533: runWithNewPath(newPath, inlinedResource);
534: }
535: }
536: inlinedResource = null;
537: // Dispose the text widget regardless
538: disposeTextWidget();
539: // Ensure the Navigator tree has focus, which it may not if
540: // the
541: // text widget previously had focus.
542: if (navigatorTree != null
543: && !navigatorTree.isDisposed()) {
544: navigatorTree.setFocus();
545: }
546: } finally {
547: saving = false;
548: }
549: }
550: };
551: getTree().getShell().getDisplay().asyncExec(query);
552: }
553:
554: /**
555: * The <code>RenameResourceAction</code> implementation of this
556: * <code>SelectionListenerAction</code> method ensures that this action is
557: * disabled if any of the selections are not resources or resources that are
558: * not local.
559: */
560: protected boolean updateSelection(IStructuredSelection selection) {
561: disposeTextWidget();
562:
563: if (selection.size() > 1) {
564: return false;
565: }
566: if (!super .updateSelection(selection)) {
567: return false;
568: }
569:
570: IResource currentResource = getCurrentResource();
571: if (currentResource == null || !currentResource.exists()) {
572: return false;
573: }
574:
575: return true;
576: }
577:
578: /**
579: * Set the text action handler.
580: *
581: * @param actionHandler
582: * the action handler
583: */
584: public void setTextActionHandler(TextActionHandler actionHandler) {
585: textActionHandler = actionHandler;
586: }
587:
588: /**
589: * Returns the model provider ids that are known to the client that
590: * instantiated this operation.
591: *
592: * @return the model provider ids that are known to the client that
593: * instantiated this operation.
594: * @since 3.2
595: */
596: public String[] getModelProviderIds() {
597: return modelProviderIds;
598: }
599:
600: /**
601: * Sets the model provider ids that are known to the client that
602: * instantiated this operation. Any potential side effects reported by these
603: * models during validation will be ignored.
604: *
605: * @param modelProviderIds
606: * the model providers known to the client who is using this
607: * operation.
608: * @since 3.2
609: */
610: public void setModelProviderIds(String[] modelProviderIds) {
611: this .modelProviderIds = modelProviderIds;
612: }
613:
614: /*
615: * (non-Javadoc)
616: *
617: * @see org.eclipse.ui.actions.WorkspaceAction#createOperation(org.eclipse.core.runtime.IStatus[])
618: *
619: * Overridden to create and execute an undoable operation that performs the
620: * rename.
621: * @since 3.3
622: */
623: protected IRunnableWithProgress createOperation(
624: final IStatus[] errorStatus) {
625: return new IRunnableWithProgress() {
626: public void run(IProgressMonitor monitor) {
627: IResource[] resources = (IResource[]) getActionResources()
628: .toArray(
629: new IResource[getActionResources()
630: .size()]);
631: // Rename is only valid for a single resource. This has already
632: // been validated.
633: if (resources.length == 1) {
634: // check for overwrite
635: IWorkspaceRoot workspaceRoot = resources[0]
636: .getWorkspace().getRoot();
637: IResource newResource = workspaceRoot
638: .findMember(newPath);
639: boolean go = true;
640: if (newResource != null) {
641: go = checkOverwrite(getShell(), newResource);
642: }
643: if (go) {
644: MoveResourcesOperation op = new MoveResourcesOperation(
645: resources[0],
646: newPath,
647: IDEWorkbenchMessages.RenameResourceAction_operationTitle);
648: op.setModelProviderIds(getModelProviderIds());
649: try {
650: PlatformUI
651: .getWorkbench()
652: .getOperationSupport()
653: .getOperationHistory()
654: .execute(
655: op,
656: monitor,
657: WorkspaceUndoUtil
658: .getUIInfoAdapter(getShell()));
659: } catch (ExecutionException e) {
660: if (e.getCause() instanceof CoreException) {
661: errorStatus[0] = ((CoreException) e
662: .getCause()).getStatus();
663: } else {
664: errorStatus[0] = new Status(
665: IStatus.ERROR,
666: PlatformUI.PLUGIN_ID,
667: getProblemsMessage(), e);
668: }
669: }
670: }
671: }
672: }
673: };
674: }
675: }
|