File indexing completed on 2024-05-19 05:42:13
0001 #include <ct_lvtmdl_debugmodel.h> 0002 0003 /* 0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk> 0005 // SPDX-License-Identifier: Apache-2.0 0006 // 0007 // Licensed under the Apache License, Version 2.0 (the "License"); 0008 // you may not use this file except in compliance with the License. 0009 // You may obtain a copy of the License at 0010 // 0011 // http://www.apache.org/licenses/LICENSE-2.0 0012 // 0013 // Unless required by applicable law or agreed to in writing, software 0014 // distributed under the License is distributed on an "AS IS" BASIS, 0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0016 // See the License for the specific language governing permissions and 0017 // limitations under the License. 0018 */ 0019 0020 #include <QDebug> 0021 #include <QDir> 0022 #include <QLoggingCategory> 0023 #include <QMutex> 0024 0025 #include <preferences.h> 0026 0027 Q_LOGGING_CATEGORY(debugModel, "lakoswidgets.lvtmdl.basetablemodel") 0028 0029 namespace { 0030 0031 QString msgTypeToString(int type) 0032 { 0033 switch (type) { 0034 case QtMsgType::QtCriticalMsg: 0035 return "Critical"; 0036 case QtMsgType::QtDebugMsg: 0037 return "Debug"; 0038 case QtMsgType::QtFatalMsg: 0039 return "Fatal"; 0040 case QtMsgType::QtInfoMsg: 0041 return "Info"; 0042 case QtMsgType::QtWarningMsg: 0043 return "Warning"; 0044 } 0045 return {}; 0046 } 0047 0048 } // namespace 0049 namespace Codethink::lvtmdl { 0050 0051 static DebugModel *self = nullptr; // NOLINT 0052 0053 struct DebugModel::Private { 0054 constexpr static int BUFFER_ELEMENTS = 5000; 0055 0056 std::vector<DebugData> data; 0057 QMutex mtx; 0058 0059 Private(): data(BUFFER_ELEMENTS){}; 0060 }; 0061 0062 DebugModel::DebugModel(): d(std::make_unique<DebugModel::Private>()) 0063 { 0064 if (self) { 0065 qCDebug(debugModel) << "Only one Debug Model should exist at a given time"; 0066 } 0067 self = this; 0068 } 0069 0070 DebugModel::~DebugModel() 0071 { 0072 self = nullptr; 0073 } 0074 0075 void DebugModel::addData(const DebugData& data) 0076 { 0077 QMutexLocker locker(&self->d->mtx); 0078 0079 beginInsertRows(QModelIndex(), rowCount(), rowCount()); 0080 d->data.push_back(data); 0081 endInsertRows(); 0082 } 0083 0084 int DebugModel::rowCount(const QModelIndex& parent) const 0085 { 0086 Q_UNUSED(parent); 0087 return (int) d->data.size(); 0088 } 0089 0090 int DebugModel::columnCount(const QModelIndex& parent) const 0091 { 0092 Q_UNUSED(parent); 0093 return DebugModel::Columns::COUNT; 0094 } 0095 0096 QVariant DebugModel::headerData(int section, Qt::Orientation orientation, int role) const 0097 { 0098 if (orientation == Qt::Orientation::Vertical) { 0099 return {}; 0100 } 0101 0102 if (role != Qt::DisplayRole) { 0103 return {}; 0104 } 0105 0106 switch (section) { 0107 case DebugModel::Columns::e_CATEGORY: 0108 return tr("Category"); 0109 case DebugModel::Columns::e_MESSAGE: 0110 return tr("Message"); 0111 case DebugModel::Columns::e_FILE: 0112 return tr("File"); 0113 case DebugModel::Columns::e_FUNCTION: 0114 return tr("Function"); 0115 case DebugModel::Columns::e_LINE: 0116 return tr("Line"); 0117 case DebugModel::Columns::e_VERSION: 0118 return tr("Version"); 0119 case DebugModel::Columns::e_TYPE: 0120 return tr("Type"); 0121 } 0122 return {}; 0123 } 0124 0125 QVariant DebugModel::data(const QModelIndex& idx, int role) const 0126 { 0127 if (!idx.isValid()) { 0128 return {}; 0129 } 0130 0131 if (idx.row() >= rowCount()) { 0132 return {}; 0133 } 0134 0135 const DebugData data = d->data[idx.row()]; 0136 if (role == Qt::DisplayRole) { 0137 switch (idx.column()) { 0138 case DebugModel::Columns::e_CATEGORY: 0139 return data.category; 0140 case DebugModel::Columns::e_MESSAGE: 0141 return data.message; 0142 case DebugModel::Columns::e_FILE: 0143 return data.file; 0144 case DebugModel::Columns::e_FUNCTION: 0145 return data.function; 0146 case DebugModel::Columns::e_LINE: 0147 return data.line; 0148 case DebugModel::Columns::e_VERSION: 0149 return data.version; 0150 case DebugModel::Columns::e_TYPE: 0151 return msgTypeToString(data.type); 0152 } 0153 } 0154 0155 return {}; 0156 } 0157 0158 void DebugModel::clear() 0159 { 0160 beginResetModel(); 0161 d->data.clear(); 0162 d->data.resize(0); 0163 endResetModel(); 0164 } 0165 0166 bool DebugModel::saveAs(const QString& filename) const 0167 { 0168 QFile dump(filename); 0169 QTextStream textStream(&dump); 0170 0171 if (!dump.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { 0172 return false; 0173 } 0174 0175 for (const auto& line : d->data) { 0176 if (line.message.isEmpty()) { 0177 continue; 0178 } 0179 0180 const QString type = msgTypeToString(line.type); 0181 QString output = line.category + " " + type + ": "; 0182 if (!line.file.isEmpty()) { 0183 output += line.file + " "; 0184 } 0185 0186 if (!line.function.isEmpty()) { 0187 output += line.function + " "; 0188 } 0189 0190 if (!line.line.isEmpty() && line.line != "0") { 0191 output += line.line + " "; 0192 } 0193 0194 output += line.message + "\n"; 0195 textStream << output; 0196 } 0197 0198 dump.close(); 0199 return true; 0200 } 0201 0202 void DebugModel::debugMessageHandler(QtMsgType msgType, const QMessageLogContext& context, const QString& message) 0203 { 0204 if (self && Preferences::storeDebugOutput()) { 0205 DebugData d; 0206 d.category = context.category; 0207 d.file = context.file; 0208 d.function = context.function; 0209 d.line = QString::number(context.line); 0210 d.message = message; 0211 d.type = msgType; 0212 d.version = context.version; 0213 self->addData(d); 0214 } 0215 0216 if (!Preferences::enableDebugOutput()) { 0217 return; 0218 } 0219 0220 if (message.trimmed().isEmpty()) { 0221 return; 0222 } 0223 0224 // clang-tidy cert-err33-c requires us to check the return value of printf 0225 auto checkRet = [](int ret) { 0226 assert(ret > 0); 0227 (void) ret; 0228 }; 0229 0230 QString extra; 0231 if (context.file) { 0232 QString file(context.file); 0233 extra = file.split(QDir::separator()).last() + ":" + QString::number(context.line); 0234 } 0235 0236 QByteArray localMsg = message.toLocal8Bit(); 0237 QByteArray extraMsg = extra.toLocal8Bit(); 0238 switch (msgType) { 0239 case QtDebugMsg: 0240 checkRet(fprintf(stderr, "Debug: %s %s\n", extraMsg.constData(), localMsg.constData())); 0241 break; 0242 case QtInfoMsg: 0243 checkRet(fprintf(stderr, "Info: %s%s\n", extraMsg.constData(), localMsg.constData())); 0244 break; 0245 case QtWarningMsg: 0246 checkRet(fprintf(stderr, "Warning: %s%s\n", extraMsg.constData(), localMsg.constData())); 0247 break; 0248 case QtCriticalMsg: 0249 checkRet(fprintf(stderr, "Critical: %s%s\n", extraMsg.constData(), localMsg.constData())); 0250 break; 0251 case QtFatalMsg: 0252 checkRet(fprintf(stderr, "Fatal: %s%s\n", extraMsg.constData(), localMsg.constData())); 0253 abort(); 0254 } 0255 } 0256 0257 struct DebugModelFilter::Private { 0258 bool filterDebug = false; 0259 bool filterWarning = false; 0260 bool filterInfo = false; 0261 bool filterCritical = false; 0262 bool filterFatal = false; 0263 bool invertStringFilter = false; 0264 QAbstractItemModel *ignoreCategoriesModel = nullptr; 0265 QString filterString; 0266 }; 0267 0268 DebugModelFilter::DebugModelFilter(): d(std::make_unique<DebugModelFilter::Private>()) 0269 { 0270 } 0271 0272 DebugModelFilter::~DebugModelFilter() = default; 0273 0274 bool DebugModelFilter::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const 0275 { 0276 Q_UNUSED(source_parent); 0277 0278 auto *currModel = qobject_cast<DebugModel *>(sourceModel()); 0279 0280 // TODO: Less String comparisons; 0281 const QString type = 0282 currModel->data(currModel->index(source_row, DebugModel::Columns::e_TYPE), Qt::DisplayRole).toString(); 0283 if (d->filterDebug && type == tr("Debug")) { 0284 return false; 0285 } 0286 if (d->filterCritical && type == tr("Critical")) { 0287 return false; 0288 } 0289 if (d->filterFatal && type == tr("Fatal")) { 0290 return false; 0291 } 0292 if (d->filterInfo && type == tr("Info")) { 0293 return false; 0294 } 0295 if (d->filterWarning && type == tr("Warning")) { 0296 return false; 0297 } 0298 0299 if (d->ignoreCategoriesModel) { 0300 for (int i = 0, end = d->ignoreCategoriesModel->rowCount(); i < end; i++) { 0301 const QModelIndex ignoreIdx = d->ignoreCategoriesModel->index(i, 0); 0302 const QString categoryToIgnore = d->ignoreCategoriesModel->data(ignoreIdx, Qt::DisplayRole).toString(); 0303 const QModelIndex categoryIdx = currModel->index(source_row, DebugModel::e_CATEGORY); 0304 const QString currCategory = currModel->data(categoryIdx, Qt::DisplayRole).toString(); 0305 0306 // TODO: Allow Partial Matches? 0307 if (categoryToIgnore == currCategory) { 0308 return false; 0309 } 0310 } 0311 } 0312 0313 if (d->filterString.size()) { 0314 const QModelIndex messageIdx = currModel->index(source_row, DebugModel::Columns::e_MESSAGE); 0315 const QString currMessage = currModel->data(messageIdx, Qt::DisplayRole).toString(); 0316 const bool lineContainsFilter = currMessage.contains(d->filterString); 0317 0318 if (d->invertStringFilter) { 0319 return !lineContainsFilter; 0320 } 0321 return lineContainsFilter; 0322 } 0323 0324 return true; 0325 } 0326 0327 bool DebugModelFilter::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const 0328 { 0329 Q_UNUSED(source_left); 0330 Q_UNUSED(source_right); 0331 return true; 0332 } 0333 0334 void DebugModelFilter::setFilterDebug(bool filter) 0335 { 0336 if (d->filterDebug == filter) { 0337 return; 0338 } 0339 d->filterDebug = filter; 0340 invalidateFilter(); 0341 } 0342 0343 void DebugModelFilter::setFilterWarning(bool filter) 0344 { 0345 if (d->filterWarning == filter) { 0346 return; 0347 } 0348 d->filterWarning = filter; 0349 invalidateFilter(); 0350 } 0351 0352 void DebugModelFilter::setFilterInfo(bool filter) 0353 { 0354 if (d->filterInfo == filter) { 0355 return; 0356 } 0357 d->filterInfo = filter; 0358 invalidateFilter(); 0359 } 0360 0361 void DebugModelFilter::setFilterCrtical(bool filter) 0362 { 0363 if (d->filterCritical == filter) { 0364 return; 0365 } 0366 d->filterCritical = filter; 0367 invalidateFilter(); 0368 } 0369 0370 void DebugModelFilter::setFilterFatal(bool filter) 0371 { 0372 if (d->filterFatal == filter) { 0373 return; 0374 } 0375 d->filterFatal = filter; 0376 invalidateFilter(); 0377 } 0378 0379 void DebugModelFilter::setIgnoreCategoriesModel(QAbstractItemModel *model) 0380 { 0381 if (d->ignoreCategoriesModel) { 0382 disconnect(d->ignoreCategoriesModel, &QAbstractItemModel::rowsInserted, this, nullptr); 0383 disconnect(d->ignoreCategoriesModel, &QAbstractItemModel::rowsRemoved, this, nullptr); 0384 } 0385 0386 d->ignoreCategoriesModel = model; 0387 connect(model, &QAbstractItemModel::rowsInserted, this, [this] { 0388 invalidateFilter(); 0389 }); 0390 0391 connect(model, &QAbstractItemModel::rowsRemoved, this, [this] { 0392 invalidateFilter(); 0393 }); 0394 } 0395 0396 void DebugModelFilter::setFilterString(const QString& str) 0397 { 0398 if (d->filterString == str) { 0399 return; 0400 } 0401 d->filterString = str; 0402 invalidateFilter(); 0403 } 0404 0405 void DebugModelFilter::setInvertMessageFilter(bool value) 0406 { 0407 if (d->invertStringFilter == value) { 0408 return; 0409 } 0410 d->invertStringFilter = value; 0411 invalidateFilter(); 0412 } 0413 0414 } // namespace Codethink::lvtmdl