• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
17 
18 package com.android.ide.eclipse.adt.internal.wizards.newxmlfile;
19 
20 import static com.android.AndroidConstants.RES_QUALIFIER_SEP;
21 import static com.android.ide.common.layout.LayoutConstants.HORIZONTAL_SCROLL_VIEW;
22 import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT;
23 import static com.android.ide.common.layout.LayoutConstants.SCROLL_VIEW;
24 import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT;
25 import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT;
26 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
27 import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP_CHAR;
28 import static com.android.ide.eclipse.adt.internal.wizards.newxmlfile.ChooseConfigurationPage.RES_FOLDER_ABS;
29 
30 import com.android.ide.common.resources.configuration.FolderConfiguration;
31 import com.android.ide.common.resources.configuration.ResourceQualifier;
32 import com.android.ide.eclipse.adt.AdtConstants;
33 import com.android.ide.eclipse.adt.AdtPlugin;
34 import com.android.ide.eclipse.adt.AdtUtils;
35 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
36 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
37 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
38 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
39 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
40 import com.android.ide.eclipse.adt.internal.editors.menu.descriptors.MenuDescriptors;
41 import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors;
42 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
43 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
44 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
45 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.ProjectCombo;
46 import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
47 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
48 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
49 import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
50 import com.android.resources.ResourceFolderType;
51 import com.android.sdklib.IAndroidTarget;
52 import com.android.sdklib.SdkConstants;
53 import com.android.util.Pair;
54 
55 import org.eclipse.core.resources.IFile;
56 import org.eclipse.core.resources.IProject;
57 import org.eclipse.core.resources.IResource;
58 import org.eclipse.core.runtime.CoreException;
59 import org.eclipse.core.runtime.IAdaptable;
60 import org.eclipse.core.runtime.IPath;
61 import org.eclipse.core.runtime.IStatus;
62 import org.eclipse.jdt.core.IJavaProject;
63 import org.eclipse.jface.dialogs.IMessageProvider;
64 import org.eclipse.jface.viewers.ArrayContentProvider;
65 import org.eclipse.jface.viewers.ColumnLabelProvider;
66 import org.eclipse.jface.viewers.IBaseLabelProvider;
67 import org.eclipse.jface.viewers.IStructuredSelection;
68 import org.eclipse.jface.viewers.TableViewer;
69 import org.eclipse.jface.wizard.WizardPage;
70 import org.eclipse.swt.SWT;
71 import org.eclipse.swt.events.ModifyEvent;
72 import org.eclipse.swt.events.ModifyListener;
73 import org.eclipse.swt.events.SelectionAdapter;
74 import org.eclipse.swt.events.SelectionEvent;
75 import org.eclipse.swt.graphics.Image;
76 import org.eclipse.swt.layout.GridData;
77 import org.eclipse.swt.layout.GridLayout;
78 import org.eclipse.swt.widgets.Combo;
79 import org.eclipse.swt.widgets.Composite;
80 import org.eclipse.swt.widgets.Label;
81 import org.eclipse.swt.widgets.Table;
82 import org.eclipse.swt.widgets.Text;
83 import org.eclipse.ui.IEditorPart;
84 import org.eclipse.ui.IWorkbenchPage;
85 import org.eclipse.ui.IWorkbenchWindow;
86 import org.eclipse.ui.PlatformUI;
87 import org.eclipse.ui.part.FileEditorInput;
88 
89 import java.util.ArrayList;
90 import java.util.Collections;
91 import java.util.HashSet;
92 import java.util.List;
93 
94 /**
95  * This is the first page of the {@link NewXmlFileWizard} which provides the ability to create
96  * skeleton XML resources files for Android projects.
97  * <p/>
98  * This page is used to select the project, resource type and file name.
99  */
100 class NewXmlFileCreationPage extends WizardPage {
101 
102     @Override
setVisible(boolean visible)103     public void setVisible(boolean visible) {
104         super.setVisible(visible);
105         // Ensure the initial focus is in the Name field; you usually don't need
106         // to edit the default text field (the project name)
107         if (visible && mFileNameTextField != null) {
108             mFileNameTextField.setFocus();
109         }
110 
111         validatePage();
112     }
113 
114     /**
115      * Information on one type of resource that can be created (e.g. menu, pref, layout, etc.)
116      */
117     static class TypeInfo {
118         private final String mUiName;
119         private final ResourceFolderType mResFolderType;
120         private final String mTooltip;
121         private final Object mRootSeed;
122         private ArrayList<String> mRoots = new ArrayList<String>();
123         private final String mXmlns;
124         private final String mDefaultAttrs;
125         private final String mDefaultRoot;
126         private final int mTargetApiLevel;
127 
TypeInfo(String uiName, String tooltip, ResourceFolderType resFolderType, Object rootSeed, String defaultRoot, String xmlns, String defaultAttrs, int targetApiLevel)128         public TypeInfo(String uiName,
129                         String tooltip,
130                         ResourceFolderType resFolderType,
131                         Object rootSeed,
132                         String defaultRoot,
133                         String xmlns,
134                         String defaultAttrs,
135                         int targetApiLevel) {
136             mUiName = uiName;
137             mResFolderType = resFolderType;
138             mTooltip = tooltip;
139             mRootSeed = rootSeed;
140             mDefaultRoot = defaultRoot;
141             mXmlns = xmlns;
142             mDefaultAttrs = defaultAttrs;
143             mTargetApiLevel = targetApiLevel;
144         }
145 
146         /** Returns the UI name for the resource type. Unique. Never null. */
getUiName()147         String getUiName() {
148             return mUiName;
149         }
150 
151         /** Returns the tooltip for the resource type. Can be null. */
getTooltip()152         String getTooltip() {
153             return mTooltip;
154         }
155 
156         /**
157          * Returns the name of the {@link ResourceFolderType}.
158          * Never null but not necessarily unique,
159          * e.g. two types use  {@link ResourceFolderType#XML}.
160          */
getResFolderName()161         String getResFolderName() {
162             return mResFolderType.getName();
163         }
164 
165         /**
166          * Returns the matching {@link ResourceFolderType}.
167          * Never null but not necessarily unique,
168          * e.g. two types use  {@link ResourceFolderType#XML}.
169          */
getResFolderType()170         ResourceFolderType getResFolderType() {
171             return mResFolderType;
172         }
173 
174         /**
175          * Returns the seed used to fill the root element values.
176          * The seed might be either a String, a String array, an {@link ElementDescriptor},
177          * a {@link DocumentDescriptor} or null.
178          */
getRootSeed()179         Object getRootSeed() {
180             return mRootSeed;
181         }
182 
183         /**
184          * Returns the default root element that should be selected by default. Can be
185          * null.
186          *
187          * @param project the associated project, or null if not known
188          */
getDefaultRoot(IProject project)189         String getDefaultRoot(IProject project) {
190             return mDefaultRoot;
191         }
192 
193         /**
194          * Returns the list of all possible root elements for the resource type.
195          * This can be an empty ArrayList but not null.
196          * <p/>
197          * TODO: the root list SHOULD depend on the currently selected project, to include
198          * custom classes.
199          */
getRoots()200         ArrayList<String> getRoots() {
201             return mRoots;
202         }
203 
204         /**
205          * If the generated resource XML file requires an "android" XMLNS, this should be set
206          * to {@link SdkConstants#NS_RESOURCES}. When it is null, no XMLNS is generated.
207          */
getXmlns()208         String getXmlns() {
209             return mXmlns;
210         }
211 
212         /**
213          * When not null, this represent extra attributes that must be specified in the
214          * root element of the generated XML file. When null, no extra attributes are inserted.
215          *
216          * @param project the project to get the attributes for
217          * @param root the selected root element string, never null
218          */
getDefaultAttrs(IProject project, String root)219         String getDefaultAttrs(IProject project, String root) {
220             return mDefaultAttrs;
221         }
222 
223         /**
224          * When not null, represents an extra string that should be written inside
225          * the element when constructed
226          *
227          * @param project the project to get the child content for
228          * @param root the chosen root element
229          * @return a string to be written inside the root element, or null if nothing
230          */
getChild(IProject project, String root)231         String getChild(IProject project, String root) {
232             return null;
233         }
234 
235         /**
236          * The minimum API level required by the current SDK target to support this feature.
237          *
238          * @return the minimum API level
239          */
getTargetApiLevel()240         public int getTargetApiLevel() {
241             return mTargetApiLevel;
242         }
243     }
244 
245     /**
246      * TypeInfo, information for each "type" of file that can be created.
247      */
248     private static final TypeInfo[] sTypes = {
249         new TypeInfo(
250                 "Layout",                                                   // UI name
251                 "An XML file that describes a screen layout.",              // tooltip
252                 ResourceFolderType.LAYOUT,                                  // folder type
253                 AndroidTargetData.DESCRIPTOR_LAYOUT,                        // root seed
254                 LINEAR_LAYOUT,                                              // default root
255                 SdkConstants.NS_RESOURCES,                                  // xmlns
256                 "",                                                         // not used, see below
257                 1                                                           // target API level
258                 ) {
259 
260                 @Override
261                 String getDefaultRoot(IProject project) {
262                     // TODO: Use GridLayout by default for new SDKs
263                     // (when we've ironed out all the usability issues)
264                     //Sdk currentSdk = Sdk.getCurrent();
265                     //if (project != null && currentSdk != null) {
266                     //    IAndroidTarget target = currentSdk.getTarget(project);
267                     //    // fill_parent was renamed match_parent in API level 8
268                     //    if (target != null && target.getVersion().getApiLevel() >= 13) {
269                     //        return GRID_LAYOUT;
270                     //    }
271                     //}
272 
273                     return LINEAR_LAYOUT;
274                 };
275 
276                 // The default attributes must be determined dynamically since whether
277                 // we use match_parent or fill_parent depends on the API level of the
278                 // project
279                 @Override
280                 String getDefaultAttrs(IProject project, String root) {
281                     Sdk currentSdk = Sdk.getCurrent();
282                     String fill = VALUE_FILL_PARENT;
283                     if (currentSdk != null) {
284                         IAndroidTarget target = currentSdk.getTarget(project);
285                         // fill_parent was renamed match_parent in API level 8
286                         if (target != null && target.getVersion().getApiLevel() >= 8) {
287                             fill = VALUE_MATCH_PARENT;
288                         }
289                     }
290 
291                     // Only set "vertical" orientation of LinearLayouts by default;
292                     // for GridLayouts for example we want to rely on the real default
293                     // of the layout
294                     String size = String.format(
295                             "android:layout_width=\"%1$s\"\n"        //$NON-NLS-1$
296                             + "android:layout_height=\"%2$s\"",        //$NON-NLS-1$
297                             fill, fill);
298                     if (LINEAR_LAYOUT.equals(root)) {
299                         return "android:orientation=\"vertical\"\n" + size; //$NON-NLS-1$
300                     } else {
301                         return size;
302                     }
303                 }
304 
305                 @Override
306                 String getChild(IProject project, String root) {
307                     // Create vertical linear layouts inside new scroll views
308                     if (SCROLL_VIEW.equals(root) || HORIZONTAL_SCROLL_VIEW.equals(root)) {
309                         return "    <LinearLayout "         //$NON-NLS-1$
310                             + getDefaultAttrs(project, root).replace('\n', ' ')
311                             + "></LinearLayout>\n";         //$NON-NLS-1$
312                     }
313                     return null;
314                 }
315         },
316         new TypeInfo("Values",                                              // UI name
317                 "An XML file with simple values: colors, strings, dimensions, etc.", // tooltip
318                 ResourceFolderType.VALUES,                                  // folder type
319                 ResourcesDescriptors.ROOT_ELEMENT,                          // root seed
320                 null,                                                       // default root
321                 null,                                                       // xmlns
322                 null,                                                       // default attributes
323                 1                                                           // target API level
324                 ),
325         new TypeInfo("Drawable",                                            // UI name
326                 "An XML file that describes a drawable.",                   // tooltip
327                 ResourceFolderType.DRAWABLE,                                // folder type
328                 AndroidTargetData.DESCRIPTOR_DRAWABLE,                      // root seed
329                 null,                                                       // default root
330                 SdkConstants.NS_RESOURCES,                                  // xmlns
331                 null,                                                       // default attributes
332                 1                                                           // target API level
333                 ),
334         new TypeInfo("Menu",                                                // UI name
335                 "An XML file that describes an menu.",                      // tooltip
336                 ResourceFolderType.MENU,                                    // folder type
337                 MenuDescriptors.MENU_ROOT_ELEMENT,                          // root seed
338                 null,                                                       // default root
339                 SdkConstants.NS_RESOURCES,                                  // xmlns
340                 null,                                                       // default attributes
341                 1                                                           // target API level
342                 ),
343         new TypeInfo("Color List",                                          // UI name
344                 "An XML file that describes a color state list.",           // tooltip
345                 ResourceFolderType.COLOR,                                   // folder type
346                 AndroidTargetData.DESCRIPTOR_COLOR,                         // root seed
347                 "selector",  //$NON-NLS-1$                                  // default root
348                 SdkConstants.NS_RESOURCES,                                  // xmlns
349                 null,                                                       // default attributes
350                 1                                                           // target API level
351                 ),
352         new TypeInfo("Property Animation",                                  // UI name
353                 "An XML file that describes a property animation",          // tooltip
354                 ResourceFolderType.ANIMATOR,                                // folder type
355                 AndroidTargetData.DESCRIPTOR_ANIMATOR,                      // root seed
356                 "set", //$NON-NLS-1$                                        // default root
357                 SdkConstants.NS_RESOURCES,                                  // xmlns
358                 null,                                                       // default attributes
359                 11                                                          // target API level
360                 ),
361         new TypeInfo("Tween Animation",                                     // UI name
362                 "An XML file that describes a tween animation.",            // tooltip
363                 ResourceFolderType.ANIM,                                    // folder type
364                 AndroidTargetData.DESCRIPTOR_ANIM,                          // root seed
365                 "set", //$NON-NLS-1$                                        // default root
366                 null,                                                       // xmlns
367                 null,                                                       // default attributes
368                 1                                                           // target API level
369                 ),
370         new TypeInfo("AppWidget Provider",                                  // UI name
371                 "An XML file that describes a widget provider.",            // tooltip
372                 ResourceFolderType.XML,                                     // folder type
373                 AndroidTargetData.DESCRIPTOR_APPWIDGET_PROVIDER,            // root seed
374                 null,                                                       // default root
375                 SdkConstants.NS_RESOURCES,                                  // xmlns
376                 null,                                                       // default attributes
377                 3                                                           // target API level
378                 ),
379         new TypeInfo("Preference",                                          // UI name
380                 "An XML file that describes preferences.",                  // tooltip
381                 ResourceFolderType.XML,                                     // folder type
382                 AndroidTargetData.DESCRIPTOR_PREFERENCES,                   // root seed
383                 SdkConstants.CLASS_NAME_PREFERENCE_SCREEN,                  // default root
384                 SdkConstants.NS_RESOURCES,                                  // xmlns
385                 null,                                                       // default attributes
386                 1                                                           // target API level
387                 ),
388         new TypeInfo("Searchable",                                          // UI name
389                 "An XML file that describes a searchable.",                 // tooltip
390                 ResourceFolderType.XML,                                     // folder type
391                 AndroidTargetData.DESCRIPTOR_SEARCHABLE,                    // root seed
392                 null,                                                       // default root
393                 SdkConstants.NS_RESOURCES,                                  // xmlns
394                 null,                                                       // default attributes
395                 1                                                           // target API level
396                 ),
397         // Still missing: Interpolator, Raw and Mipmap. Raw should probably never be in
398         // this menu since it's not often used for creating XML files.
399     };
400 
401     private NewXmlFileWizard.Values mValues;
402     private ProjectCombo mProjectButton;
403     private Text mFileNameTextField;
404     private Combo mTypeCombo;
405     private IStructuredSelection mInitialSelection;
406     private ResourceFolderType mInitialFolderType;
407     private boolean mInternalTypeUpdate;
408     private TargetChangeListener mSdkTargetChangeListener;
409     private Table mRootTable;
410     private TableViewer mRootTableViewer;
411 
412     // --- UI creation ---
413 
414     /**
415      * Constructs a new {@link NewXmlFileCreationPage}.
416      * <p/>
417      * Called by {@link NewXmlFileWizard#createMainPage}.
418      */
NewXmlFileCreationPage(String pageName, NewXmlFileWizard.Values values)419     protected NewXmlFileCreationPage(String pageName, NewXmlFileWizard.Values values) {
420         super(pageName);
421         mValues = values;
422         setPageComplete(false);
423     }
424 
setInitialSelection(IStructuredSelection initialSelection)425     public void setInitialSelection(IStructuredSelection initialSelection) {
426         mInitialSelection = initialSelection;
427     }
428 
setInitialFolderType(ResourceFolderType initialType)429     public void setInitialFolderType(ResourceFolderType initialType) {
430         mInitialFolderType = initialType;
431     }
432 
433     /**
434      * Called by the parent Wizard to create the UI for this Wizard Page.
435      *
436      * {@inheritDoc}
437      *
438      * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
439      */
440     @SuppressWarnings("unused") // SWT constructors have side effects, they aren't unused
createControl(Composite parent)441     public void createControl(Composite parent) {
442         // This UI is maintained with WindowBuilder.
443 
444         Composite composite = new Composite(parent, SWT.NULL);
445         composite.setLayout(new GridLayout(2, false /*makeColumnsEqualWidth*/));
446         composite.setLayoutData(new GridData(GridData.FILL_BOTH));
447 
448         // label before type radios
449         Label typeLabel = new Label(composite, SWT.NONE);
450         typeLabel.setText("Resource Type:");
451 
452         mTypeCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
453         mTypeCombo.setToolTipText("What type of resource would you like to create?");
454         mTypeCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
455         if (mInitialFolderType != null) {
456             mTypeCombo.setEnabled(false);
457         }
458         mTypeCombo.addSelectionListener(new SelectionAdapter() {
459             @Override
460             public void widgetSelected(SelectionEvent e) {
461                 TypeInfo type = getSelectedType();
462                 if (type != null) {
463                     onSelectType(type);
464                 }
465             }
466         });
467 
468         // separator
469         Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
470         GridData gd2 = new GridData(GridData.GRAB_HORIZONTAL);
471         gd2.horizontalAlignment = SWT.FILL;
472         gd2.horizontalSpan = 2;
473         separator.setLayoutData(gd2);
474 
475         // Project: [button]
476         String tooltip = "The Android Project where the new resource file will be created.";
477         Label projectLabel = new Label(composite, SWT.NONE);
478         projectLabel.setText("Project:");
479         projectLabel.setToolTipText(tooltip);
480 
481         ProjectChooserHelper helper =
482                 new ProjectChooserHelper(getShell(), null /* filter */);
483 
484         mProjectButton = new ProjectCombo(helper, composite, mValues.project);
485         mProjectButton.setToolTipText(tooltip);
486         mProjectButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
487         mProjectButton.addSelectionListener(new SelectionAdapter() {
488             @Override
489             public void widgetSelected(SelectionEvent e) {
490                 IProject project = mProjectButton.getSelectedProject();
491                 if (project != mValues.project) {
492                     changeProject(project);
493                 }
494             };
495         });
496 
497         // Filename: [text]
498         Label fileLabel = new Label(composite, SWT.NONE);
499         fileLabel.setText("File:");
500         fileLabel.setToolTipText("The name of the resource file to create.");
501 
502         mFileNameTextField = new Text(composite, SWT.BORDER);
503         mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
504         mFileNameTextField.setToolTipText(tooltip);
505         mFileNameTextField.addModifyListener(new ModifyListener() {
506             public void modifyText(ModifyEvent e) {
507                 mValues.name = mFileNameTextField.getText();
508                 validatePage();
509             }
510         });
511 
512         // separator
513         Label rootSeparator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
514         GridData gd = new GridData(GridData.GRAB_HORIZONTAL);
515         gd.horizontalAlignment = SWT.FILL;
516         gd.horizontalSpan = 2;
517         rootSeparator.setLayoutData(gd);
518 
519         // Root Element:
520         // [TableViewer]
521         Label rootLabel = new Label(composite, SWT.NONE);
522         rootLabel.setText("Root Element:");
523         new Label(composite, SWT.NONE);
524 
525         mRootTableViewer = new TableViewer(composite, SWT.BORDER | SWT.FULL_SELECTION);
526         mRootTable = mRootTableViewer.getTable();
527         GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
528         tableGridData.heightHint = 200;
529         mRootTable.setLayoutData(tableGridData);
530 
531         setControl(composite);
532 
533         // Update state the first time
534         setErrorMessage(null);
535         setMessage(null);
536 
537         initializeFromSelection(mInitialSelection);
538         updateAvailableTypes();
539         initializeFromFixedType();
540         initializeRootValues();
541         installTargetChangeListener();
542 
543         initialSelectType();
544         validatePage();
545     }
546 
initialSelectType()547     private void initialSelectType() {
548         TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
549         int typeIndex = getTypeComboIndex(mValues.type);
550         if (typeIndex == -1) {
551             typeIndex = 0;
552         } else {
553             assert mValues.type == types[typeIndex];
554         }
555         mTypeCombo.select(typeIndex);
556         onSelectType(types[typeIndex]);
557         updateRootCombo(types[typeIndex]);
558     }
559 
installTargetChangeListener()560     private void installTargetChangeListener() {
561         mSdkTargetChangeListener = new TargetChangeListener() {
562             @Override
563             public IProject getProject() {
564                 return mValues.project;
565             }
566 
567             @Override
568             public void reload() {
569                 if (mValues.project != null) {
570                     changeProject(mValues.project);
571                 }
572             }
573         };
574 
575         AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
576     }
577 
578     @Override
dispose()579     public void dispose() {
580 
581         if (mSdkTargetChangeListener != null) {
582             AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
583             mSdkTargetChangeListener = null;
584         }
585 
586         super.dispose();
587     }
588 
589     /**
590      * Returns the selected root element string, if any.
591      *
592      * @return The selected root element string or null.
593      */
getRootElement()594     public String getRootElement() {
595         int index = mRootTable.getSelectionIndex();
596         if (index >= 0) {
597             Object[] roots = (Object[]) mRootTableViewer.getInput();
598             return roots[index].toString();
599         }
600         return null;
601     }
602 
603     /**
604      * Called by {@link NewXmlFileWizard} to initialize the page with the selection
605      * received by the wizard -- typically the current user workbench selection.
606      * <p/>
607      * Things we expect to find out from the selection:
608      * <ul>
609      * <li>The project name, valid if it's an android nature.</li>
610      * <li>The current folder, valid if it's a folder under /res</li>
611      * <li>An existing filename, in which case the user will be asked whether to override it.</li>
612      * </ul>
613      * <p/>
614      * The selection can also be set to a {@link Pair} of {@link IProject} and a workspace
615      * resource path (where the resource path does not have to exist yet, such as res/anim/).
616      *
617      * @param selection The selection when the wizard was initiated.
618      */
initializeFromSelection(IStructuredSelection selection)619     private boolean initializeFromSelection(IStructuredSelection selection) {
620         if (selection == null) {
621             return false;
622         }
623 
624         // Find the best match in the element list. In case there are multiple selected elements
625         // select the one that provides the most information and assign them a score,
626         // e.g. project=1 + folder=2 + file=4.
627         IProject targetProject = null;
628         String targetWsFolderPath = null;
629         String targetFileName = null;
630         int targetScore = 0;
631         for (Object element : selection.toList()) {
632             if (element instanceof IAdaptable) {
633                 IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
634                 IProject project = res != null ? res.getProject() : null;
635 
636                 // Is this an Android project?
637                 try {
638                     if (project == null || !project.hasNature(AdtConstants.NATURE_DEFAULT)) {
639                         continue;
640                     }
641                 } catch (CoreException e) {
642                     // checking the nature failed, ignore this resource
643                     continue;
644                 }
645 
646                 int score = 1; // we have a valid project at least
647 
648                 IPath wsFolderPath = null;
649                 String fileName = null;
650                 assert res != null; // Eclipse incorrectly thinks res could be null, so tell it no
651                 if (res.getType() == IResource.FOLDER) {
652                     wsFolderPath = res.getProjectRelativePath();
653                 } else if (res.getType() == IResource.FILE) {
654                     if (AdtUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) {
655                         fileName = res.getName();
656                     }
657                     wsFolderPath = res.getParent().getProjectRelativePath();
658                 }
659 
660                 // Disregard this folder selection if it doesn't point to /res/something
661                 if (wsFolderPath != null &&
662                         wsFolderPath.segmentCount() > 1 &&
663                         SdkConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) {
664                     score += 2;
665                 } else {
666                     wsFolderPath = null;
667                     fileName = null;
668                 }
669 
670                 score += fileName != null ? 4 : 0;
671 
672                 if (score > targetScore) {
673                     targetScore = score;
674                     targetProject = project;
675                     targetWsFolderPath = wsFolderPath != null ? wsFolderPath.toString() : null;
676                     targetFileName = fileName;
677                 }
678             } else if (element instanceof Pair<?,?>) {
679                 // Pair of Project/String
680                 @SuppressWarnings("unchecked")
681                 Pair<IProject,String> pair = (Pair<IProject,String>)element;
682                 targetScore = 1;
683                 targetProject = pair.getFirst();
684                 targetWsFolderPath = pair.getSecond();
685                 targetFileName = "";
686             }
687         }
688 
689         if (targetProject == null) {
690             // Try to figure out the project from the active editor
691             IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
692             if (window != null) {
693                 IWorkbenchPage page = window.getActivePage();
694                 if (page != null) {
695                     IEditorPart activeEditor = page.getActiveEditor();
696                     if (activeEditor instanceof AndroidXmlEditor) {
697                         Object input = ((AndroidXmlEditor) activeEditor).getEditorInput();
698                         if (input instanceof FileEditorInput) {
699                             FileEditorInput fileInput = (FileEditorInput) input;
700                             targetScore = 1;
701                             IFile file = fileInput.getFile();
702                             targetProject = file.getProject();
703                             IPath path = file.getParent().getProjectRelativePath();
704                             targetWsFolderPath = path != null ? path.toString() : null;
705                         }
706                     }
707                 }
708             }
709         }
710 
711         if (targetProject == null) {
712             // If we didn't find a default project based on the selection, check how many
713             // open Android projects we can find in the current workspace. If there's only
714             // one, we'll just select it by default.
715 
716             IJavaProject[] projects = BaseProjectHelper.getAndroidProjects(new IProjectFilter() {
717                 public boolean accept(IProject project) {
718                     return project.isAccessible();
719                 }
720             });
721 
722             if (projects != null && projects.length == 1) {
723                 targetScore = 1;
724                 targetProject = projects[0].getProject();
725             }
726         }
727 
728         // Now set the UI accordingly
729         if (targetScore > 0) {
730             mValues.project = targetProject;
731             mValues.folderPath = targetWsFolderPath;
732             mProjectButton.setSelectedProject(targetProject);
733             mFileNameTextField.setText(targetFileName != null ? targetFileName : ""); //$NON-NLS-1$
734 
735             // If the current selection context corresponds to a specific file type,
736             // select it.
737             if (targetWsFolderPath != null) {
738                 int pos = targetWsFolderPath.lastIndexOf(WS_SEP_CHAR);
739                 if (pos >= 0) {
740                     targetWsFolderPath = targetWsFolderPath.substring(pos + 1);
741                 }
742                 String[] folderSegments = targetWsFolderPath.split(RES_QUALIFIER_SEP);
743                 if (folderSegments.length > 0) {
744                     String folderName = folderSegments[0];
745                     selectTypeFromFolder(folderName);
746                 }
747             }
748         }
749 
750         return true;
751     }
752 
initializeFromFixedType()753     private void initializeFromFixedType() {
754         if (mInitialFolderType != null) {
755             for (TypeInfo type : sTypes) {
756                 if (type.getResFolderType() == mInitialFolderType) {
757                     mValues.type = type;
758                     updateFolderPath(type);
759                     break;
760                 }
761             }
762         }
763     }
764 
765     /**
766      * Given a folder name, such as "drawable", select the corresponding type in
767      * the dropdown.
768      */
selectTypeFromFolder(String folderName)769     void selectTypeFromFolder(String folderName) {
770         List<TypeInfo> matches = new ArrayList<TypeInfo>();
771         boolean selected = false;
772 
773         TypeInfo selectedType = getSelectedType();
774         for (TypeInfo type : sTypes) {
775             if (type.getResFolderName().equals(folderName)) {
776                 matches.add(type);
777                 selected |= type == selectedType;
778             }
779         }
780 
781         if (matches.size() == 1) {
782             // If there's only one match, select it if it's not already selected
783             if (!selected) {
784                 selectType(matches.get(0));
785             }
786         } else if (matches.size() > 1) {
787             // There are multiple type candidates for this folder. This can happen
788             // for /res/xml for example. Check to see if one of them is currently
789             // selected. If yes, leave the selection unchanged. If not, deselect all type.
790             if (!selected) {
791                 selectType(null);
792             }
793         } else {
794             // Nothing valid was selected.
795             selectType(null);
796         }
797     }
798 
799     /**
800      * Initialize the root values of the type infos based on the current framework values.
801      */
initializeRootValues()802     private void initializeRootValues() {
803         IProject project = mValues.project;
804         for (TypeInfo type : sTypes) {
805             // Clear all the roots for this type
806             ArrayList<String> roots = type.getRoots();
807             if (roots.size() > 0) {
808                 roots.clear();
809             }
810 
811             // depending of the type of the seed, initialize the root in different ways
812             Object rootSeed = type.getRootSeed();
813 
814             if (rootSeed instanceof String) {
815                 // The seed is a single string, Add it as-is.
816                 roots.add((String) rootSeed);
817             } else if (rootSeed instanceof String[]) {
818                 // The seed is an array of strings. Add them as-is.
819                 for (String value : (String[]) rootSeed) {
820                     roots.add(value);
821                 }
822             } else if (rootSeed instanceof Integer && project != null) {
823                 // The seed is a descriptor reference defined in AndroidTargetData.DESCRIPTOR_*
824                 // In this case add all the children element descriptors defined, recursively,
825                 // and avoid infinite recursion by keeping track of what has already been added.
826 
827                 // Note: if project is null, the root list will be empty since it has been
828                 // cleared above.
829 
830                 // get the AndroidTargetData from the project
831                 IAndroidTarget target = null;
832                 AndroidTargetData data = null;
833 
834                 target = Sdk.getCurrent().getTarget(project);
835                 if (target == null) {
836                     // A project should have a target. The target can be missing if the project
837                     // is an old project for which a target hasn't been affected or if the
838                     // target no longer exists in this SDK. Simply log the error and dismiss.
839 
840                     AdtPlugin.log(IStatus.INFO,
841                             "NewXmlFile wizard: no platform target for project %s",  //$NON-NLS-1$
842                             project.getName());
843                     continue;
844                 } else {
845                     data = Sdk.getCurrent().getTargetData(target);
846 
847                     if (data == null) {
848                         // We should have both a target and its data.
849                         // However if the wizard is invoked whilst the platform is still being
850                         // loaded we can end up in a weird case where we have a target but it
851                         // doesn't have any data yet.
852                         // Lets log a warning and silently ignore this root.
853 
854                         AdtPlugin.log(IStatus.INFO,
855                               "NewXmlFile wizard: no data for target %s, project %s",  //$NON-NLS-1$
856                               target.getName(), project.getName());
857                         continue;
858                     }
859                 }
860 
861                 IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed);
862                 ElementDescriptor descriptor = provider.getDescriptor();
863                 if (descriptor != null) {
864                     HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
865                     initRootElementDescriptor(roots, descriptor, visited);
866                 }
867 
868                 // Sort alphabetically.
869                 Collections.sort(roots);
870             }
871         }
872     }
873 
874     /**
875      * Helper method to recursively insert all XML names for the given {@link ElementDescriptor}
876      * into the roots array list. Keeps track of visited nodes to avoid infinite recursion.
877      * Also avoids inserting the top {@link DocumentDescriptor} which is generally synthetic
878      * and not a valid root element.
879      */
initRootElementDescriptor(ArrayList<String> roots, ElementDescriptor desc, HashSet<ElementDescriptor> visited)880     private void initRootElementDescriptor(ArrayList<String> roots,
881             ElementDescriptor desc, HashSet<ElementDescriptor> visited) {
882         if (!(desc instanceof DocumentDescriptor)) {
883             String xmlName = desc.getXmlName();
884             if (xmlName != null && xmlName.length() > 0) {
885                 roots.add(xmlName);
886             }
887         }
888 
889         visited.add(desc);
890 
891         for (ElementDescriptor child : desc.getChildren()) {
892             if (!visited.contains(child)) {
893                 initRootElementDescriptor(roots, child, visited);
894             }
895         }
896     }
897 
898     /**
899      * Changes mProject to the given new project and update the UI accordingly.
900      * <p/>
901      * Note that this does not check if the new project is the same as the current one
902      * on purpose, which allows a project to be updated when its target has changed or
903      * when targets are loaded in the background.
904      */
changeProject(IProject newProject)905     private void changeProject(IProject newProject) {
906         mValues.project = newProject;
907 
908         // enable types based on new API level
909         updateAvailableTypes();
910         initialSelectType();
911 
912         // update the folder name based on API level
913         updateFolderPath(mValues.type);
914 
915         // update the Type with the new descriptors.
916         initializeRootValues();
917 
918         // update the combo
919         updateRootCombo(mValues.type);
920 
921         validatePage();
922     }
923 
onSelectType(TypeInfo type)924     private void onSelectType(TypeInfo type) {
925         // Do nothing if this is an internal modification or if the widget has been
926         // deselected.
927         if (mInternalTypeUpdate) {
928             return;
929         }
930 
931         mValues.type = type;
932 
933         if (type == null) {
934             return;
935         }
936 
937         // update the combo
938         updateRootCombo(type);
939 
940         // update the folder path
941         updateFolderPath(type);
942 
943         validatePage();
944     }
945 
946     /** Updates the selected type in the type dropdown control */
setSelectedType(TypeInfo type)947     private void setSelectedType(TypeInfo type) {
948         TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
949         if (types != null) {
950             for (int i = 0, n = types.length; i < n; i++) {
951                 if (types[i] == type) {
952                     mTypeCombo.select(i);
953                     break;
954                 }
955             }
956         }
957     }
958 
959     /** Returns the selected type in the type dropdown control */
getSelectedType()960     private TypeInfo getSelectedType() {
961         int index = mTypeCombo.getSelectionIndex();
962         if (index != -1) {
963             TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
964             return types[index];
965         }
966 
967         return null;
968     }
969 
970     /** Returns the selected index in the type dropdown control */
getTypeComboIndex(TypeInfo type)971     private int getTypeComboIndex(TypeInfo type) {
972         TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
973         for (int i = 0, n = types.length; i < n; i++) {
974             if (type == types[i]) {
975                 return i;
976             }
977         }
978 
979         return -1;
980     }
981 
982     /** Updates the folder path to reflect the given type */
updateFolderPath(TypeInfo type)983     private void updateFolderPath(TypeInfo type) {
984         String wsFolderPath = mValues.folderPath;
985         String newPath = null;
986         FolderConfiguration config = mValues.configuration;
987         ResourceQualifier qual = config.getInvalidQualifier();
988         if (qual == null) {
989             // The configuration is valid. Reformat the folder path using the canonical
990             // value from the configuration.
991             newPath = RES_FOLDER_ABS + config.getFolderName(type.getResFolderType());
992         } else {
993             // The configuration is invalid. We still update the path but this time
994             // do it manually on the string.
995             if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
996                 wsFolderPath = wsFolderPath.replaceFirst(
997                         "^(" + RES_FOLDER_ABS +")[^-]*(.*)",         //$NON-NLS-1$ //$NON-NLS-2$
998                         "\\1" + type.getResFolderName() + "\\2");    //$NON-NLS-1$ //$NON-NLS-2$
999             } else {
1000                 newPath = RES_FOLDER_ABS + config.getFolderName(type.getResFolderType());
1001             }
1002         }
1003 
1004         if (newPath != null && !newPath.equals(wsFolderPath)) {
1005             mValues.folderPath = newPath;
1006         }
1007     }
1008 
1009     /**
1010      * Helper method that fills the values of the "root element" combo box based
1011      * on the currently selected type radio button. Also disables the combo is there's
1012      * only one choice. Always select the first root element for the given type.
1013      *
1014      * @param type The currently selected {@link TypeInfo}, or null
1015      */
updateRootCombo(TypeInfo type)1016     private void updateRootCombo(TypeInfo type) {
1017         IBaseLabelProvider labelProvider = new ColumnLabelProvider() {
1018             @Override
1019             public Image getImage(Object element) {
1020                 return IconFactory.getInstance().getIcon(element.toString());
1021             }
1022         };
1023         mRootTableViewer.setContentProvider(new ArrayContentProvider());
1024         mRootTableViewer.setLabelProvider(labelProvider);
1025 
1026         if (type != null) {
1027             // get the list of roots. The list can be empty but not null.
1028             ArrayList<String> roots = type.getRoots();
1029             mRootTableViewer.setInput(roots.toArray());
1030 
1031             int index = 0; // default is to select the first one
1032             String defaultRoot = type.getDefaultRoot(mValues.project);
1033             if (defaultRoot != null) {
1034                 index = roots.indexOf(defaultRoot);
1035             }
1036             mRootTable.select(index < 0 ? 0 : index);
1037             mRootTable.showSelection();
1038         }
1039     }
1040 
1041     /**
1042      * Helper method to select the current type in the type dropdown
1043      *
1044      * @param type The TypeInfo matching the radio button to selected or null to deselect them all.
1045      */
1046     private void selectType(TypeInfo type) {
1047         mInternalTypeUpdate = true;
1048         mValues.type = type;
1049         if (type == null) {
1050             if (mTypeCombo.getSelectionIndex() != -1) {
1051                 mTypeCombo.deselect(mTypeCombo.getSelectionIndex());
1052             }
1053         } else {
1054             setSelectedType(type);
1055         }
1056         updateRootCombo(type);
1057         mInternalTypeUpdate = false;
1058     }
1059 
1060     /**
1061      * Add the available types in the type combobox, based on whether they are available
1062      * for the current SDK.
1063      * <p/>
1064      * A type is available either if:
1065      * - if mProject is null, API level 1 is considered valid
1066      * - if mProject is !null, the project->target->API must be >= to the type's API level.
1067      */
1068     private void updateAvailableTypes() {
1069         IProject project = mValues.project;
1070         IAndroidTarget target = project != null ? Sdk.getCurrent().getTarget(project) : null;
1071         int currentApiLevel = 1;
1072         if (target != null) {
1073             currentApiLevel = target.getVersion().getApiLevel();
1074         }
1075 
1076         List<String> items = new ArrayList<String>(sTypes.length);
1077         List<TypeInfo> types = new ArrayList<TypeInfo>(sTypes.length);
1078         for (int i = 0, n = sTypes.length; i < n; i++) {
1079             TypeInfo type = sTypes[i];
1080             if (type.getTargetApiLevel() <= currentApiLevel) {
1081                 items.add(type.getUiName());
1082                 types.add(type);
1083             }
1084         }
1085         mTypeCombo.setItems(items.toArray(new String[items.size()]));
1086         mTypeCombo.setData(types.toArray(new TypeInfo[types.size()]));
1087     }
1088 
1089     /**
1090      * Validates the fields, displays errors and warnings.
1091      * Enables the finish button if there are no errors.
1092      */
1093     private void validatePage() {
1094         String error = null;
1095         String warning = null;
1096 
1097         // -- validate type
1098         TypeInfo type = mValues.type;
1099         if (error == null) {
1100             if (type == null) {
1101                 error = "One of the types must be selected (e.g. layout, values, etc.)";
1102             }
1103         }
1104 
1105         // -- validate project
1106         if (mValues.project == null) {
1107             error = "Please select an Android project.";
1108         }
1109 
1110         // -- validate type API level
1111         if (error == null) {
1112             IAndroidTarget target = Sdk.getCurrent().getTarget(mValues.project);
1113             int currentApiLevel = 1;
1114             if (target != null) {
1115                 currentApiLevel = target.getVersion().getApiLevel();
1116             }
1117 
1118             assert type != null;
1119             if (type.getTargetApiLevel() > currentApiLevel) {
1120                 error = "The API level of the selected type (e.g. AppWidget, etc.) is not " +
1121                         "compatible with the API level of the project.";
1122             }
1123         }
1124 
1125         // -- validate filename
1126         if (error == null) {
1127             String fileName = mValues.getFileName();
1128             assert type != null;
1129             ResourceFolderType folderType = type.getResFolderType();
1130             error = ResourceNameValidator.create(true, folderType).isValid(fileName);
1131         }
1132 
1133         // -- validate destination file doesn't exist
1134         if (error == null) {
1135             IFile file = mValues.getDestinationFile();
1136             if (file != null && file.exists()) {
1137                 warning = "The destination file already exists";
1138             }
1139         }
1140 
1141         // -- update UI & enable finish if there's no error
1142         setPageComplete(error == null);
1143         if (error != null) {
1144             setMessage(error, IMessageProvider.ERROR);
1145         } else if (warning != null) {
1146             setMessage(warning, IMessageProvider.WARNING);
1147         } else {
1148             setErrorMessage(null);
1149             setMessage(null);
1150         }
1151     }
1152 
1153     /**
1154      * Returns the {@link TypeInfo} for the given {@link ResourceFolderType}, or null if
1155      * not found
1156      *
1157      * @param folderType the {@link ResourceFolderType} to look for
1158      * @return the corresponding {@link TypeInfo}
1159      */
1160     static TypeInfo getTypeInfo(ResourceFolderType folderType) {
1161         for (TypeInfo typeInfo : sTypes) {
1162             if (typeInfo.getResFolderType() == folderType) {
1163                 return typeInfo;
1164             }
1165         }
1166 
1167         return null;
1168     }
1169 }
1170