xref: /haiku/src/apps/icon-o-matic/transformable/TransformBox.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
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 	fMousePos = where;
205 	fCurrentState = _DragStateFor(where, ZoomLevel());
206 	fCurrentState->UpdateViewCursor(fView, fMousePos);
207 
208 	return true;
209 }
210 
211 
212 // DoubleClicked
213 bool
214 TransformBox::DoubleClicked(BPoint where)
215 {
216 	return false;
217 }
218 
219 
220 // #pragma mark -
221 
222 
223 // Bounds
224 BRect
225 TransformBox::Bounds()
226 {
227 	// convert from canvas view coordinates
228 	BPoint lt = fLeftTop;
229 	BPoint rt = fRightTop;
230 	BPoint lb = fLeftBottom;
231 	BPoint rb = fRightBottom;
232 	BPoint c = fPivot;
233 
234 	TransformFromCanvas(lt);
235 	TransformFromCanvas(rt);
236 	TransformFromCanvas(lb);
237 	TransformFromCanvas(rb);
238 	TransformFromCanvas(c);
239 
240 	BRect r;
241 	r.left = min5(lt.x, rt.x, lb.x, rb.x, c.x);
242 	r.top = min5(lt.y, rt.y, lb.y, rb.y, c.y);
243 	r.right = max5(lt.x, rt.x, lb.x, rb.x, c.x);
244 	r.bottom = max5(lt.y, rt.y, lb.y, rb.y, c.y);
245 	return r;
246 }
247 
248 
249 // TrackingBounds
250 BRect
251 TransformBox::TrackingBounds(BView* withinView)
252 {
253 	return withinView->Bounds();
254 }
255 
256 
257 // #pragma mark -
258 
259 
260 // ModifiersChanged
261 void
262 TransformBox::ModifiersChanged(uint32 modifiers)
263 {
264 	fModifiers = modifiers;
265 	if (fDragging && fCurrentState) {
266 		fCurrentState->DragTo(fMousePos, fModifiers);
267 	}
268 }
269 
270 
271 // HandleKeyDown
272 bool
273 TransformBox::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command)
274 {
275 	bool handled = true;
276 	BPoint translation(B_ORIGIN);
277 
278 	float offset = 1.0;
279 	if (modifiers & B_SHIFT_KEY)
280 		offset /= ZoomLevel();
281 
282 	switch (key) {
283 		case B_UP_ARROW:
284 			translation.y = -offset;
285 			break;
286 		case B_DOWN_ARROW:
287 			translation.y = offset;
288 			break;
289 		case B_LEFT_ARROW:
290 			translation.x = -offset;
291 			break;
292 		case B_RIGHT_ARROW:
293 			translation.x = offset;
294 			break;
295 
296 		default:
297 			handled = false;
298 			break;
299 	}
300 
301 	if (!handled)
302 		return false;
303 
304 	if (!fCurrentCommand) {
305 		fCurrentCommand = MakeCommand("Translate", -1);
306 	}
307 
308 	TranslateBy(translation);
309 
310 	return true;
311 }
312 
313 
314 // HandleKeyUp
315 bool
316 TransformBox::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command)
317 {
318 	if (fCurrentCommand) {
319 		*_command = FinishTransaction();
320 		return true;
321 	}
322 	return false;
323 }
324 
325 
326 // UpdateCursor
327 bool
328 TransformBox::UpdateCursor()
329 {
330 	if (fCurrentState) {
331 		fCurrentState->UpdateViewCursor(fView, fMousePos);
332 		return true;
333 	}
334 	return false;
335 }
336 
337 
338 // #pragma mark -
339 
340 
341 // AttachedToView
342 void
343 TransformBox::AttachedToView(BView* view)
344 {
345 	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
346 }
347 
348 
349 // DetachedFromView
350 void
351 TransformBox::DetachedFromView(BView* view)
352 {
353 	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
354 }
355 
356 
357 // pragma mark -
358 
359 
360 // Update
361 void
362 TransformBox::Update(bool deep)
363 {
364 	// recalculate the points from the original box
365 	fLeftTop = fOriginalBox.LeftTop();
366 	fRightTop = fOriginalBox.RightTop();
367 	fLeftBottom = fOriginalBox.LeftBottom();
368 	fRightBottom = fOriginalBox.RightBottom();
369 
370 	fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0;
371 	fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0;
372 
373 	fPivot += fPivotOffset;
374 
375 	// transform the points for display
376 	Transform(&fLeftTop);
377 	Transform(&fRightTop);
378 	Transform(&fLeftBottom);
379 	Transform(&fRightBottom);
380 
381 	Transform(&fPivot);
382 }
383 
384 
385 // OffsetCenter
386 void
387 TransformBox::OffsetCenter(BPoint offset)
388 {
389 	if (offset != BPoint(0.0, 0.0)) {
390 		fPivotOffset += offset;
391 		Update(false);
392 	}
393 }
394 
395 
396 // Center
397 BPoint
398 TransformBox::Center() const
399 {
400 	return fPivot;
401 }
402 
403 
404 // SetBox
405 void
406 TransformBox::SetBox(BRect box)
407 {
408 	if (fOriginalBox != box) {
409 		fOriginalBox = box;
410 		Update(false);
411 	}
412 }
413 
414 
415 // FinishTransaction
416 Command*
417 TransformBox::FinishTransaction()
418 {
419 	Command* command = fCurrentCommand;
420 	if (fCurrentCommand) {
421 		fCurrentCommand->SetNewTransformation(Pivot(), Translation(),
422 			LocalRotation(), LocalXScale(), LocalYScale());
423 		fCurrentCommand = NULL;
424 	}
425 	return command;
426 }
427 
428 
429 // NudgeBy
430 void
431 TransformBox::NudgeBy(BPoint offset)
432 {
433 	if (!fNudging && !fCurrentCommand) {
434 		fCurrentCommand = MakeCommand("Move", 0/*MOVE*/);
435 		fNudging = true;
436 	}
437 	if (fNudging) {
438 		TranslateBy(offset);
439 	}
440 }
441 
442 
443 // FinishNudging
444 Command*
445 TransformBox::FinishNudging()
446 {
447 	fNudging = false;
448 	return FinishTransaction();
449 }
450 
451 
452 // TransformFromCanvas
453 void
454 TransformBox::TransformFromCanvas(BPoint& point) const
455 {
456 }
457 
458 
459 // TransformToCanvas
460 void
461 TransformBox::TransformToCanvas(BPoint& point) const
462 {
463 }
464 
465 
466 // ZoomLevel
467 float
468 TransformBox::ZoomLevel() const
469 {
470 	return 1.0;
471 }
472 
473 
474 // ViewSpaceRotation
475 double
476 TransformBox::ViewSpaceRotation() const
477 {
478 	// assume no inherited transformation
479 	return LocalRotation();
480 }
481 
482 
483 // #pragma mark -
484 
485 
486 // AddListener
487 bool
488 TransformBox::AddListener(TransformBoxListener* listener)
489 {
490 	if (listener && !fListeners.HasItem((void*)listener))
491 		return fListeners.AddItem((void*)listener);
492 	return false;
493 }
494 
495 
496 // RemoveListener
497 bool
498 TransformBox::RemoveListener(TransformBoxListener* listener)
499 {
500 	return fListeners.RemoveItem((void*)listener);
501 }
502 
503 
504 // #pragma mark -
505 
506 
507 // TODO: why another version?
508 // point_line_dist
509 float
510 point_line_dist(BPoint start, BPoint end, BPoint p, float radius)
511 {
512 	BRect r(min_c(start.x, end.x), min_c(start.y, end.y), max_c(start.x, end.x),
513 		max_c(start.y, end.y));
514 	r.InsetBy(-radius, -radius);
515 	if (r.Contains(p)) {
516 		return fabs(agg::calc_line_point_distance(start.x, start.y, end.x, end.y,
517 			p.x, p.y));
518 	}
519 
520 	return min_c(point_point_distance(start, p), point_point_distance(end, p));
521 }
522 
523 
524 // _DragStateFor
525 //! where is expected in canvas view coordinates
526 DragState*
527 TransformBox::_DragStateFor(BPoint where, float canvasZoom)
528 {
529 	DragState* state = NULL;
530 	// convert to canvas zoom level
531 	//
532 	// the conversion is necessary, because the "hot regions"
533 	// around a point should be the same size no matter what
534 	// zoom level the canvas is displayed at
535 
536 	float inset = INSET / canvasZoom;
537 
538 	// priorities:
539 	// transformation center point has highest priority ?!?
540 	if (point_point_distance(where, fPivot) < inset)
541 		state = fOffsetCenterState;
542 
543 	if (!state) {
544 		// next, the inner area of the box
545 
546 		// for the following calculations
547 		// we can apply the inverse transformation to all points
548 		// this way we have to consider BRects only, not transformed polygons
549 		BPoint lt = fLeftTop;
550 		BPoint rb = fRightBottom;
551 		BPoint w = where;
552 
553 		InverseTransform(&w);
554 		InverseTransform(&lt);
555 		InverseTransform(&rb);
556 
557 		// next priority has the inside of the box
558 		BRect iR(lt, rb);
559 		float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0));
560 		float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0));
561 
562 		iR.InsetBy(hInset, vInset);
563 		if (iR.Contains(w))
564 			state = fTranslateState;
565 	}
566 
567 	if (!state) {
568 		// next priority have the corners
569 		float dLT = point_point_distance(fLeftTop, where);
570 		float dRT = point_point_distance(fRightTop, where);
571 		float dLB = point_point_distance(fLeftBottom, where);
572 		float dRB = point_point_distance(fRightBottom, where);
573 		float d = min4(dLT, dRT, dLB, dRB);
574 		if (d < inset) {
575 			if (d == dLT)
576 				state = fDragLTState;
577 			else if (d == dRT)
578 				state = fDragRTState;
579 			else if (d == dLB)
580 				state = fDragLBState;
581 			else if (d == dRB)
582 				state = fDragRBState;
583 		}
584 	}
585 
586 	if (!state) {
587 		// next priority have the sides
588 		float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset);
589 		float dR = point_line_dist(fRightTop, fRightBottom, where, inset);
590 		float dT = point_line_dist(fLeftTop, fRightTop, where, inset);
591 		float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset);
592 		float d = min4(dL, dR, dT, dB);
593 		if (d < inset) {
594 			if (d == dL)
595 				state = fDragLState;
596 			else if (d == dR)
597 				state = fDragRState;
598 			else if (d == dT)
599 				state = fDragTState;
600 			else if (d == dB)
601 				state = fDragBState;
602 		}
603 	}
604 
605 	if (!state) {
606 		BPoint lt = fLeftTop;
607 		BPoint rb = fRightBottom;
608 		BPoint w = where;
609 
610 		InverseTransform(&w);
611 		InverseTransform(&lt);
612 		InverseTransform(&rb);
613 
614 		// check inside of the box again
615 		BRect iR(lt, rb);
616 		if (iR.Contains(w)) {
617 			state = fTranslateState;
618 		} else {
619 			// last priority has the rotate state
620 			state = fRotateState;
621 		}
622 	}
623 
624 	return state;
625 }
626 
627 
628 // _StrokeBWLine
629 void
630 TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const
631 {
632 	// find out how to offset the second line optimally
633 	BPoint offset(0.0, 0.0);
634 	// first, do we have a more horizontal line or a more vertical line?
635 	float xDiff = to.x - from.x;
636 	float yDiff = to.y - from.y;
637 	if (fabs(xDiff) > fabs(yDiff)) {
638 		// horizontal
639 		if (xDiff > 0.0) {
640 			offset.y = -1.0;
641 		} else {
642 			offset.y = 1.0;
643 		}
644 	} else {
645 		// vertical
646 		if (yDiff < 0.0) {
647 			offset.x = -1.0;
648 		} else {
649 			offset.x = 1.0;
650 		}
651 	}
652 	// stroke two lines in high and low color of the view
653 	into->StrokeLine(from, to, B_SOLID_LOW);
654 	from += offset;
655 	to += offset;
656 	into->StrokeLine(from, to, B_SOLID_HIGH);
657 }
658 
659 
660 // _StrokeBWPoint
661 void
662 TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const
663 {
664 	double x = point.x;
665 	double y = point.y;
666 
667 	double x1 = x;
668 	double y1 = y - 5.0;
669 
670 	double x2 = x - 5.0;
671 	double y2 = y - 5.0;
672 
673 	double x3 = x - 5.0;
674 	double y3 = y;
675 
676 	agg::trans_affine m;
677 
678 	double xOffset = -x;
679 	double yOffset = -y;
680 
681 	agg::trans_affine_rotation r(angle * M_PI / 180.0);
682 
683 	r.transform(&xOffset, &yOffset);
684 	xOffset = x + xOffset;
685 	yOffset = y + yOffset;
686 
687 	m.multiply(r);
688 	m.multiply(agg::trans_affine_translation(xOffset, yOffset));
689 
690 	m.transform(&x, &y);
691 	m.transform(&x1, &y1);
692 	m.transform(&x2, &y2);
693 	m.transform(&x3, &y3);
694 
695 	BPoint p[4];
696 	p[0] = BPoint(x, y);
697 	p[1] = BPoint(x1, y1);
698 	p[2] = BPoint(x2, y2);
699 	p[3] = BPoint(x3, y3);
700 
701 	into->FillPolygon(p, 4, B_SOLID_HIGH);
702 
703 	into->StrokeLine(p[0], p[1], B_SOLID_LOW);
704 	into->StrokeLine(p[1], p[2], B_SOLID_LOW);
705 	into->StrokeLine(p[2], p[3], B_SOLID_LOW);
706 	into->StrokeLine(p[3], p[0], B_SOLID_LOW);
707 }
708 
709 
710 // #pragma mark -
711 
712 
713 // _NotifyDeleted
714 void
715 TransformBox::_NotifyDeleted() const
716 {
717 	BList listeners(fListeners);
718 	int32 count = listeners.CountItems();
719 	for (int32 i = 0; i < count; i++) {
720 		TransformBoxListener* listener
721 			= (TransformBoxListener*)listeners.ItemAtFast(i);
722 		listener->TransformBoxDeleted(this);
723 	}
724 }
725 
726