2 * Copyright (C) 2009 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.sdkuilib.internal.widgets;
19 import com.android.sdklib.internal.avd.AvdManager;
20 import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
21 import com.android.sdkuilib.internal.repository.SettingsController;
22 import com.android.sdkuilib.ui.GridDialog;
24 import org.eclipse.jface.dialogs.IDialogConstants;
25 import org.eclipse.jface.window.Window;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.events.ModifyEvent;
28 import org.eclipse.swt.events.ModifyListener;
29 import org.eclipse.swt.events.SelectionAdapter;
30 import org.eclipse.swt.events.SelectionEvent;
31 import org.eclipse.swt.events.VerifyEvent;
32 import org.eclipse.swt.events.VerifyListener;
33 import org.eclipse.swt.layout.GridData;
34 import org.eclipse.swt.layout.GridLayout;
35 import org.eclipse.swt.widgets.Button;
36 import org.eclipse.swt.widgets.Composite;
37 import org.eclipse.swt.widgets.Control;
38 import org.eclipse.swt.widgets.Group;
39 import org.eclipse.swt.widgets.Label;
40 import org.eclipse.swt.widgets.Shell;
41 import org.eclipse.swt.widgets.Text;
43 import java.awt.Toolkit;
44 import java.io.BufferedReader;
46 import java.io.FileReader;
47 import java.io.IOException;
48 import java.util.HashMap;
50 import java.util.regex.Matcher;
51 import java.util.regex.Pattern;
54 * Dialog dealing with emulator launch options. The following options are supported:
60 * Values are stored (in the class as static field) to be reused while the app is still running.
61 * The Monitor dpi is stored in the settings if availabe.
63 final class AvdStartDialog extends GridDialog {
64 // static field to reuse values during the same session.
65 private static boolean sWipeData = false;
66 private static int sMonitorDpi = 72; // used if there's no setting controller.
67 private static final Map<String, String> sSkinScaling = new HashMap<String, String>();
69 private static final Pattern sScreenSizePattern = Pattern.compile("\\d*(\\.\\d?)?");
71 private final AvdInfo mAvd;
72 private final String mSdkLocation;
73 private final SettingsController mSettingsController;
75 private Text mScreenSize;
76 private Text mMonitorDpi;
77 private Button mScaleButton;
79 private float mScale = 0.f;
80 private boolean mWipeData = false;
81 private int mDensity = 160; // medium density
82 private int mSize1 = -1;
83 private int mSize2 = -1;
84 private String mSkinDisplay;
85 private boolean mEnableScaling = true;
86 private Label mScaleField;
88 AvdStartDialog(Shell parentShell, AvdInfo avd, String sdkLocation,
89 SettingsController settingsController) {
90 super(parentShell, 2, false);
92 mSdkLocation = sdkLocation;
93 mSettingsController = settingsController;
95 throw new IllegalArgumentException("avd cannot be null");
97 if (mSdkLocation == null) {
98 throw new IllegalArgumentException("sdkLocation cannot be null");
104 public boolean getWipeData() {
109 * Returns the scaling factor, or 0.f if none are set.
111 public float getScale() {
116 public void createDialogContent(final Composite parent) {
119 Label l = new Label(parent, SWT.NONE);
122 l = new Label(parent, SWT.NONE);
123 l.setText(mSkinDisplay == null ? "None" : mSkinDisplay);
124 l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
126 l = new Label(parent, SWT.NONE);
127 l.setText("Density:");
129 l = new Label(parent, SWT.NONE);
130 l.setText(getDensityText());
131 l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
133 mScaleButton = new Button(parent, SWT.CHECK);
134 mScaleButton.setText("Scale display to real size");
135 mScaleButton.setEnabled(mEnableScaling);
136 boolean defaultState = mEnableScaling && sSkinScaling.get(mAvd.getName()) != null;
137 mScaleButton.setSelection(defaultState);
138 mScaleButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
139 gd.horizontalSpan = 2;
140 final Group scaleGroup = new Group(parent, SWT.NONE);
141 scaleGroup.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
142 gd.horizontalIndent = 30;
143 gd.horizontalSpan = 2;
144 scaleGroup.setLayout(new GridLayout(3, false));
146 l = new Label(scaleGroup, SWT.NONE);
147 l.setText("Screen Size (in):");
148 mScreenSize = new Text(scaleGroup, SWT.BORDER);
149 mScreenSize.setText(getScreenSize());
150 mScreenSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
151 mScreenSize.addVerifyListener(new VerifyListener() {
152 public void verifyText(VerifyEvent event) {
153 // combine the current content and the new text
154 String text = mScreenSize.getText();
155 text = text.substring(0, event.start) + event.text + text.substring(event.end);
157 // now make sure it's a match for the regex
158 event.doit = sScreenSizePattern.matcher(text).matches();
161 mScreenSize.addModifyListener(new ModifyListener() {
162 public void modifyText(ModifyEvent event) {
167 // empty composite, only 2 widgets on this line.
168 new Composite(scaleGroup, SWT.NONE).setLayoutData(gd = new GridData());
169 gd.widthHint = gd.heightHint = 0;
171 l = new Label(scaleGroup, SWT.NONE);
172 l.setText("Monitor dpi:");
173 mMonitorDpi = new Text(scaleGroup, SWT.BORDER);
174 mMonitorDpi.setText(Integer.toString(getMonitorDpi()));
175 mMonitorDpi.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
177 mMonitorDpi.addVerifyListener(new VerifyListener() {
178 public void verifyText(VerifyEvent event) {
179 // check for digit only.
180 for (int i = 0 ; i < event.text.length(); i++) {
181 char letter = event.text.charAt(i);
182 if (letter < '0' || letter > '9') {
189 mMonitorDpi.addModifyListener(new ModifyListener() {
190 public void modifyText(ModifyEvent event) {
195 Button button = new Button(scaleGroup, SWT.PUSH | SWT.FLAT);
197 button.setToolTipText("Click to figure out your monitor's pixel density");
198 button.addSelectionListener(new SelectionAdapter() {
200 public void widgetSelected(SelectionEvent arg0) {
201 ResolutionChooserDialog dialog = new ResolutionChooserDialog(parent.getShell());
202 if (dialog.open() == Window.OK) {
203 mMonitorDpi.setText(Integer.toString(dialog.getDensity()));
208 l = new Label(scaleGroup, SWT.NONE);
210 mScaleField = new Label(scaleGroup, SWT.NONE);
211 mScaleField.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
212 true /*grabExcessHorizontalSpace*/,
213 true /*grabExcessVerticalSpace*/,
214 2 /*horizontalSpan*/,
215 1 /*verticalSpan*/));
216 setScale(mScale); // set initial text value
218 enableGroup(scaleGroup, defaultState);
220 mScaleButton.addSelectionListener(new SelectionAdapter() {
222 public void widgetSelected(SelectionEvent event) {
223 boolean enabled = mScaleButton.getSelection();
224 enableGroup(scaleGroup, enabled);
233 final Button wipeButton = new Button(parent, SWT.CHECK);
234 wipeButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
235 gd.horizontalSpan = 2;
236 wipeButton.setText("Wipe user data");
237 wipeButton.setSelection(mWipeData = sWipeData);
238 wipeButton.addSelectionListener(new SelectionAdapter() {
240 public void widgetSelected(SelectionEvent arg0) {
241 mWipeData = wipeButton.getSelection();
245 l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
246 l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
247 gd.horizontalSpan = 2;
249 // if the scaling is enabled by default, we must initialize the value of mScale
255 /** On Windows we need to manually enable/disable the children of a group */
256 private void enableGroup(final Group group, boolean enabled) {
257 group.setEnabled(enabled);
258 for (Control c : group.getChildren()) {
259 c.setEnabled(enabled);
264 protected void configureShell(Shell newShell) {
265 super.configureShell(newShell);
266 newShell.setText("Launch Options");
270 protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
271 if (id == IDialogConstants.OK_ID) {
275 return super.createButton(parent, id, label, defaultButton);
279 protected void okPressed() {
280 // override ok to store some info
281 // first the monitor dpi
282 String dpi = mMonitorDpi.getText();
283 if (dpi.length() > 0) {
284 sMonitorDpi = Integer.parseInt(dpi);
286 // if there is a setting controller, save it
287 if (mSettingsController != null) {
288 mSettingsController.setMonitorDensity(sMonitorDpi);
289 mSettingsController.saveSettings();
293 // now the scale factor
294 String key = mAvd.getName();
295 sSkinScaling.remove(key);
296 if (mScaleButton.getSelection()) {
297 String size = mScreenSize.getText();
298 if (size.length() > 0) {
299 sSkinScaling.put(key, size);
303 // and then the wipe-data checkbox
304 sWipeData = mWipeData;
306 // finally continue with the ok action
310 private void computeSkinData() {
311 Map<String, String> prop = mAvd.getProperties();
312 String dpi = prop.get("hw.lcd.density");
313 if (dpi != null && dpi.length() > 0) {
314 mDensity = Integer.parseInt(dpi);
317 findSkinResolution();
320 private void onScaleChange() {
321 String sizeStr = mScreenSize.getText();
322 if (sizeStr.length() == 0) {
327 String dpiStr = mMonitorDpi.getText();
328 if (dpiStr.length() == 0) {
333 int dpi = Integer.parseInt(dpiStr);
334 float size = Float.parseFloat(sizeStr);
336 * We are trying to emulate the following device:
337 * resolution: 'mSize1'x'mSize2'
338 * density: 'mDensity'
339 * screen diagonal: 'size'
340 * ontop a monitor running at 'dpi'
342 // We start by computing the screen diagonal in pixels, if the density was really mDensity
343 float diagonalPx = (float)Math.sqrt(mSize1*mSize1+mSize2*mSize2);
344 // Now we would convert this in actual inches:
345 // diagonalIn = diagonal / mDensity
346 // the scale factor is a mix of adapting to the new density and to the new size.
347 // (size/diagonalIn) * (dpi/mDensity)
348 // this can be simplified to:
349 setScale((size * dpi) / diagonalPx);
352 private void setScale(float scale) {
355 // Do the rounding exactly like AvdSelector will do.
356 scale = Math.round(scale * 100);
360 mScaleField.setText("default"); //$NON-NLS-1$
362 mScaleField.setText(String.format("%.2f", scale)); //$NON-NLS-1$
367 * Returns the monitor dpi to start with.
368 * This can be coming from the settings, the session-based storage, or the from whatever Java
371 private int getMonitorDpi() {
372 if (mSettingsController != null) {
373 sMonitorDpi = mSettingsController.getMonitorDensity();
376 if (sMonitorDpi == -1) { // first time? try to get a value
377 sMonitorDpi = Toolkit.getDefaultToolkit().getScreenResolution();
384 * Returns the screen size to start with.
385 * <p/>If an emulator with the same skin was already launched, scaled, the size used is reused.
386 * <p/>Otherwise the default is returned (3)
388 private String getScreenSize() {
389 String size = sSkinScaling.get(mAvd.getName());
398 * Returns a display string for the density.
400 private String getDensityText() {
405 return "Medium (160)";
410 return Integer.toString(mDensity);
414 * Finds the skin resolution and sets it in {@link #mSize1} and {@link #mSize2}.
416 private void findSkinResolution() {
417 Map<String, String> prop = mAvd.getProperties();
418 String skinName = prop.get(AvdManager.AVD_INI_SKIN_NAME);
420 if (skinName != null) {
421 Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skinName);
422 if (m != null && m.matches()) {
423 mSize1 = Integer.parseInt(m.group(1));
424 mSize2 = Integer.parseInt(m.group(2));
425 mSkinDisplay = skinName;
426 mEnableScaling = true;
431 // The resolution is inside the layout file of the skin.
432 mEnableScaling = false; // default to false for now.
434 // path to the skin layout file.
435 String skinPath = prop.get(AvdManager.AVD_INI_SKIN_PATH);
436 if (skinPath != null) {
437 File skinFolder = new File(mSdkLocation, skinPath);
438 if (skinFolder.isDirectory()) {
439 File layoutFile = new File(skinFolder, "layout");
440 if (layoutFile.isFile()) {
441 if (parseLayoutFile(layoutFile)) {
442 mSkinDisplay = String.format("%1$s (%2$dx%3$d)", skinName, mSize1, mSize2);
443 mEnableScaling = true;
445 mSkinDisplay = skinName;
453 * Parses a layout file.
455 * the format is relatively easy. It's a collection of items defined as
460 * content is either 1+ items or 1+ properties
461 * properties are defined as
462 * ≶name>≶whitespace>≶value>
464 * We're going to look for an item called display, with 2 properties height and width.
465 * This is very basic parser.
467 * @param layoutFile the file to parse
468 * @return true if both sizes where found.
470 private boolean parseLayoutFile(File layoutFile) {
472 BufferedReader input = new BufferedReader(new FileReader(layoutFile));
475 while ((line = input.readLine()) != null) {
476 // trim to remove whitespace
478 int len = line.length();
479 if (len == 0) continue;
481 // check if this is a new item
482 if (line.charAt(len-1) == '{') {
483 // this is the start of a node
484 String[] tokens = line.split(" ");
485 if ("display".equals(tokens[0])) {
486 // this is the one we're looking for!
487 while ((mSize1 == -1 || mSize2 == -1) &&
488 (line = input.readLine()) != null) {
489 // trim to remove whitespace
492 if (len == 0) continue;
494 if ("}".equals(line)) { // looks like we're done with the item.
498 tokens = line.split(" ");
499 if (tokens.length >= 2) {
500 // there can be multiple space between the name and value
501 // in which case we'll get an extra empty token in the middle.
502 if ("width".equals(tokens[0])) {
503 mSize1 = Integer.parseInt(tokens[tokens.length-1]);
504 } else if ("height".equals(tokens[0])) {
505 mSize2 = Integer.parseInt(tokens[tokens.length-1]);
510 return mSize1 != -1 && mSize2 != -1;
515 // if it reaches here, display was not found.
516 // false is returned below.
517 } catch (IOException e) {