* Copyright 2006-2009, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2023, Haiku, Inc.
* All rights reserved. Distributed under the terms of the MIT License.
*
* Authors:
* Zardshard
*/
#include "PathManipulator.h"
#include <algorithm>
#include <float.h>
#include <stdio.h>
#include <vector>
#include <Catalog.h>
#include <Cursor.h>
#include <Locale.h>
#include <Message.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <StackOrHeapArray.h>
#include <Window.h>
#include "cursors.h"
#include "support.h"
#include "CanvasView.h"
#include "AddPointCommand.h"
#include "ChangePointCommand.h"
#include "InsertPointCommand.h"
#include "FlipPointsCommand.h"
#include "NudgePointsCommand.h"
#include "RemovePointsCommand.h"
#include "SplitPointsCommand.h"
#include "TransformPointsBox.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-PathManipulator"
#define POINT_EXTEND 3.0
#define CONTROL_POINT_EXTEND 2.0
#define INSERT_DIST_THRESHOLD 7.0
#define MOVE_THRESHOLD 9.0
enum {
UNDEFINED,
NEW_PATH,
ADD_POINT,
INSERT_POINT,
MOVE_POINT,
MOVE_POINT_IN,
MOVE_POINT_OUT,
CLOSE_PATH,
TOGGLE_SHARP,
TOGGLE_SHARP_IN,
TOGGLE_SHARP_OUT,
REMOVE_POINT,
REMOVE_POINT_IN,
REMOVE_POINT_OUT,
SELECT_POINTS,
TRANSFORM_POINTS,
TRANSLATE_POINTS,
SELECT_SUB_PATH,
};
enum {
MSG_TRANSFORM = 'strn',
MSG_REMOVE_POINTS = 'srmp',
MSG_UPDATE_SHAPE_UI = 'udsi',
MSG_SPLIT_POINTS = 'splt',
MSG_FLIP_POINTS = 'flip',
};
inline const char*
string_for_mode(uint32 mode)
{
switch (mode) {
case UNDEFINED:
return "UNDEFINED";
case NEW_PATH:
return "NEW_PATH";
case ADD_POINT:
return "ADD_POINT";
case INSERT_POINT:
return "INSERT_POINT";
case MOVE_POINT:
return "MOVE_POINT";
case MOVE_POINT_IN:
return "MOVE_POINT_IN";
case MOVE_POINT_OUT:
return "MOVE_POINT_OUT";
case CLOSE_PATH:
return "CLOSE_PATH";
case TOGGLE_SHARP:
return "TOGGLE_SHARP";
case TOGGLE_SHARP_IN:
return "TOGGLE_SHARP_IN";
case TOGGLE_SHARP_OUT:
return "TOGGLE_SHARP_OUT";
case REMOVE_POINT:
return "REMOVE_POINT";
case REMOVE_POINT_IN:
return "REMOVE_POINT_IN";
case REMOVE_POINT_OUT:
return "REMOVE_POINT_OUT";
case SELECT_POINTS:
return "SELECT_POINTS";
case TRANSFORM_POINTS:
return "TRANSFORM_POINTS";
case TRANSLATE_POINTS:
return "TRANSLATE_POINTS";
case SELECT_SUB_PATH:
return "SELECT_SUB_PATH";
}
return "<unknown mode>";
}
class PathManipulator::Selection : protected std::vector<int32>
{
public:
inline Selection(int32 count = 20)
: _inherited() { reserve(count); }
inline ~Selection() {}
inline void Add(int32 value)
{
if (value >= 0) {
insert(std::upper_bound(begin(), end(), value), value);
}
}
inline bool Remove(int32 value)
{
if (!Contains(value))
return false;
erase(std::lower_bound(begin(), end(), value));
return true;
}
inline bool Contains(int32 value) const
{ return std::binary_search(begin(), end(), value); }
inline bool IsEmpty() const
{ return size() == 0; }
inline int32 IndexAt(int32 index) const
{ return at(index); }
inline void MakeEmpty()
{ clear(); }
inline const int32* Items() const
{ return &(*this)[0]; }
inline const int32 CountItems() const
{ return size(); }
inline Selection& operator =(const Selection& other)
{
_inherited::operator=(other);
return *this;
}
inline bool operator ==(const Selection& other)
{ return (_inherited)*this == (_inherited)other; }
inline bool operator !=(const Selection& other)
{ return (_inherited)*this != (_inherited)other; }
private:
typedef std::vector<int32> _inherited;
};
PathManipulator::PathManipulator(VectorPath* path)
: Manipulator(NULL),
fCanvasView(NULL),
fCommandDown(false),
fOptionDown(false),
fShiftDown(false),
fAltDown(false),
fClickToClose(false),
fMode(NEW_PATH),
fFallBackMode(SELECT_POINTS),
fMouseDown(false),
fPath(path),
fCurrentPathPoint(-1),
fChangePointCommand(NULL),
fInsertPointCommand(NULL),
fAddPointCommand(NULL),
fSelection(new Selection()),
fOldSelection(new Selection()),
fTransformBox(NULL),
fNudgeOffset(0.0, 0.0),
fLastNudgeTime(system_time()),
fNudgeCommand(NULL)
{
fPath->AcquireReference();
fPath->AddListener(this);
fPath->AddObserver(this);
}
PathManipulator::~PathManipulator()
{
delete fChangePointCommand;
delete fInsertPointCommand;
delete fAddPointCommand;
delete fSelection;
delete fOldSelection;
delete fTransformBox;
delete fNudgeCommand;
fPath->RemoveObserver(this);
fPath->RemoveListener(this);
fPath->ReleaseReference();
}
class StrokePathIterator : public VectorPath::Iterator {
public:
StrokePathIterator(CanvasView* canvasView,
BView* drawingView)
: fCanvasView(canvasView),
fDrawingView(drawingView)
{
fDrawingView->SetHighColor(0, 0, 0, 255);
fDrawingView->SetDrawingMode(B_OP_OVER);
}
virtual ~StrokePathIterator()
{}
virtual void MoveTo(BPoint point)
{
fBlack = true;
fSkip = false;
fDrawingView->SetHighColor(0, 0, 0, 255);
fCanvasView->ConvertFromCanvas(&point);
fDrawingView->MovePenTo(point);
}
virtual void LineTo(BPoint point)
{
fCanvasView->ConvertFromCanvas(&point);
if (!fSkip) {
if (fBlack)
fDrawingView->SetHighColor(255, 255, 255, 255);
else
fDrawingView->SetHighColor(0, 0, 0, 255);
fBlack = !fBlack;
fDrawingView->StrokeLine(point);
} else {
fDrawingView->MovePenTo(point);
}
fSkip = !fSkip;
}
private:
CanvasView* fCanvasView;
BView* fDrawingView;
bool fBlack;
bool fSkip;
};
void
PathManipulator::Draw(BView* into, BRect updateRect)
{
#if __HAIKU__
uint32 flags = into->Flags();
into->SetFlags(flags | B_SUBPIXEL_PRECISE);
#endif
StrokePathIterator iterator(fCanvasView, into);
fPath->Iterate(&iterator, fCanvasView->ZoomLevel());
#if __HAIKU__
into->SetFlags(flags);
#endif
into->SetLowColor(0, 0, 0, 255);
BPoint point;
BPoint pointIn;
BPoint pointOut;
rgb_color focusColor = (rgb_color){ 255, 0, 0, 255 };
rgb_color highlightColor = (rgb_color){ 60, 60, 255, 255 };
for (int32 i = 0; fPath->GetPointsAt(i, point, pointIn, pointOut); i++) {
bool highlight = fCurrentPathPoint == i;
bool selected = fSelection->Contains(i);
rgb_color normal = selected ? focusColor : (rgb_color){ 0, 0, 0, 255 };
into->SetLowColor(normal);
into->SetHighColor(255, 255, 255, 255);
fCanvasView->ConvertFromCanvas(&point);
fCanvasView->ConvertFromCanvas(&pointIn);
fCanvasView->ConvertFromCanvas(&pointOut);
into->SetDrawingMode(B_OP_INVERT);
into->StrokeLine(point, pointIn);
into->StrokeLine(point, pointOut);
if (highlight && (fMode == MOVE_POINT ||
fMode == TOGGLE_SHARP ||
fMode == REMOVE_POINT ||
fMode == SELECT_POINTS ||
fMode == CLOSE_PATH)) {
into->SetLowColor(highlightColor);
}
into->SetDrawingMode(B_OP_COPY);
BRect r(point, point);
r.InsetBy(-POINT_EXTEND, -POINT_EXTEND);
into->StrokeRect(r, B_SOLID_LOW);
r.InsetBy(1.0, 1.0);
into->FillRect(r, B_SOLID_HIGH);
if (highlight && (fMode == MOVE_POINT_IN ||
fMode == TOGGLE_SHARP_IN ||
fMode == REMOVE_POINT_IN ||
fMode == SELECT_POINTS))
into->SetLowColor(highlightColor);
else
into->SetLowColor(normal);
if (selected) {
into->SetHighColor(220, 220, 220, 255);
} else {
into->SetHighColor(170, 170, 170, 255);
}
if (pointIn != point) {
r.Set(pointIn.x - CONTROL_POINT_EXTEND, pointIn.y - CONTROL_POINT_EXTEND,
pointIn.x + CONTROL_POINT_EXTEND, pointIn.y + CONTROL_POINT_EXTEND);
into->StrokeRect(r, B_SOLID_LOW);
r.InsetBy(1.0, 1.0);
into->FillRect(r, B_SOLID_HIGH);
}
if (highlight && (fMode == MOVE_POINT_OUT ||
fMode == TOGGLE_SHARP_OUT ||
fMode == REMOVE_POINT_OUT ||
fMode == SELECT_POINTS))
into->SetLowColor(highlightColor);
else
into->SetLowColor(normal);
if (pointOut != point) {
r.Set(pointOut.x - CONTROL_POINT_EXTEND, pointOut.y - CONTROL_POINT_EXTEND,
pointOut.x + CONTROL_POINT_EXTEND, pointOut.y + CONTROL_POINT_EXTEND);
into->StrokeRect(r, B_SOLID_LOW);
r.InsetBy(1.0, 1.0);
into->FillRect(r, B_SOLID_HIGH);
}
}
if (fTransformBox) {
fTransformBox->Draw(into, updateRect);
}
}
bool
PathManipulator::MouseDown(BPoint where)
{
fMouseDown = true;
if (fMode == TRANSFORM_POINTS) {
if (fTransformBox) {
fTransformBox->MouseDown(where);
}
return true;
}
if (fMode == MOVE_POINT &&
fSelection->CountItems() > 1 &&
fSelection->Contains(fCurrentPathPoint)) {
fMode = TRANSLATE_POINTS;
}
if (fMode == ADD_POINT || fMode == TRANSLATE_POINTS)
fCanvasView->FilterMouse(&where);
BPoint canvasWhere = where;
fCanvasView->ConvertToCanvas(&canvasWhere);
delete fChangePointCommand;
fChangePointCommand = NULL;
switch (fMode) {
case TOGGLE_SHARP:
case TOGGLE_SHARP_IN:
case TOGGLE_SHARP_OUT:
case MOVE_POINT:
case MOVE_POINT_IN:
case MOVE_POINT_OUT:
case REMOVE_POINT_IN:
case REMOVE_POINT_OUT:
fChangePointCommand = new ChangePointCommand(fPath,
fCurrentPathPoint,
fSelection->Items(),
fSelection->CountItems());
_Select(fCurrentPathPoint, fShiftDown);
break;
}
switch (fMode) {
case ADD_POINT:
_AddPoint(canvasWhere);
break;
case INSERT_POINT:
_InsertPoint(canvasWhere, fCurrentPathPoint);
break;
case TOGGLE_SHARP:
_SetSharp(fCurrentPathPoint);
break;
case TOGGLE_SHARP_IN:
_SetInOutConnected(fCurrentPathPoint, false);
_SetMode(MOVE_POINT_IN);
break;
case TOGGLE_SHARP_OUT:
_SetInOutConnected(fCurrentPathPoint, false);
_SetMode(MOVE_POINT_OUT);
break;
case MOVE_POINT:
case MOVE_POINT_IN:
case MOVE_POINT_OUT:
break;
case CLOSE_PATH:
break;
case REMOVE_POINT:
if (fPath->CountPoints() == 1) {
} else {
fCanvasView->Perform(new RemovePointsCommand(fPath,
fCurrentPathPoint,
fSelection->Items(),
fSelection->CountItems()));
_RemovePoint(fCurrentPathPoint);
}
break;
case REMOVE_POINT_IN:
_RemovePointIn(fCurrentPathPoint);
break;
case REMOVE_POINT_OUT:
_RemovePointOut(fCurrentPathPoint);
break;
case SELECT_POINTS: {
bool appendSelection;
if (fPath->IsClosed())
appendSelection = fShiftDown;
else
appendSelection = fShiftDown && fCurrentPathPoint >= 0;
if (!appendSelection) {
fSelection->MakeEmpty();
_UpdateSelection();
}
*fOldSelection = *fSelection;
if (fCurrentPathPoint >= 0) {
_Select(fCurrentPathPoint, appendSelection);
}
fCanvasView->BeginRectTracking(BRect(where, where),
B_TRACK_RECT_CORNER);
break;
}
}
fTrackingStart = canvasWhere;
fLastCanvasPos = where;
fCanvasView->ConvertToCanvas(&fLastCanvasPos);
UpdateCursor();
return true;
}
void
PathManipulator::MouseMoved(BPoint where)
{
fCanvasView->FilterMouse(&where);
BPoint canvasWhere = where;
fCanvasView->ConvertToCanvas(&canvasWhere);
if (fLastCanvasPos == canvasWhere)
return;
fLastCanvasPos = canvasWhere;
if (fMode == TRANSFORM_POINTS) {
if (fTransformBox) {
fTransformBox->MouseMoved(where);
}
return;
}
if (fMode == CLOSE_PATH) {
_SetMode(MOVE_POINT);
delete fChangePointCommand;
fChangePointCommand = new ChangePointCommand(fPath,
fCurrentPathPoint,
fSelection->Items(),
fSelection->CountItems());
}
switch (fMode) {
case ADD_POINT:
case INSERT_POINT:
case TOGGLE_SHARP:
fPath->SetPointOut(fCurrentPathPoint, canvasWhere, true);
break;
case MOVE_POINT:
fPath->SetPoint(fCurrentPathPoint, canvasWhere);
break;
case MOVE_POINT_IN:
fPath->SetPointIn(fCurrentPathPoint, canvasWhere);
break;
case MOVE_POINT_OUT:
fPath->SetPointOut(fCurrentPathPoint, canvasWhere);
break;
case SELECT_POINTS: {
BRect r;
r.left = min_c(fTrackingStart.x, canvasWhere.x);
r.top = min_c(fTrackingStart.y, canvasWhere.y);
r.right = max_c(fTrackingStart.x, canvasWhere.x);
r.bottom = max_c(fTrackingStart.y, canvasWhere.y);
_Select(r);
break;
}
case TRANSLATE_POINTS: {
BPoint offset = canvasWhere - fTrackingStart;
_Nudge(offset);
fTrackingStart = canvasWhere;
break;
}
}
}
Command*
PathManipulator::MouseUp()
{
if (!fMouseDown)
return NULL;
fMouseDown = false;
if (fMode == TRANSFORM_POINTS) {
if (fTransformBox) {
return fTransformBox->MouseUp();
}
return NULL;
}
Command* command = NULL;
switch (fMode) {
case ADD_POINT:
command = fAddPointCommand;
fAddPointCommand = NULL;
_SetMode(MOVE_POINT_OUT);
break;
case INSERT_POINT:
command = fInsertPointCommand;
fInsertPointCommand = NULL;
break;
case SELECT_POINTS:
if (*fSelection != *fOldSelection) {
}
fCanvasView->EndRectTracking();
break;
case TOGGLE_SHARP:
case TOGGLE_SHARP_IN:
case TOGGLE_SHARP_OUT:
case MOVE_POINT:
case MOVE_POINT_IN:
case MOVE_POINT_OUT:
case REMOVE_POINT_IN:
case REMOVE_POINT_OUT:
command = fChangePointCommand;
fChangePointCommand = NULL;
break;
case TRANSLATE_POINTS:
if (!fNudgeCommand) {
*fOldSelection = *fSelection;
if (fCurrentPathPoint >= 0) {
_Select(fCurrentPathPoint, fShiftDown);
}
if (*fSelection != *fOldSelection) {
}
} else {
command = _FinishNudging();
}
break;
}
return command;
}
bool
PathManipulator::MouseOver(BPoint where)
{
if (fMode == TRANSFORM_POINTS) {
if (fTransformBox) {
return fTransformBox->MouseOver(where);
}
return false;
}
BPoint canvasWhere = where;
fCanvasView->ConvertToCanvas(&canvasWhere);
if (fMouseDown && fLastCanvasPos == canvasWhere)
return false;
fLastCanvasPos = canvasWhere;
fCanvasView->ConvertToCanvas(&where);
_SetModeForMousePos(where);
return true;
}
bool
PathManipulator::DoubleClicked(BPoint where)
{
return false;
}
bool
PathManipulator::ShowContextMenu(BPoint where)
{
if (fCurrentPathPoint >= 0 && !fSelection->Contains(fCurrentPathPoint)) {
fSelection->MakeEmpty();
_UpdateSelection();
*fOldSelection = *fSelection;
_Select(fCurrentPathPoint, false);
}
BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
BMessage* message;
BMenuItem* item;
bool hasSelection = fSelection->CountItems() > 0;
if (fCurrentPathPoint < 0) {
message = new BMessage(B_SELECT_ALL);
item = new BMenuItem(B_TRANSLATE("Select all"), message, 'A');
menu->AddItem(item);
menu->AddSeparatorItem();
}
message = new BMessage(MSG_TRANSFORM);
item = new BMenuItem(B_TRANSLATE("Transform"), message, 'T');
item->SetEnabled(hasSelection);
menu->AddItem(item);
message = new BMessage(MSG_SPLIT_POINTS);
item = new BMenuItem(B_TRANSLATE("Split"), message);
item->SetEnabled(hasSelection);
menu->AddItem(item);
message = new BMessage(MSG_FLIP_POINTS);
item = new BMenuItem(B_TRANSLATE("Flip"), message);
item->SetEnabled(hasSelection);
menu->AddItem(item);
message = new BMessage(MSG_REMOVE_POINTS);
item = new BMenuItem(B_TRANSLATE("Remove"), message);
item->SetEnabled(hasSelection);
menu->AddItem(item);
menu->SetTargetForItems(fCanvasView);
menu->SetAsyncAutoDestruct(true);
menu->SetFont(be_plain_font);
where = fCanvasView->ConvertToScreen(where);
BRect mouseRect(where, where);
mouseRect.InsetBy(-10.0, -10.0);
where += BPoint(5.0, 5.0);
menu->Go(where, true, false, mouseRect, true);
return true;
}
BRect
PathManipulator::Bounds()
{
BRect r = _ControlPointRect();
fCanvasView->ConvertFromCanvas(&r);
return r;
}
BRect
PathManipulator::TrackingBounds(BView* withinView)
{
return withinView->Bounds();
}
bool
PathManipulator::MessageReceived(BMessage* message, Command** _command)
{
bool result = true;
switch (message->what) {
case MSG_TRANSFORM:
if (!fSelection->IsEmpty())
_SetMode(TRANSFORM_POINTS);
break;
case MSG_REMOVE_POINTS:
*_command = _Delete();
break;
case MSG_SPLIT_POINTS:
*_command = new SplitPointsCommand(fPath,
fSelection->Items(),
fSelection->CountItems());
break;
case MSG_FLIP_POINTS:
*_command = new FlipPointsCommand(fPath,
fSelection->Items(),
fSelection->CountItems());
break;
case B_SELECT_ALL: {
int32 count = fPath->CountPoints();
int32 indices[count];
for (int32 i = 0; i < count; i++)
indices[i] = i;
_Select(indices, count);
break;
}
default:
result = false;
break;
}
return result;
}
void
PathManipulator::ModifiersChanged(uint32 modifiers)
{
fCommandDown = modifiers & B_COMMAND_KEY;
fOptionDown = modifiers & B_CONTROL_KEY;
fShiftDown = modifiers & B_SHIFT_KEY;
fAltDown = modifiers & B_OPTION_KEY;
if (fTransformBox) {
fTransformBox->ModifiersChanged(modifiers);
return;
}
if (!fMouseDown)
_SetModeForMousePos(fLastCanvasPos);
}
bool
PathManipulator::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command)
{
bool result = true;
float nudgeDist = 1.0;
if (modifiers & B_SHIFT_KEY)
nudgeDist /= fCanvasView->ZoomLevel();
switch (key) {
case B_RETURN:
if (fTransformBox) {
_SetTransformBox(NULL);
}
break;
case B_ESCAPE:
if (fTransformBox) {
fTransformBox->Cancel();
_SetTransformBox(NULL);
} else if (fFallBackMode == NEW_PATH) {
fFallBackMode = SELECT_POINTS;
_SetTransformBox(NULL);
}
break;
case 't':
case 'T':
if (!fSelection->IsEmpty())
_SetMode(TRANSFORM_POINTS);
else
result = false;
break;
case B_UP_ARROW:
_Nudge(BPoint(0.0, -nudgeDist));
break;
case B_DOWN_ARROW:
_Nudge(BPoint(0.0, nudgeDist));
break;
case B_LEFT_ARROW:
_Nudge(BPoint(-nudgeDist, 0.0));
break;
case B_RIGHT_ARROW:
_Nudge(BPoint(nudgeDist, 0.0));
break;
case B_DELETE:
if (!fSelection->IsEmpty())
*_command = _Delete();
else
result = false;
break;
default:
result = false;
}
return result;
}
bool
PathManipulator::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command)
{
bool handled = true;
switch (key) {
case B_UP_ARROW:
case B_DOWN_ARROW:
case B_LEFT_ARROW:
case B_RIGHT_ARROW:
*_command = _FinishNudging();
break;
default:
handled = false;
break;
}
return handled;
}
bool
PathManipulator::UpdateCursor()
{
if (fTransformBox)
return fTransformBox->UpdateCursor();
const uchar* cursorData;
switch (fMode) {
case ADD_POINT:
cursorData = kPathAddCursor;
break;
case INSERT_POINT:
cursorData = kPathInsertCursor;
break;
case MOVE_POINT:
case MOVE_POINT_IN:
case MOVE_POINT_OUT:
case TRANSLATE_POINTS:
cursorData = kPathMoveCursor;
break;
case CLOSE_PATH:
cursorData = kPathCloseCursor;
break;
case TOGGLE_SHARP:
case TOGGLE_SHARP_IN:
case TOGGLE_SHARP_OUT:
cursorData = kPathSharpCursor;
break;
case REMOVE_POINT:
case REMOVE_POINT_IN:
case REMOVE_POINT_OUT:
cursorData = kPathRemoveCursor;
break;
case SELECT_POINTS:
cursorData = kPathSelectCursor;
break;
case SELECT_SUB_PATH:
cursorData = B_HAND_CURSOR;
break;
case UNDEFINED:
default:
cursorData = kStopCursor;
break;
}
BCursor cursor(cursorData);
fCanvasView->SetViewCursor(&cursor, true);
fCanvasView->Sync();
return true;
}
void
PathManipulator::AttachedToView(BView* view)
{
fCanvasView = dynamic_cast<CanvasView*>(view);
}
void
PathManipulator::DetachedFromView(BView* view)
{
fCanvasView = NULL;
}
void
PathManipulator::ObjectChanged(const Observable* object)
{
BRect currentBounds = _ControlPointRect();
_InvalidateCanvas(currentBounds | fPreviousBounds);
fPreviousBounds = currentBounds;
if (!fMouseDown && !fTransformBox)
_SetModeForMousePos(fLastCanvasPos);
}
void
PathManipulator::PointAdded(int32 index)
{
ObjectChanged(fPath);
}
void
PathManipulator::PointRemoved(int32 index)
{
fSelection->Remove(index);
ObjectChanged(fPath);
}
void
PathManipulator::PointChanged(int32 index)
{
ObjectChanged(fPath);
}
void
PathManipulator::PathChanged()
{
ObjectChanged(fPath);
}
void
PathManipulator::PathClosedChanged()
{
ObjectChanged(fPath);
}
void
PathManipulator::PathReversed()
{
int32 count = fSelection->CountItems();
int32 pointCount = fPath->CountPoints();
if (count > 0) {
Selection temp;
for (int32 i = 0; i < count; i++) {
temp.Add((pointCount - 1) - fSelection->IndexAt(i));
}
*fSelection = temp;
}
ObjectChanged(fPath);
}
uint32
PathManipulator::ControlFlags() const
{
uint32 flags = 0;
return flags;
}
void
PathManipulator::ReversePath()
{
int32 count = fSelection->CountItems();
int32 pointCount = fPath->CountPoints();
if (count > 0) {
Selection temp;
for (int32 i = 0; i < count; i++) {
temp.Add((pointCount - 1) - fSelection->IndexAt(i));
}
*fSelection = temp;
}
fPath->Reverse();
}
void
PathManipulator::_SetMode(uint32 mode)
{
if (fMode != mode) {
fMode = mode;
if (fMode == TRANSFORM_POINTS) {
_SetTransformBox(new TransformPointsBox(fCanvasView,
this,
fPath,
fSelection->Items(),
fSelection->CountItems()));
} else {
if (fTransformBox)
_SetTransformBox(NULL);
}
if (BWindow* window = fCanvasView->Window()) {
window->PostMessage(MSG_UPDATE_SHAPE_UI);
}
UpdateCursor();
}
}
void
PathManipulator::_SetTransformBox(TransformPointsBox* transformBox)
{
if (fTransformBox == transformBox)
return;
BRect dirty(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN);
if (fTransformBox) {
dirty = fTransformBox->Bounds();
delete fTransformBox;
}
fTransformBox = transformBox;
if (fTransformBox) {
fTransformBox->MouseMoved(fLastCanvasPos);
if (fMode != TRANSFORM_POINTS) {
fMode = TRANSFORM_POINTS;
}
dirty = dirty | fTransformBox->Bounds();
} else {
if (fMode == TRANSFORM_POINTS) {
_SetModeForMousePos(fLastCanvasPos);
}
}
if (dirty.IsValid()) {
dirty.InsetBy(-8, -8);
fCanvasView->Invalidate(dirty);
}
}
void
PathManipulator::_AddPoint(BPoint where)
{
if (fPath->AddPoint(where)) {
fCurrentPathPoint = fPath->CountPoints() - 1;
delete fAddPointCommand;
fAddPointCommand = new AddPointCommand(fPath, fCurrentPathPoint,
fSelection->Items(),
fSelection->CountItems());
_Select(fCurrentPathPoint, fShiftDown);
}
}
BPoint
scale_point(BPoint a, BPoint b, float scale)
{
return BPoint(a.x + (b.x - a.x) * scale,
a.y + (b.y - a.y) * scale);
}
void
PathManipulator::_InsertPoint(BPoint where, int32 index)
{
double scale;
BPoint point;
BPoint pointIn;
BPoint pointOut;
BPoint previous;
BPoint previousOut;
BPoint next;
BPoint nextIn;
if (fPath->FindBezierScale(index - 1, where, &scale)
&& scale >= 0.0 && scale <= 1.0
&& fPath->GetPoint(index - 1, scale, point)) {
fPath->GetPointAt(index - 1, previous);
fPath->GetPointOutAt(index - 1, previousOut);
fPath->GetPointAt(index, next);
fPath->GetPointInAt(index, nextIn);
where = scale_point(previousOut, nextIn, scale);
previousOut = scale_point(previous, previousOut, scale);
nextIn = scale_point(next, nextIn, 1 - scale);
pointIn = scale_point(previousOut, where, scale);
pointOut = scale_point(nextIn, where, 1 - scale);
if (fPath->AddPoint(point, index)) {
fPath->SetPointIn(index, pointIn);
fPath->SetPointOut(index, pointOut);
delete fInsertPointCommand;
fInsertPointCommand = new InsertPointCommand(fPath, index,
fSelection->Items(),
fSelection->CountItems());
fPath->SetPointOut(index - 1, previousOut);
fPath->SetPointIn(index + 1, nextIn);
fCurrentPathPoint = index;
_ShiftSelection(fCurrentPathPoint, 1);
_Select(fCurrentPathPoint, fShiftDown);
}
}
}
void
PathManipulator::_SetInOutConnected(int32 index, bool connected)
{
fPath->SetInOutConnected(index, connected);
}
void
PathManipulator::_SetSharp(int32 index)
{
BPoint p;
fPath->GetPointAt(index, p);
fPath->SetPoint(index, p, p, p, true);
}
void
PathManipulator::_RemoveSelection()
{
Selection selection = *fSelection;
int32 count = selection.CountItems();
for (int32 i = 0; i < count; i++) {
if (!fPath->RemovePoint(selection.IndexAt(i) - i))
break;
}
fPath->SetClosed(fPath->IsClosed() && fPath->CountPoints() > 1);
fSelection->MakeEmpty();
}
void
PathManipulator::_RemovePoint(int32 index)
{
if (fPath->RemovePoint(index)) {
_Deselect(index);
_ShiftSelection(index + 1, -1);
}
}
void
PathManipulator::_RemovePointIn(int32 index)
{
BPoint p;
if (fPath->GetPointAt(index, p)) {
fPath->SetPointIn(index, p);
fPath->SetInOutConnected(index, false);
}
}
void
PathManipulator::_RemovePointOut(int32 index)
{
BPoint p;
if (fPath->GetPointAt(index, p)) {
fPath->SetPointOut(index, p);
fPath->SetInOutConnected(index, false);
}
}
Command*
PathManipulator::_Delete()
{
Command* command = NULL;
if (!fMouseDown) {
if (fTransformBox) {
_SetTransformBox(NULL);
}
if (fSelection->CountItems() == fPath->CountPoints()) {
} else {
command = new RemovePointsCommand(fPath,
fSelection->Items(),
fSelection->CountItems());
_RemoveSelection();
}
_SetModeForMousePos(fLastCanvasPos);
}
return command;
}
void
PathManipulator::_Select(BRect r)
{
BPoint p;
BPoint pIn;
BPoint pOut;
int32 count = fPath->CountPoints();
Selection temp;
for (int32 i = 0; i < count && fPath->GetPointsAt(i, p, pIn, pOut); i++) {
if (r.Contains(p) || r.Contains(pIn) || r.Contains(pOut)) {
temp.Add(i);
}
}
count = fOldSelection->CountItems();
for (int32 i = 0; i < count; i++) {
int32 index = fOldSelection->IndexAt(i);
if (temp.Contains(index))
temp.Remove(index);
else
temp.Add(index);
}
if (temp != *fSelection) {
*fSelection = temp;
_UpdateSelection();
}
}
void
PathManipulator::_Select(int32 index, bool extend)
{
if (!extend)
fSelection->MakeEmpty();
if (fSelection->Contains(index))
fSelection->Remove(index);
else
fSelection->Add(index);
_UpdateSelection();
}
void
PathManipulator::_Select(const int32* indices, int32 count, bool extend)
{
if (extend) {
for (int32 i = 0; i < count; i++) {
if (!fSelection->Contains(indices[i]))
fSelection->Add(indices[i]);
}
} else {
fSelection->MakeEmpty();
for (int32 i = 0; i < count; i++) {
fSelection->Add(indices[i]);
}
}
_UpdateSelection();
}
void
PathManipulator::_Deselect(int32 index)
{
if (fSelection->Contains(index)) {
fSelection->Remove(index);
_UpdateSelection();
}
}
void
PathManipulator::_ShiftSelection(int32 startIndex, int32 direction)
{
int32 count = fSelection->CountItems();
if (count > 0) {
for (int32 i = 0; i < count; i++) {
int32 index = fSelection->IndexAt(i);
if (index >= startIndex) {
fSelection->Remove(index);
fSelection->Add(index + direction);
}
}
}
_UpdateSelection();
}
bool
PathManipulator::_IsSelected(int32 index) const
{
return fSelection->Contains(index);
}
void
PathManipulator::_InvalidateCanvas(BRect rect) const
{
fCanvasView->ConvertFromCanvas(&rect);
fCanvasView->Invalidate(rect);
}
void
PathManipulator::_InvalidateHighlightPoints(int32 newIndex, uint32 newMode)
{
BRect oldRect = _ControlPointRect(fCurrentPathPoint, fMode);
BRect newRect = _ControlPointRect(newIndex, newMode);
if (oldRect.IsValid())
_InvalidateCanvas(oldRect);
if (newRect.IsValid())
_InvalidateCanvas(newRect);
}
void
PathManipulator::_UpdateSelection() const
{
_InvalidateCanvas(_ControlPointRect());
if (BWindow* window = fCanvasView->Window()) {
window->PostMessage(MSG_UPDATE_SHAPE_UI);
}
}
BRect
PathManipulator::_ControlPointRect() const
{
BRect r = fPath->ControlPointBounds();
r.InsetBy(-POINT_EXTEND, -POINT_EXTEND);
return r;
}
BRect
PathManipulator::_ControlPointRect(int32 index, uint32 mode) const
{
BRect rect(0.0, 0.0, -1.0, -1.0);
if (index >= 0) {
BPoint p, pIn, pOut;
fPath->GetPointsAt(index, p, pIn, pOut);
switch (mode) {
case MOVE_POINT:
case TOGGLE_SHARP:
case REMOVE_POINT:
case CLOSE_PATH:
rect.Set(p.x, p.y, p.x, p.y);
rect.InsetBy(-POINT_EXTEND, -POINT_EXTEND);
break;
case MOVE_POINT_IN:
case TOGGLE_SHARP_IN:
case REMOVE_POINT_IN:
rect.Set(pIn.x, pIn.y, pIn.x, pIn.y);
rect.InsetBy(-CONTROL_POINT_EXTEND, -CONTROL_POINT_EXTEND);
break;
case MOVE_POINT_OUT:
case TOGGLE_SHARP_OUT:
case REMOVE_POINT_OUT:
rect.Set(pOut.x, pOut.y, pOut.x, pOut.y);
rect.InsetBy(-CONTROL_POINT_EXTEND, -CONTROL_POINT_EXTEND);
break;
case SELECT_POINTS:
rect.Set(min4(p.x, pIn.x, pOut.x, pOut.x),
min4(p.y, pIn.y, pOut.y, pOut.y),
max4(p.x, pIn.x, pOut.x, pOut.x),
max4(p.y, pIn.y, pOut.y, pOut.y));
rect.InsetBy(-POINT_EXTEND, -POINT_EXTEND);
break;
}
}
return rect;
}
void
PathManipulator::_SetModeForMousePos(BPoint where)
{
uint32 mode = UNDEFINED;
int32 index = -1;
float zoomLevel = fCanvasView->ZoomLevel();
BPoint point;
BPoint pointIn;
BPoint pointOut;
for (int32 i = 0; fPath->GetPointsAt(i, point, pointIn, pointOut)
&& mode == UNDEFINED; i++) {
float distM = point_point_distance(point, where) * zoomLevel;
float distIn = point_point_distance(pointIn, where) * zoomLevel;
float distOut = point_point_distance(pointOut, where) * zoomLevel;
if (distM < MOVE_THRESHOLD) {
if (i == 0 && fClickToClose
&& !fPath->IsClosed() && fPath->CountPoints() > 1) {
mode = fCommandDown ? TOGGLE_SHARP :
(fOptionDown ? REMOVE_POINT : CLOSE_PATH);
index = i;
} else {
mode = fCommandDown ? TOGGLE_SHARP :
(fOptionDown ? REMOVE_POINT : MOVE_POINT);
index = i;
}
}
if (distM - distIn > 0.00001
&& distIn < MOVE_THRESHOLD) {
mode = fCommandDown ? TOGGLE_SHARP_IN :
(fOptionDown ? REMOVE_POINT_IN : MOVE_POINT_IN);
index = i;
}
if (distIn - distOut > 0.00001
&& distOut < distM && distOut < MOVE_THRESHOLD) {
mode = fCommandDown ? TOGGLE_SHARP_OUT :
(fOptionDown ? REMOVE_POINT_OUT : MOVE_POINT_OUT);
index = i;
}
}
int32 pointCount = fPath->CountPoints();
if (fShiftDown && pointCount > 0) {
mode = SELECT_POINTS;
}
if (fAltDown) {
mode = NEW_PATH;
index = -1;
}
if (mode == UNDEFINED) {
float distance;
if (fPath->GetDistance(where, &distance, &index)) {
if (distance < (INSERT_DIST_THRESHOLD / zoomLevel)) {
mode = INSERT_POINT;
}
} else {
index = fCurrentPathPoint;
}
}
if (mode == UNDEFINED) {
if (fFallBackMode == SELECT_POINTS) {
if (fPath->IsClosed() && pointCount > 0) {
mode = SELECT_POINTS;
index = -1;
} else {
mode = ADD_POINT;
index = pointCount - 1;
}
} else {
mode = fFallBackMode;
}
}
if (mode != fMode || index != fCurrentPathPoint) {
_InvalidateHighlightPoints(index, mode);
_SetMode(mode);
fCurrentPathPoint = index;
}
}
void
PathManipulator::_Nudge(BPoint direction)
{
bigtime_t now = system_time();
if (now - fLastNudgeTime > 500000) {
fCanvasView->Perform(_FinishNudging());
}
fLastNudgeTime = now;
fNudgeOffset += direction;
if (fTransformBox) {
fTransformBox->NudgeBy(direction);
return;
}
if (!fNudgeCommand) {
bool fromSelection = !fSelection->IsEmpty();
int32 count = fromSelection ? fSelection->CountItems()
: fPath->CountPoints();
int32 indices[count];
BStackOrHeapArray<control_point, 64> points(count);
for (int32 i = 0; i < count; i++) {
indices[i] = fromSelection ? fSelection->IndexAt(i) : i;
fPath->GetPointsAt(indices[i],
points[i].point,
points[i].point_in,
points[i].point_out,
&points[i].connected);
}
fNudgeCommand = new NudgePointsCommand(fPath, indices, points, count);
fNudgeCommand->SetNewTranslation(fNudgeOffset);
fNudgeCommand->Redo();
} else {
fNudgeCommand->SetNewTranslation(fNudgeOffset);
fNudgeCommand->Redo();
}
if (!fMouseDown)
_SetModeForMousePos(fLastCanvasPos);
}
Command*
PathManipulator::_FinishNudging()
{
fNudgeOffset = BPoint(0.0, 0.0);
Command* command;
if (fTransformBox) {
command = fTransformBox->FinishNudging();
} else {
command = fNudgeCommand;
fNudgeCommand = NULL;
}
return command;
}