1 package ini
.trakem2
.display
;
3 import ini
.trakem2
.display
.Display
;
4 import ini
.trakem2
.display
.DisplayCanvas
;
5 import ini
.trakem2
.display
.Displayable
;
6 import ini
.trakem2
.display
.Paintable
;
7 import ini
.trakem2
.display
.Selection
;
8 import ini
.trakem2
.display
.Layer
;
9 import ini
.trakem2
.display
.LayerSet
;
10 import ini
.trakem2
.display
.graphics
.GraphicsSource
;
11 import ini
.trakem2
.display
.TransformationStep
;
12 import ini
.trakem2
.utils
.ProjectToolbar
;
13 import java
.util
.Collection
;
14 import java
.awt
.Rectangle
;
15 import java
.awt
.Graphics2D
;
16 import java
.awt
.event
.MouseEvent
;
17 import java
.awt
.Rectangle
;
18 import java
.awt
.Color
;
19 import java
.awt
.Graphics
;
20 import java
.awt
.Point
;
21 import java
.util
.Collections
;
22 import java
.util
.Collection
;
23 import java
.util
.LinkedList
;
24 import java
.util
.HashSet
;
25 import java
.util
.HashMap
;
26 import java
.util
.List
;
27 import java
.util
.TreeMap
;
28 import java
.util
.Iterator
;
29 import java
.util
.ArrayList
;
32 import java
.awt
.Stroke
;
33 import java
.awt
.BasicStroke
;
34 import java
.awt
.AlphaComposite
;
35 import java
.awt
.Composite
;
36 import java
.awt
.Graphics2D
;
37 import java
.awt
.geom
.AffineTransform
;
38 import java
.awt
.geom
.Area
;
39 import java
.awt
.image
.ColorModel
;
40 import java
.awt
.event
.MouseEvent
;
42 import ini
.trakem2
.utils
.M
;
43 import ini
.trakem2
.utils
.Utils
;
44 import ini
.trakem2
.utils
.IJError
;
45 import ini
.trakem2
.display
.YesNoDialog
;
46 import ini
.trakem2
.utils
.History
;
47 import ini
.trakem2
.ControlWindow
;
48 import ini
.trakem2
.Project
;
50 public class AffineTransformMode
implements Mode
{
52 private final Display display
;
53 private final History history
;
54 private final ATGS atgs
= new AffineTransformMode
.ATGS();
56 public AffineTransformMode(final Display display
) {
57 this.display
= display
;
58 ProjectToolbar
.setTool(ProjectToolbar
.SELECT
);
62 this.handles
= new Handle
[]{NW
, N
, NE
, E
, SE
, S
, SW
, W
, RO
, floater
};
63 accum_affine
= new AffineTransform();
64 history
= new History(); // unlimited steps
65 history
.add(new TransformationStep(getTransformationsCopy()));
66 display
.getCanvas().repaint(false);
69 /** 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. */
70 private HashMap
<Displayable
,AffineTransform
> getTransformationsCopy() {
71 final HashMap
<Displayable
,AffineTransform
> ht_copy
= new HashMap
<Displayable
,AffineTransform
>();
72 for (final Displayable d
: display
.getSelection().getAffected()) {
73 ht_copy
.put(d
, d
.getAffineTransformCopy());
78 /** Add an undo step to the internal history. */
79 private void addUndoStep() {
80 if (mouse_dragged
|| display
.getSelection().isEmpty()) return;
81 if (null == history
) return;
82 if (history
.indexAtStart() || (history
.indexAtEnd() && -1 != history
.index())) {
83 history
.add(new TransformationStep(getTransformationsCopy()));
85 // remove history elements from index+1 to end
90 synchronized public void undoOneStep() {
91 LayerSet layerset
= display
.getLayer().getParent();
92 if (null == history
) return;
93 // store the current state if at end:
94 Utils
.log2("index at end: " + history
.indexAtEnd());
96 Map
.Entry
<Displayable
,AffineTransform
> Be
= ((TransformationStep
)history
.getCurrent()).ht
.entrySet().iterator().next();
98 if (history
.indexAtEnd()) {
99 HashMap
<Displayable
,AffineTransform
> m
= getTransformationsCopy();
100 history
.append(new TransformationStep(m
));
101 Be
= m
.entrySet().iterator().next(); // must set again, for the other one was the last step, not the current state.
104 // disable application to other layers (too big a headache)
107 TransformationStep step
= (TransformationStep
)history
.undoOneStep();
108 if (null == step
) return; // no more steps
109 LayerSet
.applyTransforms(step
.ht
);
112 // call fixAffinePoints with the diff affine transform, as computed from first selected object
118 AffineTransform A
= step
.ht
.get(Be
.getKey()); // the t0
119 AffineTransform C
= new AffineTransform(Be
.getValue());
120 C
.concatenate(A
.createInverse());
122 } catch (Exception e
) {
127 synchronized public void redoOneStep() {
128 if (null == history
) return;
130 Map
.Entry
<Displayable
,AffineTransform
> Ae
= ((TransformationStep
)history
.getCurrent()).ht
.entrySet().iterator().next();
132 TransformationStep step
= (TransformationStep
)history
.redoOneStep();
133 if (null == step
) return; // no more steps
134 LayerSet
.applyTransforms(step
.ht
);
137 // call fixAffinePoints with the diff affine transform, as computed from first selected object
141 AffineTransform B
= step
.ht
.get(Ae
.getKey());
142 AffineTransform C
= new AffineTransform(Ae
.getValue());
144 C
.concatenate(B
.createInverse());
146 } catch (Exception e
) {
151 public boolean isDragging() {
155 private class ATGS
implements GraphicsSource
{
156 public List
<?
extends Paintable
> asPaintable(final List
<?
extends Paintable
> ds
) {
159 /** Paints the transformation handles and a bounding box around all selected. */
160 public void paintOnTop(final Graphics2D g
, final Display display
, final Rectangle srcRect
, final double magnification
) {
161 final Stroke original_stroke
= g
.getStroke();
162 AffineTransform original
= g
.getTransform();
163 g
.setTransform(new AffineTransform());
165 //Utils.log("box painting: " + box);
167 // 30 pixel line, 10 pixel gap, 10 pixel line, 10 pixel gap
168 float mag
= (float)magnification
;
169 float[] dashPattern
= { 30, 10, 10, 10 };
170 g
.setStroke(new BasicStroke(2, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
, 10, dashPattern
, 0));
171 g
.setColor(Color
.yellow
);
173 //g.drawRect(box.x, box.y, box.width, box.height);
174 g
.draw(original
.createTransformedShape(box
));
175 g
.setStroke(new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
));
176 // paint handles for scaling (boxes) and rotating (circles), and floater
177 for (int i
=0; i
<handles
.length
; i
++) {
178 handles
[i
].paint(g
, srcRect
, magnification
);
181 g
.setStroke(new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
));
182 RO
.paint(g
, srcRect
, magnification
);
183 ((RotationHandle
)RO
).paintMoving(g
, srcRect
, magnification
, display
.getCanvas().getCursorLoc());
186 if (null != affine_handles
) {
187 for (final AffinePoint ap
: affine_handles
) {
192 g
.setTransform(original
);
193 g
.setStroke(original_stroke
);
197 public GraphicsSource
getGraphicsSource() {
201 public boolean canChangeLayer() { return false; }
202 public boolean canZoom() { return true; }
203 public boolean canPan() { return true; }
208 /* From former Selection class: the affine transformation GUI */
210 private final int iNW
= 0;
211 private final int iN
= 1;
212 private final int iNE
= 2;
213 private final int iE
= 3;
214 private final int iSE
= 4;
215 private final int iS
= 5;
216 private final int iSW
= 6;
217 private final int iW
= 7;
218 private final int rNW
= 8;
219 private final int rNE
= 9;
220 private final int rSE
= 10;
221 private final int rSW
= 11;
222 private final int ROTATION
= 12;
223 private final int FLOATER
= 13;
224 private final Handle NW
= new BoxHandle(0,0, iNW
);
225 private final Handle N
= new BoxHandle(0,0, iN
);
226 private final Handle NE
= new BoxHandle(0,0, iNE
);
227 private final Handle E
= new BoxHandle(0,0, iE
);
228 private final Handle SE
= new BoxHandle(0,0, iSE
);
229 private final Handle S
= new BoxHandle(0,0, iS
);
230 private final Handle SW
= new BoxHandle(0,0, iSW
);
231 private final Handle W
= new BoxHandle(0,0, iW
);
232 private final Handle RO
= new RotationHandle(0,0, ROTATION
);
233 /** Pivot of rotation. Always checked first on mouse pressed, before other handles. */
234 private final Floater floater
= new Floater(0, 0, FLOATER
);
235 private final Handle
[] handles
;
236 private Handle grabbed
= null;
237 private boolean dragging
= false; // means: dragging the whole transformation box
238 private boolean rotating
= false;
239 private boolean mouse_dragged
= false;
240 private Rectangle box
;
242 private int x_d_old
, y_d_old
, x_d
, y_d
;
244 /** Handles have screen coordinates. */
245 private abstract class Handle
{
248 Handle(int x
, int y
, int id
) {
253 abstract public void paint(Graphics2D g
, Rectangle srcRect
, double mag
);
254 /** Radius is the dectection "radius" around the handle x,y. */
255 public boolean contains(int x_p
, int y_p
, double radius
) {
256 if (x
- radius
<= x_p
&& x
+ radius
>= x_p
257 && y
- radius
<= y_p
&& y
+ radius
>= y_p
) return true;
260 public void set(int x
, int y
) {
264 abstract void drag(MouseEvent me
, int dx
, int dy
);
267 private class BoxHandle
extends Handle
{
268 BoxHandle(int x
, int y
, int id
) {
271 public void paint(final Graphics2D g
, final Rectangle srcRect
, final double mag
) {
272 final int x
= (int)((this.x
- srcRect
.x
)*mag
);
273 final int y
= (int)((this.y
- srcRect
.y
)*mag
);
274 DisplayCanvas
.drawHandle(g
, x
, y
, 1.0); // ignoring magnification for the sizes, since Selection is painted differently
276 public void drag(MouseEvent me
, int dx
, int dy
) {
277 Rectangle box_old
= (Rectangle
)box
.clone();
278 //Utils.log2("dx,dy: " + dx + "," + dy + " before mod");
279 double res
= dx
/ 2.0;
280 res
-= Math
.floor(res
);
282 int extra
= (int)res
;
285 switch (this.id
) { // java sucks to such an extent, I don't even bother
287 if (x
+ dx
>= E
.x
) return;
288 if (y
+ dy
>= S
.y
) return;
297 if (y
+ dy
>= S
.y
) return;
304 if (x
+ dx
<= W
.x
) return;
305 if (y
+ dy
>= S
.y
) return;
313 if (x
+ dx
<= W
.x
) return;
319 if (x
+ dx
<= W
.x
) return;
320 if (y
+ dy
<= N
.y
) return;
327 if (y
+ dy
<= N
.y
) return;
333 if (x
+ dx
>= E
.x
) return;
334 if (y
+ dy
<= N
.y
) return;
342 if (x
+ dx
>= E
.x
) return;
350 double px
= (double)box
.width
/ (double)box_old
.width
;
351 double py
= (double)box
.height
/ (double)box_old
.height
;
352 // displacement: specific of each element of the selection and their links, depending on where they are.
354 final AffineTransform at
= new AffineTransform();
355 at
.translate( anchor_x
, anchor_y
);
357 at
.translate( -anchor_x
, -anchor_y
);
361 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
363 for (final Displayable d
: display
.getSelection().getAffected()) {
364 d
.preTransform(at
, false);
369 setHandles(box
); // overkill. As Graham said, most newly available chip resources are going to be wasted. They are already.
373 private final double rotate(MouseEvent me
) {
374 // center of rotation is the floater
375 double cos
= Utils
.getCos(x_d_old
- floater
.x
, y_d_old
- floater
.y
, x_d
- floater
.x
, y_d
- floater
.y
);
376 //double sin = Math.sqrt(1 - cos*cos);
377 //double delta = M.getAngle(cos, sin);
378 double delta
= Math
.acos(cos
); // same thing as the two lines above
379 // need to compute the sign of rotation as well: the cross-product!
381 // a = (3,0,0) and b = (0,2,0)
382 // 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).
384 if (Utils
.isControlDown(me
)) {
385 delta
= Math
.toDegrees(delta
);
386 if (me
.isShiftDown()) {
387 // 1 degree angle increments
388 delta
= (int)(delta
+ 0.5);
390 // 10 degrees angle increments: snap to closest
391 delta
= (int)((delta
+ 5.5 * (delta
< 0 ?
-1 : 1)) / 10) * 10;
393 Utils
.showStatus("Angle: " + delta
+ " degrees");
394 delta
= Math
.toRadians(delta
);
396 // 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.
399 if (Double
.isNaN(delta
)) {
400 Utils
.log2("Selection rotation handle: ignoring NaN angle");
404 double zc
= (x_d_old
- floater
.x
) * (y_d
- floater
.y
) - (x_d
- floater
.x
) * (y_d_old
- floater
.y
);
409 rotate(Math
.toDegrees(delta
), floater
.x
, floater
.y
);
413 private class RotationHandle
extends Handle
{
414 final int shift
= 50;
415 RotationHandle(int x
, int y
, int id
) {
418 public void paint(final Graphics2D g
, final Rectangle srcRect
, final double mag
) {
419 final int x
= (int)((this.x
- srcRect
.x
)*mag
) + shift
;
420 final int y
= (int)((this.y
- srcRect
.y
)*mag
);
421 final int fx
= (int)((floater
.x
- srcRect
.x
)*mag
);
422 final int fy
= (int)((floater
.y
- srcRect
.y
)*mag
);
423 draw(g
, fx
, fy
, x
, y
);
425 private void draw(final Graphics2D g
, int fx
, int fy
, int x
, int y
) {
426 g
.setColor(Color
.white
);
427 g
.drawLine(fx
, fy
, x
, y
);
428 g
.fillOval(x
-4, y
-4, 9, 9);
429 g
.setColor(Color
.black
);
430 g
.drawOval(x
-2, y
-2, 5, 5);
432 public void paintMoving(final Graphics2D g
, final Rectangle srcRect
, final double mag
, final Point mouse
) {
433 // mouse as xMouse,yMouse from ImageCanvas: world coordinates, not screen!
434 final int fx
= (int)((floater
.x
- srcRect
.x
)*mag
);
435 final int fy
= (int)((floater
.y
- srcRect
.y
)*mag
);
437 double vx
= (mouse
.x
- srcRect
.x
)*mag
- fx
;
438 double vy
= (mouse
.y
- srcRect
.y
)*mag
- fy
;
439 //double len = Math.sqrt(vx*vx + vy*vy);
440 //vx = (vx / len) * 50;
441 //vy = (vy / len) * 50;
442 draw(g
, fx
, fy
, fx
+ (int)vx
, fy
+ (int)vy
);
444 public boolean contains(int x_p
, int y_p
, double radius
) {
445 final double mag
= display
.getCanvas().getMagnification();
446 final double x
= this.x
+ shift
/ mag
;
447 final double y
= this.y
;
448 if (x
- radius
<= x_p
&& x
+ radius
>= x_p
449 && y
- radius
<= y_p
&& y
+ radius
>= y_p
) return true;
452 public void drag(MouseEvent me
, int dx
, int dy
) {
453 /// Bad design, I know, I'm ignoring the dx,dy
455 // center is the floater
461 private class Floater
extends Handle
{
462 Floater(int x
, int y
, int id
) {
465 public void paint(Graphics2D g
, Rectangle srcRect
, double mag
) {
466 int x
= (int)((this.x
- srcRect
.x
)*mag
);
467 int y
= (int)((this.y
- srcRect
.y
)*mag
);
468 Composite co
= g
.getComposite();
469 g
.setXORMode(Color
.white
);
470 g
.drawOval(x
-10, y
-10, 21, 21);
471 g
.drawRect(x
-1, y
-15, 3, 31);
472 g
.drawRect(x
-15, y
-1, 31, 3);
473 g
.setComposite(co
); // undo XOR paint
475 public Rectangle
getBoundingBox(Rectangle b
) {
478 b
.width
= this.x
+ 31;
479 b
.height
= this.y
+ 31;
482 public void drag(MouseEvent me
, int dx
, int dy
) {
488 public void center() {
489 this.x
= RO
.x
= box
.x
+ box
.width
/2;
490 this.y
= RO
.y
= box
.y
+ box
.height
/2;
492 public boolean contains(int x_p
, int y_p
, double radius
) {
493 return super.contains(x_p
, y_p
, radius
*3.5);
497 public void centerFloater() {
501 /** No display bounds are checked, the floater can be placed wherever you want. */
502 public void setFloater(int x
, int y
) {
507 public int getFloaterX() { return floater
.x
; }
508 public int getFloaterY() { return floater
.y
; }
510 private void setHandles(final Rectangle b
) {
513 final int tw
= b
.width
;
514 final int th
= b
.height
;
516 N
.set(tx
+ tw
/2, ty
);
518 E
.set(tx
+ tw
, ty
+ th
/2);
519 SE
.set(tx
+ tw
, ty
+ th
);
520 S
.set(tx
+ tw
/2, ty
+ th
);
522 W
.set(tx
, ty
+ th
/2);
525 private AffineTransform accum_affine
= null;
527 /** Skips current layer, since its done already. */
528 synchronized protected void applyAndPropagate(final Set
<Layer
> sublist
) {
529 if (null == accum_affine
) {
530 Utils
.log2("Cannot apply to other layers: undo/redo was used.");
533 if (0 == sublist
.size()) {
534 Utils
.logAll("No layers to apply to!");
537 // Check if there are links across affected layers
538 if (Displayable
.areThereLayerCrossLinks(sublist
, false)) {
539 if (ControlWindow
.isGUIEnabled()) {
540 YesNoDialog yn
= ControlWindow
.makeYesNoDialog("Warning!", "Some objects are linked!\nThe transformation would alter interrelationships.\nProceed anyway?");
541 if ( ! yn
.yesPressed()) return;
543 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.");
548 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
549 for (final Layer l
: sublist
) {
550 al
.addAll(l
.getDisplayables());
552 display
.getLayer().getParent().addTransformStep(al
);
555 // Must capture last step of free affine when using affine points:
556 if (null != free_affine
&& null != model
) {
557 accum_affine
.preConcatenate(free_affine
);
558 accum_affine
.preConcatenate(model
.createAffine());
562 for (final Layer l
: sublist
) {
563 if (display
.getLayer() == l
) continue; // already applied
564 l
.apply(Displayable
.class, accum_affine
);
567 // Record current state as last step in undo queue
568 display
.getLayer().getParent().addTransformStep(al
);
571 public boolean apply() {
572 // Notify each Displayable that any set of temporary transformations are over.
573 // The transform is the same, has not changed. This is just sending an event.
574 for (final Displayable d
: display
.getSelection().getAffected()) {
575 d
.setAffineTransform( d
.getAffineTransform() );
580 public boolean cancel() {
581 if (null != history
) {
583 display
.getLayer().getParent().applyTransforms(((TransformationStep
)history
.get(0)).ht
);
588 private class AffinePoint
{
590 AffinePoint(int x
, int y
) {
594 public boolean equals(Object ob
) {
595 //if (!ob.getClass().equals(AffinePoint.class)) return false;
596 AffinePoint ap
= (AffinePoint
) ob
;
597 double mag
= display
.getCanvas().getMagnification();
598 double dx
= mag
* ( ap
.x
- this.x
);
599 double dy
= mag
* ( ap
.y
- this.y
);
600 double d
= dx
* dx
+ dy
* dy
;
603 void translate(int dx
, int dy
) {
607 private void paint(Graphics2D g
) {
608 int x
= display
.getCanvas().screenX(this.x
);
609 int y
= display
.getCanvas().screenY(this.y
);
610 Utils
.drawPoint(g
, x
, y
);
614 private ArrayList
<AffinePoint
> affine_handles
= null;
615 private ArrayList
< mpicbg
.models
.PointMatch
> matches
= null;
616 private mpicbg
.models
.Point
[] p
= null;
617 private mpicbg
.models
.Point
[] q
= null;
618 private mpicbg
.models
.AbstractAffineModel2D
<?
> model
= null;
619 private AffineTransform free_affine
= null;
620 private HashMap initial_affines
= null;
622 private void forgetAffine() {
623 affine_handles
= null;
628 initial_affines
= null;
631 private void initializeModel() {
632 // Store current "initial" state in the accumulated affine
633 if (null != free_affine
&& null != model
&& null != accum_affine
) {
634 accum_affine
.preConcatenate(free_affine
);
635 accum_affine
.preConcatenate(model
.createAffine());
638 free_affine
= new AffineTransform();
639 initial_affines
= getTransformationsCopy();
641 int size
= affine_handles
.size();
650 model
= new mpicbg
.models
.TranslationModel2D();
653 model
= new mpicbg
.models
.SimilarityModel2D();
656 model
= new mpicbg
.models
.AffineModel2D();
659 p
= new mpicbg
.models
.Point
[size
];
660 q
= new mpicbg
.models
.Point
[size
];
661 matches
= new ArrayList
< mpicbg
.models
.PointMatch
>();
663 for (final AffinePoint ap
: affine_handles
) {
664 p
[i
] = new mpicbg
.models
.Point(new float[]{ap
.x
, ap
.y
});
666 matches
.add(new mpicbg
.models
.PointMatch(p
[i
], q
[i
]));
671 private void freeAffine(AffinePoint affp
) {
672 // The selected point
673 final float[] w
= q
[affine_handles
.indexOf(affp
)].getW();
679 } catch (Exception e
) {}
681 final AffineTransform model_affine
= model
.createAffine();
682 for (final Iterator it
= initial_affines
.entrySet().iterator(); it
.hasNext(); ) {
683 final Map
.Entry e
= (Map
.Entry
)it
.next();
684 final Displayable d
= (Displayable
)e
.getKey();
685 final AffineTransform at
= new AffineTransform((AffineTransform
)e
.getValue());
686 at
.preConcatenate(free_affine
);
687 at
.preConcatenate(model_affine
);
688 d
.setAffineTransform(at
);
692 private void fixAffinePoints(final AffineTransform at
) {
693 if (null != matches
) {
694 float[] po
= new float[2];
695 for (final AffinePoint affp
: affine_handles
) {
698 at
.transform(po
, 0, po
, 0, 1);
702 // Model will be reinitialized when needed
703 free_affine
.setToIdentity();
708 private AffinePoint affp
= null;
711 public void mousePressed(MouseEvent me
, int x_p
, int y_p
, double magnification
) {
712 grabbed
= null; // reset
713 if (me
.isShiftDown()) {
714 if (Utils
.isControlDown(me
) && null != affine_handles
) {
715 if (affine_handles
.remove(new AffinePoint(x_p
, y_p
))) {
716 if (0 == affine_handles
.size()) affine_handles
= null;
717 else initializeModel();
721 if (null == affine_handles
) {
722 affine_handles
= new ArrayList
<AffinePoint
>();
724 if (affine_handles
.size() < 3) {
725 affine_handles
.add(new AffinePoint(x_p
, y_p
));
726 if (1 == affine_handles
.size()) {
727 free_affine
= new AffineTransform();
728 initial_affines
= getTransformationsCopy();
733 } else if (null != affine_handles
) {
734 int index
= affine_handles
.indexOf(new AffinePoint(x_p
, y_p
));
736 affp
= affine_handles
.get(index
);
742 double radius
= 4 / magnification
;
743 if (radius
< 1) radius
= 1;
744 // start with floater (the last)
745 for (int i
=handles
.length
-1; i
>-1; i
--) {
746 if (handles
[i
].contains(x_p
, y_p
, radius
)) {
747 grabbed
= handles
[i
];
748 if (grabbed
.id
>= rNW
&& grabbed
.id
<= ROTATION
) rotating
= true;
753 // if none grabbed, then drag the whole thing
754 dragging
= false; //reset
755 if (box
.x
<= x_p
&& box
.y
<= y_p
&& box
.x
+ box
.width
>= x_p
&& box
.y
+ box
.height
>= y_p
) {
759 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
) {
760 // Store old for rotation handle:
763 this.x_d_old
= x_d_old
;
764 this.y_d_old
= y_d_old
;
766 // compute translation
767 int dx
= x_d
- x_d_old
;
768 int dy
= y_d
- y_d_old
;
770 execDrag(me
, dx
, dy
);
772 mouse_dragged
= true; // after execDrag, so the first undo step is added.
774 private void execDrag(MouseEvent me
, int dx
, int dy
) {
775 if (0 == dx
&& 0 == dy
) return;
777 affp
.translate(dx
, dy
);
779 // Model has been canceled by a transformation from the other handles
782 // Passing on the translation from start
787 if (null != grabbed
) {
788 // drag the handle and perform whatever task it has assigned
789 grabbed
.drag(me
, dx
, dy
);
790 } else if (dragging
) {
791 // drag all selected and linked
801 public void mouseReleased(MouseEvent me
, int x_p
, int y_p
, int x_d
, int y_d
, int x_r
, int y_r
) {
803 // Record current state for selected Displayable set, if there was any change:
804 final int dx
= x_r
- x_p
;
805 final int dy
= y_r
- y_p
;
806 if (0 != dx
|| 0 != dy
) {
807 display
.getLayerSet().addTransformStep(display
.getSelection().getAffected()); // all selected and their links: i.e. all that will change
810 // me is null when calling from Display, because of popup interfering with mouseReleased
811 if (null != me
) execDrag(me
, x_r
- x_d
, y_r
- y_d
);
817 if ((null != grabbed
&& grabbed
.id
<= iW
) || dragging
) {
824 mouse_dragged
= false;
827 /** Recalculate box and reset handles. */
828 public void resetBox() {
830 Rectangle b
= new Rectangle();
831 for (final Displayable d
: display
.getSelection().getSelected()) {
832 b
= d
.getBoundingBox(b
);
833 if (null == box
) box
= (Rectangle
)b
.clone();
836 if (null != box
) setHandles(box
);
839 /** Rotate the objects in the current selection by the given angle, in degrees, relative to the x_o, y_o origin. */
840 public void rotate(final double angle
, final int xo
, final int yo
) {
841 final AffineTransform at
= new AffineTransform();
842 at
.rotate(Math
.toRadians(angle
), xo
, yo
);
846 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
848 for (final Displayable d
: display
.getSelection().getAffected()) {
849 d
.preTransform(at
, false); // all linked ones included in the hashset
855 /** Translate all selected objects and their links by the given differentials. The floater position is unaffected; if you want to update it call centerFloater() */
856 public void translate(final double dx
, final double dy
) {
857 final AffineTransform at
= new AffineTransform();
858 at
.translate(dx
, dy
);
862 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
864 for (final Displayable d
: display
.getSelection().getAffected()) {
865 d
.preTransform(at
, false); // all linked ones already included in the hashset
871 /** Scale all selected objects and their links by by the given scales, relative to the floater position. . */
872 public void scale(final double sx
, final double sy
) {
873 if (0 == sx
|| 0 == sy
) {
874 Utils
.showMessage("Cannot scale to 0.");
878 final AffineTransform at
= new AffineTransform();
879 at
.translate(floater
.x
, floater
.y
);
881 at
.translate(-floater
.x
, -floater
.y
);
885 if (null != accum_affine
) accum_affine
.preConcatenate(at
);
887 for (final Displayable d
: display
.getSelection().getAffected()) {
888 d
.preTransform(at
, false); // all linked ones already included in the hashset
894 public Rectangle
getRepaintBounds() {
895 Rectangle b
= display
.getSelection().getLinkedBox();
896 b
.add(floater
.getBoundingBox(new Rectangle()));
900 public void srcRectUpdated(Rectangle srcRect
, double magnification
) {}
901 public void magnificationUpdated(Rectangle srcRect
, double magnification
) {}