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