/* * 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.animations.Motion; import com.sun.lwuit.animations.Transition; import com.sun.lwuit.layouts.FlowLayout; import com.sun.lwuit.layouts.Layout; import com.sun.lwuit.plaf.UIManager; import com.sun.lwuit.geom.Dimension; import com.sun.lwuit.geom.Rectangle; import com.sun.lwuit.impl.LWUITImplementation; import com.sun.lwuit.plaf.LookAndFeel; import com.sun.lwuit.plaf.Style; import java.util.Vector; /** * A composite pattern with {@link Component}, allows nesting and arranging multiple * components using a pluggable layout manager architecture. Containers can be nested * one within the other to form elaborate UI's. * * @see com.sun.lwuit.layouts * @see Component * @author Chen Fishbein */ public class Container extends Component { private static boolean enableLayoutOnPaint = true; private Component leadComponent; private Layout layout; private java.util.Vector components = new java.util.Vector(); private boolean shouldLayout = true; boolean scrollableX; boolean scrollableY; private java.util.Vector cmpTransitions; private int scrollIncrement = 20; private boolean blockFocus = false; /** * Constructs a new Container with a new layout manager. * * @param layout the specified layout manager */ public Container(Layout layout) { super(); setUIID("Container"); this.layout = layout; setFocusable(false); LookAndFeel laf = UIManager.getInstance().getLookAndFeel(); setSmoothScrolling(laf.isDefaultSmoothScrolling()); } /** * Constructs a new Container, with a {@link FlowLayout}. */ public Container() { this(new FlowLayout()); } /** * Sets the lead component for this container, a lead component takes over the entire * component hierarchy and receives all the events for the container hierarchy. * * @param lead component that takes over the hierarchy */ public void setLeadComponent(Component lead) { leadComponent = lead; if(isInitialized()) { initLead(); } } void focusGainedInternal() { super.focusGainedInternal(); if(leadComponent != null) { setFocusLead(true); } } void focusLostInternal() { super.focusLostInternal(); if(leadComponent != null) { setFocusLead(false); } } /** * Returns the lead component for this hierarchy if such a component is defined * * @return the lead component */ public Component getLeadComponent() { if(leadComponent != null) { return leadComponent; } if(hasLead) { return super.getLeadComponent(); } return null; } /** * Returns the lead container thats handling the leading, this is useful for * a container hierachy where the parent container might not be the leader * * @return the lead component */ public Container getLeadParent() { if(leadComponent != null) { return this; } if(hasLead) { return getParent().getLeadParent(); } return null; } private void initLead() { disableFocusAndInitLead(this); setFocusable(true); hasLead = true; } /** * @inheritDoc */ public void keyPressed(int k) { if(leadComponent != null) { leadComponent.keyPressed(k); repaint(); } } /** * @inheritDoc */ public void keyReleased(int k) { if(leadComponent != null) { leadComponent.keyReleased(k); repaint(); } } private void disableFocusAndInitLead(Container c) { for(int iter = 0 ; iter < c.getComponentCount() ; iter++) { Component cu = c.getComponentAt(iter); if(cu instanceof Container) { disableFocusAndInitLead((Container)cu); } cu.setFocusable(false); cu.hasLead = true; } } /** * Returns the layout manager responsible for arranging this container * * @return the container layout manager */ public Layout getLayout() { return layout; } /** * Sets the layout manager responsible for arranging this container * * @param layout the specified layout manager */ public void setLayout(Layout layout) { this.layout = layout; } /** * Same as setShouldCalcPreferredSize(true) but made accessible for * layout managers */ public void invalidate() { setShouldCalcPreferredSize(true); } /** * Flags this container to preform layout * * @param layout */ protected void setShouldLayout(boolean layout) { if (!shouldCalcScrollSize) { this.shouldCalcScrollSize = layout; } if (shouldLayout != layout) { shouldLayout = layout; shouldCalcPreferredSize = layout; shouldCalcScrollSize = layout; Container parent = getParent(); if(parent != null){ parent.setShouldLayout(layout); } } } /** * @inheritDoc */ public void setShouldCalcPreferredSize(boolean shouldCalcPreferredSize) { // minor optimization preventing repeated invokations to setShouldCalcPreferredSize if(shouldCalcPreferredSize && this.shouldLayout && this.shouldCalcPreferredSize && !isInitialized()) { Container p = getParent(); if(p != null && p.shouldLayout && p.shouldCalcPreferredSize) { return; } } super.setShouldCalcPreferredSize(shouldCalcPreferredSize); shouldLayout = shouldCalcPreferredSize; if (shouldLayout) { int size = components.size(); for(int iter = 0 ; iter < size ; iter++) { Component cmp = (Component) components.elementAt(iter); if (cmp instanceof Container) { ((Container) cmp).setShouldCalcPreferredSize(shouldCalcPreferredSize); } } } } /** * Returns the width for layout manager purposes, this takes scrolling * into consideration unlike the getWidth method. * * @return the layout width */ public int getLayoutWidth() { if (isScrollableX()) { return Math.max(getWidth(), getPreferredW()); } else { Container parent = getScrollableParent(); if (parent != null && parent.isScrollableX()) { return Math.max(getWidth(), getPreferredW()); } int width = getWidth(); if (width <= 0) { return getPreferredW(); } return width; } } /** * Returns the height for layout manager purposes, this takes scrolling * into consideration unlike the getWidth method. * * @return the layout height */ public int getLayoutHeight() { if (scrollableY) { return Math.max(getHeight(), getPreferredH()); } else { Container parent = getScrollableParent(); if (parent != null && parent.scrollableY) { return Math.max(getHeight(), getPreferredH()); } int height = getHeight(); if (height <= 1) { return getPreferredH(); } return height; } } /** * Invokes apply/setRTL recursively on all the children components of this container * * @param rtl right to left bidi indication * @see Component#setRTL(boolean) */ public void applyRTL(boolean rtl) { setRTL(rtl); int c = getComponentCount(); for(int iter = 0 ; iter < c ; iter++) { Component current = getComponentAt(iter); if(current instanceof Container) { ((Container)current).applyRTL(rtl); } else { current.setRTL(rtl); } } } /** * Returns a parent container that is scrollable or null if no parent is * scrollable. * * @return a parent container that is scrollable or null if no parent is * scrollable. */ private Container getScrollableParent() { Container parent = getParent(); while (parent != null) { if (parent.isScrollable()) { return parent; } parent = parent.getParent(); } return null; } /** * Adds a Component to the Container * * @param cmp the component to be added */ public void addComponent(Component cmp) { layout.addLayoutComponent(null, cmp, this); insertComponentAt(components.size(), cmp); } /** * Adds a Component to the Container * * @param constraints this method is useful when the Layout requires a constraint * such as the BorderLayout. * In this case you need to specify an additional data when you add a Component, * such as "CENTER", "NORTH"... * * @param cmp component to add */ public void addComponent(Object constraints, Component cmp) { layout.addLayoutComponent(constraints, cmp, this); insertComponentAt(components.size(), cmp); } /** * Adds a Component to the Container * * @param index location to insert the Component * @param constraints this method is useful when the Layout requires a constraint * such as the BorderLayout. * In this case you need to specify an additional data when you add a Component, * such as "CENTER", "NORTH"... * @param cmp component to add */ public void addComponent(int index, Object constraints, Component cmp) { layout.addLayoutComponent(constraints, cmp, this); insertComponentAt(index, cmp); } void insertComponentAt(int index, Component cmp) { if (cmp.getParent() != null) { throw new IllegalArgumentException("Component is already contained in Container: " + cmp.getParent()); } if(cmp instanceof Form) { cmp.setVisible(true); cmp.setPreferredSize(null); } cmp.setParent(this); components.insertElementAt(cmp, index); setShouldCalcPreferredSize(true); if (isInitialized()) { cmp.initComponentImpl(); } } /** * This method adds the Component at a specific index location in the Container * Components array. * * @param index location to insert the Component * @param cmp the Component to add * @throws ArrayIndexOutOfBoundsException if index is out of bounds * @throws IllegalArgumentException if Component is already contained or * the cmp is a Form Component */ public void addComponent(int index, Component cmp) { layout.addLayoutComponent(null, cmp, this); insertComponentAt(index, cmp); } /** * This method replaces the current Component with the next Component. * Current Component must be contained in this Container. * This method returns when transition has finished. * * @param current a Component to remove from the Container * @param next a Component that replaces the current Component * @param t a Transition between the add and removal of the Components * a Transition can be null */ public void replaceAndWait(final Component current, final Component next, final Transition t) { replaceComponents(current, next, t, true, false, null, 0, 0); } /** * This method replaces the current Component with the next Component. * Current Component must be contained in this Container. * This method returns when transition has finished. * * @param current a Component to remove from the Container * @param next a Component that replaces the current Component * @param t a Transition between the add and removal of the Components * a Transition can be null * @param layoutAnimationSpeed the speed of the layout animation after replace is completed */ public void replaceAndWait(final Component current, final Component next, final Transition t, int layoutAnimationSpeed) { enableLayoutOnPaint = false; replaceComponents(current, next, t, true, false, null, 0, layoutAnimationSpeed); if(layoutAnimationSpeed > 0) { animateLayoutAndWait(layoutAnimationSpeed); } enableLayoutOnPaint = true; } /** * This method replaces the current Component with the next Component * * @param current a Component to remove from the Container * @param next a Component that replaces the current Component * @param t a Transition between the add and removal of the Components * a Transition can be null * @param onFinish invoked when the replace operation is completed, may be null * @param growSpeed after replace is completed the component can gradually grow/shrink to fill up * available room, set this to 0 for immediate growth or any larger number for gradual animation. -1 indicates * a special case where no validation occurs */ public void replace(final Component current, final Component next, final Transition t, Runnable onFinish, int growSpeed) { replaceComponents(current, next, t, false, false, onFinish, growSpeed, 0); } /** * This method replaces the current Component with the next Component. * Current Component must be contained in this Container. * This method returns when transition has finished. * * @param current a Component to remove from the Container * @param next a Component that replaces the current Component * @param t a Transition between the add and removal of the Components * a Transition can be null * @param dropEvents indicates if the display should drop all events * while this Component replacing is happening */ public void replaceAndWait(final Component current, final Component next, final Transition t, boolean dropEvents) { replaceComponents(current, next, t, true, dropEvents, null, 0, 0); } /** * This method replaces the current Component with the next Component. * Current Component must be contained in this Container. * This method return immediately. * * @param current a Component to remove from the Container * @param next a Component that replaces the current Component * @param t a Transition between the add and removal of the Components * a Transition can be null */ public void replace(final Component current, final Component next, final Transition t) { replaceComponents(current, next, t, false, false, null, 0, 0); } private void replaceComponents(final Component current, final Component next, final Transition t, boolean wait, boolean dropEvents, Runnable onFinish, int growSpeed, int layoutAnimationSpeed) { if (!contains(current)) { throw new IllegalArgumentException("Component " + current + " is not contained in this Container"); } if (t == null || !isVisible() || getComponentForm() == null) { replace(current, next, false); return; } setScrollX(0); setScrollY(0); next.setX(current.getX()); next.setY(current.getY()); next.setWidth(current.getWidth()); next.setHeight(current.getHeight()); next.setParent(this); if (next instanceof Container) { ((Container) next).layoutContainer(); } final Anim anim = new Anim(this, current, next, t); anim.onFinish = onFinish; anim.growSpeed = growSpeed; anim.layoutAnimationSpeed = layoutAnimationSpeed; // register the transition animation getComponentForm().registerAnimatedInternal(anim); //wait until animation has finished if (wait) { Display.getInstance().invokeAndBlock(anim, dropEvents); } } private boolean isParentOf(Component c) { c = c.getParent(); if (c == null || c instanceof Form) { return false; } return (c == this) || isParentOf(c); } private boolean requestFocusChild(boolean avoidRepaint) { for (int iter = 0; iter < getComponentCount(); iter++) { Component c = getComponentAt(iter); if (c.isFocusable()) { if(avoidRepaint) { getComponentForm().setFocusedInternal(c); } else { c.requestFocus(); } return true; } if (c instanceof Container && ((Container) c).requestFocusChild(avoidRepaint)) { return true; } } return false; } private void cancelRepaintsRecursively(Component c, LWUITImplementation l) { if(c instanceof Container) { Container cnt = (Container)c; int count = cnt.getComponentCount(); for(int i = 0 ; i < count ; i++) { cancelRepaintsRecursively(cnt.getComponentAt(i), l); } } l.cancelRepaint(c); } private void cancelRepaintsRecursively(Component c) { cancelRepaintsRecursively(c, Display.getInstance().getImplementation()); } void replace(final Component current, final Component next, boolean avoidRepaint) { int index = components.indexOf(current); boolean currentFocused = false; if (current.getComponentForm() != null) { Component currentF = current.getComponentForm().getFocused(); currentFocused = currentF == current; if (!currentFocused && current instanceof Container && currentF != null && ((Container) current).isParentOf(currentF)) { currentFocused = true; } } Object constraint = layout.getComponentConstraint(current); if (constraint != null) { removeComponentImpl(current); layout.addLayoutComponent(constraint, next, Container.this); } else { removeComponentImpl(current); } cancelRepaintsRecursively(current); next.setParent(null); if (index < 0) { index = 0; } insertComponentAt(index, next); if (currentFocused) { if (next.isFocusable()) { if(avoidRepaint) { getComponentForm().setFocusedInternal(next); } else { next.requestFocus(); } } else { if (next instanceof Container) { ((Container) next).requestFocusChild(avoidRepaint); } } } } /** * @inheritDoc */ void initComponentImpl() { if (!isInitialized()) { super.initComponentImpl(); } int size = components.size(); for(int iter = 0 ; iter < size ; iter++) { ((Component) components.elementAt(iter)).initComponentImpl(); } if(leadComponent != null) { initLead(); } } /** * @inheritDoc */ public boolean isEnabled() { if(leadComponent != null) { return leadComponent.isEnabled(); } return super.isEnabled(); } /** * removes a Component from the Container, notice that removed component might still have * a pending repaint in the queue that won't be removed. Calling form.repaint() will workaround * such an issue. * * @param cmp the removed component */ public void removeComponent(Component cmp) { removeComponentImpl(cmp); } /** * removes a Component from the Container * * @param cmp the removed component */ void removeComponentImpl(Component cmp) { Form parentForm = cmp.getComponentForm(); layout.removeLayoutComponent(cmp); cmp.deinitializeImpl(); components.removeElement(cmp); cmp.setParent(null); if (parentForm != null) { if (parentForm.getFocused() == cmp || cmp instanceof Container && ((Container) cmp).contains(parentForm.getFocused())) { parentForm.setFocused(null); } if (cmp.isSmoothScrolling()) { parentForm.deregisterAnimatedInternal(cmp); } } Display.getInstance().getImplementation().cancelRepaint(cmp); if(cmp instanceof Form) { cmp.setVisible(false); } setShouldCalcPreferredSize(true); } /** * Cleansup the initialization flags in the hierachy */ void deinitializeImpl() { super.deinitializeImpl(); int size = components.size(); for (int iter = 0; iter < size; iter++) { ((Component) components.elementAt(iter)).deinitializeImpl(); } flushReplace(); } /** * Flushes ongoing replace operations to prevent two concurrent replace operations from colliding. * If there is no ongoing replace nothing will occur */ public void flushReplace() { if (cmpTransitions != null) { int size = cmpTransitions.size(); for (int iter = 0; iter < size; iter++) { ((Anim) cmpTransitions.elementAt(iter)).destroy(); } cmpTransitions.removeAllElements(); cmpTransitions = null; } } /** * remove all Components from container, notice that removed component might still have * a pending repaint in the queue that won't be removed. Calling form.repaint() will workaround * such an issue. */ public void removeAll() { Form parentForm = getComponentForm(); if (parentForm != null) { Component focus = parentForm.getFocused(); if (focus != null && contains(focus)) { parentForm.setFocused(null); } } Object[] arr = new Object[components.size()]; components.copyInto(arr); for (int i = 0; i < arr.length; i++) { removeComponent((Component) arr[i]); } } /** * Re-layout the container, this is useful when we modify the container hierarchy and * need to redo the layout */ public void revalidate() { setShouldCalcPreferredSize(true); Form root = getComponentForm(); if (root != null) { root.layoutContainer(); root.repaint(); } else { layoutContainer(); repaint(); } } /** * @inheritDoc */ public void paint(Graphics g) { if(enableLayoutOnPaint) { layoutContainer(); } g.translate(getX(), getY()); int size = components.size(); LWUITImplementation impl = Display.getInstance().getImplementation(); for (int i = 0; i < size; i++) { Component cmp = (Component)components.elementAt(i); cmp.paintInternal(impl.getComponentScreenGraphics(this, g), false); } int tx = g.getTranslateX(); int ty = g.getTranslateY(); g.translate(-tx, -ty); paintGlass(g); g.translate(tx, ty); g.translate(-getX(), -getY()); } /** * 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) { super.paintGlassImpl(g); int tx = g.getTranslateX(); int ty = g.getTranslateY(); g.translate(-tx, -ty); paintGlass(g); g.translate(tx, ty); } /** * 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 */ protected void paintGlass(Graphics g) { } void paintIntersecting(Graphics g, Component cmp, int x, int y, int w, int h, boolean above) { if (layout.isOverlapSupported() && components.contains(cmp)) { int indexOfComponent = components.indexOf(cmp); int startIndex; int endIndex; if (above) { startIndex = indexOfComponent + 1; endIndex = components.size(); } else { startIndex = 0; endIndex = indexOfComponent; } for (int i = startIndex; i < endIndex; i++) { Component cmp2 = (Component) components.elementAt(i); if(Rectangle.intersects(x, y, w, h, cmp2.getAbsoluteX() + cmp2.getScrollX(), cmp2.getAbsoluteY() + cmp2.getScrollY(), cmp2.getBounds().getSize().getWidth(), cmp2.getBounds().getSize().getHeight())){ cmp2.paintInternal(g, false); } } } } /** * Performs the layout of the container if a layout is necessary */ public void layoutContainer() { //will compute the container + components and will layout the components. if (shouldLayout) { shouldLayout = false; doLayout(); } } /** * Lays out the container */ void doLayout() { layout.layoutContainer(this); int count = getComponentCount(); for (int i = 0; i < count; i++) { Component c = getComponentAt(i); if (c instanceof Container) { ((Container) c).layoutContainer(); }else{ c.laidOut(); } } laidOut(); } /** * Returns the number of components * * @return the Component count */ public int getComponentCount() { return components.size(); } /** * Returns the Component at a given index * * @param index of the Component you wish to get * @return a Component * @throws ArrayIndexOutOfBoundsException if an invalid index was given. */ public Component getComponentAt( int index) { return (Component) components.elementAt(index); } /** * Returns the Component index in the Container * * @param cmp the component to search for * @return the Component index in the Container or -1 if not found */ public int getComponentIndex(Component cmp) { int count = getComponentCount(); for (int i = 0; i < count; i++) { Component c = getComponentAt(i); if (c.equals(cmp)) { return i; } } return -1; } /** * Returns true if the given component is within the hierarchy of this container * * @param cmp a Component to check * @return true if this Component contains in this Container */ public boolean contains(Component cmp) { boolean found = false; int count = getComponentCount(); for (int i = 0; i < count; i++) { Component c = getComponentAt(i); if (c.equals(cmp)) { return true; } if (c instanceof Container) { found = ((Container) c).contains(cmp); if (found) { return true; } } } return false; } /** * Makes sure the component is visible in the scroll if this container is * scrollable * * @param c the component that will be scrolling for visibility */ protected void scrollComponentToVisible(Component c) { if (isScrollable()) { if (c != null) { Rectangle r = c.getVisibleBounds(); if (c.getParent() != null) { // special case for the first component to allow the user to scroll all the // way to the top Form f = getComponentForm(); if (f != null && f.findFirstFocusable() == c) { // support this use case only if the component doesn't explicitly declare visible bounds if(r == c.getBounds()) { scrollRectToVisible(new Rectangle(0, 0, c.getX() + Math.min(c.getWidth(), getWidth()), c.getY() + Math.min(c.getHeight(), getHeight())), this); return; } } } scrollRectToVisible(r.getX(), r.getY(), Math.min(r.getSize().getWidth(), getWidth()), Math.min(r.getSize().getHeight(), getHeight()), c); } } } /** * This method scrolls the Container if Scrollable towards the given * Component based on the given direction. * * @param direction is the direction of the navigation (Display.GAME_UP, * Display.GAME_DOWN, ...) * @param next the Component to move the scroll towards. * * @return true if next Component is now visible. */ boolean moveScrollTowards(int direction, Component next) { if (isScrollable()) { Component current = null; Form f = getComponentForm(); current = f.getFocused(); boolean cyclic = f.isCyclicFocus(); f.setCyclicFocus(false); boolean edge = false; boolean currentLarge = false; boolean scrollOutOfBounds = false; int x = getScrollX(); int y = getScrollY(); int w = getWidth(); int h = getHeight(); switch (direction) { case Display.GAME_UP: y = getScrollY() - scrollIncrement; edge = f.findNextFocusUp() == null; currentLarge = (current != null && current.getVisibleBounds().getSize().getHeight() > getHeight()); scrollOutOfBounds = y < 0; if(scrollOutOfBounds){ y = 0; } break; case Display.GAME_DOWN: y = getScrollY() + scrollIncrement; edge = f.findNextFocusDown() == null; currentLarge = (current != null && current.getVisibleBounds().getSize().getHeight() > getHeight()); scrollOutOfBounds = y > getScrollDimension().getHeight() - getHeight(); if(scrollOutOfBounds){ y = getScrollDimension().getHeight() - getHeight(); } break; case Display.GAME_RIGHT: x = getScrollX() + scrollIncrement; edge = f.findNextFocusRight() == null; currentLarge = (current != null && current.getVisibleBounds().getSize().getWidth() > getWidth()); scrollOutOfBounds = x > getScrollDimension().getWidth() - getWidth(); if(scrollOutOfBounds){ x = getScrollDimension().getWidth() - getWidth(); } break; case Display.GAME_LEFT: x = getScrollX() - scrollIncrement; edge = f.findNextFocusLeft() == null; currentLarge = (current != null && current.getVisibleBounds().getSize().getWidth() > getWidth()); scrollOutOfBounds = x < 0; if(scrollOutOfBounds){ x = 0; } break; } f.setCyclicFocus(cyclic); //if the Form doesn't contain a focusable Component simply move the //viewport by pixels if(next == null || next == this){ scrollRectToVisible(x, y, w, h, this); return false; } boolean nextIntersects = contains(next) && Rectangle.intersects(next.getAbsoluteX(), next.getAbsoluteY(), next.getWidth(), next.getHeight(), getAbsoluteX() + x, getAbsoluteY() + y, w, h); if ((nextIntersects && !currentLarge && !edge) || (Rectangle.contains( getAbsoluteX() + getScrollX(), getAbsoluteY() + getScrollY(), w, h, next.getAbsoluteX(), next.getAbsoluteY(), next.getWidth(), next.getHeight()))) { //scrollComponentToVisible(next); return true; } else { if (!scrollOutOfBounds) { scrollRectToVisible(x, y, w, h, this); //if after moving the scroll the current focus is out of the //view port and the next focus is in the view port move //the focus if (nextIntersects && !Rectangle.intersects(current.getAbsoluteX(), current.getAbsoluteY(), current.getWidth(), current.getHeight(), getAbsoluteX() + x, getAbsoluteY() + y, w, h)) { return true; } return false; } else { //scrollComponentToVisible(next); return true; } } } return true; } /** * Returns a Component that exists in the given x, y coordinates by traversing * component objects and invoking contains * * @param x absolute screen location * @param y absolute screen location * @return a Component if found, null otherwise * @see Component#contains */ public Component getComponentAt(int x, int y) { int count = getComponentCount(); boolean overlaps = getLayout().isOverlapSupported(); Component component = null; for (int i = count - 1; i >= 0; i--) { Component cmp = getComponentAt(i); if (cmp.contains(x, y)) { component = cmp; if (!overlaps && component.isFocusable()) { return component; } if (cmp instanceof Container) { component = ((Container) cmp).getComponentAt(x, y); } if (!overlaps || component.isFocusable() || component.isGrabsPointerEvents()) { return component; } } } if (component != null){ return component; } if (contains(x, y)) { return this; } return null; } /** * @inheritDoc */ public void pointerHover(int[] x, int[] y) { if(!isDragActivated()) { Component c = getComponentAt(x[0], y[0]); if(c != null && c.isFocusable()) { c.requestFocus(); } } super.pointerDragged(x[0], y[0]); } /** * @inheritDoc */ public void pointerPressed(int x, int y) { clearDrag(); setDragActivated(false); Component cmp = getComponentAt(x, y); if (cmp == this) { super.pointerPressed(x, y); } else if (cmp != null) { cmp.pointerPressed(x, y); } } /** * @inheritDoc */ protected Dimension calcPreferredSize() { Dimension d = layout.getPreferredSize(this); Style style = getStyle(); if(style.getBorder() != null && d.getWidth() != 0 && d.getHeight() != 0) { d.setWidth(Math.max(style.getBorder().getMinimumWidth(), d.getWidth())); d.setHeight(Math.max(style.getBorder().getMinimumHeight(), d.getHeight())); } return d; } /** * @inheritDoc */ protected String paramString() { String className = layout.getClass().getName(); String layoutStr = className.substring(className.lastIndexOf('.') + 1); return super.paramString() + ", layout = " + layoutStr + ", scrollableX = " + scrollableX + ", scrollableY = " + scrollableY + ", components = " + getComponentsNames(); } /** * Return the conatainer components objects as list of Strings * @return the conatainer components objects as list of Strings */ private String getComponentsNames() { String ret = "["; int size = components.size(); for(int iter = 0 ; iter < size ; iter++) { String className = components.elementAt(iter).getClass().getName(); ret += className.substring(className.lastIndexOf('.') + 1) + ", "; } if (ret.length() > 1) { ret = ret.substring(0, ret.length() - 2); } ret = ret + "]"; return ret; } /** * @inheritDoc */ public void refreshTheme() { super.refreshTheme(); int size = components.size(); for(int iter = 0 ; iter < size ; iter++) { Component cmp = (Component) components.elementAt(iter); cmp.refreshTheme(); } } /** * @inheritDoc */ public boolean isScrollableX() { return scrollableX && (getScrollDimension().getWidth() > getWidth()); } /** * @inheritDoc */ public boolean isScrollableY() { return scrollableY && (getScrollDimension().getHeight() > getHeight() || isAlwaysTensile()); } /** * @inheritDoc */ public int getSideGap() { // isScrollableY() in the base method is very expensive since it triggers getScrollDimension before the layout is complete! if(scrollSize == null) { if (scrollableY && isScrollVisible()) { return UIManager.getInstance().getLookAndFeel().getVerticalScrollWidth(); } } else { return super.getSideGap(); } return 0; } /** * @inheritDoc */ public int getBottomGap() { // isScrollableY() in the base method is very expensive since it triggers getScrollDimension before the layout is complete! if (scrollableX && isScrollVisible()) { return UIManager.getInstance().getLookAndFeel().getHorizontalScrollHeight(); } return 0; } /** * Sets whether the component should/could scroll on the X axis * * @param scrollableX whether the component should/could scroll on the X axis */ public void setScrollableX(boolean scrollableX) { this.scrollableX = scrollableX; } /** * Sets whether the component should/could scroll on the Y axis * * @param scrollableY whether the component should/could scroll on the Y axis */ public void setScrollableY(boolean scrollableY) { this.scrollableY = scrollableY; } /** * The equivalent of calling both setScrollableY and setScrollableX * * @param scrollable whether the component should/could scroll on the * X and Y axis */ public void setScrollable(boolean scrollable) { setScrollableX(scrollable); setScrollableY(scrollable); } /** * @inheritDoc */ public void setCellRenderer(boolean cellRenderer) { if (isCellRenderer() != cellRenderer) { super.setCellRenderer(cellRenderer); int size = getComponentCount(); for (int iter = 0; iter < size; iter++) { getComponentAt(iter).setCellRenderer(cellRenderer); } } } /** * Determines the scroll increment size of this Container. * This value is in use when the current foucs element within this Container * is larger than this Container size. * * @param scrollIncrement the size in pixels. */ public void setScrollIncrement(int scrollIncrement) { this.scrollIncrement = scrollIncrement; } /** * Gets the Container scroll increment * * @return the scroll increment in pixels. */ public int getScrollIncrement() { return scrollIncrement; } /** * Finds the first focusable Component on this Container * * @return a focusable Component or null if not exists; */ public Component findFirstFocusable() { int size = getComponentCount(); for (int iter = 0; iter < size; iter++) { Component current = getComponentAt(iter); if(current.isVisible()) { if(current.isFocusable()){ return current; } if (current instanceof Container && !((Container)current).isBlockFocus()) { Component cmp = ((Container)current).findFirstFocusable(); if(cmp != null){ return cmp; } } } } return null; } /** * Recusively focuses components for the lead component functionality */ private void setFocusLead(boolean f) { int count = getComponentCount(); for (int i = 0; i < count; i++) { Component c = getComponentAt(i); if(c instanceof Container) { ((Container)c).setFocusLead(f); } c.setFocus(f); if(f) { c.fireFocusGained(); } else { c.fireFocusLost(); } } } /** * @inheritDoc */ protected void dragInitiated() { super.dragInitiated(); if(leadComponent != null) { leadComponent.dragInitiated(); } } /** * This method will recursively set all the Container chidrens to be * enabled/disabled. * If the Container is disabled and a child Component changed it's state to * be enabled, the child Component will be treated as an enabled Component. * @param enabled */ public void setEnabled(boolean enabled) { super.setEnabled(enabled); int count = getComponentCount(); for (int i = 0; i < count; i++) { Component c = getComponentAt(i); c.setEnabled(enabled); } } /** * @inheritDoc */ protected int getGridPosY() { int scroll = getScrollY(); int size = getComponentCount(); int bestRow = 0; for(int iter = 0 ; iter < size ; iter++) { Component c = getComponentAt(iter); int y = c.getY(); if(Math.abs(scroll - y) < Math.abs(scroll - bestRow)) { bestRow = y; } } if(Math.abs(scroll - bestRow) > 2) { return bestRow; } return scroll; } /** * Returns false for the special case where a container has an opaque/flattened child that * occupies its entire face */ private boolean shouldPaintContainerBackground() { if(getComponentCount() == 1) { Style s = getStyle(); if(s.getPadding(TOP) == 0 && s.getPadding(BOTTOM) == 0 && s.getPadding(LEFT) == 0 && s.getPadding(RIGHT) == 0) { Component c = getComponentAt(0); if(c.getWidth() == getWidth() && c.getHeight() == getHeight()) { if(c.isFlatten() || (c.getStyle().getBgTransparency() & 0xff) == 0xff) { return false; } if(c instanceof Container) { return ((Container)c).shouldPaintContainerBackground(); } } } } return true; } /** * @inheritDoc */ public void paintBackground(Graphics g) { if(isFlatten()) { super.paintBackgrounds(g); return; } if(shouldPaintContainerBackground()) { super.paintBackground(g); } } /** * @inheritDoc */ protected int getGridPosX() { int scroll = getScrollX(); int size = getComponentCount(); int bestCol = 0; for(int iter = 0 ; iter < size ; iter++) { Component c = getComponentAt(iter); int x = c.getX(); if(Math.abs(scroll - x) < Math.abs(scroll - bestCol)) { bestCol = x; } } if(Math.abs(scroll - bestCol) > 2) { return bestCol; } return scroll; } /** * This method blocks all children from getting focus * * @param blockFocus */ void setBlockFocus(boolean blockFocus) { this.blockFocus = blockFocus; } /** * Returns true if focus is blocked for this Container * * @return */ boolean isBlockFocus() { return blockFocus; } /** * Animates a pending layout into place, this effectively replaces revalidate with a more visual form of animation. This method * waits until the operation is completed before returning * * @param duration the duration in milliseconds for the animation */ public void animateLayoutAndWait(final int duration) { animateLayout(duration, true); } /** * Animates a pending layout into place, this effectively replaces revalidate with a more visual form of animation * * @param duration the duration in milliseconds for the animation */ public void animateLayout(final int duration) { animateLayout(duration, false); } /** * @inheritDoc */ public void drop(Component dragged, int x, int y) { int i = getComponentIndex(dragged); if(i > -1) { Component dest = getComponentAt(x, y); if(dest != dragged) { int destIndex = getComponentIndex(dest); if(destIndex > -1 && destIndex != i) { removeComponent(dragged); Object con = getLayout().getComponentConstraint(dragged); if(con != null) { addComponent(destIndex, con, dragged); } else { addComponent(destIndex, dragged); } } } } animateLayout(400); } /** * Animates a pending layout into place, this effectively replaces revalidate with a more visual form of animation * * @param duration the duration in milliseconds for the animation */ private void animateLayout(final int duration, boolean wait) { enableLayoutOnPaint = false; final int componentCount = getComponentCount(); int[] beforeX = new int[componentCount]; int[] beforeY = new int[componentCount]; int[] beforeW = new int[componentCount]; int[] beforeH = new int[componentCount]; final Motion[] xMotions = new Motion[componentCount]; final Motion[] yMotions = new Motion[componentCount]; final Motion[] wMotions = new Motion[componentCount]; final Motion[] hMotions = new Motion[componentCount]; for(int iter = 0 ; iter < componentCount ; iter++) { Component current = getComponentAt(iter); beforeX[iter] = current.getX(); beforeY[iter] = current.getY(); beforeW[iter] = current.getWidth(); beforeH[iter] = current.getHeight(); } layoutContainer(); for(int iter = 0 ; iter < componentCount ; iter++) { Component current = getComponentAt(iter); xMotions[iter] = Motion.createSplineMotion(beforeX[iter], current.getX(), duration); yMotions[iter] = Motion.createSplineMotion(beforeY[iter], current.getY(), duration); wMotions[iter] = Motion.createSplineMotion(beforeW[iter], current.getWidth(), duration); hMotions[iter] = Motion.createSplineMotion(beforeH[iter], current.getHeight(), duration); xMotions[iter].start(); yMotions[iter].start(); wMotions[iter].start(); hMotions[iter].start(); current.setX(beforeX[iter]); current.setY(beforeY[iter]); current.setWidth(beforeW[iter]); current.setHeight(beforeH[iter]); } Anim a = new Anim(this, duration, new Motion[][] { xMotions, yMotions, wMotions, hMotions }); getComponentForm().registerAnimated(a); if(wait) { Display.getInstance().invokeAndBlock(a); } } static class Anim implements Animation, Runnable { private int animationType; private long startTime; private int duration; private Transition t; private Component current; private Component next; private boolean started = false; private Container thisContainer; private boolean finished = false; private Form parent; private Motion[][] motions; Runnable onFinish; int growSpeed; int layoutAnimationSpeed; public Anim(Container thisContainer, int duration, Motion[][] motions) { startTime = System.currentTimeMillis(); animationType = 2; this.duration = duration; this.thisContainer = thisContainer; this.motions = motions; } public Anim(Container thisContainer, Component current, Component next, Transition t) { animationType = 1; this.t = t; this.next = next; this.current = current; this.thisContainer = thisContainer; this.parent = thisContainer.getComponentForm(); } public boolean animate() { switch(animationType) { case 2: int componentCount = thisContainer.getComponentCount(); for(int iter = 0 ; iter < componentCount ; iter++) { Component currentCmp = thisContainer.getComponentAt(iter); // this might happen if a container was replaced during animation if(currentCmp == null) { continue; } currentCmp.setX(motions[0][iter].getValue()); currentCmp.setY(motions[1][iter].getValue()); currentCmp.setWidth(motions[2][iter].getValue()); currentCmp.setHeight(motions[3][iter].getValue()); } thisContainer.repaint(); if(System.currentTimeMillis() - startTime >= duration) { enableLayoutOnPaint = true; Form f = thisContainer.getComponentForm(); f.deregisterAnimated(this); f.revalidate(); synchronized(this) { finished = true; notify(); } } return false; default: if (!started) { t.init(current, next); t.initTransition(); started = true; if (thisContainer.cmpTransitions == null) { thisContainer.cmpTransitions = new Vector(); } thisContainer.cmpTransitions.addElement(this); } boolean notFinished = t.animate(); if (!notFinished) { thisContainer.cmpTransitions.removeElement(this); destroy(); } return notFinished; } } public void destroy() { parent.deregisterAnimatedInternal(this); next.setParent(null); thisContainer.replace(current, next, growSpeed > 0 || layoutAnimationSpeed > 0); //release the events blocking t.cleanup(); if(thisContainer.cmpTransitions.size() == 0 && growSpeed > -1){ if(growSpeed > 0) { current.growShrink(growSpeed); } else { if(layoutAnimationSpeed <= 0) { parent.revalidate(); } } } synchronized(this) { finished = true; notify(); } if(onFinish != null) { onFinish.run(); } } public void paint(Graphics g) { t.paint(g); } public boolean isFinished() { return finished; } public void run() { while (!isFinished()) { try { synchronized(this) { wait(50); } } catch (InterruptedException ex) { ex.printStackTrace(); } } } } }