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 }