diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/Animated.java b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/Animated.java index 119c94a573cc856df640ef7ab7639551d4d9e4e9..ff23eca0f2fb8bc7ece3de02905ac9fea84191b8 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/Animated.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/Animated.java @@ -16,7 +16,7 @@ */ package lu.list.itis.dkd.tui.widget.animation; -import java.util.List; +import java.util.Collection; /** * @author mack @@ -37,7 +37,7 @@ public interface Animated { /** * @return */ - public List> getAnimationProperties(); + public Collection> getAnimationProperties(); /** * diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationEventSource.java b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationEventSource.java index 842787c3b5cbc6c4b27bedb05a6ce25c3b0dfa3a..16188f9c9bc8ae4d71a8ae318e9f063a66a4c625 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationEventSource.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationEventSource.java @@ -16,12 +16,11 @@ */ package lu.list.itis.dkd.tui.widget.animation; -import com.google.common.collect.Multimap; -import com.google.common.collect.TreeMultimap; - import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; // *************************************************************************** // * Class Definition and Members * @@ -34,7 +33,7 @@ import java.util.List; */ public class AnimationEventSource { private ArrayList listeners; - private Multimap keyframes; + private Map> keyframes; // *************************************************************************** // * Constants * @@ -51,7 +50,7 @@ public class AnimationEventSource { */ public AnimationEventSource() { listeners = new ArrayList<>(); - keyframes = TreeMultimap.create(); + keyframes = new HashMap<>(); } // --------------------------------------------------------------------------- @@ -81,7 +80,10 @@ public class AnimationEventSource { */ public void addAnimationListenerAtKeyFrame(AnimationListener listener, int keyFrame) { listeners.add(listener); - keyframes.put(keyFrame, listener); + if (!keyframes.containsKey(listener)) { + keyframes.put(keyFrame, new ArrayList<>()); + } + keyframes.get(keyFrame).add(listener); } // --------------------------------------------------------------------------- @@ -90,7 +92,9 @@ public class AnimationEventSource { * @param listener */ public void addAnimationListener(AnimationListener listener) { - listeners.add(listener); + if (!listeners.contains(listener)) { + listeners.add(listener); + } } // --------------------------------------------------------------------------- diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationListener.java b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationListener.java index 10c96ceafc35b7e1bb2c90838245083048a245c7..5e4f2710c4f7b856e833f179ca767a7f3c536b9a 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationListener.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationListener.java @@ -25,7 +25,7 @@ package lu.list.itis.dkd.tui.widget.animation; // * Interface Definition and Members * // *************************************************************************** -public interface AnimationListener extends Comparable { +public interface AnimationListener { /** * @param event diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationProperty.java b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationProperty.java index 528b3e8dba0f1a610864dcb91235d878dcfe2616..62a062209bdc6237a9f6ff30d84f9eb03278211d 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationProperty.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/animation/AnimationProperty.java @@ -103,6 +103,24 @@ public class AnimationProperty implements TimelineCallback { // * Class Body * // *************************************************************************** // --------------------------------------------------------------------------- + /** + * @param listener + */ + // --------------------------------------------------------------------------- + + public void addAnimationListener(AnimationListener listener) { + listeners.addAnimationListener(listener); + } + + // --------------------------------------------------------------------------- + /** + * @param listener + */ + // --------------------------------------------------------------------------- + + public void removeAnimationListener(AnimationListener listener) { + listeners.removeAnimationListener(listener); + } // --------------------------------------------------------------------------- /** @@ -220,10 +238,10 @@ public class AnimationProperty implements TimelineCallback { // --------------------------------------------------------------------------- /** - * Allows controlling looping behavior. When reversing is enabled, (true), - * animation plays back and forth whenever the end is reached. Specify false to - * disable reversing. It should be noted that reversing requires the looping property to be set - * in order to have any effect + * Allows controlling looping behavior. When reversing is enabled, (true), animation + * plays back and forth whenever the end is reached. Specify false to disable + * reversing. It should be noted that reversing requires the looping property to be set in order to + * have any effect * * @param reversing * true to enable reversing, false to disable @@ -318,11 +336,13 @@ public class AnimationProperty implements TimelineCallback { // --------------------------------------------------------------------------- /** * starts the animation of the property represented by this instance + * + * @param backwards */ // --------------------------------------------------------------------------- @SuppressWarnings("incomplete-switch") - public void start() { + public void start(boolean backwards) { if (timeline != null) { @@ -338,6 +358,8 @@ public class AnimationProperty implements TimelineCallback { if (looping) { timeline.playLoop((reversing ? RepeatBehavior.REVERSE : RepeatBehavior.LOOP)); + } else if (backwards) { + timeline.playReverse(); } else { timeline.play(); } @@ -387,6 +409,7 @@ public class AnimationProperty implements TimelineCallback { direction = Direction.NONE; keyFrameIterator = null; nextKeyFrame = null; + this.listeners.notifyAnimationListener(new AnimationEvent(this, AnimationEvent.AnimationState.DONE)); break; // $CASES-OMITTED$ default: diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedCorona.java b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedCorona.java index 1952f286fe17cdbb1eaae4389076e9720fd9eb6f..61603162aeb9b398bfbe51c783ef1b8cbb0001cf 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedCorona.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedCorona.java @@ -31,9 +31,8 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -47,7 +46,7 @@ import java.util.Map; public abstract class AnimatedCorona extends Corona implements Animated { - protected List> animationProperties; + protected Map> animationProperties; protected Map animationSetters; private static final Logger LOGGER = LoggerFactory.getLogger(AnimatedCorona.class.getSimpleName()); @@ -81,7 +80,7 @@ public abstract class AnimatedCorona extends Corona implements Animated { public AnimatedCorona(AnimatedCorona original) { super(original); - animationProperties = new ArrayList<>(original.animationProperties); + animationProperties = new HashMap<>(original.animationProperties); this.validateAnimationProperties(); } @@ -95,7 +94,7 @@ public abstract class AnimatedCorona extends Corona implements Animated { private void validateAnimationProperties() { animationSetters = getAnimationSetters(this.getClass()); - for (AnimationProperty property : animationProperties) { + for (AnimationProperty property : animationProperties.values()) { if (!animationSetters.containsKey(property.getProperty())) { LOGGER.error("Corona {} does not support animated Property {}!", this.getClass().getName(), property.getProperty()); //$NON-NLS-1$ } @@ -153,8 +152,19 @@ public abstract class AnimatedCorona extends Corona implements Animated { /** {@inheritDoc} */ @Override - public List> getAnimationProperties() { - return animationProperties; + public Collection> getAnimationProperties() { + return animationProperties.values(); + } + + // --------------------------------------------------------------------------- + /** + * @param identifier + * @return + */ + // --------------------------------------------------------------------------- + + public AnimationProperty getAnimatedProperty(String identifier) { + return animationProperties.get(identifier); } // --------------------------------------------------------------------------- @@ -168,9 +178,9 @@ public abstract class AnimatedCorona extends Corona implements Animated { /** {@inheritDoc} */ @Override public void start() { - for (AnimationProperty property : animationProperties) { + for (AnimationProperty property : animationProperties.values()) { property.setAnimatedObject(this); - property.start(); + property.start(false); } } @@ -179,7 +189,7 @@ public abstract class AnimatedCorona extends Corona implements Animated { /** {@inheritDoc} */ @Override public void stop() { - for (AnimationProperty property : animationProperties) { + for (AnimationProperty property : animationProperties.values()) { property.stop(); } this.resetAnimatedProperties(); diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedShapeCorona.java b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedShapeCorona.java index 9afe52e26363e940e06842771298afba17446476..c2069ed8ddb3a4057ec31c66007b0298fc798f62 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedShapeCorona.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/AnimatedShapeCorona.java @@ -32,7 +32,7 @@ import java.awt.Graphics2D; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; -import java.util.List; +import java.util.Collection; /** * @author Nico Mack [nico.mack@list.lu] @@ -114,7 +114,7 @@ public class AnimatedShapeCorona extends AnimatedCorona { @Override public void resetAnimatedProperties() { - List> properties = this.getAnimationProperties(); + Collection> properties = this.getAnimationProperties(); for (AnimationProperty property : properties) { if (PROPERTY_OPACITY.equals(property.getProperty())) { diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/HtmlBox.java b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/HtmlBox.java index ee30b59f3b45bf00a93f7f925174e7310c52b707..d04f7e0c9aa4732a0cc2797d0bc5141bb186e900 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/HtmlBox.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/HtmlBox.java @@ -66,10 +66,10 @@ public class HtmlBox extends SelectableCorona implements InformationReceiver styleRules; + protected BufferedImage rendered; private Stroke borderStroke; private Rectangle2D shapeBounds; - private BufferedImage rendered; private Point renderedCentre; private JEditorPane editor; diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/builder/BaseAnimatedCoronaBuilder.java b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/builder/BaseAnimatedCoronaBuilder.java index b1466c04e9891f1c9882bbbef62cc0427259b3d4..0774f3a598b96d60991d46bda4deec82bcd05a44 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/corona/builder/BaseAnimatedCoronaBuilder.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/corona/builder/BaseAnimatedCoronaBuilder.java @@ -50,8 +50,8 @@ import lu.list.itis.dkd.tui.widget.corona.AnimatedCorona; import org.jdom2.Element; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; /** * @author mack @@ -61,7 +61,7 @@ import java.util.List; */ public abstract class BaseAnimatedCoronaBuilder> extends CoronaBuilder { /** Time the fading corona takes to appear, i.e. going from transparent to opaque */ - public List> animationProperties = new ArrayList<>(); + public Map> animationProperties = new HashMap<>(); // *************************************************************************** // * Constants * @@ -105,7 +105,7 @@ public abstract class BaseAnimatedCoronaBuilder property = buildProperty(propertyNode, context, callback); - animationProperties.add(property); + animationProperties.put(property.getProperty(), property); } } } @@ -143,7 +143,7 @@ public abstract class BaseAnimatedCoronaBuilder property) { - this.animationProperties.add(property); + this.animationProperties.put(property.getProperty(), property); return (B) this; } diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/touch/FiniteStateMachine.java b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/FiniteStateMachine.java new file mode 100644 index 0000000000000000000000000000000000000000..8660119c53d88099eb97eebe91bc899785899917 --- /dev/null +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/FiniteStateMachine.java @@ -0,0 +1,44 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.widget.touch; + +/** + * @author Nico Mack [nico.mack@list.lu] + * @since 2.5 + * @version 2.5.0 + */ +// *************************************************************************** +// * Class Definition and Members * +// *************************************************************************** + +@SuppressWarnings({"javadoc", "squid:S1118"}) +public abstract class FiniteStateMachine { + + public static final int RELEASED_STATE = 0; + public static final int TAPPED_STATE = 1; + public static final int TOUCHED_STATE = 2; + public static final int DRAGGED_STATE = 3; + public static final int LATCHED_STATE = 4; + + public static final int TOUCHED_EVENT = 0; + public static final int DRAGGED_EVENT = 1; + public static final int RELEASED_EVENT = 2; + public static final int EXPIRED_EVENT = 3; + + public static final int IDLE_ACTION = 0; + public static final int TAP_ACTION = 1; +} diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/touch/Latching.java b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/Latching.java new file mode 100644 index 0000000000000000000000000000000000000000..f360e0c74f1d67bdcdd256732be5ed78e25f62f9 --- /dev/null +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/Latching.java @@ -0,0 +1,48 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.widget.touch; + +/** + * @author Nico Mack [nico.mack@list.lu] + * @since 2.5 + * @version 2.5.0 + */ +public final class Latching extends FiniteStateMachine { + + @SuppressWarnings("javadoc") + public static final int[][] TRANSITIONS = + { + /* TOUCHED DRAGGED RELEASED EXPIRED */ + /* RELEASED_STATE */ {TAPPED_STATE, RELEASED_STATE, RELEASED_STATE, RELEASED_STATE}, + /* TAPPED_STATE */ {TAPPED_STATE, DRAGGED_STATE, LATCHED_STATE, TOUCHED_STATE}, + /* TOUCHED_STATE */ {TOUCHED_STATE, DRAGGED_STATE, LATCHED_STATE, TOUCHED_STATE}, + /* DRAGGED_STATE */ {DRAGGED_STATE, DRAGGED_STATE, RELEASED_STATE, DRAGGED_STATE}, + /* LATCHED_STATE */ {LATCHED_STATE, DRAGGED_STATE, RELEASED_STATE, RELEASED_STATE} + }; + + @SuppressWarnings("javadoc") + public static final int[][] ACTIONS = + { + /* TOUCHED DRAGGED RELEASED EXPIRED */ + /* RELEASED_STATE */ {IDLE_ACTION, IDLE_ACTION, IDLE_ACTION, IDLE_ACTION}, + /* TAPPED_STATE */ {IDLE_ACTION, IDLE_ACTION, TAP_ACTION, IDLE_ACTION}, + /* TOUCHED_STATE */ {IDLE_ACTION, IDLE_ACTION, IDLE_ACTION, IDLE_ACTION}, + /* DRAGGED_STATE */ {IDLE_ACTION, IDLE_ACTION, IDLE_ACTION, IDLE_ACTION}, + /* LATCHED_STATE */ {IDLE_ACTION, IDLE_ACTION, IDLE_ACTION, IDLE_ACTION} + }; + +} diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/touch/NonLatching.java b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/NonLatching.java new file mode 100644 index 0000000000000000000000000000000000000000..bba3a57f8d5370c708d50681523cebeab59f100e --- /dev/null +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/NonLatching.java @@ -0,0 +1,46 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.widget.touch; + +/** + * @author Nico Mack [nico.mack@list.lu] + * @since 2.5 + * @version 2.5.0 + */ +public final class NonLatching extends FiniteStateMachine { + + @SuppressWarnings({"javadoc", "squid:S2386"}) + public static final int[][] TRANSITIONS = + { + /* TOUCHED DRAGGED RELEASED EXPIRED */ + /* RELEASED_STATE */ {TAPPED_STATE, RELEASED_STATE, RELEASED_STATE, RELEASED_STATE}, + /* TAPPED_STATE */ {TAPPED_STATE, DRAGGED_STATE, RELEASED_STATE, TOUCHED_STATE}, + /* TOUCHED_STATE */ {TOUCHED_STATE, DRAGGED_STATE, RELEASED_STATE, TOUCHED_STATE}, + /* DRAGGED_STATE */ {DRAGGED_STATE, DRAGGED_STATE, RELEASED_STATE, DRAGGED_STATE} + }; + + @SuppressWarnings({"javadoc", "squid:S2386"}) + public static final int[][] ACTIONS = + { + /* TOUCHED DRAGGED RELEASED EXPIRED */ + /* RELEASED_STATE */ {IDLE_ACTION, IDLE_ACTION, IDLE_ACTION, IDLE_ACTION}, + /* TAPPED_STATE */ {IDLE_ACTION, IDLE_ACTION, TAP_ACTION, IDLE_ACTION}, + /* TOUCHED_STATE */ {IDLE_ACTION, IDLE_ACTION, IDLE_ACTION, IDLE_ACTION}, + /* DRAGGED_STATE */ {IDLE_ACTION, IDLE_ACTION, IDLE_ACTION, IDLE_ACTION} + }; + +} diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/touch/TapEvent.java b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/TapEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..e23a0749a28ec2c9beb1d82b254868189d1a19b9 --- /dev/null +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/TapEvent.java @@ -0,0 +1,122 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.widget.touch; + +import lu.list.itis.dkd.tui.utility.Point; + +/** + * @author Nico Mack [nico.mack@list.lu] + * @since 2.5 + * @version 2.5.0 + */ +// *************************************************************************** +// * Class Definition and Members * +// *************************************************************************** + +public class TapEvent { + private int touchId; + private Point position; + private long timestamp; + + // --------------------------------------------------------------------------- + // *************************************************************************** + // * Constructor(s) * + // *************************************************************************** + // --------------------------------------------------------------------------- + /** + * @param touchId + * @param position + */ + // --------------------------------------------------------------------------- + + public TapEvent(int touchId, Point position) { + this.touchId = touchId; + this.position = position; + this.timestamp = System.currentTimeMillis(); + } + + // --------------------------------------------------------------------------- + // *************************************************************************** + // * Class Body * + // *************************************************************************** + // --------------------------------------------------------------------------- + /** + * Simple getter method for touchId. + * + * @return The value of touchId. + */ + // --------------------------------------------------------------------------- + + public int getTouchId() { + return touchId; + } + + // --------------------------------------------------------------------------- + /** + * Simple setter method for touchId. + * + * @param touchId + * The value to set touchId to. + */ + // --------------------------------------------------------------------------- + + public void setTouchId(int touchId) { + this.touchId = touchId; + } + + // --------------------------------------------------------------------------- + /** + * Simple getter method for position. + * + * @return The value of position. + */ + // --------------------------------------------------------------------------- + + public Point getPosition() { + return position; + } + + // --------------------------------------------------------------------------- + /** + * Simple setter method for position. + * + * @param position + * The value to set position to. + */ + // --------------------------------------------------------------------------- + + public void setPosition(Point position) { + this.position = position; + } + + // --------------------------------------------------------------------------- + /** + * @return + */ + // --------------------------------------------------------------------------- + + public long getTimestamp() { + return this.timestamp; + } + + // --------------------------------------------------------------------------- + // *************************************************************************** + // * End of class * + // *************************************************************************** + // --------------------------------------------------------------------------- + +} diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/touch/Touch.java b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/Touch.java new file mode 100644 index 0000000000000000000000000000000000000000..7776058ff45bb3b61612645bec636625d1d78afb --- /dev/null +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/Touch.java @@ -0,0 +1,164 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.widget.touch; + +import lu.list.itis.dkd.tui.utility.Point; + +import javax.swing.Timer; + +/** + * @author Nico Mack [nico.mack@list.lu] + * @since 2.5 + * @version 2.5.0 + */ +// *************************************************************************** +// * Class Definition and Members * +// *************************************************************************** + +public class Touch { + private Integer id; + private Integer state; + private Timer tapTimer; + private Point position; + + // --------------------------------------------------------------------------- + // *************************************************************************** + // * Constructor(s) * + // *************************************************************************** + // --------------------------------------------------------------------------- + /** + * @param id + * @param initialState + */ + // --------------------------------------------------------------------------- + + public Touch(Integer id, int initialState) { + this.id = id; + this.state = initialState; + } + + // --------------------------------------------------------------------------- + // *************************************************************************** + // * Class Body * + // *************************************************************************** + // --------------------------------------------------------------------------- + /** + * Simple getter method for id. + * + * @return The value of id. + */ + // --------------------------------------------------------------------------- + + public Integer getId() { + return id; + } + + // --------------------------------------------------------------------------- + /** + * Simple setter method for id. + * + * @param id + * The value to set id to. + */ + // --------------------------------------------------------------------------- + + public void setId(Integer id) { + this.id = id; + } + + // --------------------------------------------------------------------------- + /** + * Simple getter method for state. + * + * @return The value of state. + */ + // --------------------------------------------------------------------------- + + public Integer getState() { + return state; + } + + // --------------------------------------------------------------------------- + /** + * Simple setter method for state. + * + * @param state + * The value to set state to. + */ + // --------------------------------------------------------------------------- + + public void setState(Integer state) { + this.state = state; + } + + // --------------------------------------------------------------------------- + /** + * Simple getter method for tapTimer. + * + * @return The value of tapTimer. + */ + // --------------------------------------------------------------------------- + + public Timer getTapTimer() { + return tapTimer; + } + + // --------------------------------------------------------------------------- + /** + * Simple setter method for tapTimer. + * + * @param tapTimer + * The value to set tapTimer to. + */ + // --------------------------------------------------------------------------- + + public void setTapTimer(Timer tapTimer) { + this.tapTimer = tapTimer; + } + + // --------------------------------------------------------------------------- + /** + * Simple getter method for position. + * + * @return The value of position. + */ + // --------------------------------------------------------------------------- + + public Point getPosition() { + return position; + } + + // --------------------------------------------------------------------------- + /** + * Simple setter method for position. + * + * @param position + * The value to set position to. + */ + // --------------------------------------------------------------------------- + + public void setPosition(Point position) { + this.position = position; + } + + // --------------------------------------------------------------------------- + // *************************************************************************** + // * End of Class * + // *************************************************************************** + // --------------------------------------------------------------------------- + +} diff --git a/TULIP/src/lu/list/itis/dkd/tui/widget/touch/TouchManager.java b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/TouchManager.java index af04756d4da3ee27d5228e7197fe7452656b8356..baca4631d323d700359c1b3eb46a15062bc007cc 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/widget/touch/TouchManager.java +++ b/TULIP/src/lu/list/itis/dkd/tui/widget/touch/TouchManager.java @@ -18,6 +18,7 @@ package lu.list.itis.dkd.tui.widget.touch; import lu.list.itis.dkd.tui.utility.Point; import lu.list.itis.dkd.tui.utility.ScreenCoordinates; +import lu.list.itis.dkd.tui.utility.StringUtils; import lu.list.itis.dkd.tui.widget.corona.Corona; import lu.list.itis.dkd.tui.widget.corona.ShapeFactory; @@ -27,9 +28,15 @@ import org.slf4j.LoggerFactory; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import javax.swing.Timer; + /** * @author Nico Mack [nico.mack@list.lu] * @since 2.5 @@ -39,44 +46,27 @@ import java.util.concurrent.ConcurrentHashMap; // * Class Definition and Members * // *************************************************************************** -public class TouchManager { +public class TouchManager implements ActionListener { - private ConcurrentHashMap touchStates = new ConcurrentHashMap<>(); - private ConcurrentHashMap touchPositions = new ConcurrentHashMap<>(); + private ConcurrentHashMap touches = new ConcurrentHashMap<>(); + private List listeners; private boolean isLatching; - private Point lastTouch; private Shape cursorShape; + private Point lastTouch; + // *************************************************************************** // * Constants * // *************************************************************************** - private static final int RELEASED_STATE = 0; - private static final int TOUCHED_STATE = 1; - private static final int DRAGGED_STATE = 2; - private static final int LATCHED_STATE = 3; - - private static final int TOUCHED = 0; - private static final int DRAGGED = 1; - private static final int RELEASED = 2; - - private static final int[][] NON_LATCHING_TRANSITIONS = - { - /* TOUCHED DRAGGED RELEASED */ - /* RELEASED_STATE */ {TOUCHED_STATE, RELEASED_STATE, RELEASED_STATE}, - /* TOUCHED_STATE */ {TOUCHED_STATE, DRAGGED_STATE, RELEASED_STATE}, - /* DRAGGED_STATE */ {DRAGGED_STATE, DRAGGED_STATE, RELEASED_STATE}, - }; - - private static final int[][] LATCHING_TRANSITIONS = - { - /* TOUCHED DRAGGED RELEASED */ - /* RELEASED_STATE */ {TOUCHED_STATE, RELEASED_STATE, RELEASED_STATE}, - /* TOUCHED_STATE */ {TOUCHED_STATE, DRAGGED_STATE, LATCHED_STATE}, - /* DRAGGED_STATE */ {DRAGGED_STATE, DRAGGED_STATE, RELEASED_STATE}, - /* LATCHED_STATE */ {LATCHED_STATE, DRAGGED_STATE, RELEASED_STATE} - }; + private static final int TAP_INTERVAL = 250; + + @SuppressWarnings("nls") + private static final String[] STATE_NAMES = {"RLSD", "TAPD", "TCHD", "DRAG", "LTCH"}; + + @SuppressWarnings("nls") + private static final String[] EVENT_NAMES = {"tch", "drg", "rel", "exp"}; private static final Logger LOGGER = LoggerFactory.getLogger(TouchManager.class.getSimpleName()); @@ -85,46 +75,106 @@ public class TouchManager { // * Constructor(s) * // *************************************************************************** // --------------------------------------------------------------------------- - /** * */ + // --------------------------------------------------------------------------- + public TouchManager() { isLatching = false; + listeners = new ArrayList<>(); cursorShape = ShapeFactory.buildCircle(2); } // --------------------------------------------------------------------------- - /** * @param latching */ + // --------------------------------------------------------------------------- + public TouchManager(boolean latching) { isLatching = latching; + listeners = new ArrayList<>(); cursorShape = ShapeFactory.buildCircle(2); } + // --------------------------------------------------------------------------- // *************************************************************************** // * Primitives * // *************************************************************************** // --------------------------------------------------------------------------- + private void performAction(int oldState, int event, Touch touch) { + int action; + + action = (isLatching) ? Latching.ACTIONS[oldState][event] : NonLatching.ACTIONS[oldState][event]; + + switch (action) { + case FiniteStateMachine.IDLE_ACTION: + break; + + case FiniteStateMachine.TAP_ACTION: + this.notifyTapListeners(touch); + break; + + default: + LOGGER.warn("Unhandled action detected!"); //$NON-NLS-1$ + } + } + + // --------------------------------------------------------------------------- + private int changeState(int oldState, int event) { int newState; - newState = (isLatching) ? LATCHING_TRANSITIONS[oldState][event] - : NON_LATCHING_TRANSITIONS[oldState][event]; + newState = (isLatching) ? Latching.TRANSITIONS[oldState][event] : NonLatching.TRANSITIONS[oldState][event]; if (LOGGER.isDebugEnabled()) { - LOGGER.debug("State Change [{}] =[{}]=> [{}]", oldState, event, newState); //$NON-NLS-1$ + LOGGER.debug("State Change [{}] =[{}]=> [{}]", STATE_NAMES[oldState], EVENT_NAMES[event], STATE_NAMES[newState]); //$NON-NLS-1$ } + return newState; } + // --------------------------------------------------------------------------- + + private void notifyTapListeners(Touch touch) { + if (this.listeners.isEmpty()) { + return; + } + + TapEvent event = new TapEvent(touch.getId(), touch.getPosition()); + for (TapListener listener : this.listeners) { + listener.tapped(event); + } + } + // --------------------------------------------------------------------------- // *************************************************************************** // * Class Body * // *************************************************************************** + // --------------------------------------------------------------------------- + /** + * @param listener + */ + // --------------------------------------------------------------------------- + + public void addTapListener(TapListener listener) { + if (!this.listeners.contains(listener)) { + this.listeners.add(listener); + } + } + + // --------------------------------------------------------------------------- + /** + * @param listener + */ + // --------------------------------------------------------------------------- + + public void removeTapListener(TapListener listener) { + this.listeners.remove(listener); + } + // --------------------------------------------------------------------------- /** * @param touchId @@ -133,15 +183,15 @@ public class TouchManager { // --------------------------------------------------------------------------- public boolean isDragged(int touchId) { - if (this.touchStates.containsKey(touchId)) { - return this.touchStates.get(touchId).equals(DRAGGED_STATE); + if (this.touches.containsKey(touchId)) { + Touch touch = this.touches.get(touchId); + return touch.getState().equals(FiniteStateMachine.DRAGGED_STATE); } return false; } // --------------------------------------------------------------------------- /** - * @param touchId * @return */ // --------------------------------------------------------------------------- @@ -149,21 +199,22 @@ public class TouchManager { public boolean isMultitouch() { int count = 0; - for (Integer state : this.touchStates.values()) { - count += (state != RELEASED_STATE) ? 1 : 0; + for (Touch touch : this.touches.values()) { + count += (touch.getState().equals(FiniteStateMachine.RELEASED_STATE)) ? 0 : 1; } - return (count > 1); } // --------------------------------------------------------------------------- - /** * @param touchId * @return */ + // --------------------------------------------------------------------------- + public Point getTouchPosition(int touchId) { - return this.touchPositions.get(touchId); + Touch touch = this.touches.get(touchId); + return touch.getPosition(); } // --------------------------------------------------------------------------- @@ -181,9 +232,10 @@ public class TouchManager { public boolean eventInsideCorona(Corona corona, TouchEvent event) { - lastTouch = event.getPosition(); Point touchPosition = corona.getPointRelativeToCentre(event.getPosition()); - return (corona.getShape().contains(touchPosition)); + Shape shape = corona.getShape(); + + return ((shape != null) ? shape.contains(touchPosition) : false); } // --------------------------------------------------------------------------- @@ -201,27 +253,40 @@ public class TouchManager { // --------------------------------------------------------------------------- public boolean touch(TouchEvent event) { - int oldState = RELEASED_STATE; + int oldState; int newState; int touchId = event.getObjectId(); - + Touch touch; boolean touched = false; - if (!this.touchStates.containsKey(touchId)) { - newState = this.changeState(oldState, TOUCHED); - } else { - oldState = this.touchStates.get(touchId); - newState = this.changeState(oldState, TOUCHED); + lastTouch = event.getPosition(); + + touch = this.touches.get(touchId); + if (touch == null) { + touch = new Touch(touchId, FiniteStateMachine.RELEASED_STATE); } - touched = ((newState == TOUCHED_STATE) || (newState == LATCHED_STATE)) && (newState != oldState); - this.touchStates.put(touchId, newState); + oldState = touch.getState(); + this.performAction(touch.getState(), FiniteStateMachine.TOUCHED_EVENT, touch); + newState = this.changeState(oldState, FiniteStateMachine.TOUCHED_EVENT); + touch.setState(newState); + + touched = (newState != oldState) && ((newState == FiniteStateMachine.TAPPED_STATE) + || (newState == FiniteStateMachine.TOUCHED_STATE) + || (newState == FiniteStateMachine.LATCHED_STATE)); + if (touched) { - this.touchPositions.put(touchId, event.getPosition()); + touch.setPosition(event.getPosition()); + Timer tapTimer = new Timer(TAP_INTERVAL, this); + tapTimer.setActionCommand(Integer.toString(touchId)); + tapTimer.setRepeats(false); + tapTimer.start(); + touch.setTapTimer(tapTimer); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Cursor {} touched @ Position [{}]", touchId, event.getPosition()); //$NON-NLS-1$ } } + this.touches.put(touchId, touch); return touched; } @@ -240,21 +305,18 @@ public class TouchManager { // --------------------------------------------------------------------------- public boolean drag(TouchEvent event) { - int oldState; - int newState; int touchId = event.getObjectId(); - + Touch touch = this.touches.get(touchId); boolean dragged = false; - if (this.touchStates.containsKey(touchId)) { - oldState = this.touchStates.get(touchId); - newState = this.changeState(oldState, DRAGGED); - if (newState != oldState) - this.touchStates.put(touchId, newState); - dragged = (newState == DRAGGED_STATE); + lastTouch = event.getPosition(); + + if (touch != null) { + this.performAction(touch.getState(), FiniteStateMachine.DRAGGED_EVENT, touch); + touch.setState(this.changeState(touch.getState(), FiniteStateMachine.DRAGGED_EVENT)); + dragged = (touch.getState().equals(FiniteStateMachine.DRAGGED_STATE)); if (dragged) { - lastTouch = event.getPosition(); - this.touchPositions.put(touchId, event.getPosition()); + touch.setPosition(event.getPosition()); } } return dragged; @@ -274,35 +336,38 @@ public class TouchManager { // --------------------------------------------------------------------------- public boolean release(TouchEvent event) { - int newState; int touchId = event.getObjectId(); - + Touch touch = this.touches.get(touchId); boolean released = false; - if (this.touchStates.containsKey(touchId)) { - int oldState = this.touchStates.get(touchId); - newState = this.changeState(oldState, RELEASED); - released = (newState == RELEASED_STATE); + if (touch != null) { + this.performAction(touch.getState(), FiniteStateMachine.RELEASED_EVENT, touch); + touch.setState(this.changeState(touch.getState(), FiniteStateMachine.RELEASED_EVENT)); + released = (touch.getState().equals(FiniteStateMachine.RELEASED_STATE)); if (released) { - this.touchStates.remove(touchId); - this.touchPositions.remove(touchId); + if (touch.getTapTimer() != null) { + touch.getTapTimer().stop(); + } + this.touches.remove(touchId); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Cursor {} released @ Position [{}]", touchId, event.getPosition()); //$NON-NLS-1$ } } else { - this.touchStates.put(touchId, newState); - this.touchPositions.put(touchId, event.getPosition()); + touch.setPosition(event.getPosition()); } } return released; } // --------------------------------------------------------------------------- + /** + * + */ + // --------------------------------------------------------------------------- public void clear() { - lastTouch = null; - this.touchStates.clear(); + this.touches.clear(); } // --------------------------------------------------------------------------- @@ -312,12 +377,51 @@ public class TouchManager { // --------------------------------------------------------------------------- public void paint(Graphics2D canvas) { + + // for (Touch touch : this.touches.values()) { + // + // Point position = touch.getPosition(); + // if (position == null) + // continue; + // + // position = position.clone().toCoordinates(ScreenCoordinates.class); + // + // AffineTransform translate = AffineTransform.getTranslateInstance(position.x, position.y); + // canvas.setPaint(Color.WHITE); + // canvas.draw(translate.createTransformedShape(cursorShape)); + // } + if (lastTouch != null) { - lastTouch = lastTouch.toCoordinates(ScreenCoordinates.class); - AffineTransform translate = AffineTransform.getTranslateInstance(lastTouch.x, lastTouch.y); + Point position = lastTouch.clone().toCoordinates(ScreenCoordinates.class); + + AffineTransform translate = AffineTransform.getTranslateInstance(position.x, position.y); canvas.setPaint(Color.WHITE); canvas.draw(translate.createTransformedShape(cursorShape)); } + + + } + + // --------------------------------------------------------------------------- + /** {@inheritDoc} */ + // --------------------------------------------------------------------------- + + @Override + public void actionPerformed(ActionEvent timerExpiry) { + Integer touchId = StringUtils.getIntegerValue(timerExpiry.getActionCommand()); + if (touchId != null) { + Touch touch = this.touches.get(touchId); + if (touch != null) { + touch.getTapTimer().stop(); + this.performAction(touch.getState(), FiniteStateMachine.EXPIRED_EVENT, touch); + touch.setState(this.changeState(touch.getState(), FiniteStateMachine.EXPIRED_EVENT)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Timer {} expired!", touchId); //$NON-NLS-1$ + } + } + } else { + LOGGER.error("Unable to extract touchId from {}!", timerExpiry.getActionCommand()); //$NON-NLS-1$ + } } // ---------------------------------------------------------------------------