Use internal SNAPSHOT couplings again
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / AffineTransformMode.java
bloba98d05e65cb56cb12a7afe827877ddfeee2051c1
1 package ini.trakem2.display;
3 import ini.trakem2.ControlWindow;
4 import ini.trakem2.display.graphics.GraphicsSource;
5 import ini.trakem2.utils.History;
6 import ini.trakem2.utils.IJError;
7 import ini.trakem2.utils.ProjectToolbar;
8 import ini.trakem2.utils.Utils;
10 import java.awt.BasicStroke;
11 import java.awt.Color;
12 import java.awt.Composite;
13 import java.awt.Graphics2D;
14 import java.awt.Point;
15 import java.awt.Rectangle;
16 import java.awt.Stroke;
17 import java.awt.event.MouseEvent;
18 import java.awt.geom.AffineTransform;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
25 public class AffineTransformMode implements Mode {
27 private final Display display;
28 private final History history;
29 private final ATGS atgs = new AffineTransformMode.ATGS();
31 public AffineTransformMode(final Display display) {
32 this.display = display;
33 ProjectToolbar.setTool(ProjectToolbar.SELECT);
34 // Init:
35 resetBox();
36 floater.center();
37 this.handles = new Handle[]{NW, N, NE, E, SE, S, SW, W, RO, floater};
38 accum_affine = new AffineTransform();
39 history = new History(); // unlimited steps
40 history.add(new TransformationStep(getTransformationsCopy()));
41 display.getCanvas().repaint(false);
44 /** 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. */
45 private HashMap<Displayable,AffineTransform> getTransformationsCopy() {
46 final HashMap<Displayable,AffineTransform> ht_copy = new HashMap<Displayable,AffineTransform>();
47 for (final Displayable d : display.getSelection().getAffected()) {
48 ht_copy.put(d, d.getAffineTransformCopy());
50 return ht_copy;
53 /** Add an undo step to the internal history. */
54 private void addUndoStep() {
55 if (mouse_dragged || display.getSelection().isEmpty()) return;
56 if (null == history) return;
57 if (history.indexAtStart() || (history.indexAtEnd() && -1 != history.index())) {
58 history.add(new TransformationStep(getTransformationsCopy()));
59 } else {
60 // remove history elements from index+1 to end
61 history.clip();
65 synchronized public void undoOneStep() {
66 if (null == history) return;
67 // store the current state if at end:
68 Utils.log2("index at end: " + history.indexAtEnd());
70 Map.Entry<Displayable,AffineTransform> Be = ((TransformationStep)history.getCurrent()).ht.entrySet().iterator().next();
72 if (history.indexAtEnd()) {
73 HashMap<Displayable,AffineTransform> m = getTransformationsCopy();
74 history.append(new TransformationStep(m));
75 Be = m.entrySet().iterator().next(); // must set again, for the other one was the last step, not the current state.
78 // disable application to other layers (too big a headache)
79 accum_affine = null;
80 // undo one step
81 TransformationStep step = (TransformationStep)history.undoOneStep();
82 if (null == step) return; // no more steps
83 LayerSet.applyTransforms(step.ht);
84 resetBox();
86 // call fixAffinePoints with the diff affine transform, as computed from first selected object
88 try {
89 // t0 t1
90 // CA = B
91 // C = BA^(-1)
92 AffineTransform A = step.ht.get(Be.getKey()); // the t0
93 AffineTransform C = new AffineTransform(Be.getValue());
94 C.concatenate(A.createInverse());
95 fixAffinePoints(C);
96 } catch (Exception e) {
97 IJError.print(e);
101 synchronized public void redoOneStep() {
102 if (null == history) return;
104 Map.Entry<Displayable,AffineTransform> Ae = ((TransformationStep)history.getCurrent()).ht.entrySet().iterator().next();
106 TransformationStep step = (TransformationStep)history.redoOneStep();
107 if (null == step) return; // no more steps
108 LayerSet.applyTransforms(step.ht);
109 resetBox();
111 // call fixAffinePoints with the diff affine transform, as computed from first selected object
112 // t0 t1
113 // A = CB
114 // AB^(-1) = C
115 AffineTransform B = step.ht.get(Ae.getKey());
116 AffineTransform C = new AffineTransform(Ae.getValue());
117 try {
118 C.concatenate(B.createInverse());
119 fixAffinePoints(C);
120 } catch (Exception e) {
121 IJError.print(e);
125 public boolean isDragging() {
126 return dragging;
129 private class ATGS implements GraphicsSource {
130 public List<? extends Paintable> asPaintable(final List<? extends Paintable> ds) {
131 return ds;
133 /** Paints the transformation handles and a bounding box around all selected. */
134 public void paintOnTop(final Graphics2D g, final Display display, final Rectangle srcRect, final double magnification) {
135 final Stroke original_stroke = g.getStroke();
136 AffineTransform original = g.getTransform();
137 g.setTransform(new AffineTransform());
138 if (!rotating) {
139 //Utils.log("box painting: " + box);
141 // 30 pixel line, 10 pixel gap, 10 pixel line, 10 pixel gap
142 //float mag = (float)magnification;
143 float[] dashPattern = { 30, 10, 10, 10 };
144 g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, dashPattern, 0));
145 g.setColor(Color.yellow);
146 // paint box
147 //g.drawRect(box.x, box.y, box.width, box.height);
148 g.draw(original.createTransformedShape(box));
149 g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
150 // paint handles for scaling (boxes) and rotating (circles), and floater
151 for (int i=0; i<handles.length; i++) {
152 handles[i].paint(g, srcRect, magnification);
154 } else {
155 g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
156 RO.paint(g, srcRect, magnification);
157 ((RotationHandle)RO).paintMoving(g, srcRect, magnification, display.getCanvas().getCursorLoc());
160 if (null != affine_handles) {
161 for (final AffinePoint ap : affine_handles) {
162 ap.paint(g);
166 g.setTransform(original);
167 g.setStroke(original_stroke);
171 public GraphicsSource getGraphicsSource() {
172 return atgs;
175 public boolean canChangeLayer() { return false; }
176 public boolean canZoom() { return true; }
177 public boolean canPan() { return true; }
182 /* From former Selection class: the affine transformation GUI */
184 private final int iNW = 0;
185 private final int iN = 1;
186 private final int iNE = 2;
187 private final int iE = 3;
188 private final int iSE = 4;
189 private final int iS = 5;
190 private final int iSW = 6;
191 private final int iW = 7;
192 private final int ROTATION = 12;
193 private final int FLOATER = 13;
194 private final Handle NW = new BoxHandle(0,0, iNW);
195 private final Handle N = new BoxHandle(0,0, iN);
196 private final Handle NE = new BoxHandle(0,0, iNE);
197 private final Handle E = new BoxHandle(0,0, iE);
198 private final Handle SE = new BoxHandle(0,0, iSE);
199 private final Handle S = new BoxHandle(0,0, iS);
200 private final Handle SW = new BoxHandle(0,0, iSW);
201 private final Handle W = new BoxHandle(0,0, iW);
202 private final Handle RO = new RotationHandle(0,0, ROTATION);
203 /** Pivot of rotation. Always checked first on mouse pressed, before other handles. */
204 private final Floater floater = new Floater(0, 0, FLOATER);
205 private final Handle[] handles;
206 private Handle grabbed = null;
207 private boolean dragging = false; // means: dragging the whole transformation box
208 private boolean rotating = false;
209 private boolean mouse_dragged = false;
210 private Rectangle box;
212 private int x_d_old, y_d_old, x_d, y_d;
214 /** Handles have screen coordinates. */
215 private abstract class Handle {
216 public int x, y;
217 public final int id;
218 Handle(int x, int y, int id) {
219 this.x = x;
220 this.y = y;
221 this.id = id;
223 abstract public void paint(Graphics2D g, Rectangle srcRect, double mag);
224 /** Radius is the dectection "radius" around the handle x,y. */
225 public boolean contains(int x_p, int y_p, double radius) {
226 if (x - radius <= x_p && x + radius >= x_p
227 && y - radius <= y_p && y + radius >= y_p) return true;
228 return false;
230 public void set(int x, int y) {
231 this.x = x;
232 this.y = y;
234 abstract void drag(MouseEvent me, int dx, int dy);
237 private class BoxHandle extends Handle {
238 BoxHandle(int x, int y, int id) {
239 super(x,y,id);
241 public void paint(final Graphics2D g, final Rectangle srcRect, final double mag) {
242 final int x = (int)((this.x - srcRect.x)*mag);
243 final int y = (int)((this.y - srcRect.y)*mag);
244 DisplayCanvas.drawHandle(g, x, y, 1.0); // ignoring magnification for the sizes, since Selection is painted differently
246 public void drag(MouseEvent me, int dx, int dy) {
247 Rectangle box_old = (Rectangle)box.clone();
248 //Utils.log2("dx,dy: " + dx + "," + dy + " before mod");
249 double res = dx / 2.0;
250 res -= Math.floor(res);
251 res *= 2;
252 int anchor_x = 0,
253 anchor_y = 0;
254 switch (this.id) { // java sucks to such an extent, I don't even bother
255 case iNW:
256 if (x + dx >= E.x) return;
257 if (y + dy >= S.y) return;
258 box.x += dx;
259 box.y += dy;
260 box.width -= dx;
261 box.height -= dy;
262 anchor_x = SE.x;
263 anchor_y = SE.y;
264 break;
265 case iN:
266 if (y + dy >= S.y) return;
267 box.y += dy;
268 box.height -= dy;
269 anchor_x = S.x;
270 anchor_y = S.y;
271 break;
272 case iNE:
273 if (x + dx <= W.x) return;
274 if (y + dy >= S.y) return;
275 box.y += dy;
276 box.width += dx;
277 box.height -= dy;
278 anchor_x = SW.x;
279 anchor_y = SW.y;
280 break;
281 case iE:
282 if (x + dx <= W.x) return;
283 box.width += dx;
284 anchor_x = W.x;
285 anchor_y = W.y;
286 break;
287 case iSE:
288 if (x + dx <= W.x) return;
289 if (y + dy <= N.y) return;
290 box.width += dx;
291 box.height += dy;
292 anchor_x = NW.x;
293 anchor_y = NW.y;
294 break;
295 case iS:
296 if (y + dy <= N.y) return;
297 box.height += dy;
298 anchor_x = N.x;
299 anchor_y = N.y;
300 break;
301 case iSW:
302 if (x + dx >= E.x) return;
303 if (y + dy <= N.y) return;
304 box.x += dx;
305 box.width -= dx;
306 box.height += dy;
307 anchor_x = NE.x;
308 anchor_y = NE.y;
309 break;
310 case iW:
311 if (x + dx >= E.x) return;
312 box.x += dx;
313 box.width -= dx;
314 anchor_x = E.x;
315 anchor_y = E.y;
316 break;
318 // proportion:
319 double px = (double)box.width / (double)box_old.width;
320 double py = (double)box.height / (double)box_old.height;
321 // displacement: specific of each element of the selection and their links, depending on where they are.
323 final AffineTransform at = new AffineTransform();
324 at.translate( anchor_x, anchor_y );
325 at.scale( px, py );
326 at.translate( -anchor_x, -anchor_y );
328 addUndoStep();
330 if (null != accum_affine) accum_affine.preConcatenate(at);
332 Displayable.preConcatenate(at, display.getSelection().getAffected());
333 fixAffinePoints(at);
335 // finally:
336 setHandles(box); // overkill. As Graham said, most newly available chip resources are going to be wasted. They are already.
340 private final double rotate(MouseEvent me) {
341 // center of rotation is the floater
342 double cos = Utils.getCos(x_d_old - floater.x, y_d_old - floater.y, x_d - floater.x, y_d - floater.y);
343 //double sin = Math.sqrt(1 - cos*cos);
344 //double delta = M.getAngle(cos, sin);
345 double delta = Math.acos(cos); // same thing as the two lines above
346 // need to compute the sign of rotation as well: the cross-product!
347 // cross-product:
348 // a = (3,0,0) and b = (0,2,0)
349 // 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).
351 if (Utils.isControlDown(me)) {
352 delta = Math.toDegrees(delta);
353 if (me.isShiftDown()) {
354 // 1 degree angle increments
355 delta = (int)(delta + 0.5);
356 } else {
357 // 10 degrees angle increments: snap to closest
358 delta = (int)((delta + 5.5 * (delta < 0 ? -1 : 1)) / 10) * 10;
360 Utils.showStatus("Angle: " + delta + " degrees");
361 delta = Math.toRadians(delta);
363 // 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.
366 if (Double.isNaN(delta)) {
367 Utils.log2("Selection rotation handle: ignoring NaN angle");
368 return Double.NaN;
371 double zc = (x_d_old - floater.x) * (y_d - floater.y) - (x_d - floater.x) * (y_d_old - floater.y);
372 // correction:
373 if (zc < 0) {
374 delta = -delta;
376 rotate(Math.toDegrees(delta), floater.x, floater.y);
377 return delta;
380 private class RotationHandle extends Handle {
381 final int shift = 50;
382 RotationHandle(int x, int y, int id) {
383 super(x, y, id);
385 public void paint(final Graphics2D g, final Rectangle srcRect, final double mag) {
386 final int x = (int)((this.x - srcRect.x)*mag) + shift;
387 final int y = (int)((this.y - srcRect.y)*mag);
388 final int fx = (int)((floater.x - srcRect.x)*mag);
389 final int fy = (int)((floater.y - srcRect.y)*mag);
390 draw(g, fx, fy, x, y);
392 private void draw(final Graphics2D g, int fx, int fy, int x, int y) {
393 g.setColor(Color.white);
394 g.drawLine(fx, fy, x, y);
395 g.fillOval(x -4, y -4, 9, 9);
396 g.setColor(Color.black);
397 g.drawOval(x -2, y -2, 5, 5);
399 public void paintMoving(final Graphics2D g, final Rectangle srcRect, final double mag, final Point mouse) {
400 // mouse as xMouse,yMouse from ImageCanvas: world coordinates, not screen!
401 final int fx = (int)((floater.x - srcRect.x)*mag);
402 final int fy = (int)((floater.y - srcRect.y)*mag);
403 // vector
404 double vx = (mouse.x - srcRect.x)*mag - fx;
405 double vy = (mouse.y - srcRect.y)*mag - fy;
406 //double len = Math.sqrt(vx*vx + vy*vy);
407 //vx = (vx / len) * 50;
408 //vy = (vy / len) * 50;
409 draw(g, fx, fy, fx + (int)vx, fy + (int)vy);
411 public boolean contains(int x_p, int y_p, double radius) {
412 final double mag = display.getCanvas().getMagnification();
413 final double x = this.x + shift / mag;
414 final double y = this.y;
415 if (x - radius <= x_p && x + radius >= x_p
416 && y - radius <= y_p && y + radius >= y_p) return true;
417 return false;
419 public void drag(MouseEvent me, int dx, int dy) {
420 /// Bad design, I know, I'm ignoring the dx,dy
421 // how:
422 // center is the floater
424 rotate(me);
428 private class Floater extends Handle {
429 Floater(int x, int y, int id) {
430 super(x,y, id);
432 public void paint(Graphics2D g, Rectangle srcRect, double mag) {
433 int x = (int)((this.x - srcRect.x)*mag);
434 int y = (int)((this.y - srcRect.y)*mag);
435 Composite co = g.getComposite();
436 g.setXORMode(Color.white);
437 g.drawOval(x -10, y -10, 21, 21);
438 g.drawRect(x -1, y -15, 3, 31);
439 g.drawRect(x -15, y -1, 31, 3);
440 g.setComposite(co); // undo XOR paint
442 public Rectangle getBoundingBox(Rectangle b) {
443 b.x = this.x - 15;
444 b.y = this.y - 15;
445 b.width = this.x + 31;
446 b.height = this.y + 31;
447 return b;
449 public void drag(MouseEvent me, int dx, int dy) {
450 this.x += dx;
451 this.y += dy;
452 RO.x = this.x;
453 RO.y = this.y;
455 public void center() {
456 this.x = RO.x = box.x + box.width/2;
457 this.y = RO.y = box.y + box.height/2;
459 public boolean contains(int x_p, int y_p, double radius) {
460 return super.contains(x_p, y_p, radius*3.5);
464 public void centerFloater() {
465 floater.center();
468 /** No display bounds are checked, the floater can be placed wherever you want. */
469 public void setFloater(int x, int y) {
470 floater.x = x;
471 floater.y = y;
474 public int getFloaterX() { return floater.x; }
475 public int getFloaterY() { return floater.y; }
477 private void setHandles(final Rectangle b) {
478 final int tx = b.x;
479 final int ty = b.y;
480 final int tw = b.width;
481 final int th = b.height;
482 NW.set(tx, ty);
483 N.set(tx + tw/2, ty);
484 NE.set(tx + tw, ty);
485 E.set(tx + tw, ty + th/2);
486 SE.set(tx + tw, ty + th);
487 S.set(tx + tw/2, ty + th);
488 SW.set(tx, ty + th);
489 W.set(tx, ty + th/2);
492 private AffineTransform accum_affine = null;
494 /** Skips current layer, since its done already. */
495 synchronized protected void applyAndPropagate(final Set<Layer> sublist) {
496 if (null == accum_affine) {
497 Utils.log2("Cannot apply to other layers: undo/redo was used.");
498 return;
500 if (0 == sublist.size()) {
501 Utils.logAll("No layers to apply to!");
502 return;
504 // Check if there are links across affected layers
505 if (Displayable.areThereLayerCrossLinks(sublist, false)) {
506 if (ControlWindow.isGUIEnabled()) {
507 YesNoDialog yn = ControlWindow.makeYesNoDialog("Warning!", "Some objects are linked!\nThe transformation would alter interrelationships.\nProceed anyway?");
508 if ( ! yn.yesPressed()) return;
509 } else {
510 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.");
511 return;
514 // Add undo step
515 final ArrayList<Displayable> al = new ArrayList<Displayable>();
516 for (final Layer l : sublist) {
517 al.addAll(l.getDisplayables());
519 display.getLayer().getParent().addTransformStep(al);
522 // Must capture last step of free affine when using affine points:
523 if (null != free_affine && null != model) {
524 accum_affine.preConcatenate(free_affine);
525 accum_affine.preConcatenate(model.createAffine());
528 // Apply!
529 for (final Layer l : sublist) {
530 if (display.getLayer() == l) continue; // already applied
531 l.apply(Displayable.class, accum_affine);
534 // Record current state as last step in undo queue
535 display.getLayer().getParent().addTransformStep(al);
538 public boolean apply() {
539 // Notify each Displayable that any set of temporary transformations are over.
540 // The transform is the same, has not changed. This is just sending an event.
541 for (final Displayable d : display.getSelection().getAffected()) {
542 d.setAffineTransform( d.getAffineTransform() );
544 return true;
547 public boolean cancel() {
548 if (null != history) {
549 // apply first
550 LayerSet.applyTransforms(((TransformationStep)history.get(0)).ht);
552 return true;
555 private class AffinePoint {
556 int x, y;
557 AffinePoint(int x, int y) {
558 this.x = x;
559 this.y = y;
561 public boolean equals(Object ob) {
562 //if (!ob.getClass().equals(AffinePoint.class)) return false;
563 AffinePoint ap = (AffinePoint) ob;
564 double mag = display.getCanvas().getMagnification();
565 double dx = mag * ( ap.x - this.x );
566 double dy = mag * ( ap.y - this.y );
567 double d = dx * dx + dy * dy;
568 return d < 64.0;
570 void translate(int dx, int dy) {
571 x += dx;
572 y += dy;
574 private void paint(Graphics2D g) {
575 int x = display.getCanvas().screenX(this.x);
576 int y = display.getCanvas().screenY(this.y);
577 Utils.drawPoint(g, x, y);
581 private ArrayList<AffinePoint> affine_handles = null;
582 private ArrayList< mpicbg.models.PointMatch > matches = null;
583 private mpicbg.models.Point[] p = null;
584 private mpicbg.models.Point[] q = null;
585 private mpicbg.models.AbstractAffineModel2D<?> model = null;
586 private AffineTransform free_affine = null;
587 private HashMap<Displayable,AffineTransform> initial_affines = null;
590 private void forgetAffine() {
591 affine_handles = null;
592 matches = null;
593 p = q = null;
594 model = null;
595 free_affine = null;
596 initial_affines = null;
600 private void initializeModel() {
601 // Store current "initial" state in the accumulated affine
602 if (null != free_affine && null != model && null != accum_affine) {
603 accum_affine.preConcatenate(free_affine);
604 accum_affine.preConcatenate(model.createAffine());
607 free_affine = new AffineTransform();
608 initial_affines = getTransformationsCopy();
610 int size = affine_handles.size();
612 switch (size) {
613 case 0:
614 model = null;
615 q = p = null;
616 matches = null;
617 return;
618 case 1:
619 model = new mpicbg.models.TranslationModel2D();
620 break;
621 case 2:
622 model = new mpicbg.models.SimilarityModel2D();
623 break;
624 case 3:
625 model = new mpicbg.models.AffineModel2D();
626 break;
628 p = new mpicbg.models.Point[size];
629 q = new mpicbg.models.Point[size];
630 matches = new ArrayList< mpicbg.models.PointMatch >();
631 int i = 0;
632 for (final AffinePoint ap : affine_handles) {
633 p[i] = new mpicbg.models.Point(new float[]{ap.x, ap.y});
634 q[i] = p[i].clone();
635 matches.add(new mpicbg.models.PointMatch(p[i], q[i]));
636 i++;
640 private void freeAffine(AffinePoint affp) {
641 // The selected point
642 final float[] w = q[affine_handles.indexOf(affp)].getW();
643 w[0] = affp.x;
644 w[1] = affp.y;
646 try {
647 model.fit(matches);
648 } catch (Exception e) {}
650 final AffineTransform model_affine = model.createAffine();
651 for (final Map.Entry<Displayable,AffineTransform> e : initial_affines.entrySet()) {
652 final AffineTransform at = new AffineTransform(e.getValue());
653 at.preConcatenate(free_affine);
654 at.preConcatenate(model_affine);
655 e.getKey().setAffineTransform(at);
659 private void fixAffinePoints(final AffineTransform at) {
660 if (null != matches) {
661 float[] po = new float[2];
662 for (final AffinePoint affp : affine_handles) {
663 po[0] = affp.x;
664 po[1] = affp.y;
665 at.transform(po, 0, po, 0, 1);
666 affp.x = (int)po[0];
667 affp.y = (int)po[1];
669 // Model will be reinitialized when needed
670 free_affine.setToIdentity();
671 model = null;
675 private AffinePoint affp = null;
678 public void mousePressed(MouseEvent me, int x_p, int y_p, double magnification) {
679 grabbed = null; // reset
680 if (me.isShiftDown()) {
681 if (Utils.isControlDown(me) && null != affine_handles) {
682 if (affine_handles.remove(new AffinePoint(x_p, y_p))) {
683 if (0 == affine_handles.size()) affine_handles = null;
684 else initializeModel();
686 return;
688 if (null == affine_handles) {
689 affine_handles = new ArrayList<AffinePoint>();
691 if (affine_handles.size() < 3) {
692 affine_handles.add(new AffinePoint(x_p, y_p));
693 if (1 == affine_handles.size()) {
694 free_affine = new AffineTransform();
695 initial_affines = getTransformationsCopy();
697 initializeModel();
699 return;
700 } else if (null != affine_handles) {
701 int index = affine_handles.indexOf(new AffinePoint(x_p, y_p));
702 if (-1 != index) {
703 affp = affine_handles.get(index);
704 return;
708 // find scale handle
709 double radius = 4 / magnification;
710 if (radius < 1) radius = 1;
711 // start with floater (the last)
712 for (int i=handles.length -1; i>-1; i--) {
713 if (handles[i].contains(x_p, y_p, radius)) {
714 grabbed = handles[i];
715 if (grabbed.id > iW && grabbed.id <= ROTATION) rotating = true;
716 return;
720 // if none grabbed, then drag the whole thing
721 dragging = false; //reset
722 if (box.x <= x_p && box.y <= y_p && box.x + box.width >= x_p && box.y + box.height >= y_p) {
723 dragging = true;
726 public void mouseDragged(MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_d_old, int y_d_old) {
727 // Store old for rotation handle:
728 this.x_d = x_d;
729 this.y_d = y_d;
730 this.x_d_old = x_d_old;
731 this.y_d_old = y_d_old;
733 // compute translation
734 int dx = x_d - x_d_old;
735 int dy = y_d - y_d_old;
737 execDrag(me, dx, dy);
738 display.getCanvas().repaint(true);
740 mouse_dragged = true; // after execDrag, so the first undo step is added.
742 private void execDrag(MouseEvent me, int dx, int dy) {
743 if (0 == dx && 0 == dy) return;
744 if (null != affp) {
745 affp.translate(dx, dy);
746 if (null == model) {
747 // Model has been canceled by a transformation from the other handles
748 initializeModel();
750 // Passing on the translation from start
751 freeAffine(affp);
752 return;
755 if (null != grabbed) {
756 // drag the handle and perform whatever task it has assigned
757 grabbed.drag(me, dx, dy);
758 } else if (dragging) {
759 // drag all selected and linked
760 translate(dx, dy);
761 //and the box!
762 box.x += dx;
763 box.y += dy;
764 // and the handles!
765 setHandles(box);
769 public void mouseReleased(MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_r, int y_r) {
771 // Record current state for selected Displayable set, if there was any change:
772 final int dx = x_r - x_p;
773 final int dy = y_r - y_p;
774 if (0 != dx || 0 != dy) {
775 display.getLayerSet().addTransformStep(display.getSelection().getAffected()); // all selected and their links: i.e. all that will change
778 // me is null when calling from Display, because of popup interfering with mouseReleased
779 if (null != me) {
780 execDrag(me, x_r - x_d, y_r - y_d);
781 display.getCanvas().repaint(true);
784 // recalculate box
785 resetBox();
787 //reset
788 if ((null != grabbed && grabbed.id <= iW) || dragging) {
789 floater.center();
792 grabbed = null;
793 dragging = false;
794 rotating = false;
795 affp = null;
796 mouse_dragged = false;
799 /** Recalculate box and reset handles. */
800 public void resetBox() {
801 box = null;
802 Rectangle b = new Rectangle();
803 for (final Displayable d : display.getSelection().getSelected()) {
804 b = d.getBoundingBox(b);
805 if (null == box) box = (Rectangle)b.clone();
806 box.add(b);
808 if (null != box) setHandles(box);
811 /** Rotate the objects in the current selection by the given angle, in degrees, relative to the x_o, y_o origin. */
812 public void rotate(final double angle, final int xo, final int yo) {
813 final AffineTransform at = new AffineTransform();
814 at.rotate(Math.toRadians(angle), xo, yo);
816 addUndoStep();
818 if (null != accum_affine) accum_affine.preConcatenate(at);
820 Displayable.preConcatenate(at, display.getSelection().getAffected());
821 fixAffinePoints(at);
822 resetBox();
825 /** Translate all selected objects and their links by the given differentials. The floater position is unaffected; if you want to update it call centerFloater() */
826 public void translate(final double dx, final double dy) {
827 final AffineTransform at = new AffineTransform();
828 at.translate(dx, dy);
830 addUndoStep();
832 if (null != accum_affine) accum_affine.preConcatenate(at);
834 Displayable.preConcatenate(at, display.getSelection().getAffected());
835 fixAffinePoints(at);
836 resetBox();
839 /** Scale all selected objects and their links by by the given scales, relative to the floater position. . */
840 public void scale(final double sx, final double sy) {
841 if (0 == sx || 0 == sy) {
842 Utils.showMessage("Cannot scale to 0.");
843 return;
846 final AffineTransform at = new AffineTransform();
847 at.translate(floater.x, floater.y);
848 at.scale(sx, sy);
849 at.translate(-floater.x, -floater.y);
851 addUndoStep();
853 if (null != accum_affine) accum_affine.preConcatenate(at);
855 Displayable.preConcatenate(at, display.getSelection().getAffected());
856 fixAffinePoints(at);
857 resetBox();
860 public Rectangle getRepaintBounds() {
861 Rectangle b = display.getSelection().getLinkedBox();
862 b.add(floater.getBoundingBox(new Rectangle()));
863 return b;
866 public void srcRectUpdated(Rectangle srcRect, double magnification) {}
867 public void magnificationUpdated(Rectangle srcRect, double magnification) {}