package org.j4me.ui; import java.util.*; import javax.microedition.lcdui.*; import org.j4me.logging.*; /** * The <code>DeviceScreen</code> class is a base class for any screen that needs complete * control over how it is painted. It is based on and similar to the MIDP * <code>Canvas</code> class. * <p> * This class removes the following methods from the MIDP <code>Canvas</code> class: * <ul> * <li><code>isDoubleBuffered</code> - This class is always double buffered. * <li><code>hasPointerEvents</code> and <code>hasPointerMotionEvents</code> - There is * no use for this method. Implement the pointer event methods and * if the device has no pointer they will be ignored. * <li> - Same reason as <code>hasPointerEvents</code>. * <li><code>hasRepeatEvents</code> - This class always has key repeat events. * <li><code>getKeyName</code> - The application should define the names to make them * consistent across all devices. * <li><code>getKeyCode</code> - The key code is passed into the key event methods. * <li><code>getGameAction</code> - The game action is passed into the key event methods. * <li><code>sizeChanged</code> - This method is notoriously buggy and applications * should use <code>getWidth</code> and <code>getHeight</code> instead. * <li><code>getTicker</code> and <code>setTicker</code> - The ticker functionality has * not been implemented. * <li><code>addCommand</code>, <code>removeCommand</code>, and <code>setCommandListener</code> - * Menu options have been replaced with something that works across all * MIDP 2.0 devices. There are left and right menu options. See * <code>setMenuText</code> for details. * </ul> * * @see javax.microedition.lcdui.Canvas */ public abstract class DeviceScreen { /**custom for SaferMobile **/ public static boolean FULLSCREEN_MODE_DEFAULT = true; /** * Constant for the <code>LEFT</code> game action. */ public static final int LEFT = -1 * javax.microedition.lcdui.Canvas.LEFT; /** * Constant for the <code>RIGHT</code> game action. */ public static final int RIGHT = -1 * javax.microedition.lcdui.Canvas.RIGHT; /** * Constant for the <code>UP</code> game action. */ public static final int UP = -1 * javax.microedition.lcdui.Canvas.UP; /** * Constant for the <code>DOWN</code> game action. */ public static final int DOWN = -1 * javax.microedition.lcdui.Canvas.DOWN; /** * Constant for the <code>FIRE</code> game action. */ public static final int FIRE = -1 * javax.microedition.lcdui.Canvas.FIRE; /** * Constant for the general purpose "A" game action. */ public static final int GAME_A = -1 * javax.microedition.lcdui.Canvas.GAME_A; /** * Constant for the general purpose "B" game action. */ public static final int GAME_B = -1 * javax.microedition.lcdui.Canvas.GAME_B; /** * Constant for the general purpose "C" game action. */ public static final int GAME_C = -1 * javax.microedition.lcdui.Canvas.GAME_C; /** * Constant for the general purpose "D" game action. */ public static final int GAME_D = -1 * javax.microedition.lcdui.Canvas.GAME_D; /** * <code>keyCode</code> for ITU-T key 0. * <p> * Constant value 48 is set to <code>KEY_NUM0</code>. */ public static final int KEY_NUM0 = javax.microedition.lcdui.Canvas.KEY_NUM0; /** * <code>keyCode</code> for ITU-T key 1. * <p> * Constant value 49 is set to <code>KEY_NUM1</code>. */ public static final int KEY_NUM1 = javax.microedition.lcdui.Canvas.KEY_NUM1; /** * <code>keyCode</code> for ITU-T key 2. * <p> * Constant value 50 is set to <code>KEY_NUM2</code>. */ public static final int KEY_NUM2 = javax.microedition.lcdui.Canvas.KEY_NUM2; /** * <code>keyCode</code> for ITU-T key 3. * <p> * Constant value 51 is set to <code>KEY_NUM3</code>. */ public static final int KEY_NUM3 = javax.microedition.lcdui.Canvas.KEY_NUM3; /** * <code>keyCode</code> for ITU-T key 4. * <p> * Constant value 52 is set to <code>KEY_NUM4</code>. */ public static final int KEY_NUM4 = javax.microedition.lcdui.Canvas.KEY_NUM4; /** * <code>keyCode</code> for ITU-T key 5. * <p> * Constant value 53 is set to <code>KEY_NUM5</code>. */ public static final int KEY_NUM5 = javax.microedition.lcdui.Canvas.KEY_NUM5; /** * <code>keyCode</code> for ITU-T key 6. * <p> * Constant value 54 is set to <code>KEY_NUM6</code>. */ public static final int KEY_NUM6 = javax.microedition.lcdui.Canvas.KEY_NUM6; /** * <code>keyCode</code> for ITU-T key 7. * <p> * Constant value 55 is set to <code>KEY_NUM7</code>. */ public static final int KEY_NUM7 = javax.microedition.lcdui.Canvas.KEY_NUM7; /** * <code>keyCode</code> for ITU-T key 8. * <p> * Constant value 56 is set to <code>KEY_NUM8</code>. */ public static final int KEY_NUM8 = javax.microedition.lcdui.Canvas.KEY_NUM8; /** * <code>keyCode</code> for ITU-T key 9. * <p> * Constant value 57 is set to <code>KEY_NUM9</code>. */ public static final int KEY_NUM9 = javax.microedition.lcdui.Canvas.KEY_NUM9; /** * <code>keyCode</code> for ITU-T key "pound" (#). * <p> * Constant value 35 is set to <code>KEY_POUND</code>. */ public static final int KEY_POUND = javax.microedition.lcdui.Canvas.KEY_POUND; /** * <code>keyCode</code> for ITU-T key "star" (*). * <p> * Constant value 42 is set to <code>KEY_STAR</code>. */ public static final int KEY_STAR = javax.microedition.lcdui.Canvas.KEY_STAR; /** * Constant for the left soft menu key found on MIDP 2.0 devices. */ public static final int MENU_LEFT = -21; /** * Constant for the right soft menu key found on MIDP 2.0 devices. */ public static final int MENU_RIGHT = -22; /** * The actual <code>Canvas</code> object that controls the device's screen. * This object wraps it. */ private final CanvasWrapper slave; /** * When <code>false</code> this class will paint the menu bar at the bottom * of the screen. When <code>true</code> it will not. */ private boolean fullScreenMode = false; /** * What is written as a title bar for this canvas. When this is <code>null</code> * no title bar will be written. To show the header without any text * set this to the empty string "". */ private String title; /** * The text for the left menu button. This is the negative side used * for canceling and going back to previous screens. */ private String leftMenu; /** * The text for the right menu button. This is the positive side used * for accepting input, invoking menus, and moving forward in the * application's state. */ private String rightMenu; /** * Implicitly called by derived classes to setup a new J4ME canvas. */ public DeviceScreen () { // Create a wrapper around the canvas. slave = new CanvasWrapper( this ); } /** * Returns the LCDUI <code>Canvas</code> wrapped by this screen. This is * required for some APIs. * * @return The <code>javax.microedition.lcdui.Canvas</code> wrapped by this screen. */ public Canvas getCanvas () { return slave; } /** * Makes this object take over the device's screen. * <p> * The previous screen will have its <code>hideNotify</code> method called. * Then this screen's <code>showNotify</code> method will be invoked followed * by the <code>paint</code> method. */ public void show () { // Set the wrapped canvas as the current screen. UIManager.setScreen( this, slave ); } /** * Checks if this screen is actually visible on the display. In * order for a screen to be visible, all of the following must be true: * the MIDlet must be running in the foreground, the screen must be the * current one, and the screen must not be obscured by a system screen. * * @return <code>true</code> if this screen is currently visible; <code>false</code> * otherwise. */ public boolean isShown () { if ( UIManager.getScreen() == this ) { return true; } else { return false; } } /** * The implementation calls <code>showNotify()</code> immediately prior to * this <code>Canvas</code> being made visible on the display. <code>Canvas</code> * subclasses may override this method to perform tasks before being * shown, such as setting up animations, starting timers, etc. The * default implementation of this method in class <code>Canvas</code> is empty. */ public void showNotify () { } /** * The implementation calls <code>hideNotify()</code> shortly after the * <code>Canvas</code> has been removed from the display. <code>Canvas</code> * subclasses may override this method in order to pause animations, * revoke timers, etc. The default implementation of this method in * class <code>Canvas</code> is empty. */ public void hideNotify () { } /** * Shows or hides the menu bar at the bottom of the screen. * * @param mode is <code>true</code> if the <code>DeviceScreen</code> is to be in full * screen mode, <code>false</code> otherwise. */ public void setFullScreenMode (boolean mode) { this.fullScreenMode = mode; } /** * Returns if the title bar and menu bar are hidden or not. * * @return <code>true</code> if in full screen mode (title bar and menu * bar are hidden); <code>false</code> otherwise. */ public boolean isFullScreenMode () { return fullScreenMode; } /** * Gets the title of this screen. If this returns <code>null</code> the * screen has no title. * * @return The title of this screen. */ public String getTitle () { return title; } /** * Sets the title of this screen. The default is <code>null</code> meaning no * title. * <p> * For the title to be visible full screen mode must be off. * This can be done with the <code>setFullScreenMode</code> method. * * @param title is the new title for the screen. */ public void setTitle (String title) { this.title = title; // Notify the slave screen. slave.setTitle( title ); slave.repaint(); } /** * Returns if this screen displays a title bar at the top. Title * bars require both setting a title (through <code>setTitle</code>) and * that full screen mode is off (<code>setFullScreenMode(false)</code>). * * @return <code>true</code> if the screen has a title bar; <code>false</code> * if it does not. */ public boolean hasTitleBar () { // Full screen mode off? if ( fullScreenMode == false ) { // There is some title? if ( title != null ) { // Device supports title bars? if ( slave.supportsTitleBar() ) { return true; } } } // If we made it here no title bar should be displayed. return false; } /** * Returns the title of this screen. * * @return The title of this screen. If no title is set this returns * the empty string "". */ public String toString () { if ( title == null ) { return getClass().getName(); } else { return title; } } /** * Sets the menu bar text. * <p> * For the menu to be visible full screen mode must be off. * This can be done with the <code>setFullScreenMode</code> method. * * @param left is the text for the negative menu option or <code>null</code> * to remove the button. Negative menu options are things like canceling * a form and moving back to a previous screen. * @param right is the text for the positive menu option or <code>null</code> * to remove the button. Positive menu options are things like accepting * a form, advancing to the next screen, or displaying a menu. * @see #declineNotify() * @see #acceptNotify() */ public void setMenuText (String left, String right) { this.leftMenu = left; this.rightMenu = right; // Notify the slave screen. slave.setMenuText( left, right ); slave.repaint(); } /** * Returns the text for the left menu button. The left menu button is * for negative operations such as canceling a form and going back to * a previous screen. * * @return The text for the left menu button. If there is no button * this returns <code>null</code>. */ public String getLeftMenuText () { return leftMenu; } /** * Returns the text for the right menu button. The right menu button is * for positive operations such as accepting a form and opening a menu. * * @return The text for the right menu button. If there is no button * this returns <code>null</code>. */ public String getRightMenuText () { return rightMenu; } /** * Returns if this screen displays a menu bar at the bottom. Menu bars * require both setting at least one menu option (through * <code>setMenuText</code>) and that full screen mode is off * (<code>setFullScreenMode(false)</code>). * * @return <code>true</code> if the screen has a menu bar; <code>false</code> * if it does not. */ public boolean hasMenuBar () { // Full screen mode off? if ( fullScreenMode == false ) { // There is some menu text? if ( (leftMenu != null) || (rightMenu != null) ) { // Device supports menus? if ( slave.supportsMenuBar() ) { return true; } } } // If we made it here no menu bar should be displayed. return false; } /** * Returns the width of the usuable portion of this canvas. The usable * portion excludes anything on the sides of the screen such as scroll * bars. * * @return The number of pixels wide the usable portion of the canvas is. */ public int getWidth () { return slave.getWidth(); } /** * Returns the height of the usuable portion of this canvas. The usable * portion excludes the title area and menu bar unless this canvas has been * set to full screen mode. * * @return The number of pixels high the usable portion of the canvas is. */ public int getHeight () { Theme theme = UIManager.getTheme(); // Get the height of the entire canvas. int height = getScreenHeight(); // Remove the height of the title bar. if ( hasTitleBar() ) { height -= theme.getTitleHeight(); } // Remove the height of the menu bar. if ( hasMenuBar() ) { height -= theme.getMenuHeight(); } return height; } /** * Gets the width of the entire screen in pixels. * <p> * <i>Platform bug note.</i> Motorola and early Nokia phones return the * incorrect size until after the first screen has actually been displayed. * So, for example, calling this from a constructor before any screen has * been displayed will give incorrect data. The workaround is to put up * another screen first, such as a splash screen. * * @return The number of pixels wide the entire screen is. */ public int getScreenWidth () { return slave.getWidth(); } /** * Gets the height of the entire screen in pixels. This includes * the title area at the top of the screen and menu bar at the bottom. * Use <code>getHeight</code> to get the actual usable area of the canvas. * <p> * <i>Platform bug note.</i> Motorola and early Nokia phones return the * incorrect size until after the first screen has actually been displayed. * So, for example, calling this from a constructor before any screen has * been displayed will give incorrect data. The workaround is to put up * another screen first, such as a splash screen. * * @return The number of pixels high the entire screen is. */ public int getScreenHeight () { return slave.getHeight(); } /** * Method called by the framework when the user clicks on the left menu button. * The default implementation does nothing. * * @see #getLeftMenuText() */ protected void declineNotify () { } /** * Method called by the framework when the user clicks the right menu button. * The default implementation does nothing. * * @see #getRightMenuText() */ protected void acceptNotify () { } /** * Called when a key is pressed. It can be identified using the * constants defined in this class. * <p> * Special keys, like the joystick and menu buttons, have negative * values. Input characters, like the number keys, are positive. * <p> * Unlike the MIDP <code>Canvas</code> class which requires tranlation of game keys * this implementation does not. The <code>keyCode</code> value will match the * constants of this class. * * @param keyCode is the key code of the key that was pressed. * Negative values are special keys like the joystick and menu buttons. */ protected void keyPressed (int keyCode) { } /** * Called when a key is repeated (held down). It can be identified using the * constants defined in this class. * <p> * Special keys, like the joystick and menu buttons, have negative * values. Input characters, like the number keys, are positive. * <p> * Unlike the MIDP <code>Canvas</code> class which requires tranlation of game keys * this implementation does not. The <code>keyCode</code> value will match the * constants of this class. * <p> * Also unlike the MIDP <code>Canvas</code> class, this implementation always * supports this method and does so the same across all devices. * * @param keyCode is the key code of the key that was held down. * Negative values are special keys like the joystick and menu buttons. */ protected void keyRepeated (int keyCode) { } /** * Called when a key is released. It can be identified using the * constants defined in this class. * <p> * Special keys, like the joystick and menu buttons, have negative * values. Input characters, like the number keys, are positive. * <p> * Unlike the MIDP <code>Canvas</code> class which requires tranlation of game keys * this implementation does not. The <code>keyCode</code> value will match the * constants of this class. * * @param keyCode is the key code of the key that was released. * Negative values are special keys like the joystick and menu buttons. */ protected void keyReleased (int keyCode) { } /** * Called when the pointer is pressed. * * @param x is the horizontal location where the pointer was pressed * relative to the canvas area (i.e. does not include the title or * menu bars). * @param y is the vertical location where the pointer was pressed * relative to the canvas area (i.e. does not include the title or * menu bars). */ protected void pointerPressed (int x, int y) { } /** * Called when the pointer is released. * * @param x is the horizontal location where the pointer was pressed * relative to the canvas area (i.e. does not include the title or * menu bars). * @param y is the vertical location where the pointer was pressed * relative to the canvas area (i.e. does not include the title or * menu bars). */ protected void pointerReleased (int x, int y) { } /** * Called when the pointer is dragged. * * @param x is the horizontal location where the pointer was pressed * relative to the canvas area (i.e. does not include the title or * menu bars). * @param y is the vertical location where the pointer was pressed * relative to the canvas area (i.e. does not include the title or * menu bars). */ protected void pointerDragged (int x, int y) { } /** * Requests a repaint for the entire <code>Canvas</code>. The effect is identical to * <code>repaint(0, 0, getWidth(), getHeight());</code>. */ public void repaint () { // Make sure the wrapper is in full-screen mode. // There is a bug on some implementations that turns the screen // off full-screen mode. This can be seen when going to a // javax.microedition.lcdui.TextBox screen and back to this one. slave.setFullScreenMode( FULLSCREEN_MODE_DEFAULT ); // Do the repaint. slave.repaint(); } /** * Requests a repaint for the specified region of the <code>Canvas<code>. Calling this * method may result in subsequent call to <code>paint()</code>, where the passed * <code>Graphics</code> object's clip region will include at least the specified region. * <p> * If the canvas is not visible, or if width and height are zero or less, or if * the rectangle does not specify a visible region of the display, this call has * no effect. * <p> * The call to <code>paint()</code> occurs asynchronously of the call to <code>repaint()</code>. * That is, <code>repaint()</code> will not block waiting for <code>paint()</code> to finish. The * <code>paint()</code> method will either be called after the caller of <code>repaint()</code> returns * to the implementation (if the caller is a callback) or on another thread entirely. * <p> * To synchronize with its <code>paint()</code> routine applications can use * <code>serviceRepaints()</code>, or they can code explicit synchronization into their * <code>paint()</code> routine. * <p> * The origin of the coordinate system is above and to the left of the pixel in * the upper left corner of the displayable area of the <code>Canvas</code>. The * X-coordinate is positive right and the Y-coordinate is positive downwards. * * @param x is the x coordinate of the rectangle to be repainted. * @param y is the y coordinate of the rectangle to be repainted. * @param width is the width of the rectangle to be repainted. * @param height is the height of the rectangle to be repainted. * @see Canvas#serviceRepaints() */ public void repaint (int x, int y, int width, int height) { if ( hasTitleBar() ) { // Offset the user's y by the height of the title bar. Theme theme = UIManager.getTheme(); int titleHeight = theme.getTitleHeight(); y += titleHeight; } slave.repaint( x, y, width, height ); } /** * Forces any pending repaint requests to be serviced immediately. This * method blocks until the pending requests have been serviced. If there * are no pending repaints, or if this canvas is not visible on the * display, this call does nothing and returns immediately. */ public void serviceRepaints () { slave.serviceRepaints(); } /** * Paints the background of the main section of the screen. This includes * everything except for the title bar at the top and menu bar at the bottom. * However, if this canvas is in full screen mode, then this method paints the entire * screen. * <p> * After this method is called, the <code>paintCanvas</code> method will be. * <p> * Override this method to change the background for just this screen. Override * <code>Theme.paintBackground</code> to change the background for the entire application. * * @param g is the <code>Graphics</code> object to paint with. * @see #paint(Graphics) */ protected void paintBackground (Graphics g) { UIManager.getTheme().paintBackground( g ); } /** * Paints the main section of the screen. This includes everything except * for the title bar at the top and menu bar at the bottom. However, * if this canvas is in full screen mode, then this method paints the entire * screen. * <p> * Before this method is called, the <code>paintBackground</code> method will be. * Any painting done here will go over the background. * <p> * Override this method to paint the main area of the screen. * * @param g is the <code>Graphics</code> object to paint with. * @see #paintBackground(Graphics) */ protected abstract void paint (Graphics g); /** * Paints the title bar of the canvas. This method is called only * when the title has been set through <code>setTitle</code> and the canvas * is not in full screen mode. * <p> * Override this method to change the appearance of the title bar * for just this canvas. To change them for the entire application, * override <code>Theme.paintTitleBar</code>. * * @param g is the <code>Graphics</code> object to paint with. * @param title is the text for the title bar as defined by the * canvas class. * @param width is the width of the title bar in pixels. * @param height is the height of the title bar in pixels. */ protected void paintTitleBar (Graphics g, String title, int width, int height) { UIManager.getTheme().paintTitleBar( g, title, width, height ); } /** * Paints the menu bar at the bottom of the canvas. This method is * not called if the canvas is in full screen mode. * <p> * Override this method to change the appearance or functionality of * the menu for just this canvas. To change them for the entire * application, override <code>Theme.paintMenuBar</code>. Be careful not * to write strings that are too long and will not fit on the menu bar. * * @param g is the <code>Graphics</code> object to paint with. * @param left is the text to write on the left side of the menu bar. * The left side is associated with dimissing input such as a * "Cancel" button. * @param highlightLeft is <code>true</code> if the menu text <code>left</code> * should be highlighted to indicate the left menu button is currently * pressed. * @param right is the text to write on the right side of the menu bar. * The right side is associated with accepting input such as an * "OK" button. * @param highlightRight is <code>true</code> if the menu text <code>right</code> * should be highlighted to indicate the right menu button is currently * pressed. * @param width is the width of the menu bar in pixels. * @param height is the height of the menu bar in pixels. */ protected void paintMenuBar (Graphics g, String left, boolean highlightLeft, String right, boolean highlightRight, int width, int height) { UIManager.getTheme().paintMenuBar( g, left, highlightLeft, right, highlightRight, width, height ); } /** * Returns if the clip area of <code>g</code> intersects the given rectangle. * * @param g is the current <code>Graphics</code> object for a <code>paint</code> * operation. * @param x is the pixel at the left edge of the rectangle. * @param y is the pixel at the top edge of the rectangle. * @param w is width of the rectangle in pixels. * @param h is height of the rectangle in pixels. * @return <code>true</code> if the rectangle is to be painted by <code>g</code>; * <code>false</code> otherwise. */ public static boolean intersects (Graphics g, int x, int y, int w, int h) { // Get the graphic's clip dimensions. int gx = g.getClipX(); int gy = g.getClipY(); int gw = g.getClipWidth(); int gh = g.getClipHeight(); // Make the width/height into the right/bottom. gw += gx; gh += gy; w += x; h += y; // Check for intersections. // (overflow || intersect) boolean intersects = (w < x || w > gx) && (h < y || h > gy) && (gw < gx || gw > x) && (gh < gy || gh > y); return intersects; } } /** * Wraps the LCDUI's <code>Canvas</code> class. It masks differences between * platforms. It is used as the actual <code>Screen</code> for all J4ME screens. */ final class CanvasWrapper extends javax.microedition.lcdui.Canvas implements CommandListener { /** * The interval, in milliseconds, between signaling repeat events. * It can be thought of as events raised per second by taking 1000 * and dividing it by this number (e.g. 1000 / 250 = 4 events per second). */ private static final short REPEAT_PERIOD = 200; /** * seris60 phones don't like the fullscreen mode */ private static boolean series60; /** * When <code>true</code> this is running on a BlackBerry device. When * <code>false</code> it is not. * <p> * BlackBerry phones have limited native MIDlet support. The biggest * issue is they do not have the left and right menu buttons on other * MIDP 2.0 phones. Instead they have a "Menu" key and a "Return" * key that are big parts of the overall BlackBerry experience. To * capture these key events, and make the application consistent with * the BlackBerry experience, we need to create a standard LCDUI menu * which the BlackBerry framework ties to these buttons. */ private static boolean blackberry; /** * When <code>true</code> this is running in IBM's J9 JVM (also known as * WEME or WebSphere Everyplace Micro Edition). When <code>false</code> * it is not. * <p> * For Windows Mobile and Palm phones the only reliable J2ME JVM is * IBM's J9 JVM. There are some other good implementations that * come with some phones, such as the Motorola Q which uses Motorola's * JVM. * <p> * J9 does not support detecting soft menu buttons. We need to use * its title area and menu bar instead of our own to capture these * events. This also hooks into the design of these devices more * closely so the application resembles others on the phone. */ private static boolean ibmJ9; /** * When <code>true</code> this is running on Tao's JVM. When <code>false</code> * it is not. * <p> * The Tao JVM runs on Windows Mobile and behaves similarly to IBM's J9. */ private static boolean tao; /** * The screen that uses this object for screen operations. * <p> * This should only be <code>null</code> when using a dummy screen to get * the dimensions. For more information see the <code>sizeChanged</code> * method's comments for more information. */ private final DeviceScreen master; /** * Executes the <code>keyRepeated</code> job every <code>REPEAT_PERIOD</code>. * This value will be <code>null</code> if the user is not holding down * any keys. * <p> * Note we have to create new <code>Timer</code> objects every time we * repeat keys. It does not work to create a timer once here and * then try to reuse it. <i>To prevent multiple timer objects * synchronize access on <code>this</code> when dealing with * <code>keyRepeatTimer</code>.</i> */ private Timer keyRepeatTimer = null; /** * If <code>true</code> the left menu button text should be highlighted. This * happens when the user presses the left menu button to indicate the * event was received. Normally this will be <code>false</code> showing no * menu work is in progress. */ private boolean highlightLeftMenu; /** * If <code>true</code> the right menu button text should be highlighted. This * happens when the user presses the right menu button to indicate the * event was received. Normally this will be <code>false</code> showing no * menu work is in progress. */ private boolean highlightRightMenu; /** * When we do not paint the menu bar, such as with BlackBerries and IBM's * J9 JVM, this contains the left menu choice. Otherwise this will be * <code>null</code>. * * @see #lcduiRightMenuCommand */ private Command lcduiLeftMenuCommand; /** * When we do not paint the menu bar, such as with BlackBerries and IBM's * J9 JVM, this contains the right menu choice. Otherwise this will be * <code>null</code>. * * @see #lcduiLeftMenuCommand */ private Command lcduiRightMenuCommand; /** * Static initializer for data that is shared by all screens. */ static { String platform = System.getProperty( "microedition.platform" ); platform = platform.toLowerCase(); // Check if running on a series60 nokia. /* * The name of the host platform or device. In Nokia devices the name consists of “Nokia�, the device model, and software version separated by “/�. There is no space between “Nokia� and the model number nor on either side of “/�. Formally, the syntax of the platform string is: Nokia MODEL_NUMBER “/� SW_VERSION. For example, Nokia6310i/4.42 or Nokia3510i/p1.25. */ try { if (platform.indexOf("S60")!=-1) { series60 = true; } else { try{ Class.forName("com.symbian.gcf.NativeInputStream"); series60 = true; }catch(Exception e){ series60 = false; } } } catch (Throwable e) // ClassNotFoundException, NoClassDefFoundError { series60 = false; } // Check if running on a BlackBerry. try { Class.forName( "net.rim.device.api.ui.UiApplication" ); blackberry = true; } catch (Throwable e) // ClassNotFoundException, NoClassDefFoundError { blackberry = false; } // Check if running on IBM's J9 JVM. try { Class.forName( "java.lang.J9VMInternals" ); ibmJ9 = true; } catch ( Exception e) { ibmJ9 = false; } // Check if running on Tao's JVM. if ( platform.indexOf("intent") > -1 ) { tao = true; } else { tao = false; } } /** * Constructs a wrapper for a <code>Canvas</code>. * * @param master is the J4ME screen that uses this object. */ public CanvasWrapper (DeviceScreen master) { this.master = master; // Always remove the menu bar and replace with our own. // There are special cases, like BlackBerry's and IBM's J9 // on Windows Mobile, where this is not true. These are // handled by setMenuText() and other methods. setFullScreenMode(DeviceScreen.FULLSCREEN_MODE_DEFAULT); if (blackberry) addCommandBack (); setCommandListener( this ); } private void addCommandBack () { Command cmdBack = new Command("Back", Command.BACK, 0 ); addCommand (cmdBack); } /** * Causes the <code>keyRepeated</code> method to fire on devices that do * not natively support it. */ private final class KeyRepeater extends TimerTask { private int key; public KeyRepeater (int key) { this.key = key; } public void run () { if ( master.isShown() ) { try { master.keyRepeated( key ); } catch (Throwable t) { t.printStackTrace(); } } } } /** * If <code>keyRepeatTimer</code> is running, this method stops it. */ private synchronized void stopRepeatTimer () { if ( keyRepeatTimer != null ) { keyRepeatTimer.cancel(); keyRepeatTimer = null; } } /** * Called when the user presses any key. This method checks * for joystick movements which work the Yardage Anywhere * cursor. * * @param key is code of the key that was pressed. */ protected void keyPressed (int key) { int translatedKey = translateKeyCode( key ); // Stop simulating key repeated events. // Holding a key down, then pressing another key, will stop // the first key's keyReleased() method from being called on // some phones like Sony Ericssons. Kill the repeat timer // here so that key no longer receives keyRepeat() events. stopRepeatTimer(); // Notify the master. master.keyPressed( translatedKey ); // If this is a menu key raise an event. if ( translatedKey == DeviceScreen.MENU_LEFT || translatedKey == DeviceScreen.LEFT) { // Highlight the menu option immediately. if ( master.hasMenuBar() ) { highlightLeftMenu = true; repaintMenuBar( true ); } // Raise a menu event. master.declineNotify(); } else if ( translatedKey == DeviceScreen.MENU_RIGHT || translatedKey == DeviceScreen.RIGHT) { // Highlight the menu option immediately. if ( master.hasMenuBar() ) { highlightRightMenu = true; repaintMenuBar( true ); } // Raise a menu event. master.acceptNotify(); } // Do not forward the key event! // super.keyPressed and .keyReleased can cause platform-specific // and often undesirable behavior. For example on the Sony Ericsson // w810i they can turn on the music player or browser. // Start a timer for generating key repeat events. synchronized ( this ) // synchronize so we don't ever create more than one timer { keyRepeatTimer = new Timer(); keyRepeatTimer.schedule( new KeyRepeater(translatedKey), REPEAT_PERIOD, REPEAT_PERIOD ); } } /** * Called when the user releases any key. * * @param key is code of the key that was released. */ protected void keyReleased (int key) { // Stop simulating key repeated events. stopRepeatTimer(); // Notify the master. int translatedKey = translateKeyCode( key ); master.keyReleased( translatedKey ); // If this is a menu key stop highlighting it. if ( master.hasMenuBar() ) { if ( translatedKey == DeviceScreen.MENU_LEFT ) { highlightLeftMenu = false; repaintMenuBar( false ); } else if ( translatedKey == DeviceScreen.MENU_RIGHT ) { highlightRightMenu = false; repaintMenuBar( false ); } } // Do not forward the key event! // super.keyPressed and .keyReleased can cause platform-specific // and often undesirable behavior. For example on the Sony Ericsson // w810i they can turn on the music player or browser. } /** * Maps key values to the constants defined in the outter class. * * @param key is code of the button pressed. * @return The integer value for the key. */ private int translateKeyCode (int key) { // Some phones have a bug where they treat the FIRE key as "Enter". if ( tao && (key == 13) ) { return DeviceScreen.FIRE; } // Is it a normal key? // BlackBerry devices give the trackwheel and trackball movements // as values through 6 and getGameAction does not translate them. // There are no ASCII values used below 8 (backspace) so this is // fine. if ( key > 6 ) { return key; } //blackberry has an escape/back key that should be used as menu let if (key == 27) //should be escape { return DeviceScreen.MENU_LEFT; } // Is it a well defined game key such as a joystick movement? int action; try { action = getGameAction( key ); } catch (Exception e) { // Some phones throw an exception for unsupported keys. // For example the Sony Ericsson K700. return key; } if ( action != 0 ) { // We make all action keys negative. This allows code to // check for special keys by seeing if the value is less // than 0. return -1 * action; } // Is it the left menu button? if ( (key == -6) || (key == -21) || (key == -1) ) { // -6: The Sun WTK emulator and Sony Ericcson phones // -21: Motorola phones such as the SLVR // -1: Siemens return DeviceScreen.MENU_LEFT; } // Is it the right menu button? if ( (key == -7) || (key == -22) || (key == -4) ) { // -7: The Sun WTK emulator and Sony Ericcson phones // -22: Motorola phones such as the SLVR // -4: Siemens return DeviceScreen.MENU_RIGHT; } // Otherwise it is undefined such as: // Motorola center "Menu" soft key: -23 // Sony Ericsson "Return" soft key under the left soft key: -11 // Sony Ericsson "Clear" soft key under the right soft key: -8 // Sony Ericsson "Camera" key on side of phone: -25 // Sony Ericsson "Volume Up" key on side of phone: -36 // Sony Ericsson "Volume Down" key on side of phone: -37 // Sony Ericsson "Play/Pause Music" key on side of phone: -23 (Note this is the same as Motorola's center menu button) // Sony Ericsson "Music Player" key: -22 (Note this is the same as Motorola's right menu button) // Sony Ericsson "Internet Browser" key: (Unavailable) return key; } /** * Called when a stylus presses the screen. * * @param x is the horizontal location where the pointer was pressed * @param y is the vertical location where the pointer was pressed */ protected void pointerPressed (int x, int y) { Theme theme = UIManager.getTheme(); boolean processed = false; // Was the stylus pressed over a menu item? if ( master.hasMenuBar() ) { int menuHeight = theme.getMenuHeight(); int menuStart = super.getHeight() - menuHeight; if ( y > menuStart ) { // The user clicked on the menu. int width = super.getWidth(); if ( x < (width / 2) ) { // The left menu item was clicked. master.declineNotify(); } else { // The right menu item was clicked. master.acceptNotify(); } processed = true; } } // Was the stylus pressed over the title bar? boolean hasTitle = master.hasTitleBar(); if ( (processed == false) && (hasTitle == true) ) { int titleHeight = theme.getTitleHeight(); if ( y < titleHeight ) { // Ignore clicks on the title bar. processed = true; } } // Notify the master. if ( processed == false ) { // Adjust the press location to fit in the canvas area. int py = y; if ( hasTitle ) { py -= theme.getTitleHeight(); } // Forward the event. master.pointerPressed( x, py ); } // Continue processing the pointer event. super.pointerPressed( x, y ); } /** * Called when a stylus drags across the screen. * * @param x is the horizontal location where the pointer was pressed * @param y is the vertical location where the pointer was pressed */ protected void pointerDragged (int x, int y) { // Adjust the press location to fit in the canvas area. int py = y; if ( master.hasTitleBar() ) { Theme theme = UIManager.getTheme(); py -= theme.getTitleHeight(); } // Notify the master. master.pointerDragged( x, py ); // Forward the pointer event. super.pointerDragged( x, y ); } /** * Called when a stylus is lifted off the screen. * * @param x is the horizontal location where the pointer was pressed * @param y is the vertical location where the pointer was pressed */ protected void pointerReleased (int x, int y) { // Adjust the press location to fit in the canvas area. int py = y; if ( master.hasTitleBar() ) { Theme theme = UIManager.getTheme(); py -= theme.getTitleHeight(); } // Notify the master. master.pointerReleased( x, py ); // Forward the pointer event. super.pointerReleased( x, y ); } /** * Sets the title bar text. This is called by the <code>master</code> when * its title is changed. * * @param title is the text that appears in the title bar across the * top of the screen. * * @see DeviceScreen#setTitle(String) */ public void setTitle (String title) { // Does this JVM support our title bar feature? if ( supportsTitleBar() == false ) { // These JVMs always shows a title bar. We might as well use that // instead of painting a duplicate on our own. super.setTitle( title ); } // For other JVMs we'll display the title on our own. There is no // need to have the LCDUI do it for us. } /** * Sets the menu bar text. This is called by the <code>master</code> when * its menu is changed. * * @param left is the text for the negative menu option or <code>null</code> * to remove the button. Negative menu options are things like canceling * a form and moving back to a previous screen. * @param right is the text for the positive menu option or <code>null</code> * to remove the button. Positive menu options are things like accepting * a form, advancing to the next screen, or displaying a menu. * * @see DeviceScreen#setMenuText(String, String) */ public void setMenuText (String left, String right) { /* // Register for getting LCDUI menu commands. setCommandListener( this ); // Does this JVM support our own menu bar? if ( supportsMenuBar() == false ) { // BlackBerry phones do not have MIDP 2.0 left and right menu buttons. // We must capture the BlackBerry phone's "Menu" and "Return" buttons // using the LCDUI menu functionality. // // IBM's J9 does not forward the left and right menu button codes to // us. We have to use LCDUI menu functionality. // Remove the existing menu commands. if ( lcduiLeftMenuCommand != null ) { removeCommand( lcduiLeftMenuCommand ); lcduiLeftMenuCommand = null; } if ( lcduiRightMenuCommand != null ) { removeCommand( lcduiRightMenuCommand ); lcduiRightMenuCommand = null; } // Register new LCDUI menu commands. if ( left != null ) { int position; if ( blackberry ) { // This will be hooked up to the "Return" key and appear // second on the BlackBerry menu. position = 2; } else // ibmJ9 { // This will make the button appear on the left side of the menu. position = 1; } lcduiLeftMenuCommand = new Command( left, Command.CANCEL, position ); addCommand( lcduiLeftMenuCommand ); } if ( right != null ) { int position; if ( blackberry ) { // This will appear first on the BlackBerry menu. position = 1; } else // ibmJ9 { // This will make the button appear on the right side of the menu. position = 2; } lcduiRightMenuCommand = new Command( right, Command.OK, position ); addCommand( lcduiRightMenuCommand ); // Add a dummy left menu for IBM's J9 JVM. Otherwise the right // menu would actually be on the left. if ( (ibmJ9 || tao) && (left == null) ) { lcduiLeftMenuCommand = new Command( "", Command.CANCEL, 1 ); addCommand( lcduiLeftMenuCommand ); } } } */ } /** * Indicates that a command event <code>c</code> has occurred on * <code>Displayable<code> d. * * @param c is a <code>Command</code> object identifying the command. * @param d is the <code>Displayable</code> on which this event has occurred. * It will always be <code>this</code> screen. * * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command, javax.microedition.lcdui.Displayable) */ public void commandAction (Command c, Displayable d) { // Was it our left menu command? if ( (lcduiLeftMenuCommand != null) && (c == lcduiLeftMenuCommand) ) { keyPressed( DeviceScreen.MENU_LEFT ); keyReleased( DeviceScreen.MENU_LEFT ); } // Was it our right menu command? if ( (lcduiRightMenuCommand != null) && (c == lcduiRightMenuCommand) ) { keyPressed( DeviceScreen.MENU_RIGHT ); keyReleased( DeviceScreen.MENU_RIGHT ); } if (c.getCommandType() == Command.BACK || c.getCommandType() == Command.CANCEL) { keyPressed( DeviceScreen.MENU_LEFT ); } } /** * Returns if the device supports having a menu bar at the bottom of the * screen. If not then no menu bar will painted at the bottom of the * screen and a standard LCDUI menu will be used instead. * * @return <code>true</code> if the device supports a menu bar or <code>false</code> * if it does not. */ public boolean supportsMenuBar () { if ( blackberry || ibmJ9 || tao ) { // These JVMs do not show our menu bar at the bottom of the // screen. Instead they use the LCDUI menu system. return false; } else { // This phone can have a menu bar at the bottom of the screen. return true; } } /** * Returns if the device supports having a title bar at the top of the * screen. If not then no title bar will painted at the top of the * screen and a standard LCDUI title will be used instead. * * @return <code>true</code> if the device supports a title bar or <code>false</code> * if it does not. */ public boolean supportsTitleBar () { if ( ibmJ9 || tao ) { // These JVMs always show a title bar. We might as well use it. return false; } else { // This phone can have a title bar at the top of the screen. return true; } } /** * Called when this screen is no longer going to be displayed. * * @see javax.microedition.lcdui.Canvas#hideNotify() */ protected void hideNotify () { // If the key repeat timer is running, stop it. stopRepeatTimer(); // Don't highlight the menu options (in case we return to this screen). highlightLeftMenu = false; highlightRightMenu = false; // Continue to hide the screen. super.hideNotify(); } /** * Forces a repaint of the menu bar. * * @param immediate when <code>true</code> finishes the painting before * this method returns; <code>false</code> does it in the normal paint * cycle. */ private void repaintMenuBar (boolean immediate) { Theme theme = UIManager.getTheme(); int menuHeight = theme.getMenuHeight(); int y = getHeight() - menuHeight; this.repaint( 0, y, getWidth(), menuHeight ); if ( immediate ) { this.serviceRepaints(); } } /** * Paints the screen. If not in full screen mode, this includes the * title bar and menu bar. * <p> * Typically derviced classes will only override the <code>paintCanvas</code> * method. * * @param g is the <code>Graphics</code> object to paint with. * @see #paintTitleBar(Graphics, String, int, int, int, int) * @see #paintBackground(Graphics, int, int, int, int) * @see #paintCanvas(Graphics, int, int, int, int) * @see #paintMenuBar(Graphics, String, String, int, int, int, int) */ protected void paint (Graphics g) { try { // Get some painting attributes. Theme theme = UIManager.getTheme(); int width = getWidth(); int height = getHeight(); int titleHeight = 0; int menuHeight = 0; String title = null; String leftMenuText = null; String rightMenuText = null; // Get the original clip. int clipX = g.getClipX(); int clipY = g.getClipY(); int clipWidth = g.getClipWidth(); int clipHeight = g.getClipHeight(); // Paint the title bar and/or menu bar? // if ( master.isFullScreenMode() == false ) // { // Paint the title bar at the top of the screen. title = master.getTitle(); //if ( master.hasTitleBar() ) //{ // We'll paint the title after the canvas area. titleHeight = theme.getTitleHeight(); //} // Paint the menu bar at the bottom of the screen. /* if ( master.hasMenuBar() ) { // Set the menu text. leftMenuText = master.getLeftMenuText(); rightMenuText = master.getRightMenuText(); if ( leftMenuText == null ) { leftMenuText = ""; } if ( rightMenuText == null ) { rightMenuText = ""; } // Set the height of the menu. menuHeight = theme.getMenuHeight(); }*/ // Set the graphics object for painting the canvas area. height = height - titleHeight - menuHeight; g.translate( 0, titleHeight ); g.clipRect( 0, 0, width, height ); //} // Paint the canvas area. if ( DeviceScreen.intersects(g, 0, 0, width, height) ) { master.paintBackground( g ); g.setFont( theme.getFont() ); g.setColor( theme.getFontColor() ); master.paint( g ); } // Restore the original graphics object. g.translate( 0, -titleHeight ); g.setClip( clipX, clipY, clipWidth, clipHeight ); // Paint the title bar. // We do this after the canvas so it will paint over any // canvas spillage. if ( titleHeight > 0 ) { if ( DeviceScreen.intersects(g, 0, 0, width, titleHeight) ) { // Set the graphics object for painting the title bar. g.clipRect( 0, 0, width, titleHeight ); // Actually paint the title bar. master.paintTitleBar( g, title, width, titleHeight ); // Restore the original graphics object. g.setClip( clipX, clipY, clipWidth, clipHeight ); } } // Paint the menu bar. if ( menuHeight > 0 ) { int y = getHeight() - menuHeight; if ( DeviceScreen.intersects(g, 0, y, width, menuHeight) ) { // Set the graphics object for painting the menu bar. g.translate( 0, y ); g.clipRect( 0, 0, width, menuHeight ); // Clear the background first. // On the Sony Ericsson w810i things drawn accidentally // over the menu space can show through gradient backgrounds. // Clear the area completely first. int menuBackgroundColor = theme.getMenuBarBackgroundColor(); g.setColor( menuBackgroundColor ); g.fillRect( 0, 0, width, menuHeight ); // Actually paint the menu bar. master.paintMenuBar( g, leftMenuText, highlightLeftMenu, rightMenuText, highlightRightMenu, width, menuHeight ); // Restore the original graphics object. g.translate( 0, -y ); g.setClip( clipX, clipY, clipWidth, clipHeight ); } } } catch (Throwable t) { // Unhandled exception in paint() will crash an application and not // tell you why. This lets the programmer know what caused the problem. Log.warn("Unhandled exception in paint for " + master, t); } } }