| /* |
| * 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 |
| * ≶name> { |
| * ≶content> |
| * } |
| * |
| * content is either 1+ items or 1+ properties |
| * properties are defined as |
| * ≶name>≶whitespace>≶value> |
| * |
| * 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; |
| } |
| } |