Blob Blame Raw


#include "tundo.h"
#include "tthreadmessage.h"
#include "tvectorimage.h"
#include "drawutil.h"
#include "controlpointselection.h"
#include "tproperty.h"
#include "tenv.h"

#include "tools/tool.h"
#include "tools/toolutils.h"
#include "tools/cursors.h"
#include "tools/toolhandle.h"

#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/strokegenerator.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tobjecthandle.h"
#include "toonz/stage2.h"
#include "toonz/tstageobject.h"

// For Qt translation support
#include <QCoreApplication>
#include <QKeyEvent>

using namespace ToolUtils;

TEnv::StringVar CPSelectionType("ControlPointEditorToolSelectionType",
                                "Rectangular");
TEnv::IntVar AutoSelectDrawing("ControlPointEditorToolAutoSelectDrawing", 1);
TEnv::IntVar Snap("ControlPointEditorToolSnap", 0);
TEnv::IntVar SnapSensitivity("ControlPointEditorToolSnapSensitivity", 0);

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

#define LOW_WSTR L"Low"
#define MEDIUM_WSTR L"Medium"
#define HIGH_WSTR L"High"

#define RECTANGULAR_WSTR L"Rectangular"
#define FREEHAND_WSTR L"Freehand"

const double SNAPPING_LOW    = 5.0;
const double SNAPPING_MEDIUM = 25.0;
const double SNAPPING_HIGH   = 100.0;

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

/*! Restituisce i parametri riferiti allo stroke della curva che si vuole
   muovere.
    I parametri dipendono da come sono i punti in \b beforeIndex, \b nextIndex
   (cuspidi, lineari).
    Puo' restituire due range nei casi in cui lo stroke e' selfLoop e la curva
   e' a cavallo del punto di chiusura.
*/
void getSegmentParameter(ControlPointEditorStroke *cpEditor, int beforeIndex,
                         int nextIndex, double &w0, double &w1, double &q0,
                         double &q1) {
  TStroke *stroke = cpEditor->getStroke();
  if (!stroke) return;
  q0 = q1 = w0 = w1 = -1;
  int cpCount       = cpEditor->getControlPointCount();
  // Il punto di controllo precedente non e' lincato
  if (cpEditor->isSpeedOutLinear(beforeIndex) ||
      cpEditor->isSpeedInLinear(beforeIndex) || cpEditor->isCusp(beforeIndex)) {
    if (cpEditor->isSelfLoop() && beforeIndex == 0 &&
        nextIndex == cpCount - 1)  // Nel caso selfLoop si invertono i valori
      w1 = 1;
    else
      w0 = stroke->getParameterAtControlPoint(
          cpEditor->getIndexPointInStroke(beforeIndex));
  } else  // Punto di controllo precedente lincato
  {
    if (!cpEditor->isSelfLoop() || beforeIndex != 0)
      w0 = stroke->getParameterAtControlPoint(
          cpEditor->getIndexPointInStroke(beforeIndex) - 4);
    else {
      if (nextIndex == 1)  // Primo chunk
      {
        w0 = 0;
        q0 = stroke->getParameterAtControlPoint(
            cpEditor->getIndexPointInStroke(cpCount - 1));
        q1 = 1;
      } else if (nextIndex == cpCount - 1)  // Ultimo chunk
      {
        w1 = 1;
        q0 = 0;
        q1 = stroke->getParameterAtControlPoint(
            cpEditor->getIndexPointInStroke(1));
      } else {
        assert(0);
      }  // Non dovrebbe mai accadere
    }
  }
  // Il punto di controllo successivo non e' lincato
  if (cpEditor->isSpeedInLinear(nextIndex) ||
      cpEditor->isSpeedOutLinear(nextIndex) || cpEditor->isCusp(nextIndex)) {
    if (cpEditor->isSelfLoop() && beforeIndex == 0 &&
        nextIndex == cpCount - 1)  // Nel caso selfLoop si invertono i valori
      w0 = stroke->getParameterAtControlPoint(
          cpEditor->getIndexPointInStroke(nextIndex));
    else
      w1 = stroke->getParameterAtControlPoint(
          cpEditor->getIndexPointInStroke(nextIndex));
  } else  // Punto di controllo successivo lincato
  {
    if (!cpEditor->isSelfLoop() || nextIndex != cpCount - 1 || beforeIndex != 0)
      w1 = stroke->getParameterAtControlPoint(
          cpEditor->getIndexPointInStroke(nextIndex) + 4);
    else if (nextIndex == cpCount - 1)  // Ultimo chunk
      w0 = stroke->getParameterAtControlPoint(
          cpEditor->getIndexPointInStroke(nextIndex) - 4);
    else {
      assert(0);
    }  // Non dovrebbe mai accadere, i vari casi dovrebbero essere gestiti sopra
  }
}

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

class ControlPointEditorTool final : public TTool {
  Q_DECLARE_TR_FUNCTIONS(ControlPointEditorTool)

  bool m_draw;
  bool m_isMenuViewed;
  int m_lastPointSelected;
  bool m_isImageChanged;
  ControlPointSelection m_selection;
  ControlPointEditorStroke m_controlPointEditorStroke;
  std::pair<int, int> m_moveSegmentLimitation;  // Indici dei punti di controllo
                                                // che limitano la curva da
                                                // muovere
  ControlPointEditorStroke m_moveControlPointEditorStroke;  // Usate per muovere
                                                            // la curva durante
                                                            // il drag.
  TRectD m_selectingRect;
  TPointD m_pos;

