// --------------------------------------------------------------------
// Overlay for creating IpePath objects
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License
    along with Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipepath.h"

#include "ipecreatepath.h"
#include "ipecanvas.h"

#include <qevent.h>
#include <qpainter.h>

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

/*! \class CreateRectangle
  \brief Overlay for creating rectangles and squares.
*/

//! Constructor starts selection.
IpeCreateRectangle::IpeCreateRectangle(QMouseEvent *ev, IpeCanvas *canvas,
				       IpeOverlayServices *services)
  : IpeOverlay(canvas), iServices(services)
{
  // need to work out coordinates in user space
  iV[0] = iCanvas->Pos();
  iV[1] = iV[0];
  iSquare = (ev->state() & QMouseEvent::ShiftButton);
}

void IpeCreateRectangle::Draw(QPaintEvent *, QPainter *qPainter) const
{
  qPainter->setPen(CreateColor());
  IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter);
  painter.Transform(iCanvas->CanvasTfm());
  painter.NewPath();
  painter.Rect(IpeRect(iV[0], iV[1]));
  painter.DrawPath();
}

void IpeCreateRectangle::Compute()
{
  iV[1] = iCanvas->Pos();
  if (iSquare) {
    IpeVector d = iV[1] - iV[0];
    IpeVector sign(d.iX > 0 ? 1 : -1, d.iY > 0 ? 1 : -1);
    double dd = IpeMax(IpeAbs(d.iX), IpeAbs(d.iY));
    iV[1] = iV[0] + dd * sign;
  }
}

void IpeCreateRectangle::MouseMove(QMouseEvent *)
{
  Compute();
  iCanvas->UpdateOverlay();
}

void IpeCreateRectangle::MousePress(QMouseEvent *)
{
  Compute();
  IpePath *obj = new IpePath(iServices->OvSvcAttributes(),
			     IpeRect(iV[0], iV[1]));
  iServices->OvSvcAddObject(obj);
  iCanvas->FinishOverlay();
}

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

/*! \class IpeCreateSplinegon
  \brief Overlay for creating IpePath splinegons.
*/

//! Constructor starts selection.
IpeCreateSplinegon::IpeCreateSplinegon(QMouseEvent *, IpeCanvas *canvas,
				 IpeOverlayServices *services)
  : IpeOverlay(canvas), iServices(services)
{
  iV.push_back(iCanvas->Pos());
  iV.push_back(iV[0]);
  iCanvas->SetDoubleBuffer(true);
  Explain();
}

void IpeCreateSplinegon::Explain() const
{
  iCanvas->Message(QObject::tr("Left: Add vertex | Right: Add final vertex | Del: Delete vertex"));
}

void IpeCreateSplinegon::KeyPress(QKeyEvent *ev)
{
  if (ev->key() == Qt::Key_Delete) {
    if (iV.size() == 2) {
      iCanvas->FinishOverlay();
      ev->accept();
      return;
    }
    iV.pop_back();
    iCanvas->UpdateOverlay();
  } else
    ev->ignore();
  Explain();
}

void IpeCreateSplinegon::Draw(QPaintEvent *, QPainter *qPainter) const
{
  qPainter->setPen(CreateColor());
  IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter);
  painter.Transform(iCanvas->CanvasTfm());
  painter.Push();
  painter.NewPath();
  if (iV.size() < 3) {
    painter.MoveTo(iV[0]);
    painter.LineTo(iV[1]);
  } else {
    std::vector<IpeBezier> bez;
    IpeBezier::ClosedSpline(iV.size(), &(iV[0]), bez);
    painter.MoveTo(bez.front().iV[0]);
    for (uint i = 0; i < bez.size(); ++i)
      painter.IpePainter::CurveTo(bez[i]);
    painter.ClosePath();
  }
  painter.DrawPath();
  painter.Pop();
}

void IpeCreateSplinegon::MouseMove(QMouseEvent *)
{
  iV.back() = iCanvas->Pos();
  iCanvas->UpdateOverlay();
}

