/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores * CA 94065 USA or visit www.oracle.com if you need additional information or * have any questions. */ package com.sun.lwuit; import com.sun.lwuit.animations.Animation; 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.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 { private Painter glassPane; private Container contentPane = new Container(new FlowLayout()); Container titleArea = new Container(new BorderLayout()); private Label title = new Label("", "Title"); private MenuBar menuBar; private Component dragged; private Component dragOver; /** * 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; /** * Contains a list of components that would like to animate their state */ private Vector internalAnimatableComponents; /** * Contains a list of components that would like to animate their state */ private Vector animatableComponents; //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 */ boolean tint; /** * Default color for the screen tint when a dialog or a menu is shown */ private int tintColor; /** * 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; private EventDispatcher showListener; int initialPressX; int initialPressY; private EventDispatcher orientationListener; /** * 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)); super.setAlwaysTensile(false); title.setEndsWith3Points(false); titleArea.addComponent(BorderLayout.CENTER, title); titleArea.setUIID("TitleArea"); addComponentToForm(BorderLayout.NORTH, titleArea); addComponentToForm(BorderLayout.CENTER, contentPane); contentPane.setUIID("ContentPane"); contentPane.setScrollableY(true); LookAndFeel laf = UIManager.getInstance().getLookAndFeel(); initLaf(laf); focusScrolling = laf.isFocusScrolling(); try { menuBar = (MenuBar)laf.getMenuBarClass().newInstance(); } catch (Exception ex) { ex.printStackTrace(); menuBar = new MenuBar(); } menuBar.initMenuBar(this); tintColor = laf.getDefaultFormTintColor(); // hardcoded, anything else is just pointless... formStyle.setBgTransparency(0xFF); } /** * @inheritDoc */ public boolean isAlwaysTensile() { return getContentPane().isAlwaysTensile(); } /** * @inheritDoc */ public void setAlwaysTensile(boolean alwaysTensile) { getContentPane().setAlwaysTensile(alwaysTensile); } /** * Title area manipulation might break with future changes to LWUIT and might * damage themeing/functionality of the LWUIT application in some platforms * * @return the container containing the title */ public Container getTitleArea() { return titleArea; } /** * This listener would be invoked when show is completed * * @param l listener */ public void addShowListener(ActionListener l) { if(showListener == null) { showListener = new EventDispatcher(); } showListener.addListener(l); } /** * Removes the show listener * * @param l the listener */ public void removeShowListener(ActionListener l) { if(showListener == null) { return; } showListener.removeListener(l); } /** * This listener is invoked when device orientation changes on devices that support orientation change * * @param l listener */ public void addOrientationListener(ActionListener l) { if(orientationListener == null) { orientationListener = new EventDispatcher(); } orientationListener.addListener(l); } /** * This listener is invoked when device orientation changes on devices that support orientation change * * @param l the listener */ public void removeOrientationListener(ActionListener l) { if(orientationListener == null) { return; } orientationListener.removeListener(l); } /** * 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() { setVisible(false); } /** * 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() { setVisible(true); } /** * 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); if(orientationListener != null) { orientationListener.fireActionEvent(new ActionEvent(this)); } Style formStyle = getStyle(); w = w - (formStyle.getMargin(isRTL(), Component.LEFT) + formStyle.getMargin(isRTL(), Component.RIGHT)); h = h - (formStyle.getMargin(false, Component.TOP) + formStyle.getMargin(false, Component.BOTTOM)); 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(getParent() != null) { super.paintGlassImpl(g); return; } 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); if(dragged != null && dragged.isDragAndDropInitialized()) { int[] c = g.getClip(); g.setClip(0, 0, getWidth(), getHeight()); dragged.drawDraggedImage(g); g.setClip(c); } } /** * 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 * @deprecated this method doesn't take into consideration multiple styles */ 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) { titleArea.replace(this.title, title, false); 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) { titleArea.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(); } /** * Sets the current dragged Component */ void setDraggedComponent(Component dragged) { this.dragged = dragged; } /** * 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); } /** * 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(), 0, 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(0, source.getAbsoluteY(), Integer.MAX_VALUE, source.getHeight(), dest.getAbsoluteX(), dest.getAbsoluteY(), dest.getWidth(), dest.getHeight()); } /** * 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) { menuBar.setDefaultCommand(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() { return menuBar.getDefaultCommand(); } /** * 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) { menuBar.setClearCommand(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 menuBar.getClearCommand(); } /** * 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) { menuBar.setBackCommand(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 menuBar.getBackCommand(); } /** * 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 * @deprecated Use the style directly */ 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()) { int b =Display.getInstance().getCommandBehavior(); if(b == Display.COMMAND_BEHAVIOR_BUTTON_BAR_TITLE_BACK || b == Display.COMMAND_BEHAVIOR_BUTTON_BAR_TITLE_RIGHT) { titleArea.revalidate(); } 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); } void addComponentToForm(Object constraints, Component cmp) { super.addComponent(constraints, cmp); } void removeComponentFromForm(Component cmp) { super.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); } } /** * @inheritDoc */ public boolean animate() { if(getParent() != null) { repaintAnimations(); } return super.animate(); } /** * 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 (titleArea != null) { titleArea.refreshTheme(); } super.refreshTheme(); // when changing the theme the menu behavior might also change hideMenu(); restoreMenu(); Command[] cmds = new Command[getCommandCount()]; for(int iter = 0 ; iter < cmds.length ; iter++) { cmds[iter] = getCommand(iter); } removeAllCommands(); for(int iter = 0 ; iter < cmds.length ; iter++) { addCommand(cmds[iter], getCommandCount()); } revalidate(); } /** * 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 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 == menuBar.getCancelMenuItem()) { actionCommand(cmd); return; } Component c = getFocused(); if (c != null) { c.fireClicked(); } return; } if (cmd != menuBar.getSelectCommand()) { 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(contentPane.findFirstFocusable()); if(!Display.getInstance().shouldRenderSelection()) { return; } layoutContainer(); } } /** * 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); } if(Display.getInstance().isNativeCommands()) { Display.getInstance().getImplementation().setNativeCommands(menuBar.getCommands()); } if(getParent() != null) { getParent().getComponentForm().registerAnimated(this); } } /** * @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() { } void onShowCompletedImpl() { onShowCompleted(); if(showListener != null) { showListener.fireActionEvent(new ActionEvent(this)); } } /** * 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) { if(!title.isVisible()) { includeTitle = false; } Style titleStyle = title.getStyle(); Style contentStyle = contentPane.getUnselectedStyle(); if (includeTitle) { titleStyle.setMargin(Component.TOP, top, false); titleStyle.setMargin(Component.BOTTOM, 0, false); titleStyle.setMargin(Component.LEFT, left, false); titleStyle.setMargin(Component.RIGHT, right, false); contentStyle.setMargin(Component.TOP, 0, false); contentStyle.setMargin(Component.BOTTOM, bottom, false); contentStyle.setMargin(Component.LEFT, left, false); contentStyle.setMargin(Component.RIGHT, right, false); } else { titleStyle.setMargin(Component.TOP, 0, false); titleStyle.setMargin(Component.BOTTOM, 0, false); titleStyle.setMargin(Component.LEFT, 0, false); titleStyle.setMargin(Component.RIGHT, 0, false); contentStyle.setMargin(Component.TOP, top, false); contentStyle.setMargin(Component.BOTTOM, bottom, false); contentStyle.setMargin(Component.LEFT, left, false); contentStyle.setMargin(Component.RIGHT, right, false); } if (p instanceof BGPainter && ((BGPainter) p).getPreviousForm() != null) { ((BGPainter) p).setPreviousForm(previousForm); } else { BGPainter b = new BGPainter(this, p); 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 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() { if(getParent() != null) { return super.getComponentForm(); } return this; } /** * Invoked by display to hide the menu during transition * * @see restoreMenu */ void hideMenu() { menuBar.unInstallMenuBar(); } /** * Invoked by display to restore the menu after transition * * @see hideMenu */ void restoreMenu() { menuBar.installMenuBar(); } void setFocusedInternal(Component focused) { this.focused = focused; } /** * 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; } /** * 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 (menuBar.handlesKeycode(keyCode)) { menuBar.keyPressed(keyCode); return; } //Component focused = focusManager.getFocused(); if (focused != null) { if(focused.isEnabled()){ focused.keyPressed(keyCode); } if(focused == null) { initFocused(); return; } if (focused.handlesInput()) { return; } if (focused.getComponentForm() == this) { //if the arrow keys have been pressed update the focus. updateFocus(game); } else { initFocused(); } } else { initFocused(); if(focused == null) { getContentPane().moveScrollTowards(game, null); return; } } } /** * @inheritDoc */ public Layout getLayout() { return contentPane.getLayout(); } /** * @inheritDoc */ public void keyReleased(int keyCode) { int game = Display.getInstance().getGameAction(keyCode); if (menuBar.handlesKeycode(keyCode)) { menuBar.keyReleased(keyCode); return; } //Component focused = focusManager.getFocused(); if (focused != null) { if (focused.getComponentForm() == this) { if (focused.isEnabled()) { 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) { if(focused.isEnabled()){ focused.keyRepeated(keyCode); } int game = Display.getInstance().getGameAction(keyCode); // this has issues in the WTK // Fix for issue 433: the focus might be changed by the key repeated method in a way that can turn it to null if (focused != null && !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)); } //check if the click is relevant to the menu bar. if (menuBar.contains(x, y)) { Component cmp = menuBar.getComponentAt(x, y); if (cmp != null && cmp.isEnabled()) { cmp.pointerPressed(x, y); setDraggedOver(cmp, x, y, true); tactileTouchVibe(x, y, cmp); } return; } if (y >= contentPane.getY()) { Component cmp = contentPane.getComponentAt(x, y); setDraggedOver(cmp, x, y, true); if(cmp != null) { cmp.initDragAndDrop(x, y); if(cmp.hasLead) { Container leadParent; if(cmp instanceof Container) { leadParent = ((Container)cmp).getLeadParent(); } else { leadParent = cmp.getParent().getLeadParent(); } leadParent.repaint(); setFocused(leadParent); cmp.getLeadComponent().pointerPressed(x, y); } else { if(cmp.isEnabled()) { if(cmp.isFocusable()) { setFocused(cmp); } cmp.pointerPressed(x, y); tactileTouchVibe(x, y, cmp); } } } } else { Component cmp = titleArea.getComponentAt(x, y); if (cmp != null && cmp.isEnabled() && cmp.isFocusable()) { cmp.pointerPressed(x, y); tactileTouchVibe(x, y, cmp); } } initialPressX = x; initialPressY = y; } /** * 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); } } private void setDraggedOver(Component cmp, int x, int y, boolean triggerLeave) { if(dragOver != null && dragOver != cmp && triggerLeave) { dragOver.pointerDraggedLeave(x, y); } if(cmp != null && cmp != dragOver) { cmp.pointerDraggedEnter(x, y); } dragOver = cmp; } /** * @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 (menuBar.contains(x, y)) { cmp = menuBar.getComponentAt(x, y); } } if (cmp != null) { if (cmp.isFocusable() && cmp.isEnabled()) { setFocused(cmp); } setDraggedOver(cmp, x, y, true); cmp.pointerDragged(x, y); cmp.repaint(); } else { setDraggedOver(null, x, y, true); } } /** * @inheritDoc */ public void pointerHoverReleased(int[] x, int[] y) { if (dragged != null) { dragged.pointerHoverReleased(x, y); dragged = null; return; } Component cmp = contentPane.getComponentAt(x[0], y[0]); if (cmp != null) { if (cmp.isFocusable() && cmp.isEnabled()) { setFocused(cmp); } cmp.pointerHoverReleased(x, y); cmp.repaint(); } } /** * @inheritDoc */ public void pointerHoverPressed(int[] x, int[] y) { Component cmp = contentPane.getComponentAt(x[0], y[0]); if (cmp != null) { if (cmp.isFocusable() && cmp.isEnabled()) { setFocused(cmp); } cmp.pointerHoverPressed(x, y); cmp.repaint(); } } /** * @inheritDoc */ public void pointerHover(int[] x, int[] y) { if (dragged != null) { dragged.pointerHover(x, y); return; } Component cmp = contentPane.getComponentAt(x[0], y[0]); if (cmp != null) { if (cmp.isFocusable() && cmp.isEnabled()) { 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() { return isSingleFocusMode(0, getContentPane()) == 1; } private int isSingleFocusMode(int b, Container c) { int t = c.getComponentCount(); for(int iter = 0 ; iter < t ; iter++) { Component cmp = c.getComponentAt(iter); if(cmp.isFocusable()) { if(b > 0) { return 2; } b = 1; } if(cmp instanceof Container) { b = isSingleFocusMode(b, (Container)cmp); if(b > 1) { return b; } } } return b; } /** * @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); setDraggedOver(null, x, y, cmp != dragOver); if (cmp != null && cmp.isEnabled()) { cmp.pointerReleased(x, y); } return; } if (y >= contentPane.getY()) { Component cmp = contentPane.getComponentAt(x, y); if(cmp != null && cmp.isEnabled()) { if(cmp.hasLead) { Container leadParent; if(cmp instanceof Container) { leadParent = ((Container)cmp).getLeadParent(); } else { leadParent = cmp.getParent().getLeadParent(); } leadParent.repaint(); setFocused(leadParent); setDraggedOver(null, x, y, cmp.getLeadComponent() != dragOver); cmp.getLeadComponent().pointerReleased(x, y); } else { if(cmp.isEnabled()) { if (cmp.isFocusable()) { setFocused(cmp); } setDraggedOver(null, x, y, cmp != dragOver); cmp.pointerReleased(x, y); } } } } else { Component cmp = titleArea.getComponentAt(x, y); if (cmp != null && cmp.isEnabled()) { cmp.pointerReleased(x, y); } } } else { if(dragged.isDragAndDropInitialized()) { dragged.dragFinished(x, y); dragged = null; } 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); addCommand(cmd, 0); } /** * 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; } private Component findNextFocusHorizontal(Component focused, Component bestCandidate, Container root, boolean right) { int count = root.getComponentCount(); for(int iter = 0 ; iter < count ; iter++) { Component current = root.getComponentAt(iter); if(current.isFocusable()) { if(isInSameRow(focused, current)) { int currentX = current.getAbsoluteX(); int focusedX = focused.getAbsoluteX(); if(right) { if(focusedX < currentX) { if(bestCandidate != null) { if(bestCandidate.getAbsoluteX() < currentX) { continue; } } bestCandidate = current; } } else { if(focusedX > currentX) { if(bestCandidate != null) { if(bestCandidate.getAbsoluteX() > currentX) { continue; } } bestCandidate = current; } } } } if(current instanceof Container && !(((Container)current).isBlockFocus())) { bestCandidate = findNextFocusHorizontal(focused, bestCandidate, (Container)current, right); } } return bestCandidate; } private Component findNextFocusVertical(Component focused, Component bestCandidate, Container root, boolean down) { int count = root.getComponentCount(); for(int iter = 0 ; iter < count ; iter++) { Component current = root.getComponentAt(iter); if(current.isFocusable()) { int currentY = current.getAbsoluteY(); int focusedY = focused.getAbsoluteY(); if(down) { if(focusedY < currentY) { if(bestCandidate != null) { boolean exitingInSame = isInSameColumn(focused, bestCandidate); if(bestCandidate.getAbsoluteY() < currentY) { if(exitingInSame) { continue; } if(isInSameRow(current, bestCandidate) && !isInSameColumn(focused, current)) { continue; } } if(exitingInSame && isInSameRow(current, bestCandidate)) { continue; } } bestCandidate = current; } } else { if(focusedY > currentY) { if(bestCandidate != null) { boolean exitingInSame = isInSameColumn(focused, bestCandidate); if(bestCandidate.getAbsoluteY() > currentY) { if(exitingInSame) { continue; } if(isInSameRow(current, bestCandidate) && !isInSameColumn(focused, current)) { continue; } } if(exitingInSame && isInSameRow(current, bestCandidate)) { continue; } } bestCandidate = current; } } } if(current instanceof Container && !(((Container)current).isBlockFocus())) { bestCandidate = findNextFocusVertical(focused, bestCandidate, (Container)current, down); } } return bestCandidate; } /** * This method returns the next focusable Component vertically * @param down if true will the return the next focusable on the bottom else * on the top * @return a focusable Component or null if not found */ public Component findNextFocusVertical(boolean down) { Component c = findNextFocusVertical(focused, null, contentPane, down); if(c != null) { return c; } if(cyclicFocus) { c = findNextFocusVertical(focused, null, contentPane, !down); if(c != null) { Component current = findNextFocusVertical(c, null, contentPane, !down); while(current != null) { c = current; current = findNextFocusVertical(c, null, contentPane, !down); } return c; } } return null; } /** * This method returns the next focusable Component horizontally * @param right if true will the return the next focusable on the right else * on the left * @return a focusable Component or null if not found */ public Component findNextFocusHorizontal(boolean right) { Component c = findNextFocusHorizontal(focused, null, contentPane, right); if(c != null) { return c; } if(cyclicFocus) { c = findNextFocusHorizontal(focused, null, contentPane, !right); if(c != null) { Component current = findNextFocusHorizontal(c, null, contentPane, !right); while(current != null) { c = current; current = findNextFocusHorizontal(c, null, contentPane, !right); } return c; } } return null; } Component findNextFocusDown() { if(focused != null) { if(focused.getNextFocusDown() != null) { return focused.getNextFocusDown(); } return findNextFocusVertical(true); } return null; } Component findNextFocusUp() { if(focused != null) { if(focused.getNextFocusUp() != null) { return focused.getNextFocusUp(); } return findNextFocusVertical(false); } return null; } Component findNextFocusRight() { if(focused != null) { if(focused.getNextFocusRight() != null) { return focused.getNextFocusRight(); } return findNextFocusHorizontal(true); } return null; } Component findNextFocusLeft() { if(focused != null) { if(focused.getNextFocusLeft() != null) { return focused.getNextFocusLeft(); } return findNextFocusHorizontal(false); } return null; } /** * 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 = findNextFocusDown(); if (down != null) { focused = down; } break; } case Display.GAME_UP: { Component up = findNextFocusUp(); if(up != null) { focused = up; } break; } case Display.GAME_RIGHT: { Component right = findNextFocusRight(); if (right != null) { focused = right; } break; } case Display.GAME_LEFT: { Component left = findNextFocusLeft(); if (left != null) { focused = left; } 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); scrollComponentToVisible(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); } } /** * @inheritDoc */ public void setRTL(boolean r) { super.setRTL(r); contentPane.setRTL(r); } /** * @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; } /** * 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(); } /** * Returns the associated Menu Bar object * * @return the associated Menu Bar object */ public MenuBar getMenuBar() { return menuBar; } /** * Sets the associated MenuBar Object. * * @param menuBar */ public void setMenuBar(MenuBar menuBar){ this.menuBar = 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; } }