/* * 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.geom.Rectangle; import com.sun.lwuit.geom.Dimension; import com.sun.lwuit.plaf.Style; import com.sun.lwuit.animations.Transition; import com.sun.lwuit.events.ActionEvent; import com.sun.lwuit.events.ActionListener; import com.sun.lwuit.list.ListCellRenderer; import com.sun.lwuit.layouts.BorderLayout; import com.sun.lwuit.layouts.FlowLayout; import com.sun.lwuit.layouts.GridLayout; import com.sun.lwuit.layouts.Layout; import com.sun.lwuit.plaf.LookAndFeel; import com.sun.lwuit.plaf.UIManager; import com.sun.lwuit.util.EventDispatcher; import java.util.Hashtable; import java.util.Vector; /** * Top level component that serves as the root for the UI, this {@link Container} * handles the menus and title while placing content between them. By default a * forms central content (the content pane) is scrollable. * * Form contains Title bar, MenuBar and a ContentPane. * Calling to addComponent on the Form is delegated to the contenPane.addComponent * * *<pre> * * ************************** * * Title * * ************************** * * * * * * * * ContentPane * * * * * * * * ************************** * * MenuBar * * ************************** *</pre> * @author Chen Fishbein */ public class Form extends Container { Command selectMenuItem; Command cancelMenuItem; private Painter glassPane; private Container contentPane = new Container(new FlowLayout()); private Label title = new Label("", "Title"); private MenuBar menuBar = new MenuBar(); private Command selectCommand; private Command defaultCommand; private Component dragged; /** * Indicates the command that is defined as the back command out of this form. * A back command can be used both to map to a hardware button (e.g. on the Sony Ericsson devices) * and by elements such as transitions etc. to change the behavior based on * direction (e.g. slide to the left to enter screen and slide to the right to exit with back). */ private Command backCommand; /** * Indicates whether lists and containers should scroll only via focus and thus "jump" when * moving to a larger component as was the case in older versions of LWUIT. */ protected boolean focusScrolling; /** * Used by the combo box to block some default LWUIT behaviors */ static boolean comboLock; /** * Indicates the command that is defined as the clear command out of this form similar * in spirit to the back command */ private Command clearCommand; /** * Contains a list of components that would like to animate their state */ private Vector animatableComponents; /** * Contains a list of components that would like to animate their state */ private Vector internalAnimatableComponents; /** * This member holds the left soft key value */ static int leftSK; /** * This member holds the right soft key value */ static int rightSK; /** * This member holds the 2nd right soft key value * this is used for different BB devices */ static int rightSK2; /** * This member holds the back command key value */ static int backSK; /** * This member holds the clear command key value */ static int clearSK; static int backspaceSK; static { // RIM and potentially other devices reinitialize the static initializer thus overriding // the new static values set by the initialized display https://lwuit.dev.java.net/issues/show_bug.cgi?id=232 if(Display.getInstance() == null || Display.getInstance().getImplementation() == null) { leftSK = -6; rightSK = -7; rightSK2 = -7; backSK = -11; clearSK = -8; backspaceSK = -8; } } //private FormSwitcher formSwitcher; private Component focused; private Vector mediaComponents; /** * This member allows us to define an animation that will draw the transition for * entering this form. A transition is an animation that would occur when * switching from one form to another. */ private Transition transitionInAnimator; /** * This member allows us to define a an animation that will draw the transition for * exiting this form. A transition is an animation that would occur when * switching from one form to another. */ private Transition transitionOutAnimator; /** * a listener that is invoked when a command is clicked allowing multiple commands * to be handled by a single block */ private EventDispatcher commandListener; private EventDispatcher pointerPressedListeners; private EventDispatcher pointerReleasedListeners; private EventDispatcher pointerDraggedListeners; /** * Relevant for modal forms where the previous form should be rendered underneath */ private Form previousForm; /** * Indicates that this form should be tinted when painted */ private boolean tint; /** * Default color for the screen tint when a dialog or a menu is shown */ private int tintColor; /** * Allows us to cache the next focus component ordered from top to down, this * vector is guaranteed to have all focusable children in it. */ private Vector focusDownSequence; /** * Allows us to cache the next focus component ordered from left to right, this * vector is guaranteed to have all focusable children in it. */ private Vector focusRightSequence; /** * Listeners for key release events */ private Hashtable keyListeners; /** * Listeners for game key release events */ private Hashtable gameKeyListeners; /** * Indicates whether focus should cycle within the form */ private boolean cyclicFocus = true; private int tactileTouchDuration; /** * Default constructor creates a simple form */ public Form() { super(new BorderLayout()); setUIID("Form"); // forms/dialogs are not visible by default setVisible(false); Style formStyle = getStyle(); int w = Display.getInstance().getDisplayWidth() - (formStyle.getMargin(isRTL(), Component.LEFT) + formStyle.getMargin(isRTL(), Component.RIGHT)); int h = Display.getInstance().getDisplayHeight() - (formStyle.getMargin(false, Component.TOP) + formStyle.getMargin(false, Component.BOTTOM)); setWidth(w); setHeight(h); setPreferredSize(new Dimension(w, h)); title.setEndsWith3Points(false); super.addComponent(BorderLayout.NORTH, title); super.addComponent(BorderLayout.CENTER, contentPane); super.addComponent(BorderLayout.SOUTH, menuBar); contentPane.setUIID("ContentPane"); contentPane.setScrollableY(true); LookAndFeel laf = UIManager.getInstance().getLookAndFeel(); initLaf(laf); tintColor = laf.getDefaultFormTintColor(); selectMenuItem = createMenuSelectCommand(); cancelMenuItem = createMenuCancelCommand(); // hardcoded, anything else is just pointless... formStyle.setBgTransparency(0xFF); } /** * Sets the style of the menu bar programmatically * * @param s new style * @deprecated use setSoftButtonStyle instead */ public void setMenuStyle(Style s) { menuBar.setStyle(s); } /** * Sets the style of the menu bar programmatically * * @param s new style */ public void setSoftButtonStyle(Style s) { menuBar.setUnSelectedStyle(s); } /** * Retrieves the style of the menu bar programmatically * * @return the style of the softbutton */ public Style getSoftButtonStyle() { return menuBar.getStyle(); } /** * This method is only invoked when the underlying canvas for the form is hidden * this method isn't called for form based events and is generally usable for * suspend/resume based behavior */ protected void hideNotify() { } /** * This method is only invoked when the underlying canvas for the form is shown * this method isn't called for form based events and is generally usable for * suspend/resume based behavior */ protected void showNotify() { } /** * This method is only invoked when the underlying canvas for the form gets * a size changed event. * This method will trigger a relayout of the Form. * This method will get the callback only if this Form is the Current Form * * @param w the new width of the Form * @param h the new height of the Form */ protected void sizeChanged(int w, int h) { } /** * This method is only invoked when the underlying canvas for the form gets * a size changed event. * This method will trigger a relayout of the Form. * This method will get the callback only if this Form is the Current Form * @param w the new width of the Form * @param h the new height of the Form */ void sizeChangedInternal(int w, int h) { sizeChanged(w, h); setSize(new Dimension(w, h)); setShouldCalcPreferredSize(true); doLayout(); repaint(); } /** * Allows a developer that doesn't derive from the form to draw on top of the * form regardless of underlying changes or animations. This is useful for * watermarks or special effects (such as tinting) it is also useful for generic * drawing of validation errors etc... A glass pane is generally * transparent or translucent and allows the the UI bellow to be seen. * * @param glassPane a new glass pane to install. It is generally recommended to * use a painter chain if more than one painter is required. */ public void setGlassPane(Painter glassPane) { this.glassPane = glassPane; repaint(); } /** * This method can be overriden by a component to draw on top of itself or its children * after the component or the children finished drawing in a similar way to the glass * pane but more refined per component * * @param g the graphics context */ void paintGlassImpl(Graphics g) { if (glassPane != null) { int tx = g.getTranslateX(); int ty = g.getTranslateY(); g.translate(-tx, -ty); glassPane.paint(g, getBounds()); g.translate(tx, ty); } paintGlass(g); } /** * Allows a developer that doesn't derive from the form to draw on top of the * form regardless of underlying changes or animations. This is useful for * watermarks or special effects (such as tinting) it is also useful for generic * drawing of validation errors etc... A glass pane is generally * transparent or translucent and allows the the UI bellow to be seen. * * @return the instance of the glass pane for this form * @see com.sun.lwuit.painter.PainterChain#installGlassPane(Form, com.sun.lwuit.Painter) */ public Painter getGlassPane() { return glassPane; } /** * Sets the style of the title programmatically * * @param s new style */ public void setTitleStyle(Style s) { title.setUnSelectedStyle(s); } /** * Allows modifying the title attributes beyond style (e.g. setting icon/alignment etc.) * * @return the component representing the title for the form */ public Label getTitleComponent() { return title; } /** * Allows replacing the title with a different title component, thus allowing * developers to create more elaborate title objects. * * @param title new title component */ public void setTitleComponent(Label title) { super.replace(this.title, title); this.title = title; } /** * Allows replacing the title with a different title component, thus allowing * developers to create more elaborate title objects. This version of the * method allows special effects for title replacement such as transitions * for title entering * * @param title new title component * @param t transition for title replacement */ public void setTitleComponent(Label title, Transition t) { super.replace(this.title, title, t); this.title = title; } /** * Add a key listener to the given keycode for a callback when the key is released * * @param keyCode code on which to send the event * @param listener listener to invoke when the key code released. */ public void addKeyListener(int keyCode, ActionListener listener) { if (keyListeners == null) { keyListeners = new Hashtable(); } addKeyListener(keyCode, listener, keyListeners); } /** * Removes a key listener from the given keycode * * @param keyCode code on which the event is sent * @param listener listener instance to remove */ public void removeKeyListener(int keyCode, ActionListener listener) { if (keyListeners == null) { return; } removeKeyListener(keyCode, listener, keyListeners); } /** * Removes a game key listener from the given game keycode * * @param keyCode code on which the event is sent * @param listener listener instance to remove */ public void removeGameKeyListener(int keyCode, ActionListener listener) { if (gameKeyListeners == null) { return; } removeKeyListener(keyCode, listener, gameKeyListeners); } private void addKeyListener(int keyCode, ActionListener listener, Hashtable keyListeners) { if (keyListeners == null) { keyListeners = new Hashtable(); } Integer code = new Integer(keyCode); Vector vec = (Vector) keyListeners.get(code); if (vec == null) { vec = new Vector(); vec.addElement(listener); keyListeners.put(code, vec); return; } if (!vec.contains(listener)) { vec.addElement(listener); } } private void removeKeyListener(int keyCode, ActionListener listener, Hashtable keyListeners) { if (keyListeners == null) { return; } Integer code = new Integer(keyCode); Vector vec = (Vector) keyListeners.get(code); if (vec == null) { return; } vec.removeElement(listener); if (vec.size() == 0) { keyListeners.remove(code); } } /** * Add a game key listener to the given gamekey for a callback when the * key is released * * @param keyCode code on which to send the event * @param listener listener to invoke when the key code released. */ public void addGameKeyListener(int keyCode, ActionListener listener) { if (gameKeyListeners == null) { gameKeyListeners = new Hashtable(); } addKeyListener(keyCode, listener, gameKeyListeners); } /** * Returns the number of buttons on the menu bar for use with getSoftButton() * * @return the number of softbuttons */ public int getSoftButtonCount() { return menuBar.getSoftButtons().length; } /** * Returns the button representing the softbutton, this allows modifying softbutton * attributes and behavior programmatically rather than by using the command API. * Notice that this API behavior is fragile since the button mapped to a particular * offset might change based on the command API * * @param offset the offest of the softbutton * @return a button that can be manipulated */ public Button getSoftButton(int offset) { return menuBar.getSoftButtons()[offset]; } /** * Returns the style of the menu * * @return the style of the menu */ public Style getMenuStyle() { return menuBar.getMenuStyle(); } /** * Returns the style of the title * * @return the style of the title */ public Style getTitleStyle() { return title.getStyle(); } /** * Allows the display to skip the menu dialog if that is the current form */ Form getPreviousForm() { return previousForm; } /** * @inheritDoc */ void initLaf(LookAndFeel laf) { transitionOutAnimator = laf.getDefaultFormTransitionOut(); transitionInAnimator = laf.getDefaultFormTransitionIn(); } /** * Resets the cache focus vectors, this is a good idea when we remove * or add an element to the layout. */ void clearFocusVectors() { focusDownSequence = null; focusRightSequence = null; } /** * Sets the current dragged Component */ void setDraggedComponent(Component dragged) { this.dragged = dragged; } synchronized void initFocusRight() { if (focusRightSequence == null) { focusRightSequence = new Vector(); findAllFocusable(contentPane, focusRightSequence, true); } } synchronized void initFocusDown() { if (focusDownSequence == null) { focusDownSequence = new Vector(); findAllFocusable(contentPane, focusDownSequence, false); } } /** * Adds a component to the vector in the appropriate location based on its * focus order */ private void addSortedComponentRight(Vector components, Component c) { int componentCount = components.size(); int componentX = c.getAbsoluteX(); int bestSpot = 0; boolean rtl = isRTL(); Component scrollableParent = findScrollableAncestor(c); // find components in the same row and add the component either at the end // of the line or at its start for (int iter = 0; iter < componentCount; iter++) { Component current = (Component) components.elementAt(iter); // this component is in the same row... Component currentScrollParent = findScrollableAncestor(current); if (currentScrollParent == scrollableParent) { if (isInSameRow(current, c)) { int currentX = current.getAbsoluteX(); if (((!rtl) && (currentX > componentX)) || ((rtl) && (currentX < componentX))) { continue; } bestSpot = iter + 1; continue; } } else { Component tempScrollableParent = scrollableParent; if (scrollableParent == null) { tempScrollableParent = c; } Component tempCurrentScrollParent = currentScrollParent; if (currentScrollParent == null) { tempCurrentScrollParent = current; } if (((!rtl) && (tempCurrentScrollParent.getAbsoluteX() > tempScrollableParent.getAbsoluteX())) || ((rtl) && (tempCurrentScrollParent.getAbsoluteX() < tempScrollableParent.getAbsoluteX()))) { continue; } if (isInSameRow(tempCurrentScrollParent, tempScrollableParent)) { bestSpot = iter + 1; continue; } } if (current.getAbsoluteY() < c.getAbsoluteY()) { bestSpot = iter + 1; } } components.insertElementAt(c, bestSpot); } /** * Returns the first scrollable ancestor for this component or null if no * such ancestor exists */ private Component findScrollableAncestor(Component c) { c = c.getParent(); if (c == null || c.isScrollable()) { return c; } return findScrollableAncestor(c); } /** ` * Adds a component to the vector in the appropriate location based on its * focus order */ private void addSortedComponentDown(Vector components, Component c) { int componentCount = components.size(); int componentY = c.getAbsoluteY(); int bestSpot = 0; boolean rtl = isRTL(); Component scrollableParent = findScrollableAncestor(c); // find components in the same column and add the component either at the end // of the line or at its start for (int iter = 0; iter < componentCount; iter++) { Component current = (Component) components.elementAt(iter); // this component is in the same column... Component currentScrollParent = findScrollableAncestor(current); if (currentScrollParent == scrollableParent) { if (isInSameColumn(current, c)) { int currentY = current.getAbsoluteY(); if (currentY > componentY) { continue; } bestSpot = iter + 1; continue; } } else { Component tempScrollableParent = scrollableParent; if (scrollableParent == null) { tempScrollableParent = c; } Component tempCurrentScrollParent = currentScrollParent; if (currentScrollParent == null) { tempCurrentScrollParent = current; } if (tempCurrentScrollParent.getAbsoluteY() > tempScrollableParent.getAbsoluteY()) { continue; } if (isInSameColumn(tempCurrentScrollParent, tempScrollableParent)) { bestSpot = iter + 1; continue; } } if (((!rtl) && (current.getAbsoluteX() < c.getAbsoluteX())) || ((rtl) && (current.getAbsoluteX() > c.getAbsoluteX()))) { bestSpot = iter + 1; } } components.insertElementAt(c, bestSpot); } /** * Returns true if the given dest component is in the column of the source component */ private boolean isInSameColumn(Component source, Component dest) { return Rectangle.intersects(source.getAbsoluteX(), source.getAbsoluteY(), source.getWidth(), Integer.MAX_VALUE, dest.getAbsoluteX(), dest.getAbsoluteY(), dest.getWidth(), dest.getHeight()); } /** * Returns true if the given dest component is in the row of the source component */ private boolean isInSameRow(Component source, Component dest) { return Rectangle.intersects(source.getAbsoluteX(), source.getAbsoluteY(), Integer.MAX_VALUE, source.getHeight(), dest.getAbsoluteX(), dest.getAbsoluteY(), dest.getWidth(), dest.getHeight()); } /** * Adds a component to the vector in the appropriate location based on its * focus order */ private void addSortedComponent(Vector components, Component c, boolean toTheRight) { if (toTheRight) { addSortedComponentRight(components, c); } else { addSortedComponentDown(components, c); } } /** * Default command is invoked when a user presses fire, this functionality works * well in some situations but might collide with elements such as navigation * and combo boxes. Use with caution. * * @param defaultCommand the command to treat as default */ public void setDefaultCommand(Command defaultCommand) { this.defaultCommand = defaultCommand; } /** * Default command is invoked when a user presses fire, this functionality works * well in some situations but might collide with elements such as navigation * and combo boxes. Use with caution. * * @return the command to treat as default */ public Command getDefaultCommand() { if (selectCommand != null) { return selectCommand; } return defaultCommand; } /** * Indicates the command that is defined as the clear command in this form. * A clear command can be used both to map to a "clear" hardware button * if such a button exists. * * @param clearCommand the command to treat as the clear Command */ public void setClearCommand(Command clearCommand) { this.clearCommand = clearCommand; } /** * Indicates the command that is defined as the clear command in this form. * A clear command can be used both to map to a "clear" hardware button * if such a button exists. * * @return the command to treat as the clear Command */ public Command getClearCommand() { return clearCommand; } /** * Indicates the command that is defined as the back command out of this form. * A back command can be used both to map to a hardware button (e.g. on the Sony Ericsson devices) * and by elements such as transitions etc. to change the behavior based on * direction (e.g. slide to the left to enter screen and slide to the right to exit with back). * * @param backCommand the command to treat as the back Command */ public void setBackCommand(Command backCommand) { this.backCommand = backCommand; } /** * Indicates the command that is defined as the back command out of this form. * A back command can be used both to map to a hardware button (e.g. on the Sony Ericsson devices) * and by elements such as transitions etc. to change the behavior based on * direction (e.g. slide to the left to enter screen and slide to the right to exit with back). * * @return the command to treat as the back Command */ public Command getBackCommand() { return backCommand; } /** * Finds all focusable components in the hierarchy */ private void findAllFocusable(Container c, Vector v, boolean toTheRight) { int size = c.getComponentCount(); for (int iter = 0; iter < size; iter++) { Component current = c.getComponentAt(iter); if (current instanceof Container) { findAllFocusable((Container) current, v, toTheRight); } if (current.isFocusable()) { addSortedComponent(v, current, toTheRight); } } } /** * Sets the title after invoking the constructor * * @param title the form title */ public Form(String title) { this(); this.title.setText(title); } /** * This method returns the Content pane instance * * @return a content pane instance */ public Container getContentPane() { return contentPane; } /** * Removes all Components from the Content Pane */ public void removeAll() { contentPane.removeAll(); } /** * Sets the background image to show behind the form * * @param bgImage the background image */ public void setBgImage(Image bgImage) { getStyle().setBgImage(bgImage); } /** * @inheritDoc */ public void setLayout(Layout layout) { contentPane.setLayout(layout); } /** * Sets the Form title to the given text * * @param title the form title */ public void setTitle(String title) { this.title.setText(title); if(isInitialized() && this.title.isTickerEnabled()) { if(this.title.shouldTickerStart()) { this.title.startTicker(UIManager.getInstance().getLookAndFeel().getTickerSpeed(), true); } else { if(this.title.isTickerRunning()) { this.title.stopTicker(); } } } } /** * Returns the Form title text * * @return returns the form title */ public String getTitle() { return title.getText(); } /** * Adds Component to the Form's Content Pane * * @param cmp the added param */ public void addComponent(Component cmp) { contentPane.addComponent(cmp); } /** * @inheritDoc */ public void addComponent(Object constraints, Component cmp) { contentPane.addComponent(constraints, cmp); } /** * @inheritDoc */ public void addComponent(int index, Object constraints, Component cmp) { contentPane.addComponent(index, constraints, cmp); } /** * Adds Component to the Form's Content Pane * * @param cmp the added param */ public void addComponent(int index, Component cmp) { contentPane.addComponent(index, cmp); } /** * @inheritDoc */ public void replace(Component current, Component next, Transition t) { contentPane.replace(current, next, t); } /** * @inheritDoc */ public void replaceAndWait(Component current, Component next, Transition t) { contentPane.replaceAndWait(current, next, t); } /** * Removes a component from the Form's Content Pane * * @param cmp the component to be removed */ public void removeComponent(Component cmp) { contentPane.removeComponent(cmp); } /** * Registering media component to this Form, that like to receive * animation events * * @param mediaCmp the Form media component to be registered */ void registerMediaComponent(Component mediaCmp) { if (mediaComponents == null) { mediaComponents = new Vector(); } if (!mediaComponents.contains(mediaCmp)) { mediaComponents.addElement(mediaCmp); } } /** * Used by the implementation to prevent flickering when flushing the double buffer * * @return true if the form has media components within it */ public final boolean hasMedia() { return mediaComponents != null && mediaComponents.size() > 0; } /** * Indicate that cmp would no longer like to receive animation events * * @param cmp component that would no longer receive animation events */ void deregisterMediaComponent(Component mediaCmp) { mediaComponents.removeElement(mediaCmp); } /** * The given component is interested in animating its appearance and will start * receiving callbacks when it is visible in the form allowing it to animate * its appearance. This method would not register a compnent instance more than once * * @param cmp component that would be animated */ public void registerAnimated(Animation cmp) { if (animatableComponents == null) { animatableComponents = new Vector(); } if (!animatableComponents.contains(cmp)) { animatableComponents.addElement(cmp); } Display.getInstance().notifyDisplay(); } /** * Identical to the none-internal version, the difference between the internal/none-internal * is that it references a different vector that is unaffected by the user actions. * That is why we can dynamically register/deregister without interfearing with user interaction. */ void registerAnimatedInternal(Animation cmp) { if (internalAnimatableComponents == null) { internalAnimatableComponents = new Vector(); } if (!internalAnimatableComponents.contains(cmp)) { internalAnimatableComponents.addElement(cmp); } Display.getInstance().notifyDisplay(); } /** * Identical to the none-internal version, the difference between the internal/none-internal * is that it references a different vector that is unaffected by the user actions. * That is why we can dynamically register/deregister without interfearing with user interaction. */ void deregisterAnimatedInternal(Animation cmp) { if (internalAnimatableComponents != null) { internalAnimatableComponents.removeElement(cmp); } } /** * Indicate that cmp would no longer like to receive animation events * * @param cmp component that would no longer receive animation events */ public void deregisterAnimated(Animation cmp) { if (animatableComponents != null) { animatableComponents.removeElement(cmp); } } /** * Returns the offset of the component within the up/down focus sequence * * @return offset between 0 and number of components or -1 for an error */ int getFocusPosition(Component c) { initFocusDown(); return focusDownSequence.indexOf(c); } int getFocusCount() { initFocusDown(); return focusDownSequence.size(); } /** * Makes sure all animations are repainted so they would be rendered in every * frame */ void repaintAnimations() { if (animatableComponents != null) { loopAnimations(animatableComponents, null); } if (internalAnimatableComponents != null) { loopAnimations(internalAnimatableComponents, animatableComponents); } } private void loopAnimations(Vector v, Vector notIn) { // we don't save size() in a varible since the animate method may deregister // the animation thus invalidating the size for (int iter = 0; iter < v.size(); iter++) { Animation c = (Animation) v.elementAt(iter); if(c == null || notIn != null && notIn.contains(c)) { continue; } if (c.animate()) { if (c instanceof Component) { Rectangle rect = ((Component) c).getDirtyRegion(); if (rect != null) { Dimension d = rect.getSize(); // this probably can't happen but we got a really weird partial stack trace to this // method and this check doesn't hurt if(d != null) { ((Component) c).repaint(rect.getX(), rect.getY(), d.getWidth(), d.getHeight()); } } else { ((Component) c).repaint(); } } else { Display.getInstance().repaint(c); } } } } /** * If this method returns true the EDT won't go to sleep indefinitely * * @return true is form has animation; otherwise false */ boolean hasAnimations() { return (animatableComponents != null && animatableComponents.size() > 0) || (internalAnimatableComponents != null && internalAnimatableComponents.size() > 0); } /** * @inheritDoc */ public void refreshTheme() { // when changing the theme when a title/menu bar is not visible the refresh // won't apply to them. We need to protect against this occurance. if (menuBar != null) { menuBar.refreshTheme(); } if (title != null) { title.refreshTheme(); } super.refreshTheme(); } /** * Exposing the background painting for the benefit of animations * * @param g the form graphics */ public void paintBackground(Graphics g) { super.paintBackground(g); } /** * This property allows us to define a an animation that will draw the transition for * entering this form. A transition is an animation that would occur when * switching from one form to another. * * @return the Form in transition */ public Transition getTransitionInAnimator() { return transitionInAnimator; } /** * This property allows us to define a an animation that will draw the transition for * entering this form. A transition is an animation that would occur when * switching from one form to another. * * @param transitionInAnimator the Form in transition */ public void setTransitionInAnimator(Transition transitionInAnimator) { this.transitionInAnimator = transitionInAnimator; } /** * This property allows us to define a an animation that will draw the transition for * exiting this form. A transition is an animation that would occur when * switching from one form to another. * * @return the Form out transition */ public Transition getTransitionOutAnimator() { return transitionOutAnimator; } /** * This property allows us to define a an animation that will draw the transition for * exiting this form. A transition is an animation that would occur when * switching from one form to another. * * @param transitionOutAnimator the Form out transition */ public void setTransitionOutAnimator(Transition transitionOutAnimator) { this.transitionOutAnimator = transitionOutAnimator; } /** * A listener that is invoked when a command is clicked allowing multiple commands * to be handled by a single block * * @param commandListener the command action listener * @deprecated use add/removeCommandListener instead */ public void setCommandListener(ActionListener commandListener) { if(commandListener == null) { this.commandListener = null; return; } addCommandListener(commandListener); } /** * A listener that is invoked when a command is clicked allowing multiple commands * to be handled by a single block * * @param l the command action listener */ public void addCommandListener(ActionListener l) { if(commandListener == null) { commandListener = new EventDispatcher(); } commandListener.addListener(l); } /** * A listener that is invoked when a command is clicked allowing multiple commands * to be handled by a single block * * @param l the command action listener */ public void removeCommandListener(ActionListener l) { commandListener.removeListener(l); } /** * Invoked to allow subclasses of form to handle a command from one point * rather than implementing many command instances. All commands selected * on the form will trigger this method implicitly. * * @param cmd the form commmand object */ protected void actionCommand(Command cmd) { } /** * Dispatches a command via the standard form mechanism of firing a command event * * @param cmd The command to dispatch * @param ev the event to dispatch */ public void dispatchCommand(Command cmd, ActionEvent ev) { cmd.actionPerformed(ev); if(!ev.isConsumed()) { actionCommandImpl(cmd, ev); } } /** * Invoked to allow subclasses of form to handle a command from one point * rather than implementing many command instances */ void actionCommandImpl(Command cmd) { actionCommandImpl(cmd, new ActionEvent(cmd)); } /** * Invoked to allow subclasses of form to handle a command from one point * rather than implementing many command instances */ void actionCommandImpl(Command cmd, ActionEvent ev) { if (cmd == null) { return; } if(comboLock) { if(cmd == cancelMenuItem) { actionCommand(cmd); return; } Component c = getFocused(); if (c != null) { c.fireClicked(); } return; } if (cmd != selectCommand) { if (commandListener != null) { commandListener.fireActionEvent(ev); if(ev.isConsumed()) { return; } } actionCommand(cmd); } else { Component c = getFocused(); if (c != null) { c.fireClicked(); } } } void initFocused() { if (focused == null) { setFocused(findFirstFocusable(contentPane)); layoutContainer(); initFocusDown(); if(focusDownSequence == null) { initFocusDown(); if (focusDownSequence.size() > 0) { setFocused((Component) focusDownSequence.elementAt(0)); } } else { if (focusDownSequence.size() > 0) { setFocused((Component) focusDownSequence.elementAt(0)); } } } } /** * Displays the current form on the screen */ public void show() { show(false); } /** * Displays the current form on the screen, this version of the method is * useful for "back" navigation since it reverses the direction of the transition. */ public void showBack() { show(true); } /** * Displays the current form on the screen */ private void show(boolean reverse) { if (transitionOutAnimator == null && transitionInAnimator == null) { initLaf(UIManager.getInstance().getLookAndFeel()); } initFocused(); onShow(); tint = false; com.sun.lwuit.Display.getInstance().setCurrent(this, reverse); } /** * @inheritDoc */ void initComponentImpl() { super.initComponentImpl(); LookAndFeel lf = UIManager.getInstance().getLookAndFeel(); tactileTouchDuration = lf.getTactileTouchDuration(); if(title.getText() != null && title.shouldTickerStart()) { title.startTicker(lf.getTickerSpeed(), true); focusScrolling = lf.isFocusScrolling(); } } /** * @inheritDoc */ public void setSmoothScrolling(boolean smoothScrolling) { // invoked by the constructor for component if (contentPane != null) { contentPane.setSmoothScrolling(smoothScrolling); } } /** * @inheritDoc */ public boolean isSmoothScrolling() { return contentPane.isSmoothScrolling(); } /** * @inheritDoc */ public int getScrollAnimationSpeed() { return contentPane.getScrollAnimationSpeed(); } /** * @inheritDoc */ public void setScrollAnimationSpeed(int animationSpeed) { contentPane.setScrollAnimationSpeed(animationSpeed); } /** * Allows subclasses to bind functionality that occurs when * a specific form or dialog appears on the screen */ protected void onShow() { } /** * Allows subclasses to bind functionality that occurs when * a specific form or dialog is "really" showing hence when * the transition is totally complete (unlike onShow which is called * on intent). The necessity for this is for special cases like * media that might cause artifacts if played during a transition. */ protected void onShowCompleted() { } /** * This method shows the form as a modal alert allowing us to produce a behavior * of an alert/dialog box. This method will block the calling thread even if the * calling thread is the EDT. Notice that this method will not release the block * until dispose is called even if show() from another form is called! * <p>Modal dialogs Allow the forms "content" to "hang in mid air" this is especially useful for * dialogs where you would want the underlying form to "peek" from behind the * form. * * @param top space in pixels between the top of the screen and the form * @param bottom space in pixels between the bottom of the screen and the form * @param left space in pixels between the left of the screen and the form * @param right space in pixels between the right of the screen and the form * @param includeTitle whether the title should hang in the top of the screen or * be glued onto the content pane * @param modal indictes if this is a modal or modeless dialog true for modal dialogs */ void showModal(int top, int bottom, int left, int right, boolean includeTitle, boolean modal, boolean reverse) { Display.getInstance().flushEdt(); if (previousForm == null){ previousForm = Display.getInstance().getCurrent(); // special case for application opening with a dialog before any form is shown if (previousForm == null) { previousForm = new Form(); previousForm.show(); } else { if(previousForm instanceof Dialog) { Dialog previousDialog = (Dialog)previousForm; if(previousDialog.isDisposed()) { previousForm = Display.getInstance().getCurrentUpcoming(); } } } previousForm.tint = true; } Painter p = getStyle().getBgPainter(); if (top > 0 || bottom > 0 || left > 0 || right > 0) { Style titleStyle = title.getStyle(); Style contentStyle = contentPane.getUnselectedStyle(); if (includeTitle) { titleStyle.setMargin(Component.TOP, top, true); titleStyle.setMargin(Component.BOTTOM, 0, true); titleStyle.setMargin(Component.LEFT, left, true); titleStyle.setMargin(Component.RIGHT, right, true); contentStyle.setMargin(Component.TOP, 0, true); contentStyle.setMargin(Component.BOTTOM, bottom, true); contentStyle.setMargin(Component.LEFT, left, true); contentStyle.setMargin(Component.RIGHT, right, true); } else { titleStyle.setMargin(Component.TOP, 0, true); titleStyle.setMargin(Component.BOTTOM, 0, true); titleStyle.setMargin(Component.LEFT, 0, true); titleStyle.setMargin(Component.RIGHT, 0, true); contentStyle.setMargin(Component.TOP, top, true); contentStyle.setMargin(Component.BOTTOM, bottom, true); contentStyle.setMargin(Component.LEFT, left, true); contentStyle.setMargin(Component.RIGHT, right, true); } if (p instanceof BGPainter && ((BGPainter) p).getPreviousForm() != null) { ((BGPainter) p).setPreviousForm(previousForm); ((BGPainter) p).setParent(this); } else { BGPainter b = new BGPainter(this, p); b.setIgnorCoordinates(true); getStyle().setBgPainter(b); b.setPreviousForm(previousForm); } revalidate(); } initFocused(); if (getTransitionOutAnimator() == null && getTransitionInAnimator() == null) { initLaf(UIManager.getInstance().getLookAndFeel()); } initComponentImpl(); Display.getInstance().setCurrent(this, reverse); onShow(); if (modal) { // called to display a dialog and wait for modality Display.getInstance().invokeAndBlock(new RunnableWrapper(this, p, reverse)); // if the virtual keyboard was opend by the dialog close it if(Display.getInstance().isVirtualKeyboardShowingSupported()) { Display.getInstance().setShowVirtualKeyboard(false); } } } /** * The default version of show modal shows the dialog occupying the center portion * of the screen. */ void showModal(boolean reverse) { showDialog(true, reverse); } /** * The default version of show dialog shows the dialog occupying the center portion * of the screen. */ void showDialog(boolean modal, boolean reverse) { int h = Display.getInstance().getDisplayHeight() - menuBar.getPreferredH() - title.getPreferredH(); int w = Display.getInstance().getDisplayWidth(); int topSpace = h / 100 * 20; int bottomSpace = h / 100 * 10; int sideSpace = w / 100 * 20; showModal(topSpace, bottomSpace, sideSpace, sideSpace, true, modal, reverse); } /** * Works only for modal forms by returning to the previous form */ void dispose() { disposeImpl(); } boolean isDisposed() { return false; } /** * Works only for modal forms by returning to the previous form */ void disposeImpl() { if (previousForm != null) { previousForm.tint = false; if (previousForm instanceof Dialog) { if (!((Dialog) previousForm).isDisposed()) { Display.getInstance().setCurrent(previousForm, false); } } else { Display.getInstance().setCurrent(previousForm, false); } // enable GC to cleanup the previous form if no longer referenced previousForm = null; } } boolean isMenu() { return false; } /** * @inheritDoc */ void repaint(Component cmp) { if (isVisible()) { Display.getInstance().repaint(cmp); } } /** * @inheritDoc */ public final Form getComponentForm() { return this; } /** * Invoked by display to hide the menu during transition * * @see restoreMenu */ void hideMenu() { super.removeComponent(menuBar); } /** * Invoked by display to restore the menu after transition * * @see hideMenu */ void restoreMenu() { if (menuBar.getParent() == null) { super.addComponent(BorderLayout.SOUTH, menuBar); } } /** * Sets the focused component and fires the appropriate events to make it so * * @param focused the newly focused component or null for no focus */ public void setFocused(Component focused) { if (this.focused == focused && focused != null) { this.focused.repaint(); return; } Component oldFocus = this.focused; this.focused = focused; boolean triggerRevalidate = false; if (oldFocus != null) { triggerRevalidate = changeFocusState(oldFocus, false); //if we need to revalidate no need to repaint the Component, it will //be painted from the Form if (!triggerRevalidate && oldFocus.getParent() != null) { oldFocus.repaint(); } } // a listener might trigger a focus change event essentially // invalidating focus so we shouldn't break that if (focused != null && this.focused == focused) { triggerRevalidate = changeFocusState(focused, true) || triggerRevalidate; //if we need to revalidate no need to repaint the Component, it will //be painted from the Form if(!triggerRevalidate){ focused.repaint(); } } if(triggerRevalidate){ revalidate(); } } /** * This method changes the cmp state to be focused/unfocused and fires the * focus gained/lost events. * @param cmp the Component to change the focus state * @param gained if true this Component needs to gain focus if false * it needs to lose focus * @return this method returns true if the state change needs to trigger a * revalidate */ private boolean changeFocusState(Component cmp, boolean gained){ boolean trigger = false; Style selected = cmp.getSelectedStyle(); Style unselected = cmp.getUnselectedStyle(); //if selected style is different then unselected style there is a good //chance we need to trigger a revalidate if(!selected.getFont().equals(unselected.getFont()) || selected.getPadding(false, Component.TOP) != unselected.getPadding(false, Component.TOP) || selected.getPadding(false, Component.BOTTOM) != unselected.getPadding(false, Component.BOTTOM) || selected.getPadding(isRTL(), Component.RIGHT) != unselected.getPadding(isRTL(), Component.RIGHT) || selected.getPadding(isRTL(), Component.LEFT) != unselected.getPadding(isRTL(), Component.LEFT) || selected.getMargin(false, Component.TOP) != unselected.getMargin(false, Component.TOP) || selected.getMargin(false, Component.BOTTOM) != unselected.getMargin(false, Component.BOTTOM) || selected.getMargin(isRTL(), Component.RIGHT) != unselected.getMargin(isRTL(), Component.RIGHT) || selected.getMargin(isRTL(), Component.LEFT) != unselected.getMargin(isRTL(), Component.LEFT)){ trigger = true; } int prefW = 0; int prefH = 0; if(trigger){ Dimension d = cmp.getPreferredSize(); prefW = d.getWidth(); prefH = d.getHeight(); } if (gained) { cmp.setFocus(true); cmp.fireFocusGained(); fireFocusGained(cmp); } else { cmp.setFocus(false); cmp.fireFocusLost(); fireFocusLost(cmp); } //if the styles are different there is a chance the preffered size is //still the same therefore make sure there is a real need to preform //a revalidate if(trigger){ cmp.setShouldCalcPreferredSize(true); Dimension d = cmp.getPreferredSize(); if(prefW != d.getWidth() || prefH != d.getHeight()){ cmp.setShouldCalcPreferredSize(false); trigger = false; } } return trigger; } /** * Find the first focusable Component * * @param c a Container that holds potential Component * @return a focusable Component or null if not exists; */ private Component findFirstFocusable(Container c) { int size = c.getComponentCount(); for (int iter = 0; iter < size; iter++) { Component current = c.getComponentAt(iter); if(current.isFocusable()){ return current; } if (current instanceof Container) { Component cmp = findFirstFocusable((Container)current); if(cmp != null){ return cmp; } } } return null; } /** * Returns the current focus component for this form * * @return the current focus component for this form */ public Component getFocused() { return focused; } /** * @inheritDoc */ protected void longKeyPress(int keyCode) { if (focused != null) { if (focused.getComponentForm() == this) { focused.longKeyPress(keyCode); } } } /** * @inheritDoc */ protected void longPointerPress(int x, int y){ if (focused != null && focused.contains(x, y)) { if (focused.getComponentForm() == this) { focused.longPointerPress(x, y); } } } /** * @inheritDoc */ public void keyPressed(int keyCode) { int game = Display.getInstance().getGameAction(keyCode); if (keyCode == leftSK || (keyCode == rightSK || keyCode == rightSK2) || keyCode == backSK || (keyCode == clearSK && clearCommand != null) || (keyCode == backspaceSK && clearCommand != null) || (Display.getInstance().isThirdSoftButton() && game == Display.GAME_FIRE)) { menuBar.keyPressed(keyCode); return; } //Component focused = focusManager.getFocused(); if (focused != null) { focused.keyPressed(keyCode); if(focused == null) { initFocused(); return; } if (focused.handlesInput()) { return; } if (focused.getComponentForm() == this) { if (focused != null && focused.handlesInput()) { return; } //if the arrow keys have been pressed update the focus. updateFocus(Display.getInstance().getGameAction(keyCode)); } else { initFocused(); } } else { initFocused(); if(focused == null) { contentPane.moveScrollTowards(Display.getInstance().getGameAction(keyCode), null); return; } } } /** * @inheritDoc */ public Layout getLayout() { return contentPane.getLayout(); } /** * @inheritDoc */ public void keyReleased(int keyCode) { int game = Display.getInstance().getGameAction(keyCode); if (keyCode == leftSK || (keyCode == rightSK || keyCode == rightSK2) || keyCode == backSK || (keyCode == clearSK && clearCommand != null) || (keyCode == backspaceSK && clearCommand != null) || (Display.getInstance().isThirdSoftButton() && game == Display.GAME_FIRE)) { menuBar.keyReleased(keyCode); return; } //Component focused = focusManager.getFocused(); if (focused != null) { if (focused.getComponentForm() == this) { focused.keyReleased(keyCode); } } // prevent the default action from stealing the behavior from the popup/combo box... if (game == Display.GAME_FIRE) { Command defaultCmd = getDefaultCommand(); if (defaultCmd != null) { defaultCmd.actionPerformed(new ActionEvent(defaultCmd, keyCode)); actionCommandImpl(defaultCmd); } } fireKeyEvent(keyListeners, keyCode); fireKeyEvent(gameKeyListeners, game); } private void fireKeyEvent(Hashtable keyListeners, int keyCode) { if (keyListeners != null) { Vector listeners = (Vector) keyListeners.get(new Integer(keyCode)); if (listeners != null) { ActionEvent evt = new ActionEvent(this, keyCode); for (int iter = 0; iter < listeners.size(); iter++) { ((ActionListener) listeners.elementAt(iter)).actionPerformed(evt); if (evt.isConsumed()) { return; } } } } } /** * @inheritDoc */ public void keyRepeated(int keyCode) { if (focused != null) { focused.keyRepeated(keyCode); int game = Display.getInstance().getGameAction(keyCode); // this has issues in the WTK if (!focused.handlesInput() && (game == Display.GAME_DOWN || game == Display.GAME_UP || game == Display.GAME_LEFT || game == Display.GAME_RIGHT)) { keyPressed(keyCode); keyReleased(keyCode); } } else { keyPressed(keyCode); keyReleased(keyCode); } } private void tactileTouchVibe(int x, int y, Component cmp) { if(tactileTouchDuration > 0 && cmp.isTactileTouch(x, y)) { Display.getInstance().vibrate(tactileTouchDuration); } } /** * @inheritDoc */ public void pointerPressed(int x, int y) { if(pointerPressedListeners != null) { pointerPressedListeners.fireActionEvent(new ActionEvent(this, x, y)); } //if there is no popup on the screen an click is relevant to the menu bar. if (menuBar.contains(x, y)) { Component cmp = menuBar.getComponentAt(x, y); if (cmp != null) { cmp.pointerPressed(x, y); tactileTouchVibe(x, y, cmp); } return; } Component cmp = contentPane.getComponentAt(x, y); if (cmp != null && cmp.isFocusable()) { setFocused(cmp); cmp.pointerPressed(x, y); tactileTouchVibe(x, y, cmp); } } /** * Adds a listener to the pointer event * * @param l callback to receive pointer events */ public void addPointerPressedListener(ActionListener l) { if(pointerPressedListeners == null) { pointerPressedListeners = new EventDispatcher(); } pointerPressedListeners.addListener(l); } /** * Removes the listener from the pointer event * * @param l callback to remove */ public void removePointerPressedListener(ActionListener l) { if(pointerPressedListeners != null) { pointerPressedListeners.removeListener(l); } } /** * Adds a listener to the pointer event * * @param l callback to receive pointer events */ public void addPointerReleasedListener(ActionListener l) { if(pointerReleasedListeners == null) { pointerReleasedListeners = new EventDispatcher(); } pointerReleasedListeners.addListener(l); } /** * Removes the listener from the pointer event * * @param l callback to remove */ public void removePointerReleasedListener(ActionListener l) { if(pointerReleasedListeners != null) { pointerReleasedListeners.removeListener(l); } } /** * Adds a listener to the pointer event * * @param l callback to receive pointer events */ public void addPointerDraggedListener(ActionListener l) { if(pointerDraggedListeners == null) { pointerDraggedListeners = new EventDispatcher(); } pointerDraggedListeners.addListener(l); } /** * Removes the listener from the pointer event * * @param l callback to remove */ public void removePointerDraggedListener(ActionListener l) { if(pointerDraggedListeners != null) { pointerDraggedListeners.removeListener(l); } } /** * @inheritDoc */ public void pointerDragged(int x, int y) { if(pointerDraggedListeners != null) { pointerDraggedListeners.fireActionEvent(new ActionEvent(this, x, y)); } if (dragged != null) { dragged.pointerDragged(x, y); return; } Component cmp = contentPane.getComponentAt(x, y); if (cmp != null) { if (cmp.isFocusable()) { setFocused(cmp); } cmp.pointerDragged(x, y); cmp.repaint(); } } /** * @inheritDoc */ public void pointerHoverReleased(int[] x, int[] y) { Component cmp = contentPane.getComponentAt(x[0], y[0]); if (cmp != null) { if (cmp.isFocusable()) { setFocused(cmp); } cmp.pointerHoverReleased(x, y); cmp.repaint(); } } /** * @inheritDoc */ public void pointerHover(int[] x, int[] y) { Component cmp = contentPane.getComponentAt(x[0], y[0]); if (cmp != null) { if (cmp.isFocusable()) { setFocused(cmp); } cmp.pointerHover(x, y); cmp.repaint(); } } /** * Returns true if there is only one focusable member in this form. This is useful * so setHandlesInput would always be true for this case. * * @return true if there is one focusable component in this form, false for 0 or more */ public boolean isSingleFocusMode() { initFocusDown(); return focusDownSequence.size() == 1; } /** * @inheritDoc */ public void pointerReleased(int x, int y) { if(pointerReleasedListeners != null) { pointerReleasedListeners.fireActionEvent(new ActionEvent(this, x, y)); } if (dragged == null) { //if the pointer was released on the menu invoke the appropriate //soft button. if (menuBar.contains(x, y)) { Component cmp = menuBar.getComponentAt(x, y); if (cmp != null) { cmp.pointerReleased(x, y); } return; } Component cmp = contentPane.getComponentAt(x, y); if (cmp != null) { if (cmp.isFocusable()) { setFocused(cmp); } cmp.pointerReleased(x, y); } } else { dragged.pointerReleased(x, y); dragged = null; } } /** * @inheritDoc */ public void setScrollableY(boolean scrollableY) { getContentPane().setScrollableY(scrollableY); } /** * @inheritDoc */ public void setScrollableX(boolean scrollableX) { getContentPane().setScrollableX(scrollableX); } /** * @inheritDoc */ public int getComponentIndex(Component cmp) { return getContentPane().getComponentIndex(cmp); } /** * Adds a command to the menu bar softkeys or into the menu dialog, * this version of add allows us to place a command in an arbitrary location. * This allows us to force a command into the softkeys when order of command * addition can't be changed. * * @param cmd the Form command to be added * @param offset position in which the command is added */ public void addCommand(Command cmd, int offset) { menuBar.addCommand(cmd, offset); } /** * A helper method to check the amount of commands within the form menu * * @return the number of commands */ public int getCommandCount() { return menuBar.getCommandCount(); } /** * Returns the command occupying the given index * * @param index offset of the command * @return the command at the given index */ public Command getCommand(int index) { return menuBar.getCommand(index); } /** * Adds a command to the menu bar softkeys. * The Commands are placed in the order they are added. * If the Form has 1 Command it will be placed on the right. * If the Form has 2 Commands the first one that was added will be placed on * the right and the second one will be placed on the left. * If the Form has more then 2 Commands the first one will stay on the left * and a Menu will be added with all the remain Commands. * * @param cmd the Form command to be added */ public void addCommand(Command cmd) { menuBar.addCommand(cmd); } /** * Removes the command from the menu bar softkeys * * @param cmd the Form command to be removed */ public void removeCommand(Command cmd) { menuBar.removeCommand(cmd); } /** * Indicates whether focus should cycle within the form * * @param cyclicFocus marks whether focus should cycle */ public void setCyclicFocus(boolean cyclicFocus) { this.cyclicFocus = cyclicFocus; } /** * Indicates whether focus should cycle within the form * * @return true if focus should cycle */ public boolean isCyclicFocus() { return cyclicFocus; } private void updateFocus(int gameAction) { Component focused = getFocused(); switch (gameAction) { case Display.GAME_DOWN: { Component down = focused.getNextFocusDown(); if ( down != null && down.getComponentForm() == this) { focused = down; } else { initFocusDown(); int i = focusDownSequence.indexOf(focused) + 1; if (focusDownSequence.size() > 0) { if (i == focusDownSequence.size()) { if (cyclicFocus) { i = 0; } else { i = focusDownSequence.size() - 1; } } focused = (Component) focusDownSequence.elementAt(i); } } break; } case Display.GAME_UP: { Component up = focused.getNextFocusUp(); if (up != null && up.getComponentForm() == this) { focused = up; } else { initFocusDown(); if (focusDownSequence.size() > 0) { int i = focusDownSequence.indexOf(focused) - 1; if (i < 0) { if (cyclicFocus) { i = focusDownSequence.size() - 1; } else { i = 0; } } focused = (Component) focusDownSequence.elementAt(i); } } break; } case Display.GAME_RIGHT: { Component right = focused.getNextFocusRight(); if (right != null && right.getComponentForm() == this) { focused = right; } else { initFocusRight(); if (focusRightSequence.size() > 0) { int i = focusRightSequence.indexOf(focused) + 1; if (i == focusRightSequence.size()) { if (cyclicFocus) { i = 0; } else { i = focusRightSequence.size() - 1; } } focused = (Component) focusRightSequence.elementAt(i); } } break; } case Display.GAME_LEFT: { Component left = focused.getNextFocusLeft(); if (left != null && left.getComponentForm() == this) { focused = left; } else { initFocusRight(); if (focusRightSequence.size() > 0) { int i = focusRightSequence.indexOf(focused) - 1; if (i < 0) { if (cyclicFocus) { i = focusRightSequence.size() - 1; } else { i = 0; } } focused = (Component) focusRightSequence.elementAt(i); } } break; } default: return; } //if focused is now visible we need to give it the focus. if (isFocusScrolling()) { setFocused(focused); if (focused != null) { scrollComponentToVisible(focused); } } else { if (moveScrollTowards(gameAction, focused)) { setFocused(focused); } } } /** * @inheritDoc */ boolean moveScrollTowards(int direction, Component c) { //if the current focus item is in a scrollable Container //try and move it first Component current = getFocused(); if(current != null){ Container parent; if(current instanceof Container){ parent = (Container) current; }else{ parent = current.getParent(); } while (parent != null) { if (parent.isScrollable()) { return parent.moveScrollTowards(direction, c); } parent = parent.getParent(); } } return true; } /** * Makes sure the component is visible in the scroll if this container * is scrollable * * @param c the componant to be visible */ public void scrollComponentToVisible(Component c) { initFocused(); Container parent = c.getParent(); while (parent != null) { if (parent.isScrollable()) { parent.scrollComponentToVisible(c); return; } parent = parent.getParent(); } } /** * Determine the cell renderer used to render menu elements for themeing the * look of the menu options * * @param menuCellRenderer the menu cell renderer */ public void setMenuCellRenderer(ListCellRenderer menuCellRenderer) { menuBar.setMenuCellRenderer(menuCellRenderer); } /** * Clear menu commands from the menu bar */ public void removeAllCommands() { menuBar.removeAllCommands(); } /** * Request focus for a form child component * * @param cmp the form child component */ void requestFocus(Component cmp) { if (cmp.isFocusable() && contains(cmp)) { scrollComponentToVisible(cmp); setFocused(cmp); } } /** * Factory method that returns the Form select Command. * This Command is used when Display.getInstance().isThirdSoftButton() * returns true. * This method can be overridden to customize the Command on the Form. * * @return Command */ protected Command createSelectCommand(){ return new Command(UIManager.getInstance().localize("select", "Select")); } /** * Factory method that returns the Form Menu select Command. * This method can be overridden to customize the Command on the Form. * * @return Command */ protected Command createMenuSelectCommand(){ LookAndFeel lf = UIManager.getInstance().getLookAndFeel(); return new Command(UIManager.getInstance().localize("select", "Select"), lf.getMenuIcons()[0]); } /** * @inheritDoc */ public void setRTL(boolean r) { super.setRTL(r); contentPane.setRTL(r); } /** * Factory method that returns the Form Menu cancel Command. * This method can be overridden to customize the Command on the Form. * * @return Command */ protected Command createMenuCancelCommand(){ LookAndFeel lf = UIManager.getInstance().getLookAndFeel(); return new Command(UIManager.getInstance().localize("cancel", "Cancel"), lf.getMenuIcons()[1]); } /** * @inheritDoc */ public void paint(Graphics g) { paintBackground(g); super.paint(g); if (tint) { g.setColor(tintColor); g.fillRect(0, 0, getWidth(), getHeight(), (byte) ((tintColor >> 24) & 0xff)); } } /** * @inheritDoc */ public void setScrollable(boolean scrollable) { contentPane.setScrollable(scrollable); } /** * @inheritDoc */ public void setVisible(boolean visible) { super.setVisible(visible); if (mediaComponents != null) { int size = mediaComponents.size(); for (int i = 0; i < size; i++) { Component mediaCmp = (Component) mediaComponents.elementAt(i); mediaCmp.setVisible(visible); } } } /** * Default color for the screen tint when a dialog or a menu is shown * * @return the tint color when a dialog or a menu is shown */ public int getTintColor() { return tintColor; } /** * Default color for the screen tint when a dialog or a menu is shown * * @param tintColor the tint color when a dialog or a menu is shown */ public void setTintColor(int tintColor) { this.tintColor = tintColor; } void addSelectCommand(String selectText) { if (Display.getInstance().isThirdSoftButton()) { if (selectCommand == null) { selectCommand = createSelectCommand(); } selectCommand.setCommandName(selectText); addCommand(selectCommand); } } void removeSelectCommand() { if (Display.getInstance().isThirdSoftButton()) { removeCommand(selectCommand); } } /** * Sets the menu transitions for showing/hiding the menu, can be null... * * @param transitionIn the transition that will play when the menu appears * @param transitionOut the transition that will play when the menu is folded */ public void setMenuTransitions(Transition transitionIn, Transition transitionOut) { menuBar.setTransitions(transitionIn, transitionOut); } /** * @inheritDoc */ protected String paramString() { return super.paramString() + ", title = " + title + ", visible = " + isVisible(); } /** * A menu is implemented as a dialog, this method allows you to override dialog * display in order to customize the dialog menu in various ways * * @param menu a dialog containing menu options that can be customized * @return the command selected by the user in the dialog (not menu) Select or Cancel */ protected Command showMenuDialog(Dialog menu) { int marginLeft = (int) (Form.this.getWidth() * 0.25f); int marginRight = 0; if (isReverseSoftButtons()) { marginRight = marginLeft; marginLeft = 0; } int height = Form.this.getHeight() / 2; if(UIManager.getInstance().getLookAndFeel().isTouchMenus()) { marginLeft = 0; marginRight = 0; height = Math.max(Form.this.getHeight() / 4, getContentPane().getHeight() - getTitleComponent().getHeight() - menu.getContentPane().getPreferredH() - menu.getStyle().getMargin(TOP) - menu.getStyle().getMargin(BOTTOM)); } return menu.show(height, 0, marginLeft, marginRight, true); } /** * Allows an individual form to reverse the layout direction of the softbuttons, this method is RTL * sensitive and might reverse the result based on RTL state * * @return The value of UIManager.getInstance().getLookAndFeel().isReverseSoftButtons() */ protected boolean isReverseSoftButtons() { LookAndFeel lf = UIManager.getInstance().getLookAndFeel(); if(isRTL()) { return !lf.isReverseSoftButtons(); } return lf.isReverseSoftButtons(); } /** * Calculates the amount of columns to give to the touch commands within the grid * * @param grid container that will be arranged in the grid containing the components * @return an integer representing the touch command grid size */ protected int calculateTouchCommandGridColumns(Container grid) { int count = grid.getComponentCount(); int maxWidth = 0; for(int iter = 0 ; iter < count ; iter++) { Component c = grid.getComponentAt(iter); Style s = c.getUnselectedStyle(); // bidi doesn't matter since this is just a summary of width maxWidth = Math.max(maxWidth, c.getPreferredW() + s.getPadding(false, LEFT) + s.getPadding(false, RIGHT) + s.getMargin(false, LEFT) + s.getMargin(false, RIGHT)); } return Math.max(2, getWidth() / maxWidth); } /** * Creates a touch command for use as a touch menu item * * @param c command to map into the returned button * @return a button that would fire the touch command appropriately */ protected Button createTouchCommandButton(Command c) { Button b = new Button(c); b.setTactileTouch(true); b.setTextPosition(Label.BOTTOM); b.setAlignment(CENTER); b.setUIID("TouchCommand"); return b; } /** * Creates the component containing the commands within the given vector * used for showing the menu dialog, this method calls the createCommandList * method by default however it allows more elaborate menu creation. * * @param commands list of command objects * @return Component that will result in the parent menu dialog recieving a command event */ protected Component createCommandComponent(Vector commands) { // Create a touch based menu interface if(UIManager.getInstance().getLookAndFeel().isTouchMenus()) { Container menu = new Container(); for(int iter = 0 ; iter < commands.size() ; iter++) { Command c = (Command)commands.elementAt(iter); menu.addComponent(createTouchCommandButton(c)); } int cols = calculateTouchCommandGridColumns(menu); menu.setLayout(new GridLayout(Math.max(1, commands.size() / cols + commands.size() % cols != 0 ? 1:0 ), cols)); return menu; } return createCommandList(commands); } /** * Creates the list component containing the commands within the given vector * used for showing the menu dialog * * @param commands list of command objects * @return List object */ protected List createCommandList(Vector commands) { List l = new List(commands); l.setUIID("CommandList"); Component c = (Component) l.getRenderer(); c.setUIID("Command"); c = l.getRenderer().getListFocusComponent(l); c.setUIID("CommandFocus"); l.setFixedSelection(List.FIXED_NONE_CYCLIC); return l; } Command getComponentSelectedCommand(Component cmp) { if(cmp instanceof List) { List l = (List)cmp; return (Command) l.getSelectedItem(); } else { cmp = cmp.getComponentForm().getFocused(); if(cmp instanceof Button) { return ((Button)cmp).getCommand(); } } // nothing to do for this case... return null; } MenuBar getMenuBar() { return menuBar; } /** * Indicates whether lists and containers should scroll only via focus and thus "jump" when * moving to a larger component as was the case in older versions of LWUIT. * * @return the value of focusScrolling */ public boolean isFocusScrolling() { return focusScrolling; } /** * Indicates whether lists and containers should scroll only via focus and thus "jump" when * moving to a larger component as was the case in older versions of LWUIT. * * @param focusScrolling the new value for focus scrolling */ public void setFocusScrolling(boolean focusScrolling) { this.focusScrolling = focusScrolling; } class MenuBar extends Container implements ActionListener { private Command menuCommand; private Vector commands = new Vector(); private Button[] soft; private Command[] softCommand; private Button left; private Button right; private Button main; private ListCellRenderer menuCellRenderer; private Transition transitionIn; private Transition transitionOut; private Component commandList; private Style menuStyle; private int topMargin, bottomMargin; public MenuBar() { LookAndFeel lf = UIManager.getInstance().getLookAndFeel(); menuStyle = UIManager.getInstance().getComponentStyle("Menu"); setUnSelectedStyle(UIManager.getInstance().getComponentStyle("SoftButton")); menuCommand = new Command(UIManager.getInstance().localize("menu", "Menu"), lf.getMenuIcons()[2]); // use the slide transition by default if (lf.getDefaultMenuTransitionIn() != null || lf.getDefaultMenuTransitionOut() != null) { transitionIn = lf.getDefaultMenuTransitionIn(); transitionOut = lf.getDefaultMenuTransitionOut(); } else { transitionIn = CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, true, 300, true); transitionOut = CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, false, 300, true); } menuCellRenderer = lf.getMenuRenderer(); if (Display.getInstance().getImplementation().getSoftkeyCount() > 1) { if (Display.getInstance().isThirdSoftButton()) { setLayout(new GridLayout(1, 3)); soft = new Button[]{createSoftButton(), createSoftButton(), createSoftButton()}; main = soft[0]; main.setAlignment(Label.CENTER); left = soft[1]; right = soft[2]; if (Form.this.isRTL()) { addComponent(right); addComponent(main); addComponent(left); } else { addComponent(left); addComponent(main); addComponent(right); } if (isReverseSoftButtons()) { Button b = soft[1]; soft[1] = soft[2]; soft[2] = b; } } else { setLayout(new GridLayout(1, 2)); soft = new Button[]{createSoftButton(), createSoftButton()}; main = soft[0]; left = soft[0]; right = soft[1]; if (Form.this.isRTL()) { addComponent(right); addComponent(left); } else { addComponent(left); addComponent(right); } if (isReverseSoftButtons()) { Button b = soft[0]; soft[0] = soft[1]; soft[1] = b; } } // It doesn't make sense for softbuttons to have ... at the end for(int iter = 0 ; iter < soft.length ; iter++) { soft[iter].setEndsWith3Points(false); } } else { // special case for touch screens we still want the 3 softbutton areas... if(Display.getInstance().isThirdSoftButton()) { setLayout(new GridLayout(1, 3)); soft = new Button[]{createSoftButton(), createSoftButton(), createSoftButton()}; main = soft[0]; main.setAlignment(Label.CENTER); left = soft[1]; right = soft[2]; addComponent(left); addComponent(main); addComponent(right); if (isReverseSoftButtons()) { Button b = soft[1]; soft[1] = soft[2]; soft[2] = b; } } else { soft = new Button[]{createSoftButton()}; } } if (left != null) { if (Form.this.isRTL()) { left.setAlignment(Label.RIGHT); right.setAlignment(Label.LEFT); } else { left.setAlignment(Label.LEFT); right.setAlignment(Label.RIGHT); } } softCommand = new Command[soft.length]; } /** * Updates the command mapping to the softbuttons */ private void updateCommands() { if (soft.length > 1) { soft[0].setText(""); soft[1].setText(""); soft[0].setIcon(null); soft[1].setIcon(null); int commandSize = commands.size(); if (soft.length > 2) { soft[2].setText(""); if (commandSize > 2) { if (commandSize > 3) { softCommand[2] = menuCommand; } else { softCommand[2] = (Command) commands.elementAt(commands.size() - 3); } soft[2].setText(softCommand[2].getCommandName()); soft[2].setIcon(softCommand[2].getIcon()); } else { softCommand[2] = null; } } if (commandSize > 0) { softCommand[0] = (Command) commands.elementAt(commands.size() - 1); soft[0].setText(softCommand[0].getCommandName()); soft[0].setIcon(softCommand[0].getIcon()); if (commandSize > 1) { if (soft.length == 2 && commandSize > 2) { softCommand[1] = menuCommand; } else { softCommand[1] = (Command) commands.elementAt(commands.size() - 2); } soft[1].setText(softCommand[1].getCommandName()); soft[1].setIcon(softCommand[1].getIcon()); } else { softCommand[1] = null; } } else { softCommand[0] = null; softCommand[1] = null; } // we need to add the menu bar to an already visible form if (commandSize == 1) { if (Form.this.isVisible()) { Form.this.revalidate(); } } repaint(); } } /** * Invoked when a softbutton is pressed */ public void actionPerformed(ActionEvent evt) { if (evt.isConsumed()) { return; } Object src = evt.getSource(); if (commandList == null) { Button source = (Button) src; for (int iter = 0; iter < soft.length; iter++) { if (source == soft[iter]) { if (softCommand[iter] == menuCommand) { showMenu(); return; } if (softCommand[iter] != null) { ActionEvent e = new ActionEvent(softCommand[iter]); softCommand[iter].actionPerformed(e); if (!e.isConsumed()) { actionCommandImpl(softCommand[iter]); } } return; } } } else { // the list for the menu sent the event if (src instanceof Button) { for (int iter = 0; iter < soft.length; iter++) { if (src == soft[iter]) { Container parent = commandList.getParent(); while (parent != null) { if (parent instanceof Dialog) { ((Dialog) parent).actionCommand(softCommand[iter]); return; } parent = parent.getParent(); } } } } Command c = getComponentSelectedCommand(commandList); Container parent = commandList.getParent(); while (parent != null) { if (parent instanceof Dialog) { ((Dialog) parent).actionCommand(c); return; } parent = parent.getParent(); } } } private Button createSoftButton() { Button b = new Button(); b.addActionListener(this); b.setFocusPainted(false); b.setFocusable(false); b.setTactileTouch(true); updateSoftButtonStyle(b); return b; } private void updateSoftButtonStyle(Button b) { Style s = new Style(getUnselectedStyle()); b.setUnselectedStyle(s); b.setPressedStyle(s); s.setBgImage(null); s.setBorder(null); s.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED); // remove the padding/margin when we don't have softbuttons if(Display.getInstance().getImplementation().getSoftkeyCount() > 1) { s.setMargin(topMargin, bottomMargin, 2, 2); } else { s.setMargin(0, 0, 0, 0); s.setPadding(0, 0, 0, 0); } s.setBgTransparency(0); } public void setUnSelectedStyle(Style style) { topMargin = style.getMargin(false, Component.TOP); bottomMargin = style.getMargin(false, Component.BOTTOM); style.setMargin(Component.TOP, 0, true); style.setMargin(Component.BOTTOM, 0, true); super.setUnSelectedStyle(style); if(soft != null){ for (int iter = 0; iter < soft.length; iter++) { updateSoftButtonStyle(soft[iter]); } } } /** * Prevents scaling down of the menu when there is no text on the menu bar */ protected Dimension calcPreferredSize() { if (soft.length > 1) { Dimension d = super.calcPreferredSize(); if ((soft[0].getText() == null || soft[0].getText().equals("")) && (soft[1].getText() == null || soft[1].getText().equals("")) && soft[0].getIcon() == null && soft[1].getIcon() == null && (soft.length < 3 || ((soft[2].getText() == null || soft[2].getText().equals("")) && soft[2].getIcon() == null)) ) { d.setHeight(0); } return d; } return super.calcPreferredSize(); } /** * Sets the menu transitions for showing/hiding the menu, can be null... */ public void setTransitions(Transition transitionIn, Transition transitionOut) { this.transitionIn = transitionIn; this.transitionOut = transitionOut; } private void showMenu() { final Dialog d = new Dialog(); d.setDisposeWhenPointerOutOfBounds(true); d.setDialogStyle(new Style(menuStyle)); d.setMenu(true); d.setSoftButtonStyle(new Style(getSoftButtonStyle())); menuStyle.removeStyleListener(d.getContentPane()); d.setTransitionInAnimator(transitionIn); d.setTransitionOutAnimator(transitionOut); d.setLayout(new BorderLayout()); d.setScrollable(false); ((Form) d).menuBar.commandList = createCommandComponent(commands); if (menuCellRenderer != null && ((Form) d).menuBar.commandList instanceof List) { ((List)((Form) d).menuBar.commandList).setListCellRenderer(menuCellRenderer); } d.getContentPane().getStyle().setMargin(0, 0, 0, 0); d.addComponent(BorderLayout.CENTER, ((Form) d).menuBar.commandList); if (Display.getInstance().isThirdSoftButton()) { d.addCommand(selectMenuItem); d.addCommand(cancelMenuItem); } else { d.addCommand(cancelMenuItem); if(soft.length > 1) { d.addCommand(selectMenuItem); } } d.setClearCommand(cancelMenuItem); d.setBackCommand(cancelMenuItem); if(((Form) d).menuBar.commandList instanceof List) { ((List)((Form) d).menuBar.commandList).addActionListener(((Form) d).menuBar); } Command result = showMenuDialog(d); if (result != cancelMenuItem) { Command c = null; if (result == selectMenuItem) { c = getComponentSelectedCommand(((Form) d).menuBar.commandList); if (c != null) { ActionEvent e = new ActionEvent(c); c.actionPerformed(e); } } else { c = result; // a touch menu will always send its commands on its own... if(!UIManager.getInstance().getLookAndFeel().isTouchMenus()) { c = result; if (c != null) { ActionEvent e = new ActionEvent(c); c.actionPerformed(e); } } } // menu item was handled internally in a touch interface that is not a touch menu if (c != null) { actionCommandImpl(c); } } if(((Form) d).menuBar.commandList instanceof List) { ((List)((Form) d).menuBar.commandList).removeActionListener(((Form) d).menuBar); } Form upcoming = Display.getInstance().getCurrentUpcoming(); if (upcoming == Form.this) { d.disposeImpl(); } else { Form.this.tint = (upcoming instanceof Dialog); } } public Button[] getSoftButtons() { return soft; } public String getUIID() { return "SoftButton"; } public void addCommand(Command cmd) { // prevent duplicate commands which might happen in some edge cases // with the select command if (commands.contains(cmd)) { return; } // special case for default commands which are placed at the end and aren't overriden later if (soft.length > 2 && cmd == getDefaultCommand()) { commands.addElement(cmd); } else { commands.insertElementAt(cmd, 0); } updateCommands(); } /** * Returns the command occupying the given index * * @param index offset of the command * @return the command at the given index */ public Command getCommand(int index) { return (Command) commands.elementAt(index); } public int getCommandCount() { return commands.size(); } public void addCommand(Command cmd, int index) { // prevent duplicate commands which might happen in some edge cases // with the select command if (commands.contains(cmd)) { return; } commands.insertElementAt(cmd, index); updateCommands(); } public void removeAllCommands() { commands.removeAllElements(); updateCommands(); } public void removeCommand(Command cmd) { commands.removeElement(cmd); updateCommands(); } public void setMenuCellRenderer(ListCellRenderer menuCellRenderer) { this.menuCellRenderer = menuCellRenderer; } public Style getMenuStyle() { return menuStyle; } public void keyPressed(int keyCode) { if (commands.size() > 0) { if (keyCode == leftSK) { if (left != null) { left.pressed(); } } else { // it might be a back command or the fire... if ((keyCode == rightSK || keyCode == rightSK2)) { if (right != null) { right.pressed(); } } else { if (Display.getInstance().getGameAction(keyCode) == Display.GAME_FIRE) { main.pressed(); } } } } } public void keyReleased(int keyCode) { if (commands.size() > 0) { if (Display.getInstance().getImplementation().getSoftkeyCount() < 2 && keyCode == leftSK) { if (commandList != null) { Container parent = commandList.getParent(); while (parent != null) { if (parent instanceof Dialog && ((Dialog) parent).isMenu()) { return; } parent = parent.getParent(); } } showMenu(); return; } else { if (keyCode == leftSK) { if (left != null) { left.released(); } return; } else { // it might be a back command... if ((keyCode == rightSK || keyCode == rightSK2)) { if (right != null) { right.released(); } return; } else { if (Display.getInstance().getGameAction(keyCode) == Display.GAME_FIRE) { main.released(); return; } } } } } // allows a back/clear command to occur regardless of whether the // command was added to the form Command c = null; if (keyCode == backSK) { // the back command should be invoked c = getBackCommand(); } else { if (keyCode == clearSK || keyCode == backspaceSK) { c = getClearCommand(); } } if (c != null) { ActionEvent ev = new ActionEvent(c, keyCode); c.actionPerformed(ev); if(!ev.isConsumed()) { actionCommandImpl(c); } } } public void refreshTheme() { super.refreshTheme(); if (menuStyle.isModified()) { menuStyle.merge(UIManager.getInstance().getComponentStyle("Menu")); } else { menuStyle = UIManager.getInstance().getComponentStyle("Menu"); } if (menuCellRenderer != null) { List tmp = new List(); tmp.setListCellRenderer(menuCellRenderer); tmp.refreshTheme(); } for (int iter = 0; iter < soft.length; iter++) { updateSoftButtonStyle(soft[iter]); } revalidate(); } } }