All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.ui.ComponentSelector Maven / Gradle / Ivy

There is a newer version: 7.0.161
Show newest version
/*
 * Copyright (c) 2012, Codename One 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.  Codename One 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 Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.ui;


import com.codename1.components.SpanButton;
import com.codename1.components.SpanLabel;
import com.codename1.io.Util;
import com.codename1.ui.animations.CommonTransitions;
import com.codename1.ui.animations.ComponentAnimation;
import com.codename1.ui.animations.Transition;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.events.ActionSource;
import com.codename1.ui.events.DataChangedListener;
import com.codename1.ui.events.FocusListener;
import com.codename1.ui.events.ScrollListener;
import com.codename1.ui.events.StyleListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.Layout;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.Style;
import com.codename1.util.SuccessCallback;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * A tool to facilitate selection and manipulation of UI components as sets.  This uses fluent API style, similar
 * to jQuery to make it easy to find UI components and modify them as groups. 
 * 
 * 

Set Selection

* *

Sets of components can either be created by explicitly adding components to the set, or by * providing a "selector" string that specifies how the set should be formed. Some examples:

* *
    *
  • {@code $("Label") } - The set of all components on the current form with UIID="Label"
  • *
  • {@code $("#AddressField") } - The set of components with name="AddressField"
  • *
  • {@code $("TextField#AddressField") } - The set of components with UIID=TextField and Name=AddressField
  • *
  • {@code $("Label, Button")} - Set of components in current form with UIID="Label" or UIID="Button"
  • *
  • {@code $("Label", myContainer)} - Set of labels within the container {@code myContainer}
  • *
  • {@code $("MyContainer *") } - All descendants of the container with UIID="MyContainer". Will not include "MyContainer".
  • *
  • {@code $("MyContainer > *") } - All children of of the container with UIID="MyContainer".
  • *
  • {@code $("MyContainer > Label) } - All children with UIID=Label of container with UIID=MyContainer
  • *
  • {@code $("Label").getParent() } - All parent components of labels in the current form.
  • *
* *

Tags

* *

To make selection more flexible, you can "tag" components so that they can be easily targeted by a selector. * You can add tags to components using {@link #addTags(java.lang.String...) }, and remove them using {@link #removeTags(java.lang.String...) }. * Once you have tagged a component, it can be targeted quite easily using a selector. Tags are specified in a selector with a {@literal .} * prefix. E.g.:

* *
    *
  • {@code $(".my-tag") } - The set of all components with tag "my-tag".
  • *
  • {@code $("Label.my-tag") } - The set of all components with tag "my-tag" and UIID="Label"
  • *
  • {@code $("Label.my-tag.myother-tag")} - The set of all components with tags "my-tag" and "myother-tag" and UIID="Label". Matches only components that include all of those tags.
  • *
* *

Modifying Components in Set

* *

While component selection alone in the ComponentSelector is quite powerful, the true power comes when * you start to operate on the entire set of components using the Fluent API of ComponentSelector. ComponentSelector * includes wrapper methods for most of the mutator methods of {@link Component}, {@link Container}, and a few other common * component types.

* *

For example, the following two snippets are equivalent:

* *
 * {@code
 * for (Component c : $("Label")) {
 *     c.getStyle().setFgColor(0xff0000);
 * }
 * }
 * 
* * and * *
 * {@code 
 * $("Label").setFgColor(0xff0000);
 * }
 * 
* *

The second snippet is clearly easier to type and more compact. But we can take it further. The Fluent API * style allows you to chain together multiple method calls. This even makes it desirable to operate on * single-element sets. E.g.:

* *
 * {@code
 * Button myButton = $(new Button("Some text"))
        .setUIID("Label")
        .addTags("cell", "row-"+rowNum, "col-"+colNum, rowNum%2==0 ? "even":"odd")
        .putClientProperty("row", rowNum)
        .putClientProperty("col", colNum)
        .asComponent(Button.class);
 * }
 * 
* *

* The above snippet wraps a new Button in a ComponentSelector, then uses the fluent API to apply several properties * to the button, before using {@link #asComponent()} to return the Button itself. *

* *

API Overview

* *

ComponentSelector includes a few different types of methods:

* *
    *
  1. Wrapper methods for {@link Component}, {@link Container}, etc... to operate on all applicable components in the set.
  2. *
  3. Component Tree Traversal Methods to return other sets of components based on the current set. E.g. {@link #find(java.lang.String) }, {@link #getParent()}, {@link #getComponentAt(int) }, * {@link #closest(java.lang.String) }, {@link #nextSibling() }, {@link #prevSibling() }, {@link #parents(java.lang.String) }, * {@link #getComponentForm() }, and many more.
  4. *
  5. Effects. E.g. {@link #fadeIn() }, {@link #fadeOut() }, {@link #slideDown() }, {@link #slideUp() }, {@link #animateLayout(int) }, * {@link #animateHierarchy(int) }, etc..
  6. *
  7. Convenience methods to help with common tasks. E.g. {@link #$(java.lang.Runnable) } as a short-hand for {@link Display#callSerially(java.lang.Runnable) } *
  8. Methods to implement {@link java.util.Set} because ComponentSelector is a set.
  9. *
* * *

Effects

* *

The following is an example form that demonstrates the use of ComponentSelector to * easily create effects on components in a form.

* *

*

* *

Advanced Use of Tags

* *

The following shows the use of tags to help with striping a table, and selecting rows * when clicked on.

* *

*

* *

See full Demo App in this Github Repo

* *

Modifying Style Properties

* *

Modifying styles deserves special mention because components have multiple {@link Style} objects associated with them. * {@link Component#getStyle() } returns the style of the component in its current state. {@link Component#getPressedStyle() } * gets the pressed style of the component, {@link Component#getSelectedStyle() } get its selected style, etc..

* *

ComponentSelector wraps each of the {@literal getXXXStyle() } methods of {@link Component} with corresponding methods * that return proxy styles for all components in the set. {@link #getStyle() } returns a proxy {@link Style} that proxies * all of the styles returned from each of the {@link Component#getStyle()} methods in the set. {@link #getPressedStyle() } returns * a proxy for all of the pressed styles, etc..

* *

Example Modifying Text Color of All Buttons in a container when they are pressed only

* *
 * {@code
 * Style pressed = $("Button", myContainer).getPressedStyle();
 * pressed.setFgColor(0xff0000);
 * }
 * 
* *

A slightly more elegant pattern would be to use the {@link #selectPressedStyle() } method to set the default * style for mutations to "pressed". Then we could use the fluent API of ComponentSelector to chain multiple style * mutations. E.g.: *

* *
 * {@code 
 * $("Button", myContainer)
 *     .selectPressedStyle()
 *     .setFgColor(0xffffff)
 *     .setBgColor(0x0)
 *     .setBgTransparency(255);
 * }
 * 
* *

A short-hand for this would be to add the {@literal :pressed} pseudo-class to the selector. E.g.

* *
 * {@code 
 * $("Button:pressed", myContainer)
 *     .setFgColor(0xffffff)
 *     .setBgColor(0x0)
 *     .setBgTransparency(255);
 * }
 * 
* *

The following style pseudo-classes are supported:

* *
    *
  • {@literal :pressed} - Same as calling {@link #selectPressedStyle() }
  • *
  • {@literal :selected} - Same as calling {@link #selectSelectedStyle() }
  • *
  • {@literal :unselected} - Same as calling {@link #selectUnselectedStyle() }
  • *
  • {@literal :all} - Same as calling {@link #selectAllStyles() }
  • *
  • {@literal :*} - Alias for {@literal :all}
  • *
  • {@literal :disabled} - Same as calling {@link #selectDisabledStyle() }
  • *
* *

You can chain calls to {@literal selectedXXXStyle()}, enabling to chain together mutations of multiple different * style properties. E.g To change the pressed foreground color, and then change the selected foreground color, you could do:

* *
 * {@code
 * $("Button", myContainer)
 *    .selectPressedStyle()
 *    .setFgColor(0x0000ff)
 *    .selectSelectedStyle()
 *    .setFgColor(0x00ff00);
 * }
 * 
* *

Filtering Sets

* *

There are many ways to remove components from a set. Obviously you can use the standard {@link java.util.Set } methods * to explicitly remove components from your set:

* *
 * {@code
 * ComponentSelector sel = $("Button").remove(myButton, true);
 *     // The set of all buttons on the current form, except myButton
 * }
 * 
* * or * *
 * {@code
 * ComponentSelector sel = $("Button").removeAll($(".some-tag"), true);
 *    // The set of all buttons that do NOT contain the tag ".some-tag"
 * }
 * 
* *

You could also use the {@link #filter(com.codename1.ui.ComponentSelector.Filter) } to explicitly * declare which elements should be kept, and which should be discarded:

* *
 * {@code 
 * ComponentSelector sel = $("Button").filter(c->{
 *     return c.isVisible();
 * });
 *     // The set of all buttons that are currently visible.
 * }
 * 
* *

Tree Navigation

* *

* One powerful aspect of working with sets of components is that you can generate very specific * sets of components using very simple queries. Consider the following queries:

* *
    *
  • {@code $(myButton1, myButton2).getParent()} - The set of parents of {@literal myButton1} and {@literal myButton2}. If they have the * same parent, then this set will only contain a single element: the common parent container. If they have different parents, then this * set will include both parent containers.
  • *
  • {@code $(myButton).getParent().find(">TextField")} - The set of siblings of {@literal myButton} that have UIID=TextField
  • *
  • {@code $(myButton).closest(".some-tag")} - The set containing the "nearest" parent container of {@literal myButton} that has * the tag ".some-tag". If there are no matching components, then this will be an empty set. This is formed by crawling up the tree * until it finds a matching component. Works the same as jQuery's {@literal closest() } method.
  • *
  • {@code $(".my-tag").getComponentAt(4)} - The set of 5th child components of containers with tag ".my-tag".
  • *
* * @see jQuery/CSS Style Selectors for Codename One * @author shannah */ public class ComponentSelector implements Iterable, Set { private static final String PROPERTY_TAG = "com.codename1.ui.ComponentSelector#tags"; private String name; private String uiid; private String[] tags; private String[] tagsNeedles; private String state; private ComponentSelector parent; private Set aggregateSelectors; private final Set roots; private Set results; private boolean childrenOnly = false; private Style currentStyle = null; private int currentStyleType = 0; private static final int ALL_STYLES=1; private static final int PRESSED_STYLE=2; private static final int SELECTED_STYLE=3; private static final int UNSELECTED_STYLE=4; private static final int DISABLED_STYLE=5; private Style currentStyle() { if (currentStyle == null) { switch (currentStyleType) { case ALL_STYLES: currentStyle = getAllStyles(); break; case PRESSED_STYLE: currentStyle = getPressedStyle(); break; case SELECTED_STYLE: currentStyle = getSelectedStyle(); break; case UNSELECTED_STYLE: currentStyle = getUnselectedStyle(); break; case DISABLED_STYLE: currentStyle = getDisabledStyle(); break; default: currentStyle = getStyle(); break; } } return currentStyle; } /** * Interface used for providing callbacks that receive a Component as input. */ public static interface ComponentClosure { /** * Callback to apply. * @param c Component that is passed to the closure. */ public void call(Component c); } /** * Interface used by {@link #map(com.codename1.ui.ComponentSelector.ComponentMapper) } to form a new set of * components based on the components in one set. */ public static interface ComponentMapper { /** * Maps component {@literal c } to a replacement component. * @param c The source component. * @return The component that should replace {@literal c} in the new set. */ public Component map(Component c); } /** * Interface used by {@link #filter(com.codename1.ui.ComponentSelector.Filter) } to form a new set of * components based on the components in one set. */ public static interface Filter { /** * Determines whether component {@literal c} should be included in new set. * @param c The component to test for inclusion in new set. * @return True if {@literal c } should be included in new set. False otherwise */ public boolean filter(Component c); } /** * Wraps provided components in a ComponentSelector set. * @param cmps Components to be includd in the set. * @return ComponentSelector with specified components. */ public static ComponentSelector $(Component... cmps) { return new ComponentSelector(cmps); } /** * Alias of {@link #$(com.codename1.ui.Component...) } * @param cmps * @return */ public static ComponentSelector select(Component... cmps) { return $(cmps); } private void setDirty() { currentStyle = null; } /** * Creates a ComponentInspector with the source component of the provided event. * @param e The event whose source component is added to the set. * @return A ComponentSelector with a single component - the source of the event. */ public static ComponentSelector $(ActionEvent e) { Object src = e.getSource(); if (src == null) { return new ComponentSelector(); } else if (src instanceof Component) { return new ComponentSelector((Component)src); } return new ComponentSelector(); } /** * Alias of {@link #$(com.codename1.ui.events.ActionEvent) } * @param e * @return */ public static ComponentSelector select(ActionEvent e) { return $(e); } /** * Wraps {@link Display#callSerially(java.lang.Runnable) } * @param r * @return Empty ComponentSelector. */ public static ComponentSelector $(Runnable r) { Display.getInstance().callSerially(r); return new ComponentSelector(); } /** * Alias of {@link #$(java.lang.Runnable) } * @param r * @return */ public static ComponentSelector select(Runnable r) { return $(r); } /** * Applies the given callback to each component in the set. * @param closure Callback which will be called once for each component in the set. * @return Self for chaining. */ public ComponentSelector each(ComponentClosure closure) { for (Component c : this) { closure.call(c); } return this; } /** * Creates a new set based on the elements of the current set and a mapping function * which defines the elements that should be in the new set. * @param mapper The mapper which will be called once for each element in the set. The return value * of the mapper function dictates which component should be included in the resulting set. * @return A new set of components. */ public ComponentSelector map(ComponentMapper mapper) { LinkedHashSet out = new LinkedHashSet(); for (Component c : this) { Component res = mapper.map(c); if (res != null) { out.add(res); } } return new ComponentSelector(out); } /** * Creates a new set of components formed by filtering the current set using a filter function. * @param filter The filter function called for each element in the set. If it returns true, * then the element is included in the resulting set. If false, it will not be included. * @return A new set with the results of the filter. */ public ComponentSelector filter(Filter filter) { LinkedHashSet out = new LinkedHashSet(); for (Component c : this) { boolean res = filter.filter(c); if (res) { out.add(c); } } return new ComponentSelector(out); } /** * Filters the current found set against the given selector. * @param selector The selector to filter the found set on. * @return A new set of elements matching the selector. */ public ComponentSelector filter(String selector) { ComponentSelector matcher = new ComponentSelector(selector, new Label()); LinkedHashSet matches = new LinkedHashSet(); for (Component c : this) { if (matcher.match(c)) { matches.add(c); } } return matcher.addAll(matches, true); } /** * Creates a new set of components consisting of all of the parents of components in this set. * Only parent components matching the provided selector will be included in the set. * @param selector Selector to filter the parent components. * @return New set with parents of elements in current set. */ public ComponentSelector parent(String selector) { ComponentSelector matcher = new ComponentSelector(selector, new Label()); LinkedHashSet matches = new LinkedHashSet(); for (Component c : this) { Component parent = c.getParent(); if (parent != null && matcher.match(parent)) { matches.add(parent); } } return matcher.addAll(matches, true); } /** * Creates new set of components consisting of all of the ancestors of components in this set which * match the provided selector. * @param selector The selector to filter the ancestors. * @return New set with ancestors of elements in current set. */ public ComponentSelector parents(String selector) { ComponentSelector matcher = new ComponentSelector(selector, new Label()); LinkedHashSet matches = new LinkedHashSet(); for (Component c : this) { Component parent = c.getParent(); while (parent != null) { if (matcher.match(parent)) { matches.add(parent); } parent = parent.getParent(); } } return matcher.addAll(matches, true); } /** * Creates a new set of components consistng of all "closest" ancestors of components * in this set which match the provided selector. * @param selector The selector to use to match the nearest ancestor. * @return New set with ancestors of components in current set. */ public ComponentSelector closest(String selector) { ComponentSelector matcher = new ComponentSelector(selector, new Label()); LinkedHashSet matches = new LinkedHashSet(); for (Component c : this) { Component parent = c.getParent(); while (parent != null) { if (matcher.match(parent)) { matches.add(parent); break; } parent = parent.getParent(); } } return matcher.addAll(matches, true); } /** * Creates new set consisting of the first child of each component in the current set. * @return New set with first child of each component in current set. */ public ComponentSelector firstChild() { LinkedHashSet out = new LinkedHashSet(); for (Component c : this) { if (c instanceof Container) { Container cnt = (Container)c; if (cnt.getComponentCount() > 0) { out.add(cnt.getComponentAt(0)); } } } return new ComponentSelector(out); } /** * Creates new set consisting of the last child of each component in the current set. * @return New set with last child of each component in current set. */ public ComponentSelector lastChild() { LinkedHashSet out = new LinkedHashSet(); for (Component c : this) { if (c instanceof Container) { Container cnt = (Container)c; if (cnt.getComponentCount() > 0) { out.add(cnt.getComponentAt(cnt.getComponentCount()-1)); } } } return new ComponentSelector(out); } /** * Creates set of "next" siblings of components in this set. * @return New ComponentSelector with next siblings of this set. */ public ComponentSelector nextSibling() { LinkedHashSet out = new LinkedHashSet(); for (Component c : this) { Container parent = c.getParent(); if (parent != null) { int index = parent.getComponentIndex(c); if (index < parent.getComponentCount()-1) { out.add(parent.getComponentAt(index+1)); } } } return new ComponentSelector(out); } /** * Creates set of "previous" siblings of components in this set. * @return New ComponentSelector with previous siblings of this set. */ public ComponentSelector prevSibling() { LinkedHashSet out = new LinkedHashSet(); for (Component c : this) { Container parent = c.getParent(); if (parent != null) { int index = parent.getComponentIndex(c); if (index > 0) { out.add(parent.getComponentAt(index-1)); } } } return new ComponentSelector(out); } /** * Animates this set of components, replacing any modified style properties of the * destination style to the components. * @param destStyle The style to apply to the components via animation. * @param duration The duration of the animation (ms) * @param callback Callback to call after animation is complete. * @return Self for chaining * @see Component#createStyleAnimation(java.lang.String, int) */ public ComponentSelector animateStyle(Style destStyle, int duration, final SuccessCallback callback) { ArrayList animations = new ArrayList(); AnimationManager mgr = null; for (Component c : this) { AnimationManager cmgr = c.getAnimationManager(); if (cmgr != null) { mgr = cmgr; Style sourceStyle = c.getUnselectedStyle(); destStyle.merge(sourceStyle); animations.add(c.createStyleAnimation(sourceStyle, destStyle, duration, null)); } } if (mgr != null) { ComponentAnimation anim = ComponentAnimation.compoundAnimation(animations.toArray(new ComponentAnimation[animations.size()])); mgr.addAnimation(anim, new Runnable() { public void run() { if (callback != null) { callback.onSucess(ComponentSelector.this); } } }); } return this; } /** * Fade in this set of components. Prior to calling this, the component visibility * should be set to "false". This uses the default duration of 500ms. * @return Self for chaining. */ public ComponentSelector fadeIn() { return fadeIn(500); } /** * Fade in this set of components. Prior to calling this, the component visibility * should be set to "false". * @param duration The duration of the fade in. * @return Self for chaining. */ public ComponentSelector fadeIn(int duration) { return fadeIn(duration, null); } /** * Fade in this set of components. Prior to calling this, the component visibility should * be set to "false". * @param duration The duration of the fade in. * @param callback Callback to run when animation completes. * @return */ public ComponentSelector fadeIn(final int duration, final SuccessCallback callback) { final String placeholderProperty = "com.codename1.ui.ComponentSelector#fadeOutPlaceholder"; AnimationManager mgr = null; ArrayList animations1 = new ArrayList(); final ArrayList animations2 = new ArrayList(); final ArrayList animatingComponents = new ArrayList(); for (Component c : this) { Container parent = c.getParent(); if (parent != null) { AnimationManager cmgr = c.getAnimationManager(); if (cmgr != null) { mgr = cmgr; Container placeholder = new Container(); //placeholder.getStyle().setBgColor(0xff0000); //placeholder.getStyle().setBgTransparency(255); //placeholder.setShowEvenIfBlank(true); c.putClientProperty(placeholderProperty, placeholder); Component.setSameHeight(placeholder, c); Component.setSameWidth(placeholder, c); $(placeholder) .setMargin(c.getStyle().getMarginTop(), c.getStyle().getMarginRight(false), c.getStyle().getMarginBottom(), c.getStyle().getMarginLeft(false)) .setPadding(c.getStyle().getPaddingTop(), c.getStyle().getPaddingRight(false), c.getStyle().getPaddingBottom(), c.getStyle().getPaddingLeft(false)); //System.out.println("Placeholder height "+c.getHeight()); //parent.replace(c, placeholder, false); //c.setHidden(false); ComponentAnimation a = parent.createReplaceTransition(c, placeholder, CommonTransitions.createEmpty()); animations1.add(a); animatingComponents.add(c); } //centerBackground.add(BorderLayout.CENTER, boxy); } } if (mgr != null) { mgr.addAnimation(ComponentAnimation.compoundAnimation(animations1.toArray(new ComponentAnimation[animations1.size()])), new Runnable() { public void run() { AnimationManager mgr = null; for (final Component c : animatingComponents) { Container placeholder = (Container)c.getClientProperty(placeholderProperty); if (placeholder != null) { //System.out.println("Placeholder height after replace "+(c.getHeight() + c.getStyle().getMarginBottom() + c.getStyle().getMarginTop())); //System.out.println("Placeholder not null"); c.putClientProperty(placeholderProperty, null); AnimationManager cmgr = placeholder.getAnimationManager(); if (cmgr != null) { //System.out.println("Animation manager not null"); mgr = cmgr; c.setVisible(true); Container parent = placeholder.getParent(); if (parent != null) { //System.out.println("Parent not null"); ComponentAnimation a = parent.createReplaceTransition(placeholder, c, CommonTransitions.createFade(duration)); animations2.add(a); } } } } if (mgr != null) { final AnimationManager fmgr = mgr; $(new Runnable() { public void run() { fmgr.addAnimation(ComponentAnimation.compoundAnimation(animations2.toArray(new ComponentAnimation[animations2.size()])), new Runnable() { public void run() { if (callback != null) { callback.onSucess(ComponentSelector.this); } } }); } }); } } }); } return this; } /** * Fades in this component and blocks until animation is complete. * @return Self for chaining. */ public ComponentSelector fadeInAndWait() { return fadeInAndWait(500); } /** * Fades in this component and blocks until animation is complete. * @param duration The duration of the animation. * @return Self for chaining. */ public ComponentSelector fadeInAndWait(final int duration) { final String placeholderProperty = "com.codename1.ui.ComponentSelector#fadeOutPlaceholder"; AnimationManager mgr = null; ArrayList animations1 = new ArrayList(); final ArrayList animations2 = new ArrayList(); final ArrayList animatingComponents = new ArrayList(); for (Component c : this) { Container parent = c.getParent(); if (parent != null) { AnimationManager cmgr = c.getAnimationManager(); if (cmgr != null) { mgr = cmgr; Container placeholder = new Container(); //placeholder.getStyle().setBgColor(0xff0000); //placeholder.getStyle().setBgTransparency(255); //placeholder.setShowEvenIfBlank(true); c.putClientProperty(placeholderProperty, placeholder); Component.setSameHeight(placeholder, c); Component.setSameWidth(placeholder, c); $(placeholder) .setMargin(c.getStyle().getMarginTop(), c.getStyle().getMarginRight(false), c.getStyle().getMarginBottom(), c.getStyle().getMarginLeft(false)) .setPadding(c.getStyle().getPaddingTop(), c.getStyle().getPaddingRight(false), c.getStyle().getPaddingBottom(), c.getStyle().getPaddingLeft(false)); //System.out.println("Placeholder height "+c.getHeight()); //parent.replace(c, placeholder, false); //c.setHidden(false); ComponentAnimation a = parent.createReplaceTransition(c, placeholder, CommonTransitions.createEmpty()); animations1.add(a); animatingComponents.add(c); } //centerBackground.add(BorderLayout.CENTER, boxy); } } if (mgr != null) { mgr.addAnimationAndBlock(ComponentAnimation.compoundAnimation(animations1.toArray(new ComponentAnimation[animations1.size()]))); for (final Component c : animatingComponents) { Container placeholder = (Container)c.getClientProperty(placeholderProperty); if (placeholder != null) { //System.out.println("Placeholder height after replace "+(c.getHeight() + c.getStyle().getMarginBottom() + c.getStyle().getMarginTop())); //System.out.println("Placeholder not null"); c.putClientProperty(placeholderProperty, null); AnimationManager cmgr = placeholder.getAnimationManager(); if (cmgr != null) { //System.out.println("Animation manager not null"); mgr = cmgr; c.setVisible(true); Container parent = placeholder.getParent(); if (parent != null) { //System.out.println("Parent not null"); ComponentAnimation a = parent.createReplaceTransition(placeholder, c, CommonTransitions.createFade(duration)); animations2.add(a); } } } } if (mgr != null) { mgr.addAnimationAndBlock( ComponentAnimation.compoundAnimation(animations2.toArray(new ComponentAnimation[animations2.size()])) ); } } return this; } /** * Returns true if the first component in this set is visible. * @return True if first component in this set is visible. * @see Component#isVisible() */ public boolean isVisible() { for (Component c : this) { return c.isVisible(); } return false; } /** * Returns true if first component in this set is hidden. * @return True if first component in set is hidden. * @see Component#isHidden() */ public boolean isHidden() { for (Component c : this) { return c.isHidden(); } return false; } /** * Fades out components in this set. Uses default duration of 500ms. * @return Self for chaining. */ public ComponentSelector fadeOut() { return fadeOut(500); } /** * Fades out components in this set. * @param duration Duration of animation. * @return Self for chaining. */ public ComponentSelector fadeOut(int duration) { return fadeOut(duration, null); } /** * Fades out components in this set. * @param duration Duration of animation. * @param callback Callback to run when animation completes. * @return Self for chaining. */ public ComponentSelector fadeOut(int duration, final SuccessCallback callback) { final String placeholderProperty = "com.codename1.ui.ComponentSelector#fadeOutPlaceholder"; AnimationManager mgr = null; ArrayList animations = new ArrayList(); final ArrayList animatingComponents = new ArrayList(); for (Component c : this) { Container parent = c.getParent(); if (parent != null) { AnimationManager cmgr = c.getAnimationManager(); if (cmgr != null) { mgr = cmgr; Container placeholder = new Container(); //placeholder.setShowEvenIfBlank(true); c.putClientProperty(placeholderProperty, placeholder); Component.setSameHeight(placeholder, c); Component.setSameWidth(placeholder, c); $(placeholder) .setMargin(c.getStyle().getMarginTop(), c.getStyle().getMarginRight(false), c.getStyle().getMarginBottom(), c.getStyle().getMarginLeft(false)) .setPadding(c.getStyle().getPaddingTop(), c.getStyle().getPaddingRight(false), c.getStyle().getPaddingBottom(), c.getStyle().getPaddingLeft(false)); ComponentAnimation a = parent.createReplaceTransition(c, placeholder, CommonTransitions.createFade(duration)); animations.add(a); animatingComponents.add(c); } //centerBackground.add(BorderLayout.CENTER, boxy); } } if (mgr != null) { mgr.addAnimation(ComponentAnimation.compoundAnimation(animations.toArray(new ComponentAnimation[animations.size()])), new Runnable() { public void run() { for (final Component c : animatingComponents) { //c.setHidden(true); c.setVisible(false); final Container placeholder = (Container)c.getClientProperty(placeholderProperty); c.putClientProperty(placeholderProperty, null); if (placeholder != null) { Container parent = placeholder.getParent(); /* if (parent == null) { System.out.println("Deferring replace back"); $(new Runnable() { public void run() { Container parent = placeholder.getParent(); if (parent != null) { System.out.println("Found parent after deferral"); parent.replace(placeholder, c, CommonTransitions.createEmpty()); } } }); } else { */ if (parent != null) { parent.replace(placeholder, c, CommonTransitions.createEmpty()); } //} } } if (callback != null) { callback.onSucess(ComponentSelector.this); } } }); } return this; } /** * Hide the matched components with a sliding motion. * @param duration Duration of animation * @return Self for chaining. */ public ComponentSelector slideUp(int duration) { return slideUp(duration, null); } /** * Hide the matched elements with a sliding motion. * @param duration Duration of animation. * @param callback Callback to run when animation completes * @return Self for chaining. */ public ComponentSelector slideUp(int duration, final SuccessCallback callback) { final ArrayList animatedComponents = new ArrayList(); for (Component c : this) { c.setHeight(0); animatedComponents.add(c); } getParent().animateUnlayout(duration, 255, new SuccessCallback() { public void onSucess(ComponentSelector value) { for (Component c : animatedComponents) { c.setVisible(false); } getParent().revalidate(); if (callback != null) { callback.onSucess(ComponentSelector.this); } } }); return this; } /** * Hide the matched elements with a sliding motion. Blocks until animation is complete. * @param duration Duration of animation. * @return Self for chaining. */ public ComponentSelector slideUpAndWait(int duration) { for (Component c : this) { c.setHeight(0); } getParent().animateUnlayoutAndWait(duration, 255); return this; } /** * Display the matched elements with a sliding motion. Uses default duration of 500ms * @return Self for chaining. */ public ComponentSelector slideDown() { return slideDown(500); } /** * Hide the matched elements with a sliding motion. Uses default duration of 500ms * @return Self for chaining. */ public ComponentSelector slideUp() { return slideUp(500); } /** * Display the matched elements with a sliding motion. * @param duration Duration of animation. * @return Self for chaining. */ public ComponentSelector slideDown(int duration) { return slideDown(duration, null); } /** * Display the matched elements with a sliding motion. * @param duration Duration of animation. * @param callback Callback to run when animation completes. * @return Self for chaining. */ public ComponentSelector slideDown(final int duration, final SuccessCallback callback) { for (Component c : this) { c.setHeight(0); c.setVisible(true); } getParent().animateLayout(duration, callback); return this; } /** * Display the matched elements with a sliding motion. Blocks until animation is complete. * @param duration Duration of animation. * @return Self for chaining. */ public ComponentSelector slideDownAndWait(final int duration) { final ArrayList animatedComponents = new ArrayList(); for (Component c : this) { c.setHeight(0); c.setVisible(true); animatedComponents.add(c); } getParent().animateLayoutAndWait(duration); return this; } /** * Hide the matched elements by fading them to transparent. Blocks thread until animation is complete. * @param duration Duration of animation. * @return Self for chaining. */ public ComponentSelector fadeOutAndWait(int duration) { final String placeholderProperty = "com.codename1.ui.ComponentSelector#fadeOutPlaceholder"; AnimationManager mgr = null; ArrayList animations = new ArrayList(); final ArrayList animatingComponents = new ArrayList(); for (Component c : this) { Container parent = c.getParent(); if (parent != null) { AnimationManager cmgr = c.getAnimationManager(); if (cmgr != null) { mgr = cmgr; Container placeholder = new Container(); //placeholder.setShowEvenIfBlank(true); c.putClientProperty(placeholderProperty, placeholder); Component.setSameHeight(placeholder, c); Component.setSameWidth(placeholder, c); $(placeholder) .setMargin(c.getStyle().getMarginTop(), c.getStyle().getMarginRight(false), c.getStyle().getMarginBottom(), c.getStyle().getMarginLeft(false)) .setPadding(c.getStyle().getPaddingTop(), c.getStyle().getPaddingRight(false), c.getStyle().getPaddingBottom(), c.getStyle().getPaddingLeft(false)); ComponentAnimation a = parent.createReplaceTransition(c, placeholder, CommonTransitions.createFade(duration)); animations.add(a); animatingComponents.add(c); } //centerBackground.add(BorderLayout.CENTER, boxy); } } if (mgr != null) { mgr.addAnimationAndBlock(ComponentAnimation.compoundAnimation(animations.toArray(new ComponentAnimation[animations.size()]))); for (final Component c : animatingComponents) { c.setVisible(false); final Container placeholder = (Container)c.getClientProperty(placeholderProperty); c.putClientProperty(placeholderProperty, null); if (placeholder != null) { Container parent = placeholder.getParent(); if (parent != null) { parent.replace(placeholder, c, CommonTransitions.createEmpty()); } } } } return this; } /** * Replaces the matched components within respective parents with replacements defined by the provided mapper. Replacements * are replaced in the UI itself (i.e. {@code c.getParent().replace(c, replacement)}) with an empty * transition. * @param mapper Mapper that defines the replacements for each component in the set. If the mapper returns * the input component, then no change is made for that component. A null return value cause the component * to be removed from its parent. Returning a Component results in that component replacing the original component * within its parent. * @return A new ComponentSelector with the replacement components. */ public ComponentSelector replace(ComponentMapper mapper) { return replace(mapper, CommonTransitions.createEmpty()); } /** * Replaces the matched components within respective parents with replacements defined by the provided mapper. Replacements * are replaced in the UI itself (i.e. {@code c.getParent().replace(c, replacement)}) with the provided transition. * @param mapper Mapper that defines the replacements for each component in the set. If the mapper returns * the input component, then no change is made for that component. A null return value cause the component * to be removed from its parent. Returning a Component results in that component replacing the original component * within its parent. * @param t Transition to use for replacements. * @return A new ComponentSelector with the replacement components. */ public ComponentSelector replace(ComponentMapper mapper, Transition t) { ArrayList animations = new ArrayList(); LinkedHashSet replacements = new LinkedHashSet(); for (Component c : this) { Container parent = c.getParent(); Component replacement = mapper.map(c); if (parent != null) { if (replacement != c) { if (replacement != null) { animations.add(parent.createReplaceTransition(c, replacement, t.copy(false))); } else { c.remove(); } } } if (replacement != null) { replacements.add(replacement); } } AnimationManager mgr = getAnimationManager(); if (mgr != null && animations.size() > 0) { mgr.addAnimation(ComponentAnimation.compoundAnimation(animations.toArray(new ComponentAnimation[animations.size()]))); } return new ComponentSelector(replacements); } /** * Replaces the matched components within respective parents with replacements defined by the provided mapper. Replacements * are replaced in the UI itself (i.e. {@code c.getParent().replace(c, replacement)}) with the provided transition. * Blocks the thread until the transition animation is complete. * @param mapper Mapper that defines the replacements for each component in the set. If the mapper returns * the input component, then no change is made for that component. A null return value cause the component * to be removed from its parent. Returning a Component results in that component replacing the original component * within its parent. * @param t * @return A new ComponentSelector with the replacement components. */ public ComponentSelector replaceAndWait(ComponentMapper mapper, Transition t) { ArrayList animations = new ArrayList(); LinkedHashSet replacements = new LinkedHashSet(); for (Component c : this) { Container parent = c.getParent(); Component replacement = mapper.map(c); if (parent != null) { if (replacement != c) { if (replacement != null) { animations.add(parent.createReplaceTransition(c, replacement, t.copy(false))); } else { c.remove(); } } } if (replacement != null) { replacements.add(replacement); } } AnimationManager mgr = getAnimationManager(); if (mgr != null && animations.size() > 0) { mgr.addAnimationAndBlock(ComponentAnimation.compoundAnimation(animations.toArray(new ComponentAnimation[animations.size()]))); } return new ComponentSelector(replacements); } /** * Creates a component selector that wraps the provided components. The provided * components are treated as the "results" of this selector. Not the roots. However * you can use {@link #find(java.lang.String) } to perform a query using this selector * as the roots. * @param cmps Components to add to this selector results. */ public ComponentSelector(Component... cmps) { this.roots = new LinkedHashSet(); this.results = new LinkedHashSet(); for (Component cmp : cmps) { this.results.add(cmp); } } /** * Creates a new ComponentSelector with the provided set of components. * @param cmps The components to include in the set. * @return ComponentSelector with provided components. */ public static ComponentSelector $(Set cmps) { return new ComponentSelector(cmps); } /** * Alias of {@link #$(java.util.Set) } * @param cmps * @return */ public static ComponentSelector select(Set cmps) { return $(cmps); } /** * Creates a component selector that wraps the provided components. The provided * components are treated as the "results" of this selector. Not the roots. However * you can use {@link #find(java.lang.String) } to perform a query using this selector * as the roots. * @param cmps Components to add to this selector results. */ public ComponentSelector(Set cmps) { this.roots = new LinkedHashSet(); this.results = new LinkedHashSet(); this.results.addAll(cmps); } /** * Creates a new ComponentSelector with the components matched by the provided selector. The current form * is used as the root for searches. Will throw a runtime exception if there is no current form. * @param selector A selector string that defines which components to include in the * set. * @return ComponentSelector with matching components. */ public static ComponentSelector $(String selector) { return new ComponentSelector(selector); } /** * Alias of {@link #$(java.lang.String) } * @param selector * @return */ public static ComponentSelector select(String selector) { return $(selector); } /** * Creates a selector that will query the current form. If there is no * current form, then this selector will have no roots. *

Generally it is better to provide a root explicitly using {@link ComponentSelector#ComponentSelector(java.lang.String, com.codename1.ui.Component...) * to ensure that the selector has a tree to walk down.

* @param selector The selector string. */ public ComponentSelector(String selector) { this.roots = new LinkedHashSet(); Form f = Display.getInstance().getCurrent(); if (f != null) { this.roots.add(f); } else { throw new RuntimeException("Attempt to create selector on current form, but there is no current form. Best practice is to explicitly provide a root for your ComponentSelector."); } parse(selector); } /** * Creates a ComponentSelector with the components matched by the provided selector in the provided * roots' subtrees. * @param selector Selector string to define which components will be included in the set. * @param roots Roots for the selector to search. Only components within the roots' subtrees will be included in the set. * @return ComponentSelector with matching components. */ public static ComponentSelector $(String selector, Component... roots) { return new ComponentSelector(selector, roots); } /** * Alias of {@link #$(java.lang.String, com.codename1.ui.Component...) } * @param selector * @param roots * @return */ public static ComponentSelector select(String selector, Component... roots) { return $(selector, roots); } /** * Creates a selector with the provided roots. This will only search through the subtrees * of the provided roots to find results that match the provided selector string. * @param selector The selector string * @param roots The roots for this selector. */ public ComponentSelector(String selector, Component... roots) { this.roots = new LinkedHashSet(); for (Component root : roots) { this.roots.add(root); } parse(selector); } /** * Creates a ComponentSelector with the components matched by the provided selector in the provided * roots' subtrees. * @param selector Selector string to define which components will be included in the set. * @param roots Roots for the selector to search. Only components within the roots' subtrees will be included in the set. * @return ComponentSelector with matching components. */ public static ComponentSelector $(String selector, Collection roots) { return new ComponentSelector(selector, roots); } /** * Alias of {@link #$(java.lang.String, java.util.Collection) } * @param selector * @param roots * @return */ public static ComponentSelector select(String selector, Collection roots) { return $(selector, roots); } /** * Creates a selector with the provided roots. This will only search through the subtrees * of the provided roots to find results that match the provided selector string. * @param selector The selector string * @param roots The roots for this selector. */ public ComponentSelector(String selector, Collection roots) { this.roots = new LinkedHashSet(); this.roots.addAll(roots); parse(selector); } /** * Uses the results of this selector as the roots to create a new selector with * the provided selector string. * @param selector The selector string. * @return A new ComponentSelector with the results of the query. */ public ComponentSelector find(String selector) { return new ComponentSelector(selector, resultsImpl()); } private void parse(String selector) { selector = selector.trim(); if (selector.indexOf(",") != -1) { // this is an aggregate selector String[] parts = Util.split(selector, ","); aggregateSelectors = new LinkedHashSet(); for (String part : parts) { part = part.trim(); if (part.length() == 0) { continue; } aggregateSelectors.add(new ComponentSelector(part, roots)); } return; } String[] parts = Util.split(selector, " "); int len = parts.length; if (len > 1) { StringBuilder parentSelector = new StringBuilder(); for (int i=0; i".equals(parts[i])) { if (i < len-1) { parts[i] = ">" + parts[i+1].trim(); for (int j=i+1; j'"); } } if (i>0 && i < len-1) { parentSelector.append(" "); } if (i < len-1) { parentSelector.append(parts[i]); } if (i == len-1) { selector = parts[i]; } } if (parentSelector.length() > 0) { parent = new ComponentSelector(parentSelector.toString(), roots); roots.clear(); } } if (selector.indexOf(">") == 0) { childrenOnly = true; selector = selector.substring(1).trim(); } if (selector.indexOf(",") != -1) { throw new IllegalArgumentException("Invalid character in selector "+selector); } else { ComponentSelector out = this; selector = selector.trim(); int pos; if ((pos = selector.indexOf(":")) != -1) { out.state = selector.substring(pos+1); selector = selector.substring(0, pos); } if ((pos = selector.indexOf(".")) != -1) { out.tags = Util.split(selector.substring(pos+1), "."); len = out.tags.length; out.tagsNeedles = new String[len]; String[] needles = out.tagsNeedles; String[] tags = out.tags; for (int i=0; i= 0) { out.name = selector.substring(pos+1); selector = selector.substring(0, pos); } if (selector.length() > 0 && !"*".equals(selector)) { out.uiid = selector; } if (state != null) { if ("pressed".equals(state)) { currentStyleType = PRESSED_STYLE; } else if ("selected".equals(state)) { currentStyleType = SELECTED_STYLE; } else if ("unselected".equals(state)) { currentStyleType = UNSELECTED_STYLE; } else if ("disabled".equals(state)) { currentStyleType = DISABLED_STYLE; } else if ("all".equals(state) || "*".equals(state)) { currentStyleType = ALL_STYLES; } } //return out; } } private boolean match(Component c) { if (name != null && !name.equals(c.getName())) { return false; } if (uiid != null && !uiid.equals(c.getUIID())) { return false; } if (tags != null) { String ctags = (String)c.getClientProperty(PROPERTY_TAG); if (ctags != null) { for (String ctag : tagsNeedles) { if (ctags.indexOf(ctag) == -1) { return false; } } } else { return false; } } return true; } /** * Returns the results of this selector. * @return */ public Iterator iterator() { return resultsImpl().iterator(); } private Set resultsImpl() { if (results == null) { results = new LinkedHashSet(); if (aggregateSelectors != null) { for (ComponentSelector sel : aggregateSelectors) { results.addAll(sel.resultsImpl()); } return results; } if (parent != null) { roots.clear(); roots.addAll(parent.resultsImpl()); } for (Component root : roots) { if (childrenOnly) { if (root instanceof Container) { Container cnt = (Container)root; for (Component child : cnt) { if (match(child)) { results.add(child); } } } } else { if (root instanceof Container) { Container cnt = (Container)root; for (int iter = 0 ; iter < cnt.getComponentCount() ; iter++) { Component child = cnt.getComponentAt(iter); resultsImpl(results, child); } } } } } return results; } private Set resultsImpl(Set out, Component root) { if (match(root)) { out.add(root); } if (root instanceof Container) { Container cnt = (Container)root; for (int iter = 0 ; iter < cnt.getComponentCount() ; iter++) { Component child = cnt.getComponentAt(iter); resultsImpl(out, child); } } return out; } /** * Gets a proxy style that wraps the result of {@link Component#getStyle() } of each component in set. * @return */ public Style getStyle() { HashSet