File indexing completed on 2024-05-12 13:04:47
0001 /* 0002 * This file is part of the KDE project 0003 * 0004 * SPDX-FileCopyrightText: 2013 Dan Leinir Turthra Jensen <admin@leinir.dk> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.0-or-later 0007 * 0008 */ 0009 0010 #include "CQTextToCModel.h" 0011 #include "CQTextDocumentCanvas.h" 0012 0013 #include <KoTextDocumentLayout.h> 0014 #include <KoTextLayoutRootArea.h> 0015 #include <KoTextPage.h> 0016 #include <styles/KoParagraphStyle.h> 0017 #include <KWDocument.h> 0018 #include <frames/KWTextFrameSet.h> 0019 0020 #include <QTextObject> 0021 #include <QTextDocument> 0022 #include <QTextCursor> 0023 #include <QTimer> 0024 0025 Q_DECLARE_METATYPE(QTextDocument*) 0026 0027 struct TextToCModelEntry { 0028 TextToCModelEntry() 0029 : level(0) 0030 , pageNumber(0) 0031 {} 0032 QString title; 0033 int level; 0034 int pageNumber; 0035 }; 0036 0037 class CQTextToCModel::Private { 0038 public: 0039 Private() 0040 : canvas(0) 0041 , document(0) 0042 , documentLayout(0) 0043 {} 0044 0045 QList<TextToCModelEntry*> entries; 0046 0047 CQTextDocumentCanvas* canvas; 0048 QTextDocument* document; 0049 KoTextDocumentLayout* documentLayout; 0050 0051 QTimer updateTimer; 0052 QTimer doneTimer; 0053 0054 int resolvePageNumber(const QTextBlock &headingBlock) { 0055 KoTextDocumentLayout *layout = qobject_cast<KoTextDocumentLayout*>(document->documentLayout()); 0056 KoTextLayoutRootArea *rootArea = layout->rootAreaForPosition(headingBlock.position()); 0057 if (rootArea) { 0058 if (rootArea->page()) { 0059 return rootArea->page()->visiblePageNumber(); 0060 } else { 0061 // had root but no page; 0062 } 0063 } 0064 return 0; 0065 } 0066 }; 0067 0068 CQTextToCModel::CQTextToCModel(QObject* parent) 0069 : QAbstractListModel(parent) 0070 , d(new Private) 0071 { 0072 QHash<int, QByteArray> roleNames; 0073 roleNames[Title] = "title"; 0074 roleNames[Level] = "level"; 0075 roleNames[PageNumber] = "pageNumber"; 0076 setRoleNames(roleNames); 0077 0078 connect(&d->updateTimer, SIGNAL(timeout()), this, SLOT(timeout())); 0079 d->updateTimer.setInterval(5000); // after 5 seconds of pause we update 0080 d->updateTimer.setSingleShot(true); 0081 0082 connect(&d->doneTimer, SIGNAL(timeout()), this, SLOT(updateToC())); 0083 d->doneTimer.setInterval(1000); // after 1 seconds of silence we assume layout is done 0084 d->doneTimer.setSingleShot(true); 0085 } 0086 0087 CQTextToCModel::~CQTextToCModel() 0088 { 0089 delete d; 0090 } 0091 0092 QVariant CQTextToCModel::data(const QModelIndex& index, int role) const 0093 { 0094 QVariant result; 0095 if (index.isValid()) { 0096 int row = index.row(); 0097 if (row > -1 && row < d->entries.count()) { 0098 const TextToCModelEntry* entry = d->entries.at(row); 0099 switch(role) { 0100 case PageNumber: 0101 result.setValue<int>(entry->pageNumber); 0102 break; 0103 case Level: 0104 result.setValue<int>(entry->level); 0105 break; 0106 case Title: 0107 default: 0108 // Allowing the fallthrough here (explicitly mentioning our own Title entry) 0109 // means that the predefined Qt Quick roles are also allowed to be filled 0110 // with useful data 0111 result.setValue<QString>(entry->title); 0112 break; 0113 } 0114 } 0115 } 0116 return result; 0117 } 0118 0119 int CQTextToCModel::rowCount(const QModelIndex& parent) const 0120 { 0121 if (parent.isValid()) { 0122 return 0; 0123 } 0124 return d->entries.count(); 0125 } 0126 0127 void CQTextToCModel::requestGeneration() 0128 { 0129 if (d->document->characterCount() < 2) { 0130 return; 0131 } 0132 d->updateTimer.stop(); 0133 d->updateTimer.start(); 0134 } 0135 0136 void CQTextToCModel::startDoneTimer() 0137 { 0138 //we delay acting on the finishedLayout signal by 1 second. This way we 0139 // don't act on it until every header has had a chance to be layouted 0140 // in words (we assume that a new finishedLayout signal will arrive within that 0141 // 1 second) 0142 d->doneTimer.start(); 0143 } 0144 0145 void CQTextToCModel::timeout() 0146 { 0147 d->updateTimer.stop(); 0148 d->documentLayout->scheduleLayout(); 0149 } 0150 0151 void CQTextToCModel::updateToC() 0152 { 0153 beginResetModel(); 0154 QTextBlock block = d->document->firstBlock(); 0155 qDeleteAll(d->entries.begin(), d->entries.end()); 0156 d->entries.clear(); 0157 0158 while (block.isValid()) { 0159 QTextBlockFormat format = block.blockFormat(); 0160 if (format.hasProperty(KoParagraphStyle::OutlineLevel)) { 0161 TextToCModelEntry* entry = new TextToCModelEntry(); 0162 entry->title = block.text(); 0163 entry->level = format.intProperty(KoParagraphStyle::OutlineLevel); 0164 entry->pageNumber = d->resolvePageNumber(block); 0165 d->entries.append(entry); 0166 } 0167 block = block.next(); 0168 } 0169 endResetModel(); 0170 } 0171 0172 QObject* CQTextToCModel::canvas() const 0173 { 0174 return d->canvas; 0175 } 0176 0177 void CQTextToCModel::setCanvas(QObject* newCanvas) 0178 { 0179 beginResetModel(); 0180 if (d->documentLayout) { 0181 d->documentLayout->disconnect(this); 0182 } 0183 d->canvas = 0; 0184 d->document = 0; 0185 d->documentLayout = 0; 0186 CQTextDocumentCanvas* canvas = qobject_cast<CQTextDocumentCanvas*>(newCanvas); 0187 if (canvas) { 0188 d->canvas = canvas; 0189 d->document = canvas->document()->mainFrameSet()->document(); 0190 d->documentLayout = static_cast<KoTextDocumentLayout *>(d->document->documentLayout()); 0191 0192 // connect to layoutIsDirty 0193 connect(d->documentLayout, SIGNAL(layoutIsDirty()), this, SLOT(requestGeneration())); 0194 0195 // connect to FinishedLayout 0196 connect(d->documentLayout, SIGNAL(finishedLayout()), this, SLOT(startDoneTimer())); 0197 } 0198 emit canvasChanged(); 0199 endResetModel(); 0200 }