Commit a8af8a3b authored by Nico Mack's avatar Nico Mack

Implementation of connectable feature

parent a7a72941
......@@ -32,9 +32,16 @@ COLOURSC_NODE=colourscheme
CONDITION_NODE=condition
CONDITIONS_NODE=conditions
CONDITION_BUILDER_NAMESPACE=lu.list.itis.dkd.tui.event.conditional.builder
CONNECTABLE_NODE=connectable
CONNECTION_BUILDER_NAMESPACE=lu.list.itis.dkd.tui.feature.connection.builder
CONNECTIONS_NODE=connections
CONNECTION_NODE=connection
CONNECTORS_NODE=connectors
CONNECTOR_NODE=connector
CONTENT_NODE=content
CONTENT_BUILDER_NAMESPACE=lu.list.itis.dkd.tui.content.builder
CONTROL_POINTS_NODE=controlPoints
CONTROL_POINT_NODE=controlPoint
CORNER_RADIUS_NODE=cornerRadius
CORONA_BUILDER_NAMESPACE=lu.list.itis.dkd.tui.widget.corona.builder
CORONA_NODE=corona
......@@ -64,6 +71,7 @@ EFFECTIVITY_THRESHOLD_NODE=effectivityThreshold
EMPTY_STRING=
END_NODE=end
ENDING_NODE=ending
ENDING_OFFSET_NODE=endingOffset
ENERGY_THRESHOLD_NODE=energyThreshold
EXCLUSIVE_NODE=exclusive
EXPRESSION_NODE=expression
......@@ -102,6 +110,7 @@ INNER_RADIUS_NODE=innerRadius
INTERACTIONS_NODE=interactions
INTERACTION_NODE=interaction
INTERACTION_BUILDER_NAMESPACE=lu.list.itis.dkd.tui.event.interaction.builder
INTERPOLATION_STEPS_NODE=interpolationSteps
INSET_BORDER_NODE=insetBorder
JAVA_AWT_COLOR_NAMESPACE=java.awt.Color
LABEL_COLOUR_ELEMENT=labelColour
......
......@@ -24,6 +24,7 @@ import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.adapter.TangibleObject;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.feature.connection.Connection;
import lu.list.itis.dkd.tui.utility.IdMapper;
import lu.list.itis.dkd.tui.utility.StringUtils;
import lu.list.itis.dkd.tui.widget.BaseWidget;
......@@ -61,6 +62,7 @@ import java.util.concurrent.ConcurrentHashMap;
@NonNullByDefault
public abstract class TangibleObjectManager {
protected static volatile ConcurrentHashMap<String, Connection> connectionMap = new ConcurrentHashMap<>();
/**
* This dictionary stores all tangibles detected by reacTIVision relevant to the application.
*/
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2016.
*
* This file is part of TULIP.
*
* TULIP is licensed under a dual-licensing scheme. For non-commercial purposes, the LGPL version 3,
* as stated below, is applicable. For all commercial purposes TULIP is licensed under a LIST
* proprietary license. Please contact LIST at tto@list.lu to obtain a commercial license.
*
* For all non-commercial purposes, 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 <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.bootstrapping;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.feature.connection.Connection;
import lu.list.itis.dkd.tui.feature.connection.builder.BaseConnectionBuilder;
import lu.list.itis.dkd.tui.utility.Externalization;
import org.jdom2.Element;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author Eric Tobias [eric.tobias@list.lu]
* @since 2.2
* @version 2.3.0
*/
public class ConnectionBootstrapper {
/**
* Method used to determine the appropriate builder for a given object and then issue a build call.
*
* @param bootstrapContext
* @param callback
*
* @param tetherNode
* The node from a larger document that contains, as children, all the necessary information
* to resolve the correct builder and build the final tether.
* @return The final tether as defined by the children of the element node.
* @throws ClassNotFoundException
* Thrown when the class of the builder for the widget or those of the nested corona
* builder(s) could not be found.
* @throws SecurityException
* Thrown when the constructor cannot be retrieved due to some security constraints.
* @throws NoSuchMethodException
* Thrown when no constructor with the given parameter type is available.
* @throws InvocationTargetException
* Thrown if the invocation of any constructor through reflection throws an exception.
* @throws IllegalArgumentException
* Thrown when the provided argument is not eligible for the builder's constructor.
* @throws IllegalAccessException
* Thrown if this Constructor object is enforcing Java language access control and the
* underlying constructor is inaccessible.
* @throws InstantiationException
* Thrown if the class that declares the underlying constructor represents an abstract
* class.
* @throws BuildException
*/
@SuppressWarnings("unchecked")
public static Connection buildConnectionFromElement(Element connectionNode, BootstrapContext bootstrapContext, BootstrapCallback callback) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, BuildException {
Connection instance = null;
Element type = connectionNode.getChild(Externalization.TYPE_NODE);
Class<?> builder = Class.forName(Externalization.CONNECTION_BUILDER_NAMESPACE + Externalization.NAMESPACE_SEPARATOR + type.getValue() + Externalization.BUILDER_CLASS_POSTFIX);
if ((bootstrapContext == null) || (bootstrapContext.size() == 0)) {
Constructor<BaseConnectionBuilder<?>> constructor = (Constructor<BaseConnectionBuilder<?>>) builder.getConstructor(new Class[] {Element.class});
instance = constructor.newInstance(new Object[] {connectionNode}).build();
} else {
Constructor<BaseConnectionBuilder<?>> constructor = (Constructor<BaseConnectionBuilder<?>>) builder.getConstructor(new Class[] {Element.class, BootstrapContext.class, BootstrapCallback.class});
instance = constructor.newInstance(new Object[] {connectionNode, bootstrapContext, callback}).build();
}
return instance;
}
}
\ No newline at end of file
......@@ -24,6 +24,7 @@ import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.tui.TangibleApplication;
import lu.list.itis.dkd.tui.TangibleObjectManager;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.feature.connection.Connection;
import lu.list.itis.dkd.tui.utility.Externalization;
import lu.list.itis.dkd.tui.utility.StringUtils;
import lu.list.itis.dkd.tui.utility.Templating;
......@@ -159,6 +160,8 @@ public class TangibleObjectBootstrapper extends TangibleObjectManager {
for (Element connectionNode : connectionsNode.getChildren()) {
if (Externalization.TETHERS_NODE.equals(connectionNode.getName())) {
this.loadTethers(connectionNode);
} else if (Externalization.CONNECTORS_NODE.equals(connectionNode.getName())) {
this.loadConnectors(connectionNode);
} else {
throw new BuildException(StringUtils.build("Unrecognized connection type {} !", connectionsNode.getName())); //$NON-NLS-1$
}
......@@ -194,6 +197,23 @@ public class TangibleObjectBootstrapper extends TangibleObjectManager {
}
}
private void loadConnectors(Element connectorsRootNode) throws BuildException {
List<Element> connectorNodes = connectorsRootNode.getChildren(Externalization.CONNECTOR_NODE);
try {
for (Element connectorNode : connectorNodes) {
Connection connection = ConnectionBootstrapper.buildConnectionFromElement(connectorNode, null, null);
if (connectionMap.put(connection.getName(), connection) != null) {
LOGGER.warn("Multiple instances of connection with name {} exist!", connection.getName()); //$NON-NLS-1$
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) {
LOGGER.error("One of the desired connections could not be build!", e); //$NON-NLS-1$
connectionMap.clear();
throw new BuildException("One of the desired connections could not be build!", e); //$NON-NLS-1$
}
}
/**
* builds a cursor from the specified template node. A second parameter allows specifying the
* bootstrapping context, a map holding the names and the values of variables for interpolation of
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2019. 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 <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.feature.connection;
import lu.list.itis.dkd.tui.utility.Point;
import java.util.List;
/**
* @author nico.mack@list.lu
* @since 2.6
* @version 1.0.0
* @param <T>
*/
public interface Connectable<T> extends Comparable<T> {
/**
* @author mack
* @since [major].[minor]
* @version [major].[minor].[micro]
*/
public enum End {
ORIGIN, ENDING
}
/**
* @return
*/
public Point getConnector();
/**
* @return
*/
public List<Connection> getConnections();
/**
* @return
*/
public boolean isConnected();
/**
* @return
*/
public boolean isExclusive();
/**
* @return
*/
public int getNumberOfConnections();
/**
* @param candidate
* @return
*/
public boolean canConnect(Connectable<?> candidate);
/**
* @param connector
* @return
*/
public boolean connect(Connectable<?> candidate, Connection connection, End end);
/**
* @param connector
* @return
*/
public boolean disconnect(Connectable<?> connected, End end);
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2019. 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 <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.feature.connection;
import lu.list.itis.dkd.tui.content.Drawable;
import lu.list.itis.dkd.tui.feature.connection.builder.BaseConnectionBuilder;
/**
* @author nico.mack@list.lu
* @since 2.6
* @version 1.0.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public abstract class Connection implements Drawable {
protected String name;
protected Connectable<?> origin;
protected Connectable<?> ending;
/**
* Field indicating the draw priority of the Connection. <code>Integer#MAX_VALUE</code> is the
* highest priority, hence, will be drawn last. Default: <code>0</code>.
*/
protected int drawPriority = 0;
// ***************************************************************************
// * Constants *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param builder
*/
// ---------------------------------------------------------------------------
public Connection(BaseConnectionBuilder<?> builder) {
this.name = builder.name;
this.drawPriority = builder.drawPriority;
}
// ---------------------------------------------------------------------------
public Connection(Connection original) {
this.name = original.name;
this.drawPriority = original.drawPriority;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
public String getName() {
return this.name;
}
// ---------------------------------------------------------------------------
/**
* Setter for setting the origin of the tether
*
* @param position
* specifies the new origin of this tether
*/
// ---------------------------------------------------------------------------
public void setOrigin(Connectable<?> connectable) {
this.origin = connectable;
}
// ---------------------------------------------------------------------------
/**
* Setter for setting the ending of the tether
*
* @param position
* specifies the new ending of this tether
*/
// ---------------------------------------------------------------------------
public void setEnding(Connectable<?> connectable) {
this.ending = connectable;
}
// ---------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
public int getDrawPriority() {
return this.drawPriority;
}
// ---------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
public int compareTo(Drawable drawable) {
if (drawable == null)
throw new NullPointerException();
return Integer.compare(drawPriority, drawable.getDrawPriority());
}
// ---------------------------------------------------------------------------
@Override
public abstract Connection clone();
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of class *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2019. 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 <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.feature.connection;
import lu.list.itis.dkd.tui.feature.connection.Connectable.End;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.TreeMultimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
/**
* @author nico.mack@list.lu
* @since 2.6
* @version 1.0.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ConnectionManager {
private Connectable<?> connectable;
private boolean exclusive;
private List<Class<? extends Connectable<?>>> accepted;
private List<Class<? extends Connectable<?>>> rejected;
private Multimap<Connectable<?>, Connection> connections;
// ***************************************************************************
// * Constants
// ***************************************************************************
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class.getSimpleName());
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param connectable
* @param exclusive
*/
// ---------------------------------------------------------------------------
public ConnectionManager(Connectable<?> connectable, boolean exclusive) {
this.connectable = connectable;
this.exclusive = exclusive;
this.accepted = new ArrayList<>();
this.rejected = new ArrayList<>();
this.connections = Multimaps.synchronizedSortedSetMultimap(TreeMultimap.create());
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param acceptedClazz
*/
// ---------------------------------------------------------------------------
public void accept(Class<? extends Connectable<?>> acceptedClazz) {
if (!this.accepted.contains(acceptedClazz)) {
this.accepted.add(acceptedClazz);
}
}
// ---------------------------------------------------------------------------
/**
* @param rejectedClazz
*/
// ---------------------------------------------------------------------------
public void reject(Class<? extends Connectable<?>> rejectedClazz) {
if (!this.rejected.contains(rejectedClazz)) {
this.rejected.add(rejectedClazz);
}
}
// ---------------------------------------------------------------------------
/**
* @param candidate
* @return
*/
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
public boolean canConnect(Connectable<?> candidate) {
boolean passed = false;
Class<? extends Connectable<?>> candidateClazz = (Class<? extends Connectable<?>>) candidate.getClass();
passed = (this.accepted.isEmpty() || this.accepted.contains(candidateClazz));
passed &= (this.rejected.isEmpty() || !this.rejected.contains(candidateClazz));
return passed;
}
// ---------------------------------------------------------------------------
public List<Connection> getConnections() {
return new ArrayList<>(this.connections.values());
}
// ---------------------------------------------------------------------------
/**
* @return
*/
// ---------------------------------------------------------------------------
public boolean isConnected() {
return !this.connections.isEmpty();
}
// ---------------------------------------------------------------------------
/**
* @return
*/
// ---------------------------------------------------------------------------
public boolean isExclusive() {
return this.exclusive;
}
// ---------------------------------------------------------------------------
/**
* @return
*/
// ---------------------------------------------------------------------------
public int getNumberOfConnections() {
return this.connections.values().size();
}
// ---------------------------------------------------------------------------
/**
* @param candidate
* @param connection
* @param end
* @return
*/
// ---------------------------------------------------------------------------
public boolean connect(Connectable<?> candidate, Connection connection, End end) {
boolean connectionEstablished = false;
boolean isConnectable = !(this.exclusive && this.isConnected()) && canConnect(candidate);
if (isConnectable && !this.connections.containsEntry(candidate, connection)) {
switch (end) {
case ORIGIN:
connection.setOrigin(this.connectable);
candidate.connect(connectable, connection, Connectable.End.ENDING);
break;
case ENDING:
connection.setEnding(this.connectable);
break;
default:
LOGGER.error("Undefined Connection End {} specified!", end); //$NON-NLS-1$
}
connectionEstablished = this.connections.put(candidate, connection);
}
return connectionEstablished;
}
// ---------------------------------------------------------------------------
/**
* @param connected
* @param end
* @return
*/
// ---------------------------------------------------------------------------
public boolean disconnect(Connectable<?> connected, End end) {
boolean connectionTornDown = false;
if (this.connections.containsKey(connected)) {
this.connections.removeAll(connected);
if (end == Connectable.End.ORIGIN) {
connectionTornDown = connected.disconnect(this.connectable, Connectable.End.ENDING);
} else {
connectionTornDown = true;
}
}
return connectionTornDown;
}
// ---------------------------------------------------------------------------
/**
* @param canvas
*/
// ---------------------------------------------------------------------------
public void updateConnections(Graphics2D canvas) {
for (Connection connection : this.connections.values()) {
connection.paint(canvas);
}
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class
// ***************************************************************************
// ---------------------------------------------------------------------------
}
package lu.list.itis.dkd.tui.feature.connection;
import lu.list.itis.dkd.tui.feature.connection.builder.BaseSplineBuilder;
import lu.list.itis.dkd.tui.utility.AngleUtils;
import lu.list.itis.dkd.tui.utility.Calibration;
import lu.list.itis.dkd.tui.utility.Linear;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.PolarCoordinateHelper;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.utility.ShapeUtils;
import lu.list.itis.dkd.tui.widget.corona.ShapeFactory;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
/**
* @author mack
* @since [major].[minor]
* @version [major].[minor].[micro]
*/
public class Spline extends Connection implements Cloneable {
protected double originOffsetAngle;
protected double endingOffsetAngle;
protected int interpolationSteps;
protected Polygon spline;
protected float strokeWidth;
protected Color strokeColour;
private Linear first;
private Linear second;
private Point intersection;
private Shape marker = ShapeFactory.buildCircle(2);
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final double SCREEN_DIAGONAL = Math.sqrt(Math.pow(Calibration.getScreenWidth(), 2) + Math.pow(Calibration.getScreenHeight(), 2));
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *