package net.sf.eclipsefp.haskell.ui.internal.preferences.scion; import java.io.File; import net.sf.eclipsefp.haskell.ui.internal.util.UITexts; import net.sf.eclipsefp.haskell.ui.util.HaskellUIImages; import net.sf.eclipsefp.haskell.ui.util.IImageNames; import net.sf.eclipsefp.haskell.util.FileUtil; import org.eclipse.jface.preference.StringButtonFieldEditor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.FileDialog; /** * A specialized {@link StringFieldEditor} that only allows executable files to be entered. * On Unix systems, this means that the file must be executable for the current user. * On Windows systems, it must have one of the common file extensions (.exe, .bat, ...). * * Ideally, this would inherit from {@link FileFieldEditor}, but that class was not written * with subclassing in mind. For example, it overrides {@link #checkState} with a version * that does not call {@link #doCheckState}, and it does not allow reading of its * <code>enforceAbsolute</code> setting. * * If the empty string is allowed, this is interpreted as being an optional file entry. * In that case, the file must only exist and be executable in case the field is not empty. * * @author thomas */ public class ExecutableFileFieldEditor extends StringButtonFieldEditor { /** * List of legal file extension suffixes, or <code>null</code> * for system defaults. */ private String[] extensions = null; /** * Indicates whether the path must be absolute; * <code>false</code> by default. */ private boolean enforceAbsolute = false; /** * Creates a new file field editor */ protected ExecutableFileFieldEditor() { // nothing to do } /** * Creates a file field editor. * * @param name the name of the preference this field editor works on * @param labelText the label text of the field editor * @param parent the parent of the field editor's control */ public ExecutableFileFieldEditor(final String name, final String labelText, final Composite parent) { this(name, labelText, false, parent); } /** * Creates a file field editor. * * @param name the name of the preference this field editor works on * @param labelText the label text of the field editor * @param enforceAbsolute <code>true</code> if the file path * must be absolute, and <code>false</code> otherwise * @param parent the parent of the field editor's control */ public ExecutableFileFieldEditor(final String name, final String labelText, final boolean enforceAbsolute, final Composite parent) { this(name, labelText, enforceAbsolute, VALIDATE_ON_FOCUS_LOST, parent); } /** * Creates a file field editor. * * @param name the name of the preference this field editor works on * @param labelText the label text of the field editor * @param enforceAbsolute <code>true</code> if the file path * must be absolute, and <code>false</code> otherwise * @param validationStrategy either {@link StringButtonFieldEditor#VALIDATE_ON_KEY_STROKE} * to perform on the fly checking, or {@link StringButtonFieldEditor#VALIDATE_ON_FOCUS_LOST} * (the default) to perform validation only after the text has been typed in * @param parent the parent of the field editor's control. * @since 3.4 * @see StringButtonFieldEditor#VALIDATE_ON_KEY_STROKE * @see StringButtonFieldEditor#VALIDATE_ON_FOCUS_LOST */ public ExecutableFileFieldEditor(final String name, final String labelText, final boolean enforceAbsolute, final int validationStrategy, final Composite parent) { init(name, labelText); this.enforceAbsolute = enforceAbsolute; setErrorMessage(UITexts.executableFileFieldEditor_errorDoesNotExist); setChangeButtonText(UITexts.dots); setValidateStrategy(validationStrategy); createControl(parent); } /* (non-Javadoc) * @see org.eclipse.jface.preference.StringButtonFieldEditor#doFillIntoGrid(org.eclipse.swt.widgets.Composite, int) */ @Override protected void doFillIntoGrid( final Composite parent, final int numColumns ) { super.doFillIntoGrid( parent, numColumns ); Button b=getChangeControl( parent ); b.setText( "" ); b.setImage( HaskellUIImages.getImage( IImageNames.FOLDER ) ); b.setToolTipText( JFaceResources.getString("openBrowse"));//$NON-NLS-1$ b.setLayoutData( new GridData() ); } /* (non-Javadoc) * Method declared on StringButtonFieldEditor. * Opens the file chooser dialog and returns the selected file. */ @Override protected String changePressed() { File f = new File(getTextControl().getText()); if (!f.exists()) { f = null; } File d = getFile(f); if (d == null) { return null; } return d.getAbsolutePath(); } /** * Checks whether the text input field contains a valid value or not. * * @return <code>true</code> if the field value is valid, * and <code>false</code> if invalid */ @Override public boolean checkState() { if (getTextControl() == null) { return false; } String txt = getTextControl().getText(); boolean result = isEmptyStringAllowed() || txt.length() > 0; result = result && doCheckState(); if (result) { clearErrorMessage(); } else { showErrorMessage(getErrorMessage()); } return result; } /** * Helper to open the file chooser dialog. * @param startingDirectory the directory to open the dialog on. * @return File The File the user selected or <code>null</code> if they * do not. */ private File getFile(final File startingDirectory) { FileDialog dialog = new FileDialog(getShell(), SWT.OPEN | SWT.SHEET); if (startingDirectory != null) { dialog.setFileName(startingDirectory.getPath()); } if (extensions != null) { dialog.setFilterExtensions(extensions); } String file = dialog.open(); if (file != null) { file = file.trim(); if (file.length() > 0) { return new File(file); } } return null; } /** * Sets this file field editor's file extension filter. * * @param extensions a list of file extension, or <code>null</code> * to set the filter to the system's default value */ public void setFileExtensions(final String[] extensions) { this.extensions = extensions; } /** * Returns true if the current value is an existing file that is executable. * If this cannot be determined with certainty, true is returned. * If false is returned, the error message is set. */ @Override protected boolean doCheckState() { String fileName = getTextControl().getText(); if (isEmptyStringAllowed() && fileName.length() == 0) { return true; } File file = new File(fileName); if (!file.exists() && !file.isAbsolute() && !enforceAbsolute){ String path = FileUtil.getPath(); for (String dir : path.split(File.pathSeparator)) { file=new File(dir,fileName); if (file.exists()){ break; } } } return checkFileExists(file) && checkFileAbsolute(file) && checkFileExecutable(file); } /** * Checks whether the file exists and is a normal file. * If not, returns false and sets the error message. */ protected boolean checkFileExists(final File file) { if (!file.isFile()) { setErrorMessage( UITexts.executableFileFieldEditor_errorDoesNotExist ); return false; } return true; } /** * Checks wether the file path is absolute if that was requested. * If not, returns false and sets the error message. * Assumes that the file exists. */ protected boolean checkFileAbsolute(final File file) { if (enforceAbsolute && !file.isAbsolute()) { setErrorMessage( UITexts.executableFileFieldEditor_errorNotAbsolute ); return false; } return true; } /** * Checks whether the file is executable. * If not, returns false and sets the error message. * Assumes that the file exists. */ protected boolean checkFileExecutable(final File file) { if (!FileUtil.isExecutable(file)) { setErrorMessage(UITexts.executableFileFieldEditor_errorNotExecutable); return false; } return true; } }