blob: 1658f98b2ab36e6bf2f4bf43478e9f3e76e055ce [file] [log] [blame]
/*
* 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.nativewindow;
import static com.motorola.studio.android.common.log.StudioLogger.error;
import static com.motorola.studio.android.common.log.StudioLogger.info;
import java.util.Timer;
import java.util.TimerTask;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import com.motorola.studio.android.AndroidPlugin;
import com.motorola.studio.android.adt.DDMSFacade;
import com.motorola.studio.android.common.preferences.DialogWithToggleUtils;
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.IAndroidSkin;
import com.motorola.studio.android.emulator.core.utils.TelnetAndroidInput;
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.nativeos.NativeUIUtils;
public class NativeWindowComposite extends ScrolledComposite implements IAndroidComposite
{
/**
* Preference key of the Question Dialog about changing zoom
*
*/
private static String LOOSE_ORIGINAL_SCALE_KEY_PREFERENCE = "loose.original.scale";
//Constants
private static final double MINIMUM_ZOOM_FACTOR = 0.10;
private static final double ZOOM_FIT = 0.0;
private Composite contentComposite;
private IAndroidEmulatorInstance androidInstance;
private long windowHandle;
private long originalParentHandle;
private long windowProperties;
private Point windowSize;
private Point nativeWindowSize;
private NativeWindowMonitor nativeWindowMonitor;
protected boolean resizing;
private boolean isFitToWindow;
private double zoomFactor = 0.99;
private double fitZoomFactor = ZOOM_FIT;
private boolean forceNativeWindowSizeUpdate;
private boolean isOriginalScale;
private boolean zoomLocked;
private class NativeWindowMonitor extends Timer
{
private Timer timer;
private MonitorTask monitorTask;
public NativeWindowMonitor(long interval)
{
timer = new Timer();
monitorTask = new MonitorTask();
timer.schedule(monitorTask, interval, interval);
}
private class MonitorTask extends TimerTask
{
@Override
public void run()
{
Point newWindowSize =
NativeUIUtils.getWindowSize(originalParentHandle, windowHandle);
if ((windowHandle <= 0) || !newWindowSize.equals(windowSize))
{
Display display = Display.getDefault();
if (!display.isDisposed())
{
try
{
display.syncExec(new Runnable()
{
public void run()
{
updateContentComposite();
}
});
}
catch (SWTException e)
{
//Do nothing in case the widget is disposed, occurs when the tool is closing.
}
}
}
if (NativeUIUtils.isWindowEnabled(windowHandle))
{
Display display = Display.getDefault();
if (!display.isDisposed())
{
try
{
display.syncExec(new Runnable()
{
public void run()
{
if (!contentComposite.isDisposed())
{
contentComposite.forceFocus();
}
}
});
}
catch (SWTException e)
{
//Do nothing in case the widget is disposed, occurs when the tool is closing.
}
}
}
}
}
public void stopMonitoring()
{
timer.cancel();
timer = null;
monitorTask = null;
}
}
public NativeWindowComposite(Composite parent, IAndroidSkin androidSkin,
final IAndroidEmulatorInstance instance)
{
super(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
info("Creating Native Window Composite for " + instance.getName());
getVerticalBar().setEnabled(true);
getHorizontalBar().setEnabled(true);
this.setLayout(new FillLayout());
androidInstance = instance;
nativeWindowMonitor = new NativeWindowMonitor(500);
addControlListener(new ControlAdapter()
{
final boolean[] running = new boolean[1];
/*
* (non-Javadoc)
* @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent)
*/
@Override
public void controlResized(ControlEvent event)
{
if (isFitToWindow)
{
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
//do nothing
}
if (running[0])
{
return;
}
running[0] = true;
Display.getCurrent().asyncExec(new Runnable()
{
public void run()
{
running[0] = false;
if (!getShell().isDisposed())
{
calculateFitZoomFactor(forceNativeWindowSizeUpdate);
applyZoomFactor();
}
}
});
}
}
});
createContentComposite(instance);
info("Created Native Window Composite for " + instance.getName());
}
/**
* Creates the content composite that will be parent of emulator native window
*
* @param instance A android instance from which the composite could be retrieved if it is already created
*/
public void createContentComposite(IAndroidEmulatorInstance instance)
{
contentComposite = instance.getComposite();
if (contentComposite != null)
{
info("Instance already has a composite");
contentComposite.setParent(this);
contentComposite.setVisible(true);
}
else
{
contentComposite = new Composite(this, SWT.EMBEDDED | SWT.NO_BACKGROUND);
}
this.setContent(contentComposite);
if (instance.getProperties().getProperty("Command_Line").contains("-scale"))
{
isOriginalScale = true;
}
//Force to update native window size at 100% when first using nativeWindowSize field
forceNativeWindowSizeUpdate = true;
//Avoid perform apply zoom factor when creating composite
zoomLocked = true;
draw();
}
/**
* Changes the parent from OS to content composite keeping the original properties and parent window reference
*/
private void draw()
{
if (contentComposite != null)
{
windowHandle = androidInstance.getWindowHandle();
//If the instance does not contain the window handle, it should be retrieved
//from native emulator window and assigned to instance
if (windowHandle <= 0)
{
int port =
AndroidLogicUtils.getEmulatorPort(DDMSFacade
.getSerialNumberByName(androidInstance.getName()));
windowHandle = NativeUIUtils.getWindowHandle(androidInstance.getName(), port);
androidInstance.setWindowHandle(windowHandle);
}
if ((windowProperties <= 0) && (windowHandle > 0))
{
windowProperties = NativeUIUtils.getWindowProperties(windowHandle);
info("Native Window Properties:" + windowProperties);
}
//Set Window Style
if (windowHandle > 0)
{
NativeUIUtils.setWindowStyle(windowHandle);
}
if (originalParentHandle <= 0)
{
originalParentHandle = windowHandle;
}
//Retrieve window size before changing parent
if (windowHandle > 0)
{
windowSize = NativeUIUtils.getWindowSize(originalParentHandle, windowHandle);
}
//Set the new Parent and store the original parent
if ((originalParentHandle <= 0) || (originalParentHandle == windowHandle))
{
if (windowHandle > 0)
{
originalParentHandle =
NativeUIUtils.embedWindow(windowHandle, contentComposite);
info("Native Window Parent:" + originalParentHandle);
}
}
else
{
NativeUIUtils.embedWindow(windowHandle, contentComposite);
}
if (windowSize == null)
{
windowSize = new Point(700, 500);
}
//Update composite size
contentComposite
.setSize(contentComposite.computeSize(windowSize.x, windowSize.y, true));
contentComposite.redraw();
this.update();
this.setMinSize(contentComposite.computeSize(windowSize.x, windowSize.y));
this.layout();
}
else
{
createContentComposite(androidInstance);
}
}
public void changeToNextLayout()
{
contentComposite.setVisible(false);
contentComposite.setLocation(0, 0);
NativeUIUtils.sendNextLayoutCommand(originalParentHandle, windowHandle);
updateContentComposite();
forceNativeWindowSizeUpdate = true;
if (isFitToWindow)
{
//Force update to fit zoom factor
setZoomFactor(ZOOM_FIT);
}
applyZoomFactor();
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
//do nothing
}
contentComposite.setVisible(true);
}
/*
* (non-Javadoc)
* @see org.eclipse.swt.widgets.Widget#dispose()
*/
@Override
public void dispose()
{
info("Disposing Native Window Composite");
if (nativeWindowMonitor != null)
{
nativeWindowMonitor.stopMonitoring();
nativeWindowMonitor = null;
info("Disposed Native Window Monitor");
}
if (windowHandle > 0)
{
info("Restoring original properties for window: " + windowHandle);
NativeUIUtils.setWindowProperties(windowHandle, windowProperties);
boolean shallUnembed =
AndroidPlugin.getDefault().getPreferenceStore()
.getBoolean(AndroidPlugin.SHALL_UNEMBED_EMULATORS_PREF_KEY);
if ((originalParentHandle > 0) && shallUnembed)
{
info("Setting original parent: " + originalParentHandle + " for window"
+ windowHandle);
NativeUIUtils.unembedWindow(windowHandle, originalParentHandle);
//Force update when redrawing
androidInstance.setWindowHandle(0);
info("Restoring window: " + windowHandle);
NativeUIUtils.restoreWindow(windowHandle);
}
}
if (!Platform.getOS().equals(Platform.OS_WIN32))
{
info("Trying to store the content composite in instance");
if (contentComposite != null)
{
info("Is instance started? :" + androidInstance.isStarted());
if (androidInstance.isStarted())
{
try
{
contentComposite.setParent(PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getShell());
this.setContent(null);
contentComposite.setVisible(true);
androidInstance.setComposite(contentComposite);
}
catch (Exception e)
{
error("Error trying to store the content composite :" + e.getMessage());
}
}
}
}
super.dispose();
}
/**
* Apply the zoom factor to the instance
*/
public void applyZoomFactor()
{
if (!isOriginalScale && !zoomLocked)
{
contentComposite.setLocation(0, 0);
IInputLogic inputLogic = androidInstance.getInputLogic();
TelnetAndroidInput telnetAndroidInput = (TelnetAndroidInput) inputLogic;
NativeUIUtils.hideWindow(windowHandle);
if (isFitToWindow)
{
telnetAndroidInput.sendWindowScale(fitZoomFactor);
}
else
{
telnetAndroidInput.sendWindowScale(zoomFactor);
}
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
//do nothing
}
telnetAndroidInput.dispose();
NativeUIUtils.showWindow(windowHandle);
updateContentComposite();
}
}
void calculateFitZoomFactor(boolean requireNativeSizeUpdate)
{
// 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
fitZoomFactor = MINIMUM_ZOOM_FACTOR;
}
else
{
// if the layout was changed, it is needed to retrieve the native window size at 100%
// that size is required to the correct ratio calculus
if (requireNativeSizeUpdate)
{
forceNativeWindowSizeUpdate = false;
updateNativeWindowSize();
}
else
{
double widthRatio = (double) (clientArea.width) / nativeWindowSize.x;
double heightRatio = (double) (clientArea.height) / nativeWindowSize.y;
fitZoomFactor =
(Math.min(widthRatio, heightRatio) > MINIMUM_ZOOM_FACTOR ? Math.min(
widthRatio, heightRatio) : MINIMUM_ZOOM_FACTOR);
}
}
}
/**
* This method brings the emulator window to 100% zoom factor to retrieve their native size
*/
private void updateNativeWindowSize()
{
info("Updating Native Window Size");
setZoomFactor(1.0d);
applyZoomFactor();
nativeWindowSize = NativeUIUtils.getWindowSize(originalParentHandle, windowHandle);
setZoomFactor(ZOOM_FIT);
applyZoomFactor();
info("Updated Native Window Size");
}
private void updateContentComposite()
{
if (!this.isDisposed())
{
windowSize = NativeUIUtils.getWindowSize(originalParentHandle, windowHandle);
if (windowSize != null)
{
if ((contentComposite != null) && !contentComposite.isDisposed())
{
contentComposite.setSize(windowSize.x, windowSize.y);
contentComposite.redraw();
}
this.setMinSize(windowSize.x, windowSize.y);
draw();
this.redraw();
info("Updated Content Composite");
}
}
}
/**
* Gets the current zoom factor.
*
* @return the zoom factor
*/
public double getZoomFactor()
{
if (isFitToWindow)
{
return fitZoomFactor;
}
return zoomFactor;
}
/**
* Sets the zoom factor.
*
* @param zoom the zoom factor
*
*/
public void setZoomFactor(double zoom)
{
boolean execute = true;
zoomLocked = false;
if (isOriginalScale)
{
execute =
DialogWithToggleUtils.showQuestion(LOOSE_ORIGINAL_SCALE_KEY_PREFERENCE,
EmulatorNLS.QUESTION_NativeWindow_LooseOriginalScale_Title,
EmulatorNLS.QUESTION_NativeWindow_LooseOriginalScale_Text);
}
if (execute)
{
isOriginalScale = false;
if (zoom == ZOOM_FIT)
{
isFitToWindow = true;
calculateFitZoomFactor(forceNativeWindowSizeUpdate);
}
else
{
isOriginalScale = false;
isFitToWindow = false;
}
zoomFactor = zoom;
}
}
@Override
public boolean setFocus()
{
NativeUIUtils.setWindowFocus(windowHandle);
return super.setFocus();
}
public void applyLayout(String layoutName)
{
setLayout(new FillLayout());
draw();
}
public KeyListener getKeyListener()
{
return null;
}
public MouseListener getMouseListener()
{
return null;
}
public MouseMoveListener getMouseMoveListener()
{
return null;
}
public boolean isFitToWindowSelected()
{
return isFitToWindow;
}
}