File indexing completed on 2024-05-12 16:34:59

0001 /* This file is part of the Calligra project, made within the KDE community.
0002  *
0003  * Copyright (C) 2013,2016 Friedrich W. H. Kossebau <friedrich@kogmbh.com>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Library General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, write to
0017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019  */
0020 
0021 #include <TextDocumentStructureModel.h>
0022 
0023 #include "TextShapeDebug.h"
0024 // Qt
0025 #include <QTextDocument>
0026 #include <QTextFrame>
0027 #include <QTextBlock>
0028 #include <QTextCursor>
0029 #include <QTextLayout>
0030 
0031 
0032 NodeData NodeData::fromLine(int blockNumber, int lineNumber)
0033 {
0034     NodeData nodeData;
0035     nodeData.type = Line;
0036     nodeData.blockNumber = blockNumber;
0037     nodeData.lineNumber = lineNumber;
0038     return nodeData;
0039 }
0040 
0041 NodeData NodeData::fromBlock(int blockNumber)
0042 {
0043     NodeData nodeData;
0044     nodeData.type = Block;
0045     nodeData.blockNumber = blockNumber;
0046     return nodeData;
0047 }
0048 
0049 NodeData NodeData::fromFrame(QTextFrame* frame)
0050 {
0051     NodeData nodeData;
0052     nodeData.type = Frame;
0053     nodeData.frame = frame;
0054     return nodeData;
0055 }
0056 
0057 
0058 TextDocumentStructureModel::TextDocumentStructureModel(QObject *parent)
0059   : QAbstractItemModel(parent)
0060 {
0061     connect(this, SIGNAL(modelReset()), SLOT(onModelReset()));
0062 }
0063 
0064 TextDocumentStructureModel::~TextDocumentStructureModel()
0065 {
0066 }
0067 
0068 
0069 int TextDocumentStructureModel::columnCount(const QModelIndex &index) const
0070 {
0071     Q_UNUSED(index);
0072 
0073     return endColumn;
0074 }
0075 
0076 int TextDocumentStructureModel::rowCount(const QModelIndex &index) const
0077 {
0078     debugTextShape << "-------------------------- index:"<<index<<m_textDocument;
0079     if (! m_textDocument) {
0080         return 0;
0081     }
0082 
0083     if (! index.isValid()) {
0084         // one root frame
0085         return 1;
0086     }
0087 
0088     Q_ASSERT(index.internalId() < uint(m_nodeDataTable.count()));
0089 
0090     const NodeData &nodeData = m_nodeDataTable.at(index.internalId());
0091 
0092     if (nodeData.type == NodeData::Frame) {
0093         QTextFrame* frame = nodeData.frame;
0094 
0095         // count frames and blocks
0096         int count = 0;
0097         for (QTextFrame::iterator iterator = frame->begin(); !iterator.atEnd(); ++iterator) {
0098             ++count;
0099         }
0100         return count;
0101     }
0102 
0103     if (nodeData.type == NodeData::Block) {
0104         QTextBlock block = m_textDocument->findBlockByNumber(nodeData.blockNumber);
0105         QTextLayout* textLayout = block.layout();
0106         if (textLayout) {
0107             return textLayout->lineCount();
0108         }
0109     }
0110 
0111     // anything else no childs for now
0112     return 0;
0113 }
0114 
0115 QVariant TextDocumentStructureModel::data(const QModelIndex &index, int role) const
0116 {
0117     if (! m_textDocument || ! index.isValid()) {
0118         return QVariant();
0119     }
0120 
0121     Q_ASSERT(index.internalId() < uint(m_nodeDataTable.count()));
0122 
0123     const NodeData &nodeData = m_nodeDataTable.at(index.internalId());
0124 
0125     switch (role) {
0126         case Qt::DisplayRole:
0127         {
0128             switch (nodeData.type) {
0129                 case NodeData::Frame: {
0130                     QTextFrame* frame = nodeData.frame;
0131                     return QLatin1String(frame->metaObject()->className());
0132                     break;
0133                 }
0134                 case NodeData::Block:
0135                     return QStringLiteral("Block");
0136                     break;
0137                 case NodeData::Line: {
0138                     QTextBlock block = m_textDocument->findBlockByNumber(nodeData.blockNumber);
0139                     QTextLayout *layout = block.layout();
0140                     if (layout) {
0141                         QTextLine line = layout->lineAt(nodeData.lineNumber);
0142                         return block.text().mid(line.textStart(), line.textLength());
0143                     }
0144                     break;
0145                 }
0146             }
0147             break;
0148         }
0149     }
0150 
0151     return QVariant();
0152 }
0153 
0154 QModelIndex TextDocumentStructureModel::parent(const QModelIndex &index) const
0155 {
0156     debugTextShape << "-------------------------- index:"<<index<<m_textDocument;
0157     if (! m_textDocument || ! index.isValid()) {
0158         return QModelIndex();
0159     }
0160 
0161     Q_ASSERT(index.internalId() < uint(m_nodeDataTable.count()));
0162 
0163     const NodeData &nodeData = m_nodeDataTable.at(index.internalId());
0164 
0165     if (nodeData.type == NodeData::Line) {
0166         QTextBlock block = m_textDocument->findBlockByNumber(nodeData.blockNumber);
0167         // QTextBlock's API has no option to query the parentframe, so get it via a cursor
0168         QTextCursor cursor(block);
0169         QTextFrame* parentFrame = cursor.currentFrame();
0170         int row = 0;
0171         for (QTextFrame::iterator iterator = parentFrame->begin(); !iterator.atEnd(); ++iterator) {
0172             if (iterator.currentFrame() == parentFrame) {
0173                 break;
0174             }
0175             ++row;
0176         }
0177         return createIndex(row, 0, blockIndex(block));
0178     }
0179 
0180     QTextFrame* parentFrame;
0181     if (nodeData.type == NodeData::Frame) {
0182         parentFrame = nodeData.frame->parentFrame();
0183     } else {
0184         QTextBlock block = m_textDocument->findBlockByNumber(nodeData.blockNumber);
0185         Q_ASSERT(block.isValid());
0186         // QTextBlock's API has no option to query the parentframe, so get it via a cursor
0187         QTextCursor cursor(block);
0188         parentFrame = cursor.currentFrame();
0189     }
0190 
0191     if (! parentFrame) {
0192         return QModelIndex();
0193     }
0194 
0195     QTextFrame* grandParentFrame = parentFrame->parentFrame();
0196     // parent is root frame?
0197     if (! grandParentFrame) {
0198         Q_ASSERT(parentFrame == m_textDocument->rootFrame());
0199         return createIndex(0, 0, static_cast<quintptr>(0));
0200     }
0201 
0202     // find position of parentFrame
0203     bool posFound = false;
0204     int row = 0;
0205     for (QTextFrame::iterator iterator = grandParentFrame->begin(); !iterator.atEnd(); ++iterator) {
0206         if (iterator.currentFrame() == parentFrame) {
0207             posFound = true;
0208             break;
0209         }
0210         ++row;
0211     }
0212     Q_ASSERT(posFound);Q_UNUSED(posFound);
0213     return createIndex(row, 0, frameIndex(parentFrame));
0214 }
0215 
0216 QModelIndex TextDocumentStructureModel::index(int row, int column, const QModelIndex &parentIndex) const
0217 {
0218     debugTextShape << "-------------------------- row:" << row << "column:"<<column << "index:"<<parentIndex<<m_textDocument;
0219     if (! m_textDocument) {
0220         return QModelIndex();
0221     }
0222 
0223     if (! parentIndex.isValid()) {
0224         return createIndex(row, column, static_cast<quintptr>(0));
0225     }
0226 
0227     Q_ASSERT(parentIndex.internalId() < uint(m_nodeDataTable.count()));
0228 
0229     const NodeData &parentNodeData = m_nodeDataTable.at(parentIndex.internalId());
0230 
0231     int index = -1;
0232 
0233     if (parentNodeData.type == NodeData::Frame) {
0234         QTextFrame* parentFrame = parentNodeData.frame;
0235         int count = 0;
0236         for (QTextFrame::iterator iterator = parentFrame->begin(); !iterator.atEnd(); ++iterator) {
0237             if (count == row) {
0238                 QTextFrame *frame = iterator.currentFrame();
0239                 if (frame) {
0240                     index = frameIndex(frame);
0241                     break;
0242                 } else {
0243                     QTextBlock block = iterator.currentBlock();
0244                     if (block.isValid()) {
0245                         index = blockIndex(block);
0246                         break;
0247                     }
0248                 }
0249             }
0250             ++count;
0251         }
0252     } else {
0253         // can be only block otherwise for now
0254         Q_ASSERT(parentNodeData.type == NodeData::Block);
0255         QTextBlock block = m_textDocument->findBlockByNumber(parentNodeData.blockNumber);
0256         QTextLayout *layout = block.layout();
0257         if (layout) {
0258             QTextLine line = layout->lineAt(row);
0259             index = lineIndex(block, line);
0260         }
0261     }
0262 
0263     Q_ASSERT(index != -1);
0264     return createIndex(row, column, index);
0265 }
0266 
0267 bool TextDocumentStructureModel::hasChildren(const QModelIndex &parentIndex) const
0268 {
0269     debugTextShape << "-------------------------- parentIndex:"<<parentIndex<<m_textDocument;
0270     if (! m_textDocument) {
0271         return false;
0272     }
0273     // there is one root children
0274     if (! parentIndex.isValid()) {
0275         return true;
0276     }
0277 
0278     Q_ASSERT(parentIndex.internalId() < uint(m_nodeDataTable.count()));
0279 
0280     const NodeData &nodeData = m_nodeDataTable.at(parentIndex.internalId());
0281 
0282     if (nodeData.type == NodeData::Frame) {
0283         return (! nodeData.frame->begin().atEnd());
0284     }
0285 
0286     if (nodeData.type == NodeData::Block) {
0287         QTextBlock block = m_textDocument->findBlockByNumber(nodeData.blockNumber);
0288         QTextLayout* textLayout = block.layout();
0289         return (textLayout && textLayout->lineCount() > 0);
0290     }
0291 
0292     return false;
0293 }
0294 
0295 
0296 void TextDocumentStructureModel::setTextDocument(QTextDocument* textDocument)
0297 {
0298     beginResetModel();
0299     if (m_textDocument) {
0300         m_textDocument->disconnect(this);
0301     }
0302 
0303     m_textDocument = textDocument;
0304 
0305     if (m_textDocument) {
0306         connect(m_textDocument, SIGNAL(contentsChanged()), SLOT(onContentsChanged()));
0307     }
0308 
0309     endResetModel();
0310 }
0311 
0312 int TextDocumentStructureModel::lineIndex(const QTextBlock &block, const QTextLine &line) const
0313 {
0314     int index;
0315 
0316     const int blockNumber = block.blockNumber();
0317     QHash<int, BlockData>::Iterator blockIt = m_blockNumberTable.find(blockNumber);
0318     Q_ASSERT(blockIt != m_blockNumberTable.end());
0319 
0320     const int lineNumber = line.lineNumber();
0321     QHash<int, int> &lineNumberTable = blockIt.value().lineNumberTable;
0322     QHash<int, int>::ConstIterator it = lineNumberTable.constFind(lineNumber);
0323     if (it == lineNumberTable.constEnd()) {
0324         index = m_nodeDataTable.count();
0325         lineNumberTable.insert(lineNumber, index);
0326         m_nodeDataTable.append(NodeData::fromLine(blockNumber, lineNumber));
0327     } else {
0328         index = it.value();
0329     }
0330 
0331     return index;
0332 }
0333 
0334 int TextDocumentStructureModel::blockIndex(const QTextBlock &block) const
0335 {
0336     int index;
0337 
0338     const int blockNumber = block.blockNumber();
0339     QHash<int, BlockData>::ConstIterator it = m_blockNumberTable.constFind(blockNumber);
0340     if (it == m_blockNumberTable.constEnd()) {
0341         index = m_nodeDataTable.count();
0342         m_blockNumberTable.insert(blockNumber, BlockData(index));
0343         m_nodeDataTable.append(NodeData::fromBlock(blockNumber));
0344     } else {
0345         index = it.value().nodeIndex;
0346     }
0347 
0348     return index;
0349 }
0350 
0351 int TextDocumentStructureModel::frameIndex(QTextFrame *frame) const
0352 {
0353     int index;
0354 
0355     QHash<QTextFrame*, int>::ConstIterator it = m_frameTable.constFind(frame);
0356     if (it == m_frameTable.constEnd()) {
0357         index = m_nodeDataTable.count();
0358         m_frameTable.insert(frame, index);
0359         m_nodeDataTable.append(NodeData::fromFrame(frame));
0360     } else {
0361         index = it.value();
0362     }
0363 
0364     return index;
0365 }
0366 
0367 void TextDocumentStructureModel::onContentsChanged()
0368 {
0369     beginResetModel();
0370     endResetModel();
0371 }
0372 
0373 void TextDocumentStructureModel::onModelReset()
0374 {
0375     debugTextShape << "-------------------------- "<<m_textDocument;
0376     m_nodeDataTable.clear();
0377     m_blockNumberTable.clear();
0378     m_frameTable.clear();
0379 
0380     // prefill table with root node
0381     if (m_textDocument) {
0382         QTextFrame *rootFrame = m_textDocument->rootFrame();
0383         m_frameTable.insert(rootFrame, 0);
0384         m_nodeDataTable.append(NodeData::fromFrame(rootFrame));
0385     }
0386 }