  TPropertyGroup m_prop;
  TEnumProperty m_selectType;

  TBoolProperty
      m_autoSelectDrawing;  // Consente di scegliere se swichare tra i livelli.

  TBoolProperty m_snap;
  TEnumProperty m_snapSensitivity;
  double m_snapMinDistance;
  bool m_foundSnap;
  TPointD m_snapPoint;

  TPointD m_firstPos;       // The first point inserted in m_track
  StrokeGenerator m_track;  // Lazo selection generator.
  TStroke *m_stroke;        // Stores the stroke generated by m_track.

  enum Action {
    NONE,
    RECT_SELECTION,
    FREEHAND_SELECTION,
    CP_MOVEMENT,
    SEGMENT_MOVEMENT,
    IN_SPEED_MOVEMENT,
    OUT_SPEED_MOVEMENT
  };
  Action m_action;

  enum CursorType { NORMAL, ADD, EDIT_SPEED, EDIT_SEGMENT, NO_ACTIVE };
  CursorType m_cursorType;

  TUndo *m_undo;

  void selectRegion(TStroke *stroke);
  void startFreehand(const TPointD &pos);
  void freehandDrag(const TPointD &pos);
  void closeFreehand(const TPointD &pos);

public:
  ControlPointEditorTool();

  ToolType getToolType() const override { return TTool::LevelWriteTool; }

  void updateTranslation() override;

  TPropertyGroup *getProperties(int targetType) override { return &m_prop; }

  // da TSelectionOwner: chiamato quando la selezione corrente viene cambiata
  void onSelectionChanged() { invalidate(); }

  // da TSelectionOwner: chiamato quando si vuole ripristinare una vecchia
  // selezione
  // attualmente non usato
  bool select(const TSelection *) { return false; }
  ControlPointEditorStroke getControlPointEditorStroke() {
    return m_controlPointEditorStroke;
  };

  void initUndo();

  void getNearestStrokeColumnIndexes(std::vector<int> &indexes, TPointD pos);

  void drawMovingSegment();
  void drawControlPoint();
  void draw() override;
  void mouseMove(const TPointD &pos, const TMouseEvent &e) override;
  void leftButtonDown(const TPointD &pos, const TMouseEvent &e) override;
  void rightButtonDown(const TPointD &pos, const TMouseEvent &) override;

  void moveControlPoints(const TPointD &delta);
  void moveSpeed(const TPointD &delta, bool isIn);
  void moveSegment(const TPointD &delta, bool dragging, bool isShiftPressed);

  void leftButtonDrag(const TPointD &pos, const TMouseEvent &e) override;
  void leftButtonUp(const TPointD &pos, const TMouseEvent &e) override;
  void addContextMenuItems(QMenu *menu) override;

  void linkSpeedInOut(int index);
  void unlinkSpeedInOut(int pointIndex);

  bool keyDown(QKeyEvent *event) override;
  void onEnter() override;
  void onLeave() override;
  bool onPropertyChanged(std::string propertyName) override;

  void onActivate() override;
  void onDeactivate() override;
  void onImageChanged() override;
  int getCursorId() const override;

  // returns true if the pressed key is recognized and processed.
  bool isEventAcceptable(QEvent *e) override;

  TPointD calculateSnap(TPointD pos);
  void drawSnap();
  TPointD getSnap(TPointD pos);
  void resetSnap();

} controlPointEditorTool;

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

TPointD ControlPointEditorTool::calculateSnap(TPointD pos) {
  m_foundSnap = false;
  TVectorImageP vi(TTool::getImage(false));
  TPointD snapPoint = pos;
  if (vi && m_snap.getValue()) {
    double minDistance = m_snapMinDistance;

    int i, strokeNumber = vi->getStrokeCount();

    TStroke *selfStroke = m_controlPointEditorStroke.getStroke();
    TStroke *stroke;
    double distance, outW, w;

    for (i = 0; i < strokeNumber; i++) {
      stroke = vi->getStroke(i);
      if (stroke != selfStroke) {
        if (stroke->getNearestW(pos, outW, distance) &&
            distance < minDistance) {
          minDistance = distance;
          if (areAlmostEqual(outW, 0.0, 1e-3))
            w = 0.0;
          else if (areAlmostEqual(outW, 1.0, 1e-3))
            w = 1.0;
          else
            w = outW;
          TThickPoint point = stroke->getPoint(w);
          snapPoint         = TPointD(point.x, point.y);
          m_foundSnap       = true;
          m_snapPoint       = snapPoint;
        }
      }
    }
  }
  return snapPoint;
}

void ControlPointEditorTool::drawSnap() {
  double thick = 6.0;
  if (m_foundSnap) {
    tglColor(TPixelD(0.1, 0.9, 0.1));
    tglDrawCircle(m_snapPoint, thick);
  }
}

TPointD ControlPointEditorTool::getSnap(TPointD pos) {
  if (m_foundSnap)
    return m_snapPoint;
  else
    return pos;
}

void ControlPointEditorTool::resetSnap() { m_foundSnap = false; }

