/*
* Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.dtv.lwuit;
import com.sun.dtv.lwuit.animations.Animation;
import com.sun.dtv.lwuit.animations.CommonTransitions;
import com.sun.dtv.lwuit.animations.Transition;
import com.sun.dtv.lwuit.geom.Dimension;
import java.util.Enumeration;
import java.util.Vector;
/**
* Central class for the API that manages rendering/events and is used to place top
* level components ({@link Form}) on the "display". Before any Form is shown the Developer must
* invoke Display.init(Object m) in order to register the current MIDlet.
* <p>This class handles the main thread for the toolkit referenced here on as the EDT
* (Event Dispatch Thread) similar to the Swing EDT. This thread encapsulates the platform
* specific event delivery and painting semantics and enables threading features such as
* animations etc...
* <p>The EDT should not be blocked since paint operations and events would also be blocked
* in much the same way as they would be in other platforms. In order to serialize calls back
* into the EDT use the methods {@link Display#callSerially} & {@link Display#callSeriallyAndWait}.
* <p>Notice that all LWUIT calls occur on the EDT (events, painting, animations etc...), LWUIT
* should normally be manipulated on the EDT as well (hence the {@link Display#callSerially} &
* {@link Display#callSeriallyAndWait} methods). Theoretically it should be possible to manipulate
* some LWUIT features from other threads but this can't be guaranteed to work for all use cases.
*
* @author Chen Fishbein, Shai Almog
*/
public final class Display {
/**
* Indicates whether this is a touch device
*/
private boolean touchScreen;
/**
* Light mode allows the UI to adapt and show less visual effects/lighter versions
* of these visual effects to work properly on low end devices.
*/
private boolean lightMode;
/**
* Game action for fire
*/
public static final int GAME_FIRE = 8;
/**
* Game action for left key
*/
public static final int GAME_LEFT = 2;
/**
* Game action for right key
*/
public static final int GAME_RIGHT = 5;
/**
* Game action for UP key
*/
public static final int GAME_UP = 1;
/**
* Game action for left key
*/
public static final int GAME_DOWN = 6;
/**
* An attribute that encapsulates '#' int value.
*/
public static final int KEY_POUND = '#';
private static final Display INSTANCE = new Display();
/**
* On some devices getKeyCode returns numeric values for game actions,
* this breaks the code since we filter these values. We pick unused
* negative values for game keys and assign them to game keys for getKeyCode.
*/
private static int[] portableKeyCodes;
private static int[] portableKeyCodeValues;
private Form current;
private VirtualImplementation implementation = new VirtualImplementation();
/**
* Contains the call serially pending elements
*/
private Vector pendingSerialCalls = new Vector();
/**
* This is the instance of the EDT used internally to indicate whether
* we are executing on the EDT or some arbitrary thread
*/
private Thread edt;
/**
* Contains animations that must be played in full by the EDT before anything further
* may be processed. This is useful for transitions/intro's etc... that animate without
* user interaction.
*/
private Vector animationQueue;
/**
* Indicates whether the 3rd softbutton should be supported on this device
*/
private boolean thirdSoftButton = false;
private boolean editingText;
/**
* Ignore all calls to show occuring during edit, they are discarded immediately
*/
public static final int SHOW_DURING_EDIT_IGNORE = 1;
/**
* If show is called while editing text in the native text box an exception is thrown
*/
public static final int SHOW_DURING_EDIT_EXCEPTION = 2;
/**
* Allow show to occur during edit and discard all user input at this moment
*/
public static final int SHOW_DURING_EDIT_ALLOW_DISCARD = 3;
/**
* Allow show to occur during edit and save all user input at this moment
*/
public static final int SHOW_DURING_EDIT_ALLOW_SAVE = 4;
/**
* Show will update the current form to which the OK button of the text box
* will return
*/
public static final int SHOW_DURING_EDIT_SET_AS_NEXT = 5;
private int showDuringEdit;
static final Object lock = new Object();
/**
* Private constructor to prevent instanciation
*/
private Display() {
}
Vector getAnimationQueue() {
return animationQueue;
}
/**
* This is the Display initalization method.
* This method must be called before any Form is shown
*
* @param m the main running MIDlet
*/
public static void init(Object m) {
INSTANCE.implementation.init(m);
int width = INSTANCE.getDisplayWidth();
int height = INSTANCE.getDisplayHeight();
int colors = INSTANCE.numColors();
// if the resolution is very high and the amount of memory is very low while the device
// itself has many colors (requiring 32 bits per pixel) then we should concerve memory
// by activating light mode.
INSTANCE.lightMode = colors > 65536 && width * height * 30 > Runtime.getRuntime().totalMemory();
// this can happen on some cases where an application was restarted etc...
// generally its probably a bug but we can let it slide...
if(INSTANCE.edt == null) {
INSTANCE.touchScreen = INSTANCE.implementation.hasPointerEvents();
// initialize the JWT EDT which from now on will take all responsibility
// for the event delivery.
INSTANCE.edt = new Thread(INSTANCE.implementation, "EDT");
INSTANCE.edt.start();
}
}
/**
* Return the Display instance
*
* @return the Display instance
*/
public static Display getInstance(){
return INSTANCE;
}
/**
* Indicates the maximum frames the API will try to draw every second
* by defult this is set to 10. The advantage of limiting
* framerate is to allow the CPU to perform other tasks besides drawing.
* Notice that when no change is occuring on the screen no frame is drawn and
* so a high/low FPS will have no effect then.
* 10FPS would be very reasonable for a business application.
*
* @param rate the frame rate
*/
public void setFramerate(int rate) {
implementation.setFramerate(rate);
}
/**
* Vibrates the device for the given length of time
*
* @param duration length of time to vibrate
*/
public void vibrate(int duration) {
implementation.vibrate(duration);
}
/**
* Flash the backlight of the device for the given length of time
*
* @param duration length of time to flash the backlight
*/
public void flashBacklight(int duration) {
implementation.flashBacklight(duration);
}
void blockEvents(boolean block){
implementation.blockEvents(block);
}
/**
* Invoking the show() method of a form/dialog while the user is editing
* text in the native text box can have several behaviors: SHOW_DURING_EDIT_IGNORE,
* SHOW_DURING_EDIT_EXCEPTION, SHOW_DURING_EDIT_ALLOW_DISCARD,
* SHOW_DURING_EDIT_ALLOW_SAVE, SHOW_DURING_EDIT_SET_AS_NEXT
*
* @param showDuringEdit one of the following: SHOW_DURING_EDIT_IGNORE,
* SHOW_DURING_EDIT_EXCEPTION, SHOW_DURING_EDIT_ALLOW_DISCARD,
* SHOW_DURING_EDIT_ALLOW_SAVE, SHOW_DURING_EDIT_SET_AS_NEXT
*/
public void setShowDuringEditBehavior(int showDuringEdit) {
this.showDuringEdit = showDuringEdit;
}
/**
* Returns the status of the show during edit flag
*
* @return one of the following: SHOW_DURING_EDIT_IGNORE,
* SHOW_DURING_EDIT_EXCEPTION, SHOW_DURING_EDIT_ALLOW_DISCARD,
* SHOW_DURING_EDIT_ALLOW_SAVE, SHOW_DURING_EDIT_SET_AS_NEXT
*/
public int getShowDuringEditBehavior() {
return showDuringEdit;
}
/**
* Indicates the maximum frames the API will try to draw every second
*
* @return the frame rate
*/
public int getFrameRate() {
return implementation.getFrameRate();
}
/**
* Returns true if we are currently in the event dispatch thread.
* This is useful for generic code that can be used both with the
* EDT and outside of it.
*
* @return true if we are currently in the event dispatch thread;
* otherwise false
*/
public boolean isEdt() {
return edt == Thread.currentThread();
}
/**
* Plays sound for the dialog
*/
void playDialogSound(final int type) {
implementation.playDialogSound(type);
}
/**
* Causes the runnable to be invoked on the event dispatch thread. This method
* returns immediately and will not wait for the serial call to occur
*
* @param r runnable (NOT A THREAD!) that will be invoked on the EDT serial to
* the paint and key handling events
* @throws IllegalStateException if this method is invoked on the event dispatch thread (e.g. during
* paint or event handling).
*/
public void callSerially(Runnable r){
if(isEdt()) {
throw new IllegalStateException("Call serially must never be invoked from the EDT");
}
synchronized(lock) {
pendingSerialCalls.addElement(r);
lock.notify();
}
}
/**
* Identical to callSerially with the added benefit of waiting for the Runnable method to complete.
*
* @param r runnable (NOT A THREAD!) that will be invoked on the EDT serial to
* the paint and key handling events
* @throws IllegalStateException if this method is invoked on the event dispatch thread (e.g. during
* paint or event handling).
*/
public void callSeriallyAndWait(Runnable r){
RunnableWrapper c = new RunnableWrapper(r, 0);
callSerially(c);
synchronized(lock) {
while(!c.isDone()) {
try {
lock.wait();
} catch(InterruptedException err) {}
}
}
}
/**
* Allows us to "flush" the edt to allow any pending transitions and input to go
* by before continuing with our other tasks.
*/
void flushEdt() {
while(!implementation.shouldEDTSleepNoFormAnimation()) {
implementation.edtLoopImpl();
}
}
boolean hasNoSerialCallsPending() {
return pendingSerialCalls.size() == 0;
}
/**
* Used by the EDT to process all the calls submitted via call serially
*/
void processSerialCalls() {
int size = pendingSerialCalls.size();
if(size > 0) {
Runnable[] array = new Runnable[size];
// copy all elements to an array and remove them otherwise invokeAndBlock from
// within a callSerially() can cause an infinite loop...
for(int iter = 0 ; iter < size ; iter++) {
array[iter] = (Runnable)pendingSerialCalls.elementAt(iter);
}
pendingSerialCalls.removeAllElements();
for(int iter = 0 ; iter < size ; iter++) {
array[iter].run();
}
// after finishing an event cycle there might be serial calls waiting
// to return.
synchronized(lock){
lock.notify();
}
}
}
/**
* Invokes runnable and blocks the current thread, if the current thread is the
* edt it will still be blocked however a separate thread would be launched
* to perform the duties of the EDT while it is blocked. Once blocking is finished
* the EDT would be restored to its original position. This is very similar to the
* "foxtrot" Swing toolkit and allows coding "simpler" logic that requires blocking
* code in the middle of event sensitive areas.
*
* @param r runnable (NOT A THREAD!) that will be invoked synchroniously by this method
*/
public void invokeAndBlock(Runnable r){
if(isEdt()) {
synchronized(lock) {
// this class allows a runtime exception to propogate correctly out of the
// internal thread
RunnableWrapper w = new RunnableWrapper(r, 1);
Thread t = new Thread(w);
t.start();
// loop over the EDT until the thread completes then return
while(t.isAlive()) {
try {
if(implementation.shouldEDTSleep()) {
lock.wait(50);
} else {
implementation.edtLoopImpl();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
// if the thread thew an exception we need to throw it onwards
if(w.getErr() != null) {
throw w.getErr();
}
}
} else {
r.run();
}
}
/**
* Indicates if this is a touch screen device that will return pen events,
* defaults to true if the device has pen events but can be overriden by
* the developer.
*/
public boolean isTouchScreenDevice() {
return touchScreen;
}
/**
* Indicates if this is a touch screen device that will return pen events,
* defaults to true if the device has pen events but can be overriden by
* the developer.
*/
public void setTouchScreenDevice(boolean touchScreen) {
this.touchScreen = touchScreen;
}
/**
* Displays the given Form on the screen.
*
* @param newForm the Form to Display
*/
void setCurrent(final Form newForm){
if(edt == null) {
throw new IllegalStateException("Initialize must be invoked before setCurrent!");
}
if(editingText) {
switch(showDuringEdit) {
case SHOW_DURING_EDIT_ALLOW_DISCARD:
break;
case SHOW_DURING_EDIT_ALLOW_SAVE:
implementation.saveTextBox();
break;
case SHOW_DURING_EDIT_EXCEPTION:
throw new IllegalStateException("Show during edit");
case SHOW_DURING_EDIT_IGNORE:
return;
case SHOW_DURING_EDIT_SET_AS_NEXT:
current = newForm;
return;
}
}
if(!isEdt()) {
callSerially(new RunnableWrapper(newForm, null));
return;
}
//comentar pra ver o que acontece,,,
if(current != null){
current.hidePopups();
if(current.isInitialized()) {
current.deinitializeImpl();
}
}
if(!newForm.isInitialized()) {
newForm.initComponentImpl();
}
//fim
newForm.setShouldCalcPreferredSize(true);
newForm.layoutContainer();
synchronized(lock) {
boolean transitionExists = false;
Form current = this.current;
if(animationQueue != null && animationQueue.size() > 0) {
Object o = animationQueue.lastElement();
if(o instanceof Transition) {
current = (Form)((Transition)o).getDestination();
}
}
// make sure the fold menu occurs as expected then set the current
// to the correct parent!
if(current != null && current instanceof Dialog && ((Dialog)current).isMenu()) {
Transition t = current.getTransitionOutAnimator();
if(t != null) {
// go back to the parent form first
initTransition(t.copy(), current, ((Dialog)current).getPreviousForm());
}
current = ((Dialog)current).getPreviousForm();
}
// prevent the transition from occuring from a form into itself
if(newForm != current) {
if((current != null && current.getTransitionOutAnimator() != null) || newForm.getTransitionInAnimator() != null) {
if(animationQueue == null) {
animationQueue = new Vector();
}
// prevent form transitions from breaking our dialog based
// transitions which are a bit sensitive
if(current != null && (!(newForm instanceof Dialog))) {
Transition t = current.getTransitionOutAnimator();
if(current != null && t != null) {
initTransition(t.copy(), current, newForm);
transitionExists = true;
}
}
if(current != null && !(current instanceof Dialog)) {
Transition t = newForm.getTransitionInAnimator();
if(t != null) {
initTransition(t.copy(), current, newForm);
transitionExists = true;
}
}
}
}
lock.notify();
if(!transitionExists) {
if(animationQueue == null || animationQueue.size() == 0) {
setCurrentForm(newForm);
} else {
// we need to add an empty transition to "serialize" this
// screen change...
Transition t = CommonTransitions.createEmpty();
initTransition(t, current, newForm);
}
}
}
}
/**
* Initialize the transition and add it to the queue
*/
private void initTransition(Transition transition, Form source, Form dest) {
dest.setVisible(true);
transition.init(source, dest);
animationQueue.addElement(transition);
if(animationQueue.size() == 1) {
transition.initTransition();
}
}
void setCurrentForm(Form newForm){
if(this.current != null){
this.current.setVisible(false);
}
this.current = newForm;
this.current.setVisible(true);
//implementation.confirmControlsView();
if(isEdt() && (this.current.getWidth() != implementation.getDisplayWidth() ||
this.current.getHeight() != implementation.getDisplayHeight())){
this.current.setSize(new Dimension(implementation.getDisplayWidth(),implementation.getDisplayHeight()));
this.current.setShouldCalcPreferredSize(true);
this.current.layoutContainer();
}
repaint(current);
}
/**
* Indicate to the implementation whether the flush graphics bug exists on this
* device. By default the flushGraphics bug is set to "true" and only disabled
* on handsets known 100% to be safe
*
* @param flushGraphicsBug true if the bug exists on this device (the safe choice)
* false for slightly higher performance.
*/
public void setFlashGraphicsBug(boolean flushGraphicsBug) {
implementation.setFlashGraphicsBug(flushGraphicsBug);
}
/**
* Indicates whether a delay should exist between calls to flush graphics during
* transition. In some devices flushGraphics is asynchronious causing it to be
* very slow with our background thread. The solution is to add a short wait allowing
* the implementation time to paint the screen. This value is set automatically by default
* but can be overriden for some devices.
*
* @param transitionDelay -1 for no delay otherwise delay in milliseconds
*/
public void setTransitionYield(int transitionDelay) {
implementation.setTransitionYield(transitionDelay);
}
/**
* Encapsulates the editing code which is specific to the platform, some platforms
* would allow "in place editing" MIDP does not.
*
* @param cmp the {@link TextArea} component
*/
void editString(Component cmp, int maxSize, int constraint, String text) {
editingText = true;
implementation.editString(cmp, maxSize, constraint, text);
editingText = false;
}
/**
* Returns the video control for the media player
*
* @param player the media player
* @return the video control for the media player
*/
Object getVideoControl(Object player) {
return implementation.getVideoControl(player);
}
Form getCurrentInternal() {
return current;
}
/**
* Same as getCurrent with the added exception of looking into the future
* transitions and returning the last current in the transition (the upcoming
* value for current)
*
* @return the form currently displayed on the screen or null if no form is
* currently displayed
*/
Form getCurrentUpcoming() {
Form upcoming = null;
// we are in the middle of a transition so we should extract the next form
if(animationQueue != null) {
Enumeration e = animationQueue.elements();
while(e.hasMoreElements()) {
Object o = e.nextElement();
if(o instanceof Transition) {
upcoming = (Form)((Transition)o).getDestination();
}
}
}
if(upcoming == null) {
return getCurrent();
}
return upcoming;
}
/**
* Return the form currently displayed on the screen or null if no form is
* currently displayed.
*
* @return the form currently displayed on the screen or null if no form is
* currently displayed
*/
public Form getCurrent(){
if(current != null && current instanceof Dialog && ((Dialog)current).isMenu()) {
Form p = current.getPreviousForm();
if(p != null) {
return p;
}
// we are in the middle of a transition so we should extract the next form
Enumeration e = animationQueue.elements();
while(e.hasMoreElements()) {
Object o = e.nextElement();
if(o instanceof Transition) {
return (Form)((Transition)o).getDestination();
}
}
}
return current;
}
/**
* Return the number of alpha levels supported by the implementation.
*
* @return the number of alpha levels supported by the implementation
*/
public int numAlphaLevels(){
return implementation.numAlphaLevels();
}
/**
* Returns the number of colors applicable on the device, note that the API
* does not support gray scale devices.
*
* @return the number of colors applicable on the device
*/
public int numColors() {
return implementation.numColors();
}
/**
* Light mode allows the UI to adapt and show less visual effects/lighter versions
* of these visual effects to work properly on low end devices.
*/
public boolean isLightMode() {
return lightMode;
}
/**
* Light mode allows the UI to adapt and show less visual effects/lighter versions
* of these visual effects to work properly on low end devices.
*/
public void setLightMode(boolean lightMode) {
this.lightMode = lightMode;
}
/**
* Return the width of the display
*
* @return the width of the display
*/
public int getDisplayWidth(){
return implementation.getDisplayWidth();
}
/**
* Return the height of the display
*
* @return the height of the display
*/
public int getDisplayHeight(){
return implementation.getDisplayHeight();
}
/**
* Causes the given component to repaint, used internally by Form
*
* @param cmp the given component to repaint
*/
void repaint(final Animation cmp){
implementation.repaint(cmp);
}
/**
* Returns the game action code matching the given key combination
*
* @param keyCode key code received from the event
* @return game action matching this keycode
*/
public int getGameAction(int keyCode){
try {
// prevent game actions from being returned by numeric keypad thus screwing up
// keypad based navigation and text input
if(keyCode >= '0' && keyCode <= '9') {
return 0;
}
if(portableKeyCodes != null) {
for(int iter = 0 ; iter < portableKeyCodeValues.length ; iter++) {
if(portableKeyCodeValues[iter] == keyCode) {
return portableKeyCodes[iter];
}
}
}
return implementation.getGameAction(keyCode);
} catch(IllegalArgumentException err) {
// this is a stupid MIDP requirement some implementations throw this
// exception for some keys
return 0;
}
}
/**
* Returns the keycode matching the given game action constant (the opposite of getGameAction).
* On some devices getKeyCode returns numeric keypad values for game actions,
* this breaks the code since we filter these values (to prevent navigation on '2').
* We pick unused negative values for game keys and assign them to game keys for
* getKeyCode so they will work with getGameAction.
*
* @param gameAction game action constant from this class
* @return keycode matching this constant
* @deprecated this method doesn't work properly across device and is mocked up here
* mostly for the case of unit testing. Do not use it for anything other than that! Do
* not rely on getKeyCode(GAME_*) == keyCodeFromKeyEvent, this will never actually happen!
*/
public int getKeyCode(int gameAction){
if(portableKeyCodes == null) {
portableKeyCodes = new int[] {GAME_DOWN, GAME_LEFT, GAME_RIGHT, GAME_UP, GAME_FIRE};
portableKeyCodeValues = new int[5];
int currentValue = -500;
int offset = 0;
while(offset < portableKeyCodeValues.length) {
currentValue--;
try {
if(implementation.getGameAction(currentValue) != 0) {
continue;
}
} catch(IllegalArgumentException ignor) {
// this is good, the game key is unassigned
}
portableKeyCodeValues[offset] = currentValue;
offset++;
}
}
for(int iter = 0 ; iter < portableKeyCodes.length ; iter++) {
if(portableKeyCodes[iter] == gameAction) {
return portableKeyCodeValues[iter];
}
}
return 0;
}
/**
* Allows overriding the softkeys initialized by the software to a different value.
* This method MUST be invoked after init() has completed.
* <p>In order to maintain the default value 0 can be passed as a value for a softkey
* thus resulting in no effect e.g. setSoftkeyCodes(0, 0, 0, -8); will only affect the back key.
* @param left the left softkey code
* @param right the right softkey code
* @param clear the clear softkey code
* @param back the back softkey code
*/
public void setSoftkeyCodes(int left, int right, int clear, int back) {
if(left != 0) {
Form.leftSK = left;
}
if(right != 0) {
Form.rightSK = right;
}
if(clear != 0) {
Form.clearSK = clear;
}
if(back != 0) {
Form.backSK = back;
}
}
/**
* Indicates whether the 3rd softbutton should be supported on this device
*/
public boolean isThirdSoftButton() {
return thirdSoftButton;
}
/**
* Indicates whether the 3rd softbutton should be supported on this device
*/
public void setThirdSoftButton(boolean thirdSoftButton) {
this.thirdSoftButton = thirdSoftButton;
}
VirtualImplementation getVirtualImplementation() {
return implementation;
}
}