View Javadoc

1   /**
2    * MicroEmulator 
3    * Copyright (C) 2001-2007 Bartek Teodorczyk <barteo@barteo.net>
4    * Copyright (C) 2007 Rushabh Doshi <radoshi@cs.stanford.edu> Pelago, Inc
5    * 
6    * This library is free software; you can redistribute it and/or modify it
7    * under the terms of the GNU Lesser General Public License as published by the
8    * Free Software Foundation; either version 2.1 of the License, or (at your
9    * option) any later version.
10   * 
11   * This library is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14   * for more details.
15   * 
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with this library; if not, write to the Free Software Foundation,
18   * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19   * 
20   * Contributor(s): 
21   *   3GLab
22   *   Andres Navarro
23   *   
24   *  @version $Id: Display.java 1662 2008-03-24 19:57:30Z vlads $
25   */
26  package javax.microedition.lcdui;
27  
28  import java.util.Timer;
29  import java.util.TimerTask;
30  
31  import javax.microedition.lcdui.game.GameCanvas;
32  import javax.microedition.midlet.MIDlet;
33  
34  import org.microemu.CommandManager;
35  import org.microemu.DisplayAccess;
36  import org.microemu.GameCanvasKeyAccess;
37  import org.microemu.MIDletBridge;
38  import org.microemu.device.DeviceFactory;
39  import org.microemu.device.ui.DisplayableUI;
40  
41  import edu.emory.mathcs.backport.java.util.concurrent.BlockingQueue;
42  import edu.emory.mathcs.backport.java.util.concurrent.LinkedBlockingQueue;
43  import edu.emory.mathcs.backport.java.util.concurrent.Semaphore;
44  
45  public class Display {
46  
47      public static final int LIST_ELEMENT = 1;
48  
49      public static final int CHOICE_GROUP_ELEMENT = 2;
50  
51      public static final int ALERT = 3;
52  
53      public static final int COLOR_BACKGROUND = 0;
54  
55      public static final int COLOR_FOREGROUND = 1;
56  
57      public static final int COLOR_HIGHLIGHTED_BACKGROUND = 2;
58  
59      public static final int COLOR_HIGHLIGHTED_FOREGROUND = 3;
60  
61      public static final int COLOR_BORDER = 4;
62  
63      public static final int COLOR_HIGHLIGHTED_BORDER = 5;
64  
65      // private PaintThread paintThread = null;
66      // private EventDispatcher eventDispatcher = null;
67  
68      private Displayable current = null;
69  
70      private DisplayAccessor accessor = null;
71  
72      private static final String EVENT_DISPATCHER_NAME = "event-thread";
73  
74      private final EventDispatcher eventDispatcher = new EventDispatcher();
75  
76      private final class GaugePaintTask implements Runnable {
77  
78          public void run() {
79              if (current != null) {
80                  if (current instanceof Alert) {
81                      Gauge gauge = ((Alert) current).indicator;
82                      if (gauge != null && gauge.hasIndefiniteRange() && gauge.getValue() == Gauge.CONTINUOUS_RUNNING) {
83                          gauge.updateIndefiniteFrame();
84                      }
85                  } else if (current instanceof Form) {
86                      Item[] items = ((Form) current).items;
87                      for (int i = 0; i < items.length; i++) {
88                          Item it = items[i];
89                          if (it != null && it instanceof Gauge) {
90                              Gauge gauge = (Gauge) it;
91  
92                              if (gauge.hasIndefiniteRange() && gauge.getValue() == Gauge.CONTINUOUS_RUNNING) {
93                                  gauge.updateIndefiniteFrame();
94                              }
95                          }
96                      }
97                  }
98              }
99          }
100     }
101 
102     /**
103      * @author radoshi
104      * 
105      */
106     private final class TickerPaintTask implements Runnable {
107 
108         public void run() {
109             if (current != null) {
110                 Ticker ticker = current.getTicker();
111                 if (ticker != null) {
112                     synchronized (ticker) {
113                         if (ticker.resetTextPosTo != -1) {
114                             ticker.textPos = ticker.resetTextPosTo;
115                             ticker.resetTextPosTo = -1;
116                         }
117                         ticker.textPos -= Ticker.PAINT_MOVE;
118                     }
119                     repaint(current, 0, 0, current.getWidth(), current.getHeight());
120                 }
121             }
122         }
123     }
124 
125     /**
126      * Simple method that eats interrupted exception since in most cases we
127      * can't do anything about these anyway
128      * 
129      * @param event
130      */
131     private void putInQueue(Runnable runnable) {
132         try {
133             eventQueue.put(runnable);
134         } catch (InterruptedException exception) {
135             // nothing to do
136             exception.printStackTrace();
137         }
138     }
139 
140     /**
141      * Wrap a key event as a runnable so it can be thrown into the event
142      * processing queue. Note that this may be a bit buggy, since events are
143      * supposed to propogate to the head of the queue and not get tied behind
144      * other repaints or serial calls in the queue.
145      * 
146      * @author radoshi
147      * 
148      */
149     private final class KeyEvent implements Runnable {
150 
151         static final short KEY_PRESSED = 0;
152 
153         static final short KEY_RELEASED = 1;
154 
155         static final short KEY_REPEATED = 2;
156 
157         private short type;
158 
159         private int keyCode;
160 
161         KeyEvent(short type, int keyCode) {
162             this.type = type;
163             this.keyCode = keyCode;
164         }
165 
166         public void run() {
167             switch (type) {
168             case KEY_PRESSED:
169                 if (current != null) {
170                     current.keyPressed(keyCode);
171                 }
172                 break;
173 
174             case KEY_RELEASED:
175                 if (current != null) {
176                     current.keyReleased(keyCode);
177                 }
178                 break;
179 
180             case KEY_REPEATED:
181                 if (current != null) {
182                     current.keyRepeated(keyCode);
183                 }
184                 break;
185             }
186         }
187     }
188     
189     private final class PointerEvent implements Runnable {
190     	
191     	static final short POINTER_PRESSED = 0;
192     	
193     	static final short POINTER_RELEASED = 1;
194     	
195     	static final short POINTER_DRAGGED = 2;
196     	
197     	private short type;
198     	
199     	private int x;
200     	
201     	private int y;
202     	
203     	PointerEvent(short type, int x, int y) {
204     		this.type = type;
205     		this.x = x;
206     		this.y = y;
207     	}
208     	
209         public void run() {
210             switch (type) {
211             case POINTER_PRESSED:
212                 if (current != null) {
213                     current.pointerPressed(x, y);
214                 }
215             	break;
216             case POINTER_RELEASED:
217                 if (current != null) {
218                     current.pointerReleased(x, y);
219                 }
220             	break;
221             case POINTER_DRAGGED:
222                 if (current != null) {
223                     current.pointerDragged(x, y);
224                 }
225             	break;
226             }
227         }   	
228     }
229 
230     private final class ShowHideNotifyEvent implements Runnable {
231     	
232     	static final short SHOW_NOTIFY = 0;
233     	
234     	static final short HIDE_NOTIFY = 1;
235     	
236     	private short type;
237     	
238     	private Displayable current;
239     	
240     	private Displayable nextDisplayable;
241     	
242     	ShowHideNotifyEvent(short type, Displayable current, Displayable nextDisplayable) {
243     		this.type = type;
244     		this.current = current;
245     		this.nextDisplayable = nextDisplayable;
246     	}
247     	
248         public void run() {
249             switch (type) {
250             case SHOW_NOTIFY:
251                 if (current != null) {
252                 	putInQueue(new ShowHideNotifyEvent(ShowHideNotifyEvent.HIDE_NOTIFY, current, nextDisplayable));
253                 }
254 
255                 if (nextDisplayable instanceof Alert) {
256                     setCurrent((Alert) nextDisplayable, current);
257                     return;
258                 }
259 
260                 // Andres Navarro
261                 // TODO uncomment and test with JBenchmark2
262                 /*
263                  * if (nextDisplayable instanceof GameCanvas) { // clear the keys of
264                  * the GameCanvas
265                  * MIDletBridge.getMIDletAccess().getGameCanvasKeyAccess().setActualKeyState(
266                  * (GameCanvas) nextDisplayable, 0); }
267                  */
268                 // Andres Navarro
269                 nextDisplayable.showNotify(Display.this);
270                 Display.this.current = nextDisplayable;                
271                 
272                 setScrollUp(false);
273                 setScrollDown(false);
274                 updateCommands();
275                 nextDisplayable.repaint();
276                 
277                 break;
278             case HIDE_NOTIFY:
279             	current.hideNotify(Display.this);
280             	break;
281             }
282         }   	
283     }
284 
285     private class DisplayAccessor implements DisplayAccess {
286 
287         Display display;
288 
289         DisplayAccessor(Display d) {
290 
291             display = d;
292         }
293 
294         public void commandAction(Command c, Displayable d) {
295             if (c.equals(CommandManager.CMD_SCREEN_UP)) {
296                 if (d != null && d instanceof Screen) {
297                     ((Screen) d).scroll(Canvas.UP);
298                 }
299             } else if (c.equals(CommandManager.CMD_SCREEN_DOWN)) {
300                 if (d != null && d instanceof Screen) {
301                     ((Screen) d).scroll(Canvas.DOWN);
302                 }
303             } else if (c.isRegularCommand()) {
304                 if (d == null) {
305                     return;
306                 }
307                 CommandListener listener = d.getCommandListener();
308                 if (listener == null) {
309                     return;
310                 }
311                 listener.commandAction(c, d);
312             } else {
313                 // item contained command
314                 Item item = c.getFocusedItem();
315 
316                 ItemCommandListener listener = item.getItemCommandListener();
317                 if (listener == null) {
318                     return;
319                 }
320                 listener.commandAction(c.getOriginalCommand(), item);
321             }
322         }
323 
324         public Display getDisplay() {
325             return display;
326         }
327 
328         // Andres Navarro
329         private void processGameCanvasKeyEvent(GameCanvas c, int k, boolean press) {
330             // TODO Game Canvas keys need more work
331             // and better integration with the microemulator
332             // maybe actualKeyState in GameCanvas should be
333             // global and should update even while no GameCanvas
334             // is current
335             GameCanvasKeyAccess access = MIDletBridge.getMIDletAccess().getGameCanvasKeyAccess();
336             int gameCode = c.getGameAction(k);
337             boolean suppress = false;
338             if (gameCode != 0) {
339                 // valid game key
340                 if (press)
341                     access.recordKeyPressed(c, gameCode);
342                 else
343                     access.recordKeyReleased(c, gameCode);
344                 suppress = access.suppressedKeyEvents(c);
345             }
346             if (!suppress) {
347                 if (press) {
348                     putInQueue(new KeyEvent(KeyEvent.KEY_PRESSED, k));
349                 } else {
350                     putInQueue(new KeyEvent(KeyEvent.KEY_RELEASED, k));
351                 }
352             }
353         }
354 
355         // TODO according to the specification this should be
356         // only between show and hide notify...
357         // check later
358         // Andres Navarro
359         public void keyPressed(int keyCode) {
360             // Andres Navarro
361             if (current != null && current instanceof GameCanvas) {
362                 processGameCanvasKeyEvent((GameCanvas) current, keyCode, true);
363             } else {
364                 putInQueue(new KeyEvent(KeyEvent.KEY_PRESSED, keyCode));
365             }
366         }
367 
368         public void keyRepeated(int keyCode) {
369             putInQueue(new KeyEvent(KeyEvent.KEY_REPEATED, keyCode));
370         }
371 
372         public void keyReleased(int keyCode) {
373             // Andres Navarro
374             if (current != null && current instanceof GameCanvas) {
375                 processGameCanvasKeyEvent((GameCanvas) current, keyCode, false);
376             } else {
377                 putInQueue(new KeyEvent(KeyEvent.KEY_RELEASED, keyCode));
378             }
379         }
380 
381         public void pointerPressed(int x, int y) {
382             if (current != null) {
383             	putInQueue(new PointerEvent(PointerEvent.POINTER_PRESSED, x, y));
384             }
385         }
386 
387         public void pointerReleased(int x, int y) {
388             if (current != null) {
389             	putInQueue(new PointerEvent(PointerEvent.POINTER_RELEASED, x, y));
390             }
391         }
392 
393         public void pointerDragged(int x, int y) {
394 
395             if (current != null) {
396             	putInQueue(new PointerEvent(PointerEvent.POINTER_DRAGGED, x, y));
397             }
398         }
399 
400         public void paint(Graphics g) {
401         	// TODO consider removal of DisplayAccess::paint(..)
402             if (current != null) {
403                 try {
404                     current.paint(g);
405                 } catch (Throwable th) {
406                     th.printStackTrace();
407                 }
408                 g.translate(-g.getTranslateX(), -g.getTranslateY());
409             }
410         }
411 
412         public Displayable getCurrent() {
413             return getDisplay().getCurrent();
414         }
415 
416         public DisplayableUI getCurrentUI() {
417         	Displayable current = getCurrent();        	
418         	if (current == null) {
419         		return null;
420         	} else {
421         		return current.ui;
422         	}
423         }
424 
425         public boolean isFullScreenMode() {
426             Displayable current = getCurrent();
427 
428             if (current instanceof Canvas) {
429                 return ((Canvas) current).fullScreenMode;
430             } else {
431                 return false;
432             }
433         }
434 
435         public void serviceRepaints() {
436             getDisplay().serviceRepaints();
437         }
438 
439         public void setCurrent(Displayable d) {
440             getDisplay().setCurrent(d);
441         }
442 
443         public void sizeChanged(int width, int height) {
444             if (current != null) {
445                 current.sizeChanged(width, height);
446                 updateCommands();
447             }
448         }
449 
450         public void updateCommands() {
451             getDisplay().updateCommands();
452         }
453 
454         public void clean() {
455             if (current != null) {
456                 current.hideNotify();
457             }
458             eventDispatcher.cancel();
459 			timer.cancel();
460         }
461     }
462 
463     private class AlertTimeout implements Runnable {
464 
465         int time;
466 
467         AlertTimeout(int time) {
468             this.time = time;
469         }
470 
471         public void run() {
472             try {
473                 Thread.sleep(time);
474             } catch (InterruptedException ex) {
475                 ex.printStackTrace();
476             }
477 
478             Displayable d = current;
479             if (d != null && d instanceof Alert) {
480                 Alert alert = (Alert) d;
481                 if (alert.time != Alert.FOREVER) {
482                     alert.getCommandListener().commandAction((Command) alert.getCommands().get(0), alert);
483                 }
484             }
485         }
486     }
487 
488     private final Semaphore serviceRepaintSemaphore = new Semaphore(0);
489 
490     private final class PaintTask implements Runnable {
491 
492         private int x = -1, y = -1, width = -1, height = -1;
493 
494         PaintTask(int x, int y, int width, int height) {
495             this.x = x;
496             this.y = y;
497             this.width = width;
498             this.height = height;
499         }
500 
501         public void run() {
502             DeviceFactory.getDevice().getDeviceDisplay().repaint(x, y, width, height);
503         }
504 
505         /**
506          * Do a 2-D merge of the paint areas
507          * 
508          * @param task
509          */
510         public final void merge(PaintTask task) {
511             int xMax = x + width;
512             int yMax = y + height;
513 
514             this.x = Math.min(this.x, task.x);
515             xMax = Math.max(xMax, task.x + task.width);
516 
517             this.y = Math.min(this.y, task.y);
518             yMax = Math.max(yMax, task.y + task.height);
519 
520             this.width = xMax - x;
521             this.height = yMax - y;
522         }
523 
524     }
525 
526     /**
527      * TODO: User a priority queue instead The problem is that key events should
528      * propogate to the head of the queue, even if inserted at the tail. A
529      * priority queue is probably a more appropriate structure to use
530      * 
531      * @author radoshi
532      */
533     private final BlockingQueue eventQueue = new LinkedBlockingQueue();
534 
535     /**
536      * Management of all events including paints, keyboard events and serial
537      * runners
538      * 
539      * @author radoshi
540      * 
541      */
542     private final class EventDispatcher implements Runnable {
543 
544     	private Thread thread;
545     	
546         private volatile boolean cancelled = false;
547 
548         public void run() {
549 
550             while (!cancelled) {
551                 try {
552                     Runnable runnable = (Runnable) eventQueue.take();
553 
554                     if (runnable instanceof PaintTask) {
555 
556                         PaintTask paint = (PaintTask) runnable;
557 
558                         while (eventQueue.peek() != null && eventQueue.peek() instanceof PaintTask) {
559                             paint.merge((PaintTask) eventQueue.take());
560                         }
561 
562                     }
563                     runnable.run();
564 
565                 } catch (InterruptedException exception) {
566                     // nothing to do really, just keep retrying
567                 }
568             }
569         }
570 
571         /**
572          * Do not service any more events
573          */
574         public final void cancel() {
575             this.cancelled = true;
576             thread.interrupt();
577         }
578 
579     }
580 
581     private final Timer timer = new Timer();
582 
583     /**
584      * Wrap any runnable as a timertask so that when the timer gets fired, the
585      * runnable gets run
586      * 
587      * @author radoshi
588      * 
589      */
590     private final class RunnableWrapper extends TimerTask {
591 
592         private final Runnable runnable;
593 
594         RunnableWrapper(Runnable runnable) {
595             this.runnable = runnable;
596         }
597 
598         public void run() {
599             putInQueue(runnable);
600         }
601 
602     }
603 
604     Display() {
605 
606         accessor = new DisplayAccessor(this);
607 
608         startEventDispatcher();
609 
610         timer.scheduleAtFixedRate(new RunnableWrapper(new TickerPaintTask()), 0, Ticker.PAINT_TIMEOUT);
611 
612         timer.scheduleAtFixedRate(new RunnableWrapper(new GaugePaintTask()), 0, Ticker.PAINT_TIMEOUT);
613     }
614 
615     private final void startEventDispatcher() {
616     	eventDispatcher.thread = new Thread(eventDispatcher, EVENT_DISPATCHER_NAME);
617     	eventDispatcher.thread.setDaemon(true);
618     	eventDispatcher.thread.start();
619     }
620 
621     public void callSerially(Runnable runnable) {
622         putInQueue(runnable);
623     }
624 
625     public int numAlphaLevels() {
626         return DeviceFactory.getDevice().getDeviceDisplay().numAlphaLevels();
627     }
628 
629     public int numColors() {
630         return DeviceFactory.getDevice().getDeviceDisplay().numColors();
631     }
632 
633     public boolean flashBacklight(int duration) {
634         // TODO
635         return false;
636     }
637 
638     public static Display getDisplay(MIDlet m) {
639         Display result;
640 
641         if (MIDletBridge.getMIDletAccess(m).getDisplayAccess() == null) {
642             result = new Display();
643             MIDletBridge.getMIDletAccess(m).setDisplayAccess(result.accessor);
644         } else {
645             result = MIDletBridge.getMIDletAccess(m).getDisplayAccess().getDisplay();
646         }
647 
648         return result;
649     }
650 
651     public int getColor(int colorSpecifier) {
652         // TODO implement better
653         switch (colorSpecifier) {
654         case COLOR_BACKGROUND:
655         case COLOR_HIGHLIGHTED_FOREGROUND:
656         case COLOR_HIGHLIGHTED_BORDER:
657             return 0xFFFFFF;
658         default:
659             return 0x000000;
660         }
661     }
662 
663     public int getBorderStyle(boolean highlighted) {
664         // TODO implement better
665         return highlighted ? Graphics.DOTTED : Graphics.SOLID;
666     }
667 
668     public int getBestImageWidth(int imageType) {
669         // TODO implement
670         return 0;
671     }
672 
673     public int getBestImageHeight(int imageType) {
674 
675         // TODO implement
676         return 0;
677     }
678 
679     public Displayable getCurrent() {
680         return current;
681     }
682 
683     public boolean isColor() {
684         return DeviceFactory.getDevice().getDeviceDisplay().isColor();
685     }
686 
687     public void setCurrent(Displayable nextDisplayable) {
688         if (nextDisplayable != null) {
689         	putInQueue(new ShowHideNotifyEvent(ShowHideNotifyEvent.SHOW_NOTIFY, current, nextDisplayable));
690         }
691     }
692 
693     public void setCurrent(Alert alert, Displayable nextDisplayable) {
694         // TODO check if nextDisplayble is Alert
695     	// TODO change to putInQueue implementation
696         Alert.nextDisplayable = nextDisplayable;
697 
698         current = alert;
699 
700         current.showNotify(this);
701         updateCommands();
702         current.repaint();
703 
704         if (alert.getTimeout() != Alert.FOREVER) {
705             AlertTimeout at = new AlertTimeout(alert.getTimeout());
706             Thread t = new Thread(at);
707             t.start();
708         }
709     }
710 
711     public void setCurrentItem(Item item) {
712         // TODO implement
713     }
714 
715     public boolean vibrate(int duration) {
716         return DeviceFactory.getDevice().vibrate(duration);
717     }
718 
719     // Who call this?? (Andres Navarro)
720     void clearAlert() {
721         setCurrent(Alert.nextDisplayable);
722     }
723 
724     static int getGameAction(int keyCode) {
725         return DeviceFactory.getDevice().getInputMethod().getGameAction(keyCode);
726     }
727 
728     static int getKeyCode(int gameAction) {
729         return DeviceFactory.getDevice().getInputMethod().getKeyCode(gameAction);
730     }
731 
732     static String getKeyName(int keyCode) throws IllegalArgumentException {
733         return DeviceFactory.getDevice().getInputMethod().getKeyName(keyCode);
734     }
735 
736     boolean isShown(Displayable d) {
737         if (current == null || current != d) {
738             return false;
739         } else {
740             return true;
741         }
742     }
743 
744     void repaint(Displayable d, int x, int y, int width, int height) {
745         if (current == d) {
746             putInQueue(new PaintTask(x, y, width, height));
747         }
748     }
749 
750     void serviceRepaints() {
751         //
752         // If service repaints is being called from the event thread, then we
753         // just execute an immediate repaint and call it a day. If it is being
754         // called from another thread, then we setup a repaint barrier and wait
755         // for that barrier to execute
756         //
757         if (EVENT_DISPATCHER_NAME.equals(Thread.currentThread().getName())) {
758             DeviceFactory.getDevice().getDeviceDisplay().repaint(0, 0, current.getWidth(), current.getHeight());
759             return;
760         }
761 
762         // put in a repaint task and block until that task is completed
763         putInQueue(new Runnable() {
764 
765             public void run() {
766                 serviceRepaintSemaphore.release();
767             }
768 
769         });
770 
771         try {
772             serviceRepaintSemaphore.acquire();
773         } catch (InterruptedException exception) {
774             // nothing to do - fall out of service repaints
775             exception.printStackTrace();
776         }
777     }
778 
779     void setScrollDown(boolean state) {
780         DeviceFactory.getDevice().getDeviceDisplay().setScrollDown(state);
781     }
782 
783     void setScrollUp(boolean state) {
784         DeviceFactory.getDevice().getDeviceDisplay().setScrollUp(state);
785     }
786 
787     void updateCommands() {
788         if (current == null) {
789             CommandManager.getInstance().updateCommands(null);
790         } else {
791             CommandManager.getInstance().updateCommands(current.getCommands());
792         }
793         repaint(current, 0, 0, current.getWidth(), current.getHeight());
794     }
795 
796 }