Home | History | Annotate | Download | only in newxmlfile
      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
    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 
    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. */
    147         String getUiName() {
    148             return mUiName;
    149         }
    150 
    151         /** Returns the tooltip for the resource type. Can be null. */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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      */
    419     protected NewXmlFileCreationPage(String pageName, NewXmlFileWizard.Values values) {
    420         super(pageName);
    421         mValues = values;
    422         setPageComplete(false);
    423     }
    424 
    425     public void setInitialSelection(IStructuredSelection initialSelection) {
    426         mInitialSelection = initialSelection;
    427     }
    428 
    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
    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 
    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 
    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
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    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 */
    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 */
    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 */
    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 */
    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      */
   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