* Copyright 2006-2009, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "TransformBox.h"
#include <stdio.h>
#include <agg_trans_affine.h>
#include <agg_math.h>
#include <View.h>
#include "support.h"
#include "TransformBoxStates.h"
#include "StateView.h"
#include "TransformCommand.h"
#define INSET 8.0
using namespace TransformBoxStates;
TransformBox::TransformBox(StateView* view, BRect box)
:
ChannelTransform(),
Manipulator(NULL),
fOriginalBox(box),
fLeftTop(box.LeftTop()),
fRightTop(box.RightTop()),
fLeftBottom(box.LeftBottom()),
fRightBottom(box.RightBottom()),
fPivot((fLeftTop.x + fRightBottom.x) / 2.0,
(fLeftTop.y + fRightBottom.y) / 2.0),
fPivotOffset(B_ORIGIN),
fCurrentCommand(NULL),
fCurrentState(NULL),
fDragging(false),
fMousePos(-10000.0, -10000.0),
fModifiers(0),
fNudging(false),
fView(view),
fDragLTState(new DragCornerState(this, DragCornerState::LEFT_TOP_CORNER)),
fDragRTState(new DragCornerState(this, DragCornerState::RIGHT_TOP_CORNER)),
fDragLBState(new DragCornerState(this, DragCornerState::LEFT_BOTTOM_CORNER)),
fDragRBState(new DragCornerState(this, DragCornerState::RIGHT_BOTTOM_CORNER)),
fDragLState(new DragSideState(this, DragSideState::LEFT_SIDE)),
fDragRState(new DragSideState(this, DragSideState::RIGHT_SIDE)),
fDragTState(new DragSideState(this, DragSideState::TOP_SIDE)),
fDragBState(new DragSideState(this, DragSideState::BOTTOM_SIDE)),
fRotateState(new RotateBoxState(this)),
fTranslateState(new DragBoxState(this)),
fOffsetCenterState(new OffsetCenterState(this))
{
}
TransformBox::~TransformBox()
{
_NotifyDeleted();
delete fCurrentCommand;
delete fDragLTState;
delete fDragRTState;
delete fDragLBState;
delete fDragRBState;
delete fDragLState;
delete fDragRState;
delete fDragTState;
delete fDragBState;
delete fRotateState;
delete fTranslateState;
delete fOffsetCenterState;
}
void
TransformBox::Draw(BView* into, BRect updateRect)
{
BPoint lt = fLeftTop;
BPoint rt = fRightTop;
BPoint lb = fLeftBottom;
BPoint rb = fRightBottom;
BPoint c = fPivot;
TransformFromCanvas(lt);
TransformFromCanvas(rt);
TransformFromCanvas(lb);
TransformFromCanvas(rb);
TransformFromCanvas(c);
into->SetDrawingMode(B_OP_COPY);
into->SetHighColor(255, 255, 255, 255);
into->SetLowColor(0, 0, 0, 255);
_StrokeBWLine(into, lt, rt);
_StrokeBWLine(into, rt, rb);
_StrokeBWLine(into, rb, lb);
_StrokeBWLine(into, lb, lt);
double rotation = ViewSpaceRotation();
_StrokeBWPoint(into, lt, rotation);
_StrokeBWPoint(into, rt, rotation + 90.0);
_StrokeBWPoint(into, rb, rotation + 180.0);
_StrokeBWPoint(into, lb, rotation + 270.0);
BRect cr(c, c);
cr.InsetBy(-3.0, -3.0);
into->StrokeEllipse(cr, B_SOLID_HIGH);
cr.InsetBy(1.0, 1.0);
into->StrokeEllipse(cr, B_SOLID_LOW);
into->SetDrawingMode(B_OP_COPY);
}
bool
TransformBox::MouseDown(BPoint where)
{
fView->FilterMouse(&where);
TransformToCanvas(where);
fDragging = true;
if (fCurrentState) {
fCurrentState->SetOrigin(where);
delete fCurrentCommand;
fCurrentCommand = MakeCommand(fCurrentState->ActionName());
}
return true;
}
void
TransformBox::MouseMoved(BPoint where)
{
fView->FilterMouse(&where);
TransformToCanvas(where);
if (fMousePos != where) {
fMousePos = where;
if (fCurrentState) {
fCurrentState->DragTo(fMousePos, fModifiers);
fCurrentState->UpdateViewCursor(fView, fMousePos);
}
}
}
Command*
TransformBox::MouseUp()
{
fDragging = false;
return FinishTransaction();
}
bool
TransformBox::MouseOver(BPoint where)
{
TransformToCanvas(where);
fMousePos = where;
fCurrentState = _DragStateFor(where, ZoomLevel());
fCurrentState->UpdateViewCursor(fView, fMousePos);
return true;
}
bool
TransformBox::DoubleClicked(BPoint where)
{
return false;
}
BRect
TransformBox::Bounds()
{
BPoint lt = fLeftTop;
BPoint rt = fRightTop;
BPoint lb = fLeftBottom;
BPoint rb = fRightBottom;
BPoint c = fPivot;
TransformFromCanvas(lt);
TransformFromCanvas(rt);
TransformFromCanvas(lb);
TransformFromCanvas(rb);
TransformFromCanvas(c);
BRect bounds;
bounds.left = min5(lt.x, rt.x, lb.x, rb.x, c.x);
bounds.top = min5(lt.y, rt.y, lb.y, rb.y, c.y);
bounds.right = max5(lt.x, rt.x, lb.x, rb.x, c.x);
bounds.bottom = max5(lt.y, rt.y, lb.y, rb.y, c.y);
return bounds;
}
BRect
TransformBox::TrackingBounds(BView* withinView)
{
return withinView->Bounds();
}
void
TransformBox::ModifiersChanged(uint32 modifiers)
{
fModifiers = modifiers;
if (fDragging && fCurrentState) {
fCurrentState->DragTo(fMousePos, fModifiers);
}
}
bool
TransformBox::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command)
{
bool handled = true;
BPoint translation(B_ORIGIN);
float offset = 1.0;
if (modifiers & B_SHIFT_KEY)
offset /= ZoomLevel();
switch (key) {
case B_UP_ARROW:
translation.y = -offset;
break;
case B_DOWN_ARROW:
translation.y = offset;
break;
case B_LEFT_ARROW:
translation.x = -offset;
break;
case B_RIGHT_ARROW:
translation.x = offset;
break;
default:
handled = false;
break;
}
if (!handled)
return false;
if (!fCurrentCommand) {
fCurrentCommand = MakeCommand("Translate");
}
TranslateBy(translation);
return true;
}
bool
TransformBox::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command)
{
if (fCurrentCommand) {
*_command = FinishTransaction();
return true;
}
return false;
}
bool
TransformBox::UpdateCursor()
{
if (fCurrentState) {
fCurrentState->UpdateViewCursor(fView, fMousePos);
return true;
}
return false;
}
void
TransformBox::AttachedToView(BView* view)
{
view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
}
void
TransformBox::DetachedFromView(BView* view)
{
view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
}
void
TransformBox::Update(bool deep)
{
fLeftTop = fOriginalBox.LeftTop();
fRightTop = fOriginalBox.RightTop();
fLeftBottom = fOriginalBox.LeftBottom();
fRightBottom = fOriginalBox.RightBottom();
fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0;
fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0;
fPivot += fPivotOffset;
Transform(&fLeftTop);
Transform(&fRightTop);
Transform(&fLeftBottom);
Transform(&fRightBottom);
Transform(&fPivot);
}
void
TransformBox::OffsetCenter(BPoint offset)
{
if (offset != BPoint(0.0, 0.0)) {
fPivotOffset += offset;
Update(false);
}
}
BPoint
TransformBox::Center() const
{
return fPivot;
}
void
TransformBox::SetBox(BRect box)
{
if (fOriginalBox != box) {
fOriginalBox = box;
Update(false);
}
}
Command*
TransformBox::FinishTransaction()
{
Command* command = fCurrentCommand;
if (fCurrentCommand) {
fCurrentCommand->SetNewTransformation(Pivot(), Translation(),
LocalRotation(), LocalXScale(), LocalYScale());
fCurrentCommand = NULL;
}
return command;
}
void
TransformBox::NudgeBy(BPoint offset)
{
if (!fNudging && !fCurrentCommand) {
fCurrentCommand = MakeCommand("Move");
fNudging = true;
}
if (fNudging) {
TranslateBy(offset);
}
}
Command*
TransformBox::FinishNudging()
{
fNudging = false;
return FinishTransaction();
}
void
TransformBox::TransformFromCanvas(BPoint& point) const
{
}
void
TransformBox::TransformToCanvas(BPoint& point) const
{
}
float
TransformBox::ZoomLevel() const
{
return 1.0;
}
double
TransformBox::ViewSpaceRotation() const
{
return LocalRotation();
}
bool
TransformBox::AddListener(TransformBoxListener* listener)
{
if (listener && !fListeners.HasItem((void*)listener))
return fListeners.AddItem((void*)listener);
return false;
}
bool
TransformBox::RemoveListener(TransformBoxListener* listener)
{
return fListeners.RemoveItem((void*)listener);
}
float
point_line_dist(BPoint start, BPoint end, BPoint p, float radius)
{
BRect r(min_c(start.x, end.x), min_c(start.y, end.y), max_c(start.x, end.x),
max_c(start.y, end.y));
r.InsetBy(-radius, -radius);
if (r.Contains(p)) {
return fabs(agg::calc_line_point_distance(start.x, start.y, end.x, end.y,
p.x, p.y));
}
return min_c(point_point_distance(start, p), point_point_distance(end, p));
}
DragState*
TransformBox::_DragStateFor(BPoint where, float canvasZoom)
{
DragState* state = NULL;
float inset = INSET / canvasZoom;
if (point_point_distance(where, fPivot) < inset)
state = fOffsetCenterState;
if (!state) {
BPoint lt = fLeftTop;
BPoint rb = fRightBottom;
BPoint w = where;
InverseTransform(&w);
InverseTransform(<);
InverseTransform(&rb);
BRect iR(lt, rb);
float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0));
float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0));
iR.InsetBy(hInset, vInset);
if (iR.Contains(w))
state = fTranslateState;
}
if (!state) {
float dLT = point_point_distance(fLeftTop, where);
float dRT = point_point_distance(fRightTop, where);
float dLB = point_point_distance(fLeftBottom, where);
float dRB = point_point_distance(fRightBottom, where);
float d = min4(dLT, dRT, dLB, dRB);
if (d < inset) {
if (d == dLT)
state = fDragLTState;
else if (d == dRT)
state = fDragRTState;
else if (d == dLB)
state = fDragLBState;
else if (d == dRB)
state = fDragRBState;
}
}
if (!state) {
float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset);
float dR = point_line_dist(fRightTop, fRightBottom, where, inset);
float dT = point_line_dist(fLeftTop, fRightTop, where, inset);
float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset);
float d = min4(dL, dR, dT, dB);
if (d < inset) {
if (d == dL)
state = fDragLState;
else if (d == dR)
state = fDragRState;
else if (d == dT)
state = fDragTState;
else if (d == dB)
state = fDragBState;
}
}
if (!state) {
BPoint lt = fLeftTop;
BPoint rb = fRightBottom;
BPoint w = where;
InverseTransform(&w);
InverseTransform(<);
InverseTransform(&rb);
BRect iR(lt, rb);
if (iR.Contains(w)) {
state = fTranslateState;
} else {
state = fRotateState;
}
}
return state;
}
void
TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const
{
BPoint offset(0.0, 0.0);
float xDiff = to.x - from.x;
float yDiff = to.y - from.y;
if (fabs(xDiff) > fabs(yDiff)) {
if (xDiff > 0.0) {
offset.y = -1.0;
} else {
offset.y = 1.0;
}
} else {
if (yDiff < 0.0) {
offset.x = -1.0;
} else {
offset.x = 1.0;
}
}
into->StrokeLine(from, to, B_SOLID_LOW);
from += offset;
to += offset;
into->StrokeLine(from, to, B_SOLID_HIGH);
}
void
TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const
{
double x = point.x;
double y = point.y;
double x1 = x;
double y1 = y - 5.0;
double x2 = x - 5.0;
double y2 = y - 5.0;
double x3 = x - 5.0;
double y3 = y;
agg::trans_affine m;
double xOffset = -x;
double yOffset = -y;
agg::trans_affine_rotation r(angle * M_PI / 180.0);
r.transform(&xOffset, &yOffset);
xOffset = x + xOffset;
yOffset = y + yOffset;
m.multiply(r);
m.multiply(agg::trans_affine_translation(xOffset, yOffset));
m.transform(&x, &y);
m.transform(&x1, &y1);
m.transform(&x2, &y2);
m.transform(&x3, &y3);
BPoint p[4];
p[0] = BPoint(x, y);
p[1] = BPoint(x1, y1);
p[2] = BPoint(x2, y2);
p[3] = BPoint(x3, y3);
into->FillPolygon(p, 4, B_SOLID_HIGH);
into->StrokeLine(p[0], p[1], B_SOLID_LOW);
into->StrokeLine(p[1], p[2], B_SOLID_LOW);
into->StrokeLine(p[2], p[3], B_SOLID_LOW);
into->StrokeLine(p[3], p[0], B_SOLID_LOW);
}
void
TransformBox::_NotifyDeleted() const
{
BList listeners(fListeners);
int32 count = listeners.CountItems();
for (int32 i = 0; i < count; i++) {
TransformBoxListener* listener
= (TransformBoxListener*)listeners.ItemAtFast(i);
listener->TransformBoxDeleted(this);
}
}