Home | History | Annotate | Download | only in templates
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.eclipse.adt.internal.wizards.templates;
     17 
     18 
     19 import static com.android.SdkConstants.ATTR_ID;
     20 import static com.android.ide.eclipse.adt.AdtUtils.extractClassName;
     21 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplatePage.WIZARD_PAGE_WIDTH;
     22 
     23 import com.android.annotations.Nullable;
     24 import com.android.sdklib.SdkVersionInfo;
     25 import com.android.ide.eclipse.adt.AdtPlugin;
     26 import com.android.ide.eclipse.adt.AdtUtils;
     27 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     29 import com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage;
     30 import com.android.ide.eclipse.adt.internal.wizards.newproject.ProjectNamePage;
     31 import com.android.sdklib.AndroidVersion;
     32 import com.android.sdklib.IAndroidTarget;
     33 import com.google.common.collect.Lists;
     34 import com.google.common.collect.Maps;
     35 
     36 import org.eclipse.core.resources.IResource;
     37 import org.eclipse.core.resources.IWorkspace;
     38 import org.eclipse.core.resources.ResourcesPlugin;
     39 import org.eclipse.core.runtime.IPath;
     40 import org.eclipse.core.runtime.IStatus;
     41 import org.eclipse.core.runtime.Platform;
     42 import org.eclipse.core.runtime.Status;
     43 import org.eclipse.jface.dialogs.IMessageProvider;
     44 import org.eclipse.jface.fieldassist.ControlDecoration;
     45 import org.eclipse.jface.fieldassist.FieldDecoration;
     46 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
     47 import org.eclipse.jface.wizard.WizardPage;
     48 import org.eclipse.swt.SWT;
     49 import org.eclipse.swt.events.FocusEvent;
     50 import org.eclipse.swt.events.FocusListener;
     51 import org.eclipse.swt.events.ModifyEvent;
     52 import org.eclipse.swt.events.ModifyListener;
     53 import org.eclipse.swt.events.SelectionEvent;
     54 import org.eclipse.swt.events.SelectionListener;
     55 import org.eclipse.swt.graphics.Image;
     56 import org.eclipse.swt.layout.GridData;
     57 import org.eclipse.swt.layout.GridLayout;
     58 import org.eclipse.swt.widgets.Combo;
     59 import org.eclipse.swt.widgets.Composite;
     60 import org.eclipse.swt.widgets.Control;
     61 import org.eclipse.swt.widgets.Label;
     62 import org.eclipse.swt.widgets.Text;
     63 
     64 import java.util.ArrayList;
     65 import java.util.List;
     66 import java.util.Map;
     67 
     68 /**
     69  * First wizard page in the "New Project From Template" wizard
     70  */
     71 public class NewProjectPage extends WizardPage
     72         implements ModifyListener, SelectionListener, FocusListener {
     73     private static final int FIELD_WIDTH = 300;
     74     private static final String SAMPLE_PACKAGE_PREFIX = "com.example."; //$NON-NLS-1$
     75     /** Suffix added by default to activity names */
     76     static final String ACTIVITY_NAME_SUFFIX = "Activity";              //$NON-NLS-1$
     77     /** Prefix added to default layout names */
     78     static final String LAYOUT_NAME_PREFIX = "activity_";               //$NON-NLS-1$
     79     private static final int INITIAL_MIN_SDK = 8;
     80 
     81     private final NewProjectWizardState mValues;
     82     private Map<String, Integer> mMinNameToApi;
     83     private Parameter mThemeParameter;
     84     private Combo mThemeCombo;
     85 
     86     private Text mProjectText;
     87     private Text mPackageText;
     88     private Text mApplicationText;
     89     private Combo mMinSdkCombo;
     90     private Combo mTargetSdkCombo;
     91     private Combo mBuildSdkCombo;
     92     private Label mHelpIcon;
     93     private Label mTipLabel;
     94 
     95     private boolean mIgnore;
     96     private ControlDecoration mApplicationDec;
     97     private ControlDecoration mProjectDec;
     98     private ControlDecoration mPackageDec;
     99     private ControlDecoration mBuildTargetDec;
    100     private ControlDecoration mMinSdkDec;
    101     private ControlDecoration mTargetSdkDec;
    102     private ControlDecoration mThemeDec;
    103 
    104     NewProjectPage(NewProjectWizardState values) {
    105         super("newAndroidApp"); //$NON-NLS-1$
    106         mValues = values;
    107         setTitle("New Android Application");
    108         setDescription("Creates a new Android Application");
    109     }
    110 
    111     @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused
    112     @Override
    113     public void createControl(Composite parent) {
    114         Composite container = new Composite(parent, SWT.NULL);
    115         setControl(container);
    116         GridLayout gl_container = new GridLayout(4, false);
    117         gl_container.horizontalSpacing = 10;
    118         container.setLayout(gl_container);
    119 
    120         Label applicationLabel = new Label(container, SWT.NONE);
    121         applicationLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
    122         applicationLabel.setText("Application Name:");
    123 
    124         mApplicationText = new Text(container, SWT.BORDER);
    125         GridData gdApplicationText = new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1);
    126         gdApplicationText.widthHint = FIELD_WIDTH;
    127         mApplicationText.setLayoutData(gdApplicationText);
    128         mApplicationText.addModifyListener(this);
    129         mApplicationText.addFocusListener(this);
    130         mApplicationDec = createFieldDecoration(mApplicationText,
    131                 "The application name is shown in the Play Store, as well as in the " +
    132                 "Manage Application list in Settings.");
    133 
    134         Label projectLabel = new Label(container, SWT.NONE);
    135         projectLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
    136         projectLabel.setText("Project Name:");
    137         mProjectText = new Text(container, SWT.BORDER);
    138         GridData gdProjectText = new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1);
    139         gdProjectText.widthHint = FIELD_WIDTH;
    140         mProjectText.setLayoutData(gdProjectText);
    141         mProjectText.addModifyListener(this);
    142         mProjectText.addFocusListener(this);
    143         mProjectDec = createFieldDecoration(mProjectText,
    144                 "The project name is only used by Eclipse, but must be unique within the " +
    145                 "workspace. This can typically be the same as the application name.");
    146 
    147         Label packageLabel = new Label(container, SWT.NONE);
    148         packageLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
    149         packageLabel.setText("Package Name:");
    150 
    151         mPackageText = new Text(container, SWT.BORDER);
    152         GridData gdPackageText = new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1);
    153         gdPackageText.widthHint = FIELD_WIDTH;
    154         mPackageText.setLayoutData(gdPackageText);
    155         mPackageText.addModifyListener(this);
    156         mPackageText.addFocusListener(this);
    157         mPackageDec = createFieldDecoration(mPackageText,
    158                 "The package name must be a unique identifier for your application.\n" +
    159                 "It is typically not shown to users, but it *must* stay the same " +
    160                 "for the lifetime of your application; it is how multiple versions " +
    161                 "of the same application are considered the \"same app\".\nThis is " +
    162                 "typically the reverse domain name of your organization plus one or " +
    163                 "more application identifiers, and it must be a valid Java package " +
    164                 "name.");
    165         new Label(container, SWT.NONE);
    166 
    167         new Label(container, SWT.NONE);
    168         new Label(container, SWT.NONE);
    169         new Label(container, SWT.NONE);
    170 
    171         // Min SDK
    172 
    173         Label minSdkLabel = new Label(container, SWT.NONE);
    174         minSdkLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
    175         minSdkLabel.setText("Minimum Required SDK:");
    176 
    177         mMinSdkCombo = new Combo(container, SWT.READ_ONLY);
    178         GridData gdMinSdkCombo = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1);
    179         gdMinSdkCombo.widthHint = FIELD_WIDTH;
    180         mMinSdkCombo.setLayoutData(gdMinSdkCombo);
    181 
    182         // Pick most recent platform
    183         IAndroidTarget[] targets = getCompilationTargets();
    184         mMinNameToApi = Maps.newHashMap();
    185         List<String> targetLabels = new ArrayList<String>(targets.length);
    186         for (IAndroidTarget target : targets) {
    187             String targetLabel;
    188             if (target.isPlatform()
    189                     && target.getVersion().getApiLevel() <= AdtUtils.getHighestKnownApiLevel()) {
    190                 targetLabel = AdtUtils.getAndroidName(target.getVersion().getApiLevel());
    191             } else {
    192                 targetLabel = AdtUtils.getTargetLabel(target);
    193             }
    194             targetLabels.add(targetLabel);
    195             mMinNameToApi.put(targetLabel, target.getVersion().getApiLevel());
    196         }
    197 
    198         List<String> codeNames = Lists.newArrayList();
    199         int buildTargetIndex = -1;
    200         for (int i = 0, n = targets.length; i < n; i++) {
    201             IAndroidTarget target = targets[i];
    202             AndroidVersion version = target.getVersion();
    203             int apiLevel = version.getApiLevel();
    204             if (version.isPreview()) {
    205                 String codeName = version.getCodename();
    206                 String targetLabel = codeName + " Preview";
    207                 codeNames.add(targetLabel);
    208                 mMinNameToApi.put(targetLabel, apiLevel);
    209             } else if (target.isPlatform()
    210                     && (mValues.target == null ||
    211                         apiLevel > mValues.target.getVersion().getApiLevel())) {
    212                 mValues.target = target;
    213                 buildTargetIndex = i;
    214             }
    215         }
    216         List<String> labels = new ArrayList<String>(24);
    217         for (String label : AdtUtils.getKnownVersions()) {
    218             labels.add(label);
    219         }
    220         assert labels.size() >= 15; // *Known* versions to ADT, not installed/available versions
    221         for (String codeName : codeNames) {
    222             labels.add(codeName);
    223         }
    224         String[] versions = labels.toArray(new String[labels.size()]);
    225         mMinSdkCombo.setItems(versions);
    226         if (mValues.target != null && mValues.target.getVersion().isPreview()) {
    227             mValues.minSdk = mValues.target.getVersion().getCodename();
    228             mMinSdkCombo.setText(mValues.minSdk);
    229             mValues.iconState.minSdk = mValues.target.getVersion().getApiLevel();
    230             mValues.minSdkLevel = mValues.iconState.minSdk;
    231         } else {
    232             mMinSdkCombo.select(INITIAL_MIN_SDK - 1);
    233             mValues.minSdk = Integer.toString(INITIAL_MIN_SDK);
    234             mValues.minSdkLevel = INITIAL_MIN_SDK;
    235             mValues.iconState.minSdk = INITIAL_MIN_SDK;
    236         }
    237         mMinSdkCombo.addSelectionListener(this);
    238         mMinSdkCombo.addFocusListener(this);
    239         mMinSdkDec = createFieldDecoration(mMinSdkCombo,
    240                 "Choose the lowest version of Android that your application will support. Lower " +
    241                 "API levels target more devices, but means fewer features are available. By " +
    242                 "targeting API 8 and later, you reach approximately 95% of the market.");
    243         new Label(container, SWT.NONE);
    244 
    245         // Target SDK
    246         Label targetSdkLabel = new Label(container, SWT.NONE);
    247         targetSdkLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
    248         targetSdkLabel.setText("Target SDK:");
    249 
    250         mTargetSdkCombo = new Combo(container, SWT.READ_ONLY);
    251         GridData gdTargetSdkCombo = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1);
    252         gdTargetSdkCombo.widthHint = FIELD_WIDTH;
    253         mTargetSdkCombo.setLayoutData(gdTargetSdkCombo);
    254 
    255         mTargetSdkCombo.setItems(versions);
    256         mTargetSdkCombo.select(mValues.targetSdkLevel - 1);
    257 
    258         mTargetSdkCombo.addSelectionListener(this);
    259         mTargetSdkCombo.addFocusListener(this);
    260         mTargetSdkDec = createFieldDecoration(mTargetSdkCombo,
    261                 "Choose the highest API level that the application is known to work with. " +
    262                 "This attribute informs the system that you have tested against the target " +
    263                 "version and the system should not enable any compatibility behaviors to " +
    264                 "maintain your app's forward-compatibility with the target version. " +
    265                 "The application is still able to run on older versions " +
    266                 "(down to minSdkVersion). Your application may look dated if you are not " +
    267                 "targeting the current version.");
    268         new Label(container, SWT.NONE);
    269 
    270         // Build Version
    271 
    272         Label buildSdkLabel = new Label(container, SWT.NONE);
    273         buildSdkLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
    274         buildSdkLabel.setText("Compile With:");
    275 
    276         mBuildSdkCombo = new Combo(container, SWT.READ_ONLY);
    277         GridData gdBuildSdkCombo = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1);
    278         gdBuildSdkCombo.widthHint = FIELD_WIDTH;
    279         mBuildSdkCombo.setLayoutData(gdBuildSdkCombo);
    280         mBuildSdkCombo.setData(targets);
    281         mBuildSdkCombo.setItems(targetLabels.toArray(new String[targetLabels.size()]));
    282         if (buildTargetIndex != -1) {
    283             mBuildSdkCombo.select(buildTargetIndex);
    284         }
    285 
    286         mBuildSdkCombo.addSelectionListener(this);
    287         mBuildSdkCombo.addFocusListener(this);
    288         mBuildTargetDec = createFieldDecoration(mBuildSdkCombo,
    289                 "Choose a target API to compile your code against, from your installed SDKs. " +
    290                 "This is typically the most recent version, or the first version that supports " +
    291                 "all the APIs you want to directly access without reflection.");
    292         new Label(container, SWT.NONE);
    293 
    294         TemplateMetadata metadata = mValues.template.getTemplate();
    295         if (metadata != null) {
    296             mThemeParameter = metadata.getParameter("baseTheme"); //$NON-NLS-1$
    297             if (mThemeParameter != null && mThemeParameter.element != null) {
    298                 Label themeLabel = new Label(container, SWT.NONE);
    299                 themeLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
    300                 themeLabel.setText("Theme:");
    301 
    302                 mThemeCombo = NewTemplatePage.createOptionCombo(mThemeParameter, container,
    303                         mValues.parameters, this, this);
    304                 GridData gdThemeCombo = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1);
    305                 gdThemeCombo.widthHint = FIELD_WIDTH;
    306                 mThemeCombo.setLayoutData(gdThemeCombo);
    307                 new Label(container, SWT.NONE);
    308 
    309                 mThemeDec = createFieldDecoration(mThemeCombo,
    310                         "Choose the base theme to use for the application");
    311             }
    312         }
    313 
    314         new Label(container, SWT.NONE);
    315         new Label(container, SWT.NONE);
    316         new Label(container, SWT.NONE);
    317         new Label(container, SWT.NONE);
    318 
    319         Label label = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
    320         label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 4, 1));
    321 
    322         mHelpIcon = new Label(container, SWT.NONE);
    323         mHelpIcon.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false, 1, 1));
    324         Image icon = IconFactory.getInstance().getIcon("quickfix");
    325         mHelpIcon.setImage(icon);
    326         mHelpIcon.setVisible(false);
    327 
    328         mTipLabel = new Label(container, SWT.WRAP);
    329         mTipLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
    330 
    331         // Reserve space for 4 lines
    332         mTipLabel.setText("\n\n\n\n"); //$NON-NLS-1$
    333 
    334         // Reserve enough width to accommodate the various wizard pages up front
    335         // (since they are created lazily, and we don't want the wizard to dynamically
    336         // resize itself for small size adjustments as each successive page is slightly
    337         // larger)
    338         Label dummy = new Label(container, SWT.NONE);
    339         GridData data = new GridData();
    340         data.horizontalSpan = 4;
    341         data.widthHint = WIZARD_PAGE_WIDTH;
    342         dummy.setLayoutData(data);
    343     }
    344 
    345     /**
    346      * Updates the theme selection such that it's valid for the current build
    347      * and min sdk targets. Also runs {@link #validatePage} in case no valid entry was found.
    348      * Does nothing if called on a template that does not supply a theme.
    349      */
    350     void updateTheme() {
    351         if (mThemeParameter != null) {
    352             // Pick the highest theme version that works for the current SDK level
    353             Parameter parameter = NewTemplatePage.getParameter(mThemeCombo);
    354             assert parameter == mThemeParameter;
    355             if (parameter != null) {
    356                 String[] optionIds = (String[]) mThemeCombo.getData(ATTR_ID);
    357                 for (int index = optionIds.length - 1; index >= 0; index--) {
    358                     IStatus status = NewTemplatePage.validateCombo(null, mThemeParameter,
    359                             index, mValues.minSdkLevel, mValues.getBuildApi());
    360                     if (status == null || status.isOK()) {
    361                         String optionId = optionIds[index];
    362                         parameter.value = optionId;
    363                         parameter.edited = optionId != null && !optionId.toString().isEmpty();
    364                         mValues.parameters.put(parameter.id, optionId);
    365                         try {
    366                             mIgnore = true;
    367                             mThemeCombo.select(index);
    368                         } finally {
    369                             mIgnore = false;
    370                         }
    371                         break;
    372                     }
    373                 }
    374             }
    375 
    376             validatePage();
    377         }
    378     }
    379 
    380     private IAndroidTarget[] getCompilationTargets() {
    381         Sdk current = Sdk.getCurrent();
    382         if (current == null) {
    383             return new IAndroidTarget[0];
    384         }
    385         IAndroidTarget[] targets = current.getTargets();
    386         List<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
    387 
    388         for (IAndroidTarget target : targets) {
    389             if (target.isPlatform() == false &&
    390                     (target.getOptionalLibraries() == null ||
    391                             target.getOptionalLibraries().length == 0)) {
    392                 continue;
    393             }
    394             list.add(target);
    395         }
    396 
    397         return list.toArray(new IAndroidTarget[list.size()]);
    398     }
    399 
    400     private ControlDecoration createFieldDecoration(Control control, String description) {
    401         ControlDecoration dec = new ControlDecoration(control, SWT.LEFT);
    402         dec.setMarginWidth(2);
    403         FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault().
    404            getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION);
    405         dec.setImage(errorFieldIndicator.getImage());
    406         dec.setDescriptionText(description);
    407         control.setToolTipText(description);
    408 
    409         return dec;
    410     }
    411 
    412     @Override
    413     public void setVisible(boolean visible) {
    414         super.setVisible(visible);
    415 
    416         // DURING DEVELOPMENT ONLY
    417         //if (assertionsEnabled()) {
    418         //    String uniqueProjectName = AdtUtils.getUniqueProjectName("Test", "");
    419         //    mProjectText.setText(uniqueProjectName);
    420         //    mPackageText.setText("test.pkg");
    421         //}
    422 
    423         validatePage();
    424     }
    425 
    426     // ---- Implements ModifyListener ----
    427 
    428     @Override
    429     public void modifyText(ModifyEvent e) {
    430         if (mIgnore) {
    431             return;
    432         }
    433 
    434         Object source = e.getSource();
    435         if (source == mProjectText) {
    436             mValues.projectName = mProjectText.getText();
    437             updateProjectLocation(mValues.projectName);
    438             mValues.projectModified = true;
    439 
    440             try {
    441                 mIgnore = true;
    442                 if (!mValues.applicationModified) {
    443                     mValues.applicationName = mValues.projectName;
    444                     mApplicationText.setText(mValues.projectName);
    445                 }
    446                 updateActivityNames(mValues.projectName);
    447             } finally {
    448                 mIgnore = false;
    449             }
    450             suggestPackage(mValues.projectName);
    451         } else if (source == mPackageText) {
    452             mValues.packageName = mPackageText.getText();
    453             mValues.packageModified = true;
    454         } else if (source == mApplicationText) {
    455             mValues.applicationName = mApplicationText.getText();
    456             mValues.applicationModified = true;
    457 
    458             try {
    459                 mIgnore = true;
    460                 if (!mValues.projectModified) {
    461                     mValues.projectName = appNameToProjectName(mValues.applicationName);
    462                     mProjectText.setText(mValues.projectName);
    463                     updateProjectLocation(mValues.projectName);
    464                 }
    465                 updateActivityNames(mValues.applicationName);
    466             } finally {
    467                 mIgnore = false;
    468             }
    469             suggestPackage(mValues.applicationName);
    470         }
    471 
    472         validatePage();
    473     }
    474 
    475     private String appNameToProjectName(String appName) {
    476         // Strip out whitespace (and capitalize subsequent words where spaces were removed
    477         boolean upcaseNext = false;
    478         StringBuilder sb = new StringBuilder(appName.length());
    479         for (int i = 0, n = appName.length(); i < n; i++) {
    480             char c = appName.charAt(i);
    481             if (c == ' ') {
    482                 upcaseNext = true;
    483             } else if (upcaseNext) {
    484                 sb.append(Character.toUpperCase(c));
    485                 upcaseNext = false;
    486             } else {
    487                 sb.append(c);
    488             }
    489         }
    490 
    491         appName = sb.toString().trim();
    492 
    493         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    494         IStatus nameStatus = workspace.validateName(appName, IResource.PROJECT);
    495         if (nameStatus.isOK()) {
    496             return appName;
    497         }
    498 
    499         sb = new StringBuilder(appName.length());
    500         for (int i = 0, n = appName.length(); i < n; i++) {
    501             char c = appName.charAt(i);
    502             if (Character.isLetterOrDigit(c) || c == '.' || c == '-') {
    503                 sb.append(c);
    504             }
    505         }
    506 
    507         return sb.toString().trim();
    508     }
    509 
    510     /** If the project should be created in the workspace, then update the project location
    511      * based on the project name. */
    512     private void updateProjectLocation(String projectName) {
    513         if (projectName == null) {
    514             projectName = "";
    515         }
    516 
    517         if (mValues.useDefaultLocation) {
    518             IPath workspace = Platform.getLocation();
    519             String projectLocation = workspace.append(projectName).toOSString();
    520             mValues.projectLocation = projectLocation;
    521         }
    522     }
    523 
    524     private void updateActivityNames(String name) {
    525         try {
    526             mIgnore = true;
    527             if (!mValues.activityNameModified) {
    528                 mValues.activityName = extractClassName(name) + ACTIVITY_NAME_SUFFIX;
    529             }
    530             if (!mValues.activityTitleModified) {
    531                 mValues.activityTitle = name;
    532             }
    533         } finally {
    534             mIgnore = false;
    535         }
    536     }
    537 
    538     // ---- Implements SelectionListener ----
    539 
    540     @Override
    541     public void widgetSelected(SelectionEvent e) {
    542         if (mIgnore) {
    543             return;
    544         }
    545 
    546         Object source = e.getSource();
    547         if (source == mMinSdkCombo) {
    548             mValues.minSdk = getSelectedMinSdk();
    549             Integer minSdk = mMinNameToApi.get(mValues.minSdk);
    550             if (minSdk == null) {
    551                 try {
    552                     minSdk = Integer.parseInt(mValues.minSdk);
    553                 } catch (NumberFormatException nufe) {
    554                     // If not a number, then the string is a codename, so treat it
    555                     // as a preview version.
    556                     minSdk = SdkVersionInfo.HIGHEST_KNOWN_API + 1;
    557                 }
    558             }
    559             mValues.iconState.minSdk = minSdk.intValue();
    560             mValues.minSdkLevel = minSdk.intValue();
    561 
    562             // If higher than build target, adjust build target
    563             if (mValues.minSdkLevel > mValues.getBuildApi()) {
    564                 // Try to find a build target with an adequate build API
    565                 IAndroidTarget[] targets = (IAndroidTarget[]) mBuildSdkCombo.getData();
    566                 IAndroidTarget best = null;
    567                 int bestApi = Integer.MAX_VALUE;
    568                 int bestTargetIndex = -1;
    569                 for (int i = 0; i < targets.length; i++) {
    570                     IAndroidTarget target = targets[i];
    571                     if (!target.isPlatform()) {
    572                         continue;
    573                     }
    574                     int api = target.getVersion().getApiLevel();
    575                     if (api >= mValues.minSdkLevel && api < bestApi) {
    576                         best = target;
    577                         bestApi = api;
    578                         bestTargetIndex = i;
    579                     }
    580                 }
    581 
    582                 if (best != null) {
    583                     assert bestTargetIndex != -1;
    584                     mValues.target = best;
    585                     try {
    586                         mIgnore = true;
    587                         mBuildSdkCombo.select(bestTargetIndex);
    588                     } finally {
    589                         mIgnore = false;
    590                     }
    591                 }
    592             }
    593 
    594             // If higher than targetSdkVersion, adjust targetSdkVersion
    595             if (mValues.minSdkLevel > mValues.targetSdkLevel) {
    596                 mValues.targetSdkLevel = mValues.minSdkLevel;
    597                 try {
    598                     mIgnore = true;
    599                     setSelectedTargetSdk(mValues.targetSdkLevel);
    600                 } finally {
    601                     mIgnore = false;
    602                 }
    603             }
    604         } else if (source == mBuildSdkCombo) {
    605             mValues.target = getSelectedBuildTarget();
    606 
    607             // If lower than min sdk target, adjust min sdk target
    608             if (mValues.target.getVersion().isPreview()) {
    609                 mValues.minSdk = mValues.target.getVersion().getCodename();
    610                 try {
    611                     mIgnore = true;
    612                     mMinSdkCombo.setText(mValues.minSdk);
    613                 } finally {
    614                     mIgnore = false;
    615                 }
    616             } else {
    617                 String minSdk = mValues.minSdk;
    618                 int buildApiLevel = mValues.target.getVersion().getApiLevel();
    619                 if (minSdk != null && !minSdk.isEmpty()
    620                         && Character.isDigit(minSdk.charAt(0))
    621                         && buildApiLevel < Integer.parseInt(minSdk)) {
    622                     mValues.minSdk = Integer.toString(buildApiLevel);
    623                     try {
    624                         mIgnore = true;
    625                         setSelectedMinSdk(buildApiLevel);
    626                     } finally {
    627                         mIgnore = false;
    628                     }
    629                 }
    630             }
    631         } else if (source == mTargetSdkCombo) {
    632             mValues.targetSdkLevel = getSelectedTargetSdk();
    633         }
    634 
    635         validatePage();
    636     }
    637 
    638     private String getSelectedMinSdk() {
    639         // If you're using a preview build, such as android-JellyBean, you have
    640         // to use the codename, e.g. JellyBean, as the minimum SDK as well.
    641         IAndroidTarget buildTarget = getSelectedBuildTarget();
    642         if (buildTarget != null && buildTarget.getVersion().isPreview()) {
    643             return buildTarget.getVersion().getCodename();
    644         }
    645 
    646         // +1: First API level (at index 0) is 1
    647         return Integer.toString(mMinSdkCombo.getSelectionIndex() + 1);
    648     }
    649 
    650     private int getSelectedTargetSdk() {
    651         // +1: First API level (at index 0) is 1
    652         return mTargetSdkCombo.getSelectionIndex() + 1;
    653     }
    654 
    655     private void setSelectedMinSdk(int api) {
    656         mMinSdkCombo.select(api - 1); // -1: First API level (at index 0) is 1
    657     }
    658 
    659     private void setSelectedTargetSdk(int api) {
    660         mTargetSdkCombo.select(api - 1); // -1: First API level (at index 0) is 1
    661     }
    662 
    663     @Nullable
    664     private IAndroidTarget getSelectedBuildTarget() {
    665         IAndroidTarget[] targets = (IAndroidTarget[]) mBuildSdkCombo.getData();
    666         int index = mBuildSdkCombo.getSelectionIndex();
    667         if (index >= 0 && index < targets.length) {
    668             return targets[index];
    669         } else {
    670             return null;
    671         }
    672     }
    673 
    674     private void suggestPackage(String original) {
    675         if (!mValues.packageModified) {
    676             // Create default package name
    677             StringBuilder sb = new StringBuilder();
    678             sb.append(SAMPLE_PACKAGE_PREFIX);
    679             appendPackage(sb, original);
    680 
    681             String pkg = sb.toString();
    682             if (pkg.endsWith(".")) { //$NON-NLS-1$
    683                 pkg = pkg.substring(0, pkg.length() - 1);
    684             }
    685             mValues.packageName = pkg;
    686             try {
    687                 mIgnore = true;
    688                 mPackageText.setText(mValues.packageName);
    689             } finally {
    690                 mIgnore = false;
    691             }
    692         }
    693     }
    694 
    695     private static void appendPackage(StringBuilder sb, String string) {
    696         for (int i = 0, n = string.length(); i < n; i++) {
    697             char c = string.charAt(i);
    698             if (i == 0 && Character.isJavaIdentifierStart(c)
    699                     || i != 0 && Character.isJavaIdentifierPart(c)) {
    700                 sb.append(Character.toLowerCase(c));
    701             } else if ((c == '.')
    702                     && (sb.length() > 0 && sb.charAt(sb.length() - 1) != '.')) {
    703                 sb.append('.');
    704             } else if (c == '-') {
    705                 sb.append('_');
    706             }
    707         }
    708     }
    709 
    710     @Override
    711     public void widgetDefaultSelected(SelectionEvent e) {
    712     }
    713 
    714     // ---- Implements FocusListener ----
    715 
    716     @Override
    717     public void focusGained(FocusEvent e) {
    718         Object source = e.getSource();
    719         String tip = "";
    720         if (source == mApplicationText) {
    721             tip = mApplicationDec.getDescriptionText();
    722         } else if (source == mProjectText) {
    723             tip = mProjectDec.getDescriptionText();
    724         } else if (source == mBuildSdkCombo) {
    725             tip = mBuildTargetDec.getDescriptionText();
    726         } else if (source == mMinSdkCombo) {
    727             tip = mMinSdkDec.getDescriptionText();
    728         } else if (source == mPackageText) {
    729             tip = mPackageDec.getDescriptionText();
    730             if (mPackageText.getText().startsWith(SAMPLE_PACKAGE_PREFIX)) {
    731                 int length = SAMPLE_PACKAGE_PREFIX.length();
    732                 if (mPackageText.getText().length() > length
    733                         && SAMPLE_PACKAGE_PREFIX.endsWith(".")) { //$NON-NLS-1$
    734                     length--;
    735                 }
    736                 mPackageText.setSelection(0, length);
    737             }
    738         } else if (source == mTargetSdkCombo) {
    739             tip = mTargetSdkDec.getDescriptionText();
    740         } else if (source == mThemeCombo) {
    741             tip = mThemeDec.getDescriptionText();
    742         }
    743         mTipLabel.setText(tip);
    744         mHelpIcon.setVisible(tip.length() > 0);
    745     }
    746 
    747     @Override
    748     public void focusLost(FocusEvent e) {
    749         mTipLabel.setText("");
    750         mHelpIcon.setVisible(false);
    751     }
    752 
    753     // Validation
    754 
    755     private void validatePage() {
    756         IStatus status = mValues.template.validateTemplate(mValues.minSdkLevel,
    757                 mValues.getBuildApi());
    758         if (status != null && !status.isOK()) {
    759             updateDecorator(mApplicationDec, null, true);
    760             updateDecorator(mPackageDec, null, true);
    761             updateDecorator(mProjectDec, null, true);
    762             updateDecorator(mThemeDec, null, true);
    763             /* These never get marked with errors:
    764             updateDecorator(mBuildTargetDec, null, true);
    765             updateDecorator(mMinSdkDec, null, true);
    766             updateDecorator(mTargetSdkDec, null, true);
    767             */
    768         } else {
    769             IStatus appStatus = validateAppName();
    770             if (appStatus != null && (status == null
    771                     || appStatus.getSeverity() > status.getSeverity())) {
    772                 status = appStatus;
    773             }
    774 
    775             IStatus projectStatus = validateProjectName();
    776             if (projectStatus != null && (status == null
    777                     || projectStatus.getSeverity() > status.getSeverity())) {
    778                 status = projectStatus;
    779             }
    780 
    781             IStatus packageStatus = validatePackageName();
    782             if (packageStatus != null && (status == null
    783                     || packageStatus.getSeverity() > status.getSeverity())) {
    784                 status = packageStatus;
    785             }
    786 
    787             IStatus locationStatus = ProjectContentsPage.validateLocationInWorkspace(mValues);
    788             if (locationStatus != null && (status == null
    789                     || locationStatus.getSeverity() > status.getSeverity())) {
    790                 status = locationStatus;
    791             }
    792 
    793             if (status == null || status.getSeverity() != IStatus.ERROR) {
    794                 if (mValues.target == null) {
    795                     status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
    796                             "Select an Android build target version");
    797                 }
    798             }
    799 
    800             if (status == null || status.getSeverity() != IStatus.ERROR) {
    801                 if (mValues.minSdk == null || mValues.minSdk.isEmpty()) {
    802                     status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
    803                             "Select a minimum SDK version");
    804                 } else {
    805                     AndroidVersion version = mValues.target.getVersion();
    806                     if (version.isPreview()) {
    807                         if (version.getCodename().equals(mValues.minSdk) == false) {
    808                             status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    809                             "Preview platforms require the min SDK version to match their codenames.");
    810                        }
    811                     } else if (mValues.target.getVersion().compareTo(
    812                             mValues.minSdkLevel,
    813                             version.isPreview() ? mValues.minSdk : null) < 0) {
    814                         status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
    815                             "The minimum SDK version is higher than the build target version");
    816                     }
    817                     if (status == null || status.getSeverity() != IStatus.ERROR) {
    818                         if (mValues.targetSdkLevel < mValues.minSdkLevel) {
    819                             status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    820                                 "The target SDK version should be at least as high as the minimum SDK version");
    821                         }
    822                     }
    823                 }
    824             }
    825 
    826             IStatus themeStatus = validateTheme();
    827             if (themeStatus != null && (status == null
    828                     || themeStatus.getSeverity() > status.getSeverity())) {
    829                 status = themeStatus;
    830             }
    831         }
    832 
    833         setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
    834         if (status != null) {
    835             setMessage(status.getMessage(),
    836                     status.getSeverity() == IStatus.ERROR
    837                         ? IMessageProvider.ERROR : IMessageProvider.WARNING);
    838         } else {
    839             setErrorMessage(null);
    840             setMessage(null);
    841         }
    842     }
    843 
    844     private IStatus validateAppName() {
    845         String appName = mValues.applicationName;
    846         IStatus status = null;
    847         if (appName == null || appName.isEmpty()) {
    848             status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    849                     "Enter an application name (shown in launcher)");
    850         } else if (Character.isLowerCase(mValues.applicationName.charAt(0))) {
    851             status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
    852                     "The application name for most apps begins with an uppercase letter");
    853         }
    854 
    855         updateDecorator(mApplicationDec, status, true);
    856 
    857         return status;
    858     }
    859 
    860     private IStatus validateProjectName() {
    861         IStatus status = ProjectNamePage.validateProjectName(mValues.projectName);
    862         updateDecorator(mProjectDec, status, true);
    863 
    864         return status;
    865     }
    866 
    867     private IStatus validatePackageName() {
    868         IStatus status;
    869         if (mValues.packageName == null || mValues.packageName.startsWith(SAMPLE_PACKAGE_PREFIX)) {
    870             if (mValues.packageName != null
    871                     && !mValues.packageName.equals(SAMPLE_PACKAGE_PREFIX)) {
    872                 status = ApplicationInfoPage.validatePackage(mValues.packageName);
    873                 if (status == null || status.isOK()) {
    874                     status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
    875                         String.format("The prefix '%1$s' is meant as a placeholder and should " +
    876                                       "not be used", SAMPLE_PACKAGE_PREFIX));
    877                 }
    878             } else {
    879                 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    880                         "Package name must be specified.");
    881             }
    882         } else {
    883             status = ApplicationInfoPage.validatePackage(mValues.packageName);
    884         }
    885 
    886         updateDecorator(mPackageDec, status, true);
    887 
    888         return status;
    889     }
    890 
    891     private IStatus validateTheme() {
    892         IStatus status = null;
    893 
    894         if (mThemeParameter != null) {
    895             status = NewTemplatePage.validateCombo(null, mThemeParameter,
    896                     mThemeCombo.getSelectionIndex(),  mValues.minSdkLevel,
    897                     mValues.getBuildApi());
    898 
    899             updateDecorator(mThemeDec, status, true);
    900         }
    901 
    902         return status;
    903     }
    904 
    905     private void updateDecorator(ControlDecoration decorator, IStatus status, boolean hasInfo) {
    906         if (hasInfo) {
    907             int severity = status != null ? status.getSeverity() : IStatus.OK;
    908             setDecoratorType(decorator, severity);
    909         } else {
    910             if (status == null || status.isOK()) {
    911                 decorator.hide();
    912             } else {
    913                 decorator.show();
    914             }
    915         }
    916     }
    917 
    918     private void setDecoratorType(ControlDecoration decorator, int severity) {
    919         String id;
    920         if (severity == IStatus.ERROR) {
    921             id = FieldDecorationRegistry.DEC_ERROR;
    922         } else if (severity == IStatus.WARNING) {
    923             id = FieldDecorationRegistry.DEC_WARNING;
    924         } else {
    925             id = FieldDecorationRegistry.DEC_INFORMATION;
    926         }
    927         FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault().
    928                 getFieldDecoration(id);
    929         decorator.setImage(errorFieldIndicator.getImage());
    930     }
    931 }
    932