

#include "selectiontool.h"
#include "rasterselectiontool.h"
#include "vectorselectiontool.h"
#include "tcurveutil.h"
#include "tenv.h"
#include "drawutil.h"
#include "tools/toolhandle.h"
#include "tools/cursors.h"
#include "toonz/stage2.h"
#include "toonz/tobjecthandle.h"

#include <QKeyEvent>

#include <memory>

using namespace ToolUtils;
using namespace DragSelectionTool;

TEnv::StringVar SelectionType("SelectionType", "Rectangular");

//-----------------------------------------------------------------------------

template <typename Tv, typename Tr, typename... Args>
DragSelectionTool::DragTool *createNewDragTool(SelectionTool *st,
                                               Args... args) {
  VectorSelectionTool *vst = dynamic_cast<VectorSelectionTool *>(st);
  RasterSelectionTool *rst = dynamic_cast<RasterSelectionTool *>(st);
  if (vst)
    return new Tv(vst, args...);
  else if (rst)
    return new Tr(rst, args...);
  return nullptr;
}

DragSelectionTool::DragTool *createNewMoveSelectionTool(SelectionTool *st) {
  return createNewDragTool<VectorMoveSelectionTool, RasterMoveSelectionTool>(
      st);
}

DragSelectionTool::DragTool *createNewRotationTool(SelectionTool *st) {
  return createNewDragTool<VectorRotationTool, RasterRotationTool>(st);
}

DragSelectionTool::DragTool *createNewFreeDeformTool(SelectionTool *st) {
  return createNewDragTool<VectorFreeDeformTool, RasterFreeDeformTool>(st);
}

DragSelectionTool::DragTool *createNewScaleTool(SelectionTool *st,
                                                ScaleType type) {
  return createNewDragTool<VectorScaleTool, RasterScaleTool>(st, type);
}

//=============================================================================
namespace {
//-----------------------------------------------------------------------------

// Return index of point with min x or y
int tminPoint(std::vector<TPointD> points, bool isX) {
  int i;
  int index = 0;
  TPointD p = points[0];
  for (i = 1; i < (int)points.size(); i++) {
    TPointD nextP = points[i];
    if ((isX && p.x < nextP.x) || (!isX && p.y < nextP.y)) continue;
    index = i;
  }
  return index;
}

//-----------------------------------------------------------------------------

int tminPoint(TPointD p0, TPointD p1, bool isX) {
  std::vector<TPointD> v;
  v.push_back(p0);
  v.push_back(p1);
  return tminPoint(v, isX);
}

//=============================================================================
}  // namespace
//-----------------------------------------------------------------------------

//=============================================================================
// FourPoints
//-----------------------------------------------------------------------------

FourPoints DragSelectionTool::FourPoints::orderedPoints() const {
  FourPoints newPoints;
  int i;
  std::vector<TPointD> allPoints;
  allPoints.push_back(m_p00);
  allPoints.push_back(m_p01);
  allPoints.push_back(m_p10);
  allPoints.push_back(m_p11);
  int minXindex1 = tminPoint(allPoints, true);
  std::vector<TPointD> points;
  for (i = 0; i < 4; i++)
    if (i != minXindex1) points.push_back(allPoints[i]);
  int minXindex2 = tminPoint(points, true);

  int index = tminPoint(allPoints[minXindex1], points[minXindex2], false);
  TPointD newPoint1 = allPoints[minXindex1];
  TPointD newPoint2 = points[minXindex2];
  if (index == 1) std::swap(newPoint1, newPoint2);
  newPoints.setP00(newPoint1);
  newPoints.setP01(newPoint2);

  std::vector<TPointD> points2;
  for (i = 0; i < 3; i++)
    if (i != minXindex2) points2.push_back(points[i]);

  index = tminPoint(points2, false);
  newPoints.setP10(points2[index]);
  newPoints.setP11(points2[(index == 0) ? 1 : 0]);
  return newPoints;
}

//-----------------------------------------------------------------------------

TPointD DragSelectionTool::FourPoints::getPoint(int index) const {
  if (index == 0)
    return m_p00;
  else if (index == 1)
    return m_p10;
  else if (index == 2)
    return m_p11;
  else if (index == 3)
    return m_p01;
  else if (index == 4)
    return (m_p00 + m_p10) * 0.5;
  else if (index == 5)
    return (m_p10 + m_p11) * 0.5;
  else if (index == 6)
    return (m_p11 + m_p01) * 0.5;
  else if (index == 7)
    return (m_p01 + m_p00) * 0.5;
  return TPointD();
}

//-----------------------------------------------------------------------------

void DragSelectionTool::FourPoints::setPoint(int index, const TPointD &p) {
  if (index == 0)
    m_p00 = p;
  else if (index == 1)
    m_p10 = p;
  else if (index == 2)
    m_p11 = p;
  else if (index == 3)
    m_p01 = p;
}

//-----------------------------------------------------------------------------

FourPoints DragSelectionTool::FourPoints::enlarge(double d) {
  TPointD v   = normalize(getP10() - getP00());
  TPointD p00 = getP00() - d * v;
  TPointD p10 = getP10() + d * v;
  v           = normalize(getP11() - getP10());
  p10         = p10 - d * v;
  TPointD p11 = getP11() + d * v;
  v           = normalize(getP01() - getP11());
  p11         = p11 - d * v;
  TPointD p01 = getP01() + d * v;
  v           = normalize(getP00() - getP01());
  p01         = p01 - d * v;
  p00         = p00 + d * v;
  return FourPoints(p00, p01, p10, p11);
}

//-----------------------------------------------------------------------------

bool DragSelectionTool::FourPoints::isEmpty() {
  return ((getP00().x == getP01().x && getP01().x == getP10().x &&
           getP10().x == getP11().x) ||
          (getP00().y == getP01().y && getP01().y == getP10().y &&
           getP10().y == getP11().y));
}

//-----------------------------------------------------------------------------

void DragSelectionTool::FourPoints::empty() {
  m_p00 = TPointD();
  m_p01 = TPointD();
  m_p10 = TPointD();
  m_p11 = TPointD();
}

//-----------------------------------------------------------------------------

bool DragSelectionTool::FourPoints::contains(TPointD p) {
  double maxDistance =
      std::max(tdistance2(getP00(), getP11()), tdistance2(getP10(), getP01()));
  TPointD outP = p + maxDistance * TPointD(1, 1);
  TSegment segment(outP, p);
  std::vector<DoublePair> d;
  int inters = intersect(TSegment(getP00(), getP10()), segment, d);
  inters += intersect(TSegment(getP10(), getP11()), segment, d);
  inters += intersect(TSegment(getP11(), getP01()), segment, d);
  inters += intersect(TSegment(getP01(), getP00()), segment, d);
  return inters % 2 == 1;
}

//-----------------------------------------------------------------------------

