/* * 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; } }