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"