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 }