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 NewProjectPage(NewProjectWizardState values)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 createControl(Composite parent)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 */ updateTheme()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 getCompilationTargets()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 createFieldDecoration(Control control, String description)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 setVisible(boolean visible)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 modifyText(ModifyEvent e)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 appNameToProjectName(String appName)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. */ updateProjectLocation(String projectName)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 updateActivityNames(String name)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 widgetSelected(SelectionEvent e)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 getSelectedMinSdk()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 getSelectedTargetSdk()650 private int getSelectedTargetSdk() { 651 // +1: First API level (at index 0) is 1 652 return mTargetSdkCombo.getSelectionIndex() + 1; 653 } 654 setSelectedMinSdk(int api)655 private void setSelectedMinSdk(int api) { 656 mMinSdkCombo.select(api - 1); // -1: First API level (at index 0) is 1 657 } 658 setSelectedTargetSdk(int api)659 private void setSelectedTargetSdk(int api) { 660 mTargetSdkCombo.select(api - 1); // -1: First API level (at index 0) is 1 661 } 662 663 @Nullable getSelectedBuildTarget()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 suggestPackage(String original)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 appendPackage(StringBuilder sb, String string)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 widgetDefaultSelected(SelectionEvent e)711 public void widgetDefaultSelected(SelectionEvent e) { 712 } 713 714 // ---- Implements FocusListener ---- 715 716 @Override focusGained(FocusEvent e)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 focusLost(FocusEvent e)748 public void focusLost(FocusEvent e) { 749 mTipLabel.setText(""); 750 mHelpIcon.setVisible(false); 751 } 752 753 // Validation 754 validatePage()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 validateAppName()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 validateProjectName()860 private IStatus validateProjectName() { 861 IStatus status = ProjectNamePage.validateProjectName(mValues.projectName); 862 updateDecorator(mProjectDec, status, true); 863 864 return status; 865 } 866 validatePackageName()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 validateTheme()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 updateDecorator(ControlDecoration decorator, IStatus status, boolean hasInfo)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 setDecoratorType(ControlDecoration decorator, int severity)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