ADT: Create project from existing sample in New Project Wizard

The UI workflow:
- user selects the new "Create project from existingsample" radio button
- user selects an SDK target in the NPW
- a popup is filled with all available samples

This also fixes 2 edge cases:
- when the selected sample contains a build|default.propertie that is
  a base of the currently selected SDK target, don't reset it.
- when the user manually changes the project name or the app name for
  the sample, simply empty the fields and the next selected sample
  will recompute the adequate names. Any non-empty user input is never
  overridden.

SDK BUG 2121819

Change-Id: I69afafa460b86942046ef6baca3d4c7ee7dbe97d
This commit is contained in:
Raphael
2009-10-02 19:41:49 -07:00
parent 480332f6c5
commit badc593c5e
2 changed files with 220 additions and 28 deletions

View File

@@ -1,3 +1,6 @@
0.9.4:
- New "Create project from sample" choice in the New Project Wizard.
0.9.3: 0.9.3:
- New wizard to create Android JUnit Test Projects. - New wizard to create Android JUnit Test Projects.
- New AVD wizard. - New AVD wizard.

View File

@@ -54,6 +54,7 @@ import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Event;
@@ -65,6 +66,7 @@ import org.eclipse.swt.widgets.Text;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@@ -90,9 +92,12 @@ public class NewProjectCreationPage extends WizardPage {
/** Initial value for all name fields (project, activity, application, package). Used /** Initial value for all name fields (project, activity, application, package). Used
* whenever a value is requested before controls are created. */ * whenever a value is requested before controls are created. */
private static final String INITIAL_NAME = ""; //$NON-NLS-1$ private static final String INITIAL_NAME = ""; //$NON-NLS-1$
/** Initial value for the Create New Project radio; False means Create From Existing would be /** Initial value for the Create New Project radio. */
* the default.*/
private static final boolean INITIAL_CREATE_NEW_PROJECT = true; 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. */ /** Initial value for the Use Default Location check box. */
private static final boolean INITIAL_USE_DEFAULT_LOCATION = true; private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
/** Initial value for the Create Activity check box. */ /** Initial value for the Create Activity check box. */
@@ -127,6 +132,7 @@ public class NewProjectCreationPage extends WizardPage {
private Text mActivityNameField; private Text mActivityNameField;
private Text mApplicationNameField; private Text mApplicationNameField;
private Button mCreateNewProjectRadio; private Button mCreateNewProjectRadio;
private Button mCreateFromSampleRadio;
private Button mUseDefaultLocation; private Button mUseDefaultLocation;
private Label mLocationLabel; private Label mLocationLabel;
private Text mLocationPathField; private Text mLocationPathField;
@@ -145,6 +151,10 @@ public class NewProjectCreationPage extends WizardPage {
private boolean mApplicationNameModifiedByUser; private boolean mApplicationNameModifiedByUser;
private boolean mInternalMinSdkVersionUpdate; private boolean mInternalMinSdkVersionUpdate;
private final ArrayList<String> mSamplesPaths = new ArrayList<String>();
private Combo mSamplesCombo;
/** /**
* Creates a new project creation wizard page. * Creates a new project creation wizard page.
@@ -249,6 +259,12 @@ public class NewProjectCreationPage extends WizardPage {
: mCreateNewProjectRadio.getSelection(); : 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. */ /** Returns the value of the "Create Activity" checkbox. */
public boolean isCreateActivity() { public boolean isCreateActivity() {
return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY
@@ -330,6 +346,7 @@ public class NewProjectCreationPage extends WizardPage {
// Update state the first time // Update state the first time
enableLocationWidgets(); enableLocationWidgets();
loadSamplesForTarget(null /*target*/);
// Show description the first time // Show description the first time
setErrorMessage(null); setErrorMessage(null);
@@ -407,9 +424,10 @@ public class NewProjectCreationPage extends WizardPage {
mCreateNewProjectRadio = new Button(group, SWT.RADIO); mCreateNewProjectRadio = new Button(group, SWT.RADIO);
mCreateNewProjectRadio.setText("Create new project in workspace"); mCreateNewProjectRadio.setText("Create new project in workspace");
mCreateNewProjectRadio.setSelection(INITIAL_CREATE_NEW_PROJECT); mCreateNewProjectRadio.setSelection(INITIAL_CREATE_NEW_PROJECT);
Button existing_project_radio = new Button(group, SWT.RADIO); Button existing_project_radio = new Button(group, SWT.RADIO);
existing_project_radio.setText("Create project from existing source"); existing_project_radio.setText("Create project from existing source");
existing_project_radio.setSelection(!INITIAL_CREATE_NEW_PROJECT); existing_project_radio.setSelection(INITIAL_CREATE_FROM_SOURCE);
mUseDefaultLocation = new Button(group, SWT.CHECK); mUseDefaultLocation = new Button(group, SWT.CHECK);
mUseDefaultLocation.setText("Use default location"); mUseDefaultLocation.setText("Use default location");
@@ -462,6 +480,33 @@ public class NewProjectCreationPage extends WizardPage {
onOpenDirectoryBrowser(); onOpenDirectoryBrowser();
} }
}); });
mCreateFromSampleRadio = new Button(group, SWT.RADIO);
mCreateFromSampleRadio.setText("Create project from existing sample");
mCreateFromSampleRadio.setSelection(INITIAL_CREATE_FROM_SAMPLE);
mCreateFromSampleRadio.addSelectionListener(location_listener);
Composite samples_group = new Composite(group, SWT.NONE);
samples_group.setLayout(new GridLayout(2, /* num columns */
false /* columns of not equal size */));
samples_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
samples_group.setFont(parent.getFont());
new Label(samples_group, SWT.NONE).setText("Samples:");
mSamplesCombo = new Combo(samples_group, SWT.DROP_DOWN | SWT.READ_ONLY);
mSamplesCombo.setEnabled(false);
mSamplesCombo.select(0);
mSamplesCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSamplesCombo.setToolTipText("Select a sample");
mSamplesCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onSampleSelected();
}
});
} }
/** /**
@@ -625,9 +670,22 @@ public class NewProjectCreationPage extends WizardPage {
return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$ return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$
} }
/** Returns the current project location, depending on the Use Default Location check box. */ /** 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() { private String getProjectLocation() {
if (mInfo.isNewProject() && mInfo.useDefaultLocation()) { if (mInfo.isCreateFromSample()) {
return getSelectedSamplePath();
} else if (mInfo.isNewProject() && mInfo.useDefaultLocation()) {
return Platform.getLocation().toString(); return Platform.getLocation().toString();
} else { } else {
return getLocationPathFieldValue(); return getLocationPathFieldValue();
@@ -680,6 +738,16 @@ public class NewProjectCreationPage extends WizardPage {
} }
} }
/**
* A sample was selected. Update the location field, manifest and validate.
*/
private void onSampleSelected() {
// 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: * 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) * the location path is enabled when using the "existing source" mode (i.e. not new project)
@@ -687,8 +755,9 @@ public class NewProjectCreationPage extends WizardPage {
*/ */
private void enableLocationWidgets() { private void enableLocationWidgets() {
boolean is_new_project = mInfo.isNewProject(); boolean is_new_project = mInfo.isNewProject();
boolean use_default = mInfo.useDefaultLocation(); boolean is_create_from_sample = mInfo.isCreateFromSample();
boolean location_enabled = !is_new_project || !use_default; 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(); boolean create_activity = mInfo.isCreateActivity();
mUseDefaultLocation.setEnabled(is_new_project); mUseDefaultLocation.setEnabled(is_new_project);
@@ -697,6 +766,8 @@ public class NewProjectCreationPage extends WizardPage {
mLocationPathField.setEnabled(location_enabled); mLocationPathField.setEnabled(location_enabled);
mBrowseButton.setEnabled(location_enabled); mBrowseButton.setEnabled(location_enabled);
mSamplesCombo.setEnabled(is_create_from_sample && mSamplesPaths.size() > 0);
mPackageNameField.setEnabled(is_new_project); mPackageNameField.setEnabled(is_new_project);
mCreateActivityCheck.setEnabled(is_new_project); mCreateActivityCheck.setEnabled(is_new_project);
mActivityNameField.setEnabled(is_new_project & create_activity); mActivityNameField.setEnabled(is_new_project & create_activity);
@@ -718,6 +789,12 @@ public class NewProjectCreationPage extends WizardPage {
* @param abs_dir A new absolute directory path or null to use the default. * @param abs_dir A new absolute directory path or null to use the default.
*/ */
private void updateLocationPathField(String abs_dir) { 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 is_new_project = mInfo.isNewProject();
boolean use_default = mInfo.useDefaultLocation(); boolean use_default = mInfo.useDefaultLocation();
boolean custom_location = !is_new_project || !use_default; boolean custom_location = !is_new_project || !use_default;
@@ -872,6 +949,10 @@ public class NewProjectCreationPage extends WizardPage {
mMinSdkVersionField.setText(target.getVersion().getApiString()); mMinSdkVersionField.setText(target.getVersion().getApiString());
mInternalMinSdkVersionUpdate = false; mInternalMinSdkVersionUpdate = false;
} }
loadSamplesForTarget(target);
enableLocationWidgets();
onSampleSelected();
} }
/** /**
@@ -969,15 +1050,15 @@ public class NewProjectCreationPage extends WizardPage {
String[] ids = activityName.split(AndroidConstants.RE_DOT); String[] ids = activityName.split(AndroidConstants.RE_DOT);
activityName = ids[ids.length - 1]; activityName = ids[ids.length - 1];
} }
if (mProjectNameField.getText().length() == 0 || if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) {
!mProjectNameModifiedByUser) {
mInternalProjectNameUpdate = true; mInternalProjectNameUpdate = true;
mProjectNameModifiedByUser = false;
mProjectNameField.setText(activityName); mProjectNameField.setText(activityName);
mInternalProjectNameUpdate = false; mInternalProjectNameUpdate = false;
} }
if (mApplicationNameField.getText().length() == 0 || if (mApplicationNameField.getText().length() == 0 || !mApplicationNameModifiedByUser) {
!mApplicationNameModifiedByUser) {
mInternalApplicationNameUpdate = true; mInternalApplicationNameUpdate = true;
mApplicationNameModifiedByUser = false;
mApplicationNameField.setText(activityName); mApplicationNameField.setText(activityName);
mInternalApplicationNameUpdate = false; mInternalApplicationNameUpdate = false;
} }
@@ -1004,8 +1085,7 @@ public class NewProjectCreationPage extends WizardPage {
// For the project name, remove any dots // For the project name, remove any dots
packageName = packageName.replace('.', '_'); packageName = packageName.replace('.', '_');
if (mProjectNameField.getText().length() == 0 || if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) {
!mProjectNameModifiedByUser) {
mInternalProjectNameUpdate = true; mInternalProjectNameUpdate = true;
mProjectNameField.setText(packageName); mProjectNameField.setText(packageName);
mInternalProjectNameUpdate = false; mInternalProjectNameUpdate = false;
@@ -1015,7 +1095,8 @@ public class NewProjectCreationPage extends WizardPage {
} }
// Select the target matching the manifest's sdk or build properties, if any // Select the target matching the manifest's sdk or build properties, if any
boolean foundTarget = false; IAndroidTarget foundTarget = null;
IAndroidTarget currentTarget = mInfo.getSdkTarget();
ProjectProperties p = ProjectProperties.create(projectLocation, null); ProjectProperties p = ProjectProperties.create(projectLocation, null);
if (p != null) { if (p != null) {
@@ -1023,33 +1104,43 @@ public class NewProjectCreationPage extends WizardPage {
p.merge(PropertyType.BUILD).merge(PropertyType.DEFAULT); p.merge(PropertyType.BUILD).merge(PropertyType.DEFAULT);
String v = p.getProperty(ProjectProperties.PROPERTY_TARGET); String v = p.getProperty(ProjectProperties.PROPERTY_TARGET);
IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(v); IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(v);
if (target != null) { // We can change the current target if:
mSdkTargetSelector.setSelection(target); // - we found a new target
foundTarget = true; // - there is no current target
// - there is a current target but the new target is not <= to the current one.
if (target != null &&
(currentTarget == null || !target.isCompatibleBaseFor(currentTarget))) {
foundTarget = target;
} }
} }
if (!foundTarget && minSdkVersion != null) { if (foundTarget == null && minSdkVersion != null) {
// Otherwise try to match the requested sdk version
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (target.getVersion().equals(minSdkVersion)) { if (target != null &&
mSdkTargetSelector.setSelection(target); target.getVersion().equals(minSdkVersion) &&
foundTarget = true; (currentTarget == null || !target.isCompatibleBaseFor(currentTarget))) {
foundTarget = target;
break; break;
} }
} }
} }
if (!foundTarget) { if (foundTarget == null) {
// Or last attemp, try to match a sample project location
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (projectLocation.startsWith(target.getLocation())) { if (target != null &&
mSdkTargetSelector.setSelection(target); projectLocation.startsWith(target.getLocation()) &&
foundTarget = true; (currentTarget == null || !target.isCompatibleBaseFor(currentTarget))) {
foundTarget = target;
break; break;
} }
} }
} }
if (!foundTarget) { if (foundTarget != null) {
mSdkTargetSelector.setSelection(foundTarget);
} else {
mInternalMinSdkVersionUpdate = true; mInternalMinSdkVersionUpdate = true;
if (minSdkVersion != null) { if (minSdkVersion != null) {
mMinSdkVersionField.setText(minSdkVersion); mMinSdkVersionField.setText(minSdkVersion);
@@ -1058,6 +1149,104 @@ public class NewProjectCreationPage extends WizardPage {
} }
} }
/**
* Updates the list of all samples for the given target SDK.
* The list is stored in mSamplesPaths as absolute directory paths.
* The combo is recreated to match this.
*/
private void loadSamplesForTarget(IAndroidTarget target) {
// Keep the name of the old selection (if there were any samples)
String oldChoice = null;
if (mSamplesPaths.size() > 0) {
int selIndex = mSamplesCombo.getSelectionIndex();
if (selIndex > -1) {
oldChoice = mSamplesCombo.getItem(selIndex);
}
}
// Clear all current content
mSamplesCombo.removeAll();
mSamplesPaths.clear();
if (target != null) {
// Get the sample root path and recompute the list of samples
String samplesRootPath = target.getPath(IAndroidTarget.SAMPLES);
File samplesDir = new File(samplesRootPath);
findSamplesManifests(samplesDir, mSamplesPaths);
if (mSamplesPaths.size() == 0) {
// Odd, this target has no samples. Could happen with an addon.
mSamplesCombo.add("This target has no samples. Please select another target.");
mSamplesCombo.select(0);
return;
}
// Recompute the description of each sample (the relative path
// to the sample root). Also try to find the old selection.
int selIndex = 0;
int i = 0;
int n = samplesRootPath.length();
for (String path : mSamplesPaths) {
if (path.length() > n) {
path = path.substring(n);
if (path.charAt(0) == File.separatorChar) {
path = path.substring(1);
}
if (path.endsWith(File.separator)) {
path = path.substring(0, path.length() - 1);
}
path = path.replaceAll(Pattern.quote(File.separator), " > ");
}
if (oldChoice != null && oldChoice.equals(path)) {
selIndex = i;
}
mSamplesCombo.add(path);
i++;
}
mSamplesCombo.select(selIndex);
} else {
mSamplesCombo.add("Please select a target.");
mSamplesCombo.select(0);
}
}
/**
* Recursively find potential sample directories under the given directory.
* Actually lists any directory that contains an android manifest.
* Paths found are added the samplesPaths list.
*/
private void findSamplesManifests(File samplesDir, ArrayList<String> samplesPaths) {
if (!samplesDir.isDirectory()) {
return;
}
for (File f : samplesDir.listFiles()) {
if (f.isDirectory()) {
// Assume this is a sample if it contains an android manifest.
File manifestFile = new File(f, SdkConstants.FN_ANDROID_MANIFEST_XML);
if (manifestFile.isFile()) {
samplesPaths.add(f.getPath());
}
// Recurse in the project, to find embedded tests sub-projects
// We can however skip this recursion for known android sub-dirs that
// can't have projects, namely for sources, assets and resources.
String leaf = f.getName();
if (!SdkConstants.FD_SOURCES.equals(leaf) &&
!SdkConstants.FD_ASSETS.equals(leaf) &&
!SdkConstants.FD_RES.equals(leaf)) {
findSamplesManifests(f, samplesPaths);
}
}
}
}
/** /**
* Returns whether this page's controls currently all contain valid values. * Returns whether this page's controls currently all contain valid values.
* *
@@ -1069,10 +1258,10 @@ public class NewProjectCreationPage extends WizardPage {
int status = validateProjectField(workspace); int status = validateProjectField(workspace);
if ((status & MSG_ERROR) == 0) { if ((status & MSG_ERROR) == 0) {
status |= validateLocationPath(workspace); status |= validateSdkTarget();
} }
if ((status & MSG_ERROR) == 0) { if ((status & MSG_ERROR) == 0) {
status |= validateSdkTarget(); status |= validateLocationPath(workspace);
} }
if ((status & MSG_ERROR) == 0) { if ((status & MSG_ERROR) == 0) {
status |= validatePackageField(); status |= validatePackageField();