/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * References: * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage */ package com.android.ide.eclipse.adt.internal.wizards.newproject; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage.TestInfo; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; import com.android.sdklib.internal.project.ProjectProperties.PropertyType; import com.android.sdklib.xml.AndroidManifest; import com.android.sdklib.xml.ManifestData; import com.android.sdklib.xml.ManifestData.Activity; import com.android.sdkuilib.internal.widgets.SdkTargetSelector; import org.eclipse.core.filesystem.URIUtil; 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.jdt.core.JavaConventions; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Rectangle; 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.Event; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkingSet; import java.io.File; import java.io.FileFilter; import java.net.URI; import java.util.ArrayList; import java.util.regex.Pattern; /** * NewAndroidProjectCreationPage is a project creation page that provides the * following fields: * <ul> * <li> Project name * <li> SDK Target * <li> Application name * <li> Package name * <li> Activity name * </ul> * Note: this class is public so that it can be accessed from unit tests. * It is however an internal class. Its API may change without notice. * It should semantically be considered as a private final class. * Do not derive from this class. */ public class NewProjectCreationPage extends WizardPage { // constants private static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$ /** Initial value for all name fields (project, activity, application, package). Used * whenever a value is requested before controls are created. */ private static final String INITIAL_NAME = ""; //$NON-NLS-1$ /** Initial value for the Create New Project radio. */ private static final boolean INITIAL_CREATE_NEW_PROJECT = true; /** Initial value for the Create Project From Sample. */ private static final boolean INITIAL_CREATE_FROM_SAMPLE = false; /** Initial value for the Create Project From Existing Source. */ private static final boolean INITIAL_CREATE_FROM_SOURCE = false; /** Initial value for the Use Default Location check box. */ private static final boolean INITIAL_USE_DEFAULT_LOCATION = true; /** Initial value for the Create Activity check box. */ private static final boolean INITIAL_CREATE_ACTIVITY = true; /** Pattern for characters accepted in a project name. Since this will be used as a * directory name, we're being a bit conservative on purpose. It cannot start with a space. */ private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$ /** Last user-browsed location, static so that it be remembered for the whole session */ private static String sCustomLocationOsPath = ""; //$NON-NLS-1$ private static boolean sAutoComputeCustomLocation = true; private final int MSG_NONE = 0; private final int MSG_WARNING = 1; private final int MSG_ERROR = 2; /** Structure with the externally visible information from this Main Project page. */ private final MainInfo mInfo = new MainInfo(); /** Structure with the externally visible information from the Test Project page. * This is null if there's no such page, meaning the main project page is standalone. */ private TestInfo mTestInfo; private String mUserPackageName = ""; //$NON-NLS-1$ private String mUserActivityName = ""; //$NON-NLS-1$ private boolean mUserCreateActivityCheck = INITIAL_CREATE_ACTIVITY; private String mSourceFolder = ""; //$NON-NLS-1$ // widgets private Text mProjectNameField; private Text mPackageNameField; private Text mActivityNameField; private Text mApplicationNameField; private Button mCreateNewProjectRadio; private Button mCreateFromSampleRadio; private Button mUseDefaultLocation; private Label mLocationLabel; private Text mLocationPathField; private Button mBrowseButton; private Button mCreateActivityCheck; private Text mMinSdkVersionField; private SdkTargetSelector mSdkTargetSelector; private ITargetChangeListener mSdkTargetChangeListener; private boolean mInternalLocationPathUpdate; private boolean mInternalProjectNameUpdate; private boolean mInternalApplicationNameUpdate; private boolean mInternalCreateActivityUpdate; private boolean mInternalActivityNameUpdate; private boolean mProjectNameModifiedByUser; private boolean mApplicationNameModifiedByUser; private final ArrayList<String> mSamplesPaths = new ArrayList<String>(); private Combo mSamplesCombo; private WorkingSetGroup mWorkingSetGroup; /** * Creates a new project creation wizard page. */ public NewProjectCreationPage() { super(MAIN_PAGE_NAME); setPageComplete(false); setTitle("New Android Project"); setDescription("Creates a new Android Project resource."); mWorkingSetGroup = new WorkingSetGroup(); setWorkingSets(new IWorkingSet[0]); } public void init(IStructuredSelection selection, IWorkbenchPart activePart) { setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart)); } // --- Getters used by NewProjectWizard --- /** * Structure that collects all externally visible information from this page. * This is used by the calling wizard to actually do the work or by other pages. * <p/> * This interface is provided so that the adt-test counterpart can override the returned * information. */ public interface IMainInfo { public IPath getLocationPath(); /** * 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. * * @return the project location path or its anticipated initial value. */ /** Returns the value of the project name field with leading and trailing spaces removed. */ public String getProjectName(); /** Returns the value of the package name field with spaces trimmed. */ public String getPackageName(); /** Returns the value of the activity name field with spaces trimmed. */ public String getActivityName(); /** Returns the value of the min sdk version field with spaces trimmed. */ public String getMinSdkVersion(); /** Returns the value of the application name field with spaces trimmed. */ public String getApplicationName(); /** Returns the value of the "Create New Project" radio. */ public boolean isNewProject(); /** Returns the value of the "Create Activity" checkbox. */ public boolean isCreateActivity(); /** Returns the value of the Use Default Location field. */ public boolean useDefaultLocation(); /** Returns the internal source folder (for the "existing project" mode) or the default * "src" constant. */ public String getSourceFolder(); /** Returns the current sdk target or null if none has been selected yet. */ public IAndroidTarget getSdkTarget(); /** Returns the current working sets or null if none has been selected yet. */ public IWorkingSet[] getSelectedWorkingSets(); } /** * Structure that collects all externally visible information from this page. * This is used by the calling wizard to actually do the work or by other pages. */ public class MainInfo implements IMainInfo { /** * 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. * * @return the project location path or its anticipated initial value. */ public IPath getLocationPath() { return new Path(getProjectLocation()); } /** Returns the value of the project name field with leading and trailing spaces removed. */ public String getProjectName() { return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim(); } /** Returns the value of the package name field with spaces trimmed. */ public String getPackageName() { return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim(); } /** Returns the value of the activity name field with spaces trimmed. */ public String getActivityName() { return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim(); } /** Returns the value of the min sdk version field with spaces trimmed. */ public String getMinSdkVersion() { return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim(); //$NON-NLS-1$ } /** Returns the value of the application name field with spaces trimmed. */ public String getApplicationName() { // Return the name of the activity as default application name. return mApplicationNameField == null ? getActivityName() : mApplicationNameField.getText().trim(); } /** Returns the value of the "Create New Project" radio. */ public boolean isNewProject() { return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT : mCreateNewProjectRadio.getSelection(); } /** Returns the value of the "Create from Existing Sample" radio. */ public boolean isCreateFromSample() { return mCreateFromSampleRadio == null ? INITIAL_CREATE_FROM_SAMPLE : mCreateFromSampleRadio.getSelection(); } /** Returns the value of the "Create Activity" checkbox. */ public boolean isCreateActivity() { return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY : mCreateActivityCheck.getSelection(); } /** Returns the value of the Use Default Location field. */ public boolean useDefaultLocation() { return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION : mUseDefaultLocation.getSelection(); } /** Returns the internal source folder (for the "existing project" mode) or the default * "src" constant. */ public String getSourceFolder() { if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) { return SdkConstants.FD_SOURCES; } else { return mSourceFolder; } } /** Returns the current sdk target or null if none has been selected yet. */ public IAndroidTarget getSdkTarget() { return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected(); } /** Returns the current sdk target or null if none has been selected yet. */ public IWorkingSet[] getSelectedWorkingSets() { return getWorkingSets(); } } /** * Returns a {@link MainInfo} structure that collects all externally visible information * from this page, to be used by the calling wizard or by other pages. */ public IMainInfo getMainInfo() { return mInfo; } /** * Grabs the {@link TestInfo} structure that collects externally visible fields from the * test project page. This may be null. */ public void setTestInfo(TestInfo testInfo) { mTestInfo = testInfo; } /** * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when * the dialog is made visible. */ @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible) { mProjectNameField.setFocus(); validatePageComplete(); } } // --- UI creation --- /** * Creates the top level control for this dialog page under the given parent * composite. * * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) */ public void createControl(Composite parent) { final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL); scrolledComposite.setFont(parent.getFont()); scrolledComposite.setExpandHorizontal(true); scrolledComposite.setExpandVertical(true); initializeDialogUnits(parent); final Composite composite = new Composite(scrolledComposite, SWT.NULL); composite.setFont(parent.getFont()); scrolledComposite.setContent(composite); composite.setLayout(new GridLayout()); composite.setLayoutData(new GridData(GridData.FILL_BOTH)); createProjectNameGroup(composite); createLocationGroup(composite); createTargetGroup(composite); createPropertiesGroup(composite); createWorkingSetGroup(composite); // Update state the first time enableLocationWidgets(); loadSamplesForTarget(null /*target*/); mSdkTargetChangeListener.onSdkLoaded(); scrolledComposite.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle r = scrolledComposite.getClientArea(); scrolledComposite.setMinSize(composite.computeSize(r.width, SWT.DEFAULT)); } }); // Show description the first time setErrorMessage(null); setMessage(null); setControl(scrolledComposite); // Validate. This will complain about the first empty field. validatePageComplete(); } @Override public void dispose() { if (mSdkTargetChangeListener != null) { AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener); mSdkTargetChangeListener = null; } super.dispose(); } /** * Creates the group for the project name: * [label: "Project Name"] [text field] * * @param parent the parent composite */ private final void createProjectNameGroup(Composite parent) { Composite group = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(); layout.numColumns = 2; group.setLayout(layout); group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); // new project label Label label = new Label(group, SWT.NONE); label.setText("Project name:"); label.setFont(parent.getFont()); label.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); // new project name entry field mProjectNameField = new Text(group, SWT.BORDER); GridData data = new GridData(GridData.FILL_HORIZONTAL); mProjectNameField.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); mProjectNameField.setLayoutData(data); mProjectNameField.setFont(parent.getFont()); mProjectNameField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { if (!mInternalProjectNameUpdate) { mProjectNameModifiedByUser = true; } updateLocationPathField(null); } }); } /** * Creates the group for the Project options: * [radio] Create new project * [radio] Create project from existing sources * [check] Use default location * Location [text field] [browse button] * * @param parent the parent composite */ private final void createLocationGroup(Composite parent) { Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); // Layout has 4 columns of non-equal size group.setLayout(new GridLayout()); group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); group.setFont(parent.getFont()); group.setText("Contents"); mCreateNewProjectRadio = new Button(group, SWT.RADIO); mCreateNewProjectRadio.setText("Create new project in workspace"); mCreateNewProjectRadio.setSelection(INITIAL_CREATE_NEW_PROJECT); Button existing_project_radio = new Button(group, SWT.RADIO); existing_project_radio.setText("Create project from existing source"); existing_project_radio.setSelection(INITIAL_CREATE_FROM_SOURCE); mUseDefaultLocation = new Button(group, SWT.CHECK); mUseDefaultLocation.setText("Use default location"); mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION); SelectionListener location_listener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); enableLocationWidgets(); extractNamesFromAndroidManifest(); validatePageComplete(); } }; mCreateNewProjectRadio.addSelectionListener(location_listener); existing_project_radio.addSelectionListener(location_listener); mUseDefaultLocation.addSelectionListener(location_listener); Composite location_group = new Composite(group, SWT.NONE); location_group.setLayout(new GridLayout(3, /* num columns */ false /* columns of not equal size */)); location_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); location_group.setFont(parent.getFont()); mLocationLabel = new Label(location_group, SWT.NONE); mLocationLabel.setText("Location:"); mLocationPathField = new Text(location_group, SWT.BORDER); GridData data = new GridData(GridData.FILL, /* horizontal alignment */ GridData.BEGINNING, /* vertical alignment */ true, /* grabExcessHorizontalSpace */ false, /* grabExcessVerticalSpace */ 1, /* horizontalSpan */ 1); /* verticalSpan */ mLocationPathField.setLayoutData(data); mLocationPathField.setFont(parent.getFont()); mLocationPathField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { onLocationPathFieldModified(); } }); mBrowseButton = new Button(location_group, SWT.PUSH); mBrowseButton.setText("Browse..."); setButtonLayoutData(mBrowseButton); mBrowseButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onOpenDirectoryBrowser(); } }); mCreateFromSampleRadio = new Button(group, SWT.RADIO); mCreateFromSampleRadio.setText("Create project from existing sample"); mCreateFromSampleRadio.setSelection(INITIAL_CREATE_FROM_SAMPLE); mCreateFromSampleRadio.addSelectionListener(location_listener); Composite samples_group = new Composite(group, SWT.NONE); samples_group.setLayout(new GridLayout(2, /* num columns */ false /* columns of not equal size */)); samples_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); samples_group.setFont(parent.getFont()); new Label(samples_group, SWT.NONE).setText("Samples:"); mSamplesCombo = new Combo(samples_group, SWT.DROP_DOWN | SWT.READ_ONLY); mSamplesCombo.setEnabled(false); mSamplesCombo.select(0); mSamplesCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mSamplesCombo.setToolTipText("Select a sample"); mSamplesCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onSampleSelected(); } }); } /** * Creates the target group. * It only contains an SdkTargetSelector. */ private void createTargetGroup(Composite parent) { Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); // Layout has 1 column group.setLayout(new GridLayout()); group.setLayoutData(new GridData(GridData.FILL_BOTH)); group.setFont(parent.getFont()); group.setText("Build Target"); // The selector is created without targets. They are added below in the change listener. mSdkTargetSelector = new SdkTargetSelector(group, null); mSdkTargetSelector.setSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onSdkTargetModified(); updateLocationPathField(null); validatePageComplete(); } }); mSdkTargetChangeListener = new ITargetChangeListener() { public void onSdkLoaded() { // Update the sdk target selector with the new targets // get the targets from the sdk IAndroidTarget[] targets = null; if (Sdk.getCurrent() != null) { targets = Sdk.getCurrent().getTargets(); } mSdkTargetSelector.setTargets(targets); // If there's only one target, select it. // This will invoke the selection listener on the selector defined above. if (targets != null && targets.length == 1) { mSdkTargetSelector.setSelection(targets[0]); } } public void onProjectTargetChange(IProject changedProject) { // Ignore } public void onTargetLoaded(IAndroidTarget target) { // Ignore } }; AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener); } /** * Creates the group for the project properties: * - Package name [text field] * - Activity name [text field] * - Application name [text field] * * @param parent the parent composite */ private final void createPropertiesGroup(Composite parent) { // package specification group Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); GridLayout layout = new GridLayout(); layout.numColumns = 2; group.setLayout(layout); group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); group.setFont(parent.getFont()); group.setText("Properties"); // new application label Label label = new Label(group, SWT.NONE); label.setText("Application name:"); label.setFont(parent.getFont()); label.setToolTipText("Name of the Application. This is a free string. It can be empty."); // new application name entry field mApplicationNameField = new Text(group, SWT.BORDER); GridData data = new GridData(GridData.FILL_HORIZONTAL); mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty."); mApplicationNameField.setLayoutData(data); mApplicationNameField.setFont(parent.getFont()); mApplicationNameField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { if (!mInternalApplicationNameUpdate) { mApplicationNameModifiedByUser = true; } } }); // new package label label = new Label(group, SWT.NONE); label.setText("Package name:"); label.setFont(parent.getFont()); label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components."); // new package name entry field mPackageNameField = new Text(group, SWT.BORDER); data = new GridData(GridData.FILL_HORIZONTAL); mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components."); mPackageNameField.setLayoutData(data); mPackageNameField.setFont(parent.getFont()); mPackageNameField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { onPackageNameFieldModified(); } }); // new activity label mCreateActivityCheck = new Button(group, SWT.CHECK); mCreateActivityCheck.setText("Create Activity:"); mCreateActivityCheck.setToolTipText("Specifies if you want to create a default Activity."); mCreateActivityCheck.setFont(parent.getFont()); mCreateActivityCheck.setSelection(INITIAL_CREATE_ACTIVITY); mCreateActivityCheck.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { onCreateActivityCheckModified(); enableLocationWidgets(); } }); // new activity name entry field mActivityNameField = new Text(group, SWT.BORDER); data = new GridData(GridData.FILL_HORIZONTAL); mActivityNameField.setToolTipText("Name of the Activity class to create. Must be a valid Java identifier."); mActivityNameField.setLayoutData(data); mActivityNameField.setFont(parent.getFont()); mActivityNameField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { onActivityNameFieldModified(); } }); // min sdk version label label = new Label(group, SWT.NONE); label.setText("Min SDK Version:"); label.setFont(parent.getFont()); label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty."); // min sdk version entry field mMinSdkVersionField = new Text(group, SWT.BORDER); data = new GridData(GridData.FILL_HORIZONTAL); label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty."); mMinSdkVersionField.setLayoutData(data); mMinSdkVersionField.setFont(parent.getFont()); mMinSdkVersionField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { validatePageComplete(); } }); } private void createWorkingSetGroup(final Composite composite) { Composite group = mWorkingSetGroup.createControl(composite); group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); } //--- Internal getters & setters ------------------ /** Returns the location path field value with spaces trimmed. */ private String getLocationPathFieldValue() { return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$ } /** Returns the current selected sample path, * or an empty string if there's no valid selection. */ private String getSelectedSamplePath() { int selIndex = mSamplesCombo.getSelectionIndex(); if (selIndex >= 0 && selIndex < mSamplesPaths.size()) { return mSamplesPaths.get(selIndex); } return ""; } /** Returns the current project location, depending on the Use Default Location check box * or the Create From Sample check box. */ private String getProjectLocation() { if (mInfo.isCreateFromSample()) { return getSelectedSamplePath(); } else if (mInfo.isNewProject() && mInfo.useDefaultLocation()) { return Platform.getLocation().toString(); } else { return getLocationPathFieldValue(); } } /** * 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 */ private IProject getProjectHandle() { return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName()); } // --- UI Callbacks ---- /** * Display a directory browser and update the location path field with the selected path */ private void onOpenDirectoryBrowser() { String existing_dir = getLocationPathFieldValue(); // Disable the path if it doesn't exist if (existing_dir.length() == 0) { existing_dir = null; } else { File f = new File(existing_dir); if (!f.exists()) { existing_dir = null; } } DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell()); dd.setMessage("Browse for folder"); dd.setFilterPath(existing_dir); String abs_dir = dd.open(); if (abs_dir != null) { updateLocationPathField(abs_dir); extractNamesFromAndroidManifest(); validatePageComplete(); } } /** * A sample was selected. Update the location field, manifest and validate. */ private void onSampleSelected() { if (mInfo.isCreateFromSample()) { // Note that getProjectLocation() is automatically updated to use the currently // selected sample. We just need to refresh the manifest data & validate. extractNamesFromAndroidManifest(); validatePageComplete(); } } /** * Enables or disable the location widgets depending on the user selection: * the location path is enabled when using the "existing source" mode (i.e. not new project) * or in new project mode with the "use default location" turned off. */ private void enableLocationWidgets() { boolean is_new_project = mInfo.isNewProject(); boolean is_create_from_sample = mInfo.isCreateFromSample(); boolean use_default = mInfo.useDefaultLocation() && !is_create_from_sample; boolean location_enabled = (!is_new_project || !use_default) && !is_create_from_sample; boolean create_activity = mInfo.isCreateActivity(); mUseDefaultLocation.setEnabled(is_new_project); mLocationLabel.setEnabled(location_enabled); mLocationPathField.setEnabled(location_enabled); mBrowseButton.setEnabled(location_enabled); mSamplesCombo.setEnabled(is_create_from_sample && mSamplesPaths.size() > 0); // Most fields are only editable in new-project mode. When importing // an existing project/sample we won't edit existing files anyway so the // user won't be able to customize them, mApplicationNameField.setEnabled(is_new_project); mMinSdkVersionField.setEnabled(is_new_project); mPackageNameField.setEnabled(is_new_project); mCreateActivityCheck.setEnabled(is_new_project); mActivityNameField.setEnabled(is_new_project & create_activity); updateLocationPathField(null); updatePackageAndActivityFields(); } /** * Updates the location directory path field. * <br/> * When custom user selection is enabled, use the abs_dir argument if not null and also * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the * user selection to be remembered when the user switches from default to custom. * <br/> * When custom user selection is disabled, use the workspace default location with the * current project name. This does not change the internally cached abs_dir. * * @param abs_dir A new absolute directory path or null to use the default. */ private void updateLocationPathField(String abs_dir) { // We don't touch the location path if using the "Create From Sample" mode if (mInfo.isCreateFromSample()) { return; } boolean is_new_project = mInfo.isNewProject(); boolean use_default = mInfo.useDefaultLocation(); boolean custom_location = !is_new_project || !use_default; if (!mInternalLocationPathUpdate) { mInternalLocationPathUpdate = true; if (custom_location) { if (abs_dir != null) { // We get here if the user selected a directory with the "Browse" button. // Disable auto-compute of the custom location unless the user selected // the exact same path. sAutoComputeCustomLocation = sAutoComputeCustomLocation && abs_dir.equals(sCustomLocationOsPath); sCustomLocationOsPath = TextProcessor.process(abs_dir); } else if (sAutoComputeCustomLocation || (!is_new_project && !new File(sCustomLocationOsPath).isDirectory())) { // By default select the samples directory of the current target IAndroidTarget target = mInfo.getSdkTarget(); if (target != null) { sCustomLocationOsPath = target.getPath(IAndroidTarget.SAMPLES); } // If we don't have a target, select the base directory of the // "universal sdk". If we don't even have that, use a root drive. if (sCustomLocationOsPath == null || sCustomLocationOsPath.length() == 0) { if (Sdk.getCurrent() != null) { sCustomLocationOsPath = Sdk.getCurrent().getSdkLocation(); } else { sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath(); } } } if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) { mLocationPathField.setText(sCustomLocationOsPath); } } else { String value = Platform.getLocation().append(mInfo.getProjectName()).toString(); value = TextProcessor.process(value); if (!mLocationPathField.getText().equals(value)) { mLocationPathField.setText(value); } } validatePageComplete(); mInternalLocationPathUpdate = false; } } /** * The location path field is either modified internally (from updateLocationPathField) * or manually by the user when the custom_location mode is not set. * * Ignore the internal modification. When modified by the user, memorize the choice and * validate the page. */ private void onLocationPathFieldModified() { if (!mInternalLocationPathUpdate) { // When the updates doesn't come from updateLocationPathField, it must be the user // editing the field manually, in which case we want to save the value internally // and we disable auto-compute of the custom location (to avoid overriding the user // value) String newPath = getLocationPathFieldValue(); sAutoComputeCustomLocation = sAutoComputeCustomLocation && newPath.equals(sCustomLocationOsPath); sCustomLocationOsPath = newPath; extractNamesFromAndroidManifest(); validatePageComplete(); } } /** * The package name field is either modified internally (from extractNamesFromAndroidManifest) * or manually by the user when the custom_location mode is not set. * * Ignore the internal modification. When modified by the user, memorize the choice and * validate the page. */ private void onPackageNameFieldModified() { if (mInfo.isNewProject()) { mUserPackageName = mInfo.getPackageName(); validatePageComplete(); } } /** * The create activity checkbox is either modified internally (from * extractNamesFromAndroidManifest) or manually by the user. * * Ignore the internal modification. When modified by the user, memorize the choice and * validate the page. */ private void onCreateActivityCheckModified() { if (mInfo.isNewProject() && !mInternalCreateActivityUpdate) { mUserCreateActivityCheck = mInfo.isCreateActivity(); } validatePageComplete(); } /** * The activity name field is either modified internally (from extractNamesFromAndroidManifest) * or manually by the user when the custom_location mode is not set. * * Ignore the internal modification. When modified by the user, memorize the choice and * validate the page. */ private void onActivityNameFieldModified() { if (mInfo.isNewProject() && !mInternalActivityNameUpdate) { mUserActivityName = mInfo.getActivityName(); validatePageComplete(); } } /** * Called when an SDK target is modified. * * Also changes the minSdkVersion field to reflect the sdk api level that has * just been selected. */ private void onSdkTargetModified() { IAndroidTarget target = mInfo.getSdkTarget(); loadSamplesForTarget(target); enableLocationWidgets(); onSampleSelected(); } /** * Called when the radio buttons are changed between the "create new project" and the * "use existing source" mode. This reverts the fields to whatever the user manually * entered before. */ private void updatePackageAndActivityFields() { if (mInfo.isNewProject()) { if (mUserPackageName.length() > 0 && !mPackageNameField.getText().equals(mUserPackageName)) { mPackageNameField.setText(mUserPackageName); } if (mUserActivityName.length() > 0 && !mActivityNameField.getText().equals(mUserActivityName)) { mInternalActivityNameUpdate = true; mActivityNameField.setText(mUserActivityName); mInternalActivityNameUpdate = false; } if (mUserCreateActivityCheck != mCreateActivityCheck.getSelection()) { mInternalCreateActivityUpdate = true; mCreateActivityCheck.setSelection(mUserCreateActivityCheck); mInternalCreateActivityUpdate = false; } } } /** * Extract names from an android manifest. * This is done only if the user selected the "use existing source" and a manifest xml file * can actually be found in the custom user directory. */ private void extractNamesFromAndroidManifest() { if (mInfo.isNewProject()) { return; } String projectLocation = getProjectLocation(); File f = new File(projectLocation); if (!f.isDirectory()) { return; } Path path = new Path(f.getPath()); String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString(); ManifestData manifestData = AndroidManifestHelper.parseForData(osPath); if (manifestData == null) { return; } String packageName = null; Activity activity = null; String activityName = null; String minSdkVersion = null; try { packageName = manifestData.getPackage(); minSdkVersion = manifestData.getMinSdkVersionString(); // try to get the first launcher activity. If none, just take the first activity. activity = manifestData.getLauncherActivity(); if (activity == null) { Activity[] activities = manifestData.getActivities(); if (activities != null && activities.length > 0) { activity = activities[0]; } } } catch (Exception e) { // ignore exceptions } if (packageName != null && packageName.length() > 0) { mPackageNameField.setText(packageName); } if (activity != null) { activityName = AndroidManifest.extractActivityName(activity.getName(), packageName); } if (activityName != null && activityName.length() > 0) { mInternalActivityNameUpdate = true; mInternalCreateActivityUpdate = true; mActivityNameField.setText(activityName); // we are "importing" an existing activity, not creating a new one mCreateActivityCheck.setSelection(false); mInternalCreateActivityUpdate = false; mInternalActivityNameUpdate = false; // If project name and application names are empty, use the activity // name as a default. If the activity name has dots, it's a part of a // package specification and only the last identifier must be used. if (activityName.indexOf('.') != -1) { String[] ids = activityName.split(AndroidConstants.RE_DOT); activityName = ids[ids.length - 1]; } if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) { mInternalProjectNameUpdate = true; mProjectNameModifiedByUser = false; mProjectNameField.setText(activityName); mInternalProjectNameUpdate = false; } if (mApplicationNameField.getText().length() == 0 || !mApplicationNameModifiedByUser) { mInternalApplicationNameUpdate = true; mApplicationNameModifiedByUser = false; mApplicationNameField.setText(activityName); mInternalApplicationNameUpdate = false; } } else { mInternalActivityNameUpdate = true; mInternalCreateActivityUpdate = true; mActivityNameField.setText(""); //$NON-NLS-1$ mCreateActivityCheck.setSelection(false); mInternalCreateActivityUpdate = false; mInternalActivityNameUpdate = false; // There is no activity name to use to fill in the project and application // name. However if there's a package name, we can use this as a base. if (packageName != null && packageName.length() > 0) { // Package name is a java identifier, so it's most suitable for // an application name. if (mApplicationNameField.getText().length() == 0 || !mApplicationNameModifiedByUser) { mInternalApplicationNameUpdate = true; mApplicationNameField.setText(packageName); mInternalApplicationNameUpdate = false; } // For the project name, remove any dots packageName = packageName.replace('.', '_'); if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) { mInternalProjectNameUpdate = true; mProjectNameField.setText(packageName); mInternalProjectNameUpdate = false; } } } // Select the target matching the manifest's sdk or build properties, if any IAndroidTarget foundTarget = null; // This is the target currently in the UI IAndroidTarget currentTarget = mInfo.getSdkTarget(); // If there's a current target defined, we do not allow to change it when // operating in the create-from-sample mode -- since the available sample list // is tied to the current target, so changing it would invalidate the project we're // trying to load in the first place. if (currentTarget == null || !mInfo.isCreateFromSample()) { ProjectPropertiesWorkingCopy p = ProjectProperties.create(projectLocation, null); if (p != null) { // Check the {build|default}.properties files if present p.merge(PropertyType.BUILD).merge(PropertyType.DEFAULT); String v = p.getProperty(ProjectProperties.PROPERTY_TARGET); IAndroidTarget desiredTarget = Sdk.getCurrent().getTargetFromHashString(v); // We can change the current target if: // - we found a new desired target // - there is no current target // - or the current target can't run the desired target if (desiredTarget != null && (currentTarget == null || !desiredTarget.canRunOn(currentTarget))) { foundTarget = desiredTarget; } } if (foundTarget == null && minSdkVersion != null) { // Otherwise try to match the requested min-sdk-version if we find an // exact match, regardless of the currently selected target. for (IAndroidTarget existingTarget : mSdkTargetSelector.getTargets()) { if (existingTarget != null && existingTarget.getVersion().equals(minSdkVersion)) { foundTarget = existingTarget; break; } } } if (foundTarget == null) { // Or last attempt, try to match a sample project location and use it // if we find an exact match, regardless of the currently selected target. for (IAndroidTarget existingTarget : mSdkTargetSelector.getTargets()) { if (existingTarget != null && projectLocation.startsWith(existingTarget.getLocation())) { foundTarget = existingTarget; break; } } } } if (foundTarget != null) { mSdkTargetSelector.setSelection(foundTarget); } // It's OK for an import to not a minSdkVersion and we should respect it. mMinSdkVersionField.setText(minSdkVersion == null ? "" : minSdkVersion); //$NON-NLS-1$ } /** * Updates the list of all samples for the given target SDK. * The list is stored in mSamplesPaths as absolute directory paths. * The combo is recreated to match this. */ private void loadSamplesForTarget(IAndroidTarget target) { // Keep the name of the old selection (if there were any samples) String oldChoice = null; if (mSamplesPaths.size() > 0) { int selIndex = mSamplesCombo.getSelectionIndex(); if (selIndex > -1) { oldChoice = mSamplesCombo.getItem(selIndex); } } // Clear all current content mSamplesCombo.removeAll(); mSamplesPaths.clear(); if (target != null) { // Get the sample root path and recompute the list of samples String samplesRootPath = target.getPath(IAndroidTarget.SAMPLES); File samplesDir = new File(samplesRootPath); findSamplesManifests(samplesDir, mSamplesPaths); if (mSamplesPaths.size() == 0) { // Odd, this target has no samples. Could happen with an addon. mSamplesCombo.add("This target has no samples. Please select another target."); mSamplesCombo.select(0); return; } // Recompute the description of each sample (the relative path // to the sample root). Also try to find the old selection. int selIndex = 0; int i = 0; int n = samplesRootPath.length(); for (String path : mSamplesPaths) { if (path.length() > n) { path = path.substring(n); if (path.charAt(0) == File.separatorChar) { path = path.substring(1); } if (path.endsWith(File.separator)) { path = path.substring(0, path.length() - 1); } path = path.replaceAll(Pattern.quote(File.separator), " > "); } if (oldChoice != null && oldChoice.equals(path)) { selIndex = i; } mSamplesCombo.add(path); i++; } mSamplesCombo.select(selIndex); } else { mSamplesCombo.add("Please select a target."); mSamplesCombo.select(0); } } /** * Recursively find potential sample directories under the given directory. * Actually lists any directory that contains an android manifest. * Paths found are added the samplesPaths list. */ private void findSamplesManifests(File samplesDir, ArrayList<String> samplesPaths) { if (!samplesDir.isDirectory()) { return; } for (File f : samplesDir.listFiles()) { if (f.isDirectory()) { // Assume this is a sample if it contains an android manifest. File manifestFile = new File(f, SdkConstants.FN_ANDROID_MANIFEST_XML); if (manifestFile.isFile()) { samplesPaths.add(f.getPath()); } // Recurse in the project, to find embedded tests sub-projects // We can however skip this recursion for known android sub-dirs that // can't have projects, namely for sources, assets and resources. String leaf = f.getName(); if (!SdkConstants.FD_SOURCES.equals(leaf) && !SdkConstants.FD_ASSETS.equals(leaf) && !SdkConstants.FD_RES.equals(leaf)) { findSamplesManifests(f, samplesPaths); } } } } /** * Returns whether this page's controls currently all contain valid values. * * @return <code>true</code> if all controls are valid, and * <code>false</code> if at least one is invalid */ private boolean validatePage() { IWorkspace workspace = ResourcesPlugin.getWorkspace(); int status = validateProjectField(workspace); if ((status & MSG_ERROR) == 0) { status |= validateSdkTarget(); } if ((status & MSG_ERROR) == 0) { status |= validateLocationPath(workspace); } if ((status & MSG_ERROR) == 0) { status |= validatePackageField(); } if ((status & MSG_ERROR) == 0) { status |= validateActivityField(); } if ((status & MSG_ERROR) == 0) { status |= validateMinSdkVersionField(); } if ((status & MSG_ERROR) == 0) { status |= validateSourceFolder(); } if (status == MSG_NONE) { setStatus(null, MSG_NONE); } // Return false if there's an error so that the finish button be disabled. return (status & MSG_ERROR) == 0; } /** * Validates the page and updates the Next/Finish buttons */ private void validatePageComplete() { setPageComplete(validatePage()); } /** * Validates the project name field. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateProjectField(IWorkspace workspace) { // Validate project field String projectName = mInfo.getProjectName(); if (projectName.length() == 0) { return setStatus("Project name must be specified", MSG_ERROR); } // Limit the project name to shell-agnostic characters since it will be used to // generate the final package if (!sProjectNamePattern.matcher(projectName).matches()) { return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.", MSG_ERROR); } IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT); if (!nameStatus.isOK()) { return setStatus(nameStatus.getMessage(), MSG_ERROR); } if (getProjectHandle().exists()) { return setStatus("A project with that name already exists in the workspace", MSG_ERROR); } if (mTestInfo != null && mTestInfo.getCreateTestProject() && projectName.equals(mTestInfo.getProjectName())) { return setStatus("The main project name and the test project name must be different.", MSG_WARNING); } return MSG_NONE; } /** * Validates the location path field. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateLocationPath(IWorkspace workspace) { Path path = new Path(getProjectLocation()); if (mInfo.isNewProject()) { if (!mInfo.useDefaultLocation()) { // If not using the default value validate the location. URI uri = URIUtil.toURI(path.toOSString()); IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(), uri); if (!locationStatus.isOK()) { return setStatus(locationStatus.getMessage(), MSG_ERROR); } else { // The location is valid as far as Eclipse is concerned (i.e. mostly not // an existing workspace project.) Check it either doesn't exist or is // a directory that is empty. File f = path.toFile(); if (f.exists() && !f.isDirectory()) { return setStatus("A directory name must be specified.", MSG_ERROR); } else if (f.isDirectory()) { // However if the directory exists, we should put a warning if it is not // empty. We don't put an error (we'll ask the user again for confirmation // before using the directory.) String[] l = f.list(); if (l.length != 0) { return setStatus("The selected output directory is not empty.", MSG_WARNING); } } } } else { // Otherwise validate the path string is not empty if (getProjectLocation().length() == 0) { return setStatus("A directory name must be specified.", MSG_ERROR); } File dest = path.append(mInfo.getProjectName()).toFile(); if (dest.exists()) { return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.", mInfo.getProjectName()), MSG_ERROR); } } } else { // Must be an existing directory File f = path.toFile(); if (!f.isDirectory()) { return setStatus("An existing directory name must be specified.", MSG_ERROR); } // Check there's an android manifest in the directory String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString(); File manifestFile = new File(osPath); if (!manifestFile.isFile()) { return setStatus( String.format("File %1$s not found in %2$s.", SdkConstants.FN_ANDROID_MANIFEST_XML, f.getName()), MSG_ERROR); } // Parse it and check the important fields. ManifestData manifestData = AndroidManifestHelper.parseForData(osPath); if (manifestData == null) { return setStatus( String.format("File %1$s could not be parsed.", osPath), MSG_ERROR); } String packageName = manifestData.getPackage(); if (packageName == null || packageName.length() == 0) { return setStatus( String.format("No package name defined in %1$s.", osPath), MSG_ERROR); } Activity[] activities = manifestData.getActivities(); if (activities == null || activities.length == 0) { // This is acceptable now as long as no activity needs to be created if (mInfo.isCreateActivity()) { return setStatus( String.format("No activity name defined in %1$s.", osPath), MSG_ERROR); } } // If there's already a .project, tell the user to use import instead. if (path.append(".project").toFile().exists()) { //$NON-NLS-1$ return setStatus("An Eclipse project already exists in this directory. Consider using File > Import > Existing Project instead.", MSG_WARNING); } } return MSG_NONE; } /** * Validates the sdk target choice. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateSdkTarget() { if (mInfo.getSdkTarget() == null) { return setStatus("An SDK Target must be specified.", MSG_ERROR); } return MSG_NONE; } /** * Validates the sdk target choice. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateMinSdkVersionField() { // If the current target is a preview, explicitly indicate minSdkVersion // must be set to this target name. // Since the field is only editable in new-project mode, we can't produce an // error when importing an existing project. if (mInfo.isNewProject() && mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getVersion().isPreview() && mInfo.getSdkTarget().getVersion().equals(mInfo.getMinSdkVersion()) == false) { return setStatus( String.format("The SDK target is a preview. Min SDK Version must be set to '%s'.", mInfo.getSdkTarget().getVersion().getCodename()), MSG_ERROR); } // If the min sdk version is empty, it is always accepted. if (mInfo.getMinSdkVersion().length() == 0) { return MSG_NONE; } if (mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getVersion().equals(mInfo.getMinSdkVersion()) == false) { return setStatus("The API level for the selected SDK target does not match the Min SDK Version.", mInfo.getSdkTarget().getVersion().isPreview() ? MSG_ERROR : MSG_WARNING); } return MSG_NONE; } /** * Validates the activity name field. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateActivityField() { // Disregard if not creating an activity if (!mInfo.isCreateActivity()) { return MSG_NONE; } // Validate activity field String activityFieldContents = mInfo.getActivityName(); if (activityFieldContents.length() == 0) { return setStatus("Activity name must be specified.", MSG_ERROR); } // The activity field can actually contain part of a sub-package name // or it can start with a dot "." to indicates it comes from the parent package name. String packageName = ""; //$NON-NLS-1$ int pos = activityFieldContents.lastIndexOf('.'); if (pos >= 0) { packageName = activityFieldContents.substring(0, pos); if (packageName.startsWith(".")) { //$NON-NLS-1$ packageName = packageName.substring(1); } activityFieldContents = activityFieldContents.substring(pos + 1); } // the activity field can contain a simple java identifier, or a // package name or one that starts with a dot. So if it starts with a dot, // ignore this dot -- the rest must look like a package name. if (activityFieldContents.charAt(0) == '.') { activityFieldContents = activityFieldContents.substring(1); } // Check it's a valid activity string int result = MSG_NONE; IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ if (!status.isOK()) { result = setStatus(status.getMessage(), status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); } // Check it's a valid package string if (result != MSG_ERROR && packageName.length() > 0) { status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ if (!status.isOK()) { result = setStatus(status.getMessage() + " (in the activity name)", status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); } } return result; } /** * Validates the package name field. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validatePackageField() { // Validate package field String packageFieldContents = mInfo.getPackageName(); if (packageFieldContents.length() == 0) { return setStatus("Package name must be specified.", MSG_ERROR); } // Check it's a valid package string int result = MSG_NONE; IStatus status = JavaConventions.validatePackageName(packageFieldContents, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ if (!status.isOK()) { result = setStatus(status.getMessage(), status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); } // The Android Activity Manager does not accept packages names with only one // identifier. Check the package name has at least one dot in them (the previous rule // validated that if such a dot exist, it's not the first nor last characters of the // string.) if (result != MSG_ERROR && packageFieldContents.indexOf('.') == -1) { return setStatus("Package name must have at least two identifiers.", MSG_ERROR); } return result; } /** * Validates that an existing project actually has a source folder. * * For project in "use existing source" mode, this tries to find the source folder. * A source folder should be just under the project directory and it should have all * the directories composing the package+activity name. * * As a side effect, it memorizes the source folder in mSourceFolder. * * TODO: support multiple source folders for multiple activities. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateSourceFolder() { // This check does nothing when creating a new project. // This check is also useless when no activity is present or created. if (mInfo.isNewProject() || !mInfo.isCreateActivity()) { return MSG_NONE; } String osTarget = mInfo.getActivityName(); if (osTarget.indexOf('.') == -1) { osTarget = mInfo.getPackageName() + File.separator + osTarget; } else if (osTarget.indexOf('.') == 0) { osTarget = mInfo.getPackageName() + osTarget; } osTarget = osTarget.replace('.', File.separatorChar) + AndroidConstants.DOT_JAVA; String projectPath = getProjectLocation(); File projectDir = new File(projectPath); File[] all_dirs = projectDir.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isDirectory(); } }); for (File f : all_dirs) { Path path = new Path(f.getAbsolutePath()); File java_activity = path.append(osTarget).toFile(); if (java_activity.isFile()) { mSourceFolder = f.getName(); return MSG_NONE; } } if (all_dirs.length > 0) { return setStatus( String.format("%1$s can not be found under %2$s.", osTarget, projectPath), MSG_ERROR); } else { return setStatus( String.format("No source folders can be found in %1$s.", projectPath), MSG_ERROR); } } /** * Sets the error message for the wizard with the given message icon. * * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING. * @return As a convenience, always returns messageType so that the caller can return * immediately. */ private int setStatus(String message, int messageType) { if (message == null) { setErrorMessage(null); setMessage(null); } else if (!message.equals(getMessage())) { setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR); } return messageType; } /** * Returns the working sets to which the new project should be added. * * @return the selected working sets to which the new project should be added */ public IWorkingSet[] getWorkingSets() { return mWorkingSetGroup.getSelectedWorkingSets(); } /** * Sets the working sets to which the new project should be added. * * @param workingSets the initial selected working sets */ public void setWorkingSets(IWorkingSet[] workingSets) { assert workingSets != null; mWorkingSetGroup.setWorkingSets(workingSets); } }