TRectD DragSelectionTool::FourPoints::getBox() const {
  double x0 = std::min({getP00().x, getP10().x, getP01().x, getP11().x});
  double y0 = std::min({getP00().y, getP10().y, getP01().y, getP11().y});
  double x1 = std::max({getP00().x, getP10().x, getP01().x, getP11().x});
  double y1 = std::max({getP00().y, getP10().y, getP01().y, getP11().y});
  return TRectD(TPointD(x0, y0), TPointD(x1, y1));
}

//-----------------------------------------------------------------------------

FourPoints &DragSelectionTool::FourPoints::operator=(const TRectD &r) {
  setP00(r.getP00());
  setP01(r.getP01());
  setP10(r.getP10());
  setP11(r.getP11());
  return *this;
}

//-----------------------------------------------------------------------------

bool DragSelectionTool::FourPoints::operator==(const FourPoints &p) const {
  return getP00() == p.getP00() && getP01() == p.getP01() &&
         getP10() == p.getP10() && getP11() == p.getP11();
}

//-----------------------------------------------------------------------------

FourPoints DragSelectionTool::FourPoints::operator*(const TAffine &aff) const {
  FourPoints p;
  p.setP00(aff * getP00());
  p.setP10(aff * getP10());
  p.setP11(aff * getP11());
  p.setP01(aff * getP01());
  return p;
}

//-----------------------------------------------------------------------------

void DragSelectionTool::drawFourPoints(const FourPoints &rect,
                                       const TPixel32 &color,
                                       unsigned short stipple,
                                       bool doContrast) {
  GLint src, dst;
  bool isEnabled;
  tglColor(color);
  if (doContrast) {
    if (color == TPixel32::Black) tglColor(TPixel32(90, 90, 90));
    isEnabled = glIsEnabled(GL_BLEND);
    glGetIntegerv(GL_BLEND_SRC, &src);
    glGetIntegerv(GL_BLEND_DST, &dst);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
  }

  if (stipple != 0xffff) {
    glLineStipple(1, stipple);
    glEnable(GL_LINE_STIPPLE);
  }

  glBegin(GL_LINE_STRIP);
  tglVertex(rect.getP00());
  tglVertex(rect.getP01());
  tglVertex(rect.getP11());
  tglVertex(rect.getP10());
  tglVertex(rect.getP00());
  glEnd();
  glDisable(GL_LINE_STIPPLE);
  if (doContrast) {
    if (!isEnabled) glDisable(GL_BLEND);
    glBlendFunc(src, dst);
  }
}

//=============================================================================
namespace {
//-----------------------------------------------------------------------------

//=============================================================================
// UndoMoveCenter
//-----------------------------------------------------------------------------

class UndoMoveCenter final : public TUndo {
  SelectionTool *m_tool;
  TAffine m_aff;

public:
  UndoMoveCenter(SelectionTool *tool, const TAffine &aff)
      : m_tool(tool), m_aff(aff) {}
  ~UndoMoveCenter() {}
  void undo() const override {
    m_tool->setCenter(m_aff.inv() * m_tool->getCenter());
    m_tool->invalidate();
  }
  void redo() const override {
    m_tool->setCenter(m_aff * m_tool->getCenter());
    m_tool->invalidate();
  }
  int getSize() const override { return sizeof(*this) + sizeof(*m_tool); }

  QString getHistoryString() override { return QObject::tr("Move Center"); }
};

//=============================================================================
// MoveCenterTool
//-----------------------------------------------------------------------------

class MoveCenterTool final : public DragTool {
  TPointD m_startPos;
  TAffine m_transform;

public:
  MoveCenterTool(SelectionTool *tool)
      : DragTool(tool), m_startPos(), m_transform() {}
  void translateCenter(TAffine aff) {
    getTool()->setCenter(aff * getTool()->getCenter());
    m_transform *= aff;
    getTool()->invalidate();
  }
  void leftButtonDown(const TPointD &pos, const TMouseEvent &e) override {
    m_startPos = pos;
  }
  void leftButtonDrag(const TPointD &pos, const TMouseEvent &e) override {
    TPointD delta      = pos - m_startPos;
    FourPoints bbox    = getTool()->getBBox();
    TPointD bboxCenter = 0.5 * (bbox.getP11() + bbox.getP00());
    double maxDistance2 =
        32 * getTool()->getPixelSize() * getTool()->getPixelSize();
    TAffine aff       = m_transform.inv() * TTranslation(delta);
    TPointD newCenter = aff * getTool()->getCenter();
    if (tdistance2(newCenter, bboxCenter) < maxDistance2)
      translateCenter(TTranslation(bboxCenter - getTool()->getCenter()));
    else
      translateCenter(aff);
  }
  void leftButtonUp(const TPointD &pos, const TMouseEvent &e) override {
    UndoMoveCenter *undo = new UndoMoveCenter(getTool(), m_transform);
    TUndoManager::manager()->add(undo);
  }
  void draw() override {}
};

//=============================================================================
}  // namespace
//-----------------------------------------------------------------------------

//=============================================================================
// DeformTool
//-----------------------------------------------------------------------------

DragSelectionTool::DeformTool::DeformTool(SelectionTool *tool)
    : DragTool(tool)
    , m_curPos()
    , m_isDragging(false)
    , m_startScaleValue(tool->m_deformValues.m_scaleValue) {}

//-----------------------------------------------------------------------------

int DragSelectionTool::DeformTool::getSymmetricPointIndex(int index) const {
  if (index == 0 || index == 4 || index == 1 || index == 5) return index + 2;
  return index - 2;
}

//-----------------------------------------------------------------------------

int DragSelectionTool::DeformTool::getBeforePointIndex(int index) const {
  if (index < 4) return (index == 0) ? 7 : index + 3;
  return index - 4;
}

//-----------------------------------------------------------------------------

int DragSelectionTool::DeformTool::getNextPointIndex(int index) const {
  if (index < 4) return index + 4;
  return (index == 7) ? 0 : index - 3;
}

//-----------------------------------------------------------------------------

int DragSelectionTool::DeformTool::getBeforeVertexIndex(int index) const {
  if (index < 4) return (index == 0) ? 3 : index - 1;
  return index - 4;
}

//-----------------------------------------------------------------------------

int DragSelectionTool::DeformTool::getNextVertexIndex(int index) const {
  if (index < 4) return (index == 3) ? 0 : index + 1;
  return (index == 7) ? 0 : index - 3;
}

//-----------------------------------------------------------------------------

void DragSelectionTool::DeformTool::leftButtonDown(const TPointD &pos,
                                                   const TMouseEvent &e) {
  m_isDragging = true;
  m_curPos     = pos;
  setStartPos(pos);
}

//-----------------------------------------------------------------------------

void DragSelectionTool::DeformTool::leftButtonUp(const TPointD &pos,
                                                 const TMouseEvent &e) {
  addTransformUndo();
  m_isDragging = false;
}