//=============================================================================
// Spline Editor Tool
//-----------------------------------------------------------------------------

ControlPointEditorTool::ControlPointEditorTool()
    : TTool("T_ControlPointEditor")
    , m_draw(false)
    , m_lastPointSelected(-1)
    , m_isImageChanged(false)
    , m_selectingRect(TRectD())
    , m_autoSelectDrawing("Auto Select Drawing", true)
    , m_snap("snap", false)
    , m_snapSensitivity("Sensitivity:")
    , m_selectType("Type:")
    , m_action(NONE)
    , m_cursorType(NORMAL)
    , m_undo(0)
    , m_isMenuViewed(false)
    , m_moveControlPointEditorStroke()
    , m_moveSegmentLimitation() {
  bind(TTool::Vectors);
  m_prop.bind(m_selectType);
  m_prop.bind(m_autoSelectDrawing);
  m_prop.bind(m_snap);
  m_prop.bind(m_snapSensitivity);
  m_selection.setControlPointEditorStroke(&m_controlPointEditorStroke);

  m_selectType.addValue(RECTANGULAR_WSTR);
  m_selectType.addValue(FREEHAND_WSTR);
  m_selectType.setId("Type");

  m_autoSelectDrawing.setId("AutoSelectDrawing");
  m_snap.setId("Snap");
  m_snapSensitivity.addValue(LOW_WSTR);
  m_snapSensitivity.addValue(MEDIUM_WSTR);
  m_snapSensitivity.addValue(HIGH_WSTR);
  m_snapSensitivity.setId("SnapSensitivity");
}

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

void ControlPointEditorTool::updateTranslation() {
  m_autoSelectDrawing.setQStringName(tr("Auto Select Drawing"));
  m_snap.setQStringName(tr("Snap"));
  m_selectType.setQStringName(tr("Type:"));
  m_selectType.setItemUIName(RECTANGULAR_WSTR, tr("Rectangular"));
  m_selectType.setItemUIName(FREEHAND_WSTR, tr("Freehand"));

  m_snapSensitivity.setQStringName(tr(""));
  m_snapSensitivity.setItemUIName(LOW_WSTR, tr("Low"));
  m_snapSensitivity.setItemUIName(MEDIUM_WSTR, tr("Med"));
  m_snapSensitivity.setItemUIName(HIGH_WSTR, tr("High"));
}

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

void ControlPointEditorTool::initUndo() {
  if (TTool::getApplication()->getCurrentObject()->isSpline()) {
    m_undo =
        new UndoPath(getXsheet()->getStageObject(getObjectId())->getSpline());
    return;
  }
  TVectorImageP vi(getImage(false));
  if (!vi) return;
  TXshSimpleLevel *level =
      TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
  UndoControlPointEditor *undo =
      new UndoControlPointEditor(level, getCurrentFid());
  int index = m_controlPointEditorStroke.getStrokeIndex();
  if (index > -1) undo->addOldStroke(index, vi->getVIStroke(index));
  m_undo = undo;
}

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

void ControlPointEditorTool::getNearestStrokeColumnIndexes(
    std::vector<int> &indexes, TPointD pos) {
  TTool::Application *app = TTool::getApplication();
  TXsheet *xsh            = app->getCurrentXsheet()->getXsheet();
  int currentFrame        = app->getCurrentFrame()->getFrameIndex();
  std::vector<int> newIndexes;
  TAffine aff = getMatrix();
  int i       = 0;
  for (i = 0; i < (int)indexes.size(); i++) {
    if (xsh->getColumn(i)->isLocked()) continue;
    int index        = indexes[i];
    TVectorImageP vi = xsh->getCell(currentFrame, index).getImage(false);
    if (!vi) continue;
    double dist2, t = 0;
    UINT strokeIndex = -1;
    TPointD p        = getColumnMatrix(index).inv() * getMatrix() * pos;
    if (vi->getNearestStroke(p, t, strokeIndex, dist2) &&
        dist2 < 25 * getPixelSize() * getPixelSize())
      newIndexes.push_back(index);
  }
  indexes.clear();
  indexes = newIndexes;
}

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

void ControlPointEditorTool::drawMovingSegment() {
  int beforeIndex = m_moveSegmentLimitation.first;
  int nextIndex   = m_moveSegmentLimitation.second;
  if (m_action != SEGMENT_MOVEMENT || beforeIndex == -1 || nextIndex == -1 ||
      !m_moveControlPointEditorStroke.getStroke())
    return;
  tglColor(TPixel::Green);
  double w0, w1;
  double q0, q1;
  getSegmentParameter(&m_moveControlPointEditorStroke, beforeIndex, nextIndex,
                      w0, w1, q0, q1);
  if (w0 != -1 && w1 != -1)  // Dovrebbero essere sempre diversi...
    drawStrokeCenterline(*m_moveControlPointEditorStroke.getStroke(),
                         getPixelSize(), w0, w1);
  if (q0 != -1 && q1 != -1)
    drawStrokeCenterline(*m_moveControlPointEditorStroke.getStroke(),
                         getPixelSize(), q0, q1);
}

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

