xref: /haiku/src/apps/icon-o-matic/transformable/TransformBox.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
1 /*
2  * Copyright 2006, 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 // #pragma mark -
303 
304 // UpdateCursor
305 void
306 TransformBox::UpdateCursor()
307 {
308 	if (fCurrentState)
309 		fCurrentState->UpdateViewCursor(fView, fMousePos);
310 }
311 
312 // AttachedToView
313 void
314 TransformBox::AttachedToView(BView* view)
315 {
316 	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
317 }
318 
319 // DetachedFromView
320 void
321 TransformBox::DetachedFromView(BView* view)
322 {
323 	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
324 }
325 
326 // pragma mark -
327 
328 // Update
329 void
330 TransformBox::Update(bool deep)
331 {
332 	// recalculate the points from the original box
333 	fLeftTop = fOriginalBox.LeftTop();
334 	fRightTop = fOriginalBox.RightTop();
335 	fLeftBottom = fOriginalBox.LeftBottom();
336 	fRightBottom = fOriginalBox.RightBottom();
337 
338 	fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0;
339 	fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0;
340 
341 	fPivot += fPivotOffset;
342 
343 	// transform the points for display
344 	Transform(&fLeftTop);
345 	Transform(&fRightTop);
346 	Transform(&fLeftBottom);
347 	Transform(&fRightBottom);
348 
349 	Transform(&fPivot);
350 }
351 
352 // OffsetCenter
353 void
354 TransformBox::OffsetCenter(BPoint offset)
355 {
356 	if (offset != BPoint(0.0, 0.0)) {
357 		fPivotOffset += offset;
358 		Update(false);
359 	}
360 }
361 
362 // Center
363 BPoint
364 TransformBox::Center() const
365 {
366 	return fPivot;
367 }
368 
369 // SetBox
370 void
371 TransformBox::SetBox(BRect box)
372 {
373 	if (fOriginalBox != box) {
374 		fOriginalBox = box;
375 		Update(false);
376 	}
377 }
378 
379 // FinishTransaction
380 Command*
381 TransformBox::FinishTransaction()
382 {
383 	Command* command = fCurrentCommand;
384 	if (fCurrentCommand) {
385 		fCurrentCommand->SetNewTransformation(Pivot(),
386 											  Translation(),
387 											  LocalRotation(),
388 											  LocalXScale(),
389 											  LocalYScale());
390 		fCurrentCommand = NULL;
391 	}
392 	return command;
393 }
394 
395 // NudgeBy
396 void
397 TransformBox::NudgeBy(BPoint offset)
398 {
399 	if (!fNudging && !fCurrentCommand) {
400 		fCurrentCommand = MakeCommand("Move", 0/*MOVE*/);
401 		fNudging = true;
402 	}
403 	if (fNudging) {
404 		TranslateBy(offset);
405 	}
406 }
407 
408 // FinishNudging
409 Command*
410 TransformBox::FinishNudging()
411 {
412 	fNudging = false;
413 	return FinishTransaction();
414 }
415 
416 // TransformFromCanvas
417 void
418 TransformBox::TransformFromCanvas(BPoint& point) const
419 {
420 }
421 
422 // TransformToCanvas
423 void
424 TransformBox::TransformToCanvas(BPoint& point) const
425 {
426 }
427 
428 // ZoomLevel
429 float
430 TransformBox::ZoomLevel() const
431 {
432 	return 1.0;
433 }
434 
435 // ViewSpaceRotation
436 double
437 TransformBox::ViewSpaceRotation() const
438 {
439 	// assume no inherited transformation
440 	return LocalRotation();
441 }
442 
443 // #pragma mark -
444 
445 // AddListener
446 bool
447 TransformBox::AddListener(TransformBoxListener* listener)
448 {
449 	if (listener && !fListeners.HasItem((void*)listener))
450 		return fListeners.AddItem((void*)listener);
451 	return false;
452 }
453 
454 // RemoveListener
455 bool
456 TransformBox::RemoveListener(TransformBoxListener* listener)
457 {
458 	return fListeners.RemoveItem((void*)listener);
459 }
460 
461 // #pragma mark -
462 
463 // TODO: why another version?
464 // point_line_dist
465 float
466 point_line_dist(BPoint start, BPoint end, BPoint p, float radius)
467 {
468 	BRect r(min_c(start.x, end.x),
469 			min_c(start.y, end.y),
470 			max_c(start.x, end.x),
471 			max_c(start.y, end.y));
472 	r.InsetBy(-radius, -radius);
473 	if (r.Contains(p)) {
474 		return fabs(agg::calc_line_point_distance(start.x, start.y,
475 												  end.x, end.y,
476 												  p.x, p.y));
477 	}
478 	return min_c(point_point_distance(start, p),
479 				 point_point_distance(end, p));
480 }
481 
482 // _DragStateFor
483 //
484 // where is expected in canvas view coordinates
485 DragState*
486 TransformBox::_DragStateFor(BPoint where, float canvasZoom)
487 {
488 	DragState* state = NULL;
489 	// convert to canvas zoom level
490 	//
491 	// the conversion is necessary, because the "hot regions"
492 	// around a point should be the same size no matter what
493 	// zoom level the canvas is displayed at
494 
495 	float inset = INSET / canvasZoom;
496 
497 	// priorities:
498 	// transformation center point has highest priority ?!?
499 	if (point_point_distance(where, fPivot) < inset)
500 		state = fOffsetCenterState;
501 
502 	if (!state) {
503 		// next, the inner area of the box
504 
505 		// for the following calculations
506 		// we can apply the inverse transformation to all points
507 		// this way we have to consider BRects only, not transformed polygons
508 		BPoint lt = fLeftTop;
509 		BPoint rb = fRightBottom;
510 		BPoint w = where;
511 
512 		InverseTransform(&w);
513 		InverseTransform(&lt);
514 		InverseTransform(&rb);
515 
516 		// next priority has the inside of the box
517 		BRect iR(lt, rb);
518 		float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0));
519 		float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0));
520 
521 		iR.InsetBy(hInset, vInset);
522 		if (iR.Contains(w))
523 			state = fTranslateState;
524 	}
525 
526 	if (!state) {
527 		// next priority have the corners
528 		float dLT = point_point_distance(fLeftTop, where);
529 		float dRT = point_point_distance(fRightTop, where);
530 		float dLB = point_point_distance(fLeftBottom, where);
531 		float dRB = point_point_distance(fRightBottom, where);
532 		float d = min4(dLT, dRT, dLB, dRB);
533 		if (d < inset) {
534 			if (d == dLT)
535 				state = fDragLTState;
536 			else if (d == dRT)
537 				state = fDragRTState;
538 			else if (d == dLB)
539 				state = fDragLBState;
540 			else if (d == dRB)
541 				state = fDragRBState;
542 		}
543 	}
544 
545 	if (!state) {
546 		// next priority have the sides
547 		float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset);
548 		float dR = point_line_dist(fRightTop, fRightBottom, where, inset);
549 		float dT = point_line_dist(fLeftTop, fRightTop, where, inset);
550 		float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset);
551 		float d = min4(dL, dR, dT, dB);
552 		if (d < inset) {
553 			if (d == dL)
554 				state = fDragLState;
555 			else if (d == dR)
556 				state = fDragRState;
557 			else if (d == dT)
558 				state = fDragTState;
559 			else if (d == dB)
560 				state = fDragBState;
561 		}
562 	}
563 
564 	if (!state) {
565 		BPoint lt = fLeftTop;
566 		BPoint rb = fRightBottom;
567 		BPoint w = where;
568 
569 		InverseTransform(&w);
570 		InverseTransform(&lt);
571 		InverseTransform(&rb);
572 
573 		// check inside of the box again
574 		BRect iR(lt, rb);
575 		if (iR.Contains(w)) {
576 			state = fTranslateState;
577 		} else {
578 			// last priority has the rotate state
579 			state = fRotateState;
580 		}
581 	}
582 
583 	return state;
584 }
585 
586 // _StrokeBWLine
587 void
588 TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const
589 {
590 	// find out how to offset the second line optimally
591 	BPoint offset(0.0, 0.0);
592 	// first, do we have a more horizontal line or a more vertical line?
593 	float xDiff = to.x - from.x;
594 	float yDiff = to.y - from.y;
595 	if (fabs(xDiff) > fabs(yDiff)) {
596 		// horizontal
597 		if (xDiff > 0.0) {
598 			offset.y = -1.0;
599 		} else {
600 			offset.y = 1.0;
601 		}
602 	} else {
603 		// vertical
604 		if (yDiff < 0.0) {
605 			offset.x = -1.0;
606 		} else {
607 			offset.x = 1.0;
608 		}
609 	}
610 	// stroke two lines in high and low color of the view
611 	into->StrokeLine(from, to, B_SOLID_LOW);
612 	from += offset;
613 	to += offset;
614 	into->StrokeLine(from, to, B_SOLID_HIGH);
615 }
616 
617 // _StrokeBWPoint
618 void
619 TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const
620 {
621 	double x = point.x;
622 	double y = point.y;
623 
624 	double x1 = x;
625 	double y1 = y - 5.0;
626 
627 	double x2 = x - 5.0;
628 	double y2 = y - 5.0;
629 
630 	double x3 = x - 5.0;
631 	double y3 = y;
632 
633 	agg::trans_affine m;
634 
635 	double xOffset = -x;
636 	double yOffset = -y;
637 
638 	agg::trans_affine_rotation r(angle * PI / 180.0);
639 
640 	r.transform(&xOffset, &yOffset);
641 	xOffset = x + xOffset;
642 	yOffset = y + yOffset;
643 
644 	m.multiply(r);
645 	m.multiply(agg::trans_affine_translation(xOffset, yOffset));
646 
647 	m.transform(&x, &y);
648 	m.transform(&x1, &y1);
649 	m.transform(&x2, &y2);
650 	m.transform(&x3, &y3);
651 
652 	BPoint p[4];
653 	p[0] = BPoint(x, y);
654 	p[1] = BPoint(x1, y1);
655 	p[2] = BPoint(x2, y2);
656 	p[3] = BPoint(x3, y3);
657 
658 	into->FillPolygon(p, 4, B_SOLID_HIGH);
659 
660 	into->StrokeLine(p[0], p[1], B_SOLID_LOW);
661 	into->StrokeLine(p[1], p[2], B_SOLID_LOW);
662 	into->StrokeLine(p[2], p[3], B_SOLID_LOW);
663 	into->StrokeLine(p[3], p[0], B_SOLID_LOW);
664 }
665 
666 // #pragma mark -
667 
668 // _NotifyDeleted
669 void
670 TransformBox::_NotifyDeleted() const
671 {
672 	BList listeners(fListeners);
673 	int32 count = listeners.CountItems();
674 	for (int32 i = 0; i < count; i++) {
675 		TransformBoxListener* listener
676 			= (TransformBoxListener*)listeners.ItemAtFast(i);
677 		listener->TransformBoxDeleted(this);
678 	}
679 }
680 
681 // #pragma mark -
682 
683 // _SetState
684 void
685 TransformBox::_SetState(DragState* state)
686 {
687 	if (state != fCurrentState) {
688 		fCurrentState = state;
689 		fCurrentState->UpdateViewCursor(fView, fMousePos);
690 	}
691 }
692 
693