//=============================================================================
// Rotation
//-----------------------------------------------------------------------------

DragSelectionTool::Rotation::Rotation(DeformTool *deformTool)
    : m_curAng(), m_dstAng(), m_deformTool(deformTool) {}

//-----------------------------------------------------------------------------

TPointD DragSelectionTool::Rotation::getStartCenter() const {
  return m_deformTool->getTool()->getCenter();
}

//-----------------------------------------------------------------------------

void DragSelectionTool::Rotation::leftButtonDrag(const TPointD &pos,
                                                 const TMouseEvent &e) {
  SelectionTool *tool  = m_deformTool->getTool();
  TPointD center       = tool->getCenter();
  TPointD curPos       = m_deformTool->getCurPos();
  TPointD delta        = pos - curPos;
  TPointD a            = pos - center;
  TPointD b            = (pos - delta) - center;
  double a2            = norm2(a);
  double b2            = norm2(b);
  const double epsilon = 1e-8;
  double dang          = 0;
  double scale         = 1;
  if (a2 <= epsilon || b2 <= epsilon) return;
  dang = asin(cross(a, b) / sqrt(a2 * b2)) * -M_180_PI;
  if (e.isShiftPressed()) {
    m_dstAng += dang;
    double ang = tfloor((int)(m_dstAng + 22.5), 45);
    dang       = ang - m_curAng;
    m_curAng   = ang;
  } else {
    m_dstAng += dang;
    dang     = m_dstAng - m_curAng;
    m_curAng = m_dstAng;
  }

  tool->m_deformValues.m_rotationAngle =
      tool->m_deformValues.m_rotationAngle + dang;
  m_deformTool->transform(TRotation(center, dang), dang);
  m_deformTool->setCurPos(pos);
  TTool::getApplication()->getCurrentTool()->notifyToolChanged();
}

//-----------------------------------------------------------------------------

void DragSelectionTool::Rotation::draw() {
  tglDrawSegment(m_deformTool->getCurPos(),
                 m_deformTool->getTool()->getCenter());
}

//=============================================================================
// FreeDeform
//-----------------------------------------------------------------------------

DragSelectionTool::FreeDeform::FreeDeform(DeformTool *deformTool)
    : m_deformTool(deformTool) {}

//-----------------------------------------------------------------------------

void DragSelectionTool::FreeDeform::leftButtonDrag(const TPointD &pos,
                                                   const TMouseEvent &e) {
  SelectionTool *tool = m_deformTool->getTool();
  TPointD delta       = pos - m_deformTool->getCurPos();
  double pixelSize    = tool->getPixelSize();
  bool isFastDragging = norm2(delta) > 9.0 * pixelSize * pixelSize;

  TPointD center     = tool->getCenter();
  int index          = tool->getSelectedPoint();
  FourPoints bbox    = tool->getBBox();
  FourPoints newBbox = bbox;
  if (index < 4)
    bbox.setPoint(index, bbox.getPoint(index) + delta);
  else {
    int beforeIndex = m_deformTool->getBeforeVertexIndex(index);
    bbox.setPoint(beforeIndex, bbox.getPoint(beforeIndex) + delta);
    bbox.setPoint(index, bbox.getPoint(index) + delta);
    int nextIndex = m_deformTool->getNextVertexIndex(index);
    bbox.setPoint(nextIndex, bbox.getPoint(nextIndex) + delta);
  }
  tool->setBBox(bbox);
  m_deformTool->setCurPos(pos);
  m_deformTool->applyTransform(bbox, isFastDragging);
}

//-----------------------------------------------------------------------------

void DragSelectionTool::FreeDeform::leftButtonUp() {
  m_deformTool->applyTransform(m_deformTool->getTool()->getBBox());
}

//=============================================================================
// MoveSelection
//-----------------------------------------------------------------------------

DragSelectionTool::MoveSelection::MoveSelection(DeformTool *deformTool)
    : m_deformTool(deformTool), m_lastDelta(), m_firstPos() {}

//-----------------------------------------------------------------------------

void DragSelectionTool::MoveSelection::leftButtonDown(const TPointD &pos,
                                                      const TMouseEvent &e) {
  m_deformTool->setCurPos(pos);
  m_firstPos = pos;
}

//-----------------------------------------------------------------------------

void DragSelectionTool::MoveSelection::leftButtonDrag(const TPointD &pos,
                                                      const TMouseEvent &e) {
  TAffine aff;
  TPointD curPos = m_deformTool->getCurPos();
  TPointD delta  = pos - curPos;
  if (e.isShiftPressed()) {
    if (m_lastDelta == TPointD()) {
      TPointD totalDelta = curPos - m_firstPos;
      aff                = TTranslation(totalDelta).inv();
    } else
      aff = TTranslation(m_lastDelta).inv();
    if (fabs((curPos - m_firstPos).x) > fabs((curPos - m_firstPos).y))
      m_lastDelta = TPointD((curPos - m_firstPos).x, 0);
    else
      m_lastDelta = TPointD(0, (curPos - m_firstPos).y);
    aff *= TTranslation(m_lastDelta);
  } else
    aff = TTranslation(delta);
  double factor = 1.0 / Stage::inch;
  m_deformTool->getTool()->m_deformValues.m_moveValue =
      m_deformTool->getTool()->m_deformValues.m_moveValue + factor * delta;
  m_deformTool->transform(aff);
  m_deformTool->setCurPos(pos);
  TTool::getApplication()->getCurrentTool()->notifyToolChanged();
}

//=============================================================================
// Scale
//-----------------------------------------------------------------------------

DragSelectionTool::Scale::Scale(DeformTool *deformTool, ScaleType type)
    : m_deformTool(deformTool)
    , m_startCenter(deformTool->getTool()->getCenter())
    , m_type(type)
    , m_isShiftPressed(false)
    , m_isAltPressed(false)
    , m_scaleInCenter(true) {
  int i;
  for (i = 0; i < (int)m_deformTool->getTool()->getBBoxsCount(); i++)
    m_startBboxs.push_back(m_deformTool->getTool()->getBBox(i));
}

//-----------------------------------------------------------------------------

TPointD DragSelectionTool::Scale::getIntersectionPoint(const TPointD &point0,
                                                       const TPointD &point1,
                                                       const TPointD &point2,
                                                       const TPointD &point3,
                                                       const TPointD &p) const {
  // Parametri della retta passante per point0, point1
  double d1x = point0.x - point1.x;
  double m1  = d1x == 0 ? 0 : (point0.y - point1.y) / d1x;
  double q1  = point1.y - m1 * point1.x;
  // Parametri della retta passante per p parallela alla retta passante per
  // point1, point2
  double d2x = point2.x - point3.x;
  double m2  = d2x == 0 ? 0 : (point2.y - point3.y) / d2x;
  double q2  = p.y - m2 * p.x;
  // Calcolo l'intersezione tra le due rette
  double x, y, m, q;
  if (d1x == 0) {
    x = point0.x;
    m = m2;
    q = q2;
  } else if (d2x == 0) {
    x = p.x;
    m = m1;
    q = q1;
  } else {
    assert(m1 != m2);
    x = (q1 - q2) / (m2 - m1);
    m = m1;
    q = q1;
  }
  y = m * x + q;
  return TPointD(x, y);
}

