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