Home | History | Annotate | Download | only in newxmlfile
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.eclipse.adt.internal.wizards.newxmlfile;
     17 
     18 import com.android.AndroidConstants;
     19 import com.android.ide.common.resources.configuration.ResourceQualifier;
     20 import com.android.ide.eclipse.adt.AdtConstants;
     21 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector;
     22 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState;
     23 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.SelectorMode;
     24 import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileCreationPage.TypeInfo;
     25 import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard.Values;
     26 import com.android.sdklib.SdkConstants;
     27 
     28 import org.eclipse.core.resources.IFile;
     29 import org.eclipse.jface.dialogs.IMessageProvider;
     30 import org.eclipse.jface.wizard.IWizardPage;
     31 import org.eclipse.jface.wizard.WizardPage;
     32 import org.eclipse.swt.SWT;
     33 import org.eclipse.swt.events.ModifyEvent;
     34 import org.eclipse.swt.events.ModifyListener;
     35 import org.eclipse.swt.layout.GridData;
     36 import org.eclipse.swt.layout.GridLayout;
     37 import org.eclipse.swt.widgets.Composite;
     38 import org.eclipse.swt.widgets.Label;
     39 import org.eclipse.swt.widgets.Text;
     40 
     41 /**
     42  * Second page of the {@link NewXmlFileWizard}.
     43  * <p>
     44  * This page is used for choosing the current configuration or specific resource
     45  * folder.
     46  */
     47 public class ChooseConfigurationPage extends WizardPage {
     48     private Values mValues;
     49     private Text mWsFolderPathTextField;
     50     private ConfigurationSelector mConfigSelector;
     51     private boolean mInternalWsFolderPathUpdate;
     52     private boolean mInternalConfigSelectorUpdate;
     53 
     54     /** Absolute destination folder root, e.g. "/res/" */
     55     static final String RES_FOLDER_ABS = AdtConstants.WS_RESOURCES + AdtConstants.WS_SEP;
     56     /** Relative destination folder root, e.g. "res/" */
     57     static final String RES_FOLDER_REL = SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP;
     58 
     59     /**
     60      * Create the wizard.
     61      *
     62      * @param values value object holding current wizard state
     63      */
     64     public ChooseConfigurationPage(NewXmlFileWizard.Values values) {
     65         super("chooseConfig");
     66         mValues = values;
     67         setTitle("Choose Configuration Folder");
     68     }
     69 
     70     @Override
     71     public void setVisible(boolean visible) {
     72         super.setVisible(visible);
     73         if (visible) {
     74             if (mValues.folderPath != null) {
     75                 mWsFolderPathTextField.setText(mValues.folderPath);
     76             }
     77         }
     78     }
     79 
     80     public void createControl(Composite parent) {
     81         // This UI is maintained with WindowBuilder.
     82 
     83         Composite composite = new Composite(parent, SWT.NULL);
     84         composite.setLayout(new GridLayout(2, false /* makeColumnsEqualWidth */));
     85         composite.setLayoutData(new GridData(GridData.FILL_BOTH));
     86 
     87         // label before configuration selector
     88         Label label = new Label(composite, SWT.NONE);
     89         label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
     90         label.setText("Optional: Choose a specific configuration to limit the XML to:");
     91 
     92         // configuration selector
     93         mConfigSelector = new ConfigurationSelector(composite, SelectorMode.DEFAULT);
     94         GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
     95         gd.verticalAlignment = SWT.FILL;
     96         gd.horizontalAlignment = SWT.FILL;
     97         gd.horizontalSpan = 2;
     98         gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
     99         mConfigSelector.setLayoutData(gd);
    100         mConfigSelector.setOnChangeListener(new ConfigurationChangeListener());
    101 
    102         // Folder name: [text]
    103         String tooltip = "The folder where the file will be generated, relative to the project.";
    104 
    105         Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
    106         GridData gdSeparator = new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1);
    107         gdSeparator.heightHint = 10;
    108         separator.setLayoutData(gdSeparator);
    109         Label folderLabel = new Label(composite, SWT.NONE);
    110         folderLabel.setText("Folder:");
    111         folderLabel.setToolTipText(tooltip);
    112 
    113         mWsFolderPathTextField = new Text(composite, SWT.BORDER);
    114         mWsFolderPathTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    115         mWsFolderPathTextField.setToolTipText(tooltip);
    116         mWsFolderPathTextField.addModifyListener(new ModifyListener() {
    117             public void modifyText(ModifyEvent e) {
    118                 onWsFolderPathUpdated();
    119             }
    120         });
    121 
    122         setControl(composite);
    123     }
    124 
    125     /**
    126      * Callback called when the Folder text field is changed, either programmatically
    127      * or by the user.
    128      */
    129     private void onWsFolderPathUpdated() {
    130         if (mInternalWsFolderPathUpdate) {
    131             return;
    132         }
    133 
    134         String wsFolderPath = mWsFolderPathTextField.getText();
    135 
    136         // This is a custom path, we need to sanitize it.
    137         // First it should start with "/res/". Then we need to make sure there are no
    138         // relative paths, things like "../" or "./" or even "//".
    139         wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/");  //$NON-NLS-1$ //$NON-NLS-2$
    140         wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", "");                   //$NON-NLS-1$ //$NON-NLS-2$
    141         wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", "");               //$NON-NLS-1$ //$NON-NLS-2$
    142 
    143         // We get "res/foo" from selections relative to the project when we want a "/res/foo" path.
    144         if (wsFolderPath.startsWith(RES_FOLDER_REL)) {
    145             wsFolderPath = RES_FOLDER_ABS + wsFolderPath.substring(RES_FOLDER_REL.length());
    146 
    147             mInternalWsFolderPathUpdate = true;
    148             mWsFolderPathTextField.setText(wsFolderPath);
    149             mInternalWsFolderPathUpdate = false;
    150         }
    151 
    152         mValues.folderPath = wsFolderPath;
    153 
    154         if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
    155             wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length());
    156 
    157             int pos = wsFolderPath.indexOf(AdtConstants.WS_SEP_CHAR);
    158             if (pos >= 0) {
    159                 wsFolderPath = wsFolderPath.substring(0, pos);
    160             }
    161 
    162             String[] folderSegments = wsFolderPath.split(AndroidConstants.RES_QUALIFIER_SEP);
    163 
    164             if (folderSegments.length > 0) {
    165                 String folderName = folderSegments[0];
    166 
    167                 // update config selector
    168                 mInternalConfigSelectorUpdate = true;
    169                 mConfigSelector.setConfiguration(folderSegments);
    170                 mInternalConfigSelectorUpdate = false;
    171 
    172                 IWizardPage previous = ((NewXmlFileWizard) getWizard()).getPreviousPage(this);
    173                 if (previous instanceof NewXmlFileCreationPage) {
    174                     NewXmlFileCreationPage p = (NewXmlFileCreationPage) previous;
    175                     p.selectTypeFromFolder(folderName);
    176                 }
    177             }
    178         }
    179 
    180         validatePage();
    181     }
    182 
    183     /**
    184      * Callback called when the configuration has changed in the {@link ConfigurationSelector}.
    185      */
    186     private class ConfigurationChangeListener implements Runnable {
    187         public void run() {
    188             if (mInternalConfigSelectorUpdate) {
    189                 return;
    190             }
    191 
    192             resetFolderPath(true /*validate*/);
    193         }
    194     }
    195 
    196     /**
    197      * Reset the current Folder path based on the UI selection
    198      * @param validate if true, force a call to {@link #validatePage()}.
    199      */
    200     private void resetFolderPath(boolean validate) {
    201         TypeInfo type = mValues.type;
    202         if (type != null) {
    203             mConfigSelector.getConfiguration(mValues.configuration);
    204             StringBuilder sb = new StringBuilder(RES_FOLDER_ABS);
    205             sb.append(mValues.configuration.getFolderName(type.getResFolderType()));
    206 
    207             mInternalWsFolderPathUpdate = true;
    208             String newPath = sb.toString();
    209             mValues.folderPath = newPath;
    210             mWsFolderPathTextField.setText(newPath);
    211             mInternalWsFolderPathUpdate = false;
    212 
    213             if (validate) {
    214                 validatePage();
    215             }
    216         }
    217     }
    218 
    219     /**
    220      * Returns the destination folder path relative to the project or an empty string.
    221      *
    222      * @return the currently edited folder
    223      */
    224     public String getWsFolderPath() {
    225         return mWsFolderPathTextField == null ? "" : mWsFolderPathTextField.getText(); //$NON-NLS-1$
    226     }
    227 
    228     /**
    229      * Validates the fields, displays errors and warnings.
    230      * Enables the finish button if there are no errors.
    231      */
    232     private void validatePage() {
    233         String error = null;
    234         String warning = null;
    235 
    236         // -- validate folder configuration
    237         if (error == null) {
    238             ConfigurationState state = mConfigSelector.getState();
    239             if (state == ConfigurationState.INVALID_CONFIG) {
    240                 ResourceQualifier qual = mConfigSelector.getInvalidQualifier();
    241                 if (qual != null) {
    242                     error =
    243                       String.format("The qualifier '%1$s' is invalid in the folder configuration.",
    244                             qual.getName());
    245                 }
    246             } else if (state == ConfigurationState.REGION_WITHOUT_LANGUAGE) {
    247                 error = "The Region qualifier requires the Language qualifier.";
    248             }
    249         }
    250 
    251         // -- validate generated path
    252         if (error == null) {
    253             String wsFolderPath = getWsFolderPath();
    254             if (!wsFolderPath.startsWith(RES_FOLDER_ABS)) {
    255                 error = String.format("Target folder must start with %1$s.", RES_FOLDER_ABS);
    256             }
    257         }
    258 
    259         // -- validate destination file doesn't exist
    260         if (error == null) {
    261             IFile file = mValues.getDestinationFile();
    262             if (file != null && file.exists()) {
    263                 warning = "The destination file already exists";
    264             }
    265         }
    266 
    267         // -- update UI & enable finish if there's no error
    268         setPageComplete(error == null);
    269         if (error != null) {
    270             setMessage(error, IMessageProvider.ERROR);
    271         } else if (warning != null) {
    272             setMessage(warning, IMessageProvider.WARNING);
    273         } else {
    274             setErrorMessage(null);
    275             setMessage(null);
    276         }
    277     }
    278 }
    279