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
28TransformBoxListener::TransformBoxListener()
29{
30}
31
32
33TransformBoxListener::~TransformBoxListener()
34{
35}
36
37
38// #pragma mark -
39
40
41// constructor
42TransformBox::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
85TransformBox::~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
108void
109TransformBox::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
151bool
152TransformBox::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
172void
173TransformBox::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
190Command*
191TransformBox::MouseUp()
192{
193	fDragging = false;
194	return FinishTransaction();
195}
196
197
198// MouseOver
199bool
200TransformBox::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
215bool
216TransformBox::DoubleClicked(BPoint where)
217{
218	return false;
219}
220
221
222// #pragma mark -
223
224
225// Bounds
226BRect
227TransformBox::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
252BRect
253TransformBox::TrackingBounds(BView* withinView)
254{
255	return withinView->Bounds();
256}
257
258
259// #pragma mark -
260
261
262// ModifiersChanged
263void
264TransformBox::ModifiersChanged(uint32 modifiers)
265{
266	fModifiers = modifiers;
267	if (fDragging && fCurrentState) {
268		fCurrentState->DragTo(fMousePos, fModifiers);
269	}
270}
271
272
273// HandleKeyDown
274bool
275TransformBox::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
317bool
318TransformBox::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
329bool
330TransformBox::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
344void
345TransformBox::AttachedToView(BView* view)
346{
347	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
348}
349
350
351// DetachedFromView
352void
353TransformBox::DetachedFromView(BView* view)
354{
355	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
356}
357
358
359// pragma mark -
360
361
362// Update
363void
364TransformBox::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
388void
389TransformBox::OffsetCenter(BPoint offset)
390{
391	if (offset != BPoint(0.0, 0.0)) {
392		fPivotOffset += offset;
393		Update(false);
394	}
395}
396
397
398// Center
399BPoint
400TransformBox::Center() const
401{
402	return fPivot;
403}
404
405
406// SetBox
407void
408TransformBox::SetBox(BRect box)
409{
410	if (fOriginalBox != box) {
411		fOriginalBox = box;
412		Update(false);
413	}
414}
415
416
417// FinishTransaction
418Command*
419TransformBox::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
432void
433TransformBox::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
446Command*
447TransformBox::FinishNudging()
448{
449	fNudging = false;
450	return FinishTransaction();
451}
452
453
454// TransformFromCanvas
455void
456TransformBox::TransformFromCanvas(BPoint& point) const
457{
458}
459
460
461// TransformToCanvas
462void
463TransformBox::TransformToCanvas(BPoint& point) const
464{
465}
466
467
468// ZoomLevel
469float
470TransformBox::ZoomLevel() const
471{
472	return 1.0;
473}
474
475
476// ViewSpaceRotation
477double
478TransformBox::ViewSpaceRotation() const
479{
480	// assume no inherited transformation
481	return LocalRotation();
482}
483
484
485// #pragma mark -
486
487
488// AddListener
489bool
490TransformBox::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
499bool
500TransformBox::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
511float
512point_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
528DragState*
529TransformBox::_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
631void
632TransformBox::_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
663void
664TransformBox::_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
716void
717TransformBox::_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
733void
734TransformBox::_SetState(DragState* state)
735{
736	if (state != fCurrentState) {
737		fCurrentState = state;
738		fCurrentState->UpdateViewCursor(fView, fMousePos);
739	}
740}
741
742