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
28using namespace TransformBoxStates;
29
30
31// constructor
32TransformBox::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
75TransformBox::~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
98void
99TransformBox::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
141bool
142TransformBox::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
160void
161TransformBox::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
177Command*
178TransformBox::MouseUp()
179{
180	fDragging = false;
181	return FinishTransaction();
182}
183
184
185// MouseOver
186bool
187TransformBox::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
200bool
201TransformBox::DoubleClicked(BPoint where)
202{
203	return false;
204}
205
206
207// #pragma mark -
208
209
210// Bounds
211BRect
212TransformBox::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
237BRect
238TransformBox::TrackingBounds(BView* withinView)
239{
240	return withinView->Bounds();
241}
242
243
244// #pragma mark -
245
246
247// ModifiersChanged
248void
249TransformBox::ModifiersChanged(uint32 modifiers)
250{
251	fModifiers = modifiers;
252	if (fDragging && fCurrentState) {
253		fCurrentState->DragTo(fMousePos, fModifiers);
254	}
255}
256
257
258// HandleKeyDown
259bool
260TransformBox::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
302bool
303TransformBox::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
314bool
315TransformBox::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
329void
330TransformBox::AttachedToView(BView* view)
331{
332	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
333}
334
335
336// DetachedFromView
337void
338TransformBox::DetachedFromView(BView* view)
339{
340	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
341}
342
343
344// pragma mark -
345
346
347// Update
348void
349TransformBox::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
373void
374TransformBox::OffsetCenter(BPoint offset)
375{
376	if (offset != BPoint(0.0, 0.0)) {
377		fPivotOffset += offset;
378		Update(false);
379	}
380}
381
382
383// Center
384BPoint
385TransformBox::Center() const
386{
387	return fPivot;
388}
389
390
391// SetBox
392void
393TransformBox::SetBox(BRect box)
394{
395	if (fOriginalBox != box) {
396		fOriginalBox = box;
397		Update(false);
398	}
399}
400
401
402// FinishTransaction
403Command*
404TransformBox::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
417void
418TransformBox::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
431Command*
432TransformBox::FinishNudging()
433{
434	fNudging = false;
435	return FinishTransaction();
436}
437
438
439// TransformFromCanvas
440void
441TransformBox::TransformFromCanvas(BPoint& point) const
442{
443}
444
445
446// TransformToCanvas
447void
448TransformBox::TransformToCanvas(BPoint& point) const
449{
450}
451
452
453// ZoomLevel
454float
455TransformBox::ZoomLevel() const
456{
457	return 1.0;
458}
459
460
461// ViewSpaceRotation
462double
463TransformBox::ViewSpaceRotation() const
464{
465	// assume no inherited transformation
466	return LocalRotation();
467}
468
469
470// #pragma mark -
471
472
473// AddListener
474bool
475TransformBox::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
484bool
485TransformBox::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
496float
497point_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
513DragState*
514TransformBox::_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
616void
617TransformBox::_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
648void
649TransformBox::_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
701void
702TransformBox::_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