File indexing completed on 2024-05-12 05:17:26

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "consoleoutputwidget.h"
0008 #include "ui_consoleoutputwidget.h"
0009 
0010 #include <KLocalizedString>
0011 
0012 #include <QAbstractTableModel>
0013 #include <QDebug>
0014 #include <QIcon>
0015 
0016 #include <cstring>
0017 
0018 class ConsoleOutputModel : public QAbstractTableModel
0019 {
0020     Q_OBJECT
0021 public:
0022     explicit ConsoleOutputModel(QObject *parent = nullptr);
0023     ~ConsoleOutputModel();
0024 
0025     enum Role {
0026         SourceFileRole = Qt::UserRole,
0027         SourceLineRole
0028     };
0029 
0030     int columnCount(const QModelIndex &parent) const override;
0031     int rowCount(const QModelIndex &parent) const override;
0032     QVariant data(const QModelIndex &index, int role) const override;
0033     QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
0034 
0035     void clear();
0036 
0037     void handleMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg);
0038 
0039 private:
0040     struct Message {
0041         QString msg;
0042         QString file;
0043         QString function;
0044         QtMsgType type;
0045         int line;
0046     };
0047     std::vector<Message> m_messages;
0048     QtMessageHandler m_prevHandler = nullptr;
0049 };
0050 
0051 static ConsoleOutputModel *sConsoleOutput = nullptr;
0052 
0053 void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
0054 {
0055     sConsoleOutput->handleMessage(type, context, msg);
0056 }
0057 
0058 ConsoleOutputModel::ConsoleOutputModel(QObject *parent)
0059     : QAbstractTableModel(parent)
0060 {
0061     sConsoleOutput = this;
0062     m_prevHandler = qInstallMessageHandler(messageHandler);
0063 }
0064 
0065 ConsoleOutputModel::~ConsoleOutputModel()
0066 {
0067     qInstallMessageHandler(nullptr);
0068     sConsoleOutput = nullptr;
0069 }
0070 
0071 int ConsoleOutputModel::columnCount(const QModelIndex &parent) const
0072 {
0073     Q_UNUSED(parent);
0074     return 3;
0075 }
0076 
0077 int ConsoleOutputModel::rowCount(const QModelIndex &parent) const
0078 {
0079     if (parent.isValid()) {
0080         return 0;
0081     }
0082     return m_messages.size();
0083 }
0084 
0085 QVariant ConsoleOutputModel::data(const QModelIndex& index, int role) const
0086 {
0087     if (role == Qt::DisplayRole) {
0088         const auto &msg = m_messages[index.row()];
0089         switch (index.column()) {
0090             case 0:
0091                 return i18n("%1:%2", msg.file, msg.line);
0092             case 1:
0093                 return msg.function;
0094             case 2:
0095                 return msg.msg;
0096         }
0097     }
0098 
0099     if (role == Qt::DecorationRole && index.column() == 0) {
0100         switch (m_messages[index.row()].type) {
0101             case QtDebugMsg: return QIcon::fromTheme(QStringLiteral("dialog-question"));
0102             case QtInfoMsg: return QIcon::fromTheme(QStringLiteral("dialog-information"));
0103             case QtWarningMsg: return QIcon::fromTheme(QStringLiteral("dialog-warning"));
0104             case QtCriticalMsg: return QIcon::fromTheme(QStringLiteral("dialog-error"));
0105             case QtFatalMsg: return QIcon::fromTheme(QStringLiteral("tools-report-bug"));
0106         }
0107     }
0108 
0109     if (role == SourceFileRole) {
0110         return m_messages[index.row()].file;
0111     }
0112     if (role == SourceLineRole) {
0113         return m_messages[index.row()].line;
0114     }
0115 
0116     return {};
0117 }
0118 
0119 QVariant ConsoleOutputModel::headerData(int section, Qt::Orientation orientation, int role) const
0120 {
0121     if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
0122         switch (section) {
0123             case 0: return i18n("Location");
0124             case 1: return i18n("Function");
0125             case 2: return i18n("Message");
0126         }
0127     }
0128     return QAbstractTableModel::headerData(section, orientation, role);
0129 }
0130 
0131 void ConsoleOutputModel::clear()
0132 {
0133     if (m_messages.empty()) {
0134         return;
0135     }
0136 
0137     beginRemoveRows({}, 0, m_messages.size() - 1);
0138     m_messages.clear();
0139     endRemoveRows();
0140 }
0141 
0142 void ConsoleOutputModel::handleMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg)
0143 {
0144     m_prevHandler(type, context, msg);
0145     if (std::strcmp(context.category, "js") == 0) {
0146         // script debug output
0147         beginInsertRows({}, m_messages.size(), m_messages.size());
0148         Message m;
0149         m.msg = msg;
0150         m.file = QString::fromUtf8(context.file);
0151         m.function = QString::fromUtf8(context.function);
0152         m.line = context.line;
0153         m.type = type;
0154         m_messages.push_back(std::move(m));
0155         endInsertRows();
0156     } else if (std::strcmp(context.category, "org.kde.kitinerary") == 0 && type == QtWarningMsg && msg.startsWith(QLatin1String("JS ERROR"))) {
0157         // script engine errors
0158         beginInsertRows({}, m_messages.size(), m_messages.size());
0159         Message m;
0160         const auto idx1 = msg.indexOf(QLatin1String("]:"), 11);
0161         if (idx1 > 0) {
0162             m.file = msg.mid(11, idx1 - 11);
0163         }
0164         const auto idx2 = msg.indexOf(QLatin1Char(':'), idx1 + 2);
0165         if (idx2 > idx1) {
0166             m.line = QStringView(msg).mid(idx1 + 2, idx2 - idx1 - 2).toInt();
0167             m.msg = msg.mid(idx2 + 1);
0168         } else {
0169             m.msg = msg;
0170         }
0171         m.type = QtFatalMsg;
0172         m_messages.push_back(std::move(m));
0173         endInsertRows();
0174     }
0175 }
0176 
0177 
0178 ConsoleOutputWidget::ConsoleOutputWidget(QWidget *parent)
0179     : QWidget(parent)
0180     , ui(new Ui::ConsoleOutputWidget)
0181     , m_model(new ConsoleOutputModel(this))
0182 {
0183     ui->setupUi(this);
0184     ui->logView->setModel(m_model);
0185 
0186     connect(ui->logView, &QTreeView::activated, this, [this](const auto &idx) {
0187         if (!idx.isValid()) {
0188             return;
0189         }
0190         const auto file = idx.data(ConsoleOutputModel::SourceFileRole).toString();
0191         const auto line = idx.data(ConsoleOutputModel::SourceLineRole).toInt();
0192         Q_EMIT navigateToSource(file, line);
0193     });
0194 }
0195 
0196 ConsoleOutputWidget::~ConsoleOutputWidget() = default;
0197 
0198 void ConsoleOutputWidget::clear()
0199 {
0200     m_model->clear();
0201 }
0202 
0203 #include "consoleoutputwidget.moc"