// --------------------------------------------------------------------
// Overlay for the canvas --- shows moving objects etc.
// --------------------------------------------------------------------
/*

    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 "ipetext.h"
#include "ipepage.h"
#include "ipeundo.h"

#include "ipeoverlay.h"
#include "ipecanvas.h"

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

inline QPoint QPt(const IpeVector &v)
{
  return QPoint(int(v.iX), int(v.iY));
}

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

/*! \class IpeOverlayPainter
  \brief IpePainter for painting into an overlay.

  Draws a simplified presentation of objects.
*/

/*! Needs IpeStyleSheet only to look up IpeReference objects. */
IpeOverlayPainter::IpeOverlayPainter(const IpeStyleSheet *sheet,
			       QPainter *painter)
  : IpePainter(sheet), iPainter(painter)
{
  iPainter->setFont(QApplication::font());
}

void IpeOverlayPainter::DoMoveTo(const IpeVector &v)
{
  iPos = QPt(Matrix() * v);
  iPainter->moveTo(iPos);
  iPathBegin = iPos;
}

void IpeOverlayPainter::DoLineTo(const IpeVector &v)
{
  iPos = QPt(Matrix() * v);
  iPainter->lineTo(iPos);
}

void IpeOverlayPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2,
				  const IpeVector &v3)
{
  QPointArray arr(4);
  arr.setPoint(0, iPos);
  arr.setPoint(1, QPt(Matrix() * v1));
  arr.setPoint(2, QPt(Matrix() * v2));
  iPos = QPt(Matrix() * v3);
  arr.setPoint(3, iPos);
#if QT_VERSION >= 0x030000
  iPainter->drawCubicBezier(arr);
#else
  iPainter->drawQuadBezier(arr);
#endif
  iPainter->moveTo(iPos);
}

void IpeOverlayPainter::DoClosePath()
{
  iPainter->lineTo(iPathBegin);
}

//! Draw outline of bitmap only.
void IpeOverlayPainter::DoDrawBitmap(IpeBitmap)
{
  iPainter->moveTo(QPt(Matrix().Translation()));
  iPainter->lineTo(QPt(Matrix() * IpeVector(1.0, 0.0)));
  iPainter->lineTo(QPt(Matrix() * IpeVector(1.0, 1.0)));
  iPainter->lineTo(QPt(Matrix() * IpeVector(0.0, 1.0)));
  iPainter->lineTo(QPt(Matrix().Translation()));
}

//! Draw bounding box of text only.
void IpeOverlayPainter::DoDrawText(const IpeText *text)
{
  iPainter->moveTo(QPt(Matrix() * IpeVector(0,0)));
  iPainter->lineTo(QPt(Matrix() * IpeVector(0, text->TotalHeight())));
  iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(),
					    text->TotalHeight())));
  iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(), 0)));
  if (text->GetXForm())
    iPainter->lineTo(QPt(Matrix() * IpeVector(0,0)));
}

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

/*! \class IpeOverlay
  \brief Abstract base class for various modes that draw overlays.

  The Canvas doesn't know about the various modes for object creation,
  editing, and moving, but delegates the handling to a subclass of
  IpeOverlay.  The ObjectCreator classes are IpeOverlay's, so are
  IpeSelecting, IpeTransforming, and IpePanning.
*/

//! Constructor.
IpeOverlay::IpeOverlay(IpeCanvas *canvas)
  : iCanvas(canvas)
{
  // nothing
}

//! Virtual destructor.
IpeOverlay::~IpeOverlay()
{
  // nothing
}

//! Handle mouse press event.
/*! Default implementation does nothing. */
void IpeOverlay::MousePress(QMouseEvent *)
{
  // nothing
}

//! Handle mouse release event.
/*! Default implementation does nothing. */
void IpeOverlay::MouseRelease(QMouseEvent *)
{
  // nothing
}

//! Handle mouse move event.
/*! Default implementation does nothing. */
void IpeOverlay::MouseMove(QMouseEvent *)
{
  // nothing
}

//! Handle key press.
/*! Default implementation ignores it. */
void IpeOverlay::KeyPress(QKeyEvent *ev)
{
  ev->ignore();
}

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

/*! \class IpePanning
  \brief Canvas panning operation
*/