//-----------------------------------------------------------------------------

DragSelectionTool::FourPoints DragSelectionTool::Scale::bboxScale(
    int index, const FourPoints &oldBbox, const TPointD &pos) {
  FourPoints bbox = oldBbox;
  TPointD p       = oldBbox.getPoint(index);
  int nextIndex   = m_deformTool->getNextVertexIndex(index);
  TPointD nextP   = oldBbox.getPoint(nextIndex);
  int nextIndex2  = m_deformTool->getNextVertexIndex(nextIndex);
  TPointD next2P  = oldBbox.getPoint(nextIndex2);
  TPointD newP    = getIntersectionPoint(next2P, nextP, nextP, p, pos);
  bbox.setPoint(nextIndex, newP);

  int beforeIndex  = m_deformTool->getBeforeVertexIndex(index);
  TPointD beforeP  = oldBbox.getPoint(beforeIndex);
  int before2Index = m_deformTool->getBeforeVertexIndex(beforeIndex);
  TPointD before2P = oldBbox.getPoint(before2Index);
  newP             = getIntersectionPoint(before2P, beforeP, beforeP, p, pos);
  bbox.setPoint(beforeIndex, newP);

  if (index < 4) bbox.setPoint(index, pos);

  return bbox;
}

//-----------------------------------------------------------------------------

TPointD DragSelectionTool::Scale::computeScaleValue(int movedIndex,
                                                    const FourPoints newBbox) {
  TPointD p = m_startBboxs[0].getPoint(movedIndex);
  if (movedIndex < 4) {
    int beforeIndex = m_deformTool->getBeforePointIndex(movedIndex);
    int nextIndex   = m_deformTool->getNextPointIndex(movedIndex);
    FourPoints bbox = bboxScale(nextIndex, newBbox, p);
    TPointD scale1  = computeScaleValue(beforeIndex, bbox);
    bbox            = bboxScale(beforeIndex, newBbox, p);
    TPointD scale2  = computeScaleValue(nextIndex, bbox);
    if (movedIndex % 2 == 0)
      return TPointD(scale1.x, scale2.y);
    else
      return TPointD(scale2.x, scale1.y);
  }
  int symmetricIndex = m_deformTool->getSymmetricPointIndex(movedIndex);
  TPointD s          = m_startBboxs[0].getPoint(symmetricIndex);
  TPointD center     = m_scaleInCenter ? m_startCenter : s;
  TPointD nearP =
      m_startBboxs[0].getPoint(m_deformTool->getBeforePointIndex(movedIndex));
  TPointD pc   = getIntersectionPoint(nearP, p, p, s, center);
  TPointD newp = newBbox.getPoint(movedIndex);
  TPointD news = newBbox.getPoint(symmetricIndex);
  TPointD newNearP =
      newBbox.getPoint(m_deformTool->getBeforePointIndex(movedIndex));
  TPointD newpc = getIntersectionPoint(newNearP, newp, newp, news, center);

  double newD             = tdistance2(newpc, center);
  double oldD             = tdistance2(pc, center);
  double f                = sqrt(newD / oldD) - 1;
  TPointD startScaleValue = m_deformTool->getStartScaleValue();
  if (movedIndex % 2 == 1) {
    double sign = (pc.x < center.x && newpc.x < center.x) ||
                          (pc.x > center.x && newpc.x > center.x)
                      ? 1
                      : -1;
    double x =
        startScaleValue.x == 0 ? f : startScaleValue.x + startScaleValue.x * f;
    return TPointD(sign * x, startScaleValue.y);
  } else {
    double sign = (pc.y < center.y && newpc.y < center.y) ||
                          (pc.y > center.y && newpc.y > center.y)
                      ? 1
                      : -1;
    double y =
        startScaleValue.y == 0 ? f : startScaleValue.y + startScaleValue.y * f;
    return TPointD(startScaleValue.x, sign * y);
  }
}

//-----------------------------------------------------------------------------

TPointD DragSelectionTool::Scale::getScaledPoint(int index,
                                                 const FourPoints &oldBbox,
                                                 const TPointD scaleValue,
                                                 const TPointD center) {
  TPointD p          = oldBbox.getPoint(index);
  int symmetricIndex = m_deformTool->getSymmetricPointIndex(index);
  TPointD s          = oldBbox.getPoint(symmetricIndex);
  if (index < 4) {
    int beforeIndex = m_deformTool->getBeforePointIndex(index);
    int nextIndex   = m_deformTool->getNextPointIndex(index);
    TPointD newbp   = getScaledPoint(beforeIndex, oldBbox, scaleValue, center);
    TPointD newnp   = getScaledPoint(nextIndex, oldBbox, scaleValue, center);
    TPointD bp = oldBbox.getPoint(m_deformTool->getBeforePointIndex(index));
    TPointD np = oldBbox.getPoint(m_deformTool->getNextPointIndex(index));
    TPointD in = getIntersectionPoint(np, p, bp, p, newbp);
    return getIntersectionPoint(newbp, in, np, p, newnp);
  }
  TPointD nearP = oldBbox.getPoint(m_deformTool->getBeforePointIndex(index));
  TPointD nearS =
      oldBbox.getPoint(m_deformTool->getBeforePointIndex(symmetricIndex));
  TPointD pc = getIntersectionPoint(nearP, p, p, s, center);
  TPointD sc = getIntersectionPoint(nearS, s, p, s, center);
  if (center == pc) return pc;
  TPointD v       = normalize(center - pc);
  double currentD = tdistance(sc, pc);
  double startD   = (index % 2 == 1)
                      ? currentD / m_deformTool->getStartScaleValue().x
                      : currentD / m_deformTool->getStartScaleValue().y;
  double factor = (index % 2 == 1) ? scaleValue.x : scaleValue.y;
  double d = (currentD - startD * factor) * tdistance(center, pc) / currentD;
  return TPointD(pc.x + d * v.x, pc.y + d * v.y);
}

//-----------------------------------------------------------------------------