void ControlPointEditorTool::drawControlPoint() {
  TPixel color1         = TPixel(79, 128, 255);
  TPixel color2         = TPixel::White;
  TPixel color_handle   = TPixel(96, 64, 201);
  int controlPointCount = m_controlPointEditorStroke.getControlPointCount();

  double pix    = getPixelSize() * 2.0f;
  double pix1_5 = 1.5 * pix, pix2 = pix + pix, pix2_5 = pix1_5 + pix,
         pix3 = pix2 + pix, pix3_5 = pix2_5 + pix, pix4 = pix3 + pix;

  double maxDist2 = sq(5.0 * pix);
  double dist2    = 0;
  int pointIndex;
  ControlPointEditorStroke::PointType pointType =
      m_controlPointEditorStroke.getPointTypeAt(m_pos, maxDist2, pointIndex);
  int i;
  for (i = 0; i < controlPointCount; i++) {
    TThickPoint point = m_controlPointEditorStroke.getControlPoint(i);
    TPointD pa        = m_controlPointEditorStroke.getSpeedInPoint(i);
    TPointD pb        = m_controlPointEditorStroke.getSpeedOutPoint(i);
    tglColor(color_handle);
    tglDrawSegment(pa, point);
    if (i == pointIndex && pointType == ControlPointEditorStroke::SPEED_IN)
      tglFillRect(pa.x - pix2_5, pa.y - pix2_5, pa.x + pix2_5, pa.y + pix2_5);
    else
      tglFillRect(pa.x - pix1_5, pa.y - pix1_5, pa.x + pix1_5, pa.y + pix1_5);

    tglDrawSegment(pb, point);
    if (i == pointIndex && pointType == ControlPointEditorStroke::SPEED_OUT)
      tglFillRect(pb.x - pix2_5, pb.y - pix2_5, pb.x + pix2_5, pb.y + pix2_5);
    else
      tglFillRect(pb.x - pix1_5, pb.y - pix1_5, pb.x + pix1_5, pb.y + pix1_5);

    tglColor(color1);

    if (i == pointIndex &&
        pointType == ControlPointEditorStroke::CONTROL_POINT) {
      tglFillRect(point.x - pix3_5, point.y - pix3_5, point.x + pix3_5,
                  point.y + pix3_5);
      if (!m_selection.isSelected(i)) {
        tglColor(color2);
        tglFillRect(point.x - pix2_5, point.y - pix2_5, point.x + pix2_5,
                    point.y + pix2_5);
      }
    } else {
      tglFillRect(point.x - pix2, point.y - pix2, point.x + pix2,
                  point.y + pix2);
      if (!m_selection.isSelected(i)) {
        tglColor(color2);
        tglFillRect(point.x - pix, point.y - pix, point.x + pix, point.y + pix);
      }
    }
  }
}

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

void ControlPointEditorTool::draw() {
  TVectorImageP vi(getImage(false));
  if (!m_draw) return;

  int currentStroke = m_controlPointEditorStroke.getStrokeIndex();
  if (!vi || currentStroke == -1 ||
      m_controlPointEditorStroke.getControlPointCount() == 0 ||
      vi->getStrokeCount() == 0 || (int)vi->getStrokeCount() <= currentStroke) {
    m_controlPointEditorStroke.setStroke((TVectorImage *)0, -1);
    return;
  }

  TPixel color1, color2;
  if (m_action == RECT_SELECTION)  // Disegna il rettangolo per la selezione
  {
    color1 = TPixel32::
        Black;  // TransparencyCheck::instance()->isEnabled()?TPixel32::White:TPixel32::Black;
    drawRect(m_selectingRect, color1, 0x3F33, true);
  } else if (m_action == FREEHAND_SELECTION && !m_track.isEmpty()) {
    TPixel color = ToonzCheck::instance()->getChecks() & ToonzCheck::eBlackBg
                       ? TPixel32::White
                       : TPixel32::Black;
    tglColor(color);
    m_track.drawAllFragments();
  }

  if (m_controlPointEditorStroke.getControlPointCount() <= 0) return;

  color1          = TPixel(79, 128, 255);
  color2          = TPixel::White;
  TStroke *stroke = m_controlPointEditorStroke.getStroke();
  tglColor(color1);
  drawStrokeCenterline(*stroke, getPixelSize());

  drawControlPoint();

  drawMovingSegment();

  drawSnap();
}

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

void ControlPointEditorTool::mouseMove(const TPointD &pos,
                                       const TMouseEvent &e) {
  // Scelgo il cursore in base alla distanza del mouse dalla curva e dai punti
  // di controllo
  TVectorImageP vi(getImage(false));
  if (!vi) {
    m_controlPointEditorStroke.setStroke((TVectorImage *)0, -1);
    m_cursorType = NO_ACTIVE;
    return;
  } else
    m_cursorType = NORMAL;

  m_pos = pos;

  if (!m_draw || m_controlPointEditorStroke.getStrokeIndex() == -1) return;
  if (e.isAltPressed())
    m_cursorType = EDIT_SPEED;
  else {
    double maxDist  = 5 * getPixelSize();
    double maxDist2 = maxDist * maxDist;
    int pointIndexCP;
    ControlPointEditorStroke::PointType pointType =
        m_controlPointEditorStroke.getPointTypeAt(pos, maxDist2, pointIndexCP);
    if (pointType == ControlPointEditorStroke::SEGMENT && e.isCtrlPressed())
      m_cursorType = ADD;
    // else if(pointType == ControlPointEditorStroke::SEGMENT)
    // m_cursorType=EDIT_SEGMENT;
    else
      m_cursorType = NORMAL;
  }
  invalidate();
}

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

