Commit ee78ff8e authored by Nico Mack's avatar Nico Mack

Moved Marker package from tulip-gis into core tulip projet

parent efd5a8d3
package lu.list.itis.dkd.tui.bootstrapping;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.marker.Marker;
import lu.list.itis.dkd.tui.marker.builder.MarkerBuilder;
import lu.list.itis.dkd.tui.utility.Externalization;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* @author nico.mack@list.lu
* @since 2.5
* @version 2.5.0
*/
public class MarkerBootstrapper {
private static final Logger LOGGER = LoggerFactory.getLogger(MarkerBootstrapper.class.getSimpleName());
/**
* Method used to determine the appropriate builder for a given object and then issue a build
* call.
*
* @param objectNode
* The node from a larger document that contains, as children, all the necessary
* information to resolve the correct builder and build the final widget.
* @return The final widget 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.
*/
@SuppressWarnings("unchecked")
private static Marker buildMarkerFromElement(Element markerNode, BootstrapContext context, BootstrapCallback callback) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
Marker instance = null;
Element type = markerNode.getChild(Externalization.TYPE_NODE);
Class<?> builder = Class.forName(Externalization.MARKER_BUILDER_NAMESPACE + Externalization.NAMESPACE_SEPARATOR + type.getValue() + Externalization.BUILDER_CLASS_POSTFIX);
if ((context == null) || (context.size() == 0)) {
Constructor<MarkerBuilder<?>> constructor = (Constructor<MarkerBuilder<?>>) builder.getConstructor(new Class[] {Element.class});
instance = constructor.newInstance(new Object[] {markerNode}).build();
} else {
Constructor<MarkerBuilder<?>> constructor = (Constructor<MarkerBuilder<?>>) builder.getConstructor(new Class[] {Element.class, BootstrapContext.class, BootstrapCallback.class});
instance = constructor.newInstance(new Object[] {markerNode, context, callback}).build();
}
return instance;
}
/**
* builds a widget 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 variable properties in template definition.
*
* @param templateNode
* specifies the template node to build the widget from
* @param bootstrapContext
* specifies the names of variables and their respective values for interpolation of
* properties in template definition
* @param callback
* @return an instance of the widget as specified in the provided template node.
* @throws BuildException
* if build of widget failed
*/
public static List<Marker> buildMarkerFromTemplate(Element templateNode, BootstrapContext bootstrapContext, BootstrapCallback callback) throws BuildException {
List<Marker> instances = new ArrayList<>();
List<Element> markerNodes = templateNode.getChildren(Externalization.MARKER_NODE);
try {
for (Element markerNode : markerNodes) {
instances.add(buildMarkerFromElement(markerNode, bootstrapContext, callback));
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) {
instances.clear();
LOGGER.error("A marker could not be build from specified template!", e); //$NON-NLS-1$
throw new BuildException("A marker could not be build from specified template!" + e.getMessage(), e); //$NON-NLS-1$
}
return instances;
}
}
package lu.list.itis.dkd.tui.marker;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.content.Content;
import lu.list.itis.dkd.tui.marker.builder.MarkerBuilder;
import lu.list.itis.dkd.tui.utility.CoronaComparator;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.widget.corona.Corona;
import com.google.common.collect.Multimap;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public abstract class Marker implements Comparable<Marker> {
/** The name given to this marker. */
protected String name;
/** The visual components to display as part of the widget. It's background. */
protected Multimap<Integer, Corona> coronas;
/** The positions where the widget's handles are at. */
protected HashMap<Integer, Point> positions;
protected Map map;
protected int drawPriority = 50;
protected boolean active;
private CoronaComparator coronaComparator;
// ***************************************************************************
// * Constants *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
public Marker(MarkerBuilder<?> builder) {
this.name = builder.name;
this.coronas = builder.coronas;
this.positions = builder.positions;
this.drawPriority = builder.drawPriority;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Method used to keep the conona's positions aligned with the base of the widget.
*
* @pre <code>positions.contains(handleID);</code>
* @post <code>forAll coronas.get(handleID) as corona : corona.getCentre().equals(positions.get(handleID);</code>
*/
// ---------------------------------------------------------------------------
protected void updateCoronas(int handleID) {
Point centre = positions.get(handleID);
if (centre != null) {
for (Corona corona : coronas.get(handleID)) {
corona.setHandleCentre(centre.clone());
corona.invalidate();
}
}
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Method for retrieving all coronas of a particular class.
*
* @param <T>
* The type of the class of corona to retrieve.
* @param _class
* The class of corona's to retrieve.
* @return A list of coronas matching the criterion.
*/
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
public <T> List<T> getCoronas(Class<T> _class) {
List<T> results = new ArrayList<>();
for (Corona corona : coronas.values()) {
if (_class.isAssignableFrom(corona.getClass())) {
results.add((T) corona);
}
}
return results;
}
// ---------------------------------------------------------------------------
/**
* Method for retrieving all coronas held by any of the handles associated to this marker.
*
* @return A {@link List} holding all coronas that are attached to any of the handles associated
* to this {@link Marker}'s concrete instance.
*/
// ---------------------------------------------------------------------------
public List<Corona> getCoronas() {
return new ArrayList<>(coronas.values());
}
// ---------------------------------------------------------------------------
public void setPosition(Integer handleId, Point position) {
if (positions.containsKey(handleId)) {
positions.put(handleId, position);
updateCoronas(handleId);
}
}
// ---------------------------------------------------------------------------
public HashMap<Integer, Point> getPositions() {
return positions;
}
// ---------------------------------------------------------------------------
public void setActive(boolean isActive) {
coronas.values().forEach(corona -> corona.setActive(isActive));
this.active = isActive;
}
// ---------------------------------------------------------------------------
/**
* Draws a the visual feedback for any marker. The method will iterate through all coronas and
* call their <code>paint()</code> method. The consistency of their position is ensured due to
* the <code>actionMove(TUIObject)</code> method issuing a call to <code>
* updateCoronas(long)</code>.
*
* @param canvas
* The Graphics2D instance to draw on.
*/
// ---------------------------------------------------------------------------
public void paint(Graphics2D canvas) {
List<Corona> sorted = new ArrayList<>(this.coronas.values());
Collections.sort(sorted, coronaComparator);
for (Corona corona : sorted) {
if (corona.isInvalidated()) {
corona.paint(canvas);
corona.validate();
}
}
}
// ---------------------------------------------------------------------------
public void updatePositions() {
for (Integer handleId : positions.keySet()) {
updateCoronas(handleId);
}
}
// ---------------------------------------------------------------------------
/**
* Comparison as defined by the {@link Comparable} interface. This comparison is based on the
* integer comparison of the draw priorities, ordering lower priorities first. This will result
* in lower priority {@link Content} instances being overlapped by prioritized ones.<br>
* <br>
*
* {@inheritDoc}
*/
// ---------------------------------------------------------------------------
@Override
public int compareTo(@Nullable Marker marker) {
if (marker == null)
throw new NullPointerException();
return Integer.valueOf(getDrawPriority()).compareTo(Integer.valueOf(marker.getDrawPriority()));
}
// ---------------------------------------------------------------------------
/**
* Getter method for the draw priority. The method will have to do the computation to account
* for the initialTranslation and the current position of the widget influencing the content if
* any.
*
* @return The draw priority of this content instance.
*/
// ---------------------------------------------------------------------------
protected int getDrawPriority() {
return drawPriority;
}
}
package lu.list.itis.dkd.tui.marker;
import lu.list.itis.dkd.tui.marker.builder.BaseTetherableMarkerBuilder;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.widget.tether.Tether;
import lu.list.itis.dkd.tui.widget.tether.TetherManager;
import lu.list.itis.dkd.tui.widget.tether.Tetherable;
import lu.list.itis.dkd.tui.widget.tether.listener.TetherListener;
//***************************************************************************
//* Class Definition and Members *
//***************************************************************************
public abstract class TetherableMarker extends Marker implements Tetherable {
protected Point tetherOrigin;
protected double tetheringDistance;
protected boolean draggable;
protected boolean rotatesWithTether;
protected TetherManager tetherManager;
// ***************************************************************************
// * Constants *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
public TetherableMarker(BaseTetherableMarkerBuilder<?> builder) {
super (builder);
this.tetherManager = new TetherManager(this, builder.exclusive);
this.tetherManager.setProviders(builder.providers);
this.tetherManager.setReceivers(builder.receivers);
this.tetherOrigin = builder.tetherOrigin;
this.tetheringDistance = builder.tetheringDistance;
this.draggable = builder.draggable;
this.rotatesWithTether = builder.rotatesWithTether;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Method used to keep the conona's positions aligned with the base of the widget.
*
* @pre <code>positions.contains(handleID);</code>
* @post <code>forAll coronas.get(handleID) as corona : corona.getCentre().equals(positions.get(handleID);</code>
*/
// ---------------------------------------------------------------------------
@Override
public void setPosition (Integer handleId, Point position) {
super.setPosition(handleId, position);
if (positions.containsKey(handleId)) {
tetherManager.move(position.clone());
}
}
// ---------------------------------------------------------------------------
@Override
public void updatePositions () {
super.updatePositions();
for (Point position: positions.values()) {
tetherManager.move(position.clone());
}
}
// ---------------------------------------------------------------------------
@Override
public void setActive (boolean isActive) {
super.setActive(isActive);
if (!isActive) {
this.tetherManager.lift();
}
}
// ---------------------------------------------------------------------------
@Override
public boolean isProviderFor(Tether tether) {
return this.tetherManager.isProviderFor(tether);
}
// ---------------------------------------------------------------------------
@Override
public boolean isReceiverFor(Tether tether) {
return this.tetherManager.isReceiverFor(tether);
}
// ---------------------------------------------------------------------------
@Override
public boolean isPotentialTether() {
return this.tetherManager.isPotentialTether();
}
// ---------------------------------------------------------------------------
@Override
public Point getTetherOrigin() {
return this.tetherOrigin;
}
// ---------------------------------------------------------------------------
@Override
public void setTetherOrigin(Point position) {
this.tetherOrigin = position;
}
// ---------------------------------------------------------------------------
@Override
public double getTetheringDistance() {
return this.tetheringDistance;
}
// ---------------------------------------------------------------------------
@Override
public void addTetherListener(TetherListener listener) {
this.tetherManager.addTetherListener(listener);
}
// ---------------------------------------------------------------------------
@Override
public void removeTetherListener(TetherListener listener) {
this.tetherManager.removeTetherListener(listener);
}
// ---------------------------------------------------------------------------
@Override
public void addPotentialTether(Tetherable potential, Tether tether, boolean propagate) {
this.tetherManager.addPotentialTether(potential, tether, propagate);
}
// ---------------------------------------------------------------------------
@Override
public void tetherWith(Tetherable otherTetherable) {
this.tetherManager.tetherWith(otherTetherable);
}
// ---------------------------------------------------------------------------
@Override
public void separateFrom(Tetherable tethered) {
this.tetherManager.separateFrom(tethered);
}
// ---------------------------------------------------------------------------
@Override
public boolean isTethered() {
return this.tetherManager.isTethered();
}
// ---------------------------------------------------------------------------
@Override
public boolean isDraggable() {
return this.draggable;
}
// ---------------------------------------------------------------------------
@Override
public boolean isActive() {
return this.active;
}
// ---------------------------------------------------------------------------
@Override
public boolean rotatesWithTether() {
return this.rotatesWithTether;
}
// ---------------------------------------------------------------------------
@Override
public boolean isTetheredWith(Tetherable otherTetherable) {
return this.tetherManager.isTetheredWith(otherTetherable);
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class
// ***************************************************************************
// ---------------------------------------------------------------------------
}
package lu.list.itis.dkd.tui.marker.builder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.jdom2.Element;
import com.google.common.base.Strings;
import com.google.common.collect.TreeMultimap;
import com.jgoodies.common.base.Preconditions;
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.CoordinateStateBootstrapper;
import lu.list.itis.dkd.tui.bootstrapping.CoronaBootstrapper;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.marker.TetherableMarker;
import lu.list.itis.dkd.tui.utility.CoordinateState;
import lu.list.itis.dkd.tui.utility.Externalization;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
//***************************************************************************
//* Class Definition and Members *
//***************************************************************************
public abstract class BaseTetherableMarkerBuilder<B extends BaseTetherableMarkerBuilder<B>> extends MarkerBuilder<B> {
public double tetheringDistance;
public Point tetherOrigin = new Point();
// public int handleID = 0;
public boolean draggable = false;
public boolean exclusive = true;
public boolean rotatesWithTether = false;
public List<String> providers = new ArrayList<>();
public List<String> receivers = new ArrayList<>();
// ***************************************************************************
// * Constants *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
protected BaseTetherableMarkerBuilder() {}
// ---------------------------------------------------------------------------
protected BaseTetherableMarkerBuilder(Element rootElement) throws BuildException {
super (rootElement);
this.buildFromBootstrap(rootElement, null, null);
}
// ---------------------------------------------------------------------------
/**
* Constructor initializing all fields from an {@link Element} containing as child elements all
* the information on fields to initialize.
*
* @param rootElement
* The element harbouring, on child nodes, the necessary information to initialize all
* fields of the builder.
* @throws BuildException
* Thrown when any of the fields fail to populate due to an error in reading information
* from the XML file.
*/
// ---------------------------------------------------------------------------
protected BaseTetherableMarkerBuilder(Element rootElement, BootstrapContext context, BootstrapCallback callback) throws BuildException {
super(rootElement, context, callback);
this.buildFromBootstrap(rootElement, context, callback);
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
*
* @param rootElement
* @param context
* @param callback
* @throws BuildException
*/
private void buildFromBootstrap(@Nullable Element rootElement, BootstrapContext context, BootstrapCallback callback) throws BuildException {
positions = new HashMap<>();
coronas = TreeMultimap.create();
for (Element handleNode : rootElement.getChild(Externalization.HANDLES_NODE).getChildren(Externalization.HANDLE_NODE)) {
int id = BootstrappingUtils.getContentAsInteger(handleNode, null, BootstrappingUtils.MANDATORY, null, context);
positions.put(id, null);
}
name = BootstrappingUtils.getContentAsString(rootElement, Externalization.NAME_NODE, BootstrappingUtils.OPTIONAL, Externalization.EMPTY_STRING, context);
coronas = CoronaBootstrapper.getCoronas(rootElement.getChild(Externalization.CORONAS_NODE), context, callback);
Element tetherableNode = rootElement.getChild(Externalization.TETHERABLE_NODE);
if (null != tetherableNode) {
Element distanceNode = tetherableNode.getChild(Externalization.DISTANCE_NODE);
if (distanceNode != null) {
this.tetheringDistance = this.buildDistance(distanceNode, context, callback);
}
Element providersNode = tetherableNode.getChild(Externalization.PROVIDERS_NODE);
this.providers = this.buildProviders(providersNode, context, callback);
Element receiversNode = tetherableNode.getChild(Externalization.RECEIVERS_NODE);
this.receivers = this.buildReceivers(receiversNode, context, callback);
this.draggable = BootstrappingUtils.getContentAsBoolean(tetherableNode, Externalization.DRAGGABLE_NODE, BootstrappingUtils.OPTIONAL, false, context);
this.exclusive = BootstrappingUtils.getContentAsBoolean(tetherableNode, Externalization.EXCLUSIVE_NODE, BootstrappingUtils.OPTIONAL, false, context);
this.rotatesWithTether = BootstrappingUtils.getContentAsBoolean(tetherableNode, Externalization.ROTATE_WITH_TETHER_NODE, BootstrappingUtils.OPTIONAL, false, context);
}
}
// ---------------------------------------------------------------------------
private double buildDistance(@Nullable Element distanceNode, BootstrapContext context, BootstrapCallback callback) throws BuildException {
Preconditions.checkNotNull(distanceNode, "Distance node MUST not be null!"); //$NON-NLS-1$
double distance = BootstrappingUtils.getContentAsDouble(distanceNode, null, BootstrappingUtils.MANDATORY, null, context);
Point convertor = new Point(ScreenCoordinates.class);
CoordinateState coordinates;