Warning, file /office/calligra/libs/textlayout/FloatingAnchorStrategy.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2007, 2009, 2010 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2011 Matus Hanzes <matus.hanzes@ixonos.com>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Library General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, write to
0017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019  */
0020 
0021 #include "FloatingAnchorStrategy.h"
0022 
0023 #include "KoTextDocumentLayout.h"
0024 #include "KoTextLayoutObstruction.h"
0025 
0026 #include <KoShapeContainer.h>
0027 #include <KoTextShapeData.h>
0028 #include <KoTextLayoutRootArea.h>
0029 #include <KoAnchorTextRange.h>
0030 
0031 #include <TextLayoutDebug.h>
0032 
0033 #include <QTextLayout>
0034 #include <QTextBlock>
0035 
0036 FloatingAnchorStrategy::FloatingAnchorStrategy(KoAnchorTextRange *anchorRange, KoTextLayoutRootArea *rootArea)
0037     : AnchorStrategy(anchorRange->anchor(), rootArea)
0038     , m_obstruction(new KoTextLayoutObstruction(anchorRange->anchor()->shape(), QTransform()))
0039     , m_anchorRange(anchorRange)
0040 {
0041 }
0042 
0043 FloatingAnchorStrategy::~FloatingAnchorStrategy()
0044 {
0045 }
0046 
0047 
0048 void FloatingAnchorStrategy::updateObstruction(qreal documentOffset)
0049 {
0050     KoTextDocumentLayout *layout = dynamic_cast<KoTextDocumentLayout *>(m_anchorRange->document()->documentLayout());
0051 
0052     QTransform matrix = m_anchor->shape()->absoluteTransformation(0);
0053     matrix = matrix * m_anchor->shape()->parent()->absoluteTransformation(0).inverted();
0054     matrix.translate(0, documentOffset);
0055     m_obstruction->changeMatrix(matrix);
0056 
0057     layout->registerAnchoredObstruction(m_obstruction);
0058 }
0059 
0060 //should return true while we are still moving around
0061 bool FloatingAnchorStrategy::moveSubject()
0062 {
0063     if (!m_anchor->shape()->parent()) {
0064         return false; // let's fake we moved to force another relayout
0065     }
0066 
0067     // get the page data
0068     KoTextShapeData *data = qobject_cast<KoTextShapeData*>(m_anchor->shape()->parent()->userData());
0069     if (!data) {
0070         return false; // let's fake we moved to force another relayout
0071     }
0072 
0073     QTextBlock block = m_anchorRange->document()->findBlock(m_anchorRange->position());
0074     QTextLayout *layout = block.layout();
0075 
0076     // there should be always at least one line
0077     if (layout->lineCount() == 0) {
0078         return false; // let's fake we moved to force another relayout
0079     }
0080 
0081     // The bounding rect of the textshape in document coords
0082     QRectF containerBoundingRect = m_anchor->shape()->parent()->boundingRect();
0083 
0084     // The (to be calculated) reference rect for anchoring in document coords
0085     QRectF anchorBoundingRect;
0086 
0087     // This is in coords relative to texshape
0088     QPointF newPosition;
0089 
0090     QPointF offset;
0091     if (m_anchor->horizontalPos() == KoShapeAnchor::HFromLeft
0092         || m_anchor->horizontalPos() == KoShapeAnchor::HFromInside) {
0093         offset.setX(m_anchor->offset().x());
0094     }
0095     if (m_anchor->verticalPos() == KoShapeAnchor::VFromTop) {
0096         offset.setY(m_anchor->offset().y());
0097     }
0098 
0099     // set anchor bounding rectangle horizontal position and size
0100     if (!countHorizontalRel(anchorBoundingRect, containerBoundingRect, block, layout)) {
0101         return false; // let's fake we moved to force another relayout
0102     }
0103 
0104     // set anchor bounding rectangle vertical position
0105     if (!countVerticalRel(anchorBoundingRect, containerBoundingRect, data, block, layout)) {
0106         return false; // let's fake we moved to force another relayout
0107     }
0108 
0109     // Set shape horizontal alignment inside anchor bounding rectangle
0110     countHorizontalPos(newPosition, anchorBoundingRect);
0111 
0112     // Set shape vertical alignment inside anchor bounding rectangle
0113     countVerticalPos(newPosition, anchorBoundingRect);
0114 
0115     newPosition += offset;
0116 
0117     //check the border of page and move the shape back to have it visible
0118     checkPageBorder(newPosition);
0119 
0120     newPosition -= containerBoundingRect.topLeft();
0121 
0122     //check the border of layout environment and move the shape back to have it within
0123     if (m_anchor->flowWithText()) {
0124         checkLayoutEnvironment(newPosition, data);
0125     }
0126 
0127 
0128     checkStacking(newPosition);
0129 
0130     if (newPosition == m_anchor->shape()->position()) {
0131         if (m_anchor->shape()->textRunAroundSide() != KoShape::RunThrough) {
0132             updateObstruction(data->documentOffset());
0133         }
0134         return true;
0135     }
0136 
0137     // set the shape to the proper position based on the data
0138     m_anchor->shape()->update();
0139     m_anchor->shape()->setPosition(newPosition);
0140     m_anchor->shape()->update();
0141 
0142     if (m_anchor->shape()->textRunAroundSide() != KoShape::RunThrough) {
0143         updateObstruction(data->documentOffset());
0144     }
0145 
0146     return true;
0147 }
0148 
0149 bool FloatingAnchorStrategy::countHorizontalRel(QRectF &anchorBoundingRect, const QRectF &containerBoundingRect, QTextBlock &block, QTextLayout *layout)
0150 {
0151     switch (m_anchor->horizontalRel()) {
0152     case KoShapeAnchor::HPage:
0153         anchorBoundingRect.setX(pageRect().x());
0154         anchorBoundingRect.setWidth(pageRect().width());
0155         break;
0156 
0157     case KoShapeAnchor::HFrameContent:
0158     case KoShapeAnchor::HFrame:
0159         anchorBoundingRect.setX(containerBoundingRect.x());
0160         anchorBoundingRect.setWidth(containerBoundingRect.width());
0161         break;
0162 
0163     case KoShapeAnchor::HPageContent:
0164         anchorBoundingRect.setX(pageContentRect().x());
0165         anchorBoundingRect.setWidth(pageContentRect().width());
0166         break;
0167 
0168     case KoShapeAnchor::HParagraph:
0169         anchorBoundingRect.setX(paragraphRect().x() + containerBoundingRect.x());
0170         anchorBoundingRect.setWidth(paragraphRect().width());
0171         break;
0172 
0173     case KoShapeAnchor::HParagraphContent:
0174         anchorBoundingRect.setX(paragraphContentRect().x() + containerBoundingRect.x());
0175         anchorBoundingRect.setWidth(paragraphContentRect().width());
0176         break;
0177 
0178     case KoShapeAnchor::HChar: {
0179         QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
0180         if (!tl.isValid())
0181             return false; // lets go for a second round.
0182         anchorBoundingRect.setX(tl.cursorToX(m_anchorRange->position() - block.position()) + containerBoundingRect.x());
0183         anchorBoundingRect.setWidth(0.1); // just some small value
0184         break;
0185     }
0186     case KoShapeAnchor::HPageStartMargin: {
0187         int horizontalPos = m_anchor->horizontalPos();
0188         // if verticalRel is HFromInside or HInside or HOutside and the page number is even,
0189         // than set anchorBoundingRect to HPageEndMargin area
0190         if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
0191                 horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
0192             anchorBoundingRect.setX(containerBoundingRect.x() + containerBoundingRect.width());
0193             anchorBoundingRect.setWidth(pageRect().width() - anchorBoundingRect.x());
0194         } else {
0195             anchorBoundingRect.setX(pageRect().x());
0196             anchorBoundingRect.setWidth(containerBoundingRect.x());
0197         }
0198         break;
0199     }
0200     case KoShapeAnchor::HPageEndMargin:
0201     {
0202         int horizontalPos = m_anchor->horizontalPos();
0203         // if verticalRel is HFromInside or HInside or HOutside and the page number is even,
0204         // than set anchorBoundingRect to HPageStartMargin area
0205         if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
0206                 horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
0207             anchorBoundingRect.setX(pageRect().x());
0208             anchorBoundingRect.setWidth(containerBoundingRect.x());
0209         } else {
0210             anchorBoundingRect.setX(containerBoundingRect.x() + containerBoundingRect.width());
0211             anchorBoundingRect.setWidth(pageRect().width() - anchorBoundingRect.x());
0212         }
0213         break;
0214     }
0215     case KoShapeAnchor::HParagraphStartMargin:
0216     {
0217         int horizontalPos = m_anchor->horizontalPos();
0218         // if verticalRel is HFromInside or HInside or HOutside and the page number is even,
0219         // than set anchorBoundingRect to HParagraphEndMargin area
0220         if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
0221                 horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
0222 //FIXME             anchorBoundingRect.setX(state->x() + containerBoundingRect.x() + state->width());
0223             anchorBoundingRect.setWidth(containerBoundingRect.x() + containerBoundingRect.width() - anchorBoundingRect.x());
0224         } else {
0225             anchorBoundingRect.setX(containerBoundingRect.x());
0226 //FIXME             anchorBoundingRect.setWidth(state->x());
0227         }
0228         break;
0229     }
0230     case KoShapeAnchor::HParagraphEndMargin:
0231     {
0232         int horizontalPos = m_anchor->horizontalPos();
0233         // if verticalRel is HFromInside or HInside or HOutside and the page number is even,
0234         // than set anchorBoundingRect to HParagraphStartMargin area
0235         if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
0236                 horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
0237             anchorBoundingRect.setX(containerBoundingRect.x());
0238 //FIXME             anchorBoundingRect.setWidth(state->x());
0239         } else {
0240 //FIXME             anchorBoundingRect.setX(state->x() + containerBoundingRect.x() + state->width());
0241             anchorBoundingRect.setWidth(containerBoundingRect.x() + containerBoundingRect.width() - anchorBoundingRect.x());
0242         }
0243         break;
0244     }
0245     default :
0246         warnTextLayout << "horizontal-rel not handled";
0247     }
0248     return true;
0249 }
0250 
0251 void FloatingAnchorStrategy::countHorizontalPos(QPointF &newPosition, const QRectF &anchorBoundingRect)
0252 {
0253     switch (m_anchor->horizontalPos()) {
0254     case KoShapeAnchor::HCenter:
0255         newPosition.setX(anchorBoundingRect.x() + anchorBoundingRect.width()/2
0256          - m_anchor->shape()->size().width()/2);
0257         break;
0258 
0259     case KoShapeAnchor::HFromInside:
0260     case KoShapeAnchor::HInside:
0261     {
0262         if (pageNumber()%2 == 1) {
0263             newPosition.setX(anchorBoundingRect.x());
0264         } else {
0265             newPosition.setX(anchorBoundingRect.right() -
0266                     m_anchor->shape()->size().width() - 2*m_anchor->offset().x() );
0267         }
0268         break;
0269     }
0270     case KoShapeAnchor::HLeft:
0271     case KoShapeAnchor::HFromLeft:
0272         newPosition.setX(anchorBoundingRect.x());
0273         break;
0274 
0275     case KoShapeAnchor::HOutside:
0276     {
0277         if (pageNumber()%2 == 1) {
0278             newPosition.setX(anchorBoundingRect.right());
0279         } else {
0280             QSizeF size = m_anchor->shape()->boundingRect().size();
0281             newPosition.setX(anchorBoundingRect.x() - size.width() - m_anchor->offset().x());
0282         }
0283         break;
0284     }
0285     case KoShapeAnchor::HRight: {
0286         QSizeF size = m_anchor->shape()->boundingRect().size();
0287         newPosition.setX(anchorBoundingRect.right() - size.width());
0288         break;
0289     }
0290     default :
0291         warnTextLayout << "horizontal-pos not handled";
0292     }
0293 }
0294 
0295 bool FloatingAnchorStrategy::countVerticalRel(QRectF &anchorBoundingRect, const QRectF &containerBoundingRect,
0296                                           KoTextShapeData *data, QTextBlock &block, QTextLayout *layout)
0297 {
0298     //FIXME proper handle VFrame and VFrameContent but fallback to VPage/VPageContent for now to produce better results
0299 
0300     switch (m_anchor->verticalRel()) {
0301     case KoShapeAnchor::VPage:
0302         anchorBoundingRect.setY(pageRect().y());
0303         anchorBoundingRect.setHeight(pageRect().height());
0304         break;
0305 
0306     case KoShapeAnchor::VFrame:
0307     case KoShapeAnchor::VFrameContent:
0308         anchorBoundingRect.setY(containerBoundingRect.y());
0309         anchorBoundingRect.setHeight(containerBoundingRect.height());
0310         break;
0311 
0312     case KoShapeAnchor::VPageContent:
0313         anchorBoundingRect.setY(pageContentRect().y());
0314         anchorBoundingRect.setHeight(pageContentRect().height());
0315         break;
0316 
0317     case KoShapeAnchor::VParagraph:
0318         anchorBoundingRect.setY(paragraphRect().y() + containerBoundingRect.y()  - data->documentOffset());
0319         anchorBoundingRect.setHeight(paragraphRect().height());
0320         break;
0321 
0322     case KoShapeAnchor::VParagraphContent: {
0323         anchorBoundingRect.setY(paragraphContentRect().y() + containerBoundingRect.y()  - data->documentOffset());
0324         anchorBoundingRect.setHeight(paragraphContentRect().height());
0325     }
0326     break;
0327 
0328     case KoShapeAnchor::VLine: {
0329         QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
0330         if (!tl.isValid())
0331             return false; // lets go for a second round.
0332         QSizeF size = m_anchor->shape()->boundingRect().size();
0333         anchorBoundingRect.setY(tl.y() - size.height()
0334                         + containerBoundingRect.y() - data->documentOffset());
0335         anchorBoundingRect.setHeight(2*size.height());
0336     }
0337     break;
0338 
0339     case KoShapeAnchor::VText: // same as char apparently only used when as-char
0340     case KoShapeAnchor::VChar: {
0341          QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
0342          if (!tl.isValid())
0343              return false; // lets go for a second round.
0344          anchorBoundingRect.setY(tl.y() + containerBoundingRect.y() - data->documentOffset());
0345          anchorBoundingRect.setHeight(tl.height());
0346      }
0347      break;
0348 
0349     case KoShapeAnchor::VBaseline: {
0350          QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
0351          if (!tl.isValid())
0352              return false; // lets go for a second round.
0353          QSizeF size = m_anchor->shape()->boundingRect().size();
0354          anchorBoundingRect.setY(tl.y() + tl.ascent() - size.height()
0355             + containerBoundingRect.y() - data->documentOffset());
0356          anchorBoundingRect.setHeight(2*size.height());
0357      }
0358      break;
0359     default :
0360      warnTextLayout << "vertical-rel not handled";
0361     }
0362     return true;
0363 }
0364 
0365 void FloatingAnchorStrategy::countVerticalPos(QPointF &newPosition, const QRectF &anchorBoundingRect)
0366 {
0367     switch (m_anchor->verticalPos()) {
0368     case KoShapeAnchor::VBottom:
0369         newPosition.setY(anchorBoundingRect.bottom() - m_anchor->shape()->size().height());
0370         break;
0371     case KoShapeAnchor::VBelow:
0372         newPosition.setY(anchorBoundingRect.bottom());
0373         break;
0374 
0375     case KoShapeAnchor::VMiddle:
0376         newPosition.setY(anchorBoundingRect.y() + anchorBoundingRect.height()/2 - m_anchor->shape()->size().height()/2);
0377         break;
0378 
0379     case KoShapeAnchor::VFromTop:
0380     case KoShapeAnchor::VTop:
0381         newPosition.setY(anchorBoundingRect.y());
0382         break;
0383 
0384     default :
0385         warnTextLayout << "vertical-pos not handled";
0386     }
0387 
0388 }
0389 
0390 void FloatingAnchorStrategy::checkLayoutEnvironment(QPointF &newPosition, KoTextShapeData *data)
0391 {
0392     QSizeF size = m_anchor->shape()->boundingRect().size();
0393 
0394     //check left border and move the shape back to have the whole shape within
0395     if (newPosition.x() < layoutEnvironmentRect().x()) {
0396         newPosition.setX(layoutEnvironmentRect().x());
0397     }
0398 
0399     //check right border and move the shape back to have the whole shape within
0400     if (newPosition.x() + size.width() > layoutEnvironmentRect().right()) {
0401         newPosition.setX(layoutEnvironmentRect().right() - size.width());
0402     }
0403 
0404     //check top border and move the shape back to have the whole shape within
0405     if (newPosition.y() < layoutEnvironmentRect().y() - data->documentOffset()) {
0406         newPosition.setY(layoutEnvironmentRect().y() - data->documentOffset());
0407     }
0408 
0409     //check bottom border and move the shape back to have the whole shape within
0410     if (newPosition.y() + size.height() > layoutEnvironmentRect().bottom() - data->documentOffset()) {
0411         newPosition.setY(layoutEnvironmentRect().bottom() - size.height() - data->documentOffset());
0412     }
0413 }
0414 
0415 void FloatingAnchorStrategy::checkPageBorder(QPointF &newPosition)
0416 {
0417     QSizeF size = m_anchor->shape()->boundingRect().size();
0418 
0419     //check left border and move the shape back to have the whole shape visible
0420     if (newPosition.x() < pageRect().x()) {
0421         newPosition.setX(pageRect().x());
0422     }
0423 
0424     //check right border and move the shape back to have the whole shape visible
0425     if (newPosition.x() + size.width() > pageRect().x() + pageRect().width()) {
0426         newPosition.setX(pageRect().x() + pageRect().width() - size.width());
0427     }
0428 
0429     //check top border and move the shape back to have the whole shape visible
0430     if (newPosition.y() < pageRect().y()) {
0431         newPosition.setY(pageRect().y());
0432     }
0433 
0434     //check bottom border and move the shape back to have the whole shape visible
0435     if (newPosition.y() + size.height() > pageRect().y() + pageRect().height()) {
0436         newPosition.setY(pageRect().y() + pageRect().height() - size.height());
0437     }
0438 }
0439 
0440 // If the horizontal-pos is Left or Right then we need to check if there are other
0441 // objects anchored with horizontal-pos left or right. If there are then we need
0442 // to "stack" our object on them what means that rather then floating this object
0443 // over the other it is needed to adjust the position to be sure they are not
0444 // floating over each other.
0445 void FloatingAnchorStrategy::checkStacking(QPointF &newPosition)
0446 {
0447     if (m_anchor->anchorType() != KoShapeAnchor::AnchorParagraph || (m_anchor->horizontalPos() != KoShapeAnchor::HLeft && m_anchor->horizontalPos() != KoShapeAnchor::HRight))
0448         return;
0449 
0450     int idx = m_rootArea->documentLayout()->textAnchors().indexOf(m_anchor);
0451     Q_ASSERT_X(idx >= 0, __FUNCTION__, QString("WTF? How can our anchor not be in the anchor-list but still be called?").toLocal8Bit());
0452 
0453     QSizeF size = m_anchor->shape()->boundingRect().size();
0454     for(int i = 0; i < idx; ++i) {
0455         KoShapeAnchor *a = m_rootArea->documentLayout()->textAnchors()[i];
0456         if (m_anchor->anchorType() != a->anchorType() || m_anchor->horizontalPos() != a->horizontalPos())
0457             continue;
0458 
0459         QRectF thisRect(newPosition, size);
0460         QRectF r(a->shape()->boundingRect());
0461         if (thisRect.intersects(r)) {
0462             if (m_anchor->horizontalPos() == KoShapeAnchor::HLeft)
0463                 newPosition.setX(a->shape()->position().x() + r.width());
0464             else // KoShapeAnchor::HRight
0465                 newPosition.setX(a->shape()->position().x() - size.width());
0466         }
0467     }
0468 }