From fd89717330b826494ae37b48ddcdbada11c0b9a7 Mon Sep 17 00:00:00 2001 From: jagannatharjun Date: Wed, 15 Apr 2020 22:18:00 +0530 Subject: [PATCH] Convert the Log widget to use custom View/Model Co-authored-by: sledgehammer999 --- src/app/application.cpp | 1 + src/base/logger.cpp | 4 +- src/gui/CMakeLists.txt | 8 +- src/gui/executionlogwidget.cpp | 87 ++++---- src/gui/executionlogwidget.h | 22 +- src/gui/gui.pri | 8 +- src/gui/log/logfiltermodel.cpp | 52 +++++ src/gui/log/logfiltermodel.h | 48 +++++ src/gui/log/loglistview.cpp | 140 +++++++++++++ .../{loglistwidget.h => log/loglistview.h} | 31 +-- src/gui/log/logmodel.cpp | 188 ++++++++++++++++++ src/gui/log/logmodel.h | 104 ++++++++++ src/gui/loglistwidget.cpp | 108 ---------- src/gui/mainwindow.cpp | 4 +- 14 files changed, 610 insertions(+), 195 deletions(-) create mode 100644 src/gui/log/logfiltermodel.cpp create mode 100644 src/gui/log/logfiltermodel.h create mode 100644 src/gui/log/loglistview.cpp rename src/gui/{loglistwidget.h => log/loglistview.h} (70%) create mode 100644 src/gui/log/logmodel.cpp create mode 100644 src/gui/log/logmodel.h delete mode 100644 src/gui/loglistwidget.cpp diff --git a/src/app/application.cpp b/src/app/application.cpp index db99b3e5a..59792bf06 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -133,6 +133,7 @@ Application::Application(int &argc, char **argv) , m_commandLineArgs(parseCommandLine(this->arguments())) { qRegisterMetaType("Log::Msg"); + qRegisterMetaType("Log::Peer"); setApplicationName("qBittorrent"); setOrganizationDomain("qbittorrent.org"); diff --git a/src/base/logger.cpp b/src/base/logger.cpp index a42907d2a..795d4af61 100644 --- a/src/base/logger.cpp +++ b/src/base/logger.cpp @@ -72,7 +72,7 @@ void Logger::freeInstance() void Logger::addMessage(const QString &message, const Log::MsgType &type) { QWriteLocker locker(&m_lock); - const Log::Msg msg = {m_msgCounter++, QDateTime::currentMSecsSinceEpoch(), type, message.toHtmlEscaped()}; + const Log::Msg msg = {m_msgCounter++, QDateTime::currentMSecsSinceEpoch(), type, message}; m_messages.push_back(msg); locker.unlock(); @@ -82,7 +82,7 @@ void Logger::addMessage(const QString &message, const Log::MsgType &type) void Logger::addPeer(const QString &ip, const bool blocked, const QString &reason) { QWriteLocker locker(&m_lock); - const Log::Peer msg = {m_peerCounter++, QDateTime::currentMSecsSinceEpoch(), ip.toHtmlEscaped(), blocked, reason.toHtmlEscaped()}; + const Log::Peer msg = {m_peerCounter++, QDateTime::currentMSecsSinceEpoch(), ip, blocked, reason}; m_peers.push_back(msg); locker.unlock(); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 50d11d796..ce8c45616 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -28,7 +28,9 @@ fspathedit.h hidabletabwidget.h ipsubnetwhitelistoptionsdialog.h lineedit.h -loglistwidget.h +log/logfiltermodel.h +log/loglistview.h +log/logmodel.h mainwindow.h optionsdialog.h previewlistdelegate.h @@ -81,7 +83,9 @@ fspathedit.cpp hidabletabwidget.cpp ipsubnetwhitelistoptionsdialog.cpp lineedit.cpp -loglistwidget.cpp +log/logfiltermodel.cpp +log/loglistview.cpp +log/logmodel.cpp mainwindow.cpp optionsdialog.cpp previewlistdelegate.cpp diff --git a/src/gui/executionlogwidget.cpp b/src/gui/executionlogwidget.cpp index 64f893859..2aa3e9cd5 100644 --- a/src/gui/executionlogwidget.cpp +++ b/src/gui/executionlogwidget.cpp @@ -29,35 +29,48 @@ #include "executionlogwidget.h" #include +#include #include -#include "base/global.h" -#include "loglistwidget.h" +#include "log/logfiltermodel.h" +#include "log/loglistview.h" +#include "log/logmodel.h" #include "ui_executionlogwidget.h" #include "uithememanager.h" -ExecutionLogWidget::ExecutionLogWidget(QWidget *parent, const Log::MsgTypes &types) +ExecutionLogWidget::ExecutionLogWidget(const Log::MsgTypes types, QWidget *parent) : QWidget(parent) , m_ui(new Ui::ExecutionLogWidget) - , m_msgList(new LogListWidget(MAX_LOG_MESSAGES, types, this)) - , m_peerList(new LogListWidget(MAX_LOG_MESSAGES, Log::ALL, this)) + , m_messageFilterModel(new LogFilterModel(types, this)) { m_ui->setupUi(this); + LogMessageModel *messageModel = new LogMessageModel(this); + m_messageFilterModel->setSourceModel(messageModel); + LogListView *messageView = new LogListView(this); + messageView->setModel(m_messageFilterModel); + messageView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(messageView, &LogListView::customContextMenuRequested, this, [this, messageView, messageModel](const QPoint &pos) + { + displayContextMenu(pos, messageView, messageModel); + }); + + LogPeerModel *peerModel = new LogPeerModel(this); + LogListView *peerView = new LogListView(this); + peerView->setModel(peerModel); + peerView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(peerView, &LogListView::customContextMenuRequested, this, [this, peerView, peerModel](const QPoint &pos) + { + displayContextMenu(pos, peerView, peerModel); + }); + + m_ui->tabGeneral->layout()->addWidget(messageView); + m_ui->tabBan->layout()->addWidget(peerView); + #ifndef Q_OS_MACOS m_ui->tabConsole->setTabIcon(0, UIThemeManager::instance()->getIcon("view-calendar-journal")); m_ui->tabConsole->setTabIcon(1, UIThemeManager::instance()->getIcon("view-filter")); #endif - m_ui->tabGeneral->layout()->addWidget(m_msgList); - m_ui->tabBan->layout()->addWidget(m_peerList); - - const Logger *const logger = Logger::instance(); - for (const Log::Msg &msg : asConst(logger->getMessages())) - addLogMessage(msg); - for (const Log::Peer &peer : asConst(logger->getPeers())) - addPeerMessage(peer); - connect(logger, &Logger::newLogMessage, this, &ExecutionLogWidget::addLogMessage); - connect(logger, &Logger::newLogPeer, this, &ExecutionLogWidget::addPeerMessage); } ExecutionLogWidget::~ExecutionLogWidget() @@ -65,42 +78,24 @@ ExecutionLogWidget::~ExecutionLogWidget() delete m_ui; } -void ExecutionLogWidget::showMsgTypes(const Log::MsgTypes &types) +void ExecutionLogWidget::setMessageTypes(const Log::MsgTypes types) { - m_msgList->showMsgTypes(types); + m_messageFilterModel->setMessageTypes(types); } -void ExecutionLogWidget::addLogMessage(const Log::Msg &msg) +void ExecutionLogWidget::displayContextMenu(const QPoint &pos, const LogListView *view, const BaseLogModel *model) const { - QString colorName; - switch (msg.type) { - case Log::INFO: - colorName = QLatin1String("blue"); - break; - case Log::WARNING: - colorName = QLatin1String("orange"); - break; - case Log::CRITICAL: - colorName = QLatin1String("red"); - break; - default: - colorName = QApplication::palette().color(QPalette::WindowText).name(); + QMenu *menu = new QMenu; + menu->setAttribute(Qt::WA_DeleteOnClose); + + // only show copy action if any of the row is selected + if (view->currentIndex().isValid()) { + const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")); + connect(copyAct, &QAction::triggered, view, &LogListView::copySelection); } - const QDateTime time = QDateTime::fromMSecsSinceEpoch(msg.timestamp); - const QString text = QString::fromLatin1("%1 - %3") - .arg(time.toString(Qt::SystemLocaleShortDate), colorName, msg.message); - m_msgList->appendLine(text, msg.type); -} + const QAction *clearAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear")); + connect(clearAct, &QAction::triggered, model, &BaseLogModel::reset); -void ExecutionLogWidget::addPeerMessage(const Log::Peer &peer) -{ - const QDateTime time = QDateTime::fromMSecsSinceEpoch(peer.timestamp); - const QString msg = QString::fromLatin1("%1 - %2") - .arg(time.toString(Qt::SystemLocaleShortDate), peer.ip); - - const QString text = peer.blocked - ? tr("%1 was blocked %2", "0.0.0.0 was blocked due to reason").arg(msg, peer.reason) - : tr("%1 was banned", "0.0.0.0 was banned").arg(msg); - m_peerList->appendLine(text, Log::NORMAL); + menu->popup(view->mapToGlobal(pos)); } diff --git a/src/gui/executionlogwidget.h b/src/gui/executionlogwidget.h index 8e5953ca8..d6f6ad674 100644 --- a/src/gui/executionlogwidget.h +++ b/src/gui/executionlogwidget.h @@ -30,33 +30,33 @@ #define EXECUTIONLOGWIDGET_H #include -#include "base/logger.h" -class LogListWidget; +#include "base/logger.h" namespace Ui { class ExecutionLogWidget; } +class BaseLogModel; +class LogFilterModel; +class LogListView; + class ExecutionLogWidget : public QWidget { Q_OBJECT public: - ExecutionLogWidget(QWidget *parent, const Log::MsgTypes &types); - void showMsgTypes(const Log::MsgTypes &types); + ExecutionLogWidget(Log::MsgTypes types, QWidget *parent); ~ExecutionLogWidget(); - -private slots: - void addLogMessage(const Log::Msg &msg); - void addPeerMessage(const Log::Peer &peer); + + void setMessageTypes(Log::MsgTypes types); private: - Ui::ExecutionLogWidget *m_ui; + void displayContextMenu(const QPoint &pos, const LogListView *view, const BaseLogModel *model) const; - LogListWidget *m_msgList; - LogListWidget *m_peerList; + Ui::ExecutionLogWidget *m_ui; + LogFilterModel *m_messageFilterModel; }; #endif // EXECUTIONLOGWIDGET_H diff --git a/src/gui/gui.pri b/src/gui/gui.pri index 0dbeb36fb..28ed64806 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -22,7 +22,9 @@ HEADERS += \ $$PWD/hidabletabwidget.h \ $$PWD/ipsubnetwhitelistoptionsdialog.h \ $$PWD/lineedit.h \ - $$PWD/loglistwidget.h \ + $$PWD/log/logfiltermodel.h \ + $$PWD/log/loglistview.h \ + $$PWD/log/logmodel.h \ $$PWD/mainwindow.h \ $$PWD/optionsdialog.h \ $$PWD/previewlistdelegate.h \ @@ -86,7 +88,9 @@ SOURCES += \ $$PWD/hidabletabwidget.cpp \ $$PWD/ipsubnetwhitelistoptionsdialog.cpp \ $$PWD/lineedit.cpp \ - $$PWD/loglistwidget.cpp \ + $$PWD/log/logfiltermodel.cpp \ + $$PWD/log/loglistview.cpp \ + $$PWD/log/logmodel.cpp \ $$PWD/mainwindow.cpp \ $$PWD/optionsdialog.cpp \ $$PWD/previewlistdelegate.cpp \ diff --git a/src/gui/log/logfiltermodel.cpp b/src/gui/log/logfiltermodel.cpp new file mode 100644 index 000000000..033deb2c9 --- /dev/null +++ b/src/gui/log/logfiltermodel.cpp @@ -0,0 +1,52 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 sledgehammer999 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "logfiltermodel.h" + +#include "logmodel.h" + +LogFilterModel::LogFilterModel(const Log::MsgTypes types, QObject *parent) + : QSortFilterProxyModel(parent) + , m_types(types) +{ +} + +void LogFilterModel::setMessageTypes(const Log::MsgTypes types) +{ + m_types = types; + invalidateFilter(); +} + +bool LogFilterModel::filterAcceptsRow(const int sourceRow, const QModelIndex &sourceParent) const +{ + const QAbstractItemModel *const sourceModel = this->sourceModel(); + const QModelIndex index = sourceModel->index(sourceRow, 0, sourceParent); + const Log::MsgType type = static_cast(sourceModel->data(index, BaseLogModel::TypeRole).toInt()); + + return m_types.testFlag(type); +} diff --git a/src/gui/log/logfiltermodel.h b/src/gui/log/logfiltermodel.h new file mode 100644 index 000000000..1b2afc376 --- /dev/null +++ b/src/gui/log/logfiltermodel.h @@ -0,0 +1,48 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 sledgehammer999 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +#include "base/logger.h" + +class LogFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_DISABLE_COPY(LogFilterModel) + +public: + explicit LogFilterModel(Log::MsgTypes types = Log::ALL, QObject *parent = nullptr); + void setMessageTypes(Log::MsgTypes types); + +private: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + Log::MsgTypes m_types; +}; diff --git a/src/gui/log/loglistview.cpp b/src/gui/log/loglistview.cpp new file mode 100644 index 000000000..252986b3a --- /dev/null +++ b/src/gui/log/loglistview.cpp @@ -0,0 +1,140 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2020 Prince Gupta + * Copyright (C) 2019 sledgehammer999 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "loglistview.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "logmodel.h" +#include "uithememanager.h" + +namespace +{ + const QString SEPARATOR = QStringLiteral(" - "); + + int horizontalAdvance(const QFontMetrics &fontMetrics, const QString &text) + { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + return fontMetrics.horizontalAdvance(text); +#else + return fontMetrics.width(text); +#endif + } + + QString logText(const QModelIndex &index) + { + return QString::fromLatin1("%1%2%3").arg(index.data(BaseLogModel::TimeRole).toString(), SEPARATOR + , index.data(BaseLogModel::MessageRole).toString()); + } + + class LogItemDelegate : public QStyledItemDelegate + { + public: + using QStyledItemDelegate::QStyledItemDelegate; + + private: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + painter->save(); + QStyledItemDelegate::paint(painter, option, index); // paints background, focus rect and selection rect + + const QStyle *style = option.widget ? option.widget->style() : QApplication::style();; + const QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget) + .adjusted(1, 0, 0, 0); // shift 1 to avoid text being too close to focus rect + + QFont font = option.font; + if (option.font.pointSize() > 0) + font.setPointSize(option.font.pointSize()); // somehow this needs to be set directly otherwise painter will use default font + painter->setFont(font); + + const QPen originalPen = painter->pen(); + QPen coloredPen = originalPen; + coloredPen.setColor(Qt::darkGray); + painter->setPen(coloredPen); + const QString time = index.data(BaseLogModel::TimeRole).toString(); + style->drawItemText(painter, textRect, option.displayAlignment, option.palette, (option.state & QStyle::State_Enabled), time); + + painter->setPen(originalPen); + const QFontMetrics fontMetrics = painter->fontMetrics(); // option.fontMetrics adds extra padding to QFontMetrics::width + const int separatorCoordinateX = horizontalAdvance(fontMetrics, time); + style->drawItemText(painter, textRect.adjusted(separatorCoordinateX, 0, 0, 0), option.displayAlignment, option.palette + , (option.state & QStyle::State_Enabled), SEPARATOR); + + coloredPen.setColor(index.data(BaseLogModel::ForegroundRole).value()); + painter->setPen(coloredPen); + const int messageCoordinateX = separatorCoordinateX + horizontalAdvance(fontMetrics, SEPARATOR); + style->drawItemText(painter, textRect.adjusted(messageCoordinateX, 0, 0, 0), option.displayAlignment, option.palette + , (option.state & QStyle::State_Enabled), index.data(BaseLogModel::MessageRole).toString()); + painter->restore(); + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + const QSize minimumFontPadding(4, 4); + const QSize fontSize = option.fontMetrics.size(0, logText(index)) + minimumFontPadding; + const QSize defaultSize = QStyledItemDelegate::sizeHint(option, index); + const QSize margins = (defaultSize - fontSize).expandedTo({0, 0}); + return fontSize + margins; + } + }; +} + +LogListView::LogListView(QWidget *parent) + : QListView(parent) +{ + setSelectionMode(QAbstractItemView::ExtendedSelection); + setItemDelegate(new LogItemDelegate(this)); + +#if defined(Q_OS_MAC) + setAttribute(Qt::WA_MacShowFocusRect, false); +#endif +} + +void LogListView::keyPressEvent(QKeyEvent *event) +{ + if (event->matches(QKeySequence::Copy)) + copySelection(); + else + QListView::keyPressEvent(event); +} + +void LogListView::copySelection() const +{ + QStringList list; + const QModelIndexList selectedIndexes = selectionModel()->selectedRows(); + for (const QModelIndex &index : selectedIndexes) + list.append(logText(index)); + QApplication::clipboard()->setText(list.join('\n')); +} diff --git a/src/gui/loglistwidget.h b/src/gui/log/loglistview.h similarity index 70% rename from src/gui/loglistwidget.h rename to src/gui/log/loglistview.h index 6dcb21d26..1faf0df55 100644 --- a/src/gui/loglistwidget.h +++ b/src/gui/log/loglistview.h @@ -1,6 +1,7 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2011 Christophe Dumez + * Copyright (C) 2020 Prince Gupta + * Copyright (C) 2019 sledgehammer999 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,35 +27,21 @@ * exception statement from your version. */ -#ifndef LOGLISTWIDGET_H -#define LOGLISTWIDGET_H +#pragma once -#include -#include "base/logger.h" +#include -class QKeyEvent; - -class LogListWidget : public QListWidget +class LogListView : public QListView { Q_OBJECT + Q_DISABLE_COPY(LogListView) public: - // -1 is the portable way to have all the bits set - explicit LogListWidget(int maxLines, const Log::MsgTypes &types = Log::ALL, QWidget *parent = nullptr); - void showMsgTypes(const Log::MsgTypes &types); + explicit LogListView(QWidget *parent = nullptr); public slots: - void appendLine(const QString &line, const Log::MsgType &type); - -protected slots: - void copySelection(); - -protected: - void keyPressEvent(QKeyEvent *event) override; + void copySelection() const; private: - const int m_maxLines; - Log::MsgTypes m_types; + void keyPressEvent(QKeyEvent *event) override; }; - -#endif // LOGLISTWIDGET_H diff --git a/src/gui/log/logmodel.cpp b/src/gui/log/logmodel.cpp new file mode 100644 index 000000000..e72be6d9e --- /dev/null +++ b/src/gui/log/logmodel.cpp @@ -0,0 +1,188 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2020 Prince Gupta + * Copyright (C) 2019 sledgehammer999 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "logmodel.h" + +#include +#include +#include +#include + +#include "base/global.h" + +namespace +{ + const int MAX_VISIBLE_MESSAGES = 20000; +} + +BaseLogModel::Message::Message(const QString &time, const QString &message, const QColor &foreground, const Log::MsgType type) + : m_time(time) + , m_message(message) + , m_foreground(foreground) + , m_type(type) +{ +} + +QVariant BaseLogModel::Message::time() const +{ + return m_time; +} + +QVariant BaseLogModel::Message::message() const +{ + return m_message; +} + +QVariant BaseLogModel::Message::foreground() const +{ + return m_foreground; +} + +QVariant BaseLogModel::Message::type() const +{ + return m_type; +} + +BaseLogModel::BaseLogModel(QObject *parent) + : QAbstractListModel(parent) + , m_messages(MAX_VISIBLE_MESSAGES) +{ +} + +int BaseLogModel::rowCount(const QModelIndex &) const +{ + return m_messages.size(); +} + +int BaseLogModel::columnCount(const QModelIndex &) const +{ + return 1; +} + +QVariant BaseLogModel::data(const QModelIndex &index, const int role) const +{ + if (!index.isValid()) + return {}; + + const int messageIndex = index.row(); + if ((messageIndex < 0) || (messageIndex >= static_cast(m_messages.size()))) + return {}; + + const Message &message = m_messages[messageIndex]; + switch (role) { + case TimeRole: + return message.time(); + case MessageRole: + return message.message(); + case ForegroundRole: + return message.foreground(); + case TypeRole: + return message.type(); + default: + return {}; + } +} + +void BaseLogModel::addNewMessage(const BaseLogModel::Message &message) +{ + // if row is inserted on filled up buffer, the size will not change + // but because of calling of beginInsertRows function we'll have ghost rows. + if (m_messages.size() == MAX_VISIBLE_MESSAGES) { + const int lastMessage = m_messages.size() - 1; + beginRemoveRows(QModelIndex(), lastMessage, lastMessage); + m_messages.pop_back(); + endRemoveRows(); + } + + beginInsertRows(QModelIndex(), 0, 0); + m_messages.push_front(message); + endInsertRows(); +} + +void BaseLogModel::reset() +{ + beginResetModel(); + m_messages.clear(); + endResetModel(); +} + +LogMessageModel::LogMessageModel(QObject *parent) + : BaseLogModel(parent) +{ + for (const Log::Msg &msg : asConst(Logger::instance()->getMessages())) + handleNewMessage(msg); + connect(Logger::instance(), &Logger::newLogMessage, this, &LogMessageModel::handleNewMessage); +} + +void LogMessageModel::handleNewMessage(const Log::Msg &message) +{ + const QString time = QDateTime::fromMSecsSinceEpoch(message.timestamp).toString(Qt::SystemLocaleShortDate); + const QString messageText = message.message; + + QColor foreground; + switch (message.type) { + // The RGB QColor constructor is used for performance + case Log::NORMAL: + foreground = QApplication::palette().color(QPalette::WindowText); + break; + case Log::INFO: + foreground = QColor(0, 0, 255); // blue + break; + case Log::WARNING: + foreground = QColor(255, 165, 0); // orange + break; + case Log::CRITICAL: + foreground = QColor(255, 0, 0); // red + break; + default: + Q_ASSERT(false); + break; + } + + addNewMessage({time, messageText, foreground, message.type}); +} + +LogPeerModel::LogPeerModel(QObject *parent) + : BaseLogModel(parent) +{ + for (const Log::Peer &peer : asConst(Logger::instance()->getPeers())) + handleNewMessage(peer); + connect(Logger::instance(), &Logger::newLogPeer, this, &LogPeerModel::handleNewMessage); +} + +void LogPeerModel::handleNewMessage(const Log::Peer &peer) +{ + const QString time = QDateTime::fromMSecsSinceEpoch(peer.timestamp).toString(Qt::SystemLocaleShortDate); + const QString message = peer.blocked + ? tr("%1 was blocked due to %2", "0.0.0.0 was blocked due to reason").arg(peer.ip, peer.reason) + : tr("%1 was banned", "0.0.0.0 was banned").arg(peer.ip); + const QColor foreground = Qt::red; + + addNewMessage({time, message, foreground, Log::NORMAL}); +} diff --git a/src/gui/log/logmodel.h b/src/gui/log/logmodel.h new file mode 100644 index 000000000..a13abfdfc --- /dev/null +++ b/src/gui/log/logmodel.h @@ -0,0 +1,104 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2020 Prince Gupta + * Copyright (C) 2019 sledgehammer999 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +#include + +#include "base/logger.h" + +class BaseLogModel : public QAbstractListModel +{ + Q_DISABLE_COPY(BaseLogModel) + +public: + enum MessageTypeRole + { + TimeRole = Qt::UserRole, + MessageRole, + ForegroundRole, + TypeRole + }; + + explicit BaseLogModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = {}) const override; + int columnCount(const QModelIndex &parent = {}) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + void reset(); + +protected: + class Message + { + public: + Message(const QString &time, const QString &message, const QColor &foreground, Log::MsgType type); + + QVariant time() const; + QVariant message() const; + QVariant foreground() const; + QVariant type() const; + + private: + QVariant m_time; + QVariant m_message; + QVariant m_foreground; + QVariant m_type; + }; + + void addNewMessage(const Message &message); + +private: + boost::circular_buffer_space_optimized m_messages; +}; + +class LogMessageModel : public BaseLogModel +{ + Q_OBJECT + Q_DISABLE_COPY(LogMessageModel) + +public: + explicit LogMessageModel(QObject *parent = nullptr); + +private slots: + void handleNewMessage(const Log::Msg &message); +}; + +class LogPeerModel : public BaseLogModel +{ + Q_OBJECT + Q_DISABLE_COPY(LogPeerModel) + +public: + explicit LogPeerModel(QObject *parent = nullptr); + +private slots: + void handleNewMessage(const Log::Peer &peer); +}; diff --git a/src/gui/loglistwidget.cpp b/src/gui/loglistwidget.cpp deleted file mode 100644 index a39da20a7..000000000 --- a/src/gui/loglistwidget.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2011 Christophe Dumez - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - */ - -#include "loglistwidget.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "base/global.h" -#include "uithememanager.h" - -LogListWidget::LogListWidget(const int maxLines, const Log::MsgTypes &types, QWidget *parent) - : QListWidget(parent) - , m_maxLines(maxLines) - , m_types(types) -{ - // Allow multiple selections - setSelectionMode(QAbstractItemView::ExtendedSelection); - // Context menu - auto *copyAct = new QAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"), this); - auto *clearAct = new QAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear"), this); - connect(copyAct, &QAction::triggered, this, &LogListWidget::copySelection); - connect(clearAct, &QAction::triggered, this, &LogListWidget::clear); - addAction(copyAct); - addAction(clearAct); - setContextMenuPolicy(Qt::ActionsContextMenu); -} - -void LogListWidget::showMsgTypes(const Log::MsgTypes &types) -{ - m_types = types; - for (int i = 0; i < count(); ++i) { - QListWidgetItem *tempItem = item(i); - if (!tempItem) continue; - - Log::MsgType itemType = static_cast(tempItem->data(Qt::UserRole).toInt()); - setRowHidden(i, !(m_types & itemType)); - } -} - -void LogListWidget::keyPressEvent(QKeyEvent *event) -{ - if (event->matches(QKeySequence::Copy)) - copySelection(); - else - QListWidget::keyPressEvent(event); -} - -void LogListWidget::appendLine(const QString &line, const Log::MsgType &type) -{ - // We need to use QLabel here to support rich text - auto *lbl = new QLabel(line); - lbl->setTextFormat(Qt::RichText); - lbl->setContentsMargins(4, 2, 4, 2); - - auto *item = new QListWidgetItem; - item->setSizeHint(lbl->sizeHint()); - item->setData(Qt::UserRole, type); - insertItem(0, item); - setItemWidget(item, lbl); - setRowHidden(0, !(m_types & type)); - - const int nbLines = count(); - // Limit log size - if (nbLines > m_maxLines) - delete takeItem(nbLines - 1); -} - -void LogListWidget::copySelection() -{ - const QRegularExpression htmlTag("<[^>]+>"); - - QStringList strings; - for (QListWidgetItem *it : asConst(selectedItems())) - strings << static_cast(itemWidget(it))->text().remove(htmlTag); - - QApplication::clipboard()->setText(strings.join('\n')); -} diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 02c7a0e28..35ba20bc3 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -487,7 +487,7 @@ int MainWindow::executionLogMsgTypes() const void MainWindow::setExecutionLogMsgTypes(const int value) { - m_executionLog->showMsgTypes(static_cast(value)); + m_executionLog->setMessageTypes(static_cast(value)); settings()->storeValue(KEY_EXECUTIONLOG_TYPES, value); } @@ -1866,7 +1866,7 @@ void MainWindow::on_actionExecutionLogs_triggered(bool checked) { if (checked) { Q_ASSERT(!m_executionLog); - m_executionLog = new ExecutionLogWidget(m_tabs, static_cast(executionLogMsgTypes())); + m_executionLog = new ExecutionLogWidget(static_cast(executionLogMsgTypes()), m_tabs); #ifdef Q_OS_MACOS m_tabs->addTab(m_executionLog, tr("Execution Log")); #else