001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 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.dialogs;
011:
012: import java.io.ByteArrayInputStream;
013: import java.io.InputStream;
014: import java.lang.reflect.InvocationTargetException;
015: import java.net.URI;
016: import java.util.Iterator;
017: import org.eclipse.core.commands.ExecutionException;
018: import org.eclipse.core.resources.IFile;
019: import org.eclipse.core.resources.IFolder;
020: import org.eclipse.core.resources.IResource;
021: import org.eclipse.core.resources.IResourceStatus;
022: import org.eclipse.core.resources.IWorkspace;
023: import org.eclipse.core.resources.IWorkspaceRoot;
024: import org.eclipse.core.resources.ResourcesPlugin;
025: import org.eclipse.core.runtime.CoreException;
026: import org.eclipse.core.runtime.IAdaptable;
027: import org.eclipse.core.runtime.IPath;
028: import org.eclipse.core.runtime.IProgressMonitor;
029: import org.eclipse.core.runtime.IStatus;
030: import org.eclipse.core.runtime.OperationCanceledException;
031: import org.eclipse.core.runtime.Preferences;
032: import org.eclipse.core.runtime.jobs.ISchedulingRule;
033: import org.eclipse.jface.dialogs.ErrorDialog;
034: import org.eclipse.jface.dialogs.MessageDialog;
035: import org.eclipse.jface.operation.IRunnableWithProgress;
036: import org.eclipse.jface.viewers.IStructuredSelection;
037: import org.eclipse.jface.wizard.WizardPage;
038: import org.eclipse.osgi.util.NLS;
039: import org.eclipse.swt.SWT;
040: import org.eclipse.swt.events.SelectionAdapter;
041: import org.eclipse.swt.events.SelectionEvent;
042: import org.eclipse.swt.graphics.Point;
043: import org.eclipse.swt.layout.GridData;
044: import org.eclipse.swt.layout.GridLayout;
045: import org.eclipse.swt.widgets.Button;
046: import org.eclipse.swt.widgets.Composite;
047: import org.eclipse.swt.widgets.Event;
048: import org.eclipse.swt.widgets.Listener;
049: import org.eclipse.swt.widgets.Shell;
050: import org.eclipse.ui.PlatformUI;
051: import org.eclipse.ui.ide.undo.CreateFileOperation;
052: import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
053: import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
054: import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
055: import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
056: import org.eclipse.ui.internal.ide.dialogs.CreateLinkedResourceGroup;
057: import org.eclipse.ui.internal.ide.misc.ResourceAndContainerGroup;
058:
059: /**
060: * Standard main page for a wizard that creates a file resource.
061: * <p>
062: * This page may be used by clients as-is; it may be also be subclassed to suit.
063: * </p>
064: * <p>
065: * Subclasses may override
066: * <ul>
067: * <li><code>getInitialContents</code></li>
068: * <li><code>getNewFileLabel</code></li>
069: * </ul>
070: * </p>
071: * <p>
072: * Subclasses may extend
073: * <ul>
074: * <li><code>handleEvent</code></li>
075: * </ul>
076: * </p>
077: */
078: public class WizardNewFileCreationPage extends WizardPage implements
079: Listener {
080: private static final int SIZING_CONTAINER_GROUP_HEIGHT = 250;
081:
082: // the current resource selection
083: private IStructuredSelection currentSelection;
084:
085: // cache of newly-created file
086: private IFile newFile;
087:
088: private URI linkTargetPath;
089:
090: // widgets
091: private ResourceAndContainerGroup resourceGroup;
092:
093: private Button advancedButton;
094:
095: private CreateLinkedResourceGroup linkedResourceGroup;
096:
097: private Composite linkedResourceParent;
098:
099: private Composite linkedResourceComposite;
100:
101: // initial value stores
102: private String initialFileName;
103:
104: /**
105: * The file extension to use for this page's file name field when
106: * it does not exist yet.
107: * @see WizardNewFileCreationPage#setFileExtension(String)
108: * @since 3.3
109: */
110: private String initialFileExtension;
111:
112: private IPath initialContainerFullPath;
113:
114: /**
115: * Height of the "advanced" linked resource group. Set when the advanced
116: * group is first made visible.
117: */
118: private int linkedResourceGroupHeight = -1;
119:
120: /**
121: * First time the advanced group is validated.
122: */
123: private boolean firstLinkCheck = true;
124:
125: /**
126: * Creates a new file creation wizard page. If the initial resource
127: * selection contains exactly one container resource then it will be used as
128: * the default container resource.
129: *
130: * @param pageName
131: * the name of the page
132: * @param selection
133: * the current resource selection
134: */
135: public WizardNewFileCreationPage(String pageName,
136: IStructuredSelection selection) {
137: super (pageName);
138: setPageComplete(false);
139: this .currentSelection = selection;
140: }
141:
142: /**
143: * Creates the widget for advanced options.
144: *
145: * @param parent
146: * the parent composite
147: */
148: protected void createAdvancedControls(Composite parent) {
149: Preferences preferences = ResourcesPlugin.getPlugin()
150: .getPluginPreferences();
151:
152: if (preferences
153: .getBoolean(ResourcesPlugin.PREF_DISABLE_LINKING) == false) {
154: linkedResourceParent = new Composite(parent, SWT.NONE);
155: linkedResourceParent.setFont(parent.getFont());
156: linkedResourceParent.setLayoutData(new GridData(
157: GridData.FILL_HORIZONTAL));
158: GridLayout layout = new GridLayout();
159: layout.marginHeight = 0;
160: layout.marginWidth = 0;
161: linkedResourceParent.setLayout(layout);
162:
163: advancedButton = new Button(linkedResourceParent, SWT.PUSH);
164: advancedButton.setFont(linkedResourceParent.getFont());
165: advancedButton.setText(IDEWorkbenchMessages.showAdvanced);
166: GridData data = setButtonLayoutData(advancedButton);
167: data.horizontalAlignment = GridData.BEGINNING;
168: advancedButton.setLayoutData(data);
169: advancedButton.addSelectionListener(new SelectionAdapter() {
170: public void widgetSelected(SelectionEvent e) {
171: handleAdvancedButtonSelect();
172: }
173: });
174: }
175: linkedResourceGroup = new CreateLinkedResourceGroup(
176: IResource.FILE, new Listener() {
177: public void handleEvent(Event e) {
178: setPageComplete(validatePage());
179: firstLinkCheck = false;
180: }
181: }, new CreateLinkedResourceGroup.IStringValue() {
182: public void setValue(String string) {
183: resourceGroup.setResource(string);
184: }
185:
186: public String getValue() {
187: return resourceGroup.getResource();
188: }
189: });
190: }
191:
192: /**
193: * (non-Javadoc) Method declared on IDialogPage.
194: */
195: public void createControl(Composite parent) {
196: initializeDialogUnits(parent);
197: // top level group
198: Composite topLevel = new Composite(parent, SWT.NONE);
199: topLevel.setLayout(new GridLayout());
200: topLevel.setLayoutData(new GridData(
201: GridData.VERTICAL_ALIGN_FILL
202: | GridData.HORIZONTAL_ALIGN_FILL));
203: topLevel.setFont(parent.getFont());
204: PlatformUI.getWorkbench().getHelpSystem().setHelp(topLevel,
205: IIDEHelpContextIds.NEW_FILE_WIZARD_PAGE);
206:
207: // resource and container group
208: resourceGroup = new ResourceAndContainerGroup(topLevel, this ,
209: getNewFileLabel(),
210: IDEWorkbenchMessages.WizardNewFileCreationPage_file,
211: false, SIZING_CONTAINER_GROUP_HEIGHT);
212: resourceGroup.setAllowExistingResources(false);
213: initialPopulateContainerNameField();
214: createAdvancedControls(topLevel);
215: if (initialFileName != null) {
216: resourceGroup.setResource(initialFileName);
217: }
218: if (initialFileExtension != null) {
219: resourceGroup.setResourceExtension(initialFileExtension);
220: }
221: validatePage();
222: // Show description on opening
223: setErrorMessage(null);
224: setMessage(null);
225: setControl(topLevel);
226: }
227:
228: /**
229: * Creates a file resource given the file handle and contents.
230: *
231: * @param fileHandle
232: * the file handle to create a file resource with
233: * @param contents
234: * the initial contents of the new file resource, or
235: * <code>null</code> if none (equivalent to an empty stream)
236: * @param monitor
237: * the progress monitor to show visual progress with
238: * @exception CoreException
239: * if the operation fails
240: * @exception OperationCanceledException
241: * if the operation is canceled
242: *
243: * @deprecated As of 3.3, use or override {@link #createNewFile()} which
244: * uses the undoable operation support. To supply customized
245: * file content for a subclass, use
246: * {@link #getInitialContents()}.
247: */
248: protected void createFile(IFile fileHandle, InputStream contents,
249: IProgressMonitor monitor) throws CoreException {
250: if (contents == null) {
251: contents = new ByteArrayInputStream(new byte[0]);
252: }
253:
254: try {
255: // Create a new file resource in the workspace
256: if (linkTargetPath != null) {
257: fileHandle.createLink(linkTargetPath,
258: IResource.ALLOW_MISSING_LOCAL, monitor);
259: } else {
260: IPath path = fileHandle.getFullPath();
261: IWorkspaceRoot root = ResourcesPlugin.getWorkspace()
262: .getRoot();
263: int numSegments = path.segmentCount();
264: if (numSegments > 2
265: && !root.getFolder(path.removeLastSegments(1))
266: .exists()) {
267: // If the direct parent of the path doesn't exist, try to
268: // create the
269: // necessary directories.
270: for (int i = numSegments - 2; i > 0; i--) {
271: IFolder folder = root.getFolder(path
272: .removeLastSegments(i));
273: if (!folder.exists()) {
274: folder.create(false, true, monitor);
275: }
276: }
277: }
278: fileHandle.create(contents, false, monitor);
279: }
280: } catch (CoreException e) {
281: // If the file already existed locally, just refresh to get contents
282: if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) {
283: fileHandle.refreshLocal(IResource.DEPTH_ZERO, null);
284: } else {
285: throw e;
286: }
287: }
288:
289: if (monitor.isCanceled()) {
290: throw new OperationCanceledException();
291: }
292: }
293:
294: /**
295: * Creates a file resource handle for the file with the given workspace
296: * path. This method does not create the file resource; this is the
297: * responsibility of <code>createFile</code>.
298: *
299: * @param filePath
300: * the path of the file resource to create a handle for
301: * @return the new file resource handle
302: * @see #createFile
303: */
304: protected IFile createFileHandle(IPath filePath) {
305: return IDEWorkbenchPlugin.getPluginWorkspace().getRoot()
306: .getFile(filePath);
307: }
308:
309: /**
310: * Creates the link target path if a link target has been specified.
311: */
312: protected void createLinkTarget() {
313: linkTargetPath = linkedResourceGroup.getLinkTargetURI();
314: }
315:
316: /**
317: * Creates a new file resource in the selected container and with the
318: * selected name. Creates any missing resource containers along the path;
319: * does nothing if the container resources already exist.
320: * <p>
321: * In normal usage, this method is invoked after the user has pressed Finish
322: * on the wizard; the enablement of the Finish button implies that all
323: * controls on on this page currently contain valid values.
324: * </p>
325: * <p>
326: * Note that this page caches the new file once it has been successfully
327: * created; subsequent invocations of this method will answer the same file
328: * resource without attempting to create it again.
329: * </p>
330: * <p>
331: * This method should be called within a workspace modify operation since it
332: * creates resources.
333: * </p>
334: *
335: * @return the created file resource, or <code>null</code> if the file was
336: * not created
337: */
338: public IFile createNewFile() {
339: if (newFile != null) {
340: return newFile;
341: }
342:
343: // create the new file and cache it if successful
344:
345: final IPath containerPath = resourceGroup
346: .getContainerFullPath();
347: IPath newFilePath = containerPath.append(resourceGroup
348: .getResource());
349: final IFile newFileHandle = createFileHandle(newFilePath);
350: final InputStream initialContents = getInitialContents();
351:
352: createLinkTarget();
353: IRunnableWithProgress op = new IRunnableWithProgress() {
354: public void run(IProgressMonitor monitor) {
355: CreateFileOperation op = new CreateFileOperation(
356: newFileHandle,
357: linkTargetPath,
358: initialContents,
359: IDEWorkbenchMessages.WizardNewFileCreationPage_title);
360: try {
361: PlatformUI
362: .getWorkbench()
363: .getOperationSupport()
364: .getOperationHistory()
365: .execute(
366: op,
367: monitor,
368: WorkspaceUndoUtil
369: .getUIInfoAdapter(getShell()));
370: } catch (final ExecutionException e) {
371: getContainer().getShell().getDisplay().syncExec(
372: new Runnable() {
373: public void run() {
374: if (e.getCause() instanceof CoreException) {
375: ErrorDialog
376: .openError(
377: getContainer()
378: .getShell(), // Was
379: // Utilities.getFocusShell()
380: IDEWorkbenchMessages.WizardNewFileCreationPage_errorTitle,
381: null, // no special
382: // message
383: ((CoreException) e
384: .getCause())
385: .getStatus());
386: } else {
387: IDEWorkbenchPlugin
388: .log(
389: getClass(),
390: "createNewFile()", e.getCause()); //$NON-NLS-1$
391: MessageDialog
392: .openError(
393: getContainer()
394: .getShell(),
395: IDEWorkbenchMessages.WizardNewFileCreationPage_internalErrorTitle,
396: NLS
397: .bind(
398: IDEWorkbenchMessages.WizardNewFileCreationPage_internalErrorMessage,
399: e
400: .getCause()
401: .getMessage()));
402: }
403: }
404: });
405: }
406: }
407: };
408: try {
409: getContainer().run(true, true, op);
410: } catch (InterruptedException e) {
411: return null;
412: } catch (InvocationTargetException e) {
413: // Execution Exceptions are handled above but we may still get
414: // unexpected runtime errors.
415: IDEWorkbenchPlugin.log(getClass(),
416: "createNewFile()", e.getTargetException()); //$NON-NLS-1$
417: MessageDialog
418: .openError(
419: getContainer().getShell(),
420: IDEWorkbenchMessages.WizardNewFileCreationPage_internalErrorTitle,
421: NLS
422: .bind(
423: IDEWorkbenchMessages.WizardNewFileCreationPage_internalErrorMessage,
424: e.getTargetException()
425: .getMessage()));
426:
427: return null;
428: }
429:
430: newFile = newFileHandle;
431:
432: return newFile;
433: }
434:
435: /**
436: * Returns the scheduling rule to use when creating the resource at the
437: * given container path. The rule should be the creation rule for the
438: * top-most non-existing parent.
439: *
440: * @param resource
441: * The resource being created
442: * @return The scheduling rule for creating the given resource
443: * @since 3.1
444: * @deprecated As of 3.3, scheduling rules are provided by the undoable
445: * operation that this page creates and executes.
446: */
447: protected ISchedulingRule createRule(IResource resource) {
448: IResource parent = resource.getParent();
449: while (parent != null) {
450: if (parent.exists()) {
451: return resource.getWorkspace().getRuleFactory()
452: .createRule(resource);
453: }
454: resource = parent;
455: parent = parent.getParent();
456: }
457: return resource.getWorkspace().getRoot();
458: }
459:
460: /**
461: * Returns the current full path of the containing resource as entered or
462: * selected by the user, or its anticipated initial value.
463: *
464: * @return the container's full path, anticipated initial value, or
465: * <code>null</code> if no path is known
466: */
467: public IPath getContainerFullPath() {
468: return resourceGroup.getContainerFullPath();
469: }
470:
471: /**
472: * Returns the current file name as entered by the user, or its anticipated
473: * initial value.
474: * <br><br>
475: * The current file name will include the file extension if
476: * the preconditions are met.
477: * @see WizardNewFileCreationPage#setFileExtension(String)
478: *
479: * @return the file name, its anticipated initial value, or
480: * <code>null</code> if no file name is known
481: */
482: public String getFileName() {
483: if (resourceGroup == null) {
484: return initialFileName;
485: }
486:
487: return resourceGroup.getResource();
488: }
489:
490: /**
491: * Returns the file extension to use when creating the new file.
492: *
493: * @return the file extension or <code>null</code>.
494: * @see WizardNewFileCreationPage#setFileExtension(String)
495: * @since 3.3
496: */
497: public String getFileExtension() {
498: if (resourceGroup == null) {
499: return initialFileExtension;
500: }
501: return resourceGroup.getResourceExtension();
502: }
503:
504: /**
505: * Returns a stream containing the initial contents to be given to new file
506: * resource instances. <b>Subclasses</b> may wish to override. This default
507: * implementation provides no initial contents.
508: *
509: * @return initial contents to be given to new file resource instances
510: */
511: protected InputStream getInitialContents() {
512: return null;
513: }
514:
515: /**
516: * Returns the label to display in the file name specification visual
517: * component group.
518: * <p>
519: * Subclasses may reimplement.
520: * </p>
521: *
522: * @return the label to display in the file name specification visual
523: * component group
524: */
525: protected String getNewFileLabel() {
526: return IDEWorkbenchMessages.WizardNewFileCreationPage_fileLabel;
527: }
528:
529: /**
530: * Shows/hides the advanced option widgets.
531: */
532: protected void handleAdvancedButtonSelect() {
533: Shell shell = getShell();
534: Point shellSize = shell.getSize();
535: Composite composite = (Composite) getControl();
536:
537: if (linkedResourceComposite != null) {
538: linkedResourceComposite.dispose();
539: linkedResourceComposite = null;
540: composite.layout();
541: shell.setSize(shellSize.x, shellSize.y
542: - linkedResourceGroupHeight);
543: advancedButton.setText(IDEWorkbenchMessages.showAdvanced);
544: } else {
545: linkedResourceComposite = linkedResourceGroup
546: .createContents(linkedResourceParent);
547: if (linkedResourceGroupHeight == -1) {
548: Point groupSize = linkedResourceComposite.computeSize(
549: SWT.DEFAULT, SWT.DEFAULT, true);
550: linkedResourceGroupHeight = groupSize.y;
551: }
552: shell.setSize(shellSize.x, shellSize.y
553: + linkedResourceGroupHeight);
554: composite.layout();
555: advancedButton.setText(IDEWorkbenchMessages.hideAdvanced);
556: }
557: }
558:
559: /**
560: * The <code>WizardNewFileCreationPage</code> implementation of this
561: * <code>Listener</code> method handles all events and enablements for
562: * controls on this page. Subclasses may extend.
563: */
564: public void handleEvent(Event event) {
565: setPageComplete(validatePage());
566: }
567:
568: /**
569: * Sets the initial contents of the container name entry field, based upon
570: * either a previously-specified initial value or the ability to determine
571: * such a value.
572: */
573: protected void initialPopulateContainerNameField() {
574: if (initialContainerFullPath != null) {
575: resourceGroup
576: .setContainerFullPath(initialContainerFullPath);
577: } else {
578: Iterator it = currentSelection.iterator();
579: if (it.hasNext()) {
580: Object object = it.next();
581: IResource selectedResource = null;
582: if (object instanceof IResource) {
583: selectedResource = (IResource) object;
584: } else if (object instanceof IAdaptable) {
585: selectedResource = (IResource) ((IAdaptable) object)
586: .getAdapter(IResource.class);
587: }
588: if (selectedResource != null) {
589: if (selectedResource.getType() == IResource.FILE) {
590: selectedResource = selectedResource.getParent();
591: }
592: if (selectedResource.isAccessible()) {
593: resourceGroup
594: .setContainerFullPath(selectedResource
595: .getFullPath());
596: }
597: }
598: }
599: }
600: }
601:
602: /**
603: * Sets the value of this page's container name field, or stores it for
604: * future use if this page's controls do not exist yet.
605: *
606: * @param path
607: * the full path to the container
608: */
609: public void setContainerFullPath(IPath path) {
610: if (resourceGroup == null) {
611: initialContainerFullPath = path;
612: } else {
613: resourceGroup.setContainerFullPath(path);
614: }
615: }
616:
617: /**
618: * Sets the value of this page's file name field, or stores it for future
619: * use if this page's controls do not exist yet.
620: *
621: * @param value
622: * new file name
623: */
624: public void setFileName(String value) {
625: if (resourceGroup == null) {
626: initialFileName = value;
627: } else {
628: resourceGroup.setResource(value);
629: }
630: }
631:
632: /**
633: * Set the only file extension allowed for this page's file name field.
634: * If this page's controls do not exist yet, store it for future use.
635: * <br><br>
636: * If a file extension is specified, then it will always be
637: * appended with a '.' to the text from the file name field for
638: * validation when the following conditions are met:
639: * <br><br>
640: * (1) File extension length is greater than 0
641: * <br>
642: * (2) File name field text length is greater than 0
643: * <br>
644: * (3) File name field text does not already end with a '.' and the file
645: * extension specified (case sensitive)
646: * <br><br>
647: * The file extension will not be reflected in the actual file
648: * name field until the file name field loses focus.
649: *
650: * @param value
651: * The file extension without the '.' prefix
652: * (e.g. 'java', 'xml')
653: * @since 3.3
654: */
655: public void setFileExtension(String value) {
656: if (resourceGroup == null) {
657: initialFileExtension = value;
658: } else {
659: resourceGroup.setResourceExtension(value);
660: }
661: }
662:
663: /**
664: * Checks whether the linked resource target is valid. Sets the error
665: * message accordingly and returns the status.
666: *
667: * @return IStatus validation result from the CreateLinkedResourceGroup
668: */
669: protected IStatus validateLinkedResource() {
670: IPath containerPath = resourceGroup.getContainerFullPath();
671: IPath newFilePath = containerPath.append(resourceGroup
672: .getResource());
673: IFile newFileHandle = createFileHandle(newFilePath);
674: IStatus status = linkedResourceGroup
675: .validateLinkLocation(newFileHandle);
676:
677: if (status.getSeverity() == IStatus.ERROR) {
678: if (firstLinkCheck) {
679: setMessage(status.getMessage());
680: } else {
681: setErrorMessage(status.getMessage());
682: }
683: } else if (status.getSeverity() == IStatus.WARNING) {
684: setMessage(status.getMessage(), WARNING);
685: setErrorMessage(null);
686: }
687: return status;
688: }
689:
690: /**
691: * Returns whether this page's controls currently all contain valid values.
692: *
693: * @return <code>true</code> if all controls are valid, and
694: * <code>false</code> if at least one is invalid
695: */
696: protected boolean validatePage() {
697: boolean valid = true;
698:
699: if (!resourceGroup.areAllValuesValid()) {
700: // if blank name then fail silently
701: if (resourceGroup.getProblemType() == ResourceAndContainerGroup.PROBLEM_RESOURCE_EMPTY
702: || resourceGroup.getProblemType() == ResourceAndContainerGroup.PROBLEM_CONTAINER_EMPTY) {
703: setMessage(resourceGroup.getProblemMessage());
704: setErrorMessage(null);
705: } else {
706: setErrorMessage(resourceGroup.getProblemMessage());
707: }
708: valid = false;
709: }
710:
711: String resourceName = resourceGroup.getResource();
712: IWorkspace workspace = ResourcesPlugin.getWorkspace();
713: IStatus result = workspace.validateName(resourceName,
714: IResource.FILE);
715: if (!result.isOK()) {
716: setErrorMessage(result.getMessage());
717: return false;
718: }
719:
720: IStatus linkedResourceStatus = null;
721: if (valid) {
722: linkedResourceStatus = validateLinkedResource();
723: if (linkedResourceStatus.getSeverity() == IStatus.ERROR) {
724: valid = false;
725: }
726: }
727: // validateLinkedResource sets messages itself
728: if (valid
729: && (linkedResourceStatus == null || linkedResourceStatus
730: .isOK())) {
731: setMessage(null);
732: setErrorMessage(null);
733: }
734: return valid;
735: }
736:
737: /*
738: * @see DialogPage.setVisible(boolean)
739: */
740: public void setVisible(boolean visible) {
741: super.setVisible(visible);
742: if (visible) {
743: resourceGroup.setFocus();
744: }
745: }
746: }
|