//! Constructor starts panning.
IpePanning::IpePanning(QMouseEvent *ev, const IpePage *page, IpeCanvas *canvas)
  : IpeOverlay(canvas)
{
  iPage = page;
  iMouseDown = IpeVector(ev->x(), -ev->y());
  iPan = iCanvas->Pan();
#if QT_VERSION >= 0x030000
  iCanvas->setCursor(QCursor(Qt::PointingHandCursor));
#else
  iCanvas->setCursor(QCursor(PointingHandCursor));
#endif
}

void IpePanning::Draw(QPaintEvent *, QPainter *qPainter) const
{
  qPainter->setPen(Qt::blue);

  IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter);
  painter.Translate(iCanvas->Center());
  painter.Transform(IpeLinear(iCanvas->Zoom(), 0, 0, -iCanvas->Zoom()));
  painter.Translate(-iPan);
  painter.NewPath();
  painter.Rect(iCanvas->MediaBox());
  painter.DrawPath();
  painter.Push();
  for (IpePage::const_iterator it = iPage->begin(); it != iPage->end(); ++it) {
    it->Object()->Draw(painter);
  }
  painter.Pop();
}

void IpePanning::MouseRelease(QMouseEvent *ev)
{
  IpeVector displ = IpeVector(ev->x(), -ev->y()) - iMouseDown;
  iPan = iCanvas->Pan() - (1.0/iCanvas->Zoom()) * displ;
  iCanvas->SetPan(iPan);
  iCanvas->FinishOverlay();
}

void IpePanning::MouseMove(QMouseEvent *ev)
{
  IpeVector displ = IpeVector(ev->x(), -ev->y()) - iMouseDown;
  iPan = iCanvas->Pan() - (1.0/iCanvas->Zoom()) * displ;
  iCanvas->UpdateOverlay();
}

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

/*! \class IpeSelecting
  \brief Object selection operation
*/

class SelectCompare {
public:
  int operator()(const IpeSelecting::SObj &lhs,
		 const IpeSelecting::SObj &rhs) const
  {
    return (lhs.iDistance < rhs.iDistance);
  }
};

//! Constructor starts selection.
IpeSelecting::IpeSelecting(QMouseEvent *ev, IpePage *page, int view,
			   IpeCanvas *canvas,
			   bool nonDestructive, int selectDistance,
			   IpeOverlayServices *services)
  : IpeOverlay(canvas), iServices(services)
{
  iPage = page;
  iNonDestructive = nonDestructive;
  iSelectDistance = selectDistance;
  iMouseDown = IpeVector(ev->x(), ev->y());
  iDragging = false;
  iView = view;

  // coordinates in user space
  IpeVector v = iCanvas->UnsnappedPos();

  double bound = iSelectDistance / iCanvas->Zoom();

  // determine visible and unlocked layers
  std::vector<bool> layerVisible;
  iPage->MakeLayerTable(layerVisible, iView, true);

  // Collect objects close enough
  double d;
  for (IpePage::reverse_iterator it = iPage->rbegin();
       it != iPage->rend(); ++it) {
    if (layerVisible[it->Layer()]) {
      if ((d = it->Distance(v, bound)) < bound) {
	SObj obj;
	obj.iIt = it.base();
	--obj.iIt;
	obj.iDistance = d;
	iObjs.push_back(obj);
      }
    }
  }
  iCur = 0;
  std::stable_sort(iObjs.begin(), iObjs.end(), SelectCompare());
#if QT_VERSION >= 0x030000
  iCanvas->setCursor(QCursor(Qt::CrossCursor));
#else
  iCanvas->setCursor(QCursor(CrossCursor));
#endif
}

void IpeSelecting::KeyPress(QKeyEvent *ev)
{
  if (!iDragging && ev->key() == Qt::Key_Space && iObjs.size() > 0) {
    iCur++;
    if (iCur >= int(iObjs.size()))
      iCur = 0;
    iCanvas->UpdateOverlay();
  } else
    ev->ignore();
}

