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.internal.ide.dialogs;
011:
012: import java.net.URI;
013: import java.net.URISyntaxException;
014: import org.eclipse.core.filesystem.EFS;
015: import org.eclipse.core.filesystem.IFileInfo;
016: import org.eclipse.core.filesystem.IFileStore;
017: import org.eclipse.core.filesystem.URIUtil;
018: import org.eclipse.core.resources.IPathVariableManager;
019: import org.eclipse.core.resources.IResource;
020: import org.eclipse.core.resources.IWorkspace;
021: import org.eclipse.core.resources.ResourcesPlugin;
022: import org.eclipse.core.runtime.IPath;
023: import org.eclipse.core.runtime.IStatus;
024: import org.eclipse.core.runtime.Path;
025: import org.eclipse.core.runtime.Status;
026: import org.eclipse.jface.dialogs.Dialog;
027: import org.eclipse.jface.dialogs.IDialogConstants;
028: import org.eclipse.swt.SWT;
029: import org.eclipse.swt.events.ModifyEvent;
030: import org.eclipse.swt.events.ModifyListener;
031: import org.eclipse.swt.events.SelectionAdapter;
032: import org.eclipse.swt.events.SelectionEvent;
033: import org.eclipse.swt.events.SelectionListener;
034: import org.eclipse.swt.graphics.Font;
035: import org.eclipse.swt.graphics.FontMetrics;
036: import org.eclipse.swt.graphics.GC;
037: import org.eclipse.swt.layout.GridData;
038: import org.eclipse.swt.layout.GridLayout;
039: import org.eclipse.swt.widgets.Button;
040: import org.eclipse.swt.widgets.Composite;
041: import org.eclipse.swt.widgets.Control;
042: import org.eclipse.swt.widgets.DirectoryDialog;
043: import org.eclipse.swt.widgets.Event;
044: import org.eclipse.swt.widgets.FileDialog;
045: import org.eclipse.swt.widgets.Label;
046: import org.eclipse.swt.widgets.Listener;
047: import org.eclipse.swt.widgets.Text;
048: import org.eclipse.ui.ide.dialogs.PathVariableSelectionDialog;
049: import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
050: import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
051: import org.eclipse.ui.internal.ide.filesystem.FileSystemConfiguration;
052: import org.eclipse.ui.internal.ide.filesystem.FileSystemSupportRegistry;
053:
054: /**
055: * Widget group for specifying a linked file or folder target.
056: *
057: * @since 2.1
058: */
059: public class CreateLinkedResourceGroup {
060: private Listener listener;
061:
062: private String linkTarget = ""; //$NON-NLS-1$
063:
064: private int type;
065:
066: private boolean createLink = false;
067:
068: // used to compute layout sizes
069: private FontMetrics fontMetrics;
070:
071: // widgets
072: private Composite groupComposite;
073:
074: private Text linkTargetField;
075:
076: private Button browseButton;
077:
078: private Button variablesButton;
079:
080: private Label resolvedPathLabelText;
081:
082: private Label resolvedPathLabelData;
083:
084: private final IStringValue updatableResourceName;
085:
086: /**
087: * Helper interface intended for updating a string value based on the
088: * currently selected link target.
089: *
090: * @since 3.2
091: */
092: public static interface IStringValue {
093: /**
094: * Sets the String value.
095: *
096: * @param string
097: * a non-null String
098: */
099: void setValue(String string);
100:
101: /**
102: * Gets the String value.
103: *
104: * @return the current value, or <code>null</code>
105: */
106: String getValue();
107: }
108:
109: private String lastUpdatedValue;
110:
111: private FileSystemSelectionArea fileSystemSelectionArea;
112:
113: /**
114: * Creates a link target group
115: *
116: * @param type
117: * specifies the type of resource to link to.
118: * <code>IResource.FILE</code> or <code>IResource.FOLDER</code>
119: * @param listener
120: * listener to notify when one of the widgets' value is changed.
121: * @param updatableResourceName
122: * an updatable string value that will be updated to reflect the
123: * link target's last segment, or <code>null</code>. Updating
124: * will only happen if the current value of that string is null
125: * or the empty string, or if it has not been changed since the
126: * last time it was updated.
127: */
128: public CreateLinkedResourceGroup(int type, Listener listener,
129: IStringValue updatableResourceName) {
130: this .type = type;
131: this .listener = listener;
132: this .updatableResourceName = updatableResourceName;
133: if (updatableResourceName != null) {
134: lastUpdatedValue = updatableResourceName.getValue();
135: }
136: }
137:
138: /**
139: * Creates the widgets
140: *
141: * @param parent
142: * parent composite of the widget group
143: * @return the widget group
144: */
145: public Composite createContents(Composite parent) {
146: Font font = parent.getFont();
147: initializeDialogUnits(parent);
148: // top level group
149: groupComposite = new Composite(parent, SWT.NONE);
150: GridLayout layout = new GridLayout();
151: groupComposite.setLayout(layout);
152: groupComposite
153: .setLayoutData(new GridData(
154: GridData.VERTICAL_ALIGN_FILL
155: | GridData.FILL_HORIZONTAL));
156: groupComposite.setFont(font);
157:
158: final Button createLinkButton = new Button(groupComposite,
159: SWT.CHECK);
160: if (type == IResource.FILE) {
161: createLinkButton
162: .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_linkFileButton);
163: } else {
164: createLinkButton
165: .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_linkFolderButton);
166: }
167: createLinkButton.setSelection(createLink);
168: createLinkButton.setFont(font);
169: SelectionListener selectionListener = new SelectionAdapter() {
170: public void widgetSelected(SelectionEvent e) {
171: createLink = createLinkButton.getSelection();
172: browseButton.setEnabled(createLink);
173: variablesButton.setEnabled(createLink);
174: // Set the required field color if the field is enabled
175: linkTargetField.setEnabled(createLink);
176: if (fileSystemSelectionArea != null)
177: fileSystemSelectionArea.setEnabled(createLink);
178:
179: if (listener != null) {
180: listener.handleEvent(new Event());
181: }
182: }
183: };
184: createLinkButton.addSelectionListener(selectionListener);
185:
186: createLinkLocationGroup(groupComposite, createLink);
187: return groupComposite;
188: }
189:
190: /**
191: * Creates the link target location widgets.
192: *
193: * @param locationGroup
194: * the parent composite
195: * @param enabled
196: * sets the initial enabled state of the widgets
197: */
198: private void createLinkLocationGroup(Composite locationGroup,
199: boolean enabled) {
200: Button button = new Button(locationGroup, SWT.CHECK);
201: int indent = button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
202:
203: button.dispose();
204:
205: // linkTargetGroup is necessary to decouple layout from
206: // resolvedPathGroup layout
207: Composite linkTargetGroup = new Composite(locationGroup,
208: SWT.NONE);
209: GridLayout layout = new GridLayout();
210: layout.numColumns = 4;
211: layout.marginHeight = 0;
212: layout.marginWidth = 0;
213: linkTargetGroup.setLayout(layout);
214: GridData data = new GridData(GridData.FILL_HORIZONTAL);
215: data.horizontalIndent = indent;
216: linkTargetGroup.setLayoutData(data);
217:
218: // link target location entry field
219: linkTargetField = new Text(linkTargetGroup, SWT.BORDER);
220: data = new GridData(GridData.FILL_HORIZONTAL);
221: data.widthHint = IDialogConstants.ENTRY_FIELD_WIDTH;
222: data.horizontalSpan = 2;
223: linkTargetField.setLayoutData(data);
224: linkTargetField.setEnabled(enabled);
225: linkTargetField.addModifyListener(new ModifyListener() {
226: public void modifyText(ModifyEvent e) {
227: linkTarget = linkTargetField.getText();
228: resolveVariable();
229: if (updatableResourceName != null) {
230: String value = updatableResourceName.getValue();
231: if (value == null
232: || value.equals("") || value.equals(lastUpdatedValue)) { //$NON-NLS-1$
233: IPath linkTargetPath = new Path(linkTarget);
234: String lastSegment = linkTargetPath
235: .lastSegment();
236: if (lastSegment != null) {
237: lastUpdatedValue = lastSegment;
238: updatableResourceName.setValue(lastSegment);
239: }
240: }
241: }
242: if (listener != null) {
243: listener.handleEvent(new Event());
244: }
245: }
246: });
247:
248: // browse button
249: browseButton = new Button(linkTargetGroup, SWT.PUSH);
250: browseButton
251: .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_browseButton);
252: browseButton.addSelectionListener(new SelectionAdapter() {
253: public void widgetSelected(SelectionEvent event) {
254: handleLinkTargetBrowseButtonPressed();
255: }
256: });
257: browseButton.setEnabled(enabled);
258: setButtonLayoutData(browseButton);
259:
260: // variables button
261: variablesButton = new Button(linkTargetGroup, SWT.PUSH);
262: variablesButton
263: .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_variablesButton);
264: variablesButton.addSelectionListener(new SelectionAdapter() {
265: public void widgetSelected(SelectionEvent event) {
266: handleVariablesButtonPressed();
267: }
268: });
269: variablesButton.setEnabled(enabled);
270: setButtonLayoutData(variablesButton);
271:
272: createFileSystemSelection(linkTargetGroup, enabled);
273:
274: createResolvedPathGroup(locationGroup, indent);
275:
276: if (linkTarget != null) {
277: linkTargetField.setText(linkTarget);
278: }
279: }
280:
281: /**
282: * Create the file system selection area.
283: *
284: * @param composite
285: * @param enabled
286: * the initial enablement state.
287: */
288: private void createFileSystemSelection(Composite composite,
289: boolean enabled) {
290:
291: // Always use the default if that is all there is.
292: if (FileSystemSupportRegistry.getInstance().hasOneFileSystem()) {
293: return;
294: }
295:
296: fileSystemSelectionArea = new FileSystemSelectionArea();
297: fileSystemSelectionArea.createContents(composite);
298: fileSystemSelectionArea.setEnabled(enabled);
299: }
300:
301: /**
302: * Create the composite for the resolved path.
303: *
304: * @param locationGroup
305: * @param indent
306: */
307: private void createResolvedPathGroup(Composite locationGroup,
308: int indent) {
309: GridLayout layout;
310: GridData data;
311: Composite resolvedPathGroup = new Composite(locationGroup,
312: SWT.NONE);
313: layout = new GridLayout();
314: layout.numColumns = 2;
315: layout.marginHeight = 0;
316: layout.marginWidth = 0;
317: resolvedPathGroup.setLayout(layout);
318: data = new GridData(GridData.FILL_HORIZONTAL);
319: data.horizontalIndent = indent;
320: resolvedPathGroup.setLayoutData(data);
321:
322: resolvedPathLabelText = new Label(resolvedPathGroup, SWT.SINGLE);
323: resolvedPathLabelText
324: .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_resolvedPathLabel);
325: resolvedPathLabelText.setVisible(false);
326:
327: resolvedPathLabelData = new Label(resolvedPathGroup, SWT.SINGLE);
328: data = new GridData(GridData.FILL_HORIZONTAL);
329: resolvedPathLabelData.setLayoutData(data);
330: resolvedPathLabelData.setVisible(false);
331: }
332:
333: /**
334: * Returns a new status object with the given severity and message.
335: *
336: * @return a new status object with the given severity and message.
337: */
338: private IStatus createStatus(int severity, String message) {
339: return new Status(severity, IDEWorkbenchPlugin.getDefault()
340: .getBundle().getSymbolicName(), severity, message, null);
341: }
342:
343: /**
344: * Disposes the group's widgets.
345: */
346: public void dispose() {
347: if (groupComposite != null
348: && groupComposite.isDisposed() == false) {
349: groupComposite.dispose();
350: }
351: }
352:
353: /**
354: * Returns the link target location entered by the user.
355: *
356: * @return the link target location entered by the user. null if the user
357: * chose not to create a link.
358: */
359: public URI getLinkTargetURI() {
360: if (!createLink)
361: return null;
362: // resolve path variable if we have a relative path
363: if (!linkTarget.startsWith("/")) { //$NON-NLS-1$
364: IPathVariableManager pathVariableManager = ResourcesPlugin
365: .getWorkspace().getPathVariableManager();
366: try {
367:
368: URI path = new URI(linkTarget.replace(
369: java.io.File.separatorChar, '/'));
370: URI resolved = pathVariableManager.resolveURI(path);
371: if (path != resolved) {
372: // we know this is a path variable, but return unresolved
373: // path so resource will be created with variable intact
374: return path;
375: }
376: } catch (URISyntaxException e) {
377: // link target is not a valid URI. Fall through to handle this
378: // below
379: }
380: }
381:
382: FileSystemConfiguration configuration = getSelectedConfiguration();
383: if (configuration == null) {
384: return URIUtil.toURI(linkTarget);
385: }
386: // validate non-local file system location
387: return configuration.getContributor().getURI(linkTarget);
388: }
389:
390: /**
391: * Opens a file or directory browser depending on the link type.
392: */
393: private void handleLinkTargetBrowseButtonPressed() {
394: IFileStore store = null;
395: String selection = null;
396: FileSystemConfiguration config = getSelectedConfiguration();
397: boolean isDefault = config == null
398: || (FileSystemSupportRegistry.getInstance()
399: .getDefaultConfiguration()).equals(config);
400:
401: if (linkTarget.length() > 0) {
402: store = IDEResourceInfoUtils.getFileStore(linkTarget);
403: if (!store.fetchInfo().exists()) {
404: store = null;
405: }
406: }
407: if (type == IResource.FILE) {
408: if (isDefault) {
409: FileDialog dialog = new FileDialog(linkTargetField
410: .getShell());
411: dialog
412: .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_targetSelectionTitle);
413: if (store != null) {
414: if (store.fetchInfo().isDirectory()) {
415: dialog.setFilterPath(linkTarget);
416: } else {
417: dialog.setFileName(linkTarget);
418: }
419: }
420: selection = dialog.open();
421: } else {
422: URI uri = config.getContributor().browseFileSystem(
423: linkTarget, linkTargetField.getShell());
424: if (uri != null)
425: selection = uri.toString();
426: }
427: } else {
428: String filterPath = null;
429: if (store != null) {
430: IFileStore path = store;
431: if (!store.fetchInfo().isDirectory()) {
432: path = store.getParent();
433: }
434: if (path != null) {
435: filterPath = store.toString();
436: }
437: }
438:
439: if (isDefault) {
440: DirectoryDialog dialog = new DirectoryDialog(
441: linkTargetField.getShell());
442: dialog
443: .setMessage(IDEWorkbenchMessages.CreateLinkedResourceGroup_targetSelectionLabel);
444: if (filterPath != null)
445: dialog.setFilterPath(filterPath);
446: selection = dialog.open();
447: } else {
448: String initialPath = IDEResourceInfoUtils.EMPTY_STRING;
449: if (filterPath != null)
450: initialPath = filterPath;
451: URI uri = config.getContributor().browseFileSystem(
452: initialPath, linkTargetField.getShell());
453: if (uri != null)
454: selection = uri.toString();
455: }
456: }
457: if (selection != null) {
458: linkTargetField.setText(selection);
459: }
460: }
461:
462: /**
463: * Return the selected configuration or <code>null</code> if there is not
464: * one selected.
465: *
466: * @return FileSystemConfiguration or <code>null</code>
467: */
468: private FileSystemConfiguration getSelectedConfiguration() {
469: if (fileSystemSelectionArea == null)
470: return null;
471: return fileSystemSelectionArea.getSelectedConfiguration();
472: }
473:
474: /**
475: * Opens a path variable selection dialog
476: */
477: private void handleVariablesButtonPressed() {
478: int variableTypes = IResource.FOLDER;
479:
480: // allow selecting file and folder variables when creating a
481: // linked file
482: if (type == IResource.FILE) {
483: variableTypes |= IResource.FILE;
484: }
485:
486: PathVariableSelectionDialog dialog = new PathVariableSelectionDialog(
487: linkTargetField.getShell(), variableTypes);
488: if (dialog.open() == IDialogConstants.OK_ID) {
489: String[] variableNames = (String[]) dialog.getResult();
490: if (variableNames != null && variableNames.length == 1) {
491: linkTargetField.setText(variableNames[0]);
492: }
493: }
494: }
495:
496: /**
497: * Initializes the computation of horizontal and vertical dialog units based
498: * on the size of current font.
499: * <p>
500: * This method must be called before <code>setButtonLayoutData</code> is
501: * called.
502: * </p>
503: *
504: * @param control
505: * a control from which to obtain the current font
506: */
507: protected void initializeDialogUnits(Control control) {
508: // Compute and store a font metric
509: GC gc = new GC(control);
510: gc.setFont(control.getFont());
511: fontMetrics = gc.getFontMetrics();
512: gc.dispose();
513: }
514:
515: /**
516: * Tries to resolve the value entered in the link target field as a
517: * variable, if the value is a relative path. Displays the resolved value if
518: * the entered value is a variable.
519: */
520: private void resolveVariable() {
521: IPathVariableManager pathVariableManager = ResourcesPlugin
522: .getWorkspace().getPathVariableManager();
523: IPath path = new Path(linkTarget);
524: IPath resolvedPath = pathVariableManager.resolvePath(path);
525:
526: if (path.equals(resolvedPath)) {
527: resolvedPathLabelText.setVisible(false);
528: resolvedPathLabelData.setVisible(false);
529: } else {
530: resolvedPathLabelText.setVisible(true);
531: resolvedPathLabelData.setVisible(true);
532: }
533: resolvedPathLabelData.setText(resolvedPath.toOSString());
534: }
535:
536: /**
537: * Sets the <code>GridData</code> on the specified button to be one that
538: * is spaced for the current dialog page units. The method
539: * <code>initializeDialogUnits</code> must be called once before calling
540: * this method for the first time.
541: *
542: * @param button
543: * the button to set the <code>GridData</code>
544: * @return the <code>GridData</code> set on the specified button
545: */
546: private GridData setButtonLayoutData(Button button) {
547: GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
548: int widthHint = Dialog.convertHorizontalDLUsToPixels(
549: fontMetrics, IDialogConstants.BUTTON_WIDTH);
550: data.widthHint = Math.max(widthHint, button.computeSize(
551: SWT.DEFAULT, SWT.DEFAULT, true).x);
552: button.setLayoutData(data);
553: return data;
554: }
555:
556: /**
557: * Sets the value of the link target field
558: *
559: * @param target
560: * the value of the link target field
561: */
562: public void setLinkTarget(String target) {
563: linkTarget = target;
564: if (linkTargetField != null
565: && linkTargetField.isDisposed() == false) {
566: linkTargetField.setText(target);
567: }
568: }
569:
570: /**
571: * Validates the type of the given file against the link type specified in
572: * the constructor.
573: *
574: * @param linkTargetFile
575: * file to validate
576: * @return IStatus indicating the validation result. IStatus.OK if the given
577: * file is valid.
578: */
579: private IStatus validateFileType(IFileInfo linkTargetFile) {
580: if (type == IResource.FILE && linkTargetFile.isDirectory()) {
581: return createStatus(
582: IStatus.ERROR,
583: IDEWorkbenchMessages.CreateLinkedResourceGroup_linkTargetNotFile);
584: } else if (type == IResource.FOLDER
585: && !linkTargetFile.isDirectory()) {
586: return createStatus(
587: IStatus.ERROR,
588: IDEWorkbenchMessages.CreateLinkedResourceGroup_linkTargetNotFolder);
589: }
590: return Status.OK_STATUS;
591: }
592:
593: /**
594: * Validates this page's controls.
595: *
596: * @param linkHandle
597: * The target to check
598: *
599: * @return IStatus indicating the validation result. IStatus.OK if the
600: * specified link target is valid given the linkHandle.
601: */
602: public IStatus validateLinkLocation(IResource linkHandle) {
603: if (linkTargetField == null || linkTargetField.isDisposed()
604: || !createLink) {
605: return Status.OK_STATUS;
606: }
607: IWorkspace workspace = IDEWorkbenchPlugin.getPluginWorkspace();
608: FileSystemConfiguration configuration = getSelectedConfiguration();
609: if (configuration == null
610: || EFS.SCHEME_FILE.equals(configuration.getScheme())) {
611: // Special handling for UNC paths. See bug 90825
612: IPath location = new Path(linkTarget);
613: if (location.isUNC()) {
614: return createStatus(
615: IStatus.WARNING,
616: IDEWorkbenchMessages.CreateLinkedResourceGroup_unableToValidateLinkTarget);
617: }
618: }
619: URI locationURI = getLinkTargetURI();
620: IStatus locationStatus = workspace.validateLinkLocationURI(
621: linkHandle, locationURI);
622: if (locationStatus.getSeverity() == IStatus.ERROR) {
623: return locationStatus;
624: }
625:
626: // use the resolved link target name
627: URI resolved = workspace.getPathVariableManager().resolveURI(
628: locationURI);
629: IFileInfo linkTargetFile = IDEResourceInfoUtils
630: .getFileInfo(resolved);
631: if (linkTargetFile != null && linkTargetFile.exists()) {
632: IStatus fileTypeStatus = validateFileType(linkTargetFile);
633: if (!fileTypeStatus.isOK()) {
634: return fileTypeStatus;
635: }
636: } else if (locationStatus.isOK()) {
637: // locationStatus takes precedence over missing location warning.
638: return createStatus(
639: IStatus.WARNING,
640: IDEWorkbenchMessages.CreateLinkedResourceGroup_linkTargetNonExistent);
641: }
642: return locationStatus;
643: }
644: }
|