Java tutorial
/* * Copyright 2013 Phil Brown * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package self.philbrown.javaQuery; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.HierarchyBoundsListener; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.InputMethodEvent; import java.awt.event.InputMethodListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; import javax.swing.AbstractButton; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.text.JTextComponent; import javax.swing.text.View; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.http.client.methods.HttpUriRequest; import org.jdesktop.core.animation.timing.Animator; import org.jdesktop.core.animation.timing.Interpolator; import org.jdesktop.core.animation.timing.PropertySetter; import org.jdesktop.core.animation.timing.TimingTarget; import org.jdesktop.core.animation.timing.TimingTargetAdapter; import org.jdesktop.swing.animation.timing.sources.SwingTimerTimingSource; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.w3c.dom.Document; import self.philbrown.javaQuery.SwipeDetector.SwipeListener; import self.philbrown.javaQuery.animation.AccelerateDecelerateInterpolator; import self.philbrown.javaQuery.animation.AccelerateInterpolator; import self.philbrown.javaQuery.animation.AnimatorSet; import self.philbrown.javaQuery.animation.AnticipateInterpolator; import self.philbrown.javaQuery.animation.AnticipateOvershootInterpolator; import self.philbrown.javaQuery.animation.BounceInterpolator; import self.philbrown.javaQuery.animation.DecelerateInterpolator; import self.philbrown.javaQuery.animation.LinearInterpolator; import self.philbrown.javaQuery.animation.OvershootInterpolator; /** * Partial port of jQuery to Java * @author Phil Brown * @since 1:20:41 PM Aug 28, 2013 * */ public class $ { /** * Data types for <em>ajax</em> request responses */ public static enum DataType { /** JavaScript Object Notation */ JSON, /** Extensible Markup Language */ XML, /** Textual response */ TEXT, /** Bourne Script response*/ SCRIPT } /** * Relates to the interpolator used for <em>javaQuery</em> animations */ public static enum Easing { /** Rate of change starts out slowly and then accelerates. */ ACCELERATE, /** Rate of change starts and ends slowly but accelerates through the middle. */ ACCELERATE_DECELERATE, /** change starts backward then flings forward. */ ANTICIPATE, /** change starts backward, flings forward and overshoots the target value, then finally goes back to the final value. */ ANTICIPATE_OVERSHOOT, /** change bounces at the end. */ BOUNCE, /** Rate of change starts out quickly and and then decelerates. */ DECELERATE, /** Rate of change is constant. */ LINEAR, /** change flings forward and overshoots the last value then comes back. */ OVERSHOOT } /** Used to correctly call methods that use simple type parameters via reflection */ private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPE_MAP = buildPrimitiveTypeMap(); /** Inflates the mapping of data types to primitive types */ private static Map<Class<?>, Class<?>> buildPrimitiveTypeMap() { Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>(); map.put(Float.class, float.class); map.put(Double.class, double.class); map.put(Integer.class, int.class); map.put(Boolean.class, boolean.class); map.put(Long.class, long.class); map.put(Short.class, short.class); map.put(Byte.class, byte.class); return map; } //setup animation timing source so that animations are updated 60 times per second (60FPS). //This configuration can be changed at any time by the developer by accessing the static method //in the Animator class. static { Animator.setDefaultTimingSource(new SwingTimerTimingSource(1 / 60, TimeUnit.SECONDS)); } /** * Optional data referenced by this javaQuery Object. Best practice is to make this a * {@link WeakReference} to avoid memory leaks. */ private Object data; /** The current views that will be manipulated */ private List<Component> views; /** The lowest level view registered with {@code this} javaQuery. */ private Component rootView; /** Function to be called when this{@link #view} gains focus */ private Function onFocus; /** Function to be called when this {@link #view} no longer has focus. */ private Function offFocus; /** Function to be called when a key is pressed down when this {@link #view} has the focus. */ private Function keyDown; /** Function to be called when a key is pressed when this {@link #view} has the focus. */ private Function keyPress; /** Function to be called when a key is released when this {@link #view} has the focus. */ private Function keyUp; /** Function to be called when a swipe event is captured by this {@link #view}. */ private Function swipe; /** Function to be called when a swipe-up event is captured by this {@link #view}. */ private Function swipeUp; /** Function to be called when a swipe-down event is captured by this {@link #view}. */ private Function swipeDown; /** Function to be called when a swipe-left event is captured by this {@link #view}. */ private Function swipeLeft; /** Function to be called when a swipe-right event is captured by this {@link #view}. */ private Function swipeRight; /** Used to detect swipes on this {@link #view}. */ private SwipeDetector swiper; /** Contains a mapping of {@code javaQuery} extensions. */ private static Map<String, Constructor<?>> extensions = new HashMap<String, Constructor<?>>(); public $(Component view) { this.views = new ArrayList<Component>(); this.views.add(view); this.rootView = view; } public $(List<Component> views) { if (views == null) { throw new NullPointerException("Cannot create javaQuery Instance with null List."); } else if (views.isEmpty()) { throw new NullPointerException("Cannot create javaQuery Instance with empty List."); } this.views = views; this.rootView = views.get(0); } public $(Component... views) { if (views == null) { throw new NullPointerException("Cannot create javaQuery Instance with null Array."); } else if (views.length == 0) { throw new NullPointerException("Cannot create javaQuery Instance with empty Array."); } this.rootView = views[0]; this.views = Arrays.asList(views); } public static $ with(Component view) { return new $(view); } public static $ with(List<Component> views) { return new $(views); } public static $ with(Component... views) { return new $(views); } private Component findComponentWithName(String name) { return findComponentWithName(name, rootView); } /** * Finds a component that is identified by the given name * @param name * @return the found component, or {@code null} if it was not found. */ public static Component findComponentWithName(String name, Component parent)//FIXME: debug to ensure this is working! { if (parent.getName() != null && parent.equals(name)) return parent; else if (parent instanceof Container) { for (Component c : ((Container) parent).getComponents()) { Component found = findComponentWithName(name, c); if (found != null) { return found; } } } return null; } /** Sets the set of views to the parents of all currently-selected views */ public $ parent() { List<Component> _views = new ArrayList<Component>(); for (Component view : this.views) { Component parent = view.getParent(); if (parent != null && !_views.contains(parent)) { _views.add(parent); } } this.views.clear(); this.views = _views; return this; } /** Sets the set of views to the children of the current set of views with the given child index */ public $ child(int index) { List<Component> _views = new ArrayList<Component>(); for (Component view : views) { if (view instanceof Container) { Component v = ((Container) view).getComponent(index); if (v != null) _views.add(v); } } views.clear(); views = _views; return this; } /** * Refreshes the listeners for focus changes */ private void setupFocusListener() { for (final Component view : views) { view.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent event) { if (onFocus != null) onFocus.invoke($.with(view)); } @Override public void focusLost(FocusEvent event) { if (offFocus != null) offFocus.invoke($.with(view)); } }); } } /** * Refreshes the listeners for key events */ private void setupKeyListener() { for (final Component view : views) { view.addKeyListener(new KeyListener() { @Override public void keyPressed(KeyEvent event) { if (keyDown != null) keyDown.invoke($.with(view), event.getKeyCode(), event); } @Override public void keyReleased(KeyEvent event) { if (keyUp != null) keyUp.invoke($.with(view), event.getKeyCode(), event); } @Override public void keyTyped(KeyEvent event) { if (keyPress != null) keyPress.invoke($.with(view), event.getKeyCode(), event); } }); } } /** * Refreshes the listeners for swipe events */ private void setupSwipeListener() { for (Component view : views) { view.addMouseListener(new SwipeDetector(view, new SwipeListener() { @Override public void onUpSwipe(Component v) { if (swipeUp != null) { swipeUp.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.UP); } } @Override public void onRightSwipe(Component v) { if (swipeRight != null) { swipeRight.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.RIGHT); } } @Override public void onLeftSwipe(Component v) { if (swipeLeft != null) { swipeLeft.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.LEFT); } } @Override public void onDownSwipe(Component v) { if (swipeDown != null) { swipeDown.invoke($.with(v)); } else if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.DOWN); } } @Override public void onStartSwipe(Component v) { if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.START); } } @Override public void onStopSwipe(Component v) { if (swipe != null) { swipe.invoke($.with(v), SwipeDetector.Direction.STOP); } } })); } } ////Effects /** * Animates the selected views using the JSON properties, the given duration, the easing function, * and with the onComplete callback * @param properties JSON String of an {@link AnimationOptions} Object * @param duration the duration of the animation, in milliseconds * @param easing the Easing function to use * @param complete the Function to invoke once the animation has completed for all views * @return this * @see Easing * @see #animate(Map, long, Easing, Function) * @see #animate(String, AnimationOptions) * @see #animate(Map, AnimationOptions) */ public $ animate(String properties, long duration, Easing easing, Function complete) { return animate(properties, AnimationOptions.create().duration(duration).easing(easing).complete(complete)); } /** * Animate the current views. Example: * <pre> * $.with(mView).animate("{ * left: 1000px, * top: 0%, * width: 50%, * alpha: 0.5 * }", * new AnimationOptions("{ duration: 3000, * easing: linear * }").complete(new Function() { * public void invoke($ javaQuery, Object... args) { * javaQuery.alert("Animation Complete!"); * } * }); * </pre> * @param properties to animate, in CSS representation * @param options the {@link AnimationOptions} for the animation * @return this */ public $ animate(String properties, AnimationOptions options) { try { JSONObject props = new JSONObject(properties); @SuppressWarnings("unchecked") Iterator<String> iterator = props.keys(); Map<String, Object> map = new HashMap<String, Object>(); while (iterator.hasNext()) { String key = iterator.next(); try { Object value = props.get(key); map.put(key, value); } catch (JSONException e) { Log.w("javaQuery", "Cannot handle CSS String. Some values may not be animated."); } } return animate(map, options); } catch (JSONException e) { Log.w("javaQuery", "Cannot handle CSS String. Unable to animate."); return this; } } /** * Animate the currently selected views * @param properties mapping of {@link AnimationOptions} attributes * @param duration the length of time for the animation to last * @param easing the Easing to use to interpolate the animation * @param complete the Function to call once the animation has completed or has been canceled. * @return this * @see QuickMap */ public $ animate(Map<String, Object> properties, long duration, Easing easing, final Function complete) { return animate(properties, AnimationOptions.create().duration(duration).easing(easing).complete(complete)); } /** * This reusable chunk of code can set up the given animation using the given animation options * @param options the options used to manipulate how the animation behaves * @return the container for placing views that will be animated using the given options */ private AnimatorSet animationWithOptions(final AnimationOptions options, List<Animator> animators) { AnimatorSet animation = new AnimatorSet(); animation.playTogether(animators); animation.setDuration(options.duration()); animation.addTarget(new TimingTargetAdapter() { @Override public void cancel(Animator animation) { if (options.fail() != null) options.fail().invoke($.this); if (options.complete() != null) options.complete().invoke($.this); } @Override public void end(Animator source) { if (options.success() != null) options.success().invoke($.this); if (options.complete() != null) options.complete().invoke($.this); } }); animation.addTarget(new TimingTargetAdapter() { @Override public void cancel(Animator animation) { if (options.fail() != null) options.fail().invoke($.this); if (options.complete() != null) options.complete().invoke($.this); } @Override public void end(Animator animation) { if (options.success() != null) options.success().invoke($.this); if (options.complete() != null) options.complete().invoke($.this); } }); if (options.debug()) { animation.setDebugName("$"); animation.addTarget(new TimingTarget() { @Override public void begin(Animator source) { Log.d(Log.buildString("animation ", options.getDebugCount()), "begin"); } @Override public void end(Animator source) { Log.d(Log.buildString("animation ", options.getDebugCount()), "end"); } @Override public void cancel(Animator source) { Log.d(Log.buildString("animation ", options.getDebugCount()), "cancel"); } @Override public void repeat(Animator source) { Log.d(Log.buildString("animation ", options.getDebugCount()), "repeat"); } @Override public void reverse(Animator source) { Log.d(Log.buildString("animation ", options.getDebugCount()), "reverse"); } @Override public void timingEvent(Animator source, double fraction) { Log.d(Log.buildString("animation ", options.getDebugCount()), Log.buildString("timingEvent with fraction ", fraction)); } }); } Interpolator interpolator = null; if (options.easing() == null) options.easing(Easing.LINEAR); final Easing easing = options.easing(); switch (easing) { case ACCELERATE: { interpolator = new AccelerateInterpolator(); break; } case ACCELERATE_DECELERATE: { interpolator = new AccelerateDecelerateInterpolator(); break; } case ANTICIPATE: { interpolator = new AnticipateInterpolator(); break; } case ANTICIPATE_OVERSHOOT: { interpolator = new AnticipateOvershootInterpolator(); break; } case BOUNCE: { interpolator = new BounceInterpolator(); break; } case DECELERATE: { interpolator = new DecelerateInterpolator(); break; } case OVERSHOOT: { interpolator = new OvershootInterpolator(); break; } //linear is default. case LINEAR: default: interpolator = new LinearInterpolator(); break; } //allow custom interpolator if (options.specialEasing() != null) interpolator = options.specialEasing(); animation.setInterpolator(interpolator); return animation; } /** * Interprets the CSS-style String and sets the value * @param view the view that will change. * @param key the name of the attribute * @param _value the end animation value * @return the computed value */ private Object getAnimationValue(Component view, String key, String _value) { Object value = null; String[] split = (_value).split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)"); if (split.length == 1) { if (split[0].contains(".")) { value = Float.valueOf(split[0]); } else { value = Integer.valueOf(split[0]); } } else { if (split.length > 2) { Log.w("javaQuery", "parsererror for key " + key); return null; } if (split[1].equalsIgnoreCase("px")) { //this is the default. Just determine if float or int if (split[0].contains(".")) { value = Float.valueOf(split[0]); } else { value = Integer.valueOf(split[0]); } } else if (split[1].equalsIgnoreCase("dip") || split[1].equalsIgnoreCase("dp") || split[1].equalsIgnoreCase("sp")) { Log.w("$", "Dimension not supported"); if (split[0].contains(".")) { value = Float.valueOf(split[0]); } else { value = Integer.valueOf(split[0]); } } else if (split[1].equalsIgnoreCase("in")) { float pt = view(0).getGraphics().getFontMetrics().getFont().deriveFont(1).getSize2D() / 72; if (split[0].contains(".")) { value = Float.parseFloat(split[0]) * pt; } else { value = Integer.parseInt(split[0]) * pt; } } else if (split[1].equalsIgnoreCase("mm")) { float pt = view(0).getGraphics().getFontMetrics().getFont().deriveFont(1).getSize2D() / 72; if (split[0].contains(".")) { value = Float.parseFloat(split[0]) * pt / 25.4; } else { value = Integer.parseInt(split[0]) * pt / 25.4; } } else if (split[1].equalsIgnoreCase("pt")) { if (split[0].contains(".")) { value = view(0).getGraphics().getFontMetrics().getFont().deriveFont(Float.parseFloat(split[0])) .getSize2D(); } else { value = view(0).getGraphics().getFontMetrics().getFont().deriveFont(Integer.parseInt(split[0])) .getSize2D(); } } else if (split[1].equals("%")) { Rectangle windowBounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); Component parent = view.getParent(); float pixels = 0; if (parent == null) { pixels = windowBounds.width; //use best guess for width or height dpi if (split[0].equalsIgnoreCase("y") || split[0].equalsIgnoreCase("top") || split[0].equalsIgnoreCase("bottom")) { pixels = windowBounds.height; } } else { pixels = parent.getWidth(); if (split[0].equalsIgnoreCase("y") || split[0].equalsIgnoreCase("top") || split[0].equalsIgnoreCase("bottom")) { pixels = parent.getHeight(); } } float percent = 0; if (pixels != 0) percent = Float.valueOf(split[0]) / 100 * pixels; if (split[0].contains(".")) { value = percent; } else { value = (int) percent; } } else { Log.w("javaQuery", "invalid units for Object with key " + key); return null; } } return value; } /** * Animate multiple view properties at the same time. Example: * <pre> * $.with(myView).animate(new QuickMap(QuickEntry.qe("alpha", .8f), QuickEntry.qe("width", 50%)), 400, Easing.LINEAR, null); * </pre> * @param properties mapping of property names and final values to animate * @param options the options for setting the duration, easing, etc of the animation * @return this */ public $ animate(Map<String, Object> properties, final AnimationOptions options) { List<Animator> animations = new ArrayList<Animator>(); for (Entry<String, Object> entry : properties.entrySet()) { final String key = entry.getKey(); //Java sometimes will interpret these Strings as Numbers, so some trickery is needed below Object value = entry.getValue(); for (final Component view : this.views) { Animator anim = null; if (value instanceof String) value = getAnimationValue(view, key, (String) value); //final Object fValue = value; if (value != null) { //special color cases if (key.equals("alpha") || key.equals("red") || key.equals("green") || key.equals("blue")) { if (key.equals("alpha") && view instanceof JComponent) { ((JComponent) view).setOpaque(false); } try { final Method getComponent = Color.class .getMethod(Log.buildString("get", capitalize(key))); final int colorComponent = (Integer) getComponent.invoke(view.getBackground()); final ColorHelper color = new ColorHelper(view.getBackground()); final Method setComponent = ColorHelper.class.getMethod( Log.buildString("set", capitalize(key)), new Class<?>[] { int.class }); anim = new Animator(); //if integer - assume 0-255 if (value instanceof Integer || is(value, int.class)) { anim.addTarget(PropertySetter.getTarget(color, key, colorComponent, Integer.parseInt(value.toString()))); } //if float - assume 0.0-1.0 else if (value instanceof Float || is(value, float.class)) { anim.addTarget(PropertySetter.getTarget(color, key, colorComponent, (int) (255 * Float.parseFloat(value.toString())))); } anim.addTarget(new TimingTargetAdapter() { @Override public void timingEvent(Animator source, double fraction) { double d = source.getInterpolator().interpolate(fraction); try { setComponent.invoke(color, (int) d); view.setBackground(color.getColor()); // if (view instanceof JComponent) // ((JComponent) view).revalidate(); view.repaint(); } catch (Throwable t) { if (options.debug()) t.printStackTrace(); } } }); } catch (Throwable t) { if (options.debug()) t.printStackTrace(); } } else { final Rectangle params = view.getBounds(); try { final Field field = params.getClass().getField(key); if (field != null) { anim = new Animator(); anim.addTarget(PropertySetter.getTarget(params, key, field.get(params), value)); anim.addTarget(new TimingTargetAdapter() { @Override public void timingEvent(Animator source, double fraction) { Rectangle bounds = view.getBounds(); double d = source.getInterpolator().interpolate(fraction); try { field.set(bounds, d); } catch (Throwable t) { if (options.debug()) t.printStackTrace(); } view.setBounds(bounds); view.repaint(); if (options.progress() != null) { options.progress().invoke($.with(view), key, d, source.getDuration() - source.getTotalElapsedTime()); } } }); } } catch (Throwable t) { if (options.debug()) Log.w("$", String.format(Locale.US, "%s is not a LayoutParams attribute.", key)); } if (anim == null) { anim = new Animator(); Object first; try { final Method getter = view.getClass() .getMethod(Log.buildString("get", capitalize(key))); first = getter.invoke(view); } catch (Throwable t) { first = 0; } anim.addTarget(PropertySetter.getTarget(view, key, first, value)); if (options.progress() != null) { anim.addTarget(new TimingTargetAdapter() { @Override public void timingEvent(Animator source, double fraction) { double d = source.getInterpolator().interpolate(fraction); if (options.progress() != null) { options.progress().invoke($.with(view), key, d, source.getDuration() - source.getTotalElapsedTime()); } } }); } } } if (options.repeatCount() >= 1) anim.setRepeatCount(options.repeatCount()); if (options.reverse()) anim.setRepeatBehavior(Animator.RepeatBehavior.REVERSE); animations.add(anim); } } } AnimatorSet animation = animationWithOptions(options, animations); animation.start(); return this; } /** * Shortcut method for animating the alpha attribute of this {@link #view} to 1.0. * @param options use to modify the behavior of the animation */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void fadeIn(AnimationOptions options) { this.animate(new QuickMap(QuickEntry.qe("alpha", new Float(1.0f))), options); } /** * Shortcut method for animating the alpha attribute of the selected views to 1.0. * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void fadeIn(long duration, final Function complete) { fadeIn(new AnimationOptions().duration(duration).complete(complete)); } /** * Shortcut method for animating the alpha attribute of the selected views to 0.0. * @param options use to modify the behavior of the animation */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void fadeOut(AnimationOptions options) { this.animate(new QuickMap(QuickEntry.qe("alpha", new Float(0.0f))), options); } /** * Shortcut method for animating the alpha attribute of this {@link #view} to 0.0. * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void fadeOut(long duration, final Function complete) { fadeOut(new AnimationOptions().duration(duration).complete(complete)); } /** * Shortcut method for animating the alpha attribute of the selected views to the given value. * @param opacity the alpha value at the end of the animation * @param options use to modify the behavior of the animation */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void fadeTo(float opacity, AnimationOptions options) { this.animate(new QuickMap(QuickEntry.qe("alpha", new Float(opacity))), options); } /** * Shortcut method for animating the alpha attribute of this {@link #view} to the given value. * @param duration the length of time the animation should last * @param opacity the alpha value at the end of the animation * @param complete the function to call when the animation has completed */ public void fadeTo(long duration, float opacity, final Function complete) { fadeTo(opacity, new AnimationOptions().duration(duration).complete(complete)); } /** * For each selected view, if its alpha is less than 0.5, it will fade in. Otherwise, it will * fade out. * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void fadeToggle(long duration, final Function complete) { List<Component> zeros = new ArrayList<Component>(); List<Component> ones = new ArrayList<Component>(); for (Component view : this.views) { if (view.getGraphics().getColor().getAlpha() < 0.5) zeros.add(view); else ones.add(view); } $.with(zeros).fadeIn(duration, complete); $.with(ones).fadeOut(duration, complete); } /** * If this {@link #view} has an alpha of less than 0.5, it will fade in. Otherwise, it will * fade out. * @param options use to modify the behavior of the animation */ public void fadeToggle(AnimationOptions options) { List<Component> zeros = new ArrayList<Component>(); List<Component> ones = new ArrayList<Component>(); for (Component view : this.views) { if (view.getGraphics().getColor().getAlpha() < 0.5) zeros.add(view); else ones.add(view); } $.with(zeros).fadeIn(options); $.with(ones).fadeOut(options); } /** * Animates the selected views out of their parent views by sliding it down, past its bottom * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideDown(long duration, final Function complete) { slideDown(new AnimationOptions().duration(duration).complete(complete)); } /** * Animates the selected views out of its parent by sliding it down, past its bottom * @param options use to modify the behavior of the animation */ @SuppressWarnings("unchecked") public void slideDown(final AnimationOptions options) { for (final Component view : this.views) { Component parent = view.getParent(); float y = 0; if (parent != null) { y = parent.getHeight(); } else { Rectangle display = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); y = display.height; } $.with(view).animate(QuickMap.qm(QuickEntry.qe("y", y)), options); } } /** * Animates the selected views out of its parent by sliding it up, past its top * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideUp(long duration, final Function complete) { slideUp(new AnimationOptions().duration(duration).complete(complete)); } /** * Animates the selected views out of its parent by sliding it up, past its top * @param options use to modify the behavior of the animation */ @SuppressWarnings("unchecked") public void slideUp(final AnimationOptions options) { animate(QuickMap.qm($.entry("y", 0f)), options); } /** * Animates the selected views out of its parent by sliding it right, past its edge * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideRight(long duration, final Function complete) { slideRight(new AnimationOptions().duration(duration).complete(complete)); } /** * Animates the selected views out of its parent by sliding it right, past its edge * @param options use to modify the behavior of the animation */ @SuppressWarnings("unchecked") public void slideRight(final AnimationOptions options) { for (final Component view : this.views) { Component parent = view.getParent(); float x = 0; if (parent != null) { x = parent.getWidth(); } else { Rectangle display = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); x = display.height; } $.with(view).animate(QuickMap.qm($.entry("x", x)), options); } } /** * Animates the selected views out of its parent by sliding it left, past its edge * @param duration the length of time the animation should last * @param complete the function to call when the animation has completed */ public void slideLeft(long duration, final Function complete) { slideLeft(new AnimationOptions().duration(duration).complete(complete)); } /** * Animates the selected views out of its parent by sliding it left, past its edge * @param options use to modify the behavior of the animation */ @SuppressWarnings("unchecked") public void slideLeft(final AnimationOptions options) { animate(QuickMap.qm($.entry("x", 0)), options); } /** * Gets the value for the given attribute of the first view in the current selection. * This is done using reflection, and as such * expects a <em>get-</em> or <em>is-</em> prefixed method name for the view. * @param s the name of the attribute to retrieve * @return the value of the given attribute name on the first view in the current selection */ public Object attr(String s) { try { Method m = view(0).getClass().getMethod("get" + capitalize(s)); return m.invoke(view(0)); } catch (Throwable t) { try { Method m = view(0).getClass().getMethod("is" + capitalize(s)); return m.invoke(view(0)); } catch (Throwable t2) { Log.w("javaQuery", view(0).getClass().getSimpleName() + "has no getter method for the variable " + s + "."); return null; } } } /** * Sets the value of the given attribute on each view in the current selection. This is done * using reflection, and as such a <em>set-</em>prefixed method name for each view. * @param s the name of the attribute to set * @param o the value to set to the given attribute * @return this */ public $ attr(String s, Object o) { for (Component view : this.views) { try { Class<?> objClass = o.getClass(); Class<?> simpleClass = PRIMITIVE_TYPE_MAP.get(objClass); if (simpleClass != null) { objClass = simpleClass; } try { Method m = view.getClass().getMethod("set" + capitalize(s), new Class<?>[] { objClass }); m.invoke(view, o); } catch (Throwable t) { //try using NineOldAndroids Log.w("javaQuery", view.getClass().getSimpleName() + ".set" + capitalize(s) + "(" + o.getClass().getSimpleName() + ") is not a method!"); } } catch (Throwable t) { Log.w("javaQuery", view.getClass().getSimpleName() + ".set" + capitalize(s) + "(" + o.getClass().getSimpleName() + ") is not a method!"); } } return this; } /** * @return the view at the given index of the current selection */ public Component view(int index) { return this.views.get(index); } /** * @return the current data. */ public Object data() { return this.data; } /** * Sets the data associated with this javaQuery * @param data the data to set * @return this */ public $ data(Object data) { this.data = data; return this; } /** * Adds a subview to the first view in the selection * @param v the subview to add * @return this */ public $ add(Component v) { if (v == null || v.getParent() != null) { Log.w("javaQuery", "Cannot add View"); return this; } if (view(0) instanceof Container) { ((Container) view(0)).add(v); } return this; } /** * Removes a subview from the first view in the current selection * @param v the subview to remove * @return this */ public $ remove(Component v) { if (view(0) instanceof Container) { ((Container) view(0)).remove(v); } return null; } /** * Sets the visibility of the current selection to {@link View#VISIBLE} * @return this */ public $ hide() { for (Component view : views) { view.setVisible(true); } return this; } /** * Sets the visibility of this {@link #view} to {@link View#INVISIBLE} * @return this */ public $ show() { for (Component view : views) { view.setVisible(false); } return this; } ///Event Handler Attachment /** * Listen for all events triggered using the {@link #notify(String)} or {@link #notify(String, Map)} * method * @param event the name of the event * @param callback the function to call when the event is triggered. * @see #listenToOnce(String, Function) */ public static void listenTo(String event, Function callback) { EventCenter.bind(event, callback, null); } /** * Listen for the next event triggered using the {@link #notify(String)} or * {@link #notify(String, Map)} method * @param event the name of the event * @param callback the function to call when the event is triggered. * @see #listenTo(String, Function) */ public static void listenToOnce(final String event, final Function callback) { EventCenter.bind(event, new Function() { @Override public void invoke($ javaQuery, Object... params) { callback.invoke(javaQuery, params); EventCenter.unbind(event, this, null); } }, null); } /** * Stop listening for events triggered using the {@link #notify(String)} and * {@link #notify(String, Map)} methods * @param event the name of the event * @param callback the function to no longer call when the event is triggered. * @see #listenTo(String, Function) */ public static void stopListening(String event, Function callback) { EventCenter.unbind(event, callback, null); } /** * Trigger a notification for functions registered to the given event String * @param event the event string to which registered listeners will respond * @see #listenTo(String, Function) * @see #listenToOnce(String, Function) */ public $ notify(String event) { EventCenter.trigger(this, event, null, null); return this; } /** * Trigger a notification for functions registered to the given event String * @param event the event string to which registered listeners will respond * @param data Object passed to the notified functions * @see #listenTo(String, Function) * @see #listenToOnce(String, Function) */ public $ notify(String event, Map<String, Object> data) { EventCenter.trigger(this, event, data, null); return this; } /** * Defines an event type that is handled by {@link $#change(Function)}. * @author Phil Brown * @since 1:16:05 PM Sep 4, 2013 * */ public enum ChangeEvent { INPUT_CARET_POSITION_CHANGED(InputMethodEvent.class), INPUT_METHOD_TEXT_CHANGED( InputMethodEvent.class), HIERARCHY_ANCESTOR_MOVED( HierarchyEvent.class), HIERARCHY_ANCENSTOR_RESIZED( HierarchyEvent.class), HIERARCHY_CHANGED(HierarchyEvent.class), PROPERTY_CHANGED( PropertyChangeEvent.class), COMPONENT_HIDDEN( ComponentEvent.class), COMPONENT_MOVED( ComponentEvent.class), COMPONENT_RESIZED( ComponentEvent.class), COMPONENT_SHOWN( ComponentEvent.class), COMPONENT_ADDED( ContainerEvent.class), COMPONENT_REMOVED( ContainerEvent.class); /** Class of the event received by the callback function */ private Class<?> eventClass; /** * Constructor * @param eventClass */ ChangeEvent(Class<?> eventClass) { this.eventClass = eventClass; } /** Get the class of the event received by the callback function. */ public Class<?> getEventClass() { return eventClass; } }; /** * Registers common component changes * @param function receives an instance of javaQuery with the changed view selected, as well as the * following arguments: * <ol> * <li>{@link changeEvent} to define the type of event that was triggered * <li>The associated event * </ol> * @return * @see changeEvent */ public $ change(final Function function) { for (final Component component : views) { component.addInputMethodListener(new InputMethodListener() { @Override public void caretPositionChanged(InputMethodEvent event) { function.invoke($.with(component), ChangeEvent.INPUT_CARET_POSITION_CHANGED, event); } @Override public void inputMethodTextChanged(InputMethodEvent event) { function.invoke($.with(component), ChangeEvent.INPUT_METHOD_TEXT_CHANGED, event); } }); component.addHierarchyBoundsListener(new HierarchyBoundsListener() { @Override public void ancestorMoved(HierarchyEvent event) { function.invoke($.with(component), ChangeEvent.HIERARCHY_ANCESTOR_MOVED, event); } @Override public void ancestorResized(HierarchyEvent event) { function.invoke($.with(component), ChangeEvent.HIERARCHY_ANCENSTOR_RESIZED, event); } }); component.addHierarchyListener(new HierarchyListener() { @Override public void hierarchyChanged(HierarchyEvent event) { function.invoke($.with(component), ChangeEvent.HIERARCHY_CHANGED, event); } }); component.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { function.invoke($.with(component), ChangeEvent.PROPERTY_CHANGED, event); } }); component.addComponentListener(new ComponentListener() { @Override public void componentHidden(ComponentEvent event) { function.invoke($.with(component), ChangeEvent.COMPONENT_HIDDEN, event); } @Override public void componentMoved(ComponentEvent event) { function.invoke($.with(component), ChangeEvent.COMPONENT_MOVED, event); } @Override public void componentResized(ComponentEvent event) { function.invoke($.with(component), ChangeEvent.COMPONENT_RESIZED, event); } @Override public void componentShown(ComponentEvent event) { function.invoke($.with(component), ChangeEvent.COMPONENT_SHOWN, event); } }); if (component instanceof Container) { ((Container) component).addContainerListener(new ContainerListener() { @Override public void componentAdded(ContainerEvent event) { function.invoke($.with(component), ChangeEvent.COMPONENT_ADDED, event); } @Override public void componentRemoved(ContainerEvent event) { function.invoke($.with(component), ChangeEvent.COMPONENT_REMOVED, event); } }); } } return this; } /** * Get the value associated with the first view in the current selection. If the view is a * JTextComponent, this method returns the String text. If it is a Button, the boolean selected * state is returned. If it is a JLable, the Icon is returned. * @return the value of this view, or <em>null</em> if not applicable. */ public Object val() { if (view(0) instanceof JTextComponent) { return ((JTextComponent) view(0)).getText(); } else if (view(0) instanceof AbstractButton) { return ((AbstractButton) view(0)).isSelected(); } else if (view(0) instanceof JLabel) { return ((JLabel) view(0)).getIcon(); } return null; } /** * Set the value associated with the views in the current selection. If the view is a JTextComponent, * this method sets the String text. If it is a Button, the boolean selected state is set. * If it is an JLabel, the Icon (ImageIcon, BufferedImage, or String) is set. All other view types * are ignored. * @return this */ public $ val(Object object) { for (Component view : this.views) { if (view instanceof JTextComponent && object instanceof String) { ((JTextComponent) view).setText((String) object); } else if (view instanceof AbstractButton && object instanceof Boolean) { ((AbstractButton) view).setSelected((Boolean) object); } else if (view instanceof JLabel) { if (object instanceof ImageIcon) { ((JLabel) view).setIcon((ImageIcon) object); } else if (object instanceof String) { $.with(view).image((String) object); } else if (object instanceof Image) { ((JLabel) view).setIcon(new ImageIcon((Image) object)); } } else if (view instanceof Window) { if (object instanceof Image) ((Window) view).setIconImage((Image) object); } } return this; } /** * Triggers a click event on the views in the current selection * @return this */ public $ click() { for (Component view : this.views) { for (MouseListener ml : view.getMouseListeners()) { ml.mousePressed(new MouseEvent(view, 0, 0, 0, view.getWidth() / 2, view.getHeight() / 2, 1, false)); } } return this; } /** * Invokes the given Function every time each view in the current selection is clicked. The only * parameter passed to the given function is a javaQuery instance containing the clicked view * @param function the function to call when this view is clicked * @return this */ public $ click(final Function function) { for (final Component view : this.views) { if (view instanceof AbstractButton) { ((AbstractButton) view).addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { function.invoke($.with(view)); } }); } else { view.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { function.invoke($.with(view)); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } }); } } return this; } /** * Invokes the given Function for click events on each view in the current selection. * The function will receive two arguments: * <ol> * <li>a javaQuery containing the clicked view * <li>{@code eventData} * </ol> * @param eventData the second argument to pass to the {@code function} * @param function the function to invoke * @return */ public $ click(final Object eventData, final Function function) { for (final Component view : this.views) { view.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { function.invoke($.with(view), eventData); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } }); } return this; } /** * Triggers a long-click event on this each view in the current selection * @return this */ public $ dblclick() { for (Component view : this.views) { for (MouseListener ml : view.getMouseListeners()) { ml.mousePressed(new MouseEvent(view, 0, 0, 0, view.getWidth() / 2, view.getHeight() / 2, 2, false)); } } return this; } /** * Invokes the given Function every time each view in the current selection is long-clicked. * The only parameter passed to the given function a javaQuery instance with the long-clicked view. * @param function the function to call when this view is long-clicked * @return this */ public $ dblclick(final Function function) { for (final Component view : this.views) { view.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) function.invoke($.with(view)); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } }); } return this; } /** * Invokes the given Function for long-click events on the views in the current selection. * The function will receive two arguments: * <ol> * <li>a javaQuery containing the long-clicked view * <li>{@code eventData} * </ol> * @param eventData the second argument to pass to the {@code function} * @param function the function to invoke * @return */ public $ dblclick(final Object eventData, final Function function) { for (final Component view : this.views) { view.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) function.invoke($.with(view), eventData); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } }); } return this; } /** * Handles swipe events. This will override any onTouchListener added. * @param function will receive this javaQuery and a {@link SwipeDetector.Direction} corresponding * to the direction of the swipe. * @return this */ public $ swipe(Function function) { swipe = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Up. This will cause any function set by * {@link #swipe(Function)} to not get called for up events. * @param function the function to invoke * @return this */ public $ swipeUp(Function function) { swipeUp = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Left. This will cause any function set by * {@link #swipe(Function)} to not get called for left events. * @param function the function to invoke * @return this */ public $ swipeLeft(Function function) { swipeLeft = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Down. This will cause any function set by * {@link #swipe(Function)} to not get called for down events. * @param function the function to invoke * @return this */ public $ swipeDown(Function function) { swipeDown = function; setupSwipeListener(); return this; } /** * Sets the function that is called when the user swipes Right. This will cause any function set by * {@link #swipe(Function)} to not get called for right events. * @param function the function to invoke * @return this */ public $ swipeRight(Function function) { swipeRight = function; setupSwipeListener(); return this; } /** * Triggers a swipe-up event on the current selection * @return this */ public $ swipeUp() { if (swiper != null) swiper.performSwipeUp(); return this; } /** * Triggers a swipe-down event on the current selection * @return this */ public $ swipeDown() { if (swiper != null) swiper.performSwipeDown(); return this; } /** * Triggers a swipe-left event on the current selection * @return this */ public $ swipeLeft() { if (swiper != null) swiper.performSwipeLeft(); return this; } /** * Triggers a swipe-right event on the current selection * @return this */ public $ swipeRight() { if (swiper != null) swiper.performSwipeRight(); return this; } /** * Sets the function to call when a view in the current selection has gained focus. This function * will receive an instance of javaQuery for this view as its only parameter * @param function the function to invoke * @return this */ public $ focus(Function function) { onFocus = function; setupFocusListener();//fixes any changes to the onfocuschanged listener return this; } /** * Gives focus to the first focusable view in the current selection. * @return this */ public $ focus() { for (Component view : this.views) { view.requestFocus(); if (view.hasFocus()) { break; } } return this; } /** * Sets the function to call when this {@link #view} loses focus. * @param function the function to invoke. Will receive a javaQuery instance containing * the view as its only parameter * @return this */ public $ focusout(Function function) { offFocus = function; setupFocusListener(); return this; } /** * Removes focus from all views in the current selection. * @return this */ public $ focusout() { for (Component view : this.views) { view.setFocusable(false); view.setFocusable(true); } return this; } /** * Set the function to call when a key-down event has been detected on this view. * @param function the Function to invoke. Receives a javaQuery containing the responding view * and two variable arguments: * <ol> * <li>the Integer key code * <li>the {@link KeyEvent} Object that was produced * </ol> * @return this */ public $ keydown(Function function) { keyDown = function; setupKeyListener(); return this; } /** * Set the function to call when a key-press event has been detected on this view. * @param function the Function to invoke. Receives a javaQuery containing the responding view * and two variable arguments: * <ol> * <li>the Integer key code * <li>the {@link KeyEvent} Object that was produced * </ol> * @return this */ public $ keypress(Function function) { keyPress = function; setupKeyListener(); return this; } /** * Set the function to call when a key-up event has been detected on this view. * @param function the Function to invoke. Receives a javaQuery containing the responding view * and two variable arguments: * <ol> * <li>the Integer key code * <li>the {@link KeyEvent} Object that was produced * </ol> * @return this */ public $ keyup(Function function) { keyUp = function; setupKeyListener(); return this; } /////Miscellaneous /** * Invokes the given function for each view in the current selection. Function receives a * javaQuery instance containing the single view, and an integer of the current index * @param function the function to invoke * @return this */ public $ each(Function function) { for (int i = 0; i < views.size(); i++) { function.invoke($.with(views.get(i)), i); } return this; } /** * Gets the number of child views contained in the first selected view * @return the number of child views contained in the first selected view */ public int children() { if (view(0) instanceof Container) { return ((Container) view(0)).getComponentCount(); } else return 0; } /** * If the first view of the current selection is a subclass of {@link AdapterView}, this will loop through all the * adapter data and invoke the given function, passing the varargs: * <ol> * <li>the item from the adapter * <li>the index * </ol> * Otherwise, if the first view in the current selection is a subclass of {@link Container}, {@code each} will * loop through all the child views, and wrap each one in a javaQuery object. The invoked * function will receive it, and an int for the index of the selected child view. * @param function Function the function to invoke * @return this */ public $ children(Function function) { if (view(0) instanceof Container) { Container group = (Container) view(0); for (int i = 0; i < group.getComponentCount(); i++) { function.invoke($.with(group.getComponent(i)), i); } } return this; } /** * Loops through all the sibling views of the first view in the current selection, and wraps * each in a javaQuery object. When invoked, the given function will receive two parameters: * <ol> * <li>the javaQuery for the view * <li>the child index of the sibling * </ol> * @param function receives the javaQuery for the view, and the index for arg1 */ public $ siblings(Function function) { Component parent = view(0).getParent(); if (parent != null && parent instanceof Container) { Container group = (Container) parent; for (int i = 0; i < group.getComponentCount(); i++) { function.invoke($.with(group.getComponent(i)), i); } } return this; } /** * Gets all the views in the current selection after the given start index * @param start the starting position of the views to pass to the new instance of javaQuery. * @return a javaQuery object containing the views from {@code start} to the end of the list. */ public $ slice(int start) { return $.with(this.views.subList(start, this.views.size())); } /** * Gets all the views in the current selection after the given start index and before the given * end index * @param start the starting position of the views to pass to the new instance of javaQuery. * @return a javaQuery object containing the views from {@code start} to {@code end}. */ public $ slice(int start, int end) { return $.with(this.views.subList(start, end)); } /** @return the number of views that are currently selected */ public int length() { return this.views.size(); } /** @return the number of views that are currently selected */ public int size() { return length(); } /** * Checks to see if the first view in the current selection is a subclass of the given class name * @param className the name of the superclass to check * @return {@code true} if the view is a subclass of the given class name. * Otherwise, {@code false}. */ public boolean is(String className) { try { Class<?> clazz = Class.forName(className); if (clazz.isInstance(view(0))) return true; return false; } catch (Throwable t) { return false; } } /** * Checks to see if the given Object is a subclass of the given class name * @param obj the Object to check * @param className the name of the superclass to check * @return {@code true} if the view is a subclass of the given class name. * Otherwise, {@code false}. */ public static boolean is(Object obj, String className) { try { Class<?> clazz = Class.forName(className); if (clazz.isInstance(obj)) return true; return false; } catch (Throwable t) { return false; } } /** * Checks to see if the given Object is a subclass of the given class * @param obj the Object to check * @param clazz the class to check * @return {@code true} if the view is a subclass of the given class name. * Otherwise, {@code false}. */ public static boolean is(Object obj, Class<?> clazz) { if (clazz.isInstance(obj)) return true; return false; } /** * Removes each view in the current selection from the layout */ public void remove() { for (Component view : this.views) { Component parent = view.getParent(); if (parent != null && parent instanceof Container) { ((Container) parent).remove(view); } } } /////Selectors /** * Recursively selects all subviews of the given view * @param v the parent view of all the suclasses to select * @return a list of all views that are subviews in the * view hierarchy with the given view as the root */ private List<Component> recursivelySelectAllSubViews(Component v) { List<Component> list = new ArrayList<Component>(); if (v instanceof Container) { for (int i = 0; i < ((Container) v).getComponentCount(); i++) { list.addAll(recursivelySelectAllSubViews(((Container) v).getComponent(i))); } } list.add(v); return list; } /** * Recursively selects all subviews of the given view that are subclasses of the given Object type * @param v the parent view of all the suclasses to select * @return a list of all views that are subviews in the * view hierarchy with the given view as the root */ private List<Component> recursivelySelectByType(Component v, Class<?> clazz) { List<Component> list = new ArrayList<Component>(); if (v instanceof Container) { for (int i = 0; i < ((Container) v).getComponentCount(); i++) { list.addAll(recursivelySelectByType(((Container) v).getComponent(i), clazz)); } } if (clazz.isInstance(v)) list.add(v); return list; } /** * Select all subviews of the currently-selected views * @return a javaQuery Object with all the views */ public $ selectAll() { List<Component> subviews = new ArrayList<Component>(); for (Component view : this.views) { subviews.addAll(recursivelySelectAllSubViews(view)); } return $.with(subviews); } /** * Select all subviews of the currently-selected views that are subclasses of the given {@code className}. * @param className * @return all the selected views in a javaQuery wrapper */ public $ selectByType(String className) { Class<?> clazz = null; try { clazz = Class.forName(className); } catch (Throwable t) { return null; } List<Component> subviews = new ArrayList<Component>(); for (Component view : this.views) { subviews.addAll(recursivelySelectByType(view, clazz)); } return $.with(subviews); } /** * Selects the child views of the first view in the current selection * @return a javaQuery Objects containing the child views. If the view is a subclass of * {@link AdapterView}, the {@link #data() data} of the javaQuery will be set to an Object[] * of adapter items. */ public $ selectChildren() { List<Component> list = new ArrayList<Component>(); if (view(0) instanceof Container) { for (int i = 0; i < ((Container) view(0)).getComponentCount(); i++) { list.add(((Container) view(0)).getComponent(i)); } } return $.with(list); } /** * Selects all subviews of the given view that do not contain subviews * @param v the view whose subviews will be retrieved * @return a list empty views */ private List<Component> recursivelySelectEmpties(Component v) { List<Component> list = new ArrayList<Component>(); if (v instanceof Container && ((Container) v).getComponentCount() > 0) { for (int i = 0; i < ((Container) v).getComponentCount(); i++) { list.addAll(recursivelySelectEmpties(((Container) v).getComponent(i))); } } else { list.add(v); } return list; } /** * Select all non-Containers, or Containers with no children, that lay within the view * hierarchy of the current selection * @return a javaQuery object containing the selection */ public $ selectEmpties() { List<Component> subviews = new ArrayList<Component>(); for (Component view : this.views) { subviews.addAll(recursivelySelectEmpties(view)); } return $.with(subviews); } /** * Searches the view hierarchy rooted at the given view in order to find the currently * selected view * @param view the view to search within * @return the selected view, or null if no view in the given hierarchy was found. */ private Component recursivelyFindSelectedSubView(Component view) { if (view.hasFocus()) return view; else if (view instanceof Container) { Component v = null; for (int i = 0; i < ((Container) view).getComponentCount(); i++) { v = recursivelyFindSelectedSubView(((Container) view).getComponent(i)); if (v != null) return v; } return null; } else return null; } /** * Selects the currently-focused view. * @return a javaQuery Object created with the currently-selected View, if there is one */ public $ selectFocused() { Component focused = recursivelyFindSelectedSubView(rootView); if (focused != null) return $.with(focused); for (Component view : this.views) { focused = recursivelyFindSelectedSubView(view); if (focused != null) return $.with(focused); } return $.with(view(0)); } /** * Select all {@link View#INVISIBLE invisible}, {@link View#GONE gone}, and 0-alpha views within the * view hierarchy rooted at the given view * @param v the view hierarchy in which to search * @return a list the found views */ private List<Component> recursivelySelectHidden(Component v) { List<Component> list = new ArrayList<Component>(); if (v instanceof Container) { for (int i = 0; i < ((Container) v).getComponentCount(); i++) { list.addAll(recursivelySelectHidden(((Container) v).getComponent(i))); } } if (!v.isVisible() || v.isOpaque()) list.add(v); return list; } /** * Select all {@link View#VISIBLE visible} and 1-alpha views within the given view hierarchy * @param v the view to search in * @return a list the found views */ private List<Component> recursivelySelectVisible(Component v) { List<Component> list = new ArrayList<Component>(); if (v instanceof Container) { for (int i = 0; i < ((Container) v).getComponentCount(); i++) { list.addAll(recursivelySelectVisible(((Container) v).getComponent(i))); } } if (v.isVisible() || !v.isOpaque()) list.add(v); return list; } /** * Select all {@link View#INVISIBLE invisible}, {@link View#GONE gone}, and 0-alpha views within * the view hierarchy of the currently selected views * @return a javaQuery Object containing the found views */ public $ selectHidden() { List<Component> subviews = new ArrayList<Component>(); for (Component view : this.views) { subviews.addAll(recursivelySelectHidden(view)); } return $.with(subviews); } /** * Select all {@link View#VISIBLE visible} and 1-alpha views within the view hierarchy * of the currenly selected views * @return a javaQuery Object containing the found views */ public $ selectVisible() { List<Component> subviews = new ArrayList<Component>(); for (Component view : this.views) { subviews.addAll(recursivelySelectVisible(view)); } return $.with(subviews); } /** * Set the current selection to the view with the given name * @param name the name of the view to manipulate * @return this */ public $ name(String name) { Component view = this.findComponentWithName(name); if (view != null) { this.views.clear(); this.rootView = view; this.views.add(view); } return this; } /** * Selects all views within the given current selection that are the single * children of their parent views * @param v the view whose hierarchy will be checked * @return a list of the found views. */ private List<Component> recursivelySelectOnlyChilds(Component v) { List<Component> list = new ArrayList<Component>(); if (v instanceof Container) { for (int i = 0; i < ((Container) v).getComponentCount(); i++) { list.addAll(recursivelySelectOnlyChilds(((Container) v).getComponent(i))); } } if (v.getParent() instanceof Container && ((Container) v.getParent()).getComponentCount() == 1) list.add(v); return list; } /** * Selects all views within the current selection that are the single children of their * parent views * @return a javaQuery Object containing the found views. */ public $ selectOnlyChilds() { List<Component> subviews = new ArrayList<Component>(); for (Component view : this.views) { subviews.addAll(recursivelySelectOnlyChilds(view)); } return $.with(subviews); } /** * Selects all views in the current selection that can contain other child views * @return a javaQuery Object containing the found Containers */ public $ selectParents() { List<Component> subviews = new ArrayList<Component>(); for (Component view : this.views) { subviews.addAll(recursivelySelectByType(view, Container.class)); } return $.with(subviews); } /////Extensions /** * Add an extension with the reference <em>name</em> * @param name String used by the {@link #ext(String)} method for calling this extension * @param clazz the name of the subclass of {@link $Extension} that will be mapped to {@code name}. * Calling {@code $.with(this).ext(myExtension); } will now create a new instance of the given * {@code clazz}, passing in {@code this} instance of <em>$</em>, then calling the {@code invoke} * method. * @throws ClassNotFoundException if {@code clazz} is not a valid class name, or if it is not a * subclass of {@code $Extension}. * @throws NoSuchMethodException * @throws SecurityException * @throws IllegalAccessException * @throws InstantiationException * @note be aware that there is no check for if this extension overwrites a different extension. */ public static void extend(String name, String clazz) throws ClassNotFoundException, SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException { Class<?> _class = Class.forName(clazz); Class<?> _super = _class.getSuperclass(); if (_super == null || _super != $Extension.class) { throw new ClassNotFoundException("clazz must be subclass of $Extension!"); } Constructor<?> constructor = _class.getConstructor(new Class<?>[] { $.class }); extensions.put(name, constructor); } /** * Add an extension with the reference <em>name</em> * @param name String used by the {@link #ext(String)} method for calling this extension * @param clazz subclass of {@link $Extension} that will be mapped to {@code name}. * Calling {@code $.with(this).ext(MyExtension.class); } will now create a new instance of the given * {@code clazz}, passing in {@code this} instance of <em>$</em>, then calling the {@code invoke} * method. * @throws ClassNotFoundException if {@code clazz} is not a valid class name, or if it is not a * subclass of {@code $Extension}. * @throws NoSuchMethodException * @throws SecurityException * @throws IllegalAccessException * @throws InstantiationException * @note be aware that there is no check for if this extension overwrites a different extension. */ public static void extend(String name, Class<?> clazz) throws ClassNotFoundException, SecurityException, NoSuchMethodException { Class<?> _super = clazz.getSuperclass(); if (_super == null || _super != $Extension.class) { throw new ClassNotFoundException("clazz must be subclass of $Extension!"); } Constructor<?> constructor = clazz.getConstructor(new Class<?>[] { $.class }); extensions.put(name, constructor); } /** * Load the extension with the name defined in {@link #extend(String, String)} * @param extension the name of the extension to load * @return the new extension instance */ public $Extension ext(String extension, Object... args) { Constructor<?> constructor = extensions.get(extension); try { $Extension $e = ($Extension) constructor.newInstance(this); $e.invoke(args); return $e; } catch (Throwable t) { t.printStackTrace(); return null; } } /////File IO /** * Write a byte stream to file * @param s the bytes to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new data to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. */ public static void write(byte[] s, String fileName, boolean append) { write(s, fileName, append, null, null); } /** * Write a String to file * @param s the String to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new String to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. */ public static void write(String s, String fileName, boolean append) { write(s.getBytes(), fileName, append, null, null); } /** * Write a String to file, and execute functions once complete. * @param s the String to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new String to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. * @param success Function to invoke on a successful file-write. Parameters received will be: * <ol> * <li>the String to write * <li>the File that was written (to) * </ol> * @param error Function to invoke on a file I/O error. Parameters received will be: * <ol> * <li>the String to write * <li>the String reason * </ol> */ @SuppressWarnings("unchecked") public static void write(final String s, final String fileName, boolean append, final String notifySuccess, String notifyError) { File file = new File(fileName); if (!file.canWrite()) { if (notifyError != null) EventCenter.trigger(null, notifyError, (Map<String, Object>) $.map($.entry("data", s.getBytes()), $.entry("message", "You do not have file write privelages")), null); return; } try { FileWriter writer = new FileWriter(file); if (append) { writer.append(s); } else { writer.write(s); } if (notifySuccess != null) EventCenter.trigger(null, notifySuccess, (Map<String, Object>) $.map($.entry("data", s.getBytes()), $.entry("message", "Success")), null); } catch (Throwable t) { if (notifyError != null) EventCenter.trigger(null, notifyError, (Map<String, Object>) $.map($.entry("data", s.getBytes()), $.entry("message", "IO Error")), null); } } /** * Write a byte stream to file, and execute functions once complete. * @param s the bytes to write to the file * @param path defines the save location of the file * @param append {@code true} to append the new bytes to the end of the file. {@code false} to overwrite any existing file. * @param async {@code true} if the operation should be performed asynchronously. Otherwise, {@code false}. * @param notifySuccess Notification to post with parameters on a successful file write. Parameters received will be: * <ol> * <li>"data" : the byte[] to write * <li>"message" : a message * </ol> * @param notifyError. Notification to post with parameters on a failed file write. Parameters received will be: * <ol> * <li>"data" : the byte[] to write * <li>"message" : a message * </ol> */ public static void write(final byte[] s, String fileName, boolean append, String notifySuccess, String notifyError) { write(new String(s), fileName, append, notifySuccess, notifyError); } ////Utilities /** * Convert an Object Array to a JSONArray * @param array an Object[] containing any of: JSONObject, JSONArray, String, Boolean, Integer, * Long, Double, NULL, or null. May not include NaNs or infinities. Unsupported values are not * permitted and will cause the JSONArray to be in an inconsistent state. * @return the newly-created JSONArray Object */ public static JSONArray makeArray(Object[] array) { JSONArray json = new JSONArray(); for (Object obj : array) { json.put(obj); } return json; } /** * Convert a JSONArray to an Object Array * @param array the array to convert * @return the converted array */ public static Object[] makeArray(JSONArray array) { Object[] obj = new Object[array.length()]; for (int i = 0; i < array.length(); i++) { try { obj[i] = array.get(i); } catch (Throwable t) { obj[i] = JSONObject.NULL; } } return obj; } /** * Converts a JSON String to a Map * @param json the String to convert * @return a Key-Value Mapping of attributes declared in the JSON string. * @throws JSONException thrown if the JSON string is malformed or incorrectly written */ public static Map<String, ?> map(String json) throws JSONException { return map(new JSONObject(json)); } /** * Convert a {@code JSONObject} to a Map * @param json the JSONObject to parse * @return a Key-Value mapping of the Objects set in the JSONObject * @throws JSONException if the JSON is malformed */ public static Map<String, ?> map(JSONObject json) throws JSONException { @SuppressWarnings("unchecked") Iterator<String> iterator = json.keys(); Map<String, Object> map = new HashMap<String, Object>(); while (iterator.hasNext()) { String key = iterator.next(); try { Object value = json.get(key); map.put(key, value); } catch (JSONException e) { throw e; } catch (Throwable t) { if (key != null) Log.w("AjaxOptions", "Could not set value " + key); else throw new NullPointerException("Iterator reference is null."); } } return map; } /** * Shortcut method for creating a Map of Key-Value pairings. For example:<br> * $.map($.entry(0, "Zero"), $.entry(1, "One"), $.entry(2, "Two")); * @param entries the MapEntry Objects used to populate the map. * @return a new map with the given entries * @see #entry(Object, Object) */ public static Map<?, ?> map(Entry<?, ?>... entries) { return QuickMap.qm(entries); } /** * Shortcut method for creating a Key-Value pairing. For example:<br> * $.map($.entry(0, "Zero"), $.entry(1, "One"), $.entry(2, "Two")); * @param key the key * @param value the value * @return the Key-Value pairing Object * @see #map(Entry...) */ public static Entry<?, ?> entry(Object key, Object value) { return QuickEntry.qe(key, value); } /** * Merges the contents of two arrays together into the first array. */ public static void merge(Object[] array1, Object[] array2) { Object[] newArray = new Object[array1.length + array2.length]; for (int i = 0; i < array1.length; i++) { newArray[i] = array1[i]; } for (int i = 0; i < array2.length; i++) { newArray[i] = array2[i]; } array1 = newArray; } /** * @return a new Function that does nothing when invoked */ public static Function noop() { return new Function() { @Override public void invoke($ javaQuery, Object... args) { } }; } /** * @return the current time, in milliseconds */ public static long now() { return new Date().getTime(); } /** * Parses a JSON string into a JSONObject or JSONArray * @param json the String to parse * @return JSONObject or JSONArray (depending on the given string) if parse succeeds. Otherwise {@code null}. */ public Object parseJSON(String json) { try { if (json.startsWith("{")) return new JSONObject(json); else return new JSONArray(json); } catch (JSONException e) { return null; } } /** * Parses XML into an XML Document * @param xml the XML to parse * @return XML Document if parse succeeds. Otherwise {@code null}. */ public Document parseXML(String xml) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { return factory.newDocumentBuilder().parse(xml); } catch (Throwable t) { return null; } } /////AJAX /** * Perform a new Ajax Task using the AjaxOptions set in the given Key-Value Map * @param options {@link AjaxOptions} options */ public static void ajax(Map<String, Object> options) { ajax(new JSONObject(options)); } /** * Perform a new Ajax Task using the given JSON string to configure the {@link AjaxOptions} * @param options {@link AjaxOptions} as a JSON String */ public static void ajax(String options) { try { ajax(new JSONObject(options)); } catch (Throwable t) { throw new NullPointerException("Could not parse JSON!"); } } /** * Perform a new Ajax Task using the given JSONObject to configure the {@link AjaxOptions} * @param options {@link AjaxOptions} as a JSONObject Object */ public static void ajax(JSONObject options) { try { new AjaxTask(options).execute(); } catch (Throwable t) { Log.e("javaQuery", "Could not complete ajax task!"); } } /** * Perform an Ajax Task using the given {@code AjaxOptions} * @param options the options to set for the Ajax Task */ public static void ajax(AjaxOptions options) { try { new AjaxTask(options).execute(); } catch (Throwable t) { Log.e("javaQuery", "Could not complete ajax task!"); } } /** * Perform an Ajax Task. This is usually done as the result of an Ajax Error * @param request the request * @param options the configuration * @see AjaxTask.AjaxError */ public static void ajax(HttpUriRequest request, AjaxOptions options) { new AjaxTask(request, options).execute(); } ///////ajax shortcut methods /** * Shortcut method to use the default AjaxOptions to perform an Ajax GET request * @param url the URL to access * @param data the data to pass, if any * @param success the Function to invoke once the task completes successfully. * @param dataType the type of data to expect as a response from the URL. See * {@link AjaxOptions#dataType()} for a list of available data types */ public static void get(String url, Object data, Function success, String dataType) { $.ajax(new AjaxOptions().url(url).data(data).success(success).dataType(dataType)); } /** * Shortcut method to use the default Ajax Options to perform an Ajax GET request and receive * a JSON-formatted response * @param url the URL to access * @param data the data to send, if any * @param success Function to invoke once the task completes successfully. */ public static void getJSON(String url, Object data, Function success) { get(url, data, success, "JSON"); } /** * Shortcut method to use the default Ajax Options to perform an Ajax GET request and receive * a Script response * @param url the URL to access * @param data the data to send, if any * @param success Function to invoke once the task completes successfully. * @see {@link ScriptResponse} */ public static void getScript(String url, Function success) { $.ajax(new AjaxOptions().url(url).success(success).dataType("SCRIPT")); } /** * Shortcut method to use the default Ajax Options to perform an Ajax POST request * @param url the URL to access * @param data the data to post * @param success Function to invoke once the task completes successfully. * @param dataType the type of data to expect as a response from the URL. See * {@link AjaxOptions#dataType()} for a list of available data types */ public static void post(String url, Object data, Function success, String dataType) { $.ajax(new AjaxOptions().type("POST").url(url).data(data).success(success).dataType(dataType)); } /** * Register an event to invoke a Function every time a global Ajax Task completes * @param complete the Function to call. */ public static void ajaxComplete(Function complete) { EventCenter.bind("ajaxComplete", complete, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task completes. * @see #ajaxComplete(Function) */ public static void ajaxComplete() { EventCenter.trigger("ajaxComplete", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task receives an error * @param error the Function to call. */ public static void ajaxError(Function error) { EventCenter.bind("ajaxError", error, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task receives an error. * @see #ajaxError(Function) */ public static void ajaxError() { EventCenter.trigger("ajaxError", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task sends data * @param send the Function to call. */ public static void ajaxSend(Function send) { EventCenter.bind("ajaxSend", send, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task sends data. * @see #ajaxSend(Function) */ public static void ajaxSend() { EventCenter.trigger("ajaxSend", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task begins, if no other * global task is running. * @param start the Function to call. */ public static void ajaxStart(Function start) { EventCenter.bind("ajaxStart", start, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task begins, if * no other global task is running. * @see #ajaxStart(Function) */ public static void ajaxStart() { EventCenter.trigger("ajaxStart", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task stops, if it was * the last global task running * @param stop the Function to call. */ public static void ajaxStop(Function stop) { EventCenter.bind("ajaxStop", stop, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task stops, if it was * the last global task running * @see #ajaxStop(Function) */ public static void ajaxStop() { EventCenter.trigger("ajaxStop", null, null); } /** * Register an event to invoke a Function every time a global Ajax Task completes successfully. * @param start the Function to invoke. */ public static void ajaxSuccess(Function success) { EventCenter.bind("ajaxSuccess", success, null); } /** * Manually invoke the Function set to be called every time a global Ajax Task completes * successfully. * @see #ajaxSuccess(Function) */ public static void ajaxSuccess() { EventCenter.trigger("ajaxSuccess", null, null); } /** * Handle custom Ajax options or modify existing options before each request is sent and before they are processed by $.ajax(). * Note that this will be run in the background thread, so any changes to the UI must be made through a call to Activity.runOnUiThread(). * @param prefilter {@link Function} that will receive one Map argument with the following contents: * <ul> * <li>"options" : AjaxOptions for the request * <li>"request" : HttpClient request Object * </ul> * */ public static void ajaxPrefilter(Function prefilter) { EventCenter.bind("ajaxPrefilter", prefilter, null); } /** * Setup global options * @param options */ public static void ajaxSetup(AjaxOptions options) { AjaxOptions.ajaxSetup(options); } /** * Clears async task queue */ public static void ajaxKillAll() { AjaxTask.killTasks(); } /** * Load data from the server and place the returned HTML into the matched element * @param url A string containing the URL to which the request is sent. * @param data A plain object or string that is sent to the server with the request. * @param complete A callback function that is executed when the request completes. Will receive * two arguments: 1. response text, 2. text status */ public void load(String url, Object data, final Function complete) { $.ajax(new AjaxOptions().url(url).data(data).complete(new Function() { @Override public void invoke($ javaQuery, Object... params) { $.this.html(params[0].toString()); complete.invoke($.this, params); } })); } //// Convenience /** * Include the html string the selected views. If a view has a setText method, it is used. Otherwise, * a new TextView is created. This html can also handle image tags for both urls and local files. * Local files should be the name (for example, for R.id.ic_launcher, just use ic_launcher). * @param html the HTML String to include */ public $ html(String html) { for (Component view : this.views) { try { Method m = view.getClass().getMethod("setText", new Class<?>[] { String.class }); m.invoke(view, html); } catch (Throwable t) { if (view instanceof Container) { try { //no setText method. Try a TextView JLabel label = new JLabel(); int rgba = Color.HSBtoRGB(0, 0, 0); rgba |= (0 & 0xff); label.setBackground(new Color(rgba, true)); label.setBounds(label.getParent().getBounds()); ((Container) view).add(label); label.setText(html); } catch (Throwable t2) { //unable to set content Log.w("javaQuery", "unable to set HTML content"); } } else { //unable to set content Log.w("javaQuery", "unable to set textual content"); } } } return this; } /** * Includes the given text string inside of the selected views. If a view has a setText method, it is used * otherwise, if possible, a textview is added as a child to display the text. * @param text the text to include */ public $ text(String text) { for (Component view : this.views) { try { Method m = view.getClass().getMethod("setText", new Class<?>[] { String.class }); m.invoke(view, text); } catch (Throwable t) { if (view instanceof Container) { try { //no setText method. Try a TextView JLabel label = new JLabel(); int rgba = Color.HSBtoRGB(0, 0, 0); rgba |= (0 & 0xff); label.setBackground(new Color(rgba, true)); label.setBounds(label.getParent().getBounds()); ((Container) view).add(label); label.setText(text); } catch (Throwable t2) { //unable to set content Log.w("javaQuery", "unable to set textual content"); } } else { //unable to set content Log.w("javaQuery", "unable to set textual content"); } } } return this; } /** * Includes the given image inside of the selected views. If a view is an `ImageView`, its image * is set. Otherwise, the background image of the view is set. * @param image the drawable image to include * @return this */ public $ image(Image image) { for (Component v : views) { if (v instanceof JLabel) { ((JLabel) v).setIcon(new ImageIcon(image)); } else if (v instanceof Window) { ((Window) v).setIconImage(image); } else { if (v instanceof Container) { JLabel l = new JLabel(new ImageIcon(image)); l.setBounds(v.getBounds()); ((Container) v).add(l); } else if (v.getParent() != null && v.getParent() instanceof Container) { JLabel l = new JLabel(new ImageIcon(image)); l.setBounds(v.getBounds()); ((Container) v.getParent()).add(l); } } } return this; } /** * For `ImageView`s, this will set the image to the given asset or url. Otherwise, it will set the * background image for the selected views. * @param source asset path, file path (starting with "file://") or URL to image * @return this */ public $ image(String source) { return image(source, -1, -1, null); } /** * For `ImageView`s, this will set the image to the given asset or url. Otherwise, it will set the * background image for the selected views. * @param source asset path, file path (starting with "file://") or URL to image * @param width specifies the output bitmap width * @param height specifies the output bitmap height * @param error if the given source is a file or asset, this receives a javaQuery wrapping the * current context and the {@code Throwable} error. Otherwise, this will receive an * Ajax error. * @return this * @see AjaxOptions#error(Function) */ public $ image(String source, int width, int height, Function error) { if (source.startsWith("file://")) { try { Image image = ImageIO.read(new File(source.substring(7))); for (Component v : views) { if (v instanceof JLabel) { ((JLabel) v).setIcon(new ImageIcon(image)); } else if (v instanceof Window) { ((Window) v).setIconImage(image); } else { if (v instanceof Container) { JLabel l = new JLabel(new ImageIcon(image)); l.setBounds(v.getBounds()); ((Container) v).add(l); } else if (v.getParent() != null && v.getParent() instanceof Container) { JLabel l = new JLabel(new ImageIcon(image)); l.setBounds(v.getBounds()); ((Container) v.getParent()).add(l); } } } } catch (Throwable t) { if (error != null) { error.invoke($.with(view(0)), t); } } } else { boolean fallthrough = false; try { new URL(source); } catch (Throwable t) { fallthrough = true; } if (fallthrough) { AjaxOptions options = new AjaxOptions().url(source).type("GET").dataType("image").context(view(0)) .global(false).success(new Function() { @Override public void invoke($ javaQuery, Object... params) { Image image = (Image) params[0]; for (Component v : views) { if (v instanceof JLabel) { ((JLabel) v).setIcon(new ImageIcon(image)); } else if (v instanceof Window) { ((Window) v).setIconImage(image); } else { if (v instanceof Container) { JLabel l = new JLabel(new ImageIcon(image)); l.setBounds(v.getBounds()); ((Container) v).add(l); } else if (v.getParent() != null && v.getParent() instanceof Container) { JLabel l = new JLabel(new ImageIcon(image)); l.setBounds(v.getBounds()); ((Container) v.getParent()).add(l); } } } } }); if (error != null) { options.error(error); } if (width >= 0) { options.imageWidth(width); } if (height >= 0) { options.imageHeight(height); } $.ajax(options); } } return this; } /** * Iterates through the selected views and sets the images to the given images (in order) * @param source asset path, file path (starting with "file://") or URL to image * @return this */ public $ image(final List<String> sources) { this.each(new Function() { @Override public void invoke($ javaQuery, Object... params) { javaQuery.image(sources.get((Integer) params[0])); } }); return this; } /** * Iterates through the selected views and sets the images to the given images (in order) * @param sources the file paths or URLs to set * @param width the output width of the image * @param height the output height of the image * @param error if the given source is a file or asset, this receives a javaQuery wrapping the * current context and the {@code Throwable} error. Otherwise, this will receive an * Ajax error. * @return this * @see AjaxOptions#error(Function) */ public $ image(final List<String> sources, final int width, final int height, final Function error) { this.each(new Function() { @Override public void invoke($ javaQuery, Object... params) { javaQuery.image(sources.get((Integer) params[0]), width, height, error); } }); return this; } /** * Show an alert. * @param text the message to display. * @see #alert(String, String) */ public static void alert(String text) { alert(null, text); } /** * Show an alert * @param context used to display the alert window * @param title the title of the alert window. Use {@code null} to show no title * @param text the alert message * @see #alert(String) */ public static void alert(String title, String text) { JDialog alert = new JDialog(); if (title != null) alert.setTitle(title); if (text != null) alert.add(new JLabel(text, JLabel.CENTER)); alert.setVisible(true); } ////Callbacks /** * A multi-purpose callbacks list object that provides a powerful way to manage callback lists. * Registered callback functions will receive a {@code null} for their <em>javaQuery</em> * variable. To receive a non-{@code null} variable, you must provide a <em>Context</em>. * @return a new instance of {@link Callbacks} */ public static Callbacks Callbacks() { return new Callbacks(); } //////CSS-based /** * @return the computed height for the first view in the current selection */ public int height() { return view(0).getHeight(); } /** * Set the height of the selected views * @param height the new height * @return this */ public $ height(int height) { for (Component view : this.views) { Rectangle bounds = view.getBounds(); bounds.height = height; view.setBounds(bounds); } return this; } /** * @return the computed width for the first view in the current selection */ public int width() { return view(0).getWidth(); } /** * Set the width of the views in the current selection * @param width the new width * @return this */ public $ width(int width) { for (Component view : this.views) { Rectangle bounds = view.getBounds(); bounds.width = width; view.setBounds(bounds); } return this; } /** * @return the coordinates of the first view in the current selection. */ public Point offset() { return new Point(view(0).getBounds().x, view(0).getBounds().y); } /** * Set the coordinates of each selected view, relative to the document. * @param x the x-coordinate, in pixels * @param y the y-coordinate, in pixels * @return this */ public $ offset(int x, int y) { return position(x, y); } /** * @return the coordinates of the first view in the current selection, * relative to the offset parent. */ public Point position() { return new Point(view(0).getX(), view(0).getY()); } /** * Sets the coordinates of each selected view, relative to its offset parent * @param x the x-coordinate * @param y the y-coordinate * @return */ public $ position(int x, int y) { for (Component view : this.views) { Rectangle bounds = view.getBounds(); bounds.x = x; bounds.y = y; view.setBounds(bounds); } return this; } //Timer Functions /** * Schedule a task for single execution after a specified delay. * @param function the task to schedule. Receives no args. Note that the function will be * run on a Timer thread, and not the UI Thread. * @param delay amount of time in milliseconds before execution. * @return the created Timer */ public static Timer setTimeout(final Function function, long delay) { Timer t = new Timer(); t.schedule(new TimerTask() { @Override public void run() { function.invoke(null); } }, delay); return t; } /** * Schedule a task for repeated fixed-rate execution after a specific delay has passed. * @param the task to schedule. Receives no args. Note that the function will be * run on a Timer thread, and not the UI Thread. * @param delay amount of time in milliseconds before execution. * @return the created Timer */ public static Timer setInterval(final Function function, long delay) { Timer t = new Timer(); t.scheduleAtFixedRate(new TimerTask() { @Override public void run() { function.invoke(null); } }, 0, delay); return t; } /** * Capitalizes the first letter of the given string. * @param string the string whose first letter should be capitalized * @return the given string with its first letter capitalized * @throws NullPointerException if the string is null or empty */ public static String capitalize(String string) { if (string == null || string.trim().length() == 0) throw new NullPointerException("Cannot handle null or empty string"); StringBuilder strBuilder = new StringBuilder(string); strBuilder.setCharAt(0, Character.toUpperCase(strBuilder.charAt(0))); return strBuilder.toString(); } }