diff --git a/src/plugins/coreplugin/CMakeLists.txt b/src/plugins/coreplugin/CMakeLists.txt index d7f04d88ed5..6079989c718 100644 --- a/src/plugins/coreplugin/CMakeLists.txt +++ b/src/plugins/coreplugin/CMakeLists.txt @@ -209,8 +209,6 @@ add_qtc_plugin(Core locator/spotlightlocatorfilter.h locator/urllocatorfilter.cpp locator/urllocatorfilter.h - loggingmanager.cpp - loggingmanager.h loggingviewer.cpp loggingviewer.h manhattanstyle.cpp diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index dca909d0752..bfd0669c338 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -9,18 +9,18 @@ #include "icore.h" #include "idocument.h" #include "iwizardfactory.h" +#include "loggingviewer.h" #include "modemanager.h" #include "session.h" #include "settingsdatabase.h" #include "themechooser.h" #include "actionmanager/actionmanager.h" +#include "coreconstants.h" #include "documentmanager.h" -#include "editormanager/editormanager.h" +#include "fileutils.h" #include "find/findplugin.h" #include "locator/locator.h" -#include "coreconstants.h" -#include "fileutils.h" #include #include @@ -490,6 +490,7 @@ QString CorePlugin::msgCrashpadInformation() ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown() { + LoggingViewer::hideLoggingView(); Find::aboutToShutdown(); m_locator->aboutToShutdown(); ICore::aboutToShutdown(); diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs index 37c8a9b73f8..9bd3923a6fb 100644 --- a/src/plugins/coreplugin/coreplugin.qbs +++ b/src/plugins/coreplugin/coreplugin.qbs @@ -102,8 +102,6 @@ QtcPlugin { "iwizardfactory.h", "jsexpander.cpp", "jsexpander.h", - "loggingmanager.cpp", - "loggingmanager.h", "loggingviewer.cpp", "loggingviewer.h", "manhattanstyle.cpp", diff --git a/src/plugins/coreplugin/loggingmanager.cpp b/src/plugins/coreplugin/loggingmanager.cpp deleted file mode 100644 index 608e7ce4706..00000000000 --- a/src/plugins/coreplugin/loggingmanager.cpp +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "loggingmanager.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -// -// WARNING! Do not use qDebug(), qWarning() or similar inside this file - -// same applies for indirect usages (e.g. QTC_ASSERT() and the like). -// Using static functions of QLoggingCategory may cause dead locks as well. -// - -using namespace Utils; - -namespace Core { -namespace Internal { - -static QtMessageHandler s_originalMessageHandler = nullptr; - -static LoggingViewManager *s_instance = nullptr; - -static QString levelToString(QtMsgType t) -{ - switch (t) { - case QtMsgType::QtCriticalMsg: return {"critical"}; - case QtMsgType::QtDebugMsg: return {"debug"}; - case QtMsgType::QtInfoMsg: return {"info"}; - case QtMsgType::QtWarningMsg: return {"warning"}; - default: - return {"fatal"}; // wrong but we don't care - } -} - -static QtMsgType parseLevel(const QString &level) -{ - switch (level.at(0).toLatin1()) { - case 'c': return QtMsgType::QtCriticalMsg; - case 'd': return QtMsgType::QtDebugMsg; - case 'i': return QtMsgType::QtInfoMsg; - case 'w': return QtMsgType::QtWarningMsg; - default: - return QtMsgType::QtFatalMsg; // wrong but we don't care - } -} - -static bool parseLine(const QString &line, FilterRuleSpec *filterRule) -{ - const QStringList parts = line.split('='); - if (parts.size() != 2) - return false; - - const QString category = parts.at(0); - static const QRegularExpression regex("^(.+?)(\\.(debug|info|warning|critical))?$"); - const QRegularExpressionMatch match = regex.match(category); - if (!match.hasMatch()) - return false; - - const QString categoryName = match.captured(1); - if (categoryName.size() > 2) { - if (categoryName.mid(1, categoryName.size() - 2).contains('*')) - return false; - } else if (categoryName.size() == 2) { - if (categoryName.count('*') == 2) - return false; - } - filterRule->category = categoryName; - - if (match.capturedLength(2) == 0) - filterRule->level = std::nullopt; - else - filterRule->level = std::make_optional(parseLevel(match.captured(2).mid(1))); - - const QString enabled = parts.at(1); - if (enabled == "true" || enabled == "false") { - filterRule->enabled = (enabled == "true"); - return true; - } - return false; -} - -static QList fetchOriginalRules() -{ - QList rules; - - auto appendRulesFromFile = [&rules](const QString &fileName) { - QSettings iniSettings(fileName, QSettings::IniFormat); - iniSettings.beginGroup("Rules"); - const QStringList keys = iniSettings.allKeys(); - for (const QString &key : keys) { - const QString value = iniSettings.value(key).toString(); - FilterRuleSpec filterRule; - if (parseLine(key + "=" + value, &filterRule)) - rules.append(filterRule); - } - iniSettings.endGroup(); - }; - - Utils::FilePath iniFile = Utils::FilePath::fromString( - QLibraryInfo::path(QLibraryInfo::DataPath)).pathAppended("qtlogging.ini"); - if (iniFile.exists()) - appendRulesFromFile(iniFile.toString()); - - const QString qtProjectString = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, - "QtProject/qtlogging.ini"); - if (!qtProjectString.isEmpty()) - appendRulesFromFile(qtProjectString); - - iniFile = Utils::FilePath::fromString(qtcEnvironmentVariable("QT_LOGGING_CONF")); - if (iniFile.exists()) - appendRulesFromFile(iniFile.toString()); - - if (qtcEnvironmentVariableIsSet("QT_LOGGING_RULES")) { - const QStringList rulesStrings = qtcEnvironmentVariable("QT_LOGGING_RULES").split(';'); - for (const QString &rule : rulesStrings) { - FilterRuleSpec filterRule; - if (parseLine(rule, &filterRule)) - rules.append(filterRule); - } - } - return rules; -} - -LoggingViewManager::LoggingViewManager(QObject *parent) - : QObject(parent) - , m_originalLoggingRules(qtcEnvironmentVariable("QT_LOGGING_RULES")) -{ - qRegisterMetaType(); - s_instance = this; - s_originalMessageHandler = qInstallMessageHandler(logMessageHandler); - m_enabled = true; - m_originalRules = fetchOriginalRules(); - prefillCategories(); - QLoggingCategory::setFilterRules("*=true"); -} - -LoggingViewManager::~LoggingViewManager() -{ - m_enabled = false; - qInstallMessageHandler(s_originalMessageHandler); - s_originalMessageHandler = nullptr; - qputenv("QT_LOGGING_RULES", m_originalLoggingRules.toLocal8Bit()); - QLoggingCategory::setFilterRules("*=false"); - resetFilterRules(); - s_instance = nullptr; -} - -LoggingViewManager *LoggingViewManager::instance() -{ - return s_instance; -} - -void LoggingViewManager::logMessageHandler(QtMsgType type, const QMessageLogContext &context, - const QString &mssg) -{ - if (!s_instance->m_enabled) { - if (s_instance->enabledInOriginalRules(context, type)) - s_originalMessageHandler(type, context, mssg); - return; - } - - if (!context.category) { - s_originalMessageHandler(type, context, mssg); - return; - } - - const QString category = QString::fromLocal8Bit(context.category); - auto it = s_instance->m_categories.find(category); - if (it == s_instance->m_categories.end()) { - if (!s_instance->m_listQtInternal && category.startsWith("qt.")) - return; - LoggingCategoryEntry entry; - entry.level = QtMsgType::QtDebugMsg; - entry.enabled = (category == "default") || s_instance->enabledInOriginalRules(context, type); - it = s_instance->m_categories.insert(category, entry); - emit s_instance->foundNewCategory(category, entry); - } - - const LoggingCategoryEntry entry = it.value(); - if (entry.enabled && enabled(type, entry.level)) { - const QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz"); - emit s_instance->receivedLog(timestamp, category, - LoggingViewManager::messageTypeToString(type), mssg); - } -} - -bool LoggingViewManager::isCategoryEnabled(const QString &category) -{ - auto entry = m_categories.find(category); - if (entry == m_categories.end()) // shall not happen - paranoia - return false; - - return entry.value().enabled; -} - -void LoggingViewManager::setCategoryEnabled(const QString &category, bool enabled) -{ - auto entry = m_categories.find(category); - if (entry == m_categories.end()) // shall not happen - paranoia - return; - - entry->enabled = enabled; -} - -void LoggingViewManager::setLogLevel(const QString &category, QtMsgType type) -{ - auto entry = m_categories.find(category); - if (entry == m_categories.end()) // shall not happen - paranoia - return; - - entry->level = type; -} - -void LoggingViewManager::setListQtInternal(bool listQtInternal) -{ - m_listQtInternal = listQtInternal; -} - -void LoggingViewManager::appendOrUpdate(const QString &category, const LoggingCategoryEntry &entry) -{ - auto it = m_categories.find(category); - bool append = it == m_categories.end(); - m_categories.insert(category, entry); - if (append) - emit foundNewCategory(category, entry); - else - emit updatedCategory(category, entry); -} - -/* - * Does not check categories for being present, will perform early exit if m_categories is not empty - */ -void LoggingViewManager::prefillCategories() -{ - if (!m_categories.isEmpty()) - return; - - for (int i = 0, end = m_originalRules.size(); i < end; ++i) { - const FilterRuleSpec &rule = m_originalRules.at(i); - if (rule.category.startsWith('*') || rule.category.endsWith('*')) - continue; - - bool enabled = rule.enabled; - // check following rules whether they might overwrite - for (int j = i + 1; j < end; ++j) { - const FilterRuleSpec &secondRule = m_originalRules.at(j); - const QRegularExpression regex( - QRegularExpression::wildcardToRegularExpression(secondRule.category)); - if (!regex.match(rule.category).hasMatch()) - continue; - - if (secondRule.level.has_value() && rule.level != secondRule.level) - continue; - - enabled = secondRule.enabled; - } - LoggingCategoryEntry entry; - entry.level = rule.level.value_or(QtMsgType::QtInfoMsg); - entry.enabled = enabled; - m_categories.insert(rule.category, entry); - } -} - -void LoggingViewManager::resetFilterRules() -{ - for (const FilterRuleSpec &rule : std::as_const(m_originalRules)) { - const QString level = rule.level.has_value() ? '.' + levelToString(rule.level.value()) - : QString(); - const QString ruleString = rule.category + level + '=' + (rule.enabled ? "true" : "false"); - QLoggingCategory::setFilterRules(ruleString); - } -} - -bool LoggingViewManager::enabledInOriginalRules(const QMessageLogContext &context, QtMsgType type) -{ - if (!context.category) - return false; - const QString category = QString::fromUtf8(context.category); - bool result = false; - for (const FilterRuleSpec &rule : std::as_const(m_originalRules)) { - const QRegularExpression regex( - QRegularExpression::wildcardToRegularExpression(rule.category)); - if (regex.match(category).hasMatch()) { - if (rule.level.has_value()) { - if (rule.level.value() == type) - result = rule.enabled; - } else { - result = rule.enabled; - } - } - } - return result; -} - -} // namespace Internal -} // namespace Core diff --git a/src/plugins/coreplugin/loggingmanager.h b/src/plugins/coreplugin/loggingmanager.h deleted file mode 100644 index 1b8830c0aa1..00000000000 --- a/src/plugins/coreplugin/loggingmanager.h +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include -#include -#include - -#include - -namespace Core { -namespace Internal { - -struct FilterRuleSpec -{ - QString category; - std::optional level; - bool enabled; -}; - -class LoggingCategoryEntry -{ -public: - QtMsgType level = QtDebugMsg; - bool enabled = false; - QColor color; -}; - -class LoggingViewManager : public QObject -{ - Q_OBJECT -public: - static inline QString messageTypeToString(QtMsgType type) - { - switch (type) { - case QtDebugMsg: return {"Debug"}; - case QtInfoMsg: return {"Info"}; - case QtCriticalMsg: return {"Critical"}; - case QtWarningMsg: return {"Warning"}; - case QtFatalMsg: return {"Fatal"}; - default: return {"Unknown"}; - } - } - - static inline QtMsgType messageTypeFromString(const QString &type) - { - if (type.isEmpty()) - return QtDebugMsg; - - // shortcut - only handle expected - switch (type.at(0).toLatin1()) { - case 'I': - return QtInfoMsg; - case 'C': - return QtCriticalMsg; - case 'W': - return QtWarningMsg; - case 'D': - default: - return QtDebugMsg; - } - } - - explicit LoggingViewManager(QObject *parent = nullptr); - ~LoggingViewManager(); - - static LoggingViewManager *instance(); - - static inline bool enabled(QtMsgType current, QtMsgType stored) - { - if (stored == QtMsgType::QtInfoMsg) - return true; - if (current == stored) - return true; - if (stored == QtMsgType::QtDebugMsg) - return current != QtMsgType::QtInfoMsg; - if (stored == QtMsgType::QtWarningMsg) - return current == QtMsgType::QtCriticalMsg || current == QtMsgType::QtFatalMsg; - if (stored == QtMsgType::QtCriticalMsg) - return current == QtMsgType::QtFatalMsg; - return false; - } - - static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, - const QString &mssg); - - void setEnabled(bool enabled) { m_enabled = enabled; } - bool isEnabled() const { return m_enabled; } - bool isCategoryEnabled(const QString &category); - void setCategoryEnabled(const QString &category, bool enabled); - void setLogLevel(const QString &category, QtMsgType type); - void setListQtInternal(bool listQtInternal); - QList originalRules() const { return m_originalRules; } - - QMap categories() const { return m_categories; } - void appendOrUpdate(const QString &category, const LoggingCategoryEntry &entry); - -signals: - void receivedLog(const QString ×tamp, const QString &type, const QString &category, - const QString &msg); - void foundNewCategory(const QString &category, const LoggingCategoryEntry &entry); - void updatedCategory(const QString &category, const LoggingCategoryEntry &entry); - -private: - void prefillCategories(); - void resetFilterRules(); - bool enabledInOriginalRules(const QMessageLogContext &context, QtMsgType type); - - QMap m_categories; - const QString m_originalLoggingRules; - QList m_originalRules; - bool m_enabled = false; - bool m_listQtInternal = false; -}; - -} // namespace Internal -} // namespace Core - -Q_DECLARE_METATYPE(Core::Internal::LoggingCategoryEntry) diff --git a/src/plugins/coreplugin/loggingviewer.cpp b/src/plugins/coreplugin/loggingviewer.cpp index 742e65a34c5..bf1f8b736f0 100644 --- a/src/plugins/coreplugin/loggingviewer.cpp +++ b/src/plugins/coreplugin/loggingviewer.cpp @@ -3,26 +3,22 @@ #include "loggingviewer.h" -#include "actionmanager/actionmanager.h" #include "coreicons.h" #include "coreplugintr.h" #include "icore.h" -#include "loggingmanager.h" -#include #include #include +#include #include #include +#include #include #include #include -#include #include -#include #include -#include #include #include #include @@ -31,138 +27,348 @@ #include #include #include -#include -#include #include -#include +#include #include -#include #include -namespace Core { -namespace Internal { +namespace Core::Internal { -class LoggingCategoryItem +static QColor colorForCategory(const QString &category); +void setCategoryColor(const QString &category, const QColor &color); + +QHash s_categoryColor; + +static inline QString messageTypeToString(QtMsgType type) { -public: - QString name; - LoggingCategoryEntry entry; + switch (type) { + case QtDebugMsg: + return {"Debug"}; + case QtInfoMsg: + return {"Info"}; + case QtCriticalMsg: + return {"Critical"}; + case QtWarningMsg: + return {"Warning"}; + case QtFatalMsg: + return {"Fatal"}; + default: + return {"Unknown"}; + } +} - static LoggingCategoryItem fromJson(const QJsonObject &object, bool *ok); +class LogCategoryRegistry : public QObject +{ + Q_OBJECT +public: + static LogCategoryRegistry &instance() + { + static LogCategoryRegistry s_instance; + return s_instance; + } + + static void filter(QLoggingCategory *category) + { + if (s_oldFilter) + s_oldFilter(category); + + LogCategoryRegistry::instance().onFilter(category); + } + + void start() + { + if (m_started) + return; + + m_started = true; + s_oldFilter = QLoggingCategory::installFilter(&LogCategoryRegistry::filter); + } + + QList categories() { return m_categories; } + +signals: + void newLogCategory(QLoggingCategory *category); + +private: + LogCategoryRegistry() = default; + + void onFilter(QLoggingCategory *category) + { + if (!m_categories.contains(category)) { + m_categories.append(category); + emit newLogCategory(category); + } + } + +private: + static QLoggingCategory::CategoryFilter s_oldFilter; + + QList m_categories; + bool m_started{false}; }; -LoggingCategoryItem LoggingCategoryItem::fromJson(const QJsonObject &object, bool *ok) +QLoggingCategory::CategoryFilter LogCategoryRegistry::s_oldFilter; + +struct SavedEntry { - if (!object.contains("name")) { - *ok = false; - return {}; + QColor color; + QString name; + QtMsgType level; + std::optional> levels; + + static Utils::expected_str fromJson(const QJsonObject &obj) + { + if (!obj.contains("name")) + return Utils::make_unexpected(Tr::tr("Entry is missing a logging category name.")); + + SavedEntry result; + result.name = obj.value("name").toString(); + + if (!obj.contains("entry")) + return Utils::make_unexpected(Tr::tr("Entry is missing data.")); + + auto entry = obj.value("entry").toObject(); + if (entry.contains("color")) + result.color = QColor(entry.value("color").toString()); + + if (entry.contains("level")) { + int lvl = entry.value("level").toInt(0); + if (lvl < QtDebugMsg || lvl > QtInfoMsg) + return Utils::make_unexpected(Tr::tr("Invalid level: %1").arg(lvl)); + result.level = static_cast(lvl); + } + + if (entry.contains("levels")) { + QVariantMap map = entry.value("levels").toVariant().toMap(); + std::array levels{ + map.contains("Debug") && map["Debug"].toBool(), + map.contains("Warning") && map["Warning"].toBool(), + map.contains("Critical") && map["Critical"].toBool(), + true, + map.contains("Info") && map["Info"].toBool(), + }; + result.levels = levels; + } + + return result; } - const QJsonValue entryVal = object.value("entry"); - if (entryVal.isUndefined()) { - *ok = false; - return {}; - } - const QJsonObject entryObj = entryVal.toObject(); - if (!entryObj.contains("level")) { - *ok = false; - return {}; +}; + +class LoggingCategoryEntry +{ +public: + LoggingCategoryEntry(const QString &name) + : m_name(name) + {} + LoggingCategoryEntry(QLoggingCategory *category) + : m_name(QString::fromUtf8(category->categoryName())) + { + setLogCategory(category); } - LoggingCategoryEntry entry; - entry.level = QtMsgType(entryObj.value("level").toInt()); - entry.enabled = true; - if (entryObj.contains("color")) - entry.color = QColor(entryObj.value("color").toString()); - LoggingCategoryItem item {object.value("name").toString(), entry}; - *ok = true; - return item; -} + LoggingCategoryEntry(const SavedEntry &savedEntry) + : m_name(savedEntry.name) + { + m_saved = savedEntry.levels; + m_color = savedEntry.color; + if (!m_saved) { + m_saved = std::array(); + for (int i = QtDebugMsg; i <= QtInfoMsg; ++i) { + (*m_saved)[i] = savedEntry.level <= i; + } + } + } + + QString name() const { return m_name; } + QColor color() const { return m_color; } + void setColor(const QColor &c) { m_color = c; } + + void setUseOriginal(bool useOriginal) + { + if (!m_useOriginal && m_category && m_originalSettings) { + m_saved = std::array{}; + + for (int i = QtDebugMsg; i < QtInfoMsg; i++) { + (*m_saved)[i] = m_category->isEnabled(static_cast(i)); + m_category->setEnabled(static_cast(i), (*m_originalSettings)[i]); + } + + } else if (!useOriginal && m_useOriginal && m_saved && m_category) { + for (int i = QtDebugMsg; i < QtInfoMsg; i++) + m_category->setEnabled(static_cast(i), (*m_saved)[i]); + } + m_useOriginal = useOriginal; + } + + bool isEnabled(QtMsgType msgType) const + { + if (m_category) + return m_category->isEnabled(msgType); + if (m_saved) + return (*m_saved)[msgType]; + return false; + } + + void setEnabled(QtMsgType msgType, bool isEnabled) + { + QTC_ASSERT(!m_useOriginal, return); + + if (m_category) + m_category->setEnabled(msgType, isEnabled); + + if (m_saved) + (*m_saved)[msgType] = isEnabled; + } + + void setSaved(const SavedEntry &entry) + { + QTC_ASSERT(entry.name == name(), return); + + m_saved = entry.levels; + m_color = entry.color; + if (!m_saved) { + m_saved = std::array(); + for (int i = QtDebugMsg; i <= QtInfoMsg; ++i) { + (*m_saved)[i] = entry.level <= i; + } + } + if (m_category) + setLogCategory(m_category); + } + + void setLogCategory(QLoggingCategory *category) + { + QTC_ASSERT(QString::fromUtf8(category->categoryName()) == m_name, return); + + m_category = category; + if (!m_originalSettings) { + m_originalSettings = { + category->isDebugEnabled(), + category->isWarningEnabled(), + category->isCriticalEnabled(), + true, // always enable fatal + category->isInfoEnabled(), + }; + } + + if (m_saved && !m_useOriginal) { + m_category->setEnabled(QtDebugMsg, m_saved->at(0)); + m_category->setEnabled(QtWarningMsg, m_saved->at(1)); + m_category->setEnabled(QtCriticalMsg, m_saved->at(2)); + m_category->setEnabled(QtInfoMsg, m_saved->at(4)); + } + } + + bool isDebugEnabled() const { return isEnabled(QtDebugMsg); } + bool isWarningEnabled() const { return isEnabled(QtWarningMsg); } + bool isCriticalEnabled() const { return isEnabled(QtCriticalMsg); } + bool isInfoEnabled() const { return isEnabled(QtInfoMsg); } + +private: + QString m_name; + QLoggingCategory *m_category{nullptr}; + std::optional> m_originalSettings; + std::optional> m_saved; + QColor m_color; + bool m_useOriginal{false}; +}; class LoggingCategoryModel : public QAbstractListModel { Q_OBJECT public: - LoggingCategoryModel() = default; - ~LoggingCategoryModel() override; + LoggingCategoryModel(QObject *parent) + : QAbstractListModel(parent) + { + auto newCategory = [this](QLoggingCategory *category) { + QString name = QString::fromUtf8(category->categoryName()); + auto itExists = std::find_if(m_categories.begin(), + m_categories.end(), + [name](const auto &cat) { return name == cat.name(); }); - bool append(const QString &category, const LoggingCategoryEntry &entry = {}); - bool update(const QString &category, const LoggingCategoryEntry &entry); - int columnCount(const QModelIndex &) const final { return 3; } - int rowCount(const QModelIndex & = QModelIndex()) const final { return m_categories.count(); } + if (itExists != m_categories.end()) { + itExists->setLogCategory(category); + } else { + LoggingCategoryEntry entry(category); + append(entry); + } + }; + + for (QLoggingCategory *cat : LogCategoryRegistry::instance().categories()) + newCategory(cat); + + connect(&LogCategoryRegistry::instance(), + &LogCategoryRegistry::newLogCategory, + this, + newCategory); + + LogCategoryRegistry::instance().start(); + }; + + ~LoggingCategoryModel() override; + enum Column { Color, Name, Debug, Warning, Critical, Fatal, Info }; + + void append(const LoggingCategoryEntry &entry); + int columnCount(const QModelIndex &) const final { return 7; } + int rowCount(const QModelIndex & = QModelIndex()) const final { return m_categories.size(); } QVariant data(const QModelIndex &index, int role) const final; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) final; Qt::ItemFlags flags(const QModelIndex &index) const final; - QVariant headerData(int section, Qt::Orientation orientation, + QVariant headerData(int section, + Qt::Orientation orientation, int role = Qt::DisplayRole) const final; - void reset(); - void setFromManager(LoggingViewManager *manager); - QList enabledCategories() const; - void disableAll(); -signals: - void categoryChanged(const QString &category, bool enabled); - void colorChanged(const QString &category, const QColor &color); - void logLevelChanged(const QString &category, QtMsgType logLevel); + void saveEnabledCategoryPreset() const; + void loadAndUpdateFromPreset(); + + void setUseOriginal(bool useOriginal) + { + if (useOriginal != m_useOriginal) { + beginResetModel(); + for (auto &entry : m_categories) + entry.setUseOriginal(useOriginal); + + m_useOriginal = useOriginal; + endResetModel(); + } + } + + const QList &categories() const { return m_categories; } private: - QList m_categories; + QList m_categories; + bool m_useOriginal{false}; }; -LoggingCategoryModel::~LoggingCategoryModel() -{ - reset(); -} +LoggingCategoryModel::~LoggingCategoryModel() {} -bool LoggingCategoryModel::append(const QString &category, const LoggingCategoryEntry &entry) +void LoggingCategoryModel::append(const LoggingCategoryEntry &entry) { - // no check? - beginInsertRows(QModelIndex(), m_categories.size(), m_categories.size()); - m_categories.append(new LoggingCategoryItem{category, entry}); + beginInsertRows(QModelIndex(), m_categories.size(), m_categories.size() + 1); + m_categories.push_back(entry); endInsertRows(); - return true; -} - -bool LoggingCategoryModel::update(const QString &category, const LoggingCategoryEntry &entry) -{ - if (m_categories.size() == 0) // should not happen - return false; - - int row = 0; - for (int end = m_categories.size(); row < end; ++row) { - if (m_categories.at(row)->name == category) - break; - } - if (row == m_categories.size()) // should not happen - return false; - - setData(index(row, 0), Qt::Checked, Qt::CheckStateRole); - setData(index(row, 1), LoggingViewManager::messageTypeToString(entry.level), Qt::EditRole); - setData(index(row, 2), entry.color, Qt::DecorationRole); - return true; } QVariant LoggingCategoryModel::data(const QModelIndex &index, int role) const { - static const QColor defaultColor = Utils::creatorTheme()->palette().text().color(); if (!index.isValid()) return {}; - if (role == Qt::DisplayRole) { - if (index.column() == 0) - return m_categories.at(index.row())->name; - if (index.column() == 1) { - return LoggingViewManager::messageTypeToString( - m_categories.at(index.row())->entry.level); - } - } - if (role == Qt::DecorationRole && index.column() == 2) { - const QColor color = m_categories.at(index.row())->entry.color; + + if (index.column() == Column::Name && role == Qt::DisplayRole) { + return m_categories.at(index.row()).name(); + } else if (role == Qt::DecorationRole && index.column() == Column::Color) { + const QColor color = m_categories.at(index.row()).color(); if (color.isValid()) return color; + + static const QColor defaultColor = Utils::creatorTheme()->palette().text().color(); return defaultColor; - } - if (role == Qt::CheckStateRole && index.column() == 0) { - const LoggingCategoryEntry entry = m_categories.at(index.row())->entry; - return entry.enabled ? Qt::Checked : Qt::Unchecked; + } else if (role == Qt::CheckStateRole && index.column() >= Column::Debug + && index.column() <= Column::Info) { + const LoggingCategoryEntry &entry = m_categories.at(index.row()); + return entry.isEnabled(static_cast(index.column() - Column::Debug)) + ? Qt::Checked + : Qt::Unchecked; } return {}; } @@ -172,27 +378,28 @@ bool LoggingCategoryModel::setData(const QModelIndex &index, const QVariant &val if (!index.isValid()) return false; - if (role == Qt::CheckStateRole && index.column() == 0) { - LoggingCategoryItem *item = m_categories.at(index.row()); - const Qt::CheckState current = item->entry.enabled ? Qt::Checked : Qt::Unchecked; + if (role == Qt::CheckStateRole && index.column() >= Column::Debug + && index.column() <= Column::Info) { + QtMsgType msgType = static_cast(index.column() - Column::Debug); + auto &entry = m_categories[index.row()]; + bool isEnabled = entry.isEnabled(msgType); + + const Qt::CheckState current = isEnabled ? Qt::Checked : Qt::Unchecked; + if (current != value.toInt()) { - item->entry.enabled = !item->entry.enabled; - emit categoryChanged(item->name, item->entry.enabled); + entry.setEnabled(msgType, value.toInt() == Qt::Checked); return true; } - } else if (role == Qt::DecorationRole && index.column() == 2) { - LoggingCategoryItem *item = m_categories.at(index.row()); + } else if (role == Qt::DecorationRole && index.column() == Column::Color) { + auto &category = m_categories[index.row()]; + QColor currentColor = category.color(); QColor color = value.value(); - if (color.isValid() && color != item->entry.color) { - item->entry.color = color; - emit colorChanged(item->name, color); + if (color.isValid() && color != currentColor) { + category.setColor(color); + setCategoryColor(category.name(), color); + emit dataChanged(index, index, {Qt::DisplayRole}); return true; } - } else if (role == Qt::EditRole && index.column() == 1) { - LoggingCategoryItem *item = m_categories.at(index.row()); - item->entry.level = LoggingViewManager::messageTypeFromString(value.toString()); - emit logLevelChanged(item->name, item->entry.level); - return true; } return false; @@ -203,111 +410,47 @@ Qt::ItemFlags LoggingCategoryModel::flags(const QModelIndex &index) const if (!index.isValid()) return Qt::NoItemFlags; - // ItemIsEnabled should depend on availability (Qt logging enabled?) - if (index.column() == 0) - return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; - if (index.column() == 1) - return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + if (index.column() == LoggingCategoryModel::Column::Fatal) + return Qt::NoItemFlags; + + if (index.column() == Column::Name || index.column() == Column::Color) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + if (m_useOriginal) + return Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; } QVariant LoggingCategoryModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section >= 0 && section < 3) { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section >= 0 && section < 8) { switch (section) { - case 0: return Tr::tr("Category"); - case 1: return Tr::tr("Type"); - case 2: return Tr::tr("Color"); + case Column::Name: + return Tr::tr("Category"); + case Column::Color: + return Tr::tr("Color"); + case Column::Debug: + return Tr::tr("Debug"); + case Column::Warning: + return Tr::tr("Warning"); + case Column::Critical: + return Tr::tr("Critical"); + case Column::Fatal: + return Tr::tr("Fatal"); + case Column::Info: + return Tr::tr("Info"); } } return {}; } -void LoggingCategoryModel::reset() -{ - beginResetModel(); - qDeleteAll(m_categories); - m_categories.clear(); - endResetModel(); -} - -void LoggingCategoryModel::setFromManager(LoggingViewManager *manager) -{ - beginResetModel(); - qDeleteAll(m_categories); - m_categories.clear(); - const QMap categories = manager->categories(); - auto it = categories.begin(); - for (auto end = categories.end() ; it != end; ++it) - m_categories.append(new LoggingCategoryItem{it.key(), it.value()}); - endResetModel(); -} - -QList LoggingCategoryModel::enabledCategories() const -{ - QList result; - for (auto item : m_categories) { - if (item->entry.enabled) - result.append({item->name, item->entry}); - } - return result; -} - -void LoggingCategoryModel::disableAll() -{ - for (int row = 0, end = m_categories.count(); row < end; ++row) - setData(index(row, 0), Qt::Unchecked, Qt::CheckStateRole); -} - -class LoggingLevelDelegate : public QStyledItemDelegate -{ -public: - explicit LoggingLevelDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} - ~LoggingLevelDelegate() = default; - -protected: - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - void setEditorData(QWidget *editor, const QModelIndex &index) const override; - void setModelData(QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index) const override; -}; - -QWidget *LoggingLevelDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, - const QModelIndex &index) const -{ - if (!index.isValid() || index.column() != 1) - return nullptr; - QComboBox *combo = new QComboBox(parent); - combo->addItems({ {"Critical"}, {"Warning"}, {"Debug"}, {"Info"} }); - return combo; -} - -void LoggingLevelDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QComboBox *combo = qobject_cast(editor); - if (!combo) - return; - - const int i = combo->findText(index.data().toString()); - if (i >= 0) - combo->setCurrentIndex(i); -} - -void LoggingLevelDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index) const -{ - QComboBox *combo = qobject_cast(editor); - if (combo) - model->setData(index, combo->currentText()); -} - class LogEntry { public: QString timestamp; - QString category; QString type; + QString category; QString message; QString outputLine(bool printTimestamp, bool printType) const @@ -325,95 +468,157 @@ public: } }; +class LoggingEntryModel : public Utils::ListModel +{ +public: + ~LoggingEntryModel() { qInstallMessageHandler(m_originalMessageHandler); } + + static void logMessageHandler(QtMsgType type, + const QMessageLogContext &context, + const QString &mssg) + { + instance().msgHandler(type, context, mssg); + } + + static QVariant logEntryDataAccessor(const LogEntry &entry, int column, int role) + { + if (column >= 0 && column <= 3 && (role == Qt::DisplayRole || role == Qt::ToolTipRole)) { + switch (column) { + case 0: + return entry.timestamp; + case 1: + return entry.category; + case 2: + return entry.type; + case 3: { + if (role == Qt::ToolTipRole) + return entry.message; + return entry.message.left(1000); + } + } + } + if (role == Qt::TextAlignmentRole) + return Qt::AlignTop; + if (column == 1 && role == Qt::ForegroundRole) + return colorForCategory(entry.category); + return {}; + } + + static LoggingEntryModel &instance() + { + static LoggingEntryModel model; + return model; + } + + void setEnabled(bool enabled) { m_enabled = enabled; } + +private: + LoggingEntryModel() + { + setHeader({Tr::tr("Timestamp"), Tr::tr("Category"), Tr::tr("Type"), Tr::tr("Message")}); + setDataAccessor(&logEntryDataAccessor); + + m_originalMessageHandler = qInstallMessageHandler(logMessageHandler); + } + + void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) + { + if (!m_enabled) { + m_originalMessageHandler(type, context, msg); + return; + } + + if (!context.category) { + m_originalMessageHandler(type, context, msg); + return; + } + + const QString category = QString::fromLocal8Bit(context.category); + + const QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz"); + + if (rowCount() >= 1000000) // limit log to 1000000 items + destroyItem(itemForIndex(index(0, 0))); + + appendItem(LogEntry{timestamp, messageTypeToString(type), category, msg}); + } + +private: + QtMessageHandler m_originalMessageHandler{nullptr}; + bool m_enabled{true}; +}; + class LoggingViewManagerWidget : public QDialog { public: - explicit LoggingViewManagerWidget(QWidget *parent); - ~LoggingViewManagerWidget() + ~LoggingViewManagerWidget() { LoggingEntryModel::instance().setEnabled(false); } + + static LoggingViewManagerWidget *instance() { - setEnabled(false); - delete m_manager; + static QPointer instance = new LoggingViewManagerWidget( + Core::ICore::dialogParent()); + return instance; } - static QColor colorForCategory(const QString &category); +protected: + void showEvent(QShowEvent *) override + { + if (!m_stopLog->isChecked()) + m_categoryModel->setUseOriginal(false); + + LoggingEntryModel::instance().setEnabled(!m_stopLog->isChecked()); + } + void hideEvent(QHideEvent *) override + { + m_categoryModel->setUseOriginal(true); + LoggingEntryModel::instance().setEnabled(false); + } + +private: + explicit LoggingViewManagerWidget(QWidget *parent); + private: void showLogViewContextMenu(const QPoint &pos) const; void showLogCategoryContextMenu(const QPoint &pos) const; void saveLoggingsToFile() const; - void saveEnabledCategoryPreset() const; - void loadAndUpdateFromPreset(); - LoggingViewManager *m_manager = nullptr; - void setCategoryColor(const QString &category, const QColor &color); - // should category model be owned directly by the manager? or is this duplication of - // categories in manager and widget beneficial? + QSortFilterProxyModel *m_sortFilterModel = nullptr; LoggingCategoryModel *m_categoryModel = nullptr; Utils::BaseTreeView *m_logView = nullptr; Utils::BaseTreeView *m_categoryView = nullptr; - Utils::ListModel *m_logModel = nullptr; QToolButton *m_timestamps = nullptr; QToolButton *m_messageTypes = nullptr; - static QHash m_categoryColor; + QToolButton *m_stopLog = nullptr; }; -QHash LoggingViewManagerWidget::m_categoryColor; - -static QVariant logEntryDataAccessor(const LogEntry &entry, int column, int role) -{ - if (column >= 0 && column <= 3 && (role == Qt::DisplayRole || role == Qt::ToolTipRole)) { - switch (column) { - case 0: return entry.timestamp; - case 1: return entry.category; - case 2: return entry.type; - case 3: { - if (role == Qt::ToolTipRole) - return entry.message; - return entry.message.left(1000); - } - } - } - if (role == Qt::TextAlignmentRole) - return Qt::AlignTop; - if (column == 1 && role == Qt::ForegroundRole) - return LoggingViewManagerWidget::colorForCategory(entry.category); - return {}; -} - LoggingViewManagerWidget::LoggingViewManagerWidget(QWidget *parent) : QDialog(parent) - , m_manager(new LoggingViewManager) { setWindowTitle(Tr::tr("Logging Category Viewer")); - setModal(false); - - auto mainLayout = new QVBoxLayout; - - auto buttonsLayout = new QHBoxLayout; - buttonsLayout->setSpacing(0); - // add further buttons.. auto save = new QToolButton; save->setIcon(Utils::Icons::SAVEFILE.icon()); save->setToolTip(Tr::tr("Save Log")); - buttonsLayout->addWidget(save); + auto clean = new QToolButton; clean->setIcon(Utils::Icons::CLEAN.icon()); clean->setToolTip(Tr::tr("Clear")); - buttonsLayout->addWidget(clean); - auto stop = new QToolButton; - stop->setIcon(Utils::Icons::STOP_SMALL.icon()); - stop->setToolTip(Tr::tr("Stop Logging")); - buttonsLayout->addWidget(stop); + + m_stopLog = new QToolButton; + m_stopLog->setIcon(Utils::Icons::STOP_SMALL.icon()); + m_stopLog->setToolTip(Tr::tr("Stop Logging")); + m_stopLog->setCheckable(true); + auto qtInternal = new QToolButton; qtInternal->setIcon(Core::Icons::QTLOGO.icon()); qtInternal->setToolTip(Tr::tr("Toggle Qt Internal Logging")); qtInternal->setCheckable(true); qtInternal->setChecked(false); - buttonsLayout->addWidget(qtInternal); + auto autoScroll = new QToolButton; autoScroll->setIcon(Utils::Icons::ARROW_DOWN.icon()); autoScroll->setToolTip(Tr::tr("Auto Scroll")); autoScroll->setCheckable(true); autoScroll->setChecked(true); - buttonsLayout->addWidget(autoScroll); + m_timestamps = new QToolButton; auto icon = Utils::Icon({{":/utils/images/stopwatch.png", Utils::Theme::PanelTextColorMid}}, Utils::Icon::Tint); @@ -421,7 +626,7 @@ LoggingViewManagerWidget::LoggingViewManagerWidget(QWidget *parent) m_timestamps->setToolTip(Tr::tr("Timestamps")); m_timestamps->setCheckable(true); m_timestamps->setChecked(true); - buttonsLayout->addWidget(m_timestamps); + m_messageTypes = new QToolButton; icon = Utils::Icon({{":/utils/images/message.png", Utils::Theme::PanelTextColorMid}}, Utils::Icon::Tint); @@ -429,18 +634,9 @@ LoggingViewManagerWidget::LoggingViewManagerWidget(QWidget *parent) m_messageTypes->setToolTip(Tr::tr("Message Types")); m_messageTypes->setCheckable(true); m_messageTypes->setChecked(false); - buttonsLayout->addWidget(m_messageTypes); - buttonsLayout->addSpacerItem(new QSpacerItem(10, 10, QSizePolicy::Expanding)); - mainLayout->addLayout(buttonsLayout); - - auto horizontal = new QHBoxLayout; m_logView = new Utils::BaseTreeView; - m_logModel = new Utils::ListModel; - m_logModel->setHeader({Tr::tr("Timestamp"), Tr::tr("Category"), Tr::tr("Type"), Tr::tr("Message")}); - m_logModel->setDataAccessor(&logEntryDataAccessor); - m_logView->setModel(m_logModel); - horizontal->addWidget(m_logView); + m_logView->setModel(&LoggingEntryModel::instance()); m_logView->setUniformRowHeights(true); m_logView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_logView->setFrameStyle(QFrame::Box); @@ -449,91 +645,123 @@ LoggingViewManagerWidget::LoggingViewManagerWidget(QWidget *parent) m_logView->setColumnHidden(2, true); m_logView->setContextMenuPolicy(Qt::CustomContextMenu); + m_categoryModel = new LoggingCategoryModel(this); + m_sortFilterModel = new QSortFilterProxyModel(m_categoryModel); + m_sortFilterModel->setSourceModel(m_categoryModel); + m_sortFilterModel->sort(LoggingCategoryModel::Column::Name); + m_sortFilterModel->setSortRole(Qt::DisplayRole); + m_sortFilterModel->setFilterKeyColumn(LoggingCategoryModel::Column::Name); + m_categoryView = new Utils::BaseTreeView; m_categoryView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_categoryView->setUniformRowHeights(true); m_categoryView->setFrameStyle(QFrame::Box); m_categoryView->setAttribute(Qt::WA_MacShowFocusRect, false); m_categoryView->setSelectionMode(QAbstractItemView::SingleSelection); m_categoryView->setContextMenuPolicy(Qt::CustomContextMenu); - m_categoryModel = new LoggingCategoryModel; - m_categoryModel->setFromManager(m_manager); - auto sortFilterModel = new QSortFilterProxyModel(this); - sortFilterModel->setSourceModel(m_categoryModel); - sortFilterModel->sort(0); - m_categoryView->setModel(sortFilterModel); - m_categoryView->setItemDelegateForColumn(1, new LoggingLevelDelegate(this)); - horizontal->addWidget(m_categoryView); - horizontal->setStretch(0, 5); - horizontal->setStretch(1, 3); + m_categoryView->setModel(m_sortFilterModel); + + for (int i = LoggingCategoryModel::Column::Color; i < LoggingCategoryModel::Column::Info; i++) + m_categoryView->resizeColumnToContents(i); + + QSplitter *splitter{nullptr}; + + using namespace Layouting; + // clang-format off + Column { + Row { + spacing(0), + save, + clean, + m_stopLog, + qtInternal, + autoScroll, + m_timestamps, + m_messageTypes, + st, + }, + Splitter { + bindTo(&splitter), + m_logView, + m_categoryView, + } + }.attachTo(this); + // clang-format on + + splitter->setOrientation(Qt::Horizontal); - mainLayout->addLayout(horizontal); - setLayout(mainLayout); resize(800, 300); - connect(m_manager, &LoggingViewManager::receivedLog, - this, [this](const QString ×tamp, - const QString &type, - const QString &category, - const QString &msg) { - if (m_logModel->rowCount() >= 1000000) // limit log to 1000000 items - m_logModel->destroyItem(m_logModel->itemForIndex(m_logModel->index(0, 0))); - m_logModel->appendItem(LogEntry{timestamp, type, category, msg}); - }, Qt::QueuedConnection); - connect(m_logModel, &QAbstractItemModel::rowsInserted, this, [this, autoScroll] { + connect( + &LoggingEntryModel::instance(), + &LoggingEntryModel::rowsInserted, + this, + [this, autoScroll] { if (autoScroll->isChecked()) m_logView->scrollToBottom(); - }, Qt::QueuedConnection); - connect(m_manager, &LoggingViewManager::foundNewCategory, - m_categoryModel, &LoggingCategoryModel::append, Qt::QueuedConnection); - connect(m_manager, &LoggingViewManager::updatedCategory, - m_categoryModel, &LoggingCategoryModel::update, Qt::QueuedConnection); - connect(m_categoryModel, &LoggingCategoryModel::categoryChanged, - m_manager, &LoggingViewManager::setCategoryEnabled); - connect(m_categoryModel, &LoggingCategoryModel::colorChanged, - this, &LoggingViewManagerWidget::setCategoryColor); - connect(m_categoryModel, &LoggingCategoryModel::logLevelChanged, - m_manager, &LoggingViewManager::setLogLevel); - connect(m_categoryView, &Utils::BaseTreeView::activated, - this, [this, sortFilterModel](const QModelIndex &index) { - const QModelIndex modelIndex = sortFilterModel->mapToSource(index); - const QVariant value = m_categoryModel->data(modelIndex, Qt::DecorationRole); - if (!value.isValid()) - return; - const QColor original = value.value(); - if (!original.isValid()) - return; - QColor changed = QColorDialog::getColor(original, this); - if (!changed.isValid()) - return; - if (original != changed) - m_categoryModel->setData(modelIndex, changed, Qt::DecorationRole); - }); - connect(save, &QToolButton::clicked, - this, &LoggingViewManagerWidget::saveLoggingsToFile); - connect(m_logView, &Utils::BaseTreeView::customContextMenuRequested, - this, &LoggingViewManagerWidget::showLogViewContextMenu); - connect(m_categoryView, &Utils::BaseTreeView::customContextMenuRequested, - this, &LoggingViewManagerWidget::showLogCategoryContextMenu); - connect(clean, &QToolButton::clicked, m_logModel, &Utils::ListModel::clear); - connect(stop, &QToolButton::clicked, this, [this, stop] { - if (m_manager->isEnabled()) { - m_manager->setEnabled(false); - stop->setIcon(Utils::Icons::RUN_SMALL.icon()); - stop->setToolTip(Tr::tr("Start Logging")); + }, + Qt::QueuedConnection); + + connect(m_categoryView, + &QAbstractItemView::activated, + m_sortFilterModel, + [this](const QModelIndex &index) { + const QVariant value = m_sortFilterModel->data(index, Qt::DecorationRole); + if (!value.isValid()) + return; + const QColor original = value.value(); + if (!original.isValid()) + return; + QColor changed = QColorDialog::getColor(original, this); + if (!changed.isValid()) + return; + if (original != changed) + m_sortFilterModel->setData(index, changed, Qt::DecorationRole); + }); + connect(save, &QToolButton::clicked, this, &LoggingViewManagerWidget::saveLoggingsToFile); + connect(m_logView, + &QAbstractItemView::customContextMenuRequested, + this, + &LoggingViewManagerWidget::showLogViewContextMenu); + connect(m_categoryView, + &QAbstractItemView::customContextMenuRequested, + this, + &LoggingViewManagerWidget::showLogCategoryContextMenu); + connect(clean, + &QToolButton::clicked, + &LoggingEntryModel::instance(), + &Utils::ListModel::clear); + connect(m_stopLog, &QToolButton::toggled, this, [this](bool checked) { + LoggingEntryModel::instance().setEnabled(!checked); + + if (checked) { + m_stopLog->setIcon(Utils::Icons::RUN_SMALL.icon()); + m_stopLog->setToolTip(Tr::tr("Start Logging")); + m_categoryModel->setUseOriginal(true); } else { - m_manager->setEnabled(true); - stop->setIcon(Utils::Icons::STOP_SMALL.icon()); - stop->setToolTip(Tr::tr("Stop Logging")); + m_stopLog->setIcon(Utils::Icons::STOP_SMALL.icon()); + m_stopLog->setToolTip(Tr::tr("Stop Logging")); + m_categoryModel->setUseOriginal(false); } }); - connect(qtInternal, &QToolButton::toggled, m_manager, &LoggingViewManager::setListQtInternal); - connect(m_timestamps, &QToolButton::toggled, this, [this](bool checked){ + + m_sortFilterModel->setFilterRegularExpression("^(?!qt\\.).+"); + + connect(qtInternal, &QToolButton::toggled, m_sortFilterModel, [this](bool checked) { + if (checked) { + m_sortFilterModel->setFilterRegularExpression(""); + } else { + m_sortFilterModel->setFilterRegularExpression("^(?!qt\\.).+"); + } + }); + + connect(m_timestamps, &QToolButton::toggled, this, [this](bool checked) { m_logView->setColumnHidden(0, !checked); }); - connect(m_messageTypes, &QToolButton::toggled, this, [this](bool checked){ + connect(m_messageTypes, &QToolButton::toggled, this, [this](bool checked) { m_logView->setColumnHidden(2, !checked); }); + + ICore::registerWindow(this, Context("Qtc.LogViewer")); } void LoggingViewManagerWidget::showLogViewContextMenu(const QPoint &pos) const @@ -548,104 +776,162 @@ void LoggingViewManagerWidget::showLogViewContextMenu(const QPoint &pos) const QString copied; const bool useTS = m_timestamps->isChecked(); const bool useLL = m_messageTypes->isChecked(); - for (int row = 0, end = m_logModel->rowCount(); row < end; ++row) { + for (int row = 0, end = LoggingEntryModel::instance().rowCount(); row < end; ++row) { if (selectionModel->isRowSelected(row, QModelIndex())) - copied.append(m_logModel->dataAt(row).outputLine(useTS, useLL)); + copied.append(LoggingEntryModel::instance().dataAt(row).outputLine(useTS, useLL)); } - QGuiApplication::clipboard()->setText(copied); + Utils::setClipboardAndSelection(copied); }); connect(copyAll, &QAction::triggered, &m, [this] { QString copied; const bool useTS = m_timestamps->isChecked(); const bool useLL = m_messageTypes->isChecked(); - for (int row = 0, end = m_logModel->rowCount(); row < end; ++row) - copied.append(m_logModel->dataAt(row).outputLine(useTS, useLL)); + for (int row = 0, end = LoggingEntryModel::instance().rowCount(); row < end; ++row) + copied.append(LoggingEntryModel::instance().dataAt(row).outputLine(useTS, useLL)); - QGuiApplication::clipboard()->setText(copied); + Utils::setClipboardAndSelection(copied); }); m.exec(m_logView->mapToGlobal(pos)); } void LoggingViewManagerWidget::showLogCategoryContextMenu(const QPoint &pos) const { + QModelIndex idx = m_categoryView->indexAt(pos); + QMenu m; + auto uncheckAll = new QAction(Tr::tr("Uncheck All"), &m); + + int column = 0; + + if (idx.isValid()) { + column = idx.column(); + if (column >= LoggingCategoryModel::Column::Debug + && column <= LoggingCategoryModel::Column::Info) { + bool isChecked = idx.data(Qt::CheckStateRole).toBool(); + + QString text = isChecked ? Tr::tr("Uncheck All %1") : Tr::tr("Check All %1"); + + uncheckAll->setText(text.arg(messageTypeToString( + static_cast(column - LoggingCategoryModel::Column::Debug)))); + + connect(uncheckAll, &QAction::triggered, m_sortFilterModel, [this, idx, isChecked] { + for (int i = 0; i < m_sortFilterModel->rowCount(); ++i) { + m_sortFilterModel->setData(m_sortFilterModel->index(i, idx.column()), + !isChecked, + Qt::CheckStateRole); + } + }); + } else { + connect(uncheckAll, &QAction::triggered, m_sortFilterModel, [this] { + for (int i = 0; i < m_sortFilterModel->rowCount(); ++i) { + for (int c = LoggingCategoryModel::Column::Debug; + c <= LoggingCategoryModel::Column::Info; + ++c) { + m_sortFilterModel->setData(m_sortFilterModel->index(i, c), + false, + Qt::CheckStateRole); + } + } + }); + } + } + // minimal load/save - plugins could later provide presets on their own? auto savePreset = new QAction(Tr::tr("Save Enabled as Preset..."), &m); m.addAction(savePreset); auto loadPreset = new QAction(Tr::tr("Update from Preset..."), &m); m.addAction(loadPreset); - auto uncheckAll = new QAction(Tr::tr("Uncheck All"), &m); m.addAction(uncheckAll); - connect(savePreset, &QAction::triggered, - this, &LoggingViewManagerWidget::saveEnabledCategoryPreset); - connect(loadPreset, &QAction::triggered, - this, &LoggingViewManagerWidget::loadAndUpdateFromPreset); - connect(uncheckAll, &QAction::triggered, - m_categoryModel, &LoggingCategoryModel::disableAll); + connect(savePreset, + &QAction::triggered, + m_categoryModel, + &LoggingCategoryModel::saveEnabledCategoryPreset); + connect(loadPreset, + &QAction::triggered, + m_categoryModel, + &LoggingCategoryModel::loadAndUpdateFromPreset); m.exec(m_categoryView->mapToGlobal(pos)); } void LoggingViewManagerWidget::saveLoggingsToFile() const { - // should we just let it continue without temporarily disabling? - const bool enabled = m_manager->isEnabled(); - const QScopeGuard cleanup([this, enabled] { m_manager->setEnabled(enabled); }); - if (enabled) - m_manager->setEnabled(false); const Utils::FilePath fp = Utils::FileUtils::getSaveFilePath(ICore::dialogParent(), - Tr::tr("Save Logs As")); + Tr::tr("Save Logs As"), + {}, + "*.log"); if (fp.isEmpty()) return; + const bool useTS = m_timestamps->isChecked(); const bool useLL = m_messageTypes->isChecked(); QFile file(fp.path()); if (file.open(QIODevice::WriteOnly)) { - for (int row = 0, end = m_logModel->rowCount(); row < end; ++row) { - qint64 res = file.write( m_logModel->dataAt(row).outputLine(useTS, useLL).toUtf8()); + for (int row = 0, end = LoggingEntryModel::instance().rowCount(); row < end; ++row) { + qint64 res = file.write( + LoggingEntryModel::instance().dataAt(row).outputLine(useTS, useLL).toUtf8()); if (res == -1) { - QMessageBox::critical( - ICore::dialogParent(), Tr::tr("Error"), - Tr::tr("Failed to write logs to \"%1\".").arg(fp.toUserOutput())); + QMessageBox::critical(ICore::dialogParent(), + Tr::tr("Error"), + Tr::tr("Failed to write logs to \"%1\".") + .arg(fp.toUserOutput())); break; } } file.close(); } else { - QMessageBox::critical( - ICore::dialogParent(), Tr::tr("Error"), - Tr::tr("Failed to open file \"%1\" for writing logs.").arg(fp.toUserOutput())); + QMessageBox::critical(ICore::dialogParent(), + Tr::tr("Error"), + Tr::tr("Failed to open file \"%1\" for writing logs.") + .arg(fp.toUserOutput())); } } -void LoggingViewManagerWidget::saveEnabledCategoryPreset() const +void LoggingCategoryModel::saveEnabledCategoryPreset() const { Utils::FilePath fp = Utils::FileUtils::getSaveFilePath(ICore::dialogParent(), - Tr::tr("Save Enabled Categories As...")); + Tr::tr("Save Enabled Categories As..."), + {}, + "*.json"); if (fp.isEmpty()) return; - const QList enabled = m_categoryModel->enabledCategories(); - // write them to file + + auto minLevel = [](const LoggingCategoryEntry &logCategory) { + for (int i = QtDebugMsg; i <= QtInfoMsg; i++) { + if (logCategory.isEnabled(static_cast(i))) + return i; + } + return QtInfoMsg + 1; + }; + QJsonArray array; - for (const LoggingCategoryItem &item : enabled) { + + for (auto item : m_categories) { QJsonObject itemObj; - itemObj.insert("name", item.name); + itemObj.insert("name", item.name()); QJsonObject entryObj; - entryObj.insert("level", item.entry.level); - if (item.entry.color.isValid()) - entryObj.insert("color", item.entry.color.name(QColor::HexArgb)); + entryObj.insert("level", minLevel(item)); + if (item.color().isValid()) + entryObj.insert("color", item.color().name(QColor::HexArgb)); + + QVariantMap levels = {{"Debug", item.isDebugEnabled()}, + {"Warning", item.isWarningEnabled()}, + {"Critical", item.isCriticalEnabled()}, + {"Info", item.isInfoEnabled()}}; + entryObj.insert("levels", QJsonValue::fromVariant(levels)); + itemObj.insert("entry", entryObj); array.append(itemObj); } QJsonDocument doc(array); if (!fp.writeFileContents(doc.toJson(QJsonDocument::Compact))) - QMessageBox::critical( - ICore::dialogParent(), Tr::tr("Error"), - Tr::tr("Failed to write preset file \"%1\".").arg(fp.toUserOutput())); + QMessageBox::critical(ICore::dialogParent(), + Tr::tr("Error"), + Tr::tr("Failed to write preset file \"%1\".").arg(fp.toUserOutput())); } -void LoggingViewManagerWidget::loadAndUpdateFromPreset() +void LoggingCategoryModel::loadAndUpdateFromPreset() { Utils::FilePath fp = Utils::FileUtils::getOpenFilePath(ICore::dialogParent(), Tr::tr("Load Enabled Categories From")); @@ -663,13 +949,15 @@ void LoggingViewManagerWidget::loadAndUpdateFromPreset() QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(*contents, &error); if (error.error != QJsonParseError::NoError) { - QMessageBox::critical(ICore::dialogParent(), Tr::tr("Error"), - Tr::tr("Failed to read preset file \"%1\": %2").arg(fp.toUserOutput()) - .arg(error.errorString())); + QMessageBox::critical(ICore::dialogParent(), + Tr::tr("Error"), + Tr::tr("Failed to read preset file \"%1\": %2") + .arg(fp.toUserOutput()) + .arg(error.errorString())); return; } bool formatError = false; - QList presetItems; + QList presetItems; if (doc.isArray()) { const QJsonArray array = doc.array(); for (const QJsonValue &value : array) { @@ -678,57 +966,81 @@ void LoggingViewManagerWidget::loadAndUpdateFromPreset() break; } const QJsonObject itemObj = value.toObject(); - bool ok = true; - LoggingCategoryItem item = LoggingCategoryItem::fromJson(itemObj, &ok); - if (!ok) { + Utils::expected_str item = SavedEntry::fromJson(itemObj); + if (!item) { formatError = true; break; } - presetItems.append(item); + presetItems.append(*item); } } else { formatError = true; } if (formatError) { - QMessageBox::critical(ICore::dialogParent(), Tr::tr("Error"), + QMessageBox::critical(ICore::dialogParent(), + Tr::tr("Error"), Tr::tr("Unexpected preset file format.")); } - for (const LoggingCategoryItem &item : presetItems) - m_manager->appendOrUpdate(item.name, item.entry); + + int idx = 0; + + for (auto it = presetItems.begin(); it != presetItems.end(); ++it, ++idx) { + QList::iterator itExisting + = std::find_if(m_categories.begin(), + m_categories.end(), + [e = *it](const LoggingCategoryEntry &cat) { + return cat.name() == e.name; + }); + + if (it->color.isValid()) + setCategoryColor(it->name, it->color); + + if (itExisting != m_categories.end()) { + itExisting->setSaved(*it); + emit dataChanged(createIndex(idx, Column::Color), createIndex(idx, Column::Info)); + } else { + LoggingCategoryEntry newEntry(*it); + append(newEntry); + } + } } -QColor LoggingViewManagerWidget::colorForCategory(const QString &category) +QColor colorForCategory(const QString &category) { - auto entry = m_categoryColor.find(category); - if (entry == m_categoryColor.end()) + auto entry = s_categoryColor.find(category); + if (entry == s_categoryColor.end()) return Utils::creatorTheme()->palette().text().color(); return entry.value(); } -void LoggingViewManagerWidget::setCategoryColor(const QString &category, const QColor &color) +void setCategoryColor(const QString &category, const QColor &color) { const QColor baseColor = Utils::creatorTheme()->palette().text().color(); if (color != baseColor) - m_categoryColor.insert(category, color); + s_categoryColor.insert(category, color); else - m_categoryColor.remove(category); + s_categoryColor.remove(category); } void LoggingViewer::showLoggingView() { - ActionManager::command(Constants::LOGGER)->action()->setEnabled(false); - auto widget = new LoggingViewManagerWidget(ICore::dialogParent()); - QObject::connect(widget, &QDialog::finished, widget, [widget] { - ActionManager::command(Constants::LOGGER)->action()->setEnabled(true); - // explicitly disable manager again - widget->deleteLater(); - }); - ICore::registerWindow(widget, Context("Qtc.LogViewer")); - widget->show(); + LoggingViewManagerWidget *staticLogWidget = LoggingViewManagerWidget::instance(); + QTC_ASSERT(staticLogWidget, return); + + staticLogWidget->show(); + staticLogWidget->raise(); + staticLogWidget->activateWindow(); } -} // namespace Internal -} // namespace Core +void LoggingViewer::hideLoggingView() +{ + LoggingViewManagerWidget *staticLogWidget = LoggingViewManagerWidget::instance(); + QTC_ASSERT(staticLogWidget, return); + staticLogWidget->close(); + delete staticLogWidget; +} + +} // namespace Core::Internal #include "loggingviewer.moc" diff --git a/src/plugins/coreplugin/loggingviewer.h b/src/plugins/coreplugin/loggingviewer.h index cb8bf031ca4..4cde2a6501f 100644 --- a/src/plugins/coreplugin/loggingviewer.h +++ b/src/plugins/coreplugin/loggingviewer.h @@ -3,14 +3,13 @@ #pragma once -namespace Core { -namespace Internal { +namespace Core::Internal { class LoggingViewer { public: static void showLoggingView(); + static void hideLoggingView(); }; -} // Internal -} // Core +} // namespace Core::Internal