void ControlPointEditorTool::leftButtonDown(const TPointD &pos,
                                            const TMouseEvent &e) {
  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) {
    getViewer()->doPickGuideStroke(pos);
    return;
  }

  m_pos           = pos;
  double pix      = getPixelSize() * 2.0f;
  double maxDist  = 5 * pix;
  double maxDist2 = maxDist * maxDist;
  double dist2    = 0;
  int pointIndex;
  ControlPointEditorStroke::PointType pointType =
      m_controlPointEditorStroke.getPointTypeAt(pos, maxDist2, pointIndex);

  if (pointType == ControlPointEditorStroke::NONE) {
    // ho cliccato lontano dalla curva corrente
    TTool::Application *app = TTool::getApplication();
    if (m_autoSelectDrawing.getValue()) {
      // Non sono in nessun gadget
      std::vector<int> columnIndexes;
      getViewer()->posToColumnIndexes(e.m_pos, columnIndexes,
                                      getPixelSize() * 5, false);
      getNearestStrokeColumnIndexes(columnIndexes, pos);
      if (!columnIndexes.empty()) {
        int currentColumnIndex = app->getCurrentColumn()->getColumnIndex();
        int columnIndex;
        if (columnIndexes.size() == 1)
          columnIndex = columnIndexes[0];
        else {
          ToolUtils::ColumChooserMenu *menu = new ToolUtils::ColumChooserMenu(
              app->getCurrentXsheet()->getXsheet(), columnIndexes);
          m_isMenuViewed = true;
          columnIndex    = menu->execute();
        }
        TXshColumn *column =
            app->getCurrentXsheet()->getXsheet()->getColumn(columnIndex);
        if (columnIndex >= 0 && columnIndex != currentColumnIndex && column &&
            !column->isLocked()) {
          TAffine aff = getMatrix();
          app->getCurrentColumn()->setColumnIndex(columnIndex);
          updateMatrix();
          currentColumnIndex = columnIndex;
          m_pos              = getMatrix().inv() * aff * pos;
        }
      }
    }

    TVectorImageP vi = getImage(false);
    if (!vi) return;
    double dist2, t = 0;
    UINT index = -1;
    if (vi->getNearestStroke(m_pos, t, index, dist2) &&
        dist2 < 25 * getPixelSize() * getPixelSize()) {
      // ho cliccato vicino alla curva index-esima
      assert(0 <= index && index < vi->getStrokeCount());
      m_controlPointEditorStroke.setStroke(vi, index);
      m_action = NONE;
      m_selection.makeCurrent();
    } else {
      if (m_selectType.getValue() == RECTANGULAR_WSTR) {
        m_action        = RECT_SELECTION;
        m_selectingRect = TRectD(m_pos.x, m_pos.y, m_pos.x + 1, m_pos.y + 1);
        if (m_selectingRect.x0 > m_selectingRect.x1)
          std::swap(m_selectingRect.x1, m_selectingRect.x0);
        if (m_selectingRect.y0 > m_selectingRect.y1)
          std::swap(m_selectingRect.y1, m_selectingRect.y0);

      } else if (m_selectType.getValue() == FREEHAND_WSTR) {
        m_action = FREEHAND_SELECTION;
        startFreehand(pos);
      }
    }
    m_selection.selectNone();
    return;
  }
  TVectorImageP vi = getImage(true);
  if (!vi) return;

  if (pointType == ControlPointEditorStroke::SPEED_IN ||
      pointType == ControlPointEditorStroke::SPEED_OUT) {
    bool isIn = pointType == ControlPointEditorStroke::SPEED_IN;
    m_selection.selectNone();
    m_selection.select(pointIndex);
    m_action = isIn ? IN_SPEED_MOVEMENT : OUT_SPEED_MOVEMENT;
    if (e.isAltPressed()) {
      initUndo();
      if (m_controlPointEditorStroke.isCusp(pointIndex))
        linkSpeedInOut(pointIndex);
      else
        unlinkSpeedInOut(pointIndex);
      TUndoManager::manager()->add(m_undo);
      m_undo = 0;
    }
    m_selection.makeCurrent();
  } else if (pointType == ControlPointEditorStroke::CONTROL_POINT) {
    if (e.isAltPressed()) {
      m_action = NONE;
      m_selection.selectNone();
      m_selection.select(pointIndex);
      initUndo();
      bool isSpeedIn  = m_controlPointEditorStroke.isSpeedInLinear(pointIndex);
      bool isSpeedOut = m_controlPointEditorStroke.isSpeedOutLinear(pointIndex);
      m_controlPointEditorStroke.setLinear(pointIndex,
                                           !isSpeedIn && !isSpeedOut);
      TUndoManager::manager()->add(m_undo);
      m_undo = 0;
      return;
    }
    if (e.isCtrlPressed()) {
      if (m_selection.isSelected(pointIndex))
        m_selection.unselect(pointIndex);
      else
        m_selection.select(pointIndex);
    } else if (m_selection.isEmpty() || !m_selection.isSelected(pointIndex)) {
      m_selection.selectNone();
      m_selection.select(pointIndex);
    }
    m_lastPointSelected = pointIndex;
    m_action            = CP_MOVEMENT;
    m_selection.makeCurrent();
  } else if (pointType == ControlPointEditorStroke::SEGMENT &&
             !e.isAltPressed()) {
    m_selection.selectNone();
    if (e.isCtrlPressed()) {
      // Aggiungo un punto
      initUndo();
      pointIndex = m_controlPointEditorStroke.addControlPoint(pos);
      m_selection.select(pointIndex);
      m_action = CP_MOVEMENT;
      TUndoManager::manager()->add(m_undo);
      m_lastPointSelected = -1;
      notifyImageChanged();
    } else {
      // Inizio a muovere la curva
      int precPointIndex, nextPointIndex;
      precPointIndex = pointIndex;
      nextPointIndex =
          (m_controlPointEditorStroke.isSelfLoop() &&
           precPointIndex ==
               m_controlPointEditorStroke.getControlPointCount() - 1)
              ? 0
              : precPointIndex + 1;
      if (precPointIndex > -1 && nextPointIndex > -1) {
        if (precPointIndex > nextPointIndex)
          std::swap(precPointIndex, nextPointIndex);
        m_moveSegmentLimitation.first  = precPointIndex;
        m_moveSegmentLimitation.second = nextPointIndex;
      }
      m_moveControlPointEditorStroke = *m_controlPointEditorStroke.clone();
      // Se e' premuto shift setto a cusp i due punti di controllo che
      // delimitano il segmento.
      if (e.isShiftPressed()) {
        if (!m_controlPointEditorStroke.isCusp(precPointIndex)) {
          m_controlPointEditorStroke.setCusp(precPointIndex, true, false);
          m_moveControlPointEditorStroke.setCusp(precPointIndex, true, false);
        }
        if (!m_controlPointEditorStroke.isCusp(nextPointIndex)) {
          m_controlPointEditorStroke.setCusp(nextPointIndex, true, true);
          m_moveControlPointEditorStroke.setCusp(nextPointIndex, true, true);
        }
      }
      m_action = SEGMENT_MOVEMENT;
    }
    m_selection.makeCurrent();
  }

  int currentStroke = m_controlPointEditorStroke.getStrokeIndex();
  if (currentStroke != -1) initUndo();
  invalidate();
  m_isImageChanged = false;
}

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

