diff --git a/CMakeLists.txt b/CMakeLists.txt index d7c9e6fd..17589f9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,16 +308,23 @@ SET(MULTIMC_SOURCES gui/dialogs/NotificationDialog.cpp # GUI - widgets - gui/widgets/Common.h gui/widgets/Common.cpp - gui/widgets/ModListView.h - gui/widgets/ModListView.cpp - gui/widgets/VersionListView.h - gui/widgets/VersionListView.cpp - gui/widgets/LabeledToolButton.h + gui/widgets/Common.h + gui/widgets/IconLabel.cpp + gui/widgets/IconLabel.h gui/widgets/LabeledToolButton.cpp - gui/widgets/MCModInfoFrame.h + gui/widgets/LabeledToolButton.h + gui/widgets/LineSeparator.cpp + gui/widgets/LineSeparator.h gui/widgets/MCModInfoFrame.cpp + gui/widgets/MCModInfoFrame.h + gui/widgets/ModListView.cpp + gui/widgets/ModListView.h + gui/widgets/ServerStatus.cpp + gui/widgets/ServerStatus.h + gui/widgets/VersionListView.cpp + gui/widgets/VersionListView.h + # GUI - instance group view gui/groupview/Group.cpp diff --git a/gui/MainWindow.h b/gui/MainWindow.h index ad160da7..2c421b88 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -200,7 +200,7 @@ private: Task *m_versionLoadTask; QLabel *m_statusLeft; - QLabel *m_statusRight; + class ServerStatus *m_statusRight; QMenu *accountMenu; QToolButton *accountMenuButton; diff --git a/gui/widgets/IconLabel.cpp b/gui/widgets/IconLabel.cpp new file mode 100644 index 00000000..1bfe8dc9 --- /dev/null +++ b/gui/widgets/IconLabel.cpp @@ -0,0 +1,30 @@ +#include "IconLabel.h" + +#include +#include +#include +#include +#include + +IconLabel::IconLabel(QWidget *parent, QIcon icon, QSize size) + : QWidget(parent), m_icon(icon), m_size(size) +{ + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); +} + +QSize IconLabel::sizeHint() const +{ + return m_size; +} + +void IconLabel::setIcon(QIcon icon) +{ + m_icon = icon; + update(); +} + +void IconLabel::paintEvent(QPaintEvent *) +{ + QPainter p(this); + m_icon.paint(&p, contentsRect()); +} diff --git a/gui/widgets/IconLabel.h b/gui/widgets/IconLabel.h new file mode 100644 index 00000000..a2f1eef3 --- /dev/null +++ b/gui/widgets/IconLabel.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +class QStyleOption; + +/** + * This is a trivial widget that paints a QIcon of the specified size. + */ +class IconLabel : public QWidget +{ + Q_OBJECT + +public: + /// Create a line separator. orientation is the orientation of the line. + explicit IconLabel(QWidget *parent, QIcon icon, QSize size); + + virtual QSize sizeHint() const; + virtual void paintEvent(QPaintEvent *); + + void setIcon(QIcon icon); + +private: + QSize m_size; + QIcon m_icon; +}; diff --git a/gui/widgets/LineSeparator.cpp b/gui/widgets/LineSeparator.cpp new file mode 100644 index 00000000..f4ee173d --- /dev/null +++ b/gui/widgets/LineSeparator.cpp @@ -0,0 +1,37 @@ +#include "LineSeparator.h" + +#include +#include +#include +#include + +void LineSeparator::initStyleOption(QStyleOption *option) const +{ + option->initFrom(this); + // in a horizontal layout, the line is vertical (and vice versa) + if (m_orientation == Qt::Vertical) + option->state |= QStyle::State_Horizontal; +} + +LineSeparator::LineSeparator(QWidget *parent, Qt::Orientation orientation) + : QWidget(parent), m_orientation(orientation) +{ + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); +} + +QSize LineSeparator::sizeHint() const +{ + QStyleOption opt; + initStyleOption(&opt); + const int extent = + style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget()); + return QSize(extent, extent); +} + +void LineSeparator::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QStyleOption opt; + initStyleOption(&opt); + style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget()); +} diff --git a/gui/widgets/LineSeparator.h b/gui/widgets/LineSeparator.h new file mode 100644 index 00000000..376f2056 --- /dev/null +++ b/gui/widgets/LineSeparator.h @@ -0,0 +1,18 @@ +#pragma once +#include + +class QStyleOption; + +class LineSeparator : public QWidget +{ + Q_OBJECT + +public: + /// Create a line separator. orientation is the orientation of the line. + explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Vertical); + QSize sizeHint() const; + void paintEvent(QPaintEvent *); + void initStyleOption(QStyleOption *option) const; +private: + Qt::Orientation m_orientation = Qt::Vertical; +}; diff --git a/gui/widgets/ServerStatus.cpp b/gui/widgets/ServerStatus.cpp index 1b1bb6f7..e540a301 100644 --- a/gui/widgets/ServerStatus.cpp +++ b/gui/widgets/ServerStatus.cpp @@ -1,4 +1,6 @@ #include "ServerStatus.h" +#include "LineSeparator.h" +#include "IconLabel.h" #include "logic/status/StatusChecker.h" #include "MultiMC.h" @@ -8,118 +10,106 @@ #include #include #include +#include -ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) - :QWidget(parent, f) +ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { - clear(); - goodIcon = QPixmap(":/icons/multimc/48x48/status-good.png"); - goodIcon.setDevicePixelRatio(2.0); - badIcon = QPixmap(":/icons/multimc/48x48/status-bad.png"); - badIcon.setDevicePixelRatio(2.0); - addStatus(tr("No status available"), false); + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + goodIcon = QIcon::fromTheme("status-good"); + badIcon = QIcon::fromTheme("status-bad"); + + addStatus("minecraft.net", tr("Web")); + addLine(); + addStatus("account.mojang.com", tr("Account")); + addLine(); + addStatus("skins.minecraft.net", tr("Skins")); + addLine(); + addStatus("authserver.mojang.com", tr("Auth")); + addLine(); + addStatus("sessionserver.mojang.com", tr("Session")); + m_statusRefresh = new QToolButton(this); m_statusRefresh->setCheckable(true); m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly); m_statusRefresh->setIcon(QIcon::fromTheme("refresh")); + layout->addWidget(m_statusRefresh); + + setLayout(layout); + // Start status checker { - connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this, - &ServerStatus::updateStatusUI); - connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this, - &ServerStatus::updateStatusFailedUI); - + auto reloader = MMC->statusChecker().get(); + connect(reloader, &StatusChecker::statusChanged, this, &ServerStatus::StatusChanged); + connect(reloader, &StatusChecker::statusLoading, this, &ServerStatus::StatusReloading); connect(m_statusRefresh, &QAbstractButton::clicked, this, &ServerStatus::reloadStatus); - connect(&statusTimer, &QTimer::timeout, this, &ServerStatus::reloadStatus); - statusTimer.setSingleShot(true); - + MMC->statusChecker()->startTimer(60000); reloadStatus(); } - } -ServerStatus::addLine() +ServerStatus::~ServerStatus() { - auto line = new QFrame(this); - line->setFrameShape(QFrame::VLine); - line->setFrameShadow(QFrame::Sunken); - layout->addWidget(line); -} - -ServerStatus::addStatus(QString name, bool online) -{ - auto label = new QLabel(this); - label->setText(name); - if(online) - label->setPixmap(goodIcon); - else - label->setPixmap(badIcon); - layout->addWidget(label); -} - -ServerStatus::clear() -{ - if(layout) - delete layout; - layout = new QHBoxLayout(this); -} - -void ServerStatus::StatusChanged(QMap statusEntries) -{ - clear(); - int howmany = statusEntries.size(); - int index = 0; - auto iter = statusEntries.begin(); - while (iter != statusEntries.end()) - { - addStatus(); - index++; - } -} - -static QString convertStatus(const QString &status) -{ - QString ret = "?"; - - if (status == "green") - ret = "↑"; - else if (status == "yellow") - ret = "-"; - else if (status == "red") - ret = "↓"; - - return "" + ret + ""; } void ServerStatus::reloadStatus() { - m_statusRefresh->setChecked(true); MMC->statusChecker()->reloadStatus(); - // updateStatusUI(); } -static QString makeStatusString(const QMap statuses) +void ServerStatus::addLine() { - QString status = ""; - status += "Web: " + convertStatus(statuses["minecraft.net"]); - status += " Account: " + convertStatus(statuses["account.mojang.com"]); - status += " Skins: " + convertStatus(statuses["skins.minecraft.net"]); - status += " Auth: " + convertStatus(statuses["authserver.mojang.com"]); - status += " Session: " + convertStatus(statuses["sessionserver.mojang.com"]); - - return status; + layout->addWidget(new LineSeparator(this)); } -void ServerStatus::updateStatusUI() +void ServerStatus::addStatus(QString key, QString name) { - m_statusRefresh->setChecked(false); - MMC->statusChecker()->getStatusEntries(); - statusTimer.start(60 * 1000); + { + auto label = new IconLabel(this, badIcon, QSize(16, 16)); + label->setToolTip(key); + serverLabels[key] = label; + layout->addWidget(label); + } + { + auto label = new QLabel(this); + label->setText(name); + label->setToolTip(key); + layout->addWidget(label); + } } -void ServerStatus::updateStatusFailedUI() +void ServerStatus::setStatus(QString key, bool value) { - m_statusRefresh->setChecked(false); - StatusChanged(); - statusTimer.start(60 * 1000); + if (!serverLabels.contains(key)) + return; + IconLabel *label = serverLabels[key]; + label->setIcon(value ? goodIcon : badIcon); +} + +void ServerStatus::StatusChanged(const QMap statusEntries) +{ + auto convertStatus = [&](QString status)->bool + { + if (status == "green") + return true; + else if (status == "yellow") + return false; + else if (status == "red") + return false; + return false; + } + ; + auto iter = statusEntries.begin(); + while (iter != statusEntries.end()) + { + QString key = iter.key(); + bool value = convertStatus(iter.value()); + setStatus(key, value); + iter++; + } +} + +void ServerStatus::StatusReloading(bool is_reloading) +{ + m_statusRefresh->setChecked(is_reloading); } diff --git a/gui/widgets/ServerStatus.h b/gui/widgets/ServerStatus.h index a116d3d4..2244031b 100644 --- a/gui/widgets/ServerStatus.h +++ b/gui/widgets/ServerStatus.h @@ -1,7 +1,11 @@ #pragma once +#include #include +#include +#include #include +class IconLabel; class QToolButton; class QHBoxLayout; @@ -10,23 +14,21 @@ class ServerStatus: public QWidget Q_OBJECT public: explicit ServerStatus(QWidget *parent = nullptr, Qt::WindowFlags f = 0); - virtual ~ServerStatus() {}; + virtual ~ServerStatus(); + ; public slots: - void updateStatusUI(); - - void updateStatusFailedUI(); - void reloadStatus(); - void StatusChanged(); + void StatusChanged(const QMap statuses); + void StatusReloading(bool is_reloading); private: /* methods */ - clear(); - addLine(); - addStatus(QString name, bool online); + void addLine(); + void addStatus(QString key, QString name); + void setStatus(QString key, bool value); private: /* data */ QHBoxLayout * layout = nullptr; QToolButton *m_statusRefresh = nullptr; - QPixmap goodIcon; - QPixmap badIcon; - QTimer statusTimer; + QMap serverLabels; + QIcon goodIcon; + QIcon badIcon; }; diff --git a/logic/status/StatusChecker.cpp b/logic/status/StatusChecker.cpp index 66f800ae..17053837 100644 --- a/logic/status/StatusChecker.cpp +++ b/logic/status/StatusChecker.cpp @@ -27,6 +27,12 @@ StatusChecker::StatusChecker() } +void StatusChecker::timerEvent(QTimerEvent *e) +{ + QObject::timerEvent(e); + reloadStatus(); +} + void StatusChecker::reloadStatus() { if (isLoadingStatus()) @@ -42,13 +48,14 @@ void StatusChecker::reloadStatus() QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished); QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed); m_statusNetJob.reset(job); + emit statusLoading(true); job->start(); } void StatusChecker::statusDownloadFinished() { QLOG_DEBUG() << "Finished loading status JSON."; - + m_statusEntries.clear(); QByteArray data; { ByteArrayDownloadPtr dl = std::dynamic_pointer_cast(m_statusNetJob->first()); @@ -121,17 +128,27 @@ QString StatusChecker::getLastLoadErrorMsg() const void StatusChecker::succeed() { + if(m_prevEntries != m_statusEntries) + { + emit statusChanged(m_statusEntries); + m_prevEntries = m_statusEntries; + } m_lastLoadError = ""; QLOG_DEBUG() << "Status loading succeeded."; m_statusNetJob.reset(); - emit statusLoaded(); + emit statusLoading(false); } void StatusChecker::fail(const QString& errorMsg) { + if(m_prevEntries != m_statusEntries) + { + emit statusChanged(m_statusEntries); + m_prevEntries = m_statusEntries; + } m_lastLoadError = errorMsg; QLOG_DEBUG() << "Failed to load status:" << errorMsg; m_statusNetJob.reset(); - emit statusLoadingFailed(errorMsg); + emit statusLoading(false); } diff --git a/logic/status/StatusChecker.h b/logic/status/StatusChecker.h index 1cb01836..df0dd06d 100644 --- a/logic/status/StatusChecker.h +++ b/logic/status/StatusChecker.h @@ -29,26 +29,27 @@ public: QString getLastLoadErrorMsg() const; - bool isStatusLoaded() const; - bool isLoadingStatus() const; QMap getStatusEntries() const; void Q_SLOT reloadStatus(); +protected: + virtual void timerEvent(QTimerEvent *); + signals: - void statusLoaded(); - void statusLoadingFailed(QString errorMsg); + void statusLoading(bool loading); + void statusChanged(QMap newStatus); protected slots: void statusDownloadFinished(); void statusDownloadFailed(); protected: + QMap m_prevEntries; QMap m_statusEntries; NetJobPtr m_statusNetJob; - bool m_loadedStatus; QString m_lastLoadError; void Q_SLOT succeed();