// --------------------------------------------------------------------
// The Ipe object type
// --------------------------------------------------------------------
/*

    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.

*/

/*! \mainpage The Ipe library (Ipelib) documentation

   The Ipe program is a relatively thin layer on top of the "Ipe
   library" (Ipelib).  Ipelib provides the geometric primitives and
   implements all the geometric objects that appear in Ipe.  Most
   tasks related to modifying an Ipe document are actually performed
   by Ipelib.

   Ipelib can easily be reused by other programs to read, write, and
   modify Ipe documents. It requires only the standard C++ library
   (including the STL), and the zlib compression library.

   Authors of ipelets (plug-ins for Ipe) will need this documentation,
   as Ipelets have to be linked with Ipelib.

*/

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

/*! \defgroup obj Ipe Objects
  \brief The Ipe object model.

  This module deals with the actual objects inside an Ipe document.
  All Ipe objects are derived from IpeObject.

*/

/*! \defgroup ipelet The Ipelet interface
  \brief Implementation of Ipe plugins.

  Ipelets are dynamically loaded plugins for Ipe. An Ipelet contains a
  single class derived from Ipelet.  It can use services of the Ipe
  program through an IpeletHelper object.
*/

/*! \defgroup doc Ipe Document
  \brief The classes managing an Ipe document.

  The main class, IpeDocument, represents an entire Ipe document,
  and allows you to load, save, access, and modify such a document.

  Various classes correspond to pages and layers.
*/

#include "ipegeo.h"
#include "ipecolor.h"
#include "ipeobj.h"
#include "ipepainter.h"

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

/*! \class IpeAllAttributes
  \ingroup attr
  \brief Collection of all object attributes.
*/

//! Constructor makes all null attributes.
IpeAllAttributes::IpeAllAttributes()
{
  iForwardArrow = false;
  iBackwardArrow = false;
  iTransformable = false;
  iMarkShape = 1;
}

/*! \class IpeObject
  \ingroup obj
  \brief Base class for all Ipe objects, composite or leaf.

  All Ipe objects are derived from this class.  It provides
  functionality common to all objects, and carries the standard
  attributes.

  All IpeObject's provide a constant time copy constructor (and a
  virtual IpeObject::Clone() method).  Objects of non-constant size
  realize this by separating the implementation and using reference
  counting.  In particular, copying a composite objects does not
  create new copies of the components.

  The common attributes are stroke and fill color, line style and
  width, and the transformation matrix for coordinates.
*/

//! Construct from XML stream.
IpeObject::IpeObject(IpeRepository *rep, const IpeXmlAttributes &attr)
{
  IpeString str;
  if (attr.Has("stroke", str))
    iStroke = rep->MakeColor(str);
  if (attr.Has("matrix", str))
    iMatrix = IpeMatrix(str);
}

/*! Create object by taking stroke color from \a attr and setting
  identity matrix. */
IpeObject::IpeObject(const IpeAllAttributes &attr)
{
  iStroke = attr.iStroke;
}

/*! Create object with null stroke and identity matrix. */
IpeObject::IpeObject()
{
  // nothing
}

//! Copy constructor.
IpeObject::IpeObject(const IpeObject &rhs)
{
  iStroke = rhs.iStroke;
  iMatrix = rhs.iMatrix;
}

//! Pure virtual destructor.
IpeObject::~IpeObject()
{
  // nothing
}

//! Write layer, stroke and matrix to XML stream.
void IpeObject::SaveAttributesAsXml(IpePainter &painter, IpeStream &stream,
				    IpeString layer) const
{
  const IpeRepository *rep = painter.StyleSheet()->Repository();
  if (!layer.empty())
    stream << " layer=\"" << layer << "\"";
  // colors are not applied if they are already set in the painter
  // furthermore, void doesn't need to be saved
  if (painter.Stroke().IsNull() && !iStroke.IsNullOrVoid())
    stream << " stroke=\"" << rep->String(iStroke) << "\"";
  if (!iMatrix.IsIdentity())
    stream << " matrix=\"" << iMatrix << "\"";
}

//! Return pointer to this object if it is an IpeGroup, 0 otherwise.
IpeGroup *IpeObject::AsGroup()
{
  return 0;
}

//! Return pointer to this object if it is an IpeFillable, 0 otherwise.
IpeFillable *IpeObject::AsFillable()
{
  return 0;
}

//! Return pointer to this object if it is an IpeText, 0 otherwise.
IpeText *IpeObject::AsText()
{
  return 0;
}

//! Return pointer to this object if it is an IpeMark, 0 otherwise.
IpeMark *IpeObject::AsMark()
{
  return 0;
}

//! Return pointer to this object if it is an IpePath, 0 otherwise.
IpePath *IpeObject::AsPath()
{
  return 0;
}

//! Return pointer to this object if it is an IpeImage , 0 otherwise.
IpeImage *IpeObject::AsImage()
{
  return 0;
}

//! Return pointer to this object if it is an IpeRef, 0 otherwise.
IpeReference *IpeObject::AsReference()
{
  return 0;
}

//! Set stroke color.
void IpeObject::SetStroke(IpeAttribute stroke)
{
  iStroke = stroke;
}

//! Set the transformation matrix.
/*! Don't use this on the IpeObject in an IpePgObject, because it
  wouldn't invalidate its bounding box.  Call IpePgObject::Transform
  instead. */
void IpeObject::SetMatrix(const IpeMatrix &matrix)
{
  iMatrix = matrix;
}

//! Extend \a box to include the graphic representation of the object.
/*! Unlike AddToBBox, this function takes into account the graphic
  representation of the object, that is, its line width, arrow size,
  mark size, etc.
  The default implementation calls AddToBBox.
*/
void IpeObject::AddToGraphicBBox(IpeRect &box, IpeStyleSheet *,
				 IpeMatrix &m) const
{
  AddToBBox(box, m);
}

//! Check all symbolic attributes.
void IpeObject::CheckStyle(const IpeStyleSheet *sheet,
			    IpeAttributeSeq &seq) const
{
  CheckSymbol(iStroke, sheet, seq);
}

//! Snap to nearby vertex.
/*! If distance between \a mouse and \a v is less than \a bound,
  set \a pos to \a v and \a bound to the distance, and return \c true. */
bool IpeObject::SnapVertex(const IpeVector &mouse, const IpeVector &v,
			   IpeVector &pos, double &bound)
{
  double d = (mouse - v).Len();
  if (d < bound) {
    pos = v;
    bound = d;
    return true;
  }
  return false;
}

void IpeObject::CheckSymbol(IpeAttribute attr, const IpeStyleSheet *sheet,
			    IpeAttributeSeq &seq)
{
  if (attr.IsSymbolic() && !sheet->Find(attr)) {
    IpeAttributeSeq::const_iterator it =
      std::find(seq.begin(), seq.end(), attr);
    if (it == seq.end())
      seq.push_back(attr);
  }
}

//! Compute boundary snapping position for transformed object.
/*! Looks only for positions closer than \a bound.
  If successful, modify \a pos and \a bound.
  The default implementation does nothing.
*/
void IpeObject::SnapBnd(const IpeVector &/* mouse */, const IpeMatrix &/* m */,
			IpeVector &/* pos */, double &/* bound */) const
{
  // nothing
}

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

/*! \class IpeFillable
  \ingroup obj
  \brief Base class for IpeFillable's with fill color and line style.
*/

//! Construct from XML stream.
IpeFillable::IpeFillable(IpeRepository *rep, const IpeXmlAttributes &attr)
  : IpeObject(rep, attr)
{
  IpeString str;
  if (attr.Has("fill", str))
    iFill = rep->MakeColor(str);
  if (attr.Has("dash", str))
    iDashStyle = rep->MakeDashStyle(str);
  if (attr.Has("pen", str))
    iLineWidth = rep->MakeScalar(IpeAttribute::ELineWidth, str);
  if (attr.Has("cap", str))
    iStrokeStyle.SetCap(IpeLex(str).GetInt());
  if (attr.Has("join", str))
    iStrokeStyle.SetJoin(IpeLex(str).GetInt());
  if (attr.Has("fillrule", str))
    iStrokeStyle.SetWindRule(str == "wind");
}

/*! Create object by taking attributes from \a attr and setting
  identity matrix. */
IpeFillable::IpeFillable(const IpeAllAttributes &attr)
  : IpeObject(attr)
{
  iFill = attr.iFill;
  iDashStyle = attr.iDashStyle;
  iLineWidth = attr.iLineWidth;
}

//! Create object with all null attributes.
IpeFillable::IpeFillable()
  : IpeObject()
{
  // nothing
}

//! Copy constructor.
IpeFillable::IpeFillable(const IpeFillable &rhs)
  : IpeObject(rhs)
{
  iFill = rhs.iFill;
  iLineWidth = rhs.iLineWidth;
  iDashStyle = rhs.iDashStyle;
  iStrokeStyle = rhs.iStrokeStyle;
}

//! Write common attributes to XML stream.
void IpeFillable::SaveFillAttributesAsXml(IpePainter &painter,
					  IpeStream &stream) const
{
  const IpeRepository *rep = painter.StyleSheet()->Repository();

  // colors are not applied if they are already set in the painter
  // furthermore, void doesn't need to be saved
  if (!painter.Fill() && !iFill.IsNullOrVoid())
    stream << " fill=\"" << rep->String(iFill) << "\"";
  if (!painter.DashStyle() && !iDashStyle.IsNullOrSolid())
    stream << " dash=\"" << rep->String(iDashStyle) << "\"";
  if (!painter.LineWidth() && iLineWidth)
    stream << " pen=\"" << rep->String(iLineWidth) << "\"";
  if (!painter.LineCap() && iStrokeStyle.Cap())
    stream << " cap=\"" << iStrokeStyle.Cap().Index() << "\"";
  if (!painter.LineJoin() && iStrokeStyle.Join())
    stream << " join=\"" << iStrokeStyle.Join().Index() << "\"";
  if (!painter.WindRule() && iStrokeStyle.WindRule()) {
    if (iStrokeStyle.WindRule().Index())
      stream << " fillrule=\"wind\"";
    else
      stream << " fillrule=\"eofill\"";
  }
}

//! Return pointer to this object.
IpeFillable *IpeFillable::AsFillable()
{
  return this;
}

//! Save graphics state, and set new attributes.
/*! Color, line width, and dash style are only set if the current
  value in the graphics state is null.

  Line cap, line join, and miter limit are only set if the value to be
  set is not the default. (I.e. a default value means to inherit the
  parent value.)

  Don't forget to pop the graphics state after doing your drawing!
*/
void IpeFillable::ApplyAttributes(IpePainter &painter) const
{
  painter.Push();
  if (!painter.Stroke())
    painter.SetStroke(Stroke());
  if (!painter.Fill())
    painter.SetFill(iFill);
  if (!painter.DashStyle())
    painter.SetDashStyle(iDashStyle);
  if (!painter.LineWidth())
    painter.SetLineWidth(iLineWidth);
  if (!painter.LineCap())
    painter.SetLineCap(iStrokeStyle.Cap());
  if (!painter.LineJoin())
    painter.SetLineJoin(iStrokeStyle.Join());
  if (!painter.WindRule())
    painter.SetWindRule(iStrokeStyle.WindRule());
  painter.Transform(Matrix());
}

//! Set fill color.
void IpeFillable::SetFill(IpeAttribute fill)
{
  iFill = fill;
}

//! Set line width.
void IpeFillable::SetLineWidth(IpeAttribute lw)
{
  iLineWidth = lw;
}

//! Set dash style.
void IpeFillable::SetDashStyle(IpeAttribute dash)
{
  iDashStyle = dash;
}

//! Set line cap and join.
void IpeFillable::SetStrokeStyle(IpeStrokeStyle attr)
{
  iStrokeStyle = attr;
}

//! Check all symbolic attributes.
void IpeFillable::CheckStyle(const IpeStyleSheet *sheet,
			      IpeAttributeSeq &seq) const
{
  IpeObject::CheckStyle(sheet, seq);
  CheckSymbol(iFill, sheet, seq);
  CheckSymbol(iDashStyle, sheet, seq);
  CheckSymbol(iLineWidth, sheet, seq);
}

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

/*! \class IpePgObject
  \ingroup obj
  \brief A (selectable) object on an IpePage.

  Adds selection status and layer to IpeObject.
*/

//! Copy constructor clones object.
IpePgObject::IpePgObject(const IpePgObject &rhs)
  : iSelect(rhs.iSelect), iLayer(rhs.iLayer)
{
  iObject = rhs.iObject->Clone();
}

IpePgObject &IpePgObject::operator=(const IpePgObject &rhs)
{
  if (this != &rhs) {
    delete iObject;
    iSelect = rhs.iSelect;
    iLayer = rhs.iLayer;
    iObject = rhs.iObject->Clone();
  }
  return *this;
}

//! Destructor.
IpePgObject::~IpePgObject()
{
  delete iObject;
}

//! Replace the object.
void IpePgObject::ReplaceObject(IpeObject *obj)
{
  delete iObject;
  iObject = obj;
  InvalidateBBox();
}

//! Return distance between object and \a v.
/*! But may just return \a bound if the distance is larger.
  This function uses the cached bounded box, and is faster than
  calling IpeObject::Distance directly. */
double IpePgObject::Distance(const IpeVector &v, double bound) const
{
  if (BBox().CertainClearance(v, bound))
    return bound;
  return iObject->Distance(v, IpeMatrix(), bound);
}

//! Transform the object.
/*! Use this function instead of calling IpeObject::SetMatrix directly,
  as the latter doesn't invalidate the cached bounding box. */
void IpePgObject::Transform(const IpeMatrix &m)
{
  InvalidateBBox();
  iObject->SetMatrix(m * iObject->Matrix());
}

//! Invalidate the bounding box (the object is somehow changed).
void IpePgObject::InvalidateBBox() const
{
  iBBox = IpeRect();
}

//! Return a bounding box for the object.
/*! IpePgObject caches the box the first time it is computed.

  Make sure you call IpePgObject::Transform instead of
  IpeObject::SetMatrix, as the latter would not invalidate the
  bounding box. */
IpeRect IpePgObject::BBox() const
{
  if (iBBox.IsEmpty())
    iObject->AddToBBox(iBBox, IpeMatrix());
  return iBBox;
}

//! Compute possible vertex snapping position.
/*! Looks only for positions closer than \a bound.
  If successful, modifies \a pos and \a bound. */
void IpePgObject::SnapVtx(const IpeVector &mouse,
			  IpeVector &pos, double &bound) const
{
  if (BBox().CertainClearance(mouse, bound))
    return;
  iObject->SnapVtx(mouse, IpeMatrix(), pos, bound);
}

//! Compute possible boundary snapping position.
/*! Looks only for positions closer than \a bound.
  If successful, modifies \a pos and \a bound. */
void IpePgObject::SnapBnd(const IpeVector &mouse,
			  IpeVector &pos, double &bound) const
{
  if (BBox().CertainClearance(mouse, bound))
    return;
  iObject->SnapBnd(mouse, IpeMatrix(), pos, bound);
}

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