/* * 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.lwuit; import com.sun.lwuit.animations.Animation; import com.sun.lwuit.animations.CommonTransitions; import com.sun.lwuit.animations.Transition; import com.sun.lwuit.events.ActionEvent; import com.sun.lwuit.events.ActionListener; import com.sun.lwuit.geom.Dimension; import com.sun.lwuit.impl.ImplementationFactory; import com.sun.lwuit.impl.LWUITImplementation; import com.sun.lwuit.util.EventDispatcher; import java.io.InputStream; 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 { private EventDispatcher errorHandler; /** * Unknown keyboard type is the default indicating the software should try * to detect the keyboard type if necessary */ public static final int KEYBOARD_TYPE_UNKNOWN = 0; /** * Numeric keypad keyboard type */ public static final int KEYBOARD_TYPE_NUMERIC = 1; /** * Full QWERTY keypad keyboard type, even if a numeric keyboard also exists */ public static final int KEYBOARD_TYPE_QWERTY = 2; /** * Touch device without a physical keyboard that should popup a keyboad */ public static final int KEYBOARD_TYPE_VIRTUAL = 3; /** * Half QWERTY which needs software assistance for completion */ public static final int KEYBOARD_TYPE_HALF_QWERTY = 4; private static final int POINTER_PRESSED = 1; private static final int POINTER_RELEASED = 2; private static final int POINTER_DRAGGED = 3; private static final int POINTER_HOVER = 8; private static final int POINTER_HOVER_RELEASED = 11; private static final int KEY_PRESSED = 4; private static final int KEY_RELEASED = 5; private static final int KEY_LONG_PRESSED = 6; private static final int SIZE_CHANGED = 7; private static final int HIDE_NOTIFY = 9; private static final int SHOW_NOTIFY = 10; /** * A pure touch device has no focus showing when the user is using the touch * interface. Selection only shows when the user actually touches the screen * or suddenly switches to using a keypad/trackball. This sort of interface * is common in Android devices */ private boolean pureTouch; private Graphics lwuitGraphics; /** * Indicates whether this is a touch device */ private boolean touchScreen; /** * Indicates whether the edt should sleep between each loop */ private boolean noSleep = false; /** * Indicates the maximum drawing speed of no more than 10 frames per second * by default (this can be increased or decreased) the advantage of limiting * framerate is to allow the CPU to perform other tasks besides drawing. * Notice that when no change is occurring on the screen no frame is drawn and * so a high/low FPS will have no effect then. */ private int framerateLock = 30; /** * 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 down 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(); static int transitionDelay = -1; private LWUITImplementation impl; private boolean lwuitRunning = false; /** * 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 occurring 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(); /** * Events to broadcast on the EDT */ private Vector inputEvents = new Vector(); private boolean longPointerCharged; private boolean pointerPressedAndNotReleased; private int pointerX, pointerY; private boolean keyRepeatCharged; private boolean longPressCharged; private long longKeyPressTime; private int longPressInterval = 800; private long nextKeyRepeatEvent; private int keyRepeatValue; private int keyRepeatInitialIntervalTime = 800; private int keyRepeatNextIntervalTime = 10; private boolean lastInteractionWasKeypad; private boolean processingSerialCalls; private int PATHLENGTH; private float[] dragPathX; private float[] dragPathY; private long[] dragPathTime; private int dragPathOffset = 0; private int dragPathLength = 0; /** * Allows a LWUIT application to minimize without forcing it to the front whenever * a new dialog is poped up */ private boolean allowMinimizing; /** * Private constructor to prevent instanciation */ private Display() { } Vector getAnimationQueue() { return animationQueue; } /** * This is the Display initialization method. * This method must be called before any Form is shown * * @param m the main running MIDlet */ public static void init(Object m) { if(INSTANCE.impl == null) { INSTANCE.lwuitRunning = true; INSTANCE.impl = ImplementationFactory.getInstance().createImplementation(); INSTANCE.impl.setDisplayLock(lock); INSTANCE.impl.init(m); INSTANCE.lwuitGraphics = new Graphics(INSTANCE.impl.getNativeGraphics()); INSTANCE.impl.setLWUITGraphics(INSTANCE.lwuitGraphics); // only enable but never disable the third softbutton if(INSTANCE.impl.isThirdSoftButton()) { INSTANCE.thirdSoftButton = true; } if(INSTANCE.impl.getSoftkeyCount() > 0) { Form.leftSK = INSTANCE.impl.getSoftkeyCode(0)[0]; if(INSTANCE.impl.getSoftkeyCount() > 1) { Form.rightSK = INSTANCE.impl.getSoftkeyCode(1)[0]; if(INSTANCE.impl.getSoftkeyCode(1).length > 1){ Form.rightSK2 = INSTANCE.impl.getSoftkeyCode(1)[1]; } } Form.backSK = INSTANCE.impl.getBackKeyCode(); Form.backspaceSK = INSTANCE.impl.getBackspaceKeyCode(); Form.clearSK = INSTANCE.impl.getClearKeyCode(); } int width = INSTANCE.getDisplayWidth(); int height = INSTANCE.getDisplayHeight(); int colors = INSTANCE.numColors(); INSTANCE.PATHLENGTH = INSTANCE.impl.getDragPathLength(); INSTANCE.dragPathX = new float[INSTANCE.PATHLENGTH]; INSTANCE.dragPathY = new float[INSTANCE.PATHLENGTH]; INSTANCE.dragPathTime = new long[INSTANCE.PATHLENGTH]; // 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.impl.isTouchDevice(); // initialize the LWUIT EDT which from now on will take all responsibility // for the event delivery. INSTANCE.edt = new Thread(new RunnableWrapper(null, 3), "EDT"); INSTANCE.edt.setPriority(Thread.NORM_PRIORITY + 1); INSTANCE.edt.start(); } } } /** * Closes down the EDT and LWUIT, under normal conditions this method is completely unnecessary * since exiting the application will shut down LWUIT. However, if the application is minimized * and the user wishes to free all resources without exiting the application then this method can be used. * Once this method is used LWUIT will no longer work and Display.init(Object) should be invoked * again for any further LWUIT call! * Notice that minimize (being a LWUIT method) MUST be invoked before invoking this method! */ public static void deinitialize() { INSTANCE.lwuitRunning = false; synchronized(lock) { lock.notifyAll(); } } /** * Return the Display instance * * @return the Display instance */ public static Display getInstance(){ return INSTANCE; } /** * This method allows us to manipulate the drag started detection logic. * If the pointer was dragged for more than this percentage of the display size it * is safe to assume that a drag is in progress. * * @return motion percentage */ public int getDragStartPercentage() { return getImplementation().getDragStartPercentage(); } /** * This method allows us to manipulate the drag started detection logic. * If the pointer was dragged for more than this percentage of the display size it * is safe to assume that a drag is in progress. * * @param dragStartPercentage percentage of the screen required to initiate drag */ public void setDragStartPercentage(int dragStartPercentage) { getImplementation().setDragStartPercentage(dragStartPercentage); } LWUITImplementation getImplementation() { return impl; } /** * Indicates the maximum frames the API will try to draw every second * by default 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 occurring 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) { framerateLock = 1000 / rate; } /** * Vibrates the device for the given length of time * * @param duration length of time to vibrate */ public void vibrate(int duration) { impl.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) { impl.flashBacklight(duration); } /** * 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 1000 / framerateLock; } /** * 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) { impl.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 */ public void callSerially(Runnable r){ 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 { // poll doneness to prevent potential race conditions lock.wait(50); } 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() { if(!isEdt()){ return; } while(!shouldEDTSleepNoFormAnimation()) { edtLoopImpl(); } while(animationQueue != null && animationQueue.size() > 0){ edtLoopImpl(); } } /** * Restores the menu in the given form */ private void restoreMenu(Form f) { if(f != null) { f.restoreMenu(); } } private void paintTransitionAnimation() { Animation ani = (Animation) animationQueue.elementAt(0); if (!ani.animate()) { animationQueue.removeElementAt(0); if (ani instanceof Transition) { Form source = (Form) ((Transition)ani).getSource(); restoreMenu(source); if (animationQueue.size() > 0) { ani = (Animation) animationQueue.elementAt(0); if (ani instanceof Transition) { ((Transition) ani).initTransition(); } }else{ Form f = (Form) ((Transition)ani).getDestination(); restoreMenu(f); if (source == null || source == impl.getCurrentForm() || source == getCurrent()) { setCurrentForm(f); } ((Transition) ani).cleanup(); } return; } } ani.paint(lwuitGraphics); impl.flushGraphics(); if(transitionDelay > 0) { // yield for a fraction, some devices don't "properly" implement // flush and so require the painting thread to get CPU too. try { synchronized(lock){ lock.wait(transitionDelay); } } catch (InterruptedException ex) { ex.printStackTrace(); } } } /** * This method represents the event thread for the UI library on which * all events are carried out. It differs from the MIDP event thread to * prevent blocking of actual input and drawing operations. This also * enables functionality such as "true" modal dialogs etc... */ void mainEDTLoop() { try { synchronized(lock){ // when there is no current form the EDT is useful only // for features such as call serially while(impl.getCurrentForm() == null) { if(shouldEDTSleep()) { lock.wait(); } // paint transition or intro animations and don't do anything else if such // animations are in progress... if(animationQueue != null && animationQueue.size() > 0) { paintTransitionAnimation(); continue; } processSerialCalls(); } } } catch(Throwable err) { err.printStackTrace(); if(!impl.handleEDTException(err)) { if(errorHandler != null) { errorHandler.fireActionEvent(new ActionEvent(err)); } else { Dialog.show("Error", "An internal application error occurred: " + err.toString(), "OK", null); } } } while(lwuitRunning) { try { // wait indefinetly but no more than the framerate if // there are no animations... If animations exist then // only wait for the framerate // Lock surrounds the should method to prevent serial calls from // getting "lost" synchronized(lock){ if(shouldEDTSleep()) { impl.edtIdle(true); lock.wait(); impl.edtIdle(false); } } edtLoopImpl(); } catch(Throwable err) { err.printStackTrace(); if(!impl.handleEDTException(err)) { if(errorHandler != null) { errorHandler.fireActionEvent(new ActionEvent(err)); } else { Dialog.show("Error", "An internal application error occurred: " + err.toString(), "OK", null); } } } } INSTANCE.impl = null; INSTANCE.lwuitGraphics = null; INSTANCE.edt = null; } long time; /** * Implementation of the event dispatch loop content */ void edtLoopImpl() { try { // transitions shouldn't be bound by framerate if(animationQueue == null || animationQueue.size() == 0) { // prevents us from waking up the EDT too much and // thus exhausting the systems resources. The + 1 // prevents us from ever waiting 0 milliseconds which // is the same as waiting with no time limit if(!noSleep){ synchronized(lock){ lock.wait(Math.max(1, framerateLock - (time))); } } } else { // paint transition or intro animations and don't do anything else if such // animations are in progress... paintTransitionAnimation(); return; } } catch(Exception ignor) { ignor.printStackTrace(); } long currentTime = System.currentTimeMillis(); while(inputEvents.size() > 0) { int[] i = (int[])inputEvents.elementAt(0); inputEvents.removeElementAt(0); handleEvent(i); } lwuitGraphics.setGraphics(impl.getNativeGraphics()); impl.paintDirty(); // draw the animations Form current = impl.getCurrentForm(); current.repaintAnimations(); // check key repeat events long t = System.currentTimeMillis(); if(keyRepeatCharged && nextKeyRepeatEvent <= t) { current.keyRepeated(keyRepeatValue); nextKeyRepeatEvent = t + keyRepeatNextIntervalTime; } if(longPressCharged && longPressInterval <= t - longKeyPressTime) { longPressCharged = false; current.longKeyPress(keyRepeatValue); } if(longPointerCharged && longPressInterval <= t - longKeyPressTime) { longPointerCharged = false; current.longPointerPress(pointerX, pointerY); } processSerialCalls(); time = System.currentTimeMillis() - currentTime; } boolean hasNoSerialCallsPending() { return pendingSerialCalls.size() == 0; } /** * Called by the underlying implementation to indicate that editing in the native * system has completed and changes should propogate into LWUIT * * @param c edited component * @param text new text for the component */ public void onEditingComplete(Component c, String text) { c.onEditComplete(text); c.fireActionEvent(); } /** * Used by the EDT to process all the calls submitted via call serially */ void processSerialCalls() { processingSerialCalls = true; 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); } synchronized(lock) { if(size == pendingSerialCalls.size()) { // this is faster pendingSerialCalls.removeAllElements(); } else { // this can occur if an element was added during the loop for(int iter = 0 ; iter < size ; iter++) { pendingSerialCalls.removeElementAt(0); } } } 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(); } } processingSerialCalls = false; } boolean isProcessingSerialCalls() { return processingSerialCalls; } void notifyDisplay(){ 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()) { // this class allows a runtime exception to propogate correctly out of the // internal thread RunnableWrapper w = new RunnableWrapper(r, 1); RunnableWrapper.pushToThreadPool(w); synchronized(lock) { try { // yeald the CPU for a very short time to let the invoke thread // get started lock.wait(2); } catch (InterruptedException ex) { ex.printStackTrace(); } } // loop over the EDT until the thread completes then return while(!w.isDone()) { edtLoopImpl(); } // 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. * * @return true if this device supports touch events */ 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. * * @param touchScreen false if this is not a touch screen device */ public void setTouchScreenDevice(boolean touchScreen) { this.touchScreen = touchScreen; } /** * Calling this method with noSleep=true will cause the edt to run without sleeping. * * @param noSleep causes the edt to stop the sleeping periods between 2 cycles */ public void setNoSleep(boolean noSleep){ this.noSleep = noSleep; } /** * Displays the given Form on the screen. * * @param newForm the Form to Display */ void setCurrent(final Form newForm, boolean reverse){ if(edt == null) { throw new IllegalStateException("Initialize must be invoked before setCurrent!"); } if(isVirtualKeyboardShowingSupported()) { setShowVirtualKeyboard(false); } if(editingText) { switch(showDuringEdit) { case SHOW_DURING_EDIT_ALLOW_DISCARD: break; case SHOW_DURING_EDIT_ALLOW_SAVE: impl.saveTextEditingState(); 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: impl.setCurrentForm(newForm); return; } } if(!isEdt()) { callSerially(new RunnableWrapper(newForm, null, reverse)); return; } Form current = impl.getCurrentForm(); if(current != null){ if(current.isInitialized()) { current.deinitializeImpl(); } } if(!newForm.isInitialized()) { newForm.initComponentImpl(); } if(newForm.getWidth() != getDisplayWidth() || newForm.getHeight() != getDisplayHeight()) { newForm.setShouldCalcPreferredSize(true); newForm.layoutContainer(); } synchronized(lock) { boolean transitionExists = false; if(animationQueue != null && animationQueue.size() > 0) { Object o = animationQueue.lastElement(); if(o instanceof Transition) { current = (Form)((Transition)o).getDestination(); impl.setCurrentForm(current); } } if(current != null) { // make sure the fold menu occurs as expected then set the current // to the correct parent! if(current instanceof Dialog && ((Dialog)current).isMenu()) { Transition t = current.getTransitionOutAnimator(); if(t != null) { // go back to the parent form first if(((Dialog)current).getPreviousForm() != null) { initTransition(t.copy(false), current, ((Dialog)current).getPreviousForm()); } } current = ((Dialog)current).getPreviousForm(); impl.setCurrentForm(current); } // prevent the transition from occurring 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) { transitionExists = initTransition(t.copy(reverse), current, newForm); } } if(current != null && !(current instanceof Dialog)) { Transition t = newForm.getTransitionInAnimator(); if(t != null) { transitionExists = initTransition(t.copy(reverse), current, newForm); } } } } } 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 boolean initTransition(Transition transition, Form source, Form dest) { try { dest.setVisible(true); transition.init(source, dest); animationQueue.addElement(transition); if (animationQueue.size() == 1) { transition.initTransition(); } } catch (Throwable e) { e.printStackTrace(); transition.cleanup(); animationQueue.removeElement(transition); return false; } return true; } void setCurrentForm(Form newForm){ boolean forceShow = false; Form current = impl.getCurrentForm(); if(current != null){ current.setVisible(false); } else { forceShow = true; } current = newForm; impl.setCurrentForm(current); current.setVisible(true); if(forceShow || !allowMinimizing) { impl.confirmControlView(); } int w = current.getWidth(); int h = current.getHeight(); if(isEdt() && ( w != impl.getDisplayWidth() || h != impl.getDisplayHeight())){ current.sizeChangedInternal(impl.getDisplayWidth(), impl.getDisplayHeight()); }else{ repaint(current); } lastKeyPressed = 0; previousKeyPressed = 0; newForm.onShowCompleted(); } /** * 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. * @deprecated this method is no longer supported use GameCanvasImplementation.setFlashGraphicsBug(f) instead */ public void setFlashGraphicsBug(boolean 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 transitionD -1 for no delay otherwise delay in milliseconds */ public void setTransitionYield(int transitionD) { transitionDelay = transitionD; } /** * 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 */ public void editString(Component cmp, int maxSize, int constraint, String text) { editingText = true; keyRepeatCharged = false; longPressCharged = false; lastKeyPressed = 0; previousKeyPressed = 0; impl.editString(cmp, maxSize, constraint, text); editingText = false; } /** * Minimizes the current application if minimization is supported by the platform (may fail). * Returns false if minimization failed. * * @return false if minimization failed true if it succeeded or seems to be successful */ public boolean minimizeApplication() { return getImplementation().minimizeApplication(); } /** * Indicates whether an application is minimized * * @return true if the application is minimized */ public boolean isMinimized() { return getImplementation().isMinimized(); } /** * Restore the minimized application if minimization is supported by the platform */ public void restoreMinimizedApplication() { getImplementation().restoreMinimizedApplication(); } private void addInputEvent(int[] ev) { synchronized(lock) { inputEvents.addElement(ev); lock.notify(); } } /** * Creates a pointer event with the following properties */ private int[] createPointerEvent(int[] x, int[] y, int eventType) { if(x.length == 1) { return new int[] {eventType, x[0], y[0]}; } int[] arr = new int[1 + x.length * 2]; arr[0] = eventType; int arrayOffset = 1; for(int iter = 0 ; iter < x.length ; iter++) { arr[arrayOffset] = x[iter]; arrayOffset++; arr[arrayOffset] = y[iter]; arrayOffset++; } return arr; } private int[] createKeyEvent(int keyCode, boolean pressed) { if(pressed) { return new int[] {KEY_PRESSED, keyCode}; } else { return new int[] {KEY_RELEASED, keyCode}; } } private int previousKeyPressed; private int lastKeyPressed; /** * Pushes a key press event with the given keycode into LWUIT * * @param keyCode keycode of the key event */ public void keyPressed(final int keyCode){ if(impl.getCurrentForm() == null){ return; } addInputEvent(createKeyEvent(keyCode, true)); lastInteractionWasKeypad = lastInteractionWasKeypad || (keyCode != Form.leftSK && keyCode != Form.clearSK && keyCode != Form.backSK); // this solves a Sony Ericsson bug where on slider open/close someone "brilliant" chose // to send a keyPress with a -43/-44 keycode... Without ever sending a key release! keyRepeatCharged = (keyCode >= 0 || getGameAction(keyCode) > 0) || keyCode == impl.getClearKeyCode(); longPressCharged = keyRepeatCharged; longKeyPressTime = System.currentTimeMillis(); keyRepeatValue = keyCode; nextKeyRepeatEvent = System.currentTimeMillis() + keyRepeatInitialIntervalTime; previousKeyPressed = lastKeyPressed; lastKeyPressed = keyCode; } /** * Pushes a key release event with the given keycode into LWUIT * * @param keyCode keycode of the key event */ public void keyReleased(final int keyCode){ keyRepeatCharged = false; longPressCharged = false; if(impl.getCurrentForm() == null){ return; } // this can happen when traversing from the native form to the current form // caused by a keypress // We need the previous key press for lwuit issue 108 which can occur when typing into // text field rapidly and pressing two buttons at once. Originally I had a patch // here specifically to the native edit but that patch doesn't work properly for // all native phone bugs (e.g. incoming phone call rejected and the key release is // sent to the java application). if(keyCode != lastKeyPressed) { if(keyCode != previousKeyPressed) { return; } else { previousKeyPressed = 0; } } else { lastKeyPressed = 0; } addInputEvent(createKeyEvent(keyCode, false)); } void keyRepeatedInternal(final int keyCode){ } /** * Pushes a pointer drag event with the given coordinates into LWUIT * * @param x the x position of the pointer * @param y the y position of the pointer */ public void pointerDragged(final int[] x, final int[] y){ if(impl.getCurrentForm() == null){ return; } longPointerCharged = false; addInputEvent(createPointerEvent(x, y, POINTER_DRAGGED)); } /** * Pushes a pointer hover event with the given coordinates into LWUIT * * @param x the x position of the pointer * @param y the y position of the pointer */ public void pointerHover(final int[] x, final int[] y){ if(impl.getCurrentForm() == null){ return; } addInputEvent(createPointerEvent(x, y, POINTER_HOVER)); } /** * Pushes a pointer hover release event with the given coordinates into LWUIT * * @param x the x position of the pointer * @param y the y position of the pointer */ public void pointerHoverReleased(final int[] x, final int[] y){ if(impl.getCurrentForm() == null){ return; } addInputEvent(createPointerEvent(x, y, POINTER_HOVER_RELEASED)); } /** * Pushes a pointer press event with the given coordinates into LWUIT * * @param x the x position of the pointer * @param y the y position of the pointer */ public void pointerPressed(final int[] x,final int[] y){ if(impl.getCurrentForm() == null){ return; } lastInteractionWasKeypad = false; longPointerCharged = true; pointerPressedAndNotReleased = true; longKeyPressTime = System.currentTimeMillis(); pointerX = x[0]; pointerY = y[0]; addInputEvent(createPointerEvent(x, y, POINTER_PRESSED)); } /** * Pushes a pointer release event with the given coordinates into LWUIT * * @param x the x position of the pointer * @param y the y position of the pointer */ public void pointerReleased(final int[] x, final int[] y){ longPointerCharged = false; pointerPressedAndNotReleased = false; if(impl.getCurrentForm() == null){ return; } addInputEvent(createPointerEvent(x, y, POINTER_RELEASED)); } /** * Notifies LWUIT of display size changes, this method is invoked by the implementation * class and is for internal use * * @param w the width of the drawing surface * @param h the height of the drawing surface */ public void sizeChanged(int w, int h){ Form current = impl.getCurrentForm(); if(current == null) { return; } if(w == current.getWidth() && h == current.getHeight()) { return; } addInputEvent(createSizeChangedEvent(w, h)); } private int[] createSizeChangedEvent(int w, int h) { return new int[] {SIZE_CHANGED, w, h}; } /** * Broadcasts hide notify into LWUIT, this method is invoked by the LWUIT implementation * to notify LWUIT of hideNotify events */ public void hideNotify(){ keyRepeatCharged = false; longPressCharged = false; longPointerCharged = false; pointerPressedAndNotReleased = false; addInputEvent(new int[]{HIDE_NOTIFY}); } /** * Broadcasts show notify into LWUIT, this method is invoked by the LWUIT implementation * to notify LWUIT of showNotify events */ public void showNotify(){ addInputEvent(new int[]{SHOW_NOTIFY}); } /** * Used by the flush functionality which doesn't care much about component * animations */ boolean shouldEDTSleepNoFormAnimation() { boolean b; synchronized(lock){ b = inputEvents.size() == 0 && hasNoSerialCallsPending() && (!keyRepeatCharged || !longPressCharged); } return b; } private void updateDragSpeedStatus(int[] ev) { //save dragging input to calculate the dragging speed later dragPathX[dragPathOffset] = pointerEvent(1, ev)[0]; dragPathY[dragPathOffset] = pointerEvent(2, ev)[0]; dragPathTime[dragPathOffset] = System.currentTimeMillis(); if (dragPathLength < PATHLENGTH) { dragPathLength++; } dragPathOffset++; if (dragPathOffset >= PATHLENGTH) { dragPathOffset = 0; } } /** * Invoked on the EDT to propagate the event */ private void handleEvent(int[] ev) { Form f = getCurrentUpcomingForm(true); switch(ev[0]) { case KEY_PRESSED: f.keyPressed(ev[1]); break; case KEY_RELEASED: f.keyReleased(ev[1]); break; case POINTER_PRESSED: dragPathLength = 0; f.pointerPressed(pointerEvent(1, ev), pointerEvent(2, ev)); break; case POINTER_RELEASED: f.pointerReleased(pointerEvent(1, ev), pointerEvent(2, ev)); break; case POINTER_DRAGGED: updateDragSpeedStatus(ev); f.pointerDragged(pointerEvent(1, ev), pointerEvent(2, ev)); break; case POINTER_HOVER: updateDragSpeedStatus(ev); f.pointerHover(pointerEvent(1, ev), pointerEvent(2, ev)); break; case POINTER_HOVER_RELEASED: f.pointerHoverReleased(pointerEvent(1, ev), pointerEvent(2, ev)); break; case SIZE_CHANGED: f.sizeChangedInternal(ev[1], ev[2]); break; case HIDE_NOTIFY: f.hideNotify(); break; case SHOW_NOTIFY: f.showNotify(); break; } } private int[] pointerEvent(int off, int[] event) { int[] peX = new int[event.length / 2]; int offset = 0; for(int iter = off ; iter < event.length ; iter+=2 ) { peX[offset] = event[iter]; offset++; } return peX; } /** * Returns true for a case where the EDT has nothing at all to do */ boolean shouldEDTSleep() { Form current = impl.getCurrentForm(); return (current == null || (!current.hasAnimations())) && (animationQueue == null || animationQueue.size() == 0) && inputEvents.size() == 0 && (!impl.hasPendingPaints()) && hasNoSerialCallsPending() && !keyRepeatCharged && !longPointerCharged; } /** * 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 impl.getVideoControl(player); } Form getCurrentInternal() { return impl.getCurrentForm(); } /** * 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() { return getCurrentUpcomingForm(false); } private Form getCurrentUpcomingForm(boolean includeMenus) { Form upcoming = null; // we are in the middle of a transition so we should extract the next form if(animationQueue != null) { int size = animationQueue.size(); for(int iter = 0 ; iter < size ; iter++) { Object o = animationQueue.elementAt(iter); if(o instanceof Transition) { upcoming = (Form)((Transition)o).getDestination(); } } } if(upcoming == null) { if(includeMenus){ Form f = impl.getCurrentForm(); if(f instanceof Dialog) { if(((Dialog)f).isDisposed()) { return getCurrent(); } } return f; }else{ 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(){ Form current = impl.getCurrentForm(); if(current != null && current instanceof Dialog) { if(((Dialog)current).isMenu() || ((Dialog)current).isDisposed()) { Form p = current.getPreviousForm(); if(p != null) { return p; } // we are in the middle of a transition so we should extract the next form if(animationQueue != null) { int size = animationQueue.size(); for(int iter = 0 ; iter < size ; iter++) { Object o = animationQueue.elementAt(iter); 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 impl.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 impl.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. * * @return true if this is light mode * @deprecated this method is no longer used, it was too unreliable */ 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. * * @param lightMode true to activate light mode * @deprecated this method is no longer used, it was too unreliable */ public void setLightMode(boolean lightMode) { this.lightMode = lightMode; } /** * Return the width of the display * * @return the width of the display */ public int getDisplayWidth(){ return impl.getDisplayWidth(); } /** * Return the height of the display * * @return the height of the display */ public int getDisplayHeight(){ return impl.getDisplayHeight(); } /** * Causes the given component to repaint, used internally by Form * * @param cmp the given component to repaint */ void repaint(final Animation cmp){ impl.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){ return impl.getGameAction(keyCode); } /** * 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){ return impl.getKeyCode(gameAction); } /** * Indicates whether the 3rd softbutton should be supported on this device * * @return true if a third softbutton should be used */ public boolean isThirdSoftButton() { return thirdSoftButton; } /** * Indicates whether the 3rd softbutton should be supported on this device * * @param thirdSoftButton true if a third softbutton should be used */ public void setThirdSoftButton(boolean thirdSoftButton) { this.thirdSoftButton = thirdSoftButton; } /** * Displays the virtual keyboard on devices that support manually poping up * the vitual keyboard * * @param show toggles the virtual keyboards visibility */ public void setShowVirtualKeyboard(boolean show) { impl.setShowVirtualKeyboard(show); } /** * Indicates if the virtual keyboard is currently showing or not * * @return true if the virtual keyboard is showing */ public boolean isVirtualKeyboardShowing() { return impl.isVirtualKeyboardShowing(); } /** * Indicates whether showing a virtual keyboard programmatically is supported * * @return false by default */ public boolean isVirtualKeyboardShowingSupported() { return impl.isVirtualKeyboardShowingSupported(); } /** * Returns the type of the input device one of: * KEYBOARD_TYPE_UNKNOWN, KEYBOARD_TYPE_NUMERIC, KEYBOARD_TYPE_QWERTY, * KEYBOARD_TYPE_VIRTUAL, KEYBOARD_TYPE_HALF_QWERTY * * @return KEYBOARD_TYPE_UNKNOWN */ public int getKeyboardType() { return impl.getKeyboardType(); } /** * Indicates whether the device supports native in place editing in which case * lightweight input logic shouldn't be used for input. * * @return false by default */ public boolean isNativeInputSupported() { return false; } /** * Indicates whether the device supports multi-touch events, this is only * relevant when touch events are supported * * @return false by default */ public boolean isMultiTouch() { return impl.isMultiTouch(); } /** * Indicates whether the device has a double layer screen thus allowing two * stages to touch events: click and hover. This is true for devices such * as the storm but can also be true for a PC with a mouse pointer floating * on top. * <p>A click touch screen will also send pointer hover events to the underlying * software and will only send the standard pointer events on click. * * @return false by default */ public boolean isClickTouchScreen() { return impl.isClickTouchScreen(); } /** * This method returns the dragging speed based on the latest dragged * events * @param yAxis indicates what axis speed is required * @return the dragging speed */ public float getDragSpeed(boolean yAxis){ float speed; if(yAxis){ speed = impl.getDragSpeed(dragPathY, dragPathTime, dragPathOffset, dragPathLength); }else{ speed = impl.getDragSpeed(dragPathX, dragPathTime, dragPathOffset, dragPathLength); } return speed; } /** * Indicates whether LWUIT should consider the bidi RTL algorithm * when drawing text or navigating with the text field cursor. * * @return true if the bidi algorithm should be considered */ public boolean isBidiAlgorithm() { return impl.isBidiAlgorithm(); } /** * Indicates whether LWUIT should consider the bidi RTL algorithm * when drawing text or navigating with the text field cursor. * * @param activate set to true to activate the bidi algorithm, false to * disable it */ public void setBidiAlgorithm(boolean activate) { impl.setBidiAlgorithm(activate); } /** * Converts the given string from logical bidi layout to visual bidi layout so * it can be rendered properly on the screen. This method is only necessary * for devices/platforms that don't have "built in" bidi support such as * Sony Ericsson devices. * See <a href="http://www.w3.org/International/articles/inline-bidi-markup/#visual">this</a> * for more on visual vs. logical ordering. * * * @param s a "logical" string with RTL characters * @return a "visual" renderable string */ public String convertBidiLogicalToVisual(String s) { return impl.convertBidiLogicalToVisual(s); } /** * Returns the index of the given char within the source string, the actual * index isn't necessarily the same when bidi is involved * See <a href="http://www.w3.org/International/articles/inline-bidi-markup/#visual">this</a> * for more on visual vs. logical ordering. * * @param source the string in which we are looking for the position * @param index the "logical" location of the cursor * @return the "visual" location of the cursor */ public int getCharLocation(String source, int index) { return impl.getCharLocation(source, index); } /** * Returns true if the given character is an RTL character * * @param c character to test * @return true if the charcter is an RTL character */ public boolean isRTL(char c) { return impl.isRTL(c); } /** * This method is essentially equivalent to cls.getResourceAsStream(String) * however some platforms might define unique ways in which to load resources * within the implementation. * * @param cls class to load the resource from * @param resource relative/absolute URL based on the Java convention * @return input stream for the resource or null if not found */ public InputStream getResourceAsStream(Class cls, String resource) { return impl.getResourceAsStream(cls, resource); } /** * An error handler will receive an action event with the source exception from the EDT * once an error handler is installed the default LWUIT error dialog will no longer appear * * @param e listener receiving the errors */ public void addEdtErrorHandler(ActionListener e) { if(errorHandler == null) { errorHandler = new EventDispatcher(); } errorHandler.addListener(e); } /** * An error handler will receive an action event with the source exception from the EDT * once an error handler is installed the default LWUIT error dialog will no longer appear * * @param e listener receiving the errors */ public void removeEdtErrorHandler(ActionListener e) { if(errorHandler != null) { errorHandler.removeListener(e); Vector v = errorHandler.getListenerVector(); if(v == null || v.size() == 0) { errorHandler = null; } } } /** * Allows a LWUIT application to minimize without forcing it to the front whenever * a new dialog is poped up * * @param allowMinimizing value */ public void setAllowMinimizing(boolean allowMinimizing) { this.allowMinimizing = allowMinimizing; } /** * Allows a LWUIT application to minimize without forcing it to the front whenever * a new dialog is poped up * * @return allowMinimizing value */ public boolean isAllowMinimizing() { return allowMinimizing; } /** * This is an internal state flag relevant only for pureTouch mode (otherwise it * will always be false). A pureTouch mode is stopped if a user switches to using * the trackball/navigation pad and this flag essentially toggles between those two modes. * * @return the shouldRenderSelection */ boolean shouldRenderSelection() { return !pureTouch || pointerPressedAndNotReleased || lastInteractionWasKeypad; } /** * A pure touch device has no focus showing when the user is using the touch * interface. Selection only shows when the user actually touches the screen * or suddenly switches to using a keypad/trackball. This sort of interface * is common in Android devices * * @return the pureTouch flag */ public boolean isPureTouch() { return pureTouch; } /** * A pure touch device has no focus showing when the user is using the touch * interface. Selection only shows when the user actually touches the screen * or suddenly switches to using a keypad/trackball. This sort of interface * is common in Android devices * * @param pureTouch the value for pureTouch */ public void setPureTouch(boolean pureTouch) { this.pureTouch = pureTouch; } }