void ControlPointEditorTool::rightButtonDown(const TPointD &pos,
                                             const TMouseEvent &) {
  TVectorImageP vi = getImage(true);
  if (!vi) return;
  double maxDist  = 5 * getPixelSize();
  double maxDist2 = maxDist * maxDist;
  double dist2    = 0;
  int pointIndex;
  ControlPointEditorStroke::PointType pointType =
      m_controlPointEditorStroke.getPointTypeAt(pos, maxDist2, pointIndex);
  if (pointType != ControlPointEditorStroke::CONTROL_POINT) return;
  m_selection.select(pointIndex);
}

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

void ControlPointEditorTool::moveControlPoints(const TPointD &delta) {
  int i;
  int cpCount = m_controlPointEditorStroke.getControlPointCount();
  for (i = 0; i < cpCount; i++)
    if (m_selection.isSelected(i))
      m_controlPointEditorStroke.moveControlPoint(i, delta);
}

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

void ControlPointEditorTool::moveSpeed(const TPointD &delta, bool isIn) {
  int i;
  for (i = 0; i < m_controlPointEditorStroke.getControlPointCount(); i++)
    if (m_selection.isSelected(i))
      m_controlPointEditorStroke.moveSpeed(i, delta, isIn, 4 * getPixelSize());
}

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

void ControlPointEditorTool::moveSegment(const TPointD &delta, bool dragging,
                                         bool isShiftPressed) {
  int beforeIndex = m_moveSegmentLimitation.first;
  int nextIndex   = m_moveSegmentLimitation.second;
  // Se e' premuto shift setto a cusp i due punti di controllo che delimitano il
  // segmento.
  if (isShiftPressed) {
    if (!m_controlPointEditorStroke.isCusp(beforeIndex)) {
      if (dragging)
        m_moveControlPointEditorStroke.setCusp(beforeIndex, true, false);
      else
        m_controlPointEditorStroke.setCusp(beforeIndex, true, false);
    }
    if (!m_controlPointEditorStroke.isCusp(nextIndex)) {
      if (dragging)
        m_moveControlPointEditorStroke.setCusp(nextIndex, true, true);
      else
        m_controlPointEditorStroke.setCusp(nextIndex, true, true);
    }
  }
  if (dragging)
    m_moveControlPointEditorStroke.moveSegment(beforeIndex, nextIndex, delta,
                                               m_pos);
  else
    m_controlPointEditorStroke.moveSegment(beforeIndex, nextIndex, delta,
                                           m_pos);
}

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

