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 }