/**
*
*/
package cz.cuni.mff.peckam.java.origamist.gui.editor;
import static java.lang.Math.abs;
import java.awt.AWTEvent;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.Group;
import javax.media.j3d.LineArray;
import javax.media.j3d.OrderedGroup;
import javax.media.j3d.PickInfo;
import javax.media.j3d.PointArray;
import javax.media.j3d.PointAttributes;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.RenderingAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.TriangleArray;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnAWTEvent;
import javax.media.j3d.WakeupOnBehaviorPost;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.vecmath.Color3f;
import javax.vecmath.Point2d;
import javax.vecmath.Point3d;
import javax.vecmath.Vector2d;
import javax.vecmath.Vector3d;
import org.apache.log4j.Logger;
import com.sun.j3d.utils.behaviors.mouse.MouseBehavior;
import com.sun.j3d.utils.pickfast.PickTool;
import com.sun.j3d.utils.pickfast.behaviors.PickMouseBehavior;
import com.sun.j3d.utils.universe.ViewInfo;
import cz.cuni.mff.peckam.java.origamist.exceptions.InvalidOperationException;
import cz.cuni.mff.peckam.java.origamist.gui.common.OSDPanel;
import cz.cuni.mff.peckam.java.origamist.gui.common.StepViewingCanvasController;
import cz.cuni.mff.peckam.java.origamist.math.Line2d;
import cz.cuni.mff.peckam.java.origamist.math.MathHelper;
import cz.cuni.mff.peckam.java.origamist.math.Segment2d;
import cz.cuni.mff.peckam.java.origamist.math.Segment3d;
import cz.cuni.mff.peckam.java.origamist.math.Triangle2d;
import cz.cuni.mff.peckam.java.origamist.model.DoubleDimension;
import cz.cuni.mff.peckam.java.origamist.model.ModelPaper;
import cz.cuni.mff.peckam.java.origamist.model.Origami;
import cz.cuni.mff.peckam.java.origamist.model.Step;
import cz.cuni.mff.peckam.java.origamist.model.jaxb.Model;
import cz.cuni.mff.peckam.java.origamist.model.jaxb.ModelColors;
import cz.cuni.mff.peckam.java.origamist.modelstate.Direction;
import cz.cuni.mff.peckam.java.origamist.modelstate.Layer;
import cz.cuni.mff.peckam.java.origamist.modelstate.ModelPoint;
import cz.cuni.mff.peckam.java.origamist.modelstate.ModelSegment;
import cz.cuni.mff.peckam.java.origamist.modelstate.ModelState;
import cz.cuni.mff.peckam.java.origamist.modelstate.ModelTriangle;
import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.ExistingLineArgument;
import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.ExistingLinesArgument;
import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.LayersArgument;
import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.LineArgument;
import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.OperationArgument;
import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.PointArgument;
import cz.cuni.mff.peckam.java.origamist.utils.LocalizedString;
import cz.cuni.mff.peckam.java.origamist.utils.ParametrizedCallable;
/**
* The controller that handles step editing on the given canvas.
*
* @author Martin Pecka
*/
public class StepEditingCanvasController extends StepViewingCanvasController
{
protected boolean isInPreview = false;
/** The transform for transforming vworld coordinates to image plate coordinates. */
protected Transform3D vWorldToImagePlate = new Transform3D();
/** The group containing all layers. */
protected Group layers = null;
protected List<Group> layerGroups = new LinkedList<Group>();
/** The group containing all lines. */
protected Group lines = null;
protected List<Group> lineGroups = new LinkedList<Group>();
/** The group that is always drawn after the model is drawn. */
protected Group overModel = null;
/** The group that holds all displayed points. */
protected Group pointGroup = null;
protected List<Group> points = new LinkedList<Group>();
protected ModelSegment lastHighlightedPointsSegment = null;
protected ModelSegment firstPointsSegment = null;
/** The group that holds the highlighted point to draw it over all other points. */
protected Group highlightedPointGroup = null;
/** The factory that creates groups for given model points. */
protected PointFactory pointFactory = new PointFactory();
/** The factory that creates groups for given model lines. */
protected LineFactory lineFactory = new LineFactory();
/** The list of (points/lines/layers - depends on pickMode) available by the last performed pick operation. */
protected List<Group> availableItems = new LinkedList<Group>();
/** The currently highlighted (picked) (point/line/layer - depends on pickMode). */
protected Group highlighted = null;
/** The currently selected points, lines and layers. */
protected HashSet<Group> selected = new HashSet<Group>();
/** The set of currently selected layers. */
protected Set<Layer> selectedLayers = new HashSet<Layer>();
/** The set of currently selected lines. */
protected Set<ModelSegment> selectedLines = new HashSet<ModelSegment>();
/** The set of currently selected points. */
protected Set<ModelPoint> selectedPoints = new HashSet<ModelPoint>();
/** All elements chosen since the last call of {@link #clearChosenItems()}. */
protected List<Group> chosen = new LinkedList<Group>();
/** The elements chosen since the last call of {@link #setCurrentOperationArgument(OperationArgument)}. */
protected Set<Group> currentChosen = new HashSet<Group>();
/** The set of lines added when choosing a line by selecting two points. */
protected Set<NewLine> newLines = new HashSet<NewLine>();
/** The currently active new line. */
protected NewLine currentNewLine = null;
/** The type of primitves the user can pick. */
protected PickMode pickMode = PickMode.POINT;
/** The manager for changing layer appearances. */
protected LayerAppearanceManager layerAppearanceManager = new LayerAppearanceManager();
/** The manager for changing point appearances. */
protected PointAppearanceManager pointAppearanceManager = new PointAppearanceManager();
/** The operation argument the editor fetches data for. */
protected OperationArgument currentOperationArgument = null;
/** If current argument is a layer argument, this list contains the list of layers to choose from. */
protected List<Layer> layersToChooseFrom = null;
/** If current argument is a layer argument, this list contains the list of layers to choose from. */
protected List<Group> layersToChooseFromAsGroups = null;
/** The transform added by behaviors. */
protected Transform3D additionalTransform = null;
/** The set of layers the currently highlighted point lies in. */
protected Set<Group> layersForHighlightedPoint = null;
/** If true, show the 2D preview window. */
protected boolean showPreview = true;
/** The OSD panel with model 2D preview. */
protected OSDPanel preview = null;
/** The OSD panel for displaying messages to the user. */
protected HelpPanel helpPanel = null;
/** The pick behavior. */
protected PickMouseBehavior behavior = null;
/** Key for helpPanel. */
protected static final String INCOMPLMETE_ARGUMENT_KEY = "incomplete.argument";
/** Key for helpPanel. */
protected static final String AVAILABLE_ITEMS_KEY = "available.items";
/** Key for helpPanel. */
protected static final String OPERATION_ARGUMENT_KEY = "operation.argument";
{
updateTransforms();
addPropertyChangeListener("pickMode", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
clearHighlighted();
clearAvailableItems();
helpPanel.showL7dMessage("editor", "pick.mode.changed.to", null,
new Object[] { pickMode.toL7dString() });
helpPanel.removeMessage(AVAILABLE_ITEMS_KEY);
}
});
MouseListener listener = new MouseListener();
addMouseWheelListener(listener);
addMouseMotionListener(listener);
addMouseListener(listener);
}
/**
* @param canvas
* @param origami
* @param step
*/
public StepEditingCanvasController(Canvas3D canvas, Origami origami, Step step)
{
super(canvas, origami, step);
}
/**
* @param canvas
*/
public StepEditingCanvasController(Canvas3D canvas)
{
super(canvas);
}
@Override
public void setOrigami(Origami origami)
{
PropertyChangeListener bgListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
colorManager.setBackground((Color) evt.getNewValue());
}
};
PropertyChangeListener fgListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
colorManager.setForeground((Color) evt.getNewValue());
}
};
if (this.origami != null) {
this.origami.removePropertyChangeListener(bgListener, Origami.MODEL_PROPERTY, Model.PAPER_PROPERTY,
ModelPaper.COLORS_PROPERTY, ModelColors.BACKGROUND_PROPERTY);
this.origami.removePropertyChangeListener(fgListener, Origami.MODEL_PROPERTY, Model.PAPER_PROPERTY,
ModelPaper.COLORS_PROPERTY, ModelColors.FOREGROUND_PROPERTY);
}
super.setOrigami(origami);
if (origami != null) {
origami.addPropertyChangeListener(bgListener, Origami.MODEL_PROPERTY, Model.PAPER_PROPERTY,
ModelPaper.COLORS_PROPERTY, ModelColors.BACKGROUND_PROPERTY);
origami.addPropertyChangeListener(fgListener, Origami.MODEL_PROPERTY, Model.PAPER_PROPERTY,
ModelPaper.COLORS_PROPERTY, ModelColors.FOREGROUND_PROPERTY);
}
}
/** Old zoom before changing step. */
private double oldZoom = 100d;
@Override
public void setStep(Step step, final Runnable afterSetCallback,
final ParametrizedCallable<?, ? super Exception> exceptionCallback)
{
if (step != null && step.getAttachedTo() == null) {
return;
}
additionalTransform = null;
if (this.step == step && this.step != null && this.step.getAttachedTo() != null) {
// additionalTransform will hold the "additional" transform added by behaviors
additionalTransform = new Transform3D(baseTransform);
additionalTransform.invert();
additionalTransform.mul(transform);
}
if (this.step != null)
oldZoom = this.step.getZoom();
super.setStep(step, afterSetCallback, exceptionCallback);
availableItems.clear();
highlighted = null;
selected.clear();
selectedLayers.clear();
selectedLines.clear();
selectedPoints.clear();
chosen.clear();
currentChosen.clear();
newLines.clear();
currentNewLine = null;
layersToChooseFrom = null;
layersToChooseFromAsGroups = null;
layersForHighlightedPoint = null;
currentOperationArgument = null;
if (helpPanel != null)
helpPanel.removeMessage(OPERATION_ARGUMENT_KEY);
}
@Override
protected void afterSetStep()
{
super.afterSetStep();
// reset the zoom according to the current step
if (step != null)
support.firePropertyChange("zoom", oldZoom, (double) step.getZoom());
if (preview != null)
preview.repaint();
}
/**
* @return If true, show the 2D preview window.
*/
public boolean isShowPreview()
{
return showPreview;
}
/**
* @param showPreview If true, show the 2D preview window.
*/
public void setShowPreview(boolean showPreview)
{
this.showPreview = showPreview;
}
@Override
protected PolygonAttributes createPolygonAttributes()
{
PolygonAttributes polyAttribs = super.createPolygonAttributes();
polyAttribs.setCapability(PolygonAttributes.ALLOW_OFFSET_WRITE);
return polyAttribs;
}
@Override
protected Appearance createBaseTrianglesAppearance()
{
Appearance appearance = super.createBaseTrianglesAppearance();
appearance.getColoringAttributes().setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
appearance.getTransparencyAttributes().setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
return appearance;
}
@Override
protected Appearance createBasicLinesAppearance()
{
Appearance appearance = super.createBasicLinesAppearance();
appearance.getColoringAttributes().setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
appearance.getTransparencyAttributes().setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
appearance.getRenderingAttributes().setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);
appearance.getRenderingAttributes().setCapability(RenderingAttributes.ALLOW_DEPTH_TEST_FUNCTION_WRITE);
return appearance;
}
@Override
protected Transform3D setupTransform() throws InvalidOperationException
{
super.setupTransform();
if (additionalTransform != null) {
double scale = transform.getScale();
transform.mul(additionalTransform);
transform.setScale(scale);
additionalTransform = null;
}
return transform;
}
@Override
protected TransformGroup setupTGroup() throws InvalidOperationException
{
try {
ModelState state = getModelState();
tGroup = new TransformGroup();
tGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
tGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
OrderedGroup og = new OrderedGroup();
og.setPickable(true);
model = new OrderedGroup();
model.setPickable(true);
layers = new TransformGroup();
layers.setPickable(true);
layerGroups.clear();
Group geometry = new BranchGroup();
TriangleArray[] triangleArrays = state.getTrianglesArrays();
Shape3D top, bottom;
Appearance appearance;
Appearance appearance2;
for (TriangleArray triangleArray : triangleArrays) {
TransformGroup group = new TransformGroup();
group.setBoundsAutoCompute(true);
group.setUserData(triangleArray.getUserData()); // contains the layer
group.setPickable(true);
group.setCapability(Shape3D.ENABLE_PICK_REPORTING);
appearance = createNormalTrianglesAppearance();
appearance2 = createInverseTrianglesAppearance();
top = new Shape3D(triangleArray, appearance);
bottom = new Shape3D(triangleArray, appearance2);
group.addChild(top);
group.addChild(bottom);
layers.addChild(group);
layerGroups.add(group);
}
geometry.addChild(layers);
LineArray[] lineArrays = state.getLineArrays();
lines = new TransformGroup();
lines.setPickable(true);
lines.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
lines.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
lines.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
lineGroups.clear();
for (LineArray lineArray : lineArrays) {
Group line = lineFactory.createLine(lineArray, (ModelSegment) lineArray.getUserData());
lines.addChild(line);
lineGroups.add(line);
}
geometry.addChild(lines);
model.addChild(geometry);
overModel = new TransformGroup();
overModel.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
overModel.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
overModel.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
overModel.setPickable(true);
model.addChild(overModel);
pointGroup = new BranchGroup();
pointGroup.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
pointGroup.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
pointGroup.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
pointGroup.setPickable(true);
model.addChild(pointGroup);
highlightedPointGroup = new BranchGroup();
highlightedPointGroup.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
highlightedPointGroup.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
highlightedPointGroup.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
highlightedPointGroup.setPickable(true);
model.addChild(highlightedPointGroup);
og.addChild(model);
setupTransform();
tGroup.setTransform(transform);
// og.addChild(getOperationSignsGroup()); // uncomment to see operation signs in editor
og.addChild(getMarkerGroups());
tGroup.addChild(og);
return tGroup;
} catch (InvalidOperationException e) {
tGroup = new TransformGroup();
tGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
// TODO create an ErrorTransformGroup that would signalize to the user that an operation is invalid
throw e;
}
}
@Override
protected void createAndAddBranchGraphChildren() throws InvalidOperationException
{
super.createAndAddBranchGraphChildren();
// setup the pick behavior
behavior = new PickMouseBehavior(canvas, branchGraph, null) {
{
pickCanvas.setMode(PickInfo.PICK_GEOMETRY);
pickCanvas.setFlags(PickInfo.SCENEGRAPHPATH | PickInfo.ALL_GEOM_INFO | PickInfo.NODE
| PickInfo.CLOSEST_INTERSECTION_POINT | PickInfo.LOCAL_TO_VWORLD);
pickCanvas.setTolerance(3f);
setSchedulingBounds(new BoundingSphere(new Point3d(), 1000));
}
@Override
public void processStimulus(@SuppressWarnings("rawtypes") Enumeration criteria)
{
WakeupCriterion wakeup;
AWTEvent[] evt = null;
int xpos = 0, ypos = 0;
while (criteria.hasMoreElements()) {
wakeup = (WakeupCriterion) criteria.nextElement();
if (wakeup instanceof WakeupOnAWTEvent)
evt = ((WakeupOnAWTEvent) wakeup).getAWTEvent();
}
if (evt != null && evt[0] instanceof MouseEvent) {
mevent = (MouseEvent) evt[0];
xpos = mevent.getPoint().x;
ypos = mevent.getPoint().y;
}
updateScene(xpos, ypos);
removeUnnecessaryListeners();
wakeupOn(wakeupCondition);
}
@Override
public void updateScene(final int x, final int y)
{
if (branchGraph == null || !branchGraph.isLive())
return;
if (isInPreview)
return;
pickCanvas.setShapeLocation(x, y);
List<PickInfo> results;
try {
results = pickMode.filterPickResults(pickCanvas, pickCanvas.pickAllSorted());
} catch (Exception ex) {
// picking points sometimes causes this exception to be thrown, but if we ignore this pick call,
// nothing serious happens
Logger.getLogger(getClass()).warn("Picking failed", ex);
return;
}
if (results.size() > 0) {
if (pickMode == PickMode.LAYER) {
boolean containsHighlighted = false;
List<Group> newAvailableItems = new LinkedList<Group>();
if (results.size() == availableItems.size()) {
Iterator<Group> it = availableItems.iterator();
boolean different = false;
for (PickInfo r : results) {
TransformGroup tg = (TransformGroup) pickCanvas.getNode(r,
PickTool.TYPE_TRANSFORM_GROUP);
if (!different && it.next() != tg)
different = true;
if (tg == highlighted)
containsHighlighted = true;
newAvailableItems.add(tg);
}
if (!different) {
helpPanel.showL7dMessage("editor", "there.are.num.available.layers.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
return;
}
} else {
for (PickInfo r : results) {
TransformGroup tg = (TransformGroup) pickCanvas.getNode(r,
PickTool.TYPE_TRANSFORM_GROUP);
if (tg == highlighted)
containsHighlighted = true;
newAvailableItems.add(tg);
}
}
availableItems = newAvailableItems;
if (containsHighlighted) {
helpPanel.showL7dMessage("editor", "there.are.num.available.layers.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
return;
}
if (layersToChooseFrom != null) {
for (Group g : availableItems) {
if (g.getUserData() instanceof Layer && layersToChooseFrom.contains(g.getUserData())) {
setHighlightedLayer(g);
helpPanel.showL7dMessage("editor", "there.are.num.available.layers.under.cursor",
null, AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
return;
}
}
setHighlightedLayer(null);
} else {
setHighlightedLayer(availableItems.get(0));
helpPanel.showL7dMessage("editor", "there.are.num.available.layers.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
}
} else if (pickMode == PickMode.LINE) {
boolean containsHighlighted = false;
List<Group> newAvailableItems = new LinkedList<Group>();
if (results.size() == availableItems.size()) {
Iterator<Group> it = availableItems.iterator();
boolean different = false;
for (PickInfo r : results) {
BranchGroup tg = (BranchGroup) pickCanvas.getNode(r, PickTool.TYPE_BRANCH_GROUP);
if (!different && it.next() != tg)
different = true;
// if some layers are selected, provide only those lines that lie in the selected layers
boolean isInSelectedLayers = selectedLayers.size() == 0;
ModelSegment seg = (ModelSegment) tg.getUserData();
for (Layer l : selectedLayers) {
if (l.liesInThisLayer(seg)) {
isInSelectedLayers = true;
break;
}
}
if (!isInSelectedLayers)
continue;
if (tg == highlighted)
containsHighlighted = true;
newAvailableItems.add(tg);
}
if (!different) {
helpPanel.showL7dMessage("editor", "there.are.num.available.lines.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
return;
}
} else {
for (PickInfo r : results) {
BranchGroup tg = (BranchGroup) pickCanvas.getNode(r, PickTool.TYPE_BRANCH_GROUP);
// if some layers are selected, provide only those lines that lie in the selected layers
boolean isInSelectedLayers = selectedLayers.size() == 0;
ModelSegment seg = (ModelSegment) tg.getUserData();
for (Layer l : selectedLayers) {
if (l.liesInThisLayer(seg)) {
isInSelectedLayers = true;
break;
}
}
if (!isInSelectedLayers)
continue;
if (tg == highlighted)
containsHighlighted = true;
newAvailableItems.add(tg);
}
}
availableItems = newAvailableItems;
helpPanel.showL7dMessage("editor", "there.are.num.available.lines.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
if (containsHighlighted)
return;
if (availableItems.size() > 0)
setHighlightedLine(availableItems.get(0));
} else if (pickMode == PickMode.POINT) {
// update the projection transform
updateTransforms();
HashSet<ModelPoint> points = new HashSet<ModelPoint>(results.size());
LinkedHashSet<Group> pointGroups = new LinkedHashSet<Group>();
LinkedList<Group> pointsToAttach = new LinkedList<Group>();
final double tolerance = 6d;
Point2d evtPos = new Point2d(x, y);
Set<Group> permanent = new HashSet<Group>(chosen);
permanent.addAll(selected);
// if there are some selected or chosen points, add the close ones to the new available points
for (Group g : permanent) {
if (g.getUserData() instanceof ModelPoint) {
if (evtPos.distance(getPointCanvasPosition(g)) < tolerance
&& liesInSelectedLinesOrLayers(g)) {
points.add((ModelPoint) g.getUserData());
pointGroups.add(g);
}
}
}
List<Group> snapPoints = new LinkedList<Group>();
main: for (PickInfo r : results) {
BranchGroup group = (BranchGroup) pickCanvas.getNode(r, PickTool.TYPE_BRANCH_GROUP);
// if we picked an existing point, just add it
if (group.getUserData() instanceof ModelPoint) {
if (pointGroups.contains(group) || !liesInSelectedLinesOrLayers(group))
continue main;
pointGroups.add(group);
points.add((ModelPoint) group.getUserData());
continue main;
}
// we don't want the currentNewLine to make snap points
if (group == currentNewLine)
continue main;
// we have picked a fold line
ModelSegment userSegment = (ModelSegment) group.getUserData();
// if any of the already available points lies on the fold line, skip this line
for (Group g : pointGroups) {
if (userSegment.getOriginal().contains(((ModelPoint) g.getUserData()).getOriginal())) {
continue main;
}
}
LineArray geom = (LineArray) r.getIntersectionInfos()[0].getGeometry();
Point3d[] edges = new Point3d[] { new Point3d(), new Point3d() };
geom.getCoordinates(0, edges);
Segment3d vworldSegment = new Segment3d(edges[0], edges[1]);
Point3d vworldIntersection = r.getClosestIntersectionPoint();
double param = vworldSegment.getParameterForPoint(vworldIntersection);
Point3d point = userSegment.getPointForParameter(param);
Point2d point2 = userSegment.getOriginal().getPointForParameter(param);
ModelPoint modelPoint = new ModelPoint(point, point2, userSegment);
if (points.contains(modelPoint))
continue;
for (ModelPoint p : points) {
if (p.epsilonEquals(modelPoint))
continue main;
}
boolean isSnapPoint = false;
// create the snap points (edges and center of the line) and try to use them
Point3d center = vworldSegment.getPointForParameter(0.5d);
List<Point3d> snaps = new LinkedList<Point3d>();
snaps.add(new Point3d(vworldSegment.getP1()));
snaps.add(new Point3d(vworldSegment.getP2()));
snaps.add(center);
if (isChoosingSecondPoint()) {
ModelPoint first = (ModelPoint) currentChosen.iterator().next().getUserData();
List<Line2d> snapLines = new LinkedList<Line2d>();
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(0, 1)));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(1, 0)));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(1, 1)));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(-1, 1)));
if (first.getContainingSegment() != null) {
Vector2d firstVector = first.getContainingSegment().getOriginal().getVector();
if (Math.abs(firstVector.x) > MathHelper.EPSILON
&& Math.abs(firstVector.y) > MathHelper.EPSILON) {
snapLines.add(new Line2d(first.getOriginal(), firstVector));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(firstVector.y,
-firstVector.x)));
double angle = Math.atan2(firstVector.y, firstVector.x);
angle += Math.PI / 4;
double tan = Math.tan(angle);
Vector2d vec = new Vector2d(1, tan);
snapLines.add(new Line2d(first.getOriginal(), vec));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(vec.y, -vec.x)));
}
}
for (Line2d snapLine : snapLines) {
Segment2d intersection = userSegment.getOriginal().getIntersection(snapLine);
if (intersection != null && intersection.isSinglePoint()) {
Point3d snapPoint = new Point3d(userSegment.getPointForParameter(userSegment
.getOriginal().getParameterForPoint(intersection.getP1())));
snapPoint.scale(origami.getModel().getPaper().getOneRelInMeters());
snaps.add(snapPoint);
}
}
}
for (Point3d p : snaps) {
if (evtPos.distance(getLocalPointCanvasPosition(p, r.getLocalToVWorld())) < tolerance) {
vworldIntersection = p;
param = vworldSegment.getParameterForPoint(p);
point = userSegment.getPointForParameter(param);
point2 = userSegment.getOriginal().getPointForParameter(param);
modelPoint = new ModelPoint(point, point2, userSegment);
isSnapPoint = true;
break;
}
}
// the point changed, so we must re-check if it doesn't collide with an already available
// point
if (isSnapPoint) {
if (points.contains(modelPoint))
continue main;
for (ModelPoint p : points) {
if (p.epsilonEquals(modelPoint))
continue main;
}
}
Group g = pointFactory.createPoint(modelPoint, vworldIntersection);
// make snap points look like squares
if (isSnapPoint)
((Shape3D) g.getChild(0)).getAppearance().getPointAttributes()
.setPointAntialiasingEnable(false);
if (!liesInSelectedLinesOrLayers(g))
continue main;
points.add(modelPoint);
pointGroups.add(g);
pointsToAttach.add(g);
if (isSnapPoint)
snapPoints.add(g);
}
// we don't rather attach the new points while we iterate over pick results, it could cause mess
for (Group g : pointsToAttach) {
pointGroup.addChild(g);
StepEditingCanvasController.this.points.add(g);
}
// change priorities of points
LinkedList<Group> newAvailableItems = new LinkedList<Group>(pointGroups);
newAvailableItems.removeAll(pointsToAttach);
newAvailableItems.addAll(0, pointsToAttach);
newAvailableItems.removeAll(snapPoints);
newAvailableItems.addAll(0, snapPoints);
for (Group g : currentChosen) {
if (newAvailableItems.remove(g))
newAvailableItems.add(0, g);
}
// detach unused old points
availableItems.removeAll(newAvailableItems);
for (Group g : availableItems) {
if (!isPermanent(g)) {
((BranchGroup) g).detach();
StepEditingCanvasController.this.points.remove(g);
}
}
availableItems = newAvailableItems;
if (availableItems.size() > 0) {
int index = 0;
if (isChoosingSecondPoint()) {
// if the user chooses a second point of a line, highlight the points that can be chosen
boolean foundChoosable = false;
for (Group g : availableItems) {
if (g.getUserData() instanceof ModelPoint) {
if (canChoosePoint(g)) {
foundChoosable = true;
break;
}
}
index++;
}
if (!foundChoosable)
index = 0;
}
if (!availableItems.contains(highlighted))
setHighlightedPoint(availableItems.get(index));
}
helpPanel.showL7dMessage("editor", "there.are.num.available.points.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
}
} else {
if (highlighted != null) {
clearHighlighted();
clearAvailableItems();
}
if (currentNewLine != null
&& (!chosen.contains(currentNewLine.p1) || !chosen.contains(currentNewLine.p2))) {
currentNewLine.detach();
newLines.remove(currentNewLine);
currentNewLine = null;
}
switch (pickMode) {
case LAYER:
helpPanel.showL7dMessage("editor", "there.are.num.available.layers.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
break;
case LINE:
helpPanel.showL7dMessage("editor", "there.are.num.available.lines.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
break;
case POINT:
helpPanel.showL7dMessage("editor", "there.are.num.available.points.under.cursor", null,
AVAILABLE_ITEMS_KEY, new Object[] { availableItems.size() });
break;
}
}
if (preview != null)
preview.repaint();
}
};
branchGraph.addChild(behavior);
MouseBehavior mouse = new MouseBehavior(canvas, tGroup) {
protected static final String BACKSIDE_VIEWING_KEY = "backside.viewing";
{
setSchedulingBounds(new BoundingSphere(new Point3d(), 1000));
}
@Override
public void processStimulus(@SuppressWarnings("rawtypes") Enumeration criteria)
{
WakeupCriterion wakeup;
AWTEvent[] events;
MouseEvent evt;
// int id;
// int dx, dy;
while (criteria.hasMoreElements()) {
wakeup = (WakeupCriterion) criteria.nextElement();
if (wakeup instanceof WakeupOnAWTEvent) {
events = ((WakeupOnAWTEvent) wakeup).getAWTEvent();
if (events.length > 0) {
evt = (MouseEvent) events[events.length - 1];
doProcess(evt);
}
}
else if (wakeup instanceof WakeupOnBehaviorPost) {
while (true) {
// access to the queue must be synchronized
synchronized (mouseq) {
if (mouseq.isEmpty())
break;
evt = (MouseEvent) mouseq.remove(0);
// consolidate MOUSE_DRAG events
while ((evt.getID() == MouseEvent.MOUSE_DRAGGED) && !mouseq.isEmpty()
&& (((MouseEvent) mouseq.get(0)).getID() == MouseEvent.MOUSE_DRAGGED)) {
evt = (MouseEvent) mouseq.remove(0);
}
}
doProcess(evt);
}
}
}
wakeupOn(mouseCriterion);
}
void doProcess(MouseEvent evt)
{
Vector3d screenNormal = new Vector3d(0, 0, 1);
baseTransform.transform(screenNormal);
Vector3d modelScreenNormal = new Vector3d(0, 0, 1);
transform.transform(modelScreenNormal);
if (screenNormal.angle(modelScreenNormal) > Math.PI / 2d) {
helpPanel.showL7dMessage("editor", BACKSIDE_VIEWING_KEY);
} else {
helpPanel.removeMessage(BACKSIDE_VIEWING_KEY);
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
// ignore - the Oracle people forgot a debugging line uncommented, so if I didn't overwrite this method,
// each mouse wheel motion would produce a line to sysout
}
};
branchGraph.addChild(mouse);
}
@Override
protected void setupUniverse() throws InvalidOperationException
{
super.setupUniverse();
createAndSetupOSD();
}
/**
* Create and setup all OSD components.
*/
protected void createAndSetupOSD()
{
if (preview == null) {
preview = new OSDPanel(canvas, 10, 10, 128, 128, false, true) {
protected final float lineWidth = 5f;
protected final float pointSizeHalf = 3f;
@Override
protected void paint(final Graphics2D graphics)
{
Graphics2D g = (Graphics2D) graphics.create();
g.setBackground(new Color(0, 0, 0, 0));
g.clearRect(0, 0, paintArea.getWidth(), paintArea.getHeight());
try {
drawTopTextureToBuffer(paintArea);
} catch (Exception e) {
return;
}
Rectangle usedPart = getUsedBufferPart(paintArea);
int x = usedPart.x, y = usedPart.y;
int w = usedPart.width, h = usedPart.height;
// usedPart contains the really used part of buffer, but we need to compensate that for the shorter
// side, its
// most distant point isn't generally 1, but something less; so we take the inverse ratio and
// multiply it with
// the shorter dimension to compensate this effect
DoubleDimension paperDim = origami.getModel().getPaper().getRelativeDimensions();
if (paperDim.getWidth() >= paperDim.getHeight()) {
h = (int) (h * paperDim.getWidth() / paperDim.getHeight());
} else {
w = (int) (w * paperDim.getHeight() / paperDim.getWidth());
}
g.setStroke(new BasicStroke(lineWidth));
for (Group gr : selected) {
if (gr.getUserData() instanceof Layer) {
Layer l = (Layer) gr.getUserData();
g.setColor(getColorManager().getSelectedFg());
paintLayer(g, l, x, y, w, h, usedPart);
} else if (gr.getUserData() instanceof ModelSegment) {
ModelSegment s = (ModelSegment) gr.getUserData();
g.setColor(getColorManager().getSelectedLine());
paintSegment(g, s, x, y, w, h, usedPart);
} else if (gr.getUserData() instanceof ModelPoint) {
ModelPoint p = (ModelPoint) gr.getUserData();
g.setColor(getColorManager().getSelectedPoint());
paintPoint(g, p, x, y, w, h, usedPart);
}
}
for (Group gr : chosen) {
if (gr.getUserData() instanceof Layer) {
Layer l = (Layer) gr.getUserData();
g.setColor(getColorManager().getChosenFg());
paintLayer(g, l, x, y, w, h, usedPart);
} else if (gr.getUserData() instanceof ModelSegment) {
ModelSegment s = (ModelSegment) gr.getUserData();
g.setColor(getColorManager().getChosenLine());
paintSegment(g, s, x, y, w, h, usedPart);
} else if (gr.getUserData() instanceof ModelPoint) {
ModelPoint p = (ModelPoint) gr.getUserData();
g.setColor(getColorManager().getChosenPoint());
paintPoint(g, p, x, y, w, h, usedPart);
}
}
for (NewLine l : newLines) {
ModelPoint p1 = (ModelPoint) l.p1.getUserData();
ModelPoint p2 = (ModelPoint) l.p2.getUserData();
g.setColor(getColorManager().getChosenLine());
paintSegment(g, new ModelSegment(p1, p2, null, 0), x, y, w, h, usedPart);
}
if (highlighted != null) {
if (highlighted.getUserData() instanceof Layer) {
Layer l = (Layer) highlighted.getUserData();
if (chosen.contains(highlighted))
g.setColor(getColorManager().getChosenHighlightFg());
else if (selected.contains(highlighted))
g.setColor(getColorManager().getSelectedHighlightFg());
else
g.setColor(getColorManager().getHighlightFg());
paintLayer(g, l, x, y, w, h, usedPart);
} else if (highlighted.getUserData() instanceof ModelSegment) {
ModelSegment s = (ModelSegment) highlighted.getUserData();
if (chosen.contains(highlighted))
g.setColor(getColorManager().getChosenHighlightedLine());
else if (selected.contains(highlighted))
g.setColor(getColorManager().getSelectedHighlightedLine());
else
g.setColor(getColorManager().getHighlightedLine());
paintSegment(g, s, x, y, w, h, usedPart);
} else if (highlighted.getUserData() instanceof ModelPoint) {
ModelPoint p = (ModelPoint) highlighted.getUserData();
if (chosen.contains(highlighted))
g.setColor(getColorManager().getChosenHighlightedPoint());
else if (selected.contains(highlighted))
g.setColor(getColorManager().getSelectedHighlightedPoint());
else
g.setColor(getColorManager().getHighlightedPoint());
paintPoint(g, p, x, y, w, h, usedPart);
}
}
g.dispose();
}
protected void paintLayer(Graphics2D g, Layer l, int x, int y, int w, int h, Rectangle usedPart)
{
for (ModelTriangle t : l.getTriangles()) {
Triangle2d t2 = t.getOriginalPosition();
int[] xpoints = new int[] { x + (int) (t2.getP1().x * w), x + (int) (t2.getP2().x * w),
x + (int) (t2.getP3().x * w) };
int[] ypoints = new int[] { y + (int) ((1 - t2.getP1().y) * h),
y + (int) ((1 - t2.getP2().y) * h), y + (int) ((1 - t2.getP3().y) * h) };
g.fillPolygon(xpoints, ypoints, 3);
}
}
protected void paintSegment(Graphics2D g, ModelSegment s, int x, int y, int w, int h, Rectangle usedPart)
{
Segment2d s2 = s.getOriginal();
int x1 = (int) (x + s2.getP1().x * w);
int x2 = (int) (x + s2.getP2().x * w);
int y1 = (int) (y + (1 - s2.getP1().y) * h);
int y2 = (int) (y + (1 - s2.getP2().y) * h);
x1 = shiftToUsedPartX(x1, lineWidth / 2, usedPart);
x2 = shiftToUsedPartX(x2, lineWidth / 2, usedPart);
y1 = shiftToUsedPartY(y1, lineWidth / 2, usedPart);
y2 = shiftToUsedPartY(y2, lineWidth / 2, usedPart);
g.drawLine(x1, y1, x2, y2);
}
protected void paintPoint(Graphics2D g, ModelPoint p, int x, int y, int w, int h, Rectangle usedPart)
{
Point2d p2 = p.getOriginal();
int x1 = (int) (x + p2.x * w - pointSizeHalf);
int y1 = (int) (y + (1 - p2.y) * h - pointSizeHalf);
x1 = shiftToUsedPartX(x1, pointSizeHalf - 1, usedPart);
y1 = shiftToUsedPartY(y1, pointSizeHalf - 1, usedPart);
g.fillRect(x1, y1, (int) (pointSizeHalf * 2), (int) (pointSizeHalf * 2));
}
protected int shiftToUsedPartX(int x, float minDistance, Rectangle usedPart)
{
return shiftToUsedPart(x, minDistance, usedPart.x, usedPart.width);
}
protected int shiftToUsedPartY(int y, float minDistance, Rectangle usedPart)
{
return shiftToUsedPart(y, minDistance, usedPart.y, usedPart.height);
}
protected int shiftToUsedPart(int coord, float minDistance, int offset, int size)
{
if (coord < offset + minDistance)
return offset + (int) minDistance;
if (coord > offset + size - minDistance)
return offset + size - (int) minDistance;
return coord;
}
};
PickMouseBehavior pick = new PickMouseBehavior(canvas, preview.getRoot(), new BoundingSphere(new Point3d(),
100000)) {
double pxTolerance = 3;
{
pickCanvas.setMode(PickInfo.PICK_GEOMETRY);
pickCanvas.setFlags(PickInfo.SCENEGRAPHPATH | PickInfo.ALL_GEOM_INFO | PickInfo.NODE
| PickInfo.CLOSEST_INTERSECTION_POINT | PickInfo.LOCAL_TO_VWORLD);
pickCanvas.setTolerance(3f);
setSchedulingBounds(new BoundingSphere(new Point3d(), 1000));
}
@Override
public void processStimulus(@SuppressWarnings("rawtypes") Enumeration criteria)
{
WakeupCriterion wakeup;
AWTEvent[] evt = null;
int xpos = 0, ypos = 0;
while (criteria.hasMoreElements()) {
wakeup = (WakeupCriterion) criteria.nextElement();
if (wakeup instanceof WakeupOnAWTEvent)
evt = ((WakeupOnAWTEvent) wakeup).getAWTEvent();
}
if (evt != null && evt[0] instanceof MouseEvent) {
mevent = (MouseEvent) evt[0];
xpos = mevent.getPoint().x;
ypos = mevent.getPoint().y;
}
updateScene(xpos, ypos);
removeUnnecessaryListeners();
wakeupOn(wakeupCondition);
}
@Override
public void updateScene(int x, int y)
{
Rectangle paperRealRect = getUsedBufferPart(preview.getPaintArea());
DoubleDimension paperDim = origami.getModel().getPaper().getRelativeDimensions();
int xpos = x - preview.getBounds().x;
int ypos = preview.getBounds().height - (y - preview.getBounds().y);
double xrel = (double) xpos / paperRealRect.width * paperDim.getWidth();
double yrel = (double) ypos / paperRealRect.height * paperDim.getHeight();
double onePxInRel = 1d / paperRealRect.width * paperDim.getWidth();
boolean wasInPreview = isInPreview;
isInPreview = xrel >= 0 && yrel >= 0 && xrel <= paperDim.getWidth() && yrel <= paperDim.getHeight();
if (wasInPreview != isInPreview)
clearHighlighted();
if (isInPreview) {
Point2d p = new Point2d(xrel, yrel);
boolean foundSomething = false;
if (pickMode == PickMode.LAYER) {
for (Group g : layerGroups) {
if (((Layer) g.getUserData()).contains(p)) {
setHighlightedLayer(g);
foundSomething = true;
break;
}
}
} else if (pickMode == PickMode.LINE || pickMode == PickMode.POINT) {
if (pickMode == PickMode.POINT) {
for (Group g : StepEditingCanvasController.this.points) {
if (((ModelPoint) g.getUserData()).getOriginal().distance(p) < pxTolerance
* onePxInRel) {
setHighlightedPoint(g);
preview.repaint();
return;
}
}
}
double nearestDistance = pxTolerance * onePxInRel;
Group nearest = null;
for (Group g : lineGroups) {
Point2d pointOnLine = ((ModelSegment) g.getUserData()).getOriginal().getNearestPoint(p);
double distance = pointOnLine.distance(p);
if (distance < nearestDistance) {
nearestDistance = distance;
nearest = g;
foundSomething = true;
if (distance < MathHelper.EPSILON)
break;
}
}
if (nearest != null) {
if (pickMode == PickMode.LINE) {
setHighlightedLine(nearest);
} else {
ModelSegment segment = (ModelSegment) nearest.getUserData();
Point2d p2 = segment.getOriginal().getNearestPoint(p);
Set<Point2d> snapPoints = new HashSet<Point2d>();
snapPoints.add(segment.getOriginal().getP1());
snapPoints.add(segment.getOriginal().getP2());
snapPoints.add(segment.getOriginal().getPointForParameter(0.5));
if (isChoosingSecondPoint()) {
ModelPoint first = (ModelPoint) currentChosen.iterator().next().getUserData();
List<Line2d> snapLines = new LinkedList<Line2d>();
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(0, 1)));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(1, 0)));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(1, 1)));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(-1, 1)));
if (first.getContainingSegment() != null) {
Vector2d firstVector = first.getContainingSegment().getOriginal()
.getVector();
if (Math.abs(firstVector.x) > MathHelper.EPSILON
&& Math.abs(firstVector.y) > MathHelper.EPSILON) {
snapLines.add(new Line2d(first.getOriginal(), firstVector));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(
firstVector.y, -firstVector.x)));
double angle = Math.atan2(firstVector.y, firstVector.x);
angle += Math.PI / 4;
double tan = Math.tan(angle);
Vector2d vec = new Vector2d(1, tan);
snapLines.add(new Line2d(first.getOriginal(), vec));
snapLines.add(new Line2d(first.getOriginal(), new Vector2d(vec.y,
-vec.x)));
}
}
for (Line2d snapLine : snapLines) {
Segment2d intersection = segment.getOriginal().getIntersection(snapLine);
if (intersection != null && intersection.isSinglePoint()) {
snapPoints.add(intersection.getP1());
}
}
}
boolean isSnapPoint = false;
for (Point2d snap : snapPoints) {
if (snap.distance(p2) < pxTolerance * onePxInRel) {
p2 = snap;
isSnapPoint = true;
break;
}
}
Point3d p3 = getModelState().locatePointFromPaperTo3D(p2);
Point3d vWorldPoint = new Point3d(p3);
vWorldPoint.scale(origami.getModel().getPaper().getOneRelInMeters());
Group g = pointFactory.createPoint(new ModelPoint(p3, p2, segment), vWorldPoint);
// make snap points look like squares
if (isSnapPoint)
((Shape3D) g.getChild(0)).getAppearance().getPointAttributes()
.setPointAntialiasingEnable(false);
setHighlightedPoint(g);
}
}
}
if (!foundSomething)
clearHighlighted();
preview.repaint();
}
}
};
preview.getRoot().addChild(pick);
preview.attachToUniverse(universe);
}
if (helpPanel == null) {
helpPanel = new HelpPanel(canvas, 10, 10, 256, 256, true, false);
helpPanel.attachToUniverse(universe);
}
}
@Override
protected ModelState getModelState()
{
return step != null ? step.getModelState(true) : null;
}
/**
* Return true if the given point can be selected - either we're not currently selecting the second point of a line,
* or we are, and the given point can be chosen as the end of the line.
*
* @param g The point to check.
* @return True if it can be chosen.
*/
protected boolean canChoosePoint(Group g)
{
if (!isChoosingSecondPoint())
return true;
if (g.getUserData() instanceof ModelPoint) {
ModelPoint start = (ModelPoint) currentChosen.iterator().next().getUserData();
return getModelState().canChooseLine(start, (ModelPoint) g.getUserData());
} else {
return false;
}
}
/**
* Return true if the given point group lies in a selected line or layer.
*
* @param point The point group to check.
* @return true if the given point group lies in a selected line or layer.
*/
protected boolean liesInSelectedLinesOrLayers(Group point)
{
if (selectedLayers.size() + selectedLines.size() == 0)
return true;
ModelPoint p = (ModelPoint) point.getUserData();
Point2d pOrig = p.getOriginal();
for (ModelSegment line : selectedLines) {
if (line.getOriginal().contains(pOrig))
return true;
}
for (Layer layer : selectedLayers) {
if (layer.contains(p))
return true;
}
return false;
}
/**
* @return The type of primitives the user can pick.
*/
public PickMode getPickMode()
{
return pickMode;
}
/**
* @param pickMode The type of primitives the user can pick.
*/
public void setPickMode(PickMode pickMode)
{
PickMode oldPickMode = this.pickMode;
this.pickMode = pickMode;
support.firePropertyChange("pickMode", oldPickMode, pickMode);
}
/**
* Return the selection state of the given item (it is determined on presence of the item in selected, chosen and
* other sets).
*
* @param group The group to find state of.
* @return The state of the item.
*/
protected SelectionState getSelectionState(Group group)
{
boolean isAvailable = (layersForHighlightedPoint != null && layersForHighlightedPoint.contains(group))
|| (layersToChooseFromAsGroups != null && layersToChooseFromAsGroups.contains(group));
SelectionState state;
if (currentChosen.contains(group)) {
if (highlighted == group)
state = SelectionState.CHOSEN_HIGHLIGHTED;
else
state = SelectionState.CHOSEN;
} else if (chosen.contains(group)) {
if (highlighted == group)
state = SelectionState.CHOSEN_HIGHLIGHTED;
else if (isAvailable)
state = SelectionState.AVAILABLE;
else
state = SelectionState.CHOSEN_OLD;
} else if (selected.contains(group)) {
if (highlighted == group)
state = SelectionState.SELECTED_HIGHLIGHTED;
else if (isAvailable)
state = SelectionState.AVAILABLE;
else
state = SelectionState.SELECTED;
} else {
if (highlighted == group)
state = SelectionState.HIGHLIGHTED;
else if (isAvailable)
state = SelectionState.AVAILABLE;
else
state = SelectionState.NORMAL;
}
return state;
}
/**
* Return true if the given item should stay visible or marked (selected/chosen) after it loses highlight.
*
* @param group The item to test.
* @return If the item should stay marked after it loses highlight.
*/
protected boolean isPermanent(Group group)
{
return selected.contains(group) || currentChosen.contains(group) || chosen.contains(group);
}
/**
* Performs the changes needed to make a highlighted item unhighlighted.
*/
protected synchronized void clearHighlighted()
{
if (highlighted == null)
return;
if (highlighted.getUserData() instanceof Layer) {
setHighlightedLayer(null);
} else if (highlighted.getUserData() instanceof ModelSegment) {
setHighlightedLine(null);
} else {
setHighlightedPoint(null);
}
}
/**
* Performs the changes needed to make all selected items unselected.
*/
protected synchronized void clearSelection()
{
if (selected.size() == 0)
return;
// temp is needed because direct usage of selected would lead to ConcurrentModificationException
List<Group> temp = new LinkedList<Group>(selected);
for (Group g : temp) {
deselect(g);
}
}
/**
* Performs the changes needed to make all chosen items unchosen.
*/
protected synchronized void clearChosen()
{
if (chosen.size() == 0)
return;
Set<Group> temp = new HashSet<Group>(chosen);
for (Group g : temp) {
deselect(g, true);
}
// chosen.clear(); //shouldn't be needed
// currentChosen.clear(); //shouldn't be needed
}
/**
* Performs the changes needed to remove all available items.
*/
protected synchronized void clearAvailableItems()
{
if (availableItems.size() == 0)
return;
for (Group g : availableItems) {
if (g.getUserData() instanceof ModelPoint && g != highlighted && !isPermanent(g)) {
((BranchGroup) g).detach();
points.remove(g);
}
}
availableItems.clear();
}
/**
* Performs the changes needed to make a layer highlighted.
*
* If another layer has been highlighted, un-highlight it before highlighting the new one.
*
* If the given layer already has been highlighted, nothing happens.
*
* @param layer The layer to highlight. Pass <code>null</code> to clear the highlight.
*/
protected synchronized void setHighlightedLayer(Group layer)
{
if (layer == highlighted)
return;
if (highlighted != null) {
Group old = highlighted;
highlighted = null;
layerAppearanceManager.setAppearance(old);
}
if (layer != null) {
highlighted = layer;
layerAppearanceManager.setAppearance(layer);
}
if (preview != null)
preview.repaint();
}
/**
* Performs the changes needed to make a line highlighted.
*
* If another line has been highlighted, un-highlight it before highlighting the new one.
*
* If the given line already has been highlighted, nothing happens.
*
* @param line The line to highlight. Pass <code>null</code> to clear the highlight.
*/
protected synchronized void setHighlightedLine(Group line)
{
if (line == highlighted)
return;
if (highlighted != null) {
Group old = highlighted;
highlighted = null;
getLineAppearanceManager().setAppearance(old);
}
if (line != null) {
highlighted = line;
getLineAppearanceManager().setAppearance(line);
}
}
/**
* Performs the changes needed to make a point highlighted.
*
* If another point has been highlighted, un-highlight it before highlighting the new one.
*
* If the given point already has been highlighted, nothing happens.
*
* @param point The point to highlight. Pass <code>null</code> to clear the highlight.
*/
protected synchronized void setHighlightedPoint(Group point)
{
if (point == highlighted)
return;
if (highlighted != null) {
((BranchGroup) highlighted).detach();
points.remove(highlighted);
if (isPermanent(highlighted) || availableItems.contains(highlighted)) {
pointGroup.addChild(highlighted);
points.add(highlighted);
}
Group old = highlighted;
highlighted = null;
pointAppearanceManager.setAppearance(old);
if (currentNewLine != null && (!chosen.contains(currentNewLine.p1) || !chosen.contains(currentNewLine.p2))) {
currentNewLine.detach();
newLines.remove(currentNewLine);
currentNewLine = null;
}
if (layersForHighlightedPoint != null) {
Set<Group> tmp = new HashSet<Group>(layersForHighlightedPoint);
layersForHighlightedPoint = null;
for (Group g : tmp)
layerAppearanceManager.setAppearance(g);
}
}
if (point != null) {
boolean choosingSecondPoint = isChoosingSecondPoint();
// if we select the second point of a line and the line isn't choosable, return
if (!canChoosePoint(point))
return;
highlighted = point;
highlightedPointGroup.moveTo((BranchGroup) highlighted);
pointAppearanceManager.setAppearance(point);
// suggest the layers the point lies in
layersForHighlightedPoint = new HashSet<Group>();
for (@SuppressWarnings("unchecked")
Enumeration<Group> e = layers.getAllChildren(); e.hasMoreElements();) {
Group g = e.nextElement();
if (((Layer) g.getUserData()).contains((Point3d) point.getUserData())) {
layersForHighlightedPoint.add(g);
layerAppearanceManager.setAppearance(g);
}
}
if (choosingSecondPoint) {
Group p1 = currentChosen.iterator().next();
if (p1 != point && p1.getUserData() instanceof ModelPoint) {
ModelSegment segment = new ModelSegment((ModelPoint) p1.getUserData(),
(ModelPoint) point.getUserData(), null, step.getId());
Point3d coord1 = new Point3d(), coord2 = new Point3d();
((PointArray) ((Shape3D) p1.getChild(0)).getGeometry()).getCoordinate(0, coord1);
((PointArray) ((Shape3D) point.getChild(0)).getGeometry()).getCoordinate(0, coord2);
LineArray array = new LineArray(2, LineArray.COORDINATES);
array.setCoordinate(0, coord1);
array.setCoordinate(1, coord2);
array.setUserData(segment);
NewLine line = new NewLine();
line.p1 = p1;
line.p2 = point;
lineFactory.createLine(array, segment, line);
// give the line the highlighted appearance
Group backup = highlighted;
highlighted = line;
currentChosen.add(line);
getLineAppearanceManager().setAppearance(line);
currentChosen.remove(line);
highlighted = backup;
overModel.addChild(line);
currentNewLine = line;
newLines.add(line);
}
}
}
}
/**
* Performs the changes needed to make a layer selected.
*
* If the layer has already been selected, nothing happens.
*
* @param layer The layer to select.
*/
protected synchronized void selectLayer(Group layer)
{
if (!(currentOperationArgument instanceof LayersArgument)) {
if (selected.contains(layer))
return;
selected.add(layer);
selectedLayers.add((Layer) layer.getUserData());
} else {
if (currentChosen.contains(layer))
return;
currentChosen.add(layer);
chosen.add(layer);
}
layerAppearanceManager.setAppearance(layer);
}
/**
* Performs the changes needed to make a selected layer not selected.
*
* If the given layer hasn't been selected, nothing happens.
*
* @param layer The layer to deselect.
*/
protected synchronized void deselectLayer(Group layer)
{
deselectLayer(layer, false);
}
/**
* Performs the changes needed to make a selected layer not selected.
*
* If the given layer hasn't been selected, nothing happens.
*
* @param layer The layer to deselect.
* @param forceUnchoose If true, unchoose the item even if it doesn't belong to currentOperationArgument.
*/
protected synchronized void deselectLayer(Group layer, boolean forceUnchoose)
{
if (!forceUnchoose && !(currentOperationArgument instanceof LayersArgument)) {
if (selected.remove(layer))
selectedLayers.remove(layer.getUserData());
} else {
if (currentChosen.remove(layer) || forceUnchoose) {
while (chosen.remove(layer) && forceUnchoose);
}
}
layerAppearanceManager.setAppearance(layer);
}
/**
* Performs the changes needed to make a line selected.
*
* If the line has already been selected, nothing happens.
*
* @param line The line to select.
*/
protected synchronized void selectLine(Group line)
{
if (!(currentOperationArgument instanceof LineArgument)) {
if (selected.contains(line))
return;
selected.add(line);
selectedLines.add((ModelSegment) line.getUserData());
} else {
if (currentChosen.contains(line))
return;
if (currentChosen.size() > 0 && !(currentOperationArgument instanceof ExistingLinesArgument)) {
helpPanel.showMessage("<html><body><span style=\"font-weight:bold;color:red;\">"
+ new LocalizedString("editor", "StepEditor.tooMuchLines") + "</span></body></html>", 4000,
INCOMPLMETE_ARGUMENT_KEY);
return;
}
currentChosen.add(line);
chosen.add(line);
}
getLineAppearanceManager().setAppearance(line);
}
/**
* Performs the changes needed to make a selected line not selected.
*
* If the given line hasn't been selected, nothing happens.
*
* @param line The line to deselect.
*/
protected synchronized void deselectLine(Group line)
{
deselectLine(line, false);
}
/**
* Performs the changes needed to make a selected line not selected.
*
* If the given line hasn't been selected, nothing happens.
*
* @param line The line to deselect.
* @param forceUnchoose If true, unchoose the item even if it doesn't belong to currentOperationArgument.
*/
protected synchronized void deselectLine(Group line, boolean forceUnchoose)
{
if (!forceUnchoose && !(currentOperationArgument instanceof LineArgument)) {
if (selected.remove(line))
selectedLines.remove(line.getUserData());
} else {
if (currentChosen.remove(line) || forceUnchoose) {
while (chosen.remove(line) && forceUnchoose);
}
}
getLineAppearanceManager().setAppearance(line);
}
/**
* Performs the changes needed to make a point selected.
*
* If the point has already been selected, nothing happens.
*
* @param point The point to select.
*/
protected synchronized void selectPoint(Group point)
{
boolean setAppearance = false;
if (currentOperationArgument == null
|| (currentOperationArgument instanceof ExistingLineArgument || !(currentOperationArgument instanceof LineArgument || currentOperationArgument instanceof PointArgument))) {
if (selected.contains(point))
return;
setAppearance = true;
selected.add(point);
selectedPoints.add((ModelPoint) point.getUserData());
} else {
if (currentChosen.contains(point))
return;
if (currentOperationArgument instanceof PointArgument) {
if (currentChosen.size() > 0) {
helpPanel.showMessage("<html><body><span style=\"font-weight:bold;color:red;\">"
+ new LocalizedString("editor", "StepEditor.tooMuchPoints") + "</span></body></html>",
4000, INCOMPLMETE_ARGUMENT_KEY);
return;
}
} else if (currentOperationArgument instanceof LineArgument
&& !(currentOperationArgument instanceof ExistingLineArgument)) {
if (currentChosen.size() > 1) {
helpPanel.showMessage("<html><body><span style=\"font-weight:bold;color:red;\">"
+ new LocalizedString("editor", "StepEditor.tooMuchPoints") + "</span></body></html>",
4000, INCOMPLMETE_ARGUMENT_KEY);
return;
} else if (currentChosen.size() == 1
&& currentChosen.iterator().next().getUserData() instanceof ModelSegment) {
helpPanel.showMessage("<html><body><span style=\"font-weight:bold;color:red;\">"
+ new LocalizedString("editor", "StepEditor.pointLineMix") + "</span></body></html>", 4000,
INCOMPLMETE_ARGUMENT_KEY);
return;
}
}
setAppearance = true;
chosen.add(point);
currentChosen.add(point);
}
if (setAppearance)
pointAppearanceManager.setAppearance(point);
}
/**
* Performs the changes needed to make a selected point not selected.
*
* If the given point hasn't been selected, nothing happens.
*
* @param point The point to deselect.
*/
protected synchronized void deselectPoint(Group point)
{
deselectPoint(point, false);
}
/**
* Performs the changes needed to make a selected point not selected.
*
* If the given point hasn't been selected, nothing happens.
*
* @param point The point to deselect.
* @param forceUnchoose If true, unchoose the item even if it doesn't belong to currentOperationArgument.
*/
protected synchronized void deselectPoint(Group point, boolean forceUnchoose)
{
boolean setAppearance = false;
if (!forceUnchoose
&& (currentOperationArgument == null || (currentOperationArgument instanceof ExistingLineArgument || !(currentOperationArgument instanceof LineArgument || currentOperationArgument instanceof PointArgument)))) {
if (selected.remove(point)) {
setAppearance = true;
selectedPoints.remove(point.getUserData());
if (point != highlighted) {
if (!availableItems.contains(point)) {
((BranchGroup) point).detach();
points.remove(point);
}
}
}
} else {
if (currentChosen.remove(point) || forceUnchoose) {
setAppearance = true;
while (chosen.remove(point) && forceUnchoose);
}
}
if (setAppearance)
pointAppearanceManager.setAppearance(point);
}
/**
* Make the given object selected (call the appropriate select method based on user data class).
*
* @param group The group to be made selected.
*/
protected synchronized void select(Group group)
{
if (group.getUserData() instanceof Layer)
selectLayer(group);
else if (group.getUserData() instanceof ModelSegment)
selectLine(group);
else if (group.getUserData() instanceof ModelPoint)
selectPoint(group);
}
/**
* Deselct the given object (call the appropriate deselect method based on user data class).
*
* @param group The group to deselect.
*/
protected synchronized void deselect(Group group)
{
deselect(group, false);
}
/**
* Deselct the given object (call the appropriate deselect method based on user data class).
*
* @param group The group to deselect.
* @param forceUnchoose If true, unchoose the item even if it doesn't belong to currentOperationArgument.
*/
protected synchronized void deselect(Group group, boolean forceUnchoose)
{
if (group.getUserData() instanceof Layer)
deselectLayer(group, forceUnchoose);
else if (group.getUserData() instanceof ModelSegment)
deselectLine(group, forceUnchoose);
else if (group.getUserData() instanceof ModelPoint)
deselectPoint(group, forceUnchoose);
}
/**
* Update the transformation matrices used to calculate on-canvas position of a 3D point.
*/
protected void updateTransforms()
{
if (canvas.getView().getViewPlatform() != null) {
ViewInfo viewInfo = new ViewInfo(canvas.getView());
vWorldToImagePlate = new Transform3D();
viewInfo.getImagePlateToVworld(canvas, vWorldToImagePlate, null);
vWorldToImagePlate.invert();
}
}
/**
* Get the on-canvas position of the given vworld point.
*
* This method doesn't alter the passed-in point.
*
* @param point A point in vworld coordinates.
* @return The pixel position of the point on the canvas.
*/
protected Point2d getVworldPointCanvasPosition(Point3d point)
{
return getVworldPointCanvasPosition(point, false);
}
/**
* Get the on-canvas position of the given vworld point.
*
* @param point A point in vworld coordinates.
* @param canAlterArgument If true, the the given point can be altered by this function, otherwise a copy of it is
* created.
* @return The pixel position of the point on the canvas.
*/
protected Point2d getVworldPointCanvasPosition(Point3d point, boolean canAlterArgument)
{
Point3d trans;
if (!canAlterArgument)
trans = new Point3d(point);
else
trans = point;
vWorldToImagePlate.transform(trans);
Point2d res = new Point2d();
canvas.getPixelLocationFromImagePlate(trans, res);
return res;
}
/**
* Get the on-canvas position of the given local point.
*
* This method doesn't alter the passed-in point.
*
* @param point A point in local coordinates.
* @param localToVworld The transform for transforming local coordinates to vworld.
* @return The pixel position of the point on the canvas.
*/
protected Point2d getLocalPointCanvasPosition(Point3d point, Transform3D localToVworld)
{
return getLocalPointCanvasPosition(point, localToVworld, false);
}
/**
* Get the on-canvas position of the given local point.
*
* @param point A point in local coordinates.
* @param localToVworld The transform for transforming local coordinates to vworld.
* @param canAlterArgument If true, the the given point can be altered by this function, otherwise a copy of it is
* created.
* @return The pixel position of the point on the canvas.
*/
protected Point2d getLocalPointCanvasPosition(Point3d point, Transform3D localToVworld, boolean canAlterArgument)
{
Point3d trans;
if (!canAlterArgument)
trans = new Point3d(point);
else
trans = point;
localToVworld.transform(trans);
return getVworldPointCanvasPosition(trans, true);
}
/**
* Get the on-canvas position of the given point.
*
* The given group must contain a child node with index 0 having its Geometry of type PointArray.
*
* @param point A point group.
* @return The pixel position of the point on the canvas.
*/
protected Point2d getPointCanvasPosition(Group point)
{
// only live points have on-screen position
if (!point.isLive())
return new Point2d(0, 0);
Point3d gPoint = new Point3d();
((PointArray) ((Shape3D) point.getChild(0)).getGeometry()).getCoordinate(0, gPoint);
Transform3D localToVworld = new Transform3D();
point.getLocalToVworld(localToVworld);
return getLocalPointCanvasPosition(gPoint, localToVworld, true);
}
/**
* @param currentOperationArgument The operation argument the editor fetches data for.
*/
public synchronized void setCurrentOperationArgument(OperationArgument currentOperationArgument)
{
this.currentOperationArgument = currentOperationArgument;
Set<Group> currChosen = new HashSet<Group>(currentChosen);
this.currentChosen.clear();
// set the old look to former current items
for (Group g : currChosen) {
if (g.getUserData() instanceof Layer)
layerAppearanceManager.setAppearance(g);
else if (g.getUserData() instanceof ModelSegment)
getLineAppearanceManager().setAppearance(g);
else if (g.getUserData() instanceof ModelPoint)
pointAppearanceManager.setAppearance(g);
}
if (currentOperationArgument instanceof LayersArgument) {
LayersArgument arg = (LayersArgument) currentOperationArgument;
if (arg.isRequired() || arg.isDefLineComplete()) {
layersToChooseFrom = new LinkedList<Layer>(getModelState().getLayers(
((LayersArgument) currentOperationArgument).getDefSegment()).keySet());
// layersToChooseFromAsGroups have to be sorted in the same way as layersToChooseFrom
layersToChooseFromAsGroups = new LinkedList<Group>();
// hash set for quick search
Set<Layer> cache = new HashSet<Layer>(layersToChooseFrom);
Hashtable<Layer, Group> dictionary = new Hashtable<Layer, Group>(layersToChooseFrom.size());
for (@SuppressWarnings("unchecked")
Enumeration<Group> e = layers.getAllChildren(); e.hasMoreElements();) {
Group g = e.nextElement();
if (cache.contains(g.getUserData()))
dictionary.put((Layer) g.getUserData(), g);
}
for (Layer l : layersToChooseFrom) {
Group g = dictionary.get(l);
layersToChooseFromAsGroups.add(g);
layerAppearanceManager.setAppearance(g);
}
}
} else {
layersToChooseFrom = null;
if (layersToChooseFromAsGroups != null) {
Set<Group> tmp = new HashSet<Group>(layersToChooseFromAsGroups);
layersToChooseFromAsGroups = null;
for (Group g : tmp)
layerAppearanceManager.setAppearance(g);
}
}
if (currentOperationArgument != null && currentOperationArgument.preferredPickMode() != null)
setPickMode(currentOperationArgument.preferredPickMode());
if (currentOperationArgument != null && currentOperationArgument.getL7dUserTip() != null) {
helpPanel.showMessage(currentOperationArgument.getL7dUserTip(), OPERATION_ARGUMENT_KEY);
} else {
helpPanel.removeMessage(OPERATION_ARGUMENT_KEY);
helpPanel.removeMessage(INCOMPLMETE_ARGUMENT_KEY);
}
}
/**
* @return If a line is chosen, return it, otherwise return <code>null</code>.
*/
public synchronized ModelSegment getChosenLine()
{
if (currentChosen.size() == 0 || step == null)
return null;
if (currentChosen.size() == 1) {
Group chosen = currentChosen.iterator().next();
if (chosen.getUserData() instanceof ModelSegment)
return (ModelSegment) chosen.getUserData();
else
return null;
}
Iterator<Group> it = currentChosen.iterator();
Group g1 = it.next();
Group g2 = it.next();
it = null;
try {
ModelPoint p1 = (ModelPoint) g1.getUserData();
ModelPoint p2 = (ModelPoint) g2.getUserData();
return new ModelSegment(p1, p2, null, step.getId());
} catch (ClassCastException e) {
return null;
}
}
/**
* @return If an existing line is chosen, return it, otherwise return <code>null</code>.
*/
public synchronized ModelSegment getChosenExistingLine()
{
if (currentChosen.size() == 0)
return null;
Group chosen = currentChosen.iterator().next();
if (chosen.getUserData() instanceof ModelSegment)
return (ModelSegment) chosen.getUserData();
return null;
}
/**
* @return If some existing lines are chosen, return them, otherwise return <code>null</code>.
*/
public synchronized List<ModelSegment> getChosenExistingLines()
{
if (currentChosen.size() == 0)
return null;
List<ModelSegment> result = new LinkedList<ModelSegment>();
for (Group chosen : currentChosen) {
if (chosen.getUserData() instanceof ModelSegment)
result.add((ModelSegment) chosen.getUserData());
}
return result.size() > 0 ? result : null;
}
/**
* @return If a point is chosen, return it, otherwise return <code>null</code>.
*/
public synchronized ModelPoint getChosenPoint()
{
if (currentChosen.size() == 0)
return null;
Group chosen = currentChosen.iterator().next();
if (chosen.getUserData() instanceof ModelPoint)
return (ModelPoint) chosen.getUserData();
return null;
}
/**
* @return If some layers are chosen, return them, otherwise return <code>null</code>.
*/
public synchronized List<Integer> getChosenLayers()
{
if (currentChosen.size() == 0 || layersToChooseFrom == null)
return null;
Set<Layer> chosenLayers = new HashSet<Layer>();
for (Group g : currentChosen) {
if (g.getUserData() instanceof Layer)
chosenLayers.add((Layer) g.getUserData());
}
List<Integer> result = new LinkedList<Integer>();
int i = 1;
for (Layer l : layersToChooseFrom) {
if (chosenLayers.contains(l))
result.add(i);
i++;
}
return result;
}
/**
* Clear all chosen items.
*/
public void clearChosenItems()
{
clearChosen();
}
/**
* Selects or chooses all items from availableItems.
*/
public void selectAllAvailableItems()
{
for (Group g : availableItems) {
if (!isPermanent(g))
select(g);
}
}
/**
* @return True if the user is currently choosing the second point of a line.
*/
public synchronized boolean isChoosingSecondPoint()
{
return currentOperationArgument != null && step != null && pickMode == PickMode.POINT
&& currentChosen.size() == 1 && currentOperationArgument.getClass() == LineArgument.class;
}
/**
* @return The message bar that can be used to display some text to the user.
*/
public ExtendedMessageBar getMessageBar()
{
return helpPanel;
}
@Override
protected ColorManager createColorManager(Color background, Color foreground)
{
return (ColorManager) (colorManager = new ColorManager(background == null ? Color.WHITE : background,
foreground == null ? Color.white : foreground));
}
@Override
protected ColorManager getColorManager()
{
return (ColorManager) super.getColorManager();
}
@Override
protected LineAppearanceManager createLineAppearanceManager()
{
return (LineAppearanceManager) (lineAppearanceManager = new LineAppearanceManager());
}
@Override
protected LineAppearanceManager getLineAppearanceManager()
{
return (LineAppearanceManager) super.getLineAppearanceManager();
}
@Override
public double getZoom()
{
if (step != null)
return step.getZoom();
return zoom;
}
@Override
public void setZoom(double zoom)
{
if (zoom < 25d)
return;
if (step != null) {
double oldZoom = step.getZoom();
step.setZoom(zoom);
support.firePropertyChange("zoom", oldZoom, zoom);
Transform3D additional = new Transform3D(transform);
Transform3D baseInverted = new Transform3D(baseTransform);
baseInverted.invert();
additional.mul(baseInverted);
setupTransform();
transform.mul(additional, transform);
if (tGroup != null)
tGroup.setTransform(transform);
}
}
protected class HighlightNextItemAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -3089302395435461134L;
@Override
public void actionPerformed(ActionEvent e)
{
List<Group> items;
if (layersToChooseFrom == null) {
items = availableItems;
} else {
items = new LinkedList<Group>(availableItems);
items.retainAll(layersToChooseFromAsGroups);
}
int selIndex = items.indexOf(highlighted);
if (selIndex > -1) {
for (int i = 1; i <= items.size(); i++) {
selIndex = (selIndex + 1) % items.size();
// BUG sometimes, only this direction and if pickMode is LAYER, the selection just switches between
// two layers instead of continuing to the next layers
switch (pickMode) {
case POINT:
Group point = items.get(selIndex);
// we could filter items the same way as we do with layersToChooseFrom, but canChoosePoint
// is computationally demanding, so we don't want to compute it for all available items, but
// just for those we really need to know
if (canChoosePoint(point)) {
setHighlightedPoint(point);
return;
}
break;
case LINE:
setHighlightedLine(items.get(selIndex));
return;
case LAYER:
setHighlightedLayer(items.get(selIndex));
return;
}
}
}
}
}
protected class HighlightPreviousItemAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -3089302395435461134L;
@Override
public void actionPerformed(ActionEvent e)
{
List<Group> items;
if (layersToChooseFrom == null) {
items = availableItems;
} else {
items = new LinkedList<Group>(availableItems);
items.retainAll(layersToChooseFromAsGroups);
}
int selIndex = items.indexOf(highlighted);
if (selIndex > -1) {
for (int i = 1; i <= items.size(); i++) {
selIndex = selIndex - 1;
if (selIndex <= -1)
selIndex += items.size();
switch (pickMode) {
case POINT:
Group point = items.get(selIndex);
// we could filter items the same way as we do with layersToChooseFrom, but canChoosePoint
// is computationally demanding, so we don't want to compute it for all available items, but
// just for those we really need to know
if (canChoosePoint(point)) {
setHighlightedPoint(point);
return;
}
break;
case LINE:
setHighlightedLine(items.get(selIndex));
return;
case LAYER:
setHighlightedLayer(items.get(selIndex));
return;
}
}
}
}
}
protected class TogglePickModeAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -5513138483903360858L;
@Override
public void actionPerformed(ActionEvent e)
{
if (currentOperationArgument == null) {
setPickMode(pickMode.getNext());
} else {
PickMode mode = pickMode.getNext();
if ((currentOperationArgument instanceof ExistingLineArgument) && mode == PickMode.POINT)
mode = mode.getNext();
if (currentOperationArgument instanceof LayersArgument)
mode = PickMode.LAYER;
setPickMode(mode);
}
}
}
protected class ToggleHighlightedItemSelectionAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -1297141033881147805L;
@Override
public void actionPerformed(ActionEvent e)
{
if (highlighted == null)
return;
boolean deselect = (selected.contains(highlighted) && currentOperationArgument == null)
|| currentChosen.contains(highlighted);
switch (pickMode) {
case POINT:
if (deselect) {
deselectPoint(highlighted);
} else {
selectPoint(highlighted);
}
break;
case LINE:
if (deselect) {
deselectLine(highlighted);
} else {
selectLine(highlighted);
}
break;
case LAYER:
if (deselect) {
deselectLayer(highlighted);
} else {
selectLayer(highlighted);
}
break;
}
}
}
protected class ClearSelectionAction extends AbstractAction
{
/** */
private static final long serialVersionUID = 7889712823950928127L;
@Override
public void actionPerformed(ActionEvent e)
{
clearSelection();
}
}
protected class SelectAllAvailableItemsAction extends AbstractAction
{
/** */
private static final long serialVersionUID = -533535285017641260L;
@Override
public void actionPerformed(ActionEvent e)
{
selectAllAvailableItems();
}
}
/**
* Mouse event and picking handling.
*
* @author Martin Pecka
*/
protected class MouseListener extends MouseAdapter
{
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
e.consume();
int steps = e.getWheelRotation();
if (steps == 0)
return;
if (!(e.isControlDown() || (e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) > 0)
&& availableItems.size() > 1 && highlighted != null) {
// perform selection among available items
if (steps > 0) {
Action action = new HighlightPreviousItemAction();
ActionEvent event = new ActionEvent(StepEditingCanvasController.this, ActionEvent.ACTION_FIRST,
"highlightNextItem");
action.actionPerformed(event);
} else if (steps < 0) {
Action action = new HighlightNextItemAction();
ActionEvent event = new ActionEvent(StepEditingCanvasController.this, ActionEvent.ACTION_FIRST,
"highlightPreviousItem");
action.actionPerformed(event);
}
if (preview != null)
preview.repaint();
}
}
@Override
public void mouseClicked(MouseEvent e)
{
if (e.getButton() == MouseEvent.BUTTON1 && highlighted != null) {
if (!e.isControlDown()) {
Action action = new ToggleHighlightedItemSelectionAction();
ActionEvent event = new ActionEvent(StepEditingCanvasController.this, ActionEvent.ACTION_FIRST,
"toggleHighlightedItemSelection");
action.actionPerformed(event);
} else {
Action action = new SelectAllAvailableItemsAction();
ActionEvent event = new ActionEvent(StepEditingCanvasController.this, ActionEvent.ACTION_FIRST,
"selectAllAvailableItems");
action.actionPerformed(event);
}
if (preview != null)
preview.repaint();
} else if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() >= 2) {
Action action = new ClearSelectionAction();
ActionEvent event = new ActionEvent(StepEditingCanvasController.this, ActionEvent.ACTION_FIRST,
"clearSelection");
action.actionPerformed(event);
if (preview != null)
preview.repaint();
}
}
@Override
public void mousePressed(MouseEvent e)
{
int mods = e.getModifiersEx();
int middle = MouseEvent.BUTTON2_DOWN_MASK;
int both = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK;
// either a middle button click or left+right button simultaneous click
if (((mods & middle) == middle) || ((mods & both) == both)) {
Action action = new TogglePickModeAction();
ActionEvent event = new ActionEvent(StepEditingCanvasController.this, ActionEvent.ACTION_FIRST,
"togglePickMode");
action.actionPerformed(event);
if (preview != null)
preview.repaint();
}
}
}
/**
* A state of a selected item.
*
* @author Martin Pecka
*/
protected enum SelectionState
{
/** Normal appearance. */
NORMAL,
/** Highlighted item. */
HIGHLIGHTED,
/** Selected non-highlighted item. */
SELECTED,
/** Selected highlighted item. */
SELECTED_HIGHLIGHTED,
/** Chosen item. */
CHOSEN,
/** Chosen highlighted item. */
CHOSEN_HIGHLIGHTED,
/** Chosen item that isn't in currentChosen. */
CHOSEN_OLD,
/** An available (or suggested) item. */
AVAILABLE
}
/**
* A manager for changing layer appearance.
*
* @author Martin Pecka
*/
protected class LayerAppearanceManager
{
/**
* Apply the appropriate appearance on the given layer.
*
* @param layer The layer to apply the appearance on.
*/
protected void setAppearance(Group layer)
{
assert layer.numChildren() == 2;
Enumeration<?> children = layer.getAllChildren();
Shape3D shape = (Shape3D) children.nextElement();
Appearance app = shape.getAppearance();
SelectionState state;
switch (state = getSelectionState(layer)) {
case NORMAL:
app.getColoringAttributes().setColor(getColorManager().getForeground3f());
app.getPolygonAttributes().setPolygonOffset(0f);
app.getPolygonAttributes().setPolygonOffsetFactor(0f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getHighlightFg3f());
app.getPolygonAttributes().setPolygonOffset(-20000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-20000f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case SELECTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedFg3f());
app.getPolygonAttributes().setPolygonOffset(-10000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-10000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
case SELECTED_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedHighlightFg3f());
app.getPolygonAttributes().setPolygonOffset(-20000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-20000f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case CHOSEN_OLD:
app.getColoringAttributes().setColor(getColorManager().getChosenOldFg3f());
app.getPolygonAttributes().setPolygonOffset(-15000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-15000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
case CHOSEN:
app.getColoringAttributes().setColor(getColorManager().getChosenFg3f());
app.getPolygonAttributes().setPolygonOffset(-11000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-11000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
case CHOSEN_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getChosenHighlightFg3f());
app.getPolygonAttributes().setPolygonOffset(-20000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-20000f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case AVAILABLE:
app.getColoringAttributes().setColor(getColorManager().getAvailableLayer3f());
app.getPolygonAttributes().setPolygonOffset(-16000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-16000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
}
shape = (Shape3D) children.nextElement();
app = shape.getAppearance();
switch (state) {
case NORMAL:
app.getColoringAttributes().setColor(getColorManager().getBackground3f());
app.getPolygonAttributes().setPolygonOffset(0f);
app.getPolygonAttributes().setPolygonOffsetFactor(0f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getHighlightBg3f());
app.getPolygonAttributes().setPolygonOffset(-20000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-20000f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case SELECTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedBg3f());
app.getPolygonAttributes().setPolygonOffset(-10000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-10000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
case SELECTED_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedHighlightBg3f());
app.getPolygonAttributes().setPolygonOffset(-20000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-20000f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case CHOSEN_OLD:
app.getColoringAttributes().setColor(getColorManager().getChosenOldBg3f());
app.getPolygonAttributes().setPolygonOffset(-15000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-15000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
case CHOSEN:
app.getColoringAttributes().setColor(getColorManager().getChosenBg3f());
app.getPolygonAttributes().setPolygonOffset(-11000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-11000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
case CHOSEN_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getChosenHighlightBg3f());
app.getPolygonAttributes().setPolygonOffset(-20000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-20000f);
app.getTransparencyAttributes().setTransparency(0f);
break;
case AVAILABLE:
app.getColoringAttributes().setColor(getColorManager().getAvailableLayer3f());
app.getPolygonAttributes().setPolygonOffset(-16000f);
app.getPolygonAttributes().setPolygonOffsetFactor(-16000f);
app.getTransparencyAttributes().setTransparency(0.3f);
break;
}
}
}
/**
* A manager for changing point appearance.
*
* @author Martin Pecka
*/
protected class PointAppearanceManager
{
/**
* Apply the appropriate appearance on the given point.
*
* @param point The point to apply the appearance on.
*/
protected void setAppearance(Group point)
{
assert point.numChildren() == 1;
Enumeration<?> children = point.getAllChildren();
Shape3D shape = (Shape3D) children.nextElement();
Appearance app = shape.getAppearance();
switch (getSelectionState(point)) {
case NORMAL:
app.getRenderingAttributes().setVisible(false);
break;
case HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getHighlightedPoint3f());
app.getTransparencyAttributes().setTransparency(0f);
app.getRenderingAttributes().setVisible(true);
break;
case SELECTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedPoint3f());
app.getTransparencyAttributes().setTransparency(0.3f);
app.getRenderingAttributes().setVisible(true);
break;
case SELECTED_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedHighlightedPoint3f());
app.getTransparencyAttributes().setTransparency(0f);
app.getRenderingAttributes().setVisible(true);
break;
case CHOSEN_OLD:
app.getColoringAttributes().setColor(getColorManager().getChosenOldPoint3f());
app.getTransparencyAttributes().setTransparency(0.3f);
app.getRenderingAttributes().setVisible(true);
break;
case CHOSEN:
app.getColoringAttributes().setColor(getColorManager().getChosenPoint3f());
app.getTransparencyAttributes().setTransparency(0.3f);
app.getRenderingAttributes().setVisible(true);
break;
case CHOSEN_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getChosenHighlightedPoint3f());
app.getTransparencyAttributes().setTransparency(0f);
app.getRenderingAttributes().setVisible(true);
break;
}
}
}
/**
* A factory for creating point groups from model points.
*
* @author Martin Pecka
*/
protected class PointFactory
{
/**
* Create the group from the given point.
*
* @param point The source point.
* @param position The position of the point in local coordinates.
* @return The group.
*/
public Group createPoint(ModelPoint point, Point3d position)
{
final BranchGroup group = new BranchGroup();
group.setUserData(point);
group.setCapability(Shape3D.ENABLE_PICK_REPORTING);
group.setCapability(BranchGroup.ALLOW_DETACH);
group.setCapability(BranchGroup.ALLOW_PARENT_READ);
group.setPickable(true);
final float pointSize = 10f;
final Appearance app = new Appearance();
app.setPointAttributes(new PointAttributes());
app.getPointAttributes().setPointAntialiasingEnable(true);
app.getPointAttributes().setPointSize(pointSize * (float) getCompositeZoom() / 100f);
app.getPointAttributes().setCapability(PointAttributes.ALLOW_SIZE_WRITE);
app.getPointAttributes().setCapability(PointAttributes.ALLOW_ANTIALIASING_WRITE);
final PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt)
{
app.getPointAttributes().setPointSize(pointSize * (float) getCompositeZoom() / 100f);
}
};
addPropertyChangeListener("zoom", listener);
removeListenersCallbacks.add(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception
{
if (group.getParent() == null) {
removePropertyChangeListener("zoom", listener);
return true;
}
return false;
}
});
app.setColoringAttributes(new ColoringAttributes());
app.getColoringAttributes().setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
app.setTransparencyAttributes(new TransparencyAttributes());
app.getTransparencyAttributes().setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
app.setRenderingAttributes(new RenderingAttributes());
app.getRenderingAttributes().setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.ALWAYS);
app.getRenderingAttributes().setVisible(false);
PointArray array = new PointArray(1, PointArray.COORDINATES);
array.setCoordinate(0, position);
group.addChild(new Shape3D(array, app));
group.compile();
return group;
}
}
/**
* A factory for creating point groups from model lines.
*
* @author Martin Pecka
*/
protected class LineFactory
{
/**
* Create the group from the given line.
*
* @param array The LineArray defining the line.
* @param segment The ModelSegment defining the line.
* @return The group.
*/
public BranchGroup createLine(LineArray array, ModelSegment segment)
{
return createLine(array, segment, null);
}
/**
* Create the group from the given line.
*
* @param array The LineArray defining the line.
* @param segment The ModelSegment defining the line.
* @param branchGroup The group to add the point to. If you pass <code>null</code>, a new group is created.
* @return The group.
*/
public BranchGroup createLine(LineArray array, ModelSegment segment, BranchGroup branchGroup)
{
BranchGroup group;
if (branchGroup == null)
group = new BranchGroup();
else
group = branchGroup;
group.setBoundsAutoCompute(true);
group.setUserData(segment);
group.setPickable(true);
group.setCapability(Shape3D.ENABLE_PICK_REPORTING);
group.setCapability(BranchGroup.ALLOW_DETACH);
group.setCapability(BranchGroup.ALLOW_PARENT_READ);
Appearance appearance = createBasicLinesAppearance();
getLineAppearanceManager().alterBasicAppearance(appearance, segment.getDirection(),
step.getId() - segment.getOriginatingStepId());
Shape3D shape = new Shape3D(array, appearance);
group.addChild(shape);
group.compile();
return group;
}
}
/**
* A manager for changing line appearance.
*
* @author Martin Pecka
*/
protected class LineAppearanceManager extends StepViewingCanvasController.LineAppearanceManager
{
/**
* Apply the appropriate appearance on the given line.
*
* @param line The line to apply the appearance on.
*/
protected void setAppearance(Group line)
{
assert line.numChildren() == 1;
Enumeration<?> children = line.getAllChildren();
Shape3D shape = (Shape3D) children.nextElement();
ModelSegment seg = (ModelSegment) line.getUserData();
Appearance app = shape.getAppearance();
Direction dir = seg.getDirection();
int age = step.getId() - seg.getOriginatingStepId();
SelectionState state = getSelectionState(line);
if (line instanceof NewLine && state == SelectionState.NORMAL)
state = SelectionState.CHOSEN;
switch (state) {
case NORMAL:
app.getColoringAttributes().setColor(getColorManager().getLine3f());
app.getTransparencyAttributes().setTransparency(0f);
app.getRenderingAttributes().setVisible(seg.getOriginatingStepId() != step.getId());
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.LESS_OR_EQUAL);
app.getLineAttributes().setLineWidth(getLineWidth(dir, age) * (float) getCompositeZoom() / 100f);
if (line.getParent() == overModel) {
lines.moveTo((BranchGroup) line);
}
break;
case HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getHighlightedLine3f());
app.getTransparencyAttributes().setTransparency(0f);
app.getRenderingAttributes().setVisible(true);
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.ALWAYS);
app.getLineAttributes().setLineWidth(
2.5f * getLineWidth(dir, age) * (float) getCompositeZoom() / 100f);
if (line.getParent() == lines) {
overModel.moveTo((BranchGroup) line);
}
break;
case SELECTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedLine3f());
app.getTransparencyAttributes().setTransparency(0.3f);
app.getRenderingAttributes().setVisible(true);
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.ALWAYS);
app.getLineAttributes().setLineWidth(
2f * getLineWidth(dir, age) * (float) getCompositeZoom() / 100f);
if (line.getParent() == lines) {
overModel.moveTo((BranchGroup) line);
}
break;
case SELECTED_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getSelectedHighlightedLine3f());
app.getTransparencyAttributes().setTransparency(0f);
app.getRenderingAttributes().setVisible(true);
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.ALWAYS);
app.getLineAttributes().setLineWidth(
2f * getLineWidth(dir, age) * (float) getCompositeZoom() / 100f);
if (line.getParent() == lines) {
overModel.moveTo((BranchGroup) line);
}
break;
case CHOSEN_OLD:
app.getColoringAttributes().setColor(getColorManager().getChosenOldLine3f());
app.getTransparencyAttributes().setTransparency(0.3f);
app.getRenderingAttributes().setVisible(true);
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.ALWAYS);
app.getLineAttributes().setLineWidth(
2f * getLineWidth(dir, age) * (float) getCompositeZoom() / 100f);
if (line.getParent() == lines) {
overModel.moveTo((BranchGroup) line);
}
break;
case CHOSEN:
app.getColoringAttributes().setColor(getColorManager().getChosenLine3f());
app.getTransparencyAttributes().setTransparency(0.3f);
app.getRenderingAttributes().setVisible(true);
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.ALWAYS);
app.getLineAttributes().setLineWidth(
2f * getLineWidth(dir, age) * (float) getCompositeZoom() / 100f);
if (line.getParent() == lines) {
overModel.moveTo((BranchGroup) line);
}
break;
case CHOSEN_HIGHLIGHTED:
app.getColoringAttributes().setColor(getColorManager().getChosenHighlightedLine3f());
app.getTransparencyAttributes().setTransparency(0f);
app.getRenderingAttributes().setVisible(true);
app.getRenderingAttributes().setDepthTestFunction(RenderingAttributes.ALWAYS);
app.getLineAttributes().setLineWidth(
2.5f * getLineWidth(dir, age) * (float) getCompositeZoom() / 100f);
if (line.getParent() == lines) {
overModel.moveTo((BranchGroup) line);
}
break;
}
}
}
/**
* Manager of all colors used in this {@link StepRenderer}.
*
* @author Martin Pecka
*/
protected class ColorManager extends StepViewingCanvasController.ColorManager
{
/** Highlighted layer color for foreground/background. */
protected Color highlightBg = new Color(150, 150, 255), highlightFg = new Color(150, 150, 255);
/** Selected layer color for foreground/background. */
protected Color selectedBg = new Color(100, 100, 200), selectedFg = new Color(100, 100, 200);
/** Highlighted selected layer color for foreground/background. */
protected Color selectedHighlightBg = new Color(125, 125, 225), selectedHighlightFg = new Color(125, 125,
225);
/** Color of a highlighted fold line. */
protected Color highlightedLine = new Color(75, 75, 150);
/** Color of a selected fold line. */
protected Color selectedLine = new Color(25, 25, 100);
/** Color of a selected highlighted fold line. */
protected Color selectedHighlightedLine = new Color(50, 50, 125);
/** Color of a highlighted point. */
protected Color highlightedPoint = new Color(0, 0, 75);
/** Color of a selected point. */
protected Color selectedPoint = new Color(50, 125, 50);
/** Color of a selected highlighted point. */
protected Color selectedHighlightedPoint = new Color(100, 200, 100);
/** Chosen layer color for foreground/background. */
protected Color chosenBg = new Color(100, 255, 100), chosenFg = new Color(150, 255, 150);
/** Highlighted chosen layer color for foreground/background. */
protected Color chosenHighlightBg = new Color(150, 255, 150), chosenHighlightFg = new Color(150, 255,
150);
/** Old chosen layer color for foreground/background. */
protected Color chosenOldBg = new Color(100, 200, 100), chosenOldFg = new Color(100, 200, 100);
/** Color of a chosen fold line. */
protected Color chosenLine = new Color(100, 255, 200);
/** Color of a highlighted chosen fold line. */
protected Color chosenHighlightedLine = new Color(150, 255, 250);
/** Color of an old chosen fold line. */
protected Color chosenOldLine = new Color(100, 200, 150);
/** Color of a chosen point. */
protected Color chosenPoint = new Color(200, 255, 100);
/** Color of a highlighted chosen point. */
protected Color chosenHighlightedPoint = new Color(250, 255, 150);
/** Color of an old chosen point. */
protected Color chosenOldPoint = new Color(150, 200, 100);
/** Color of an available layer. */
protected Color availableLayer = new Color(255, 130, 0);
/**
* @param background Paper background color.
* @param foreground Paper foreground color.
*/
public ColorManager(Color background, Color foreground)
{
super(background, foreground);
ensureColorsAreContrasting();
}
/**
* Call this method to make sure
*/
public void ensureColorsAreContrasting()
{
// TODO May implement a cleverer algorithm, or just allow to set these colors from diagram.
// This algorithm doesn't check if the highlight and selectedHighlight colors aren't the same, which would
// be a problem for program usability.
highlightBg = getContrastingColor(background, highlightBg);
highlightFg = getContrastingColor(foreground, highlightFg);
selectedHighlightBg = getContrastingColor(selectedBg, selectedHighlightBg);
selectedHighlightFg = getContrastingColor(selectedFg, selectedHighlightFg);
}
/**
* Get the well contrasting color for the specified reference color.
*
* @param refColor The reference color that can be used for contrast computations etc.
* @param color The color we want to be contrasting with refColor.
*
* @return If color has enough contrast, return it, otherwise return a more contrasting color (if color is
* brighter than refColor, than an even brighter color will be returned, and conversely).
*/
protected Color getContrastingColor(Color refColor, Color color)
{
Color result = color;
// if the color difference is sufficient, we can return
final int colDiff = abs(result.getRed() - refColor.getRed()) + abs(result.getGreen() - refColor.getGreen())
+ abs(result.getBlue() - refColor.getBlue());
// WCAG suggests 500, but we don't need such a large contrast here
if (colDiff > 180)
return result;
// if the contrast with the reference color would be too low, change the resulting color to be more
// contrasting
float[] hsbRef = new float[3];
float[] hsbRes = new float[3];
Color.RGBtoHSB(refColor.getRed(), refColor.getGreen(), refColor.getBlue(), hsbRef);
Color.RGBtoHSB(result.getRed(), result.getGreen(), result.getBlue(), hsbRes);
final float threshold = 0.2f;
final float diff = hsbRef[2] - hsbRes[2];
if (diff > -threshold && diff <= 0f) {
float bright;
if (hsbRef[2] + threshold <= 1f)
bright = hsbRef[2] + threshold;
else
bright = hsbRef[2] - threshold;
result = Color.getHSBColor(hsbRes[0], hsbRes[1], bright);
} else if (diff < threshold && diff >= 0f) {
float bright;
if (hsbRef[2] > threshold)
bright = hsbRef[2] - threshold;
else
bright = hsbRef[2] + threshold;
result = Color.getHSBColor(hsbRes[0], hsbRes[1], bright);
}
return result;
}
/**
* @return Highlighted layer background color.
*/
public Color getHighlightBg()
{
return highlightBg;
}
/**
* @return Highlighted layer background color.
*/
public Color3f getHighlightBg3f()
{
return new Color3f(highlightBg);
}
/**
* @param highlightBg Highlighted layer background color.
*/
public void setHighlightBg(Color highlightBg)
{
this.highlightBg = highlightBg;
}
/**
* @return Highlighted layer foreground color.
*/
public Color getHighlightFg()
{
return highlightFg;
}
/**
* @return Highlighted layer foreground color.
*/
public Color3f getHighlightFg3f()
{
return new Color3f(highlightFg);
}
/**
* @param highlightFg Highlighted layer foreground color.
*/
public void setHighlightFg(Color highlightFg)
{
this.highlightFg = highlightFg;
}
/**
* @return Selected layer background color.
*/
public Color getSelectedBg()
{
return selectedBg;
}
/**
* @return Selected layer background color.
*/
public Color3f getSelectedBg3f()
{
return new Color3f(selectedBg);
}
/**
* @param selectedBg Selected layer background color.
*/
public void setSelectedBg(Color selectedBg)
{
this.selectedBg = selectedBg;
}
/**
* @return Selected layer foreground color.
*/
public Color getSelectedFg()
{
return selectedFg;
}
/**
* @return Selected layer foreground color.
*/
public Color3f getSelectedFg3f()
{
return new Color3f(selectedFg);
}
/**
* @param selectedFg Selected layer foreground color.
*/
public void setSelectedFg(Color selectedFg)
{
this.selectedFg = selectedFg;
}
/**
* @return Highlighted selected layer background color.
*/
public Color getSelectedHighlightBg()
{
return selectedHighlightBg;
}
/**
* @return Highlighted selected layer background color.
*/
public Color3f getSelectedHighlightBg3f()
{
return new Color3f(selectedHighlightBg);
}
/**
* @param selectedHighlightBg Highlighted selected layer background color.
*/
public void setSelectedHighlightBg(Color selectedHighlightBg)
{
this.selectedHighlightBg = selectedHighlightBg;
}
/**
* @return Highlighted selected layer foreground color.
*/
public Color getSelectedHighlightFg()
{
return selectedHighlightFg;
}
/**
* @return Highlighted selected layer foreground color.
*/
public Color3f getSelectedHighlightFg3f()
{
return new Color3f(selectedHighlightFg);
}
/**
* @param selectedHighlightFg Highlighted selected layer foreground color.
*/
public void setSelectedHighlightFg(Color selectedHighlightFg)
{
this.selectedHighlightFg = selectedHighlightFg;
}
/**
* Set both background/foreground colors for highlighted layer.
*
* @param highlight Highlighted layer both colors.
*/
public void setHighlight(Color highlight)
{
this.highlightFg = highlight;
this.highlightBg = highlight;
}
/**
* Set both background/foreground colors for selected layer.
*
* @param selected Selected layer both colors.
*/
public void setSelected(Color selected)
{
this.selectedFg = selected;
this.selectedBg = selected;
}
/**
* Set both background/foreground colors for highlighted selected layer.
*
* @param selectedHighlight Highlighted selected layer both colors.
*/
public void setSelectedHighlight(Color selectedHighlight)
{
this.selectedHighlightFg = selectedHighlight;
this.selectedHighlightBg = selectedHighlight;
}
/**
* @return Color of a highlighted fold line.
*/
public Color getHighlightedLine()
{
return highlightedLine;
}
/**
* @return Color of a highlighted fold line.
*/
public Color3f getHighlightedLine3f()
{
return new Color3f(highlightedLine);
}
/**
* @param highlightedLine Color of a highlighted fold line.
*/
public void setHighlightedLine(Color highlightedLine)
{
this.highlightedLine = highlightedLine;
}
/**
* @return Color of a selected fold line.
*/
public Color getSelectedLine()
{
return selectedLine;
}
/**
* @return Color of a selected fold line.
*/
public Color3f getSelectedLine3f()
{
return new Color3f(selectedLine);
}
/**
* @param selectedLine Color of a selected fold line.
*/
public void setSelectedLine(Color selectedLine)
{
this.selectedLine = selectedLine;
}
/**
* @return Color of a selected highlighted fold line.
*/
public Color getSelectedHighlightedLine()
{
return selectedHighlightedLine;
}
/**
* @return Color of a selected highlighted fold line.
*/
public Color3f getSelectedHighlightedLine3f()
{
return new Color3f(selectedHighlightedLine);
}
/**
* @param selectedHighlightedLine Color of a selected highlighted fold line.
*/
public void setSelectedHighlightedLine(Color selectedHighlightedLine)
{
this.selectedHighlightedLine = selectedHighlightedLine;
}
/**
* @return Color of a highlighted point.
*/
public Color getHighlightedPoint()
{
return highlightedPoint;
}
/**
* @return Color of a highlighted point.
*/
public Color3f getHighlightedPoint3f()
{
return new Color3f(highlightedPoint);
}
/**
* @param highlightedPoint Color of a highlighted point.
*/
public void setHighlightedPoint(Color highlightedPoint)
{
this.highlightedPoint = highlightedPoint;
}
/**
* @return Color of a selected point.
*/
public Color getSelectedPoint()
{
return selectedPoint;
}
/**
* @return Color of a selected point.
*/
public Color3f getSelectedPoint3f()
{
return new Color3f(selectedPoint);
}
/**
* @param selectedPoint Color of a selected point.
*/
public void setSelectedPoint(Color selectedPoint)
{
this.selectedPoint = selectedPoint;
}
/**
* @return Color of a selected highlighted point.
*/
public Color getSelectedHighlightedPoint()
{
return selectedHighlightedPoint;
}
/**
* @return Color of a selected highlighted point.
*/
public Color3f getSelectedHighlightedPoint3f()
{
return new Color3f(selectedHighlightedPoint);
}
/**
* @param selectedHighlightedPoint Color of a selected highlighted point.
*/
public void setSelectedHighlightedPoint(Color selectedHighlightedPoint)
{
this.selectedHighlightedPoint = selectedHighlightedPoint;
}
/**
* @return Chosen layer color for foreground/background.
*/
public Color getChosenBg()
{
return chosenBg;
}
/**
* @return Chosen layer color for foreground/background.
*/
public Color3f getChosenBg3f()
{
return new Color3f(chosenBg);
}
/**
* @param chosenBg Chosen layer color for foreground/background.
*/
public void setChosenBg(Color chosenBg)
{
this.chosenBg = chosenBg;
}
/**
* @return Chosen layer color for foreground/background.
*/
public Color getChosenFg()
{
return chosenFg;
}
/**
* @return Chosen layer color for foreground/background.
*/
public Color3f getChosenFg3f()
{
return new Color3f(chosenFg);
}
/**
* @param chosenFg Chosen layer color for foreground/background.
*/
public void setChosenFg(Color chosenFg)
{
this.chosenFg = chosenFg;
}
/**
* @return Highlighted chosen layer color for foreground/background.
*/
public Color getChosenHighlightBg()
{
return chosenHighlightBg;
}
/**
* @return Highlighted chosen layer color for foreground/background.
*/
public Color3f getChosenHighlightBg3f()
{
return new Color3f(chosenHighlightBg);
}
/**
* @param chosenHighlightBg Highlighted chosen layer color for foreground/background.
*/
public void setChosenHighlightBg(Color chosenHighlightBg)
{
this.chosenHighlightBg = chosenHighlightBg;
}
/**
* @return Highlighted chosen layer color for foreground/background.
*/
public Color getChosenHighlightFg()
{
return chosenHighlightFg;
}
/**
* @return Highlighted chosen layer color for foreground/background.
*/
public Color3f getChosenHighlightFg3f()
{
return new Color3f(chosenHighlightFg);
}
/**
* @param chosenHighlightFg Highlighted chosen layer color for foreground/background.
*/
public void setChosenHighlightFg(Color chosenHighlightFg)
{
this.chosenHighlightFg = chosenHighlightFg;
}
/**
* @return Old chosen layer color for foreground/background.
*/
public Color getChosenOldBg()
{
return chosenOldBg;
}
/**
* @return Old chosen layer color for foreground/background.
*/
public Color3f getChosenOldBg3f()
{
return new Color3f(chosenOldBg);
}
/**
* @param chosenOldBg Old chosen layer color for foreground/background.
*/
public void setChosenOldBg(Color chosenOldBg)
{
this.chosenOldBg = chosenOldBg;
}
/**
* @return Old chosen layer color for foreground/background.
*/
public Color getChosenOldFg()
{
return chosenOldFg;
}
/**
* @return Old chosen layer color for foreground/background.
*/
public Color3f getChosenOldFg3f()
{
return new Color3f(chosenOldFg);
}
/**
* @param chosenOldFg Old chosen layer color for foreground/background.
*/
public void setChosenOldFg(Color chosenOldFg)
{
this.chosenOldFg = chosenOldFg;
}
/**
* @return Color of a chosen fold line.
*/
public Color getChosenLine()
{
return chosenLine;
}
/**
* @return Color of a chosen fold line.
*/
public Color3f getChosenLine3f()
{
return new Color3f(chosenLine);
}
/**
* @param chosenLine Color of a chosen fold line.
*/
public void setChosenLine(Color chosenLine)
{
this.chosenLine = chosenLine;
}
/**
* @return Color of a highlighted chosen fold line.
*/
public Color getChosenHighlightedLine()
{
return chosenHighlightedLine;
}
/**
* @return Color of a highlighted chosen fold line.
*/
public Color3f getChosenHighlightedLine3f()
{
return new Color3f(chosenHighlightedLine);
}
/**
* @param chosenHighlightedLine Color of a highlighted chosen fold line.
*/
public void setChosenHighlightedLine(Color chosenHighlightedLine)
{
this.chosenHighlightedLine = chosenHighlightedLine;
}
/**
* @return Color of an old chosen fold line.
*/
public Color getChosenOldLine()
{
return chosenOldLine;
}
/**
* @return Color of an old chosen fold line.
*/
public Color3f getChosenOldLine3f()
{
return new Color3f(chosenOldLine);
}
/**
* @param chosenOldLine Color of an old chosen fold line.
*/
public void setChosenOldLine(Color chosenOldLine)
{
this.chosenOldLine = chosenOldLine;
}
/**
* @return Color of a chosen point.
*/
public Color getChosenPoint()
{
return chosenPoint;
}
/**
* @return Color of a chosen point.
*/
public Color3f getChosenPoint3f()
{
return new Color3f(chosenPoint);
}
/**
* @param chosenPoint Color of a chosen point.
*/
public void setChosenPoint(Color chosenPoint)
{
this.chosenPoint = chosenPoint;
}
/**
* @return Color of a highlighted chosen point.
*/
public Color getChosenHighlightedPoint()
{
return chosenHighlightedPoint;
}
/**
* @return Color of a highlighted chosen point.
*/
public Color3f getChosenHighlightedPoint3f()
{
return new Color3f(chosenHighlightedPoint);
}
/**
* @param chosenHighlightedPoint Color of a highlighted chosen point.
*/
public void setChosenHighlightedPoint(Color chosenHighlightedPoint)
{
this.chosenHighlightedPoint = chosenHighlightedPoint;
}
/**
* @return Color of an old chosen point.
*/
public Color getChosenOldPoint()
{
return chosenOldPoint;
}
/**
* @return Color of an old chosen point.
*/
public Color3f getChosenOldPoint3f()
{
return new Color3f(chosenOldPoint);
}
/**
* @param chosenOldPoint Color of an old chosen point.
*/
public void setChosenOldPoint(Color chosenOldPoint)
{
this.chosenOldPoint = chosenOldPoint;
}
/**
* @return Color of an available layer.
*/
public Color getAvailableLayer()
{
return availableLayer;
}
/**
* @return Color of an available layer.
*/
public Color3f getAvailableLayer3f()
{
return new Color3f(availableLayer);
}
/**
* @param availableLayer Color of an available layer.
*/
public void setAvailableLayer(Color availableLayer)
{
this.availableLayer = availableLayer;
}
}
protected class NewLine extends BranchGroup
{
/** The first point. */
public Group p1;
/** The second point. */
public Group p2;
}
}