xref: /haiku/src/apps/icon-o-matic/transformable/TransformBox.cpp (revision c6c2c0428420c1a7a9b16a843b41ab26903bc5fb)
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(&lt);
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(&lt);
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