void ControlPointEditorTool::leftButtonDrag(const TPointD &pos,
                                            const TMouseEvent &e) {
  TVectorImageP vi(getImage(true));
  int currentStroke = m_controlPointEditorStroke.getStrokeIndex();
  if (!vi || currentStroke == -1 || m_action == NONE) return;
  QMutexLocker lock(vi->getMutex());

  TPointD delta = pos - m_pos;

  if (m_action == CP_MOVEMENT) {
    if (!m_selection.isSelected(m_lastPointSelected) && e.isCtrlPressed())
      m_selection.select(m_lastPointSelected);  // Controllo che non venga
                                                // deselezionata l'ultima
                                                // selezione nel movimento

    if (m_lastPointSelected >= 0) {
      TThickPoint cp;
      TPointD controlPoint;
      TPointD newPos;

      cp = m_controlPointEditorStroke.getControlPoint(m_lastPointSelected);
      controlPoint = TPointD(cp.x, cp.y);
      newPos       = calculateSnap(pos);
      delta        = newPos - m_pos + (m_pos - controlPoint);
    }

    m_pos = pos;

    moveControlPoints(delta);
    m_isImageChanged = true;
  }
  if (m_action == SEGMENT_MOVEMENT) {
    m_moveControlPointEditorStroke = *m_controlPointEditorStroke.clone();
    moveSegment(delta, true, e.isShiftPressed());
    m_isImageChanged = true;
  }
  if (m_action == OUT_SPEED_MOVEMENT || m_action == IN_SPEED_MOVEMENT) {
    m_pos = pos;
    moveSpeed(delta, m_action == IN_SPEED_MOVEMENT);
    m_isImageChanged = true;
  }

  if (m_action == RECT_SELECTION) {
    int cpCount        = m_controlPointEditorStroke.getControlPointCount();
    m_selectingRect.x0 = m_pos.x;
    m_selectingRect.y0 = m_pos.y;
    m_selectingRect.x1 = pos.x;
    m_selectingRect.y1 = pos.y;
    if (m_selectingRect.x0 > m_selectingRect.x1)
      std::swap(m_selectingRect.x1, m_selectingRect.x0);
    if (m_selectingRect.y0 > m_selectingRect.y1)
      std::swap(m_selectingRect.y1, m_selectingRect.y0);
    int i;
    m_selection.selectNone();
    for (i = 0; i < cpCount; i++)
      if (m_selectingRect.contains(
              m_controlPointEditorStroke.getControlPoint(i)))
        m_selection.select(i);
  } else if (m_action == FREEHAND_SELECTION) {
    freehandDrag(pos);
  }

  invalidate();
}

//---------------------------------------------------------------------------
void ControlPointEditorTool::selectRegion(TStroke *stroke) {
  int cpCount = m_controlPointEditorStroke.getControlPointCount();

  TVectorImage img;
  img.addStroke(stroke);
  img.findRegions();
  for (int rI = 0; rI < (int)img.getRegionCount(); rI++) {
    TRegion *region = img.getRegion(rI);
    for (int i = 0; i < cpCount; i++) {
      if (region->contains(m_controlPointEditorStroke.getControlPoint(i))) {
        m_selection.select(i);
      }
    }
  }
}

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

void ControlPointEditorTool::leftButtonUp(const TPointD &realPos,
                                          const TMouseEvent &e) {
  TVectorImageP vi(getImage(true));
  int currentStroke = m_controlPointEditorStroke.getStrokeIndex();
  if (!vi || currentStroke == -1) return;
  QMutexLocker lock(vi->getMutex());

  TPointD pos;
  pos = getSnap(realPos);
  resetSnap();

  if (m_action == SEGMENT_MOVEMENT) {
    m_moveControlPointEditorStroke.setStroke((TVectorImage *)0, -1);
    TPointD delta = pos - m_pos;
    moveSegment(delta, false, e.isShiftPressed());
  }

  if (m_action == RECT_SELECTION || m_action == FREEHAND_SELECTION) {
    if (m_action == FREEHAND_SELECTION) {
      closeFreehand(pos);
      selectRegion(m_stroke);
      m_track.clear();
    }

    if (m_selection.isEmpty()) {
      // Non ho selezionato nulla
      if (!TTool::getApplication()
               ->getCurrentObject()
               ->isSpline())  // se non e' una spline deseleziono
        m_controlPointEditorStroke.setStroke((TVectorImage *)0, -1);
      m_action         = NONE;
      m_isImageChanged = false;
    } else {
      m_action = CP_MOVEMENT;
      m_selection.makeCurrent();
      m_isImageChanged = false;
    }
  }

  if (m_action == NONE || !m_isImageChanged) {
    m_undo = 0;
    invalidate();
    return;
  }

  notifyImageChanged();
  invalidate();

  // Registro l'UNDO
  if (m_undo) {
    TUndoManager::manager()->add(m_undo);
    m_undo = 0;
  }
}

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

void ControlPointEditorTool::addContextMenuItems(QMenu *menu) {
  m_isMenuViewed = true;
  m_selection.addMenuItems(menu);
}

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

void ControlPointEditorTool::linkSpeedInOut(int index) {
  if ((index == 0 ||
       index == m_controlPointEditorStroke.getControlPointCount() - 1) &&
      !m_controlPointEditorStroke.isSelfLoop())
    return;
  if (m_action == IN_SPEED_MOVEMENT || m_action == CP_MOVEMENT)
    m_controlPointEditorStroke.setCusp(index, false, true);
  if (m_action == OUT_SPEED_MOVEMENT)
    m_controlPointEditorStroke.setCusp(index, false, false);
  invalidate();
}

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

void ControlPointEditorTool::unlinkSpeedInOut(int pointIndex) {
  m_controlPointEditorStroke.setCusp(pointIndex, true, true);
}

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