TPointD DragSelectionTool::Scale::getNewCenter(int index, const FourPoints bbox,
                                               const TPointD scaleValue) {
  int xIndex, yIndex;
  if (index < 4) {
    xIndex = m_deformTool->getBeforePointIndex(index);
    yIndex = m_deformTool->getNextPointIndex(index);
  } else {
    xIndex =
        m_deformTool->getNextPointIndex(m_deformTool->getNextPointIndex(index));
    yIndex = index;
  }
  if (index % 2 == 1) std::swap(xIndex, yIndex);
  FourPoints xBbox = bboxScale(xIndex, bbox, m_startCenter);
  TPointD xCenter  = getScaledPoint(
      xIndex, xBbox, scaleValue,
      xBbox.getPoint(m_deformTool->getSymmetricPointIndex(xIndex)));
  FourPoints yBbox = bboxScale(yIndex, bbox, m_startCenter);
  TPointD yCenter  = getScaledPoint(
      yIndex, yBbox, scaleValue,
      yBbox.getPoint(m_deformTool->getSymmetricPointIndex(yIndex)));
  TPointD in = getIntersectionPoint(bbox.getP00(), bbox.getP10(), bbox.getP10(),
                                    bbox.getP11(), xCenter);
  return getIntersectionPoint(in, xCenter, bbox.getP00(), bbox.getP10(),
                              yCenter);
}

//-----------------------------------------------------------------------------

FourPoints DragSelectionTool::Scale::bboxScaleInCenter(
    int index, const FourPoints &oldBbox, const TPointD newPos,
    TPointD &scaleValue, const TPointD center, bool recomputeScaleValue) {
  TPointD oldp = oldBbox.getPoint(index);
  if (areAlmostEqual(oldp.x, newPos.x, 1e-2) &&
      areAlmostEqual(oldp.y, newPos.y, 1e-2))
    return oldBbox;
  FourPoints bbox = bboxScale(index, oldBbox, newPos);
  if (recomputeScaleValue) scaleValue = computeScaleValue(index, bbox);
  if (!m_scaleInCenter) return bbox;
  int symmetricIndex = m_deformTool->getSymmetricPointIndex(index);
  // Gestisco il caso particolare in cui uno dei fattori di scalatura e' -100% e
  // center e' al centro della bbox
  if (bbox.getPoint(index) == oldBbox.getPoint(symmetricIndex)) {
    bbox.setPoint(symmetricIndex, oldBbox.getPoint(index));
    bbox.setPoint(m_deformTool->getNextPointIndex(symmetricIndex),
                  oldBbox.getPoint(m_deformTool->getBeforePointIndex(index)));
    bbox.setPoint(m_deformTool->getBeforePointIndex(symmetricIndex),
                  oldBbox.getPoint(m_deformTool->getNextPointIndex(index)));
  } else
    bbox =
        bboxScale(symmetricIndex, bbox,
                  getScaledPoint(symmetricIndex, oldBbox, scaleValue, center));
  return bbox;
}

//-----------------------------------------------------------------------------

void DragSelectionTool::Scale::leftButtonDown(const TPointD &pos,
                                              const TMouseEvent &e) {
  m_isShiftPressed = e.isShiftPressed();
  m_isAltPressed   = e.isAltPressed();
}

//-----------------------------------------------------------------------------

void DragSelectionTool::Scale::leftButtonDrag(const TPointD &pos,
                                              const TMouseEvent &e) {
  SelectionTool *tool = m_deformTool->getTool();
  bool isBboxReset    = false;
  if (m_isShiftPressed != e.isShiftPressed() ||
      m_isAltPressed != e.isAltPressed()) {
    m_deformTool->applyTransform(m_startBboxs[0]);
    tool->setBBox(m_startBboxs[0]);
    tool->setCenter(m_startCenter);
    isBboxReset      = true;
    m_isShiftPressed = e.isShiftPressed();
    m_isAltPressed   = e.isAltPressed();
  }
  TPointD newPos    = pos;
  int selectedIndex = tool->getSelectedPoint();
  if (m_isShiftPressed && m_type == ScaleType::GLOBAL) {
    TPointD point = tool->getBBox().getPoint(selectedIndex);
    TPointD delta;
    if (!isBboxReset)
      delta = pos - m_deformTool->getCurPos();
    else
      delta = pos - m_deformTool->getStartPos();
    int symmetricIndex = m_deformTool->getSymmetricPointIndex(selectedIndex);
    TPointD symmetricPoint = tool->getBBox().getPoint(symmetricIndex);
    TPointD v              = normalize(point - symmetricPoint);
    delta                  = v * (v * delta);
    newPos                 = point + delta;
  }
  m_scaleInCenter = m_isAltPressed;

  double pixelSize = tool->getPixelSize();
  bool isFastDragging =
      tdistance2(pos, m_deformTool->getCurPos()) > 9.0 * pixelSize * pixelSize;

  m_deformTool->setCurPos(pos);
  TPointD scaleValue =
      m_deformTool->transform(selectedIndex, newPos, isFastDragging);
  tool->m_deformValues.m_scaleValue = scaleValue;
  TTool::getApplication()->getCurrentTool()->notifyToolChanged();
}

//-----------------------------------------------------------------------------

void DragSelectionTool::Scale::leftButtonUp() {
  SelectionTool *tool = m_deformTool->getTool();
  TPointD newPos      = m_deformTool->getCurPos();
  int selectedIndex   = tool->getSelectedPoint();
  if (m_isShiftPressed && m_type == ScaleType::GLOBAL) {
    newPos = tool->getBBox().getPoint(selectedIndex);
  }
  m_deformTool->transform(selectedIndex, newPos);
  TTool::getApplication()->getCurrentTool()->notifyToolChanged();
}

//=============================================================================
// SelectionTool
//-----------------------------------------------------------------------------

SelectionTool::SelectionTool(int targetType)
    : TTool("T_Selection")
    , m_firstTime(true)
    , m_dragTool(0)
    , m_what(Outside)
    , m_leftButtonMousePressed(false)
    , m_shiftPressed(false)
    , m_selecting(false)
    , m_mousePosition(TPointD())
    , m_stroke(0)
    , m_justSelected(false)
    , m_strokeSelectionType("Type:")
    , m_deformValues()
    , m_cursorId(ToolCursor::CURSOR_ARROW) {
  bind(targetType);
  m_prop.bind(m_strokeSelectionType);

  m_strokeSelectionType.addValue(RECT_SELECTION);
  m_strokeSelectionType.addValue(FREEHAND_SELECTION);
  m_strokeSelectionType.addValue(POLYLINE_SELECTION);
  m_strokeSelectionType.setId("Type");
}

//-----------------------------------------------------------------------------

SelectionTool::~SelectionTool() {
  delete m_dragTool;
  if (m_stroke) {
    delete m_stroke;
    m_stroke = 0;
  }
  if (!m_freeDeformers.empty()) clearPointerContainer(m_freeDeformers);
}

//-----------------------------------------------------------------------------

void SelectionTool::clearDeformers() { clearPointerContainer(m_freeDeformers); }

//-----------------------------------------------------------------------------

TPointD SelectionTool::getCenter(int index) const {
  if (m_centers.empty()) return TPointD();
  assert((int)m_centers.size() > index);
  return m_centers[index];
}

