// --------------------------------------------------------------------
// A page of a document.
// --------------------------------------------------------------------
/*
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 "ipepage.h"
#include "ipevisitor.h"
#include "ipegroup.h"
#include "ipepath.h"
#include "ipetext.h"
#include "ipemark.h"
#include "ipepainter.h"
#include "ipeiml.h"
#include "ipeutils.h"
// --------------------------------------------------------------------
/*! \class IpeLayer
\ingroup doc
\brief A layer of an IpePage.
The objects on one IpePage can belong to any number of layers.
Layers are orthogonal to the back-to-front ordering of objects, so a
"layer" is just another attribute of the object.
Layers have several attributes:
- They may be editable or locked. Objects in a locked layer cannot
be selected, and a locked layer cannot be made active in the Ipe
UI. This more or less means that the contents of such a layer
cannot be modified---but that's a consequence of the UI, Ipelib
contains no special handling of locked layers.
- A layer may be dimmed.
- A layer may have snapping on or off---objects will behave
magnetically only if their layer has snapping on.
The PDF output generated for an IpePage depends on its \e
views. Each view may list a number of layers to be displayed at that
stage. Multiple \e views may show different subsets of layers.
*/
//! Construct with name. Default attributes.
IpeLayer::IpeLayer(IpeString name)
{
iName = name;
iFlags = 0;
}
//! Construct from a single XML tag.
IpeLayer::IpeLayer(const IpeXmlAttributes &attr)
{
iName = attr["name"];
iFlags = 0;
if (attr["visible"] == "dim")
iFlags |= EDim;
if (attr["edit"] == "no")
iFlags |= ELocked;
}
//! Write a single XML tag describing this layer.
void IpeLayer::SaveAsXml(IpeStream &stream) const
{
stream << "\n";
}
//! Set dimming.
void IpeLayer::SetDimmed(bool flag)
{
iFlags &= ~EDim;
if (flag) iFlags |= EDim;
}
//! Set locking.
void IpeLayer::SetLocked(bool flag)
{
iFlags &= ~ELocked;
if (flag) iFlags |= ELocked;
}
//! Set snapping.
void IpeLayer::SetSnapping(bool flag)
{
iFlags &= ~ENoSnapping;
if (!flag) iFlags |= ENoSnapping;
}
// --------------------------------------------------------------------
/*! \class IpeView
\ingroup doc
\brief A view of the page (set of layers, duration, effect, transition style)
An IpePage contains a whole list of these.
*/
//! Construct default view.
IpeView::IpeView()
{
iEffect = ENormal;
iDuration = 0;
iTransitionTime = 1;
}
//! Create a view from XML tag.
IpeView::IpeView(const IpeXmlAttributes &attr)
{
iEffect = ENormal;
iDuration = 0;
iTransitionTime = 1;
IpeLex st(attr["layers"]);
do {
iLayers.push_back(st.NextToken());
st.SkipWhitespace();
} while (!st.Eos());
iActive = attr["active"];
IpeString str;
if (attr.Has("duration", str))
iDuration = IpeLex(str).GetInt();
if (attr.Has("transition", str))
iTransitionTime = IpeLex(str).GetInt();
if (attr.Has("effect", str))
iEffect = TEffect(IpeLex(str).GetInt());
}
//! Write a single XML tag representing this view.
void IpeView::SaveAsXml(IpeStream &stream) const
{
stream << "::const_iterator it = iLayers.begin();
it != iLayers.end(); ++it) {
if (it != iLayers.begin())
stream << " ";
stream << *it;
}
stream << "\"";
if (!iActive.empty())
stream << " active=\"" << iActive << "\"";
if (iDuration > 0)
stream << " duration=\"" << iDuration << "\"";
if (iEffect != ENormal)
stream << " effect=\"" << int(iEffect) << "\"";
if (iTransitionTime > 1)
stream << " transition=\"" << iTransitionTime << "\"";
stream << "/>\n";
}
//! Write part of page dictionary.
/*! Write part of page dictionary indicating effect,
including the two keys /Dur and /Trans. */
void IpeView::PageDictionary(IpeStream &stream) const
{
if (iDuration > 0)
stream << "/Dur " << iDuration << "\n";
if (iEffect != ENormal) {
stream << "/Trans << /D " << iTransitionTime << " /S ";
switch (iEffect) {
case ESplitHI: stream << "/Split /Dm /H /M /I"; break;
case ESplitHO: stream << "/Split /Dm /H /M /O"; break;
case ESplitVI: stream << "/Split /Dm /V /M /I"; break;
case ESplitVO: stream << "/Split /Dm /V /M /O"; break;
case EBlindsH: stream << "/Blinds /Dm /H"; break;
case EBlindsV: stream << "/Blinds /Dm /V"; break;
case EBoxI: stream << "/Box /M /I"; break;
case EBoxO: stream << "/Box /M /O"; break;
case EWipeLR: stream << "/Wipe /Di 0"; break;
case EWipeBT: stream << "/Wipe /Di 90"; break;
case EWipeRL: stream << "/Wipe /Di 180"; break;
case EWipeTB: stream << "/Wipe /Di 270"; break;
case EDissolve: stream << "/Dissolve"; break;
case EGlitterLR: stream << "/Glitter /Di 0"; break;
case EGlitterTB: stream << "/Glitter /Di 270"; break;
case EGlitterD: stream << "/Glitter /Di 315"; break;
case ENormal: break; // to satisfy compiler
}
stream << " >>\n";
}
}
//! Does this view contain the layer \a name?
bool IpeView::HasLayer(IpeString name) const
{
return (std::find(iLayers.begin(), iLayers.end(), name) != iLayers.end());
}
//! Remove the layer \a name from this view.
void IpeView::DeleteLayer(IpeString name)
{
std::vector::iterator it =
std::find(iLayers.begin(), iLayers.end(), name);
if (it != iLayers.end())
iLayers.erase(it);
}
//! Add layer \a name to this view.
/*! It is okay if the view already contains this layer. */
void IpeView::AddLayer(IpeString name)
{
if (!HasLayer(name))
iLayers.push_back(name);
}
// --------------------------------------------------------------------
/*! \class IpePage
\ingroup doc
\brief An Ipe document page.
Its main ingredients are a sequence of IpePgObjects, a list of
IpeLayers, and a list of IpeViews.
If you need to keep track of whether a document has been modified,
you have to call SetEdited(true) whenever you modify an IpePgObject.
The functions to modify the layer sequence and the views set the
edited flag themselves.
*/
//! The default constructor creates a new empty page.
/*! This page still needs a layer and a view to be usable! */
IpePage::IpePage()
{
iEdited = false;
iGridSize = 0;
}
// --------------------------------------------------------------------
//! Save page in XML format.
void IpePage::SaveAsXml(IpePainter &painter, IpeStream &stream) const
{
stream << " 0)
stream << " gridsize=\"" << iGridSize << "\"";
if (!iSection[0].empty()) {
stream << " section=\"";
stream.PutXmlString(iSection[0]);
stream << "\"";
}
if (!iSection[1].empty()) {
stream << " subsection=\"";
stream.PutXmlString(iSection[1]);
stream << "\"";
}
stream << ">\n";
for (int i = 0; i < CountLayers(); ++i)
Layer(i).SaveAsXml(stream);
for (int i = 0; i < CountViews(); ++i)
View(i).SaveAsXml(stream);
int currentLayer = -1;
for (const_iterator it = begin(); it != end(); ++it) {
IpeString layer;
if (it->Layer() != currentLayer) {
currentLayer = it->Layer();
layer = Layer(currentLayer).iName;
}
it->Object()->SaveAsXml(painter, stream, layer);
}
stream << "\n";
}
#if 0
//! Modify a layer.
/*! Sets the edited flag. */
void IpePage::SetLayer(int index, const IpeLayer &layer)
{
assert(0 <= index && index < int(iLayers.size()));
iLayers[index] = layer;
iEdited = true;
}
#endif
//! Add a new layer at \a index (at the end if \a index is negative).
/*! Returns index of new layer, and sets edited flag.
Layer numbers of all objects on page are adjusted if necessary.
*/
int IpePage::AddLayer(const IpeLayer &layer, int index)
{
iEdited = true;
if (index < 0) {
iLayers.push_back(layer);
return (iLayers.size() - 1);
} else {
IpeLayerSeq::iterator it = iLayers.begin() + index;
iLayers.insert(it, layer);
// adjust all objects!
for (iterator pit = begin(); pit != end(); ++pit) {
int l = pit->Layer();
if (l >= index)
pit->SetLayer(l + 1);
}
return index;
}
}
//! Find layer with given name.
/*! Returns -1 if not found. */
int IpePage::FindLayer(IpeString name) const
{
for (int i = 0; i < CountLayers(); ++i)
if (Layer(i).Name() == name)
return i;
return -1;
}
const char * const layerNames[] = {
"alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta",
"theta", "iota", "kappa", "lambda", "mu", "nu", "xi",
"omicron", "pi", "rho", "sigma", "tau", "phi", "chi", "xi",
"omega" };
//! Create a new layer with unique name.
/*! The layer is inserted at index \a index, or appended if
\a index is negative.
Returns index of new layer. */
int IpePage::NewLayer(int index)
{
iEdited = true;
for (int i = 0; i < int(sizeof(layerNames)/sizeof(const char *)); ++i) {
if (FindLayer(layerNames[i]) < 0)
return AddLayer(IpeLayer(layerNames[i]), index);
}
char name[20];
int i = 1;
for (;;) {
std::sprintf(name, "alpha%d", i);
if (FindLayer(name) < 0)
return AddLayer(IpeLayer(name), index);
i++;
}
}
//! Deletes an empty layer from the page.
/*! All objects are adjusted. Panics if there are objects in the
deleted layer, of if it is the only layer.
The layer is also removed from all views.
*/
void IpePage::DeleteLayer(int index)
{
assert(iLayers.size() > 1);
for (iterator it = begin(); it != end(); ++it) {
int k = it->Layer();
assert(k != index);
if (k > index)
it->SetLayer(k-1);
}
// remove from all views
IpeString name = iLayers[index].Name();
for (int i = 0; i < CountViews(); ++i)
iViews[i].DeleteLayer(name);
iLayers.erase(iLayers.begin() + index);
iEdited = true;
}
//! Does a view exist where this layer is active?
bool IpePage::IsLayerActiveInView(int index) const
{
IpeString name = iLayers[index].Name();
for (int i = 0; i < CountViews(); ++i)
if (iViews[i].Active() == name)
return true;
return false;
}
//! Set the views for the page.
/*! This sets the edited flag. */
void IpePage::SetViews(const IpeViewSeq &views)
{
iViews = views;
iEdited = true;
}
#if 0
//! Sets one view of the page.
/*! This sets the edited flag. */
void IpePage::SetView(int index, const IpeView &view)
{
assert(0 <= index && index < int(iViews.size()));
iViews[index] = view;
iEdited = true;
}
#endif
//! Add a view at position \a index.
/*! The view is appended at the end if \a index is negative. */
void IpePage::AddView(const IpeView &view, int index)
{
if (index < 0)
iViews.push_back(view);
else
iViews.insert(iViews.begin() + index, view);
iEdited = true;
}
//! Delete view at \a index.
void IpePage::DeleteView(int index)
{
iViews.erase(iViews.begin() + index);
iEdited = true;
}
class TextBoxVisitor : public IpeVisitor {
public:
virtual void VisitText(const IpeText *obj);
public:
IpeRect iR;
IpeScalar iY;
};
void TextBoxVisitor::VisitText(const IpeText *t)
{
// look at all minipage objects that span the same horizontal extent
if (t->Type() == IpeText::ETextbox) {
IpeScalar bottom = (t->Matrix() * t->Position()).iY - t->TotalHeight();
if (bottom < iY)
iY = bottom;
}
}
//! Computes text box.
/*! Takes into account media size, margins, and text objects already
on the page .*/
IpeRect IpePage::TextBox(const IpeRect &media,
const IpeStyleSheet *sheet) const
{
IpeVector tl, br;
sheet->FindMargins(tl, br);
TextBoxVisitor vis;
vis.iR = IpeRect(media.Min() + IpeVector(tl.iX, br.iY),
media.Max() - IpeVector(br.iX, tl.iY));
vis.iY = vis.iR.Max().iY;
for (const_iterator it = begin(); it != end(); ++it)
vis(*it);
return IpeRect(vis.iR.Min(), IpeVector(vis.iR.Max().iX, vis.iY));
}
//! Set whether page has been edited.
void IpePage::SetEdited(bool edited)
{
iEdited = edited;
}
//! Set the section title at \a level.
/*! Level 0 is the section, level 1 the subsection.
This marks the page as edited. */
void IpePage::SetSection(int level, IpeString name)
{
iSection[level] = name;
iEdited = true;
}
//! Returns true iff any object on the page is selected.
bool IpePage::HasSelection() const
{
for (const_iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone)
return true;
}
return false;
}
//! Returns the primary selection, or end().
IpePage::iterator IpePage::PrimarySelection()
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() == IpePgObject::EPrimary)
return it;
}
return end();
}
//! Deselect all objects.
void IpePage::DeselectAll()
{
for (iterator it = begin(); it != end(); ++it)
it->SetSelect(IpePgObject::ENone);
}
//! Deselect all objects in this layer.
void IpePage::DeselectLayer(int layer)
{
for (iterator it = begin(); it != end(); ++it)
if (it->Layer() == layer)
it->SetSelect(IpePgObject::ENone);
}
//! Deselect all objects not in a layer of this view, or in a locked layer.
void IpePage::DeselectNotInView(int view)
{
std::vector layer;
MakeLayerTable(layer, view, true);
for (iterator it = begin(); it != end(); ++it)
if (!layer[it->Layer()])
it->SetSelect(IpePgObject::ENone);
}
/*! If no object is the primary selection, make the topmost secondary
selection the primary one. */
void IpePage::EnsurePrimarySelection()
{
for (IpePage::const_iterator it = begin(); it != end(); ++it) {
if (it->Select() == IpePgObject::EPrimary)
return;
}
IpePage::reverse_iterator it = rbegin();
while (it != rend() && it->Select() != IpePgObject::ESecondary)
++it;
if (it != rend())
it->SetSelect(IpePgObject::EPrimary);
}
//! Removes all selected objects from the page.
/*! They are added, in the same order, to \a seq. */
void IpePage::ExtractSelection(IpePgObjectSeq &seq)
{
iterator it1;
for (iterator it = begin(); it != end(); it = it1) {
it1 = it;
++it1;
if (it->Select() != IpePgObject::ENone) {
seq.push_back(*it);
erase(it);
iEdited = true;
}
}
}
//! Delete currently selected objects
void IpePage::Delete()
{
iterator it1;
for (iterator it = begin(); it != end(); it = it1) {
it1 = it;
++it1;
if (it->Select() != IpePgObject::ENone) {
erase(it);
iEdited = true;
}
}
}
//! Select all objects visible and not locked in the view.
void IpePage::SelectAll(int view)
{
std::vector layerVisible;
MakeLayerTable(layerVisible, view, true);
for (iterator it = begin(); it != end(); ++it) {
if (layerVisible[it->Layer()] && !it->Select())
it->SetSelect(IpePgObject::ESecondary);
}
EnsurePrimarySelection();
}
//! Select all objects in the given layer.
void IpePage::SelectAllInLayer(int layer)
{
DeselectAll();
IpePgObject::TSelect sel = IpePgObject::EPrimary;
for (iterator it = begin(); it != end(); ++it) {
if (it->Layer() == layer) {
it->SetSelect(sel);
sel = IpePgObject::ESecondary;
}
}
}
//! Group the selected objects together as a new group object.
/*! The new object is placed in \a layer. */
void IpePage::Group(int layer)
{
IpePgObjectSeq objs;
ExtractSelection(objs);
IpeGroup *group = new IpeGroup;
for (IpePgObjectSeq::iterator it = objs.begin(); it != objs.end(); ++it)
group->push_back(it->Object()->Clone());
push_back(IpePgObject(IpePgObject::EPrimary, layer, group));
iEdited = true;
}
//! Move selected objects to indicated layer.
void IpePage::MoveToLayer(int layer)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select())
it->SetLayer(layer);
}
iEdited = true;
}
//! Ungroup the primary selection, place objects in \a layer.
/*! Panics if no primary selection, returns false if primary selection
is not a group. */
bool IpePage::Ungroup(int layer)
{
iterator it = PrimarySelection();
const IpeGroup *group = it->Object()->AsGroup();
if (!group)
return false;
for (IpeGroup::const_iterator it1 = group->begin();
it1 != group->end(); ++it1) {
IpeObject *obj = (*it1)->Clone();
// apply attributes from group
if (group->Stroke())
obj->SetStroke(group->Stroke());
obj->SetMatrix(group->Matrix() * obj->Matrix());
IpeFillable *fobj = obj->AsFillable();
if (fobj) {
if (group->Fill())
fobj->SetFill(group->Fill());
if (group->LineWidth())
fobj->SetLineWidth(group->LineWidth());
if (group->DashStyle())
fobj->SetDashStyle(group->DashStyle());
}
if (group->TextSize()) {
if (obj->AsText())
obj->AsText()->SetSize(group->TextSize());
else if (obj->AsGroup())
obj->AsGroup()->SetTextSize(group->TextSize());
}
if (group->MarkSize()) {
if (obj->AsMark())
obj->AsMark()->SetSize(group->MarkSize());
else if (obj->AsGroup())
obj->AsGroup()->SetMarkSize(group->MarkSize());
}
if (group->MarkShape()) {
if (obj->AsMark())
obj->AsMark()->SetShape(group->MarkShape());
else if (obj->AsGroup())
obj->AsGroup()->SetMarkShape(group->MarkShape());
}
push_back(IpePgObject(IpePgObject::ESecondary, layer, obj));
}
erase(it);
iEdited = true;
EnsurePrimarySelection();
return true;
}
//! Move selected objects to front.
void IpePage::Front()
{
IpePgObjectSeq objs;
ExtractSelection(objs);
for (IpePgObjectSeq::iterator it = objs.begin();
it != objs.end(); ++it) {
push_back(*it);
iEdited = true;
}
}
//! Move selected objects to back.
void IpePage::Back()
{
IpePgObjectSeq objs;
ExtractSelection(objs);
iterator it1 = begin();
if (Layer(0).Name() == "background") {
// skip all objects in this layer
while (it1 != end() && it1->Layer() == 0)
++it1;
}
for (IpePgObjectSeq::reverse_iterator it = objs.rbegin();
it != objs.rend(); ++it) {
insert(it1, *it);
--it1;
iEdited = true;
}
}
//! Duplicate the selected objects into \a layer.
void IpePage::Duplicate(int layer)
{
// remember number of elements on page
iterator it = begin();
int n = size();
while (n-- > 0) {
if (it->Select()) {
push_back(IpePgObject(IpePgObject::ESecondary, layer,
it->Object()->Clone()));
it->SetSelect(IpePgObject::ENone); // unselect old
}
++it;
iEdited = true;
}
EnsurePrimarySelection();
}
//! Set stroke color of selected objects.
void IpePage::SetStroke(IpeAttribute color)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
it->Object()->SetStroke(color);
iEdited = true;
}
}
}
//! Set fill color of selected objects.
void IpePage::SetFill(IpeAttribute color)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpeFillable *obj = it->Object()->AsFillable();
if (obj) {
obj->SetFill(color);
iEdited = true;
}
}
}
}
//! Set line width of selected objects.
void IpePage::SetLineWidth(IpeAttribute attr)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpeFillable *obj = it->Object()->AsFillable();
if (obj) {
obj->SetLineWidth(attr);
iEdited = true;
}
}
}
}
//! Set line style of selected objects.
void IpePage::SetDashStyle(IpeAttribute attr)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpeFillable *obj = it->Object()->AsFillable();
if (obj) {
obj->SetDashStyle(attr);
iEdited = true;
}
}
}
}
//! Set arrows of selected objects.
void IpePage::SetArrows(bool forward, bool backward, IpeAttribute size)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpePath *path = it->Object()->AsPath();
if (path) {
if (forward)
path->SetForwardArrow(size);
else
path->SetForwardArrow(IpeAttribute());
if (backward)
path->SetBackwardArrow(size);
else
path->SetBackwardArrow(IpeAttribute());
iEdited = true;
}
}
}
}
//! Set arrow size of selected objects.
void IpePage::SetArrowSize(IpeAttribute size)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpePath *path = it->Object()->AsPath();
if (path) {
if (!path->ForwardArrow().IsNull())
path->SetForwardArrow(size);
if (!path->BackwardArrow().IsNull())
path->SetBackwardArrow(size);
iEdited = true;
}
}
}
}
//! Set text size of selected objects.
void IpePage::SetTextSize(IpeAttribute size)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpeText *obj = it->Object()->AsText();
if (obj) {
obj->SetSize(size);
it->InvalidateBBox();
obj->SetXForm(0); // XForm is no longer useful
iEdited = true;
} else {
IpeGroup *grp = it->Object()->AsGroup();
if (grp) {
grp->SetTextSize(size);
iEdited = true;
}
}
}
}
}
//! Set mark size of selected objects.
void IpePage::SetMarkSize(IpeAttribute size)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpeMark *obj = it->Object()->AsMark();
if (obj) {
obj->SetSize(size);
iEdited = true;
} else {
IpeGroup *grp = it->Object()->AsGroup();
if (grp) {
grp->SetMarkSize(size);
iEdited = true;
}
}
}
}
}
//! Set mark shape of selected objects.
void IpePage::SetMarkShape(int shape)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
IpeMark *obj = it->Object()->AsMark();
if (obj) {
obj->SetShape(shape);
iEdited = true;
} else {
IpeGroup *grp = it->Object()->AsGroup();
if (grp) {
grp->SetMarkShape(shape);
iEdited = true;
}
}
}
}
}
//! Copy selected objects into the stream.
void IpePage::Copy(IpeStream &stream, const IpeStyleSheet *sheet) const
{
IpeBitmapFinder bmFinder;
for (const_iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone)
it->Object()->Accept(bmFinder);
}
stream << "\n";
int id = 1;
for (std::vector::const_iterator it = bmFinder.iBitmaps.begin();
it != bmFinder.iBitmaps.end(); ++it) {
it->SaveAsXml(stream, id);
it->SetObjNum(id);
++id;
}
IpePainter painter(sheet);
for (const_iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone) {
it->Object()->SaveAsXml(painter, stream, IpeString());
}
}
stream << "\n";
}
//! Copy whole page into the stream.
void IpePage::CopyPage(IpeStream &stream, const IpeStyleSheet *sheet) const
{
IpeBitmapFinder bmFinder;
bmFinder.ScanPage(this);
stream << "\n";
int id = 1;
for (std::vector::const_iterator it = bmFinder.iBitmaps.begin();
it != bmFinder.iBitmaps.end(); ++it) {
IpeBitmap bm = *it;
bm.SaveAsXml(stream, id);
bm.SetObjNum(id);
++id;
}
IpePainter painter(sheet);
SaveAsXml(painter, stream);
stream << "\n";
}
//! Paste objects from XML source into \a layer.
/*! Returns false if XML source cannot be parsed. */
bool IpePage::Paste(int layer, IpeDataSource &source,
IpeRepository *rep)
{
IpeImlParser parser(source, rep);
IpePgObjectSeq seq;
if (!parser.ParseSelection(seq) || seq.empty())
return false;
DeselectAll();
IpePgObject::TSelect sel = IpePgObject::EPrimary;
for (IpePgObjectSeq::const_iterator it = seq.begin();
it != seq.end(); ++it) {
push_back(IpePgObject(sel, layer, it->Object()->Clone()));
sel = IpePgObject::ESecondary;
}
iEdited = true;
return true;
}
//! Make a table of layers, indicating whether visible (and unlocked).
/*! Must be called with an empty \a layers vector. */
void IpePage::MakeLayerTable(std::vector &layers, int view,
bool excludeLocked) const
{
for (int i = 0; i < CountLayers(); ++i) {
bool inView = iViews[view].HasLayer(Layer(i).Name());
if (excludeLocked && Layer(i).IsLocked())
inView = false;
layers.push_back(inView);
}
}
//! If no selected object is close, select closest object.
/*! If there is a selected object at distance at most \a d from \a
pos, return true. Otherwise, check whether the closest object to \a
pos has distance at most \a d. If so, unselect everything, make
this object the primary selection, and return true. If not, return
whether the page has a selection at all.
If \a primaryOnly is \c true, the primary selection has to be at
distance at most \a d, otherwise it'll be replaced as above.
*/
bool IpePage::UpdateCloseSelection(const IpeVector &pos, double d,
bool primaryOnly, int view)
{
// check whether a selected object is close enough
if (primaryOnly) {
iterator it = PrimarySelection();
if (it != end() && it->Distance(pos, d) < d)
return true;
} else {
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() != IpePgObject::ENone &&
it->Distance(pos, d) < d)
return true;
}
}
// no --- find closest object
std::vector layer;
MakeLayerTable(layer, view, true);
#ifdef __BORLANDC__
double d1; // Borland C++ complains if initialized
#else
double d1 = 0.0; // Some G++ versions complain if not! Argh!
#endif
iterator it1 = end();
for (iterator it = begin(); it != end(); ++it) {
if (layer[it->Layer()] && (d1 = it->Distance(pos, d)) < d) {
d = d1;
it1 = it;
}
}
// closest object close enough?
if (it1 != end()) {
// deselect all, and select it
for (iterator it = begin(); it != end(); ++it)
it->SetSelect(IpePgObject::ENone);
it1->SetSelect(IpePgObject::EPrimary);
return true;
}
return HasSelection();
}
// --------------------------------------------------------------------
//! Create one path object with all the subpaths from the selection.
/*! The new object takes the attributes from the primary selection,
and is placed in \a layer.
The function returns false if non-path objects are selected. */
bool IpePage::ComposePaths(int layer)
{
for (iterator it = begin(); it != end(); ++it) {
if (it->Select() && !it->Object()->AsPath())
return false;
}
IpePath *prim = PrimarySelection()->Object()->AsPath();
IpeAllAttributes attr;
attr.iStroke = prim->Stroke();
attr.iFill = prim->Fill();
attr.iLineWidth = prim->LineWidth();
attr.iDashStyle = prim->DashStyle();
attr.iForwardArrow = false;
attr.iBackwardArrow = false;
IpePath *obj = new IpePath(attr);
iterator it1;
for (iterator it = begin(); it != end(); it = it1) {
it1 = it;
++it1;
if (it->Select()) {
IpePath *p = it->Object()->AsPath();
for (int i = 0; i < p->NumSubPaths(); ++i)
obj->AddSubPath(p->SubPath(i)->Transform(p->Matrix()));
erase(it);
}
}
push_back(IpePgObject(IpePgObject::EPrimary, layer, obj));
iEdited = true;
return true;
}
//! Decompose one path object into separate objects for the subpaths.
/*! The new objects are placed in \a layer.
The function returns false if the primary selection is not a path object. */
bool IpePage::DecomposePath(int layer)
{
IpePath *prim = PrimarySelection()->Object()->AsPath();
if (!prim)
return false;
IpeAllAttributes attr;
attr.iStroke = prim->Stroke();
attr.iFill = prim->Fill();
attr.iLineWidth = prim->LineWidth();
attr.iDashStyle = prim->DashStyle();
attr.iForwardArrow = false;
attr.iBackwardArrow = false;
IpeMatrix tfm = prim->Matrix();
for (int i = 0; i < prim->NumSubPaths(); ++i) {
IpeSubPath *sp = prim->SubPath(i)->Clone();
IpePath *obj = new IpePath(attr);
obj->SetMatrix(tfm);
obj->AddSubPath(sp);
push_back(IpePgObject(IpePgObject::ESecondary, layer, obj));
}
erase(PrimarySelection());
EnsurePrimarySelection();
iEdited = true;
return true;
}
// --------------------------------------------------------------------
const double joinThreshold = 0.000001;
struct SSubPath {
IpeVector iV[2];
const IpeSegmentSubPath *iPath; // not owned
bool iFlip;
};
static int FindPartner(const IpeVector &v, SSubPath *subs, int beg, int end)
{
while (beg != end) {
if ((v - subs[beg].iV[0]).SqLen() < joinThreshold)
return beg;
if ((v - subs[beg].iV[1]).SqLen() < joinThreshold) {
IpeVector t = subs[beg].iV[0];
subs[beg].iV[0] = subs[beg].iV[1];
subs[beg].iV[1] = t;
subs[beg].iFlip = true;
return beg;
}
++beg;
}
return -1;
}
IpePath *IpePage::DoJoinPaths(IpePath *prim, SSubPath *subs, int size)
{
bool closePath =
((subs[0].iV[0] - subs[size-1].iV[1]).SqLen() < joinThreshold);
IpeAllAttributes attr;
attr.iStroke = prim->Stroke();
attr.iFill = prim->Fill();
attr.iLineWidth = prim->LineWidth();
attr.iDashStyle = prim->DashStyle();
attr.iForwardArrow = false;
attr.iBackwardArrow = false;
IpeSegmentSubPath *sp = new IpeSegmentSubPath;
for (int i = 0; i < size; ++i) {
const IpeSegmentSubPath *p = subs[i].iPath;
if (subs[i].iFlip) {
for (int j = p->NumSegments() - 1; j >= 0; --j)
sp->AppendReversed(p->Segment(j));
} else {
for (int j = 0; j < p->NumSegments(); ++j)
sp->Append(p->Segment(j));
}
}
IpePath *obj = new IpePath(attr);
sp->SetClosed(closePath);
obj->AddSubPath(sp);
return obj;
}
//! Join paths into one long path.
/*! Create one path object with the open subpaths from the selection
joined into one long subpath.
The new object takes the attributes from the primary selection,
and is placed in \a layer.
The function returns \c false if objects are selected that do not consist
of open subpaths only.
*/
bool IpePage::JoinPaths(int layer)
{
std::vector subs;
for (iterator it = begin(); it != end(); ++it) {
if (it->Select()) {
IpePath *p = it->Object()->AsPath();
// must be a single open subpath
if (!p || p->NumSubPaths() > 1 || p->SubPath(0)->Closed())
return false;
const IpeSegmentSubPath *sp = p->SubPath(0)->AsSegs();
SSubPath sub;
sub.iPath = sp;
sub.iV[0] = sp->Segment(0).CP(0);
IpePathSegment back = sp->Segment(-1);
sub.iV[1] = back.CP(back.NumCP() - 1);
sub.iFlip = false;
subs.push_back(sub);
}
}
// match up endpoints
if (FindPartner(subs[0].iV[1], &subs[0], 1, subs.size()) < 0) {
// not found, flip subs[0] and try again
IpeVector t = subs[0].iV[0];
subs[0].iV[0] = subs[0].iV[1];
subs[0].iV[1] = t;
subs[0].iFlip = true;
}
for (int i = 0; i < int(subs.size()) - 2; ++i) {
// invariant: subs[0] .. subs[i] form a chain
int j = FindPartner(subs[i].iV[1], &subs[0], i+1, subs.size());
if (j < 0)
return false; // no match
// flip i+1 and j
if (j != i+1) {
SSubPath sub = subs[i+1];
subs[i+1] = subs[j];
subs[j] = sub;
}
}
IpePath *prim = PrimarySelection()->Object()->AsPath();
IpePath *obj = DoJoinPaths(prim, &subs[0], subs.size());
// erase the selected objects
Delete();
push_back(IpePgObject(IpePgObject::EPrimary, layer, obj));
iEdited = true;
return true;
}
// --------------------------------------------------------------------