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 package com.android.ide.eclipse.adt.internal.wizards.newxmlfile;
     18 
     19 import static com.android.SdkConstants.DOT_XML;
     20 import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
     21 import static com.android.SdkConstants.LINEAR_LAYOUT;
     22 import static com.android.SdkConstants.RES_QUALIFIER_SEP;
     23 import static com.android.SdkConstants.SCROLL_VIEW;
     24 import static com.android.SdkConstants.VALUE_FILL_PARENT;
     25 import static com.android.SdkConstants.VALUE_MATCH_PARENT;
     26 import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP_CHAR;
     27 import static com.android.ide.eclipse.adt.internal.wizards.newxmlfile.ChooseConfigurationPage.RES_FOLDER_ABS;
     28 
     29 import com.android.SdkConstants;
     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.project.ProjectChooserHelper;
     41 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.ProjectCombo;
     42 import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
     43 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     44 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     45 import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
     46 import com.android.resources.ResourceFolderType;
     47 import com.android.sdklib.IAndroidTarget;
     48 import com.android.utils.Pair;
     49 import com.android.utils.SdkUtils;
     50 
     51 import org.eclipse.core.resources.IFile;
     52 import org.eclipse.core.resources.IProject;
     53 import org.eclipse.core.resources.IResource;
     54 import org.eclipse.core.runtime.CoreException;
     55 import org.eclipse.core.runtime.IAdaptable;
     56 import org.eclipse.core.runtime.IPath;
     57 import org.eclipse.core.runtime.IStatus;
     58 import org.eclipse.jdt.core.IJavaProject;
     59 import org.eclipse.jface.dialogs.IMessageProvider;
     60 import org.eclipse.jface.viewers.ArrayContentProvider;
     61 import org.eclipse.jface.viewers.ColumnLabelProvider;
     62 import org.eclipse.jface.viewers.IBaseLabelProvider;
     63 import org.eclipse.jface.viewers.IStructuredSelection;
     64 import org.eclipse.jface.viewers.TableViewer;
     65 import org.eclipse.jface.wizard.WizardPage;
     66 import org.eclipse.swt.SWT;
     67 import org.eclipse.swt.events.ModifyEvent;
     68 import org.eclipse.swt.events.ModifyListener;
     69 import org.eclipse.swt.events.SelectionAdapter;
     70 import org.eclipse.swt.events.SelectionEvent;
     71 import org.eclipse.swt.graphics.Image;
     72 import org.eclipse.swt.layout.GridData;
     73 import org.eclipse.swt.layout.GridLayout;
     74 import org.eclipse.swt.widgets.Combo;
     75 import org.eclipse.swt.widgets.Composite;
     76 import org.eclipse.swt.widgets.Label;
     77 import org.eclipse.swt.widgets.Table;
     78 import org.eclipse.swt.widgets.Text;
     79 import org.eclipse.ui.IEditorPart;
     80 import org.eclipse.ui.IWorkbenchPage;
     81 import org.eclipse.ui.IWorkbenchWindow;
     82 import org.eclipse.ui.PlatformUI;
     83 import org.eclipse.ui.part.FileEditorInput;
     84 
     85 import java.util.ArrayList;
     86 import java.util.Collections;
     87 import java.util.HashSet;
     88 import java.util.List;
     89 
     90 /**
     91  * This is the first page of the {@link NewXmlFileWizard} which provides the ability to create
     92  * skeleton XML resources files for Android projects.
     93  * <p/>
     94  * This page is used to select the project, resource type and file name.
     95  */
     96 class NewXmlFileCreationPage extends WizardPage {
     97 
     98     @Override
     99     public void setVisible(boolean visible) {
    100         super.setVisible(visible);
    101         // Ensure the initial focus is in the Name field; you usually don't need
    102         // to edit the default text field (the project name)
    103         if (visible && mFileNameTextField != null) {
    104             mFileNameTextField.setFocus();
    105         }
    106 
    107         validatePage();
    108     }
    109 
    110     /**
    111      * Information on one type of resource that can be created (e.g. menu, pref, layout, etc.)
    112      */
    113     static class TypeInfo {
    114         private final String mUiName;
    115         private final ResourceFolderType mResFolderType;
    116         private final String mTooltip;
    117         private final Object mRootSeed;
    118         private ArrayList<String> mRoots = new ArrayList<String>();
    119         private final String mXmlns;
    120         private final String mDefaultAttrs;
    121         private final String mDefaultRoot;
    122         private final int mTargetApiLevel;
    123 
    124         public TypeInfo(String uiName,
    125                         String tooltip,
    126                         ResourceFolderType resFolderType,
    127                         Object rootSeed,
    128                         String defaultRoot,
    129                         String xmlns,
    130                         String defaultAttrs,
    131                         int targetApiLevel) {
    132             mUiName = uiName;
    133             mResFolderType = resFolderType;
    134             mTooltip = tooltip;
    135             mRootSeed = rootSeed;
    136             mDefaultRoot = defaultRoot;
    137             mXmlns = xmlns;
    138             mDefaultAttrs = defaultAttrs;
    139             mTargetApiLevel = targetApiLevel;
    140         }
    141 
    142         /** Returns the UI name for the resource type. Unique. Never null. */
    143         String getUiName() {
    144             return mUiName;
    145         }
    146 
    147         /** Returns the tooltip for the resource type. Can be null. */
    148         String getTooltip() {
    149             return mTooltip;
    150         }
    151 
    152         /**
    153          * Returns the name of the {@link ResourceFolderType}.
    154          * Never null but not necessarily unique,
    155          * e.g. two types use  {@link ResourceFolderType#XML}.
    156          */
    157         String getResFolderName() {
    158             return mResFolderType.getName();
    159         }
    160 
    161         /**
    162          * Returns the matching {@link ResourceFolderType}.
    163          * Never null but not necessarily unique,
    164          * e.g. two types use  {@link ResourceFolderType#XML}.
    165          */
    166         ResourceFolderType getResFolderType() {
    167             return mResFolderType;
    168         }
    169 
    170         /**
    171          * Returns the seed used to fill the root element values.
    172          * The seed might be either a String, a String array, an {@link ElementDescriptor},
    173          * a {@link DocumentDescriptor} or null.
    174          */
    175         Object getRootSeed() {
    176             return mRootSeed;
    177         }
    178 
    179         /**
    180          * Returns the default root element that should be selected by default. Can be
    181          * null.
    182          *
    183          * @param project the associated project, or null if not known
    184          */
    185         String getDefaultRoot(IProject project) {
    186             return mDefaultRoot;
    187         }
    188 
    189         /**
    190          * Returns the list of all possible root elements for the resource type.
    191          * This can be an empty ArrayList but not null.
    192          * <p/>
    193          * TODO: the root list SHOULD depend on the currently selected project, to include
    194          * custom classes.
    195          */
    196         ArrayList<String> getRoots() {
    197             return mRoots;
    198         }
    199 
    200         /**
    201          * If the generated resource XML file requires an "android" XMLNS, this should be set
    202          * to {@link SdkConstants#NS_RESOURCES}. When it is null, no XMLNS is generated.
    203          */
    204         String getXmlns() {
    205             return mXmlns;
    206         }
    207 
    208         /**
    209          * When not null, this represent extra attributes that must be specified in the
    210          * root element of the generated XML file. When null, no extra attributes are inserted.
    211          *
    212          * @param project the project to get the attributes for
    213          * @param root the selected root element string, never null
    214          */
    215         String getDefaultAttrs(IProject project, String root) {
    216             return mDefaultAttrs;
    217         }
    218 
    219         /**
    220          * When not null, represents an extra string that should be written inside
    221          * the element when constructed
    222          *
    223          * @param project the project to get the child content for
    224          * @param root the chosen root element
    225          * @return a string to be written inside the root element, or null if nothing
    226          */
    227         String getChild(IProject project, String root) {
    228             return null;
    229         }
    230 
    231         /**
    232          * The minimum API level required by the current SDK target to support this feature.
    233          *
    234          * @return the minimum API level
    235          */
    236         public int getTargetApiLevel() {
    237             return mTargetApiLevel;
    238         }
    239     }
    240 
    241     /**
    242      * TypeInfo, information for each "type" of file that can be created.
    243      */
    244     private static final TypeInfo[] sTypes = {
    245         new TypeInfo(
    246                 "Layout",                                                   // UI name
    247                 "An XML file that describes a screen layout.",              // tooltip
    248                 ResourceFolderType.LAYOUT,                                  // folder type
    249                 AndroidTargetData.DESCRIPTOR_LAYOUT,                        // root seed
    250                 LINEAR_LAYOUT,                                              // default root
    251                 SdkConstants.NS_RESOURCES,                                  // xmlns
    252                 "",                                                         // not used, see below
    253                 1                                                           // target API level
    254                 ) {
    255 
    256                 @Override
    257                 String getDefaultRoot(IProject project) {
    258                     // TODO: Use GridLayout by default for new SDKs
    259                     // (when we've ironed out all the usability issues)
    260                     //Sdk currentSdk = Sdk.getCurrent();
    261                     //if (project != null && currentSdk != null) {
    262                     //    IAndroidTarget target = currentSdk.getTarget(project);
    263                     //    // fill_parent was renamed match_parent in API level 8
    264                     //    if (target != null && target.getVersion().getApiLevel() >= 13) {
    265                     //        return GRID_LAYOUT;
    266                     //    }
    267                     //}
    268 
    269                     return LINEAR_LAYOUT;
    270                 };
    271 
    272                 // The default attributes must be determined dynamically since whether
    273                 // we use match_parent or fill_parent depends on the API level of the
    274                 // project
    275                 @Override
    276                 String getDefaultAttrs(IProject project, String root) {
    277                     Sdk currentSdk = Sdk.getCurrent();
    278                     String fill = VALUE_FILL_PARENT;
    279                     if (currentSdk != null) {
    280                         IAndroidTarget target = currentSdk.getTarget(project);
    281                         // fill_parent was renamed match_parent in API level 8
    282                         if (target != null && target.getVersion().getApiLevel() >= 8) {
    283                             fill = VALUE_MATCH_PARENT;
    284                         }
    285                     }
    286 
    287                     // Only set "vertical" orientation of LinearLayouts by default;
    288                     // for GridLayouts for example we want to rely on the real default
    289                     // of the layout
    290                     String size = String.format(
    291                             "android:layout_width=\"%1$s\"\n"        //$NON-NLS-1$
    292                             + "android:layout_height=\"%2$s\"",        //$NON-NLS-1$
    293                             fill, fill);
    294                     if (LINEAR_LAYOUT.equals(root)) {
    295                         return "android:orientation=\"vertical\"\n" + size; //$NON-NLS-1$
    296                     } else {
    297                         return size;
    298                     }
    299                 }
    300 
    301                 @Override
    302                 String getChild(IProject project, String root) {
    303                     // Create vertical linear layouts inside new scroll views
    304                     if (SCROLL_VIEW.equals(root) || HORIZONTAL_SCROLL_VIEW.equals(root)) {
    305                         return "    <LinearLayout "         //$NON-NLS-1$
    306                             + getDefaultAttrs(project, root).replace('\n', ' ')
    307                             + " android:orientation=\"vertical\"" //$NON-NLS-1$
    308                             + "></LinearLayout>\n";         //$NON-NLS-1$
    309                     }
    310                     return null;
    311                 }
    312         },
    313         new TypeInfo("Values",                                              // UI name
    314                 "An XML file with simple values: colors, strings, dimensions, etc.", // tooltip
    315                 ResourceFolderType.VALUES,                                  // folder type
    316                 SdkConstants.TAG_RESOURCES,                                 // root seed
    317                 null,                                                       // default root
    318                 null,                                                       // xmlns
    319                 null,                                                       // default attributes
    320                 1                                                           // target API level
    321                 ),
    322         new TypeInfo("Drawable",                                            // UI name
    323                 "An XML file that describes a drawable.",                   // tooltip
    324                 ResourceFolderType.DRAWABLE,                                // folder type
    325                 AndroidTargetData.DESCRIPTOR_DRAWABLE,                      // root seed
    326                 null,                                                       // default root
    327                 SdkConstants.NS_RESOURCES,                                  // xmlns
    328                 null,                                                       // default attributes
    329                 1                                                           // target API level
    330                 ),
    331         new TypeInfo("Menu",                                                // UI name
    332                 "An XML file that describes an menu.",                      // tooltip
    333                 ResourceFolderType.MENU,                                    // folder type
    334                 SdkConstants.TAG_MENU,                                      // root seed
    335                 null,                                                       // default root
    336                 SdkConstants.NS_RESOURCES,                                  // xmlns
    337                 null,                                                       // default attributes
    338                 1                                                           // target API level
    339                 ),
    340         new TypeInfo("Color List",                                          // UI name
    341                 "An XML file that describes a color state list.",           // tooltip
    342                 ResourceFolderType.COLOR,                                   // folder type
    343                 AndroidTargetData.DESCRIPTOR_COLOR,                         // root seed
    344                 "selector",  //$NON-NLS-1$                                  // default root
    345                 SdkConstants.NS_RESOURCES,                                  // xmlns
    346                 null,                                                       // default attributes
    347                 1                                                           // target API level
    348                 ),
    349         new TypeInfo("Property Animation",                                  // UI name
    350                 "An XML file that describes a property animation",          // tooltip
    351                 ResourceFolderType.ANIMATOR,                                // folder type
    352                 AndroidTargetData.DESCRIPTOR_ANIMATOR,                      // root seed
    353                 "set", //$NON-NLS-1$                                        // default root
    354                 SdkConstants.NS_RESOURCES,                                  // xmlns
    355                 null,                                                       // default attributes
    356                 11                                                          // target API level
    357                 ),
    358         new TypeInfo("Tween Animation",                                     // UI name
    359                 "An XML file that describes a tween animation.",            // tooltip
    360                 ResourceFolderType.ANIM,                                    // folder type
    361                 AndroidTargetData.DESCRIPTOR_ANIM,                          // root seed
    362                 "set", //$NON-NLS-1$                                        // default root
    363                 null,                                                       // xmlns
    364                 null,                                                       // default attributes
    365                 1                                                           // target API level
    366                 ),
    367         new TypeInfo("AppWidget Provider",                                  // UI name
    368                 "An XML file that describes a widget provider.",            // tooltip
    369                 ResourceFolderType.XML,                                     // folder type
    370                 AndroidTargetData.DESCRIPTOR_APPWIDGET_PROVIDER,            // root seed
    371                 null,                                                       // default root
    372                 SdkConstants.NS_RESOURCES,                                  // xmlns
    373                 null,                                                       // default attributes
    374                 3                                                           // target API level
    375                 ),
    376         new TypeInfo("Preference",                                          // UI name
    377                 "An XML file that describes preferences.",                  // tooltip
    378                 ResourceFolderType.XML,                                     // folder type
    379                 AndroidTargetData.DESCRIPTOR_PREFERENCES,                   // root seed
    380                 SdkConstants.CLASS_NAME_PREFERENCE_SCREEN,                  // default root
    381                 SdkConstants.NS_RESOURCES,                                  // xmlns
    382                 null,                                                       // default attributes
    383                 1                                                           // target API level
    384                 ),
    385         new TypeInfo("Searchable",                                          // UI name
    386                 "An XML file that describes a searchable.",                 // tooltip
    387                 ResourceFolderType.XML,                                     // folder type
    388                 AndroidTargetData.DESCRIPTOR_SEARCHABLE,                    // root seed
    389                 null,                                                       // default root
    390                 SdkConstants.NS_RESOURCES,                                  // xmlns
    391                 null,                                                       // default attributes
    392                 1                                                           // target API level
    393                 ),
    394         // Still missing: Interpolator, Raw and Mipmap. Raw should probably never be in
    395         // this menu since it's not often used for creating XML files.
    396     };
    397 
    398     private NewXmlFileWizard.Values mValues;
    399     private ProjectCombo mProjectButton;
    400     private Text mFileNameTextField;
    401     private Combo mTypeCombo;
    402     private IStructuredSelection mInitialSelection;
    403     private ResourceFolderType mInitialFolderType;
    404     private boolean mInternalTypeUpdate;
    405     private TargetChangeListener mSdkTargetChangeListener;
    406     private Table mRootTable;
    407     private TableViewer mRootTableViewer;
    408 
    409     // --- UI creation ---
    410 
    411     /**
    412      * Constructs a new {@link NewXmlFileCreationPage}.
    413      * <p/>
    414      * Called by {@link NewXmlFileWizard#createMainPage}.
    415      */
    416     protected NewXmlFileCreationPage(String pageName, NewXmlFileWizard.Values values) {
    417         super(pageName);
    418         mValues = values;
    419         setPageComplete(false);
    420     }
    421 
    422     public void setInitialSelection(IStructuredSelection initialSelection) {
    423         mInitialSelection = initialSelection;
    424     }
    425 
    426     public void setInitialFolderType(ResourceFolderType initialType) {
    427         mInitialFolderType = initialType;
    428     }
    429 
    430     /**
    431      * Called by the parent Wizard to create the UI for this Wizard Page.
    432      *
    433      * {@inheritDoc}
    434      *
    435      * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
    436      */
    437     @Override
    438     @SuppressWarnings("unused") // SWT constructors have side effects, they aren't unused
    439     public void createControl(Composite parent) {
    440         // This UI is maintained with WindowBuilder.
    441 
    442         Composite composite = new Composite(parent, SWT.NULL);
    443         composite.setLayout(new GridLayout(2, false /*makeColumnsEqualWidth*/));
    444         composite.setLayoutData(new GridData(GridData.FILL_BOTH));
    445 
    446         // label before type radios
    447         Label typeLabel = new Label(composite, SWT.NONE);
    448         typeLabel.setText("Resource Type:");
    449 
    450         mTypeCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
    451         mTypeCombo.setToolTipText("What type of resource would you like to create?");
    452         mTypeCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    453         if (mInitialFolderType != null) {
    454             mTypeCombo.setEnabled(false);
    455         }
    456         mTypeCombo.addSelectionListener(new SelectionAdapter() {
    457             @Override
    458             public void widgetSelected(SelectionEvent e) {
    459                 TypeInfo type = getSelectedType();
    460                 if (type != null) {
    461                     onSelectType(type);
    462                 }
    463             }
    464         });
    465 
    466         // separator
    467         Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
    468         GridData gd2 = new GridData(GridData.GRAB_HORIZONTAL);
    469         gd2.horizontalAlignment = SWT.FILL;
    470         gd2.horizontalSpan = 2;
    471         separator.setLayoutData(gd2);
    472 
    473         // Project: [button]
    474         String tooltip = "The Android Project where the new resource file will be created.";
    475         Label projectLabel = new Label(composite, SWT.NONE);
    476         projectLabel.setText("Project:");
    477         projectLabel.setToolTipText(tooltip);
    478 
    479         ProjectChooserHelper helper =
    480                 new ProjectChooserHelper(getShell(), null /* filter */);
    481 
    482         mProjectButton = new ProjectCombo(helper, composite, mValues.project);
    483         mProjectButton.setToolTipText(tooltip);
    484         mProjectButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    485         mProjectButton.addSelectionListener(new SelectionAdapter() {
    486             @Override
    487             public void widgetSelected(SelectionEvent e) {
    488                 IProject project = mProjectButton.getSelectedProject();
    489                 if (project != mValues.project) {
    490                     changeProject(project);
    491                 }
    492             };
    493         });
    494 
    495         // Filename: [text]
    496         Label fileLabel = new Label(composite, SWT.NONE);
    497         fileLabel.setText("File:");
    498         fileLabel.setToolTipText("The name of the resource file to create.");
    499 
    500         mFileNameTextField = new Text(composite, SWT.BORDER);
    501         mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    502         mFileNameTextField.setToolTipText(tooltip);
    503         mFileNameTextField.addModifyListener(new ModifyListener() {
    504             @Override
    505             public void modifyText(ModifyEvent e) {
    506                 mValues.name = mFileNameTextField.getText();
    507                 validatePage();
    508             }
    509         });
    510 
    511         // separator
    512         Label rootSeparator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
    513         GridData gd = new GridData(GridData.GRAB_HORIZONTAL);
    514         gd.horizontalAlignment = SWT.FILL;
    515         gd.horizontalSpan = 2;
    516         rootSeparator.setLayoutData(gd);
    517 
    518         // Root Element:
    519         // [TableViewer]
    520         Label rootLabel = new Label(composite, SWT.NONE);
    521         rootLabel.setText("Root Element:");
    522         new Label(composite, SWT.NONE);
    523 
    524         mRootTableViewer = new TableViewer(composite, SWT.BORDER | SWT.FULL_SELECTION);
    525         mRootTable = mRootTableViewer.getTable();
    526         GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
    527         tableGridData.heightHint = 200;
    528         mRootTable.setLayoutData(tableGridData);
    529 
    530         setControl(composite);
    531 
    532         // Update state the first time
    533         setErrorMessage(null);
    534         setMessage(null);
    535 
    536         initializeFromSelection(mInitialSelection);
    537         updateAvailableTypes();
    538         initializeFromFixedType();
    539         initializeRootValues();
    540         installTargetChangeListener();
    541 
    542         initialSelectType();
    543         validatePage();
    544     }
    545 
    546     private void initialSelectType() {
    547         TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
    548         int typeIndex = getTypeComboIndex(mValues.type);
    549         if (typeIndex == -1) {
    550             typeIndex = 0;
    551         } else {
    552             assert mValues.type == types[typeIndex];
    553         }
    554         mTypeCombo.select(typeIndex);
    555         onSelectType(types[typeIndex]);
    556         updateRootCombo(types[typeIndex]);
    557     }
    558 
    559     private void installTargetChangeListener() {
    560         mSdkTargetChangeListener = new TargetChangeListener() {
    561             @Override
    562             public IProject getProject() {
    563                 return mValues.project;
    564             }
    565 
    566             @Override
    567             public void reload() {
    568                 if (mValues.project != null) {
    569                     changeProject(mValues.project);
    570                 }
    571             }
    572         };
    573 
    574         AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
    575     }
    576 
    577     @Override
    578     public void dispose() {
    579 
    580         if (mSdkTargetChangeListener != null) {
    581             AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
    582             mSdkTargetChangeListener = null;
    583         }
    584 
    585         super.dispose();
    586     }
    587 
    588     /**
    589      * Returns the selected root element string, if any.
    590      *
    591      * @return The selected root element string or null.
    592      */
    593     public String getRootElement() {
    594         int index = mRootTable.getSelectionIndex();
    595         if (index >= 0) {
    596             Object[] roots = (Object[]) mRootTableViewer.getInput();
    597             return roots[index].toString();
    598         }
    599         return null;
    600     }
    601 
    602     /**
    603      * Called by {@link NewXmlFileWizard} to initialize the page with the selection
    604      * received by the wizard -- typically the current user workbench selection.
    605      * <p/>
    606      * Things we expect to find out from the selection:
    607      * <ul>
    608      * <li>The project name, valid if it's an android nature.</li>
    609      * <li>The current folder, valid if it's a folder under /res</li>
    610      * <li>An existing filename, in which case the user will be asked whether to override it.</li>
    611      * </ul>
    612      * <p/>
    613      * The selection can also be set to a {@link Pair} of {@link IProject} and a workspace
    614      * resource path (where the resource path does not have to exist yet, such as res/anim/).
    615      *
    616      * @param selection The selection when the wizard was initiated.
    617      */
    618     private boolean initializeFromSelection(IStructuredSelection selection) {
    619         if (selection == null) {
    620             return false;
    621         }
    622 
    623         // Find the best match in the element list. In case there are multiple selected elements
    624         // select the one that provides the most information and assign them a score,
    625         // e.g. project=1 + folder=2 + file=4.
    626         IProject targetProject = null;
    627         String targetWsFolderPath = null;
    628         String targetFileName = null;
    629         int targetScore = 0;
    630         for (Object element : selection.toList()) {
    631             if (element instanceof IAdaptable) {
    632                 IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
    633                 IProject project = res != null ? res.getProject() : null;
    634 
    635                 // Is this an Android project?
    636                 try {
    637                     if (project == null || !project.hasNature(AdtConstants.NATURE_DEFAULT)) {
    638                         continue;
    639                     }
    640                 } catch (CoreException e) {
    641                     // checking the nature failed, ignore this resource
    642                     continue;
    643                 }
    644 
    645                 int score = 1; // we have a valid project at least
    646 
    647                 IPath wsFolderPath = null;
    648                 String fileName = null;
    649                 assert res != null; // Eclipse incorrectly thinks res could be null, so tell it no
    650                 if (res.getType() == IResource.FOLDER) {
    651                     wsFolderPath = res.getProjectRelativePath();
    652                 } else if (res.getType() == IResource.FILE) {
    653                     if (SdkUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) {
    654                         fileName = res.getName();
    655                     }
    656                     wsFolderPath = res.getParent().getProjectRelativePath();
    657                 }
    658 
    659                 // Disregard this folder selection if it doesn't point to /res/something
    660                 if (wsFolderPath != null &&
    661                         wsFolderPath.segmentCount() > 1 &&
    662                         SdkConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) {
    663                     score += 2;
    664                 } else {
    665                     wsFolderPath = null;
    666                     fileName = null;
    667                 }
    668 
    669                 score += fileName != null ? 4 : 0;
    670 
    671                 if (score > targetScore) {
    672                     targetScore = score;
    673                     targetProject = project;
    674                     targetWsFolderPath = wsFolderPath != null ? wsFolderPath.toString() : null;
    675                     targetFileName = fileName;
    676                 }
    677             } else if (element instanceof Pair<?,?>) {
    678                 // Pair of Project/String
    679                 @SuppressWarnings("unchecked")
    680                 Pair<IProject,String> pair = (Pair<IProject,String>)element;
    681                 targetScore = 1;
    682                 targetProject = pair.getFirst();
    683                 targetWsFolderPath = pair.getSecond();
    684                 targetFileName = "";
    685             }
    686         }
    687 
    688         if (targetProject == null) {
    689             // Try to figure out the project from the active editor
    690             IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
    691             if (window != null) {
    692                 IWorkbenchPage page = window.getActivePage();
    693                 if (page != null) {
    694                     IEditorPart activeEditor = page.getActiveEditor();
    695                     if (activeEditor instanceof AndroidXmlEditor) {
    696                         Object input = ((AndroidXmlEditor) activeEditor).getEditorInput();
    697                         if (input instanceof FileEditorInput) {
    698                             FileEditorInput fileInput = (FileEditorInput) input;
    699                             targetScore = 1;
    700                             IFile file = fileInput.getFile();
    701                             targetProject = file.getProject();
    702                             IPath path = file.getParent().getProjectRelativePath();
    703                             targetWsFolderPath = path != null ? path.toString() : null;
    704                         }
    705                     }
    706                 }
    707             }
    708         }
    709 
    710         if (targetProject == null) {
    711             // If we didn't find a default project based on the selection, check how many
    712             // open Android projects we can find in the current workspace. If there's only
    713             // one, we'll just select it by default.
    714             IJavaProject[] projects = AdtUtils.getOpenAndroidProjects();
    715             if (projects != null && projects.length == 1) {
    716                 targetScore = 1;
    717                 targetProject = projects[0].getProject();
    718             }
    719         }
    720 
    721         // Now set the UI accordingly
    722         if (targetScore > 0) {
    723             mValues.project = targetProject;
    724             mValues.folderPath = targetWsFolderPath;
    725             mProjectButton.setSelectedProject(targetProject);
    726             mFileNameTextField.setText(targetFileName != null ? targetFileName : ""); //$NON-NLS-1$
    727 
    728             // If the current selection context corresponds to a specific file type,
    729             // select it.
    730             if (targetWsFolderPath != null) {
    731                 int pos = targetWsFolderPath.lastIndexOf(WS_SEP_CHAR);
    732                 if (pos >= 0) {
    733                     targetWsFolderPath = targetWsFolderPath.substring(pos + 1);
    734                 }
    735                 String[] folderSegments = targetWsFolderPath.split(RES_QUALIFIER_SEP);
    736                 if (folderSegments.length > 0) {
    737                     mValues.configuration = FolderConfiguration.getConfig(folderSegments);
    738                     String folderName = folderSegments[0];
    739                     selectTypeFromFolder(folderName);
    740                 }
    741             }
    742         }
    743 
    744         return true;
    745     }
    746 
    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      */
    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      */
    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      */
    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      */
    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 
    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 */
    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 */
    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 */
    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 */
    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      */
   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