void IpeSelecting::Draw(QPaintEvent *, QPainter *qPainter) const
{
  if (iDragging) {
    qPainter->setPen(Qt::cyan);
    IpeRect r(iMouseDown, iCorner);
    qPainter->drawRect(int(r.Min().iX), int(r.Min().iY),
		       int(r.Width()), int(r.Height()));
  } else {
    qPainter->setPen(Qt::cyan);
    qPainter->drawEllipse(int(iMouseDown.iX - iSelectDistance),
			  int(iMouseDown.iY - iSelectDistance),
			  2 * iSelectDistance, 2 * iSelectDistance);

    if (iObjs.size() > 0) {
      // display current object
      qPainter->setPen(Qt::red);

      IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter);
      painter.Transform(iCanvas->CanvasTfm());
      painter.Push();
      iObjs[iCur].iIt->Object()->Draw(painter);
      painter.Pop();
    }
  }
}

void IpeSelecting::MouseMove(QMouseEvent *ev)
{
  iCorner = IpeVector(ev->x(), ev->y());
  if ((iCorner - iMouseDown).SqLen() > 9.0)
    iDragging = true;
  iCanvas->UpdateOverlay();
}

void IpeSelecting::MouseRelease(QMouseEvent *ev)
{
  bool changed = false;
  // IpeAutoPtr<IpePage> originalPage = new IpePage(*iPage);
  if (iDragging) {
    // select all objects inside the rectangle
    IpeVector v1 = iMouseDown - iCanvas->Center();
    v1.iX /= iCanvas->Zoom();
    v1.iY /= -iCanvas->Zoom();
    v1 += iCanvas->Pan();
    IpeVector v2 = IpeVector(ev->x(), ev->y()) - iCanvas->Center();
    v2.iX /= iCanvas->Zoom();
    v2.iY /= -iCanvas->Zoom();
    v2 += iCanvas->Pan();

    // determine visible and unlocked layers
    std::vector<bool> layerVisible;
    iPage->MakeLayerTable(layerVisible, iView, true);

    IpeRect r(v1, v2);
    for (IpePage::iterator it = iPage->begin(); it != iPage->end(); ++it) {
      if (!iNonDestructive && it->Select()) {
	changed = true;
	it->SetSelect(IpePgObject::ENone);
      }
      if (layerVisible[it->Layer()] && r.Contains(it->BBox())
	  && !it->Select()) {
	changed = true;
	it->SetSelect(IpePgObject::ESecondary);
      }
    }
    iPage->EnsurePrimarySelection();
  } else if (iObjs.size() > 0) {
    IpePage::iterator it1 = iObjs[iCur].iIt;
    if (iNonDestructive) {
      if (!it1->Select())
	// selecting unselected object
	it1->SetSelect(IpePgObject::ESecondary);
      else
	// deselecting selected object
	it1->SetSelect(IpePgObject::ENone);
      changed = true;
      iPage->EnsurePrimarySelection();
    } else {
      // destructive: unselect all
      for (IpePage::iterator it = iPage->begin(); it != iPage->end(); ++it) {
	if (it != it1 && it->Select()) {
	  changed = true;
	  it->SetSelect(IpePgObject::ENone);
	}
      }
      if (it1->Select() != IpePgObject::EPrimary)
	changed = true;
      it1->SetSelect(IpePgObject::EPrimary);
    }
  } else {
    // no object in range, deselect all
    if (!iNonDestructive) {
      changed = iPage->HasSelection();
      iPage->DeselectAll();
    }
  }
  /*
  if (changed)
    iServices->OvSvcAddUndoItem(originalPage.Take(),
    QObject::tr("selecting object"));
  */
  iCanvas->FinishOverlay();
}

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

/*! \class IpeTransforming
  \brief Transform the selected objects.

  Supports moving, rotating, scaling, and stretching.
*/

//! Constructor starts panning.
IpeTransforming::IpeTransforming(QMouseEvent *ev, IpePage *page, int view,
				 IpeCanvas *canvas, TType type,
				 IpeOverlayServices *services)
  : IpeOverlay(canvas), iPage(page), iView(view),
    iServices(services), iType(type)
{
  iWithShift = (ev->state() & QMouseEvent::ShiftButton);
  iMouseDown = iCanvas->Pos();
}

/*! Find objects to be transformed.
  Must be called after construction of IpeTransforming object.
  If it returns false, there are no suitable objects,
  and the whole overlay business must be aborted. */
