blob: a2a9218b4535c280b2575b89da70d99f7a3667b4 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.sdkuilib.internal.widgets;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
import com.android.sdkuilib.internal.repository.SettingsController;
import com.android.sdkuilib.ui.GridDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Dialog dealing with emulator launch options. The following options are supported:
* <ul>
* <li>-wipe-data</li>
* <li>-scale</li>
* </ul>
*
* Values are stored (in the class as static field) to be reused while the app is still running.
* The Monitor dpi is stored in the settings if availabe.
*/
final class AvdStartDialog extends GridDialog {
// static field to reuse values during the same session.
private static boolean sWipeData = false;
private static int sMonitorDpi = 72; // used if there's no setting controller.
private static final Map<String, String> sSkinScaling = new HashMap<String, String>();
private static final Pattern sScreenSizePattern = Pattern.compile("\\d*(\\.\\d?)?");
private final AvdInfo mAvd;
private final String mSdkLocation;
private final SettingsController mSettingsController;
private Text mScreenSize;
private Text mMonitorDpi;
private Button mScaleButton;
private float mScale = 0.f;
private boolean mWipeData = false;
private int mDensity = 160; // medium density
private int mSize1 = -1;
private int mSize2 = -1;
private String mSkinDisplay;
private boolean mEnableScaling = true;
private Label mScaleField;
AvdStartDialog(Shell parentShell, AvdInfo avd, String sdkLocation,
SettingsController settingsController) {
super(parentShell, 2, false);
mAvd = avd;
mSdkLocation = sdkLocation;
mSettingsController = settingsController;
if (mAvd == null) {
throw new IllegalArgumentException("avd cannot be null");
}
if (mSdkLocation == null) {
throw new IllegalArgumentException("sdkLocation cannot be null");
}
computeSkinData();
}
public boolean getWipeData() {
return mWipeData;
}
/**
* Returns the scaling factor, or 0.f if none are set.
*/
public float getScale() {
return mScale;
}
@Override
public void createDialogContent(final Composite parent) {
GridData gd;
Label l = new Label(parent, SWT.NONE);
l.setText("Skin:");
l = new Label(parent, SWT.NONE);
l.setText(mSkinDisplay == null ? "None" : mSkinDisplay);
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
l = new Label(parent, SWT.NONE);
l.setText("Density:");
l = new Label(parent, SWT.NONE);
l.setText(getDensityText());
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mScaleButton = new Button(parent, SWT.CHECK);
mScaleButton.setText("Scale display to real size");
mScaleButton.setEnabled(mEnableScaling);
boolean defaultState = mEnableScaling && sSkinScaling.get(mAvd.getName()) != null;
mScaleButton.setSelection(defaultState);
mScaleButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
final Group scaleGroup = new Group(parent, SWT.NONE);
scaleGroup.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalIndent = 30;
gd.horizontalSpan = 2;
scaleGroup.setLayout(new GridLayout(3, false));
l = new Label(scaleGroup, SWT.NONE);
l.setText("Screen Size (in):");
mScreenSize = new Text(scaleGroup, SWT.BORDER);
mScreenSize.setText(getScreenSize());
mScreenSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mScreenSize.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent event) {
// combine the current content and the new text
String text = mScreenSize.getText();
text = text.substring(0, event.start) + event.text + text.substring(event.end);
// now make sure it's a match for the regex
event.doit = sScreenSizePattern.matcher(text).matches();
}
});
mScreenSize.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent event) {
onScaleChange();
}
});
// empty composite, only 2 widgets on this line.
new Composite(scaleGroup, SWT.NONE).setLayoutData(gd = new GridData());
gd.widthHint = gd.heightHint = 0;
l = new Label(scaleGroup, SWT.NONE);
l.setText("Monitor dpi:");
mMonitorDpi = new Text(scaleGroup, SWT.BORDER);
mMonitorDpi.setText(Integer.toString(getMonitorDpi()));
mMonitorDpi.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.widthHint = 50;
mMonitorDpi.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent event) {
// check for digit only.
for (int i = 0 ; i < event.text.length(); i++) {
char letter = event.text.charAt(i);
if (letter < '0' || letter > '9') {
event.doit = false;
return;
}
}
}
});
mMonitorDpi.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent event) {
onScaleChange();
}
});
Button button = new Button(scaleGroup, SWT.PUSH | SWT.FLAT);
button.setText("?");
button.setToolTipText("Click to figure out your monitor's pixel density");
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
ResolutionChooserDialog dialog = new ResolutionChooserDialog(parent.getShell());
if (dialog.open() == Window.OK) {
mMonitorDpi.setText(Integer.toString(dialog.getDensity()));
}
}
});
l = new Label(scaleGroup, SWT.NONE);
l.setText("Scale:");
mScaleField = new Label(scaleGroup, SWT.NONE);
mScaleField.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
true /*grabExcessHorizontalSpace*/,
true /*grabExcessVerticalSpace*/,
2 /*horizontalSpan*/,
1 /*verticalSpan*/));
setScale(mScale); // set initial text value
enableGroup(scaleGroup, defaultState);
mScaleButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
boolean enabled = mScaleButton.getSelection();
enableGroup(scaleGroup, enabled);
if (enabled) {
onScaleChange();
} else {
setScale(0);
}
}
});
final Button wipeButton = new Button(parent, SWT.CHECK);
wipeButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
wipeButton.setText("Wipe user data");
wipeButton.setSelection(mWipeData = sWipeData);
wipeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
mWipeData = wipeButton.getSelection();
}
});
l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
// if the scaling is enabled by default, we must initialize the value of mScale
if (defaultState) {
onScaleChange();
}
}
/** On Windows we need to manually enable/disable the children of a group */
private void enableGroup(final Group group, boolean enabled) {
group.setEnabled(enabled);
for (Control c : group.getChildren()) {
c.setEnabled(enabled);
}
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Launch Options");
}
@Override
protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
if (id == IDialogConstants.OK_ID) {
label = "Launch";
}
return super.createButton(parent, id, label, defaultButton);
}
@Override
protected void okPressed() {
// override ok to store some info
// first the monitor dpi
String dpi = mMonitorDpi.getText();
if (dpi.length() > 0) {
sMonitorDpi = Integer.parseInt(dpi);
// if there is a setting controller, save it
if (mSettingsController != null) {
mSettingsController.setMonitorDensity(sMonitorDpi);
mSettingsController.saveSettings();
}
}
// now the scale factor
String key = mAvd.getName();
sSkinScaling.remove(key);
if (mScaleButton.getSelection()) {
String size = mScreenSize.getText();
if (size.length() > 0) {
sSkinScaling.put(key, size);
}
}
// and then the wipe-data checkbox
sWipeData = mWipeData;
// finally continue with the ok action
super.okPressed();
}
private void computeSkinData() {
Map<String, String> prop = mAvd.getProperties();
String dpi = prop.get("hw.lcd.density");
if (dpi != null && dpi.length() > 0) {
mDensity = Integer.parseInt(dpi);
}
findSkinResolution();
}
private void onScaleChange() {
String sizeStr = mScreenSize.getText();
if (sizeStr.length() == 0) {
setScale(0);
return;
}
String dpiStr = mMonitorDpi.getText();
if (dpiStr.length() == 0) {
setScale(0);
return;
}
int dpi = Integer.parseInt(dpiStr);
float size = Float.parseFloat(sizeStr);
/*
* We are trying to emulate the following device:
* resolution: 'mSize1'x'mSize2'
* density: 'mDensity'
* screen diagonal: 'size'
* ontop a monitor running at 'dpi'
*/
// We start by computing the screen diagonal in pixels, if the density was really mDensity
float diagonalPx = (float)Math.sqrt(mSize1*mSize1+mSize2*mSize2);
// Now we would convert this in actual inches:
// diagonalIn = diagonal / mDensity
// the scale factor is a mix of adapting to the new density and to the new size.
// (size/diagonalIn) * (dpi/mDensity)
// this can be simplified to:
setScale((size * dpi) / diagonalPx);
}
private void setScale(float scale) {
mScale = scale;
// Do the rounding exactly like AvdSelector will do.
scale = Math.round(scale * 100);
scale /= 100.f;
if (scale == 0.f) {
mScaleField.setText("default"); //$NON-NLS-1$
} else {
mScaleField.setText(String.format("%.2f", scale)); //$NON-NLS-1$
}
}
/**
* Returns the monitor dpi to start with.
* This can be coming from the settings, the session-based storage, or the from whatever Java
* can tell us.
*/
private int getMonitorDpi() {
if (mSettingsController != null) {
sMonitorDpi = mSettingsController.getMonitorDensity();
}
if (sMonitorDpi == -1) { // first time? try to get a value
sMonitorDpi = Toolkit.getDefaultToolkit().getScreenResolution();
}
return sMonitorDpi;
}
/**
* Returns the screen size to start with.
* <p/>If an emulator with the same skin was already launched, scaled, the size used is reused.
* <p/>Otherwise the default is returned (3)
*/
private String getScreenSize() {
String size = sSkinScaling.get(mAvd.getName());
if (size != null) {
return size;
}
return "3";
}
/**
* Returns a display string for the density.
*/
private String getDensityText() {
switch (mDensity) {
case 120:
return "Low (120)";
case 160:
return "Medium (160)";
case 240:
return "High (240)";
}
return Integer.toString(mDensity);
}
/**
* Finds the skin resolution and sets it in {@link #mSize1} and {@link #mSize2}.
*/
private void findSkinResolution() {
Map<String, String> prop = mAvd.getProperties();
String skinName = prop.get(AvdManager.AVD_INI_SKIN_NAME);
if (skinName != null) {
Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skinName);
if (m != null && m.matches()) {
mSize1 = Integer.parseInt(m.group(1));
mSize2 = Integer.parseInt(m.group(2));
mSkinDisplay = skinName;
mEnableScaling = true;
return;
}
}
// The resolution is inside the layout file of the skin.
mEnableScaling = false; // default to false for now.
// path to the skin layout file.
String skinPath = prop.get(AvdManager.AVD_INI_SKIN_PATH);
if (skinPath != null) {
File skinFolder = new File(mSdkLocation, skinPath);
if (skinFolder.isDirectory()) {
File layoutFile = new File(skinFolder, "layout");
if (layoutFile.isFile()) {
if (parseLayoutFile(layoutFile)) {
mSkinDisplay = String.format("%1$s (%2$dx%3$d)", skinName, mSize1, mSize2);
mEnableScaling = true;
} else {
mSkinDisplay = skinName;
}
}
}
}
}
/**
* Parses a layout file.
* <p/>
* the format is relatively easy. It's a collection of items defined as
* &lg;name&gt; {
* &lg;content&gt;
* }
*
* content is either 1+ items or 1+ properties
* properties are defined as
* &lg;name&gt;&lg;whitespace&gt;&lg;value&gt;
*
* We're going to look for an item called display, with 2 properties height and width.
* This is very basic parser.
*
* @param layoutFile the file to parse
* @return true if both sizes where found.
*/
private boolean parseLayoutFile(File layoutFile) {
try {
BufferedReader input = new BufferedReader(new FileReader(layoutFile));
String line;
while ((line = input.readLine()) != null) {
// trim to remove whitespace
line = line.trim();
int len = line.length();
if (len == 0) continue;
// check if this is a new item
if (line.charAt(len-1) == '{') {
// this is the start of a node
String[] tokens = line.split(" ");
if ("display".equals(tokens[0])) {
// this is the one we're looking for!
while ((mSize1 == -1 || mSize2 == -1) &&
(line = input.readLine()) != null) {
// trim to remove whitespace
line = line.trim();
len = line.length();
if (len == 0) continue;
if ("}".equals(line)) { // looks like we're done with the item.
break;
}
tokens = line.split(" ");
if (tokens.length >= 2) {
// there can be multiple space between the name and value
// in which case we'll get an extra empty token in the middle.
if ("width".equals(tokens[0])) {
mSize1 = Integer.parseInt(tokens[tokens.length-1]);
} else if ("height".equals(tokens[0])) {
mSize2 = Integer.parseInt(tokens[tokens.length-1]);
}
}
}
return mSize1 != -1 && mSize2 != -1;
}
}
}
// if it reaches here, display was not found.
// false is returned below.
} catch (IOException e) {
// ignore.
}
return false;
}
}