//-----------------------------------------------------------------------------

void SelectionTool::setCenter(const TPointD &center, int index) {
  if (m_centers.empty()) return;
  assert((int)m_centers.size() > index);
  m_centers[index] = center;
}

//-----------------------------------------------------------------------------

int SelectionTool::getBBoxsCount() const { return m_bboxs.size(); }

//-----------------------------------------------------------------------------

DragSelectionTool::FourPoints SelectionTool::getBBox(int index) const {
  if (m_bboxs.empty()) return DragSelectionTool::FourPoints();
  assert((int)m_bboxs.size() > index);
  return m_bboxs[index];
}

//-----------------------------------------------------------------------------

void SelectionTool::setBBox(const DragSelectionTool::FourPoints &points,
                            int index) {
  if (m_bboxs.empty()) return;
  assert((int)m_bboxs.size() > index);
  m_bboxs[index] = points;
}

//-----------------------------------------------------------------------------

FreeDeformer *SelectionTool::getFreeDeformer(int index) const {
  if (m_freeDeformers.empty()) return 0;
  return m_freeDeformers[index];
}

//-----------------------------------------------------------------------------

void SelectionTool::updateTranslation() {
  m_strokeSelectionType.setQStringName(tr("Type:"));
  m_strokeSelectionType.setItemUIName(RECT_SELECTION, tr("Rectangular"));
  m_strokeSelectionType.setItemUIName(FREEHAND_SELECTION, tr("Freehand"));
  m_strokeSelectionType.setItemUIName(POLYLINE_SELECTION, tr("Polyline"));
}
//-----------------------------------------------------------------------------

void SelectionTool::updateAction(TPointD pos, const TMouseEvent &e) {
  TImageP image    = getImage(false);
  TToonzImageP ti  = image;
  TRasterImageP ri = image;
  TVectorImageP vi = image;

  m_what     = Outside;
  m_cursorId = ToolCursor::StrokeSelectCursor;

  if (!ti && !vi && !ri) return;

  bool shift = e.isShiftPressed();

  if (shift && !m_leftButtonMousePressed && isModifiableSelectionType()) {
    m_what     = ADD_SELECTION;
    m_cursorId = ToolCursor::SplineEditorCursorAdd;
  } else if (m_leftButtonMousePressed)
    return;

  if (!isSelectionEditable()) return;

  FourPoints bbox = getBBox();

  double pixelSize = getPixelSize();
  if (!bbox.isEmpty()) {
    double maxDist  = 17 * pixelSize;
    double maxDist2 = maxDist * maxDist;
    double p        = (15 * pixelSize);
    m_selectedPoint = NONE;
    if (tdistance2(bbox.getP00(), pos) < maxDist2 + p)
      m_selectedPoint = P00;
    else if (tdistance2(bbox.getP11(), pos) < maxDist2 + p)
      m_selectedPoint = P11;
    else if (tdistance2(bbox.getP01(), pos) < maxDist2 + p)
      m_selectedPoint = P01;
    else if (tdistance2(bbox.getP10(), pos) < maxDist2 + p)
      m_selectedPoint = P10;

    if (tdistance2(bbox.getBottomLeft() + TPointD(-p, -p), pos) < maxDist2) {
      m_what     = ROTATION;
      m_cursorId = ToolCursor::RotBottomLeft;
      return;
    } else if (tdistance2(bbox.getBottomRight() + TPointD(p, -p), pos) <
               maxDist2) {
      m_what     = ROTATION;
      m_cursorId = ToolCursor::RotBottomRight;
      return;
    } else if (tdistance2(bbox.getTopRight() + TPointD(p, p), pos) < maxDist2) {
      m_what     = ROTATION;
      m_cursorId = ToolCursor::RotCursor;
      return;
    } else if (tdistance2(bbox.getTopLeft() + TPointD(-p, p), pos) < maxDist2) {
      m_what     = ROTATION;
      m_cursorId = ToolCursor::RotTopLeft;
      return;
    }
    maxDist  = 5 * pixelSize;
    maxDist2 = maxDist * maxDist;
    if (!isLevelType() && !isSelectedFramesType() &&
        tdistance2(getCenter(), pos) < maxDist2) {
      m_what     = MOVE_CENTER;
      m_cursorId = ToolCursor::PointingHandCursor;
      return;
    }
    if (tdistance2(bbox.getP00(), pos) < maxDist2 ||
        tdistance2(bbox.getP11(), pos) < maxDist2 ||
        tdistance2(bbox.getP01(), pos) < maxDist2 ||
        tdistance2(bbox.getP10(), pos) < maxDist2) {
      if (!e.isCtrlPressed() || isLevelType() || isSelectedFramesType()) {
        m_what = SCALE;
        if (tdistance2(bbox.getTopRight(), pos) < maxDist2 ||
            tdistance2(bbox.getBottomLeft(), pos) < maxDist2)
          m_cursorId = ToolCursor::ScaleCursor;
        else
          m_cursorId = ToolCursor::ScaleInvCursor;
      } else {
        m_cursorId = ToolCursor::DistortCursor;
        m_what     = DEFORM;
      }
      return;
    }
    if (isCloseToSegment(pos, TSegment(bbox.getPoint(0), bbox.getPoint(3)),
                         maxDist))
      m_selectedPoint = P0M;
    else if (isCloseToSegment(pos, TSegment(bbox.getPoint(1), bbox.getPoint(2)),
                              maxDist))
      m_selectedPoint = P1M;
    else if (isCloseToSegment(pos, TSegment(bbox.getPoint(3), bbox.getPoint(2)),
                              maxDist))
      m_selectedPoint = PM1;
    else if (isCloseToSegment(pos, TSegment(bbox.getPoint(0), bbox.getPoint(1)),
                              maxDist))
      m_selectedPoint = PM0;
    if (m_selectedPoint == P0M || m_selectedPoint == P1M) {
      if (!e.isCtrlPressed() || isLevelType() || isSelectedFramesType()) {
        m_cursorId = ToolCursor::ScaleHCursor;
        m_what     = SCALE_X;
      } else {
        m_cursorId = ToolCursor::DistortCursor;
        m_what     = DEFORM;
      }
      return;
    }
    if (m_selectedPoint == PM1 || m_selectedPoint == PM0) {
      if (!e.isCtrlPressed() || isLevelType() || isSelectedFramesType()) {
        m_cursorId = ToolCursor::ScaleVCursor;
        m_what     = SCALE_Y;
      } else {
        m_cursorId = ToolCursor::DistortCursor;
        m_what     = DEFORM;
      }
      return;
    }
    TPointD hpos = bbox.getP10() - TPointD(14 * pixelSize, 15 * pixelSize);
    TRectD rect(hpos - TPointD(14 * pixelSize, 5 * pixelSize),
                hpos + TPointD(14 * pixelSize, 5 * pixelSize));
    if (!m_deformValues.m_isSelectionModified && rect.contains(pos) && vi &&
        !TTool::getApplication()->getCurrentObject()->isSpline()) {
      m_what     = GLOBAL_THICKNESS;
      m_cursorId = ToolCursor::PumpCursor;
      return;
    }
  }
  m_selectedPoint = NONE;
  if ((isLevelType() || isSelectedFramesType()) && !isSameStyleType()) {
    m_what     = Inside;
    m_cursorId = ToolCursor::LevelSelectCursor;
  }

  if (shift) return;
  if (!vi && bbox.contains(pos)) {
    m_what = Inside;
    if (isLevelType() || isSelectedFramesType())
      m_cursorId = ToolCursor::LevelSelectCursor;
    else
      m_cursorId = ToolCursor::MoveCursor;
  }
}

