1 /* This file is part of the KDE project
2  * Copyright (C) 2000-2006 David Faure <faure@kde.org>
3  * Copyright (C) 2005-2011 Sebastian Sauer <mail@dipe.org>
4  * Copyright (C) 2005-2006, 2009 Thomas Zander <zander@kde.org>
5  * Copyright (C) 2008 Pierre Ducroquet <pinaraf@pinaraf.info>
6  * Copyright (C) 2010 by Nokia, Matus Hanzes
7  * Copyright 2012 Friedrich W. H. Kossebau <kossebau@kde.org>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24 
25 #include "KWFrameLayout.h"
26 #include "KWPageManager.h"
27 #include "KWTextFrameSet.h"
28 #include "KWPageStyle.h"
29 #include "KWPage.h"
30 #include "KWCopyShape.h"
31 #include "KWDocument.h"
32 #include "Words.h"
33 
34 #include <KoShapeRegistry.h>
35 #include <KoShapeFactoryBase.h>
36 #include <KoDocumentResourceManager.h>
37 #include <KoShapeBackground.h>
38 #include <KoColumns.h>
39 
40 #include <QPainterPath>
41 #include <QTextLayout>
42 #include <QTextDocument>
43 #include <QTextBlock>
44 #include <WordsDebug.h>
45 #include <limits.h>
46 
47 /**
48  * This shape is a helper class for drawing the background of pages
49  * and/or the separators between multiple columns.
50  */
51 class KWPageBackground : public KoShape
52 {
53 public:
KWPageBackground()54     KWPageBackground()
55     {
56         setSelectable(false);
57         setTextRunAroundSide(KoShape::RunThrough, KoShape::Background);
58     }
~KWPageBackground()59     ~KWPageBackground() override
60     {
61     }
paint(QPainter & painter,const KoViewConverter & converter,KoShapePaintingContext & paintContext)62     void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override
63     {
64         applyConversion(painter, converter);
65 
66         // paint background
67         if (background()) {
68             QPainterPath p;
69             p.addRect(QRectF(QPointF(), size()));
70             background()->paint(painter, converter, paintContext, p);
71         }
72 
73         // paint separators
74         if (! m_separatorPositions.isEmpty()) {
75             QPen separatorPen(QBrush(m_separatorColor), m_separatorWidth,
76                               Qt::PenStyle(m_separatorStyle), Qt::FlatCap);
77             painter.setPen(separatorPen);
78             foreach(qreal separatorPos, m_separatorPositions) {
79                 QLineF line(separatorPos, m_separatorY, separatorPos, m_separatorY+m_separatorHeight);
80                 painter.drawLine(line);
81             }
82         }
83     }
loadOdf(const KoXmlElement &,KoShapeLoadingContext &)84     bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override
85     {
86         return true;
87     }
saveOdf(KoShapeSavingContext &) const88     void saveOdf(KoShapeSavingContext &) const override
89     {
90     }
91 
setSeparators(KoColumns::SeparatorStyle separatorStyle,const QColor & color,const QList<qreal> & separatorPositions,qreal separatorY,qreal separatorWidth,qreal separatorHeight)92     void setSeparators(KoColumns::SeparatorStyle separatorStyle, const QColor &color,
93                        const QList<qreal> &separatorPositions, qreal separatorY,
94                        qreal separatorWidth, qreal separatorHeight)
95     {
96         m_separatorStyle = separatorStyle;
97         m_separatorColor = color;
98         m_separatorPositions = separatorPositions;
99         m_separatorY = separatorY;
100         m_separatorWidth = separatorWidth;
101         m_separatorHeight = separatorHeight;
102     }
103 
clearSeparators()104     void clearSeparators()
105     {
106         m_separatorPositions.clear();
107     }
108 
109 private:
110     KoColumns::SeparatorStyle m_separatorStyle;
111     QColor m_separatorColor;
112 
113     QList<qreal> m_separatorPositions;
114     qreal m_separatorY;
115     /** Width in pt */
116     qreal m_separatorWidth;
117     qreal m_separatorHeight;
118 };
119 
KWFrameLayout(const KWPageManager * pageManager,const QList<KWFrameSet * > & frameSets)120 KWFrameLayout::KWFrameLayout(const KWPageManager *pageManager, const QList<KWFrameSet*> &frameSets)
121         : m_pageManager(pageManager),
122         m_frameSets(frameSets),
123         m_maintext(0),
124         m_backgroundFrameSet(0),
125         m_document(0),
126         m_setup(false)
127 {
128 }
129 
createNewFramesForPage(int pageNumber)130 void KWFrameLayout::createNewFramesForPage(int pageNumber)
131 {
132     debugWords << "pageNumber=" << pageNumber;
133 
134     m_setup = false; // force reindexing of types
135     const KWPage page = m_pageManager->page(pageNumber);
136     Q_ASSERT(page.isValid());
137     const KWPageStyle pageStyle = page.pageStyle();
138 
139     // Header footer handling.
140     // first make a list of all types.
141     QList<Words::TextFrameSetType> allHFTypes;
142     allHFTypes.append(Words::OddPagesHeaderTextFrameSet);
143     allHFTypes.append(Words::EvenPagesHeaderTextFrameSet);
144     allHFTypes.append(Words::OddPagesFooterTextFrameSet);
145     allHFTypes.append(Words::EvenPagesFooterTextFrameSet);
146 
147     // create headers & footers
148     Words::TextFrameSetType origin;
149     if (shouldHaveHeaderOrFooter(pageNumber, true, &origin)) {
150         allHFTypes.removeAll(origin);
151         KWTextFrameSet *fs = getOrCreate(origin, page);
152         debugWords << "HeaderTextFrame" << fs << "sequencedShapeOn=" << sequencedShapeOn(fs, pageNumber) << "pageStyle=" << pageStyle.name();
153         if (!sequencedShapeOn(fs, pageNumber)) {
154             createCopyFrame(fs, page);
155         }
156     }
157 
158     if (shouldHaveHeaderOrFooter(pageNumber, false, &origin)) {
159         allHFTypes.removeAll(origin);
160         KWTextFrameSet *fs = getOrCreate(origin, page);
161         debugWords << "FooterTextFrame" << fs << "sequencedShapeOn=" << sequencedShapeOn(fs, pageNumber) << "pageStyle=" << pageStyle.name();
162         if (!sequencedShapeOn(fs, pageNumber)) {
163             createCopyFrame(fs, page);
164         }
165     }
166 
167     //debugWords <<"createNewFramesForPage" << pageNumber << "TextFrameSetType=" << Words::frameSetTypeName(origin);
168 
169     // delete headers/footer frames that are not needed on this page
170     foreach (KoShape *shape, sequencedShapesOnPage(page.rect())) {
171         if (KWFrameSet::from(shape)->type() != Words::TextFrameSet)
172             continue;
173         KWTextFrameSet *tfs = dynamic_cast<KWTextFrameSet*>(KWFrameSet::from(shape));
174         if (tfs && (allHFTypes.contains(tfs->textFrameSetType())
175                 || (tfs->pageStyle() != pageStyle && Words::isHeaderFooter(tfs)))) {
176             Q_ASSERT(shape);
177             KWPage p = m_pageManager->page(shape);
178             Q_ASSERT(p.isValid());
179             debugWords<<"Delete disabled header/footer shape=" << shape << "pageRect=" << page.rect() << "pageNumber=" << p.pageNumber();
180             tfs->removeShape(shape);
181             delete shape;
182         }
183     }
184 
185     // create main text shape. All columns of them.
186     const int columnsCount = pageStyle.columns().count;
187     Q_ASSERT(columnsCount >= 1);
188     KWTextFrameSet *fs = getOrCreate(Words::MainTextFrameSet, page);
189     QRectF rect(QPointF(0, page.offsetInDocument()), QSizeF(page.width(), page.height()));
190 
191     debugWords << "MainTextFrame" << fs << "pageRect=" << rect << "columnsCount=" << columnsCount;
192     int neededColumnsCount = columnsCount;
193     // Check existing column shapes
194     foreach (KoShape *shape, sequencedShapesOnPage(rect)) {
195         if (KWFrameSet::from(shape) == fs) {
196             --neededColumnsCount;
197             if (neededColumnsCount < 0) {
198                 debugWords << "Deleting KWFrame from MainTextFrame";
199                 fs->removeShape(shape);
200                 delete shape;
201             }
202         }
203     }
204     const qreal colwidth = pageStyle.pageLayout().width / columnsCount;
205     const qreal colheight = pageStyle.pageLayout().height;
206     // Create missing column shapes
207     for (int c = 0; c < neededColumnsCount; ++c) {
208         debugWords << "Creating KWFrame for MainTextFrame";
209         KoShape * shape = createTextShape(page);
210         shape->setPosition(QPoint(c * colwidth+10.0, page.offsetInDocument()+10.0));
211         shape->setSize(QSizeF(colwidth, colheight));
212         new KWFrame(shape, fs);
213     }
214 
215     if (pageStyle.background() || (columnsCount > 1)) {
216         // create page background
217         if (!m_backgroundFrameSet) {
218             m_backgroundFrameSet = new KWFrameSet(Words::BackgroundFrameSet);
219             m_backgroundFrameSet->setName("backgroundFrames");
220             emit newFrameSet(m_backgroundFrameSet);
221             Q_ASSERT(m_frameSets.contains(m_backgroundFrameSet)); // the emit should have made that so :)
222         }
223         KoShape *background = sequencedShapeOn(m_backgroundFrameSet, pageNumber);
224         if (background == 0) {
225             background = new KWPageBackground();
226             background->setPosition(QPointF(0, page.offsetInDocument()));
227             new KWFrame(background, m_backgroundFrameSet);
228             background->setTextRunAroundSide(KoShape::RunThrough);
229         }
230         background->setBackground(pageStyle.background());
231     } else {
232         // check if there is a frame, if so, delete it, we don't need it.
233         KoShape *background = sequencedShapeOn(m_backgroundFrameSet, pageNumber);
234         if (background)
235             delete background;
236     }
237 
238     layoutFramesOnPage(pageNumber);
239 }
240 
layoutFramesOnPage(int pageNumber)241 void KWFrameLayout::layoutFramesOnPage(int pageNumber)
242 {
243     /* assumes all frames are there and will do layouting of all the frames
244         - headers/footers/main FS are positioned
245         - normal frames are clipped to page */
246     KWPage page = m_pageManager->page(pageNumber);
247     Q_ASSERT(page.isValid());
248     if (!page.isValid())
249         return;
250 
251     /* +-----------------+
252        |  0              | <- pageStyle->pageLayout()->topMargin + layout->topPadding
253        |  1  [ header ]  |
254        |  2              | <- pageStyle->headerDistance()
255        |  3  [ maintxt ] |
256        |  4              | <- pageStyle->footerDistance()
257        |  5  [ footer ]  |
258        |  6              | <- pageStyle->pageLayout()->bottomMargin + layout->bottomPadding
259        +-----------------+ */
260 
261     // Create some data structures used for the layouting of the frames later
262     int minZIndex = INT_MAX;
263     qreal requestedHeight[7], minimumHeight[7], resultingPositions[7];
264     for (int i = 0; i < 7; i++) { // zero fill.
265         requestedHeight[i] = 0;
266         minimumHeight[i] = 0;
267         resultingPositions[i] = 0;
268     }
269     minimumHeight[0] = page.topMargin() + page.topPadding();
270     minimumHeight[6] = page.bottomMargin() + page.bottomPadding();
271 
272     KoPageLayout layout = page.pageStyle().pageLayout();
273     layout.leftMargin = page.leftMargin();
274     layout.rightMargin = page.rightMargin();
275     layout.leftPadding = page.leftPadding();
276     layout.rightPadding = page.rightPadding();
277     const qreal left = 0, width = page.width();
278     const qreal textWidth = width - layout.leftMargin - layout.rightMargin
279                                   - layout.leftPadding - layout.rightPadding;
280 
281     KWPageStyle pageStyle = page.pageStyle();
282     KoColumns columns = pageStyle.columns();
283     int columnIndex = 0;
284     KoShape **main;
285     KoShape *footer = 0, *header = 0;
286     KoShape *pageBackground = 0;
287     main = new KoShape*[columns.count];
288     if (columns.count > 0)
289         main[0] = 0;
290     QRectF pageRect(left, page.offsetInDocument(), width, page.height());
291     QList<KoShape *> shapes = sequencedShapesOnPage(pageRect);
292 
293     debugWords << "pageNumber=" << pageNumber << "columns=" << columns.count << "shapeCount=" << shapes.count();
294     foreach (KoShape *shape, shapes) {
295         KWTextFrameSet *textFrameSet = 0;
296         switch (KWFrameSet::from(shape)->type()) {
297         case Words::BackgroundFrameSet:
298             pageBackground = shape;
299             continue;
300         case Words::TextFrameSet:
301             textFrameSet = static_cast<KWTextFrameSet*>(KWFrameSet::from(shape));
302             if (textFrameSet->textFrameSetType() == Words::OtherTextFrameSet) {
303                 minZIndex = qMin(minZIndex, shape->zIndex());
304                 continue;
305             }
306             break;
307         case Words::OtherFrameSet:
308             minZIndex = qMin(minZIndex, shape->zIndex());
309             continue;
310         }
311         Q_ASSERT(textFrameSet);
312 
313         /*
314         KWPage page = m_pageManager->page(frame->shape());
315         Q_ASSERT(page.isValid());
316         debugWords << "textFrameSetType=" << Words::frameSetTypeName(textFrameSet->textFrameSetType())
317                  << "page=" << page.pageNumber()
318                  << "offset=" << page.offsetInDocument()
319                  << "position=" << frame->shape()->position()
320                  << "size=" << frame->shape()->size()
321                  << "outlineRect=" << frame->shape()->outlineRect()
322                  << "boundingRect=" << frame->shape()->boundingRect();
323         */
324 
325         switch (textFrameSet->textFrameSetType()) {
326         case Words::OddPagesHeaderTextFrameSet:
327         case Words::EvenPagesHeaderTextFrameSet: {
328             header = shape;
329             minimumHeight[2] = pageStyle.headerDistance();
330             minimumHeight[1] = qMax((qreal)10, pageStyle.headerMinimumHeight() - pageStyle.headerDistance());
331             requestedHeight[1] = qMax(minimumHeight[1], textFrameSet->shapes().first()->minimumHeight());
332             if (pageStyle.headerDynamicSpacing()) {
333                 minimumHeight[2] = qMax((qreal)0, minimumHeight[1] - requestedHeight[1]);
334             }
335             break;
336         }
337         case Words::OddPagesFooterTextFrameSet:
338         case Words::EvenPagesFooterTextFrameSet: {
339             footer = shape;
340             minimumHeight[4] = pageStyle.footerDistance();
341             minimumHeight[5] = qMax((qreal)10, pageStyle.footerMinimumHeight() - pageStyle.footerDistance());
342             requestedHeight[5] = qMax(minimumHeight[5], textFrameSet->shapes().first()->minimumHeight());
343             if (pageStyle.footerDynamicSpacing()) {
344                 minimumHeight[4] = qMax((qreal)0, minimumHeight[5] - requestedHeight[5]);
345             }
346             break;
347         }
348         case Words::MainTextFrameSet: {
349             if (columnIndex == columns.count) {
350                 warnWords << "Too many columns present on page, ignoring 1, columns.count=" << columns.count;
351                 break;
352             }
353             main[columnIndex] = shape;
354             ++columnIndex;
355             minimumHeight[3] = 10;
356             // make at least one line fit lest we add endless pages.
357             QTextLayout *layout = textFrameSet->document()->begin().layout();
358             if (layout && layout->lineCount() > 0) {
359                 minimumHeight[3] = qMax((qreal) 10, layout->lineAt(0).height());
360             }
361             requestedHeight[3] = -1; // rest
362             break;
363         }
364         default:;
365         }
366     }
367 
368     pageBackground = sequencedShapeOn(m_backgroundFrameSet, pageNumber);
369 
370     --minZIndex;
371     for (int i = 0; i < columns.count; ++i) {
372         Q_ASSERT_X(main[i], __FUNCTION__, QString("No TextShape for column=%1 columnsCount=%2").arg(i).arg(columns.count).toLocal8Bit());
373         if (main[i])
374             main[i]->setZIndex(minZIndex);
375     }
376     if (footer) {
377         footer->setZIndex(minZIndex);
378         // Make us compatible with ms word (seems saner too). Compatible with LO would be 0
379         footer->setRunThrough(-3); //so children will be <= -2 and thus below main text
380     }
381     if (header) {
382         header->setZIndex(minZIndex);
383         // Make us compatible with ms word (seems saner too). Compatible with LO would be 0
384         header->setRunThrough(-3); //so children will be <= -2 and thus below main text
385     }
386 
387     if (pageBackground) {
388         pageBackground->setRunThrough(-10); //so it will be below everything
389         pageBackground->setZIndex(--minZIndex);
390     }
391 
392     // spread space across items.
393     qreal heightLeft = page.height();
394     for (int i = 0; i < 7; i++) {
395         heightLeft -= qMax(minimumHeight[i], requestedHeight[i]);
396     }
397     if (heightLeft >= 0) { // easy; plenty of space
398         minimumHeight[3] += heightLeft; // add space to main text frame
399         qreal y = page.offsetInDocument();
400         for (int i = 0; i < 7; i++) {
401             resultingPositions[i] = y;
402             y += qMax(minimumHeight[i], requestedHeight[i]);
403         }
404     } else {
405         // for situations where the header + footer are too big to fit together with a
406         // minimum sized main text frame.
407         heightLeft = page.height();
408         for (int i = 0; i < 7; i++)
409             heightLeft -= minimumHeight[i];
410         qreal y = page.offsetInDocument();
411         for (int i = 0; i < 7; i++) {
412             resultingPositions[i] = y;
413             qreal row = minimumHeight[i];
414             if (requestedHeight[i] > row) {
415                 row += heightLeft / 3;
416             }
417             y += row;
418         }
419     }
420 
421     // actually move / size the frames.
422     if (columns.count > 0 && main[0]) {
423         const qreal fullColumnHeight = resultingPositions[4] - resultingPositions[3];
424         const qreal columnsXOffset = left + layout.leftMargin + layout.leftPadding;
425 
426         QRectF *columnRects = new QRectF[columns.count];
427         // uniform columns?
428         if (columns.columnData.isEmpty()) {
429             const qreal columnWidth = (textWidth - columns.gapWidth * (columns.count - 1)) / columns.count;
430             const qreal columnStep = columnWidth + columns.gapWidth;
431 
432             for (int i = 0; i < columns.count; i++) {
433                 columnRects[i] = QRectF(
434                     columnsXOffset + columnStep * i,
435                     resultingPositions[3],
436                     columnWidth,
437                     fullColumnHeight);
438             }
439         } else {
440             const qreal totalRelativeWidth = columns.totalRelativeWidth();
441 
442             int relativeColumnXOffset = 0;
443             for (int i = 0; i < columns.count; i++) {
444                 const KoColumns::ColumnDatum &columnDatum = columns.columnData.at(i);
445                 const qreal columnWidth = textWidth * columnDatum.relativeWidth / totalRelativeWidth;
446                 const qreal columnXOffset = textWidth * relativeColumnXOffset / totalRelativeWidth;
447 
448                 columnRects[i] = QRectF(
449                     columnsXOffset + columnXOffset + columnDatum.leftMargin,
450                     resultingPositions[3] + columnDatum.topMargin,
451                     columnWidth - columnDatum.leftMargin - columnDatum.rightMargin,
452                     fullColumnHeight - columnDatum.topMargin - columnDatum.bottomMargin);
453 
454                 relativeColumnXOffset += columnDatum.relativeWidth;
455             }
456         }
457 
458         //make sure the order of shapes geometrically follows the textflow order
459         for (int i = 0; i < columns.count; i++) {
460             for (int f = 0; f < columns.count; f++) {
461                 if (f == i) continue;
462                 if (qAbs(main[f]->position().x() - columnRects[i].x()) < 10.0) {
463                     qSwap(main[f], main[i]);
464                     break;
465                 }
466             }
467         }
468 
469         // finally set size and position of the shapes
470         for (int i = columns.count - 1; i >= 0; i--) {
471             main[i]->setPosition(columnRects[i].topLeft());
472             main[i]->setSize(columnRects[i].size());
473         }
474 
475         delete[] columnRects;
476 
477         // We need to store the content rect so layout can place it's anchored shapes
478         // correctly
479         page.setContentRect(QRectF(QPointF(left + layout.leftMargin + layout.leftPadding, resultingPositions[3]), QSizeF(textWidth ,resultingPositions[4] - resultingPositions[3])));
480     }
481     if (header) {
482         header->setPosition(
483             QPointF(left + layout.leftMargin + layout.leftPadding, resultingPositions[1]));
484         header->setSize(QSizeF(textWidth, resultingPositions[2] - resultingPositions[1]));
485     }
486     if (footer) {
487         footer->setPosition(
488             QPointF(left + layout.leftMargin + layout.leftPadding, resultingPositions[5]));
489         footer->setSize(QSizeF(textWidth, resultingPositions[6] - resultingPositions[5]));
490     }
491     if (pageBackground) {
492         pageBackground->setPosition(QPointF(left + layout.leftMargin, page.offsetInDocument() + layout.topMargin));
493         pageBackground->setSize(QSizeF(width - layout.leftMargin - layout.rightMargin,
494                                                 page.height() - layout.topMargin - layout.bottomMargin));
495 
496         // set separator data
497         KWPageBackground *bs = dynamic_cast<KWPageBackground *>(pageBackground);
498         if (columns.count > 1) {
499             const qreal fullColumnHeight = resultingPositions[4] - resultingPositions[3];
500 
501             QList<qreal> separatorXPositions;
502             // uniform columns?
503             if (columns.columnData.isEmpty()) {
504                 const qreal separatorXBaseOffset = layout.leftPadding - (columns.gapWidth * 0.5);
505                 const qreal columnWidth = (textWidth - columns.gapWidth * (columns.count - 1)) / columns.count;
506                 const qreal columnStep = columnWidth + columns.gapWidth;
507                 for (int i = 1; i < columns.count; ++i) {
508                     separatorXPositions << separatorXBaseOffset + columnStep * i;
509                 }
510             } else {
511                 const qreal totalRelativeWidth = columns.totalRelativeWidth();
512 
513                 int relativeColumnXOffset = 0;
514                 for (int i = 0; i < columns.count-1; i++) {
515                     const KoColumns::ColumnDatum &columnDatum = columns.columnData.at(i);
516                     relativeColumnXOffset += columnDatum.relativeWidth;
517                     const qreal columnXOffset = textWidth * relativeColumnXOffset / totalRelativeWidth;
518 
519                     separatorXPositions << layout.leftPadding + columnXOffset;
520                 }
521             }
522             const qreal separatorHeight = fullColumnHeight * columns.separatorHeight / 100.0;
523             const qreal separatorY = layout.topPadding +
524                 ((columns.separatorVerticalAlignment == KoColumns::AlignBottom) ?
525                     fullColumnHeight * (100 - columns.separatorHeight) / 100.0 :
526                  (columns.separatorVerticalAlignment == KoColumns::AlignVCenter) ?
527                     fullColumnHeight * (100 - columns.separatorHeight) / 200.0 :
528                 /* default: KoColumns::AlignTop */
529                     0);
530             bs->setSeparators(columns.separatorStyle, columns.separatorColor,
531                               separatorXPositions, separatorY,
532                               columns.separatorWidth, separatorHeight);
533         } else {
534             bs->clearSeparators();
535         }
536     }
537     delete [] main;
538 }
539 
proposeShapeMove(const KoShape * shape,QPointF & delta,const KWPage & page)540 void KWFrameLayout::proposeShapeMove(const KoShape *shape, QPointF &delta, const KWPage &page)
541 {
542     KoShapeAnchor *anchor = shape->anchor();
543     if (!anchor) {
544         return; // nothing we can do
545     }
546 
547     QRectF refRect;
548     const qreal textWidth = page.width() - page.leftMargin() - page.rightMargin()
549                                 - page.leftPadding() - page.rightPadding();
550     switch (anchor->horizontalRel()) {
551         case KoShapeAnchor::HParagraph: // LO mistakenly saves it like this sometimes - stupid LO
552         anchor->setHorizontalRel(KoShapeAnchor::HPage); // let's fix it
553         // fall through
554     case KoShapeAnchor::HPage:
555         refRect.setX(0);
556         refRect.setWidth(page.width());
557         break;
558     case KoShapeAnchor::HPageContent:
559         refRect.setX(page.leftMargin() + page.leftPadding());
560         refRect.setWidth(textWidth);
561         break;
562     case KoShapeAnchor::HPageStartMargin:
563         refRect.setX(0);
564         refRect.setRight(page.leftMargin() + page.leftPadding());
565         break;
566     case KoShapeAnchor::HPageEndMargin:
567         refRect.setX(page.width() - page.rightMargin() - page.rightPadding());
568         refRect.setRight(page.width());
569         break;
570     default:
571         break;
572     }
573     switch (anchor->verticalRel()) {
574     case KoShapeAnchor::VPage:
575         refRect.setY(page.offsetInDocument());
576         refRect.setHeight(page.height());
577         break;
578     case KoShapeAnchor::VPageContent:
579         refRect.setY(page.contentRect().y());
580         refRect.setHeight(page.contentRect().height());
581         break;
582     default:
583         break;
584     }
585     QPointF newPos = shape->position() + delta;
586     switch (anchor->horizontalPos()) {
587     case KoShapeAnchor::HLeft:
588         newPos.setX(refRect.x());
589         break;
590     case KoShapeAnchor::HCenter:
591         newPos.setX(refRect.x() + (refRect.width() - shape->size().width()) / 2);
592         break;
593     case KoShapeAnchor::HRight:
594         newPos.setX(refRect.right() - shape->size().width());
595         break;
596     default:
597         break;
598     }
599 
600     switch (anchor->verticalPos()) {
601     case KoShapeAnchor::VTop:
602         newPos.setY(refRect.y());
603         break;
604     case KoShapeAnchor::VMiddle:
605         newPos.setY(refRect.y() + (refRect.height() - shape->size().height()) / 2);
606         break;
607     case KoShapeAnchor::VBottom:
608         newPos.setY(refRect.bottom() - shape->size().height());
609         break;
610     default:
611         break;
612     }
613     delta = newPos - shape->position();
614 }
615 
shouldHaveHeaderOrFooter(int pageNumber,bool header,Words::TextFrameSetType * origin)616 bool KWFrameLayout::shouldHaveHeaderOrFooter(int pageNumber, bool header, Words::TextFrameSetType *origin)
617 {
618     KWPage page = m_pageManager->page(pageNumber);
619     Q_ASSERT(page.isValid());
620     KWPageStyle pagestyle = page.pageStyle();
621     Words::HeaderFooterType type = header ? pagestyle.headerPolicy() : pagestyle.footerPolicy();
622     switch (type) {
623         case Words::HFTypeNone:
624             return false;
625         case Words::HFTypeEvenOdd:
626             if (header)
627                 *origin = pageNumber % 2 == 0 ? Words::EvenPagesHeaderTextFrameSet : Words::OddPagesHeaderTextFrameSet;
628             else
629                 *origin = pageNumber % 2 == 0 ? Words::EvenPagesFooterTextFrameSet : Words::OddPagesFooterTextFrameSet;
630             break;
631         case Words::HFTypeUniform:
632             *origin = header ? Words::OddPagesHeaderTextFrameSet : Words::OddPagesFooterTextFrameSet;
633             break;
634     }
635     return true;
636 }
637 
sequencedShapesOnPage(int pageNumber) const638 QList<KoShape *> KWFrameLayout::sequencedShapesOnPage(int pageNumber) const
639 {
640     KWPage page = m_pageManager->page(pageNumber);
641     Q_ASSERT(page.isValid());
642     return sequencedShapesOnPage(page.rect());
643 }
644 
sequencedShapesOnPage(const QRectF & page) const645 QList<KoShape *> KWFrameLayout::sequencedShapesOnPage(const QRectF &page) const
646 {
647     // hopefully replaced with a tree
648     QList<KoShape *> answer;
649     foreach (KWFrameSet *fs, m_frameSets) {
650         foreach (KoShape *shape, fs->shapes()) {
651             // use TopLeftCorner as main,header,footer are not rotated, also see bug 275288
652             if (page.contains(shape->absolutePosition(KoFlake::TopLeftCorner)))
653                 answer.append(shape);
654         }
655     }
656     return answer;
657 }
658 
getOrCreate(Words::TextFrameSetType type,const KWPage & page)659 KWTextFrameSet *KWFrameLayout::getOrCreate(Words::TextFrameSetType type, const KWPage &page)
660 {
661     Q_ASSERT(page.isValid());
662     setup();
663     FrameSets frameSets = m_pageStyles.value(page.pageStyle());
664     KWTextFrameSet **answer = 0;
665     switch (type) {
666     case Words::OddPagesHeaderTextFrameSet:
667         answer = &frameSets.oddHeaders;
668         break;
669     case Words::EvenPagesHeaderTextFrameSet:
670         answer = &frameSets.evenHeaders;
671         break;
672     case Words::OddPagesFooterTextFrameSet:
673         answer = &frameSets.oddFooters;
674         break;
675     case Words::EvenPagesFooterTextFrameSet:
676         answer = &frameSets.evenFooters;
677         break;
678     case Words::MainTextFrameSet:
679         answer = &m_maintext;
680         break;
681     default:
682         Q_ASSERT(false); // we should never get asked for 'other'
683     }
684     Q_ASSERT(answer);
685 
686     // The frameset wasn't created yet what can happen if for example a file is
687     // loaded that does not exist or just does not create the required framesets.
688     if (*answer == 0) {
689         KWTextFrameSet *newFS = new KWTextFrameSet(m_document, type);
690         *answer = newFS;
691         if (type != Words::MainTextFrameSet) {
692             newFS->setPageStyle(page.pageStyle());
693             m_pageStyles.insert(page.pageStyle(), frameSets);
694         }
695         emit newFrameSet(newFS);
696         Q_ASSERT(m_frameSets.contains(newFS)); // the emit should have made that so :)
697     }
698 
699     return *answer;
700 }
701 
setup()702 void KWFrameLayout::setup()
703 {
704     // When framesets have been removed things needs to be updated
705     if (m_setup && ((m_maintext && !m_frameSets.contains(m_maintext)) || (m_backgroundFrameSet && !m_frameSets.contains(m_backgroundFrameSet)))) {
706         m_setup = false;
707     }
708     if (m_setup) {
709         return;
710     }
711     KWTextFrameSet *oldMainText = m_maintext;
712     m_maintext = 0;
713     m_backgroundFrameSet = 0;
714     m_pageStyles.clear();
715     foreach (KWFrameSet *fs, m_frameSets) {
716         if (fs->type() == Words::BackgroundFrameSet) {
717             m_backgroundFrameSet = fs;
718         } else if (fs->type() == Words::TextFrameSet) {
719             KWTextFrameSet *tfs = static_cast<KWTextFrameSet*>(fs);
720             FrameSets frameSets = m_pageStyles.value(tfs->pageStyle());
721             switch (tfs->textFrameSetType()) {
722             case Words::OddPagesHeaderTextFrameSet:
723                 frameSets.oddHeaders = tfs;
724                 break;
725             case Words::EvenPagesHeaderTextFrameSet:
726                 frameSets.evenHeaders = tfs;
727                 break;
728             case Words::OddPagesFooterTextFrameSet:
729                 frameSets.oddFooters = tfs;
730                 break;
731             case Words::EvenPagesFooterTextFrameSet:
732                 frameSets.evenFooters = tfs;
733                 break;
734             case Words::MainTextFrameSet:
735                 Q_ASSERT(m_maintext == 0); // there can be only one!
736                 if (tfs != oldMainText) {
737                     oldMainText = 0;
738                     disconnect(tfs, SIGNAL(shapeRemoved(KoShape*)),
739                             this, SLOT(mainShapeRemoved(KoShape*)));
740                     connect(tfs, SIGNAL(shapeRemoved(KoShape*)),
741                             this, SLOT(mainShapeRemoved(KoShape*)));
742                 }
743                 m_maintext = tfs;
744             default: ;// ignore
745             }
746             if (tfs->pageStyle().isValid())
747                 m_pageStyles.insert(tfs->pageStyle(), frameSets);
748         }
749     }
750     m_setup = true;
751 }
752 
createTextShape(const KWPage & page)753 KoShape *KWFrameLayout::createTextShape(const KWPage &page)
754 {
755     debugWords << "pageNumber=" << page.pageNumber();
756     Q_ASSERT(page.isValid());
757     KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(TextShape_SHAPEID);
758     if (!factory)
759         return 0;
760     KoDocumentResourceManager *rm = 0;
761     if (m_document)
762         rm = m_document->resourceManager();
763     KoShape *shape = factory->createDefaultShape(rm);
764     //Q_ASSERT(shape);
765     return shape;
766 }
767 
sequencedShapeOn(KWFrameSet * fs,int pageNumber) const768 KoShape *KWFrameLayout::sequencedShapeOn(KWFrameSet *fs, int pageNumber) const
769 {
770     KWPage page = m_pageManager->page(pageNumber);
771     Q_ASSERT(page.isValid());
772     foreach (KoShape *shape, sequencedShapesOnPage(page.rect())) {
773         if (KWFrameSet::from(shape) == fs)
774             return shape;
775     }
776     return 0;
777 }
778 
sequencedShapesOn(KWFrameSet * fs,int pageNumber) const779 QList<KoShape *> KWFrameLayout::sequencedShapesOn(KWFrameSet *fs, int pageNumber) const
780 {
781     KWPage page = m_pageManager->page(pageNumber);
782     Q_ASSERT(page.isValid());
783     QList<KoShape *> shapes;
784     foreach (KoShape *shape, sequencedShapesOnPage(page.rect())) {
785         if (KWFrameSet::from(shape) == fs)
786             shapes.append(shape);
787     }
788     return shapes;
789 }
790 
cleanFrameSet(KWTextFrameSet * fs)791 void KWFrameLayout::cleanFrameSet(KWTextFrameSet *fs)
792 {
793     debugWords << "frameSet=" << fs << "shapeCount=" << (fs ? fs->shapeCount() : 0);
794     if (fs == 0)
795         return;
796     if (fs->shapeCount() == 0)
797         return;
798     foreach (KWFrame *frame, fs->frames()) {
799         fs->removeShape(frame->shape());
800         delete frame->shape();
801     }
802 }
803 
createCopyFrame(KWFrameSet * fs,const KWPage & page)804 KWFrame *KWFrameLayout::createCopyFrame(KWFrameSet *fs, const KWPage &page)
805 {
806     Q_ASSERT(page.isValid());
807     debugWords << "frameSet=" << fs << "pageNumber=" << page.pageNumber() << "shapeCount=" << fs->shapeCount();
808     if (fs->shapeCount() == 0) { // special case for the headers. Just return a new textframe.
809         KWTextFrameSet *tfs = dynamic_cast<KWTextFrameSet*>(fs);
810         Q_ASSERT(tfs); // an empty, non-text frameset asking for a copy? Thats a bug.
811         KoShape *shape = createTextShape(page);
812         shape->setPosition(QPointF(10.0, page.offsetInDocument()+10.0));
813         shape->setSize(QSize(20, 10));
814         KWFrame *frame = new KWFrame(shape, tfs);
815         return frame;
816     }
817 
818     KoShape *orig = 0;
819     //Lets find the last non-copy frame in the frameset
820     for(int i = fs->shapeCount() - 1; i >= 0; --i) {
821         KoShape *candidate = fs->shapes()[i];
822         if (!dynamic_cast<KWCopyShape*>(candidate)) {
823             orig = candidate;
824             break;
825         }
826     }
827     Q_ASSERT(orig); // can't have a frameset with only copy frames.
828 
829     KWCopyShape *shape = new KWCopyShape(orig, m_pageManager);
830     shape->setPosition(QPointF(0, page.offsetInDocument()));
831     KWFrame *frame = new KWFrame(shape, fs);
832     return frame;
833 }
834 
mainFrameSet() const835 KWTextFrameSet *KWFrameLayout::mainFrameSet() const
836 {
837     const_cast<KWFrameLayout*>(this)->setup();
838     return m_maintext;
839 }
840 
getFrameSets(const KWPageStyle & pageStyle) const841 QList<KWTextFrameSet*> KWFrameLayout::getFrameSets(const KWPageStyle &pageStyle) const
842 {
843     FrameSets frameSets = m_pageStyles.value(pageStyle);
844     QList<KWTextFrameSet*> result;
845     result.append(m_maintext);
846     result.append(frameSets.oddHeaders);
847     result.append(frameSets.evenHeaders);
848     result.append(frameSets.oddFooters);
849     result.append(frameSets.evenFooters);
850     result.append(frameSets.pageBackground);
851     return result;
852 }
853 
getFrameSet(Words::TextFrameSetType type,const KWPageStyle & pageStyle) const854 KWTextFrameSet* KWFrameLayout::getFrameSet(Words::TextFrameSetType type, const KWPageStyle &pageStyle) const
855 {
856     FrameSets frameSets = m_pageStyles.value(pageStyle);
857     switch (type) {
858     case Words::OddPagesHeaderTextFrameSet:
859         return frameSets.oddHeaders;
860     case Words::EvenPagesHeaderTextFrameSet:
861         return frameSets.evenHeaders;
862     case Words::OddPagesFooterTextFrameSet:
863         return frameSets.oddFooters;
864     case Words::EvenPagesFooterTextFrameSet:
865         return frameSets.evenFooters;
866     case Words::MainTextFrameSet:
867         return m_maintext;
868     default:
869         break;
870     }
871     return 0;
872 }
873 
mainShapeRemoved(KoShape * shape)874 void KWFrameLayout::mainShapeRemoved(KoShape *shape)
875 {
876     // if a main-shape is removed we should remove all other auto-generated shapes on that page to allow
877     // the page to be removed totally.  Besides; we don't want to have a header when there is no main-shape on a page :)
878 
879     KWPage page = m_pageManager->page(shape);
880     if (!page.isValid()) return;
881     debugWords << "shape=" << shape << "pageNumber=" << page.pageNumber();
882 
883     QList<KoShape *> shapesToDelete;
884     foreach (KWFrameSet *fs, m_frameSets) {
885         KWTextFrameSet *tfs = dynamic_cast<KWTextFrameSet*> (fs);
886         if (!tfs || !Words::isAutoGenerated(tfs))
887             continue;
888         const bool isMainFs = fs == m_maintext;
889         foreach (KoShape *s, fs->shapes()) {
890             if (s == shape)
891                 continue;
892             if (page == m_pageManager->page(s)) {
893                 if (isMainFs) // there is another shape of the main text frameset on this page.
894                     return;
895                 shapesToDelete << s;
896             }
897         }
898     }
899 
900     // delete them!
901     foreach (KoShape *s, shapesToDelete) {
902         // first remove if from the frameset to make sure the doc gets a signal and removes the page if needed
903         //frame->frameSet()->removeFrame(frame);
904         // then actually delete the frame itself.
905         delete s;
906     }
907 }
908