Warning, file /office/calligra/libs/textlayout/ToCGenerator.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) 2010 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2010 Jean Nicolas Artaud <jean.nicolas.artaud@kogmbh.com>
0004  * Copyright (C) 2011 Pavol Korinek <pavol.korinek@ixonos.com>
0005  * Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com>
0006  * Copyright (C) 2011 Ko GmbH <cbo@kogmbh.com>
0007  *
0008  * This library is free software; you can redistribute it and/or
0009  * modify it under the terms of the GNU Library General Public
0010  * License as published by the Free Software Foundation; either
0011  * version 2 of the License, or (at your option) any later version.
0012  *
0013  * This library is distributed in the hope that it will be useful,
0014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016  * Library General Public License for more details.
0017  *
0018  * You should have received a copy of the GNU Library General Public License
0019  * along with this library; see the file COPYING.LIB.  If not, write to
0020  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0021  * Boston, MA 02110-1301, USA.
0022  */
0023 
0024 #include "ToCGenerator.h"
0025 
0026 #include <klocalizedstring.h>
0027 
0028 #include "KoTextDocumentLayout.h"
0029 #include "KoTextLayoutRootArea.h"
0030 #include "DummyDocumentLayout.h"
0031 
0032 #include <KoParagraphStyle.h>
0033 #include <KoTextPage.h>
0034 #include <KoTextDocument.h>
0035 #include <KoTextBlockData.h>
0036 #include <KoStyleManager.h>
0037 #include <KoTableOfContentsGeneratorInfo.h>
0038 
0039 #include <QTextDocument>
0040 #include <TextLayoutDebug.h>
0041 #include <KoBookmark.h>
0042 #include <KoTextRangeManager.h>
0043 
0044 #include <algorithm>
0045 
0046 static const QString INVALID_HREF_TARGET = "INVALID_HREF";
0047 
0048 ToCGenerator::ToCGenerator(QTextDocument *tocDocument, KoTableOfContentsGeneratorInfo *tocInfo)
0049     : QObject(tocDocument)
0050     , m_ToCDocument(tocDocument)
0051     , m_ToCInfo(tocInfo)
0052     , m_document(0)
0053     , m_documentLayout(0)
0054 {
0055     Q_ASSERT(tocDocument);
0056     Q_ASSERT(tocInfo);
0057 
0058     tocDocument->setUndoRedoEnabled(false);
0059     tocDocument->setDocumentLayout(new DummyDocumentLayout(tocDocument));
0060     KoTextDocument(tocDocument).setRelativeTabs(tocInfo->m_relativeTabStopPosition);
0061 }
0062 
0063 ToCGenerator::~ToCGenerator()
0064 {
0065     delete m_ToCInfo;
0066 }
0067 
0068 void ToCGenerator::setBlock(const QTextBlock &block)
0069 {
0070     m_block = block;
0071     m_documentLayout = static_cast<KoTextDocumentLayout *>(m_block.document()->documentLayout());
0072     m_document = m_documentLayout->document();
0073 }
0074 
0075 QString ToCGenerator::fetchBookmarkRef(const QTextBlock &block, KoTextRangeManager *textRangeManager)
0076 {
0077     QHash<int, KoTextRange *> ranges = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position() + block.length(), block.position(), block.position() + block.length());
0078 
0079     foreach (KoTextRange *range, ranges) {
0080         KoBookmark *bookmark = dynamic_cast<KoBookmark *>(range);
0081         if (bookmark) {
0082             return bookmark->name();
0083         }
0084     }
0085     return QString();
0086 }
0087 
0088 
0089 static QString removeWhitespacePrefix(const QString& text)
0090 {
0091     int firstNonWhitespaceCharIndex = 0;
0092     const int length = text.length();
0093     while (firstNonWhitespaceCharIndex < length && text.at(firstNonWhitespaceCharIndex).isSpace()) {
0094         firstNonWhitespaceCharIndex++;
0095     }
0096     return text.right(length - firstNonWhitespaceCharIndex);
0097 }
0098 
0099 
0100 bool ToCGenerator::generate()
0101 {
0102     if (!m_ToCInfo)
0103         return true;
0104 
0105     m_preservePagebreak = m_ToCDocument->begin().blockFormat().intProperty(KoParagraphStyle::BreakBefore) & KoText::PageBreak;
0106 
0107     m_success = true;
0108 
0109     QTextCursor cursor = m_ToCDocument->rootFrame()->lastCursorPosition();
0110     cursor.setPosition(m_ToCDocument->rootFrame()->firstPosition(), QTextCursor::KeepAnchor);
0111     cursor.beginEditBlock();
0112     cursor.insertBlock(QTextBlockFormat(), QTextCharFormat());
0113 
0114     KoStyleManager *styleManager = KoTextDocument(m_document).styleManager();
0115 
0116     if (!m_ToCInfo->m_indexTitleTemplate.text.isEmpty()) {
0117         KoParagraphStyle *titleStyle = styleManager->paragraphStyle(m_ToCInfo->m_indexTitleTemplate.styleId);
0118 
0119         // titleStyle == 0? then it might be in unused styles
0120         if (!titleStyle) {
0121             titleStyle = styleManager->unusedStyle(m_ToCInfo->m_indexTitleTemplate.styleId); // this should return true only for ToC template preview
0122         }
0123 
0124         if (!titleStyle) {
0125             titleStyle = styleManager->defaultTableOfcontentsTitleStyle();
0126         }
0127 
0128         QTextBlock titleTextBlock = cursor.block();
0129         titleStyle->applyStyle(titleTextBlock);
0130 
0131         cursor.insertText(m_ToCInfo->m_indexTitleTemplate.text);
0132         if (m_preservePagebreak) {
0133             QTextBlockFormat blockFormat;
0134             blockFormat.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak);
0135             cursor.mergeBlockFormat(blockFormat);
0136             m_preservePagebreak = false;
0137         }
0138         cursor.insertBlock(QTextBlockFormat(), QTextCharFormat());
0139     }
0140 
0141     // Add TOC
0142     // Iterate through all blocks to generate TOC
0143     QTextBlock block = m_document->rootFrame()->firstCursorPosition().block();
0144     int blockId = 0;
0145     for (; block.isValid(); block = block.next()) {
0146         // Choose only TOC blocks
0147         if (m_ToCInfo->m_useOutlineLevel) {
0148             if (block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) {
0149                 int level = block.blockFormat().intProperty(KoParagraphStyle::OutlineLevel);
0150                 generateEntry(level, cursor, block, blockId);
0151                 continue;
0152             }
0153         }
0154 
0155         if (m_ToCInfo->m_useIndexSourceStyles) {
0156             bool inserted = false;
0157             foreach (const IndexSourceStyles &indexSourceStyles, m_ToCInfo->m_indexSourceStyles) {
0158                 foreach (const IndexSourceStyle &indexStyle, indexSourceStyles.styles) {
0159                     if (indexStyle.styleId == block.blockFormat().intProperty(KoParagraphStyle::StyleId)) {
0160                         generateEntry(indexSourceStyles.outlineLevel, cursor, block, blockId);
0161                         inserted = true;
0162                         break;
0163                     }
0164                 }
0165                 if (inserted)
0166                     break;
0167             }
0168             if (inserted)
0169                 continue;
0170         }
0171 
0172         if (m_ToCInfo->m_useIndexMarks) {
0173             if (false) {
0174                 generateEntry(1, cursor, block, blockId);
0175                 continue;
0176             }
0177         }
0178     }
0179     cursor.endEditBlock();
0180 
0181     m_documentLayout->documentChanged(m_block.position(),1,1);
0182     return m_success;
0183 }
0184 
0185 static bool compareTab(const QVariant &tab1, const QVariant &tab2)
0186 {
0187     return tab1.value<KoText::Tab>().position < tab2.value<KoText::Tab>().position;
0188 }
0189 
0190 
0191 void ToCGenerator::generateEntry(int outlineLevel, QTextCursor &cursor, QTextBlock &block, int &blockId)
0192 {
0193     KoStyleManager *styleManager = KoTextDocument(m_document).styleManager();
0194 
0195     QString tocEntryText = block.text();
0196     tocEntryText.remove(QChar::ObjectReplacementCharacter);
0197     // some headings contain tabs, replace all occurrences with spaces
0198     tocEntryText.replace('\t',' ').remove(0x200B);
0199     tocEntryText = removeWhitespacePrefix(tocEntryText);
0200 
0201     // Add only blocks with text
0202     if (!tocEntryText.isEmpty()) {
0203         KoParagraphStyle *tocTemplateStyle = 0;
0204 
0205         if (outlineLevel >= 1 && (outlineLevel-1) < m_ToCInfo->m_entryTemplate.size()
0206                     && outlineLevel <= m_ToCInfo->m_outlineLevel) {
0207             // List's index starts with 0, outline level starts with 0
0208             const TocEntryTemplate *tocEntryTemplate = &m_ToCInfo->m_entryTemplate.at(outlineLevel - 1);
0209 
0210             // ensure that we fetched correct entry template
0211             Q_ASSERT(tocEntryTemplate->outlineLevel == outlineLevel);
0212             if (tocEntryTemplate->outlineLevel != outlineLevel) {
0213                 qDebug() << "TOC outline level not found correctly " << outlineLevel;
0214             }
0215 
0216             tocTemplateStyle = styleManager->paragraphStyle(tocEntryTemplate->styleId);
0217             if (tocTemplateStyle == 0) {
0218                 tocTemplateStyle = styleManager->defaultTableOfContentsEntryStyle(outlineLevel);
0219             }
0220 
0221             QTextBlockFormat blockFormat;
0222             if (m_preservePagebreak) {
0223                 blockFormat.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak);
0224                 m_preservePagebreak = false;
0225             }
0226             cursor.insertBlock(blockFormat, QTextCharFormat());
0227 
0228             QTextBlock tocEntryTextBlock = cursor.block();
0229             tocTemplateStyle->applyStyle( tocEntryTextBlock );
0230 
0231             KoTextBlockData bd(block);
0232 
0233             // save the current style due to hyperlinks
0234             QTextCharFormat savedCharFormat = cursor.charFormat();
0235             foreach (IndexEntry * entry, tocEntryTemplate->indexEntries) {
0236                 switch(entry->name) {
0237                     case IndexEntry::LINK_START: {
0238                         //IndexEntryLinkStart *linkStart = static_cast<IndexEntryLinkStart*>(entry);
0239 
0240                         QString target = fetchBookmarkRef(block, m_documentLayout->textRangeManager());
0241 
0242                         if (target.isNull()) {
0243                             // generate unique name for the bookmark
0244                             target = tocEntryText + "|outline" + QString::number(blockId);
0245                             blockId++;
0246 
0247                             // insert new KoBookmark
0248                             QTextCursor blockCursor(block);
0249                             KoBookmark *bookmark = new KoBookmark(blockCursor);
0250                             bookmark->setName(target);
0251                             m_documentLayout->textRangeManager()->insert(bookmark);
0252                         }
0253 
0254                         if (!target.isNull()) {
0255                             // copy it to alter subset of properties
0256                             QTextCharFormat linkCf(savedCharFormat);
0257                             linkCf.setAnchor(true);
0258                             linkCf.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor);
0259                             linkCf.setAnchorHref('#'+ target);
0260 
0261                             QBrush foreground = linkCf.foreground();
0262                             foreground.setColor(Qt::blue);
0263 
0264                             linkCf.setForeground(foreground);
0265                             linkCf.setProperty(KoCharacterStyle::UnderlineStyle, KoCharacterStyle::SolidLine);
0266                             linkCf.setProperty(KoCharacterStyle::UnderlineType, KoCharacterStyle::SingleLine);
0267                             cursor.setCharFormat(linkCf);
0268                         }
0269                         break;
0270                     }
0271                     case IndexEntry::CHAPTER: {
0272                         //IndexEntryChapter *chapter = static_cast<IndexEntryChapter*>(entry);
0273                         cursor.insertText(bd.counterText());
0274                         break;
0275                     }
0276                     case IndexEntry::SPAN: {
0277                         IndexEntrySpan *span = static_cast<IndexEntrySpan*>(entry);
0278                         cursor.insertText(span->text);
0279                         break;
0280                     }
0281                     case IndexEntry::TEXT: {
0282                         //IndexEntryText *text = static_cast<IndexEntryText*>(entry);
0283                         cursor.insertText(tocEntryText);
0284                         break;
0285                     }
0286                     case IndexEntry::TAB_STOP: {
0287                         IndexEntryTabStop *tabEntry = static_cast<IndexEntryTabStop*>(entry);
0288 
0289                         cursor.insertText("\t");
0290 
0291                         QTextBlockFormat blockFormat = cursor.blockFormat();
0292                         QList<QVariant> tabList =            (blockFormat.property(KoParagraphStyle::TabPositions)).value<QList<QVariant> >();
0293 
0294                         if (tabEntry->m_position.isEmpty()) {
0295                             tabEntry->tab.position = KoTextLayoutArea::MaximumTabPos;
0296                         } // else the position is already parsed into tab.position
0297                         tabList.append(QVariant::fromValue<KoText::Tab>(tabEntry->tab));
0298                         std::sort(tabList.begin(), tabList.end(), compareTab);
0299                         blockFormat.setProperty(KoParagraphStyle::TabPositions, QVariant::fromValue<QList<QVariant> >(tabList));
0300                         cursor.setBlockFormat(blockFormat);
0301                         break;
0302                     }
0303                     case IndexEntry::PAGE_NUMBER: {
0304                         //IndexEntryPageNumber *pageNumber = static_cast<IndexEntryPageNumber*>(entry);
0305                         cursor.insertText(resolvePageNumber(block));
0306                         break;
0307                     }
0308                     case IndexEntry::LINK_END: {
0309                         //IndexEntryLinkEnd *linkEnd = static_cast<IndexEntryLinkEnd*>(entry);
0310                         cursor.setCharFormat(savedCharFormat);
0311                         break;
0312                     }
0313                     default:{
0314                         qDebug() << "New or unknown index entry";
0315                         break;
0316                     }
0317                 }
0318             }// foreach
0319             cursor.setCharFormat(savedCharFormat);   // restore the cursor char format
0320         }
0321     }
0322 }
0323 
0324 QString ToCGenerator::resolvePageNumber(const QTextBlock &headingBlock)
0325 {
0326     KoTextDocumentLayout *layout = qobject_cast<KoTextDocumentLayout*>(m_document->documentLayout());
0327     KoTextLayoutRootArea *rootArea = layout->rootAreaForPosition(headingBlock.position());
0328     if (rootArea) {
0329         if (rootArea->page()) {
0330             return QString::number(rootArea->page()->visiblePageNumber());
0331         }
0332     else qDebug()<<"had root but no page";
0333     }
0334     m_success = false;
0335     return "###";
0336 }