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