package ini.trakem2.display;
import ini.trakem2.ControlWindow;
import ini.trakem2.display.graphics.GraphicsSource;
import ini.trakem2.utils.History;
import ini.trakem2.utils.IJError;
import ini.trakem2.utils.ProjectToolbar;
import ini.trakem2.utils.Utils;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class AffineTransformMode implements Mode {
private final Display display;
private final History history;
private final ATGS atgs = new AffineTransformMode.ATGS();
public AffineTransformMode(final Display display) {
this.display = display;
ProjectToolbar.setTool(ProjectToolbar.SELECT);
// Init:
resetBox();
floater.center();
this.handles = new Handle[]{NW, N, NE, E, SE, S, SW, W, RO, floater};
accum_affine = new AffineTransform();
history = new History(); // unlimited steps
history.add(new TransformationStep(getTransformationsCopy()));
display.getCanvas().repaint(false);
}
/** Returns a hash table with all selected Displayables as keys, and a copy of their affine transform as value. This is useful to easily create undo steps. */
private HashMap<Displayable,AffineTransform> getTransformationsCopy() {
final HashMap<Displayable,AffineTransform> ht_copy = new HashMap<Displayable,AffineTransform>();
for (final Displayable d : display.getSelection().getAffected()) {
ht_copy.put(d, d.getAffineTransformCopy());
}
return ht_copy;
}
/** Add an undo step to the internal history. */
private void addUndoStep() {
if (mouse_dragged || display.getSelection().isEmpty()) return;
if (null == history) return;
if (history.indexAtStart() || (history.indexAtEnd() && -1 != history.index())) {
history.add(new TransformationStep(getTransformationsCopy()));
} else {
// remove history elements from index+1 to end
history.clip();
}
}
@Override
synchronized public void undoOneStep() {
if (null == history) return;
// store the current state if at end:
Utils.log2("index at end: " + history.indexAtEnd());
Map.Entry<Displayable,AffineTransform> Be = ((TransformationStep)history.getCurrent()).ht.entrySet().iterator().next();
if (history.indexAtEnd()) {
final HashMap<Displayable,AffineTransform> m = getTransformationsCopy();
history.append(new TransformationStep(m));
Be = m.entrySet().iterator().next(); // must set again, for the other one was the last step, not the current state.
}
// disable application to other layers (too big a headache)
accum_affine = null;
// undo one step
final TransformationStep step = (TransformationStep)history.undoOneStep();
if (null == step) return; // no more steps
LayerSet.applyTransforms(step.ht);
resetBox();
// call fixAffinePoints with the diff affine transform, as computed from first selected object
try {
// t0 t1
// CA = B
// C = BA^(-1)
final AffineTransform A = step.ht.get(Be.getKey()); // the t0
final AffineTransform C = new AffineTransform(Be.getValue());
C.concatenate(A.createInverse());
fixAffinePoints(C);
} catch (final Exception e) {
IJError.print(e);
}
}
@Override
synchronized public void redoOneStep() {
if (null == history) return;
final Map.Entry<Displayable,AffineTransform> Ae = ((TransformationStep)history.getCurrent()).ht.entrySet().iterator().next();
final TransformationStep step = (TransformationStep)history.redoOneStep();
if (null == step) return; // no more steps
LayerSet.applyTransforms(step.ht);
resetBox();
// call fixAffinePoints with the diff affine transform, as computed from first selected object
// t0 t1
// A = CB
// AB^(-1) = C
final AffineTransform B = step.ht.get(Ae.getKey());
final AffineTransform C = new AffineTransform(Ae.getValue());
try {
C.concatenate(B.createInverse());
fixAffinePoints(C);
} catch (final Exception e) {
IJError.print(e);
}
}
@Override
public boolean isDragging() {
return dragging;
}
private class ATGS implements GraphicsSource {
@Override
public List<? extends Paintable> asPaintable(final List<? extends Paintable> ds) {
return ds;
}
/** Paints the transformation handles and a bounding box around all selected. */
@Override
public void paintOnTop(final Graphics2D g, final Display display, final Rectangle srcRect, final double magnification) {
final Stroke original_stroke = g.getStroke();
final AffineTransform original = g.getTransform();
g.setTransform(new AffineTransform());
if (!rotating) {
//Utils.log("box painting: " + box);
// 30 pixel line, 10 pixel gap, 10 pixel line, 10 pixel gap
//float mag = (float)magnification;
final float[] dashPattern = { 30, 10, 10, 10 };
g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, dashPattern, 0));
g.setColor(Color.yellow);
// paint box
//g.drawRect(box.x, box.y, box.width, box.height);
g.draw(original.createTransformedShape(box));
g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
// paint handles for scaling (boxes) and rotating (circles), and floater
for (int i=0; i<handles.length; i++) {
handles[i].paint(g, srcRect, magnification);
}
} else {
g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
RO.paint(g, srcRect, magnification);
((RotationHandle)RO).paintMoving(g, srcRect, magnification, display.getCanvas().getCursorLoc());
}
if (null != affine_handles) {
for (final AffinePoint ap : affine_handles) {
ap.paint(g);
}
}
g.setTransform(original);
g.setStroke(original_stroke);
}
}
@Override
public GraphicsSource getGraphicsSource() {
return atgs;
}
@Override
public boolean canChangeLayer() { return false; }
@Override
public boolean canZoom() { return true; }
@Override
public boolean canPan() { return true; }
/* From former Selection class: the affine transformation GUI */
private final int iNW = 0;
private final int iN = 1;
private final int iNE = 2;
private final int iE = 3;
private final int iSE = 4;
private final int iS = 5;
private final int iSW = 6;
private final int iW = 7;
private final int ROTATION = 12;
private final int FLOATER = 13;
private final Handle NW = new BoxHandle(0,0, iNW);
private final Handle N = new BoxHandle(0,0, iN);
private final Handle NE = new BoxHandle(0,0, iNE);
private final Handle E = new BoxHandle(0,0, iE);
private final Handle SE = new BoxHandle(0,0, iSE);
private final Handle S = new BoxHandle(0,0, iS);
private final Handle SW = new BoxHandle(0,0, iSW);
private final Handle W = new BoxHandle(0,0, iW);
private final Handle RO = new RotationHandle(0,0, ROTATION);
/** Pivot of rotation. Always checked first on mouse pressed, before other handles. */
private final Floater floater = new Floater(0, 0, FLOATER);
private final Handle[] handles;
private Handle grabbed = null;
private boolean dragging = false; // means: dragging the whole transformation box
private boolean rotating = false;
private boolean mouse_dragged = false;
private Rectangle box;
private int x_d_old, y_d_old, x_d, y_d;
/** Handles have screen coordinates. */
private abstract class Handle {
public int x, y;
public final int id;
Handle(final int x, final int y, final int id) {
this.x = x;
this.y = y;
this.id = id;
}
abstract public void paint(Graphics2D g, Rectangle srcRect, double mag);
/** Radius is the dectection "radius" around the handle x,y. */
public boolean contains(final int x_p, final int y_p, final double radius) {
if (x - radius <= x_p && x + radius >= x_p
&& y - radius <= y_p && y + radius >= y_p) return true;
return false;
}
public void set(final int x, final int y) {
this.x = x;
this.y = y;
}
abstract void drag(MouseEvent me, int dx, int dy);
}
private class BoxHandle extends Handle {
BoxHandle(final int x, final int y, final int id) {
super(x,y,id);
}
@Override
public void paint(final Graphics2D g, final Rectangle srcRect, final double mag) {
final int x = (int)((this.x - srcRect.x)*mag);
final int y = (int)((this.y - srcRect.y)*mag);
DisplayCanvas.drawHandle(g, x, y, 1.0); // ignoring magnification for the sizes, since Selection is painted differently
}
@Override
public void drag(final MouseEvent me, final int dx, final int dy) {
final Rectangle box_old = (Rectangle)box.clone();
//Utils.log2("dx,dy: " + dx + "," + dy + " before mod");
double res = dx / 2.0;
res -= Math.floor(res);
res *= 2;
int anchor_x = 0,
anchor_y = 0;
switch (this.id) { // java sucks to such an extent, I don't even bother
case iNW:
if (x + dx >= E.x) return;
if (y + dy >= S.y) return;
box.x += dx;
box.y += dy;
box.width -= dx;
box.height -= dy;
anchor_x = SE.x;
anchor_y = SE.y;
break;
case iN:
if (y + dy >= S.y) return;
box.y += dy;
box.height -= dy;
anchor_x = S.x;
anchor_y = S.y;
break;
case iNE:
if (x + dx <= W.x) return;
if (y + dy >= S.y) return;
box.y += dy;
box.width += dx;
box.height -= dy;
anchor_x = SW.x;
anchor_y = SW.y;
break;
case iE:
if (x + dx <= W.x) return;
box.width += dx;
anchor_x = W.x;
anchor_y = W.y;
break;
case iSE:
if (x + dx <= W.x) return;
if (y + dy <= N.y) return;
box.width += dx;
box.height += dy;
anchor_x = NW.x;
anchor_y = NW.y;
break;
case iS:
if (y + dy <= N.y) return;
box.height += dy;
anchor_x = N.x;
anchor_y = N.y;
break;
case iSW:
if (x + dx >= E.x) return;
if (y + dy <= N.y) return;
box.x += dx;
box.width -= dx;
box.height += dy;
anchor_x = NE.x;
anchor_y = NE.y;
break;
case iW:
if (x + dx >= E.x) return;
box.x += dx;
box.width -= dx;
anchor_x = E.x;
anchor_y = E.y;
break;
}
// proportion:
final double px = (double)box.width / (double)box_old.width;
final double py = (double)box.height / (double)box_old.height;
// displacement: specific of each element of the selection and their links, depending on where they are.
final AffineTransform at = new AffineTransform();
at.translate( anchor_x, anchor_y );
at.scale( px, py );
at.translate( -anchor_x, -anchor_y );
addUndoStep();
if (null != accum_affine) accum_affine.preConcatenate(at);
Displayable.preConcatenate(at, display.getSelection().getAffected());
fixAffinePoints(at);
// finally:
setHandles(box); // overkill. As Graham said, most newly available chip resources are going to be wasted. They are already.
}
}
private final double rotate(final MouseEvent me) {
// center of rotation is the floater
final double cos = Utils.getCos(x_d_old - floater.x, y_d_old - floater.y, x_d - floater.x, y_d - floater.y);
//double sin = Math.sqrt(1 - cos*cos);
//double delta = M.getAngle(cos, sin);
double delta = Math.acos(cos); // same thing as the two lines above
// need to compute the sign of rotation as well: the cross-product!
// cross-product:
// a = (3,0,0) and b = (0,2,0)
// a x b = (3,0,0) x (0,2,0) = ((0 x 0 - 2 x 0), -(3 x 0 - 0 x 0), (3 x 2 - 0 x 0)) = (0,0,6).
if (Utils.isControlDown(me)) {
delta = Math.toDegrees(delta);
if (me.isShiftDown()) {
// 1 degree angle increments
delta = (int)(delta + 0.5);
} else {
// 10 degrees angle increments: snap to closest
delta = (int)((delta + 5.5 * (delta < 0 ? -1 : 1)) / 10) * 10;
}
Utils.showStatus("Angle: " + delta + " degrees");
delta = Math.toRadians(delta);
// TODO: the angle above is just the last increment on mouse drag, not the total amount of angle accumulated since starting this mousePressed-mouseDragged-mouseReleased cycle, neither the actual angle of the selected elements. So we need to store the accumulated angle and diff from it to do the above roundings.
}
if (Double.isNaN(delta)) {
Utils.log2("Selection rotation handle: ignoring NaN angle");
return Double.NaN;
}
final double zc = (x_d_old - floater.x) * (y_d - floater.y) - (x_d - floater.x) * (y_d_old - floater.y);
// correction:
if (zc < 0) {
delta = -delta;
}
rotate(Math.toDegrees(delta), floater.x, floater.y);
return delta;
}
private class RotationHandle extends Handle {
final int shift = 50;
RotationHandle(final int x, final int y, final int id) {
super(x, y, id);
}
@Override
public void paint(final Graphics2D g, final Rectangle srcRect, final double mag) {
final int x = (int)((this.x - srcRect.x)*mag) + shift;
final int y = (int)((this.y - srcRect.y)*mag);
final int fx = (int)((floater.x - srcRect.x)*mag);
final int fy = (int)((floater.y - srcRect.y)*mag);
draw(g, fx, fy, x, y);
}
private void draw(final Graphics2D g, final int fx, final int fy, final int x, final int y) {
g.setColor(Color.white);
g.drawLine(fx, fy, x, y);
g.fillOval(x -4, y -4, 9, 9);
g.setColor(Color.black);
g.drawOval(x -2, y -2, 5, 5);
}
public void paintMoving(final Graphics2D g, final Rectangle srcRect, final double mag, final Point mouse) {
// mouse as xMouse,yMouse from ImageCanvas: world coordinates, not screen!
final int fx = (int)((floater.x - srcRect.x)*mag);
final int fy = (int)((floater.y - srcRect.y)*mag);
// vector
final double vx = (mouse.x - srcRect.x)*mag - fx;
final double vy = (mouse.y - srcRect.y)*mag - fy;
//double len = Math.sqrt(vx*vx + vy*vy);
//vx = (vx / len) * 50;
//vy = (vy / len) * 50;
draw(g, fx, fy, fx + (int)vx, fy + (int)vy);
}
@Override
public boolean contains(final int x_p, final int y_p, final double radius) {
final double mag = display.getCanvas().getMagnification();
final double x = this.x + shift / mag;
final double y = this.y;
if (x - radius <= x_p && x + radius >= x_p
&& y - radius <= y_p && y + radius >= y_p) return true;
return false;
}
@Override
public void drag(final MouseEvent me, final int dx, final int dy) {
/// Bad design, I know, I'm ignoring the dx,dy
// how:
// center is the floater
rotate(me);
}
}
private class Floater extends Handle {
Floater(final int x, final int y, final int id) {
super(x,y, id);
}
@Override
public void paint(final Graphics2D g, final Rectangle srcRect, final double mag) {
final int x = (int)((this.x - srcRect.x)*mag);
final int y = (int)((this.y - srcRect.y)*mag);
final Composite co = g.getComposite();
g.setXORMode(Color.white);
g.drawOval(x -10, y -10, 21, 21);
g.drawRect(x -1, y -15, 3, 31);
g.drawRect(x -15, y -1, 31, 3);
g.setComposite(co); // undo XOR paint
}
public Rectangle getBoundingBox(final Rectangle b) {
b.x = this.x - 15;
b.y = this.y - 15;
b.width = this.x + 31;
b.height = this.y + 31;
return b;
}
@Override
public void drag(final MouseEvent me, final int dx, final int dy) {
this.x += dx;
this.y += dy;
RO.x = this.x;
RO.y = this.y;
}
public void center() {
this.x = RO.x = box.x + box.width/2;
this.y = RO.y = box.y + box.height/2;
}
@Override
public boolean contains(final int x_p, final int y_p, final double radius) {
return super.contains(x_p, y_p, radius*3.5);
}
}
public void centerFloater() {
floater.center();
}
/** No display bounds are checked, the floater can be placed wherever you want. */
public void setFloater(final int x, final int y) {
floater.x = x;
floater.y = y;
}
public int getFloaterX() { return floater.x; }
public int getFloaterY() { return floater.y; }
private void setHandles(final Rectangle b) {
final int tx = b.x;
final int ty = b.y;
final int tw = b.width;
final int th = b.height;
NW.set(tx, ty);
N.set(tx + tw/2, ty);
NE.set(tx + tw, ty);
E.set(tx + tw, ty + th/2);
SE.set(tx + tw, ty + th);
S.set(tx + tw/2, ty + th);
SW.set(tx, ty + th);
W.set(tx, ty + th/2);
}
private AffineTransform accum_affine = null;
/** Skips current layer, since its done already. */
synchronized protected void applyAndPropagate(final Set<Layer> sublist) {
if (null == accum_affine) {
Utils.log2("Cannot apply to other layers: undo/redo was used.");
return;
}
if (0 == sublist.size()) {
Utils.logAll("No layers to apply to!");
return;
}
// Check if there are links across affected layers
if (Displayable.areThereLayerCrossLinks(sublist, false)) {
if (ControlWindow.isGUIEnabled()) {
final YesNoDialog yn = ControlWindow.makeYesNoDialog("Warning!", "Some objects are linked!\nThe transformation would alter interrelationships.\nProceed anyway?");
if ( ! yn.yesPressed()) return;
} else {
Utils.log("Can't apply: some images may be linked across layers.\n Unlink them by removing segmentation objects like arealists, pipes, profiles, etc. that cross these layers.");
return;
}
}
// Add undo step
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (final Layer l : sublist) {
al.addAll(l.getDisplayables());
}
display.getLayer().getParent().addTransformStep(al);
// Must capture last step of free affine when using affine points:
if (null != free_affine && null != model) {
accum_affine.preConcatenate(free_affine);
accum_affine.preConcatenate(model.createAffine());
}
// Apply!
for (final Layer l : sublist) {
if (display.getLayer() == l) continue; // already applied
l.apply(Displayable.class, accum_affine);
}
// Record current state as last step in undo queue
display.getLayer().getParent().addTransformStep(al);
}
@Override
public boolean apply() {
// Notify each Displayable that any set of temporary transformations are over.
// The transform is the same, has not changed. This is just sending an event.
for (final Displayable d : display.getSelection().getAffected()) {
d.setAffineTransform( d.getAffineTransform() );
}
return true;
}
@Override
public boolean cancel() {
if (null != history) {
// apply first
LayerSet.applyTransforms(((TransformationStep)history.get(0)).ht);
}
return true;
}
private class AffinePoint {
int x, y;
AffinePoint(final int x, final int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(final Object ob) {
//if (!ob.getClass().equals(AffinePoint.class)) return false;
final AffinePoint ap = (AffinePoint) ob;
final double mag = display.getCanvas().getMagnification();
final double dx = mag * ( ap.x - this.x );
final double dy = mag * ( ap.y - this.y );
final double d = dx * dx + dy * dy;
return d < 64.0;
}
void translate(final int dx, final int dy) {
x += dx;
y += dy;
}
private void paint(final Graphics2D g) {
final int x = display.getCanvas().screenX(this.x);
final int y = display.getCanvas().screenY(this.y);
Utils.drawPoint(g, x, y);
}
}
private ArrayList<AffinePoint> affine_handles = null;
private ArrayList< mpicbg.models.PointMatch > matches = null;
private mpicbg.models.Point[] p = null;
private mpicbg.models.Point[] q = null;
private mpicbg.models.AbstractAffineModel2D<?> model = null;
private AffineTransform free_affine = null;
private HashMap<Displayable,AffineTransform> initial_affines = null;
/*
private void forgetAffine() {
affine_handles = null;
matches = null;
p = q = null;
model = null;
free_affine = null;
initial_affines = null;
}
*/
private void initializeModel() {
// Store current "initial" state in the accumulated affine
if (null != free_affine && null != model && null != accum_affine) {
accum_affine.preConcatenate(free_affine);
accum_affine.preConcatenate(model.createAffine());
}
free_affine = new AffineTransform();
initial_affines = getTransformationsCopy();
final int size = affine_handles.size();
switch (size) {
case 0:
model = null;
q = p = null;
matches = null;
return;
case 1:
model = new mpicbg.models.TranslationModel2D();
break;
case 2:
model = new mpicbg.models.SimilarityModel2D();
break;
case 3:
model = new mpicbg.models.AffineModel2D();
break;
}
p = new mpicbg.models.Point[size];
q = new mpicbg.models.Point[size];
matches = new ArrayList< mpicbg.models.PointMatch >();
int i = 0;
for (final AffinePoint ap : affine_handles) {
p[i] = new mpicbg.models.Point(new double[]{ap.x, ap.y});
q[i] = p[i].clone();
matches.add(new mpicbg.models.PointMatch(p[i], q[i]));
i++;
}
}
private void freeAffine(final AffinePoint affp) {
// The selected point
final double[] w = q[affine_handles.indexOf(affp)].getW();
w[0] = affp.x;
w[1] = affp.y;
try {
model.fit(matches);
} catch (final Exception e) {}
final AffineTransform model_affine = model.createAffine();
for (final Map.Entry<Displayable,AffineTransform> e : initial_affines.entrySet()) {
final AffineTransform at = new AffineTransform(e.getValue());
at.preConcatenate(free_affine);
at.preConcatenate(model_affine);
e.getKey().setAffineTransform(at);
}
}
private void fixAffinePoints(final AffineTransform at) {
if (null != matches) {
final float[] po = new float[2];
for (final AffinePoint affp : affine_handles) {
po[0] = affp.x;
po[1] = affp.y;
at.transform(po, 0, po, 0, 1);
affp.x = (int)po[0];
affp.y = (int)po[1];
}
// Model will be reinitialized when needed
free_affine.setToIdentity();
model = null;
}
}
private AffinePoint affp = null;
@Override
public void mousePressed(final MouseEvent me, final int x_p, final int y_p, final double magnification) {
grabbed = null; // reset
if (me.isShiftDown()) {
if (Utils.isControlDown(me) && null != affine_handles) {
if (affine_handles.remove(new AffinePoint(x_p, y_p))) {
if (0 == affine_handles.size()) affine_handles = null;
else initializeModel();
}
return;
}
if (null == affine_handles) {
affine_handles = new ArrayList<AffinePoint>();
}
if (affine_handles.size() < 3) {
affine_handles.add(new AffinePoint(x_p, y_p));
if (1 == affine_handles.size()) {
free_affine = new AffineTransform();
initial_affines = getTransformationsCopy();
}
initializeModel();
}
return;
} else if (null != affine_handles) {
final int index = affine_handles.indexOf(new AffinePoint(x_p, y_p));
if (-1 != index) {
affp = affine_handles.get(index);
return;
}
}
// find scale handle
double radius = 4 / magnification;
if (radius < 1) radius = 1;
// start with floater (the last)
for (int i=handles.length -1; i>-1; i--) {
if (handles[i].contains(x_p, y_p, radius)) {
grabbed = handles[i];
if (grabbed.id > iW && grabbed.id <= ROTATION) rotating = true;
return;
}
}
// if none grabbed, then drag the whole thing
dragging = false; //reset
if (box.x <= x_p && box.y <= y_p && box.x + box.width >= x_p && box.y + box.height >= y_p) {
dragging = true;
}
}
@Override
public void mouseDragged(final MouseEvent me, final int x_p, final int y_p, final int x_d, final int y_d, final int x_d_old, final int y_d_old) {
// Store old for rotation handle:
this.x_d = x_d;
this.y_d = y_d;
this.x_d_old = x_d_old;
this.y_d_old = y_d_old;
// compute translation
final int dx = x_d - x_d_old;
final int dy = y_d - y_d_old;
execDrag(me, dx, dy);
display.getCanvas().repaint(true);
mouse_dragged = true; // after execDrag, so the first undo step is added.
}
private void execDrag(final MouseEvent me, final int dx, final int dy) {
if (0 == dx && 0 == dy) return;
if (null != affp) {
affp.translate(dx, dy);
if (null == model) {
// Model has been canceled by a transformation from the other handles
initializeModel();
}
// Passing on the translation from start
freeAffine(affp);
return;
}
if (null != grabbed) {
// drag the handle and perform whatever task it has assigned
grabbed.drag(me, dx, dy);
} else if (dragging) {
// drag all selected and linked
translate(dx, dy);
//and the box!
box.x += dx;
box.y += dy;
// and the handles!
setHandles(box);
}
}
@Override
public void mouseReleased(final MouseEvent me, final int x_p, final int y_p, final int x_d, final int y_d, final int x_r, final int y_r) {
// Record current state for selected Displayable set, if there was any change:
final int dx = x_r - x_p;
final int dy = y_r - y_p;
if (0 != dx || 0 != dy) {
display.getLayerSet().addTransformStep(display.getSelection().getAffected()); // all selected and their links: i.e. all that will change
}
// me is null when calling from Display, because of popup interfering with mouseReleased
if (null != me) {
execDrag(me, x_r - x_d, y_r - y_d);
display.getCanvas().repaint(true);
}
// recalculate box
resetBox();
//reset
if ((null != grabbed && grabbed.id <= iW) || dragging) {
floater.center();
}
grabbed = null;
dragging = false;
rotating = false;
affp = null;
mouse_dragged = false;
}
/** Recalculate box and reset handles. */
public void resetBox() {
box = null;
Rectangle b = new Rectangle();
for (final Displayable d : display.getSelection().getSelected()) {
b = d.getBoundingBox(b);
if (null == box) box = (Rectangle)b.clone();
box.add(b);
}
if (null != box) setHandles(box);
}
/** Rotate the objects in the current selection by the given angle, in degrees, relative to the x_o, y_o origin. */
public void rotate(final double angle, final int xo, final int yo) {
final AffineTransform at = new AffineTransform();
at.rotate(Math.toRadians(angle), xo, yo);
addUndoStep();
if (null != accum_affine) accum_affine.preConcatenate(at);
Displayable.preConcatenate(at, display.getSelection().getAffected());
fixAffinePoints(at);
resetBox();
}
/** Translate all selected objects and their links by the given differentials. The floater position is unaffected; if you want to update it call centerFloater() */
public void translate(final double dx, final double dy) {
final AffineTransform at = new AffineTransform();
at.translate(dx, dy);
addUndoStep();
if (null != accum_affine) accum_affine.preConcatenate(at);
Displayable.preConcatenate(at, display.getSelection().getAffected());
fixAffinePoints(at);
resetBox();
}
/** Scale all selected objects and their links by by the given scales, relative to the floater position. . */
public void scale(final double sx, final double sy) {
if (0 == sx || 0 == sy) {
Utils.showMessage("Cannot scale to 0.");
return;
}
final AffineTransform at = new AffineTransform();
at.translate(floater.x, floater.y);
at.scale(sx, sy);
at.translate(-floater.x, -floater.y);
addUndoStep();
if (null != accum_affine) accum_affine.preConcatenate(at);
Displayable.preConcatenate(at, display.getSelection().getAffected());
fixAffinePoints(at);
resetBox();
}
@Override
public Rectangle getRepaintBounds() {
final Rectangle b = display.getSelection().getLinkedBox();
b.add(floater.getBoundingBox(new Rectangle()));
return b;
}
@Override
public void srcRectUpdated(final Rectangle srcRect, final double magnification) {}
@Override
public void magnificationUpdated(final Rectangle srcRect, final double magnification) {}
}