File indexing completed on 2024-04-28 04:37:21
0001 /* 0002 SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "problemmodel.h" 0008 #include <QThread> 0009 #include <QIcon> 0010 #include <KLocalizedString> 0011 #include <interfaces/icore.h> 0012 #include <interfaces/idocument.h> 0013 #include <interfaces/idocumentcontroller.h> 0014 #include <interfaces/iprojectcontroller.h> 0015 #include <interfaces/ilanguagecontroller.h> 0016 #include <interfaces/icompletionsettings.h> 0017 #include <interfaces/iassistant.h> 0018 #include <language/editor/documentrange.h> 0019 0020 #include <shell/problem.h> 0021 #include <shell/problemstore.h> 0022 #include <shell/problemstorenode.h> 0023 #include <shell/filteredproblemstore.h> 0024 #include <shell/problemconstants.h> 0025 #include <shell/watcheddocumentset.h> 0026 0027 namespace KDevelop 0028 { 0029 0030 class ProblemModelPrivate 0031 { 0032 public: 0033 explicit ProblemModelPrivate(KDevelop::ProblemStore *store) 0034 : m_problems(store) 0035 , m_features(KDevelop::ProblemModel::NoFeatures) 0036 , m_fullUpdateTooltip(i18nc("@info:tooltip", "Re-parse all watched documents")) 0037 , m_isPlaceholderShown(false) 0038 { 0039 } 0040 0041 KDevelop::IProblem::Ptr createPlaceholdreProblem() const 0042 { 0043 Q_ASSERT(!m_placeholderText.isEmpty()); 0044 0045 KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(m_placeholderSource)); 0046 problem->setDescription(m_placeholderText); 0047 problem->setFinalLocation(m_placeholderLocation); 0048 problem->setSeverity(KDevelop::IProblem::Hint); 0049 0050 return problem; 0051 } 0052 0053 QScopedPointer<KDevelop::ProblemStore> m_problems; 0054 KDevelop::ProblemModel::Features m_features; 0055 QString m_fullUpdateTooltip; 0056 QString m_placeholderText; 0057 QString m_placeholderSource; 0058 KDevelop::DocumentRange m_placeholderLocation; 0059 bool m_isPlaceholderShown; 0060 }; 0061 0062 0063 ProblemModel::ProblemModel(QObject * parent, ProblemStore *store) 0064 : QAbstractItemModel(parent) 0065 , d_ptr(new ProblemModelPrivate(store)) 0066 { 0067 Q_D(ProblemModel); 0068 0069 if (!d->m_problems) { 0070 d->m_problems.reset(new FilteredProblemStore()); 0071 d->m_features = ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter; 0072 } 0073 0074 setScope(CurrentDocument); 0075 0076 connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemModel::setCurrentDocument); 0077 connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemModel::closedDocument); 0078 connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, 0079 &ProblemModel::documentUrlChanged); 0080 /// CompletionSettings include a list of todo markers we care for, so need to update 0081 connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemModel::forceFullUpdate); 0082 0083 if (ICore::self()->documentController()->activeDocument()) { 0084 setCurrentDocument(ICore::self()->documentController()->activeDocument()); 0085 } 0086 0087 connect(d->m_problems.data(), &ProblemStore::beginRebuild, this, &ProblemModel::onBeginRebuild); 0088 connect(d->m_problems.data(), &ProblemStore::endRebuild, this, &ProblemModel::onEndRebuild); 0089 0090 connect(d->m_problems.data(), &ProblemStore::problemsChanged, this, &ProblemModel::problemsChanged); 0091 } 0092 0093 ProblemModel::~ ProblemModel() 0094 { 0095 } 0096 0097 int ProblemModel::rowCount(const QModelIndex& parent) const 0098 { 0099 Q_D(const ProblemModel); 0100 0101 if (!parent.isValid()) { 0102 return d->m_problems->count(); 0103 } else { 0104 return d->m_problems->count(reinterpret_cast<ProblemStoreNode*>(parent.internalPointer())); 0105 } 0106 } 0107 0108 static QString displayUrl(const QUrl &url, const QUrl &base) 0109 { 0110 if (base.isParentOf(url)) { 0111 return url.toDisplayString(QUrl::PreferLocalFile).mid(base.toDisplayString(QUrl::PreferLocalFile).length()); 0112 } else { 0113 return ICore::self()->projectController()->prettyFileName(url, IProjectController::FormatPlain); 0114 } 0115 } 0116 0117 QVariant ProblemModel::data(const QModelIndex& index, int role) const 0118 { 0119 Q_D(const ProblemModel); 0120 0121 if (!index.isValid()) 0122 return QVariant(); 0123 0124 QUrl baseDirectory = d->m_problems->currentDocument().toUrl().adjusted(QUrl::RemoveFilename); 0125 IProblem::Ptr p = problemForIndex(index); 0126 if (!p.constData()) { 0127 if (role == Qt::DisplayRole && index.column() == Error) { 0128 auto *node = reinterpret_cast<ProblemStoreNode*>(index.internalPointer()); 0129 if (node) { 0130 return node->label(); 0131 } 0132 } 0133 return {}; 0134 } 0135 0136 if (role == SeverityRole) { 0137 return p->severity(); 0138 } else if (role == ProblemRole) { 0139 return QVariant::fromValue(p); 0140 } 0141 0142 switch (role) { 0143 case Qt::DisplayRole: 0144 switch (index.column()) { 0145 case Source: 0146 return p->sourceString(); 0147 case Error: 0148 return p->description(); 0149 case File: 0150 return displayUrl(p->finalLocation().document.toUrl().adjusted(QUrl::NormalizePathSegments), baseDirectory); 0151 case Line: 0152 if (p->finalLocation().isValid()) { 0153 return QString::number(p->finalLocation().start().line() + 1); 0154 } 0155 break; 0156 case Column: 0157 if (p->finalLocation().isValid()) { 0158 return QString::number(p->finalLocation().start().column() + 1); 0159 } 0160 break; 0161 } 0162 break; 0163 0164 case Qt::DecorationRole: 0165 if (index.column() == Error) { 0166 return IProblem::iconForSeverity(p->severity()); 0167 } 0168 break; 0169 case Qt::ToolTipRole: 0170 return p->explanation(); 0171 0172 default: 0173 break; 0174 } 0175 0176 return {}; 0177 } 0178 0179 QModelIndex ProblemModel::parent(const QModelIndex& index) const 0180 { 0181 auto *node = reinterpret_cast<ProblemStoreNode*>(index.internalPointer()); 0182 if (!node) { 0183 return {}; 0184 } 0185 0186 ProblemStoreNode *parent = node->parent(); 0187 if (!parent || parent->isRoot()) { 0188 return {}; 0189 } 0190 0191 int idx = parent->index(); 0192 return createIndex(idx, 0, parent); 0193 } 0194 0195 QModelIndex ProblemModel::index(int row, int column, const QModelIndex& parent) const 0196 { 0197 Q_D(const ProblemModel); 0198 0199 if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) { 0200 return QModelIndex(); 0201 } 0202 0203 auto *parentNode = reinterpret_cast<ProblemStoreNode*>(parent.internalPointer()); 0204 const ProblemStoreNode *node = d->m_problems->findNode(row, parentNode); 0205 return createIndex(row, column, (void*)node); 0206 } 0207 0208 int ProblemModel::columnCount(const QModelIndex& parent) const 0209 { 0210 Q_UNUSED(parent) 0211 return LastColumn; 0212 } 0213 0214 IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex& index) const 0215 { 0216 auto *node = reinterpret_cast<ProblemStoreNode*>(index.internalPointer()); 0217 if (!node) { 0218 return {}; 0219 } else { 0220 return node->problem(); 0221 } 0222 } 0223 0224 ProblemModel::Features ProblemModel::features() const 0225 { 0226 Q_D(const ProblemModel); 0227 0228 return d->m_features; 0229 } 0230 0231 void ProblemModel::setFeatures(Features features) 0232 { 0233 Q_D(ProblemModel); 0234 0235 d->m_features = features; 0236 } 0237 0238 QString ProblemModel::fullUpdateTooltip() const 0239 { 0240 Q_D(const ProblemModel); 0241 0242 return d->m_fullUpdateTooltip; 0243 } 0244 0245 void ProblemModel::setFullUpdateTooltip(const QString& tooltip) 0246 { 0247 Q_D(ProblemModel); 0248 0249 if (d->m_fullUpdateTooltip == tooltip) { 0250 return; 0251 } 0252 0253 d->m_fullUpdateTooltip = tooltip; 0254 emit fullUpdateTooltipChanged(); 0255 } 0256 0257 void ProblemModel::setPlaceholderText(const QString& text, const KDevelop::DocumentRange& location, const QString& source) 0258 { 0259 Q_D(ProblemModel); 0260 0261 d->m_placeholderText = text; 0262 d->m_placeholderLocation = location; 0263 d->m_placeholderSource = source; 0264 0265 if (d->m_isPlaceholderShown || d->m_problems->count() == 0) { 0266 // clearing will show/update the new placeholder 0267 clearProblems(); 0268 } 0269 } 0270 0271 void ProblemModel::addProblem(const IProblem::Ptr &problem) 0272 { 0273 Q_D(ProblemModel); 0274 0275 if (d->m_isPlaceholderShown) { 0276 setProblems({ problem }); 0277 } else { 0278 int c = d->m_problems->count(); 0279 beginInsertRows(QModelIndex(), c, c); 0280 d->m_problems->addProblem(problem); 0281 endInsertRows(); 0282 } 0283 } 0284 0285 void ProblemModel::setProblems(const QVector<IProblem::Ptr> &problems) 0286 { 0287 Q_D(ProblemModel); 0288 0289 beginResetModel(); 0290 if (problems.isEmpty() && !d->m_placeholderText.isEmpty()) { 0291 d->m_problems->setProblems({ d->createPlaceholdreProblem() }); 0292 d->m_isPlaceholderShown = true; 0293 } else { 0294 d->m_problems->setProblems(problems); 0295 d->m_isPlaceholderShown = false; 0296 } 0297 endResetModel(); 0298 } 0299 0300 void ProblemModel::clearProblems() 0301 { 0302 setProblems({}); 0303 } 0304 0305 QVector<IProblem::Ptr> ProblemModel::problems(const KDevelop::IndexedString& document) const 0306 { 0307 Q_D(const ProblemModel); 0308 0309 return d->m_problems->problems(document); 0310 } 0311 0312 QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const 0313 { 0314 Q_UNUSED(orientation); 0315 0316 if (role != Qt::DisplayRole) 0317 return {}; 0318 0319 switch (section) { 0320 case Source: 0321 return i18nc("@title:column source of problem", "Source"); 0322 case Error: 0323 return i18nc("@title:column problem description", "Problem"); 0324 case File: 0325 return i18nc("@title:column file where problem was found", "File"); 0326 case Line: 0327 return i18nc("@title:column line number with problem", "Line"); 0328 case Column: 0329 return i18nc("@title:column column number with problem", "Column"); 0330 } 0331 0332 return {}; 0333 } 0334 0335 void ProblemModel::setCurrentDocument(IDocument* document) 0336 { 0337 Q_D(ProblemModel); 0338 0339 Q_ASSERT(thread() == QThread::currentThread()); 0340 0341 QUrl currentDocument = document->url(); 0342 /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt 0343 d->m_problems->setCurrentDocument(IndexedString(currentDocument)); 0344 } 0345 0346 void ProblemModel::closedDocument(IDocument* document) 0347 { 0348 Q_D(ProblemModel); 0349 0350 if (IndexedString(document->url()) == d->m_problems->currentDocument()) 0351 { // reset current document 0352 d->m_problems->setCurrentDocument(IndexedString()); 0353 } 0354 } 0355 0356 void ProblemModel::documentUrlChanged(IDocument* document, const QUrl& previousUrl) 0357 { 0358 Q_D(ProblemModel); 0359 0360 Q_ASSERT(thread() == QThread::currentThread()); 0361 0362 const auto currentDocument = d->m_problems->currentDocument(); 0363 // If currentDocument.isEmpty(), the renamed document must have been closed already in 0364 // DocumentControllerPrivate::changeDocumentUrl() because of a conflict with another open modified document at its 0365 // new URL; another document at document->url() should be active now. So set the active document's URL as current. 0366 if (currentDocument.isEmpty() || currentDocument == IndexedString{previousUrl}) { 0367 setCurrentDocument(document); 0368 } 0369 } 0370 0371 void ProblemModel::onBeginRebuild() 0372 { 0373 beginResetModel(); 0374 } 0375 0376 void ProblemModel::onEndRebuild() 0377 { 0378 endResetModel(); 0379 } 0380 0381 void ProblemModel::setShowImports(bool showImports) 0382 { 0383 Q_D(ProblemModel); 0384 0385 Q_ASSERT(thread() == QThread::currentThread()); 0386 0387 d->m_problems->setShowImports(showImports); 0388 } 0389 0390 bool ProblemModel::showImports() const 0391 { 0392 Q_D(const ProblemModel); 0393 0394 return d->m_problems->showImports(); 0395 } 0396 0397 void ProblemModel::setScope(ProblemScope scope) 0398 { 0399 Q_D(ProblemModel); 0400 0401 Q_ASSERT(thread() == QThread::currentThread()); 0402 0403 if (!features().testFlag(ScopeFilter)) 0404 scope = ProblemScope::BypassScopeFilter; 0405 0406 /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt 0407 d->m_problems->setScope(scope); 0408 } 0409 0410 void ProblemModel::setPathForDocumentsInPathScope(const QString& path) 0411 { 0412 Q_D(ProblemModel); 0413 0414 d->m_problems->setPathForDocumentsInPathScope(path); 0415 } 0416 0417 void ProblemModel::setSeverity(int severity) 0418 { 0419 switch (severity) 0420 { 0421 case KDevelop::IProblem::Error: 0422 setSeverities(KDevelop::IProblem::Error); 0423 break; 0424 case KDevelop::IProblem::Warning: 0425 setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning); 0426 break; 0427 case KDevelop::IProblem::Hint: 0428 setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint); 0429 break; 0430 } 0431 } 0432 0433 void ProblemModel::setSeverities(KDevelop::IProblem::Severities severities) 0434 { 0435 Q_D(ProblemModel); 0436 0437 Q_ASSERT(thread() == QThread::currentThread()); 0438 /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt 0439 d->m_problems->setSeverities(severities); 0440 } 0441 0442 void ProblemModel::setGrouping(int grouping) 0443 { 0444 Q_D(ProblemModel); 0445 0446 /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt 0447 d->m_problems->setGrouping(grouping); 0448 } 0449 0450 ProblemStore* ProblemModel::store() const 0451 { 0452 Q_D(const ProblemModel); 0453 0454 return d->m_problems.data(); 0455 } 0456 0457 } 0458 0459 #include "moc_problemmodel.cpp"