/*
* Copyright (C) 2012 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.motorola.studio.android.emulator.core.emulationui;
import static com.motorola.studio.android.common.log.StudioLogger.debug;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.osgi.util.NLS;
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.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager;
import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance;
import com.motorola.studio.android.emulator.i18n.EmulatorNLS;
/**
* DESCRIPTION:
* This is a composite that is used by several emulation UI elements to choose source and
* destination elements
*
* RESPONSIBILITY:
* Provide means for the user to choose which emulator and phone number will be involved
* in a emulation
*
* COLABORATORS:
* None.
*
* USAGE:
* Add the composite to a UI element that needs to have a emulator and a phone number
* chosen by the user
*/
public class SrcDestComposite extends Composite
{
/**
* Message that will be shown near the emulator combo
*/
private String emulatorLabelStr;
/**
* Message that will be shown near the phone number text field
*/
private String phoneNumberLabelStr;
/**
* Emulator currently selected
*/
private String selectedEmulator;
/**
* Phone number currently selected
*/
private String selectedPhoneNumber;
/**
* True if the composite is valid and can provide information to the user class
* False if not.
*/
private boolean isValid = false;
/**
* Error message to be shown to the user if the composite data is not valid
*/
private String errorMessage =
NLS.bind(EmulatorNLS.ERR_SrcDestComposite_InvalidFillingBase,
EmulatorNLS.ERR_SrcDestComposite_InvalidFillingPhoneNumber,
EmulatorNLS.ERR_SrcDestComposite_InvalidFillingEmulator);
// Widgets
private Combo runningEmulatorsCombo;
private Text phoneNumberText;
// attribute for calculating label sizes (for layout purposes)
private FontMetrics fontMetrics = null;
/**
* Constructor.
*
* @param parent The parent composite of this one
* @param style Style of the composite. See constants at SWT class
* @param showSrcControls True if this composite should show
* the emulation source controls. False otherwise
* @param isEmulatorSrc True if this composite will have the emulator
* part as source in emulation. False if the phone number
* will be the source
*/
public SrcDestComposite(Composite parent, int style, boolean showSrcControls,
boolean isEmulatorSrc)
{
super(parent, style);
GridLayout layout = new GridLayout(2, false);
layout.marginHeight = 5;
layout.marginWidth = 5;
layout.verticalSpacing = 5;
layout.horizontalSpacing = 2;
this.setLayout(layout);
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
this.setLayoutData(data);
// initialize font metrics
GC gc = new GC(this);
gc.setFont(this.getFont());
fontMetrics = gc.getFontMetrics();
gc.dispose();
if (isEmulatorSrc)
{
// When emulator is the source part, its UI is build prior to phone number UI,
// and appropriate labels are used for both
debug("Using emulator as source");
emulatorLabelStr = EmulatorNLS.UI_SrcDestComposite_OriginatingRunningEmulatorLabel;
phoneNumberLabelStr = EmulatorNLS.UI_SrcDestComposite_DestinationPhoneNumberLabel;
if (showSrcControls)
{
debug("Showing source controls");
createEmulatorUI();
}
createPhoneNumberUI();
}
else
{
// When phone number is the source part, its UI is build prior to emulator UI,
// and appropriate labels are used for both
debug("Using phone number as source");
emulatorLabelStr = EmulatorNLS.UI_SrcDestComposite_DestinationRunningEmulatorLabel;
phoneNumberLabelStr = EmulatorNLS.UI_SrcDestComposite_OriginatingPhoneNumberLabel;
if (showSrcControls)
{
debug("Showing source controls");
createPhoneNumberUI();
}
createEmulatorUI();
}
addListeners();
// call the check method to refresh error message.
checkData();
}
/**
* Build the emulator part controls
*/
private void createEmulatorUI()
{
Label runningEmulatorsLabel = new Label(this, SWT.NONE);
runningEmulatorsLabel.setText(emulatorLabelStr);
GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
data.widthHint = getLabelWidthHint(runningEmulatorsLabel);
runningEmulatorsLabel.setLayoutData(data);
this.runningEmulatorsCombo = new Combo(this, SWT.BORDER | SWT.READ_ONLY);
data = new GridData(SWT.FILL, SWT.FILL, true, false);
this.runningEmulatorsCombo.setLayoutData(data);
populateEmulatorCombo();
}
/**
* Build the phone number part controls
*/
private void createPhoneNumberUI()
{
Label phoneNumberLabel = new Label(this, SWT.NONE);
phoneNumberLabel.setText(phoneNumberLabelStr);
GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
data.widthHint = getLabelWidthHint(phoneNumberLabel);
phoneNumberLabel.setLayoutData(data);
this.phoneNumberText = new Text(this, SWT.BORDER);
data = new GridData(SWT.FILL, SWT.FILL, true, false);
this.phoneNumberText.setLayoutData(data);
this.phoneNumberText.setTextLimit(40);
}
/**
* Add listeners to the composite controls
*/
private void addListeners()
{
if (runningEmulatorsCombo != null)
{
runningEmulatorsCombo.addSelectionListener(new SelectionAdapter()
{
@Override
public void widgetSelected(SelectionEvent e)
{
selectedEmulator = getCurrentlySelectedIdentifier();
checkData();
}
});
}
if (phoneNumberText != null)
{
phoneNumberText.addModifyListener(new ModifyListener()
{
public void modifyText(ModifyEvent e)
{
selectedPhoneNumber = phoneNumberText.getText();
checkData();
}
});
}
}
/**
* Defines the width hint to be used for the given label on a GridData object.
*
* @param label the label to calculate the width hint for
*
* @return the width hint
*/
private int getLabelWidthHint(Label label)
{
int widthHint = Dialog.convertHorizontalDLUsToPixels(fontMetrics, label.getText().length());
return Math.max(widthHint, label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
}
/**
* Populates the emulator combo box with the currently running emulators
*/
private void populateEmulatorCombo()
{
// Populating emulator combo with all running emulator names
// Besides, keeping an array of the identifiers as combo data.
Map<String, String> identifiersAndNames = new HashMap<String, String>();
Collection<IAndroidEmulatorInstance> startedInstances =
DeviceFrameworkManager.getInstance().getAllStartedInstances();
for (IAndroidEmulatorInstance instance : startedInstances)
{
identifiersAndNames.put(instance.getInstanceIdentifier(), instance.getName());
}
String[] instanceNamesArray = new String[identifiersAndNames.size()];
String[] identifiersArray = new String[identifiersAndNames.size()];
int i = 0;
Set<String> identifiers = identifiersAndNames.keySet();
for (String identifier : identifiers)
{
String viewerName = identifiersAndNames.get(identifier);
// It is VERY important that the index used at the data array is equal to the
// index used at the items array. According to the selected item in the combo, the
// corresponding identifier is retrieved from the data array in the future
instanceNamesArray[i] = viewerName;
identifiersArray[i] = identifier;
i++;
}
runningEmulatorsCombo.setItems(instanceNamesArray);
runningEmulatorsCombo.setData(identifiersArray);
// if there is just one emulator in the combo list,
// it will be chose by default.
if (runningEmulatorsCombo.getItemCount() == 1)
{
runningEmulatorsCombo.select(0);
selectedEmulator = getCurrentlySelectedIdentifier();
checkData();
}
}
/**
* Retrieve the identifier of the selected instance at Android Emulator combo box
*
* @return The identifier, or an empty string if no emulator is selected
*/
private String getCurrentlySelectedIdentifier()
{
String currentlySelectedSerial = "";
int index = runningEmulatorsCombo.getSelectionIndex();
if (index >= 0)
{
String[] serials = (String[]) runningEmulatorsCombo.getData();
currentlySelectedSerial = serials[index];
}
return currentlySelectedSerial;
}
/**
* Get the emulator identifier that was selected by the user
*
* @return The selected emulator identifier
*/
public String getSelectedEmulator()
{
return selectedEmulator;
}
/**
* Get the phone number that was typed by the user
*
* @return The phone number typed by the user
*/
public String getSelectedPhoneNumber()
{
return selectedPhoneNumber;
}
/**
* Tests if the values chosen/typed by the user are valid
* By invoking this method, the user class is able to know if it can proceed
*
* @return True if the user has chosen valid values. False otherwise
*/
public boolean isValid()
{
return isValid;
}
/**
* Retrieves the error message to be shown to the user if the composite is
* not valid
*
* @return The error message if the composite is not valid, or <code>null</code> if
* the composite is valid and no error message should be displayed.
*/
public String getErrorMessage()
{
return errorMessage;
}
/**
* Check if the data entered by the user is correct and set instance variables
* to store the test results
*/
private void checkData()
{
isValid = false;
boolean isEmulatorValid = false;
boolean isPhoneNumberValid = false;
boolean isUsingPhoneNumber = (phoneNumberText != null);
boolean isUsingEmulator = (runningEmulatorsCombo != null);
// Tests if emulator selection is valid.
//
// If the emulator combo is null, that means that the user decided not to use it. In
// this case, it will always be valid. Otherwise, the combo selection needs to be
// not null and not blank
if ((!isUsingEmulator) || ((selectedEmulator != null) && (!selectedEmulator.equals(""))))
{
isEmulatorValid = true;
}
// Tests if phone number selection is valid.
//
// If the phone number text is null, that means that the user decided not to use it. In
// this case, it will always be valid. Otherwise, the text field selection needs to be
// not null, not blank and can be parsed to double (that means that the contents are
// composed by numerals only)
if (!isUsingPhoneNumber)
{
isPhoneNumberValid = true;
}
else if ((selectedPhoneNumber != null) && (!selectedPhoneNumber.equals("")))
{
Pattern p = Pattern.compile("(\\d)+");
Matcher m = p.matcher(selectedPhoneNumber);
isPhoneNumberValid = m.matches();
}
// Based on previous checks, determine if the composite state is valid
if (isEmulatorValid && isPhoneNumberValid)
{
isValid = true;
errorMessage = null;
}
else
{
// If not valid, an error message will be shown. The following calculations
// are for determining which error has happened to build the message
String phoneNumberError = "";
String emulatorError = "";
if (isUsingPhoneNumber && (!isPhoneNumberValid))
{
phoneNumberError = EmulatorNLS.ERR_SrcDestComposite_InvalidFillingPhoneNumber;
}
if (isUsingEmulator && (!isEmulatorValid))
{
emulatorError = EmulatorNLS.ERR_SrcDestComposite_InvalidFillingEmulator;
}
errorMessage =
NLS.bind(EmulatorNLS.ERR_SrcDestComposite_InvalidFillingBase,
phoneNumberError, emulatorError);
}
}
}