1 /* 2 * Copyright (C) 2009 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.editors.layout.configuration; 18 19 import com.android.ide.common.resources.configuration.FolderConfiguration; 20 import com.android.ide.common.resources.configuration.LanguageQualifier; 21 import com.android.ide.common.resources.configuration.NightModeQualifier; 22 import com.android.ide.common.resources.configuration.RegionQualifier; 23 import com.android.ide.common.resources.configuration.ResourceQualifier; 24 import com.android.ide.common.resources.configuration.UiModeQualifier; 25 import com.android.ide.common.resources.configuration.VersionQualifier; 26 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 27 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice; 28 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; 29 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState; 30 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.IQualifierFilter; 31 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.SelectorMode; 32 import com.android.sdkuilib.ui.GridDialog; 33 34 import org.eclipse.jface.dialogs.Dialog; 35 import org.eclipse.jface.dialogs.IDialogConstants; 36 import org.eclipse.swt.SWT; 37 import org.eclipse.swt.events.ModifyEvent; 38 import org.eclipse.swt.events.ModifyListener; 39 import org.eclipse.swt.events.VerifyEvent; 40 import org.eclipse.swt.events.VerifyListener; 41 import org.eclipse.swt.graphics.Image; 42 import org.eclipse.swt.layout.GridData; 43 import org.eclipse.swt.layout.GridLayout; 44 import org.eclipse.swt.widgets.Composite; 45 import org.eclipse.swt.widgets.Control; 46 import org.eclipse.swt.widgets.Group; 47 import org.eclipse.swt.widgets.Label; 48 import org.eclipse.swt.widgets.Shell; 49 import org.eclipse.swt.widgets.Text; 50 51 import java.text.DecimalFormat; 52 import java.text.DecimalFormatSymbols; 53 import java.text.ParseException; 54 import java.util.regex.Pattern; 55 56 /** 57 * Dialog to edit both a {@link LayoutDevice}, and a {@link FolderConfiguration} at the same time. 58 */ 59 public class ConfigEditDialog extends GridDialog { 60 61 private static final Pattern FLOAT_PATTERN; 62 static { 63 // get the decimal separator char 64 DecimalFormatSymbols dfs = new DecimalFormatSymbols(); 65 66 // make a pattern that's basically ##.# where .# is optional and where . can be . or , 67 // depending on the locale 68 FLOAT_PATTERN = Pattern.compile( 69 "\\d*(" + //$NON-NLS-1$ 70 Pattern.quote(new String(new char[] { dfs.getDecimalSeparator() })) + 71 "\\d?)?"); //$NON-NLS-1$ 72 } 73 74 private final FolderConfiguration mConfig = new FolderConfiguration(); 75 76 private ConfigurationSelector mConfigSelector; 77 private Composite mStatusComposite; 78 private Label mStatusLabel; 79 private Label mStatusImage; 80 81 private Image mError; 82 83 private String mDeviceName; 84 private String mConfigName; 85 private float mXDpi = Float.NaN; 86 private float mYDpi = Float.NaN; 87 88 private final DecimalFormat mDecimalFormat = new DecimalFormat(); 89 90 91 public ConfigEditDialog(Shell parentShell, FolderConfiguration config) { 92 super(parentShell, 1, false); 93 mConfig.set(config); 94 } 95 96 public void setDeviceName(String name) { 97 mDeviceName = name; 98 } 99 100 public String getDeviceName() { 101 return mDeviceName; 102 } 103 104 public void setXDpi(float xdpi) { 105 mXDpi = xdpi; 106 } 107 108 public float getXDpi() { 109 return mXDpi; 110 } 111 112 public void setYDpi(float ydpi) { 113 mYDpi = ydpi; 114 } 115 116 public float getYDpi() { 117 return mYDpi; 118 } 119 120 public void setConfigName(String name) { 121 mConfigName = name; 122 } 123 124 public String getConfigName() { 125 return mConfigName; 126 } 127 128 public void setConfig(FolderConfiguration config) { 129 mConfig.set(config); 130 } 131 132 public void getConfig(FolderConfiguration config) { 133 config.set(mConfig); 134 config.updateScreenWidthAndHeight(); 135 } 136 137 @Override 138 public void createDialogContent(Composite parent) { 139 mError = IconFactory.getInstance().getIcon("error"); //$NON-NLS-1$ 140 141 Group deviceGroup = new Group(parent, SWT.NONE); 142 deviceGroup.setText("Device"); 143 deviceGroup.setLayoutData(new GridData(GridData.FILL_BOTH)); 144 deviceGroup.setLayout(new GridLayout(2, false)); 145 146 Label l = new Label(deviceGroup, SWT.None); 147 l.setText("Name"); 148 149 final Text deviceNameText = new Text(deviceGroup, SWT.BORDER); 150 deviceNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 151 if (mDeviceName != null) { 152 deviceNameText.setText(mDeviceName); 153 } 154 deviceNameText.addModifyListener(new ModifyListener() { 155 @Override 156 public void modifyText(ModifyEvent e) { 157 mDeviceName = deviceNameText.getText().trim(); 158 validateOk(); 159 } 160 }); 161 162 163 VerifyListener floatVerifier = new VerifyListener() { 164 @Override 165 public void verifyText(VerifyEvent event) { 166 // combine the current content and the new text 167 String text = ((Text)event.widget).getText(); 168 text = text.substring(0, event.start) + event.text + text.substring(event.end); 169 170 // now make sure it's a match for the regex 171 event.doit = FLOAT_PATTERN.matcher(text).matches(); 172 } 173 }; 174 175 l = new Label(deviceGroup, SWT.None); 176 l.setText("x dpi"); 177 178 final Text deviceXDpiText = new Text(deviceGroup, SWT.BORDER); 179 deviceXDpiText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 180 if (Float.isNaN(mXDpi) == false) { 181 deviceXDpiText.setText(String.format("%.1f", mXDpi)); //$NON-NLS-1$ 182 } 183 deviceXDpiText.addVerifyListener(floatVerifier); 184 deviceXDpiText.addModifyListener(new ModifyListener() { 185 @Override 186 public void modifyText(ModifyEvent e) { 187 String value = deviceXDpiText.getText(); 188 if (value.length() == 0) { 189 mXDpi = Float.NaN; 190 } else { 191 try { 192 mXDpi = mDecimalFormat.parse(value).floatValue(); 193 } catch (ParseException exception) { 194 mXDpi = Float.NaN; 195 } 196 } 197 } 198 }); 199 200 l = new Label(deviceGroup, SWT.None); 201 l.setText("y dpi"); 202 203 final Text deviceYDpiText = new Text(deviceGroup, SWT.BORDER); 204 deviceYDpiText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 205 if (Float.isNaN(mYDpi) == false) { 206 deviceYDpiText.setText(String.format("%.1f", mYDpi)); //$NON-NLS-1$ 207 } 208 deviceYDpiText.addVerifyListener(floatVerifier); 209 deviceYDpiText.addModifyListener(new ModifyListener() { 210 @Override 211 public void modifyText(ModifyEvent e) { 212 String value = deviceYDpiText.getText(); 213 if (value.length() == 0) { 214 mYDpi = Float.NaN; 215 } else { 216 try { 217 mYDpi = mDecimalFormat.parse(value).floatValue(); 218 } catch (ParseException exception) { 219 mYDpi = Float.NaN; 220 } 221 } 222 } 223 }); 224 225 Group configGroup = new Group(parent, SWT.NONE); 226 configGroup.setText("Configuration"); 227 configGroup.setLayoutData(new GridData(GridData.FILL_BOTH)); 228 configGroup.setLayout(new GridLayout(2, false)); 229 230 l = new Label(configGroup, SWT.None); 231 l.setText("Name"); 232 233 final Text configNameText = new Text(configGroup, SWT.BORDER); 234 configNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 235 if (mConfigName != null) { 236 configNameText.setText(mConfigName); 237 } 238 configNameText.addModifyListener(new ModifyListener() { 239 @Override 240 public void modifyText(ModifyEvent e) { 241 mConfigName = configNameText.getText().trim(); 242 validateOk(); 243 } 244 }); 245 246 mConfigSelector = new ConfigurationSelector(configGroup, SelectorMode.DEVICE_ONLY); 247 // configure the selector to be in "device mode" and not accept language/region/version 248 // since those are selected from a different combo 249 // FIXME: add version combo. 250 mConfigSelector.setQualifierFilter(new IQualifierFilter() { 251 @Override 252 public boolean accept(ResourceQualifier qualifier) { 253 if (qualifier instanceof LanguageQualifier || 254 qualifier instanceof RegionQualifier || 255 qualifier instanceof UiModeQualifier || 256 qualifier instanceof NightModeQualifier || 257 qualifier instanceof VersionQualifier) { 258 return false; 259 } 260 261 return true; 262 } 263 }); 264 mConfigSelector.setConfiguration(mConfig); 265 GridData gd; 266 mConfigSelector.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); 267 gd.horizontalSpan = 2; 268 gd.widthHint = ConfigurationSelector.WIDTH_HINT; 269 gd.heightHint = ConfigurationSelector.HEIGHT_HINT; 270 271 // add a listener to check on the validity of the FolderConfiguration as 272 // they are built. 273 mConfigSelector.setOnChangeListener(new Runnable() { 274 @Override 275 public void run() { 276 if (mConfigSelector.getState() == ConfigurationState.OK) { 277 mConfigSelector.getConfiguration(mConfig); 278 } 279 280 validateOk(); 281 } 282 }); 283 284 mStatusComposite = new Composite(parent, SWT.NONE); 285 mStatusComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 286 GridLayout gl = new GridLayout(2, false); 287 mStatusComposite.setLayout(gl); 288 gl.marginHeight = gl.marginWidth = 0; 289 290 mStatusImage = new Label(mStatusComposite, SWT.NONE); 291 mStatusLabel = new Label(mStatusComposite, SWT.NONE); 292 mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 293 resetStatus(); 294 } 295 296 @Override 297 protected Control createContents(Composite parent) { 298 Control c = super.createContents(parent); 299 validateOk(); 300 return c; 301 } 302 303 /** 304 * resets the status label to show the file that will be created. 305 */ 306 private void resetStatus() { 307 String displayString = Dialog.shortenText( 308 String.format("Config: %1$s", mConfig.toString()), 309 mStatusLabel); 310 mStatusLabel.setText(displayString); 311 } 312 313 private void setError(String text) { 314 String displayString = Dialog.shortenText(text, mStatusLabel); 315 mStatusLabel.setText(displayString); 316 mStatusImage.setImage(mError); 317 getButton(IDialogConstants.OK_ID).setEnabled(false); 318 } 319 320 private void validateOk() { 321 // check the device name 322 if (mDeviceName == null || mDeviceName.length() == 0) { 323 setError("Device name must not be empty"); 324 return; 325 } 326 327 // check the config name 328 if (mConfigName == null || mConfigName.length() == 0) { 329 setError("Configuration name must not be empty"); 330 return; 331 } 332 333 // and check the config itself 334 ConfigurationState state = mConfigSelector.getState(); 335 336 switch (state) { 337 case INVALID_CONFIG: 338 ResourceQualifier invalidQualifier = mConfigSelector.getInvalidQualifier(); 339 setError(String.format( 340 "Invalid Configuration: %1$s has no filter set.", 341 invalidQualifier.getName())); 342 return; 343 case REGION_WITHOUT_LANGUAGE: 344 setError("The Region qualifier requires the Language qualifier."); 345 return; 346 } 347 348 // no error 349 mStatusImage.setImage(null); 350 resetStatus(); 351 getButton(IDialogConstants.OK_ID).setEnabled(true); 352 353 // need to relayout, because of the change in size in mErrorImage. 354 mStatusComposite.layout(); 355 } 356 } 357