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 public void modifyText(ModifyEvent e) { 156 mDeviceName = deviceNameText.getText().trim(); 157 validateOk(); 158 } 159 }); 160 161 162 VerifyListener floatVerifier = new VerifyListener() { 163 public void verifyText(VerifyEvent event) { 164 // combine the current content and the new text 165 String text = ((Text)event.widget).getText(); 166 text = text.substring(0, event.start) + event.text + text.substring(event.end); 167 168 // now make sure it's a match for the regex 169 event.doit = FLOAT_PATTERN.matcher(text).matches(); 170 } 171 }; 172 173 l = new Label(deviceGroup, SWT.None); 174 l.setText("x dpi"); 175 176 final Text deviceXDpiText = new Text(deviceGroup, SWT.BORDER); 177 deviceXDpiText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 178 if (Float.isNaN(mXDpi) == false) { 179 deviceXDpiText.setText(String.format("%.1f", mXDpi)); //$NON-NLS-1$ 180 } 181 deviceXDpiText.addVerifyListener(floatVerifier); 182 deviceXDpiText.addModifyListener(new ModifyListener() { 183 public void modifyText(ModifyEvent e) { 184 String value = deviceXDpiText.getText(); 185 if (value.length() == 0) { 186 mXDpi = Float.NaN; 187 } else { 188 try { 189 mXDpi = mDecimalFormat.parse(value).floatValue(); 190 } catch (ParseException exception) { 191 mXDpi = Float.NaN; 192 } 193 } 194 } 195 }); 196 197 l = new Label(deviceGroup, SWT.None); 198 l.setText("y dpi"); 199 200 final Text deviceYDpiText = new Text(deviceGroup, SWT.BORDER); 201 deviceYDpiText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 202 if (Float.isNaN(mYDpi) == false) { 203 deviceYDpiText.setText(String.format("%.1f", mYDpi)); //$NON-NLS-1$ 204 } 205 deviceYDpiText.addVerifyListener(floatVerifier); 206 deviceYDpiText.addModifyListener(new ModifyListener() { 207 public void modifyText(ModifyEvent e) { 208 String value = deviceYDpiText.getText(); 209 if (value.length() == 0) { 210 mYDpi = Float.NaN; 211 } else { 212 try { 213 mYDpi = mDecimalFormat.parse(value).floatValue(); 214 } catch (ParseException exception) { 215 mYDpi = Float.NaN; 216 } 217 } 218 } 219 }); 220 221 Group configGroup = new Group(parent, SWT.NONE); 222 configGroup.setText("Configuration"); 223 configGroup.setLayoutData(new GridData(GridData.FILL_BOTH)); 224 configGroup.setLayout(new GridLayout(2, false)); 225 226 l = new Label(configGroup, SWT.None); 227 l.setText("Name"); 228 229 final Text configNameText = new Text(configGroup, SWT.BORDER); 230 configNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 231 if (mConfigName != null) { 232 configNameText.setText(mConfigName); 233 } 234 configNameText.addModifyListener(new ModifyListener() { 235 public void modifyText(ModifyEvent e) { 236 mConfigName = configNameText.getText().trim(); 237 validateOk(); 238 } 239 }); 240 241 mConfigSelector = new ConfigurationSelector(configGroup, SelectorMode.DEVICE_ONLY); 242 // configure the selector to be in "device mode" and not accept language/region/version 243 // since those are selected from a different combo 244 // FIXME: add version combo. 245 mConfigSelector.setQualifierFilter(new IQualifierFilter() { 246 public boolean accept(ResourceQualifier qualifier) { 247 if (qualifier instanceof LanguageQualifier || 248 qualifier instanceof RegionQualifier || 249 qualifier instanceof UiModeQualifier || 250 qualifier instanceof NightModeQualifier || 251 qualifier instanceof VersionQualifier) { 252 return false; 253 } 254 255 return true; 256 } 257 }); 258 mConfigSelector.setConfiguration(mConfig); 259 GridData gd; 260 mConfigSelector.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); 261 gd.horizontalSpan = 2; 262 gd.widthHint = ConfigurationSelector.WIDTH_HINT; 263 gd.heightHint = ConfigurationSelector.HEIGHT_HINT; 264 265 // add a listener to check on the validity of the FolderConfiguration as 266 // they are built. 267 mConfigSelector.setOnChangeListener(new Runnable() { 268 public void run() { 269 if (mConfigSelector.getState() == ConfigurationState.OK) { 270 mConfigSelector.getConfiguration(mConfig); 271 } 272 273 validateOk(); 274 } 275 }); 276 277 mStatusComposite = new Composite(parent, SWT.NONE); 278 mStatusComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 279 GridLayout gl = new GridLayout(2, false); 280 mStatusComposite.setLayout(gl); 281 gl.marginHeight = gl.marginWidth = 0; 282 283 mStatusImage = new Label(mStatusComposite, SWT.NONE); 284 mStatusLabel = new Label(mStatusComposite, SWT.NONE); 285 mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 286 resetStatus(); 287 } 288 289 @Override 290 protected Control createContents(Composite parent) { 291 Control c = super.createContents(parent); 292 validateOk(); 293 return c; 294 } 295 296 /** 297 * resets the status label to show the file that will be created. 298 */ 299 private void resetStatus() { 300 String displayString = Dialog.shortenText( 301 String.format("Config: %1$s", mConfig.toString()), 302 mStatusLabel); 303 mStatusLabel.setText(displayString); 304 } 305 306 private void setError(String text) { 307 String displayString = Dialog.shortenText(text, mStatusLabel); 308 mStatusLabel.setText(displayString); 309 mStatusImage.setImage(mError); 310 getButton(IDialogConstants.OK_ID).setEnabled(false); 311 } 312 313 private void validateOk() { 314 // check the device name 315 if (mDeviceName == null || mDeviceName.length() == 0) { 316 setError("Device name must not be empty"); 317 return; 318 } 319 320 // check the config name 321 if (mConfigName == null || mConfigName.length() == 0) { 322 setError("Configuration name must not be empty"); 323 return; 324 } 325 326 // and check the config itself 327 ConfigurationState state = mConfigSelector.getState(); 328 329 switch (state) { 330 case INVALID_CONFIG: 331 ResourceQualifier invalidQualifier = mConfigSelector.getInvalidQualifier(); 332 setError(String.format( 333 "Invalid Configuration: %1$s has no filter set.", 334 invalidQualifier.getName())); 335 return; 336 case REGION_WITHOUT_LANGUAGE: 337 setError("The Region qualifier requires the Language qualifier."); 338 return; 339 } 340 341 // no error 342 mStatusImage.setImage(null); 343 resetStatus(); 344 getButton(IDialogConstants.OK_ID).setEnabled(true); 345 346 // need to relayout, because of the change in size in mErrorImage. 347 mStatusComposite.layout(); 348 } 349 } 350