1 /*
2 * Copyright 2006-2009, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Stephan Aßmus <superstippi@gmx.de>
7 */
8
9 #include "TransformBox.h"
10
11 #include <stdio.h>
12
13 #include <agg_trans_affine.h>
14 #include <agg_math.h>
15
16 #include <View.h>
17
18 #include "support.h"
19
20 #include "TransformBoxStates.h"
21 #include "StateView.h"
22 #include "TransformCommand.h"
23
24
25 #define INSET 8.0
26
27
28 using namespace TransformBoxStates;
29
30
31 // constructor
TransformBox(StateView * view,BRect box)32 TransformBox::TransformBox(StateView* view, BRect box)
33 :
34 ChannelTransform(),
35 Manipulator(NULL),
36 fOriginalBox(box),
37
38 fLeftTop(box.LeftTop()),
39 fRightTop(box.RightTop()),
40 fLeftBottom(box.LeftBottom()),
41 fRightBottom(box.RightBottom()),
42
43 fPivot((fLeftTop.x + fRightBottom.x) / 2.0,
44 (fLeftTop.y + fRightBottom.y) / 2.0),
45 fPivotOffset(B_ORIGIN),
46 fCurrentCommand(NULL),
47 fCurrentState(NULL),
48
49 fDragging(false),
50 fMousePos(-10000.0, -10000.0),
51 fModifiers(0),
52
53 fNudging(false),
54
55 fView(view),
56
57 fDragLTState(new DragCornerState(this, DragCornerState::LEFT_TOP_CORNER)),
58 fDragRTState(new DragCornerState(this, DragCornerState::RIGHT_TOP_CORNER)),
59 fDragLBState(new DragCornerState(this, DragCornerState::LEFT_BOTTOM_CORNER)),
60 fDragRBState(new DragCornerState(this, DragCornerState::RIGHT_BOTTOM_CORNER)),
61
62 fDragLState(new DragSideState(this, DragSideState::LEFT_SIDE)),
63 fDragRState(new DragSideState(this, DragSideState::RIGHT_SIDE)),
64 fDragTState(new DragSideState(this, DragSideState::TOP_SIDE)),
65 fDragBState(new DragSideState(this, DragSideState::BOTTOM_SIDE)),
66
67 fRotateState(new RotateBoxState(this)),
68 fTranslateState(new DragBoxState(this)),
69 fOffsetCenterState(new OffsetCenterState(this))
70 {
71 }
72
73
74 // destructor
~TransformBox()75 TransformBox::~TransformBox()
76 {
77 _NotifyDeleted();
78
79 delete fCurrentCommand;
80
81 delete fDragLTState;
82 delete fDragRTState;
83 delete fDragLBState;
84 delete fDragRBState;
85
86 delete fDragLState;
87 delete fDragRState;
88 delete fDragTState;
89 delete fDragBState;
90
91 delete fRotateState;
92 delete fTranslateState;
93 delete fOffsetCenterState;
94 }
95
96
97 // Draw
98 void
Draw(BView * into,BRect updateRect)99 TransformBox::Draw(BView* into, BRect updateRect)
100 {
101 // convert to canvas view coordinates
102 BPoint lt = fLeftTop;
103 BPoint rt = fRightTop;
104 BPoint lb = fLeftBottom;
105 BPoint rb = fRightBottom;
106 BPoint c = fPivot;
107
108 TransformFromCanvas(lt);
109 TransformFromCanvas(rt);
110 TransformFromCanvas(lb);
111 TransformFromCanvas(rb);
112 TransformFromCanvas(c);
113
114 into->SetDrawingMode(B_OP_COPY);
115 into->SetHighColor(255, 255, 255, 255);
116 into->SetLowColor(0, 0, 0, 255);
117 _StrokeBWLine(into, lt, rt);
118 _StrokeBWLine(into, rt, rb);
119 _StrokeBWLine(into, rb, lb);
120 _StrokeBWLine(into, lb, lt);
121
122 double rotation = ViewSpaceRotation();
123 _StrokeBWPoint(into, lt, rotation);
124 _StrokeBWPoint(into, rt, rotation + 90.0);
125 _StrokeBWPoint(into, rb, rotation + 180.0);
126 _StrokeBWPoint(into, lb, rotation + 270.0);
127
128 BRect cr(c, c);
129 cr.InsetBy(-3.0, -3.0);
130 into->StrokeEllipse(cr, B_SOLID_HIGH);
131 cr.InsetBy(1.0, 1.0);
132 into->StrokeEllipse(cr, B_SOLID_LOW);
133 into->SetDrawingMode(B_OP_COPY);
134 }
135
136
137 // #pragma mark -
138
139
140 // MouseDown
141 bool
MouseDown(BPoint where)142 TransformBox::MouseDown(BPoint where)
143 {
144 fView->FilterMouse(&where);
145 TransformToCanvas(where);
146
147 fDragging = true;
148 if (fCurrentState) {
149 fCurrentState->SetOrigin(where);
150
151 delete fCurrentCommand;
152 fCurrentCommand = MakeCommand(fCurrentState->ActionName());
153 }
154
155 return true;
156 }
157
158
159 // MouseMoved
160 void
MouseMoved(BPoint where)161 TransformBox::MouseMoved(BPoint where)
162 {
163 fView->FilterMouse(&where);
164 TransformToCanvas(where);
165
166 if (fMousePos != where) {
167 fMousePos = where;
168 if (fCurrentState) {
169 fCurrentState->DragTo(fMousePos, fModifiers);
170 fCurrentState->UpdateViewCursor(fView, fMousePos);
171 }
172 }
173 }
174
175
176 // MouseUp
177 Command*
MouseUp()178 TransformBox::MouseUp()
179 {
180 fDragging = false;
181 return FinishTransaction();
182 }
183
184
185 // MouseOver
186 bool
MouseOver(BPoint where)187 TransformBox::MouseOver(BPoint where)
188 {
189 TransformToCanvas(where);
190
191 fMousePos = where;
192 fCurrentState = _DragStateFor(where, ZoomLevel());
193 fCurrentState->UpdateViewCursor(fView, fMousePos);
194
195 return true;
196 }
197
198
199 // DoubleClicked
200 bool
DoubleClicked(BPoint where)201 TransformBox::DoubleClicked(BPoint where)
202 {
203 return false;
204 }
205
206
207 // #pragma mark -
208
209
210 // Bounds
211 BRect
Bounds()212 TransformBox::Bounds()
213 {
214 // convert from canvas view coordinates
215 BPoint lt = fLeftTop;
216 BPoint rt = fRightTop;
217 BPoint lb = fLeftBottom;
218 BPoint rb = fRightBottom;
219 BPoint c = fPivot;
220
221 TransformFromCanvas(lt);
222 TransformFromCanvas(rt);
223 TransformFromCanvas(lb);
224 TransformFromCanvas(rb);
225 TransformFromCanvas(c);
226
227 BRect bounds;
228 bounds.left = min5(lt.x, rt.x, lb.x, rb.x, c.x);
229 bounds.top = min5(lt.y, rt.y, lb.y, rb.y, c.y);
230 bounds.right = max5(lt.x, rt.x, lb.x, rb.x, c.x);
231 bounds.bottom = max5(lt.y, rt.y, lb.y, rb.y, c.y);
232 return bounds;
233 }
234
235
236 // TrackingBounds
237 BRect
TrackingBounds(BView * withinView)238 TransformBox::TrackingBounds(BView* withinView)
239 {
240 return withinView->Bounds();
241 }
242
243
244 // #pragma mark -
245
246
247 // ModifiersChanged
248 void
ModifiersChanged(uint32 modifiers)249 TransformBox::ModifiersChanged(uint32 modifiers)
250 {
251 fModifiers = modifiers;
252 if (fDragging && fCurrentState) {
253 fCurrentState->DragTo(fMousePos, fModifiers);
254 }
255 }
256
257
258 // HandleKeyDown
259 bool
HandleKeyDown(uint32 key,uint32 modifiers,Command ** _command)260 TransformBox::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command)
261 {
262 bool handled = true;
263 BPoint translation(B_ORIGIN);
264
265 float offset = 1.0;
266 if (modifiers & B_SHIFT_KEY)
267 offset /= ZoomLevel();
268
269 switch (key) {
270 case B_UP_ARROW:
271 translation.y = -offset;
272 break;
273 case B_DOWN_ARROW:
274 translation.y = offset;
275 break;
276 case B_LEFT_ARROW:
277 translation.x = -offset;
278 break;
279 case B_RIGHT_ARROW:
280 translation.x = offset;
281 break;
282
283 default:
284 handled = false;
285 break;
286 }
287
288 if (!handled)
289 return false;
290
291 if (!fCurrentCommand) {
292 fCurrentCommand = MakeCommand("Translate");
293 }
294
295 TranslateBy(translation);
296
297 return true;
298 }
299
300
301 // HandleKeyUp
302 bool
HandleKeyUp(uint32 key,uint32 modifiers,Command ** _command)303 TransformBox::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command)
304 {
305 if (fCurrentCommand) {
306 *_command = FinishTransaction();
307 return true;
308 }
309 return false;
310 }
311
312
313 // UpdateCursor
314 bool
UpdateCursor()315 TransformBox::UpdateCursor()
316 {
317 if (fCurrentState) {
318 fCurrentState->UpdateViewCursor(fView, fMousePos);
319 return true;
320 }
321 return false;
322 }
323
324
325 // #pragma mark -
326
327
328 // AttachedToView
329 void
AttachedToView(BView * view)330 TransformBox::AttachedToView(BView* view)
331 {
332 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
333 }
334
335
336 // DetachedFromView
337 void
DetachedFromView(BView * view)338 TransformBox::DetachedFromView(BView* view)
339 {
340 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
341 }
342
343
344 // pragma mark -
345
346
347 // Update
348 void
Update(bool deep)349 TransformBox::Update(bool deep)
350 {
351 // recalculate the points from the original box
352 fLeftTop = fOriginalBox.LeftTop();
353 fRightTop = fOriginalBox.RightTop();
354 fLeftBottom = fOriginalBox.LeftBottom();
355 fRightBottom = fOriginalBox.RightBottom();
356
357 fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0;
358 fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0;
359
360 fPivot += fPivotOffset;
361
362 // transform the points for display
363 Transform(&fLeftTop);
364 Transform(&fRightTop);
365 Transform(&fLeftBottom);
366 Transform(&fRightBottom);
367
368 Transform(&fPivot);
369 }
370
371
372 // OffsetCenter
373 void
OffsetCenter(BPoint offset)374 TransformBox::OffsetCenter(BPoint offset)
375 {
376 if (offset != BPoint(0.0, 0.0)) {
377 fPivotOffset += offset;
378 Update(false);
379 }
380 }
381
382
383 // Center
384 BPoint
Center() const385 TransformBox::Center() const
386 {
387 return fPivot;
388 }
389
390
391 // SetBox
392 void
SetBox(BRect box)393 TransformBox::SetBox(BRect box)
394 {
395 if (fOriginalBox != box) {
396 fOriginalBox = box;
397 Update(false);
398 }
399 }
400
401
402 // FinishTransaction
403 Command*
FinishTransaction()404 TransformBox::FinishTransaction()
405 {
406 Command* command = fCurrentCommand;
407 if (fCurrentCommand) {
408 fCurrentCommand->SetNewTransformation(Pivot(), Translation(),
409 LocalRotation(), LocalXScale(), LocalYScale());
410 fCurrentCommand = NULL;
411 }
412 return command;
413 }
414
415
416 // NudgeBy
417 void
NudgeBy(BPoint offset)418 TransformBox::NudgeBy(BPoint offset)
419 {
420 if (!fNudging && !fCurrentCommand) {
421 fCurrentCommand = MakeCommand("Move");
422 fNudging = true;
423 }
424 if (fNudging) {
425 TranslateBy(offset);
426 }
427 }
428
429
430 // FinishNudging
431 Command*
FinishNudging()432 TransformBox::FinishNudging()
433 {
434 fNudging = false;
435 return FinishTransaction();
436 }
437
438
439 // TransformFromCanvas
440 void
TransformFromCanvas(BPoint & point) const441 TransformBox::TransformFromCanvas(BPoint& point) const
442 {
443 }
444
445
446 // TransformToCanvas
447 void
TransformToCanvas(BPoint & point) const448 TransformBox::TransformToCanvas(BPoint& point) const
449 {
450 }
451
452
453 // ZoomLevel
454 float
ZoomLevel() const455 TransformBox::ZoomLevel() const
456 {
457 return 1.0;
458 }
459
460
461 // ViewSpaceRotation
462 double
ViewSpaceRotation() const463 TransformBox::ViewSpaceRotation() const
464 {
465 // assume no inherited transformation
466 return LocalRotation();
467 }
468
469
470 // #pragma mark -
471
472
473 // AddListener
474 bool
AddListener(TransformBoxListener * listener)475 TransformBox::AddListener(TransformBoxListener* listener)
476 {
477 if (listener && !fListeners.HasItem((void*)listener))
478 return fListeners.AddItem((void*)listener);
479 return false;
480 }
481
482
483 // RemoveListener
484 bool
RemoveListener(TransformBoxListener * listener)485 TransformBox::RemoveListener(TransformBoxListener* listener)
486 {
487 return fListeners.RemoveItem((void*)listener);
488 }
489
490
491 // #pragma mark -
492
493
494 // TODO: why another version?
495 // point_line_dist
496 float
point_line_dist(BPoint start,BPoint end,BPoint p,float radius)497 point_line_dist(BPoint start, BPoint end, BPoint p, float radius)
498 {
499 BRect r(min_c(start.x, end.x), min_c(start.y, end.y), max_c(start.x, end.x),
500 max_c(start.y, end.y));
501 r.InsetBy(-radius, -radius);
502 if (r.Contains(p)) {
503 return fabs(agg::calc_line_point_distance(start.x, start.y, end.x, end.y,
504 p.x, p.y));
505 }
506
507 return min_c(point_point_distance(start, p), point_point_distance(end, p));
508 }
509
510
511 // _DragStateFor
512 //! where is expected in canvas view coordinates
513 DragState*
_DragStateFor(BPoint where,float canvasZoom)514 TransformBox::_DragStateFor(BPoint where, float canvasZoom)
515 {
516 DragState* state = NULL;
517 // convert to canvas zoom level
518 //
519 // the conversion is necessary, because the "hot regions"
520 // around a point should be the same size no matter what
521 // zoom level the canvas is displayed at
522
523 float inset = INSET / canvasZoom;
524
525 // priorities:
526 // transformation center point has highest priority ?!?
527 if (point_point_distance(where, fPivot) < inset)
528 state = fOffsetCenterState;
529
530 if (!state) {
531 // next, the inner area of the box
532
533 // for the following calculations
534 // we can apply the inverse transformation to all points
535 // this way we have to consider BRects only, not transformed polygons
536 BPoint lt = fLeftTop;
537 BPoint rb = fRightBottom;
538 BPoint w = where;
539
540 InverseTransform(&w);
541 InverseTransform(<);
542 InverseTransform(&rb);
543
544 // next priority has the inside of the box
545 BRect iR(lt, rb);
546 float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0));
547 float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0));
548
549 iR.InsetBy(hInset, vInset);
550 if (iR.Contains(w))
551 state = fTranslateState;
552 }
553
554 if (!state) {
555 // next priority have the corners
556 float dLT = point_point_distance(fLeftTop, where);
557 float dRT = point_point_distance(fRightTop, where);
558 float dLB = point_point_distance(fLeftBottom, where);
559 float dRB = point_point_distance(fRightBottom, where);
560 float d = min4(dLT, dRT, dLB, dRB);
561 if (d < inset) {
562 if (d == dLT)
563 state = fDragLTState;
564 else if (d == dRT)
565 state = fDragRTState;
566 else if (d == dLB)
567 state = fDragLBState;
568 else if (d == dRB)
569 state = fDragRBState;
570 }
571 }
572
573 if (!state) {
574 // next priority have the sides
575 float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset);
576 float dR = point_line_dist(fRightTop, fRightBottom, where, inset);
577 float dT = point_line_dist(fLeftTop, fRightTop, where, inset);
578 float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset);
579 float d = min4(dL, dR, dT, dB);
580 if (d < inset) {
581 if (d == dL)
582 state = fDragLState;
583 else if (d == dR)
584 state = fDragRState;
585 else if (d == dT)
586 state = fDragTState;
587 else if (d == dB)
588 state = fDragBState;
589 }
590 }
591
592 if (!state) {
593 BPoint lt = fLeftTop;
594 BPoint rb = fRightBottom;
595 BPoint w = where;
596
597 InverseTransform(&w);
598 InverseTransform(<);
599 InverseTransform(&rb);
600
601 // check inside of the box again
602 BRect iR(lt, rb);
603 if (iR.Contains(w)) {
604 state = fTranslateState;
605 } else {
606 // last priority has the rotate state
607 state = fRotateState;
608 }
609 }
610
611 return state;
612 }
613
614
615 // _StrokeBWLine
616 void
_StrokeBWLine(BView * into,BPoint from,BPoint to) const617 TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const
618 {
619 // find out how to offset the second line optimally
620 BPoint offset(0.0, 0.0);
621 // first, do we have a more horizontal line or a more vertical line?
622 float xDiff = to.x - from.x;
623 float yDiff = to.y - from.y;
624 if (fabs(xDiff) > fabs(yDiff)) {
625 // horizontal
626 if (xDiff > 0.0) {
627 offset.y = -1.0;
628 } else {
629 offset.y = 1.0;
630 }
631 } else {
632 // vertical
633 if (yDiff < 0.0) {
634 offset.x = -1.0;
635 } else {
636 offset.x = 1.0;
637 }
638 }
639 // stroke two lines in high and low color of the view
640 into->StrokeLine(from, to, B_SOLID_LOW);
641 from += offset;
642 to += offset;
643 into->StrokeLine(from, to, B_SOLID_HIGH);
644 }
645
646
647 // _StrokeBWPoint
648 void
_StrokeBWPoint(BView * into,BPoint point,double angle) const649 TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const
650 {
651 double x = point.x;
652 double y = point.y;
653
654 double x1 = x;
655 double y1 = y - 5.0;
656
657 double x2 = x - 5.0;
658 double y2 = y - 5.0;
659
660 double x3 = x - 5.0;
661 double y3 = y;
662
663 agg::trans_affine m;
664
665 double xOffset = -x;
666 double yOffset = -y;
667
668 agg::trans_affine_rotation r(angle * M_PI / 180.0);
669
670 r.transform(&xOffset, &yOffset);
671 xOffset = x + xOffset;
672 yOffset = y + yOffset;
673
674 m.multiply(r);
675 m.multiply(agg::trans_affine_translation(xOffset, yOffset));
676
677 m.transform(&x, &y);
678 m.transform(&x1, &y1);
679 m.transform(&x2, &y2);
680 m.transform(&x3, &y3);
681
682 BPoint p[4];
683 p[0] = BPoint(x, y);
684 p[1] = BPoint(x1, y1);
685 p[2] = BPoint(x2, y2);
686 p[3] = BPoint(x3, y3);
687
688 into->FillPolygon(p, 4, B_SOLID_HIGH);
689
690 into->StrokeLine(p[0], p[1], B_SOLID_LOW);
691 into->StrokeLine(p[1], p[2], B_SOLID_LOW);
692 into->StrokeLine(p[2], p[3], B_SOLID_LOW);
693 into->StrokeLine(p[3], p[0], B_SOLID_LOW);
694 }
695
696
697 // #pragma mark -
698
699
700 // _NotifyDeleted
701 void
_NotifyDeleted() const702 TransformBox::_NotifyDeleted() const
703 {
704 BList listeners(fListeners);
705 int32 count = listeners.CountItems();
706 for (int32 i = 0; i < count; i++) {
707 TransformBoxListener* listener
708 = (TransformBoxListener*)listeners.ItemAtFast(i);
709 listener->TransformBoxDeleted(this);
710 }
711 }
712
713