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
;
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
);
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());
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()));
60 // remove history elements from index+1 to end
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)
81 TransformationStep step
= (TransformationStep
)history
.undoOneStep();
82 if (null == step
) return; // no more steps
83 LayerSet
.applyTransforms(step
.ht
);
86 // call fixAffinePoints with the diff affine transform, as computed from first selected object
92 AffineTransform A
= step
.ht
.get(Be
.getKey()); // the t0
93 AffineTransform C
= new AffineTransform(Be
.getValue());
94 C
.concatenate(A
.createInverse());
96 } catch (Exception 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
);
111 // call fixAffinePoints with the diff affine transform, as computed from first selected object
115 AffineTransform B
= step
.ht
.get(Ae
.getKey());
116 AffineTransform C
= new AffineTransform(Ae
.getValue());
118 C
.concatenate(B
.createInverse());
120 } catch (Exception e
) {
125 public boolean isDragging() {
129 private class ATGS
implements GraphicsSource
{
130 public List
<?
extends Paintable
> asPaintable(final List
<?
extends Paintable
> 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());
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
);
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
);
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
) {
166 g
.setTransform(original
);
167 g
.setStroke(original_stroke
);
171 public GraphicsSource
getGraphicsSource() {
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
{
218 Handle(int x
, int y
, int 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;
230 public void set(int x
, int 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
) {
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
);
254 switch (this.id
) { // java sucks to such an extent, I don't even bother
256 if (x
+ dx
>= E
.x
) return;
257 if (y
+ dy
>= S
.y
) return;
266 if (y
+ dy
>= S
.y
) return;
273 if (x
+ dx
<= W
.x
) return;
274 if (y
+ dy
>= S
.y
) return;
282 if (x
+ dx
<= W
.x
) return;
288 if (x
+ dx
<= W
.x
) return;
289 if (y
+ dy
<= N
.y
) return;
296 if (y
+ dy
<= N
.y
) return;
302 if (x
+ dx
>= E
.x
) return;
303 if (y
+ dy
<= N
.y
) return;
311 if (x
+ dx
>= E
.x
) return;
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
);
326 at
.translate( -anchor_x
, -anchor_y
);
330 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
332 Displayable
.preConcatenate(at
, display
.getSelection().getAffected());
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!
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);
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");
371 double zc
= (x_d_old
- floater
.x
) * (y_d
- floater
.y
) - (x_d
- floater
.x
) * (y_d_old
- floater
.y
);
376 rotate(Math
.toDegrees(delta
), floater
.x
, floater
.y
);
380 private class RotationHandle
extends Handle
{
381 final int shift
= 50;
382 RotationHandle(int x
, int y
, int 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
);
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;
419 public void drag(MouseEvent me
, int dx
, int dy
) {
420 /// Bad design, I know, I'm ignoring the dx,dy
422 // center is the floater
428 private class Floater
extends Handle
{
429 Floater(int x
, int y
, int 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
) {
445 b
.width
= this.x
+ 31;
446 b
.height
= this.y
+ 31;
449 public void drag(MouseEvent me
, int dx
, int dy
) {
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() {
468 /** No display bounds are checked, the floater can be placed wherever you want. */
469 public void setFloater(int x
, int y
) {
474 public int getFloaterX() { return floater
.x
; }
475 public int getFloaterY() { return floater
.y
; }
477 private void setHandles(final Rectangle b
) {
480 final int tw
= b
.width
;
481 final int th
= b
.height
;
483 N
.set(tx
+ tw
/2, ty
);
485 E
.set(tx
+ tw
, ty
+ th
/2);
486 SE
.set(tx
+ tw
, ty
+ th
);
487 S
.set(tx
+ tw
/2, 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.");
500 if (0 == sublist
.size()) {
501 Utils
.logAll("No layers to apply to!");
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;
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.");
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());
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() );
547 public boolean cancel() {
548 if (null != history
) {
550 LayerSet
.applyTransforms(((TransformationStep
)history
.get(0)).ht
);
555 private class AffinePoint
{
557 AffinePoint(int x
, int 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
;
570 void translate(int dx
, int 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;
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();
619 model
= new mpicbg
.models
.TranslationModel2D();
622 model
= new mpicbg
.models
.SimilarityModel2D();
625 model
= new mpicbg
.models
.AffineModel2D();
628 p
= new mpicbg
.models
.Point
[size
];
629 q
= new mpicbg
.models
.Point
[size
];
630 matches
= new ArrayList
< mpicbg
.models
.PointMatch
>();
632 for (final AffinePoint ap
: affine_handles
) {
633 p
[i
] = new mpicbg
.models
.Point(new float[]{ap
.x
, ap
.y
});
635 matches
.add(new mpicbg
.models
.PointMatch(p
[i
], q
[i
]));
640 private void freeAffine(AffinePoint affp
) {
641 // The selected point
642 final float[] w
= q
[affine_handles
.indexOf(affp
)].getW();
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
) {
665 at
.transform(po
, 0, po
, 0, 1);
669 // Model will be reinitialized when needed
670 free_affine
.setToIdentity();
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();
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();
700 } else if (null != affine_handles
) {
701 int index
= affine_handles
.indexOf(new AffinePoint(x_p
, y_p
));
703 affp
= affine_handles
.get(index
);
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;
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
) {
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:
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;
745 affp
.translate(dx
, dy
);
747 // Model has been canceled by a transformation from the other handles
750 // Passing on the translation from start
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
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
780 execDrag(me
, x_r
- x_d
, y_r
- y_d
);
781 display
.getCanvas().repaint(true);
788 if ((null != grabbed
&& grabbed
.id
<= iW
) || dragging
) {
796 mouse_dragged
= false;
799 /** Recalculate box and reset handles. */
800 public void resetBox() {
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();
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
);
818 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
820 Displayable
.preConcatenate(at
, display
.getSelection().getAffected());
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
);
832 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
834 Displayable
.preConcatenate(at
, display
.getSelection().getAffected());
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.");
846 final AffineTransform at
= new AffineTransform();
847 at
.translate(floater
.x
, floater
.y
);
849 at
.translate(-floater
.x
, -floater
.y
);
853 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
855 Displayable
.preConcatenate(at
, display
.getSelection().getAffected());
860 public Rectangle
getRepaintBounds() {
861 Rectangle b
= display
.getSelection().getLinkedBox();
862 b
.add(floater
.getBoundingBox(new Rectangle()));
866 public void srcRectUpdated(Rectangle srcRect
, double magnification
) {}
867 public void magnificationUpdated(Rectangle srcRect
, double magnification
) {}