void IpeCreateSplinegon::MousePress(QMouseEvent *ev)
{
  if (ev->button() == QMouseEvent::LeftButton) {
    // freeze moving vertex
    iV.back() = iCanvas->Pos();
    // add new moving vertex
    iV.push_back(iCanvas->Pos());
    iCanvas->UpdateOverlay();
    Explain();
  } else if (iV.size() > 2){
    // freeze moving vertex
    iV.back() = iCanvas->Pos();
    // create IpePath object
    IpePath *obj = new IpePath(iServices->OvSvcAttributes());
    obj->AddSubPath(new IpeClosedSpline(iV));
    iServices->OvSvcAddObject(obj);
    iCanvas->FinishOverlay();
  }
}

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

static void AppendSpline(IpeSegmentSubPath *sp, const IpeVector &v)
{
  std::vector<IpeVector> vv;
  vv.push_back(v);
  vv.push_back(v);
  sp->AppendSpline(vv);
}

/*! \class IpeCreatePath
  \brief Overlay for creating IpePath objects.
*/

//! Constructor starts selection.
IpeCreatePath::IpeCreatePath(QMouseEvent *, IpeCanvas *canvas,
		       IpeOverlayServices *services, bool closed, bool spline)
  : IpeOverlay(canvas), iServices(services)
{
  iSP = new IpeSegmentSubPath;
  iSP->SetClosed(closed);
  IpeVector v = iCanvas->Pos();
  if (spline)
    AppendSpline(iSP, v);
  else
    iSP->AppendSegment(v, v);
  iCanvas->SetDoubleBuffer(true);
  Update();
}

IpeCreatePath::~IpeCreatePath()
{
  delete iSP;
}

void IpeCreatePath::Update()
{
  IpePathSegment seg = iSP->Segment(-1);
  iCanvas->SetAutoOrigin(seg.CP(seg.NumCP() - 1));
  iCanvas->UpdateOverlay();
  QString s = QObject::tr("Left: Add vtx");
  if (seg.Type() == IpePathSegment::ESpline)
    s += QObject::tr(" | Left with Shift: Switch to polyline");
  else if (seg.Type() == IpePathSegment::ESegment)
    s += QObject::tr(" | Left with Shift: Switch to spline");
  s += QObject::tr(" | Right: final vtx | Del: Del vtx");
  if (seg.Type() == IpePathSegment::ESegment &&
      iSP->NumSegments() >= 2 &&
      iSP->Segment(-2).Type() == IpePathSegment::ESegment) {
    s += QObject::tr(" | q : quadratic Bezier | a : circle arc");
    if (iSP->NumSegments() >= 3 &&
	iSP->Segment(-3).Type() == IpePathSegment::ESegment)
      s += QObject::tr(" | c : cubic Bezier");
  }
  iCanvas->Message(s);
}

// Check whether path ends with k straight segments.
// If so, remove them, and return the k + 1 control points.
bool IpeCreatePath::Decrease(int k, std::vector<IpeVector> &v)
{
  for (int i = 1; i <= k; ++i) {
    if (iSP->Segment(-i).Type() != IpePathSegment::ESegment)
      return false;
  }
  v.resize(k + 1);
  v[k] = iSP->Segment(-1).Last();
  while (k > 0) {
    v[k-1] = iSP->Segment(-1).CP(0);
    iSP->DeleteSegment(-1);
    --k;
  }
  return true;
}

IpeMatrix IpeCreatePath::ComputeMatrix(const IpeVector &v0,
				       const IpeVector &v1,
				       const IpeVector &v2)
{
  IpeVector dir = (v1 - v0).Normalized();
  IpeLine l1(v1, dir);
  IpeLine n1(v1, l1.Normal());

  if (v1 == v2)
    return IpeMatrix(v1 + n1.Dir());

  IpeLine l2(v1, (v2 - v1).Normalized());
  IpeLine n2(0.5 * (v1 + v2), l2.Normal());
  IpeVector center;
  n1.Intersects(n2, center);
  // double alpha = (v1 - center).Angle();
  // double beta = (v2 - center).Angle().Normalize(alpha);
  double radius = (v2 - center).Len();
  IpeMatrix m(radius, 0, 0, radius, center.iX, center.iY);
  if (l1.Side(v2) < 0)
    m = m * IpeLinear(1, 0, 0, -1);
  return m;
}

void IpeCreatePath::RecomputeMatrix()
{
  IpeVector v1 = iSP->Segment(-1).CP(0);
  IpeVector v2 = iSP->Segment(-1).CP(1);
  iSP->DeleteSegment(-1);
  IpeVector v0 = iSP->Segment(-1).CP(0);
  IpeMatrix m = ComputeMatrix(v0, v1, v2);
  iSP->AppendArc(m, v1, v2);
}

