javauis/lcdui_qt/src/javax/microedition/lcdui/EventDispatcher.java
branchRCL_3
changeset 65 ae942d28ec0e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javauis/lcdui_qt/src/javax/microedition/lcdui/EventDispatcher.java	Tue Aug 31 15:09:22 2010 +0300
@@ -0,0 +1,408 @@
+/*
+* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description:
+*
+*/
+
+package javax.microedition.lcdui;
+
+import org.eclipse.swt.widgets.Widget;
+
+
+/*
+ * Event dispatcher singleton class. Takes care of dispatching the UI callbacks
+ * to the MIDlet in a dedicated dispatcher thread. Owns and encapsulates an
+ * event queue of callback events.
+ */
+final class EventDispatcher
+{
+
+    private static EventDispatcher singleton;
+    private static EventQueue eventQueue;
+    private static Thread dispatcherThread;
+    private static boolean terminated;
+    private static Object wakeLock;
+    private static Object callbackLock;
+    private static boolean pendingWake;
+    private static Runnable terminatedNotification;
+    private final static String logName = "LCDUI event dispatcher: ";
+
+    /*
+     * LCDUI-internal event class for callback events.
+     */
+    static final class LCDUIEvent
+    {
+        /*
+         * High bits of the event event type identifier are used to group the
+         * events that are handled the same way in the Event Dispatcher.
+         */
+        static final int CANVASBIT = 0x80000000;
+        static final int CUSTOMITEMBIT = 0x40000000;
+        static final int DISPLAYABLEBIT = 0x20000000;
+        static final int EVENTTYPEMASK = CANVASBIT|CUSTOMITEMBIT|DISPLAYABLEBIT;
+
+        /*
+         * Event type identifiers.
+         */
+        static final int CANVAS_HIDENOTIFY = 1|CANVASBIT;
+        static final int CANVAS_KEYPRESSED = 2|CANVASBIT;
+        static final int CANVAS_KEYREPEATED = 3|CANVASBIT;
+        static final int CANVAS_KEYRELEASED = 4|CANVASBIT;
+        static final int CANVAS_PAINT_NATIVE_REQUEST = 5|CANVASBIT;
+        static final int CANVAS_PAINT_MIDLET_REQUEST = 6|CANVASBIT;
+        static final int CANVAS_POINTERDRAGGED = 7|CANVASBIT;
+        static final int CANVAS_POINTERPRESSED = 8|CANVASBIT;
+        static final int CANVAS_POINTERRELEASED = 9|CANVASBIT;
+        static final int CANVAS_SHOWNOTIFY = 10|CANVASBIT;
+        static final int DISPLAYABLE_SIZECHANGED = 11|DISPLAYABLEBIT;
+        static final int DISPLAYABLE_COMMANDACTION = 12|DISPLAYABLEBIT;
+        static final int CUSTOMITEM_HIDENOTIFY = 13|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_KEYPRESSED = 14|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_KEYREPEATED = 15|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_KEYRELEASED = 16|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_PAINT_NATIVE_REQUEST = 17|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_PAINT_MIDLET_REQUEST = 18|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_POINTERDRAGGED = 19|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_POINTERPRESSED = 20|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_POINTERRELEASED = 21|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_SHOWNOTIFY = 22|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_SIZECHANGED = 23|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_TRAVERSE = 24|CUSTOMITEMBIT;
+        static final int CUSTOMITEM_TRAVERSEOUT = 25|CUSTOMITEMBIT;
+        static final int ITEM_COMMANDACTION = 26;
+        static final int ITEMSTATELISTENER_ITEMSTATECHANGED = 27;
+        static final int RUNNABLE_CALLSERIALLY = 28;
+
+        /*
+         * Event parameters.
+         */
+        int type;
+        int x;
+        int y;
+        int width;
+        int height;
+        int keyCode;
+        Runnable runnable;
+        Command command;
+        CommandListener commandListener;
+        ItemCommandListener itemCommandListener;
+        Item item;
+
+        /*
+         * Handler object. The object that the LCDUIEvent will be passed to
+         * when the event is dispatched. Based on the event type it's known
+         * what type of an object it must be. The handler object will
+         * implement how to do the actual callback.
+         */
+        Object handlerObject;
+
+        /*
+         * The associated eSWT widget. Used to store the eSWT widget which
+         * the original eSWT event was for. E.g. in Form the CustomItem's
+         * widget may change while the event is in the queue.
+         */
+        Widget widget;
+
+        /*
+         * Used by EventQueue.
+         */
+        LCDUIEvent next;
+
+        private LCDUIEvent()
+        {
+        }
+    }
+
+    /*
+     * The event loop executed in a dedicated dispatcher thread. Events are
+     * popped from the queue and dispatched oldest first until there are no
+     * more events. Then the thread sleeps until new events are posted or
+     * termination is requested. Any exceptions thrown from the event handlers
+     * are let through to the runtime which should lead into termination of
+     * all threads in the process.
+     */
+    private static class EventLoop implements Runnable
+    {
+        private boolean noException;
+        public void run()
+        {
+            try
+            {
+                Logger.info(logName + "Dispatcher thread started");
+                while(true)
+                {
+                    LCDUIEvent event;
+                    do
+                    {
+                        synchronized(wakeLock)
+                        {
+                            if(terminated)
+                            {
+                                cleanup();
+                                noException = true;
+                                return;
+                            }
+                            event = eventQueue.pop();
+                            pendingWake = false;
+                        }
+
+                        if(event != null)
+                        {
+                            synchronized(callbackLock)
+                            {
+                                // No callbacks are sent if already terminating
+                                if(!terminated)
+                                {
+                                    handleEvent(event);
+                                }
+                            }
+                        }
+                    }
+                    while(event != null);
+
+                    // No more events
+                    waitForWake();
+                }
+            }
+            finally
+            {
+                if(noException)
+                {
+                    Logger.info(logName + "Dispatcher thread exiting normally");
+                }
+                else
+                {
+                    Logger.error(logName + "Dispatcher thread exiting abnormally");
+                }
+                if(terminatedNotification != null)
+                {
+                    terminatedNotification.run();
+                    terminatedNotification = null;
+                }
+            }
+        }
+    }
+
+    private EventDispatcher()
+    {
+        wakeLock = new Object();
+        callbackLock = new Object();
+        eventQueue = new EventQueue();
+        dispatcherThread = new Thread(new EventLoop(), this.toString());
+        dispatcherThread.start();
+    }
+
+    /*
+     * Cleanup that is done when closing down in a controlled way.
+     */
+    private static void cleanup()
+    {
+        eventQueue = null;
+        dispatcherThread = null;
+    }
+
+    /*
+     * Synchronized to the EventDispatcher class to synchronize with multiple
+     * clients requesting the instance concurrently.
+     */
+    static synchronized EventDispatcher instance()
+    {
+        if(singleton == null)
+        {
+            singleton = new EventDispatcher();
+        }
+        return singleton;
+    }
+
+    /*
+     * LCDUIEvent factory.
+     */
+    LCDUIEvent newEvent(int type, Object handlerObject)
+    {
+        LCDUIEvent event = new LCDUIEvent();
+        event.type = type;
+        event.handlerObject = handlerObject;
+        return event;
+    }
+
+    /*
+     * UI thread and the application threads call to add an event to the queue.
+     * Synchronized to the EventDispatcher instance to synchronize with queries about
+     * the queue content.
+     */
+    synchronized void postEvent(LCDUIEvent event)
+    {
+        // New events ignored after terminate() has been called.
+        if(terminated)
+        {
+            return;
+        }
+        eventQueue.push(event);
+        wake();
+    }
+
+    /*
+     * Asynchronously terminates the event dispatcher. If it's currently
+     * executing a callback it will finish it but not execute any callbacks
+     * after it. Events possibly remaining in the queue are discarded. The
+     * method is synchronized to the EventDispatcher instance to synchronize
+     * with queue content manipulation and query operations.
+     *
+     * @param runnable Called when dispatcher is out of the final callback and
+     * is going to die.
+     */
+    synchronized void terminate(Runnable runnable)
+    {
+        synchronized(wakeLock)
+        {
+            wake();
+            terminatedNotification = runnable;
+            terminated = true;
+        }
+    }
+
+    /*
+     * Canvas.serviceRepaints support is implemented in EventDispatcher so that
+     * it's possible to synchronize properly with termination and other
+     * callbacks without exposing the event dispatcher's internal
+     * implementation details to other classes. This method must be called
+     * directly in the application thread calling Canvas.serviceRepaints(). It
+     * can be either the dispatcher thread or any application thread. Never
+     * the UI thread.
+     */
+    void serviceRepaints(Canvas canvas)
+    {
+        // Synchronizing to callbackLock guarantees that dispatcher thread is
+        // not in a callback and is not going to make a callback until the lock
+        // is freed from here. If this is the dispatcher thread then it already
+        // holds the lock.
+        synchronized(callbackLock)
+        {
+            // Synchronized to the EventDispatcher instance to synchronize with
+            // terminate() calls. Lock is freed before the callback so that
+            // it's possible to complete a terminate() call during the
+            // callback. Event loop is going to check after acquiring the
+            // callbackLock if terminate() was called during this callback.
+            synchronized(this)
+            {
+                if(terminated)
+                {
+                    return;
+                }
+            }
+            // Canvas.paint() is called back directly in the thread that called
+            // serviceRepaints(). Event is not really needed but is created so
+            // that the same APIs can be used.
+            LCDUIEvent event = newEvent(LCDUIEvent.CANVAS_PAINT_MIDLET_REQUEST, canvas);
+            event.widget = canvas.getContentComp();
+            canvas.doCallback(event);
+        }
+    }
+
+    private static void wake()
+    {
+        synchronized(wakeLock)
+        {
+            wakeLock.notify();
+            pendingWake = true;
+        }
+    }
+
+    private static void waitForWake()
+    {
+        try
+        {
+            synchronized(wakeLock)
+            {
+                // If there has been a call to wake() then one more iteration
+                // must be executed before entering wait().
+                if(!pendingWake)
+                {
+                    wakeLock.wait();
+                }
+            }
+        }
+        catch(InterruptedException interruptedException)
+        {
+        }
+    }
+
+    private static void handleEvent(LCDUIEvent event)
+    {
+        switch(event.type & LCDUIEvent.EVENTTYPEMASK)
+        {
+        case LCDUIEvent.CANVASBIT:
+            handleCanvasEvent(event);
+            break;
+        case LCDUIEvent.CUSTOMITEMBIT:
+            handleCustomItemEvent(event);
+            break;
+        case LCDUIEvent.DISPLAYABLEBIT:
+            handleDisplayableEvent(event);
+            break;
+        default:
+            handleOtherEvent(event);
+            break;
+        }
+        // When returning from here all the references to the event have been
+        // lost and the objects referenced by the event can be gc'd. No need
+        // to set the fields to null.
+    }
+
+    private static void handleCanvasEvent(LCDUIEvent event)
+    {
+        Canvas canvas = (Canvas)event.handlerObject;
+        canvas.doCallback(event);
+    }
+
+    private static void handleCustomItemEvent(LCDUIEvent event)
+    {
+        Form form = (Form)event.handlerObject;
+        form.doCallback(event);
+    }
+
+    private static void handleDisplayableEvent(LCDUIEvent event)
+    {
+        Displayable displayable = (Displayable)event.handlerObject;
+        displayable.doCallback(event);
+    }
+
+    private static void handleOtherEvent(LCDUIEvent event)
+    {
+        switch(event.type)
+        {
+        case LCDUIEvent.RUNNABLE_CALLSERIALLY:
+            handleCallSerially(event);
+            break;
+        case LCDUIEvent.ITEM_COMMANDACTION:
+            handleItemCommandListenerCommandAction(event);
+            break;
+        default:
+            Logger.error(logName + "Unknown event type: " + event.type);
+            break;
+        }
+    }
+
+    private static void handleCallSerially(LCDUIEvent event)
+    {
+        Display display = (Display)event.handlerObject;
+        display.doCallback(event);
+    }
+
+    private static void handleItemCommandListenerCommandAction(LCDUIEvent event)
+    {
+        Item item = (Item)event.handlerObject;
+        item.doCallback(event);
+    }
+}