Commit 5a556945 authored by Nico Mack's avatar Nico Mack

Cleanup of code. Implemented cloning of widgets, required by feature

allowing mutliple widgets with identical IDs.
parent 5e2bc616
......@@ -171,6 +171,7 @@ STYLESHEET_NODE=styleSheet
STYLERULE_NODE=rule
PATH_NODE=path
PAGE_NODE=page
MEDIA_NODE=media
LOCATION_NODE=location
......
/**
* 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.content;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.widget.ZoomWidget;
/**
* Interface imposing functionality in order to be used with the {@link ZoomWidget}.
*
* @author Eric Tobias [eric.tobias@list.lu]
* @since 1.0
* @version 2.3.0
*/
@NonNullByDefault
public interface Zoomable {
/**
* Method used to specify the initial position of the zoom.
*
* @param position
*/
public void centre(Point position);
/**
* Method used to request all zooms be translate to the specified position.
*
* @param position
* The position to centre on.
*/
public void translate(Point position);
/**
* Method used to zoom by a factor given as parameter The direction of the zoom depends whether
* the parameter is positive or negative.
*
* @param position
* Zoom factor contained in a {@link Point}.
*/
public void zoom(@Nullable Point position);
/**
* Method called by the manager handling the {@link Zoomable} instance when the state changed to
* where it is no longer considered zooming. This method provides the opportunity to run code
* only executable when the zooming stopped.
*/
public void stoppedZooming();
/**
* Method called by the manager handling the {@link Zoomable} instance when the state changed to
* where it is no longer considered moving. This method provides the opportunity to run code
* only executable when the moving stopped.
*/
public void stoppedMoving();
}
\ No newline at end of file
......@@ -215,6 +215,7 @@ public class Externalization extends NLS {
public static String POINTING_OFFSET_NODE;
public static String PATH_NODE;
public static String PAGE_NODE;
public static String MEDIA_NODE;
public static String LOCATION_NODE;
......
......@@ -441,22 +441,6 @@ public class Point extends Float implements KdComparator<Point> {
return clone();
}
// switch (state.getClass().getSimpleName()) {
// case "TableCoordinates": //$NON-NLS-1$
// point.toTableCoordinates();
// break;
// case "ScreenCoordinates": //$NON-NLS-1$
// point.toScreenCoordinates();
// break;
// case "CameraCoordinates": //$NON-NLS-1$
// point.toCameraCoordinates();
// break;
// default:
// logger.error("This should never happen! State: {}!", state.toString()); //$NON-NLS-1$
// break;
// }
point.getState().toCoordinates(point, this.state.getClass());
Point clone = clone();
......
......@@ -30,6 +30,7 @@ import lu.list.itis.dkd.tui.widget.corona.Corona;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultimap;
import java.awt.Graphics2D;
import java.awt.Shape;
......@@ -47,10 +48,15 @@ import java.util.Set;
* Concretely, the {@link BaseWidget} caters to the base, including angle, of all handles and holds
* a map of all {@link Corona} instances defined for the widget and its handles.
*
* @author Nico Mack [nico.mack@list.lu]
* @author Eric Tobias [eric.tobias@list.lu]
* @since 1.0
* @version 2.3.1
* @version 2.5.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
@NonNullByDefault
public class BaseWidget {
/** Specifies the ID of the stage the widget is to be presented on. */
......@@ -58,10 +64,10 @@ public class BaseWidget {
/** 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. */
private HashMap<Integer, Point> positions;
protected HashMap<Integer, Point> positions;
/** The name given to this widget. */
protected String name;
/** Specifies whether this widget is an avatar for a tangible object or not. */
/** Specifies whether this widget is controlled by a tangible object or not. */
protected boolean tangible;
/** Specifies whether multiple instances of the same widget may coexist or not. */
protected boolean multipleInstances;
......@@ -71,54 +77,86 @@ public class BaseWidget {
protected Shape definingShape;
@Nullable
protected boolean onStage;
/**
* Field keeping track of the current Shape of the widget for the purpose of determining which
* screen areas need to be redrawn.
*/
@Nullable
protected Area clippingRegion;
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Constructor setting all fields as by the values specified by the builder.
*
* @param builder
* The builder instance defining all parameters.
*/
// ---------------------------------------------------------------------------
public BaseWidget(BaseBuilder<?> builder) {
coronas = builder.coronas;
positions = builder.positions;
name = builder.name;
stageId = builder.stageId;
tangible = builder.tangible;
multipleInstances = builder.multipleInstances;
networkAdapter = builder.networkAdapter;
definingShape = builder.definingShape;
coronas = builder.coronas;
positions = builder.positions;
clippingRegion = new Area();
onStage = false;
tangible = builder.tangible;
multipleInstances = builder.multipleInstances;
}
// ---------------------------------------------------------------------------
/**
* Method invoked when a handle associated with the widget was moved. This default
* implementation will set the base and angle fields to the corresponding values and update the
* coronas of the handle that the {@link TangibleObject} corresponds to.
* Copy constructor to clone widget.
*
* @param tangibleObject
* The {@link TangibleObject} that was triggering the move.
* @param original
* specifies the original widget to create an exact copy from.
*/
public void actionMove(TangibleObject tangibleObject) {
if (!tangible)
return;
positions.put(tangibleObject.getObjectId(), new Point(tangibleObject.getX(), tangibleObject.getY(), tangibleObject.getAngle(), Math.signum(tangibleObject.getRotationSpeed())));
updateCoronas(tangibleObject.getObjectId());
// ---------------------------------------------------------------------------
public BaseWidget(BaseWidget original) {
name = original.name;
stageId = original.stageId;
tangible = original.tangible;
multipleInstances = original.multipleInstances;
networkAdapter = original.networkAdapter;
definingShape = original.definingShape;
coronas = original.cloneCoronas();
positions = original.clonePositions();
clippingRegion = new Area();
onStage = false;
}
// ***************************************************************************
// * 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;
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitive(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* 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>
*/
// ---------------------------------------------------------------------------
private void updateCoronas(int handleID) {
assert positions.get(handleID) != null;
Point centre = getPosition(handleID);
......@@ -128,37 +166,42 @@ public class BaseWidget {
}
}
// ---------------------------------------------------------------------------
/**
* @return
* Method used to activate all appropriate visualisations due to the handle having been dropped
* onto the table.
*
* @param handleID
* The ID of the handle that was dropped.
*/
public boolean isTangible() {
return this.tangible;
// ---------------------------------------------------------------------------
private void startDrawingIfAppropriate(int handleID) {
for (Corona corona : coronas.get(handleID)) {
corona.onTable();
}
}
// ---------------------------------------------------------------------------
/**
* @param showIt
* Method used to cancel all visualisations for this widget's handle.
*
* @param handleID
* The handle that was lifted.
*/
public void setVisible(boolean showIt) {
if (tangible) {
return;
}
// ---------------------------------------------------------------------------
if (showIt) {
for (Entry<Integer, Point> handle : positions.entrySet()) {
if (handle.getValue() == null) {
handle.setValue(new Point());
}
updateCoronas(handle.getKey());
startDrawingIfAppropriate(handle.getKey());
}
} else {
for (Entry<Integer, Point> handle : positions.entrySet()) {
stopDrawing(handle.getKey());
}
private void stopDrawing(int handleID) {
for (Corona corona : coronas.get(handleID)) {
corona.setActive(false);
}
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Method invoked when the tangible is detected on the table surface for the first time. The
* {@link BaseWidget} instance is set to be active.
......@@ -166,6 +209,8 @@ public class BaseWidget {
* @param tangibleObject
* The {@link TangibleObject} that was triggering the drop action.
*/
// ---------------------------------------------------------------------------
public void actionDrop(TangibleObject tangibleObject) {
if (!tangible)
return;
......@@ -174,19 +219,25 @@ public class BaseWidget {
startDrawingIfAppropriate(tangibleObject.getObjectId());
}
// ---------------------------------------------------------------------------
/**
* Method used to activate all appropriate visualisations due to the handle having been dropped
* onto the table.
* Method invoked when a handle associated with the widget was moved. This default
* implementation will set the base and angle fields to the corresponding values and update the
* coronas of the handle that the {@link TangibleObject} corresponds to.
*
* @param handleID
* The ID of the handle that was dropped.
* @param tangibleObject
* The {@link TangibleObject} that was triggering the move.
*/
private void startDrawingIfAppropriate(int handleID) {
for (Corona corona : coronas.get(handleID)) {
corona.onTable();
}
// ---------------------------------------------------------------------------
public void actionMove(TangibleObject tangibleObject) {
if (!tangible)
return;
positions.put(tangibleObject.getObjectId(), new Point(tangibleObject.getX(), tangibleObject.getY(), tangibleObject.getAngle(), Math.signum(tangibleObject.getRotationSpeed())));
updateCoronas(tangibleObject.getObjectId());
}
// ---------------------------------------------------------------------------
/**
* Method invoked when the tangible is removed from the table surface. The {@link BaseWidget}
* instance will be set to no longer be active.
......@@ -194,6 +245,8 @@ public class BaseWidget {
* @param tangibleObject
* The TangibleObject that was triggering the drop action.
*/
// ---------------------------------------------------------------------------
public void actionLift(TangibleObject tangibleObject) {
if (!tangible)
return;
......@@ -202,18 +255,49 @@ public class BaseWidget {
onStage = false;
}
// ---------------------------------------------------------------------------
/**
* Method used to cancel all visualisations for this widget's handle.
* allows checking whether this widget is controlled by a tangible object or whether it can
* exist autonomously.
*
* @param handleID
* The handle that was lifted.
* @return <code>true</code> if widget is controlled by a tangible object (default), <code>
* false</code> otherwise.
*/
private void stopDrawing(int handleID) {
for (Corona corona : coronas.get(handleID)) {
corona.setActive(false);
// ---------------------------------------------------------------------------
public boolean isTangible() {
return this.tangible;
}
// ---------------------------------------------------------------------------
/**
* @param showIt
*/
// ---------------------------------------------------------------------------
public void setVisible(boolean showIt) {
if (tangible) {
return;
}
if (showIt) {
for (Entry<Integer, Point> handle : positions.entrySet()) {
if (handle.getValue() == null) {
handle.setValue(new Point());
}
updateCoronas(handle.getKey());
startDrawingIfAppropriate(handle.getKey());
}
} else {
for (Entry<Integer, Point> handle : positions.entrySet()) {
stopDrawing(handle.getKey());
}
}
}
// ---------------------------------------------------------------------------
/**
* Draws a the visual feedback for any widget. The method will iterate through all coronas and
* call their <code>paint()</code> method. The consistency of their position is ensured due to
......@@ -223,6 +307,8 @@ public class BaseWidget {
* @param canvas
* The Graphics2D instance to draw on.
*/
// ---------------------------------------------------------------------------
public void paint(Graphics2D canvas) {
clippingRegion.reset();
for (Corona corona : coronas.values()) {
......@@ -234,38 +320,63 @@ public class BaseWidget {
}
}
// ---------------------------------------------------------------------------
/**
* Method for retrieving all coronas of a particular class.
*
* @param <T>
* The type of the class of corona to retrieve.
* @param _class
* @param clazz
* 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) {
public <T> List<T> getCoronas(Class<T> clazz) {
List<T> results = new ArrayList<>();
for (Corona corona : coronas.values()) {
if (_class.isAssignableFrom(corona.getClass())) {
if (clazz.isAssignableFrom(corona.getClass())) {
results.add((T) corona);
}
}
return results;
}
// ---------------------------------------------------------------------------
/**
* Method for retrieving all coronas held by any of the handles associated to this widget.
*
* @return A {@link List} holding all coronas that are attached to any of the handles associated
* to this {@link BaseWidget}'s concrete instance.
*/
// ---------------------------------------------------------------------------
public List<Corona> getCoronas() {
return new ArrayList<>(coronas.values());
}
// ---------------------------------------------------------------------------
/**
* returns a multimap containing copies of every single corona associated with this widget. The
* map uses the widgets' potential handle IDs a keys and the corresponding coronas as values.
*
* @return a map containing a copy of all coronas handled by this widget.
*/
// ---------------------------------------------------------------------------
public Multimap<Integer, Corona> cloneCoronas() {
Multimap<Integer, Corona> clonedCoronas = TreeMultimap.create();
for (Entry<Integer, Corona> entry : coronas.entries()) {
Corona cloned = entry.getValue().clone();
clonedCoronas.put(entry.getKey(), cloned);
}
return clonedCoronas;
}
// ---------------------------------------------------------------------------
/**
* Method for returning the {@link Point} that holds the position at which the handle with the
* given ID resides.
......@@ -275,6 +386,8 @@ public class BaseWidget {
* @return The {@link Point} holding the position of the parameterized (by its ID) handle.
* <code>null</code> if no such handle is managed.
*/
// ---------------------------------------------------------------------------
public Point getPosition(int handleId) {
Point position = null;
if (positions.containsKey(handleId)) {
......@@ -285,13 +398,16 @@ public class BaseWidget {
return position;
}
// ---------------------------------------------------------------------------
/**
* Method used for returning th {@link Point} that holds the position of the handle with the
* Method used for returning the {@link Point} that holds the position of the handle with the
* lowest ID. This is useful also when the widget is only represented by a single handle and one
* wants to recover the position of that handle.
*
* @return The position as a {@link Point} of the handle with the lowest ID.
*/
// ---------------------------------------------------------------------------
public @Nullable Point getPosition() {
List<Integer> keys = new ArrayList<>(positions.keySet());
Collections.sort(keys, Ordering.natural());
......@@ -299,62 +415,99 @@ public class BaseWidget {
return this.getPosition(keys.get(0));
}
// ---------------------------------------------------------------------------
/**
* returns a hashmap containing copies of every single position associated with this widget. The
* map uses the widgets' potential handle IDs a keys and the corresponding {@link Point} as
* value.
*
* @return a map containing a copy of all positions handled by this widget.
*/
// ---------------------------------------------------------------------------
public HashMap<Integer, Point> clonePositions() {
HashMap<Integer, Point> clonedPositions = new HashMap<>();
for (Entry<Integer, Point> entry : positions.entrySet()) {
Point cloned = entry.getValue().clone();
clonedPositions.put(entry.getKey(), cloned);
}
return clonedPositions;
}
// ---------------------------------------------------------------------------
/**
*
* @return the ID of the stage this widget is to be shown on.
*/
// ---------------------------------------------------------------------------
public int getStageId() {
return this.stageId;
}
// ---------------------------------------------------------------------------
/**
* @return
* returns a list of all HandleIDs associated with this widget.
*
* @return a list with HandleIDs.
*/
// ---------------------------------------------------------------------------
public Set<Integer> getWidgetIds() {
return positions.keySet();
}
/**
* Simple getter method for positions.
*
* @return The value of positions.
*/
// public HashMap<Integer, Point> getPositions() {
// return positions;
// }
// ---------------------------------------------------------------------------
/**
* Method for returning the {@link NetworkAdapter} referenced by this instance.
*
* @return The {@link NetworkAdapter} held by this instance or <code>null</code> if no adapter
* was assigned.
*/
// ---------------------------------------------------------------------------
public @Nullable NetworkAdapter getNetworkAdapter() {
return networkAdapter;
}
// ---------------------------------------------------------------------------
/**
* Simple getter method for definingShape.
*
* @return The value of definingShape.
*/
// ---------------------------------------------------------------------------
@Deprecated
public @Nullable Shape getDefiningShape() {
return definingShape;
}
// ---------------------------------------------------------------------------
/**
* Simple getter method to retrieve the clipping region for this widget
*
* @return the clipping region if defined, <code>null</code> otherwise
*/
// ---------------------------------------------------------------------------
public Area getClippingRegion() {
return this.clippingRegion;
}
// ---------------------------------------------------------------------------
@Override
public BaseWidget clone() {
return new BaseWidget(this);