//-----------------------------------------------------------------------------

void SelectionTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) {
  TImageP image = getImage(false);
  if (!image) return;
  if (m_polyline.size() == 0) {
    modifySelectionOnClick(image, pos, e);

    if (m_what == ROTATION) m_dragTool = createNewRotationTool(this);
    if (!e.isShiftPressed() && m_what == Inside)
      m_dragTool = createNewMoveSelectionTool(this);
    else if (m_what == MOVE_CENTER)
      m_dragTool = new MoveCenterTool(this);
    else if (m_what == SCALE)
      m_dragTool = createNewScaleTool(this, ScaleType::GLOBAL);
    else if (m_what == SCALE_X)
      m_dragTool = createNewScaleTool(this, ScaleType::HORIZONTAL);
    else if (m_what == SCALE_Y)
      m_dragTool = createNewScaleTool(this, ScaleType::VERTICAL);
    else if (m_what == DEFORM)
      m_dragTool = createNewFreeDeformTool(this);
    else if (m_what == GLOBAL_THICKNESS)
      m_dragTool = new VectorChangeThicknessTool((VectorSelectionTool *)this);
    if (m_dragTool) m_dragTool->leftButtonDown(pos, e);
  } else
    m_selecting = true;
  if (m_selecting) {
    if (m_stroke) {
      delete m_stroke;
      m_stroke = 0;
    }
    if (m_strokeSelectionType.getValue() == FREEHAND_SELECTION)
      startFreehand(pos);
    if (m_strokeSelectionType.getValue() == POLYLINE_SELECTION)
      addPointPolyline(pos);
    else if (m_polyline.size() != 0)
      m_polyline.clear();
  }
  m_firstPos = m_curPos    = pos;
  m_leftButtonMousePressed = true;
  m_shiftPressed           = e.isShiftPressed();
}

//-----------------------------------------------------------------------------

void SelectionTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
  updateAction(pos, e);

  if (m_strokeSelectionType.getValue() == POLYLINE_SELECTION) {
    m_mousePosition = pos;
    invalidate();
  }
}

//-----------------------------------------------------------------------------

bool SelectionTool::keyDown(QKeyEvent *event) {
  if (isSelectionEmpty()) return false;

  TPointD delta;

  switch (event->key()) {
  case Qt::Key_Up:
    delta.y = 1;
    break;
  case Qt::Key_Down:
    delta.y = -1;
    break;
  case Qt::Key_Left:
    delta.x = -1;
    break;
  case Qt::Key_Right:
    delta.x = 1;
    break;
  default:
    return false;
    break;
  }

  if (event->modifiers() & Qt::ShiftModifier) {
    delta.x *= 10.0;
    delta.y *= 10.0;
  } else if (event->modifiers() & Qt::ControlModifier) {
    delta.x *= 0.1;
    delta.y *= 0.1;
  }

  TImageP image = getImage(true);

  TToonzImageP ti  = (TToonzImageP)image;
  TRasterImageP ri = (TRasterImageP)image;
  TVectorImageP vi = (TVectorImageP)image;

  if (!ti && !vi && !ri) return false;

  std::unique_ptr<DragTool> dragTool(createNewMoveSelectionTool(this));
  TAffine aff = TTranslation(delta);
  dragTool->transform(aff);
  double factor = 1.0 / Stage::inch;
  m_deformValues.m_moveValue += factor * delta;
  dragTool->addTransformUndo();
  TTool::getApplication()->getCurrentTool()->notifyToolChanged();

  invalidate();
  return true;
}

//-----------------------------------------------------------------------------

int SelectionTool::getCursorId() const {
  TImageP image    = getImage(false);
  TToonzImageP ti  = (TToonzImageP)image;
  TRasterImageP ri = (TRasterImageP)image;
  TVectorImageP vi = (TVectorImageP)image;

  if (!ti && !vi && !ri) return ToolCursor::StrokeSelectCursor;

  return m_cursorId;
}

//-----------------------------------------------------------------------------

void SelectionTool::drawPolylineSelection() {
  if (m_polyline.empty()) return;
  TPixel color = ToonzCheck::instance()->getChecks() & ToonzCheck::eBlackBg
                     ? TPixel32::White
                     : TPixel32::Black;
  tglColor(color);
  tglDrawCircle(m_polyline[0], 2);
  glBegin(GL_LINE_STRIP);
  for (UINT i = 0; i < m_polyline.size(); i++) tglVertex(m_polyline[i]);
  tglVertex(m_mousePosition);
  glEnd();
}

//-----------------------------------------------------------------------------

void SelectionTool::drawFreehandSelection() {
  if (m_track.isEmpty()) return;
  TPixel color = ToonzCheck::instance()->getChecks() & ToonzCheck::eBlackBg
                     ? TPixel32::White
                     : TPixel32::Black;
  tglColor(color);
  m_track.drawAllFragments();
}

//-----------------------------------------------------------------------------

void SelectionTool::drawRectSelection(const TImage *image) {
  const TVectorImage *vi   = dynamic_cast<const TVectorImage *>(image);
  unsigned short stipple   = 0x3F33;
  FourPoints selectingRect = m_selectingRect;
  if (vi && m_curPos.x >= m_firstPos.x) stipple = 0xFF00;
  drawFourPoints(selectingRect, TPixel32::Black, stipple, true);
}

//-----------------------------------------------------------------------------

