diff options
Diffstat (limited to 'eclipse/plugins')
18 files changed, 2260 insertions, 453 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index dc6062d..1d0a18d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -90,7 +90,7 @@ class="com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard" finalPerspective="org.eclipse.jdt.ui.JavaPerspective" hasPages="true" - icon="icons/android.png" + icon="icons/new_adt_project.png" id="com.android.ide.eclipse.adt.project.NewProjectWizard" name="Android Project" preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective" @@ -98,10 +98,22 @@ <wizard canFinishEarly="false" category="com.android.ide.eclipse.wizards.category" - class="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard" + class="com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectWizard" finalPerspective="org.eclipse.jdt.ui.JavaPerspective" hasPages="true" - icon="icons/android.png" + icon="icons/androidjunit.png" + id="com.android.ide.eclipse.adt.project.NewTestProjectWizard" + name="Android Test Project" + preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective" + project="true"> + </wizard> + <wizard + canFinishEarly="false" + category="com.android.ide.eclipse.wizards.category" + class="com.android.ide.eclipse.adt.wizards.newxmlfile.NewXmlFileWizard" + finalPerspective="org.eclipse.jdt.ui.JavaPerspective" + hasPages="true" + icon="icons/new_xml.png" id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard" name="Android XML File" preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective" @@ -220,11 +232,22 @@ value="com.android.ide.eclipse.adt.AndroidNature"> </filter> <action - class="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction" + class="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction" enablesFor="1" - id="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction" + icon="icons/new_xml.png" + id="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction" label="New Resource File..." - menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"> + menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1" + tooltip="Opens a wizard to help create a new Android XML Resource file"> + </action> + <action + class="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction" + enablesFor="1" + icon="icons/androidjunit.png" + id="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction" + label="New Test Project..." + menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1" + tooltip="Opens a wizard to help create a new Android Test Project"> </action> <action class="com.android.ide.eclipse.adt.project.ExportAction" @@ -484,6 +507,15 @@ tooltip="Opens a wizard to help create a new Android XML file"> </action> <action + class="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction" + icon="icons/androidjunit.png" + id="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction" + label="New Android Test Project" + style="push" + toolbarPath="android_project" + tooltip="Opens a wizard to help create a new Android Test Project"> + </action> + <action class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction" icon="icons/new_adt_project.png" id="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction" diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java index e091b13..08890a2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java @@ -71,6 +71,22 @@ public final class ProjectHelper { } /** + * Adds the corresponding source folder to the project's class path entries. + * + * @param javaProject The java project of which path entries to update. + * @param new_entry The parent source folder to remove. + * @throws JavaModelException + */ + public static void addEntryToClasspath( + IJavaProject javaProject, IClasspathEntry new_entry) + throws JavaModelException { + + IClasspathEntry[] entries = javaProject.getRawClasspath(); + entries = addEntryToClasspath(entries, new_entry); + javaProject.setRawClasspath(entries, new NullProgressMonitor()); + } + + /** * Remove a classpath entry from the array. * @param entries The class path entries to read. A copy will be returned * @param index The index to remove. @@ -265,6 +281,8 @@ public final class ProjectHelper { // If needed, check and fix compiler compliance and source compatibility ProjectHelper.checkAndFixCompilerCompliance(javaProject); } + + /** * Checks the project compiler compliance level is supported. * @param javaProject The project to check diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java index 7303b02..0a365e6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java @@ -200,7 +200,8 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage label.setText("Configuration:"); mConfigSelector = new ConfigurationSelector(group); - GridData gd = new GridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); + GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); + gd.horizontalSpan = 2; gd.widthHint = ConfigurationSelector.WIDTH_HINT; gd.heightHint = ConfigurationSelector.HEIGHT_HINT; mConfigSelector.setLayoutData(gd); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java index e0d0d5e..49766d1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java @@ -4,7 +4,7 @@ * 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 @@ -23,7 +23,9 @@ import org.eclipse.ui.IWorkbenchWizard; /** * Delegate for the toolbar action "Android Project". - * It displays the Android New Project wizard. + * It displays the Android New Project wizard to create a new Android Project (not a test project). + * + * @see NewTestProjectAction */ public class NewProjectAction extends OpenWizardAction { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java new file mode 100755 index 0000000..9cdf098 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009 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. + */ + +package com.android.ide.eclipse.adt.wizards.actions; + +import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectWizard; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ui.IWorkbenchWizard; + +/** + * Delegate for the toolbar action "Android Test Project". + * It displays the Android New Project wizard to create a new Test Project. + */ +public class NewTestProjectAction extends OpenWizardAction { + + @Override + protected IWorkbenchWizard instanciateWizard(IAction action) { + return new NewTestProjectWizard(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java index d1530d4..3ae66df 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java @@ -22,7 +22,9 @@ import org.eclipse.jface.action.IAction; import org.eclipse.ui.IWorkbenchWizard; /** - * Delegate for the toolbar action "Android Project". + * Delegate for the toolbar action "Android Project" or for the + * project > Android Project context menu. + * * It displays the Android New XML file wizard. */ public class NewXmlFileAction extends OpenWizardAction { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java deleted file mode 100644 index 20cfc82..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2008 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. - */ - -package com.android.ide.eclipse.adt.wizards.actions; - -import com.android.ide.eclipse.adt.wizards.newxmlfile.NewXmlFileWizard; - -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.wizard.WizardDialog; -import org.eclipse.ui.IObjectActionDelegate; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPart; - -public class NewXmlFileWizardAction implements IObjectActionDelegate { - - private ISelection mSelection; - private IWorkbench mWorkbench; - - /** - * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart) - */ - public void setActivePart(IAction action, IWorkbenchPart targetPart) { - mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench(); - } - - public void run(IAction action) { - if (mSelection instanceof IStructuredSelection) { - IStructuredSelection selection = (IStructuredSelection)mSelection; - - // call the new xml file wizard on the current selection. - NewXmlFileWizard wizard = new NewXmlFileWizard(); - wizard.init(mWorkbench, selection); - WizardDialog dialog = new WizardDialog(mWorkbench.getDisplay().getActiveShell(), - wizard); - dialog.open(); - } - } - - public void selectionChanged(IAction action, ISelection selection) { - this.mSelection = selection; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java index 3f0393d..c905d6a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java @@ -4,7 +4,7 @@ * 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 @@ -28,6 +28,7 @@ import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IObjectActionDelegate; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; @@ -43,7 +44,8 @@ import org.eclipse.ui.internal.util.Util; * An abstract action that displays one of our wizards. * Derived classes must provide the actual wizard to display. */ -/*package*/ abstract class OpenWizardAction implements IWorkbenchWindowActionDelegate { +/*package*/ abstract class OpenWizardAction + implements IWorkbenchWindowActionDelegate, IObjectActionDelegate { /** * The wizard dialog width, extracted from {@link NewWizardShortcutAction} @@ -60,6 +62,9 @@ import org.eclipse.ui.internal.util.Util; /** The result from the dialog */ private int mDialogResult; + private ISelection mSelection; + private IWorkbench mWorkbench; + /** Returns the wizard that was created by {@link #run(IAction)}. */ public IWorkbenchWizard getWizard() { return mWizard; @@ -70,7 +75,7 @@ import org.eclipse.ui.internal.util.Util; public int getDialogResult() { return mDialogResult; } - + /* (non-Javadoc) * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose() */ @@ -89,19 +94,23 @@ import org.eclipse.ui.internal.util.Util; * Opens and display the Android New Project Wizard. * <p/> * Most of this implementation is extracted from {@link NewWizardShortcutAction#run()}. - * + * * @param action The action that got us here. Can be null when used internally. * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction) */ public void run(IAction action) { // get the workbench and the current window - IWorkbench workbench = PlatformUI.getWorkbench(); + IWorkbench workbench = mWorkbench != null ? mWorkbench : PlatformUI.getWorkbench(); IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); - + // This code from NewWizardShortcutAction#run() gets the current window selection // and converts it to a workbench structured selection for the wizard, if possible. - ISelection selection = window.getSelectionService().getSelection(); + ISelection selection = mSelection; + if (selection == null) { + selection = window.getSelectionService().getSelection(); + } + IStructuredSelection selectionToPass = StructuredSelection.EMPTY; if (selection instanceof IStructuredSelection) { selectionToPass = (IStructuredSelection) selection; @@ -123,16 +132,16 @@ import org.eclipse.ui.internal.util.Util; // Create the wizard and initialize it with the selection mWizard = instanciateWizard(action); mWizard.init(workbench, selectionToPass); - + // It's not visible yet until a dialog is created and opened Shell parent = window.getShell(); WizardDialogEx dialog = new WizardDialogEx(parent, mWizard); dialog.create(); - + if (mWizard instanceof IUpdateWizardDialog) { ((IUpdateWizardDialog) mWizard).updateWizardDialog(dialog); } - + // This code comes straight from NewWizardShortcutAction#run() Point defaultSize = dialog.getShell().getSize(); dialog.getShell().setSize( @@ -140,13 +149,13 @@ import org.eclipse.ui.internal.util.Util; Math.max(SIZING_WIZARD_HEIGHT, defaultSize.y)); window.getWorkbench().getHelpSystem().setHelp(dialog.getShell(), IWorkbenchHelpContextIds.NEW_WIZARD_SHORTCUT); - + mDialogResult = dialog.open(); } /** * Called by {@link #run(IAction)} to instantiate the actual wizard. - * + * * @param action The action parameter from {@link #run(IAction)}. * This can be null. * @return A new wizard instance. Must not be null. @@ -157,7 +166,13 @@ import org.eclipse.ui.internal.util.Util; * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection) */ public void selectionChanged(IAction action, ISelection selection) { - // pass + mSelection = selection; } + /* (non-Javadoc) + * @see org.eclipse.ui.IObjectActionDelegate#setActivePart(org.eclipse.jface.action.IAction, org.eclipse.ui.IWorkbenchPart) + */ + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench(); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java index 20aa68b..e666390 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java @@ -25,6 +25,7 @@ package com.android.ide.eclipse.adt.wizards.newproject; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener; +import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectCreationPage.TestInfo; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.AndroidManifestParser.Activity; @@ -79,11 +80,13 @@ import java.util.regex.Pattern; * 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. + * 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$ @@ -94,7 +97,7 @@ public class NewProjectCreationPage extends WizardPage { 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. */ @@ -106,7 +109,13 @@ public class NewProjectCreationPage extends WizardPage { 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; @@ -128,98 +137,160 @@ public class NewProjectCreationPage extends WizardPage { private ITargetChangeListener mSdkTargetChangeListener; private boolean mInternalLocationPathUpdate; - protected boolean mInternalProjectNameUpdate; - protected boolean mInternalApplicationNameUpdate; + private boolean mInternalProjectNameUpdate; + private boolean mInternalApplicationNameUpdate; private boolean mInternalCreateActivityUpdate; private boolean mInternalActivityNameUpdate; - protected boolean mProjectNameModifiedByUser; - protected boolean mApplicationNameModifiedByUser; + private boolean mProjectNameModifiedByUser; + private boolean mApplicationNameModifiedByUser; private boolean mInternalMinSdkVersionUpdate; - private boolean mMinSdkVersionModifiedByUser; /** * Creates a new project creation wizard page. - * - * @param pageName the name of this page */ - public NewProjectCreationPage(String pageName) { - super(pageName); + public NewProjectCreationPage() { + super(MAIN_PAGE_NAME); setPageComplete(false); + setTitle("New Android Project"); + setDescription("Creates a new Android Project resource."); } // --- Getters used by NewProjectWizard --- + /** - * 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. + * 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 IPath getLocationPath() { - return new Path(getProjectLocation()); + 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 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(); - } + /** + * 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 activity name field with spaces trimmed. */ - public String getActivityName() { - return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim(); - } + /** 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 min sdk version field with spaces trimmed. */ - public String getMinSdkVersion() { - return mMinSdkVersionField == null ? "" : mMinSdkVersionField.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 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 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 "Create New Project" radio. */ - public boolean isNewProject() { - return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT - : mCreateNewProjectRadio.getSelection(); - } + /** 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 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 value of the "Create New Project" radio. */ + public boolean isNewProject() { + return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT + : mCreateNewProjectRadio.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 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 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; } - - /** Returns the current sdk target or null if none has been selected yet. */ - public IAndroidTarget getSdkTarget() { - return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected(); + + /** + * 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; } /** @@ -231,6 +302,7 @@ public class NewProjectCreationPage extends WizardPage { super.setVisible(visible); if (visible) { mProjectNameField.setFocus(); + validatePageComplete(); } } @@ -265,17 +337,17 @@ public class NewProjectCreationPage extends WizardPage { setControl(composite); // Validate. This will complain about the first empty field. - setPageComplete(validatePage()); + validatePageComplete(); } - + @Override public void dispose() { - + if (mSdkTargetChangeListener != null) { AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener); mSdkTargetChangeListener = null; } - + super.dispose(); } @@ -328,7 +400,7 @@ public class NewProjectCreationPage extends WizardPage { 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_BOTH)); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); group.setFont(parent.getFont()); group.setText("Contents"); @@ -349,7 +421,7 @@ public class NewProjectCreationPage extends WizardPage { super.widgetSelected(e); enableLocationWidgets(); extractNamesFromAndroidManifest(); - setPageComplete(validatePage()); + validatePageComplete(); } }; @@ -358,9 +430,9 @@ public class NewProjectCreationPage extends WizardPage { mUseDefaultLocation.addSelectionListener(location_listener); Composite location_group = new Composite(group, SWT.NONE); - location_group.setLayout(new GridLayout(4, /* num columns */ + location_group.setLayout(new GridLayout(3, /* num columns */ false /* columns of not equal size */)); - location_group.setLayoutData(new GridData(GridData.FILL_BOTH)); + location_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); location_group.setFont(parent.getFont()); mLocationLabel = new Label(location_group, SWT.NONE); @@ -371,7 +443,7 @@ public class NewProjectCreationPage extends WizardPage { GridData.BEGINNING, /* vertical alignment */ true, /* grabExcessHorizontalSpace */ false, /* grabExcessVerticalSpace */ - 2, /* horizontalSpan */ + 1, /* horizontalSpan */ 1); /* verticalSpan */ mLocationPathField.setLayoutData(data); mLocationPathField.setFont(parent.getFont()); @@ -387,7 +459,7 @@ public class NewProjectCreationPage extends WizardPage { mBrowseButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - openDirectoryBrowser(); + onOpenDirectoryBrowser(); } }); } @@ -403,7 +475,7 @@ public class NewProjectCreationPage extends WizardPage { 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); @@ -428,52 +500,23 @@ public class NewProjectCreationPage extends WizardPage { } } }; - + AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener); - + // Invoke it once to initialize the targets mSdkTargetChangeListener.onTargetsLoaded(); - + mSdkTargetSelector.setSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onSdkTargetModified(); updateLocationPathField(null); - setPageComplete(validatePage()); + validatePageComplete(); } }); } /** - * Display a directory browser and update the location path field with the selected path - */ - private void openDirectoryBrowser() { - - 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(); - setPageComplete(validatePage()); - } - } - - /** * Creates the group for the project properties: * - Package name [text field] * - Activity name [text field] @@ -508,7 +551,7 @@ public class NewProjectCreationPage extends WizardPage { if (!mInternalApplicationNameUpdate) { mApplicationNameModifiedByUser = true; } - } + } }); // new package label @@ -569,7 +612,7 @@ public class NewProjectCreationPage extends WizardPage { mMinSdkVersionField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { onMinSdkVersionFieldModified(); - setPageComplete(validatePage()); + validatePageComplete(); } }); } @@ -579,12 +622,12 @@ public class NewProjectCreationPage extends WizardPage { /** Returns the location path field value with spaces trimmed. */ private String getLocationPathFieldValue() { - return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); + return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$ } /** Returns the current project location, depending on the Use Default Location check box. */ - public String getProjectLocation() { - if (isNewProject() && useDefaultLocation()) { + private String getProjectLocation() { + if (mInfo.isNewProject() && mInfo.useDefaultLocation()) { return Platform.getLocation().toString(); } else { return getLocationPathFieldValue(); @@ -603,22 +646,51 @@ public class NewProjectCreationPage extends WizardPage { * @return the new project resource handle */ private IProject getProjectHandle() { - return ResourcesPlugin.getWorkspace().getRoot().getProject(getProjectName()); + 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(); + } + } + + /** * 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 = isNewProject(); - boolean use_default = useDefaultLocation(); + boolean is_new_project = mInfo.isNewProject(); + boolean use_default = mInfo.useDefaultLocation(); boolean location_enabled = !is_new_project || !use_default; - boolean create_activity = isCreateActivity(); - + boolean create_activity = mInfo.isCreateActivity(); + mUseDefaultLocation.setEnabled(is_new_project); mLocationLabel.setEnabled(location_enabled); @@ -646,8 +718,8 @@ public class NewProjectCreationPage extends WizardPage { * @param abs_dir A new absolute directory path or null to use the default. */ private void updateLocationPathField(String abs_dir) { - boolean is_new_project = isNewProject(); - boolean use_default = useDefaultLocation(); + boolean is_new_project = mInfo.isNewProject(); + boolean use_default = mInfo.useDefaultLocation(); boolean custom_location = !is_new_project || !use_default; if (!mInternalLocationPathUpdate) { @@ -663,7 +735,7 @@ public class NewProjectCreationPage extends WizardPage { } else if (sAutoComputeCustomLocation || (!is_new_project && !new File(sCustomLocationOsPath).isDirectory())) { // By default select the samples directory of the current target - IAndroidTarget target = getSdkTarget(); + IAndroidTarget target = mInfo.getSdkTarget(); if (target != null) { sCustomLocationOsPath = target.getPath(IAndroidTarget.SAMPLES); } @@ -682,13 +754,13 @@ public class NewProjectCreationPage extends WizardPage { mLocationPathField.setText(sCustomLocationOsPath); } } else { - String value = Platform.getLocation().append(getProjectName()).toString(); + String value = Platform.getLocation().append(mInfo.getProjectName()).toString(); value = TextProcessor.process(value); if (!mLocationPathField.getText().equals(value)) { mLocationPathField.setText(value); } } - setPageComplete(validatePage()); + validatePageComplete(); mInternalLocationPathUpdate = false; } } @@ -711,7 +783,7 @@ public class NewProjectCreationPage extends WizardPage { newPath.equals(sCustomLocationOsPath); sCustomLocationOsPath = newPath; extractNamesFromAndroidManifest(); - setPageComplete(validatePage()); + validatePageComplete(); } } @@ -723,9 +795,9 @@ public class NewProjectCreationPage extends WizardPage { * validate the page. */ private void onPackageNameFieldModified() { - if (isNewProject()) { - mUserPackageName = getPackageName(); - setPageComplete(validatePage()); + if (mInfo.isNewProject()) { + mUserPackageName = mInfo.getPackageName(); + validatePageComplete(); } } @@ -737,10 +809,10 @@ public class NewProjectCreationPage extends WizardPage { * validate the page. */ private void onCreateActivityCheckModified() { - if (isNewProject() && !mInternalCreateActivityUpdate) { - mUserCreateActivityCheck = isCreateActivity(); + if (mInfo.isNewProject() && !mInternalCreateActivityUpdate) { + mUserCreateActivityCheck = mInfo.isCreateActivity(); } - setPageComplete(validatePage()); + validatePageComplete(); } /** @@ -751,15 +823,15 @@ public class NewProjectCreationPage extends WizardPage { * validate the page. */ private void onActivityNameFieldModified() { - if (isNewProject() && !mInternalActivityNameUpdate) { - mUserActivityName = getActivityName(); - setPageComplete(validatePage()); + if (mInfo.isNewProject() && !mInternalActivityNameUpdate) { + mUserActivityName = mInfo.getActivityName(); + validatePageComplete(); } } /** * Called when the min sdk version field has been modified. - * + * * Ignore the internal modifications. When modified by the user, try to match * a target with the same API level. */ @@ -769,16 +841,16 @@ public class NewProjectCreationPage extends WizardPage { } try { - int version = Integer.parseInt(getMinSdkVersion()); - + int version = Integer.parseInt(mInfo.getMinSdkVersion()); + // Before changing, compare with the currently selected one, if any. // There can be multiple targets with the same sdk api version, so don't change // it if it's already at the right version. - IAndroidTarget curr_target = getSdkTarget(); + IAndroidTarget curr_target = mInfo.getSdkTarget(); if (curr_target != null && curr_target.getApiVersionNumber() == version) { return; } - + for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { if (target.getApiVersionNumber() == version) { mSdkTargetSelector.setSelection(target); @@ -788,20 +860,18 @@ public class NewProjectCreationPage extends WizardPage { } catch (NumberFormatException e) { // ignore } - - mMinSdkVersionModifiedByUser = true; } - + /** * Called when an SDK target is modified. - * - * If the minSdkVersion field hasn't been modified by the user yet, we change it - * to reflect the sdk api level that has just been selected. + * + * Also changes the minSdkVersion field to reflect the sdk api level that has + * just been selected. */ private void onSdkTargetModified() { - IAndroidTarget target = getSdkTarget(); - - if (target != null && !mMinSdkVersionModifiedByUser) { + IAndroidTarget target = mInfo.getSdkTarget(); + + if (target != null) { mInternalMinSdkVersionUpdate = true; mMinSdkVersionField.setText(Integer.toString(target.getApiVersionNumber())); mInternalMinSdkVersionUpdate = false; @@ -814,7 +884,7 @@ public class NewProjectCreationPage extends WizardPage { * entered before. */ private void updatePackageAndActivityFields() { - if (isNewProject()) { + if (mInfo.isNewProject()) { if (mUserPackageName.length() > 0 && !mPackageNameField.getText().equals(mUserPackageName)) { mPackageNameField.setText(mUserPackageName); @@ -826,7 +896,7 @@ public class NewProjectCreationPage extends WizardPage { mActivityNameField.setText(mUserActivityName); mInternalActivityNameUpdate = false; } - + if (mUserCreateActivityCheck != mCreateActivityCheck.getSelection()) { mInternalCreateActivityUpdate = true; mCreateActivityCheck.setSelection(mUserCreateActivityCheck); @@ -841,7 +911,7 @@ public class NewProjectCreationPage extends WizardPage { * can actually be found in the custom user directory. */ private void extractNamesFromAndroidManifest() { - if (isNewProject()) { + if (mInfo.isNewProject()) { return; } @@ -853,12 +923,12 @@ public class NewProjectCreationPage extends WizardPage { Path path = new Path(f.getPath()); String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); - + AndroidManifestParser manifestData = AndroidManifestParser.parseForData(osPath); if (manifestData == null) { return; } - + String packageName = null; Activity activity = null; String activityName = null; @@ -882,7 +952,7 @@ public class NewProjectCreationPage extends WizardPage { if (packageName != null && packageName.length() > 0) { mPackageNameField.setText(packageName); } - + if (activity != null) { activityName = AndroidManifestParser.extractActivityName(activity.getName(), packageName); @@ -922,7 +992,7 @@ public class NewProjectCreationPage extends WizardPage { 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) { @@ -944,13 +1014,13 @@ public class NewProjectCreationPage extends WizardPage { mProjectNameField.setText(packageName); mInternalProjectNameUpdate = false; } - + } } // Select the target matching the manifest's sdk or build properties, if any boolean foundTarget = false; - + ProjectProperties p = ProjectProperties.create(projectLocation, null); if (p != null) { // Check the {build|default}.properties files if present @@ -976,7 +1046,7 @@ public class NewProjectCreationPage extends WizardPage { // ignore } } - + if (!foundTarget) { for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { if (projectLocation.startsWith(target.getLocation())) { @@ -990,8 +1060,8 @@ public class NewProjectCreationPage extends WizardPage { if (!foundTarget) { mInternalMinSdkVersionUpdate = true; mMinSdkVersionField.setText( - minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" : - Integer.toString(minSdkVersion)); //$NON-NLS-1$ + minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" //$NON-NLS-1$ + : Integer.toString(minSdkVersion)); mInternalMinSdkVersionUpdate = false; } } @@ -1002,7 +1072,7 @@ public class NewProjectCreationPage extends WizardPage { * @return <code>true</code> if all controls are valid, and * <code>false</code> if at least one is invalid */ - protected boolean validatePage() { + private boolean validatePage() { IWorkspace workspace = ResourcesPlugin.getWorkspace(); int status = validateProjectField(workspace); @@ -1027,31 +1097,38 @@ public class NewProjectCreationPage extends WizardPage { 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 projectFieldContents = getProjectName(); - if (projectFieldContents.length() == 0) { + 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(projectFieldContents).matches()) { + 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(projectFieldContents, IResource.PROJECT); + IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT); if (!nameStatus.isOK()) { return setStatus(nameStatus.getMessage(), MSG_ERROR); } @@ -1061,6 +1138,13 @@ public class NewProjectCreationPage extends WizardPage { 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; } @@ -1071,8 +1155,8 @@ public class NewProjectCreationPage extends WizardPage { */ private int validateLocationPath(IWorkspace workspace) { Path path = new Path(getProjectLocation()); - if (isNewProject()) { - if (!useDefaultLocation()) { + 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(), @@ -1103,10 +1187,10 @@ public class NewProjectCreationPage extends WizardPage { return setStatus("A directory name must be specified.", MSG_ERROR); } - File dest = path.append(getProjectName()).toFile(); + 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.", - getProjectName()), MSG_ERROR); + mInfo.getProjectName()), MSG_ERROR); } } } else { @@ -1115,7 +1199,7 @@ public class NewProjectCreationPage extends WizardPage { 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(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); File manifestFile = new File(osPath); @@ -1144,7 +1228,7 @@ public class NewProjectCreationPage extends WizardPage { Activity[] activities = manifestData.getActivities(); if (activities == null || activities.length == 0) { // This is acceptable now as long as no activity needs to be created - if (isCreateActivity()) { + if (mInfo.isCreateActivity()) { return setStatus( String.format("No activity name defined in %1$s.", osPath), MSG_ERROR); @@ -1163,11 +1247,11 @@ public class NewProjectCreationPage extends WizardPage { /** * Validates the sdk target choice. - * + * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateSdkTarget() { - if (getSdkTarget() == null) { + if (mInfo.getSdkTarget() == null) { return setStatus("An SDK Target must be specified.", MSG_ERROR); } return MSG_NONE; @@ -1175,29 +1259,29 @@ public class NewProjectCreationPage extends WizardPage { /** * Validates the sdk target choice. - * + * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateMinSdkVersionField() { // If the min sdk version is empty, it is always accepted. - if (getMinSdkVersion().length() == 0) { + if (mInfo.getMinSdkVersion().length() == 0) { return MSG_NONE; } int version = AndroidManifestParser.INVALID_MIN_SDK; try { // If not empty, it must be a valid integer > 0 - version = Integer.parseInt(getMinSdkVersion()); + version = Integer.parseInt(mInfo.getMinSdkVersion()); } catch (NumberFormatException e) { // ignore } - + if (version < 1) { return setStatus("Min SDK Version must be an integer > 0.", MSG_ERROR); } - - if (getSdkTarget() != null && getSdkTarget().getApiVersionNumber() != version) { + + if (mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getApiVersionNumber() != version) { return setStatus("The API level for the selected SDK target does not match the Min SDK version.", MSG_WARNING); } @@ -1212,36 +1296,36 @@ public class NewProjectCreationPage extends WizardPage { */ private int validateActivityField() { // Disregard if not creating an activity - if (!isCreateActivity()) { + if (!mInfo.isCreateActivity()) { return MSG_NONE; } // Validate activity field - String activityFieldContents = getActivityName(); + 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 = ""; + 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, @@ -1272,7 +1356,7 @@ public class NewProjectCreationPage extends WizardPage { */ private int validatePackageField() { // Validate package field - String packageFieldContents = getPackageName(); + String packageFieldContents = mInfo.getPackageName(); if (packageFieldContents.length() == 0) { return setStatus("Package name must be specified.", MSG_ERROR); } @@ -1312,16 +1396,16 @@ public class NewProjectCreationPage extends WizardPage { 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 (isNewProject() || !isCreateActivity()) { + if (mInfo.isNewProject() || !mInfo.isCreateActivity()) { return MSG_NONE; } - String osTarget = getActivityName(); - + String osTarget = mInfo.getActivityName(); + if (osTarget.indexOf('.') == -1) { - osTarget = getPackageName() + File.separator + osTarget; + osTarget = mInfo.getPackageName() + File.separator + osTarget; } else if (osTarget.indexOf('.') == 0) { - osTarget = getPackageName() + osTarget; + osTarget = mInfo.getPackageName() + osTarget; } osTarget = osTarget.replace('.', File.separatorChar) + AndroidConstants.DOT_JAVA; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java index d3eb9d4..afc67ed 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java @@ -20,6 +20,8 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.AndroidNature; import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.IMainInfo; +import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectCreationPage.TestInfo; import com.android.ide.eclipse.common.AndroidConstants; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; @@ -39,6 +41,8 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.core.IAccessRule; +import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -71,27 +75,54 @@ import java.util.Map.Entry; * 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. + * Do not derive from this class. */ public class NewProjectWizard extends Wizard implements INewWizard { - private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$ - private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$ - private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$ - private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$ - private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$ - private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$ - private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$ - private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$ - private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$ - private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$ - private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$ - - private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$ - private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$ - private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$ - private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$ + /** + * Indicates which pages should be available in the New Project Wizard. + */ + protected enum AvailablePages { + /** + * Both the usual "Android Project" and the "Android Test Project" pages will + * be available. The first page displayed will be the former one and it can depend + * on the soon-to-be created normal project. + */ + ANDROID_AND_TEST_PROJECT, + /** + * Only the "Android Test Project" page will be available. User will have to + * select an existing Android Project. If the selection matches such a project, + * it will be used as a default. + */ + TEST_PROJECT_ONLY + } + + private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$ + private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$ + private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$ + private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$ + private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$ + private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$ + private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$ + private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$ + private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$ + private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$ + private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$ + // Warning: The expanded string PARAM_TEST_TARGET_PACKAGE must not contain the + // string "PACKAGE" since it collides with the replacement of PARAM_PACKAGE. + private static final String PARAM_TEST_TARGET_PACKAGE = "TEST_TARGET_PCKG"; //$NON-NLS-1$ + private static final String PARAM_TARGET_SELF = "TARGET_SELF"; //$NON-NLS-1$ + private static final String PARAM_TARGET_MAIN = "TARGET_MAIN"; //$NON-NLS-1$ + private static final String PARAM_TARGET_EXISTING = "TARGET_EXISTING"; //$NON-NLS-1$ + private static final String PARAM_REFERENCE_PROJECT = "REFERENCE_PROJECT"; //$NON-NLS-1$ + + private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$ + private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$ + private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$ + private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$ + private static final String PH_TEST_USES_LIBRARY = "TEST-USES-LIBRARY"; //$NON-NLS-1$ + private static final String PH_TEST_INSTRUMENTATION = "TEST-INSTRUMENTATION"; //$NON-NLS-1$ private static final String BIN_DIRECTORY = SdkConstants.FD_OUTPUT + AndroidConstants.WS_SEP; @@ -117,6 +148,12 @@ public class NewProjectWizard extends Wizard implements INewWizard { + "uses-sdk.template"; //$NON-NLS-1$ private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY + "launcher_intent_filter.template"; //$NON-NLS-1$ + private static final String TEMPLATE_TEST_USES_LIBRARY = TEMPLATES_DIRECTORY + + "test_uses-library.template"; //$NON-NLS-1$ + private static final String TEMPLATE_TEST_INSTRUMENTATION = TEMPLATES_DIRECTORY + + "test_instrumentation.template"; //$NON-NLS-1$ + + private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY + "strings.template"; //$NON-NLS-1$ @@ -124,11 +161,11 @@ public class NewProjectWizard extends Wizard implements INewWizard { + "string.template"; //$NON-NLS-1$ private static final String ICON = "icon.png"; //$NON-NLS-1$ - private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$ + private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$ - private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$ - private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$ - private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$ + private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$ + private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$ + private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$ private static final String[] DEFAULT_DIRECTORIES = new String[] { BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY }; @@ -136,15 +173,23 @@ public class NewProjectWizard extends Wizard implements INewWizard { DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY}; private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$ - private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$ - private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$ - private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$ - - protected static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$ + private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$ + private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$ + private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$ private NewProjectCreationPage mMainPage; + private NewTestProjectCreationPage mTestPage; /** Package name available when the wizard completes. */ private String mPackageName; + private final AvailablePages mAvailablePages; + + public NewProjectWizard() { + this(AvailablePages.ANDROID_AND_TEST_PROJECT); + } + + protected NewProjectWizard(AvailablePages availablePages) { + mAvailablePages = availablePages; + } /** * Initializes this creation wizard using the passed workbench and object @@ -155,13 +200,14 @@ public class NewProjectWizard extends Wizard implements INewWizard { setWindowTitle("New Android Project"); setImageDescriptor(); - mMainPage = createMainPage(); - mMainPage.setTitle("New Android Project"); - mMainPage.setDescription("Creates a new Android Project resource."); + if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) { + mMainPage = createMainPage(); + } + mTestPage = createTestPage(); } - + /** - * Creates the wizard page. + * Creates the main wizard page. * <p/> * Please do NOT override this method. * <p/> @@ -170,7 +216,20 @@ public class NewProjectWizard extends Wizard implements INewWizard { * to maintain compatibility between different versions of the plugin. */ protected NewProjectCreationPage createMainPage() { - return new NewProjectCreationPage(MAIN_PAGE_NAME); + return new NewProjectCreationPage(); + } + + /** + * Creates the test wizard page. + * <p/> + * Please do NOT override this method. + * <p/> + * This is protected so that it can be overridden by unit tests. + * However the contract of this class is private and NO ATTEMPT will be made + * to maintain compatibility between different versions of the plugin. + */ + protected NewTestProjectCreationPage createTestPage() { + return new NewTestProjectCreationPage(); } // -- Methods inherited from org.eclipse.jface.wizard.Wizard -- @@ -182,7 +241,15 @@ public class NewProjectWizard extends Wizard implements INewWizard { */ @Override public void addPages() { - addPage(mMainPage); + if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) { + addPage(mMainPage); + } + addPage(mTestPage); + + if (mMainPage != null && mTestPage != null) { + mTestPage.setMainInfo(mMainPage.getMainInfo()); + mMainPage.setTestInfo(mTestPage.getTestInfo()); + } } /** @@ -195,7 +262,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { */ @Override public boolean performFinish() { - if (!createAndroidProject()) { + if (!createAndroidProjects()) { return false; } @@ -206,21 +273,21 @@ public class NewProjectWizard extends Wizard implements INewWizard { } // -- Public Fields -- - - /** Returns the package name. Only valid once the wizard finishes. */ + + /** Returns the main project package name. Only valid once the wizard finishes. */ public String getPackageName() { return mPackageName; } - + // -- Custom Methods -- /** * Before actually creating the project for a new project (as opposed to using an * existing project), we check if the target location is a directory that either does * not exist or is empty. - * + * * If it's not empty, ask the user for confirmation. - * + * * @param destination The destination folder where the new project is to be created. * @return True if the destination doesn't exist yet or is an empty directory or is * accepted by the user. @@ -235,32 +302,107 @@ public class NewProjectWizard extends Wizard implements INewWizard { } /** + * Structure that describes all the information needed to create a project. + * This is collected from the pages by {@link NewProjectWizard#createAndroidProjects()} + * and then used by + * {@link NewProjectWizard#createProjectAsync(IProgressMonitor, ProjectInfo, ProjectInfo)}. + */ + private static class ProjectInfo { + private final IProject mProject; + private final IProjectDescription mDescription; + private final Map<String, Object> mParameters; + private final HashMap<String, String> mDictionary; + + public ProjectInfo(IProject project, + IProjectDescription description, + Map<String, Object> parameters, + HashMap<String, String> dictionary) { + mProject = project; + mDescription = description; + mParameters = parameters; + mDictionary = dictionary; + } + + public IProject getProject() { + return mProject; + } + + public IProjectDescription getDescription() { + return mDescription; + } + + public Map<String, Object> getParameters() { + return mParameters; + } + + public HashMap<String, String> getDictionary() { + return mDictionary; + } + } + + /** * Creates the android project. * @return True if the project could be created. */ - private boolean createAndroidProject() { + private boolean createAndroidProjects() { + + final ProjectInfo mainData = collectMainPageInfo(); + if (mMainPage != null && mainData == null) { + return false; + } + + final ProjectInfo testData = collectTestPageInfo(); + if (mTestPage != null && testData == null) { + return false; + } + + // Create a monitored operation to create the actual project + WorkspaceModifyOperation op = new WorkspaceModifyOperation() { + @Override + protected void execute(IProgressMonitor monitor) throws InvocationTargetException { + createProjectAsync(monitor, mainData, testData); + } + }; + + // Run the operation in a different thread + runAsyncOperation(op); + return true; + } + + /** + * Collects all the parameters needed to create the main project. + * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be + * created because parameters are incorrect or should not be created because there + * is no main page. + */ + private ProjectInfo collectMainPageInfo() { + if (mMainPage == null) { + return null; + } + + IMainInfo info = mMainPage.getMainInfo(); + IWorkspace workspace = ResourcesPlugin.getWorkspace(); - final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName()); + final IProject project = workspace.getRoot().getProject(info.getProjectName()); final IProjectDescription description = workspace.newProjectDescription(project.getName()); - // keep some variables to make them available once the wizard closes - mPackageName = mMainPage.getPackageName(); + mPackageName = info.getPackageName(); final Map<String, Object> parameters = new HashMap<String, Object>(); - parameters.put(PARAM_PROJECT, mMainPage.getProjectName()); + parameters.put(PARAM_PROJECT, info.getProjectName()); parameters.put(PARAM_PACKAGE, mPackageName); parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME); parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder()); - parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject()); - parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder()); - parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget()); - parameters.put(PARAM_MIN_SDK_VERSION, mMainPage.getMinSdkVersion()); - - if (mMainPage.isCreateActivity()) { + parameters.put(PARAM_IS_NEW_PROJECT, info.isNewProject()); + parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder()); + parameters.put(PARAM_SDK_TARGET, info.getSdkTarget()); + parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion()); + + if (info.isCreateActivity()) { // An activity name can be of the form ".package.Class" or ".Class". // The initial dot is ignored, as it is always added later in the templates. - String activityName = mMainPage.getActivityName(); + String activityName = info.getActivityName(); if (activityName.startsWith(".")) { //$NON-NLS-1$ activityName = activityName.substring(1); } @@ -269,31 +411,81 @@ public class NewProjectWizard extends Wizard implements INewWizard { // create a dictionary of string that will contain name+content. // we'll put all the strings into values/strings.xml - final HashMap<String, String> stringDictionary = new HashMap<String, String>(); - stringDictionary.put(STRING_APP_NAME, mMainPage.getApplicationName()); + final HashMap<String, String> dictionary = new HashMap<String, String>(); + dictionary.put(STRING_APP_NAME, info.getApplicationName()); - IPath path = mMainPage.getLocationPath(); + IPath path = info.getLocationPath(); IPath defaultLocation = Platform.getLocation(); if (!path.equals(defaultLocation)) { description.setLocation(path); } - - if (mMainPage.isNewProject() && !mMainPage.useDefaultLocation() && + + if (info.isNewProject() && !info.useDefaultLocation() && !validateNewProjectLocationIsEmpty(path)) { - return false; + return null; } - // Create a monitored operation to create the actual project - WorkspaceModifyOperation op = new WorkspaceModifyOperation() { - @Override - protected void execute(IProgressMonitor monitor) throws InvocationTargetException { - createProjectAsync(project, description, monitor, parameters, stringDictionary); - } - }; + return new ProjectInfo(project, description, parameters, dictionary); + } - // Run the operation in a different thread - runAsyncOperation(op); - return true; + /** + * Collects all the parameters needed to create the test project. + * + * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be + * created because parameters are incorrect or should not be created because there + * is no test page. + */ + private ProjectInfo collectTestPageInfo() { + if (mTestPage == null) { + return null; + } + + TestInfo info = mTestPage.getTestInfo(); + + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + final IProject project = workspace.getRoot().getProject(info.getProjectName()); + final IProjectDescription description = workspace.newProjectDescription(project.getName()); + + final Map<String, Object> parameters = new HashMap<String, Object>(); + parameters.put(PARAM_PROJECT, info.getProjectName()); + parameters.put(PARAM_PACKAGE, info.getPackageName()); + parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME); + parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder()); + parameters.put(PARAM_IS_NEW_PROJECT, true); + parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder()); + parameters.put(PARAM_SDK_TARGET, info.getSdkTarget()); + parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion()); + + // Test-specific parameters + parameters.put(PARAM_TEST_TARGET_PACKAGE, info.getTargetPackageName()); + + if (info.isTestingSelf()) { + parameters.put(PARAM_TARGET_SELF, true); + } + if (info.isTestingMain()) { + parameters.put(PARAM_TARGET_MAIN, true); + } + if (info.isTestingExisting()) { + parameters.put(PARAM_TARGET_EXISTING, true); + parameters.put(PARAM_REFERENCE_PROJECT, info.getExistingTestedProject()); + } + + // create a dictionary of string that will contain name+content. + // we'll put all the strings into values/strings.xml + final HashMap<String, String> dictionary = new HashMap<String, String>(); + dictionary.put(STRING_APP_NAME, info.getApplicationName()); + + IPath path = info.getLocationPath(); + IPath defaultLocation = Platform.getLocation(); + if (!path.equals(defaultLocation)) { + description.setLocation(path); + } + + if (!info.useDefaultLocation() && !validateNewProjectLocationIsEmpty(path)) { + return null; + } + + return new ProjectInfo(project, description, parameters, dictionary); } /** @@ -328,82 +520,46 @@ public class NewProjectWizard extends Wizard implements INewWizard { } /** - * Creates the actual project, sets its nature and adds the required folders - * and files to it. This is run asynchronously in a different thread. + * Creates the actual project(s). This is run asynchronously in a different thread. * - * @param project The project to create. - * @param description A description of the project. * @param monitor An existing monitor. - * @param parameters Template parameters. - * @param stringDictionary String definition. + * @param mainData Data for main project. Can be null. * @throws InvocationTargetException to wrap any unmanaged exception and * return it to the calling thread. The method can fail if it fails * to create or modify the project or if it is canceled by the user. */ - private void createProjectAsync(IProject project, IProjectDescription description, - IProgressMonitor monitor, Map<String, Object> parameters, - Map<String, String> stringDictionary) - throws InvocationTargetException { + private void createProjectAsync(IProgressMonitor monitor, + ProjectInfo mainData, + ProjectInfo testData) + throws InvocationTargetException { monitor.beginTask("Create Android Project", 100); try { - // Create project and open it - project.create(description, new SubProgressMonitor(monitor, 10)); - if (monitor.isCanceled()) throw new OperationCanceledException(); - project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10)); - - // Add the Java and android nature to the project - AndroidNature.setupProjectNatures(project, monitor); - - // Create folders in the project if they don't already exist - addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor); - String[] sourceFolders = new String[] { - (String) parameters.get(PARAM_SRC_FOLDER), - GEN_SRC_DIRECTORY - }; - addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor); - - // Create the resource folders in the project if they don't already exist. - addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor); - - // Setup class path: mark folders as source folders - IJavaProject javaProject = JavaCore.create(project); - for (String sourceFolder : sourceFolders) { - setupSourceFolder(javaProject, sourceFolder, monitor); - } - - // Mark the gen source folder as derived - IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY); - if (genSrcFolder.exists()) { - genSrcFolder.setDerived(true); + IProject mainProject = null; + + if (mainData != null) { + mainProject = createEclipseProject( + new SubProgressMonitor(monitor, 50), + mainData.getProject(), + mainData.getDescription(), + mainData.getParameters(), + mainData.getDictionary()); } - if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) { - // Create files in the project if they don't already exist - addManifest(project, parameters, stringDictionary, monitor); - - // add the default app icon - addIcon(project, monitor); - - // Create the default package components - addSampleCode(project, sourceFolders[0], parameters, stringDictionary, monitor); + if (testData != null) { - // add the string definition file if needed - if (stringDictionary.size() > 0) { - addStringDictionaryFile(project, stringDictionary, monitor); + Map<String, Object> parameters = testData.getParameters(); + if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) { + parameters.put(PARAM_REFERENCE_PROJECT, mainProject); } - // Set output location - javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(), - monitor); + createEclipseProject( + new SubProgressMonitor(monitor, 50), + testData.getProject(), + testData.getDescription(), + parameters, + testData.getDictionary()); } - Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET), - null /* apkConfigMap*/); - - // Fix the project to make sure all properties are as expected. - // Necessary for existing projects and good for new ones to. - ProjectHelper.fixProject(project); - } catch (CoreException e) { throw new InvocationTargetException(e); } catch (IOException e) { @@ -414,6 +570,111 @@ public class NewProjectWizard extends Wizard implements INewWizard { } /** + * Creates the actual project, sets its nature and adds the required folders + * and files to it. This is run asynchronously in a different thread. + * + * @param monitor An existing monitor. + * @param project The project to create. + * @param description A description of the project. + * @param parameters Template parameters. + * @param dictionary String definition. + * @return The project newly created + */ + private IProject createEclipseProject(IProgressMonitor monitor, + IProject project, + IProjectDescription description, + Map<String, Object> parameters, + Map<String, String> dictionary) + throws CoreException, IOException { + + // Create project and open it + project.create(description, new SubProgressMonitor(monitor, 10)); + if (monitor.isCanceled()) throw new OperationCanceledException(); + + project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10)); + + // Add the Java and android nature to the project + AndroidNature.setupProjectNatures(project, monitor); + + // Create folders in the project if they don't already exist + addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor); + String[] sourceFolders = new String[] { + (String) parameters.get(PARAM_SRC_FOLDER), + GEN_SRC_DIRECTORY + }; + addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor); + + // Create the resource folders in the project if they don't already exist. + addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor); + + // Setup class path: mark folders as source folders + IJavaProject javaProject = JavaCore.create(project); + for (String sourceFolder : sourceFolders) { + setupSourceFolder(javaProject, sourceFolder, monitor); + } + + // Mark the gen source folder as derived + IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY); + if (genSrcFolder.exists()) { + genSrcFolder.setDerived(true); + } + + if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) { + // Create files in the project if they don't already exist + addManifest(project, parameters, dictionary, monitor); + + // add the default app icon + addIcon(project, monitor); + + // Create the default package components + addSampleCode(project, sourceFolders[0], parameters, dictionary, monitor); + + // add the string definition file if needed + if (dictionary.size() > 0) { + addStringDictionaryFile(project, dictionary, monitor); + } + + // Set output location + javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(), + monitor); + } + + // Create the reference to the target project + if (parameters.containsKey(PARAM_REFERENCE_PROJECT)) { + IProject refProject = (IProject) parameters.get(PARAM_REFERENCE_PROJECT); + if (refProject != null) { + IProjectDescription desc = project.getDescription(); + + // Add out reference to the existing project reference. + // We just created a project with no references so we don't need to expand + // the currently-empty current list. + desc.setReferencedProjects(new IProject[] { refProject }); + + project.setDescription(desc, IResource.KEEP_HISTORY, new SubProgressMonitor(monitor, 10)); + + IClasspathEntry entry = JavaCore.newProjectEntry( + refProject.getFullPath(), //path + new IAccessRule[0], //accessRules + false, //combineAccessRules + new IClasspathAttribute[0], //extraAttributes + false //isExported + + ); + ProjectHelper.addEntryToClasspath(javaProject, entry); + } + } + + Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET), + null /* apkConfigMap*/); + + // Fix the project to make sure all properties are as expected. + // Necessary for existing projects and good for new ones to. + ProjectHelper.fixProject(project); + + return project; + } + + /** * Adds default directories to the project. * * @param project The Java Project to update. @@ -442,7 +703,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { * * @param project The Java Project to update. * @param parameters Template Parameters. - * @param stringDictionary String List to be added to a string definition + * @param dictionary String List to be added to a string definition * file. This map will be filled by this method. * @param monitor An existing monitor. * @throws CoreException if the method fails to update the project. @@ -450,7 +711,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { * project. */ private void addManifest(IProject project, Map<String, Object> parameters, - Map<String, String> stringDictionary, IProgressMonitor monitor) + Map<String, String> dictionary, IProgressMonitor monitor) throws CoreException, IOException { // get IFile to the manifest and check if it's not already there. @@ -466,23 +727,42 @@ public class NewProjectWizard extends Wizard implements INewWizard { if (parameters.containsKey(PARAM_ACTIVITY)) { // now get the activity template String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES); - + // Replace all keyword parameters to make main activity. String activities = replaceParameters(activityTemplate, parameters); - + // set the intent. String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER); - + // set the intent to the main activity activities = activities.replaceAll(PH_INTENT_FILTERS, intent); - + // set the activity(ies) in the manifest manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities); } else { // remove the activity(ies) from the manifest - manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); + manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); //$NON-NLS-1$ + } + + // Handle the case of the test projects + if (parameters.containsKey(PARAM_TEST_TARGET_PACKAGE)) { + // Set the uses-library needed by the test project + String usesLibrary = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_USES_LIBRARY); + manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, usesLibrary); + + // Set the instrumentation element needed by the test project + String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION); + manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, instru); + + // Replace PARAM_TEST_TARGET_PACKAGE itself now + manifestTemplate = replaceParameters(manifestTemplate, parameters); + + } else { + // remove the unused entries + manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, ""); //$NON-NLS-1$ + manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, ""); //$NON-NLS-1$ } - + String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION); if (minSdkVersion != null && minSdkVersion.length() > 0) { String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK); @@ -584,7 +864,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { * * @param project The Java Project to update. * @param parameters Template Parameters. - * @param stringDictionary String List to be added to a string definition + * @param dictionary String List to be added to a string definition * file. This map will be filled by this method. * @param monitor An existing monitor. * @throws CoreException if the method fails to update the project. @@ -592,12 +872,12 @@ public class NewProjectWizard extends Wizard implements INewWizard { * project. */ private void addSampleCode(IProject project, String sourceFolder, - Map<String, Object> parameters, Map<String, String> stringDictionary, + Map<String, Object> parameters, Map<String, String> dictionary, IProgressMonitor monitor) throws CoreException, IOException { // create the java package directories. IFolder pkgFolder = project.getFolder(sourceFolder); String packageName = (String) parameters.get(PARAM_PACKAGE); - + // The PARAM_ACTIVITY key will be absent if no activity should be created, // in which case activityName will be null. String activityName = (String) parameters.get(PARAM_ACTIVITY); @@ -610,7 +890,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { int pos = packageName.lastIndexOf('.'); activityName = packageName.substring(pos + 1); packageName = packageName.substring(0, pos); - + // Also update the values used in the JAVA_FILE_TEMPLATE below // (but not the ones from the manifest so don't change the caller's dictionary) java_activity_parameters = new HashMap<String, Object>(parameters); @@ -643,9 +923,9 @@ public class NewProjectWizard extends Wizard implements INewWizard { if (!file.exists()) { copyFile(LAYOUT_TEMPLATE, file, parameters, monitor); if (activityName != null) { - stringDictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!"); + dictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!"); } else { - stringDictionary.put(STRING_HELLO_WORLD, "Hello World!"); + dictionary.put(STRING_HELLO_WORLD, "Hello World!"); } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java new file mode 100755 index 0000000..7b99053 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java @@ -0,0 +1,1349 @@ +/* + * 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.wizards.newproject; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener; +import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.IMainInfo; +import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.MainInfo; +import com.android.ide.eclipse.common.project.AndroidManifestParser; +import com.android.ide.eclipse.common.project.ProjectChooserHelper; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; +import com.android.sdkuilib.SdkTargetSelector; + +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IFile; +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.CoreException; +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.IJavaProject; +import org.eclipse.jdt.core.JavaConventions; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.osgi.util.TextProcessor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +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 java.io.File; +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 NewTestProjectCreationPage extends WizardPage { + + // constants + static final String TEST_PAGE_NAME = "newAndroidTestProjectPage"; //$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 Use Default Location check box. */ + private static final boolean INITIAL_USE_DEFAULT_LOCATION = true; + /** Initial value for the Create Test Project check box. */ + private static final boolean INITIAL_CREATE_TEST_PROJECT = false; + + + /** 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 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 Test Project page. */ + private final TestInfo mInfo = new TestInfo(); + /** Structure with the externally visible information from the Main Project page. + * This is null if there's no such page, meaning the test project page is standalone. */ + private IMainInfo mMainInfo; + + // widgets + private Text mProjectNameField; + private Text mPackageNameField; + private Text mApplicationNameField; + private Button mUseDefaultLocation; + private Label mLocationLabel; + private Text mLocationPathField; + private Button mBrowseButton; + private Text mMinSdkVersionField; + private SdkTargetSelector mSdkTargetSelector; + private ITargetChangeListener mSdkTargetChangeListener; + private Button mCreateTestProjectField; + private Text mTestedProjectNameField; + private Button mProjectBrowseButton; + private ProjectChooserHelper mProjectChooserHelper; + private Button mTestSelfProjectRadio; + private Button mTestExistingProjectRadio; + + /** A list of composites that are disabled when the "Create Test Project" toggle is off. */ + private ArrayList<Composite> mToggleComposites = new ArrayList<Composite>(); + + private boolean mInternalProjectNameUpdate; + private boolean mInternalLocationPathUpdate; + private boolean mInternalPackageNameUpdate; + private boolean mInternalApplicationNameUpdate; + private boolean mInternalMinSdkVersionUpdate; + private boolean mInternalSdkTargetUpdate; + private IProject mExistingTestedProject; + private boolean mProjectNameModifiedByUser; + private boolean mApplicationNameModifiedByUser; + private boolean mPackageNameModifiedByUser; + private boolean mMinSdkVersionModifiedByUser; + private boolean mSdkTargetModifiedByUser; + + private Label mTestTargetPackageLabel; + + private String mLastExistingPackageName; + + + /** + * Creates a new project creation wizard page. + */ + public NewTestProjectCreationPage() { + super(TEST_PAGE_NAME); + setPageComplete(false); + setTitle("New Android Test Project"); + setDescription("Creates a new Android Test Project resource."); + } + + // --- 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. + */ + public class TestInfo { + + /** Returns true if a new Test Project should be created. */ + public boolean getCreateTestProject() { + return mCreateTestProjectField == null ? true : mCreateTestProjectField.getSelection(); + } + + /** + * 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 test target package name field with spaces trimmed. */ + public String getTargetPackageName() { + return mTestTargetPackageLabel == null ? INITIAL_NAME + : mTestTargetPackageLabel.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 ? "" : mApplicationNameField.getText().trim(); //$NON-NLS-1$ + } + + /** Returns the value of the Use Default Location field. */ + public boolean useDefaultLocation() { + return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION + : mUseDefaultLocation.getSelection(); + } + + /** Returns the the default "src" constant. */ + public String getSourceFolder() { + return SdkConstants.FD_SOURCES; + } + + /** Returns the current sdk target or null if none has been selected yet. */ + public IAndroidTarget getSdkTarget() { + return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected(); + } + + public boolean isTestingSelf() { + return mMainInfo == null && + (mTestSelfProjectRadio == null ? false : mTestSelfProjectRadio.getSelection()); + } + + public boolean isTestingMain() { + return mMainInfo != null; + } + + public boolean isTestingExisting() { + return mMainInfo == null && + (mTestExistingProjectRadio == null ? false + : mTestExistingProjectRadio.getSelection()); + } + + public IProject getExistingTestedProject() { + return mExistingTestedProject; + } + } + + /** + * Returns a {@link TestInfo} 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 TestInfo getTestInfo() { + return mInfo; + } + + /** + * Grabs the {@link MainInfo} structure with visible parameters from the main project page. + * This may be null. + */ + public void setMainInfo(IMainInfo mainInfo) { + mMainInfo = mainInfo; + } + + // --- 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) { + Composite composite = new Composite(parent, SWT.NULL); + composite.setFont(parent.getFont()); + + initializeDialogUnits(parent); + + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + createToggleTestProject(composite); + createTestProjectGroup(composite); + createLocationGroup(composite); + createTestTargetGroup(composite); + createTargetGroup(composite); + createPropertiesGroup(composite); + + // Update state the first time + enableLocationWidgets(); + + // Show description the first time + setErrorMessage(null); + setMessage(null); + setControl(composite); + + // Validate. This will complain about the first empty field. + validatePageComplete(); + } + + /** + * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when + * the dialog is made visible and to also update the enabled/disabled state of some + * controls (doing so in createControl doesn't always change their state somehow.) + */ + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + mProjectNameField.setFocus(); + validatePageComplete(); + onCreateTestProjectToggle(); + onExistingProjectChanged(); + } + } + + @Override + public void dispose() { + + if (mSdkTargetChangeListener != null) { + AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener); + mSdkTargetChangeListener = null; + } + + super.dispose(); + } + + /** + * Creates the "create test project" checkbox but only if there's a main page in the wizard. + * + * @param parent the parent composite + */ + private final void createToggleTestProject(Composite parent) { + + if (mMainInfo != null) { + mCreateTestProjectField = new Button(parent, SWT.CHECK); + mCreateTestProjectField.setText("Create a Test Project"); + mCreateTestProjectField.setToolTipText("Select this if you also want to create a Test Project."); + mCreateTestProjectField.setSelection(INITIAL_CREATE_TEST_PROJECT); + mCreateTestProjectField.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onCreateTestProjectToggle(); + } + }); + } + } + + /** + * Creates the group for the project name: + * [label: "Project Name"] [text field] + * + * @param parent the parent composite + */ + private final void createTestProjectGroup(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)); + + mToggleComposites.add(group); + + // --- test project name --- + + // new project label + String tooltip = "Name of the Eclipse test project to create. It cannot be empty."; + Label label = new Label(group, SWT.NONE); + label.setText("Test Project Name:"); + label.setFont(parent.getFont()); + label.setToolTipText(tooltip); + + // new project name entry field + mProjectNameField = new Text(group, SWT.BORDER); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + mProjectNameField.setToolTipText(tooltip); + mProjectNameField.setLayoutData(data); + mProjectNameField.setFont(parent.getFont()); + mProjectNameField.addListener(SWT.Modify, new Listener() { + public void handleEvent(Event event) { + if (!mInternalProjectNameUpdate) { + mProjectNameModifiedByUser = true; + } + updateLocationPathField(null); + } + }); + + } + + private final void createLocationGroup(Composite parent) { + + // --- project location --- + + Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); + group.setLayout(new GridLayout(3, /* num columns */ + false /* columns of not equal size */)); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + group.setFont(parent.getFont()); + group.setText("Content"); + + mToggleComposites.add(group); + + mUseDefaultLocation = new Button(group, SWT.CHECK); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 3; + mUseDefaultLocation.setLayoutData(gd); + mUseDefaultLocation.setText("Use default location"); + mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION); + + mUseDefaultLocation.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + enableLocationWidgets(); + validatePageComplete(); + } + }); + + + mLocationLabel = new Label(group, SWT.NONE); + mLocationLabel.setText("Location:"); + + mLocationPathField = new Text(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(group, SWT.PUSH); + mBrowseButton.setText("Browse..."); + setButtonLayoutData(mBrowseButton); + mBrowseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onOpenDirectoryBrowser(); + } + }); + } + + /** + * Creates the group for Test Target options. + * + * There are two different modes here: + * <ul> + * <li>When mMainInfo exists, this is part of a new Android Project. In which case + * the new test is tied to the soon-to-be main project and there is actually no choice. + * <li>When mMainInfo does not exist, this is a standalone new test project. In this case + * we offer 2 options for the test target: self test or against an existing Android project. + * </ul> + * + * @param parent the parent composite + */ + private final void createTestTargetGroup(Composite parent) { + + Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); + GridLayout layout = new GridLayout(); + layout.numColumns = 3; + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_BOTH)); + group.setFont(parent.getFont()); + group.setText("Test Target"); + + mToggleComposites.add(group); + + if (mMainInfo == null) { + // Standalone mode: choose between self-test and existing-project test + + Label label = new Label(group, SWT.NONE); + label.setText("Select the project to test:"); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 3; + label.setLayoutData(gd); + + mTestSelfProjectRadio = new Button(group, SWT.RADIO); + mTestSelfProjectRadio.setText("This project"); + mTestSelfProjectRadio.setSelection(false); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 3; + mTestSelfProjectRadio.setLayoutData(gd); + + mTestExistingProjectRadio = new Button(group, SWT.RADIO); + mTestExistingProjectRadio.setText("An existing Android project"); + mTestExistingProjectRadio.setSelection(mMainInfo == null); + mTestExistingProjectRadio.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onExistingProjectChanged(); + } + }); + + String tooltip = "The existing Android Project that is being tested."; + + mTestedProjectNameField = new Text(group, SWT.BORDER); + mTestedProjectNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mTestedProjectNameField.setToolTipText(tooltip); + mTestedProjectNameField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onProjectFieldUpdated(); + } + }); + + mProjectBrowseButton = new Button(group, SWT.NONE); + mProjectBrowseButton.setText("Browse..."); + mProjectBrowseButton.setToolTipText("Allows you to select the Android project to test."); + mProjectBrowseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onProjectBrowse(); + } + }); + + mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); + } else { + // Part of NPW mode: no selection. + + } + + // package label line + + Label label = new Label(group, SWT.NONE); + label.setText("Test Target Package:"); + mTestTargetPackageLabel = new Label(group, SWT.NONE); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + mTestTargetPackageLabel.setLayoutData(gd); + } + + /** + * 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"); + + mToggleComposites.add(group); + + // The selector is created without targets. They are added below in the change listener. + mSdkTargetSelector = new SdkTargetSelector(group, null); + + mSdkTargetChangeListener = new ITargetChangeListener() { + public void onProjectTargetChange(IProject changedProject) { + // Ignore + } + + public void onTargetsLoaded() { + // 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 + if (targets != null && targets.length == 1) { + mSdkTargetSelector.setSelection(targets[0]); + } + } + }; + + AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener); + + // Invoke it once to initialize the targets + mSdkTargetChangeListener.onTargetsLoaded(); + + mSdkTargetSelector.setSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onSdkTargetModified(); + updateLocationPathField(null); + validatePageComplete(); + } + }); + } + + /** + * 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"); + + mToggleComposites.add(group); + + // 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) { + if (!mInternalPackageNameUpdate) { + mPackageNameModifiedByUser = true; + } + onPackageNameFieldModified(); + } + }); + + // 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) { + onMinSdkVersionFieldModified(); + validatePageComplete(); + } + }); + } + + + //--- 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 project location, depending on the Use Default Location check box. */ + private String getProjectLocation() { + if (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 ---- + + /** + * Callback invoked when the user toggles the "Test target: Existing Android Project" + * checkbox. It enables or disable the UI to select an existing project. + */ + private void onExistingProjectChanged() { + if (mInfo.isTestingExisting()) { + boolean enabled = mTestExistingProjectRadio.getSelection(); + mTestedProjectNameField.setEnabled(enabled); + mProjectBrowseButton.setEnabled(enabled); + setExistingProject(mInfo.getExistingTestedProject()); + validatePageComplete(); + } + } + + /** + * Tries to load the defaults from the main page if possible. + */ + private void useMainProjectInformation() { + if (mInfo.isTestingMain() && mMainInfo != null) { + + String projName = String.format("%1$sTest", mMainInfo.getProjectName()); + String appName = String.format("%1$sTest", mMainInfo.getApplicationName()); + + String packageName = mMainInfo.getPackageName(); + if (packageName == null) { + packageName = ""; //$NON-NLS-1$ + } + + updateTestTargetPackageField(packageName); + + if (!mProjectNameModifiedByUser) { + mInternalProjectNameUpdate = true; + mProjectNameField.setText(projName); //$NON-NLS-1$ + mInternalProjectNameUpdate = false; + } + + if (!mApplicationNameModifiedByUser) { + mInternalApplicationNameUpdate = true; + mApplicationNameField.setText(appName); + mInternalApplicationNameUpdate = false; + } + + if (!mPackageNameModifiedByUser) { + mInternalPackageNameUpdate = true; + packageName += ".test"; //$NON-NLS-1$ + mPackageNameField.setText(packageName); + mInternalPackageNameUpdate = false; + } + + if (!mSdkTargetModifiedByUser) { + mInternalSdkTargetUpdate = true; + mSdkTargetSelector.setSelection(mMainInfo.getSdkTarget()); + mInternalSdkTargetUpdate = false; + } + + if (!mMinSdkVersionModifiedByUser) { + mInternalMinSdkVersionUpdate = true; + mMinSdkVersionField.setText(mMainInfo.getMinSdkVersion()); + mInternalMinSdkVersionUpdate = false; + } + } + } + + /** + * Callback invoked when the user edits the project text field. + */ + private void onProjectFieldUpdated() { + String project = mTestedProjectNameField.getText(); + + // Is this a valid project? + IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null /*javaModel*/); + for (IJavaProject p : projects) { + if (p.getProject().getName().equals(project)) { + setExistingProject(p.getProject()); + return; + } + } + } + + /** + * Callback called when the user uses the "Browse Projects" button. + */ + private void onProjectBrowse() { + IJavaProject p = mProjectChooserHelper.chooseJavaProject(mTestedProjectNameField.getText()); + if (p != null) { + setExistingProject(p.getProject()); + mTestedProjectNameField.setText(mExistingTestedProject.getName()); + } + } + + private void setExistingProject(IProject project) { + mExistingTestedProject = project; + + // Try to update the application, package, sdk target and minSdkVersion accordingly + if (project != null && + (!mApplicationNameModifiedByUser || + !mPackageNameModifiedByUser || + !mSdkTargetModifiedByUser || + !mMinSdkVersionModifiedByUser)) { + + IFile file = AndroidManifestParser.getManifest(project); + AndroidManifestParser manifestData = null; + if (file != null) { + try { + manifestData = AndroidManifestParser.parseForData(file); + } catch (CoreException e) { + // pass + } + } + + if (manifestData != null) { + String appName = String.format("%1$sTest", project.getName()); + String packageName = manifestData.getPackage(); + int minSdkVersion = manifestData.getApiLevelRequirement(); + IAndroidTarget sdkTarget = null; + if (Sdk.getCurrent() != null) { + sdkTarget = Sdk.getCurrent().getTarget(project); + } + + if (packageName == null) { + packageName = ""; //$NON-NLS-1$ + } + mLastExistingPackageName = packageName; + + if (!mProjectNameModifiedByUser) { + mInternalProjectNameUpdate = true; + mProjectNameField.setText(appName); + mInternalProjectNameUpdate = false; + } + + if (!mApplicationNameModifiedByUser) { + mInternalApplicationNameUpdate = true; + mApplicationNameField.setText(appName); + mInternalApplicationNameUpdate = false; + } + + if (!mPackageNameModifiedByUser) { + mInternalPackageNameUpdate = true; + packageName += ".test"; //$NON-NLS-1$ + mPackageNameField.setText(packageName); //$NON-NLS-1$ + mInternalPackageNameUpdate = false; + } + + if (!mSdkTargetModifiedByUser && sdkTarget != null) { + mInternalSdkTargetUpdate = true; + mSdkTargetSelector.setSelection(sdkTarget); + mInternalSdkTargetUpdate = false; + } + + if (!mMinSdkVersionModifiedByUser) { + mInternalMinSdkVersionUpdate = true; + mMinSdkVersionField.setText( + minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK ? + Integer.toString(minSdkVersion) : ""); //$NON-NLS-1$ + if (sdkTarget == null) { + updateSdkSelectorToMatchMinSdkVersion(); + } + mInternalMinSdkVersionUpdate = false; + } + } + } + + updateTestTargetPackageField(mLastExistingPackageName); + validatePageComplete(); + } + + /** + * 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); + validatePageComplete(); + } + } + + /** + * Callback when the "create test project" checkbox is changed. + * It enables or disables all UI groups accordingly. + */ + private void onCreateTestProjectToggle() { + boolean enabled = mInfo.getCreateTestProject(); + for (Composite c : mToggleComposites) { + enableControl(c, enabled); + } + mSdkTargetSelector.setEnabled(enabled); + + if (enabled) { + useMainProjectInformation(); + } + validatePageComplete(); + } + + /** Enables or disables controls; recursive for composite controls. */ + private void enableControl(Control c, boolean enabled) { + c.setEnabled(enabled); + if (c instanceof Composite) + for (Control c2 : ((Composite) c).getChildren()) { + enableControl(c2, enabled); + } + } + + /** + * 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 use_default = mInfo.useDefaultLocation(); + boolean location_enabled = !use_default; + + mLocationLabel.setEnabled(location_enabled); + mLocationPathField.setEnabled(location_enabled); + mBrowseButton.setEnabled(location_enabled); + + updateLocationPathField(null); + } + + /** + * 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) { + boolean use_default = mInfo.useDefaultLocation(); + boolean custom_location = !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. + sCustomLocationOsPath = TextProcessor.process(abs_dir); + } + 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 + String newPath = getLocationPathFieldValue(); + sCustomLocationOsPath = newPath; + 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() { + updateTestTargetPackageField(null); + validatePageComplete(); + } + + /** + * Changes the {@link #mTestTargetPackageLabel} field. + * + * When using the "self-test" option, the packageName argument is ignored and the + * current value from the project package is used. + * + * Otherwise the packageName is used if it is not null. + */ + private void updateTestTargetPackageField(String packageName) { + if (mInfo.isTestingSelf()) { + mTestTargetPackageLabel.setText(mInfo.getPackageName()); + + } else if (packageName != null) { + mTestTargetPackageLabel.setText(packageName); + } + } + + /** + * Called when the min sdk version field has been modified. + * + * Ignore the internal modifications. When modified by the user, try to match + * a target with the same API level. + */ + private void onMinSdkVersionFieldModified() { + if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) { + return; + } + + updateSdkSelectorToMatchMinSdkVersion(); + + mMinSdkVersionModifiedByUser = true; + } + + /** + * Try to find an SDK Target that matches the current MinSdkVersion. + * + * There can be multiple targets with the same sdk api version, so don't change + * it if it's already at the right version. Otherwise pick the first target + * that matches. + */ + private void updateSdkSelectorToMatchMinSdkVersion() { + try { + int version = Integer.parseInt(mInfo.getMinSdkVersion()); + + IAndroidTarget curr_target = mInfo.getSdkTarget(); + if (curr_target != null && curr_target.getApiVersionNumber() == version) { + return; + } + + for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { + if (target.getApiVersionNumber() == version) { + mSdkTargetSelector.setSelection(target); + return; + } + } + } catch (NumberFormatException e) { + // ignore + } + } + + /** + * 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() { + if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) { + return; + } + + IAndroidTarget target = mInfo.getSdkTarget(); + + if (target != null) { + mInternalMinSdkVersionUpdate = true; + mMinSdkVersionField.setText(Integer.toString(target.getApiVersionNumber())); + mInternalMinSdkVersionUpdate = false; + } + + mSdkTargetModifiedByUser = true; + } + + /** + * 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 = MSG_NONE; + + // there is nothing to validate if we're not going to create a test project + if (mInfo.getCreateTestProject()) { + status = validateProjectField(workspace); + if ((status & MSG_ERROR) == 0) { + status |= validateLocationPath(workspace); + } + if ((status & MSG_ERROR) == 0) { + status |= validateTestTarget(); + } + if ((status & MSG_ERROR) == 0) { + status |= validateSdkTarget(); + } + if ((status & MSG_ERROR) == 0) { + status |= validatePackageField(); + } + if ((status & MSG_ERROR) == 0) { + status |= validateMinSdkVersionField(); + } + } + 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 test target (self, main project or existing project) + * + * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. + */ + private int validateTestTarget() { + if (mInfo.isTestingExisting() && mInfo.getExistingTestedProject() == null) { + return setStatus("Please select an existing Android project as a test target.", + MSG_ERROR); + } + + return MSG_NONE; + } + + /** + * 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 (mMainInfo != null && projectName.equals(mMainInfo.getProjectName())) { + return setStatus("The main project name and the test project name must be different.", + MSG_ERROR); + } + + if (getProjectHandle().exists()) { + return setStatus("A project with that name already exists in the workspace", + MSG_ERROR); + } + + 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.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); + } + } + + 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 min sdk version is empty, it is always accepted. + if (mInfo.getMinSdkVersion().length() == 0) { + return MSG_NONE; + } + + int version = AndroidManifestParser.INVALID_MIN_SDK; + try { + // If not empty, it must be a valid integer > 0 + version = Integer.parseInt(mInfo.getMinSdkVersion()); + } catch (NumberFormatException e) { + // ignore + } + + if (version < 1) { + return setStatus("Min SDK Version must be an integer > 0.", MSG_ERROR); + } + + if (mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getApiVersionNumber() != version) { + return setStatus("The API level for the selected SDK target does not match the Min SDK version.", + MSG_WARNING); + } + + return MSG_NONE; + } + + /** + * 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 packageName = mInfo.getPackageName(); + if (packageName.length() == 0) { + return setStatus("Project package name must be specified.", MSG_ERROR); + } + + // Check it's a valid package string + int result = MSG_NONE; + IStatus status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ + if (!status.isOK()) { + result = setStatus(String.format("Project package: %s", 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 && packageName.indexOf('.') == -1) { + return setStatus("Project package name must have at least two identifiers.", MSG_ERROR); + } + + // Check that the target package name is valid too + packageName = mInfo.getTargetPackageName(); + if (packageName.length() == 0) { + return setStatus("Target package name must be specified.", MSG_ERROR); + } + + // Check it's a valid package string + status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ + if (!status.isOK()) { + result = setStatus(String.format("Target package: %s", status.getMessage()), + status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); + } + + if (result != MSG_ERROR && packageName.indexOf('.') == -1) { + return setStatus("Target name must have at least two identifiers.", MSG_ERROR); + } + + return result; + } + + /** + * 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; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java new file mode 100755 index 0000000..dd5cf76 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 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. + */ + +package com.android.ide.eclipse.adt.wizards.newproject; + + +/** + * A "New Test Android Project" Wizard. + * <p/> + * This is really the {@link NewProjectWizard} that only displays the "test project" page. + */ +public class NewTestProjectWizard extends NewProjectWizard { + + public NewTestProjectWizard() { + super(AvailablePages.TEST_PROJECT_ONLY); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template index b43e75f..5d57413 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template +++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template @@ -5,6 +5,8 @@ android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="APPLICATION_NAME"> ACTIVITIES +TEST-USES-LIBRARY </application> USES-SDK +TEST-INSTRUMENTATION </manifest> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template new file mode 100755 index 0000000..c282fbc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template @@ -0,0 +1 @@ + <instrumentation android:targetPackage="TEST_TARGET_PCKG" android:name="android.test.InstrumentationTestRunner" /> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template new file mode 100755 index 0000000..28ae7a4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template @@ -0,0 +1 @@ + <uses-library android:name="android.test.runner" /> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java index 42f8df0..50fb622 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java @@ -15,6 +15,11 @@ */ package com.android.ide.eclipse.adt.wizards.newproject; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + import java.io.File; /** @@ -26,46 +31,60 @@ public class StubSampleProjectCreationPage extends NewProjectCreationPage { private String mSampleProjectName; private String mOsSdkLocation; - public StubSampleProjectCreationPage(String pageName, - String sampleProjectName, String osSdkLocation) { - super(pageName); + public StubSampleProjectCreationPage(String sampleProjectName, String osSdkLocation) { + super(); this.mSampleProjectName = sampleProjectName; this.mOsSdkLocation = osSdkLocation; } @Override - public String getProjectName() { - return mSampleProjectName; - } + public IMainInfo getMainInfo() { + return new IMainInfo() { + public String getProjectName() { + return mSampleProjectName; + } - @Override - public String getPackageName() { - return "com.android.samples"; - } + public String getPackageName() { + return "com.android.samples"; + } - @Override - public String getActivityName() { - return mSampleProjectName; - } + public String getActivityName() { + return mSampleProjectName; + } - @Override - public String getApplicationName() { - return mSampleProjectName; - } + public String getApplicationName() { + return mSampleProjectName; + } - @Override - public boolean isNewProject() { - return false; - } + public boolean isNewProject() { + return false; + } - @Override - public String getProjectLocation() { - return mOsSdkLocation + File.separator + "samples" + File.separator + mSampleProjectName; - } + public String getSourceFolder() { + return "src"; + } - @Override - public String getSourceFolder() { - return "src"; - } + public IPath getLocationPath() { + return new Path(mOsSdkLocation + File.separator + + "samples" + File.separator + + mSampleProjectName); + } + public String getMinSdkVersion() { + return null; + } + + public IAndroidTarget getSdkTarget() { + return null; + } + + public boolean isCreateActivity() { + return false; + } + + public boolean useDefaultLocation() { + return false; + } + }; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java index 40cd636..e598df3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java @@ -49,8 +49,7 @@ public class StubSampleProjectWizard extends NewProjectWizard { */ @Override protected NewProjectCreationPage createMainPage() { - return new StubSampleProjectCreationPage(MAIN_PAGE_NAME, - mSampleProjectName, mOsSdkLocation); + return new StubSampleProjectCreationPage(mSampleProjectName, mOsSdkLocation); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java index 6aaa209..5a413fa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java @@ -67,8 +67,6 @@ public class EclipseTestCollector { /** * Returns true if given class should be added to suite - * @param testClass - * @return */ protected boolean isTestClass(Class<?> testClass) { return TestCase.class.isAssignableFrom(testClass) && @@ -78,8 +76,6 @@ public class EclipseTestCollector { /** * Returns true if given class has a public constructor - * @param testClass - * @return */ protected boolean hasPublicConstructor(Class<?> testClass) { try { @@ -94,7 +90,6 @@ public class EclipseTestCollector { * Load the class given by the plugin aka bundle file path * @param filePath - path of class in bundle * @param expectedPackage - expected package of class - * @return * @throws ClassNotFoundException */ protected Class<?> getClass(String filePath, String expectedPackage) throws ClassNotFoundException { |