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