void SelectionTool::drawCommandHandle(const TImage *image) {
  const TVectorImage *vi = dynamic_cast<const TVectorImage *>(image);
  TPixel32 frameColor(210, 210, 210);
  TPixel32 frameColor2(0, 0, 0);
  FourPoints rect = getBBox();

  drawFourPoints(rect, frameColor, 0xffff, true);

  tglColor(frameColor);

  if (m_dragTool) m_dragTool->draw();

  if (!isSelectionEditable()) return;

  double pixelSize = getPixelSize();
  if (!isLevelType() && !isSelectedFramesType()) {
    TPointD c = getCenter() + TPointD(-pixelSize, +pixelSize);

    tglColor(frameColor);
    tglDrawCircle(c, pixelSize * 5);
    tglDrawSegment(c - TPointD(pixelSize * 15, 0),
                   c + TPointD(pixelSize * 15, 0));
    tglDrawSegment(c - TPointD(0, pixelSize * 15),
                   c + TPointD(0, pixelSize * 15));
    tglColor(frameColor2);
    tglDrawCircle(getCenter(), pixelSize * 5);
    tglDrawSegment(getCenter() - TPointD(pixelSize * 15, 0),
                   getCenter() + TPointD(pixelSize * 15, 0));
    tglDrawSegment(getCenter() - TPointD(0, pixelSize * 15),
                   getCenter() + TPointD(0, pixelSize * 15));
  }

  TPointD bl(rect.getP00().x - pixelSize, rect.getP00().y + pixelSize);
  TPointD tl(rect.getP01().x - pixelSize, rect.getP01().y + pixelSize);
  TPointD br(rect.getP10().x - pixelSize, rect.getP10().y + pixelSize);
  TPointD tr(rect.getP11().x - pixelSize, rect.getP11().y + pixelSize);

  drawSquare(bl, pixelSize * 4, frameColor);
  drawSquare(tl, pixelSize * 4, frameColor);
  drawSquare(br, pixelSize * 4, frameColor);
  drawSquare(tr, pixelSize * 4, frameColor);

  drawSquare(rect.getP00(), pixelSize * 4, frameColor2);
  drawSquare(rect.getP01(), pixelSize * 4, frameColor2);
  drawSquare(rect.getP10(), pixelSize * 4, frameColor2);
  drawSquare(rect.getP11(), pixelSize * 4, frameColor2);

  if (vi && !m_deformValues.m_isSelectionModified) {
    TPointD thickCommandPos =
        rect.getP10() - TPointD(14 * pixelSize, 15 * pixelSize);
    drawRectWhitArrow(thickCommandPos, pixelSize);
  }

  drawSquare(0.5 * (br + tr), pixelSize * 4, frameColor);
  drawSquare(0.5 * (tl + tr), pixelSize * 4, frameColor);
  drawSquare(0.5 * (br + bl), pixelSize * 4, frameColor);
  drawSquare(0.5 * (tl + bl), pixelSize * 4, frameColor);

  drawSquare(0.5 * (rect.getP10() + rect.getP11()), pixelSize * 4, frameColor2);
  drawSquare(0.5 * (rect.getP01() + rect.getP11()), pixelSize * 4, frameColor2);
  drawSquare(0.5 * (rect.getP10() + rect.getP00()), pixelSize * 4, frameColor2);
  drawSquare(0.5 * (rect.getP01() + rect.getP00()), pixelSize * 4, frameColor2);
}

//-----------------------------------------------------------------------------

void SelectionTool::onActivate() {
  if (m_firstTime) {
    m_strokeSelectionType.setValue(::to_wstring(SelectionType.getValue()));
    m_firstTime = false;
  }
  if (isLevelType() || isSelectedFramesType()) return;

  doOnActivate();
}

//-----------------------------------------------------------------------------

void SelectionTool::onDeactivate() {
  if (isLevelType() || isSelectedFramesType()) return;

  doOnDeactivate();
}

//-----------------------------------------------------------------------------

void SelectionTool::onSelectionChanged() {
  computeBBox();
  invalidate();
  m_polyline.clear();
}

//-----------------------------------------------------------------------------

bool SelectionTool::onPropertyChanged(std::string propertyName) {
  if (propertyName == m_strokeSelectionType.getName()) {
    SelectionType = ::to_string(m_strokeSelectionType.getValue());
    return true;
  }
  return false;
}

//-----------------------------------------------------------------------------

//! Viene aggiunto \b pos a \b m_track e disegnato il primo pezzetto del lazzo.
//! Viene inizializzato \b m_firstPos
void SelectionTool::startFreehand(const TPointD &pos) {
  m_track.clear();
  m_firstPos       = pos;
  double pixelSize = getPixelSize();
  m_track.add(TThickPoint(pos, 0), pixelSize * pixelSize);
}

//-----------------------------------------------------------------------------

//! Viene aggiunto \b pos a \b m_track e disegnato un altro pezzetto del lazzo.
void SelectionTool::freehandDrag(const TPointD &pos) {
  double pixelSize = getPixelSize();
  m_track.add(TThickPoint(pos, 0), pixelSize * pixelSize);
}

//-----------------------------------------------------------------------------

//! Viene chiuso il lazzo (si aggiunge l'ultimo punto ad m_track) e viene creato
//! lo stroke rappresentante il lazzo.
void SelectionTool::closeFreehand(const TPointD &pos) {
  if (m_track.isEmpty()) return;
  double pixelSize = getPixelSize();
  m_track.add(TThickPoint(m_firstPos, 0), pixelSize * pixelSize);
  m_track.filterPoints();
  double error = (30.0 / 11) * pixelSize;
  m_stroke     = m_track.makeStroke(error);
  m_stroke->setStyle(1);
}

//-----------------------------------------------------------------------------

//! Viene aggiunto un punto al vettore m_polyline.
void SelectionTool::addPointPolyline(const TPointD &pos) {
  m_firstPos      = pos;
  m_mousePosition = pos;
  m_polyline.push_back(pos);
}

//-----------------------------------------------------------------------------

//! Agginge l'ultimo pos a \b m_polyline e chiude la spezzata (aggiunge \b
//! m_polyline.front() alla fine del vettore).
void SelectionTool::closePolyline(const TPointD &pos) {
  if (m_polyline.size() <= 1) return;
  if (m_polyline.back() != pos) m_polyline.push_back(pos);
  if (m_polyline.back() != m_polyline.front())
    m_polyline.push_back(m_polyline.front());

  std::vector<TThickPoint> strokePoints;
  for (UINT i = 0; i < m_polyline.size() - 1; i++) {
    strokePoints.push_back(TThickPoint(m_polyline[i], 0));
    strokePoints.push_back(
        TThickPoint(0.5 * (m_polyline[i] + m_polyline[i + 1]), 0));
  }
  strokePoints.push_back(TThickPoint(m_polyline.back(), 0));
  m_polyline.clear();
  m_stroke = new TStroke(strokePoints);
  assert(m_stroke->getPoint(0) == m_stroke->getPoint(1));
  invalidate();
}

//-----------------------------------------------------------------------------

// returns true if the pressed key is recognized and processed in the tool
// instead of triggering the shortcut command.
bool SelectionTool::isEventAcceptable(QEvent *e) {
  if (!isEnabled()) return false;
  if (isSelectionEmpty()) return false;
  // arrow keys will be used for moving the selected region
  QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
  int key             = keyEvent->key();
  return (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left ||
          key == Qt::Key_Right);
}
