diff options
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()) { |