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