bool ControlPointEditorTool::keyDown(QKeyEvent *event) {
  TVectorImageP vi(getImage(true));
  if (!vi || (vi && m_selection.isEmpty())) return false;

  // Inizializzo l'UNDO
  initUndo();

  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;
  }
  moveControlPoints(delta);

  invalidate();
  // Registro l'UNDO
  TUndoManager::manager()->add(m_undo);

  return true;
}

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

void ControlPointEditorTool::onEnter() {
  TVectorImageP vi(getImage(false));
  int currentStroke = m_controlPointEditorStroke.getStrokeIndex();
  if (m_isMenuViewed) {
    m_isMenuViewed = false;
    return;
  }
  /*m_draw=true;
  if(currentStroke==-1 || !vi)
          return;

  m_controlPointEditorStroke.setStroke((TVectorImage*)0, -1);

if(TTool::getApplication()->getCurrentObject()->isSpline())
          m_controlPointEditorStroke.setStroke(vi, 0);*/
}

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

void ControlPointEditorTool::onLeave() {
  if (m_isMenuViewed) return;
  // m_draw=false;
}

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

bool ControlPointEditorTool::onPropertyChanged(std::string propertyName) {
  CPSelectionType   = ::to_string(m_selectType.getValue());
  AutoSelectDrawing = (int)(m_autoSelectDrawing.getValue());
  Snap              = (int)(m_snap.getValue());
  SnapSensitivity   = (int)(m_snapSensitivity.getIndex());
  switch (SnapSensitivity) {
  case 0:
    m_snapMinDistance = SNAPPING_LOW;
    break;
  case 1:
    m_snapMinDistance = SNAPPING_MEDIUM;
    break;
  case 2:
    m_snapMinDistance = SNAPPING_HIGH;
    break;
  }
  return true;
}

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

void ControlPointEditorTool::onActivate() {
  // TODO: getApplication()->editImageOrSpline();
  m_selectType.setValue(::to_wstring(CPSelectionType.getValue()));
  m_autoSelectDrawing.setValue(AutoSelectDrawing ? 1 : 0);
  m_snap.setValue(Snap ? 1 : 0);
  m_snapSensitivity.setIndex(SnapSensitivity);
  switch (SnapSensitivity) {
  case 0:
    m_snapMinDistance = SNAPPING_LOW;
    break;
  case 1:
    m_snapMinDistance = SNAPPING_MEDIUM;
    break;
  case 2:
    m_snapMinDistance = SNAPPING_HIGH;
    break;
  }
  m_controlPointEditorStroke.setStroke((TVectorImage *)0, -1);
  m_draw = true;
  resetSnap();
}

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

void ControlPointEditorTool::onDeactivate() { m_draw = false; }

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

void ControlPointEditorTool::onImageChanged() {
  TVectorImageP vi(getImage(false));
  if (!vi) return;

  int currentStroke = m_controlPointEditorStroke.getStrokeIndex();
  if (!vi || currentStroke == -1 ||
      m_controlPointEditorStroke.getControlPointCount() == 0 ||
      vi->getStrokeCount() == 0 || (int)vi->getStrokeCount() <= currentStroke) {
    m_controlPointEditorStroke.setStroke((TVectorImage *)0, -1);
    return;
  } else {
    m_selection.selectNone();
    m_controlPointEditorStroke.setStroke(vi, currentStroke);
  }
}

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

int ControlPointEditorTool::getCursorId() const {
  if (m_viewer && m_viewer->getGuidedStrokePickerMode())
    return m_viewer->getGuidedStrokePickerCursor();

  switch (m_cursorType) {
  case NORMAL:
    return ToolCursor::SplineEditorCursor;
  case ADD:
    return ToolCursor::SplineEditorCursorAdd;
  case EDIT_SPEED:
    return ToolCursor::SplineEditorCursorSelect;
  case EDIT_SEGMENT:
    return ToolCursor::PinchCursor;
  case NO_ACTIVE:
    return ToolCursor::CURSOR_NO;
  default:
    return ToolCursor::SplineEditorCursor;
  }
}

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

// returns true if the pressed key is recognized and processed in the tool
// instead of triggering the shortcut command.
bool ControlPointEditorTool::isEventAcceptable(QEvent *e) {
  if (!isEnabled()) return false;
  TVectorImageP vi(getImage(false));
  if (!vi || (vi && m_selection.isEmpty())) return false;
  // arrow keys will be used for moving the selected points
  QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
  // shift + arrow will not be recognized for now
  if (keyEvent->modifiers() & Qt::ShiftModifier) return false;
  int key = keyEvent->key();
  return (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left ||
          key == Qt::Key_Right);
}

//=============================================================================

// TTool *getSplineEditorTool() {return &controlPointEditorTool;}

//=============================================================================

void ControlPointEditorTool::startFreehand(const TPointD &pos) {
  m_track.clear();
  m_firstPos       = pos;
  double pixelSize = getPixelSize();
  m_track.add(TThickPoint(pos, 0), pixelSize * pixelSize);
}

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

void ControlPointEditorTool::freehandDrag(const TPointD &pos) {
  double pixelSize = getPixelSize();
  m_track.add(TThickPoint(pos, 0), pixelSize * pixelSize);
}

//-----------------------------------------------------------------------------
void ControlPointEditorTool::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);
}