aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/dictionary.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml12
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java16
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java780
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java1809
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java1165
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java1256
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java394
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java32
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java1395
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java14
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java564
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java271
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java330
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java278
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java190
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java97
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectWizard.java117
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java33
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java8
21 files changed, 3967 insertions, 4797 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index b6042cb..ac9ff9a 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -167,6 +167,7 @@ param
params
pings
placeholder
+placeholders
plugin
popup
popups
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index 7a9a0ef..9f5584c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -127,6 +127,18 @@
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.internal.wizards.newproject.NewSampleProjectWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/new_adt_project.png"
+ id="com.android.ide.eclipse.adt.project.NewSampleProjectWizard"
+ name="Android Sample 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.internal.wizards.newxmlfile.NewXmlFileWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index f2ed49a..ba6712b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -17,6 +17,10 @@
package com.android.ide.eclipse.adt;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
@@ -240,4 +244,16 @@ public class AdtUtils {
return null;
}
+
+ /**
+ * Returns an absolute path to the given resource
+ *
+ * @param resource the resource to look up a path for
+ * @return an absolute file system path to the resource
+ */
+ public static IPath getAbsolutePath(IResource resource) {
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+ IPath workspacePath = workspace.getLocation();
+ return workspacePath.append(resource.getFullPath());
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java
index fa06fb3..cd6e9bb 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java
@@ -66,7 +66,7 @@ import org.eclipse.ui.part.FileEditorInput;
import java.util.TreeSet;
/**
- * Represents an XML attribute to select an exisintg manifest package, that can be modified using
+ * Represents an XML attribute to select an existing manifest package, that can be modified using
* a simple text field or a dialog to choose an existing package.
* <p/>
* See {@link UiTextAttributeNode} for more information.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java
new file mode 100644
index 0000000..9b2ce05
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaConventions;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.net.URI;
+
+/** Page where you choose the application name, activity name, and optional test project info */
+class ApplicationInfoPage extends WizardPage implements SelectionListener, ModifyListener,
+ ITargetChangeListener {
+ private static final String JDK_15 = "1.5"; //$NON-NLS-1$
+ private final static String DUMMY_PACKAGE = "your.package.namespace";
+
+ /** Suffix added by default to activity names */
+ static final String ACTIVITY_NAME_SUFFIX = "Activity"; //$NON-NLS-1$
+
+ private final NewProjectWizardState mValues;
+
+ private Text mApplicationText;
+ private Text mPackageText;
+ private Text mActivityText;
+ private Button mCreateActivityCheckbox;
+ private Combo mSdkCombo;
+
+ private boolean mIgnore;
+ private Button mCreateTestCheckbox;
+ private Text mTestProjectNameText;
+ private Text mTestApplicationText;
+ private Text mTestPackageText;
+ private Label mTestProjectNameLabel;
+ private Label mTestApplicationLabel;
+ private Label mTestPackageLabel;
+
+ /**
+ * Create the wizard.
+ */
+ ApplicationInfoPage(NewProjectWizardState values) {
+ super("appInfo"); //$NON-NLS-1$
+ mValues = values;
+
+ setTitle("Application Info");
+ setDescription("Configure the new Android Project");
+ AdtPlugin.getDefault().addTargetListener(this);
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ @SuppressWarnings("unused") // Eclipse marks SWT constructors with side effects as unused
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayout(new GridLayout(2, false));
+
+ Label applicationLabel = new Label(container, SWT.NONE);
+ applicationLabel.setText("Application Name:");
+
+ mApplicationText = new Text(container, SWT.BORDER);
+ mApplicationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mApplicationText.addModifyListener(this);
+
+ Label packageLabel = new Label(container, SWT.NONE);
+ packageLabel.setText("Package Name:");
+
+ mPackageText = new Text(container, SWT.BORDER);
+ mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mPackageText.addModifyListener(this);
+
+ if (mValues.mode != Mode.TEST) {
+ mCreateActivityCheckbox = new Button(container, SWT.CHECK);
+ mCreateActivityCheckbox.setText("Create Activity:");
+ mCreateActivityCheckbox.addSelectionListener(this);
+
+ mActivityText = new Text(container, SWT.BORDER);
+ mActivityText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mActivityText.addModifyListener(this);
+ }
+
+ Label minSdkLabel = new Label(container, SWT.NONE);
+ minSdkLabel.setText("Minimum SDK:");
+
+ mSdkCombo = new Combo(container, SWT.NONE);
+ GridData gdSdkCombo = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1);
+ gdSdkCombo.widthHint = 200;
+ mSdkCombo.setLayoutData(gdSdkCombo);
+ mSdkCombo.addSelectionListener(this);
+ mSdkCombo.addModifyListener(this);
+
+ onSdkLoaded();
+
+ setControl(container);
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+
+ mCreateTestCheckbox = new Button(container, SWT.CHECK);
+ mCreateTestCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mCreateTestCheckbox.setText("Create a Test Project");
+ mCreateTestCheckbox.addSelectionListener(this);
+
+ mTestProjectNameLabel = new Label(container, SWT.NONE);
+ mTestProjectNameLabel.setText("Test Project Name:");
+
+ mTestProjectNameText = new Text(container, SWT.BORDER);
+ mTestProjectNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mTestProjectNameText.addModifyListener(this);
+
+ mTestApplicationLabel = new Label(container, SWT.NONE);
+ mTestApplicationLabel.setText("Test Application:");
+
+ mTestApplicationText = new Text(container, SWT.BORDER);
+ mTestApplicationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mTestApplicationText.addModifyListener(this);
+
+ mTestPackageLabel = new Label(container, SWT.NONE);
+ mTestPackageLabel.setText("Test Package:");
+
+ mTestPackageText = new Text(container, SWT.BORDER);
+ mTestPackageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mTestPackageText.addModifyListener(this);
+ }
+
+ /** Controls whether the options for creating a paired test project should be shown */
+ private void showTestOptions(boolean visible) {
+ if (mValues.mode == Mode.SAMPLE) {
+ visible = false;
+ }
+
+ mCreateTestCheckbox.setVisible(visible);
+ mTestProjectNameLabel.setVisible(visible);
+ mTestProjectNameText.setVisible(visible);
+ mTestApplicationLabel.setVisible(visible);
+ mTestApplicationText.setVisible(visible);
+ mTestPackageLabel.setVisible(visible);
+ mTestPackageText.setVisible(visible);
+ }
+
+ /** Controls whether the options for creating a paired test project should be enabled */
+ private void enableTestOptions(boolean enabled) {
+ mTestProjectNameLabel.setEnabled(enabled);
+ mTestProjectNameText.setEnabled(enabled);
+ mTestApplicationLabel.setEnabled(enabled);
+ mTestApplicationText.setEnabled(enabled);
+ mTestPackageLabel.setEnabled(enabled);
+ mTestPackageText.setEnabled(enabled);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ try {
+ mIgnore = true;
+ if (mValues.applicationName != null) {
+ mApplicationText.setText(mValues.applicationName);
+ }
+ if (mValues.packageName != null) {
+ mPackageText.setText(mValues.packageName);
+ } else {
+ mPackageText.setText(DUMMY_PACKAGE);
+ }
+
+ if (mValues.mode != Mode.TEST) {
+ mCreateActivityCheckbox.setSelection(mValues.createActivity);
+ mActivityText.setEnabled(mValues.createActivity);
+ if (mValues.activityName != null) {
+ mActivityText.setText(mValues.activityName);
+ }
+ }
+ if (mValues.minSdk != null && mValues.minSdk.length() > 0) {
+ mSdkCombo.setText(mValues.minSdk);
+ }
+
+ showTestOptions(mValues.mode == Mode.ANY);
+ enableTestOptions(mCreateTestCheckbox.getSelection());
+
+ if (mValues.testProjectName != null) {
+ mTestProjectNameText.setText(mValues.testProjectName);
+ }
+ if (mValues.testApplicationName != null) {
+ mTestApplicationText.setText(mValues.testApplicationName);
+ }
+ if (mValues.testProjectName != null) {
+ mTestPackageText.setText(mValues.testProjectName);
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ // Start focus with the package name, since the other fields are typically assigned
+ // reasonable defaults
+ mPackageText.setFocus();
+ mPackageText.selectAll();
+
+ validatePage();
+ }
+
+ protected void setSdkTargets(IAndroidTarget[] targets, IAndroidTarget target) {
+ if (targets == null) {
+ targets = new IAndroidTarget[0];
+ }
+ int selectionIndex = -1;
+ String[] items = new String[targets.length];
+ for (int i = 0, n = targets.length; i < n; i++) {
+ items[i] = targetLabel(targets[i]);
+ if (targets[i] == target) {
+ selectionIndex = i;
+ }
+ }
+ try {
+ mIgnore = true;
+ mSdkCombo.setItems(items);
+ mSdkCombo.setData(targets);
+ if (selectionIndex != -1) {
+ mSdkCombo.select(selectionIndex);
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ private String targetLabel(IAndroidTarget target) {
+ // In the minimum SDK chooser, show the targets with api number and description,
+ // such as "11 (Android 3.0)"
+ return String.format("%1$s (%2$s)", target.getVersion().getApiString(),
+ target.getFullName());
+ }
+
+ @Override
+ public void dispose() {
+ AdtPlugin.getDefault().removeTargetListener(this);
+ super.dispose();
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ // This page is only needed when creating new projects
+ if (mValues.useExisting || mValues.mode != Mode.ANY) {
+ return true;
+ }
+
+ // Ensure that we reach this page
+ if (mValues.packageName == null) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mSdkCombo) {
+ mValues.minSdk = mSdkCombo.getText().trim();
+ IAndroidTarget[] targets = (IAndroidTarget[]) mSdkCombo.getData();
+ // An editable combo will treat item selection the same way as a user edit,
+ // so we need to see if the string looks like a labeled version
+ int index = mSdkCombo.getSelectionIndex();
+ if (index != -1) {
+ if (index >= 0 && index < targets.length) {
+ IAndroidTarget target = targets[index];
+ if (targetLabel(target).equals(mValues.minSdk)) {
+ mValues.minSdk = target.getVersion().getApiString();
+ }
+ }
+ }
+
+ // Ensure that we never pick up the (Android x.y) suffix shown in combobox
+ // for readability
+ int separator = mValues.minSdk.indexOf(' ');
+ if (separator != -1) {
+ mValues.minSdk = mValues.minSdk.substring(0, separator);
+ }
+ mValues.minSdkModifiedByUser = true;
+ mValues.updateSdkTargetToMatchMinSdkVersion();
+ } else if (source == mApplicationText) {
+ mValues.applicationName = mApplicationText.getText().trim();
+ mValues.applicationNameModifiedByUser = true;
+
+ if (!mValues.testApplicationNameModified) {
+ mValues.testApplicationName = suggestTestApplicationName(mValues.applicationName);
+ try {
+ mIgnore = true;
+ mTestApplicationText.setText(mValues.testApplicationName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ } else if (source == mPackageText) {
+ mValues.packageName = mPackageText.getText().trim();
+ mValues.packageNameModifiedByUser = true;
+
+ if (!mValues.testPackageModified) {
+ mValues.testPackageName = suggestTestPackage(mValues.packageName);
+ try {
+ mIgnore = true;
+ mTestPackageText.setText(mValues.testPackageName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ } else if (source == mActivityText) {
+ mValues.activityName = mActivityText.getText().trim();
+ mValues.activityNameModifiedByUser = true;
+ } else if (source == mTestApplicationText) {
+ mValues.testApplicationName = mTestApplicationText.getText().trim();
+ mValues.testApplicationNameModified = true;
+ } else if (source == mTestPackageText) {
+ mValues.testPackageName = mTestPackageText.getText().trim();
+ mValues.testPackageModified = true;
+ } else if (source == mTestProjectNameText) {
+ mValues.testProjectName = mTestProjectNameText.getText().trim();
+ mValues.testProjectModified = true;
+ }
+
+ validatePage();
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+
+ if (source == mCreateActivityCheckbox) {
+ mValues.createActivity = mCreateActivityCheckbox.getSelection();
+ mActivityText.setEnabled(mValues.createActivity);
+ } else if (source == mSdkCombo) {
+ int index = mSdkCombo.getSelectionIndex();
+ IAndroidTarget[] targets = (IAndroidTarget[]) mSdkCombo.getData();
+ if (index != -1) {
+ if (index >= 0 && index < targets.length) {
+ IAndroidTarget target = targets[index];
+ // Even though we are showing the logical version name, we place the
+ // actual api number as the minimum SDK
+ mValues.minSdk = target.getVersion().getApiString();
+ }
+ } else {
+ String text = mSdkCombo.getText();
+ boolean found = false;
+ for (IAndroidTarget target : targets) {
+ if (targetLabel(target).equals(text)) {
+ mValues.minSdk = target.getVersion().getApiString();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ mValues.minSdk = text;
+ }
+ }
+ } else if (source == mCreateTestCheckbox) {
+ mValues.createPairProject = mCreateTestCheckbox.getSelection();
+ enableTestOptions(mValues.createPairProject);
+ if (mValues.createPairProject) {
+ if (mValues.testProjectName == null || mValues.testProjectName.length() == 0) {
+ mValues.testProjectName = suggestTestProjectName(mValues.projectName);
+ }
+ if (mValues.testApplicationName == null ||
+ mValues.testApplicationName.length() == 0) {
+ mValues.testApplicationName =
+ suggestTestApplicationName(mValues.applicationName);
+ }
+ if (mValues.testPackageName == null || mValues.testPackageName.length() == 0) {
+ mValues.testPackageName = suggestTestPackage(mValues.packageName);
+ }
+
+ try {
+ mIgnore = true;
+ mTestProjectNameText.setText(mValues.testProjectName);
+ mTestApplicationText.setText(mValues.testApplicationName);
+ mTestPackageText.setText(mValues.testPackageName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ }
+
+ validatePage();
+ }
+
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ private void validatePage() {
+ IStatus status = validatePackage(mValues.packageName);
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validActivity = validateActivity();
+ if (validActivity != null) {
+ status = validActivity;
+ }
+ }
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validMinSdk = validateMinSdk();
+ if (validMinSdk != null) {
+ status = validMinSdk;
+ }
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validSourceFolder = validateSourceFolder();
+ if (validSourceFolder != null) {
+ status = validSourceFolder;
+ }
+ }
+
+ // If creating a test project to go along with the main project, also validate
+ // the additional test project parameters
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ if (mValues.createPairProject) {
+ IStatus validTestProject = ProjectNamePage.validateProjectName(
+ mValues.testProjectName);
+ if (validTestProject != null) {
+ status = validTestProject;
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validTestLocation = validateTestProjectLocation();
+ if (validTestLocation != null) {
+ status = validTestLocation;
+ }
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validTestPackage = validatePackage(mValues.testPackageName);
+ if (validTestPackage != null) {
+ status = new Status(validTestPackage.getSeverity(),
+ AdtPlugin.PLUGIN_ID,
+ validTestPackage.getMessage() + " (in test package)");
+ }
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ if (mValues.projectName.equals(mValues.testProjectName)) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "The main project name and the test project name must be different.");
+ }
+ }
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ private IStatus validateTestProjectLocation() {
+ assert mValues.createPairProject;
+
+ // Validate location
+ Path path = new Path(mValues.projectLocation.getPath());
+ if (!mValues.useExisting) {
+ if (!mValues.useDefaultLocation) {
+ // If not using the default value validate the location.
+ URI uri = URIUtil.toURI(path.toOSString());
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IProject handle = workspace.getRoot().getProject(mValues.testProjectName);
+ IStatus locationStatus = workspace.validateProjectLocationURI(handle, uri);
+ if (!locationStatus.isOK()) {
+ return locationStatus;
+ }
+ // 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 new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A directory name must be specified.");
+ } 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 != null && l.length != 0) {
+ return new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "The selected output directory is not empty.");
+ }
+ }
+ } else {
+ IPath destPath = path.removeLastSegments(1).append(mValues.testProjectName);
+ File dest = destPath.toFile();
+ if (dest.exists()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "There is already a file or directory named \"%1$s\" in the selected location.",
+ mValues.testProjectName));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private IStatus validateSourceFolder() {
+ // This check does nothing when creating a new project.
+ // This check is also useless when no activity is present or created.
+ mValues.sourceFolder = SdkConstants.FD_SOURCES;
+ if (!mValues.useExisting || !mValues.createActivity) {
+ return null;
+ }
+
+ String osTarget = mValues.activityName;
+ if (osTarget.indexOf('.') == -1) {
+ osTarget = mValues.packageName + File.separator + osTarget;
+ } else if (osTarget.indexOf('.') == 0) {
+ osTarget = mValues.packageName + osTarget;
+ }
+ osTarget = osTarget.replace('.', File.separatorChar) + AdtConstants.DOT_JAVA;
+
+ File projectDir = mValues.projectLocation;
+ File[] allDirs = projectDir.listFiles(new FileFilter() {
+ public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+ });
+ if (allDirs != null) {
+ boolean found = false;
+ for (File f : allDirs) {
+ Path path = new Path(f.getAbsolutePath());
+ File java_activity = path.append(osTarget).toFile();
+ if (java_activity.isFile()) {
+ mValues.sourceFolder = f.getName();
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ String projectPath = projectDir.getPath();
+ if (allDirs.length > 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("%1$s can not be found under %2$s.", osTarget,
+ projectPath));
+ } else {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("No source folders can be found in %1$s.",
+ projectPath));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private IStatus validateMinSdk() {
+ // Validate min SDK field
+ // If the min sdk version is empty, it is always accepted.
+ if (mValues.minSdk == null || mValues.minSdk.length() == 0) {
+ return null;
+ }
+
+ IAndroidTarget target = mValues.target;
+ if (target == null) {
+ return null;
+ }
+
+ // If the current target is a preview, explicitly indicate minSdkVersion
+ // must be set to this target name.
+ if (target.getVersion().isPreview() && !target.getVersion().equals(mValues.minSdk)) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "The SDK target is a preview. Min SDK Version must be set to '%s'.",
+ target.getVersion().getCodename()));
+ }
+
+ if (!target.getVersion().equals(mValues.minSdk)) {
+ return new Status(target.getVersion().isPreview() ? IStatus.ERROR : IStatus.WARNING,
+ AdtPlugin.PLUGIN_ID,
+ "The API level for the selected SDK target does not match the Min SDK Version."
+ );
+ }
+
+ return null;
+ }
+
+ private IStatus validatePackage(String packageFieldContents) {
+ // Validate package
+ if (packageFieldContents == null || packageFieldContents.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package name must be specified.");
+ } else if (packageFieldContents.equals(DUMMY_PACKAGE)) {
+ // The dummy package name is just a placeholder package (which isn't even valid
+ // because it contains the reserved Java keyword "package") but we want to
+ // make the error message say that a proper package should be entered rather than
+ // what's wrong with this specific package. (And the reason we provide a dummy
+ // package rather than a blank line is to make it more clear to beginners what
+ // we're looking for.
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package name must be specified.");
+ }
+ // Check it's a valid package string
+ IStatus status = JavaConventions.validatePackageName(packageFieldContents, JDK_15,
+ JDK_15);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ // 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 (packageFieldContents.indexOf('.') == -1) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package name must have at least two identifiers.");
+ }
+
+ return null;
+ }
+
+ private IStatus validateActivity() {
+ // Validate activity (if creating an activity)
+ if (!mValues.createActivity) {
+ return null;
+ }
+
+ // Validate activity field
+ String activityFieldContents = mValues.activityName;
+ if (activityFieldContents == null || activityFieldContents.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Activity name must be specified.");
+ } else if (ACTIVITY_NAME_SUFFIX.equals(activityFieldContents)) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a valid activity name");
+ } else if (activityFieldContents.contains("..")) { //$NON-NLS-1$
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package segments in activity name cannot be empty (..)");
+ }
+ // The activity field can actually contain part of a sub-package name
+ // or it can start with a dot "." to indicates it comes from the parent package
+ // name.
+ String packageName = ""; //$NON-NLS-1$
+ int pos = activityFieldContents.lastIndexOf('.');
+ if (pos >= 0) {
+ packageName = activityFieldContents.substring(0, pos);
+ if (packageName.startsWith(".")) { //$NON-NLS-1$
+ packageName = packageName.substring(1);
+ }
+
+ activityFieldContents = activityFieldContents.substring(pos + 1);
+ }
+
+ // the activity field can contain a simple java identifier, or a
+ // package name or one that starts with a dot. So if it starts with a dot,
+ // ignore this dot -- the rest must look like a package name.
+ if (activityFieldContents.length() > 0 && activityFieldContents.charAt(0) == '.') {
+ activityFieldContents = activityFieldContents.substring(1);
+ }
+
+ // Check it's a valid activity string
+ IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents, JDK_15,
+ JDK_15);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ // Check it's a valid package string
+ if (packageName.length() > 0) {
+ status = JavaConventions.validatePackageName(packageName, JDK_15, JDK_15);
+ if (!status.isOK()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ status.getMessage() + " (in the activity name)");
+ }
+ }
+
+ return null;
+ }
+
+ // ---- Implement ITargetChangeListener ----
+
+ public void onSdkLoaded() {
+ if (mSdkCombo == null) {
+ return;
+ }
+
+ // 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();
+ }
+ setSdkTargets(targets, mValues.target);
+ }
+
+ public void onProjectTargetChange(IProject changedProject) {
+ // Ignore
+ }
+
+ public void onTargetLoaded(IAndroidTarget target) {
+ // Ignore
+ }
+
+ public static String suggestTestApplicationName(String applicationName) {
+ if (applicationName == null) {
+ applicationName = ""; //$NON-NLS-1$
+ }
+ if (applicationName.indexOf(' ') != -1) {
+ return applicationName + " Test"; //$NON-NLS-1$
+ } else {
+ return applicationName + "Test"; //$NON-NLS-1$
+ }
+ }
+
+ public static String suggestTestProjectName(String projectName) {
+ if (projectName == null) {
+ projectName = ""; //$NON-NLS-1$
+ }
+ if (projectName.length() > 0 && Character.isUpperCase(projectName.charAt(0))) {
+ return projectName + "Test"; //$NON-NLS-1$
+ } else {
+ return projectName + "-test"; //$NON-NLS-1$
+ }
+ }
+
+
+ public static String suggestTestPackage(String packagePath) {
+ if (packagePath == null) {
+ packagePath = ""; //$NON-NLS-1$
+ }
+ return packagePath + ".test"; //$NON-NLS-1$
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java
deleted file mode 100644
index df72af6..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java
+++ /dev/null
@@ -1,1809 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * References:
- * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard
- * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
- */
-
-package com.android.ide.eclipse.adt.internal.wizards.newproject;
-
-import static com.android.ide.eclipse.adt.AdtUtils.capitalize;
-import static com.android.ide.eclipse.adt.AdtUtils.extractClassName;
-import static com.android.ide.eclipse.adt.AdtUtils.stripWhitespace;
-
-import com.android.ide.eclipse.adt.AdtConstants;
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage.TestInfo;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
-import com.android.sdklib.xml.AndroidManifest;
-import com.android.sdklib.xml.ManifestData;
-import com.android.sdklib.xml.ManifestData.Activity;
-import com.android.sdkuilib.internal.widgets.SdkTargetSelector;
-
-import org.eclipse.core.filesystem.URIUtil;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IWorkspace;
-import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.jdt.core.JavaConventions;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.osgi.util.TextProcessor;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.ControlAdapter;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.DirectoryDialog;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Group;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.ui.IWorkbenchPart;
-import org.eclipse.ui.IWorkingSet;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-/**
- * NewAndroidProjectCreationPage is a project creation page that provides the
- * following fields:
- * <ul>
- * <li> Project name
- * <li> SDK Target
- * <li> Application name
- * <li> Package name
- * <li> Activity name
- * </ul>
- * Note: this class is public so that it can be accessed from unit tests.
- * It is however an internal class. Its API may change without notice.
- * It should semantically be considered as a private final class.
- * Do not derive from this class.
- */
-public class NewProjectCreationPage extends WizardPage {
- /** Suffix added by default to activity names */
- private static final String ACTIVITY_NAME_SUFFIX = "Activity"; //$NON-NLS-1$
-
- // constants
- private static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$
-
- /** Initial value for all name fields (project, activity, application, package). Used
- * whenever a value is requested before controls are created. */
- private static final String INITIAL_NAME = ""; //$NON-NLS-1$
- /** Initial value for the Create New Project radio. */
- private static final boolean INITIAL_CREATE_NEW_PROJECT = true;
- /** Initial value for the Create Project From Sample. */
- private static final boolean INITIAL_CREATE_FROM_SAMPLE = false;
- /** Initial value for the Create Project From Existing Source. */
- private static final boolean INITIAL_CREATE_FROM_SOURCE = false;
- /** Initial value for the Use Default Location check box. */
- private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
- /** Initial value for the Create Activity check box. */
- private static final boolean INITIAL_CREATE_ACTIVITY = true;
-
-
- /** Pattern for characters accepted in a project name. Since this will be used as a
- * directory name, we're being a bit conservative on purpose. It cannot start with a space. */
- private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$
- /** Last user-browsed location, static so that it be remembered for the whole session */
- private static String sCustomLocationOsPath = ""; //$NON-NLS-1$
- private static boolean sAutoComputeCustomLocation = true;
-
- private final int MSG_NONE = 0;
- private final int MSG_WARNING = 1;
- private final int MSG_ERROR = 2;
-
- /** Structure with the externally visible information from this Main Project page. */
- private final MainInfo mInfo = new MainInfo();
- /** Structure with the externally visible information from the Test Project page.
- * This is null if there's no such page, meaning the main project page is standalone. */
- private TestInfo mTestInfo;
-
- private String mUserPackageName = ""; //$NON-NLS-1$
- private String mUserActivityName = ""; //$NON-NLS-1$
- private boolean mUserCreateActivityCheck = INITIAL_CREATE_ACTIVITY;
- private String mSourceFolder = ""; //$NON-NLS-1$
-
- // widgets
- private Text mProjectNameField;
- private Text mPackageNameField;
- private Text mActivityNameField;
- private Text mApplicationNameField;
- private Button mCreateNewProjectRadio;
- private Button mCreateFromSampleRadio;
- private Button mUseDefaultLocation;
- private Label mLocationLabel;
- private Text mLocationPathField;
- private Button mBrowseButton;
- private Button mCreateActivityCheck;
- private Text mMinSdkVersionField;
- private SdkTargetSelector mSdkTargetSelector;
- private ITargetChangeListener mSdkTargetChangeListener;
-
- private boolean mInternalLocationPathUpdate;
- private boolean mInternalProjectNameUpdate;
- private boolean mInternalApplicationNameUpdate;
- private boolean mInternalCreateActivityUpdate;
- private boolean mInternalActivityNameUpdate;
- private boolean mInternalMinSdkUpdate;
- private boolean mProjectNameModifiedByUser;
- private boolean mApplicationNameModifiedByUser;
- private boolean mActivityNameModifiedByUser;
- private boolean mMinSdkModifiedByUser;
-
- private final ArrayList<String> mSamplesPaths = new ArrayList<String>();
- private Combo mSamplesCombo;
- private WorkingSetGroup mWorkingSetGroup;
-
-
- /**
- * Creates a new project creation wizard page.
- */
- public NewProjectCreationPage() {
- super(MAIN_PAGE_NAME);
- setPageComplete(false);
- setTitle("New Android Project");
- setDescription("Creates a new Android Project resource.");
- mWorkingSetGroup = new WorkingSetGroup();
- setWorkingSets(new IWorkingSet[0]);
- }
-
- public void init(IStructuredSelection selection, IWorkbenchPart activePart) {
- setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart));
- }
-
- // --- Getters used by NewProjectWizard ---
-
-
- /**
- * Structure that collects all externally visible information from this page.
- * This is used by the calling wizard to actually do the work or by other pages.
- * <p/>
- * This interface is provided so that the adt-test counterpart can override the returned
- * information.
- */
- public interface IMainInfo {
- public IPath getLocationPath();
- /**
- * Returns the current project location path as entered by the user, or its
- * anticipated initial value. Note that if the default has been returned the
- * path in a project description used to create a project should not be set.
- *
- * @return the project location path or its anticipated initial value.
- */
- /** Returns the value of the project name field with leading and trailing spaces removed. */
- public String getProjectName();
- /** Returns the value of the package name field with spaces trimmed. */
- public String getPackageName();
- /** Returns the value of the activity name field with spaces trimmed. */
- public String getActivityName();
- /** Returns the value of the min sdk version field with spaces trimmed. */
- public String getMinSdkVersion();
- /** Returns the value of the application name field with spaces trimmed. */
- public String getApplicationName();
- /** Returns the value of the "Create New Project" radio. */
- public boolean isNewProject();
- /** Returns the value of the "Create Activity" checkbox. */
- public boolean isCreateActivity();
- /** Returns the value of the Use Default Location field. */
- public boolean useDefaultLocation();
- /** Returns the internal source folder (for the "existing project" mode) or the default
- * "src" constant. */
- public String getSourceFolder();
- /** Returns the current sdk target or null if none has been selected yet. */
- public IAndroidTarget getSdkTarget();
- /** Returns the current working sets or null if none has been selected yet. */
- public IWorkingSet[] getSelectedWorkingSets();
-
- }
-
- /**
- * Structure that collects all externally visible information from this page.
- * This is used by the calling wizard to actually do the work or by other pages.
- */
- public class MainInfo implements IMainInfo {
- /**
- * Returns the current project location path as entered by the user, or its
- * anticipated initial value. Note that if the default has been returned the
- * path in a project description used to create a project should not be set.
- *
- * @return the project location path or its anticipated initial value.
- */
- public IPath getLocationPath() {
- return new Path(getProjectLocation());
- }
-
- /** Returns the value of the project name field with leading and trailing spaces removed. */
- public String getProjectName() {
- return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
- }
-
- /** Returns the value of the package name field with spaces trimmed. */
- public String getPackageName() {
- return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
- }
-
- /** Returns the value of the activity name field with spaces trimmed. */
- public String getActivityName() {
- return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim();
- }
-
- /** Returns the value of the min sdk version field with spaces trimmed. */
- public String getMinSdkVersion() {
- return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim(); //$NON-NLS-1$
- }
-
- /** Returns the value of the application name field with spaces trimmed. */
- public String getApplicationName() {
- // Return the name of the activity as default application name.
- return mApplicationNameField == null ? getActivityName()
- : mApplicationNameField.getText().trim();
-
- }
-
- /** Returns the value of the "Create New Project" radio. */
- public boolean isNewProject() {
- return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT
- : mCreateNewProjectRadio.getSelection();
- }
-
- /** Returns the value of the "Create from Existing Sample" radio. */
- public boolean isCreateFromSample() {
- return mCreateFromSampleRadio == null ? INITIAL_CREATE_FROM_SAMPLE
- : mCreateFromSampleRadio.getSelection();
- }
-
- /** Returns the value of the "Create Activity" checkbox. */
- public boolean isCreateActivity() {
- return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY
- : mCreateActivityCheck.getSelection();
- }
-
- /** Returns the value of the Use Default Location field. */
- public boolean useDefaultLocation() {
- return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
- : mUseDefaultLocation.getSelection();
- }
-
- /** Returns the internal source folder (for the "existing project" mode) or the default
- * "src" constant. */
- public String getSourceFolder() {
- if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) {
- return SdkConstants.FD_SOURCES;
- } else {
- return mSourceFolder;
- }
- }
-
- /** Returns the current sdk target or null if none has been selected yet. */
- public IAndroidTarget getSdkTarget() {
- return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
- }
-
- /** Returns the current sdk target or null if none has been selected yet. */
- public IWorkingSet[] getSelectedWorkingSets() {
- return getWorkingSets();
- }
- }
-
- /**
- * Returns a {@link MainInfo} structure that collects all externally visible information
- * from this page, to be used by the calling wizard or by other pages.
- */
- public IMainInfo getMainInfo() {
- return mInfo;
- }
-
- /**
- * Grabs the {@link TestInfo} structure that collects externally visible fields from the
- * test project page. This may be null.
- */
- public void setTestInfo(TestInfo testInfo) {
- mTestInfo = testInfo;
- }
-
- /**
- * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
- * the dialog is made visible.
- */
- @Override
- public void setVisible(boolean visible) {
- super.setVisible(visible);
- if (visible) {
- mProjectNameField.setFocus();
- validatePageComplete();
- }
- }
-
- // --- UI creation ---
-
- /**
- * Creates the top level control for this dialog page under the given parent
- * composite.
- *
- * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
- */
- public void createControl(Composite parent) {
- final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
- scrolledComposite.setFont(parent.getFont());
- scrolledComposite.setExpandHorizontal(true);
- scrolledComposite.setExpandVertical(true);
- initializeDialogUnits(parent);
-
- final Composite composite = new Composite(scrolledComposite, SWT.NULL);
- composite.setFont(parent.getFont());
- scrolledComposite.setContent(composite);
-
- composite.setLayout(new GridLayout());
- composite.setLayoutData(new GridData(GridData.FILL_BOTH));
-
- createProjectNameGroup(composite);
- createLocationGroup(composite);
- createTargetGroup(composite);
- createPropertiesGroup(composite);
- createWorkingSetGroup(composite);
-
- // Update state the first time
- enableLocationWidgets();
- loadSamplesForTarget(null /*target*/);
- mSdkTargetChangeListener.onSdkLoaded();
-
- scrolledComposite.addControlListener(new ControlAdapter() {
- @Override
- public void controlResized(ControlEvent e) {
- Rectangle r = scrolledComposite.getClientArea();
- scrolledComposite.setMinSize(composite.computeSize(r.width, SWT.DEFAULT));
- }
- });
-
- // Show description the first time
- setErrorMessage(null);
- setMessage(null);
- setControl(scrolledComposite);
-
- // Validate. This will complain about the first empty field.
- validatePageComplete();
- }
-
- @Override
- public void dispose() {
-
- if (mSdkTargetChangeListener != null) {
- AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
- mSdkTargetChangeListener = null;
- }
-
- super.dispose();
- }
-
- /**
- * Creates the group for the project name:
- * [label: "Project Name"] [text field]
- *
- * @param parent the parent composite
- */
- private final void createProjectNameGroup(Composite parent) {
- Composite group = new Composite(parent, SWT.NONE);
- GridLayout layout = new GridLayout();
- layout.numColumns = 2;
- group.setLayout(layout);
- group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
-
- // new project label
- Label label = new Label(group, SWT.NONE);
- label.setText("Project name:");
- label.setFont(parent.getFont());
- label.setToolTipText("Name of the Eclipse project to create. It cannot be empty.");
-
- // new project name entry field
- mProjectNameField = new Text(group, SWT.BORDER);
- GridData data = new GridData(GridData.FILL_HORIZONTAL);
- mProjectNameField.setToolTipText("Name of the Eclipse project to create. It cannot be empty.");
- mProjectNameField.setLayoutData(data);
- mProjectNameField.setFont(parent.getFont());
- mProjectNameField.addListener(SWT.Modify, new Listener() {
- public void handleEvent(Event event) {
- onProjectFieldModified();
- }
- });
- }
-
- /**
- * Creates the group for the Project options:
- * [radio] Create new project
- * [radio] Create project from existing sources
- * [check] Use default location
- * Location [text field] [browse button]
- *
- * @param parent the parent composite
- */
- private final void createLocationGroup(Composite parent) {
- Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
- // Layout has 4 columns of non-equal size
- group.setLayout(new GridLayout());
- group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- group.setFont(parent.getFont());
- group.setText("Contents");
-
- mCreateNewProjectRadio = new Button(group, SWT.RADIO);
- mCreateNewProjectRadio.setText("Create new project in workspace");
- mCreateNewProjectRadio.setSelection(INITIAL_CREATE_NEW_PROJECT);
-
- Button existing_project_radio = new Button(group, SWT.RADIO);
- existing_project_radio.setText("Create project from existing source");
- existing_project_radio.setSelection(INITIAL_CREATE_FROM_SOURCE);
-
- mUseDefaultLocation = new Button(group, SWT.CHECK);
- mUseDefaultLocation.setText("Use default location");
- mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION);
-
- SelectionListener location_listener = new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- super.widgetSelected(e);
- enableLocationWidgets();
- extractNamesFromAndroidManifest();
- validatePageComplete();
- }
- };
-
- mCreateNewProjectRadio.addSelectionListener(location_listener);
- existing_project_radio.addSelectionListener(location_listener);
- mUseDefaultLocation.addSelectionListener(location_listener);
-
- Composite location_group = new Composite(group, SWT.NONE);
- location_group.setLayout(new GridLayout(3, /* num columns */
- false /* columns of not equal size */));
- location_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- location_group.setFont(parent.getFont());
-
- mLocationLabel = new Label(location_group, SWT.NONE);
- mLocationLabel.setText("Location:");
-
- mLocationPathField = new Text(location_group, SWT.BORDER);
- GridData data = new GridData(GridData.FILL, /* horizontal alignment */
- GridData.BEGINNING, /* vertical alignment */
- true, /* grabExcessHorizontalSpace */
- false, /* grabExcessVerticalSpace */
- 1, /* horizontalSpan */
- 1); /* verticalSpan */
- mLocationPathField.setLayoutData(data);
- mLocationPathField.setFont(parent.getFont());
- mLocationPathField.addListener(SWT.Modify, new Listener() {
- public void handleEvent(Event event) {
- onLocationPathFieldModified();
- }
- });
-
- mBrowseButton = new Button(location_group, SWT.PUSH);
- mBrowseButton.setText("Browse...");
- setButtonLayoutData(mBrowseButton);
- mBrowseButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onOpenDirectoryBrowser();
- }
- });
-
- mCreateFromSampleRadio = new Button(group, SWT.RADIO);
- mCreateFromSampleRadio.setText("Create project from existing sample");
- mCreateFromSampleRadio.setSelection(INITIAL_CREATE_FROM_SAMPLE);
- mCreateFromSampleRadio.addSelectionListener(location_listener);
-
- Composite samples_group = new Composite(group, SWT.NONE);
- samples_group.setLayout(new GridLayout(2, /* num columns */
- false /* columns of not equal size */));
- samples_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- samples_group.setFont(parent.getFont());
-
- new Label(samples_group, SWT.NONE).setText("Samples:");
-
- if (Platform.getWS().equals(Platform.WS_GTK)) {
- mSamplesCombo = new Combo(samples_group, SWT.SIMPLE | SWT.READ_ONLY);
- } else {
- mSamplesCombo = new Combo(samples_group, SWT.DROP_DOWN | SWT.READ_ONLY);
- }
- mSamplesCombo.setEnabled(false);
- mSamplesCombo.select(0);
- mSamplesCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- mSamplesCombo.setToolTipText("Select a sample");
-
- mSamplesCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onSampleSelected();
- }
- });
-
- }
-
- /**
- * Creates the target group.
- * It only contains an SdkTargetSelector.
- */
- private void createTargetGroup(Composite parent) {
- Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
- // Layout has 1 column
- group.setLayout(new GridLayout());
- group.setLayoutData(new GridData(GridData.FILL_BOTH));
- group.setFont(parent.getFont());
- group.setText("Build Target");
-
- // The selector is created without targets. They are added below in the change listener.
- mSdkTargetSelector = new SdkTargetSelector(group, null);
-
- mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onSdkTargetModified();
- updateLocationPathField(null);
- validatePageComplete();
- }
- });
-
- mSdkTargetChangeListener = new ITargetChangeListener() {
- public void onSdkLoaded() {
- // Update the sdk target selector with the new targets
-
- // get the targets from the sdk
- IAndroidTarget[] targets = null;
- if (Sdk.getCurrent() != null) {
- targets = Sdk.getCurrent().getTargets();
- }
- mSdkTargetSelector.setTargets(targets);
-
- // If there's only one target, select it.
- // This will invoke the selection listener on the selector defined above.
- if (targets != null && targets.length == 1) {
- mSdkTargetSelector.setSelection(targets[0]);
- } else if (targets != null) {
- // Pick the highest available platform by default (see issue #17505
- // for related discussion.)
- IAndroidTarget initialTarget = null;
- for (IAndroidTarget target : targets) {
- if (target.isPlatform()
- && !target.getVersion().isPreview()
- && (initialTarget == null ||
- target.getVersion().getApiLevel() >
- initialTarget.getVersion().getApiLevel())) {
- initialTarget = target;
- }
- }
- if (initialTarget != null) {
- mSdkTargetSelector.setSelection(initialTarget);
- }
- }
- }
-
- public void onProjectTargetChange(IProject changedProject) {
- // Ignore
- }
-
- public void onTargetLoaded(IAndroidTarget target) {
- // Ignore
- }
- };
-
- AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
- }
-
- /**
- * Creates the group for the project properties:
- * - Package name [text field]
- * - Activity name [text field]
- * - Application name [text field]
- *
- * @param parent the parent composite
- */
- private final void createPropertiesGroup(Composite parent) {
- // package specification group
- Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
- GridLayout layout = new GridLayout();
- layout.numColumns = 2;
- group.setLayout(layout);
- group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- group.setFont(parent.getFont());
- group.setText("Properties");
-
- // new application label
- Label label = new Label(group, SWT.NONE);
- label.setText("Application name:");
- label.setFont(parent.getFont());
- label.setToolTipText("Name of the Application. This is a free string. It can be empty.");
-
- // new application name entry field
- mApplicationNameField = new Text(group, SWT.BORDER);
- GridData data = new GridData(GridData.FILL_HORIZONTAL);
- mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty.");
- mApplicationNameField.setLayoutData(data);
- mApplicationNameField.setFont(parent.getFont());
- mApplicationNameField.addListener(SWT.Modify, new Listener() {
- public void handleEvent(Event event) {
- onApplicationFieldModified();
- }
- });
-
- // new package label
- label = new Label(group, SWT.NONE);
- label.setText("Package name:");
- label.setFont(parent.getFont());
- label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
-
- // new package name entry field
- mPackageNameField = new Text(group, SWT.BORDER);
- data = new GridData(GridData.FILL_HORIZONTAL);
- mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
- mPackageNameField.setLayoutData(data);
- mPackageNameField.setFont(parent.getFont());
- mPackageNameField.addListener(SWT.Modify, new Listener() {
- public void handleEvent(Event event) {
- onPackageNameFieldModified();
- }
- });
-
- // new activity label
- mCreateActivityCheck = new Button(group, SWT.CHECK);
- mCreateActivityCheck.setText("Create Activity:");
- mCreateActivityCheck.setToolTipText("Specifies if you want to create a default Activity.");
- mCreateActivityCheck.setFont(parent.getFont());
- mCreateActivityCheck.setSelection(INITIAL_CREATE_ACTIVITY);
- mCreateActivityCheck.addListener(SWT.Selection, new Listener() {
- public void handleEvent(Event event) {
- onCreateActivityCheckModified();
- enableLocationWidgets();
- }
- });
-
- // new activity name entry field
- mActivityNameField = new Text(group, SWT.BORDER);
- data = new GridData(GridData.FILL_HORIZONTAL);
- mActivityNameField.setToolTipText("Name of the Activity class to create. Must be a valid Java identifier.");
- mActivityNameField.setLayoutData(data);
- mActivityNameField.setFont(parent.getFont());
- mActivityNameField.addListener(SWT.Modify, new Listener() {
- public void handleEvent(Event event) {
- onActivityNameFieldModified();
- }
- });
-
- // min sdk version label
- label = new Label(group, SWT.NONE);
- label.setText("Min SDK Version:");
- label.setFont(parent.getFont());
- label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
-
- // min sdk version entry field
- mMinSdkVersionField = new Text(group, SWT.BORDER);
- data = new GridData(GridData.FILL_HORIZONTAL);
- label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
- mMinSdkVersionField.setLayoutData(data);
- mMinSdkVersionField.setFont(parent.getFont());
- mMinSdkVersionField.addListener(SWT.Modify, new Listener() {
- public void handleEvent(Event event) {
- onMinSdkFieldUpdated();
- validatePageComplete();
- }
- });
- }
-
- private void createWorkingSetGroup(final Composite composite) {
- Composite group = mWorkingSetGroup.createControl(composite);
- group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- }
-
-
- //--- Internal getters & setters ------------------
-
- /** Returns the location path field value with spaces trimmed. */
- private String getLocationPathFieldValue() {
- return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$
- }
-
- /** Returns the current selected sample path,
- * or an empty string if there's no valid selection. */
- private String getSelectedSamplePath() {
- int selIndex = mSamplesCombo.getSelectionIndex();
- if (selIndex >= 0 && selIndex < mSamplesPaths.size()) {
- return mSamplesPaths.get(selIndex);
- }
- return "";
- }
-
- /** Returns the current project location, depending on the Use Default Location check box
- * or the Create From Sample check box. */
- private String getProjectLocation() {
- if (mInfo.isCreateFromSample()) {
- return getSelectedSamplePath();
- } else if (mInfo.isNewProject() && mInfo.useDefaultLocation()) {
- return Platform.getLocation().toString();
- } else {
- return getLocationPathFieldValue();
- }
- }
-
- /**
- * Creates a project resource handle for the current project name field
- * value.
- * <p>
- * This method does not create the project resource; this is the
- * responsibility of <code>IProject::create</code> invoked by the new
- * project resource wizard.
- * </p>
- *
- * @return the new project resource handle
- */
- private IProject getProjectHandle() {
- return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName());
- }
-
- // --- UI Callbacks ----
-
- /**
- * Display a directory browser and update the location path field with the selected path
- */
- private void onOpenDirectoryBrowser() {
-
- String existing_dir = getLocationPathFieldValue();
-
- // Disable the path if it doesn't exist
- if (existing_dir.length() == 0) {
- existing_dir = null;
- } else {
- File f = new File(existing_dir);
- if (!f.exists()) {
- existing_dir = null;
- }
- }
-
- DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
- dd.setMessage("Browse for folder");
- dd.setFilterPath(existing_dir);
- String abs_dir = dd.open();
-
- if (abs_dir != null) {
- updateLocationPathField(abs_dir);
- extractNamesFromAndroidManifest();
- validatePageComplete();
- }
- }
-
- /**
- * A sample was selected. Update the location field, manifest and validate.
- */
- private void onSampleSelected() {
- if (mInfo.isCreateFromSample()) {
- // Note that getProjectLocation() is automatically updated to use the currently
- // selected sample. We just need to refresh the manifest data & validate.
- extractNamesFromAndroidManifest();
- validatePageComplete();
- }
- }
-
- /**
- * Enables or disable the location widgets depending on the user selection:
- * the location path is enabled when using the "existing source" mode (i.e. not new project)
- * or in new project mode with the "use default location" turned off.
- */
- private void enableLocationWidgets() {
- boolean is_new_project = mInfo.isNewProject();
- boolean is_create_from_sample = mInfo.isCreateFromSample();
- boolean use_default = mInfo.useDefaultLocation() && !is_create_from_sample;
- boolean location_enabled = (!is_new_project || !use_default) && !is_create_from_sample;
- boolean create_activity = mInfo.isCreateActivity();
-
- mUseDefaultLocation.setEnabled(is_new_project);
-
- mLocationLabel.setEnabled(location_enabled);
- mLocationPathField.setEnabled(location_enabled);
- mBrowseButton.setEnabled(location_enabled);
-
- mSamplesCombo.setEnabled(is_create_from_sample && mSamplesPaths.size() > 0);
-
- // Most fields are only editable in new-project mode. When importing
- // an existing project/sample we won't edit existing files anyway so the
- // user won't be able to customize them,
- mApplicationNameField.setEnabled(is_new_project);
- mMinSdkVersionField.setEnabled(is_new_project);
- mPackageNameField.setEnabled(is_new_project);
- mCreateActivityCheck.setEnabled(is_new_project);
- mActivityNameField.setEnabled(is_new_project & create_activity);
-
- updateLocationPathField(null);
- updatePackageAndActivityFields();
- }
-
- /**
- * Updates the location directory path field.
- * <br/>
- * When custom user selection is enabled, use the abs_dir argument if not null and also
- * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the
- * user selection to be remembered when the user switches from default to custom.
- * <br/>
- * When custom user selection is disabled, use the workspace default location with the
- * current project name. This does not change the internally cached abs_dir.
- *
- * @param abs_dir A new absolute directory path or null to use the default.
- */
- private void updateLocationPathField(String abs_dir) {
-
- // We don't touch the location path if using the "Create From Sample" mode
- if (mInfo.isCreateFromSample()) {
- return;
- }
-
- boolean is_new_project = mInfo.isNewProject();
- boolean use_default = mInfo.useDefaultLocation();
- boolean custom_location = !is_new_project || !use_default;
-
- if (!mInternalLocationPathUpdate) {
- mInternalLocationPathUpdate = true;
- if (custom_location) {
- if (abs_dir != null) {
- // We get here if the user selected a directory with the "Browse" button.
- // Disable auto-compute of the custom location unless the user selected
- // the exact same path.
- sAutoComputeCustomLocation = sAutoComputeCustomLocation &&
- abs_dir.equals(sCustomLocationOsPath);
- sCustomLocationOsPath = TextProcessor.process(abs_dir);
- } else if (sAutoComputeCustomLocation ||
- (!is_new_project && !new File(sCustomLocationOsPath).isDirectory())) {
- // By default select the samples directory of the current target
- IAndroidTarget target = mInfo.getSdkTarget();
- if (target != null) {
- sCustomLocationOsPath = target.getPath(IAndroidTarget.SAMPLES);
- }
-
- // If we don't have a target, select the base directory of the
- // "universal sdk". If we don't even have that, use a root drive.
- if (sCustomLocationOsPath == null || sCustomLocationOsPath.length() == 0) {
- if (Sdk.getCurrent() != null) {
- sCustomLocationOsPath = Sdk.getCurrent().getSdkLocation();
- } else {
- sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath();
- }
- }
- }
- if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) {
- mLocationPathField.setText(sCustomLocationOsPath);
- }
- } else {
- String value = Platform.getLocation().append(mInfo.getProjectName()).toString();
- value = TextProcessor.process(value);
- if (!mLocationPathField.getText().equals(value)) {
- mLocationPathField.setText(value);
- }
- }
- validatePageComplete();
- mInternalLocationPathUpdate = false;
- }
- }
-
- private void onProjectFieldModified() {
- if (!mInternalProjectNameUpdate) {
- mProjectNameModifiedByUser = true;
-
- if (!mApplicationNameModifiedByUser) {
- String name = capitalize(mProjectNameField.getText());
- try {
- mInternalApplicationNameUpdate = true;
- mApplicationNameField.setText(name);
- } finally {
- mInternalApplicationNameUpdate = false;
- }
- }
- if (!mActivityNameModifiedByUser) {
- String name = capitalize(mProjectNameField.getText());
- try {
- mInternalActivityNameUpdate = true;
- mActivityNameField.setText(stripWhitespace(name) + ACTIVITY_NAME_SUFFIX);
- } finally {
- mInternalActivityNameUpdate = false;
- }
-
- }
- }
- updateLocationPathField(null);
- }
-
- private void onMinSdkFieldUpdated() {
- if (!mInternalMinSdkUpdate) {
- mMinSdkModifiedByUser = true;
- }
- }
-
- private void onApplicationFieldModified() {
- if (!mInternalApplicationNameUpdate) {
- mApplicationNameModifiedByUser = true;
- if (!mActivityNameModifiedByUser) {
- String name = extractClassName(mApplicationNameField.getText());
- if (name != null) {
- try {
- mInternalActivityNameUpdate = true;
- mActivityNameField.setText(stripWhitespace(name) + ACTIVITY_NAME_SUFFIX);
- } finally {
- mInternalActivityNameUpdate = false;
- }
- }
- }
- }
- }
-
- /**
- * The location path field is either modified internally (from updateLocationPathField)
- * or manually by the user when the custom_location mode is not set.
- *
- * Ignore the internal modification. When modified by the user, memorize the choice and
- * validate the page.
- */
- private void onLocationPathFieldModified() {
- if (!mInternalLocationPathUpdate) {
- // When the updates doesn't come from updateLocationPathField, it must be the user
- // editing the field manually, in which case we want to save the value internally
- // and we disable auto-compute of the custom location (to avoid overriding the user
- // value)
- String newPath = getLocationPathFieldValue();
- sAutoComputeCustomLocation = sAutoComputeCustomLocation &&
- newPath.equals(sCustomLocationOsPath);
- sCustomLocationOsPath = newPath;
- extractNamesFromAndroidManifest();
- validatePageComplete();
- }
- }
-
- /**
- * The package name field is either modified internally (from extractNamesFromAndroidManifest)
- * or manually by the user when the custom_location mode is not set.
- *
- * Ignore the internal modification. When modified by the user, memorize the choice and
- * validate the page.
- */
- private void onPackageNameFieldModified() {
- if (mInfo.isNewProject()) {
- mUserPackageName = mInfo.getPackageName();
- validatePageComplete();
- }
- }
-
- /**
- * The create activity checkbox is either modified internally (from
- * extractNamesFromAndroidManifest) or manually by the user.
- *
- * Ignore the internal modification. When modified by the user, memorize the choice and
- * validate the page.
- */
- private void onCreateActivityCheckModified() {
- if (mInfo.isNewProject() && !mInternalCreateActivityUpdate) {
- mUserCreateActivityCheck = mInfo.isCreateActivity();
- }
- validatePageComplete();
- }
-
- /**
- * The activity name field is either modified internally (from extractNamesFromAndroidManifest)
- * or manually by the user when the custom_location mode is not set.
- *
- * Ignore the internal modification. When modified by the user, memorize the choice and
- * validate the page.
- */
- private void onActivityNameFieldModified() {
- if (!mInternalActivityNameUpdate) {
- mActivityNameModifiedByUser = true;
- }
-
- if (mInfo.isNewProject() && !mInternalActivityNameUpdate) {
- mUserActivityName = mInfo.getActivityName();
- validatePageComplete();
- }
- }
-
- /**
- * Called when an SDK target is modified.
- *
- * Also changes the minSdkVersion field to reflect the sdk api level that has
- * just been selected.
- */
- private void onSdkTargetModified() {
- IAndroidTarget target = mInfo.getSdkTarget();
-
- // Update the minimum SDK text field?
- // We do if one of two conditions are met:
- if (target != null) {
- boolean setMinSdk = false;
- AndroidVersion version = target.getVersion();
- int apiLevel = version.getApiLevel();
- // 1. Has the user not manually edited the SDK field yet? If so, keep
- // updating it to the selected value.
- if (!mMinSdkModifiedByUser) {
- setMinSdk = true;
- } else {
- // 2. Is the API level set to a higher level than the newly selected
- // target SDK? If so, change it down to the new lower value.
- String s = mMinSdkVersionField.getText().trim();
- if (s.length() > 0) {
- try {
- int currentApi = Integer.parseInt(s);
- if (currentApi > apiLevel) {
- setMinSdk = true;
- }
- } catch (NumberFormatException nfe) {
- // User may have typed something invalid -- ignore
- }
- }
- }
- if (setMinSdk) {
- String minSdk;
- if (version.isPreview()) {
- minSdk = version.getCodename();
- } else {
- minSdk = Integer.toString(apiLevel);
- }
- try {
- mInternalMinSdkUpdate = true;
- mMinSdkVersionField.setText(minSdk);
- } finally {
- mInternalMinSdkUpdate = false;
- }
- }
- }
-
- loadSamplesForTarget(target);
- enableLocationWidgets();
- onSampleSelected();
- }
-
- /**
- * Called when the radio buttons are changed between the "create new project" and the
- * "use existing source" mode. This reverts the fields to whatever the user manually
- * entered before.
- */
- private void updatePackageAndActivityFields() {
- if (mInfo.isNewProject()) {
- if (mUserPackageName.length() > 0 &&
- !mPackageNameField.getText().equals(mUserPackageName)) {
- mPackageNameField.setText(mUserPackageName);
- }
-
- if (mUserActivityName.length() > 0 &&
- !mActivityNameField.getText().equals(mUserActivityName)) {
- mInternalActivityNameUpdate = true;
- mActivityNameField.setText(mUserActivityName);
- mInternalActivityNameUpdate = false;
- }
-
- if (mUserCreateActivityCheck != mCreateActivityCheck.getSelection()) {
- mInternalCreateActivityUpdate = true;
- mCreateActivityCheck.setSelection(mUserCreateActivityCheck);
- mInternalCreateActivityUpdate = false;
- }
- }
- }
-
- /**
- * Extract names from an android manifest.
- * This is done only if the user selected the "use existing source" and a manifest xml file
- * can actually be found in the custom user directory.
- */
- private void extractNamesFromAndroidManifest() {
- if (mInfo.isNewProject()) {
- return;
- }
-
- String projectLocation = getProjectLocation();
- File f = new File(projectLocation);
- if (!f.isDirectory()) {
- return;
- }
-
- Path path = new Path(f.getPath());
- String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
-
- ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
- if (manifestData == null) {
- return;
- }
-
- String packageName = null;
- Activity activity = null;
- String activityName = null;
- String minSdkVersion = null;
- try {
- packageName = manifestData.getPackage();
- minSdkVersion = manifestData.getMinSdkVersionString();
-
- // try to get the first launcher activity. If none, just take the first activity.
- activity = manifestData.getLauncherActivity();
- if (activity == null) {
- Activity[] activities = manifestData.getActivities();
- if (activities != null && activities.length > 0) {
- activity = activities[0];
- }
- }
- } catch (Exception e) {
- // ignore exceptions
- }
-
- if (packageName != null && packageName.length() > 0) {
- mPackageNameField.setText(packageName);
- }
-
- if (activity != null) {
- activityName = AndroidManifest.extractActivityName(activity.getName(), packageName);
- }
-
- if (activityName != null && activityName.length() > 0) {
- mInternalActivityNameUpdate = true;
- mInternalCreateActivityUpdate = true;
- mActivityNameField.setText(activityName);
- // we are "importing" an existing activity, not creating a new one
- mCreateActivityCheck.setSelection(false);
- mInternalCreateActivityUpdate = false;
- mInternalActivityNameUpdate = false;
-
- // If project name and application names are empty, use the activity
- // name as a default. If the activity name has dots, it's a part of a
- // package specification and only the last identifier must be used.
- if (activityName.indexOf('.') != -1) {
- String[] ids = activityName.split(AdtConstants.RE_DOT);
- activityName = ids[ids.length - 1];
- }
- if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) {
- mInternalProjectNameUpdate = true;
- mProjectNameModifiedByUser = false;
- mProjectNameField.setText(activityName);
- mInternalProjectNameUpdate = false;
- }
- if (mApplicationNameField.getText().length() == 0 || !mApplicationNameModifiedByUser) {
- mInternalApplicationNameUpdate = true;
- mApplicationNameModifiedByUser = false;
- mApplicationNameField.setText(activityName);
- mInternalApplicationNameUpdate = false;
- }
- } else {
- mInternalActivityNameUpdate = true;
- mInternalCreateActivityUpdate = true;
- mActivityNameField.setText(""); //$NON-NLS-1$
- mCreateActivityCheck.setSelection(false);
- mInternalCreateActivityUpdate = false;
- mInternalActivityNameUpdate = false;
-
- // There is no activity name to use to fill in the project and application
- // name. However if there's a package name, we can use this as a base.
- if (packageName != null && packageName.length() > 0) {
- // Package name is a java identifier, so it's most suitable for
- // an application name.
-
- if (mApplicationNameField.getText().length() == 0 ||
- !mApplicationNameModifiedByUser) {
- mInternalApplicationNameUpdate = true;
- mApplicationNameField.setText(packageName);
- mInternalApplicationNameUpdate = false;
- }
-
- // For the project name, remove any dots
- packageName = packageName.replace('.', '_');
- if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) {
- mInternalProjectNameUpdate = true;
- mProjectNameField.setText(packageName);
- mInternalProjectNameUpdate = false;
- }
-
- }
- }
-
- // Select the target matching the manifest's sdk or build properties, if any
- IAndroidTarget foundTarget = null;
- // This is the target currently in the UI
- IAndroidTarget currentTarget = mInfo.getSdkTarget();
-
- // If there's a current target defined, we do not allow to change it when
- // operating in the create-from-sample mode -- since the available sample list
- // is tied to the current target, so changing it would invalidate the project we're
- // trying to load in the first place.
- if (currentTarget == null || !mInfo.isCreateFromSample()) {
- ProjectProperties p = ProjectProperties.load(projectLocation, PropertyType.PROJECT);
- if (p != null) {
- String v = p.getProperty(ProjectProperties.PROPERTY_TARGET);
- IAndroidTarget desiredTarget = Sdk.getCurrent().getTargetFromHashString(v);
- // We can change the current target if:
- // - we found a new desired target
- // - there is no current target
- // - or the current target can't run the desired target
- if (desiredTarget != null &&
- (currentTarget == null || !desiredTarget.canRunOn(currentTarget))) {
- foundTarget = desiredTarget;
- }
- }
-
- if (foundTarget == null && minSdkVersion != null) {
- // Otherwise try to match the requested min-sdk-version if we find an
- // exact match, regardless of the currently selected target.
- for (IAndroidTarget existingTarget : mSdkTargetSelector.getTargets()) {
- if (existingTarget != null &&
- existingTarget.getVersion().equals(minSdkVersion)) {
- foundTarget = existingTarget;
- break;
- }
- }
- }
-
- if (foundTarget == null) {
- // Or last attempt, try to match a sample project location and use it
- // if we find an exact match, regardless of the currently selected target.
- for (IAndroidTarget existingTarget : mSdkTargetSelector.getTargets()) {
- if (existingTarget != null &&
- projectLocation.startsWith(existingTarget.getLocation())) {
- foundTarget = existingTarget;
- break;
- }
- }
- }
- }
-
- if (foundTarget != null) {
- mSdkTargetSelector.setSelection(foundTarget);
- }
-
- // It's OK for an import to not a minSdkVersion and we should respect it.
- mMinSdkVersionField.setText(minSdkVersion == null ? "" : minSdkVersion); //$NON-NLS-1$
- }
-
- /**
- * Updates the list of all samples for the given target SDK.
- * The list is stored in mSamplesPaths as absolute directory paths.
- * The combo is recreated to match this.
- */
- private void loadSamplesForTarget(IAndroidTarget target) {
-
- // Keep the name of the old selection (if there were any samples)
- String oldChoice = null;
- if (mSamplesPaths.size() > 0) {
- int selIndex = mSamplesCombo.getSelectionIndex();
- if (selIndex > -1) {
- oldChoice = mSamplesCombo.getItem(selIndex);
- }
- }
-
- // Clear all current content
- mSamplesCombo.removeAll();
- mSamplesPaths.clear();
-
- if (target != null) {
- // Get the sample root path and recompute the list of samples
- String samplesRootPath = target.getPath(IAndroidTarget.SAMPLES);
-
- File samplesDir = new File(samplesRootPath);
- findSamplesManifests(samplesDir, mSamplesPaths);
-
- if (mSamplesPaths.size() == 0) {
- // Odd, this target has no samples. Could happen with an addon.
- mSamplesCombo.add("This target has no samples. Please select another target.");
- mSamplesCombo.select(0);
- return;
- } else {
- Collections.sort(mSamplesPaths);
- }
-
- // Recompute the description of each sample (the relative path
- // to the sample root). Also try to find the old selection.
- int selIndex = 0;
- int i = 0;
- int n = samplesRootPath.length();
- Set<String> paths = new TreeSet<String>();
- for (String path : mSamplesPaths) {
- if (path.length() > n) {
- path = path.substring(n);
- if (path.charAt(0) == File.separatorChar) {
- path = path.substring(1);
- }
- if (path.endsWith(File.separator)) {
- path = path.substring(0, path.length() - 1);
- }
- path = path.replaceAll(Pattern.quote(File.separator), " > ");
- }
-
- if (oldChoice != null && oldChoice.equals(path)) {
- selIndex = i;
- }
-
- paths.add(path);
- i++;
- }
- mSamplesCombo.setItems(paths.toArray(new String[0]));
- mSamplesCombo.select(selIndex);
-
- } else {
- mSamplesCombo.add("Please select a target.");
- mSamplesCombo.select(0);
- }
- }
-
- /**
- * Recursively find potential sample directories under the given directory.
- * Actually lists any directory that contains an android manifest.
- * Paths found are added the samplesPaths list.
- */
- private void findSamplesManifests(File samplesDir, ArrayList<String> samplesPaths) {
- if (!samplesDir.isDirectory()) {
- return;
- }
-
- for (File f : samplesDir.listFiles()) {
- if (f.isDirectory()) {
- // Assume this is a sample if it contains an android manifest.
- File manifestFile = new File(f, SdkConstants.FN_ANDROID_MANIFEST_XML);
- if (manifestFile.isFile()) {
- samplesPaths.add(f.getPath());
- }
-
- // Recurse in the project, to find embedded tests sub-projects
- // We can however skip this recursion for known android sub-dirs that
- // can't have projects, namely for sources, assets and resources.
- String leaf = f.getName();
- if (!SdkConstants.FD_SOURCES.equals(leaf) &&
- !SdkConstants.FD_ASSETS.equals(leaf) &&
- !SdkConstants.FD_RES.equals(leaf)) {
- findSamplesManifests(f, samplesPaths);
- }
- }
- }
- }
-
- /**
- * Returns whether this page's controls currently all contain valid values.
- *
- * @return <code>true</code> if all controls are valid, and
- * <code>false</code> if at least one is invalid
- */
- private boolean validatePage() {
- IWorkspace workspace = ResourcesPlugin.getWorkspace();
-
- int status = validateProjectField(workspace);
- if ((status & MSG_ERROR) == 0) {
- status |= validateSdkTarget();
- }
- if ((status & MSG_ERROR) == 0) {
- status |= validateLocationPath(workspace);
- }
- if ((status & MSG_ERROR) == 0) {
- status |= validatePackageField();
- }
- if ((status & MSG_ERROR) == 0) {
- status |= validateActivityField();
- }
- if ((status & MSG_ERROR) == 0) {
- status |= validateMinSdkVersionField();
- }
- if ((status & MSG_ERROR) == 0) {
- status |= validateSourceFolder();
- }
- if (status == MSG_NONE) {
- setStatus(null, MSG_NONE);
- }
-
- // Return false if there's an error so that the finish button be disabled.
- return (status & MSG_ERROR) == 0;
- }
-
- /**
- * Validates the page and updates the Next/Finish buttons
- */
- private void validatePageComplete() {
- setPageComplete(validatePage());
- }
-
- /**
- * Validates the project name field.
- *
- * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
- */
- private int validateProjectField(IWorkspace workspace) {
- // Validate project field
- String projectName = mInfo.getProjectName();
- if (projectName.length() == 0) {
- return setStatus("Project name must be specified", MSG_ERROR);
- }
-
- // Limit the project name to shell-agnostic characters since it will be used to
- // generate the final package
- if (!sProjectNamePattern.matcher(projectName).matches()) {
- return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.",
- MSG_ERROR);
- }
-
- IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
- if (!nameStatus.isOK()) {
- return setStatus(nameStatus.getMessage(), MSG_ERROR);
- }
-
- if (getProjectHandle().exists()) {
- return setStatus("A project with that name already exists in the workspace",
- MSG_ERROR);
- }
-
- if (mTestInfo != null &&
- mTestInfo.getCreateTestProject() &&
- projectName.equals(mTestInfo.getProjectName())) {
- return setStatus("The main project name and the test project name must be different.",
- MSG_WARNING);
- }
-
- return MSG_NONE;
- }
-
- /**
- * Validates the location path field.
- *
- * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
- */
- private int validateLocationPath(IWorkspace workspace) {
- Path path = new Path(getProjectLocation());
- if (mInfo.isNewProject()) {
- if (!mInfo.useDefaultLocation()) {
- // If not using the default value validate the location.
- URI uri = URIUtil.toURI(path.toOSString());
- IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(),
- uri);
- if (!locationStatus.isOK()) {
- return setStatus(locationStatus.getMessage(), MSG_ERROR);
- } else {
- // The location is valid as far as Eclipse is concerned (i.e. mostly not
- // an existing workspace project.) Check it either doesn't exist or is
- // a directory that is empty.
- File f = path.toFile();
- if (f.exists() && !f.isDirectory()) {
- return setStatus("A directory name must be specified.", MSG_ERROR);
- } else if (f.isDirectory()) {
- // However if the directory exists, we should put a warning if it is not
- // empty. We don't put an error (we'll ask the user again for confirmation
- // before using the directory.)
- String[] l = f.list();
- if (l.length != 0) {
- return setStatus("The selected output directory is not empty.",
- MSG_WARNING);
- }
- }
- }
- } else {
- // Otherwise validate the path string is not empty
- if (getProjectLocation().length() == 0) {
- return setStatus("A directory name must be specified.", MSG_ERROR);
- }
-
- File dest = path.append(mInfo.getProjectName()).toFile();
- if (dest.exists()) {
- return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.",
- mInfo.getProjectName()), MSG_ERROR);
- }
- }
- } else {
- // Must be an existing directory
- File f = path.toFile();
- if (!f.isDirectory()) {
- return setStatus("An existing directory name must be specified.", MSG_ERROR);
- }
-
- // Check there's an android manifest in the directory
- String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
- File manifestFile = new File(osPath);
- if (!manifestFile.isFile()) {
- return setStatus(
- String.format("File %1$s not found in %2$s.",
- SdkConstants.FN_ANDROID_MANIFEST_XML, f.getName()),
- MSG_ERROR);
- }
-
- // Parse it and check the important fields.
- ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
- if (manifestData == null) {
- return setStatus(
- String.format("File %1$s could not be parsed.", osPath),
- MSG_ERROR);
- }
-
- String packageName = manifestData.getPackage();
- if (packageName == null || packageName.length() == 0) {
- return setStatus(
- String.format("No package name defined in %1$s.", osPath),
- MSG_ERROR);
- }
-
- Activity[] activities = manifestData.getActivities();
- if (activities == null || activities.length == 0) {
- // This is acceptable now as long as no activity needs to be created
- if (mInfo.isCreateActivity()) {
- return setStatus(
- String.format("No activity name defined in %1$s.", osPath),
- MSG_ERROR);
- }
- }
-
- // If there's already a .project, tell the user to use import instead.
- if (path.append(".project").toFile().exists()) { //$NON-NLS-1$
- return setStatus("An Eclipse project already exists in this directory. Consider using File > Import > Existing Project instead.",
- MSG_WARNING);
- }
- }
-
- return MSG_NONE;
- }
-
- /**
- * Validates the sdk target choice.
- *
- * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
- */
- private int validateSdkTarget() {
- if (mInfo.getSdkTarget() == null) {
- return setStatus("An SDK Target must be specified.", MSG_ERROR);
- }
- return MSG_NONE;
- }
-
- /**
- * Validates the sdk target choice.
- *
- * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
- */
- private int validateMinSdkVersionField() {
-
- // If the current target is a preview, explicitly indicate minSdkVersion
- // must be set to this target name.
- // Since the field is only editable in new-project mode, we can't produce an
- // error when importing an existing project.
- if (mInfo.isNewProject() &&
- mInfo.getSdkTarget() != null &&
- mInfo.getSdkTarget().getVersion().isPreview() &&
- mInfo.getSdkTarget().getVersion().equals(mInfo.getMinSdkVersion()) == false) {
- return setStatus(
- String.format("The SDK target is a preview. Min SDK Version must be set to '%s'.",
- mInfo.getSdkTarget().getVersion().getCodename()),
- MSG_ERROR);
- }
-
- // If the min sdk version is empty, it is always accepted.
- if (mInfo.getMinSdkVersion().length() == 0) {
- return MSG_NONE;
- }
-
- if (mInfo.getSdkTarget() != null &&
- mInfo.getSdkTarget().getVersion().equals(mInfo.getMinSdkVersion()) == false) {
- return setStatus("The API level for the selected SDK target does not match the Min SDK Version.",
- mInfo.getSdkTarget().getVersion().isPreview() ? MSG_ERROR : MSG_WARNING);
- }
-
- return MSG_NONE;
- }
-
- /**
- * Validates the activity name field.
- *
- * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
- */
- private int validateActivityField() {
- // Disregard if not creating an activity
- if (!mInfo.isCreateActivity()) {
- return MSG_NONE;
- }
-
- // Validate activity field
- String activityFieldContents = mInfo.getActivityName();
- if (activityFieldContents.length() == 0) {
- return setStatus("Activity name must be specified.", MSG_ERROR);
- }
-
- if (ACTIVITY_NAME_SUFFIX.equals(activityFieldContents)) {
- return setStatus("Enter a valid activity name", MSG_ERROR);
- }
-
- if (activityFieldContents.contains("..")) { //$NON-NLS-1$
- return setStatus("Package segments in activity name cannot be empty (..)", MSG_ERROR);
- }
-
- // The activity field can actually contain part of a sub-package name
- // or it can start with a dot "." to indicates it comes from the parent package name.
- String packageName = ""; //$NON-NLS-1$
- int pos = activityFieldContents.lastIndexOf('.');
- if (pos >= 0) {
- packageName = activityFieldContents.substring(0, pos);
- if (packageName.startsWith(".")) { //$NON-NLS-1$
- packageName = packageName.substring(1);
- }
-
- activityFieldContents = activityFieldContents.substring(pos + 1);
- }
-
- // the activity field can contain a simple java identifier, or a
- // package name or one that starts with a dot. So if it starts with a dot,
- // ignore this dot -- the rest must look like a package name.
- if (activityFieldContents.length() > 0 && activityFieldContents.charAt(0) == '.') {
- activityFieldContents = activityFieldContents.substring(1);
- }
-
- // Check it's a valid activity string
- int result = MSG_NONE;
- IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents,
- "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
- if (!status.isOK()) {
- result = setStatus(status.getMessage(),
- status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
- }
-
- // Check it's a valid package string
- if (result != MSG_ERROR && packageName.length() > 0) {
- status = JavaConventions.validatePackageName(packageName,
- "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
- if (!status.isOK()) {
- result = setStatus(status.getMessage() + " (in the activity name)",
- status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
- }
- }
-
-
- return result;
- }
-
- /**
- * Validates the package name field.
- *
- * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
- */
- private int validatePackageField() {
- // Validate package field
- String packageFieldContents = mInfo.getPackageName();
- if (packageFieldContents.length() == 0) {
- return setStatus("Package name must be specified.", MSG_ERROR);
- }
-
- // Check it's a valid package string
- int result = MSG_NONE;
- IStatus status = JavaConventions.validatePackageName(packageFieldContents, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
- if (!status.isOK()) {
- result = setStatus(status.getMessage(),
- status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
- }
-
- // The Android Activity Manager does not accept packages names with only one
- // identifier. Check the package name has at least one dot in them (the previous rule
- // validated that if such a dot exist, it's not the first nor last characters of the
- // string.)
- if (result != MSG_ERROR && packageFieldContents.indexOf('.') == -1) {
- return setStatus("Package name must have at least two identifiers.", MSG_ERROR);
- }
-
- return result;
- }
-
- /**
- * Validates that an existing project actually has a source folder.
- *
- * For project in "use existing source" mode, this tries to find the source folder.
- * A source folder should be just under the project directory and it should have all
- * the directories composing the package+activity name.
- *
- * As a side effect, it memorizes the source folder in mSourceFolder.
- *
- * TODO: support multiple source folders for multiple activities.
- *
- * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
- */
- private int validateSourceFolder() {
- // This check does nothing when creating a new project.
- // This check is also useless when no activity is present or created.
- if (mInfo.isNewProject() || !mInfo.isCreateActivity()) {
- return MSG_NONE;
- }
-
- String osTarget = mInfo.getActivityName();
-
- if (osTarget.indexOf('.') == -1) {
- osTarget = mInfo.getPackageName() + File.separator + osTarget;
- } else if (osTarget.indexOf('.') == 0) {
- osTarget = mInfo.getPackageName() + osTarget;
- }
- osTarget = osTarget.replace('.', File.separatorChar) + AdtConstants.DOT_JAVA;
-
- String projectPath = getProjectLocation();
- File projectDir = new File(projectPath);
- File[] all_dirs = projectDir.listFiles(new FileFilter() {
- public boolean accept(File pathname) {
- return pathname.isDirectory();
- }
- });
- for (File f : all_dirs) {
- Path path = new Path(f.getAbsolutePath());
- File java_activity = path.append(osTarget).toFile();
- if (java_activity.isFile()) {
- mSourceFolder = f.getName();
- return MSG_NONE;
- }
- }
-
- if (all_dirs.length > 0) {
- return setStatus(
- String.format("%1$s can not be found under %2$s.", osTarget, projectPath),
- MSG_ERROR);
- } else {
- return setStatus(
- String.format("No source folders can be found in %1$s.", projectPath),
- MSG_ERROR);
- }
- }
-
- /**
- * Sets the error message for the wizard with the given message icon.
- *
- * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING.
- * @return As a convenience, always returns messageType so that the caller can return
- * immediately.
- */
- private int setStatus(String message, int messageType) {
- if (message == null) {
- setErrorMessage(null);
- setMessage(null);
- } else if (!message.equals(getMessage())) {
- setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR);
- }
- return messageType;
- }
-
- /**
- * Returns the working sets to which the new project should be added.
- *
- * @return the selected working sets to which the new project should be added
- */
- public IWorkingSet[] getWorkingSets() {
- return mWorkingSetGroup.getSelectedWorkingSets();
- }
-
- /**
- * Sets the working sets to which the new project should be added.
- *
- * @param workingSets the initial selected working sets
- */
- public void setWorkingSets(IWorkingSet[] workingSets) {
- assert workingSets != null;
- mWorkingSetGroup.setWorkingSets(workingSets);
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
new file mode 100644
index 0000000..dc70d38
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
@@ -0,0 +1,1165 @@
+/*
+ * 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.
+ */
+
+package com.android.ide.eclipse.adt.internal.wizards.newproject;
+
+import com.android.AndroidConstants;
+import com.android.ide.common.layout.LayoutConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.project.AndroidNature;
+import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.io.StreamException;
+import com.android.resources.Density;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.filesystem.IFileSystem;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
+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.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+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;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.operation.IRunnableContext;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkingSet;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * The actual project creator invoked from the New Project Wizard
+ * <p/>
+ * 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.
+ */
+public class NewProjectCreator {
+
+ 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_IMPORT_RESOURCE_CLASS = "IMPORT_RESOURCE_CLASS"; //$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_SAMPLE_LOCATION = "SAMPLE_LOCATION"; //$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 + AdtConstants.WS_SEP;
+ private static final String BIN_CLASSES_DIRECTORY =
+ SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP +
+ SdkConstants.FD_CLASSES_OUTPUT + AdtConstants.WS_SEP;
+ private static final String RES_DIRECTORY =
+ SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP;
+ private static final String ASSETS_DIRECTORY =
+ SdkConstants.FD_ASSETS + AdtConstants.WS_SEP;
+ private static final String DRAWABLE_DIRECTORY =
+ AndroidConstants.FD_RES_DRAWABLE + AdtConstants.WS_SEP;
+ private static final String DRAWABLE_HDPI_DIRECTORY =
+ AndroidConstants.FD_RES_DRAWABLE + "-" + Density.HIGH.getResourceValue() + //$NON-NLS-1$
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_MDPI_DIRECTORY =
+ AndroidConstants.FD_RES_DRAWABLE + "-" + Density.MEDIUM.getResourceValue() + //$NON-NLS-1$
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_LDPI_DIRECTORY =
+ AndroidConstants.FD_RES_DRAWABLE + "-" + Density.LOW.getResourceValue() + //$NON-NLS-1$
+ AdtConstants.WS_SEP;
+ private static final String LAYOUT_DIRECTORY =
+ AndroidConstants.FD_RES_LAYOUT + AdtConstants.WS_SEP;
+ private static final String VALUES_DIRECTORY =
+ AndroidConstants.FD_RES_VALUES + AdtConstants.WS_SEP;
+ private static final String GEN_SRC_DIRECTORY =
+ SdkConstants.FD_GEN_SOURCES + AdtConstants.WS_SEP;
+
+ private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
+ private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
+ + "AndroidManifest.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY
+ + "activity.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
+ + "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$
+ private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY
+ + "string.template"; //$NON-NLS-1$
+ private static final String PROJECT_ICON = "ic_launcher.png"; //$NON-NLS-1$
+ private static final String ICON_HDPI = "ic_launcher_hdpi.png"; //$NON-NLS-1$
+ private static final String ICON_MDPI = "ic_launcher_mdpi.png"; //$NON-NLS-1$
+ private static final String ICON_LDPI = "ic_launcher_ldpi.png"; //$NON-NLS-1$
+
+ private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
+
+ private static final String STRING_RSRC_PREFIX = LayoutConstants.STRING_PREFIX;
+ 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, BIN_CLASSES_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
+ private static final String[] RES_DIRECTORIES = new String[] {
+ DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY };
+ private static final String[] RES_DENSITY_ENABLED_DIRECTORIES = new String[] {
+ DRAWABLE_HDPI_DIRECTORY, DRAWABLE_MDPI_DIRECTORY, DRAWABLE_LDPI_DIRECTORY,
+ LAYOUT_DIRECTORY, VALUES_DIRECTORY };
+
+ 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 final NewProjectWizardState mValues;
+ private final IRunnableContext mRunnableContext;
+ private Object mPackageName;
+
+ public NewProjectCreator(NewProjectWizardState values, IRunnableContext runnableContext) {
+ mValues = values;
+ mRunnableContext = runnableContext;
+ }
+
+ /**
+ * 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.
+ */
+ private boolean validateNewProjectLocationIsEmpty(IPath destination) {
+ File f = new File(destination.toOSString());
+ if (f.isDirectory() && f.list().length > 0) {
+ return AdtPlugin.displayPrompt("New Android Project",
+ "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?");
+ }
+ return true;
+ }
+
+ /**
+ * Structure that describes all the information needed to create a project.
+ * This is collected from the pages by {@link NewProjectCreator#createAndroidProjects()}
+ * and then used by
+ * {@link NewProjectCreator#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.
+ */
+ public boolean createAndroidProjects() {
+ final ProjectInfo mainData = collectMainPageInfo();
+ final ProjectInfo testData = collectTestPageInfo();
+
+ // 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 (mValues.mode == Mode.TEST) {
+ return null;
+ }
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProject project = workspace.getRoot().getProject(mValues.projectName);
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ // keep some variables to make them available once the wizard closes
+ mPackageName = mValues.packageName;
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_PROJECT, mValues.projectName);
+ 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, mValues.mode == Mode.ANY && !mValues.useExisting);
+ parameters.put(PARAM_SAMPLE_LOCATION, mValues.chosenSample);
+ parameters.put(PARAM_SRC_FOLDER, mValues.sourceFolder);
+ parameters.put(PARAM_SDK_TARGET, mValues.target);
+ parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
+
+ if (mValues.createActivity) {
+ parameters.put(PARAM_ACTIVITY, mValues.activityName);
+ }
+
+ // 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, mValues.applicationName);
+
+ IPath path = new Path(mValues.projectLocation.getPath());
+ IPath defaultLocation = Platform.getLocation();
+ if ((!mValues.useDefaultLocation || mValues.useExisting)
+ && !path.equals(defaultLocation)) {
+ description.setLocation(path);
+ }
+
+ if (mValues.mode == Mode.ANY && !mValues.useExisting && !mValues.useDefaultLocation &&
+ !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
+
+ /**
+ * 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 (mValues.mode != Mode.TEST && !mValues.createPairProject) {
+ return null;
+ }
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ String projectName =
+ mValues.mode == Mode.TEST ? mValues.projectName : mValues.testProjectName;
+ final IProject project = workspace.getRoot().getProject(projectName);
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+
+ String pkg =
+ mValues.mode == Mode.TEST ? mValues.packageName : mValues.testPackageName;
+
+ parameters.put(PARAM_PROJECT, projectName);
+ parameters.put(PARAM_PACKAGE, pkg);
+ 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, mValues.sourceFolder);
+ parameters.put(PARAM_SDK_TARGET, mValues.target);
+ parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
+
+ // Test-specific parameters
+ String testedPkg = mValues.createPairProject
+ ? mValues.packageName : mValues.testTargetPackageName;
+ if (testedPkg == null) {
+ assert mValues.testingSelf;
+ testedPkg = pkg;
+ }
+
+ parameters.put(PARAM_TEST_TARGET_PACKAGE, testedPkg);
+
+ if (mValues.testingSelf) {
+ parameters.put(PARAM_TARGET_SELF, true);
+ } else {
+ parameters.put(PARAM_TARGET_EXISTING, true);
+ parameters.put(PARAM_REFERENCE_PROJECT, mValues.testedProject);
+ }
+
+ if (mValues.createPairProject) {
+ parameters.put(PARAM_TARGET_MAIN, true);
+ }
+
+ // 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, mValues.testApplicationName);
+
+ IPath path = new Path(mValues.projectLocation.getPath());
+ IPath defaultLocation = Platform.getLocation();
+ if ((!mValues.useDefaultLocation || mValues.useExisting)
+ && !path.equals(defaultLocation)) {
+ description.setLocation(path);
+ }
+
+ if (!mValues.useDefaultLocation && !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
+
+ /**
+ * Runs the operation in a different thread and display generated
+ * exceptions.
+ *
+ * @param op The asynchronous operation to run.
+ */
+ private void runAsyncOperation(WorkspaceModifyOperation op) {
+ try {
+ mRunnableContext.run(true /* fork */, true /* cancelable */, op);
+ } catch (InvocationTargetException e) {
+
+ AdtPlugin.log(e, "New Project Wizard failed");
+
+ // The runnable threw an exception
+ Throwable t = e.getTargetException();
+ if (t instanceof CoreException) {
+ CoreException core = (CoreException) t;
+ if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
+ // The error indicates the file system is not case sensitive
+ // and there's a resource with a similar name.
+ MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(),
+ "Error", "Error: Case Variant Exists");
+ } else {
+ ErrorDialog.openError(AdtPlugin.getDisplay().getActiveShell(),
+ "Error", core.getMessage(), core.getStatus());
+ }
+ } else {
+ // Some other kind of exception
+ String msg = t.getMessage();
+ Throwable t1 = t;
+ while (msg == null && t1.getCause() != null) {
+ msg = t1.getMessage();
+ t1 = t1.getCause();
+ }
+ if (msg == null) {
+ msg = t.toString();
+ }
+ MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(), "Error", msg);
+ }
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Creates the actual project(s). This is run asynchronously in a different thread.
+ *
+ * @param monitor An existing monitor.
+ * @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(IProgressMonitor monitor,
+ ProjectInfo mainData,
+ ProjectInfo testData)
+ throws InvocationTargetException {
+ monitor.beginTask("Create Android Project", 100);
+ try {
+ IProject mainProject = null;
+
+ if (mainData != null) {
+ mainProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ mainData.getProject(),
+ mainData.getDescription(),
+ mainData.getParameters(),
+ mainData.getDictionary());
+
+ if (mainProject != null) {
+ final IJavaProject javaProject = JavaCore.create(mainProject);
+ Display.getDefault().syncExec(new Runnable() {
+
+ public void run() {
+ IWorkingSet[] workingSets = mValues.workingSets;
+ if (workingSets.length > 0 && javaProject != null
+ && javaProject.exists()) {
+ PlatformUI.getWorkbench().getWorkingSetManager()
+ .addToWorkingSets(javaProject, workingSets);
+ }
+ }
+ });
+ }
+ }
+
+ if (testData != null) {
+
+ Map<String, Object> parameters = testData.getParameters();
+ if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
+ parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
+ }
+
+ IProject testProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ testData.getProject(),
+ testData.getDescription(),
+ parameters,
+ testData.getDictionary());
+ if (testProject != null) {
+ final IJavaProject javaProject = JavaCore.create(testProject);
+ Display.getDefault().syncExec(new Runnable() {
+
+ public void run() {
+ IWorkingSet[] workingSets = mValues.workingSets;
+ if (workingSets.length > 0 && javaProject != null
+ && javaProject.exists()) {
+ PlatformUI.getWorkbench().getWorkingSetManager()
+ .addToWorkingSets(javaProject, workingSets);
+ }
+ }
+ });
+ }
+ }
+ } catch (CoreException e) {
+ throw new InvocationTargetException(e);
+ } catch (IOException e) {
+ throw new InvocationTargetException(e);
+ } catch (StreamException e) {
+ throw new InvocationTargetException(e);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * 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
+ * @throws StreamException
+ */
+ private IProject createEclipseProject(IProgressMonitor monitor,
+ IProject project,
+ IProjectDescription description,
+ Map<String, Object> parameters,
+ Map<String, String> dictionary)
+ throws CoreException, IOException, StreamException {
+
+ // get the project target
+ IAndroidTarget target = (IAndroidTarget) parameters.get(PARAM_SDK_TARGET);
+ boolean legacy = target.getVersion().getApiLevel() < 4;
+
+ // 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, AdtConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
+ String[] sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER),
+ GEN_SRC_DIRECTORY
+ };
+ addDefaultDirectories(project, AdtConstants.WS_ROOT, sourceFolders, monitor);
+
+ // Create the resource folders in the project if they don't already exist.
+ if (legacy) {
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
+ } else {
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DENSITY_ENABLED_DIRECTORIES, monitor);
+ }
+
+ // Setup class path: mark folders as source folders
+ IJavaProject javaProject = JavaCore.create(project);
+ setupSourceFolders(javaProject, sourceFolders, monitor);
+
+ 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, legacy, 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);
+ }
+
+ // add the default proguard config
+ File libFolder = new File((String) parameters.get(PARAM_SDK_TOOLS_DIR),
+ SdkConstants.FD_LIB);
+ addLocalFile(project,
+ new File(libFolder, SdkConstants.FN_PROGUARD_CFG),
+ monitor);
+
+ // Set output location
+ javaProject.setOutputLocation(project.getFolder(BIN_CLASSES_DIRECTORY).getFullPath(),
+ monitor);
+ }
+
+ File sampleDir = (File) parameters.get(PARAM_SAMPLE_LOCATION);
+ if (sampleDir != null) {
+ // Copy project
+ copySampleCode(project, sampleDir, parameters, dictionary, 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().initProject(project, target);
+
+ // 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.
+ * @param parentFolder The path of the parent folder. Must end with a
+ * separator.
+ * @param folders Folders to be added.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to create the directories in
+ * the project.
+ */
+ private void addDefaultDirectories(IProject project, String parentFolder,
+ String[] folders, IProgressMonitor monitor) throws CoreException {
+ for (String name : folders) {
+ if (name.length() > 0) {
+ IFolder folder = project.getFolder(parentFolder + name);
+ if (!folder.exists()) {
+ folder.create(true /* force */, true /* local */,
+ new SubProgressMonitor(monitor, 10));
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the manifest to the project.
+ *
+ * @param project The Java Project to update.
+ * @param parameters Template Parameters.
+ * @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.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addManifest(IProject project, Map<String, Object> parameters,
+ Map<String, String> dictionary, IProgressMonitor monitor)
+ throws CoreException, IOException {
+
+ // get IFile to the manifest and check if it's not already there.
+ IFile file = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
+ if (!file.exists()) {
+
+ // Read manifest template
+ String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST);
+
+ // Replace all keyword parameters
+ manifestTemplate = replaceParameters(manifestTemplate, parameters);
+
+ if (manifestTemplate == null) {
+ // Inform the user there will be not manifest.
+ AdtPlugin.logAndPrintError(null, "Create Project" /*TAG*/,
+ "Failed to generate the Android manifest. Missing template %s",
+ TEMPLATE_MANIFEST);
+ // Abort now, there's no need to continue
+ return;
+ }
+
+ if (parameters.containsKey(PARAM_ACTIVITY)) {
+ // now get the activity template
+ String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
+
+ // If the activity name doesn't contain any dot, it's in the form
+ // "ClassName" and we need to expand it to ".ClassName" in the XML.
+ String name = (String) parameters.get(PARAM_ACTIVITY);
+ if (name.indexOf('.') == -1) {
+ // Duplicate the parameters map to avoid changing the caller
+ parameters = new HashMap<String, Object>(parameters);
+ parameters.put(PARAM_ACTIVITY, "." + name); //$NON-NLS-1$
+ }
+
+ // Replace all keyword parameters to make main activity.
+ String activities = replaceParameters(activityTemplate, parameters);
+
+ // set the intent.
+ String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
+
+ if (activities != null) {
+ if (intent != null) {
+ // 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, ""); //$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);
+ if (usesLibrary != null) {
+ manifestTemplate = manifestTemplate.replaceAll(
+ PH_TEST_USES_LIBRARY, usesLibrary);
+ }
+
+ // Set the instrumentation element needed by the test project
+ String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
+ if (instru != null) {
+ 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);
+ if (usesSdkTemplate != null) {
+ String usesSdk = replaceParameters(usesSdkTemplate, parameters);
+ manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk);
+ }
+ } else {
+ manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
+ }
+
+ // Reformat the file according to the user's formatting settings
+ manifestTemplate = reformat(XmlFormatStyle.MANIFEST, manifestTemplate);
+
+ // Save in the project as UTF-8
+ InputStream stream = new ByteArrayInputStream(
+ manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
+ file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Adds the string resource file.
+ *
+ * @param project The Java Project to update.
+ * @param strings The list of strings to be added to the string file.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addStringDictionaryFile(IProject project,
+ Map<String, String> strings, IProgressMonitor monitor)
+ throws CoreException, IOException {
+
+ // create the IFile object and check if the file doesn't already exist.
+ IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + VALUES_DIRECTORY + AdtConstants.WS_SEP + STRINGS_FILE);
+ if (!file.exists()) {
+ // get the Strings.xml template
+ String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS);
+
+ // get the template for one string
+ String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING);
+
+ // get all the string names
+ Set<String> stringNames = strings.keySet();
+
+ // loop on it and create the string definitions
+ StringBuilder stringNodes = new StringBuilder();
+ for (String key : stringNames) {
+ // get the value from the key
+ String value = strings.get(key);
+
+ // Escape values if necessary
+ value = ExtractStringRefactoring.escapeString(value);
+
+ // place them in the template
+ String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
+ stringDef = stringDef.replace(PARAM_STRING_CONTENT, value);
+
+ // append to the other string
+ if (stringNodes.length() > 0) {
+ stringNodes.append('\n');
+ }
+ stringNodes.append(stringDef);
+ }
+
+ // put the string nodes in the Strings.xml template
+ stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
+ stringNodes.toString());
+
+ // reformat the file according to the user's formatting settings
+ stringDefinitionTemplate = reformat(XmlFormatStyle.RESOURCE, stringDefinitionTemplate);
+
+ // write the file as UTF-8
+ InputStream stream = new ByteArrayInputStream(
+ stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
+ file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /** Reformats the given contents with the current formatting settings */
+ private String reformat(XmlFormatStyle style, String contents) {
+ if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
+ XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
+ return XmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
+ null /*lineSeparator*/);
+ } else {
+ return contents;
+ }
+ }
+
+ /**
+ * Adds default application icon to the project.
+ *
+ * @param project The Java Project to update.
+ * @param legacy whether we're running in legacy mode (no density support)
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ */
+ private void addIcon(IProject project, boolean legacy, IProgressMonitor monitor)
+ throws CoreException {
+ if (legacy) { // density support
+ // do medium density icon only, in the default drawable folder.
+ IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
+ }
+ } else {
+ // do all 3 icons.
+ IFile file;
+
+ // high density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_HDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_HDPI), monitor);
+ }
+
+ // medium density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_MDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
+ }
+
+ // low density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_LDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_LDPI), monitor);
+ }
+ }
+ }
+
+ /**
+ * Creates a file from a data source.
+ * @param dest the file to write
+ * @param source the content of the file.
+ * @param monitor the progress monitor
+ * @throws CoreException
+ */
+ private void addFile(IFile dest, byte[] source, IProgressMonitor monitor) throws CoreException {
+ if (source != null) {
+ // Save in the project
+ InputStream stream = new ByteArrayInputStream(source);
+ dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Creates the package folder and copies the sample code in the project.
+ *
+ * @param project The Java Project to update.
+ * @param parameters Template Parameters.
+ * @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.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addSampleCode(IProject project, String sourceFolder,
+ 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);
+
+ Map<String, Object> java_activity_parameters = new HashMap<String, Object>(parameters);
+ java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, ""); //$NON-NLS-1$
+
+ if (activityName != null) {
+
+ String resourcePackageClass = null;
+
+ // An activity name can be of the form ".package.Class", ".Class" or FQDN.
+ // The initial dot is ignored, as it is always added later in the templates.
+ int lastDotIndex = activityName.lastIndexOf('.');
+
+ if (lastDotIndex != -1) {
+
+ // Resource class
+ if (lastDotIndex > 0) {
+ resourcePackageClass = packageName + "." + AdtConstants.FN_RESOURCE_BASE; //$NON-NLS-1$
+ }
+
+ // Package name
+ if (activityName.startsWith(".")) { //$NON-NLS-1$
+ packageName += activityName.substring(0, lastDotIndex);
+ } else {
+ packageName = activityName.substring(0, lastDotIndex);
+ }
+
+ // Activity Class name
+ activityName = activityName.substring(lastDotIndex + 1);
+ }
+
+ java_activity_parameters.put(PARAM_ACTIVITY, activityName);
+ java_activity_parameters.put(PARAM_PACKAGE, packageName);
+ if (resourcePackageClass != null) {
+ String importResourceClass = "\nimport " + resourcePackageClass + ";"; //$NON-NLS-1$ // $NON-NLS-2$
+ java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, importResourceClass);
+ }
+ }
+
+ String[] components = packageName.split(AdtConstants.RE_DOT);
+ for (String component : components) {
+ pkgFolder = pkgFolder.getFolder(component);
+ if (!pkgFolder.exists()) {
+ pkgFolder.create(true /* force */, true /* local */,
+ new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ if (activityName != null) {
+ // create the main activity Java file
+ String activityJava = activityName + AdtConstants.DOT_JAVA;
+ IFile file = pkgFolder.getFile(activityJava);
+ if (!file.exists()) {
+ copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor, false);
+ }
+ }
+
+ // create the layout file
+ IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
+ IFile file = layoutfolder.getFile(MAIN_LAYOUT_XML);
+ if (!file.exists()) {
+ copyFile(LAYOUT_TEMPLATE, file, parameters, monitor, true);
+ if (activityName != null) {
+ dictionary.put(STRING_HELLO_WORLD, String.format("Hello World, %1$s!",
+ activityName));
+ } else {
+ dictionary.put(STRING_HELLO_WORLD, "Hello World!");
+ }
+ }
+ }
+
+ private void copySampleCode(IProject project, File sampleDir,
+ Map<String, Object> parameters, Map<String, String> dictionary,
+ IProgressMonitor monitor) throws CoreException {
+ // Copy the sampleDir into the project directory recursively
+ IFileSystem fileSystem = EFS.getLocalFileSystem();
+ IFileStore sourceDir = fileSystem.getStore(sampleDir.toURI());
+ IFileStore destDir = fileSystem.getStore(AdtUtils.getAbsolutePath(project));
+ sourceDir.copy(destDir, EFS.OVERWRITE, null);
+ }
+
+ /**
+ * Adds a file to the root of the project
+ * @param project the project to add the file to.
+ * @param source the file to add. It'll keep the same filename once copied into the project.
+ * @throws FileNotFoundException
+ * @throws CoreException
+ */
+ private void addLocalFile(IProject project, File source, IProgressMonitor monitor)
+ throws FileNotFoundException, CoreException {
+ IFile dest = project.getFile(source.getName());
+ if (dest.exists() == false) {
+ FileInputStream stream = new FileInputStream(source);
+ dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Adds the given folder to the project's class path.
+ *
+ * @param javaProject The Java Project to update.
+ * @param sourceFolders Template Parameters.
+ * @param monitor An existing monitor.
+ * @throws JavaModelException if the classpath could not be set.
+ */
+ private void setupSourceFolders(IJavaProject javaProject, String[] sourceFolders,
+ IProgressMonitor monitor) throws JavaModelException {
+ IProject project = javaProject.getProject();
+
+ // get the list of entries.
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+
+ // remove the project as a source folder (This is the default)
+ entries = removeSourceClasspath(entries, project);
+
+ // add the source folders.
+ for (String sourceFolder : sourceFolders) {
+ IFolder srcFolder = project.getFolder(sourceFolder);
+
+ // remove it first in case.
+ entries = removeSourceClasspath(entries, srcFolder);
+ entries = ProjectHelper.addEntryToClasspath(entries,
+ JavaCore.newSourceEntry(srcFolder.getFullPath()));
+ }
+
+ javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
+ }
+
+
+ /**
+ * Removes the corresponding source folder from the class path entries if
+ * found.
+ *
+ * @param entries The class path entries to read. A copy will be returned.
+ * @param folder The parent source folder to remove.
+ * @return A new class path entries array.
+ */
+ private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) {
+ if (folder == null) {
+ return entries;
+ }
+ IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath());
+ int n = entries.length;
+ for (int i = n - 1; i >= 0; i--) {
+ if (entries[i].equals(source)) {
+ IClasspathEntry[] newEntries = new IClasspathEntry[n - 1];
+ if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i);
+ if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1);
+ n--;
+ entries = newEntries;
+ }
+ }
+ return entries;
+ }
+
+
+ /**
+ * Copies the given file from our resource folder to the new project.
+ * Expects the file to the US-ASCII or UTF-8 encoded.
+ *
+ * @throws CoreException from IFile if failing to create the new file.
+ * @throws MalformedURLException from URL if failing to interpret the URL.
+ * @throws FileNotFoundException from RandomAccessFile.
+ * @throws IOException from RandomAccessFile.length() if can't determine the
+ * length.
+ */
+ private void copyFile(String resourceFilename, IFile destFile,
+ Map<String, Object> parameters, IProgressMonitor monitor, boolean reformat)
+ throws CoreException, IOException {
+
+ // Read existing file.
+ String template = AdtPlugin.readEmbeddedTextFile(
+ TEMPLATES_DIRECTORY + resourceFilename);
+
+ // Replace all keyword parameters
+ template = replaceParameters(template, parameters);
+
+ if (reformat) {
+ // Guess the formatting style based on the file location
+ XmlFormatStyle style = XmlFormatStyle.getForFile(destFile.getProjectRelativePath());
+ if (style != null) {
+ template = reformat(style, template);
+ }
+ }
+
+ // Save in the project as UTF-8
+ InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
+ destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+
+ /**
+ * Replaces placeholders found in a string with values.
+ *
+ * @param str the string to search for placeholders.
+ * @param parameters a map of <placeholder, Value> to search for in the string
+ * @return A new String object with the placeholder replaced by the values.
+ */
+ private String replaceParameters(String str, Map<String, Object> parameters) {
+
+ if (parameters == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null parameter map. String: '%s'", str); //$NON-NLS-1$
+ return str;
+ } else if (str == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null template string"); //$NON-NLS-1$
+ return str;
+ }
+
+ for (Entry<String, Object> entry : parameters.entrySet()) {
+ if (entry != null && entry.getValue() instanceof String) {
+ Object value = entry.getValue();
+ if (value == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null value for key '%s' in template '%s'", //$NON-NLS-1$
+ entry.getKey(),
+ str);
+ } else {
+ str = str.replaceAll(entry.getKey(), (String) value);
+ }
+ }
+ }
+
+ return str;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
index b6854af..b968258 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2011 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.
@@ -13,75 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.ide.eclipse.adt.internal.wizards.newproject;
-import com.android.AndroidConstants;
-import com.android.ide.common.layout.LayoutConstants;
-import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
-import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
-import com.android.ide.eclipse.adt.internal.project.AndroidNature;
-import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
-import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.IMainInfo;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage.TestInfo;
-import com.android.io.StreamException;
-import com.android.resources.Density;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkConstants;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
-import org.eclipse.core.resources.IContainer;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IProjectDescription;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IResourceStatus;
-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.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-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;
-import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
-import org.eclipse.jface.dialogs.ErrorDialog;
-import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;
-import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
-import org.eclipse.ui.IWorkingSet;
-import org.eclipse.ui.PlatformUI;
-import org.eclipse.ui.actions.WorkspaceModifyOperation;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.net.MalformedURLException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
/**
* A "New Android Project" Wizard.
@@ -93,211 +37,65 @@ import java.util.Set;
* Do not derive from this class.
*/
public class NewProjectWizard extends Wizard implements INewWizard {
-
- /**
- * 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_IMPORT_RESOURCE_CLASS = "IMPORT_RESOURCE_CLASS"; //$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 + AdtConstants.WS_SEP;
- private static final String BIN_CLASSES_DIRECTORY =
- SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP +
- SdkConstants.FD_CLASSES_OUTPUT + AdtConstants.WS_SEP;
- private static final String RES_DIRECTORY =
- SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP;
- private static final String ASSETS_DIRECTORY =
- SdkConstants.FD_ASSETS + AdtConstants.WS_SEP;
- private static final String DRAWABLE_DIRECTORY =
- AndroidConstants.FD_RES_DRAWABLE + AdtConstants.WS_SEP;
- private static final String DRAWABLE_HDPI_DIRECTORY =
- AndroidConstants.FD_RES_DRAWABLE + "-" + Density.HIGH.getResourceValue() + //$NON-NLS-1$
- AdtConstants.WS_SEP;
- private static final String DRAWABLE_MDPI_DIRECTORY =
- AndroidConstants.FD_RES_DRAWABLE + "-" + Density.MEDIUM.getResourceValue() + //$NON-NLS-1$
- AdtConstants.WS_SEP;
- private static final String DRAWABLE_LDPI_DIRECTORY =
- AndroidConstants.FD_RES_DRAWABLE + "-" + Density.LOW.getResourceValue() + //$NON-NLS-1$
- AdtConstants.WS_SEP;
- private static final String LAYOUT_DIRECTORY =
- AndroidConstants.FD_RES_LAYOUT + AdtConstants.WS_SEP;
- private static final String VALUES_DIRECTORY =
- AndroidConstants.FD_RES_VALUES + AdtConstants.WS_SEP;
- private static final String GEN_SRC_DIRECTORY =
- SdkConstants.FD_GEN_SOURCES + AdtConstants.WS_SEP;
-
- private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
- private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
- + "AndroidManifest.template"; //$NON-NLS-1$
- private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY
- + "activity.template"; //$NON-NLS-1$
- private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
- + "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$
- private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY
- + "string.template"; //$NON-NLS-1$
- private static final String PROJECT_ICON = "ic_launcher.png"; //$NON-NLS-1$
- private static final String ICON_HDPI = "ic_launcher_hdpi.png"; //$NON-NLS-1$
- private static final String ICON_MDPI = "ic_launcher_mdpi.png"; //$NON-NLS-1$
- private static final String ICON_LDPI = "ic_launcher_ldpi.png"; //$NON-NLS-1$
-
- private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
-
- private static final String STRING_RSRC_PREFIX = LayoutConstants.STRING_PREFIX;
- 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, BIN_CLASSES_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
- private static final String[] RES_DIRECTORIES = new String[] {
- DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY };
- private static final String[] RES_DENSITY_ENABLED_DIRECTORIES = new String[] {
- DRAWABLE_HDPI_DIRECTORY, DRAWABLE_MDPI_DIRECTORY, DRAWABLE_LDPI_DIRECTORY,
- LAYOUT_DIRECTORY, VALUES_DIRECTORY };
-
private static final String PROJECT_LOGO_LARGE = "icons/android-64.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$
- private NewProjectCreationPage mMainPage;
- private NewTestProjectCreationPage mTestPage;
- /** Package name available when the wizard completes. */
- private String mPackageName;
- private final AvailablePages mAvailablePages;
+ private NewProjectWizardState mValues;
+ private ProjectNamePage mNamePage;
+ private SdkSelectionPage mSdkPage;
+ private SampleSelectionPage mSamplePage;
+ private ApplicationInfoPage mPropertiesPage;
+ private final Mode mMode;
+ /** Constructs a new wizard default project wizard */
public NewProjectWizard() {
- this(AvailablePages.ANDROID_AND_TEST_PROJECT);
+ this(Mode.ANY);
}
- protected NewProjectWizard(AvailablePages availablePages) {
- mAvailablePages = availablePages;
+ protected NewProjectWizard(Mode mode) {
+ setWindowTitle("New Android Project");
+ mMode = mode;
}
- /**
- * Initializes this creation wizard using the passed workbench and object
- * selection. Inherited from org.eclipse.ui.IWorkbenchWizard
- */
- public void init(IWorkbench workbench, IStructuredSelection selection) {
- setHelpAvailable(false); // TODO have help
- setImageDescriptor();
+ @Override
+ public void addPages() {
+ mValues = new NewProjectWizardState(mMode);
- if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
- mMainPage = createMainPage();
- setWindowTitle("New Android Project");
- } else {
- setWindowTitle("New Android Test Project");
+ if (mMode != Mode.SAMPLE) {
+ mNamePage = new ProjectNamePage(mValues);
+ addPage(mNamePage);
}
- mTestPage = createTestPage();
- }
-
- /**
- * Creates the main 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 NewProjectCreationPage createMainPage() {
- 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();
- }
+ if (mMode == Mode.TEST) {
+ addPage(new TestTargetPage(mValues));
+ }
- // -- Methods inherited from org.eclipse.jface.wizard.Wizard --
- // The Wizard class implements most defaults and boilerplate code needed by
- // IWizard
+ mSdkPage = new SdkSelectionPage(mValues);
+ addPage(mSdkPage);
- /**
- * Adds pages to this wizard.
- */
- @Override
- public void addPages() {
- if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
- addPage(mMainPage);
+ if (mMode != Mode.TEST) {
+ // Sample projects can be created when entering the new/existing wizard, or
+ // the sample wizard
+ mSamplePage = new SampleSelectionPage(mValues);
+ addPage(mSamplePage);
}
- addPage(mTestPage);
- if (mMainPage != null && mTestPage != null) {
- mTestPage.setMainInfo(mMainPage.getMainInfo());
- mMainPage.setTestInfo(mTestPage.getTestInfo());
+ if (mMode != Mode.SAMPLE) {
+ // Project properties are entered in all project types except sample projects
+ mPropertiesPage = new ApplicationInfoPage(mValues);
+ addPage(mPropertiesPage);
}
}
- /**
- * Performs any actions appropriate in response to the user having pressed
- * the Finish button, or refuse if finishing now is not permitted: here, it
- * actually creates the workspace project and then switch to the Java
- * perspective.
- *
- * @return True
- */
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ setHelpAvailable(false); // TODO have help
+ ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE);
+ setDefaultPageImageDescriptor(desc);
+ }
+
@Override
public boolean performFinish() {
- if (!createAndroidProjects()) {
+ NewProjectCreator creator = new NewProjectCreator(mValues, getContainer());
+ if (!(creator.createAndroidProjects())) {
return false;
}
@@ -307,962 +105,38 @@ public class NewProjectWizard extends Wizard implements INewWizard {
return true;
}
- // -- Public Fields --
-
- /** 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.
- */
- private boolean validateNewProjectLocationIsEmpty(IPath destination) {
- File f = new File(destination.toOSString());
- if (f.isDirectory() && f.list().length > 0) {
- return AdtPlugin.displayPrompt("New Android Project",
- "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?");
- }
- return true;
- }
-
- /**
- * 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 createAndroidProjects() {
-
- final ProjectInfo mainData = collectMainPageInfo();
- if (mMainPage != null && mainData == null) {
- return false;
- }
-
- final ProjectInfo testData = collectTestPageInfo();
-
- // 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(info.getProjectName());
- final IProjectDescription description = workspace.newProjectDescription(project.getName());
-
- // keep some variables to make them available once the wizard closes
- mPackageName = info.getPackageName();
-
- final Map<String, Object> parameters = new HashMap<String, Object>();
- 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, 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()) {
- parameters.put(PARAM_ACTIVITY, info.getActivityName());
- }
-
- // 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 != null && !path.equals(defaultLocation)) {
- description.setLocation(path);
- }
-
- if (info.isNewProject() && !info.useDefaultLocation() &&
- !validateNewProjectLocationIsEmpty(path)) {
- return null;
- }
-
- return new ProjectInfo(project, description, parameters, dictionary);
- }
-
- /**
- * 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();
-
- if (!info.getCreateTestProject()) {
- return null;
- }
-
- 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);
- }
-
- /**
- * Runs the operation in a different thread and display generated
- * exceptions.
- *
- * @param op The asynchronous operation to run.
- */
- private void runAsyncOperation(WorkspaceModifyOperation op) {
- try {
- getContainer().run(true /* fork */, true /* cancelable */, op);
- } catch (InvocationTargetException e) {
-
- AdtPlugin.log(e, "New Project Wizard failed");
-
- // The runnable threw an exception
- Throwable t = e.getTargetException();
- if (t instanceof CoreException) {
- CoreException core = (CoreException) t;
- if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
- // The error indicates the file system is not case sensitive
- // and there's a resource with a similar name.
- MessageDialog.openError(getShell(), "Error", "Error: Case Variant Exists");
- } else {
- ErrorDialog.openError(getShell(), "Error", core.getMessage(), core.getStatus());
- }
- } else {
- // Some other kind of exception
- String msg = t.getMessage();
- Throwable t1 = t;
- while (msg == null && t1.getCause() != null) {
- msg = t1.getMessage();
- t1 = t1.getCause();
- }
- if (msg == null) {
- msg = t.toString();
- }
- MessageDialog.openError(getShell(), "Error", msg);
- }
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Creates the actual project(s). This is run asynchronously in a different thread.
- *
- * @param monitor An existing monitor.
- * @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(IProgressMonitor monitor,
- ProjectInfo mainData,
- ProjectInfo testData)
- throws InvocationTargetException {
- monitor.beginTask("Create Android Project", 100);
- try {
- IProject mainProject = null;
-
- if (mainData != null) {
- mainProject = createEclipseProject(
- new SubProgressMonitor(monitor, 50),
- mainData.getProject(),
- mainData.getDescription(),
- mainData.getParameters(),
- mainData.getDictionary());
-
- if (mainProject != null) {
- final IJavaProject javaProject = JavaCore.create(mainProject);
- Display.getDefault().syncExec(new Runnable() {
-
- public void run() {
- IWorkingSet[] workingSets = mMainPage.getWorkingSets();
- if (workingSets.length > 0 && javaProject != null
- && javaProject.exists()) {
- PlatformUI.getWorkbench().getWorkingSetManager()
- .addToWorkingSets(javaProject, workingSets);
- }
- }
- });
- }
- }
-
- if (testData != null) {
-
- Map<String, Object> parameters = testData.getParameters();
- if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
- parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
- }
-
- IProject testProject = createEclipseProject(
- new SubProgressMonitor(monitor, 50),
- testData.getProject(),
- testData.getDescription(),
- parameters,
- testData.getDictionary());
- if (testProject != null) {
- final IJavaProject javaProject = JavaCore.create(testProject);
- Display.getDefault().syncExec(new Runnable() {
-
- public void run() {
- IWorkingSet[] workingSets = mTestPage.getWorkingSets();
- if (workingSets.length > 0 && javaProject != null
- && javaProject.exists()) {
- PlatformUI.getWorkbench().getWorkingSetManager()
- .addToWorkingSets(javaProject, workingSets);
- }
- }
- });
- }
- }
- } catch (CoreException e) {
- throw new InvocationTargetException(e);
- } catch (IOException e) {
- throw new InvocationTargetException(e);
- } catch (StreamException e) {
- throw new InvocationTargetException(e);
- } finally {
- monitor.done();
- }
- }
-
- /**
- * 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
- * @throws StreamException
- */
- private IProject createEclipseProject(IProgressMonitor monitor,
- IProject project,
- IProjectDescription description,
- Map<String, Object> parameters,
- Map<String, String> dictionary)
- throws CoreException, IOException, StreamException {
-
- // get the project target
- IAndroidTarget target = (IAndroidTarget) parameters.get(PARAM_SDK_TARGET);
- boolean legacy = target.getVersion().getApiLevel() < 4;
-
- // 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, AdtConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
- String[] sourceFolders = new String[] {
- (String) parameters.get(PARAM_SRC_FOLDER),
- GEN_SRC_DIRECTORY
- };
- addDefaultDirectories(project, AdtConstants.WS_ROOT, sourceFolders, monitor);
-
- // Create the resource folders in the project if they don't already exist.
- if (legacy) {
- addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
- } else {
- addDefaultDirectories(project, RES_DIRECTORY, RES_DENSITY_ENABLED_DIRECTORIES, monitor);
- }
-
- // Setup class path: mark folders as source folders
- IJavaProject javaProject = JavaCore.create(project);
- setupSourceFolders(javaProject, sourceFolders, monitor);
-
- 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, legacy, 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);
- }
-
- // add the default proguard config
- File libFolder = new File((String) parameters.get(PARAM_SDK_TOOLS_DIR),
- SdkConstants.FD_LIB);
- addLocalFile(project,
- new File(libFolder, SdkConstants.FN_PROGUARD_CFG),
- monitor);
-
- // Set output location
- javaProject.setOutputLocation(project.getFolder(BIN_CLASSES_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().initProject(project, target);
-
- // 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.
- * @param parentFolder The path of the parent folder. Must end with a
- * separator.
- * @param folders Folders to be added.
- * @param monitor An existing monitor.
- * @throws CoreException if the method fails to create the directories in
- * the project.
- */
- private void addDefaultDirectories(IProject project, String parentFolder,
- String[] folders, IProgressMonitor monitor) throws CoreException {
- for (String name : folders) {
- if (name.length() > 0) {
- IFolder folder = project.getFolder(parentFolder + name);
- if (!folder.exists()) {
- folder.create(true /* force */, true /* local */,
- new SubProgressMonitor(monitor, 10));
- }
- }
- }
- }
-
- /**
- * Adds the manifest to the project.
- *
- * @param project The Java Project to update.
- * @param parameters Template Parameters.
- * @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.
- * @throws IOException if the method fails to create the files in the
- * project.
- */
- private void addManifest(IProject project, Map<String, Object> parameters,
- Map<String, String> dictionary, IProgressMonitor monitor)
- throws CoreException, IOException {
-
- // get IFile to the manifest and check if it's not already there.
- IFile file = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
- if (!file.exists()) {
-
- // Read manifest template
- String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST);
-
- // Replace all keyword parameters
- manifestTemplate = replaceParameters(manifestTemplate, parameters);
-
- if (manifestTemplate == null) {
- // Inform the user there will be not manifest.
- AdtPlugin.logAndPrintError(null, getWindowTitle() /*TAG*/,
- "Failed to generate the Android manifest. Missing template %s",
- TEMPLATE_MANIFEST);
- // Abort now, there's no need to continue
- return;
- }
-
- if (parameters.containsKey(PARAM_ACTIVITY)) {
- // now get the activity template
- String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
-
- // If the activity name doesn't contain any dot, it's in the form
- // "ClassName" and we need to expand it to ".ClassName" in the XML.
- String name = (String) parameters.get(PARAM_ACTIVITY);
- if (name.indexOf('.') == -1) {
- // Duplicate the parameters map to avoid changing the caller
- parameters = new HashMap<String, Object>(parameters);
- parameters.put(PARAM_ACTIVITY, "." + name); //$NON-NLS-1$
- }
-
- // Replace all keyword parameters to make main activity.
- String activities = replaceParameters(activityTemplate, parameters);
-
- // set the intent.
- String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
-
- if (activities != null) {
- if (intent != null) {
- // 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, ""); //$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);
- if (usesLibrary != null) {
- manifestTemplate = manifestTemplate.replaceAll(
- PH_TEST_USES_LIBRARY, usesLibrary);
- }
-
- // Set the instrumentation element needed by the test project
- String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
- if (instru != null) {
- 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);
- if (usesSdkTemplate != null) {
- String usesSdk = replaceParameters(usesSdkTemplate, parameters);
- manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk);
- }
- } else {
- manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
- }
-
- // Reformat the file according to the user's formatting settings
- manifestTemplate = reformat(XmlFormatStyle.MANIFEST, manifestTemplate);
-
- // Save in the project as UTF-8
- InputStream stream = new ByteArrayInputStream(
- manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
- file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
- }
- }
-
- /**
- * Adds the string resource file.
- *
- * @param project The Java Project to update.
- * @param strings The list of strings to be added to the string file.
- * @param monitor An existing monitor.
- * @throws CoreException if the method fails to update the project.
- * @throws IOException if the method fails to create the files in the
- * project.
- */
- private void addStringDictionaryFile(IProject project,
- Map<String, String> strings, IProgressMonitor monitor)
- throws CoreException, IOException {
-
- // create the IFile object and check if the file doesn't already exist.
- IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
- + VALUES_DIRECTORY + AdtConstants.WS_SEP + STRINGS_FILE);
- if (!file.exists()) {
- // get the Strings.xml template
- String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS);
-
- // get the template for one string
- String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING);
-
- // get all the string names
- Set<String> stringNames = strings.keySet();
-
- // loop on it and create the string definitions
- StringBuilder stringNodes = new StringBuilder();
- for (String key : stringNames) {
- // get the value from the key
- String value = strings.get(key);
-
- // Escape values if necessary
- value = ExtractStringRefactoring.escapeString(value);
-
- // place them in the template
- String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
- stringDef = stringDef.replace(PARAM_STRING_CONTENT, value);
-
- // append to the other string
- if (stringNodes.length() > 0) {
- stringNodes.append('\n');
- }
- stringNodes.append(stringDef);
- }
-
- // put the string nodes in the Strings.xml template
- stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
- stringNodes.toString());
-
- // reformat the file according to the user's formatting settings
- stringDefinitionTemplate = reformat(XmlFormatStyle.RESOURCE, stringDefinitionTemplate);
-
- // write the file as UTF-8
- InputStream stream = new ByteArrayInputStream(
- stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
- file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
- }
- }
-
- /** Reformats the given contents with the current formatting settings */
- private String reformat(XmlFormatStyle style, String contents) {
- if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
- XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
- return XmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
- null /*lineSeparator*/);
- } else {
- return contents;
- }
- }
-
- /**
- * Adds default application icon to the project.
- *
- * @param project The Java Project to update.
- * @param legacy whether we're running in legacy mode (no density support)
- * @param monitor An existing monitor.
- * @throws CoreException if the method fails to update the project.
- */
- private void addIcon(IProject project, boolean legacy, IProgressMonitor monitor)
- throws CoreException {
- if (legacy) { // density support
- // do medium density icon only, in the default drawable folder.
- IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
- + DRAWABLE_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
- if (!file.exists()) {
- addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
- }
- } else {
- // do all 3 icons.
- IFile file;
-
- // high density
- file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
- + DRAWABLE_HDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
- if (!file.exists()) {
- addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_HDPI), monitor);
- }
-
- // medium density
- file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
- + DRAWABLE_MDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
- if (!file.exists()) {
- addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
- }
-
- // low density
- file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
- + DRAWABLE_LDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
- if (!file.exists()) {
- addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_LDPI), monitor);
- }
- }
- }
-
- /**
- * Creates a file from a data source.
- * @param dest the file to write
- * @param source the content of the file.
- * @param monitor the progress monitor
- * @throws CoreException
- */
- private void addFile(IFile dest, byte[] source, IProgressMonitor monitor) throws CoreException {
- if (source != null) {
- // Save in the project
- InputStream stream = new ByteArrayInputStream(source);
- dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
- }
- }
-
- /**
- * Creates the package folder and copies the sample code in the project.
- *
- * @param project The Java Project to update.
- * @param parameters Template Parameters.
- * @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.
- * @throws IOException if the method fails to create the files in the
- * project.
- */
- private void addSampleCode(IProject project, String sourceFolder,
- 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);
-
- Map<String, Object> java_activity_parameters = new HashMap<String, Object>(parameters);
- java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, ""); //$NON-NLS-1$
-
- if (activityName != null) {
-
- String resourcePackageClass = null;
-
- // An activity name can be of the form ".package.Class", ".Class" or FQDN.
- // The initial dot is ignored, as it is always added later in the templates.
- int lastDotIndex = activityName.lastIndexOf('.');
-
- if (lastDotIndex != -1) {
-
- // Resource class
- if (lastDotIndex > 0) {
- resourcePackageClass = packageName + "." + AdtConstants.FN_RESOURCE_BASE; //$NON-NLS-1$
- }
-
- // Package name
- if (activityName.startsWith(".")) { //$NON-NLS-1$
- packageName += activityName.substring(0, lastDotIndex);
- } else {
- packageName = activityName.substring(0, lastDotIndex);
- }
-
- // Activity Class name
- activityName = activityName.substring(lastDotIndex + 1);
- }
-
- java_activity_parameters.put(PARAM_ACTIVITY, activityName);
- java_activity_parameters.put(PARAM_PACKAGE, packageName);
- if (resourcePackageClass != null) {
- String importResourceClass = "\nimport " + resourcePackageClass + ";"; //$NON-NLS-1$ // $NON-NLS-2$
- java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, importResourceClass);
- }
- }
-
- String[] components = packageName.split(AdtConstants.RE_DOT);
- for (String component : components) {
- pkgFolder = pkgFolder.getFolder(component);
- if (!pkgFolder.exists()) {
- pkgFolder.create(true /* force */, true /* local */,
- new SubProgressMonitor(monitor, 10));
- }
- }
-
- if (activityName != null) {
- // create the main activity Java file
- String activityJava = activityName + AdtConstants.DOT_JAVA;
- IFile file = pkgFolder.getFile(activityJava);
- if (!file.exists()) {
- copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor, false);
- }
- }
-
- // create the layout file
- IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
- IFile file = layoutfolder.getFile(MAIN_LAYOUT_XML);
- if (!file.exists()) {
- copyFile(LAYOUT_TEMPLATE, file, parameters, monitor, true);
- if (activityName != null) {
- dictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
+ @Override
+ public IWizardPage getNextPage(IWizardPage page) {
+ if (page == mNamePage) {
+ // Skip the test target selection page unless creating a test project
+ if (mValues.mode != Mode.TEST) {
+ return mSdkPage;
+ }
+ } else if (page == mSdkPage) {
+ if (mValues.mode == Mode.SAMPLE) {
+ return mSamplePage;
+ } else if (mValues.mode != Mode.TEST) {
+ return mPropertiesPage;
} else {
- dictionary.put(STRING_HELLO_WORLD, "Hello World!");
+ // Done with wizard when creating from existing or creating test projects
+ return null;
}
- }
- }
-
- /**
- * Adds a file to the root of the project
- * @param project the project to add the file to.
- * @param source the file to add. It'll keep the same filename once copied into the project.
- * @throws FileNotFoundException
- * @throws CoreException
- */
- private void addLocalFile(IProject project, File source, IProgressMonitor monitor)
- throws FileNotFoundException, CoreException {
- IFile dest = project.getFile(source.getName());
- if (dest.exists() == false) {
- FileInputStream stream = new FileInputStream(source);
- dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
- }
- }
-
- /**
- * Adds the given folder to the project's class path.
- *
- * @param javaProject The Java Project to update.
- * @param sourceFolders Template Parameters.
- * @param monitor An existing monitor.
- * @throws JavaModelException if the classpath could not be set.
- */
- private void setupSourceFolders(IJavaProject javaProject, String[] sourceFolders,
- IProgressMonitor monitor) throws JavaModelException {
- IProject project = javaProject.getProject();
-
- // get the list of entries.
- IClasspathEntry[] entries = javaProject.getRawClasspath();
-
- // remove the project as a source folder (This is the default)
- entries = removeSourceClasspath(entries, project);
-
- // add the source folders.
- for (String sourceFolder : sourceFolders) {
- IFolder srcFolder = project.getFolder(sourceFolder);
-
- // remove it first in case.
- entries = removeSourceClasspath(entries, srcFolder);
- entries = ProjectHelper.addEntryToClasspath(entries,
- JavaCore.newSourceEntry(srcFolder.getFullPath()));
+ } else if (page == mSamplePage) {
+ // Nothing more to be entered for samples
+ return null;
}
- javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
+ return super.getNextPage(page);
}
-
/**
- * Removes the corresponding source folder from the class path entries if
- * found.
+ * Returns the package name currently set by the wizard
*
- * @param entries The class path entries to read. A copy will be returned.
- * @param folder The parent source folder to remove.
- * @return A new class path entries array.
+ * @return the current package name, or null
*/
- private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) {
- if (folder == null) {
- return entries;
- }
- IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath());
- int n = entries.length;
- for (int i = n - 1; i >= 0; i--) {
- if (entries[i].equals(source)) {
- IClasspathEntry[] newEntries = new IClasspathEntry[n - 1];
- if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i);
- if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1);
- n--;
- entries = newEntries;
- }
- }
- return entries;
- }
-
-
- /**
- * Copies the given file from our resource folder to the new project.
- * Expects the file to the US-ASCII or UTF-8 encoded.
- *
- * @throws CoreException from IFile if failing to create the new file.
- * @throws MalformedURLException from URL if failing to interpret the URL.
- * @throws FileNotFoundException from RandomAccessFile.
- * @throws IOException from RandomAccessFile.length() if can't determine the
- * length.
- */
- private void copyFile(String resourceFilename, IFile destFile,
- Map<String, Object> parameters, IProgressMonitor monitor, boolean reformat)
- throws CoreException, IOException {
-
- // Read existing file.
- String template = AdtPlugin.readEmbeddedTextFile(
- TEMPLATES_DIRECTORY + resourceFilename);
-
- // Replace all keyword parameters
- template = replaceParameters(template, parameters);
-
- if (reformat) {
- // Guess the formatting style based on the file location
- XmlFormatStyle style = XmlFormatStyle.getForFile(destFile.getProjectRelativePath());
- if (style != null) {
- template = reformat(style, template);
- }
- }
-
- // Save in the project as UTF-8
- InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
- destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
- }
-
- /**
- * Returns an image descriptor for the wizard logo.
- */
- private void setImageDescriptor() {
- ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE);
- setDefaultPageImageDescriptor(desc);
+ public String getPackageName() {
+ return mValues.packageName;
}
- /**
- * Replaces placeholders found in a string with values.
- *
- * @param str the string to search for placeholders.
- * @param parameters a map of <placeholder, Value> to search for in the string
- * @return A new String object with the placeholder replaced by the values.
- */
- private String replaceParameters(String str, Map<String, Object> parameters) {
-
- if (parameters == null) {
- AdtPlugin.log(IStatus.ERROR,
- "NPW replace parameters: null parameter map. String: '%s'", str); //$NON-NLS-1$
- return str;
- } else if (str == null) {
- AdtPlugin.log(IStatus.ERROR,
- "NPW replace parameters: null template string"); //$NON-NLS-1$
- return str;
- }
-
- for (Entry<String, Object> entry : parameters.entrySet()) {
- if (entry != null && entry.getValue() instanceof String) {
- Object value = entry.getValue();
- if (value == null) {
- AdtPlugin.log(IStatus.ERROR,
- "NPW replace parameters: null value for key '%s' in template '%s'", //$NON-NLS-1$
- entry.getKey(),
- str);
- } else {
- str = str.replaceAll(entry.getKey(), (String) value);
- }
- }
- }
-
- return str;
- }
+ // TBD: Call setDialogSettings etc to store persistent state between wizard invocations.
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java
new file mode 100644
index 0000000..eb4f7f6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+import com.android.sdklib.xml.AndroidManifest;
+import com.android.sdklib.xml.ManifestData;
+import com.android.sdklib.xml.ManifestData.Activity;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.ui.IWorkingSet;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link NewProjectWizardState} holds the state used by the various pages
+ * in the {@link NewProjectWizard} and its variations, and it can also be used
+ * to pass project information to the {@link NewProjectCreator}.
+ */
+public class NewProjectWizardState {
+ /** The mode to run the wizard in: creating test, or sample, or plain project */
+ public Mode mode;
+
+ /**
+ * If true, the project should be created from an existing codebase (pointed
+ * to by the {@link #projectLocation} or in the case of sample projects, the
+ * {@link #chosenSample}. Otherwise, create a brand new project from scratch.
+ */
+ public boolean useExisting;
+
+ /**
+ * Whether new projects should be created into the default project location
+ * (e.g. in the Eclipse workspace) or not
+ */
+ public boolean useDefaultLocation = true;
+
+ /** The build target SDK */
+ public IAndroidTarget target;
+ /** True if the user has manually modified the target */
+ public boolean targetModifiedByUser;
+
+ /** The location to store projects into */
+ public File projectLocation = new File(Platform.getLocation().toOSString());
+
+ /** The name of the project */
+ public String projectName = ""; //$NON-NLS-1$
+ /** True if the project name has been manually edited by the user */
+ public boolean projectNameModifiedByUser;
+
+ /** The application name */
+ public String applicationName;
+ /** True if the application name has been manually edited by the user */
+ public boolean applicationNameModifiedByUser;
+
+ /** The package path */
+ public String packageName;
+ /** True if the package name has been manually edited by the user */
+ public boolean packageNameModifiedByUser;
+
+ /** True if a new activity should be created */
+ public boolean createActivity = true;
+ /** The name of the new activity to be created */
+ public String activityName;
+ /** True if the activity name has been manually edited by the user */
+ public boolean activityNameModifiedByUser;
+
+ /** The minimum SDK version to use with the project (may be null or blank) */
+ public String minSdk;
+ /** True if the minimum SDK version has been manually edited by the user */
+ public boolean minSdkModifiedByUser;
+
+ /**
+ * The directory where the samples are found. This field is only applicable
+ * when the wizard is running in create-sample-mode.
+ */
+ public File samplesDir;
+ /** A list of paths to each of the available samples for the current SDK */
+ public List<File> samples = new ArrayList<File>();
+ /** Path to the currently chosen sample */
+ public File chosenSample;
+
+ /** The name of the source folder, relative to the project root */
+ public String sourceFolder = SdkConstants.FD_SOURCES;
+ /** The set of chosen working sets to use when creating the project */
+ public IWorkingSet[] workingSets = new IWorkingSet[0];
+
+ /**
+ * A reference to a different project that the current test project will be
+ * testing.
+ */
+ public IProject testedProject;
+ /**
+ * If true, this test project should be testing itself, otherwise it will be
+ * testing the project pointed to by {@link #testedProject}.
+ */
+ public boolean testingSelf;
+
+ // NOTE: These apply only to creating paired projects; when isTest is true
+ // we're using
+ // the normal fields above
+ /**
+ * If true, create a test project along with this plain project which will
+ * be testing the plain project. (This flag only applies when creating
+ * normal projects.)
+ */
+ public boolean createPairProject;
+ /**
+ * The application name of the test application (only applies when
+ * {@link #createPairProject} is true)
+ */
+ public String testApplicationName;
+ /**
+ * True if the testing application name has been modified by the user (only
+ * applies when {@link #createPairProject} is true)
+ */
+ public boolean testApplicationNameModified;
+ /**
+ * The package name of the test application (only applies when
+ * {@link #createPairProject} is true)
+ */
+ public String testPackageName;
+ /**
+ * True if the testing package name has been modified by the user (only
+ * applies when {@link #createPairProject} is true)
+ */
+ public boolean testPackageModified;
+ /**
+ * The project name of the test project (only applies when
+ * {@link #createPairProject} is true)
+ */
+ public String testProjectName;
+ /**
+ * True if the testing project name has been modified by the user (only
+ * applies when {@link #createPairProject} is true)
+ */
+ public boolean testProjectModified;
+ /** Package name of the tested app */
+ public String testTargetPackageName;
+
+ /**
+ * Creates a new {@link NewProjectWizardState}
+ *
+ * @param mode the mode to run the wizard in
+ */
+ public NewProjectWizardState(Mode mode) {
+ this.mode = mode;
+ if (mode == Mode.SAMPLE) {
+ useExisting = true;
+ } else if (mode == Mode.TEST) {
+ createActivity = false;
+ }
+ }
+
+ /**
+ * Extract information (package name, application name, minimum SDK etc) from
+ * the given Android project.
+ *
+ * @param path the path to the project to extract information from
+ */
+ public void extractFromAndroidManifest(Path path) {
+ String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
+
+ ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
+ if (manifestData == null) {
+ return;
+ }
+
+ String newPackageName = null;
+ Activity activity = null;
+ String newActivityName = null;
+ String minSdkVersion = null;
+ try {
+ newPackageName = manifestData.getPackage();
+ minSdkVersion = manifestData.getMinSdkVersionString();
+
+ // try to get the first launcher activity. If none, just take the first activity.
+ activity = manifestData.getLauncherActivity();
+ if (activity == null) {
+ Activity[] activities = manifestData.getActivities();
+ if (activities != null && activities.length > 0) {
+ activity = activities[0];
+ }
+ }
+ } catch (Exception e) {
+ // ignore exceptions
+ }
+
+ if (newPackageName != null && newPackageName.length() > 0) {
+ packageName = newPackageName;;
+ }
+
+ if (activity != null) {
+ newActivityName = AndroidManifest.extractActivityName(activity.getName(),
+ newPackageName);
+ }
+
+ if (newActivityName != null && newActivityName.length() > 0) {
+ activityName = newActivityName;
+ // we are "importing" an existing activity, not creating a new one
+ createActivity = false;
+
+ // If project name and application names are empty, use the activity
+ // name as a default. If the activity name has dots, it's a part of a
+ // package specification and only the last identifier must be used.
+ if (newActivityName.indexOf('.') != -1) {
+ String[] ids = newActivityName.split(AdtConstants.RE_DOT);
+ newActivityName = ids[ids.length - 1];
+ }
+ if (projectName == null || projectName.length() == 0 ||
+ !projectNameModifiedByUser) {
+ projectName = newActivityName;
+ projectNameModifiedByUser = false;
+ }
+ if (applicationName == null || applicationName.length() == 0 ||
+ !applicationNameModifiedByUser) {
+ applicationNameModifiedByUser = false;
+ applicationName = newActivityName;
+ }
+ } else {
+ activityName = ""; //$NON-NLS-1$
+
+ // 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 (newPackageName != null && newPackageName.length() > 0) {
+ // Package name is a java identifier, so it's most suitable for
+ // an application name.
+
+ if (applicationName == null || applicationName.length() == 0 ||
+ !applicationNameModifiedByUser) {
+ applicationName = newPackageName;
+ }
+
+ // For the project name, remove any dots
+ newPackageName = newPackageName.replace('.', '_');
+ if (projectName == null || projectName.length() == 0 ||
+ !projectNameModifiedByUser) {
+ projectName = newPackageName;
+ }
+
+ }
+ }
+
+ if (mode == Mode.ANY && useExisting) {
+ updateSdkTargetToMatchProject(path.toFile());
+ }
+
+ minSdk = minSdkVersion;
+ minSdkModifiedByUser = false;
+ }
+
+ /**
+ * 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.
+ */
+ public void updateSdkTargetToMatchMinSdkVersion() {
+ IAndroidTarget currentTarget = target;
+ if (currentTarget != null && currentTarget.getVersion().equals(minSdk)) {
+ return;
+ }
+
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ IAndroidTarget[] targets = sdk.getTargets();
+ for (IAndroidTarget t : targets) {
+ if (t.getVersion().equals(minSdk)) {
+ target = t;
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the SDK to reflect the SDK required by the project at the given
+ * location
+ *
+ * @param location the location of the project
+ */
+ public void updateSdkTargetToMatchProject(File location) {
+ // Select the target matching the manifest's sdk or build properties, if any
+ IAndroidTarget foundTarget = null;
+ // This is the target currently in the UI
+ IAndroidTarget currentTarget = target;
+ String projectPath = location.getPath();
+
+ // If there's a current target defined, we do not allow to change it when
+ // operating in the create-from-sample mode -- since the available sample list
+ // is tied to the current target, so changing it would invalidate the project we're
+ // trying to load in the first place.
+ if (!targetModifiedByUser) {
+ ProjectProperties p = ProjectProperties.load(projectPath,
+ PropertyType.PROJECT);
+ if (p != null) {
+ String v = p.getProperty(ProjectProperties.PROPERTY_TARGET);
+ IAndroidTarget desiredTarget = Sdk.getCurrent().getTargetFromHashString(v);
+ // We can change the current target if:
+ // - we found a new desired target
+ // - there is no current target
+ // - or the current target can't run the desired target
+ if (desiredTarget != null &&
+ (currentTarget == null || !desiredTarget.canRunOn(currentTarget))) {
+ foundTarget = desiredTarget;
+ }
+ }
+
+ Sdk sdk = Sdk.getCurrent();
+ IAndroidTarget[] targets = null;
+ if (sdk != null) {
+ targets = sdk.getTargets();
+ }
+ if (targets == null) {
+ targets = new IAndroidTarget[0];
+ }
+
+ if (foundTarget == null && minSdk != null) {
+ // Otherwise try to match the requested min-sdk-version if we find an
+ // exact match, regardless of the currently selected target.
+ for (IAndroidTarget existingTarget : targets) {
+ if (existingTarget != null &&
+ existingTarget.getVersion().equals(minSdk)) {
+ foundTarget = existingTarget;
+ break;
+ }
+ }
+ }
+
+ if (foundTarget == null) {
+ // Or last attempt, try to match a sample project location and use it
+ // if we find an exact match, regardless of the currently selected target.
+ for (IAndroidTarget existingTarget : targets) {
+ if (existingTarget != null &&
+ projectPath.startsWith(existingTarget.getLocation())) {
+ foundTarget = existingTarget;
+ break;
+ }
+ }
+ }
+ }
+
+ if (foundTarget != null) {
+ target = foundTarget;
+ }
+ }
+
+ /**
+ * Type of project being offered/created by the wizard
+ */
+ public enum Mode {
+ /** Create a sample project. Testing options are not presented. */
+ SAMPLE,
+
+ /**
+ * Create a test project, either testing itself or some other project.
+ * Note that even if in the {@link #ANY} mode, a test project can be
+ * created as a *paired* project with the main project, so this flag
+ * only means that we are creating *just* a test project
+ */
+ TEST,
+
+ /**
+ * Create an Android project, which can be a plain project, optionally
+ * with a paired test project, or a sample project (the first page
+ * contains toggles for choosing which
+ */
+ ANY;
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java
new file mode 100644
index 0000000..6b6a4c2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+
+/**
+ * A "New Sample Android Project" Wizard.
+ * <p/>
+ * This displays the new project wizard pre-configured for samples only.
+ */
+public class NewSampleProjectWizard extends NewProjectWizard {
+ /**
+ * Creates a new wizard for creating a sample Android project
+ */
+ public NewSampleProjectWizard() {
+ super(Mode.SAMPLE);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java
deleted file mode 100755
index dfdd72e..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java
+++ /dev/null
@@ -1,1395 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * References:
- * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard
- * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
- */
-
-package com.android.ide.eclipse.adt.internal.wizards.newproject;
-
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
-import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.IMainInfo;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.MainInfo;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.xml.ManifestData;
-import com.android.sdkuilib.internal.widgets.SdkTargetSelector;
-
-import org.eclipse.core.filesystem.URIUtil;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IWorkspace;
-import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.JavaConventions;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.osgi.util.TextProcessor;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.ControlAdapter;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.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 org.eclipse.ui.IWorkbenchPart;
-import org.eclipse.ui.IWorkingSet;
-
-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.
- * <p/>
- * 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;
- private WorkingSetGroup mWorkingSetGroup;
-
-
- /**
- * 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.");
- mWorkingSetGroup= new WorkingSetGroup();
- setWorkingSets(new IWorkingSet[0]);
- }
-
- public void init(IStructuredSelection selection, IWorkbenchPart activePart) {
- setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart));
- }
-
- // --- Getters used by NewProjectWizard ---
-
- /**
- * Structure that collects all externally visible information from this page.
- * This is used by the calling wizard to actually do the work or by other pages.
- */
- 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) {
- final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
- scrolledComposite.setFont(parent.getFont());
- scrolledComposite.setExpandHorizontal(true);
- scrolledComposite.setExpandVertical(true);
- initializeDialogUnits(parent);
-
- final Composite composite = new Composite(scrolledComposite, SWT.NULL);
- composite.setFont(parent.getFont());
- scrolledComposite.setContent(composite);
-
- composite.setLayout(new GridLayout());
- composite.setLayoutData(new GridData(GridData.FILL_BOTH));
-
- createToggleTestProject(composite);
- createTestProjectGroup(composite);
- createLocationGroup(composite);
- createTestTargetGroup(composite);
- createTargetGroup(composite);
- createPropertiesGroup(composite);
- createWorkingSetGroup(composite);
-
- // Update state the first time
- enableLocationWidgets();
-
- scrolledComposite.addControlListener(new ControlAdapter() {
- @Override
- public void controlResized(ControlEvent e) {
- Rectangle r = scrolledComposite.getClientArea();
- scrolledComposite.setMinSize(composite.computeSize(r.width, SWT.DEFAULT));
- }
- });
-
- // Show description the first time
- setErrorMessage(null);
- setMessage(null);
- setControl(scrolledComposite);
-
- // Validate. This will complain about the first empty field.
- validatePageComplete();
- }
-
- /**
- * 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_HORIZONTAL));
- 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(), null /*filter*/);
- } 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 onSdkLoaded() {
- // Update the sdk target selector with the new targets
-
- // get the targets from the sdk
- IAndroidTarget[] targets = null;
- if (Sdk.getCurrent() != null) {
- targets = Sdk.getCurrent().getTargets();
- }
- mSdkTargetSelector.setTargets(targets);
-
- // If there's only one target, select it
- if (targets != null && targets.length == 1) {
- mSdkTargetSelector.setSelection(targets[0]);
- }
- }
-
- public void onProjectTargetChange(IProject changedProject) {
- // Ignore
- }
-
- public void onTargetLoaded(IAndroidTarget target) {
- // Ignore
- }
- };
-
- AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
-
- // Invoke it once to initialize the targets
- mSdkTargetChangeListener.onSdkLoaded();
-
- 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();
- }
- });
- }
-
- private void createWorkingSetGroup(final Composite composite) {
- Composite group = mWorkingSetGroup.createControl(composite);
- group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
-
- mToggleComposites.add(group);
- }
-
- //--- 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) {
- useMainWorkingSets();
-
- 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;
- }
- }
- }
-
- private void useMainWorkingSets() {
- IWorkingSet[] workingSets = mMainInfo.getSelectedWorkingSets();
- if (workingSets != null) {
- // getSelectedWorkingSets returns an empty list if the working set feature is disabled.
- if (workingSets.length > 0) {
- mWorkingSetGroup.setChecked(true);
- }
- mWorkingSetGroup.setWorkingSets(workingSets);
- }
- }
-
- /**
- * 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(),
- null /*message*/);
- 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)) {
-
- ManifestData manifestData = AndroidManifestHelper.parseForData(project);
- if (manifestData != null) {
- String appName = String.format("%1$sTest", project.getName());
- String packageName = manifestData.getPackage();
- String minSdkVersion = manifestData.getMinSdkVersionString();
- 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;
- if (minSdkVersion != null) {
- mMinSdkVersionField.setText(minSdkVersion);
- }
- 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() {
- String minSdkVersion = mInfo.getMinSdkVersion();
-
- IAndroidTarget curr_target = mInfo.getSdkTarget();
- if (curr_target != null && curr_target.getVersion().equals(minSdkVersion)) {
- return;
- }
-
- for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
- if (target.getVersion().equals(minSdkVersion)) {
- mSdkTargetSelector.setSelection(target);
- return;
- }
- }
- }
-
- /**
- * 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(target.getVersion().getApiString());
- 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;
- }
-
- if (mInfo.getSdkTarget() != null &&
- mInfo.getSdkTarget().getVersion().equals(mInfo.getMinSdkVersion()) == false) {
- return setStatus("The API level for the selected SDK target does not match the Min SDK version.",
- mInfo.getSdkTarget().getVersion().isPreview() ? MSG_ERROR : MSG_WARNING);
- }
-
- return MSG_NONE;
- }
-
- /**
- * Validates the 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;
- }
-
- /**
- * Returns the working sets to which the new project should be added.
- *
- * @return the selected working sets to which the new project should be added
- */
- public IWorkingSet[] getWorkingSets() {
- return mWorkingSetGroup.getSelectedWorkingSets();
- }
-
- /**
- * Sets the working sets to which the new project should be added.
- *
- * @param workingSets the initial selected working sets
- */
- public void setWorkingSets(IWorkingSet[] workingSets) {
- assert workingSets != null;
- mWorkingSetGroup.setWorkingSets(workingSets);
- }
-
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java
index 62bf9c1..e0959f4 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2011 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.
@@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.ide.eclipse.adt.internal.wizards.newproject;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
/**
* A "New Test Android Project" Wizard.
* <p/>
- * This is really the {@link NewProjectWizard} that only displays the "test project" page.
+ * This is really the {@link NewProjectWizard} that only displays the "test project" pages.
*/
public class NewTestProjectWizard extends NewProjectWizard {
-
+ /**
+ * Creates a new wizard for creating an Android Test Project
+ */
public NewTestProjectWizard() {
- super(AvailablePages.TEST_PROJECT_ONLY);
+ super(Mode.TEST);
}
-}
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
new file mode 100644
index 0000000..e5018f1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import static com.android.ide.eclipse.adt.AdtUtils.capitalize;
+import static com.android.ide.eclipse.adt.AdtUtils.stripWhitespace;
+import static com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage.ACTIVITY_NAME_SUFFIX;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.xml.ManifestData;
+import com.android.sdklib.xml.ManifestData.Activity;
+
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.osgi.util.TextProcessor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.DirectoryDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkingSet;
+
+import java.io.File;
+import java.net.URI;
+import java.util.regex.Pattern;
+
+/**
+ * Initial page shown when creating projects which asks for the project name,
+ * the the location of the project, working sets, etc.
+ */
+class ProjectNamePage extends WizardPage implements SelectionListener, ModifyListener {
+ private final NewProjectWizardState mValues;
+ /** Flag used when setting button/text state manually to ignore listener updates */
+ private boolean mIgnore;
+ /**
+ * 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 and
+ * limiting it to shell-agnostic characters. It cannot start with a space.
+ */
+ private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$
+ /** Last user-browsed location, static so that it be remembered for the whole session */
+ private static String sCustomLocationOsPath = ""; //$NON-NLS-1$
+ private static boolean sAutoComputeCustomLocation = true;
+
+ private Text mProjectNameText;
+ private Text mLocationText;
+ private Button mCreateSampleRadioButton;
+ private Button mCreateExistingRadioButton;
+ private Button mCreateNewButton;
+ private Button mUseDefaultCheckBox;
+ private Button mBrowseButton;
+ private Label mLocationLabel;
+ private WorkingSetGroup mWorkingSetGroup;
+
+ /**
+ * Create the wizard.
+ * @param values current wizard state
+ */
+ ProjectNamePage(NewProjectWizardState values) {
+ super("projectNamePage"); //$NON-NLS-1$
+ mValues = values;
+
+ setTitle("Create Android Project");
+ setDescription("Select project name and type of project");
+ mWorkingSetGroup = new WorkingSetGroup();
+ setWorkingSets(new IWorkingSet[0]);
+ }
+
+ public void init(IStructuredSelection selection, IWorkbenchPart activePart) {
+ setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart));
+ }
+
+ /**
+ * Create contents of the wizard.
+ * @param parent the parent to add the page to
+ */
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayout(new GridLayout(3, false));
+
+ Label nameLabel = new Label(container, SWT.NONE);
+ nameLabel.setText("Project Name:");
+
+ mProjectNameText = new Text(container, SWT.BORDER);
+ mProjectNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+ mProjectNameText.addModifyListener(this);
+
+ mCreateNewButton = new Button(container, SWT.RADIO);
+ mCreateNewButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ mCreateNewButton.setText("Create new project in workspace");
+ mCreateNewButton.addSelectionListener(this);
+
+ mCreateExistingRadioButton = new Button(container, SWT.RADIO);
+ mCreateExistingRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
+ 3, 1));
+ mCreateExistingRadioButton.setText("Create project from existing source");
+ mCreateExistingRadioButton.addSelectionListener(this);
+
+ // TBD: Should we hide this completely, and make samples something you only invoke
+ // from the "New Sample Project" wizard?
+ if (mValues.mode != Mode.TEST) {
+ mCreateSampleRadioButton = new Button(container, SWT.RADIO);
+ mCreateSampleRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
+ 3, 1));
+ mCreateSampleRadioButton.setText("Create project from existing sample");
+ mCreateSampleRadioButton.addSelectionListener(this);
+ }
+
+ Label separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
+ separator.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1));
+
+ mUseDefaultCheckBox = new Button(container, SWT.CHECK);
+ mUseDefaultCheckBox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ mUseDefaultCheckBox.setText("Use default location");
+ mUseDefaultCheckBox.addSelectionListener(this);
+
+ mLocationLabel = new Label(container, SWT.NONE);
+ mLocationLabel.setText("Location:");
+
+ mLocationText = new Text(container, SWT.BORDER);
+ mLocationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mLocationText.addModifyListener(this);
+
+ mBrowseButton = new Button(container, SWT.NONE);
+ mBrowseButton.setText("Browse...");
+ mBrowseButton.addSelectionListener(this);
+
+ Composite group = mWorkingSetGroup.createControl(container);
+ group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1));
+
+ setControl(container);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ if (mValues.projectName != null) {
+ mProjectNameText.setText(mValues.projectName);
+ }
+ if (mValues.mode == Mode.ANY || mValues.mode == Mode.TEST) {
+ if (mValues.useExisting) {
+ mCreateExistingRadioButton.setSelection(true);
+ } else {
+ mCreateNewButton.setSelection(true);
+ }
+ } else if (mValues.mode == Mode.SAMPLE) {
+ mCreateSampleRadioButton.setSelection(true);
+ }
+ if (mValues.projectLocation != null) {
+ mLocationText.setText(mValues.projectLocation.getPath());
+ }
+ mUseDefaultCheckBox.setSelection(mValues.useDefaultLocation);
+ updateLocationState();
+ }
+
+ validatePage();
+ }
+
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+
+ if (source == mProjectNameText) {
+ onProjectFieldModified();
+ updateLocationPathField(null);
+ } else if (source == mLocationText && !mValues.useDefaultLocation) {
+ mValues.projectLocation = new File(mLocationText.getText().trim());
+ }
+
+ validatePage();
+ }
+
+ private void onProjectFieldModified() {
+ mValues.projectName = mProjectNameText.getText().trim();
+ mValues.projectNameModifiedByUser = true;
+
+ if (!mValues.applicationNameModifiedByUser) {
+ mValues.applicationName = capitalize(mValues.projectName);
+ if (!mValues.testApplicationNameModified) {
+ mValues.testApplicationName =
+ ApplicationInfoPage.suggestTestApplicationName(mValues.applicationName);
+ }
+ }
+ if (!mValues.activityNameModifiedByUser) {
+ String name = capitalize(mValues.projectName);
+ mValues.activityName = stripWhitespace(name) + ACTIVITY_NAME_SUFFIX;
+ }
+ if (!mValues.testProjectModified) {
+ mValues.testProjectName =
+ ApplicationInfoPage.suggestTestProjectName(mValues.projectName);
+ }
+ updateLocationPathField(null);
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+
+ if (source == mCreateNewButton && mCreateNewButton.getSelection()) {
+ mValues.useExisting = false;
+ mValues.mode = Mode.ANY;
+ updateLocationState();
+ } else if (source == mCreateExistingRadioButton
+ && mCreateExistingRadioButton.getSelection()) {
+ mValues.useExisting = true;
+ mValues.mode = Mode.ANY;
+ if (!mValues.activityNameModifiedByUser) {
+ mValues.createActivity = false;
+ }
+ updateLocationState();
+ } else if (source == mCreateSampleRadioButton && mCreateSampleRadioButton.getSelection()) {
+ mValues.useExisting = true;
+ mValues.useDefaultLocation = true;
+ if (!mUseDefaultCheckBox.getSelection()) {
+ try {
+ mIgnore = true;
+ mUseDefaultCheckBox.setSelection(true);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ mValues.mode = Mode.SAMPLE;
+ updateLocationState();
+ } else if (source == mUseDefaultCheckBox) {
+ mValues.useDefaultLocation = mUseDefaultCheckBox.getSelection();
+ updateLocationState();
+ } else if (source == mBrowseButton) {
+ onOpenDirectoryBrowser();
+ }
+
+ validatePage();
+ }
+
+ /**
+ * 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 updateLocationState() {
+ boolean isNewProject = !mValues.useExisting;
+ boolean isCreateFromSample = mValues.mode == Mode.SAMPLE;
+ boolean useDefault = mValues.useDefaultLocation && !isCreateFromSample;
+ boolean locationEnabled = (!isNewProject || !useDefault) && !isCreateFromSample;
+
+ mUseDefaultCheckBox.setEnabled(isNewProject);
+ mLocationLabel.setEnabled(locationEnabled);
+ mLocationText.setEnabled(locationEnabled);
+ mBrowseButton.setEnabled(locationEnabled);
+
+ updateLocationPathField(null);
+ }
+
+ /**
+ * Display a directory browser and update the location path field with the selected path
+ */
+ private void onOpenDirectoryBrowser() {
+
+ String existingDir = mLocationText.getText().trim();
+
+ // Disable the path if it doesn't exist
+ if (existingDir.length() == 0) {
+ existingDir = null;
+ } else {
+ File f = new File(existingDir);
+ if (!f.exists()) {
+ existingDir = null;
+ }
+ }
+
+ DirectoryDialog directoryDialog = new DirectoryDialog(mLocationText.getShell());
+ directoryDialog.setMessage("Browse for folder");
+ directoryDialog.setFilterPath(existingDir);
+ String dir = directoryDialog.open();
+
+ if (dir != null) {
+ updateLocationPathField(dir);
+ validatePage();
+ }
+ }
+
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ /**
+ * Returns the working sets to which the new project should be added.
+ *
+ * @return the selected working sets to which the new project should be added
+ */
+ public IWorkingSet[] getWorkingSets() {
+ return mWorkingSetGroup.getSelectedWorkingSets();
+ }
+
+ /**
+ * Sets the working sets to which the new project should be added.
+ *
+ * @param workingSets the initial selected working sets
+ */
+ public void setWorkingSets(IWorkingSet[] workingSets) {
+ assert workingSets != null;
+ mWorkingSetGroup.setWorkingSets(workingSets);
+ }
+
+ /**
+ * Updates the location directory path field.
+ * <br/>
+ * When custom user selection is enabled, use the absDir argument if not null and also
+ * save it internally. If absDir is null, restore the last saved absDir. 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 absDir.
+ *
+ * @param absDir A new absolute directory path or null to use the default.
+ */
+ private void updateLocationPathField(String absDir) {
+ boolean isNewProject = !mValues.useExisting || mValues.mode == Mode.SAMPLE;
+ boolean useDefault = mValues.useDefaultLocation;
+ boolean customLocation = !isNewProject || !useDefault;
+
+ if (!mIgnore) {
+ try {
+ mIgnore = true;
+ if (customLocation) {
+ if (absDir != null) {
+ // We get here if the user selected a directory with the "Browse" button.
+ // Disable auto-compute of the custom location unless the user selected
+ // the exact same path.
+ sAutoComputeCustomLocation = sAutoComputeCustomLocation &&
+ absDir.equals(sCustomLocationOsPath);
+ sCustomLocationOsPath = TextProcessor.process(absDir);
+ } else if (sAutoComputeCustomLocation ||
+ (!isNewProject && !new File(sCustomLocationOsPath).isDirectory())) {
+ // As a default import location, just suggest the home directory; the user
+ // needs to point to a project to import.
+ // TODO: Open file chooser automatically?
+ sCustomLocationOsPath = System.getProperty("user.home"); //$NON-NLS-1$
+ }
+ if (!mLocationText.getText().equals(sCustomLocationOsPath)) {
+ mLocationText.setText(sCustomLocationOsPath);
+ mValues.projectLocation = new File(sCustomLocationOsPath);
+ }
+ } else {
+ String value = Platform.getLocation().append(mValues.projectName).toString();
+ value = TextProcessor.process(value);
+ if (!mLocationText.getText().equals(value)) {
+ mLocationText.setText(value);
+ mValues.projectLocation = new File(value);
+ }
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ if (mValues.useExisting && mValues.projectLocation != null
+ && mValues.projectLocation.exists() && mValues.mode != Mode.SAMPLE) {
+ mValues.extractFromAndroidManifest(new Path(mValues.projectLocation.getPath()));
+ }
+ }
+
+ private void validatePage() {
+ IStatus status = null;
+
+ // Validate project name -- unless we're creating a sample, in which case
+ // the user will get a chance to pick the name on the Sample page
+ if (mValues.mode != Mode.SAMPLE) {
+ status = validateProjectName(mValues.projectName);
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validLocation = validateLocation();
+ if (validLocation != null) {
+ status = validLocation;
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ private IStatus validateLocation() {
+ if (mValues.mode == Mode.SAMPLE) {
+ // Samples are always created in the default directory
+ return null;
+ }
+
+ // Validate location
+ Path path = new Path(mValues.projectLocation.getPath());
+ if (!mValues.useExisting) {
+ if (!mValues.useDefaultLocation) {
+ // If not using the default value validate the location.
+ URI uri = URIUtil.toURI(path.toOSString());
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IProject handle = workspace.getRoot().getProject(mValues.projectName);
+ IStatus locationStatus = workspace.validateProjectLocationURI(handle, uri);
+ if (!locationStatus.isOK()) {
+ return locationStatus;
+ }
+ // 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 new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A directory name must be specified.");
+ } 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 != null && l.length != 0) {
+ return new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "The selected output directory is not empty.");
+ }
+ }
+ } else {
+ // Otherwise validate the path string is not empty
+ if (mValues.projectLocation.getPath().length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A directory name must be specified.");
+ }
+ File dest = path.toFile();
+ if (dest.exists()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "There is already a file or directory named \"%1$s\" in the selected location.",
+ mValues.projectName));
+ }
+ }
+ } else {
+ // Must be an existing directory
+ File f = path.toFile();
+ if (!f.isDirectory()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "An existing directory name must be specified.");
+ }
+
+ // Check there's an android manifest in the directory
+ String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
+ File manifestFile = new File(osPath);
+ if (!manifestFile.isFile()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "Choose a valid Android code directory\n" +
+ "(%1$s not found in %2$s.)",
+ SdkConstants.FN_ANDROID_MANIFEST_XML, f.getName()));
+ }
+
+ // Parse it and check the important fields.
+ ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
+ if (manifestData == null) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("File %1$s could not be parsed.", osPath));
+ }
+ String packageName = manifestData.getPackage();
+ if (packageName == null || packageName.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("No package name defined in %1$s.", osPath));
+ }
+ Activity[] activities = manifestData.getActivities();
+ if (activities == null || activities.length == 0) {
+ // This is acceptable now as long as no activity needs to be
+ // created
+ if (mValues.createActivity) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("No activity name defined in %1$s.", osPath));
+ }
+ }
+
+ // If there's already a .project, tell the user to use import instead.
+ if (path.append(".project").toFile().exists()) { //$NON-NLS-1$
+ return new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "An Eclipse project already exists in this directory.\n" +
+ "Consider using File > Import > Existing Project instead.");
+ }
+ }
+
+ return null;
+ }
+
+ static IStatus validateProjectName(String projectName) {
+ if (projectName == null || projectName.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Project name must be specified");
+ } else if (!sProjectNamePattern.matcher(projectName).matches()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.");
+ } else {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
+ if (!nameStatus.isOK()) {
+ return nameStatus;
+ } else {
+ IProject handle = workspace.getRoot().getProject(projectName);
+ if (handle.exists()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A project with that name already exists in the workspace");
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean canFlipToNextPage() {
+ // Sync working set data to the value object, since the WorkingSetGroup
+ // doesn't let us add listeners to do this lazily
+ mValues.workingSets = getWorkingSets();
+
+ return super.canFlipToNextPage();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java
new file mode 100644
index 0000000..c3acdad
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** Page where the user can select a sample to "instantiate" */
+class SampleSelectionPage extends WizardPage implements SelectionListener, ModifyListener {
+ private final NewProjectWizardState mValues;
+ private boolean mIgnore;
+
+ private Table mTable;
+ private TableViewer mTableViewer;
+ private IAndroidTarget mCurrentSamplesTarget;
+ private Text mSampleProjectName;
+
+ /**
+ * Create the wizard.
+ */
+ SampleSelectionPage(NewProjectWizardState values) {
+ super("samplePage"); //$NON-NLS-1$
+ setTitle("Select Sample");
+ setDescription("Select which sample to create");
+ mValues = values;
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayout(new GridLayout(2, false));
+
+ mTableViewer = new TableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
+ mTable = mTableViewer.getTable();
+ GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
+ gridData.heightHint = 300;
+ mTable.setLayoutData(gridData);
+ mTable.addSelectionListener(this);
+
+ setControl(container);
+
+ Label projectLabel = new Label(container, SWT.NONE);
+ projectLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ projectLabel.setText("Project Name:");
+
+ mSampleProjectName = new Text(container, SWT.BORDER);
+ mSampleProjectName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mSampleProjectName.addModifyListener(this);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ if (mValues.projectName != null) {
+ try {
+ mIgnore = true;
+ mSampleProjectName.setText(mValues.projectName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ // Update samples list if the SDK target has changed (or if it hasn't yet
+ // been populated)
+ if (mCurrentSamplesTarget != mValues.target) {
+ mCurrentSamplesTarget = mValues.target;
+ updateSamples();
+ }
+
+ validatePage();
+ }
+ }
+
+ private void updateSamples() {
+ IBaseLabelProvider labelProvider = new ColumnLabelProvider() {
+ @Override
+ public Image getImage(Object element) {
+ return AdtPlugin.getAndroidLogo();
+ }
+
+ @Override
+ public String getText(Object element) {
+ File file = (File) element;
+ String path = file.getPath();
+ int n = mValues.samplesDir.getPath().length();
+ if (path.length() > n) {
+ path = path.substring(n);
+ if (path.charAt(0) == File.separatorChar) {
+ path = path.substring(1);
+ }
+ if (path.endsWith(File.separator)) {
+ path = path.substring(0, path.length() - 1);
+ }
+ path = path.replaceAll(Pattern.quote(File.separator), " > ");
+ }
+
+ return path;
+ }
+ };
+
+ mTableViewer.setContentProvider(new ArrayContentProvider());
+ mTableViewer.setLabelProvider(labelProvider);
+
+ if (mValues.samples != null && mValues.samples.size() > 0) {
+ Object[] samples = mValues.samples.toArray();
+ mTableViewer.setInput(samples);
+
+ mTable.select(0);
+ selectSample(mValues.samples.get(0));
+ extractNamesFromAndroidManifest();
+ }
+ }
+
+ private void selectSample(File sample) {
+ mValues.chosenSample = sample;
+ if (sample != null && !mValues.projectNameModifiedByUser) {
+ mValues.projectName = sample.getName();
+ try {
+ mIgnore = true;
+ mSampleProjectName.setText(mValues.projectName);
+ } finally {
+ mIgnore = false;
+ }
+ updatedProjectName();
+ }
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ if (e.getSource() == mTable) {
+ extractNamesFromAndroidManifest();
+ int index = mTable.getSelectionIndex();
+ if (index >= 0) {
+ Object[] roots = (Object[]) mTableViewer.getInput();
+ selectSample((File) roots[index]);
+ } else {
+ selectSample(null);
+ }
+ } else {
+ assert false : e.getSource();
+ }
+
+ validatePage();
+ }
+
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ if (e.getSource() == mSampleProjectName) {
+ mValues.projectName = mSampleProjectName.getText().trim();
+ mValues.projectNameModifiedByUser = true;
+ updatedProjectName();
+ }
+
+ validatePage();
+ }
+
+ private void updatedProjectName() {
+ if (mValues.useDefaultLocation) {
+ mValues.projectLocation = Platform.getLocation().toFile();
+ }
+ }
+
+ /**
+ * A sample was selected. Update the location field, manifest and validate.
+ * Extract names from an android manifest.
+ * This is done only if the user selected the "use existing source" and a manifest xml file
+ * can actually be found in the custom user directory.
+ */
+ private void extractNamesFromAndroidManifest() {
+ if (mValues.chosenSample == null || !mValues.chosenSample.isDirectory()) {
+ return;
+ }
+
+ Path path = new Path(mValues.chosenSample.getPath());
+ mValues.extractFromAndroidManifest(path);
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ if (mValues.mode != Mode.SAMPLE) {
+ return true;
+ }
+
+ // Ensure that when creating samples, the Finish button isn't enabled until
+ // the user has reached and completed this page
+ if (mValues.chosenSample == null) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ private void validatePage() {
+ IStatus status = null;
+ if (mValues.samples == null || mValues.samples.size() == 0) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "The chosen SDK does not contain any samples");
+ } else if (mValues.chosenSample == null) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Choose a sample");
+ } else if (!mValues.chosenSample.exists()) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("Sample does not exist: %1$s", mValues.chosenSample.getPath()));
+ } else {
+ status = ProjectNamePage.validateProjectName(mValues.projectName);
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java
new file mode 100644
index 0000000..a43306a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+import com.android.sdkuilib.internal.widgets.SdkTargetSelector;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/** A page in the New Project wizard where you select the target SDK */
+class SdkSelectionPage extends WizardPage implements ITargetChangeListener {
+ private final NewProjectWizardState mValues;
+ private boolean mIgnore;
+ private SdkTargetSelector mSdkTargetSelector;
+
+ /**
+ * Create the wizard.
+ */
+ SdkSelectionPage(NewProjectWizardState values) {
+ super("sdkSelection"); //$NON-NLS-1$
+ mValues = values;
+
+ setTitle("Select Build Target");
+ AdtPlugin.getDefault().addTargetListener(this);
+ }
+
+ @Override
+ public void dispose() {
+ AdtPlugin.getDefault().removeTargetListener(this);
+ super.dispose();
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ public void createControl(Composite parent) {
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ // Layout has 1 column
+ group.setLayout(new GridLayout());
+ group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setFont(parent.getFont());
+ group.setText("Build Target");
+
+ // The selector is created without targets. They are added below in the change listener.
+ mSdkTargetSelector = new SdkTargetSelector(group, null);
+
+ mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ mValues.target = mSdkTargetSelector.getSelected();
+ mValues.targetModifiedByUser = true;
+ onSdkTargetModified();
+ validatePage();
+ }
+ });
+
+ onSdkLoaded();
+
+ setControl(group);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (mValues.mode == Mode.SAMPLE) {
+ setDescription("Choose an SDK to select a sample from");
+ } else {
+ setDescription("Choose an SDK to target");
+ }
+ try {
+ mIgnore = true;
+ if (mValues.target != null) {
+ mSdkTargetSelector.setSelection(mValues.target);
+ }
+ } finally {
+ mIgnore = false;
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ // Ensure that the Finish button isn't enabled until
+ // the user has reached and completed this page
+ if (mValues.target == null) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ /**
+ * 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 (mIgnore) {
+ return;
+ }
+
+ IAndroidTarget target = mValues.target;
+
+ // Update the minimum SDK text field?
+ // We do if one of two conditions are met:
+ if (target != null) {
+ boolean setMinSdk = false;
+ AndroidVersion version = target.getVersion();
+ int apiLevel = version.getApiLevel();
+ // 1. Has the user not manually edited the SDK field yet? If so, keep
+ // updating it to the selected value.
+ if (!mValues.minSdkModifiedByUser) {
+ setMinSdk = true;
+ } else {
+ // 2. Is the API level set to a higher level than the newly selected
+ // target SDK? If so, change it down to the new lower value.
+ String s = mValues.minSdk;
+ if (s.length() > 0) {
+ try {
+ int currentApi = Integer.parseInt(s);
+ if (currentApi > apiLevel) {
+ setMinSdk = true;
+ }
+ } catch (NumberFormatException nfe) {
+ // User may have typed something invalid -- ignore
+ }
+ }
+ }
+ if (setMinSdk) {
+ String minSdk;
+ if (version.isPreview()) {
+ minSdk = version.getCodename();
+ } else {
+ minSdk = Integer.toString(apiLevel);
+ }
+ mValues.minSdk = minSdk;
+ }
+ }
+
+ loadSamplesForTarget(target);
+ }
+
+ /**
+ * Updates the list of all samples for the given target SDK.
+ * The list is stored in mSamplesPaths as absolute directory paths.
+ * The combo is recreated to match this.
+ */
+ private void loadSamplesForTarget(IAndroidTarget target) {
+ // Keep the name of the old selection (if there were any samples)
+ File previouslyChosenSample = mValues.chosenSample;
+
+ mValues.samples.clear();
+ mValues.chosenSample = null;
+
+ if (target != null) {
+ // Get the sample root path and recompute the list of samples
+ String samplesRootPath = target.getPath(IAndroidTarget.SAMPLES);
+
+ mValues.samplesDir = new File(samplesRootPath);
+ findSamplesManifests(mValues.samplesDir, mValues.samples);
+
+ if (mValues.samples.size() == 0) {
+ return;
+ } else {
+ Collections.sort(mValues.samples);
+ }
+
+ // Recompute the description of each sample (the relative path
+ // to the sample root). Also try to find the old selection.
+ if (previouslyChosenSample != null) {
+ String previouslyChosenName = previouslyChosenSample.getName();
+ for (int i = 0, n = mValues.samples.size(); i < n; i++) {
+ File file = mValues.samples.get(i);
+ if (file.getName().equals(previouslyChosenName)) {
+ mValues.chosenSample = file;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Recursively find potential sample directories under the given directory.
+ * Actually lists any directory that contains an android manifest.
+ * Paths found are added the samplesPaths list.
+ */
+ private void findSamplesManifests(File samplesDir, List<File> samplesPaths) {
+ if (!samplesDir.isDirectory()) {
+ return;
+ }
+
+ for (File f : samplesDir.listFiles()) {
+ if (f.isDirectory()) {
+ // Assume this is a sample if it contains an android manifest.
+ File manifestFile = new File(f, SdkConstants.FN_ANDROID_MANIFEST_XML);
+ if (manifestFile.isFile()) {
+ samplesPaths.add(f);
+ }
+
+ // Recurse in the project, to find embedded tests sub-projects
+ // We can however skip this recursion for known android sub-dirs that
+ // can't have projects, namely for sources, assets and resources.
+ String leaf = f.getName();
+ if (!SdkConstants.FD_SOURCES.equals(leaf) &&
+ !SdkConstants.FD_ASSETS.equals(leaf) &&
+ !SdkConstants.FD_RES.equals(leaf)) {
+ findSamplesManifests(f, samplesPaths);
+ }
+ }
+ }
+ }
+
+ private void validatePage() {
+ String error = null;
+
+ if (mValues.target == null) {
+ error = "An SDK Target must be specified.";
+ }
+
+ if (mValues.mode == Mode.SAMPLE) {
+ // Make sure this SDK target contains samples
+ if (mValues.samples == null || mValues.samples.size() == 0) {
+ error = "This target has no samples. Please select another target.";
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ // ---- Implements ITargetChangeListener ----
+ public void onSdkLoaded() {
+ if (mSdkTargetSelector == null) {
+ return;
+ }
+
+ // Update the sdk target selector with the new targets
+
+ // get the targets from the sdk
+ IAndroidTarget[] targets = null;
+ if (Sdk.getCurrent() != null) {
+ targets = Sdk.getCurrent().getTargets();
+ }
+ mSdkTargetSelector.setTargets(targets);
+
+ // If there's only one target, select it.
+ // This will invoke the selection listener on the selector defined above.
+ if (targets != null && targets.length == 1) {
+ mValues.target = targets[0];
+ mSdkTargetSelector.setSelection(mValues.target);
+ onSdkTargetModified();
+ } else if (targets != null) {
+ // Pick the highest available platform by default (see issue #17505
+ // for related discussion.)
+ IAndroidTarget initialTarget = null;
+ for (IAndroidTarget target : targets) {
+ if (target.isPlatform()
+ && !target.getVersion().isPreview()
+ && (initialTarget == null ||
+ target.getVersion().getApiLevel() >
+ initialTarget.getVersion().getApiLevel())) {
+ initialTarget = target;
+ }
+ }
+ if (initialTarget != null) {
+ mValues.target = initialTarget;
+ try {
+ mIgnore = true;
+ mSdkTargetSelector.setSelection(mValues.target);
+ } finally {
+ mIgnore = false;
+ }
+ onSdkTargetModified();
+ }
+ }
+ }
+
+ public void onProjectTargetChange(IProject changedProject) {
+ // Ignore
+ }
+
+ public void onTargetLoaded(IAndroidTarget target) {
+ // Ignore
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java
new file mode 100644
index 0000000..112a4df
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.xml.ManifestData;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.ui.JavaElementLabelProvider;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.ui.dialogs.FilteredList;
+
+/**
+ * Page shown when creating a test project which lets you choose between testing
+ * yourself and testing a different project
+ */
+class TestTargetPage extends WizardPage implements SelectionListener {
+ private final NewProjectWizardState mValues;
+ /** Flag used when setting button/text state manually to ignore listener updates */
+ private boolean mIgnore;
+ private String mLastExistingPackageName;
+
+ private Button mCurrentRadioButton;
+ private Button mExistingRadioButton;
+ private FilteredList mProjectList;
+
+ /**
+ * Create the wizard.
+ */
+ TestTargetPage(NewProjectWizardState values) {
+ super("testTargetPage"); //$NON-NLS-1$
+ setTitle("Select TestTarget");
+ setDescription("Choose a project to test");
+ mValues = values;
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+
+ setControl(container);
+ container.setLayout(new GridLayout(2, false));
+
+ mCurrentRadioButton = new Button(container, SWT.RADIO);
+ mCurrentRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mCurrentRadioButton.setText("This project");
+ mCurrentRadioButton.addSelectionListener(this);
+
+ mExistingRadioButton = new Button(container, SWT.RADIO);
+ mExistingRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mExistingRadioButton.setText("An existing Android project:");
+ mExistingRadioButton.addSelectionListener(this);
+
+ ILabelProvider labelProvider = new JavaElementLabelProvider(
+ JavaElementLabelProvider.SHOW_DEFAULT);
+ mProjectList = new FilteredList(container,
+ SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE, labelProvider,
+ true /*ignoreCase*/, false /*allowDuplicates*/, true /* matchEmptyString*/);
+ mProjectList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+ mProjectList.addSelectionListener(this);
+ }
+
+ private void initializeList() {
+ ProjectChooserHelper helper = new ProjectChooserHelper(getShell(), null /*filter*/);
+ IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+ IJavaModel javaModel = JavaCore.create(workspaceRoot);
+ IJavaProject[] androidProjects = helper.getAndroidProjects(javaModel);
+ mProjectList.setElements(androidProjects);
+ if (mValues.testedProject != null) {
+ for (IJavaProject project : androidProjects) {
+ if (project.getProject() == mValues.testedProject) {
+ mProjectList.setSelection(new Object[] { project });
+ break;
+ }
+ }
+ } else {
+ // No initial selection: force the user to choose
+ mProjectList.setSelection(new int[0]);
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ try {
+ mIgnore = true;
+ mCurrentRadioButton.setSelection(mValues.testingSelf);
+ mExistingRadioButton.setSelection(!mValues.testingSelf);
+ mProjectList.setEnabled(!mValues.testingSelf);
+
+ if (mProjectList.isEmpty()) {
+ initializeList();
+ }
+ if (!mValues.testingSelf) {
+ mProjectList.setFocus();
+ IProject project = getSelectedProject();
+ if (project != null) {
+ // The FilteredList seems to -insist- on selecting the first item
+ // in the list, even when the selection is explicitly set to an empty
+ // array. This means the user is looking at a selection, so we need
+ // to also go ahead and select this item in the model such that the
+ // two agree, even if we would have preferred to have no initial
+ // selection.
+ mValues.testedProject = project;
+ }
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ validatePage();
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mExistingRadioButton) {
+ mProjectList.setEnabled(true);
+ mValues.testingSelf = false;
+ setExistingProject(getSelectedProject());
+ mProjectList.setFocus();
+ } else if (source == mCurrentRadioButton) {
+ mProjectList.setEnabled(false);
+ mValues.testingSelf = true;
+ mValues.testedProject = null;
+ } else {
+ // The event must be from the project list, which unfortunately doesn't
+ // pass itself as the selection event, it passes a reference to some internal
+ // table widget that it uses, so we check for this case last
+ IProject project = getSelectedProject();
+ if (project != mValues.testedProject) {
+ setExistingProject(project);
+ }
+ }
+
+ validatePage();
+ }
+
+ private IProject getSelectedProject() {
+ Object[] selection = mProjectList.getSelection();
+ IProject project = selection != null && selection.length == 1
+ ? ((IJavaProject) selection[0]).getProject() : null;
+ return project;
+ }
+
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ private void setExistingProject(IProject project) {
+ mValues.testedProject = project;
+
+ // Try to update the application, package, sdk target and minSdkVersion accordingly
+ if (project != null &&
+ (!mValues.applicationNameModifiedByUser ||
+ !mValues.packageNameModifiedByUser ||
+ !mValues.targetModifiedByUser ||
+ !mValues.minSdkModifiedByUser)) {
+ ManifestData manifestData = AndroidManifestHelper.parseForData(project);
+ if (manifestData != null) {
+ String appName = String.format("%1$sTest", project.getName());
+ String packageName = manifestData.getPackage();
+ String minSdkVersion = manifestData.getMinSdkVersionString();
+ IAndroidTarget sdkTarget = null;
+ if (Sdk.getCurrent() != null) {
+ sdkTarget = Sdk.getCurrent().getTarget(project);
+ }
+
+ if (packageName == null) {
+ packageName = ""; //$NON-NLS-1$
+ }
+ mLastExistingPackageName = packageName;
+
+ if (!mValues.projectNameModifiedByUser) {
+ mValues.projectName = appName;
+ }
+
+ if (!mValues.applicationNameModifiedByUser) {
+ mValues.applicationName = appName;
+ }
+
+ if (!mValues.packageNameModifiedByUser) {
+ packageName += ".test"; //$NON-NLS-1$
+ mValues.packageName = packageName;
+ }
+
+ if (!mValues.targetModifiedByUser && sdkTarget != null) {
+ mValues.target = sdkTarget;
+ }
+
+ if (!mValues.minSdkModifiedByUser) {
+ if (minSdkVersion != null || sdkTarget != null) {
+ mValues.minSdk = minSdkVersion;
+ }
+ if (sdkTarget == null) {
+ mValues.updateSdkTargetToMatchMinSdkVersion();
+ }
+ }
+ }
+ }
+
+ updateTestTargetPackageField(mLastExistingPackageName);
+ }
+
+ /**
+ * Updates the test target package name
+ *
+ * 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 (mValues.testingSelf) {
+ mValues.testTargetPackageName = mValues.packageName;
+ } else if (packageName != null) {
+ mValues.testTargetPackageName = packageName;
+ }
+ }
+
+ private void validatePage() {
+ String error = null;
+
+ if (!mValues.testingSelf) {
+ if (mValues.testedProject == null) {
+ error = "Please select an existing Android project as a test target.";
+ } else if (mValues.projectName.equals(mValues.testedProject.getName())) {
+ error = "The main project name and the test project name must be different.";
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
index efa03c8..a33a2f6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
@@ -27,9 +27,9 @@ import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewEleme
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizard;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
import com.android.ide.eclipse.tests.SdkTestCase;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
@@ -39,17 +39,12 @@ import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.source.ISourceViewer;
-import org.eclipse.jface.wizard.IWizardContainer;
-import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.ui.IWorkingSet;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
@@ -228,16 +223,24 @@ public class AdtProjectTest extends SdkTestCase {
}
assertNotNull(target);
- final StubProjectWizard newProjCreator = new StubProjectWizard(
- name, target);
- newProjCreator.init(null, null);
- // need to run finish on ui thread since it invokes a perspective switch
- Display.getDefault().syncExec(new Runnable() {
- public void run() {
- newProjCreator.performFinish();
- }
- });
+ IRunnableContext context = new IRunnableContext() {
+ public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
+ throws InvocationTargetException, InterruptedException {
+ runnable.run(new NullProgressMonitor());
+ }
+ };
+ NewProjectWizardState state = new NewProjectWizardState(Mode.ANY);
+ state.projectName = name;
+ state.target = target;
+ state.packageName = TEST_PROJECT_PACKAGE;
+ state.activityName = name;
+ state.applicationName = name;
+ state.createActivity = false;
+ state.useDefaultLocation = true;
+
+ NewProjectCreator creator = new NewProjectCreator(state, context);
+ creator.createAndroidProjects();
return validateProjectExists(name);
}
@@ -654,157 +657,6 @@ public class AdtProjectTest extends SdkTestCase {
}
}
- /**
- * Stub class for project creation wizard.
- * <p/>
- * Created so project creation logic can be run without UI creation/manipulation.
- */
- public class StubProjectWizard extends NewProjectWizard {
-
- private final String mProjectName;
- private final IAndroidTarget mTarget;
-
- public StubProjectWizard(String projectName, IAndroidTarget target) {
- this.mProjectName = projectName;
- this.mTarget = target;
- }
-
- /**
- * Override parent to return stub page
- */
- @Override
- protected NewProjectCreationPage createMainPage() {
- return new StubProjectCreationPage(mProjectName, mTarget);
- }
-
- /**
- * Override parent to return null page
- */
- @Override
- protected NewTestProjectCreationPage createTestPage() {
- return null;
- }
-
- /**
- * Overrides parent to return dummy wizard container
- */
- @Override
- public IWizardContainer getContainer() {
- return new IWizardContainer() {
-
- public IWizardPage getCurrentPage() {
- return null;
- }
-
- public Shell getShell() {
- return null;
- }
-
- public void showPage(IWizardPage page) {
- // pass
- }
-
- public void updateButtons() {
- // pass
- }
-
- public void updateMessage() {
- // pass
- }
-
- public void updateTitleBar() {
- // pass
- }
-
- public void updateWindowTitle() {
- // pass
- }
-
- /**
- * Executes runnable on current thread
- */
- public void run(boolean fork, boolean cancelable,
- IRunnableWithProgress runnable)
- throws InvocationTargetException, InterruptedException {
- runnable.run(new NullProgressMonitor());
- }
-
- };
- }
- }
-
- /**
- * Stub class for project creation page.
- * <p/>
- * Returns canned responses for creating a sample project.
- */
- public class StubProjectCreationPage extends NewProjectCreationPage {
-
- private final String mProjectName;
- private final IAndroidTarget mTarget;
-
- public StubProjectCreationPage(String projectName, IAndroidTarget target) {
- super();
- this.mProjectName = projectName;
- this.mTarget = target;
- setTestInfo(null);
- }
-
- @Override
- public IMainInfo getMainInfo() {
- return new IMainInfo() {
- public String getProjectName() {
- return mProjectName;
- }
-
- public String getPackageName() {
- return TEST_PROJECT_PACKAGE;
- }
-
- public String getActivityName() {
- return mProjectName;
- }
-
- public String getApplicationName() {
- return mProjectName;
- }
-
- public boolean isNewProject() {
- return true;
- }
-
- public String getSourceFolder() {
- return "src";
- }
-
- public IPath getLocationPath() {
- // Default location
- return null;//new Path(mLocation);
- }
-
- public String getMinSdkVersion() {
- return null;
- }
-
- public IAndroidTarget getSdkTarget() {
- return mTarget;
- }
-
- public boolean isCreateActivity() {
- return false;
- }
-
- public boolean useDefaultLocation() {
- return true;
- }
-
- public IWorkingSet[] getSelectedWorkingSets() {
- return new IWorkingSet[0];
- }
- };
- }
- }
-
public void testDummy() {
// This class contains shared test functionality for testcase subclasses,
// but without an actual test in the class JUnit complains (even if we make
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java
deleted file mode 100644
index f524d7f..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java
+++ /dev/null
@@ -1,97 +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.newproject;
-
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage;
-import com.android.sdklib.IAndroidTarget;
-
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.ui.IWorkingSet;
-
-/**
- * Stub class for project creation page.
- * <p/>
- * Returns canned responses for creating a sample project.
- */
-public class StubProjectCreationPage extends NewProjectCreationPage {
-
- private final String mProjectName;
- private final String mLocation;
- private final IAndroidTarget mTarget;
-
- public StubProjectCreationPage(String projectName, String projectLocation, IAndroidTarget target) {
- super();
- this.mProjectName = projectName;
- this.mLocation = projectLocation;
- this.mTarget = target;
- // don't set test project info
- setTestInfo(null);
- }
-
- @Override
- public IMainInfo getMainInfo() {
- return new IMainInfo() {
- public String getProjectName() {
- return mProjectName;
- }
-
- public String getPackageName() {
- return "com.android.samples";
- }
-
- public String getActivityName() {
- return mProjectName;
- }
-
- public String getApplicationName() {
- return mProjectName;
- }
-
- public boolean isNewProject() {
- return false;
- }
-
- public String getSourceFolder() {
- return "src";
- }
-
- public IPath getLocationPath() {
- return new Path(mLocation);
- }
-
- public String getMinSdkVersion() {
- return null;
- }
-
- public IAndroidTarget getSdkTarget() {
- return mTarget;
- }
-
- public boolean isCreateActivity() {
- return false;
- }
-
- public boolean useDefaultLocation() {
- return false;
- }
-
- public IWorkingSet[] getSelectedWorkingSets() {
- return new IWorkingSet[0];
- }
- };
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectWizard.java
deleted file mode 100644
index 18a29cc..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectWizard.java
+++ /dev/null
@@ -1,117 +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.newproject;
-
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizard;
-import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage;
-import com.android.sdklib.IAndroidTarget;
-
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.jface.wizard.IWizardContainer;
-import org.eclipse.jface.wizard.IWizardPage;
-import org.eclipse.swt.widgets.Shell;
-
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * Stub class for project creation wizard.
- * <p/>
- * Created so project creation logic can be run without UI creation/manipulation.
- */
-public class StubProjectWizard extends NewProjectWizard {
-
- private final String mProjectName;
- private final String mProjectLocation;
- private final IAndroidTarget mTarget;
-
- /**
- * Constructor
- *
- * @param projectName
- * @param projectLocation
- * @parama target
- */
- public StubProjectWizard(String projectName, String projectLocation, IAndroidTarget target) {
- this.mProjectName = projectName;
- this.mProjectLocation = projectLocation;
- this.mTarget = target;
- }
-
- /**
- * Override parent to return stub page
- */
- @Override
- protected NewProjectCreationPage createMainPage() {
- return new StubProjectCreationPage(mProjectName, mProjectLocation, mTarget);
- }
-
- /**
- * Override parent to return null page
- */
- @Override
- protected NewTestProjectCreationPage createTestPage() {
- return null;
- }
-
- /**
- * Overrides parent to return dummy wizard container
- */
- @Override
- public IWizardContainer getContainer() {
- return new IWizardContainer() {
-
- public IWizardPage getCurrentPage() {
- return null;
- }
-
- public Shell getShell() {
- return null;
- }
-
- public void showPage(IWizardPage page) {
- // pass
- }
-
- public void updateButtons() {
- // pass
- }
-
- public void updateMessage() {
- // pass
- }
-
- public void updateTitleBar() {
- // pass
- }
-
- public void updateWindowTitle() {
- // pass
- }
-
- /**
- * Executes runnable on current thread
- */
- public void run(boolean fork, boolean cancelable,
- IRunnableWithProgress runnable)
- throws InvocationTargetException, InterruptedException {
- runnable.run(new NullProgressMonitor());
- }
-
- };
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java
index 3deb3b2..675eca8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java
@@ -15,7 +15,10 @@
*/
package com.android.ide.eclipse.tests.functests.sampleProjects;
-import com.android.ide.eclipse.adt.wizards.newproject.StubProjectWizard;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
import com.android.ide.eclipse.tests.SdkTestCase;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
@@ -30,9 +33,12 @@ import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jface.operation.IRunnableContext;
+import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Display;
import java.io.File;
+import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -93,16 +99,24 @@ public class SampleProjectTest extends SdkTestCase {
prepareProject(path, target);
- final StubProjectWizard newProjCreator = new StubProjectWizard(
- name, path, target);
- newProjCreator.init(null, null);
- // need to run finish on ui thread since it invokes a perspective switch
- Display.getDefault().syncExec(new Runnable() {
- public void run() {
- newProjCreator.performFinish();
+ IRunnableContext context = new IRunnableContext() {
+ public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
+ throws InvocationTargetException, InterruptedException {
+ runnable.run(new NullProgressMonitor());
}
- });
+ };
+ NewProjectWizardState state = new NewProjectWizardState(Mode.SAMPLE);
+ state.projectName = name;
+ state.target = target;
+ state.packageName = "com.android.samples";
+ state.activityName = name;
+ state.applicationName = name;
+ state.chosenSample = new File(path);
+ state.useDefaultLocation = false;
+ state.createActivity = false;
+ NewProjectCreator creator = new NewProjectCreator(state, context);
+ creator.createAndroidProjects();
iproject = validateProjectExists(name);
validateNoProblems(iproject);
}
@@ -164,6 +178,7 @@ public class SampleProjectTest extends SdkTestCase {
}
}
}
+ failureBuilder.append("Project location: " + AdtUtils.getAbsolutePath(iproject));
assertFalse(failureBuilder.toString(), hasErrors);
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
index c46600d..13c8f6a 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
@@ -20,7 +20,6 @@ import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.io.StreamException;
import com.android.sdklib.SdkConstants;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -198,8 +197,11 @@ public class ProjectPropertiesWorkingCopy extends ProjectProperties {
} else {
// new file, just write it all
- // write the header
- writer.write(mType.getHeader());
+
+ // write the header (can be null, for example for PropertyType.LEGACY_BUILD)
+ if (mType.getHeader() != null) {
+ writer.write(mType.getHeader());
+ }
// write the properties.
for (Entry<String, String> entry : mProperties.entrySet()) {