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