Add update promoter dialog
This commit is contained in:
parent
97b485284e
commit
c04a89396c
6 changed files with 346 additions and 62 deletions
104
forms/updatepromoter.ui
Normal file
104
forms/updatepromoter.ui
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>UpdatePromoter</class>
|
||||
<widget class="QDialog" name="UpdatePromoter">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>592</width>
|
||||
<height>484</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Porymap Version Update</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Status">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Warning">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-size:13pt; color:#d7000c;">WARNING: </span><span style=" font-weight:400;">Updating Porymap may require you to update your projects. See "Breaking Changes" in the Changelog for details.</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame_Changelog">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="text_Changelog">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_StopAlerts">
|
||||
<property name="text">
|
||||
<string>Do not alert me about new updates</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close|QDialogButtonBox::Retry</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -28,6 +28,7 @@
|
|||
#include "preferenceeditor.h"
|
||||
#include "projectsettingseditor.h"
|
||||
#include "customscriptseditor.h"
|
||||
#include "updatepromoter.h"
|
||||
|
||||
|
||||
|
||||
|
@ -298,7 +299,6 @@ private slots:
|
|||
public:
|
||||
Ui::MainWindow *ui;
|
||||
Editor *editor = nullptr;
|
||||
QPointer<QNetworkAccessManager> networkAccessManager = nullptr;
|
||||
|
||||
private:
|
||||
QLabel *label_MapRulerStatus = nullptr;
|
||||
|
@ -310,6 +310,8 @@ private:
|
|||
QPointer<PreferenceEditor> preferenceEditor = nullptr;
|
||||
QPointer<ProjectSettingsEditor> projectSettingsEditor = nullptr;
|
||||
QPointer<CustomScriptsEditor> customScriptsEditor = nullptr;
|
||||
QPointer<UpdatePromoter> updatePromoter = nullptr;
|
||||
QPointer<QNetworkAccessManager> networkAccessManager = nullptr;
|
||||
FilterChildrenProxyModel *mapListProxyModel;
|
||||
QStandardItemModel *mapListModel;
|
||||
QList<QStandardItem*> *mapGroupItemsList;
|
||||
|
|
44
include/ui/updatepromoter.h
Normal file
44
include/ui/updatepromoter.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef UPDATEPROMOTER_H
|
||||
#define UPDATEPROMOTER_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
namespace Ui {
|
||||
class UpdatePromoter;
|
||||
}
|
||||
|
||||
class UpdatePromoter : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UpdatePromoter(QWidget *parent, QNetworkAccessManager *manager);
|
||||
~UpdatePromoter() {};
|
||||
|
||||
void checkForUpdates();
|
||||
void requestDialog();
|
||||
void updatePreferences();
|
||||
|
||||
private:
|
||||
Ui::UpdatePromoter *ui;
|
||||
QNetworkAccessManager *const manager;
|
||||
QNetworkReply * reply = nullptr;
|
||||
QPushButton * button_Downloads;
|
||||
QString downloadLink;
|
||||
QString changelog;
|
||||
|
||||
void resetDialog();
|
||||
void processWebpage(const QJsonDocument &data);
|
||||
void processError(const QString &err);
|
||||
|
||||
private slots:
|
||||
void dialogButtonClicked(QAbstractButton *button);
|
||||
|
||||
signals:
|
||||
void changedPreferences();
|
||||
};
|
||||
|
||||
#endif // UPDATEPROMOTER_H
|
|
@ -110,7 +110,8 @@ SOURCES += src/core/block.cpp \
|
|||
src/project.cpp \
|
||||
src/settings.cpp \
|
||||
src/log.cpp \
|
||||
src/ui/uintspinbox.cpp
|
||||
src/ui/uintspinbox.cpp \
|
||||
src/ui/updatepromoter.cpp
|
||||
|
||||
HEADERS += include/core/block.h \
|
||||
include/core/bitpacker.h \
|
||||
|
@ -204,7 +205,8 @@ HEADERS += include/core/block.h \
|
|||
include/scriptutility.h \
|
||||
include/settings.h \
|
||||
include/log.h \
|
||||
include/ui/uintspinbox.h
|
||||
include/ui/uintspinbox.h \
|
||||
include/ui/updatepromoter.h
|
||||
|
||||
FORMS += forms/mainwindow.ui \
|
||||
forms/prefabcreationdialog.ui \
|
||||
|
@ -222,7 +224,8 @@ FORMS += forms/mainwindow.ui \
|
|||
forms/colorpicker.ui \
|
||||
forms/projectsettingseditor.ui \
|
||||
forms/customscriptseditor.ui \
|
||||
forms/customscriptslistitem.ui
|
||||
forms/customscriptslistitem.ui \
|
||||
forms/updatepromoter.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources/images.qrc \
|
||||
|
|
|
@ -248,9 +248,6 @@ void MainWindow::initExtraSignals() {
|
|||
label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
}
|
||||
|
||||
// TODO: Relocate
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
void MainWindow::on_actionCheck_for_Updates_triggered() {
|
||||
checkForUpdates(true);
|
||||
}
|
||||
|
@ -259,63 +256,17 @@ void MainWindow::checkForUpdates(bool requestedByUser) {
|
|||
if (!this->networkAccessManager)
|
||||
this->networkAccessManager = new QNetworkAccessManager(this);
|
||||
|
||||
// We could get ".../releases/latest" to retrieve less data, but this would run into problems if the
|
||||
// most recent item on the releases page is not actually a new release (like the static windows build).
|
||||
static const QUrl url("https://api.github.com/repos/huderlem/porymap/releases");
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QNetworkReply * reply = this->networkAccessManager->get(request);
|
||||
|
||||
if (requestedByUser) {
|
||||
// TODO: Show dialog
|
||||
if (!this->updatePromoter) {
|
||||
this->updatePromoter = new UpdatePromoter(this, this->networkAccessManager);
|
||||
connect(this->updatePromoter, &UpdatePromoter::changedPreferences, [this] {
|
||||
if (this->preferenceEditor)
|
||||
this->preferenceEditor->updateFields();
|
||||
});
|
||||
}
|
||||
|
||||
connect(reply, &QNetworkReply::finished, [this, reply] {
|
||||
QJsonDocument data = QJsonDocument::fromJson(reply->readAll());
|
||||
QJsonArray releases = data.array();
|
||||
|
||||
// Read all the items on the releases page, stopping when we find a tag that parses as a version identifier.
|
||||
// Objects in the releases page data are sorted newest to oldest. Although I can't find a guarantee of this in
|
||||
// GitHub's API documentation, this seems unlikely to change.
|
||||
for (int i = 0; i < releases.size(); i++) {
|
||||
auto release = releases.at(i).toObject();
|
||||
|
||||
const QStringList tag = release.value("tag_name").toString().split(".");
|
||||
if (tag.length() != 3) continue;
|
||||
|
||||
bool ok;
|
||||
int major = tag.at(0).toInt(&ok);
|
||||
if (!ok) continue;
|
||||
int minor = tag.at(1).toInt(&ok);
|
||||
if (!ok) continue;
|
||||
int patch = tag.at(2).toInt(&ok);
|
||||
if (!ok) continue;
|
||||
|
||||
const QString downloadLink = release.value("html_url").toString();
|
||||
if (downloadLink.isEmpty()) continue;
|
||||
|
||||
// We've found a valid release tag, we can stop reading.
|
||||
logInfo(QString("Newest release is %1.%2.%3\n%4").arg(major).arg(minor).arg(patch).arg(downloadLink));
|
||||
|
||||
// If the release was published very recently it won't have a description yet, in which case don't tell the user.
|
||||
const QString changelog = release.value("body").toString();
|
||||
if (changelog.isEmpty()) break;
|
||||
|
||||
bool newVersionAvailable;
|
||||
if (major != PORYMAP_VERSION_MAJOR) {
|
||||
newVersionAvailable = major > PORYMAP_VERSION_MAJOR;
|
||||
} else if (minor != PORYMAP_VERSION_MINOR) {
|
||||
newVersionAvailable = minor > PORYMAP_VERSION_MINOR;
|
||||
} else {
|
||||
newVersionAvailable = patch > PORYMAP_VERSION_PATCH;
|
||||
}
|
||||
logInfo(QString("Host version is %1").arg(newVersionAvailable ? "old" : "up to date"));
|
||||
|
||||
// TODO: Show appropriate dialog
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (requestedByUser)
|
||||
this->updatePromoter->requestDialog();
|
||||
this->updatePromoter->checkForUpdates();
|
||||
}
|
||||
|
||||
void MainWindow::initEditor() {
|
||||
|
@ -2812,6 +2763,9 @@ void MainWindow::togglePreferenceSpecificUi() {
|
|||
ui->actionOpen_Project_in_Text_Editor->setEnabled(false);
|
||||
else
|
||||
ui->actionOpen_Project_in_Text_Editor->setEnabled(true);
|
||||
|
||||
if (this->updatePromoter)
|
||||
this->updatePromoter->updatePreferences();
|
||||
}
|
||||
|
||||
void MainWindow::openProjectSettingsEditor(int tab) {
|
||||
|
|
177
src/ui/updatepromoter.cpp
Normal file
177
src/ui/updatepromoter.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
#include "updatepromoter.h"
|
||||
#include "ui_updatepromoter.h"
|
||||
#include "log.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QDesktopServices>
|
||||
|
||||
UpdatePromoter::UpdatePromoter(QWidget *parent, QNetworkAccessManager *manager)
|
||||
: QDialog(parent),
|
||||
ui(new Ui::UpdatePromoter),
|
||||
manager(manager)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// Set up "Do not alert me" check box
|
||||
this->updatePreferences();
|
||||
connect(ui->checkBox_StopAlerts, &QCheckBox::stateChanged, [this](int state) {
|
||||
bool enable = (state != Qt::Checked);
|
||||
porymapConfig.setCheckForUpdates(enable);
|
||||
emit this->changedPreferences();
|
||||
});
|
||||
|
||||
// Set up button box
|
||||
this->button_Downloads = ui->buttonBox->addButton("Go to Downloads...", QDialogButtonBox::ActionRole);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &UpdatePromoter::dialogButtonClicked);
|
||||
|
||||
this->resetDialog();
|
||||
}
|
||||
|
||||
void UpdatePromoter::resetDialog() {
|
||||
this->button_Downloads->setEnabled(false);
|
||||
ui->text_Changelog->setVisible(false);
|
||||
ui->label_Warning->setVisible(false);
|
||||
ui->label_Status->setText("Checking for updates...");
|
||||
|
||||
this->changelog = QString();
|
||||
this->downloadLink = QString();
|
||||
}
|
||||
|
||||
void UpdatePromoter::checkForUpdates() {
|
||||
// Ignore request if one is still active.
|
||||
if (this->reply && !this->reply->isFinished())
|
||||
return;
|
||||
this->resetDialog();
|
||||
ui->buttonBox->button(QDialogButtonBox::Retry)->setEnabled(false);
|
||||
|
||||
// We could get ".../releases/latest" to retrieve less data, but this would run into problems if the
|
||||
// most recent item on the releases page is not actually a new release (like the static windows build).
|
||||
// By getting all releases we can also present a multi-version changelog of all changes since the host release.
|
||||
static const QNetworkRequest request(QUrl("https://api.github.com/repos/huderlem/porymap/releases"));
|
||||
this->reply = this->manager->get(request);
|
||||
|
||||
connect(this->reply, &QNetworkReply::finished, [this] {
|
||||
ui->buttonBox->button(QDialogButtonBox::Retry)->setEnabled(true);
|
||||
auto error = this->reply->error();
|
||||
if (error == QNetworkReply::NoError) {
|
||||
this->processWebpage(QJsonDocument::fromJson(this->reply->readAll()));
|
||||
} else {
|
||||
this->processError(this->reply->errorString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Read all the items on the releases page, ignoring entries without a version identifier tag.
|
||||
// Objects in the releases page data are sorted newest to oldest.
|
||||
void UpdatePromoter::processWebpage(const QJsonDocument &data) {
|
||||
bool updateAvailable = false;
|
||||
bool breakingChanges = false;
|
||||
bool foundRelease = false;
|
||||
|
||||
const QJsonArray releases = data.array();
|
||||
for (int i = 0; i < releases.size(); i++) {
|
||||
auto release = releases.at(i).toObject();
|
||||
|
||||
// Convert tag string to version numbers
|
||||
const QString tagName = release.value("tag_name").toString();
|
||||
const QStringList tag = tagName.split(".");
|
||||
if (tag.length() != 3) continue;
|
||||
bool ok;
|
||||
int major = tag.at(0).toInt(&ok);
|
||||
if (!ok) continue;
|
||||
int minor = tag.at(1).toInt(&ok);
|
||||
if (!ok) continue;
|
||||
int patch = tag.at(2).toInt(&ok);
|
||||
if (!ok) continue;
|
||||
|
||||
// We've found a valid release tag. If the version number is not newer than the host version then we can stop looking at releases.
|
||||
foundRelease = true;
|
||||
logInfo(QString("Found release %1.%2.%3").arg(major).arg(minor).arg(patch)); // TODO: Remove
|
||||
if (major <= PORYMAP_VERSION_MAJOR && minor <= PORYMAP_VERSION_MINOR && patch <= PORYMAP_VERSION_PATCH)
|
||||
break;
|
||||
|
||||
const QString description = release.value("body").toString();
|
||||
const QString url = release.value("html_url").toString();
|
||||
if (description.isEmpty() || url.isEmpty()) {
|
||||
// If the release was published very recently it won't have a description yet, in which case don't tell the user about it yet.
|
||||
// If there's no URL, something has gone wrong and we should skip this release.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this->downloadLink.isEmpty()) {
|
||||
// This is the first (newest) release we've found. Record its URL for download.
|
||||
this->downloadLink = url;
|
||||
breakingChanges = (major > PORYMAP_VERSION_MAJOR);
|
||||
}
|
||||
|
||||
// Record the changelog of this release so we can show all changes since the host release.
|
||||
this->changelog.append(QString("## %1\n%2\n\n").arg(tagName).arg(description));
|
||||
updateAvailable = true;
|
||||
}
|
||||
|
||||
if (!foundRelease) {
|
||||
// We retrieved the webpage but didn't successfully parse any releases.
|
||||
this->processError("Error parsing releases webpage");
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's a new update available the dialog will always be opened.
|
||||
// Otherwise the dialog is only open if the user requested it.
|
||||
if (updateAvailable) {
|
||||
this->button_Downloads->setEnabled(!this->downloadLink.isEmpty());
|
||||
ui->text_Changelog->setMarkdown(this->changelog);
|
||||
ui->text_Changelog->setVisible(true);
|
||||
ui->label_Warning->setVisible(breakingChanges);
|
||||
ui->label_Status->setText("A new version of Porymap is available!");
|
||||
this->show();
|
||||
} else {
|
||||
// The rest of the UI remains in the state set by resetDialog
|
||||
ui->label_Status->setText("Your version of Porymap is up to date!");
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePromoter::processError(const QString &err) {
|
||||
const QString message = QString("Failed to check for version update: %1").arg(err);
|
||||
if (this->isVisible()) {
|
||||
ui->label_Status->setText(message);
|
||||
} else {
|
||||
logWarn(message);
|
||||
}
|
||||
}
|
||||
|
||||
// The dialog can either be shown programmatically when an update is available
|
||||
// or if the user manually selects "Check for Updates" in the menu.
|
||||
// When the dialog is shown programmatically there is a check box to disable automatic alerts.
|
||||
// If the user requested the dialog (and it wasn't already open) this check box should be hidden.
|
||||
void UpdatePromoter::requestDialog() {
|
||||
if (!this->isVisible()){
|
||||
ui->checkBox_StopAlerts->setVisible(false);
|
||||
this->show();
|
||||
} else if (this->isMinimized()) {
|
||||
this->showNormal();
|
||||
} else {
|
||||
this->raise();
|
||||
this->activateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePromoter::updatePreferences() {
|
||||
const QSignalBlocker blocker(ui->checkBox_StopAlerts);
|
||||
ui->checkBox_StopAlerts->setChecked(porymapConfig.getCheckForUpdates());
|
||||
}
|
||||
|
||||
void UpdatePromoter::dialogButtonClicked(QAbstractButton *button) {
|
||||
auto buttonRole = ui->buttonBox->buttonRole(button);
|
||||
if (buttonRole == QDialogButtonBox::RejectRole) {
|
||||
this->close();
|
||||
} else if (buttonRole == QDialogButtonBox::AcceptRole) {
|
||||
// "Retry" button
|
||||
this->checkForUpdates();
|
||||
} else if (button == this->button_Downloads && !this->downloadLink.isEmpty()) {
|
||||
QDesktopServices::openUrl(QUrl(this->downloadLink));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue