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