bool IpeTransforming::FindObjects(int selectDistance)
{
  if (!UpdateCloseSelection(selectDistance))
    return false;

  const IpeSnapData &sd = iCanvas->SnapData();
  if (sd.iWithAxes) {
    iOrigin = sd.iOrigin;
  } else {
    // find bounding box of selected objects
    IpeRect bbox;
    for (IpePage::const_iterator it = iPage->begin();
	 it != iPage->end(); ++it) {
      if (it->Select() != IpePgObject::ENone) {
	bbox.AddRect(it->BBox());
      }
    }
    iOrigin = 0.5 * (bbox.Min() + bbox.Max());
    if (iType == EStretch || iType == EScale) {
      if (iMouseDown.iX > iOrigin.iX)
	iOrigin.iX = bbox.Min().iX;
      else
	iOrigin.iX = bbox.Max().iX;
      if (iMouseDown.iY > iOrigin.iY)
	iOrigin.iY = bbox.Min().iY;
      else
	iOrigin.iY = bbox.Max().iY;
    }
  }

  // iCanvas->setCursor(QCursor(PointingHandCursor));
  // begin with identity matrix
  return true;
}

/*! If there is a selected object close enough, return true.
  Otherwise, check whether the closest object is close enough.
  If so, select it and return true.
  If not, return whether the page has a selection at all. */
bool IpeTransforming::UpdateCloseSelection(int selectDistance)
{
  double d = selectDistance / iCanvas->Zoom();
  return iPage->UpdateCloseSelection(iMouseDown, d, false, iView);
}

void IpeTransforming::Draw(QPaintEvent *, QPainter *qPainter) const
{
  qPainter->setPen(Qt::darkGreen);

  IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter);
  painter.Transform(iCanvas->CanvasTfm());
  painter.Push();
  painter.Transform(iTransform);
  for (IpePage::const_iterator it = iPage->begin(); it != iPage->end(); ++it) {
    if (it->Select())
      it->Object()->Draw(painter);
  }
  painter.Pop();
}

void IpeTransforming::MouseRelease(QMouseEvent *)
{
  Compute(iCanvas->Pos());
  QString s;
  switch (iType) {
  case EMove:
    s = QObject::tr("object move");
    break;
  case ERotate:
    s = QObject::tr("rotation");
    break;
  case EScale:
    s = QObject::tr("scaling");
    break;
  case EStretch:
    s = QObject::tr("stretching");
    break;
  }

  iServices->OvSvcAddUndoItem(new IpePage(*iPage), s);
  // Update the objects
  for (IpePage::iterator it = iPage->begin(); it != iPage->end(); ++it) {
    if (it->Select() != IpePgObject::ENone)
      it->Transform(iTransform);
  }
  iPage->SetEdited(true);
  iCanvas->FinishOverlay();
}

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

// compute iTransform
void IpeTransforming::Compute(const IpeVector &v1)
{
  IpeVector u0 = iMouseDown - iOrigin;
  IpeVector u1 = v1 - iOrigin;

  switch (iType) {
  case EMove:
    if (iWithShift) {
      IpeVector d = v1 - iMouseDown;
      if (IpeAbs(d.iX) > IpeAbs(d.iY))
	iTransform = IpeMatrix(IpeVector(d.iX, 0));
      else
	iTransform = IpeMatrix(IpeVector(0, d.iY));
    } else
      iTransform = IpeMatrix(v1 - iMouseDown);
    break;
  case ERotate:
    iTransform = (IpeMatrix(iOrigin) *
		  IpeLinear(IpeAngle(u1.Angle() - u0.Angle())) *
		  IpeMatrix(-iOrigin));
    break;
  case EScale:
    {
      double factor = sqrt(u1.SqLen() / u0.SqLen());
      iTransform = (IpeMatrix(iOrigin) *
		    IpeLinear(factor, 0, 0, factor) *
		    IpeMatrix(-iOrigin));
    }
    break;
  case EStretch:
    {
      double xfactor = u0.iX == 0.0 ? 1.0 : u1.iX / u0.iX;
      double yfactor = u0.iY == 0.0 ? 1.0 : u1.iY / u0.iY;
      iTransform = (IpeMatrix(iOrigin) *
		    IpeLinear(xfactor, 0, 0, yfactor) *
		    IpeMatrix(-iOrigin));
    }
    break;
  }
}

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