/******************************************************************************* * Copyright (c) 2005 RadRails.org and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.radrails.rails.internal.ui.wizards.pages; import java.io.File; import java.util.List; import java.util.Observable; import java.util.Observer; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.ui.progress.UIJob; import org.radrails.rails.internal.core.RailsPlugin; import org.radrails.rails.internal.ui.RailsInstallDialog; import org.radrails.rails.internal.ui.wizards.NewRailsProjectWizard; import org.radrails.rails.internal.ui.wizards.WizardMessages; import org.radrails.rails.ui.RailsUIPlugin; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.wizards.NewWizardMessages; import org.rubypeople.rdt.internal.ui.wizards.dialogfields.DialogField; import org.rubypeople.rdt.internal.ui.wizards.dialogfields.IDialogFieldListener; import org.rubypeople.rdt.internal.ui.wizards.dialogfields.IStringButtonAdapter; import org.rubypeople.rdt.internal.ui.wizards.dialogfields.LayoutUtil; import org.rubypeople.rdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField; import org.rubypeople.rdt.internal.ui.wizards.dialogfields.StringButtonDialogField; import org.rubypeople.rdt.internal.ui.wizards.dialogfields.StringDialogField; import org.rubypeople.rdt.launching.RubyRuntime; import org.rubypeople.rdt.ui.RubyUI; import com.aptana.rdt.core.gems.IGemManager; import com.aptana.rdt.core.gems.Version; /** * Main dialog page for {@link NewRailsProjectWizard NewRailsProjectWizard}. * * @author mkent * @author cwilliams */ public class WizardNewRailsProjectPage extends WizardPage { private static final String LATEST = WizardMessages.WizardNewRailsProjectPage_Latest_rails_version_label; private NameGroup fNameGroup; private LocationGroup fLocationGroup; private Button generateButton; private Button fStartServerButton; private Combo dbCombo; private Combo versionCombo; private Link installRails; private Cursor hand; private Validator fValidator; /** * Constructor. * * @param pageName * the name of the wizard page */ public WizardNewRailsProjectPage(String pageName) { super(pageName); } /** * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) */ public void createControl(Composite parent) { initializeDialogUnits(parent); final Composite composite = new Composite(parent, SWT.NULL); composite.setFont(parent.getFont()); composite.setLayout(initGridLayout(new GridLayout(1, false), true)); composite.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL)); // create UI elements fNameGroup = new NameGroup(composite, ""); fLocationGroup = new LocationGroup(composite); // establish connections fNameGroup.addObserver(fLocationGroup); // FIXME When the location is "use existing source" then don't enable generation (or force it off and make user // re-check it)! // initialize all elements fNameGroup.notifyObservers(); // create and connect validator fValidator = new Validator(); fNameGroup.addObserver(fValidator); fLocationGroup.addObserver(fValidator); createOptionsGroup(composite); setControl(composite); } /** * Helper method to create the options widgets. * * @param parent * the parent composite */ private void createOptionsGroup(Composite parent) { // Create the group Group optionsGroup = new Group(parent, SWT.NONE); optionsGroup.setLayout(new GridLayout()); optionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); optionsGroup.setText(WizardMessages.WizardNewRailsProjectPage_Options_group_heading); // Create the check box Composite generate = new Composite(optionsGroup, SWT.NULL); GridLayout generateLayout = new GridLayout(); generateLayout.numColumns = 2; generate.setLayout(generateLayout); generate.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); generateButton = new Button(generate, SWT.CHECK); generateButton.setText(WizardMessages.WizardNewRailsProjectPage_Generate_project_skeleton); generateButton.setSelection(true); fLocationGroup.addObserver(new Observer() { public void update(Observable o, Object arg) { if (!fLocationGroup.isInWorkspace()) { generateButton.setSelection(false); generateSelected(false); } } }); hand = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND); installRails = new Link(generate, SWT.NONE); installRails.setText(WizardMessages.WizardNewRailsProjectPage_Install_rails_link); installRails.setEnabled(true); installRails.setVisible(railsNotInstalled()); installRails.setCursor(hand); // FIXME For me the hand appears when I hover about 10px below the link! installRails.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // Pop open a dialog to install rails! UIJob dialog = new RailsInstallDialog(RailsUIPlugin.getInstance().getGemManager()); dialog.schedule(); } }); Composite db = new Composite(optionsGroup, SWT.NULL); GridLayout dbLayout = new GridLayout(); dbLayout.numColumns = 2; db.setLayout(dbLayout); db.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); Label label = new Label(db, SWT.LEFT); label.setText(WizardMessages.WizardNewRailsProjectPage_Database_options_label); dbCombo = new Combo(db, SWT.DROP_DOWN | SWT.READ_ONLY); List<String> dbNames = RailsPlugin.getEligibleDatabaseNamesforCurrentVM(); for (String dbName : dbNames) { dbCombo.add(dbName); } if (RubyRuntime.currentVMIsJRuby()) { dbCombo.setText("derby"); } else { dbCombo.setText("sqlite3"); } Composite version = new Composite(optionsGroup, SWT.NULL); GridLayout versionLayout = new GridLayout(); versionLayout.numColumns = 2; version.setLayout(versionLayout); version.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); Label versionLabel = new Label(version, SWT.LEFT); versionLabel.setText(WizardMessages.WizardNewRailsProjectPage_Rails_version_label); versionCombo = new Combo(version, SWT.DROP_DOWN | SWT.READ_ONLY); IGemManager gemManager = RailsUIPlugin.getInstance().getGemManager(); List<Version> versions = gemManager.getVersions("rails"); versionCombo.add(LATEST); for (Version version1 : versions) { versionCombo.add(version1.toString()); } versionCombo.setText(LATEST); generateButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); generateSelected(generateButton.getSelection()); } }); // Create check box for auto starting server fStartServerButton = new Button(optionsGroup, SWT.CHECK); fStartServerButton.setText(WizardMessages.WizardNewRailsProjectPage_Start_server_label); fStartServerButton.setSelection(true); validatePage(); } private void validatePage() { fValidator.update(null, null); } /** * Creates a project resource handle for the current project name field value. * <p> * This method does not create the project resource; this is the responsibility of <code>IProject::create</code> * invoked by the new project resource wizard. * </p> * * @return the new project resource handle */ public IProject getProjectHandle() { return ResourcesPlugin.getWorkspace().getRoot().getProject(fNameGroup.getName()); } /** * @return the selection status of the generate rails skeleton option button */ public boolean getGenerateButtonSelection() { if (generateButton == null) return false; return generateButton.getSelection(); } public String getDatabaseType() { if (dbCombo == null) return ""; return dbCombo.getText(); } public String getRailsVersion() { String version = versionCombo.getText(); if (version.equals(LATEST)) return null; return version; } public boolean startServer() { if (fStartServerButton == null) return false; return fStartServerButton.getSelection(); } // // @Override // protected boolean validatePage() // { // boolean result = super.validatePage(); // if (!result) // return false; // String projectName = getProjectName(); // if (projectName != null) { // if (projectName.contains("'") || projectName.contains("\"")) { // setMessage(WizardMessages.WizardNewRailsProjectPage_Invalid_Project_Name_msg, IMessageProvider.ERROR); // return false; // } // } // if (getGenerateButtonSelection() && railsNotInstalled()) // { // setMessage(WizardMessages.WizardNewRailsProjectPage_Rails_not_installed_msg, IMessageProvider.WARNING); // } // return true; // } private boolean railsNotInstalled() { return RailsPlugin.getInstance().getRailsPath() == null; } @Override public void dispose() { hand.dispose(); super.dispose(); } /** * Request a location. Fires an event whenever the checkbox or the location field is changed, regardless of whether * the change originates from the user or has been invoked programmatically. */ private final class LocationGroup extends Observable implements Observer, IStringButtonAdapter, IDialogFieldListener { protected final SelectionButtonDialogField fWorkspaceRadio; protected final SelectionButtonDialogField fExternalRadio; protected final StringButtonDialogField fLocation; private String fPreviousExternalLocation; private static final String DIALOGSTORE_LAST_EXTERNAL_LOC = RubyUI.ID_PLUGIN + ".last.external.project"; //$NON-NLS-1$ public LocationGroup(Composite composite) { final int numColumns = 3; final Group group = new Group(composite, SWT.NONE); group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); group.setLayout(initGridLayout(new GridLayout(numColumns, false), true)); group.setText(NewWizardMessages.RubyProjectWizardFirstPage_LocationGroup_title); fWorkspaceRadio = new SelectionButtonDialogField(SWT.RADIO); fWorkspaceRadio.setDialogFieldListener(this); fWorkspaceRadio.setLabelText(NewWizardMessages.RubyProjectWizardFirstPage_LocationGroup_workspace_desc); fExternalRadio = new SelectionButtonDialogField(SWT.RADIO); fExternalRadio.setLabelText(NewWizardMessages.RubyProjectWizardFirstPage_LocationGroup_external_desc); fLocation = new StringButtonDialogField(this); fLocation.setDialogFieldListener(this); fLocation.setLabelText(NewWizardMessages.RubyProjectWizardFirstPage_LocationGroup_locationLabel_desc); fLocation.setButtonLabel(NewWizardMessages.RubyProjectWizardFirstPage_LocationGroup_browseButton_desc); fExternalRadio.attachDialogField(fLocation); fWorkspaceRadio.setSelection(true); fExternalRadio.setSelection(false); fPreviousExternalLocation = ""; //$NON-NLS-1$ fWorkspaceRadio.doFillIntoGrid(group, numColumns); fExternalRadio.doFillIntoGrid(group, numColumns); fLocation.doFillIntoGrid(group, numColumns); LayoutUtil.setHorizontalGrabbing(fLocation.getTextControl(null)); } protected void fireEvent() { setChanged(); notifyObservers(); } protected String getDefaultPath(String name) { final IPath path = Platform.getLocation().append(name); return path.toOSString(); } /* * (non-Javadoc) * @see java.util.Observer#update(java.util.Observable, java.lang.Object) */ public void update(Observable o, Object arg) { if (isInWorkspace()) { fLocation.setText(getDefaultPath(fNameGroup.getName())); } fireEvent(); } public IPath getLocation() { if (isInWorkspace()) { return Platform.getLocation(); } return Path.fromOSString(fLocation.getText().trim()); } public boolean isInWorkspace() { return fWorkspaceRadio.isSelected(); } /* * (non-Javadoc) * @see * org.eclipse.jdt.internal.ui.wizards.dialogfields.IStringButtonAdapter#changeControlPressed(org.eclipse.jdt * .internal.ui.wizards.dialogfields.DialogField) */ public void changeControlPressed(DialogField field) { final DirectoryDialog dialog = new DirectoryDialog(getShell()); dialog.setMessage(NewWizardMessages.RubyProjectWizardFirstPage_directory_message); String directoryName = fLocation.getText().trim(); if (directoryName.length() == 0) { String prevLocation = RubyPlugin.getDefault().getDialogSettings().get(DIALOGSTORE_LAST_EXTERNAL_LOC); if (prevLocation != null) { directoryName = prevLocation; } } if (directoryName.length() > 0) { final File path = new File(directoryName); if (path.exists()) dialog.setFilterPath(directoryName); } final String selectedDirectory = dialog.open(); if (selectedDirectory != null) { fLocation.setText(selectedDirectory); RubyPlugin.getDefault().getDialogSettings().put(DIALOGSTORE_LAST_EXTERNAL_LOC, selectedDirectory); } } /* * (non-Javadoc) * @see * org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener#dialogFieldChanged(org.eclipse.jdt. * internal.ui.wizards.dialogfields.DialogField) */ public void dialogFieldChanged(DialogField field) { if (field == fWorkspaceRadio) { final boolean checked = fWorkspaceRadio.isSelected(); if (checked) { fPreviousExternalLocation = fLocation.getText(); fLocation.setText(getDefaultPath(fNameGroup.getName())); } else { fLocation.setText(fPreviousExternalLocation); } } fireEvent(); } } /** * Request a project name. Fires an event whenever the text field is changed, regardless of its content. */ private final class NameGroup extends Observable implements IDialogFieldListener { protected final StringDialogField fNameField; public NameGroup(Composite composite, String initialName) { final Composite nameComposite = new Composite(composite, SWT.NONE); nameComposite.setFont(composite.getFont()); nameComposite.setLayout(initGridLayout(new GridLayout(2, false), false)); nameComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); // text field for project name fNameField = new StringDialogField(); fNameField.setLabelText(NewWizardMessages.RubyProjectWizardFirstPage_NameGroup_label_text); fNameField.setDialogFieldListener(this); setName(initialName); fNameField.doFillIntoGrid(nameComposite, 2); LayoutUtil.setHorizontalGrabbing(fNameField.getTextControl(null)); } protected void fireEvent() { setChanged(); notifyObservers(); } public String getName() { return fNameField.getText().trim(); } public void postSetFocus() { fNameField.postSetFocusOnDialogField(getShell().getDisplay()); } public void setName(String name) { fNameField.setText(name); } /* * (non-Javadoc) * @see * org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener#dialogFieldChanged(org.eclipse.jdt. * internal.ui.wizards.dialogfields.DialogField) */ public void dialogFieldChanged(DialogField field) { fireEvent(); } } /** * Validate this page and show appropriate warnings and error NewWizardMessages. */ private final class Validator implements Observer { public void update(Observable o, Object arg) { final IWorkspace workspace = RubyPlugin.getWorkspace(); final String name = fNameGroup.getName(); // check whether the project name field is empty if (name.length() == 0) { setErrorMessage(null); setMessage(NewWizardMessages.RubyProjectWizardFirstPage_Message_enterProjectName); setPageComplete(false); return; } if (name.contains("'") || name.contains("\"") || name.contains("`")) { setErrorMessage(WizardMessages.WizardNewRailsProjectPage_Invalid_Project_Name_msg); setPageComplete(false); return; } // check whether the project name is valid final IStatus nameStatus = workspace.validateName(name, IResource.PROJECT); if (!nameStatus.isOK()) { setErrorMessage(nameStatus.getMessage()); setPageComplete(false); return; } // check whether project already exists final IProject handle = getProjectHandle(); if (handle.exists()) { setErrorMessage(NewWizardMessages.RubyProjectWizardFirstPage_Message_projectAlreadyExists); setPageComplete(false); return; } final String location = fLocationGroup.getLocation().toOSString(); // check whether location is empty if (location.length() == 0) { setErrorMessage(null); setMessage(NewWizardMessages.RubyProjectWizardFirstPage_Message_enterLocation); setPageComplete(false); return; } // check whether the location is a syntactically correct path if (!Path.EMPTY.isValidPath(location)) { setErrorMessage(NewWizardMessages.RubyProjectWizardFirstPage_Message_invalidDirectory); setPageComplete(false); return; } // check whether the location has the workspace as prefix IPath projectPath = Path.fromOSString(location); if (!fLocationGroup.isInWorkspace() && Platform.getLocation().isPrefixOf(projectPath)) { setErrorMessage(NewWizardMessages.RubyProjectWizardFirstPage_Message_cannotCreateInWorkspace); setPageComplete(false); return; } // If we do not place the contents in the workspace validate the // location. if (!fLocationGroup.isInWorkspace()) { final IStatus locationStatus = workspace.validateProjectLocation(handle, projectPath); if (!locationStatus.isOK()) { setErrorMessage(locationStatus.getMessage()); setPageComplete(false); return; } } setPageComplete(true); setErrorMessage(null); setMessage(null); } } /** * Initialize a grid layout with the default Dialog settings. */ protected GridLayout initGridLayout(GridLayout layout, boolean margins) { layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); if (margins) { layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); } else { layout.marginWidth = 0; layout.marginHeight = 0; } return layout; } /** * Returns the current project location path as entered by the user, or its anticipated initial value. Note that if * the default has been returned the path in a project description used to create a project should not be set. * <p> * TODO At some point this method has to be converted to return an URI instead of an path. However, this first * requires support from Platform/UI to specify a project location different than in a local file system. FIXME * Check out ProjectContentsLocationArea for URI based implementation! * </p> * * @return the project location path or its anticipated initial value. */ public IPath getLocationPath() { return fLocationGroup.getLocation(); } protected void generateSelected(boolean selected) { dbCombo.setEnabled(selected); versionCombo.setEnabled(selected); validatePage(); } }