javauis/lcdui_qt/src/javax/microedition/lcdui/EventDispatcher.java
changeset 21 2a9601315dfc
child 23 98ccebc37403
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javauis/lcdui_qt/src/javax/microedition/lcdui/EventDispatcher.java	Mon May 03 12:27:20 2010 +0300
@@ -0,0 +1,358 @@
+/*
+* 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);
+	}
+}