com.codename1.ui.ComponentSelector Maven / Gradle / Ivy
/*
* 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:
*
*
* - Wrapper methods for {@link Component}, {@link Container}, etc... to operate on all applicable components in the set.
* - 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.
* - Effects. E.g. {@link #fadeIn() }, {@link #fadeOut() }, {@link #slideDown() }, {@link #slideUp() }, {@link #animateLayout(int) },
* {@link #animateHierarchy(int) }, etc..
* - Convenience methods to help with common tasks. E.g. {@link #$(java.lang.Runnable) } as a short-hand for {@link Display#callSerially(java.lang.Runnable) }
*
- Methods to implement {@link java.util.Set} because ComponentSelector is a set.
*
*
*
* 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