void IpeCreatePath::KeyPress(QKeyEvent *ev)
{
  std::vector<IpeVector> v;
  if (ev->key() == Qt::Key_Delete) {
    if (iSP->NumSegments() == 1 &&
	(iSP->Segment(0).Type() != IpePathSegment::ESpline ||
	 iSP->Segment(0).NumCP() < 3)) {
      iCanvas->FinishOverlay();
      ev->accept();
      return;
    }
    switch (iSP->Segment(-1).Type()) {
    case IpePathSegment::ESegment:
    case IpePathSegment::EArc:
      iSP->DeleteSegment(-1);
      break;
    case IpePathSegment::EBezier:
    case IpePathSegment::EQuad:
      break;
    case IpePathSegment::ESpline:
      if (iSP->Segment(-1).NumCP() > 2)
	iSP->DeleteCP(-1, -1);
      else
	iSP->DeleteSegment(-1);
      break;
    }
    ev->accept();
  } else if (ev->key() == Qt::Key_Q && Decrease(2, v)) {
    // Quadratic Bezier
    iSP->AppendQuad(v[0], v[1], v[2]);
    ev->accept();
  } else if (ev->key() == Qt::Key_C && Decrease(3, v)) {
    // Cubic Bezier
    iSP->AppendBezier(v[0], v[1], v[2], v[3]);
    ev->accept();
  } else if (ev->key() == Qt::Key_A && Decrease(2, v)) {
    // arc mode: not quite clear why we need a preceding line segment,
    // but somehow the tangent needs to be fixed.
    iSP->AppendSegment(v[0], v[1]);
    IpeMatrix m = ComputeMatrix(v[0], v[1], v[2]);
    iSP->AppendArc(m, v[1], v[2]);
  } else
    ev->ignore();
  Update();
}

void IpeCreatePath::Draw(QPaintEvent *, QPainter *qPainter) const
{
  if (iSP) {
    qPainter->setPen(CreateColor());
    IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter);
    painter.Transform(iCanvas->CanvasTfm());
    painter.Push();
    painter.NewPath();
    iSP->Draw(painter);
    painter.DrawPath();
    painter.Pop();
  }
}

void IpeCreatePath::MouseMove(QMouseEvent *)
{
  iSP->MoveCP(-1, -1, iCanvas->Pos());
  if (iSP->Segment(-1).Type() == IpePathSegment::EArc)
    RecomputeMatrix();
  iCanvas->UpdateOverlay();
}

void IpeCreatePath::MousePress(QMouseEvent *ev)
{
  if (ev->button() == QMouseEvent::LeftButton) {
    bool swit = (ev->state() & QMouseEvent::ShiftButton) != 0;
    IpeVector v = iCanvas->Pos();
    // freeze moving vertex
    iSP->MoveCP(-1, -1, v);
    // add new moving vertex
    IpePathSegment seg = iSP->Segment(-1);
    if (swit) {
      if (seg.Type() == IpePathSegment::ESegment)
	AppendSpline(iSP, v);
      else
	iSP->AppendSegment(v, v);
    } else {
      if (seg.Type() == IpePathSegment::ESpline)
	iSP->InsertCP(-1, seg.NumCP(), v);
      else
	iSP->AppendSegment(v, v);
    }
    Update();
  } else {
    // freeze moving vertex
    iSP->MoveCP(-1, -1, iCanvas->Pos());
    if (iSP->Segment(-1).Type() == IpePathSegment::EArc)
      RecomputeMatrix();
    // many users seem to end their paths with zero-length segments
    // because they first click left and then right.
    // We now correct this behaviour:
    IpePathSegment seg = iSP->Segment(-1);
    if (seg.Type() == IpePathSegment::ESegment &&
	seg.CP(0) == seg.CP(1) &&
	iSP->NumSegments() > 1)
      iSP->DeleteSegment(-1);
    // create IpePath object
    const IpeAllAttributes &attr = iServices->OvSvcAttributes();
    IpePath *obj = new IpePath(attr);
    if (!iSP->Closed() && !attr.iStroke.IsVoid())
      obj->SetFill(IpeAttribute());
    obj->AddSubPath(iSP);
    iSP = 0;
    iServices->OvSvcAddObject(obj);
    iCanvas->FinishOverlay();
  }
}

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