Commit 6a667a65 authored by Nico Mack's avatar Nico Mack

Implementation of Scripting capabilities

parent d8b063ce
......@@ -146,6 +146,8 @@ ROTATE_WITH_TETHER_NODE=rotateWithTether
ROTATION_DIRECTION_NODE=rotationDirection
SCALE_NODE=scale
SATURATION_NODE=saturation
SCRIPT_NODE=script
SCRIPTABLE_NODE=scriptable
SCRIPT_BUILDER_NAMESPACE=lu.list.itis.dkd.tui.scripting.builder
SCREEN_ID_NODE=screenId
SECTION_NODE=section
......
......@@ -25,6 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.regex.Pattern;
/**
* @author mack
......@@ -35,7 +36,7 @@ import java.lang.reflect.Field;
// * Class Definition and Members *
// ***************************************************************************
public class Script {
public class Script implements Cloneable {
protected Scriptable instance;
protected Field field;
......@@ -46,6 +47,8 @@ public class Script {
// * Constants *
// ***************************************************************************
private static final Pattern PROPERTY_PATTERN = Pattern.compile("([a-z0-9_]+)(\\.([a-z0-9_]+))?", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static final Logger LOGGER = LoggerFactory.getLogger(Script.class.getName());
// ---------------------------------------------------------------------------
......@@ -63,6 +66,19 @@ public class Script {
this.expression = builder.expression;
}
// ---------------------------------------------------------------------------
/**
* @param original
*/
// ---------------------------------------------------------------------------
public Script(Script original) {
this.instance = original.instance;
this.field = original.field;
this.property = original.property;
this.expression = original.expression;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives(s) *
......@@ -81,16 +97,19 @@ public class Script {
public void setScriptableInstance(Scriptable newInstance) {
Preconditions.checkArgument(newInstance != null, "Specified instance CAN'T be null!"); //$NON-NLS-1$
assert newInstance != null;
boolean lookupRequired = (this.instance == null) || !this.instance.getClass().equals(newInstance.getClass());
this.instance = newInstance;
this.field = this.instance.findProperty(this.property);
if (this.field == null) {
LOGGER.error("Failed to lookup property {} for scriptable instance {}!", this.property, this.instance); //$NON-NLS-1$
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Successful lookup of property {} for scriptable instance {}!", this.property, this.instance); //$NON-NLS-1$
if (lookupRequired) {
this.field = this.instance.findProperty(this.property);
if (this.field == null) {
LOGGER.error("Failed to lookup property {} for scriptable instance {}!", this.property, this.instance); //$NON-NLS-1$
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Successful lookup of property {} for scriptable instance {}!", this.property, this.instance); //$NON-NLS-1$
}
}
}
......@@ -105,4 +124,13 @@ public class Script {
return this.instance.setProperty(this.field, newValue);
}
// ---------------------------------------------------------------------------
@Override
public Script clone() {
return new Script(this);
}
// ---------------------------------------------------------------------------
}
\ No newline at end of file
......@@ -181,6 +181,8 @@ public class Externalization extends NLS {
public static String ROTATION_DIRECTION_NODE;
public static String SCALE_NODE;
public static String SATURATION_NODE;
public static String SCRIPT_NODE;
public static String SCRIPTABLE_NODE;
public static String SCRIPT_BUILDER_NAMESPACE;
public static String SCREEN_ID_NODE;
public static String SECTION_NODE;
......
......@@ -22,6 +22,7 @@ package lu.list.itis.dkd.tui.widget.corona;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.scripting.Script;
import lu.list.itis.dkd.tui.scripting.Scriptable;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.PolarCoordinateHelper;
......@@ -42,6 +43,8 @@ import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* The top class in a hierarchy of backgrounds that a tangible widget's handle can show.
......@@ -88,8 +91,10 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
/**
* Field indicating if a corona is to be drawn. Default: <code>false</code>
*/
protected boolean isLatching;
protected boolean isLatching = false;
/**
* Field indicating if a corona is to be drawn. Default: <code>false</code>
*/
protected boolean active = false;
/**
* Field indicating whether a corana needs to be redrawn. Default : <code>false</code>
......@@ -103,6 +108,15 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
/** Field holding the shape representing the corona. */
@Nullable
protected Shape shape;
/**
* Field holding all scripts defined for this corona.
*/
@Nullable
protected List<Script> scripts;
private boolean propertyChanged = false;
/**
* Field keeping track of the current Shape of the corona for the purpose of determining which
* screen areas need to be redrawn.
......@@ -114,14 +128,6 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
// * Constants *
// ***************************************************************************
protected static final double PI_HALF = Math.PI / 2;
protected static final double THREE_PI_HALF = 1.5 * Math.PI;
protected static final double TWO_PI = 2 * Math.PI;
protected static final double THREE_SIXTY = 360d;
protected static final double ONE_EIGHTY = 180d;
private static final Logger LOGGER = LoggerFactory.getLogger(Corona.class.getSimpleName());
// ---------------------------------------------------------------------------
......@@ -158,6 +164,8 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
isLatching = builder.isLatching;
initialRotation = builder.initialRotation;
initialTranslation = builder.initialTranslation;
scripts = builder.scripts;
this.setupScripts(scripts);
clippingRegion = new Area();
}
......@@ -183,6 +191,8 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
isLatching = original.isLatching;
initialRotation = original.initialRotation;
initialTranslation = original.initialTranslation;
scripts = this.cloneScripts();
this.setupScripts(scripts);
}
// ---------------------------------------------------------------------------
......@@ -202,6 +212,27 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
// ***************************************************************************
// * Primitive(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
private List<Script> cloneScripts() {
List<Script> cloned = null;
if ((this.scripts != null) && !this.scripts.isEmpty()) {
cloned = new ArrayList<>();
for (Script orignal : scripts) {
cloned.add(orignal.clone());
}
}
return cloned;
}
// ---------------------------------------------------------------------------
private void setupScripts(List<Script> scripts) {
if ((scripts != null) && !scripts.isEmpty()) {
scripts.forEach(script -> script.setScriptableInstance(this));
}
}
// ---------------------------------------------------------------------------
/**
* computes the euclidian distance of the initial translation with respect to the corona's centre
......@@ -221,7 +252,6 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
return new Point(ScreenCoordinates.class);
}
// ---------------------------------------------------------------------------
/**
* Computes the absolute drawing point in screen coordinates, taking into account the coronas'
......@@ -300,6 +330,30 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
return rotation;
}
// ---------------------------------------------------------------------------
private synchronized void raisePropertyChanged() {
this.propertyChanged = true;
}
// ---------------------------------------------------------------------------
/**
* @return
*/
// ---------------------------------------------------------------------------
protected synchronized boolean readAndResetPropertyChanged() {
boolean wasSet = this.propertyChanged;
this.propertyChanged = false;
return wasSet;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* Abstract method specifying the signature of a paint method.
......@@ -699,11 +753,18 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
public Field findProperty(String property) {
Field field = null;
try {
field = this.getClass().getDeclaredField(property);
} catch (NoSuchFieldException | SecurityException e) {
LOGGER.error("No such property {}!", property, e); //$NON-NLS-1$
for (Class<?> clazz = this.getClass(); clazz != null && field == null; clazz = clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(property);
} catch (NoSuchFieldException | SecurityException e) {
// Can be ignored here.
}
}
if (field == null) {
LOGGER.error("No such property {} in class {}!", property, this.getClass().getSimpleName()); //$NON-NLS-1$
}
return field;
}
......@@ -715,6 +776,7 @@ public abstract class Corona implements Comparable<Corona>, Cloneable, Touchable
try {
property.set(this, value);
success = true;
raisePropertyChanged();
} catch (IllegalArgumentException | IllegalAccessException e) {
LOGGER.error("Failed to set property {} to value {}!", property.getName(), value, e); //$NON-NLS-1$
}
......
......@@ -22,6 +22,7 @@ package lu.list.itis.dkd.tui.widget.corona;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.tui.content.InformationReceiver;
import lu.list.itis.dkd.tui.utility.AngleUtils;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.TableCoordinates;
import lu.list.itis.dkd.tui.widget.corona.builder.BaseInfoBoxBuilder;
......@@ -138,13 +139,13 @@ public class InfoBox extends Corona implements InformationReceiver<List<String>>
// ---------------------------------------------------------------------------
private Quadrant determineQuadrant(float angle) {
if (angle <= PI_HALF)
if (angle <= AngleUtils.PI_HALF)
return Quadrant.TOP_RIGHT;
if (angle <= Math.PI)
return Quadrant.BOTTOM_RIGHT;
if (angle <= THREE_PI_HALF)
if (angle <= AngleUtils.THREE_PI_HALF)
return Quadrant.BOTTOM_LEFT;
if (angle < TWO_PI)
if (angle < AngleUtils.TWO_PI)
return Quadrant.TOP_LEFT;
return Quadrant.TOP_LEFT;
}
......
......@@ -151,7 +151,7 @@ public class Sector extends SelectableCorona {
// TULIP follows the clockwise TUIO convention, we need to convert both startAngle and
// extend.
angle = (THREE_SIXTY - angle);
angle = (AngleUtils.THREE_SIXTY - angle);
Area pie = new Area(new Arc2D.Double(-outerRadius, -outerRadius, size, size, angle, -arcSpan, Arc2D.PIE));
Area background = new Area(new Ellipse2D.Double(-outerRadius, -outerRadius, size, size));
......@@ -175,7 +175,7 @@ public class Sector extends SelectableCorona {
double angle = this.getAngle();
int quadrant = AngleUtils.getQuadrant(Math.toRadians(angle));
angle = THREE_SIXTY - angle;
angle = AngleUtils.THREE_SIXTY - angle;
FontRenderContext renderingContext = new FontRenderContext(null, true, true);
......@@ -255,6 +255,10 @@ public class Sector extends SelectableCorona {
if (!active || (this.opacity == TRANSPARENT))
return;
if (this.readAndResetPropertyChanged()) {
this.buildSectorFromProperties();
}
Graphics2D localCanvas = (Graphics2D) canvas.create();
localCanvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
......
......@@ -25,8 +25,10 @@ import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapCallback;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapContext;
import lu.list.itis.dkd.tui.bootstrapping.BootstrappingUtils;
import lu.list.itis.dkd.tui.bootstrapping.ScriptBootstrapper;
import lu.list.itis.dkd.tui.bootstrapping.ShapeBootstrapper;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.scripting.Script;
import lu.list.itis.dkd.tui.utility.Externalization;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.widget.corona.Corona;
......@@ -34,6 +36,8 @@ import lu.list.itis.dkd.tui.widget.corona.Corona;
import org.jdom2.Element;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.List;
/**
* Base builder class for constructing coronas.
......@@ -57,8 +61,8 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
*/
public boolean rotateWithHandle = true;
/**
* Field indicating whether the corona should be activated as soon as the handle is dropped on
* the table Default: <code>true</code>!
* Field indicating whether the corona should be activated as soon as the handle is dropped on the
* table Default: <code>true</code>!
*/
public boolean activateWithHandle = true;
/** Field indicating whether the corona is to initial rotation on its centre or origin. */
......@@ -71,15 +75,15 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
* specifies whether touch mode of corona is latching, i.e. touched state toggles on subsequent
* touch events, or corona touch state directly depends on touch event (Push button mode).
*/
public boolean isLatching;
public boolean isLatching = false;
/**
* Field indicating when a corona is to be drawn; what type of corona it is. Default:
* <code>false</code>
*/
public boolean active = false;
/**
* Field indicating the draw priority of the corona. <code>Integer#MAX_VALUE</code> is the
* highest priority, hence, will be drawn last. Default: <code>0</code>
* Field indicating the draw priority of the corona. <code>Integer#MAX_VALUE</code> is the highest
* priority, hence, will be drawn last. Default: <code>0</code>
*/
public int drawPriority = 0;
/** Field holding the shape representing the corona. */
......@@ -88,6 +92,8 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
/** Field holding the initial rotation value in radians. Default: 0; */
public double initialRotation = 0;
public List<Script> scripts = null;
/**
* Constructor setting the centre of the corona.
*
......@@ -99,16 +105,16 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
}
/**
* Constructor initializing all fields from an {@link Element} containing as child elements all
* the information on fields to initialize. Note that fields that are not mirrored in the
* bootstrapping configuration file will default.
* Constructor initializing all fields from an {@link Element} containing as child elements all the
* information on fields to initialize. Note that fields that are not mirrored in the bootstrapping
* configuration file will default.
*
* @param rootElement
* The element harbouring, on child nodes, the necessary information to initialize all
* fields of the builder.
* The element harbouring, on child nodes, the necessary information to initialize all fields
* of the builder.
* @throws BuildException
* Exception raised when the building of a corona instance cannot complete successfully
* due to the violation of one or more contracts associated with the instance, including
* Exception raised when the building of a corona instance cannot complete successfully due
* to the violation of one or more contracts associated with the instance, including
* missing, incomplete, or erroneous parameters or values thereof.
*/
public CoronaBuilder(Element rootElement) throws BuildException {
......@@ -116,18 +122,18 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
}
/**
* Constructor initializing all fields from an {@link Element} containing as child elements all
* the information on fields to initialize. Note that fields that are not mirrored in the
* bootstrapping configuration file will default.
* Constructor initializing all fields from an {@link Element} containing as child elements all the
* information on fields to initialize. Note that fields that are not mirrored in the bootstrapping
* configuration file will default.
*
* @param rootElement
* The element harbouring, on child nodes, the necessary information to initialize all
* fields of the builder.
* The element harbouring, on child nodes, the necessary information to initialize all fields
* of the builder.
* @param context
* @param callback
* @throws BuildException
* Exception raised when the building of a corona instance cannot complete successfully
* due to the violation of one or more contracts associated with the instance, including
* Exception raised when the building of a corona instance cannot complete successfully due
* to the violation of one or more contracts associated with the instance, including
* missing, incomplete, or erroneous parameters or values thereof.
*/
public CoronaBuilder(Element rootElement, BootstrapContext context, BootstrapCallback callback) throws BuildException {
......@@ -161,14 +167,25 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
isLatching = BootstrappingUtils.getContentAsBoolean(rootElement, Externalization.LATCHING_NODE, BootstrappingUtils.OPTIONAL, false, context);
shape = ShapeBootstrapper.getShape(rootElement.getChild(Externalization.SHAPE_NODE), context, callback);
Element scriptableNode = rootElement.getChild(Externalization.SCRIPTABLE_NODE);
if (scriptableNode != null) {
List<Element> scriptNodes = scriptableNode.getChildren(Externalization.SCRIPT_NODE);
if ((scriptNodes != null) && !scriptNodes.isEmpty()) {
scripts = new ArrayList<>();
for (Element scriptNode : scriptNodes) {
scripts.add(ScriptBootstrapper.buildScriptFromTemplate(scriptNode, context, callback));
}
}
}
}
/**
* Method used to set the initialTranslation of the corona from its centre.
*
* @param translation
* The initialTranslation as a {@link Point}. Values are relative and will be summed to
* the values of the centre.
* The initialTranslation as a {@link Point}. Values are relative and will be summed to the
* values of the centre.
* @return An instance of the builder for chain calling.
*/
@SuppressWarnings("unchecked")
......@@ -219,8 +236,8 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
}
/**
* Method for setting the draw priority. Lower priorities are drawn first and hence fade into
* the background as more may be drawn onto the canvas.
* Method for setting the draw priority. Lower priorities are drawn first and hence fade into the
* background as more may be drawn onto the canvas.
*
* @param priority
* The priority value.
......@@ -261,8 +278,8 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
/**
* Method for building the final {@link Corona} instance.
*
* @return An instance of the underlying {@link Corona} instance as defined by the concrete
* builder class.
* @return An instance of the underlying {@link Corona} instance as defined by the concrete builder
* class.
* @throws BuildException
* Thrown when the {@link Corona} could not be built due to an internal error.
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment