// --------------------------------------------------------------------
// The group object
// --------------------------------------------------------------------
/*

    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 "ipegroup.h"
#include "ipepainter.h"
#include "ipevisitor.h"
#include "ipetext.h"

/*! \class IpeGroup
  \ingroup obj
  \brief The group object.

  Ipe objects can be grouped together, and the resulting composite can
  be used like any Ipe object.

  This is an application of the "Composite" pattern.
*/

//! Create empty group (objects added later).
IpeGroup::IpeGroup()
  : IpeFillable()
{
  iImp = new Imp;
  iImp->iRefCount = 1;
  iImp->iHasMarks = false;
  iImp->iHasTexts = false;
  iMarkShape = 0; // null
}

//! Create empty group with these attributes (objects added later).
IpeGroup::IpeGroup(IpeRepository *rep, const IpeXmlAttributes &attr)
  : IpeFillable(rep, attr)
{
  iImp = new Imp;
  iImp->iRefCount = 1;
  iImp->iHasMarks = false;
  iImp->iHasTexts = false;
  IpeString str;
  if (attr.Has("marksize", str))
    iMarkSize = rep->MakeScalar(IpeAttribute::EMarkSize, str);
  if (attr.Has("textsize", str))
    iTextSize = rep->MakeScalar(IpeAttribute::EMarkSize, str);
  if (attr.Has("markshape", str))
    iMarkShape = IpeLex(str).GetInt();
  else
    iMarkShape = 0; // null
}

//! Copy constructor. Constant time --- components are not copied!
IpeGroup::IpeGroup(const IpeGroup &rhs)
  : IpeFillable(rhs)
{
  iImp = rhs.iImp;
  iImp->iRefCount++;
  iTextSize = rhs.iTextSize;
  iMarkSize = rhs.iMarkSize;
  iMarkShape = rhs.iMarkShape;
}

//! Destructor.
IpeGroup::~IpeGroup()
{
  if (iImp->iRefCount == 1)
    delete iImp;
  else
    iImp->iRefCount--;
}

//! Assignment operator (constant-time).
IpeGroup &IpeGroup::operator=(const IpeGroup &rhs)
{
  if (this != &rhs) {
    if (iImp->iRefCount == 1)
      delete iImp;
    else
      iImp->iRefCount--;
    iImp = rhs.iImp;
    iImp->iRefCount++;
    iTextSize = rhs.iTextSize;
    iMarkSize = rhs.iMarkSize;
    iMarkShape = rhs.iMarkShape;
    IpeFillable::operator=(rhs);
  }
  return *this;
}

//! Clone a group object (constant-time).
IpeObject *IpeGroup::Clone() const
{
  return new IpeGroup(*this);
}

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

class IpeGroupChecker : public IpeVisitor {
public:
  IpeGroupChecker(bool &hasTexts, bool &hasMarks)
    : iHasTexts(hasTexts), iHasMarks(hasMarks) { /* nothing */ }
  virtual void VisitMark(const IpeMark *) { iHasMarks = true; }
  virtual void VisitText(const IpeText *) { iHasTexts = true; }
  virtual void VisitGroup(const IpeGroup *obj) {
    iHasTexts = iHasTexts || obj->HasTexts();
    iHasMarks = iHasMarks || obj->HasMarks();
  }
private:
  bool &iHasTexts;
  bool &iHasMarks;
};

//! Add an object.
/*! This will panic if the object shares its implementation!
  The method is only useful right after construction of the group. */
void IpeGroup::push_back(const IpeObject *obj)
{
  assert(iImp->iRefCount == 1);
  iImp->iObjects.push_back(obj);
  IpeGroupChecker checker(iImp->iHasTexts, iImp->iHasMarks);
  checker(obj);
}

//! Save all the components, one by one, in XML format.
void IpeGroup::SaveComponentsAsXml(IpePainter &painter,
				   IpeStream &stream) const
{
  ApplyAttributes(painter);
  if (!painter.MarkSize())
    painter.SetMarkSize(iMarkSize);
  if (!painter.MarkShape())
    painter.SetMarkShape(iMarkShape);
  if (!painter.TextSize())
    painter.SetTextSize(iTextSize);
  for (const_iterator it = begin(); it != end(); ++it)
    (*it)->SaveAsXml(painter, stream, IpeString());
  painter.Pop();
}

//! Call VisitGroup of visitor.
void IpeGroup::Accept(IpeVisitor &visitor) const
{
  visitor.VisitGroup(this);
}

void IpeGroup::SaveAsXml(IpePainter &painter, IpeStream &stream,
			 IpeString layer) const
{
  stream << "<group";
  SaveAttributesAsXml(painter, stream, layer);
  SaveFillAttributesAsXml(painter, stream);
  SaveGroupAttributesAsXml(painter, stream);
  stream << ">\n";
  SaveComponentsAsXml(painter, stream);
  stream << "</group>\n";
}

//! Write text and mark attributes to XML stream.
void IpeGroup::SaveGroupAttributesAsXml(IpePainter &painter,
					IpeStream &stream) const
{
  const IpeRepository *rep = painter.StyleSheet()->Repository();

  if (HasTexts() && painter.TextSize().IsNull() && !TextSize().IsNull())
    stream << " textsize=\"" << rep->String(TextSize()) << "\"";
  if (HasMarks()) {
    if (painter.MarkSize().IsNull() && !MarkSize().IsNull())
      stream << " marksize=\"" << rep->String(MarkSize()) << "\"";
    if (!painter.MarkShape() && MarkShape())
      stream << " markshape=\"" << MarkShape() << "\"";
  }
}

void IpeGroup::Draw(IpePainter &painter) const
{
  ApplyAttributes(painter);
  if (!painter.MarkSize())
    painter.SetMarkSize(iMarkSize);
  if (!painter.MarkShape())
    painter.SetMarkShape(iMarkShape);
  for (const_iterator it = begin(); it != end(); ++it)
    (*it)->Draw(painter);
  painter.Pop();
}

void IpeGroup::AddToBBox(IpeRect &box, const IpeMatrix &m) const
{
  IpeMatrix m1 = m * Matrix();
  for (const_iterator it = begin(); it != end(); ++it) {
    (*it)->AddToBBox(box, m1);
  }
}

double IpeGroup::Distance(const IpeVector &v, const IpeMatrix &m,
			  double bound) const
{
  double d = bound;
  double d1;
  IpeMatrix m1 = m * Matrix();
  for (const_iterator it = begin(); it != end(); ++it) {
    if ((d1 = (*it)->Distance(v, m1, d)) < d)
      d = d1;
  }
  return d;
}

void IpeGroup::SnapVtx(const IpeVector &mouse, const IpeMatrix &m,
		       IpeVector &pos, double &bound) const
{
  IpeMatrix m1 = m * Matrix();
  for (const_iterator it = begin(); it != end(); ++it)
    (*it)->SnapVtx(mouse, m1, pos, bound);
}

void IpeGroup::SnapBnd(const IpeVector &mouse, const IpeMatrix &m,
		       IpeVector &pos, double &bound) const
{
  IpeMatrix m1 = m * Matrix();
  for (const_iterator it = begin(); it != end(); ++it)
    (*it)->SnapBnd(mouse, m1, pos, bound);
}

//! Set size of mark objects in the group.
/*! This is a no op if there are no mark objects in the group. */
void IpeGroup::SetMarkSize(IpeAttribute size)
{
  if (HasMarks())
    iMarkSize = size;
}

//! Set shape of mark objects in the group.
/*! This is a no op if there are no mark objects in the group. */
void IpeGroup::SetMarkShape(int shape)
{
  if (HasMarks())
    iMarkShape = shape;
}

void IpeGroup::CheckStyle(const IpeStyleSheet *sheet,
			   IpeAttributeSeq &seq) const
{
  IpeFillable::CheckStyle(sheet, seq);
  CheckSymbol(iTextSize, sheet, seq);
  CheckSymbol(iMarkSize, sheet, seq);
  for (const_iterator it = begin(); it != end(); ++it)
    (*it)->CheckStyle(sheet, seq);
}

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

class XFormInvalidator : public IpeVisitor {
public:
  virtual void VisitGroup(const IpeGroup *obj);
  virtual void VisitText(const IpeText *obj);
};

void XFormInvalidator::VisitGroup(const IpeGroup *obj)
{
  for (IpeGroup::const_iterator it = obj->begin(); it != obj->end(); ++it)
    (*it)->Accept(*this);
}

void XFormInvalidator::VisitText(const IpeText *obj)
{
  obj->SetXForm(0);
}

//! Set font size of text objects in the group.
/*! This is a no op if there are no text objects in the group.  It
  invalidates (and destroys) the XForms stored with all text objects
  in the group. */
void IpeGroup::SetTextSize(IpeAttribute size)
{
  if (HasTexts() && size != iTextSize) {
    iTextSize = size;
    XFormInvalidator xin;
    xin(this);
  }
}

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