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