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 import static com.android.SdkConstants.CLASS_ACTIVITY; 19 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API; 20 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_BUILD_API; 21 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DEFAULT; 22 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_ID; 23 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_NAME; 24 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_PADDING; 25 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_WIDTH; 26 27 import com.android.annotations.NonNull; 28 import com.android.annotations.Nullable; 29 import com.android.ide.eclipse.adt.AdtPlugin; 30 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 31 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl; 32 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 33 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper; 34 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.ProjectCombo; 35 import com.android.ide.eclipse.adt.internal.wizards.templates.Parameter.Constraint; 36 import com.android.ide.eclipse.adt.internal.wizards.templates.Parameter.Type; 37 import com.android.tools.lint.detector.api.LintUtils; 38 import com.google.common.collect.Lists; 39 40 import org.eclipse.core.resources.IProject; 41 import org.eclipse.core.runtime.CoreException; 42 import org.eclipse.core.runtime.IStatus; 43 import org.eclipse.core.runtime.NullProgressMonitor; 44 import org.eclipse.core.runtime.Status; 45 import org.eclipse.jdt.core.Flags; 46 import org.eclipse.jdt.core.IJavaProject; 47 import org.eclipse.jdt.core.IType; 48 import org.eclipse.jdt.core.ITypeHierarchy; 49 import org.eclipse.jdt.core.JavaModelException; 50 import org.eclipse.jdt.core.search.IJavaSearchScope; 51 import org.eclipse.jdt.core.search.SearchEngine; 52 import org.eclipse.jdt.ui.IJavaElementSearchConstants; 53 import org.eclipse.jdt.ui.JavaUI; 54 import org.eclipse.jdt.ui.dialogs.ITypeInfoFilterExtension; 55 import org.eclipse.jdt.ui.dialogs.ITypeInfoRequestor; 56 import org.eclipse.jdt.ui.dialogs.TypeSelectionExtension; 57 import org.eclipse.jface.dialogs.IDialogConstants; 58 import org.eclipse.jface.dialogs.IInputValidator; 59 import org.eclipse.jface.dialogs.IMessageProvider; 60 import org.eclipse.jface.dialogs.ProgressMonitorDialog; 61 import org.eclipse.jface.fieldassist.ControlDecoration; 62 import org.eclipse.jface.fieldassist.FieldDecoration; 63 import org.eclipse.jface.fieldassist.FieldDecorationRegistry; 64 import org.eclipse.jface.wizard.WizardPage; 65 import org.eclipse.swt.SWT; 66 import org.eclipse.swt.events.FocusEvent; 67 import org.eclipse.swt.events.FocusListener; 68 import org.eclipse.swt.events.ModifyEvent; 69 import org.eclipse.swt.events.ModifyListener; 70 import org.eclipse.swt.events.SelectionEvent; 71 import org.eclipse.swt.events.SelectionListener; 72 import org.eclipse.swt.graphics.Image; 73 import org.eclipse.swt.layout.GridData; 74 import org.eclipse.swt.layout.GridLayout; 75 import org.eclipse.swt.widgets.Button; 76 import org.eclipse.swt.widgets.Combo; 77 import org.eclipse.swt.widgets.Composite; 78 import org.eclipse.swt.widgets.Control; 79 import org.eclipse.swt.widgets.Label; 80 import org.eclipse.swt.widgets.Shell; 81 import org.eclipse.swt.widgets.Text; 82 import org.eclipse.ui.dialogs.SelectionDialog; 83 import org.w3c.dom.Element; 84 import org.w3c.dom.Node; 85 import org.w3c.dom.NodeList; 86 87 import java.io.ByteArrayInputStream; 88 import java.util.HashMap; 89 import java.util.HashSet; 90 import java.util.List; 91 import java.util.Map; 92 import java.util.Set; 93 94 /** 95 * First wizard page in the "New Project From Template" wizard (which is parameterized 96 * via template.xml files) 97 */ 98 public class NewTemplatePage extends WizardPage 99 implements ModifyListener, SelectionListener, FocusListener { 100 /** The default width to use for the wizard page */ 101 static final int WIZARD_PAGE_WIDTH = 600; 102 103 private final NewTemplateWizardState mValues; 104 private final boolean mChooseProject; 105 private int mCustomMinSdk = -1; 106 private int mCustomBuildApi = -1; 107 private boolean mIgnore; 108 private boolean mShown; 109 private Control mFirst; 110 // TODO: Move decorators to the Parameter objects? 111 private Map<String, ControlDecoration> mDecorations = new HashMap<String, ControlDecoration>(); 112 private Label mHelpIcon; 113 private Label mTipLabel; 114 private ImageControl mPreview; 115 private Image mPreviewImage; 116 private boolean mDisposePreviewImage; 117 private ProjectCombo mProjectButton; 118 private StringEvaluator mEvaluator; 119 120 private TemplateMetadata mShowingTemplate; 121 122 /** 123 * Creates a new {@link NewTemplatePage} 124 * 125 * @param values the wizard state 126 * @param chooseProject whether the wizard should present a project chooser, 127 * and update {@code values}' project field 128 */ NewTemplatePage(NewTemplateWizardState values, boolean chooseProject)129 NewTemplatePage(NewTemplateWizardState values, boolean chooseProject) { 130 super("newTemplatePage"); //$NON-NLS-1$ 131 mValues = values; 132 mChooseProject = chooseProject; 133 } 134 135 /** 136 * @param minSdk a minimum SDK to use, provided chooseProject is false. If 137 * it is true, then the minimum SDK used for validation will be 138 * the one of the project 139 * @param buildApi the build API to use 140 */ setCustomMinSdk(int minSdk, int buildApi)141 void setCustomMinSdk(int minSdk, int buildApi) { 142 assert !mChooseProject; 143 //assert buildApi >= minSdk; 144 mCustomMinSdk = minSdk; 145 mCustomBuildApi = buildApi; 146 } 147 148 @Override createControl(Composite parent2)149 public void createControl(Composite parent2) { 150 Composite parent = new Composite(parent2, SWT.NULL); 151 setControl(parent); 152 GridLayout parentLayout = new GridLayout(3, false); 153 parentLayout.verticalSpacing = 0; 154 parentLayout.marginWidth = 0; 155 parentLayout.marginHeight = 0; 156 parentLayout.horizontalSpacing = 0; 157 parent.setLayout(parentLayout); 158 159 // Reserve enough width (since the panel is created lazily later) 160 Label label = new Label(parent, SWT.NONE); 161 GridData data = new GridData(); 162 data.widthHint = WIZARD_PAGE_WIDTH; 163 label.setLayoutData(data); 164 } 165 166 @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused onEnter()167 private void onEnter() { 168 TemplateMetadata template = mValues.getTemplateHandler().getTemplate(); 169 if (template == mShowingTemplate) { 170 return; 171 } 172 mShowingTemplate = template; 173 174 Composite parent = (Composite) getControl(); 175 176 Control[] children = parent.getChildren(); 177 if (children.length > 0) { 178 for (Control c : parent.getChildren()) { 179 c.dispose(); 180 } 181 for (ControlDecoration decoration : mDecorations.values()) { 182 decoration.dispose(); 183 } 184 mDecorations.clear(); 185 } 186 187 Composite container = new Composite(parent, SWT.NULL); 188 container.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); 189 GridLayout gl_container = new GridLayout(3, false); 190 gl_container.horizontalSpacing = 10; 191 container.setLayout(gl_container); 192 193 if (mChooseProject) { 194 // Project: [button] 195 String tooltip = "The Android Project where the new resource will be created."; 196 Label projectLabel = new Label(container, SWT.NONE); 197 projectLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 198 projectLabel.setText("Project:"); 199 projectLabel.setToolTipText(tooltip); 200 201 ProjectChooserHelper helper = 202 new ProjectChooserHelper(getShell(), null /* filter */); 203 mProjectButton = new ProjectCombo(helper, container, mValues.project); 204 mProjectButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); 205 mProjectButton.setToolTipText(tooltip); 206 mProjectButton.addSelectionListener(this); 207 208 //Label projectSeparator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL); 209 //projectSeparator.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 3, 1)); 210 } 211 212 // Add parameters 213 mFirst = null; 214 String thumb = null; 215 if (template != null) { 216 thumb = template.getThumbnailPath(); 217 String title = template.getTitle(); 218 if (title != null && !title.isEmpty()) { 219 setTitle(title); 220 } 221 String description = template.getDescription(); 222 if (description != null && !description.isEmpty()) { 223 setDescription(description); 224 } 225 226 Map<String, String> defaults = mValues.defaults; 227 Set<String> seen = null; 228 if (LintUtils.assertionsEnabled()) { 229 seen = new HashSet<String>(); 230 } 231 232 List<Parameter> parameters = template.getParameters(); 233 for (Parameter parameter : parameters) { 234 Parameter.Type type = parameter.type; 235 236 if (type == Parameter.Type.SEPARATOR) { 237 Label separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL); 238 separator.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 3, 1)); 239 continue; 240 } 241 242 String id = parameter.id; 243 assert id != null && !id.isEmpty() : ATTR_ID; 244 Object value = defaults.get(id); 245 if (value == null) { 246 value = parameter.value; 247 } 248 249 String name = parameter.name; 250 String help = parameter.help; 251 252 // Required 253 assert name != null && !name.isEmpty() : ATTR_NAME; 254 // Ensure id's are unique: 255 assert seen != null && seen.add(id) : id; 256 257 // Skip attributes that were already provided by the surrounding 258 // context. For example, when adding into an existing project, 259 // provide the minimum SDK automatically from the project. 260 if (mValues.hidden != null && mValues.hidden.contains(id)) { 261 continue; 262 } 263 264 switch (type) { 265 case STRING: { 266 // TODO: Look at the constraints to add validators here 267 // TODO: If I type.equals("layout") add resource validator for layout 268 // names 269 // TODO: If I type.equals("class") make class validator 270 271 // TODO: Handle package and id better later 272 Label label = new Label(container, SWT.NONE); 273 label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 274 1, 1)); 275 label.setText(name); 276 277 Text text = new Text(container, SWT.BORDER); 278 text.setData(parameter); 279 parameter.control = text; 280 281 if (parameter.constraints.contains(Constraint.EXISTS)) { 282 text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 283 1, 1)); 284 285 Button button = new Button(container, SWT.FLAT); 286 button.setData(parameter); 287 button.setText("..."); 288 button.addSelectionListener(this); 289 } else { 290 text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 291 2, 1)); 292 } 293 294 boolean hasValue = false; 295 if (value instanceof String) { 296 String stringValue = (String) value; 297 hasValue = !stringValue.isEmpty(); 298 text.setText(stringValue); 299 mValues.parameters.put(id, value); 300 } 301 302 if (!hasValue) { 303 if (parameter.constraints.contains(Constraint.EMPTY)) { 304 text.setMessage("Optional"); 305 } else if (parameter.constraints.contains(Constraint.NONEMPTY)) { 306 text.setMessage("Required"); 307 } 308 } 309 310 text.addModifyListener(this); 311 text.addFocusListener(this); 312 313 if (mFirst == null) { 314 mFirst = text; 315 } 316 317 if (help != null && !help.isEmpty()) { 318 text.setToolTipText(help); 319 ControlDecoration decoration = createFieldDecoration(id, text, help); 320 } 321 break; 322 } 323 case BOOLEAN: { 324 Label label = new Label(container, SWT.NONE); 325 label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 326 1, 1)); 327 328 Button checkBox = new Button(container, SWT.CHECK); 329 checkBox.setText(name); 330 checkBox.setData(parameter); 331 parameter.control = checkBox; 332 checkBox.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 333 2, 1)); 334 335 if (value instanceof Boolean) { 336 Boolean selected = (Boolean) value; 337 checkBox.setSelection(selected); 338 mValues.parameters.put(id, value); 339 } 340 341 checkBox.addSelectionListener(this); 342 checkBox.addFocusListener(this); 343 344 if (mFirst == null) { 345 mFirst = checkBox; 346 } 347 348 if (help != null && !help.isEmpty()) { 349 checkBox.setToolTipText(help); 350 ControlDecoration decoration = createFieldDecoration(id, checkBox, 351 help); 352 } 353 break; 354 } 355 case ENUM: { 356 Label label = new Label(container, SWT.NONE); 357 label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 358 1, 1)); 359 label.setText(name); 360 361 Combo combo = createOptionCombo(parameter, container, mValues.parameters, 362 this, this); 363 combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 364 2, 1)); 365 366 if (mFirst == null) { 367 mFirst = combo; 368 } 369 370 if (help != null && !help.isEmpty()) { 371 ControlDecoration decoration = createFieldDecoration(id, combo, help); 372 } 373 break; 374 } 375 case SEPARATOR: 376 // Already handled above 377 assert false : type; 378 break; 379 default: 380 assert false : type; 381 } 382 } 383 } 384 385 // Preview 386 mPreview = new ImageControl(parent, SWT.NONE, null); 387 mPreview.setDisposeImage(false); // Handled manually in this class 388 GridData gd_mImage = new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1); 389 gd_mImage.widthHint = PREVIEW_WIDTH + 2 * PREVIEW_PADDING; 390 mPreview.setLayoutData(gd_mImage); 391 392 Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); 393 GridData separatorData = new GridData(SWT.FILL, SWT.TOP, true, false, 3, 1); 394 separatorData.heightHint = 16; 395 separator.setLayoutData(separatorData); 396 397 // Generic help 398 mHelpIcon = new Label(parent, SWT.NONE); 399 mHelpIcon.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false, 1, 1)); 400 Image icon = IconFactory.getInstance().getIcon("quickfix"); 401 mHelpIcon.setImage(icon); 402 mHelpIcon.setVisible(false); 403 mTipLabel = new Label(parent, SWT.WRAP); 404 mTipLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); 405 406 setPreview(thumb); 407 408 parent.layout(true, true); 409 // TODO: This is a workaround for the fact that (at least on OSX) you end up 410 // with some visual artifacts from the control decorations in the upper left corner 411 // (outside the parent widget itself) from the initial control decoration placement 412 // prior to layout. Therefore, perform a redraw. A better solution would be to 413 // delay creation of the control decorations until layout has been performed. 414 // Let's do that soon. 415 parent.getParent().redraw(); 416 } 417 418 @NonNull createOptionCombo( @onNull Parameter parameter, @NonNull Composite container, @NonNull Map<String, Object> valueMap, @NonNull SelectionListener selectionListener, @NonNull FocusListener focusListener)419 static Combo createOptionCombo( 420 @NonNull Parameter parameter, 421 @NonNull Composite container, 422 @NonNull Map<String, Object> valueMap, 423 @NonNull SelectionListener selectionListener, 424 @NonNull FocusListener focusListener) { 425 Combo combo = new Combo(container, SWT.READ_ONLY); 426 427 List<Element> options = parameter.getOptions(); 428 assert options.size() > 0; 429 int selected = 0; 430 List<String> ids = Lists.newArrayList(); 431 List<Integer> minSdks = Lists.newArrayList(); 432 List<Integer> minBuildApis = Lists.newArrayList(); 433 List<String> labels = Lists.newArrayList(); 434 for (int i = 0, n = options.size(); i < n; i++) { 435 Element option = options.get(i); 436 String optionId = option.getAttribute(ATTR_ID); 437 assert optionId != null && !optionId.isEmpty() : ATTR_ID; 438 String isDefault = option.getAttribute(ATTR_DEFAULT); 439 if (isDefault != null && !isDefault.isEmpty() && 440 Boolean.valueOf(isDefault)) { 441 selected = i; 442 } 443 NodeList childNodes = option.getChildNodes(); 444 assert childNodes.getLength() == 1 && 445 childNodes.item(0).getNodeType() == Node.TEXT_NODE; 446 String optionLabel = childNodes.item(0).getNodeValue().trim(); 447 448 String minApiString = option.getAttribute(ATTR_MIN_API); 449 int minSdk = 1; 450 if (minApiString != null && !minApiString.isEmpty()) { 451 try { 452 minSdk = Integer.parseInt(minApiString); 453 } catch (NumberFormatException nufe) { 454 // Templates aren't allowed to contain codenames, should 455 // always be an integer 456 AdtPlugin.log(nufe, null); 457 minSdk = 1; 458 } 459 } 460 String minBuildApiString = option.getAttribute(ATTR_MIN_BUILD_API); 461 int minBuildApi = 1; 462 if (minBuildApiString != null && !minBuildApiString.isEmpty()) { 463 try { 464 minBuildApi = Integer.parseInt(minBuildApiString); 465 } catch (NumberFormatException nufe) { 466 // Templates aren't allowed to contain codenames, should 467 // always be an integer 468 AdtPlugin.log(nufe, null); 469 minBuildApi = 1; 470 } 471 } 472 minSdks.add(minSdk); 473 minBuildApis.add(minBuildApi); 474 ids.add(optionId); 475 labels.add(optionLabel); 476 } 477 combo.setData(parameter); 478 parameter.control = combo; 479 combo.setData(ATTR_ID, ids.toArray(new String[ids.size()])); 480 combo.setData(ATTR_MIN_API, minSdks.toArray(new Integer[minSdks.size()])); 481 combo.setData(ATTR_MIN_BUILD_API, minBuildApis.toArray( 482 new Integer[minBuildApis.size()])); 483 assert labels.size() > 0; 484 combo.setItems(labels.toArray(new String[labels.size()])); 485 combo.select(selected); 486 487 combo.addSelectionListener(selectionListener); 488 combo.addFocusListener(focusListener); 489 490 valueMap.put(parameter.id, ids.get(selected)); 491 492 if (parameter.help != null && !parameter.help.isEmpty()) { 493 combo.setToolTipText(parameter.help); 494 } 495 496 return combo; 497 } 498 setPreview(String thumb)499 private void setPreview(String thumb) { 500 Image oldImage = mPreviewImage; 501 boolean dispose = mDisposePreviewImage; 502 mPreviewImage = null; 503 504 if (thumb == null || thumb.isEmpty()) { 505 mPreviewImage = TemplateMetadata.getDefaultTemplateIcon(); 506 mDisposePreviewImage = false; 507 } else { 508 byte[] data = mValues.getTemplateHandler().readTemplateResource(thumb); 509 if (data != null) { 510 try { 511 mPreviewImage = new Image(getControl().getDisplay(), 512 new ByteArrayInputStream(data)); 513 mDisposePreviewImage = true; 514 } catch (Exception e) { 515 AdtPlugin.log(e, null); 516 } 517 } 518 if (mPreviewImage == null) { 519 return; 520 } 521 } 522 523 mPreview.setImage(mPreviewImage); 524 mPreview.fitToWidth(PREVIEW_WIDTH); 525 526 if (oldImage != null && dispose) { 527 oldImage.dispose(); 528 } 529 } 530 531 @Override dispose()532 public void dispose() { 533 super.dispose(); 534 535 if (mPreviewImage != null && mDisposePreviewImage) { 536 mDisposePreviewImage = false; 537 mPreviewImage.dispose(); 538 mPreviewImage = null; 539 } 540 } 541 createFieldDecoration(String id, Control control, String description)542 private ControlDecoration createFieldDecoration(String id, Control control, 543 String description) { 544 ControlDecoration decoration = new ControlDecoration(control, SWT.LEFT); 545 decoration.setMarginWidth(2); 546 FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault(). 547 getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION); 548 decoration.setImage(errorFieldIndicator.getImage()); 549 decoration.setDescriptionText(description); 550 control.setToolTipText(description); 551 mDecorations.put(id, decoration); 552 553 return decoration; 554 } 555 556 @Override isPageComplete()557 public boolean isPageComplete() { 558 // Force user to reach this page before hitting Finish 559 return mShown && super.isPageComplete(); 560 } 561 562 @Override setVisible(boolean visible)563 public void setVisible(boolean visible) { 564 if (visible) { 565 onEnter(); 566 } 567 568 super.setVisible(visible); 569 570 if (mFirst != null) { 571 mFirst.setFocus(); 572 } 573 574 if (visible) { 575 mShown = true; 576 } 577 578 validatePage(); 579 } 580 581 /** Returns the parameter associated with the given control */ 582 @Nullable getParameter(Control control)583 static Parameter getParameter(Control control) { 584 return (Parameter) control.getData(); 585 } 586 587 /** 588 * Returns the current string evaluator, if any 589 * 590 * @return the evaluator or null 591 */ 592 @Nullable getEvaluator()593 public StringEvaluator getEvaluator() { 594 return mEvaluator; 595 } 596 597 // ---- Validation ---- 598 validatePage()599 private void validatePage() { 600 int minSdk = getMinSdk(); 601 int buildApi = getBuildApi(); 602 IStatus status = mValues.getTemplateHandler().validateTemplate(minSdk, buildApi); 603 604 if (status == null || status.isOK()) { 605 if (mChooseProject && mValues.project == null) { 606 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 607 "Please select an Android project."); 608 } 609 } 610 611 for (Parameter parameter : mShowingTemplate.getParameters()) { 612 if (parameter.type == Parameter.Type.SEPARATOR) { 613 continue; 614 } 615 IInputValidator validator = parameter.getValidator(mValues.project); 616 if (validator != null) { 617 ControlDecoration decoration = mDecorations.get(parameter.id); 618 String value = parameter.value == null ? "" : parameter.value.toString(); 619 String error = validator.isValid(value); 620 if (error != null) { 621 IStatus s = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); 622 if (decoration != null) { 623 updateDecorator(decoration, s, parameter.help); 624 } 625 if (status == null || status.isOK()) { 626 status = s; 627 } 628 } else if (decoration != null) { 629 updateDecorator(decoration, null, parameter.help); 630 } 631 } 632 633 if (status == null || status.isOK()) { 634 if (parameter.control instanceof Combo) { 635 status = validateCombo(status, parameter, minSdk, buildApi); 636 } 637 } 638 } 639 640 setPageComplete(status == null || status.getSeverity() != IStatus.ERROR); 641 if (status != null) { 642 setMessage(status.getMessage(), 643 status.getSeverity() == IStatus.ERROR 644 ? IMessageProvider.ERROR : IMessageProvider.WARNING); 645 } else { 646 setErrorMessage(null); 647 setMessage(null); 648 } 649 } 650 651 /** Validates the given combo */ validateCombo(IStatus status, Parameter parameter, int minSdk, int buildApi)652 static IStatus validateCombo(IStatus status, Parameter parameter, int minSdk, int buildApi) { 653 Combo combo = (Combo) parameter.control; 654 int index = combo.getSelectionIndex(); 655 return validateCombo(status, parameter, index, minSdk, buildApi); 656 } 657 658 /** Validates the given combo assuming the value at the given index is chosen */ validateCombo(IStatus status, Parameter parameter, int index, int minSdk, int buildApi)659 static IStatus validateCombo(IStatus status, Parameter parameter, int index, 660 int minSdk, int buildApi) { 661 Combo combo = (Combo) parameter.control; 662 Integer[] optionIds = (Integer[]) combo.getData(ATTR_MIN_API); 663 // Check minSdk 664 if (index != -1 && index < optionIds.length) { 665 Integer requiredMinSdk = optionIds[index]; 666 if (requiredMinSdk > minSdk) { 667 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 668 String.format( 669 "%1$s \"%2$s\" requires a minimum SDK version of at " + 670 "least %3$d, and the current min version is %4$d", 671 parameter.name, combo.getItems()[index], requiredMinSdk, minSdk)); 672 } 673 } 674 675 // Check minimum build target 676 optionIds = (Integer[]) combo.getData(ATTR_MIN_BUILD_API); 677 if (index != -1 && index < optionIds.length) { 678 Integer requiredBuildApi = optionIds[index]; 679 if (requiredBuildApi > buildApi) { 680 status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 681 String.format( 682 "%1$s \"%2$s\" requires a build target API version of at " + 683 "least %3$d, and the current version is %4$d", 684 parameter.name, combo.getItems()[index], requiredBuildApi, buildApi)); 685 } 686 } 687 return status; 688 } 689 getMinSdk()690 private int getMinSdk() { 691 return mChooseProject ? mValues.getMinSdk() : mCustomMinSdk; 692 } 693 getBuildApi()694 private int getBuildApi() { 695 return mChooseProject ? mValues.getBuildApi() : mCustomBuildApi; 696 } 697 updateDecorator(ControlDecoration decorator, IStatus status, String help)698 private void updateDecorator(ControlDecoration decorator, IStatus status, String help) { 699 if (help != null && !help.isEmpty()) { 700 decorator.setDescriptionText(status != null ? status.getMessage() : help); 701 702 int severity = status != null ? status.getSeverity() : IStatus.OK; 703 String id; 704 if (severity == IStatus.ERROR) { 705 id = FieldDecorationRegistry.DEC_ERROR; 706 } else if (severity == IStatus.WARNING) { 707 id = FieldDecorationRegistry.DEC_WARNING; 708 } else { 709 id = FieldDecorationRegistry.DEC_INFORMATION; 710 } 711 FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault(). 712 getFieldDecoration(id); 713 decorator.setImage(errorFieldIndicator.getImage()); 714 } else { 715 if (status == null || status.isOK()) { 716 decorator.hide(); 717 } else { 718 decorator.show(); 719 } 720 } 721 } 722 723 // ---- Implements ModifyListener ---- 724 725 @Override modifyText(ModifyEvent e)726 public void modifyText(ModifyEvent e) { 727 if (mIgnore) { 728 return; 729 } 730 731 Object source = e.getSource(); 732 if (source instanceof Text) { 733 Text text = (Text) source; 734 editParameter(text, text.getText().trim()); 735 } 736 737 validatePage(); 738 } 739 740 // ---- Implements SelectionListener ---- 741 742 @Override widgetSelected(SelectionEvent e)743 public void widgetSelected(SelectionEvent e) { 744 if (mIgnore) { 745 return; 746 } 747 748 Object source = e.getSource(); 749 if (source == mProjectButton) { 750 mValues.project = mProjectButton.getSelectedProject(); 751 } else if (source instanceof Combo) { 752 Combo combo = (Combo) source; 753 String[] optionIds = (String[]) combo.getData(ATTR_ID); 754 int index = combo.getSelectionIndex(); 755 if (index != -1 && index < optionIds.length) { 756 String optionId = optionIds[index]; 757 editParameter(combo, optionId); 758 TemplateMetadata template = mValues.getTemplateHandler().getTemplate(); 759 if (template != null) { 760 setPreview(template.getThumbnailPath()); 761 } 762 } 763 } else if (source instanceof Button) { 764 Button button = (Button) source; 765 Parameter parameter = (Parameter) button.getData(); 766 if (parameter.type == Type.BOOLEAN) { 767 // Checkbox parameter 768 editParameter(button, button.getSelection()); 769 770 TemplateMetadata template = mValues.getTemplateHandler().getTemplate(); 771 if (template != null) { 772 setPreview(template.getThumbnailPath()); 773 } 774 } else { 775 // Choose button for some other parameter, usually a text 776 String activity = chooseActivity(); 777 if (activity != null) { 778 setValue(parameter, activity); 779 } 780 } 781 } 782 783 validatePage(); 784 } 785 chooseActivity()786 private String chooseActivity() { 787 try { 788 // Compute a search scope: We need to merge all the subclasses 789 // android.app.Fragment and android.support.v4.app.Fragment 790 IJavaSearchScope scope = SearchEngine.createWorkspaceScope(); 791 IProject project = mValues.project; 792 IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); 793 IType activityType = null; 794 795 if (javaProject != null) { 796 activityType = javaProject.findType(CLASS_ACTIVITY); 797 } 798 if (activityType == null) { 799 IJavaProject[] projects = BaseProjectHelper.getAndroidProjects(null); 800 for (IJavaProject p : projects) { 801 activityType = p.findType(CLASS_ACTIVITY); 802 if (activityType != null) { 803 break; 804 } 805 } 806 } 807 if (activityType != null) { 808 NullProgressMonitor monitor = new NullProgressMonitor(); 809 ITypeHierarchy hierarchy = activityType.newTypeHierarchy(monitor); 810 IType[] classes = hierarchy.getAllSubtypes(activityType); 811 scope = SearchEngine.createJavaSearchScope(classes, IJavaSearchScope.SOURCES); 812 } 813 814 Shell parent = AdtPlugin.getShell(); 815 final SelectionDialog dialog = JavaUI.createTypeDialog( 816 parent, 817 new ProgressMonitorDialog(parent), 818 scope, 819 IJavaElementSearchConstants.CONSIDER_CLASSES, false, 820 // Use ? as a default filter to fill dialog with matches 821 "?", //$NON-NLS-1$ 822 new TypeSelectionExtension() { 823 @Override 824 public ITypeInfoFilterExtension getFilterExtension() { 825 return new ITypeInfoFilterExtension() { 826 @Override 827 public boolean select(ITypeInfoRequestor typeInfoRequestor) { 828 int modifiers = typeInfoRequestor.getModifiers(); 829 if (!Flags.isPublic(modifiers) 830 || Flags.isInterface(modifiers) 831 || Flags.isEnum(modifiers)) { 832 return false; 833 } 834 return true; 835 } 836 }; 837 } 838 }); 839 840 dialog.setTitle("Choose Activity Class"); 841 dialog.setMessage("Select an Activity class (? = any character, * = any string):"); 842 if (dialog.open() == IDialogConstants.CANCEL_ID) { 843 return null; 844 } 845 846 Object[] types = dialog.getResult(); 847 if (types != null && types.length > 0) { 848 return ((IType) types[0]).getFullyQualifiedName(); 849 } 850 } catch (JavaModelException e) { 851 AdtPlugin.log(e, null); 852 } catch (CoreException e) { 853 AdtPlugin.log(e, null); 854 } 855 return null; 856 } 857 editParameter(Control control, Object value)858 private void editParameter(Control control, Object value) { 859 Parameter parameter = getParameter(control); 860 if (parameter != null) { 861 String id = parameter.id; 862 parameter.value = value; 863 parameter.edited = value != null && !value.toString().isEmpty(); 864 mValues.parameters.put(id, value); 865 866 // Update dependent variables, if any 867 List<Parameter> parameters = mShowingTemplate.getParameters(); 868 for (Parameter p : parameters) { 869 if (p == parameter || p.suggest == null || p.edited || 870 p.type == Parameter.Type.SEPARATOR) { 871 continue; 872 } 873 if (!p.suggest.contains(id)) { 874 continue; 875 } 876 877 try { 878 if (mEvaluator == null) { 879 mEvaluator = new StringEvaluator(); 880 } 881 String updated = mEvaluator.evaluate(p.suggest, parameters); 882 if (updated != null && !updated.equals(p.value)) { 883 setValue(p, updated); 884 } 885 } catch (Throwable t) { 886 // Pass: Ignore updating if something wrong happens 887 t.printStackTrace(); // during development only 888 } 889 } 890 } 891 } 892 setValue(Parameter p, String value)893 private void setValue(Parameter p, String value) { 894 p.value = value; 895 mValues.parameters.put(p.id, value); 896 897 // Update form widgets 898 boolean prevIgnore = mIgnore; 899 try { 900 mIgnore = true; 901 if (p.control instanceof Text) { 902 ((Text) p.control).setText(value); 903 } else if (p.control instanceof Button) { 904 // TODO: Handle 905 } else if (p.control instanceof Combo) { 906 // TODO: Handle 907 } else if (p.control != null) { 908 assert false : p.control; 909 } 910 } finally { 911 mIgnore = prevIgnore; 912 } 913 } 914 915 @Override widgetDefaultSelected(SelectionEvent e)916 public void widgetDefaultSelected(SelectionEvent e) { 917 } 918 919 // ---- Implements FocusListener ---- 920 921 @Override focusGained(FocusEvent e)922 public void focusGained(FocusEvent e) { 923 Object source = e.getSource(); 924 String tip = ""; 925 926 if (source instanceof Control) { 927 Control control = (Control) source; 928 Parameter parameter = getParameter(control); 929 if (parameter != null) { 930 ControlDecoration decoration = mDecorations.get(parameter.id); 931 if (decoration != null) { 932 tip = decoration.getDescriptionText(); 933 } 934 } 935 } 936 937 mTipLabel.setText(tip); 938 mHelpIcon.setVisible(tip.length() > 0); 939 } 940 941 @Override focusLost(FocusEvent e)942 public void focusLost(FocusEvent e) { 943 mTipLabel.setText(""); 944 mHelpIcon.setVisible(false); 945 } 946 } 947