/*
* 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.ui.controls.skin;
import static com.motorola.studio.android.common.log.StudioLogger.debug;
import static com.motorola.studio.android.common.log.StudioLogger.error;
import static com.motorola.studio.android.common.log.StudioLogger.info;
import java.util.Collection;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.ui.PlatformUI;
import com.motorola.studio.android.adt.DDMSFacade;
import com.motorola.studio.android.common.utilities.EclipseUtils;
import com.motorola.studio.android.emulator.core.exception.InstanceStopException;
import com.motorola.studio.android.emulator.core.exception.SkinException;
import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance;
import com.motorola.studio.android.emulator.core.model.IInputLogic;
import com.motorola.studio.android.emulator.core.skin.AndroidPressKey;
import com.motorola.studio.android.emulator.core.skin.AndroidSkinBean;
import com.motorola.studio.android.emulator.core.skin.IAndroidKey;
import com.motorola.studio.android.emulator.core.skin.IAndroidSkin;
import com.motorola.studio.android.emulator.i18n.EmulatorNLS;
import com.motorola.studio.android.emulator.logic.AndroidLogicUtils;
import com.motorola.studio.android.emulator.ui.controls.IAndroidComposite;
import com.motorola.studio.android.emulator.ui.controls.UIHelper;
import com.motorola.studio.android.emulator.ui.handlers.IHandlerConstants;
import com.motorola.studio.android.nativeos.NativeUIUtils;
/**
* DESCRIPTION: This class implements the UI part of the skin
*
* RESPONSIBILITY: - Provide the skin image with correct layout - Provide means
* for the user to send events to the emulator through the phone emulated
* keyboard - Provide means for the user to change the zoom and window scrolling
* properties
*
* COLABORATORS: None.
*
* USAGE: Create an instance of this class every time an phone instance is to be
* displayed Call the public methods to interact with the skin.
*/
public class SkinComposite extends Composite implements IAndroidComposite
{
// Constants for defining interval between keystrokes
// when using long mouse click
public static final int FIRST_REFRESH_DELAY_MS = 300;
public static final int REFRESH_DELAY_PERIOD_MS = 100;
// Minimum value to be used as zoom factor. This is necessary to avoid
// divisions to zero
private static final double MINIMUM_ZOOM_FACTOR = 0.0001;
// The step increased or decreased from the zoom factor using mouse wheel
private static final double ZOOM_STEP = 0.5;
/**
* A rectangle that represents the part of the currentSkin image that fits
* into the view/screen. It must fit inside the currentSkinImage borders (0,
* 0, current skin image width, current skin image height)
*/
private final Rectangle displayRectangle = new Rectangle(0, 0, 0, 0);
/**
* The skin elements provider
*/
private IAndroidSkin skin;
/**
* The image that is currently drawn at screen. It is one image provided by
* the skin that is scaled by the current zoom factor
*/
private Image currentSkinImage;
/**
* The key that mouse is over at the current moment
*/
private IAndroidKey currentKey;
/**
* The flag indicating that Ctrl key is pressed
*/
private boolean ctrlPressed = false;
/**
* SWT key pressed/released events listener
*/
private KeyListener keyListener;
private MouseListener mainDisplayMouseListener;
private MouseMoveListener mainDisplayMouseMoveListener;
/**
* Flag that indicates if the skin can use the scroll bars to draw itself
* bigger than the view client area
*/
private boolean scrollBarsUsed = true;
/**
* True if the mouse left button is pressed. False otherwise
*/
private boolean isMouseLeftButtonPressed;
/**
* True if the mouse right button is pressed. False otherwise
*/
private boolean isMouseRightButtonPressed;
/**
* The zoom factor whose default value is 1.0 (100%)
*/
private double zoomFactor = 1.0;
private double embeddedViewScale = 1.0;
private IInputLogic androidInput;
private boolean isMouseMainDisplayLeftButtonPressed;
IAndroidEmulatorInstance androidInstance;
/**
* Creates a SkinComposite This composite holds the screens in the correct
* positions and maps the keys
*
* @param parent
* The parent composite in which the UI part of the instance
* shall be created
* @param androidSkin
* The skin object that contain data for getting skin information
*/
public SkinComposite(Composite parent, IAndroidSkin androidSkin,
IAndroidEmulatorInstance instance)
{
super(parent, SWT.BACKGROUND | SWT.H_SCROLL | SWT.V_SCROLL);
skin = androidSkin;
androidInstance = instance;
// Add listeners
addListeners();
createListenersForMainDisplay();
setToolTipText(null);
androidInput = instance.getInputLogic();
// Init the scroll bars
initScrollBars();
if (!Platform.getOS().equals(Platform.OS_MACOSX))
{
hideEmulatorWindow();
}
}
private void hideEmulatorWindow()
{
int port =
AndroidLogicUtils.getEmulatorPort(DDMSFacade.getSerialNumberByName(androidInstance
.getName()));
long windowHandle = NativeUIUtils.getWindowHandle(androidInstance.getName(), port);
androidInstance.setWindowHandle(windowHandle);
NativeUIUtils.hideWindow(windowHandle);
}
/**
* Add listeners to the skin composite
*/
private void addListeners()
{
addPaintListener(new PaintListener()
{
/*
* (non-Javadoc)
* @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
*/
public void paintControl(PaintEvent e)
{
// This listener is invoked when a regular SWT redraw is invoked. In this case, no keys
// have changed. That's why we pass "null" as parameter
drawSkin(e.gc, null);
}
});
addMouseListener(new MouseAdapter()
{
/*
* (non-Javadoc)
* @see org.eclipse.swt.events.MouseAdapter#mouseUp(org.eclipse.swt.events.MouseEvent)
*/
@Override
public void mouseUp(MouseEvent e)
{
if (e.button == 1)
{
isMouseLeftButtonPressed = false;
}
else if (e.button == 3)
{
isMouseRightButtonPressed = false;
}
// Handle left button mouse up event
if (e.button == 1)
{
cancelMouseSelection();
}
}
/*
* (non-Javadoc)
* @see org.eclipse.swt.events.MouseAdapter#mouseDown(org.eclipse.swt.events.MouseEvent)
*/
@Override
public void mouseDown(MouseEvent e)
{
setFocus();
if (e.button == 1)
{
isMouseLeftButtonPressed = true;
}
else if (e.button == 3)
{
isMouseRightButtonPressed = true;
}
if (currentKey != null)
{
ImageData mergedImage = getKeyImageData(currentKey, false);
setSkinImage(mergedImage, currentKey, true);
// Handle left button mouse down event
if ((e.button == 1) && (!isMouseRightButtonPressed) && (currentKey != null))
{
androidInput.sendClick(currentKey.getKeysym(), true);
}
// Handle right button mouse down event
else if (e.button == 3)
{
cancelMouseSelection();
}
}
}
});
addMouseMoveListener(new MouseMoveListener()
{
/*
* (non-Javadoc)
* @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
*/
public void mouseMove(MouseEvent e)
{
int posX = (int) ((e.x + displayRectangle.x) / zoomFactor);
int posY = (int) ((e.y + displayRectangle.y) / zoomFactor);
IAndroidKey keyData = getSkinKey(posX, posY);
if ((isMouseLeftButtonPressed) && (keyData != currentKey))
{
cancelMouseSelection();
}
if (!isMouseLeftButtonPressed && (currentKey != keyData))
{
changeCurrentKey(keyData);
}
}
});
// listener to change the zoom factor using Ctrl + Mouse Wheel
addMouseWheelListener(new MouseWheelListener()
{
public void mouseScrolled(MouseEvent event)
{
if (ctrlPressed)
{
// the floor and ceil are required if "fits to window" was
// checked.
double roundedZoomFactor = Math.floor(zoomFactor / ZOOM_STEP) * ZOOM_STEP;
if ((event.count > 0) && (roundedZoomFactor < IHandlerConstants.MAXIMUM_ZOOM))
{
// increase zoom factor
setZoomFactor(roundedZoomFactor + ZOOM_STEP);
applyZoomFactor();
}
else if ((event.count < 0)
&& (roundedZoomFactor > IHandlerConstants.MINIMUM_ZOOM))
{
// decrease zoom factor
setZoomFactor(roundedZoomFactor - ZOOM_STEP);
applyZoomFactor();
}
}
}
});
addMouseTrackListener(new MouseTrackAdapter()
{
@Override
public void mouseExit(MouseEvent mouseevent)
{
changeCurrentKey(null);
}
});
addControlListener(new ControlAdapter()
{
/*
* (non-Javadoc)
* @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent)
*/
@Override
public void controlResized(ControlEvent event)
{
if (scrollBarsUsed)
{
synchronizeScrollBars();
}
else
{
ImageData imageData = getImageData(false, false);
setSkinImage(imageData, null, false);
}
}
});
}
public void applyLayout(String layoutName)
{
// Populate the attributes with information from skin
AndroidSkinBean skinBean = null;
try
{
skinBean = skin.getSkinBean(layoutName);
}
catch (SkinException e)
{
error("The skin data could not be retrieved from skin files. Cause: " + e.getMessage());
EclipseUtils.showErrorDialog(e);
}
// Create layout and set it to composite
if (skinBean != null)
{
// When changing to a new layout, the key may move to another position
// It does not make sense to keep the old key object
currentKey = null;
// Change the background color to the one that applies to the layout being set
RGB color = skin.getBackgroundColor(layoutName);
setBackground(new Color(PlatformUI.getWorkbench().getDisplay(), color));
Layout prevLayout = getLayout();
if (prevLayout instanceof AndroidSkinLayout)
{
((AndroidSkinLayout) prevLayout).dispose();
}
AndroidSkinLayout androidLayout =
new AndroidSkinLayout(skinBean, skin.isFlipSupported());
setLayout(androidLayout);
embeddedViewScale = skinBean.getEmbeddedViewScale();
layout();
redraw();
}
}
/*
* (non-Javadoc)
* @see org.eclipse.swt.widgets.Widget#dispose()
*/
@Override
public void dispose()
{
if (androidInput != null)
{
androidInput.dispose();
}
if (currentSkinImage != null)
{
currentSkinImage.dispose();
}
Layout layout = getLayout();
if (layout instanceof AndroidSkinLayout)
{
((AndroidSkinLayout) layout).dispose();
}
skin = null;
currentKey = null;
keyListener = null;
mainDisplayMouseListener = null;
mainDisplayMouseMoveListener = null;
if (!Platform.getOS().equals(Platform.OS_MACOSX))
{
long hnd = androidInstance.getWindowHandle();
if (hnd > 0)
{
NativeUIUtils.showWindow(hnd);
NativeUIUtils.restoreWindow(hnd);
}
//Force update on redrawing
androidInstance.setWindowHandle(0);
}
super.dispose();
}
/**
* Sets the zoom factor to use in the instance
*/
public void applyZoomFactor()
{
if (getZoomFactor() == 0)
{
scrollBarsUsed = false;
getVerticalBar().setEnabled(false);
getHorizontalBar().setEnabled(false);
}
else
{
scrollBarsUsed = true;
getVerticalBar().setEnabled(true);
getHorizontalBar().setEnabled(true);
}
// Resets translation
displayRectangle.x = 0;
displayRectangle.y = 0;
redrawReleasedImage();
}
/**
* Sets the flip/slide status of the phone
*/
private void redrawReleasedImage()
{
if (currentSkinImage != null)
{
ImageData imageData = getImageData(false, false);
setSkinImage(imageData, null, false);
layout();
redraw();
}
if (scrollBarsUsed)
{
synchronizeScrollBars();
}
}
/**
* Performs the skin draw operation.
*
* @param gcUsedToDraw
* The gc object associated with the skin composite that is used
* to draw the images
*/
private void drawSkin(GC gcUsedToDraw, IAndroidKey changedKey)
{
if (currentSkinImage == null)
{
IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this);
ImageData initialSkinImage = getImageData(false, false);
setSkinImage(initialSkinImage, null, false);
applyLayout(instance.getCurrentLayout());
if (scrollBarsUsed)
{
synchronizeScrollBars();
}
}
if (displayRectangle != null)
{
int srcXPos, srcYPos, srcWidth, srcHeight;
int destXPos, destYPos, destWidth, destHeight;
if (changedKey == null)
{
srcXPos = displayRectangle.x;
srcYPos = displayRectangle.y;
srcWidth = displayRectangle.width;
srcHeight = displayRectangle.height;
destXPos = 0;
destYPos = 0;
destWidth = Math.min(currentSkinImage.getImageData().width, displayRectangle.width);
destHeight =
Math.min(currentSkinImage.getImageData().height, displayRectangle.height);
}
else
{
srcXPos =
((int) (changedKey.getKeyArea().x > 0 ? changedKey.getKeyArea().x * zoomFactor
: 0));
srcYPos =
((int) (changedKey.getKeyArea().y > 0 ? changedKey.getKeyArea().y * zoomFactor
: 0));
srcWidth = ((int) (changedKey.getKeyArea().width * zoomFactor));
srcHeight = ((int) (changedKey.getKeyArea().height * zoomFactor));
destXPos = srcXPos - displayRectangle.x;
destYPos = srcYPos - displayRectangle.y;
destWidth = srcWidth;
destHeight = srcHeight;
}
gcUsedToDraw.drawImage(currentSkinImage, srcXPos, srcYPos, srcWidth, srcHeight,
destXPos, destYPos, destWidth, destHeight);
}
}
/**
* Loads a new screen image to the currentSkin attribute. This action
* updates the skin image that is drawn as skin
*
* @param imageToSet
* The new skin pixel data, as retrieved from the skin plugin
* @param changedKey
* The key that has changed, if any. If one if provided, only the key area should be redrawn.
* Can be <code>null</code>.
* @param forceDraw
* true if a draw is needed after setting the new image; false if replacing skin image only
* will be performed.
*/
private void setSkinImage(ImageData imageToSet, IAndroidKey changedKey, boolean forceDraw)
{
recalculateZoomFactor();
if (imageToSet != null)
{
if (currentSkinImage != null)
{
currentSkinImage.dispose();
}
// Scales the chosen image and sets to currentSkin attribute
//
// NOTE: width and height cannot be equal to MINIMUM_ZOOM_FACTOR,
// because this
// will raise an IllegalArgumentException when constructing the
// Image object
int width =
(zoomFactor == MINIMUM_ZOOM_FACTOR ? 1 : (int) (imageToSet.width * zoomFactor));
int height =
(zoomFactor == MINIMUM_ZOOM_FACTOR ? 1 : (int) (imageToSet.height * zoomFactor));
currentSkinImage = new Image(getDisplay(), imageToSet.scaledTo(width, height));
// It only makes sense to reset the translation if the skin image is really being changed
// It will happen if we set a image data without specifying a changed key
if (changedKey == null)
{
displayRectangle.x = 0;
displayRectangle.y = 0;
}
if (forceDraw)
{
layout();
GC gc = new GC(this);
drawSkin(gc, changedKey);
gc.dispose();
}
}
else
{
info("It was requested to set a skin image that was null. Operation aborted.");
}
}
/**
* This method is responsible to set the scroll bar attributes so that they
* reflect the size of the current image at the current zoom factor
*/
private void synchronizeScrollBars()
{
// Syncronizing only makes sense if there is a skin being drawn
if (currentSkinImage != null)
{
// Retrieves the current image and client area sizes
Rectangle imageBound = currentSkinImage.getBounds();
int cw = getClientArea().width;
int ch = getClientArea().height;
// Updates horizontal scroll bar attributes
ScrollBar horizontal = getHorizontalBar();
horizontal.setIncrement((cw / 100));
horizontal.setPageIncrement((cw / 2));
horizontal.setMaximum(imageBound.width);
horizontal.setThumb(cw);
horizontal.setSelection(displayRectangle.x);
// Updates vertical scroll bar attributes
ScrollBar vertical = getVerticalBar();
vertical.setIncrement((ch / 100));
vertical.setPageIncrement((ch / 2));
vertical.setMaximum(imageBound.height);
vertical.setThumb(ch);
vertical.setSelection(displayRectangle.y);
if (horizontal.getMaximum() > cw) // Image is wider than client area
{
horizontal.setEnabled(true);
}
else
{
horizontal.setEnabled(false);
}
if (vertical.getMaximum() > ch) // Image is wider than client area
{
vertical.setEnabled(true);
}
else
{
vertical.setEnabled(false);
}
}
}
/**
* Initialize the scroll bars This include: a) setting the initial enabled
* state b) adding the necessary listeners
*/
private void initScrollBars()
{
ScrollBar horizontal = getHorizontalBar();
horizontal.setEnabled(false);
horizontal.addSelectionListener(new SelectionAdapter()
{
/**
* @see org.eclipse.swt.events.SelectionListener#widgetSelected(SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent event)
{
// Updates the translation
displayRectangle.x = ((ScrollBar) event.widget).getSelection();
// Update the UI
layout();
redraw();
}
});
ScrollBar vertical = getVerticalBar();
vertical.setEnabled(false);
vertical.addSelectionListener(new SelectionAdapter()
{
/**
* @see org.eclipse.swt.events.SelectionListener#widgetSelected(SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent event)
{
// Updates the translation
displayRectangle.y = ((ScrollBar) event.widget).getSelection();
// Update the UI
layout();
redraw();
}
});
debug("Initialized scroll bars");
}
/**
* This method retrieves the key that is placed at the given x,y
* coordinates, considering the flip status
*
* @param x
* The X coordinate to use at key lookup
* @param y
* The Y coordinate to use at key lookup
*
* @return The key placed at the given coordinate, or null if none is found
*/
private IAndroidKey getSkinKey(int x, int y)
{
IAndroidKey keyToReturn = null;
IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this);
Collection<IAndroidKey> keyAreas = skin.getKeyDataCollection(instance.getCurrentLayout());
if (keyAreas != null)
{
for (IAndroidKey key : keyAreas)
{
if (key.isInsideKey(x, y))
{
if (key instanceof AndroidPressKey)
{
AndroidPressKey defaultKeyData = (AndroidPressKey) key;
//if (!defaultKeyData.isFlipSlideValid(instance.isFlipSlideClosed()))
if (!defaultKeyData.isFlipSlideValid(false))
{
continue;
}
}
keyToReturn = key;
break;
}
}
}
return keyToReturn;
}
/**
* Retrieves an image data. If it has never been used at the current
* session, loads it from skin. Released image is retrieved if both parameters
* are false.
*
* @param isPressed
* true if the image to be retrieved is the pressed image
* @param isEnter
* true if the image to be retrieved is the enter image. It only has effect if
* isPressed == false
*
* @return An image data containing the desired image pixels
*/
private ImageData getImageData(boolean isPressed, boolean isEnter)
{
ImageData imageData = null;
IAndroidEmulatorInstance instance = UIHelper.getInstanceAssociatedToControl(this);
try
{
if (isPressed)
{
imageData = skin.getPressedImageData(instance.getCurrentLayout());
}
else
{
if (isEnter)
{
imageData = skin.getEnterImageData(instance.getCurrentLayout());
}
else
{
imageData = skin.getReleasedImageData(instance.getCurrentLayout());
}
}
}
catch (SkinException e)
{
error("The image requested from skin could not be retrieved. isPressed=" + isPressed
+ "; message=" + e.getMessage());
EclipseUtils.showErrorDialog(e);
error("The skin could not provide an important resource. Stopping the instance");
try
{
instance.stop(true);
}
catch (InstanceStopException e1)
{
error("Error while running service for stopping virtual machine");
EclipseUtils.showErrorDialog(EmulatorNLS.GEN_Error,
EmulatorNLS.EXC_General_CannotRunStopService);
}
}
return imageData;
}
/**
* Builds an image data that is based on the released image but has the
* pressed/enter key area painted with data from the pressed/enter image
*
* @param key
* The pressed key
* @param isEnter
* Whether the image being retrieved will be used for enter or pressed
*
* @return An image data built the way described at method description
*/
private ImageData getKeyImageData(IAndroidKey key, boolean isEnter)
{
ImageData resultingImage;
ImageData releasedImage = getImageData(false, false);
ImageData keyImage = isEnter ? getImageData(false, true) : getImageData(true, false);
resultingImage = (ImageData) releasedImage.clone();
Rectangle keyArea = key.getKeyArea();
int resultingImageSize = resultingImage.width * keyArea.height;
int[] keyPixelBuffer = new int[resultingImageSize];
int startY = keyArea.y < 0 ? 0 : keyArea.y;
keyImage.getPixels(0, startY, resultingImageSize, keyPixelBuffer, 0);
for (int line = 0; line < keyArea.height; line++)
{
int pos = (line * resultingImage.width) + Math.abs(keyArea.x);
int startX = Math.abs(keyArea.x);
startY = (keyArea.y < 0 ? 0 : keyArea.y) + line;
if (startY < 0)
{
continue;
}
int putWidth = keyArea.x < 0 ? keyArea.width - startX : keyArea.width;
resultingImage.setPixels(startX, startY, putWidth, keyPixelBuffer, pos);
}
return resultingImage;
}
/**
* This method is called when a mouse selection needs to be canceled.
* Pressing the right button, releasing the left button or leaving the key
* area are examples of typical conditions.
*/
private void cancelMouseSelection()
{
// If the mouse timer is different from null, that means that a key is
// pressed
// This check is important so that event messages are not sent by
// mistake
if (currentKey != null)
{
ImageData newImageData = getImageData(false, true);
setSkinImage(newImageData, currentKey, true);
androidInput.sendClick(currentKey.getKeysym(), false);
}
}
private void changeCurrentKey(IAndroidKey newKey)
{
// The following actions are executed only if the key has changed since the last
// time a mouse move event has happened.
ImageData newImage;
if (currentKey != null)
{
// If currentKey is different from null, we know that the mouse cursor has
// left the area defined by currentKey. That is because from the previous
// if clause we know that the key has changed, and currentKey attribute
// state is out-dated until we reach the "currentKey = keyData" statement.
// In this case, we must draw the RELEASED image version of the key at the
// key location
newImage = getImageData(false, false);
setSkinImage(newImage, currentKey, true);
setToolTipText(null);
}
if (newKey != null)
{
// If keyData is different from null, we know that the mouse cursor has
// entered the area defined by keyData.
// In this case, we must draw the ENTER image version of the key at the
// key location
newImage = getKeyImageData(newKey, true);
setSkinImage(newImage, newKey, true);
setToolTipText(newKey.getToolTip());
}
currentKey = newKey;
}
/**
* Retrieves the display rectangle defined at the current moment.
*
* @return The display rectangle
*/
Rectangle getDisplayRectangle()
{
return displayRectangle;
}
/**
* Updates the size and location of the display rectangle, based on the
* current attributes of the skin (such as skin size and zoom factor) and
* view size
*/
void updateDisplayRectangle()
{
// Updating the display rectangle only makes sense if we have a skin
// being drawn
if (currentSkinImage != null)
{
// Collect the variables used in computation
//
// - clientAreaWidth, clientAreaHeight: dimensions of the view,
// measured from
// the upper left corner point (0,0) to the lower right corner point
// (width, height)
// - currentSkinWidth, currentSkinHeight: dimensions of the skin
// picture, already scaled
// by the zoom factor
int clientAreaWidth = getClientArea().width;
int clientAreaHeight = getClientArea().height;
int currentSkinHeight = currentSkinImage.getImageData().height;
int currentSkinWidth = currentSkinImage.getImageData().width;
// Updates the display rectangle y and height
//
// FIRST STEP: determine the position of the rectangle's y
// coordinate.
// - It starts by calculating if there is any blank area at the
// bottom
// of the view.
// - If there is blank space (blankY > 0) then calculate which
// point,
// if set as the display rectangle's y coordinate, would make the
// blank
// space to disappear. Store this as a candidate Y.
// - Check if the candidate Y is valid. If not valid (candidateY <
// 0)
// then use the image origin as the final y coordinate
//
// SECOND STEP: determine the width dimension of the rectangle
// - It starts by calculating which would be the coordinate of the
// lower point, assuming that the image is big enough to contain
// that
// coordinate. That value (vEdge) is the sum of the Y coordinate and
// the view height.
// - If vEdge is bigger than the current view height, that means
// that the image will occupy part of the view. Itis necessary to
// make the rectangle fit in the skin image by making it smaller in
// height than the view height itself.
// - If vEdge is smaller than the current view height, that means
// that
// the image will not fit in the view. The solution is to make the
// display rectangle height the same size as the view height. In
// this
// second case, the display rectangle will fit in the view height,
// but
// will not be bigger than the skin image height itself
int blankY = clientAreaHeight - (currentSkinHeight - displayRectangle.y);
if (blankY > 0)
{
int candidateY = displayRectangle.y - blankY;
if (candidateY > 0)
{
displayRectangle.y = candidateY;
}
else
{
displayRectangle.y = 0;
}
}
int vEdge = displayRectangle.y + clientAreaHeight;
if (vEdge > currentSkinHeight)
{
displayRectangle.height = currentSkinHeight - displayRectangle.y;
}
else
{
displayRectangle.height = clientAreaHeight;
}
// Updates the display rectangle x and width
// NOTE: a similar logic to the previous one was applied in this
// case
int blankX = clientAreaWidth - (currentSkinWidth - displayRectangle.x);
if (blankX > 0)
{
int candidateX = displayRectangle.x - blankX;
if (candidateX > 0)
{
displayRectangle.x = candidateX;
}
else
{
displayRectangle.x = 0;
}
}
int hEdge = displayRectangle.x + clientAreaWidth;
if (hEdge > currentSkinWidth)
{
displayRectangle.width = currentSkinWidth - displayRectangle.x;
}
else
{
displayRectangle.width = clientAreaWidth;
}
}
}
/**
* Recalculates the zoom factor. This is necessary when the screen or image
* dimensions change.
*/
void recalculateZoomFactor()
{
if (!scrollBarsUsed)
{
// Compute new zoom factor if the zoom mode is "Fit to Window"
Rectangle clientArea = getClientArea();
if ((clientArea.width == 0) || (clientArea.height == 0))
{
// zoom factor cannot be zero, otherwise an
// IllegalArgumentException
// is raised in some SWT methods
setZoomFactor(MINIMUM_ZOOM_FACTOR);
}
else
{
ImageData currentSkin = getImageData(false, false);
double widthRatio = (double) (clientArea.width) / currentSkin.width;
double heightRatio = (double) (clientArea.height) / currentSkin.height;
setZoomFactor(Math.min(widthRatio, heightRatio));
}
}
}
/**
* Gets the current zoom factor.
*
* @return the zoom factor
*/
public double getZoomFactor()
{
return zoomFactor;
}
/**
* Checks if the current zoom configuration is "fit to screen";
* @return
*/
public boolean isFitToWindowSelected()
{
return !scrollBarsUsed;
}
/**
* Sets the zoom factor.
*
* @param zoom
* the zoom factor
*/
public void setZoomFactor(double zoom)
{
zoomFactor = zoom;
}
/**
* Gets the listener that handles SWT key pressing and releasing events.
*
* @return the KeyListener object
*/
public KeyListener getKeyListener()
{
return keyListener;
}
/*
* (non-Javadoc)
* @see com.motorola.studio.android.emulator.ui.controls.IAndroidComposite#getMouseListener()
*/
public MouseListener getMouseListener()
{
return mainDisplayMouseListener;
}
/*
* (non-Javadoc)
* @see com.motorola.studio.android.emulator.ui.controls.IAndroidComposite#getMouseMoveListener()
*/
public MouseMoveListener getMouseMoveListener()
{
return mainDisplayMouseMoveListener;
}
private void createListenersForMainDisplay()
{
// create listener to handle keyboard key pressing highlighting the key
// in the skin.
keyListener = new KeyAdapter()
{
@Override
public void keyPressed(KeyEvent arg0)
{
int keyCode = arg0.keyCode;
if (keyCode == SWT.CTRL)
{
ctrlPressed = true;
}
else
{
if (keyCode == SWT.ARROW_DOWN || keyCode == SWT.ARROW_LEFT
|| keyCode == SWT.ARROW_RIGHT || keyCode == SWT.ARROW_UP)
{
int dpadRotation = skin.getDpadRotation(androidInstance.getCurrentLayout());
keyCode = getRotatedKeyCode(keyCode, dpadRotation);
}
// send message to emulator
androidInput.sendKey(arg0.character, keyCode, skin.getKeyCodes());
}
}
@Override
public void keyReleased(KeyEvent arg0)
{
int keyCode = arg0.keyCode;
if (keyCode == SWT.CTRL)
{
ctrlPressed = false;
}
}
};
mainDisplayMouseMoveListener = new MouseMoveListener()
{
/**
* @see org.eclipse.swt.events.MouseMoveListener#mouseMove(MouseEvent)
*/
public void mouseMove(MouseEvent e)
{
if (isMouseMainDisplayLeftButtonPressed)
{
UIHelper.ajustCoordinates(e, SkinComposite.this);
androidInput.sendMouseMove((int) (e.x / embeddedViewScale),
(int) (e.y / embeddedViewScale));
}
}
};
mainDisplayMouseListener = new MouseAdapter()
{
/**
* @see org.eclipse.swt.events.MouseListener#mouseUp(MouseEvent)
*/
@Override
public void mouseUp(MouseEvent e)
{
if (e.button == 1)
{
isMouseMainDisplayLeftButtonPressed = false;
UIHelper.ajustCoordinates(e, SkinComposite.this);
androidInput.sendMouseUp((int) (e.x / embeddedViewScale),
(int) (e.y / embeddedViewScale));
}
}
/**
* @see org.eclipse.swt.events.MouseListener#mouseDown(MouseEvent)
*/
@Override
public void mouseDown(MouseEvent e)
{
if (e.button == 1)
{
UIHelper.ajustCoordinates(e, SkinComposite.this);
androidInput.sendMouseDown((int) (e.x / embeddedViewScale),
(int) (e.y / embeddedViewScale));
isMouseMainDisplayLeftButtonPressed = true;
}
}
};
}
private int getRotatedKeyCode(int keyCode, int dpadRotation)
{
switch (dpadRotation % 4)
{
case 1:
switch (keyCode)
{
case SWT.ARROW_DOWN:
keyCode = SWT.ARROW_RIGHT;
break;
case SWT.ARROW_LEFT:
keyCode = SWT.ARROW_DOWN;
break;
case SWT.ARROW_RIGHT:
keyCode = SWT.ARROW_UP;
break;
case SWT.ARROW_UP:
keyCode = SWT.ARROW_LEFT;
break;
}
break;
case 2:
switch (keyCode)
{
case SWT.ARROW_DOWN:
keyCode = SWT.ARROW_UP;
break;
case SWT.ARROW_LEFT:
keyCode = SWT.ARROW_RIGHT;
break;
case SWT.ARROW_RIGHT:
keyCode = SWT.ARROW_LEFT;
break;
case SWT.ARROW_UP:
keyCode = SWT.ARROW_DOWN;
break;
}
break;
case 3:
switch (keyCode)
{
case SWT.ARROW_DOWN:
keyCode = SWT.ARROW_LEFT;
break;
case SWT.ARROW_LEFT:
keyCode = SWT.ARROW_UP;
break;
case SWT.ARROW_RIGHT:
keyCode = SWT.ARROW_DOWN;
break;
case SWT.ARROW_UP:
keyCode = SWT.ARROW_RIGHT;
break;
}
break;
default:
//Does nothing, no rotation needed.
break;
}
return keyCode;
}
}