Warning, file /office/calligra/libs/textlayout/ListItemsHelper.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) 2006-2007, 2010 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
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 "ListItemsHelper.h"
0022 
0023 #include <KoTextBlockData.h>
0024 #include <KoParagraphStyle.h>
0025 #include <KoTextDocument.h>
0026 #include <KoList.h>
0027 #include <KoOdfNumberDefinition.h>
0028 
0029 #include <TextLayoutDebug.h>
0030 #include <klocalizedstring.h>
0031 #include <QTextList>
0032 
0033 
0034 // ------------------- ListItemsHelper ------------
0035 /// \internal helper class for calculating text-lists prefixes and indents
0036 ListItemsHelper::ListItemsHelper(QTextList *textList, const QFont &font)
0037         : m_textList(textList)
0038         , m_fm(font, textList->document()->documentLayout()->paintDevice())
0039 {
0040 }
0041 
0042 void ListItemsHelper::recalculateBlock(QTextBlock &block)
0043 {
0044     //warnTextLayout;
0045     const QTextListFormat format = m_textList->format();
0046     const KoListStyle::LabelType labelType = static_cast<KoListStyle::LabelType>(format.style());
0047 
0048     const QString prefix = format.stringProperty(KoListStyle::ListItemPrefix);
0049     const QString suffix = format.stringProperty(KoListStyle::ListItemSuffix);
0050     const int level = format.intProperty(KoListStyle::Level);
0051     int dp = format.intProperty(KoListStyle::DisplayLevel);
0052     if (dp > level)
0053         dp = level;
0054     const int displayLevel = dp ? dp : 1;
0055 
0056     QTextBlockFormat blockFormat = block.blockFormat();
0057 
0058     // Look if we have a block that is inside a header. We need to special case them cause header-lists are
0059     // different from any other kind of list and they do build up there own global list (table of content).
0060     bool isOutline = blockFormat.intProperty(KoParagraphStyle::OutlineLevel) > 0;
0061 
0062     int startValue = 1;
0063     if (format.hasProperty(KoListStyle::StartValue))
0064         startValue = format.intProperty(KoListStyle::StartValue);
0065 
0066     int index = startValue;
0067     bool fixed = false;
0068     if (blockFormat.boolProperty(KoParagraphStyle::RestartListNumbering)) {
0069         index = format.intProperty(KoListStyle::StartValue);
0070         fixed = true;
0071     }
0072     const int paragIndex = blockFormat.intProperty(KoParagraphStyle::ListStartValue);
0073     if (paragIndex > 0) {
0074         index = paragIndex;
0075         fixed = true;
0076     }
0077 
0078     if (!fixed) {
0079         //if this is the first item then find if the list has to be continued from any other list
0080         KoList *listContinued = 0;
0081         if (m_textList->itemNumber(block) == 0 && KoTextDocument(m_textList->document()).list(m_textList) && (listContinued = KoTextDocument(m_textList->document()).list(m_textList)->listContinuedFrom())) {
0082             //find the previous list of the same level
0083             QTextList *previousTextList = listContinued->textLists().at(level - 1).data();
0084             if (previousTextList) {
0085                 QTextBlock textBlock = previousTextList->item(previousTextList->count() - 1);
0086                 if (textBlock.isValid()) {
0087                     index = KoTextBlockData(textBlock).counterIndex() + 1; //resume the previous list count
0088                 }
0089             }
0090         } else if (m_textList->itemNumber(block) > 0) {
0091             QTextBlock textBlock = m_textList->item(m_textList->itemNumber(block) - 1);
0092             if (textBlock.isValid()) {
0093                 index = KoTextBlockData(textBlock).counterIndex() + 1; //resume the previous list count
0094             }
0095         }
0096     }
0097 
0098     qreal width = 0.0;
0099     KoTextBlockData blockData(block);
0100 
0101     if (blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem)
0102         || blockFormat.boolProperty(KoParagraphStyle::IsListHeader)) {
0103         blockData.setCounterPlainText(QString());
0104         blockData.setCounterPrefix(QString());
0105         blockData.setCounterSuffix(QString());
0106         blockData.setPartialCounterText(QString());
0107         // set the counter for the current un-numbered list to the counter index of the previous list item.
0108         // index-1 because the list counter would have already incremented by one
0109         blockData.setCounterIndex(index - 1);
0110         if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader)) {
0111             blockData.setCounterWidth(format.doubleProperty(KoListStyle::MinimumWidth));
0112             blockData.setCounterSpacing(0);
0113         }
0114         return;
0115     }
0116 
0117     QString item;
0118     if (displayLevel > 1) {
0119         int checkLevel = level;
0120         int tmpDisplayLevel = displayLevel;
0121         bool counterResetRequired = true;
0122         for (QTextBlock b = block.previous(); tmpDisplayLevel > 1 && b.isValid(); b = b.previous()) {
0123             if (b.textList() == 0)
0124                 continue;
0125             QTextListFormat lf = b.textList()->format();
0126             if (lf.property(KoListStyle::StyleId) != format.property(KoListStyle::StyleId))
0127                continue; // uninteresting for us
0128             if (isOutline != bool(b.blockFormat().intProperty(KoParagraphStyle::OutlineLevel)))
0129                 continue; // also uninteresting cause the one is an outline-listitem while the other is not
0130 
0131             if (! KoListStyle::isNumberingStyle(static_cast<KoListStyle::LabelType>(lf.style()))) {
0132                 continue;
0133             }
0134 
0135             if (b.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) {
0136                 continue; //unnumbered listItems are irrelevant
0137             }
0138 
0139             const int otherLevel  = lf.intProperty(KoListStyle::Level);
0140             if (isOutline && checkLevel == otherLevel) {
0141                 counterResetRequired = false;
0142             }
0143 
0144             if (checkLevel <= otherLevel)
0145                 continue;
0146 
0147             KoTextBlockData otherData(b);
0148             if (!otherData.hasCounterData()) {
0149                 continue;
0150             }
0151             if (tmpDisplayLevel - 1 < otherLevel) { // can't just copy it fully since we are
0152                 // displaying less then the full counter
0153                 item += otherData.partialCounterText();
0154                 tmpDisplayLevel--;
0155                 checkLevel--;
0156                 for (int i = otherLevel + 1; i < level; i++) {
0157                     tmpDisplayLevel--;
0158                     KoOdfNumberDefinition numberFormat;
0159                     numberFormat.setFormatSpecification(static_cast<KoOdfNumberDefinition::FormatSpecification>(format.intProperty(KoListStyle::NumberFormat)));
0160                     numberFormat.setLetterSynchronization(format.boolProperty(KoListStyle::LetterSynchronization));
0161                     item += "." + numberFormat.formattedNumber(index); // add missing counters.
0162                 }
0163             } else { // just copy previous counter as prefix
0164                 QString otherPrefix = lf.stringProperty(KoListStyle::ListItemPrefix);
0165                 QString otherSuffix = lf.stringProperty(KoListStyle::ListItemSuffix);
0166                 QString pureCounter = otherData.counterText().mid(otherPrefix.size());
0167                 pureCounter = pureCounter.left(pureCounter.size() - otherSuffix.size());
0168                 item += pureCounter;
0169                 for (int i = otherLevel + 1; i < level; i++) {
0170                     KoOdfNumberDefinition numberFormat;
0171                     numberFormat.setFormatSpecification(static_cast<KoOdfNumberDefinition::FormatSpecification>(format.intProperty(KoListStyle::NumberFormat)));
0172                     numberFormat.setLetterSynchronization(format.boolProperty(KoListStyle::LetterSynchronization));
0173                     item += "." + numberFormat.formattedNumber(index); // add missing counters.
0174                 }
0175                 tmpDisplayLevel = 0;
0176                 if (isOutline && counterResetRequired) {
0177                     index = 1;
0178                 }
0179                 break;
0180             }
0181         }
0182         for (int i = 1; i < tmpDisplayLevel; i++) {
0183             KoOdfNumberDefinition numberFormat;
0184             numberFormat.setFormatSpecification(static_cast<KoOdfNumberDefinition::FormatSpecification>(format.intProperty(KoListStyle::NumberFormat)));
0185             numberFormat.setLetterSynchronization(format.boolProperty(KoListStyle::LetterSynchronization));
0186             item = numberFormat.formattedNumber(index) + "." + item; // add missing counters.
0187         }
0188     }
0189     bool calcWidth = true;
0190     QString partialCounterText;
0191     switch (labelType) {
0192     case KoListStyle::NumberLabelType: {
0193         KoOdfNumberDefinition::FormatSpecification spec = static_cast<KoOdfNumberDefinition::FormatSpecification>(format.intProperty(KoListStyle::NumberFormat));
0194 
0195         if (!(item.isEmpty() || item.endsWith('.') || item.endsWith(' '))) {
0196             if (spec == KoOdfNumberDefinition::Numeric || spec == KoOdfNumberDefinition::AlphabeticLowerCase ||
0197             spec == KoOdfNumberDefinition::AlphabeticUpperCase ||
0198             spec == KoOdfNumberDefinition::RomanLowerCase ||
0199             spec == KoOdfNumberDefinition::RomanUpperCase) {
0200                 item += '.';
0201             }
0202         }
0203 
0204         KoOdfNumberDefinition numberFormat;
0205         numberFormat.setFormatSpecification(spec);
0206         partialCounterText = numberFormat.formattedNumber(index);
0207         break;
0208     }
0209     case KoListStyle::BulletCharLabelType: {
0210         calcWidth = false;
0211         if (format.intProperty(KoListStyle::BulletCharacter))
0212             item = QString(QChar(format.intProperty(KoListStyle::BulletCharacter)));
0213         width = m_fm.width(item);
0214         int percent = format.intProperty(KoListStyle::RelativeBulletSize);
0215         if (percent > 0)
0216             width = width * (percent / 100.0);
0217         break;
0218     }
0219     case KoListStyle::None:
0220         calcWidth = false;
0221         width =  0.0;
0222         break;
0223     case KoListStyle::ImageLabelType:
0224         calcWidth = false;
0225         width = qMax(format.doubleProperty(KoListStyle::Width), (qreal)1.0);
0226         break;
0227     default:  // others we ignore.
0228         calcWidth = false;
0229     }
0230 
0231     blockData.setCounterIsImage(labelType == KoListStyle::ImageLabelType);
0232     blockData.setPartialCounterText(partialCounterText);
0233     blockData.setCounterIndex(index);
0234     item += partialCounterText;
0235     blockData.setCounterPlainText(item);
0236     blockData.setCounterPrefix(prefix);
0237     blockData.setCounterSuffix(suffix);
0238     if (calcWidth)
0239         width = m_fm.width(item);
0240     index++;
0241 
0242     width += m_fm.width(prefix + suffix);
0243 
0244     qreal counterSpacing = 0;
0245     if (format.boolProperty(KoListStyle::AlignmentMode)) {
0246         // for aligmentmode spacing should be 0
0247         counterSpacing = 0;
0248     } else {
0249         if (labelType != KoListStyle::None) {
0250             // see ODF spec 1.2 item 20.422
0251             counterSpacing = format.doubleProperty(KoListStyle::MinimumDistance);
0252             if (width < format.doubleProperty(KoListStyle::MinimumWidth)) {
0253                 counterSpacing -= format.doubleProperty(KoListStyle::MinimumWidth) - width;
0254             }
0255             counterSpacing = qMax(counterSpacing, qreal(0.0));
0256         }
0257         width = qMax(width, format.doubleProperty(KoListStyle::MinimumWidth));
0258     }
0259     blockData.setCounterWidth(width);
0260     blockData.setCounterSpacing(counterSpacing);
0261 }
0262 
0263 // static
0264 bool ListItemsHelper::needsRecalc(QTextList *textList)
0265 {
0266     Q_ASSERT(textList);
0267     QTextBlock tb = textList->item(0);
0268     KoTextBlockData blockData(tb);
0269     return !blockData.hasCounterData();
0270 }