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 }