/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine 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. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.ui; import totalcross.sys.*; import totalcross.ui.event.*; import totalcross.ui.gfx.*; import totalcross.ui.image.*; import totalcross.util.*; /** * Container is a control that contains child controls. */ public class Container extends Control { /** The children of the container. */ protected Control children; /** The tail of the children list. */ protected Control tail; /** The type of border of this Container */ byte borderStyle = BORDER_NONE; private int []fourColors = new int[4]; private Vector childControls; private int pressColor=-1; private PenListener pe; private boolean cpressed; /** Sets the type of background of this Container. To disable the background, set the * <code>transparentBackground</code> of the Control class to true. This field is used when * transparentBackground is set to false (default). * * If the transparent background doesn't work, try setting * <code>alwaysEraseBackground = true</code>. * * @see #BACKGROUND_SHADED * @see #BACKGROUND_SOLID * @see UIColors#shadeFactor * @since TotalCross 1.3 */ public int backgroundStyle = BACKGROUND_SOLID; /** used in the setBorderStyle method */ static public final byte BORDER_NONE=0; /** used in the setBorderStyle method */ static public final byte BORDER_LOWERED=2; /** used in the setBorderStyle method */ static public final byte BORDER_RAISED=3; /** used in the setBorderStyle method */ static public final byte BORDER_SIMPLE=5; /** used in the setBorderStyle method */ static public final byte BORDER_TOP = 1; /** used in the setBorderStyle method. * @since TotalCross 2.0 */ static public final byte BORDER_ROUNDED = 6; /** used in the bckgroundStyle field */ static public final int BACKGROUND_SOLID = 0; /** used in the backgroundStyle field */ static public final int BACKGROUND_SHADED = 1; /** used in the backgroundStyle field */ static public final int BACKGROUND_SHADED_INV = 3; /** used in the backgroundStyle field. The bright color must be the fore color, and the darker color, the back color * @since TotalCross 2.0 */ static public final int BACKGROUND_CYLINDRIC_SHADED = 2; /** Used when animating the exhibition of a container. */ public static final int TRANSITION_NONE = 0; /** Used when animating the exhibition of a container. */ public static final int TRANSITION_OPEN = 1; /** Used when animating the exhibition of a container. */ public static final int TRANSITION_CLOSE = 2; /** Used when animating the exhibition of a container. */ public static final int TRANSITION_FADE = 3; /** The color used in the border. * @since TotalCross 2.0 */ public int borderColor = -1; /** Set the transition effect when this container appears on screen. * Defaults to Control.UPDATESCREEN_AT_ONCE, which means no transition effects. * The transition is applied by the popup/unpop methods (if this container is a * Window), or in the swap method (otherwise). * <br><br>See the UIGadgets and the ContainerSwitch samples in the SDK. * @see #TRANSITION_NONE * @see #TRANSITION_OPEN * @see #TRANSITION_CLOSE * @SEE #TRANSITION_FADE * @see Window#swap(Container) * @see Window#popup() * @see Window#unpop() * @see #nextTransitionEffect * @since TotalCross 1.2 */ public int transitionEffect = TRANSITION_NONE; // guich@tc120_47 /** * Defines the total transition time. Defaults to 1000 (1 second). * * @since TotalCross 1.68 */ public static int TRANSITION_TIME = 500; static int nextTransitionEffect = TRANSITION_NONE; // guich@tc120_47 protected int lastX=-999999,lastY,lastW,lastH; // guich@200b4_100 int numChildren; protected boolean started; // guich@340_15 /** Set to true to avoid calling the methods onRemove or onAddAgain */ protected boolean ignoreOnRemove,ignoreOnAddAgain; protected boolean finishedStart; // guich@450_36: avoid repaints while we're creating our controls for the first time /** Holds the controls that will be used to transfer focus when the tab key is pressed. * You can add or remove controls from here, but be careful not to add repeated controls. * @since SuperWaba 5.5 */ public Vector tabOrder = new Vector(); // guich@550_15 protected int lastScreenWidth; /** The insets of this container. Never change it directly, otherwise some controls * may not work correctly; use setInsets instead. * @see #setInsets */ protected Insets insets = new Insets(); // guich@tc110_87 /** Set to true to always erase the background when repainting this container. * @since TotalCross 1.0 */ public boolean alwaysEraseBackground; /** Returns true if the control was found in findChild, false otherwise. * @since TotalCross 1.2 */ protected static boolean controlFound; // guich@tc120_48 // a private id used in the ListContainer class int containerId; /** Creates a container with the default colors. * Important note: this container has no default size. * <br><br> * <b>NEVER INITIALIZE THE USER INTERFACE IN THE CONSTRUCTOR</b> * <br><br> * If you're extending the Container class and adding controls in its * constructor, you may come into problems if you don't set the bounds * as the first thing. */ // guich@300_58 public Container() { asContainer = this; focusTraversable = false; // kmeehl@tc100: Container is now not focusTraversable by default. Controls extending Container will set focusTraversable explicitly. } public void setPressColor(int color) { this.pressColor = color; if (color == -1 && pe != null) { removePenListener(pe); pe = null; callListenersOnAllTargets = cpressed = false; } if (color != -1 && pe == null) { callListenersOnAllTargets = true; addPenListener(pe = new PenListener() { public void penUp(PenEvent e) { if (e.type == PenEvent.PEN_UP && isEnabled() && !hadParentScrolled()) { setPressed(!cpressed); postPressedEvent(); } } public void penDown(PenEvent e) {} public void penDrag(DragEvent e) {} public void penDragStart(DragEvent e) {} public void penDragEnd(DragEvent e) {} }); } } public void setPressed(boolean p) { cpressed = p; Window.needsPaint = true; } public boolean isPressed() { return cpressed; } /** The transition effect to apply in the next screen update. * After this effect is applied, the next effect is set to TRANSITION_NONE. * @since TotalCross 1.2 * @see #transitionEffect * @see #TRANSITION_NONE * @see #TRANSITION_OPEN * @see #TRANSITION_CLOSE * @see #TRANSITION_FADE */ public static void setNextTransitionEffect(int t) { nextTransitionEffect = t; if (t != TRANSITION_NONE) screen0 = MainWindow.getScreenShot(); } static Image screen0; public static void applyTransitionEffect() { int transitionEffect = nextTransitionEffect; nextTransitionEffect = TRANSITION_NONE; if (transitionEffect == -1) transitionEffect = TRANSITION_NONE; if (screen0 != null) // only when transitionEffect is not NONE { try { int ini0 = Vm.getTimeStamp(); Image screen1 = MainWindow.getScreenShot(); screen1.lockChanges(); Graphics g = MainWindow.mainWindowInstance.getGraphics(); if (transitionEffect == TRANSITION_FADE) { int ini = Vm.getTimeStamp(); for (int i = 1; i <= 255; i++) { screen1.alphaMask = (Vm.getTimeStamp() - ini) * 255 / TRANSITION_TIME; if (screen1.alphaMask > 255) break; g.drawImage(screen0, 0,0); g.drawImage(screen1, 0,0); updateScreen(); Vm.sleep(1); } } else { int w = totalcross.sys.Settings.screenWidth; int h = totalcross.sys.Settings.screenHeight; int remainingFrames = Math.min(w,h)/2; int mx = w/2; int my = h/2; double incX=1,incY=1; if (w > h) incX = (double)w/h; else incY = (double)h/w; int step=1; boolean isClose = transitionEffect == TRANSITION_CLOSE; Image s0 = isClose ? screen1 : screen0; Image s1 = isClose ? screen0 : screen1; boolean noDelay = Settings.platform.equals(Settings.WIN32); // the delay does not work well on win32 for (int i = isClose ? remainingFrames : 0; remainingFrames >= 0; i+=isClose?-step:step, remainingFrames -= step) { int ini = Vm.getTimeStamp(); g.clearClip(); g.drawImage(s0,0,0); int minx = (int)(mx - i*incX); int miny = (int)(my - i*incY); int maxx = (int)(mx + i*incX); int maxy = (int)(my + i*incY); g.setClip(minx,miny,maxx-minx,maxy-miny); g.drawImage(s1,0,0); updateScreen(); int frameElapsed = Vm.getTimeStamp()-ini; int totalElapsed = Vm.getTimeStamp()-ini0; int remainingTime = TRANSITION_TIME - totalElapsed; if (remainingTime <= 0) break; if (!noDelay && frameElapsed * remainingFrames < remainingTime) // on too fast computers, do a delay { Vm.sleep((remainingTime - remainingFrames * frameElapsed) / remainingFrames + 1); step = 1; } else { step = frameElapsed * remainingFrames / remainingTime; } } } } catch (Throwable e) {} screen0 = null; } } /** Sets the insets value to match the given ones. * @since TotalCross 1.01 */ public void setInsets(int left, int right, int top, int bottom) // guich@tc110_87 { int gap = borderStyle == BORDER_NONE || borderStyle == BORDER_TOP ? 0 : borderStyle == BORDER_SIMPLE ? 1 : 2; insets.left = left + gap; insets.right = right + gap; insets.top = top + gap; insets.bottom = bottom + gap; } /** Copy the current insets values into the given insets. If you call this method often, * create an Insets field and reuse it. * @param copyInto The created object where the insets values will be copied into. * @since TotalCross 1.01 */ public void getInsets(Insets copyInto) // guich@tc110_87 { copyInto.left = insets.left; copyInto.right = insets.right; copyInto.top = insets.top; copyInto.bottom = insets.bottom; } /** Adds the control to this container, using the given bounds, relative to the last control added * Same of * <pre> * add(control); * control.setRect(x,y,w,h, null,false); * </pre> * @see Control#setRect(int, int, int, int, Control, boolean) */ public void add(Control control, int x, int y, int w, int h) { add(control); control.setRect(x,y,w,h, null,false); } /** Adds the control to this container, using the given bounds, relative to the given control. * Same of * <pre> * add(control); * control.setRect(x,y,w,h, relative,false); * </pre> * @see Control#setRect(int, int, int, int, Control, boolean) */ public void add(Control control, int x, int y, int w, int h, Control relative) // guich@200b4_138 { add(control); control.setRect(x,y,w,h, relative,false); } /** Add the control to this container and set its rect * to be the given x,y and PREFERRED as width/height * relative to the last control added * Same of * <pre> * add(control); * control.setRect(x,y,PREFERRED,PREFERRED, null,false); * </pre> * @see Control#setRect(int, int, int, int, Control, boolean) */ public void add(Control control, int x, int y) { add(control); control.setRect(x,y,PREFERRED,PREFERRED, null,false); } /** Add the control to this container and set its rect * to be the given x,y and PREFERRED as width/height * relative to the given control * Same of * <pre> * add(control); * control.setRect(x,y,PREFERRED,PREFERRED, relative,false); * </pre> * @see Control#setRect(int, int, int, int, Control, boolean) */ public void add(Control control, int x, int y, Control relative) // guich@200b4_138 { add(control); control.setRect(x,y,PREFERRED,PREFERRED, relative,false); } /** * Adds a child control to this container. * <b>Important</b>: If you're swapping containers from the MainWindow, be sure * that you set the focus on the new container after calling this add method. * Otherwise, a MenuBar will not work. Or, use the handy method Window.swap */ public void add(Control control) { if (control.uiAdjustmentsBasedOnFontHeightIsSupported == Settings.uiAdjustmentsBasedOnFontHeight) // if user didn't disabled in the control's constructor, inherit from parent control.uiAdjustmentsBasedOnFontHeightIsSupported = this.uiAdjustmentsBasedOnFontHeightIsSupported; if (control.parent != null) control.parent.remove(control); if (control.asWindow != null) throw new RuntimeException("A Window can't be added to a container: use popup instead."); // set children, next, prev, tail and parent addToList(control); if (foreColor < 0) foreColor = UIColors.controlsFore; // assign the default colors if (backColor < 0) // if not yet set backColor = UIColors.controlsBack; if (control.foreColor < 0/* || control.foreColor == UIColors.controlsFore - if the user set the container's color to something else and the control's color to black, this test overrides the black color*/) control.foreColor = this.foreColor; // guich@200b4_125 if (control.backColor < 0/* || control.backColor == UIColors.controlsBack*/) control.backColor = this.backColor; // guich@200b4_125 if (this.font != MainWindow.defaultFont && control.font == MainWindow.defaultFont) // guich@tc114_56: if this container has a different font, assign it to the control control.setFont(this.font); control.onColorsChanged(true); if (control.width > 0 && finishedStart) Window.needsPaint = true; // guich@450_36: only repaint here if the setRect was already called; otherwise, repaint will be called on setRect if (control.asContainer != null && !control.asContainer.ignoreOnAddAgain && control.asContainer.started) control.asContainer.onAddAgain(); // guich@402_5 if (control.asContainer != null || (control.focusTraversable && !control.focusLess)) // kmeehl@tc100: containers are no longer focusTraversable by default, so add the container so focus can traverse to its children tabOrder.addElement(control); } void addToList(Control control) { control.next = null; if (children == null) children = control; else tail.next = control; control.prev = tail; tail = control; control.parent = this; numChildren++; } /** * Removes a child control from the container. */ public void remove(Control control) { if (control.parent != this) return; // first of all, check if we are removing the focused control Window w = getParentWindow(); if (w == null) w = Window.getTopMost(); Control c = w._focus; while (c != null && c != control) c = c.getParent(); if (c == control) // we are removing the focused control or a container that holds it w.removeFocus(); // second, check if we are removing the highlighted control c = w.highlighted; while (c != null && c != control) c = c.getParent(); if (c == control) // we are removing the highlighted control or a container that holds it w.setHighlighted(null); // finally, remove the control: set children, next, prev, tail and parent Control prev = control.prev; Control next = control.next; if (prev == null) children = next; else prev.next = next; if (next != null) next.prev = prev; if (tail == control) tail = prev; control.next = null; control.prev = null; numChildren--; Window.needsPaint = true; // guich@200b4_16: invalidate the hole container's area control.parent = null; if (control.asContainer != null && !control.asContainer.ignoreOnRemove) control.asContainer.onRemove(); // guich@402_5 tabOrder.removeElement(control); // kmeehl@tc100: remove the element from the Vector, if it is there } /** Returns the child located at the given x and y coordinates. * Usually, if a control is not found, the last visited container is returned. * In this case, controlFound is set to false. * @see #controlFound * */ public Control findChild(int x, int y) { controlFound = true; Container container = this; while (true) { // search tail to head since paint goes head to tail Control child = container.tail; while (child != null && (!child.visible || !child.contains(x, y))) // guich@240_8: fixed when adding two controls in the same location but one of them was not visible (!child.visible) child = child.prev; if (child == null) { controlFound = container.focusTraversable; return container; } if (child.asContainer == null) return child; x -= child.x; y -= child.y; container = child.asContainer; } } public Control findNearestChild(int x, int y, int minDistance) // guich@tc120_48 { int minDistance0 = minDistance; Container container = this; Control minControl = null; while (true) { boolean found = false; // search tail to head since paint goes head to tail Control child = container.tail; while (child != null) // guich@240_8: fixed when adding two controls in the same location but one of them was not visible (!child.visible) { if (child.visible) { int dist = (int)(Convert.getDistancePoint2Rect(x,y, child.x, child.y, child.x+child.width, child.y+child.height) + 0.5); if (dist < minDistance) { found = true; minControl = child; if (dist == 0) // inside? break; minDistance = dist; } } child = child.prev; } if (child == null) { if (!found && minControl != null) return minControl; if (minControl == null) // nothing found? return null; child = minControl; minDistance = minDistance0; } if (child.asContainer == null) return child; x -= child.x; y -= child.y; container = child.asContainer; } } /** Return an array of Controls that are added to this Container. If there are no Controls, returns null. */ public Control []getChildren() { if (numChildren == 0) return null; Control []ac = new Control[numChildren]; Control child = this.tail; for (int i=0; child != null; i++,child = child.prev) ac[i] = child; return ac; } public Control getFirstChild() { return children; } public int getChildrenCount() { return numChildren; } /** Sets if this container and all childrens can or not accept events */ public void setEnabled(boolean enabled) { if (internalSetEnabled(enabled, false)) { for (Control child = children; child != null; child = child.next) child.setEnabled(enabled); post(); } } /** Posts an event to the children of this container and to all containers inside this containers; recursively. @since SuperWaba 2.0 beta 4 */ public void broadcastEvent(Event e) { _onEvent(e); // guich@200b4_110: make sure this container receive this event. for (Control child = children; child != null; child = child.next) { child._onEvent(e); if (child.asContainer != null) child.asContainer.broadcastEvent(e); } } /** Called by the system to draw the children of the container. */ public void paintChildren() { for (Control child = children; child != null; child = child.next) if (child.visible) // guich@200: ignore hidden controls - note: a window added to a container may not be painted correctly { if (child.offscreen != null) getGraphics().drawImage(child.offscreen,child.x,child.y); else { child.onPaint(child.getGraphics()); if (child.asContainer != null) child.asContainer.paintChildren(); } } } /** Sets the border for this container. The insets are changed after this method is called. * The BORDER_ROUNDED sets the background to transparent. * @see #BORDER_NONE * @see #BORDER_LOWERED * @see #BORDER_RAISED * @see #BORDER_SIMPLE * @see #BORDER_TOP * @see #BORDER_ROUNDED */ public void setBorderStyle(byte border) // guich@200final_16 { int gap = border == BORDER_NONE || borderStyle == BORDER_TOP ? 0 : borderStyle == BORDER_SIMPLE ? 1 : 2; setInsets(gap,gap,gap,gap); this.borderStyle = border; if (border == BORDER_ROUNDED) transparentBackground = true; onColorsChanged(false); } /** Returns the client rect for this Container, in relative coords. * The client rectangle usually excludes the title (if applicable) and the border. * If you call this method often, consider using the other version, where a cached Rect is used; * it avoids the creation of a Rect. * @see #getClientRect(Rect) */ public Rect getClientRect() // guich@200final_15 { Rect r = new Rect(); getClientRect(r); return r; } /** Returns the client rect for this Container, in relative coords, excluding the insets. * In this version, you provide the instantiated Rect to be filled with the coords. */ protected void getClientRect(Rect r) // guich@450_36 { r.set(insets.left,insets.top,width-insets.left-insets.right,height-insets.top-insets.bottom); } protected void onColorsChanged(boolean colorsChanged) { if (borderStyle != BORDER_NONE && borderStyle != BORDER_SIMPLE && borderStyle != BORDER_TOP && borderStyle != BORDER_ROUNDED) Graphics.compute3dColors(isEnabled(), backColor, foreColor, fourColors); } /** Draws the border (if any). If you override this method, be sure to call * <code>super.onPaint(g);</code>, or the border will not be drawn. */ public void onPaint(Graphics g) { int b = pressColor != -1 && cpressed ? pressColor : backColor; if (!transparentBackground && parent != null && (b != parent.backColor || parent.asWindow != null || alwaysEraseBackground)) // guich@300_6 - guich@511_7: if parent is a window, then always repaint { switch (backgroundStyle) { case BACKGROUND_SOLID: g.backColor = b; g.fillRect(0,0,width,height); break; case BACKGROUND_SHADED: g.fillShadedRect(0,0,width,height,true,false,foreColor,b,UIColors.shadeFactor); break; case BACKGROUND_SHADED_INV: g.fillShadedRect(0,0,width,height,false,false,foreColor,b,UIColors.shadeFactor); break; case BACKGROUND_CYLINDRIC_SHADED: g.drawCylindricShade(foreColor,b,0,0,width,height); break; } } switch (borderStyle) { case BORDER_NONE: break; case BORDER_TOP: g.foreColor = borderColor != -1 ? borderColor : getForeColor(); g.drawRect(0,0,width,0); break; case BORDER_SIMPLE: g.foreColor = borderColor != -1 ? borderColor : getForeColor(); g.drawRect(0,0,width,height); break; case BORDER_ROUNDED: g.drawWindowBorder(0,0,width,height,0,0,borderColor != -1 ? borderColor : getForeColor(),b,b,b,2,false); break; default: g.draw3dRect(0,0,width,height,borderStyle,false,false,fourColors); } } /** When the container is added for the first time, the method initUI is called, * so the user interface can be initialized. * From the second time and up that the container is added, the onAddAgain method * is called instead. * <br><br> * If this container is a window, this method is not called when the window is popped. * <br><br> * If you don't want this method to be called, you must set <code>ignoreOnAddAgain = true;</code> inside * the method initUI or the class constructor. Doing so makes the user interface initialize faster. * * @since SuperWaba 4.1 */ protected void onAddAgain() { } /** Called when this container is removed from the parent. Note that, if this * container is a window, this method is not called when the window is unpopped. * <br><br> * If you don't want this method to be called, you must set <code>ignoreOnRemove = true;</code> inside * the method initUI or the class constructor. Doing so makes the user interface initialize faster. * * @since SuperWaba 4.1 */ protected void onRemove() { } /** Called to initialize the User Interface of this container. This differs from the onAddAgain * method by that this method is called only once, at the first time the control is added to the parent. * When the container is being setup, the initUI method is called; * then, the onAddAgain is called every time the container is added again. * @since SuperWaba 3.4 */ public void initUI() // guich@340_15 { } /** Called by the event dispatcher to set highlighting back to true. * A class may extend this to decide when its time to turn it on again or not. */ public void setHighlighting() { isHighlighting = true; } /** Call this method to swap this Container to the topmost window. Note that since * we have no idea who is our future parent window, we just use the topmost window. * The topmost window is the one that is currently visible. * <br>This method is useful if you have containers that are SINGLETONS and wish to * set them as the current container. E.G.: * <pre> * public class MainMenu extends Container // create a Singleton from MainMenu * { * private static instance; * public static getInstance() * { * return instance != null ? instance : (instance=new MainMenu()); * } * private MainMenu() * { * } * } * // then at some other class, you can do: * MainMenu.getInstance().swapToTopmostWindow(); * </pre> * @since SuperWaba 5.7 */ public void swapToTopmostWindow() // guich@570_79 { Window.getTopMost().swap(this); } /** Clears all children controls that are <code>focusTraversable</code>, recursively. */ public void clear() // guich@572_19 { for (Control child = children; child != null; child = child.next) if (child.focusTraversable || (child.asContainer != null && child.asContainer.tabOrder.size() > 0)) // kmeehl@tc100: containers are no longer focusTraversble by default, but may still contain focusTraversable children child.clear(); } /** * Get a list of child controls of this container which are focus candidates * @param v A vector into which to add the focus candidates. */ public void getFocusableControls(Vector v) //kmeehl@tc100 { if (!visible || !isEnabled()) return; Control child = children; for (int i = 0; i < numChildren; i++, child = child.next) { if (child.focusTraversable && !child.focusLess) { if (child.asContainer != null && !child.focusHandler) // kmeehl@tc100: if the child is a focus handler and focusable, don't look for controls within it. (This is an optimization) ((Container)child).getFocusableControls(v); // Note that calling this function directly on the focus handler in question will yield its focusable controls. if (child.isVisible() && child.isEnabled() && child.height > 0 && child.width > 0) v.addElement(child); } else if (child.asContainer != null) ((Container)child).getFocusableControls(v); } } /** * Finds the next control that should receive focus based on the direction with respect to c. * @param c The reference control from which to find the next control. * @param direction The direction in which to look from c. One of: SpecialKeys.LEFT, SpecialKeys.RIGHT, SpecialKeys.UP, SpecialKeys.DOWN * @return The control which should receive focus next, or null. * @see totalcross.sys.Settings#geographicalFocus */ public Control findNextFocusControl(Control c, int direction) // kmeehl@tc100 { Rect controlRect = c.getAbsoluteRect(); int controlRight = controlRect.x + controlRect.width; int controlBottom = controlRect.y + controlRect.height; int closestControlCoordValue = 999999; int closestOverlapCoordValue = 999999; if (direction == SpecialKeys.UP || direction == SpecialKeys.LEFT) { closestControlCoordValue = -1; closestOverlapCoordValue = -1; } if (childControls == null) childControls = new Vector(2); else childControls.removeAllElements(); getFocusableControls(childControls); int controlPastTestCoord; boolean vertical = (direction == SpecialKeys.UP || direction == SpecialKeys.DOWN); if (vertical) { controlPastTestCoord = controlRect.y; if (direction == SpecialKeys.DOWN) controlPastTestCoord += controlRect.height; } else { controlPastTestCoord = controlRect.x; if (direction == SpecialKeys.RIGHT) controlPastTestCoord += controlRect.width; } Control closestControl = null, closestOverlap = null, childControl = null; try {childControl = (Control) childControls.pop();} catch (ElementNotFoundException e) {} while (childControl != null) { Rect childRect = childControl.getAbsoluteRect(); int childRight = childRect.x + childRect.width; int childBottom = childRect.y + childRect.height; // is the child control past the current control; boolean isChildPast = false; int childCoordValue = 0; if (vertical) { childCoordValue = childRect.y; if (direction == SpecialKeys.UP) childCoordValue += childRect.height; } else { childCoordValue = childRect.x; if (direction == SpecialKeys.LEFT) childCoordValue += childRect.width; } if (direction == SpecialKeys.RIGHT || direction == SpecialKeys.DOWN) isChildPast = childCoordValue >= controlPastTestCoord; else isChildPast = childCoordValue <= controlPastTestCoord; if (isChildPast) { // does this child control overlap the current control boolean childOverlaps = ( vertical && childRect.x <= controlRight && childRight >= controlRect.x) || (!vertical && childRect.y <= controlBottom && childBottom >= controlRect.y); if (closestControl == null) { closestControl = childControl; closestControlCoordValue = childCoordValue; } else if (isCloser(childCoordValue,closestControlCoordValue,closestControl,vertical,childRect.x,childRect.y,direction) ) { closestControl = childControl; closestControlCoordValue = childCoordValue; } if (childOverlaps && (closestOverlap == null || isCloser(childCoordValue,closestOverlapCoordValue,closestOverlap,vertical,childRect.x,childRect.y,direction))) { closestOverlap = childControl; closestOverlapCoordValue = childCoordValue; } } try {childControl = (Control) childControls.pop();}catch (ElementNotFoundException e){childControl = null;} } if (closestOverlap != null) return closestOverlap; if (closestControl == null && (direction == SpecialKeys.LEFT || direction == SpecialKeys.RIGHT)) return findNextFocusControl(c, direction == SpecialKeys.LEFT?SpecialKeys.UP:SpecialKeys.DOWN); return closestControl; } private boolean isCloser(int childCoordValue, int nextControlCoordValue, Control nextControl, boolean vertical, int childX, int childY, int direction) // kmeehl@tc100 { if (childCoordValue == nextControlCoordValue) { // this control is at the same coordinate as the prior selected control. // check if this child control is more to the left/top than the previous child. if ((vertical && childX < nextControl.getAbsoluteRect().x) || (!vertical && childY < nextControl.getAbsoluteRect().y)) return true; } else { boolean toPrev = direction == SpecialKeys.DOWN || direction == SpecialKeys.RIGHT; // is new closest control? if ((toPrev && childCoordValue < nextControlCoordValue) || (!toPrev && childCoordValue > nextControlCoordValue)) { nextControlCoordValue = childCoordValue; return true; } } return false; } /** Returns the border style. */ public byte getBorderStyle() { return borderStyle; } /** Removes all controls inside this container. * @since TotalCross 1.0 */ public void removeAll() { Control[] c = getChildren(); if (c != null) for (int i =0; i < c.length; i++) remove(c[i]); } /** Increments the lastX, used in relative positioning. */ public void incLastX(int n) { lastX += n; } /** Increments the lastY, used in relative positioning. */ public void incLastY(int n) { lastY += n; } /** This method resizes the Container's width and height to the needed bounds, based on added childs. * You can add spaces at right and bottom using the insets.right/bottom properties. * Sample: * <pre> * // this sample will center two buttons of different sizes on screen * Container c = new Container(); * add(c, CENTER,BOTTOM,1000,1000); * c.add(new Button("Ok"),LEFT,TOP); * c.add(new Button("Cancel"),AFTER+5,SAME); * c.resize(); * </pre> * Note: differently of Window.resize, this method does not call setRect again, it only * changes the width and height by direct assignment. * @since TotalCross 1.14 * @see #resizeWidth * @see #resizeHeight */ public void resize() // guich@tc114_53 { resizeWidth(); resizeHeight(); } /** This method resizes the Container's width to the needed bounds, based on added childs. * You can add spaces at right using the insets.right property. * Sample: * <pre> * // this sample will center two buttons of different sizes on screen * Container c = new Container(); * add(c, CENTER,BOTTOM,1000,1000); * c.add(new Button("Ok"),LEFT,TOP); * c.add(new Button("Cancel"),AFTER+5,SAME); * c.resize(); * </pre> * Note: differently of Window.resize, this method does not call setRect again, it only * changes the width by direct assignment. * @since TotalCross 1.14 * @see #resize * @see #resizeHeight */ public void resizeWidth() // guich@tc114_53 { int maxX = 0; for (Control child = children; child != null; child = child.next) maxX = Math.max(maxX,child.x+child.width); int hborder = borderStyle == BORDER_NONE || borderStyle == BORDER_TOP ? 0: borderStyle == BORDER_SIMPLE ? 1 : 2; setW = width = maxX+insets.right+hborder/2; updateTemporary(); // guich@tc114_68 } /** This method resizes the Container's width to the needed bounds, based on added childs. * You can add spaces at bottom using the insets.bottom property. * Sample: * <pre> * // this sample will center two buttons of different sizes on screen * Container c = new Container(); * add(c, CENTER,BOTTOM,1000,1000); * c.add(new Button("Ok"),LEFT,TOP); * c.add(new Button("Cancel"),AFTER+5,SAME); * c.resize(); * </pre> * Note: differently of Window.resize, this method does not call setRect again, it only * changes the height by direct assignment. * @since TotalCross 1.14 * @see #resizeWidth * @see #resize */ public void resizeHeight() // guich@tc114_53 { int maxY = 0; for (Control child = children; child != null; child = child.next) maxY = Math.max(maxY,child.y+child.height); int hborder = borderStyle == BORDER_NONE || borderStyle == BORDER_TOP ? 0 : borderStyle == BORDER_SIMPLE ? 1 : 2; setH = height= maxY+insets.bottom+hborder/2; updateTemporary(); // guich@tc114_68 } /** Moves the focus to the next Edit or MultiEdit control. * @return The selected control or null if none was found. * @since TotalCross 1.25 */ public Control moveFocusToNextEditable(Control control, boolean forward) // guich@tc125_26 { Vector v = tabOrder; int idx = v.indexOf(control); int n = v.size(); if (idx >= 0 && n > 1) { for (int i = n-1; i >= 0; i--) { if (forward && ++idx == n) idx = 0; else if (!forward && --idx < 0) idx = n-1; Control c = (Control)v.items[idx]; if (c != this && c.isEnabled() && c.visible && (c instanceof Edit && ((Edit)c).editable) || (c instanceof MultiEdit && ((MultiEdit)c).editable)) // guich@tc100b4_12: also check for enabled/visible/editable - guich@tc120_49: skip ourself { c.requestFocus(); if (Settings.virtualKeyboard) { if (c instanceof Edit) ((Edit)c).popupKCC(); else ((MultiEdit)c).popupKCC(); } return c; } } } return null; } /** Moves the focus to the next control, which can be an Edit, a MultiEdit, or another control type. * It does not show the keyboard. * @return The selected control or null if none was found. * @since TotalCross 1.53 */ public Control moveFocusToNextControl(Control control, boolean forward) // guich@tc125_26 { Vector v = tabOrder; int idx = v.indexOf(control); int n = v.size(); if (idx >= 0 && n > 1) { for (int i = n-1; i >= 0; i--) { if (forward && ++idx == n) idx = 0; else if (!forward && --idx < 0) idx = n-1; Control c = (Control)v.items[idx]; if (c != this && c.isEnabled() && c.visible) { c.requestFocus(); return c; } } } return null; } /** Changes the focusTraversable property for this container and all controls, recursively */ public void setFocusTraversable(boolean b) { focusTraversable = b; for (Control cc = children; cc != null; cc = cc.next) { cc.focusTraversable = true; if (cc.asContainer != null) cc.asContainer.setFocusTraversable(b); } } /** Called when this container has been swapped into the Window and the swap is done. * @since TotalCross 2.0 */ public void onSwapFinished() { } }