1 // --------------------------------------------------------------------
2 // A page of a document.
3 // --------------------------------------------------------------------
4 /*
5
6 This file is part of the extensible drawing editor Ipe.
7 Copyright (c) 1993-2020 Otfried Cheong
8
9 Ipe is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
13
14 As a special exception, you have permission to link Ipe with the
15 CGAL library and distribute executables, as long as you follow the
16 requirements of the Gnu General Public License in regard to all of
17 the software in the executable aside from CGAL.
18
19 Ipe is distributed in the hope that it will be useful, but WITHOUT
20 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
22 License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with Ipe; if not, you can find it at
26 "http://www.gnu.org/copyleft/gpl.html", or write to the Free
27 Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28
29 */
30
31 #include "ipepage.h"
32 #include "ipegroup.h"
33 #include "ipereference.h"
34 #include "ipepainter.h"
35 #include "ipeiml.h"
36 #include "ipeutils.h"
37
38 using namespace ipe;
39
40 // --------------------------------------------------------------------
41
42 /*! \class ipe::Page
43 \ingroup doc
44 \brief An Ipe document page.
45
46 Its main ingredients are a sequence of Objects (with selection
47 state, layer, and a cached bounding box), a set of Layers, and
48 a sequence of Views.
49
50 Each object on a Page belongs to one of the layers of the page.
51 Layers are orthogonal to the back-to-front ordering of objects (in
52 particular, they are not "layered" - the word is a misnomer). The
53 "layer" is really just another attribute of the object.
54
55 - Layers are either editable or locked. Objects in a locked layer
56 cannot be selected, and a locked layer cannot be made active in
57 the UI. This more or less means that the contents of such a layer
58 cannot be modified---but that's a consequence of the UI, Ipelib
59 contains no special handling of locked layers.
60
61 - A layer may have snapping on or off---objects will behave
62 magnetically only if their layer has snapping on.
63
64
65 A Page is presented in a number of \e views. Each view presents
66 some of the layers of the page. In addition, each view has an
67 active layer (where objects are added when this view is shown in the
68 UI), and possibly a transition effect (Acrobat Reader eye candy).
69
70 A Page can be copied and assigned. The operation takes time linear
71 in the number of top-level object on the page.
72
73 */
74
75 //! The default constructor creates a new empty page.
76 /*! This page still needs a layer and a view to be usable! */
Page()77 Page::Page() : iTitle()
78 {
79 iUseTitle[0] = iUseTitle[1] = false;
80 iMarked = true;
81 }
82
83 //! Create a new empty page with standard settings.
84 /*! This is an empty page with layer 'alpha' and a single view. */
basic()85 Page *Page::basic()
86 {
87 // Create one empty page
88 Page *page = new Page;
89 page->addLayer("alpha");
90 page->insertView(0, "alpha");
91 page->setVisible(0, "alpha", true);
92 return page;
93 }
94
95 // --------------------------------------------------------------------
96
97 //! save page in XML format.
saveAsXml(Stream & stream) const98 void Page::saveAsXml(Stream &stream) const
99 {
100 stream << "<page";
101 if (!title().empty()) {
102 stream << " title=\"";
103 stream.putXmlString(title());
104 stream << "\"";
105 }
106 if (iUseTitle[0]) {
107 stream << " section=\"\"";
108 } else if (!iSection[0].empty()) {
109 stream << " section=\"";
110 stream.putXmlString(iSection[0]);
111 stream << "\"";
112 }
113 if (iUseTitle[1]) {
114 stream << " subsection=\"\"";
115 } else if (!iSection[1].empty()) {
116 stream << " subsection=\"";
117 stream.putXmlString(iSection[1]);
118 stream << "\"";
119 }
120 if (!iMarked)
121 stream << " marked=\"no\"";
122
123 stream << ">\n";
124 if (!iNotes.empty()) {
125 stream << "<notes>";
126 stream.putXmlString(iNotes);
127 stream << "</notes>\n";
128 }
129 for (int i = 0; i < countLayers(); ++i) {
130 stream << "<layer name=\"" << iLayers[i].iName << "\"";
131 if (iLayers[i].locked)
132 stream << " edit=\"no\"";
133 if (iLayers[i].snapMode == SnapMode::Never)
134 stream << " snap=\"never\"";
135 else if (iLayers[i].snapMode == SnapMode::Always)
136 stream << " snap=\"always\"";
137 if (!iLayers[i].iData.empty()) {
138 stream << " data=\"";
139 stream.putXmlString(iLayers[i].iData);
140 stream << "\"";
141 }
142 stream << "/>\n";
143 }
144 for (int i = 0; i < countViews(); ++i) {
145 stream << "<view layers=\"";
146 String sep;
147 for (int l = 0; l < countLayers(); ++l) {
148 if (visible(i, l)) {
149 stream << sep << layer(l);
150 sep = " ";
151 }
152 }
153 stream << "\"";
154 if (!active(i).empty())
155 stream << " active=\"" << active(i) << "\"";
156 if (!effect(i).isNormal())
157 stream << " effect=\"" << effect(i).string() << "\"";
158 if (markedView(i))
159 stream << " marked=\"yes\"";
160 if (!viewName(i).empty())
161 stream << " name=\"" << viewName(i) << "\"";
162 const AttributeMap &map = viewMap(i);
163 if (map.count() == 0 && iViews[i].iLayerMatrices.empty()) {
164 stream << "/>\n";
165 } else {
166 stream << ">\n";
167 map.saveAsXml(stream);
168 for (const auto &s : iViews[i].iLayerMatrices) {
169 stream << "<transform layer=\"" << s.iLayer << "\" matrix=\""
170 << s.iMatrix << "\"/>\n";
171 }
172 stream << "</view>\n";
173 }
174 }
175 int currentLayer = -1;
176 for (ObjSeq::const_iterator it = iObjects.begin();
177 it != iObjects.end(); ++it) {
178 String l;
179 if (it->iLayer != currentLayer) {
180 currentLayer = it->iLayer;
181 l = layer(currentLayer);
182 }
183 it->iObject->saveAsXml(stream, l);
184 }
185 stream << "</page>\n";
186 }
187
188 // --------------------------------------------------------------------
189
SLayer(String name)190 Page::SLayer::SLayer(String name)
191 {
192 iName = name;
193 locked = false;
194 snapMode = SnapMode::Visible;
195 }
196
197 //! Set free data field of the layer.
setLayerData(int index,String data)198 void Page::setLayerData(int index, String data)
199 {
200 iLayers[index].iData = data;
201 }
202
203 //! Set locking of layer \a i.
setLocked(int i,bool flag)204 void Page::setLocked(int i, bool flag)
205 {
206 iLayers[i].locked = flag;
207 }
208
209 //! Set snapping of layer \a i.
setSnapping(int i,SnapMode mode)210 void Page::setSnapping(int i, SnapMode mode)
211 {
212 iLayers[i].snapMode = mode;
213 }
214
215 //! Add a new layer.
addLayer(String name)216 void Page::addLayer(String name)
217 {
218 iLayers.push_back(SLayer(name));
219 iLayers.back().iVisible.resize(countViews());
220 for (int i = 0; i < countViews(); ++i)
221 iLayers.back().iVisible[i] = false;
222 }
223
224 //! Find layer with given name.
225 /*! Returns -1 if not found. */
findLayer(String name) const226 int Page::findLayer(String name) const
227 {
228 for (int i = 0; i < countLayers(); ++i)
229 if (layer(i) == name)
230 return i;
231 return -1;
232 }
233
234 const char * const layerNames[] = {
235 "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta",
236 "theta", "iota", "kappa", "lambda", "mu", "nu", "xi",
237 "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi",
238 "chi", "psi", "omega" };
239
240 //! Add a new layer with unique name.
addLayer()241 void Page::addLayer()
242 {
243 for (int i = 0; i < int(sizeof(layerNames)/sizeof(const char *)); ++i) {
244 if (findLayer(layerNames[i]) < 0) {
245 addLayer(layerNames[i]);
246 return;
247 }
248 }
249 char name[20];
250 int i = 1;
251 for (;;) {
252 std::sprintf(name, "alpha%d", i);
253 if (findLayer(name) < 0) {
254 addLayer(name);
255 return;
256 }
257 i++;
258 }
259 }
260
261 //! Moves the position of a layer in the layer list.
moveLayer(int index,int newIndex)262 void Page::moveLayer(int index, int newIndex)
263 {
264 assert(0 <= index && index < int(iLayers.size())
265 && 0 <= newIndex && newIndex < int(iLayers.size()));
266
267 SLayer layer = iLayers[index];
268 iLayers.erase(iLayers.begin() + index);
269 iLayers.insert(iLayers.begin() + newIndex, layer);
270
271 // Layer of object needs to be decreased by one if > index
272 // Then increase by one if >= newIndex
273 // If == index, then replace by newIndex
274 for (ObjSeq::iterator it = iObjects.begin(); it != iObjects.end(); ++it) {
275 int k = it->iLayer;
276 if (k == index) {
277 k = newIndex;
278 } else {
279 if (k > index)
280 k -= 1;
281 if (k >= newIndex)
282 k += 1;
283 }
284 it->iLayer = k;
285 }
286 }
287
288 //! Removes an empty layer from the page.
289 /*! All objects are adjusted. Panics if there are objects in the
290 deleted layer, of if it is the only layer.
291 */
removeLayer(String name)292 void Page::removeLayer(String name)
293 {
294 int index = findLayer(name);
295 assert(iLayers.size() > 1 && index >= 0);
296 for (ObjSeq::iterator it = iObjects.begin(); it != iObjects.end(); ++it) {
297 int k = it->iLayer;
298 assert(k != index);
299 if (k > index)
300 it->iLayer = k-1;
301 }
302 iLayers.erase(iLayers.begin() + index);
303 }
304
305 //! Return number of objects in each layer
objectsPerLayer(std::vector<int> & objcounts) const306 void Page::objectsPerLayer(std::vector<int> &objcounts) const
307 {
308 objcounts.clear();
309 for (int i = 0; i < countLayers(); ++i)
310 objcounts.push_back(0);
311 for (const auto & obj : iObjects)
312 objcounts[obj.iLayer] += 1;
313 }
314
315 //! Rename a layer.
renameLayer(String oldName,String newName)316 void Page::renameLayer(String oldName, String newName)
317 {
318 int l = findLayer(oldName);
319 if (l < 0)
320 return;
321 iLayers[l].iName = newName;
322 }
323
324 //! Returns a precise bounding box for the artwork on the page.
325 /*! This is meant to be used as the bounding box in PDF output. It is
326 computed by rendering all objects on the page that are visible in at
327 least one view, plus all objects in a layer named "BBOX" (even if
328 that is not visible), using a BBoxPainter. */
pageBBox(const Cascade * sheet) const329 Rect Page::pageBBox(const Cascade *sheet) const
330 {
331 // make table with all layers to be rendered
332 std::vector<bool> layers;
333 for (int l = 0; l < countLayers(); ++l) {
334 bool vis = false;
335 if (layer(l) == "BBOX")
336 vis = true;
337 else
338 for (int vno = 0; !vis && vno < countViews(); ++vno)
339 vis = visible(vno, l);
340 layers.push_back(vis);
341 }
342
343 BBoxPainter bboxPainter(sheet);
344 for (int i = 0; i < count(); ++i) {
345 if (layers[layerOf(i)])
346 object(i)->draw(bboxPainter);
347 }
348 return bboxPainter.bbox();
349 }
350
351 //! Returns a precise bounding box for the artwork in the view.
352 /*! This is meant to be used as the bounding box in PDF and EPS
353 output. It is computed by rendering all objects in the page using a
354 BBoxPainter. */
viewBBox(const Cascade * sheet,int view) const355 Rect Page::viewBBox(const Cascade *sheet, int view) const
356 {
357 BBoxPainter bboxPainter(sheet);
358 for (int i = 0; i < count(); ++i) {
359 if (objectVisible(view, i))
360 object(i)->draw(bboxPainter);
361 }
362 return bboxPainter.bbox();
363 }
364
365 //! Snapping occurs if the layer is visible and has snapping enabled.
objSnapsInView(int objNo,int view) const366 bool Page::objSnapsInView(int objNo, int view) const noexcept
367 {
368 int layer = layerOf(objNo);
369 switch (snapping(layer)) {
370 case SnapMode::Visible:
371 return visible(view, layer);
372 case SnapMode::Always:
373 return true;
374 case SnapMode::Never:
375 default:
376 return false;
377 }
378 }
379
380 // --------------------------------------------------------------------
381
382 //! Set effect of view.
383 /*! Panics if \a sym is not symbolic. */
setEffect(int index,Attribute sym)384 void Page::setEffect(int index, Attribute sym)
385 {
386 assert(sym.isSymbolic());
387 iViews[index].iEffect = sym;
388 }
389
390 //! Set active layer of view.
setActive(int index,String layer)391 void Page::setActive(int index, String layer)
392 {
393 assert(findLayer(layer) >= 0);
394 iViews[index].iActive = layer;
395 }
396
397 //! Set visibility of layer \a layer in view \a view.
setVisible(int view,String layer,bool vis)398 void Page::setVisible(int view, String layer, bool vis)
399 {
400 int index = findLayer(layer);
401 assert(index >= 0);
402 iLayers[index].iVisible[view] = vis;
403 }
404
405 //! Insert a new view at index \a i.
insertView(int i,String active)406 void Page::insertView(int i, String active)
407 {
408 iViews.insert(iViews.begin() + i, SView());
409 iViews[i].iActive = active;
410 iViews[i].iMarked = false;
411 for (int l = 0; l < countLayers(); ++l)
412 iLayers[l].iVisible.insert(iLayers[l].iVisible.begin() + i, false);
413 }
414
415 //! Remove the view at index \a i.
removeView(int i)416 void Page::removeView(int i)
417 {
418 iViews.erase(iViews.begin() + i);
419 for (int l = 0; l < countLayers(); ++l)
420 iLayers[l].iVisible.erase(iLayers[l].iVisible.begin() + i);
421 }
422
423 //! Remove all views of this page.
clearViews()424 void Page::clearViews()
425 {
426 iViews.clear();
427 for (LayerSeq::iterator it = iLayers.begin();
428 it != iLayers.end(); ++it)
429 it->iVisible.clear();
430 }
431
setMarkedView(int index,bool marked)432 void Page::setMarkedView(int index, bool marked)
433 {
434 iViews[index].iMarked = marked;
435 }
436
countMarkedViews() const437 int Page::countMarkedViews() const
438 {
439 int count = 0;
440 for (int i = 0; i < countViews(); ++i) {
441 if (markedView(i))
442 ++count;
443 }
444 return (count == 0) ? 1 : count;
445 }
446
447 //! Return index of view with given number or name.
448 /*! Input numbers are one-based.
449 Returns -1 if no such view exists.
450 */
findView(String s) const451 int Page::findView(String s) const
452 {
453 if (s.empty())
454 return -1;
455 if ('0' <= s[0] && s[0] <= '9') {
456 int no = Lex(s).getInt();
457 if (no <= 0 || no > countViews())
458 return -1;
459 return no - 1;
460 }
461 for (int i = 0; i < countViews(); ++i) {
462 if (s == viewName(i))
463 return i;
464 }
465 return -1;
466 }
467
468 //! Return matrices for all layers.
layerMatrices(int view) const469 std::vector<Matrix> Page::layerMatrices(int view) const
470 {
471 std::vector<Matrix> m(countLayers());
472 for (int i = 0; i < size(iViews[view].iLayerMatrices); ++i) {
473 int l = findLayer(iViews[view].iLayerMatrices[i].iLayer);
474 if (l >= 0)
475 m[l] = iViews[view].iLayerMatrices[i].iMatrix;
476 }
477 return m;
478 }
479
480 //! Set matrix for the given layer in this view.
setLayerMatrix(int view,int layer,const Matrix & m)481 void Page::setLayerMatrix(int view, int layer, const Matrix &m)
482 {
483 String name = iLayers[layer].iName;
484 std::vector<SLayerMatrix> &ms = iViews[view].iLayerMatrices;
485 std::vector<SLayerMatrix>::iterator it =
486 std::find_if(ms.begin(), ms.end(), [&name](const SLayerMatrix &a) {return a.iLayer == name;});
487 if (m.isIdentity()) {
488 if (it != ms.end()) ms.erase(it);
489 } else {
490 if (it != ms.end())
491 it->iMatrix = m;
492 else
493 ms.push_back({name, m});
494 }
495 }
496
497 //! Set the attribute mapping for the view.
setViewMap(int index,const AttributeMap & map)498 void Page::setViewMap(int index, const AttributeMap &map)
499 {
500 iViews[index].iAttributeMap = map;
501 }
502
503 // --------------------------------------------------------------------
504
SObject()505 Page::SObject::SObject()
506 {
507 iObject = nullptr;
508 iLayer = 0;
509 iSelect = ENotSelected;
510 }
511
SObject(const SObject & rhs)512 Page::SObject::SObject(const SObject &rhs)
513 : iSelect(rhs.iSelect), iLayer(rhs.iLayer)
514 {
515 if (rhs.iObject)
516 iObject = rhs.iObject->clone();
517 else
518 iObject = nullptr;
519 }
520
operator =(const SObject & rhs)521 Page::SObject &Page::SObject::operator=(const SObject &rhs)
522 {
523 if (this != &rhs) {
524 delete iObject;
525 iSelect = rhs.iSelect;
526 iLayer = rhs.iLayer;
527 if (rhs.iObject)
528 iObject = rhs.iObject->clone();
529 else
530 iObject = nullptr;
531 iBBox.clear(); // invalidate
532 }
533 return *this;
534 }
535
~SObject()536 Page::SObject::~SObject()
537 {
538 delete iObject;
539 }
540
541 // --------------------------------------------------------------------
542
543 //! Insert a new object at index \a i.
544 /*! Takes ownership of the object. */
insert(int i,TSelect select,int layer,Object * obj)545 void Page::insert(int i, TSelect select, int layer, Object *obj)
546 {
547 iObjects.insert(iObjects.begin() + i, SObject());
548 SObject &s = iObjects[i];
549 s.iSelect = select;
550 s.iLayer = layer;
551 s.iObject = obj;
552 }
553
554 //! Append a new object.
555 /*! Takes ownership of the object. */
append(TSelect select,int layer,Object * obj)556 void Page::append(TSelect select, int layer, Object *obj)
557 {
558 iObjects.push_back(SObject());
559 SObject &s = iObjects.back();
560 s.iSelect = select;
561 s.iLayer = layer;
562 s.iObject = obj;
563 }
564
565 //! Remove the object at index \a i.
remove(int i)566 void Page::remove(int i)
567 {
568 iObjects.erase(iObjects.begin() + i);
569 }
570
571 //! Replace the object at index \a i.
572 /*! Takes ownership of \a obj. */
replace(int i,Object * obj)573 void Page::replace(int i, Object *obj)
574 {
575 delete iObjects[i].iObject;
576 iObjects[i].iObject = obj;
577 invalidateBBox(i);
578 }
579
580 //! Return distance between object at index \a i and \a v.
581 /*! But may just return \a bound if the distance is larger.
582 This function uses the cached bounded box, and is faster than
583 calling Object::distance directly. */
distance(int i,const Vector & v,double bound) const584 double Page::distance(int i, const Vector &v, double bound) const
585 {
586 if (bbox(i).certainClearance(v, bound))
587 return bound;
588 return object(i)->distance(v, Matrix(), bound);
589 }
590
591 //! Transform the object at index \a i.
592 /*! Use this function instead of calling Object::setMatrix directly,
593 as the latter doesn't invalidate the cached bounding box. */
transform(int i,const Matrix & m)594 void Page::transform(int i, const Matrix &m)
595 {
596 invalidateBBox(i);
597 object(i)->setMatrix(m * object(i)->matrix());
598 }
599
600 //! Invalidate the bounding box at index \a i (the object is somehow changed).
invalidateBBox(int i) const601 void Page::invalidateBBox(int i) const
602 {
603 iObjects[i].iBBox.clear();
604 }
605
606 //! Return a bounding box for the object at index \a i.
607 /*! This is a bounding box including the control points of the object.
608 If you need a tight bounding box, you'll need to use the Object
609 directly.
610
611 The Page caches the box the first time it is computed.
612
613 Make sure you call Page::transform instead of Object::setMatrix,
614 as the latter would not invalidate the bounding box.
615 */
616
bbox(int i) const617 Rect Page::bbox(int i) const
618 {
619 if (iObjects[i].iBBox.isEmpty())
620 iObjects[i].iObject->addToBBox(iObjects[i].iBBox, Matrix(), true);
621 return iObjects[i].iBBox;
622 }
623
624 //! Compute possible vertex snapping position for object at index \a i.
625 /*! Looks only for positions closer than \a bound.
626 If successful, modifies \a pos and \a bound. */
snapVtx(int i,const Vector & mouse,Vector & pos,double & bound) const627 void Page::snapVtx(int i, const Vector &mouse,
628 Vector &pos, double &bound) const
629 {
630 if (bbox(i).certainClearance(mouse, bound))
631 return;
632 object(i)->snapVtx(mouse, Matrix(), pos, bound);
633 }
634
635 //! Compute possible control point snapping position for object at index \a i.
636 /*! Looks only for positions closer than \a bound.
637 If successful, modifies \a pos and \a bound. */
snapCtl(int i,const Vector & mouse,Vector & pos,double & bound) const638 void Page::snapCtl(int i, const Vector &mouse,
639 Vector &pos, double &bound) const
640 {
641 if (bbox(i).certainClearance(mouse, bound))
642 return;
643 object(i)->snapCtl(mouse, Matrix(), pos, bound);
644 }
645
646 //! Compute possible boundary snapping position for object at index \a i.
647 /*! Looks only for positions closer than \a bound.
648 If successful, modifies \a pos and \a bound. */
snapBnd(int i,const Vector & mouse,Vector & pos,double & bound) const649 void Page::snapBnd(int i, const Vector &mouse,
650 Vector &pos, double &bound) const
651 {
652 if (bbox(i).certainClearance(mouse, bound))
653 return;
654 object(i)->snapBnd(mouse, Matrix(), pos, bound);
655 }
656
657 //! Set attribute \a prop of object at index \a i to \a value.
658 /*! This method automatically invalidates the bounding box if a
659 ETextSize property is actually changed. */
setAttribute(int i,Property prop,Attribute value)660 bool Page::setAttribute(int i, Property prop, Attribute value)
661 {
662 bool changed = object(i)->setAttribute(prop, value);
663 if (changed && (prop == EPropTextSize || prop == EPropTransformations))
664 invalidateBBox(i);
665 return changed;
666 }
667
668 // --------------------------------------------------------------------
669
670 //! Return section title at \a level.
671 /*! Level 0 is the section, level 1 the subsection. */
section(int level) const672 String Page::section(int level) const
673 {
674 if (iUseTitle[level])
675 return title();
676 else
677 return iSection[level];
678 }
679
680 //! Set the section title at \a level.
681 /*! Level 0 is the section, level 1 the subsection.
682
683 If \a useTitle is \c true, then \a name is ignored, and the section
684 title will be copied from the page title (and further changes to the
685 page title are automatically reflected). */
setSection(int level,bool useTitle,String name)686 void Page::setSection(int level, bool useTitle, String name)
687 {
688 iUseTitle[level] = useTitle;
689 iSection[level] = useTitle ? String() : name;
690 }
691
692 //! Set the title of this page.
693 /*! An empty title is not displayed. */
setTitle(String title)694 void Page::setTitle(String title)
695 {
696 iTitle = title;
697 iTitleObject.setText(String("\\PageTitle{") + title + "}");
698 }
699
700 //! Return title of this page.
title() const701 String Page::title() const
702 {
703 return iTitle;
704 }
705
706 //! Set the notes of this page.
setNotes(String notes)707 void Page::setNotes(String notes)
708 {
709 iNotes = notes;
710 }
711
712 //! Set if page is marked for printing.
setMarked(bool marked)713 void Page::setMarked(bool marked)
714 {
715 iMarked = marked;
716 }
717
718 //! Return Text object representing the title text.
719 /*! Return 0 if no title is set.
720 Ownership of object remains with Page.
721 */
titleText() const722 const Text *Page::titleText() const
723 {
724 if (title().empty())
725 return nullptr;
726 return &iTitleObject;
727 }
728
729 //! Apply styling to title text object.
applyTitleStyle(const Cascade * sheet)730 void Page::applyTitleStyle(const Cascade *sheet)
731 {
732 if (title().empty())
733 return;
734 const StyleSheet::TitleStyle *ts = sheet->findTitleStyle();
735 if (!ts)
736 return;
737 iTitleObject.setMatrix(Matrix(ts->iPos));
738 iTitleObject.setSize(ts->iSize);
739 iTitleObject.setStroke(ts->iColor);
740 iTitleObject.setHorizontalAlignment(ts->iHorizontalAlignment);
741 iTitleObject.setVerticalAlignment(ts->iVerticalAlignment);
742 }
743
744 // --------------------------------------------------------------------
745
746 //! Return index of primary selection.
747 /*! Returns -1 if there is no primary selection. */
primarySelection() const748 int Page::primarySelection() const
749 {
750 for (int i = 0; i < count(); ++i)
751 if (select(i) == EPrimarySelected)
752 return i;
753 return -1;
754 }
755
756 //! Returns true iff any object on the page is selected.
hasSelection() const757 bool Page::hasSelection() const
758 {
759 for (int i = 0; i < count(); ++i)
760 if (select(i))
761 return true;
762 return false;
763 }
764
765 //! Deselect all objects.
deselectAll()766 void Page::deselectAll()
767 {
768 for (int i = 0; i < count(); ++i)
769 setSelect(i, ENotSelected);
770 }
771
772 /*! If no object is the primary selection, make the topmost secondary
773 selection the primary one. */
ensurePrimarySelection()774 void Page::ensurePrimarySelection()
775 {
776 for (int i = 0; i < count(); ++i)
777 if (select(i) == EPrimarySelected)
778 return;
779 for (int i = count() - 1; i >= 0; --i) {
780 if (select(i) == ESecondarySelected) {
781 setSelect(i, EPrimarySelected);
782 return;
783 }
784 }
785 }
786
787 //! Copy whole page with bitmaps as <ipepage> into the stream.
saveAsIpePage(Stream & stream) const788 void Page::saveAsIpePage(Stream &stream) const
789 {
790 BitmapFinder bmFinder;
791 bmFinder.scanPage(this);
792 stream << "<ipepage>\n";
793 int id = 1;
794 for (std::vector<Bitmap>::const_iterator it = bmFinder.iBitmaps.begin();
795 it != bmFinder.iBitmaps.end(); ++it) {
796 Bitmap bm = *it;
797 bm.saveAsXml(stream, id);
798 bm.setObjNum(id);
799 ++id;
800 }
801 saveAsXml(stream);
802 stream << "</ipepage>\n";
803 }
804
805 //! Copy selected objects as <ipeselection> into the stream.
saveSelection(Stream & stream) const806 void Page::saveSelection(Stream &stream) const
807 {
808 BitmapFinder bmFinder;
809 for (int i = 0; i < count(); ++i) {
810 if (select(i))
811 object(i)->accept(bmFinder);
812 }
813 stream << "<ipeselection>\n";
814 int id = 1;
815 for (std::vector<Bitmap>::const_iterator it = bmFinder.iBitmaps.begin();
816 it != bmFinder.iBitmaps.end(); ++it) {
817 Bitmap bm = *it;
818 bm.saveAsXml(stream, id);
819 bm.setObjNum(id);
820 ++id;
821 }
822 for (int i = 0; i < count(); ++i) {
823 if (select(i))
824 object(i)->saveAsXml(stream, layer(layerOf(i)));
825 }
826 stream << "</ipeselection>\n";
827 }
828
829 // --------------------------------------------------------------------
830