/* * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.glass.ui.lens; import java.io.File; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.LinkedList; import com.sun.glass.events.KeyEvent; import com.sun.glass.events.MouseEvent; import com.sun.glass.events.TouchEvent; import com.sun.glass.events.ViewEvent; import com.sun.glass.events.WindowEvent; import com.sun.glass.ui.Application; import com.sun.glass.ui.Clipboard; import com.sun.glass.ui.CommonDialogs.ExtensionFilter; import com.sun.glass.ui.CommonDialogs.FileChooserResult; import com.sun.glass.ui.Cursor; import com.sun.glass.ui.EventLoop; import com.sun.glass.ui.Menu; import com.sun.glass.ui.MenuBar; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Robot; import com.sun.glass.ui.Screen; import com.sun.glass.ui.Size; import com.sun.glass.ui.Timer; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; import sun.util.logging.PlatformLogger.Level; import java.util.Arrays; import java.util.Iterator; final class LensApplication extends Application { /** Bit to indicate that a device has touch support */ private static final int DEVICE_TOUCH = 0; /** Bit to indicate that a device has multitouch support */ private static final int DEVICE_MULTITOUCH = 1; /** Bit to indicate that a device has relative motion pointer support */ private static final int DEVICE_POINTER = 2; /** Bit to indicate that a device has arrow keys and a select key */ private static final int DEVICE_5WAY = 3; /** Bit to indicate that a device has a full PC keyboard */ private static final int DEVICE_PC_KEYBOARD = 4; /** Largest bit used in device capability bitmasks */ private static final int DEVICE_MAX = 4; /** A running count of the numbers of devices with each device capability */ private int[] deviceFlags = new int[DEVICE_MAX + 1]; /** * Values of -1 mean that these variables are not set. We will reset them to -1 whenever we * get a non-touch move event. */ private int previousTouchMoveX = -1; private int previousTouchMoveY = -1; private int previousTouchMoveScreenX = -1; private int previousTouchMoveScreenY = -1; /** True if the application wishes to show the cursor */ private boolean cursorVisible = true; Menu windowMenu; Menu editMenu; Menu fileMenu; private static final Object invokeAndWaitLock = new Object(); private static Runnable waitingFor; private static int activeEventLoopThreads = 0; private static final Object activeEventLoopLock = new Object(); private static boolean doComposite = true; static private boolean isInitialized = false; // setup for JNI private static native void _initIDs(); // initialize any native display connection/state private static native boolean _initialize(); private static native void _notifyRenderingEnd(); private EventLoop dndEventLoop; private static void initLibrary() { final String lensProperty = "glass.lens"; final String platform = AccessController.doPrivileged( (PrivilegedAction) () -> System.getProperty(lensProperty, "")); doComposite = AccessController.doPrivileged((PrivilegedAction) () -> Boolean.getBoolean("doNativeComposite")); if (isInitialized) { //make sure we make this only once return; } LensLogger.getLogger().info("LensApplication initialization"); if (platform.equals("")) { LensLogger.getLogger().severe( "System property " + lensProperty + " not defined"); } AccessController.doPrivileged((PrivilegedAction) () -> { Application.loadNativeLibrary("glass_lens_" + platform); return null; }); _initIDs(); isInitialized = true; LensLogger.getLogger().info("LensApplication initialization done"); } static { LensApplication.initLibrary(); } //cache the singleton object for native layer usage native void registerApplication(); //package protected to be used only by LensPlatformFactory LensApplication() { super(); LensLogger.getLogger().fine("LensApplication ctor called, registering in" + " native layer"); registerApplication(); } private static abstract class Event { abstract void dispatch(); } private static class RunnableEvent extends Event { private boolean wait; private Runnable runnable; RunnableEvent(boolean wait, Runnable runnable) { this.wait = wait; this.runnable = runnable; } @Override void dispatch() { runnable.run(); if (wait) { synchronized (invokeAndWaitLock) { waitingFor = null; invokeAndWaitLock.notify(); } } } @Override public String toString() { return "RunnableEvent[runnable=" + runnable + ",wait=" + wait + "]"; } } @Override public boolean hasWindowManager() { return false; } /** * This class is used to handle key events * */ private static class LensKeyEvent extends Event { //the view object to notify private LensView view; //type of the event (pressed, released...) // as defined in KeyEvent.java private int type; //key code private int keyCode; //bit mask of modifiers (shift, ctrl...) private int modifiers; // A buffer holding the char key sequence, can be 0 length private char[] chars; LensKeyEvent(LensView view, int type, int keyCode, int modifiers, char[] chars) { this.view = view; this.type = type; this.keyCode = keyCode; this.modifiers = modifiers; this.chars = chars; } @Override void dispatch() { view._notifyKey(type, keyCode, chars, modifiers); } @Override public String toString() { return "LensKeyEvent[view=" + view + ",type=" + type + ",keyCode=" + keyCode + ",modifiers=" + modifiers + ",chars=" + String.valueOf(chars) + "]"; } } /** * This class is used to handle window related events * */ private static class LensWindowEvent extends Event { static enum EType { CLOSE, DESTROY, EXPOSE, FOCUS, MOVE, RESIZE, UNGRAB, FOCUS_DISABLED }; private EType type; // The window object to notify private LensWindow window; //The event that invoked this notification, as described // in WindowEvent class private int windowEvent; //Window parameters for update private int x; private int y; private int width; private int height; /** * Generic constructor, used when no parameters need to be * updated * * @param type LensApplication event * @param window the window to notify * @param windowEvent one of the events described in WindowEvent * class */ LensWindowEvent(EType type, LensWindow window, int windowEvent) { this.type = type; this.window = window; this.windowEvent = windowEvent; } /** * Use this constructor when window parameters have been changed * * * @param type LensApplication event * @param window the window to notify * @param windowEvent one of the events described in WindowEvent * class * @param x * @param y * @param width * @param height */ LensWindowEvent(EType type, LensWindow window, int windowEvent, int x, int y, int width, int height) { this.type = type; this.window = window; this.windowEvent = windowEvent; this.x = x; this.y = y; this.width = width; this.height = height; } @Override void dispatch() { switch (type) { case FOCUS: window._notifyFocus(windowEvent); break; case MOVE: window._notifyMove(x, y); break; case RESIZE: window._notifyResize(windowEvent, width, height); break; case UNGRAB: window._notifyFocusUngrab(); break; case DESTROY: window._notifyDestroy(); break; case CLOSE: window._notifyClose(); break; case EXPOSE: window._notifyExpose(x, y, width, height); break; case FOCUS_DISABLED: window._notifyFocusDisabled(); break; default: LensLogger.getLogger().severe( "Unrecognized window event type"); } } @Override public String toString() { return super.toString() + "[window=" + window + ",type=" + type + ",windowEvent=" + windowEvent + ",x=" + x + ",y=" + y + ",width=" + width + ",height=" + height + "]"; } } private static class LensMouseEvent extends Event { private LensView target; private int action; private int x, y, absx, absy; private int button; private int modifiers; private boolean isPopupTrigger; private boolean isSynthesized; LensMouseEvent(LensView target, int action, int x, int y, int absx, int absy, int button, int modifiers, boolean isPopupTrigger, boolean isSynthesized) { this.target = target; this.action = action; this.x = x; this.y = y; this.absx = absx; this.absy = absy; this.button = button; this.modifiers = modifiers; this.isPopupTrigger = isPopupTrigger; this.isSynthesized = isSynthesized; } @Override void dispatch() { target._notifyMouse( action, button, x, y, absx, absy, modifiers, isPopupTrigger, isSynthesized); } @Override public String toString() { return "LensMouseEvent[target=" + target + ",action=" + action + ",x=" + x + ",y=" + y + ",absx=" + absx + ",absy=" + absy + ",button=" + button + ",modifiers=" + modifiers + ",isPopupTrigger=" + isPopupTrigger + ",isSynthesized=" + isSynthesized + "]"; } } private static class LensScrollEvent extends Event { private LensView target; private int x, y; private int absx, absy; private double deltaX, deltaY; private int modifiers; private int lines; private int chars; private int defaultLines; private int defaultChars; private double xMultiplier, yMultiplier; LensScrollEvent(LensView target, int x, int y, int absx, int absy, double deltaX, double deltaY, int modifiers, int lines, int chars, int defaultLines, int defaultChars, double xMultiplier, double yMultiplier) { this.target = target; this.x = x; this.y = y; this.absx = absx; this.absy = absy; this.deltaX = deltaX; this.deltaY = deltaY; this.modifiers = modifiers; this.lines = lines; this.chars = chars; this.defaultLines = defaultLines; this.defaultChars = defaultChars; this.xMultiplier = xMultiplier; this.yMultiplier = yMultiplier; } @Override void dispatch() { target._notifyScroll( x, y, absx, absy, deltaX, deltaY, modifiers, lines, chars, defaultLines, defaultChars, xMultiplier, yMultiplier); } @Override public String toString() { return "LensScrollEvent[target=" + target + ",x=" + x + ",y=" + y + ",absx=" + absx + ",absy=" + absy + ",deltaX=" + deltaX + ",deltaY=" + deltaY + ",modifiers=" + modifiers + ",lines=" + lines + ",chars=" + chars + ",defaultLines=" + defaultLines + ",defaultChars=" + defaultChars + ",xMultiplier=" + xMultiplier + ",yMultiplier=" + yMultiplier + "]"; } } private static class LensTouchEvent extends Event { private LensView view; private int state; private long id; private int x; private int y; private int absX; private int absY; LensTouchEvent(LensView view, int state, long id, int x, int y, int absX, int absY) { this.view = view; this.state = state; this.id = id; this.x = x; this.y = y; this.absX = absX; this.absY = absY; } @Override void dispatch() { LensTouchInputSupport.postTouchEvent(view, state, id, x, y, absX, absY); } @Override public String toString() { return "LensTouchEvent[view=" + view + ",state=" + state + ",id=" + id + ",x=" + x + ",y=" + y + ",absX=" + absX + ",absY=" + absY + "]"; } } private static class LensMultiTouchEvent extends Event { private LensView view; private int[] states; private long[] ids; private int[] xs; private int[] ys; private int dx; private int dy; LensMultiTouchEvent(LensView view, int[] states, long[] ids, int[] xs, int[] ys, int dx, int dy) { this.view = view; this.states = states; this.ids = ids; this.xs = xs; this.ys = ys; this.dx = dx; this.dy = dy; } @Override void dispatch() { LensTouchInputSupport.postMultiTouchEvent( view, states, ids, xs, ys, dx, dy); } @Override public String toString() { return "LensMultiTouchEvent[view=" + view + ", ids " + Arrays.toString(ids) + ", states " + Arrays.toString(states) + ", xs " + Arrays.toString(xs) + ", ys " + Arrays.toString(ys) + "]"; } } private static class LensViewEvent extends Event { private LensView target; private int x, y, width, height; private int viewEventType; LensViewEvent(LensView view, int viewEventType, int x, int y, int width, int height) { this.target = view; this.x = x; this.y = y; this.width = width; this.height = height; this.viewEventType = viewEventType; } @Override void dispatch() { target._notifyViewEvent(viewEventType); } @Override public String toString() { return "LensViewEvent[target=" + target + ", x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ", event type code " + viewEventType + ", event type name " + ViewEvent.getTypeString(viewEventType); } } private class LensDragEvent extends Event { int x, y, absx, absy; DragActions action; LensView view; //This variable are used to overcome an enum limitation (you can't //use switch clause on custom values enum) final int ENTER = DragActions.ENTER.getValue(); final int LEAVE = DragActions.LEAVE.getValue(); final int OVER = DragActions.OVER.getValue(); final int DROP = DragActions.DROP.getValue(); LensDragEvent(LensView view, int x, int y, int absx, int absy, DragActions action) { this.absx = absx; this.absy = absy; this.x = x; this.y = y; this.action = action; this.view = view; } @Override void dispatch() { if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("processing drag " + action); } switch (action) { case ENTER: view._notifyDragEnter(x, y, absx, absy, Clipboard.ACTION_COPY_OR_MOVE); break; case LEAVE: view._notifyDragLeave(); break; case OVER: view._notifyDragOver(x, y, absx, absy, Clipboard.ACTION_COPY_OR_MOVE); break; case DROP: view._notifyDragDrop(x, y, absx, absy, Clipboard.ACTION_COPY_OR_MOVE); leaveDndEventLoop(null); break; default: return; } } @Override public String toString() { return "LensDragEvent[x=" + x + ", y=" + y + ", absx=" + absx + ", absy=" + absy + ", action " + action + ", view " + view; } } private class LensMenuEvent extends Event { LensView view; int x; int y; int xAbs; int yAbs; boolean isKeyboardTrigger; LensMenuEvent(LensView view, int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { this.view = view; this.x = x; this.y = y; this.xAbs = xAbs; this.yAbs = yAbs; this.isKeyboardTrigger = isKeyboardTrigger; } @Override void dispatch() { view._notifyMenu(x, y, xAbs, yAbs, isKeyboardTrigger); } @Override public String toString() { return "LensMenuEvent[view=" + view + ", x=" + x + ", y=" + y + ", absx=" + xAbs + ", absy=" + yAbs + ", isKeyboardTrigger=" + isKeyboardTrigger + "]"; } } private class LensDeviceEvent extends Event { private int flags; private boolean attach; LensDeviceEvent(int flags, boolean attach) { this.flags = flags; this.attach = attach; } @Override void dispatch() { for (int i = 0; i <= DEVICE_MAX; i++) { if ((flags & (1 << i)) != 0) { if (attach) { deviceFlags[i] ++; } else { deviceFlags[i] --; } } } if ((flags & (1 << DEVICE_POINTER)) != 0) { // Turn on/off cursor image if (attach && (deviceFlags[DEVICE_POINTER] == 1) && cursorVisible) { LensCursor.setVisible_impl(true); } else if (!attach && (deviceFlags[DEVICE_POINTER] == 0)) { LensCursor.setVisible_impl(false); } } } } private class LensScreenEvent extends Event { @Override void dispatch() { Screen.notifySettingsChanged(); } } private final LinkedList eventList = new LinkedList(); private void postEvent(Event e) { if (Thread.currentThread() == getEventThread()) { try { e.dispatch(); } catch (Exception ex) { reportException(ex); } } else { synchronized (eventList) { eventList.addLast(e); eventList.notify(); } } } /** Posts a mouse event, filtering out sequences of similar motion or drag * events. */ private void postMouseEvent(LensView view, int eventType, int x, int y, int absx, int absy, int button, int modifiers, boolean isPopupTrigger, boolean isSynthesized) { synchronized (eventList) { if (!eventList.isEmpty() && (eventType == MouseEvent.DRAG || eventType == MouseEvent.MOVE)) { Iterator events = eventList.descendingIterator(); while (events.hasNext()) { Event lastEvent = events.next(); if (lastEvent instanceof LensMouseEvent) { LensMouseEvent e = (LensMouseEvent) lastEvent; if (e.target == view && e.action == eventType && e.button == button && e.modifiers == modifiers && e.isPopupTrigger == isPopupTrigger && e.isSynthesized == isSynthesized) { // rewrite the coordinates of the scheduled event with // the coordinates of this event. e.x = x; e.y = y; e.absx = absx; e.absy = absy; return; } } } } postEvent(new LensMouseEvent(view, eventType, x, y, absx, absy, button, modifiers, isPopupTrigger, isSynthesized)); } } private static class RunLoopControl { boolean active; // thread should continue to process events. Object release; // object to return with on leave nested } // our stack of nested run loops - note using LinkedList because we // are already using that class for events. LinkedList activeRunLoops = new LinkedList(); @Override protected Object _enterNestedEventLoop() { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("_enterNestedEventLoop"); } // we are being called on the current active event thread // via dispatch, so it is stalled until we return. // start our nested loop, which will block until that exits Object ret = _runLoop(); if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("Resuming event loop"); } // and return the value that was passed into leaveNested return ret; } @Override protected void _leaveNestedEventLoop(Object retValue) { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("_leaveNestedEventLoop"); } // we are being called from dispatch of the current running // event thread. We want to cause this thread to exit, and // restart the nested on. RunLoopControl current = activeRunLoops.pop(); assert current != null; // let the current run loop die when we return to dispatch. current.active = false; // and give it the ret object so it will return it to the // blocked nesting call. current.release = retValue; if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("_leaveNestedEventLoop"); } // when we return from this dispatched event, we will exit // because we are no longer active, and then the nested // call can return the release value we just provided. } private Object _runLoop() { // Note: this method can be recursively called // so do not add any platform initialization code here ! final RunLoopControl control = new RunLoopControl(); //push this new instance on the stack activeRunLoops.push(control); if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("Starting event loop"); } control.active = true; while (control.active) { Event event; synchronized (eventList) { if (eventList.isEmpty()) { try { eventList.wait(); } catch (InterruptedException e) { continue; } } if (eventList.isEmpty()) { continue; } event = eventList.removeFirst(); } if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().fine("Processing " + event); } try { event.dispatch(); } catch (Exception e) { reportException(e); } } if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("Leaving event loop"); } return control.release; } private static void registerEventLoop() { synchronized (activeEventLoopLock) { activeEventLoopThreads ++; LensLogger.getLogger().info( "activeEventLoopThreads := " + activeEventLoopThreads); activeEventLoopLock.notifyAll(); } } private static void unregisterEventLoop() { synchronized (activeEventLoopLock) { activeEventLoopThreads --; LensLogger.getLogger().info( "activeEventLoopThreads := " + activeEventLoopThreads); activeEventLoopLock.notifyAll(); } } private static void waitEventLoopsToFinish() { synchronized (activeEventLoopLock) { try { LensLogger.getLogger().info("Waiting for all event loops to finish"); while (activeEventLoopThreads > 0) { LensLogger.getLogger().info( "activeEventLoopThreads = " + activeEventLoopThreads); activeEventLoopLock.wait(); } } catch (InterruptedException e) { LensLogger.getLogger().severe("interrupted"); } } } @Override protected void runLoop(Runnable launchable) { _invokeLater(launchable); long stackSize = AccessController.doPrivileged( (PrivilegedAction) () -> Long.getLong("glass.lens.stackSize", 0)); Thread toolkitThread = new Thread(new ThreadGroup("Events"), () -> { if (!_initialize()) { LensLogger.getLogger().severe("Display failed initialization"); throw new RuntimeException("Display failed initialization"); } _runLoop(); }, "Lens Event Thread", stackSize); setEventThread(toolkitThread); toolkitThread.start(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { shutdown(); } }); } private static int nativeThreadCounter = 0; // Call into the native impl to start up any native device input queues // needed. Native will upcall to createNativeEventThread to start any threads. private native void nativeEventLoop(final LensApplication lensApp, long nativeEventHandler, long nativeWindow); // Note: this Native event thread is designed to listen for events // from native sources. For example, it may poll some native input devices. // It is *not* the same as the FX event thred. private static void createNativeEventThread(final long nativeEventHandler, final long data) { final LensApplication lensApplication = (LensApplication)Application.GetApplication(); Thread eventThread = new Thread(() -> { registerEventLoop(); lensApplication.nativeEventLoop(lensApplication, nativeEventHandler, data); //when the native function return //event loop has exited unregisterEventLoop(); }, ("Lens Native Event Thread " + (nativeThreadCounter++))); LensLogger.getLogger().info("Starting native event thread"); eventThread.setDaemon(true); eventThread.start(); } Object enterDnDEventLoop() { dndEventLoop = createEventLoop(); return dndEventLoop.enter(); } void leaveDndEventLoop(Object value) { dndEventLoop.leave(value); } native void shutdown(); @Override protected void finishTerminating() { LensLogger.getLogger().info("Finishing terminating"); shutdown(); synchronized (eventList) { eventList.clear(); while (!activeRunLoops.isEmpty()) { RunLoopControl control = activeRunLoops.pop(); control.active = false; } eventList.notify(); } super.finishTerminating(); } @Override protected boolean _supportsTransparentWindows() { return true; } @Override protected boolean _supportsUnifiedWindows() { return false; } //******************************************************************* // Runloop/Event queue support additions //Window events /** * handles the following window events WindowEvent.MINIMIZE / * MAXIMIZE /RESTORE / RESIZE * See Window.java::notifyResize() for more information * * @param window the window object which this event belongs to * @param eventType WindowEvent.MINIMIZE / MAXIMIZE /RESTORE / * RESIZE * @param width new width * @param height new height */ protected void notifyWindowResize(LensWindow window, int eventType, int width, int height) { if (LensLogger.getLogger().isLoggable(Level.INFO)) { LensLogger.getLogger().info( "notifyResize with "+WindowEvent.getEventName(eventType)+ " event "+ window + " to " + width + "x" + height); } if (window != null) { postEvent(new LensWindowEvent(LensWindowEvent.EType.RESIZE, window, eventType, 0, 0, width, height)); } } /** * Notify JFX that the window have been moved and provide the * new coordinates. * * This notification can be either response for JFX request to * change the window location, or user have changed it. The * later case will happen when window a manager is used. * * @param window The window which this event belongs to * @param x new X coordinate of the window * @param y new Y Coordinate of the window */ protected void notifyWindowMove(LensWindow window, int x, int y) { if (LensLogger.getLogger().isLoggable(Level.INFO)) { LensLogger.getLogger().info( "Move " + window + " to " + x + "," + y); } postEvent(new LensWindowEvent(LensWindowEvent.EType.MOVE, window, WindowEvent.MOVE, x, y, 0, 0)); } /** * This notification informs JFX on window events that doesn't * requires additional information for handling the * notification. For example window have gained/lost focus. * * @param window The window which this event belongs to * @param windowEvent the event type as defined in WindowEvent * class. */ protected void notifyWindowEvent(LensWindow window, int windowEvent) { LensWindowEvent.EType etype = null; switch (windowEvent) { case WindowEvent.FOCUS_GAINED: etype = LensWindowEvent.EType.FOCUS; break; case WindowEvent.FOCUS_LOST: etype = LensWindowEvent.EType.FOCUS; break; case WindowEvent.DESTROY: etype = LensWindowEvent.EType.DESTROY; break; case WindowEvent.CLOSE: etype = LensWindowEvent.EType.CLOSE; break; case WindowEvent.FOCUS_UNGRAB: etype = LensWindowEvent.EType.UNGRAB; break; case WindowEvent.FOCUS_DISABLED: etype = LensWindowEvent.EType.FOCUS_DISABLED; break; default: LensLogger.getLogger().warning("Unsupported event type ("+ WindowEvent.getEventName(windowEvent)+") skipping event"); return; } if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine( "notifyWindowEvent eventType = " + WindowEvent.getEventName(windowEvent)); } if (etype != null) { postEvent(new LensWindowEvent(etype, window, windowEvent)); } } protected void windowExpose(LensWindow window, int x, int y, int width, int height) { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine( "Expose " + window + " " + x + "," + y + "+" + width + "x" + height); } postEvent(new LensWindowEvent(LensWindowEvent.EType.EXPOSE, window, WindowEvent.RESIZE, x, y, width, height)); } //Input events /** * Notify key event from native layer * * @param view the window which the view is related to * @param type event type (KeyEvent.PRESS ...) * @param keyCode key code for the event (KeyEvent.VK_*) * @param modifiers bit mask of key modifiers * (KeyEvent.MODIFIER_*) * @param chars char sequence buffer. can be 0 length, must not * be null */ private void notifyKeyEvent(LensView view, int type , int keyCode, int modifiers, char[] chars) { try { if (LensLogger.getLogger().isLoggable(Level.FINER)) { LensLogger.getLogger().finer("Key event on " + view); } postEvent(new LensKeyEvent(view, type, keyCode, modifiers , chars)); } catch (Exception e) { reportException(e); } } /** * Notify mouse event from native layer * * @param view the window which the view is related to * @param eventType one of MouseEvent constants * @param x location of event inside the view * @param y location of event inside the view * @param absx location of event on the screen * @param absy location of event on the screen * @param button currently pressed button, required only in applicable events * such as MouseEvent.DOWN * @param modifiers mask of currently pressed special keys and mouse * buttons. always required * @param isPopupTrigger true when event is context menu hint (usually right * button release) * @param isSynthesized used when event is logical such MouseEvent.CLICK */ void notifyMouseEvent(LensView view, int eventType, int x, int y, int absx, int absy, int button, int modifiers, boolean isPopupTrigger, boolean isSynthesized) { try { if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("Mouse event on " + view); } //continue process events only if not already consumed if (!handleDragEvents(view, eventType, x, y, absx, absy, button, modifiers)) { postMouseEvent(view, eventType, x, y, absx, absy, button, modifiers, isPopupTrigger, isSynthesized); } } catch (Exception e) { reportException(e); } } ////// Drag And Drop Support //used to mark the button which triggered the drag - when its release //the drag ends private int cachedButtonPressed = MouseEvent.BUTTON_NONE; //The View that the drag is currently hoover above private LensView dragView = null; //mask of operations done on current drag event //mask values are ORed from DragActions enum private int dragActionsPreformed = DragActions.NONE.getValue(); //Mark if drag processing have started by upper levels, //value changes when notifyDragStart is called private boolean dragStarted = false; //Possible actioins for drag events private enum DragActions { NONE(0, "NONE"), ENTER(1 << 1, "ENTER"), LEAVE(1 << 2, "LEAVE"), OVER(1 << 3, "OVER"), DROP(1 << 4, "DROP"); public int value; private String name; DragActions(int value, String name) { this.value = value; this.name = name; } public int getValue() { return value; } @Override public String toString() { return name; } }; private native void _notfyPlatformDnDStarted(); private native void _notfyPlatformDnDEnded(); /** * This function should be only called from LensDnDClipboard after it has * been initialized. * After this method have called it meand that we are inside nested event * loop, which is responsible to handle all drag events, all other mouse * events are discarded until Drag Drop is detected. */ void notifyDragStart() { _notfyPlatformDnDStarted(); dragStarted = true; } /** * Transforms mouse events into drag events when drag detected. When this * functions returns true, it means that the event is been consumed by this * method and no further processing is required * * @param view the view which owns the event * @param eventType type of event, one of MouseEvent constants * @param x location of event inside the view * @param y location of event inside the view * @param absx location of event on the screen * @param absy location of event on the screen * @param button currently pressed button, required only in applicable * events such as MouseEvent.DOWN * @param modifiers mask of currently pressed special keys and mouse * buttons. always required * @return boolean true if event has been consumed and no further processing * required */ private boolean handleDragEvents(LensView view, int eventType, int x, int y, int absx, int absy, int button, int modifiers) { boolean eventConsumed = false; if (eventType == MouseEvent.DOWN && cachedButtonPressed == MouseEvent.BUTTON_NONE) { //save the button that might have started the drag event cachedButtonPressed = button; if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("Caching mouse button - " + button); } } else if (eventType == MouseEvent.UP && button == cachedButtonPressed) { //reset cached button on mouse up cachedButtonPressed = MouseEvent.BUTTON_NONE; if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("reset mouse button cache " + button); } if (dragStarted) { //drag button has been released while drag is active = drop if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("notifying drag DROP"); } postEvent(new LensDragEvent(view, x, y, absx, absy, DragActions.DROP)); //notify platform DnD ended _notfyPlatformDnDEnded(); //reset internal state machine dragActionsPreformed = DragActions.NONE.getValue(); dragView = null; dragStarted = false; } } else if (eventType == MouseEvent.MOVE && cachedButtonPressed != MouseEvent.BUTTON_NONE && ((modifiers & KeyEvent.MODIFIER_BUTTON_PRIMARY) == KeyEvent.MODIFIER_BUTTON_PRIMARY || (modifiers & KeyEvent.MODIFIER_BUTTON_MIDDLE) == KeyEvent.MODIFIER_BUTTON_MIDDLE || (modifiers & KeyEvent.MODIFIER_BUTTON_SECONDARY) == KeyEvent.MODIFIER_BUTTON_SECONDARY)) { //move + mouse button pressed = drag if (dragStarted) { //consume all event after drag have started eventConsumed = true; //drag has been initiated - handle drag drop/over/enter/leave events if (dragView == view && dragActionsPreformed == DragActions.NONE.getValue()) { //first notification postEvent(new LensDragEvent(view, x, y, absx, absy, DragActions.ENTER)); dragActionsPreformed |= DragActions.ENTER.getValue(); if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("Notifying DragEnter"); } } else if (dragView == view && (dragActionsPreformed & DragActions.ENTER.getValue()) == DragActions.ENTER.getValue()) { //view was notified that drag has entered to it //now we need to send DragOver notification postEvent(new LensDragEvent(view, x, y, absx, absy, DragActions.OVER)); dragActionsPreformed |= DragActions.OVER.getValue(); if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("Notifying DragOver"); } } else if (dragView != view) { //drag was moved to another view, leave the old one and //reset the actions flags and dragView, //also notify the new view for dragEnter if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("Notifying DragLeave old view"); } postEvent(new LensDragEvent(dragView, x, y, absx, absy, DragActions.LEAVE)); if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("Notifying DragEnter new view"); } postEvent(new LensDragEvent(view, x, y, absx, absy, DragActions.ENTER)); dragActionsPreformed = DragActions.ENTER.getValue(); dragView = view; } } else { eventType = MouseEvent.DRAG; if (dragView == null) { //cache the view that the drag started on dragView = view; } if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().finest("Drag detected - sending DRAG event"); } postMouseEvent(view, eventType, x, y, absx, absy, button, modifiers, false /*isPopupTrigger*/, false /*isSynthesized*/); eventConsumed = true; } } return eventConsumed; } /** * Notify scroll event from native layer * * @param view the window which the view is related to * @param x * @param y * @param absx * @param absy * @param deltaX * @param deltaY * @param modifiers * @param lines * @param chars * @param defaultLines * @param defaultChars * @param xMultiplier * @param yMultiplier * @param modifiers */ private void notifyScrollEvent(LensView view, int x, int y, int absx, int absy, double deltaX, double deltaY, int modifiers, int lines, int chars, int defaultLines, int defaultChars, double xMultiplier, double yMultiplier) { try { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("Scroll event on " + view); } postEvent(new LensScrollEvent(view, x, y, absx, absy, deltaX, deltaY, modifiers, lines, chars, defaultLines, defaultChars, xMultiplier, yMultiplier)); } catch (Exception e) { reportException(e); } } /** * Posts the touch move event. This method will inspect to see if the previous event on the queue is another * TouchMoveEvent with the same view and id. If so, it will update that event with the new x, y, absX and absY * so as to reduce the number of events on the queue. * * @param view the view which owns the event * @param id the id of the finger slot (usually 1) * @param x the x coordinate of the event * @param y the y coordinate of the event * @param absX not sure * @param absY not sure */ private void postTouchMoveEvent(LensView view, long id, int x, int y, int absX, int absY) { synchronized (eventList) { boolean isEventCompacted = false; if (!eventList.isEmpty()) { Iterator events = eventList.descendingIterator(); while (events.hasNext()) { Event lastEvent = events.next(); if (lastEvent instanceof LensTouchEvent) { LensTouchEvent e = (LensTouchEvent) lastEvent; if (e.view == view && e.id == id && (e.state == TouchEvent.TOUCH_MOVED || e.state == TouchEvent.TOUCH_STILL)) { // Rewrite the coordinates of the scheduled event // with the coordinates of this event. e.x = x; e.y = y; e.absX = absX; e.absY = absY; isEventCompacted = true; } //we have found the last touch event on the queue, break break; } else if (lastEvent instanceof LensMultiTouchEvent) { //another touch event type was posted, can't update event break; } } } if (!isEventCompacted) { postEvent(new LensTouchEvent(view, TouchEvent.TOUCH_MOVED, id, x, y, absX, absY)); } } } /** * Notify touch event from native layer * * @param view the window which the view is related to * @param state the finger state (e.g. TouchEvent.TOUCH_PRESSED) * @param id the id of the finger slot * @param x the x coordinate relative to the view origin * @param y the y coordinate relative to the view origin * @param absX the x coordinate relative to the screen origin * @param absY the y coordinate relative to the screen origin */ private void notifyTouchEvent(LensView view, int state, long id, int x, int y, int absX, int absY) { try { final boolean hadPreviousTouchMove = previousTouchMoveScreenX >= 0; if (state == TouchEvent.TOUCH_MOVED) { postTouchMoveEvent(view, id, x, y, absX, absY); } else { postEvent(new LensTouchEvent(view, state, id, x, y, absX, absY)); } if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("Touch event " + state + " at " + x + "," + y + " on " + view); } } catch (Exception e) { reportException(e); } } /** * Notify multitouch event from native layer * * @param view the window which the view is related to * @param states list of the finger states of each touch point * @param ids list of the IDs of each touch point * @param xs list of of the absolute X coordinates of each touch point * @param ys list of of the absolute Y coordinates of each touch point * @param dx value to be added to X coordinates to convert them to relative * @param dy value to be added to Y coordinates to convert them to relative */ private void notifyMultiTouchEvent(LensView view, int[] states, long[] ids, int[] xs, int[] ys, int dx, int dy) { try { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("MultiTouch event with " + states.length + " points " + " on " + view); } synchronized (eventList) { // Try to match this multitouch event against a the last multi touch // pending event on the queue, if they match and event type is move, // update the existing event with the new values instead of posting a new event // This code assume that the order of touch points doesn't //change between events boolean match = false; if (!eventList.isEmpty()) { Iterator events = eventList.descendingIterator(); while (events.hasNext()) { Event lastEvent = events.next(); if (lastEvent instanceof LensMultiTouchEvent) { LensMultiTouchEvent e = (LensMultiTouchEvent) lastEvent; if (e.view == view && e.states.length == states.length) { assert(states.length == ids.length); assert(e.states.length == e.ids.length); match = true; for (int i = 0; i < states.length && match; i++) { //check if event is motion related if ((e.states[i] != TouchEvent.TOUCH_MOVED && e.states[i] != TouchEvent.TOUCH_STILL) || (states[i] != TouchEvent.TOUCH_MOVED && states[i] != TouchEvent.TOUCH_STILL)) { match = false; } if (e.ids[i] != ids[i]) { match = false; } } if (match) { // rewrite the coordinates of the scheduled event // with the coordinates of this event. e.xs = xs; e.ys = ys; e.states = states; e.dx = dx; e.dy = dy; } } //we have found the last touch event on the queue, break break; } else if (lastEvent instanceof LensTouchEvent) { //another touch event type was posted, can't update event break; } } } if (!match) { postEvent(new LensMultiTouchEvent(view, states, ids, xs, ys, dx, dy)); } } } catch (Exception e) { reportException(e); } } /** * Notify view event from native * View events are one of the events listed in ViewEvent.java * * @param view the event occurred in * @param viewEventType the type of event as listed in * ViewEvent.java */ private void notifyViewEvent(LensView view, int viewEventType, int x, int y, int width, int height) { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine( "Notify event type " + ViewEvent.getTypeString(viewEventType) + " on " + view); } postEvent(new LensViewEvent(view, viewEventType, x, y, width, height)); } private void notifyMenuEvent(LensView view, int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { if (LensLogger.getLogger().isLoggable(Level.FINER)) { LensLogger.getLogger().finer( "Notify menu event " + "x=" + x + ", y=" + y + ", xAbs=" + xAbs + ", yAbs=" + yAbs + ", isKeyboardTrigger " + isKeyboardTrigger + ", on " + view); } if (view != null) { postEvent(new LensMenuEvent(view, x, y, xAbs, yAbs, isKeyboardTrigger)); } else { if (LensLogger.getLogger().isLoggable(Level.FINER)) { LensLogger.getLogger().finer("view is null, skipping event"); } } } /** * Notify device event from native * A device event is sent when an input device is attached or detached. * * @param flags the device type flags (a bitmask containing values up to 2^DEVICE_MAX) * @param attach true is the device was attached, false if it was detached. */ private void notifyDeviceEvent(int flags, boolean attach) { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine( "Notify device event attach=" + attach + ", flags=0x" + Integer.toHexString(flags)); } postEvent(new LensDeviceEvent(flags, attach)); } /** * Notify changes in screen settings from native layer * This method is triggered when native code detects a change in e.g. screen orientation */ private void notifyScreenSettingsChanged() { if (LensLogger.getLogger().isLoggable(Level.FINE)) { LensLogger.getLogger().fine("Notify screen settings changed"); } postEvent(new LensScreenEvent()); } //******************************************************************* public void installWindowMenu(MenuBar menubar) { // not sure what menubars look like on an embedded device yet. this.windowMenu = createMenu("Window"); } public Menu getWindowMenu() { return this.windowMenu; } @Override public void installDefaultMenus(MenuBar menubar) { } // FACTORY METHODS @Override public Window createWindow(Window owner, Screen screen, int styleMask) { return new LensWindow(owner, screen, styleMask); } @Override public Window createWindow(long parent) { return new LensWindow(parent); } @Override public View createView() { return new LensView(); } @Override public Cursor createCursor(int type) { return new LensCursor(type); } @Override public Cursor createCursor(int x, int y, Pixels pixels) { return new LensCursor(x, y, pixels); } @Override protected void staticCursor_setVisible(boolean visible) { cursorVisible = visible; if (deviceFlags[DEVICE_POINTER] >= 1) { LensCursor.setVisible_impl(visible); } } @Override protected Size staticCursor_getBestSize(int width, int height) { return LensCursor.getBestSize_impl(width, height); } @Override public Pixels createPixels(int width, int height, ByteBuffer data) { return new LensPixels(width, height, data); } @Override public Pixels createPixels(int width, int height, IntBuffer data) { return new LensPixels(width, height, data); } @Override public Pixels createPixels(int width, int height, IntBuffer data, float scalex, float scaley) { return new LensPixels(width, height, data, scalex, scaley); } @Override protected int staticPixels_getNativeFormat() { return LensPixels.getNativeFormat_impl(); } @Override public Robot createRobot() { return new LensRobot(); } @Override protected double staticScreen_getVideoRefreshPeriod() { return 0.0; // indicate millisecond resolution } @Override native protected Screen[] staticScreen_getScreens(); @Override public Timer createTimer(Runnable runnable) { return new LensTimer(runnable); } @Override protected int staticTimer_getMinPeriod() { return LensTimer.getMinPeriod_impl(); } @Override protected int staticTimer_getMaxPeriod() { return LensTimer.getMaxPeriod_impl(); } @Override protected FileChooserResult staticCommonDialogs_showFileChooser(Window owner, String folder, String filename, String title, int type, boolean multipleMode, ExtensionFilter[] extensionFilters, int defaultFilterIndex) { // FileChooser APIs should return null when they cannot show a dialog return null; } @Override protected File staticCommonDialogs_showFolderChooser(Window owner, String folder, String title) { // FileChooser APIs should return null when they cannot show a dialog return null; } @Override protected long staticView_getMultiClickTime() { return LensView._getMultiClickTime(); } @Override protected int staticView_getMultiClickMaxX() { return LensView._getMultiClickMaxX(); } @Override protected int staticView_getMultiClickMaxY() { return LensView._getMultiClickMaxY(); } @Override protected void _invokeAndWait(Runnable runnable) { if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().fine("invokeAndWait " + runnable); } synchronized (invokeAndWaitLock) { waitingFor = runnable; } synchronized (eventList) { eventList.addLast(new RunnableEvent(true, runnable)); eventList.notify(); } synchronized (invokeAndWaitLock) { while (waitingFor == runnable) { try { invokeAndWaitLock.wait(); } catch (InterruptedException ex) { } } } } @Override protected void _invokeLater(Runnable runnable) { if (LensLogger.getLogger().isLoggable(Level.FINEST)) { LensLogger.getLogger().fine("invokeLater " + runnable); } synchronized (eventList) { eventList.addLast(new RunnableEvent(false, runnable)); eventList.notify(); } } public boolean hasTwoLevelFocus() { return deviceFlags[DEVICE_PC_KEYBOARD] == 0 && deviceFlags[DEVICE_5WAY] > 0; } public boolean hasVirtualKeyboard() { return deviceFlags[DEVICE_PC_KEYBOARD] == 0 && deviceFlags[DEVICE_TOUCH] > 0; } public boolean hasTouch() { return deviceFlags[DEVICE_TOUCH] > 0; } public boolean hasMultiTouch() { return deviceFlags[DEVICE_MULTITOUCH] > 0; } public boolean hasPointer() { return deviceFlags[DEVICE_POINTER] > 0; } @Override protected native int _getKeyCodeForChar(char c); }