Launcher: UI feedback for hangar migrations
Add pop-up notification when we migrate to a new version of a hangar. Convert the ‘new version available’ message to use a pop-up notification as well.
This commit is contained in:
parent
c71f281a58
commit
df42c6f026
18 changed files with 580 additions and 65 deletions
src
GUI
CMakeLists.txtLauncherMainWindow.cxxLauncherMainWindow.hxxLauncherNotificationsController.cxxLauncherNotificationsController.hxxLauncherPackageDelegate.cxxLauncherPackageDelegate.hxxUpdateChecker.cxx
assets
qml
DidMigrateOfficialCatalogNotification.qmlDidMigrateOtherCatalogNotification.qmlLauncher.qmlNewVersionNotification.qmlNotificationArea.qmlSummary.qml
resources.qrcNetwork
|
@ -132,6 +132,10 @@ if (HAVE_QT)
|
||||||
FavouriteAircraftData.hxx
|
FavouriteAircraftData.hxx
|
||||||
UpdateChecker.cxx
|
UpdateChecker.cxx
|
||||||
UpdateChecker.hxx
|
UpdateChecker.hxx
|
||||||
|
LauncherPackageDelegate.hxx
|
||||||
|
LauncherPackageDelegate.cxx
|
||||||
|
LauncherNotificationsController.hxx
|
||||||
|
LauncherNotificationsController.cxx
|
||||||
${uic_sources}
|
${uic_sources}
|
||||||
${qrc_sources}
|
${qrc_sources}
|
||||||
${qml_sources})
|
${qml_sources})
|
||||||
|
|
|
@ -17,14 +17,16 @@
|
||||||
#include <QQmlFileSelector>
|
#include <QQmlFileSelector>
|
||||||
|
|
||||||
// launcher headers
|
// launcher headers
|
||||||
#include "QtLauncher.hxx"
|
|
||||||
#include "AddOnsController.hxx"
|
#include "AddOnsController.hxx"
|
||||||
#include "AircraftItemModel.hxx"
|
#include "AircraftModel.hxx"
|
||||||
#include "DefaultAircraftLocator.hxx"
|
#include "DefaultAircraftLocator.hxx"
|
||||||
#include "LaunchConfig.hxx"
|
#include "LaunchConfig.hxx"
|
||||||
#include "LocalAircraftCache.hxx"
|
|
||||||
#include "LauncherController.hxx"
|
#include "LauncherController.hxx"
|
||||||
|
#include "LauncherNotificationsController.hxx"
|
||||||
|
#include "LauncherPackageDelegate.hxx"
|
||||||
|
#include "LocalAircraftCache.hxx"
|
||||||
#include "LocationController.hxx"
|
#include "LocationController.hxx"
|
||||||
|
#include "QtLauncher.hxx"
|
||||||
#include "UpdateChecker.hxx"
|
#include "UpdateChecker.hxx"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -80,6 +82,8 @@ LauncherMainWindow::LauncherMainWindow(bool inSimMode) : QQuickView()
|
||||||
connect(qa, &QAction::triggered, m_controller, &LauncherController::quit);
|
connect(qa, &QAction::triggered, m_controller, &LauncherController::quit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connect(this, &QQuickView::statusChanged, this, &LauncherMainWindow::onQuickStatusChanged);
|
||||||
|
|
||||||
m_controller->initialRestoreSettings();
|
m_controller->initialRestoreSettings();
|
||||||
|
|
||||||
////////////
|
////////////
|
||||||
|
@ -103,6 +107,12 @@ LauncherMainWindow::LauncherMainWindow(bool inSimMode) : QQuickView()
|
||||||
auto updater = new UpdateChecker(this);
|
auto updater = new UpdateChecker(this);
|
||||||
ctx->setContextProperty("_updates", updater);
|
ctx->setContextProperty("_updates", updater);
|
||||||
|
|
||||||
|
auto packageDelegate = new LauncherPackageDelegate(this);
|
||||||
|
ctx->setContextProperty("_packages", packageDelegate);
|
||||||
|
|
||||||
|
auto notifications = new LauncherNotificationsController{this, engine()};
|
||||||
|
ctx->setContextProperty("_notifications", notifications);
|
||||||
|
|
||||||
if (!inSimMode) {
|
if (!inSimMode) {
|
||||||
auto addOnsCtl = new AddOnsController(this, m_controller->config());
|
auto addOnsCtl = new AddOnsController(this, m_controller->config());
|
||||||
ctx->setContextProperty("_addOns", addOnsCtl);
|
ctx->setContextProperty("_addOns", addOnsCtl);
|
||||||
|
@ -120,25 +130,22 @@ LauncherMainWindow::LauncherMainWindow(bool inSimMode) : QQuickView()
|
||||||
setSource(QUrl("qrc:///qml/Launcher.qml"));
|
setSource(QUrl("qrc:///qml/Launcher.qml"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
void LauncherMainWindow::onQuickStatusChanged(QQuickView::Status status)
|
||||||
void LauncherMainWindow::onQuickStatusChanged(QQuickWidget::Status status)
|
|
||||||
{
|
{
|
||||||
if (status == QQuickWidget::Error) {
|
if (status == QQuickView::Error) {
|
||||||
QQuickWidget* qw = qobject_cast<QQuickWidget*>(sender());
|
|
||||||
QString errorString;
|
QString errorString;
|
||||||
|
|
||||||
Q_FOREACH(auto err, qw->errors()) {
|
Q_FOREACH (auto err, errors()) {
|
||||||
errorString.append("\n" + err.toString());
|
errorString.append("\n" + err.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
QMessageBox::critical(this, "UI loading failures.",
|
QMessageBox::critical(nullptr, "UI loading failures.",
|
||||||
tr("Problems occurred loading the user interface. This is often due to missing modules on your system. "
|
tr("Problems occurred loading the user interface. This is usually due to missing modules on your system. "
|
||||||
"Please report this error to the FlightGear developer list or forum, and take care to mention your system "
|
"Please report this error to the FlightGear developer list or forum, and take care to mention your system "
|
||||||
"distribution, etc. Please also include the information provided below.\n")
|
"distribution, etc. Please also include the information provided below.\n") +
|
||||||
+ errorString);
|
errorString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
LauncherMainWindow::~LauncherMainWindow()
|
LauncherMainWindow::~LauncherMainWindow()
|
||||||
{
|
{
|
||||||
|
|
|
@ -48,6 +48,10 @@ public:
|
||||||
bool wasRejected();
|
bool wasRejected();
|
||||||
|
|
||||||
bool event(QEvent *event) override;
|
bool event(QEvent *event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onQuickStatusChanged(QQuickView::Status status);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LauncherController* m_controller;
|
LauncherController* m_controller;
|
||||||
};
|
};
|
||||||
|
|
167
src/GUI/LauncherNotificationsController.cxx
Normal file
167
src/GUI/LauncherNotificationsController.cxx
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// Written by James Turner, started October 2020
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 James Turner <james@flightgear.org>
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "LauncherNotificationsController.hxx"
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
static LauncherNotificationsController* static_instance = nullptr;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const int IdRole = Qt::UserRole + 1;
|
||||||
|
const int SourceRole = Qt::UserRole + 2;
|
||||||
|
const int ArgsRole = Qt::UserRole + 3;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class LauncherNotificationsController::NotificationsModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int rowCount(const QModelIndex&) const override
|
||||||
|
{
|
||||||
|
return static_cast<int>(_data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex& index, int role) const override
|
||||||
|
{
|
||||||
|
const int row = index.row();
|
||||||
|
if ((row < 0) || (row >= static_cast<int>(_data.size()))) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& d = _data.at(row);
|
||||||
|
switch (role) {
|
||||||
|
case IdRole: return d.id;
|
||||||
|
case SourceRole: return d.source;
|
||||||
|
case ArgsRole: return QVariant::fromValue(d.args);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> roleNames() const override
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
||||||
|
result[IdRole] = "id";
|
||||||
|
result[SourceRole] = "source";
|
||||||
|
result[ArgsRole] = "args";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeIndex(int index)
|
||||||
|
{
|
||||||
|
beginRemoveRows({}, index, index);
|
||||||
|
_data.erase(_data.begin() + index);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(QString id, QUrl source, QJSValue args)
|
||||||
|
{
|
||||||
|
const int newRow = static_cast<int>(_data.size());
|
||||||
|
beginInsertRows({}, newRow, newRow);
|
||||||
|
_data.push_back({id, source, args});
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Data {
|
||||||
|
QString id;
|
||||||
|
QUrl source;
|
||||||
|
QJSValue args;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Data> _data;
|
||||||
|
};
|
||||||
|
|
||||||
|
LauncherNotificationsController::LauncherNotificationsController(QObject* pr, QQmlEngine* engine) : QObject(pr)
|
||||||
|
{
|
||||||
|
Q_ASSERT(static_instance == nullptr);
|
||||||
|
static_instance = this;
|
||||||
|
|
||||||
|
_model = new NotificationsModel;
|
||||||
|
|
||||||
|
_qmlEngine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherNotificationsController::~LauncherNotificationsController()
|
||||||
|
{
|
||||||
|
static_instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherNotificationsController* LauncherNotificationsController::instance()
|
||||||
|
{
|
||||||
|
return static_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractItemModel* LauncherNotificationsController::notifications() const
|
||||||
|
{
|
||||||
|
return _model;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJSValue LauncherNotificationsController::argsForIndex(int index) const
|
||||||
|
{
|
||||||
|
if ((index < 0) || (index >= static_cast<int>(_model->_data.size()))) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto& d = _model->_data.at(index);
|
||||||
|
qDebug() << Q_FUNC_INFO << index;
|
||||||
|
return d.args;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJSEngine* LauncherNotificationsController::jsEngine()
|
||||||
|
{
|
||||||
|
return _qmlEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherNotificationsController::dismissIndex(int index)
|
||||||
|
{
|
||||||
|
const auto& d = _model->_data.at(index);
|
||||||
|
|
||||||
|
// if the notificsation supports persistent dismissal, then record this
|
||||||
|
// fact in the global settings, so we don't show it again.
|
||||||
|
// restore defaults will of course clear these settings, but that's
|
||||||
|
// desirable anyway.
|
||||||
|
if (d.args.property("persistent-dismiss").toBool()) {
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("dismissed-notifications");
|
||||||
|
settings.setValue(d.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_model->removeIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherNotificationsController::postNotification(QString id, QUrl source, QJSValue args)
|
||||||
|
{
|
||||||
|
const bool supportsPersistentDismiss = args.property("persistent-dismiss").toBool();
|
||||||
|
if (supportsPersistentDismiss) {
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("dismissed-notifications");
|
||||||
|
bool alreadyDimissed = settings.value(id).toBool();
|
||||||
|
if (alreadyDimissed) {
|
||||||
|
qWarning() << "Skipping notification" << id << ", was previousl dimissed by user";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_model->append(id, source, args);
|
||||||
|
}
|
59
src/GUI/LauncherNotificationsController.hxx
Normal file
59
src/GUI/LauncherNotificationsController.hxx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Written by James Turner, started October 2020
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 James Turner <james@flightgear.org>
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QJSValue>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
// forward decls
|
||||||
|
class QAbstractItemModel;
|
||||||
|
class QJSEngine;
|
||||||
|
class QQmlEngine;
|
||||||
|
|
||||||
|
class LauncherNotificationsController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QAbstractItemModel* active READ notifications CONSTANT)
|
||||||
|
public:
|
||||||
|
LauncherNotificationsController(QObject* pr, QQmlEngine* qmlEngine);
|
||||||
|
~LauncherNotificationsController();
|
||||||
|
|
||||||
|
static LauncherNotificationsController* instance();
|
||||||
|
|
||||||
|
QAbstractItemModel* notifications() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QJSValue argsForIndex(int index) const;
|
||||||
|
|
||||||
|
QJSEngine* jsEngine();
|
||||||
|
public slots:
|
||||||
|
void dismissIndex(int index);
|
||||||
|
|
||||||
|
void postNotification(QString id, QUrl source, QJSValue args = {});
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
class NotificationsModel;
|
||||||
|
|
||||||
|
NotificationsModel* _model = nullptr;
|
||||||
|
QQmlEngine* _qmlEngine = nullptr;
|
||||||
|
};
|
73
src/GUI/LauncherPackageDelegate.cxx
Normal file
73
src/GUI/LauncherPackageDelegate.cxx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
|
||||||
|
// Copyright (C) 2020 James Turner <james@flightgear.org>
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "LauncherPackageDelegate.hxx"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QJSEngine>
|
||||||
|
|
||||||
|
#include <simgear/package/Root.hxx>
|
||||||
|
|
||||||
|
#include <Main/globals.hxx>
|
||||||
|
#include <Network/HTTPClient.hxx>
|
||||||
|
|
||||||
|
#include "LauncherNotificationsController.hxx"
|
||||||
|
|
||||||
|
LauncherPackageDelegate::LauncherPackageDelegate(QObject* parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
globals->packageRoot()->addDelegate(this);
|
||||||
|
const auto http = globals->get_subsystem<FGHTTPClient>();
|
||||||
|
_defaultCatalogId = http->getDefaultCatalogId();
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherPackageDelegate::~LauncherPackageDelegate()
|
||||||
|
{
|
||||||
|
globals->packageRoot()->removeDelegate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherPackageDelegate::catalogRefreshed(simgear::pkg::CatalogRef aCatalog, simgear::pkg::Delegate::StatusCode aReason)
|
||||||
|
{
|
||||||
|
if ((aReason != Delegate::STATUS_REFRESHED) || !aCatalog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nc = LauncherNotificationsController::instance();
|
||||||
|
|
||||||
|
if (aCatalog->migratedFrom() != simgear::pkg::CatalogRef{}) {
|
||||||
|
QJSValue args = nc->jsEngine()->newObject();
|
||||||
|
|
||||||
|
args.setProperty("newCatalogName", QString::fromStdString(aCatalog->name()));
|
||||||
|
|
||||||
|
if (aCatalog->id() == _defaultCatalogId) {
|
||||||
|
nc->postNotification("did-migrate-official-catalog-to-" + QString::fromStdString(_defaultCatalogId),
|
||||||
|
QUrl{"qrc:///qml/DidMigrateOfficialCatalogNotification.qml"},
|
||||||
|
args);
|
||||||
|
} else {
|
||||||
|
nc->postNotification("did-migrate-catalog-to-" + QString::fromStdString(aCatalog->id()),
|
||||||
|
QUrl{"qrc:///qml/DidMigrateOtherCatalogNotification.qml"},
|
||||||
|
args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherPackageDelegate::finishInstall(simgear::pkg::InstallRef ref, simgear::pkg::Delegate::StatusCode status)
|
||||||
|
{
|
||||||
|
Q_UNUSED(ref)
|
||||||
|
Q_UNUSED(status)
|
||||||
|
}
|
37
src/GUI/LauncherPackageDelegate.hxx
Normal file
37
src/GUI/LauncherPackageDelegate.hxx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef LAUNCHERPACKAGEDELEGATE_HXX
|
||||||
|
#define LAUNCHERPACKAGEDELEGATE_HXX
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <simgear/package/Delegate.hxx>
|
||||||
|
#include <simgear/package/Install.hxx>
|
||||||
|
#include <simgear/package/Package.hxx>
|
||||||
|
#include <simgear/package/Root.hxx>
|
||||||
|
|
||||||
|
class LauncherPackageDelegate : public QObject,
|
||||||
|
public simgear::pkg::Delegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LauncherPackageDelegate(QObject* parent = nullptr);
|
||||||
|
~LauncherPackageDelegate();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void catalogRefreshed(simgear::pkg::CatalogRef aCatalog, StatusCode aReason) override;
|
||||||
|
|
||||||
|
// mandatory overrides, not actually needed here.
|
||||||
|
void startInstall(simgear::pkg::InstallRef) override {}
|
||||||
|
void installProgress(simgear::pkg::InstallRef, unsigned int, unsigned int) override{};
|
||||||
|
void finishInstall(simgear::pkg::InstallRef ref, StatusCode status) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void didMigrateOfficialHangarChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _defaultCatalogId;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LAUNCHERPACKAGEDELEGATE_HXX
|
|
@ -135,6 +135,7 @@ void UpdateChecker::receivedUpdateXML(QByteArray body)
|
||||||
const auto s = body.toStdString();
|
const auto s = body.toStdString();
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
auto nc = LauncherNotificationsController::instance();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const char* buffer = s.c_str();
|
const char* buffer = s.c_str();
|
||||||
|
@ -156,6 +157,8 @@ void UpdateChecker::receivedUpdateXML(QByteArray body)
|
||||||
m_updateUri = QUrl(QString::fromStdString(newVersionUri));
|
m_updateUri = QUrl(QString::fromStdString(newVersionUri));
|
||||||
emit statusChanged(m_status);
|
emit statusChanged(m_status);
|
||||||
|
|
||||||
|
nc->postNotification("flightgear-update-major", QUrl{"qrc:///qml/NewVersionNotification.qml"});
|
||||||
|
|
||||||
return; // don't consider minor updates
|
return; // don't consider minor updates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,6 +175,8 @@ void UpdateChecker::receivedUpdateXML(QByteArray body)
|
||||||
const std::string newVersionUri = props->getStringValue("download-uri");
|
const std::string newVersionUri = props->getStringValue("download-uri");
|
||||||
m_updateUri = QUrl(QString::fromStdString(newVersionUri));
|
m_updateUri = QUrl(QString::fromStdString(newVersionUri));
|
||||||
emit statusChanged(m_status);
|
emit statusChanged(m_status);
|
||||||
|
|
||||||
|
nc->postNotification("flightgear-update-point", QUrl{"qrc:///qml/NewVersionNotification.qml"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const sg_exception &e) {
|
} catch (const sg_exception &e) {
|
||||||
|
|
BIN
src/GUI/assets/white-cross-icon.png
Normal file
BIN
src/GUI/assets/white-cross-icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 290 B |
21
src/GUI/qml/DidMigrateOfficialCatalogNotification.qml
Normal file
21
src/GUI/qml/DidMigrateOfficialCatalogNotification.qml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import QtQuick 2.4
|
||||||
|
import "."
|
||||||
|
|
||||||
|
Text {
|
||||||
|
signal dismiss();
|
||||||
|
|
||||||
|
readonly property string updateAllLink: "\"launcher:update-all\"";
|
||||||
|
readonly property string newName: _notifications.argsForIndex(model.index).newCatalogName
|
||||||
|
|
||||||
|
text: qsTr("An updated version of the official aircraft hangar '%2' was automatically installed. " +
|
||||||
|
"Existing aircraft have been marked for update, <a href=%1>click here to update them all</a>").arg(updateAllLink).arg(newName)
|
||||||
|
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.pixelSize: Style.subHeadingFontPixelSize
|
||||||
|
color: "white"
|
||||||
|
|
||||||
|
onLinkActivated: {
|
||||||
|
_launcher.requestUpdateAllAircraft();
|
||||||
|
dismiss(); // request our dismissal
|
||||||
|
}
|
||||||
|
}
|
21
src/GUI/qml/DidMigrateOtherCatalogNotification.qml
Normal file
21
src/GUI/qml/DidMigrateOtherCatalogNotification.qml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import QtQuick 2.4
|
||||||
|
import "."
|
||||||
|
|
||||||
|
Text {
|
||||||
|
signal dismiss();
|
||||||
|
|
||||||
|
readonly property string updateAllLink: "\"launcher:update-all\"";
|
||||||
|
readonly property string newName: _notifications.argsForIndex(model.index).newCatalogName
|
||||||
|
|
||||||
|
text: qsTr("An updated version of the hangar '%2' was automatically installed. " +
|
||||||
|
"Existing aircraft have been marked for update, <a href=%1>click here to update them all</a>").arg(updateAllLink).arg(newName)
|
||||||
|
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.pixelSize: Style.subHeadingFontPixelSize
|
||||||
|
color: "white"
|
||||||
|
|
||||||
|
onLinkActivated: {
|
||||||
|
_launcher.requestUpdateAllAircraft();
|
||||||
|
dismiss(); // request our dismissal
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import QtQuick 2.4
|
import QtQuick 2.4
|
||||||
|
import QtQml 2.4
|
||||||
import FlightGear 1.0
|
import FlightGear 1.0
|
||||||
import "."
|
import "."
|
||||||
|
|
||||||
|
@ -149,6 +150,18 @@ Item {
|
||||||
source: "qrc:///qml/Summary.qml"
|
source: "qrc:///qml/Summary.qml"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationArea {
|
||||||
|
id: notifications
|
||||||
|
// only show on the summary page
|
||||||
|
visible: sidebar.selectedPage === 0
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectPage(index)
|
function selectPage(index)
|
||||||
{
|
{
|
||||||
sidebar.setSelectedPage(index);
|
sidebar.setSelectedPage(index);
|
||||||
|
|
28
src/GUI/qml/NewVersionNotification.qml
Normal file
28
src/GUI/qml/NewVersionNotification.qml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
import QtQuick 2.4
|
||||||
|
import FlightGear.Launcher 1.0
|
||||||
|
import "."
|
||||||
|
|
||||||
|
ClickableText {
|
||||||
|
signal dismiss();
|
||||||
|
|
||||||
|
text: msg.arg(_updates.updateVersion)
|
||||||
|
readonly property string msg: (_updates.status == UpdateChecker.MajorUpdate) ?
|
||||||
|
qsTr("A new release of FlightGear is available (%1): click for more information")
|
||||||
|
: qsTr("Updated version %1 is available: click here to download")
|
||||||
|
|
||||||
|
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.pixelSize: Style.subHeadingFontPixelSize
|
||||||
|
color: "white"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
_launcher.launchUrl(_updates.updateUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissed()
|
||||||
|
{
|
||||||
|
_updates.ignoreUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
111
src/GUI/qml/NotificationArea.qml
Normal file
111
src/GUI/qml/NotificationArea.qml
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import QtQuick 2.4
|
||||||
|
import QtQml 2.4
|
||||||
|
|
||||||
|
import FlightGear.Launcher 1.0
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
width: 500
|
||||||
|
|
||||||
|
readonly property int ourMargin: Style.margin * 2
|
||||||
|
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: notificationBox
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: boxRoot
|
||||||
|
|
||||||
|
property alias content: contentLoader.sourceComponent
|
||||||
|
|
||||||
|
width: notificationsColumn.width
|
||||||
|
height: contentLoader.height + (ourMargin * 2)
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
color: Style.themeColor
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.darker(Style.themeColor)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
opacity: Style.panelOpacity
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
// height is not anchored, can float
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
left: parent.left
|
||||||
|
right: closeButton.left
|
||||||
|
margins: ourMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
id: contentLoader
|
||||||
|
source: model.source
|
||||||
|
|
||||||
|
// don't set height, comes from content
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: contentLoader.item
|
||||||
|
onDismiss: {
|
||||||
|
_notifications.dismissIndex(model.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: closeButton
|
||||||
|
source: "qrc:///white-delete-icon"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
right: parent.right
|
||||||
|
margins: ourMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (contentLoader.item.dismissed) {
|
||||||
|
contentLoader.item.dismissed();
|
||||||
|
}
|
||||||
|
|
||||||
|
_notifications.dismissIndex(model.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // of notification box
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure clicks 'near' the notifications don't go to other UI
|
||||||
|
MouseArea {
|
||||||
|
width: parent.width
|
||||||
|
height: notificationsColumn.height
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: notificationsColumn
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
left: parent.left
|
||||||
|
margins: Style.strutSize
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: Style.strutSize
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: _notifications.active
|
||||||
|
delegate: notificationBox
|
||||||
|
}
|
||||||
|
} // of boxes column
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import QtQuick 2.4
|
import QtQuick 2.4
|
||||||
|
import QtQml 2.4
|
||||||
import FlightGear.Launcher 1.0
|
import FlightGear.Launcher 1.0
|
||||||
import "."
|
import "."
|
||||||
|
|
||||||
|
@ -77,54 +78,7 @@ Item {
|
||||||
styleColor: "black"
|
styleColor: "black"
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Style.margin
|
|
||||||
anchors {
|
|
||||||
left: logoText.left
|
|
||||||
right: logoText.right
|
|
||||||
}
|
|
||||||
|
|
||||||
// anchoring to logoText bottom doesn't work as expected because of
|
|
||||||
// dynamic text sizing, so bind it manually
|
|
||||||
y: logoText.y + Style.margin + logoText.contentHeight
|
|
||||||
|
|
||||||
id: updateMessage
|
|
||||||
|
|
||||||
visible: _updates.status != UpdateChecker.NoUpdate
|
|
||||||
|
|
||||||
readonly property string msg: (_updates.status == UpdateChecker.MajorUpdate) ?
|
|
||||||
qsTr("A new release of FlightGear is available (%1): click for more information")
|
|
||||||
: qsTr("Updated version %1 is available: click here to download")
|
|
||||||
|
|
||||||
ClickableText {
|
ClickableText {
|
||||||
text: parent.msg.arg(_updates.updateVersion)
|
|
||||||
baseTextColor: "white"
|
|
||||||
style: Text.Outline
|
|
||||||
styleColor: "black"
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: Style.headingFontPixelSize
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
_launcher.launchUrl(_updates.updateUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClickableText {
|
|
||||||
text: qsTr("(or click to ignore this)")
|
|
||||||
baseTextColor: "white"
|
|
||||||
style: Text.Outline
|
|
||||||
styleColor: "black"
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: Style.headingFontPixelSize
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
_updates.ignoreUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClickableText {
|
|
||||||
visible: !updateMessage.visible
|
|
||||||
anchors {
|
anchors {
|
||||||
left: logoText.left
|
left: logoText.left
|
||||||
right: logoText.right
|
right: logoText.right
|
||||||
|
@ -360,5 +314,5 @@ Item {
|
||||||
width: 1; height: 1
|
width: 1; height: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // of summary box
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,7 @@
|
||||||
<file>qml/LocationAltitudeRow.qml</file>
|
<file>qml/LocationAltitudeRow.qml</file>
|
||||||
<file>qml/CatalogDelegate.qml</file>
|
<file>qml/CatalogDelegate.qml</file>
|
||||||
<file alias="clear-text-icon">assets/icons8-clear-symbol-26.png</file>
|
<file alias="clear-text-icon">assets/icons8-clear-symbol-26.png</file>
|
||||||
|
<file alias="white-delete-icon">assets/white-cross-icon.png</file>
|
||||||
<file>qml/FlightPlan.qml</file>
|
<file>qml/FlightPlan.qml</file>
|
||||||
<file>qml/PlainTextEditBox.qml</file>
|
<file>qml/PlainTextEditBox.qml</file>
|
||||||
<file>qml/HeaderBox.qml</file>
|
<file>qml/HeaderBox.qml</file>
|
||||||
|
@ -136,6 +137,11 @@
|
||||||
<file alias="favourite-icon-outline">assets/icons8-christmas-star-outline.png</file>
|
<file alias="favourite-icon-outline">assets/icons8-christmas-star-outline.png</file>
|
||||||
<file>qml/FirstRun.qml</file>
|
<file>qml/FirstRun.qml</file>
|
||||||
<file>qml/HelpSupport.qml</file>
|
<file>qml/HelpSupport.qml</file>
|
||||||
|
<file>qml/NotificationArea.qml</file>
|
||||||
|
<file>qml/NewVersionNotification.qml</file>
|
||||||
|
<file>qml/DidMigrateOfficialCatalogNotification.qml</file>
|
||||||
|
<file>qml/DidMigrateOtherCatalogNotification.qml</file>
|
||||||
|
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/preview">
|
<qresource prefix="/preview">
|
||||||
<file alias="close-icon">assets/preview-close.png</file>
|
<file alias="close-icon">assets/preview-close.png</file>
|
||||||
|
|
|
@ -124,12 +124,15 @@ bool FGHTTPClient::isDefaultCatalogInstalled() const
|
||||||
return getDefaultCatalog().valid();
|
return getDefaultCatalog().valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FGHTTPClient::addDefaultCatalog()
|
pkg::CatalogRef FGHTTPClient::addDefaultCatalog()
|
||||||
{
|
{
|
||||||
pkg::CatalogRef defaultCatalog = getDefaultCatalog();
|
pkg::CatalogRef defaultCatalog = getDefaultCatalog();
|
||||||
if (!defaultCatalog.valid()) {
|
if (!defaultCatalog.valid()) {
|
||||||
pkg::Catalog::createFromUrl(globals->packageRoot(), getDefaultCatalogUrl());
|
auto cat = pkg::Catalog::createFromUrl(globals->packageRoot(), getDefaultCatalogUrl());
|
||||||
|
return cat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return defaultCatalog;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FGHTTPClient::getDefaultCatalogId() const
|
std::string FGHTTPClient::getDefaultCatalogId() const
|
||||||
|
|
|
@ -21,8 +21,10 @@
|
||||||
#ifndef FG_HTTP_CLIENT_HXX
|
#ifndef FG_HTTP_CLIENT_HXX
|
||||||
#define FG_HTTP_CLIENT_HXX
|
#define FG_HTTP_CLIENT_HXX
|
||||||
|
|
||||||
#include <simgear/structure/subsystem_mgr.hxx>
|
|
||||||
#include <simgear/io/HTTPClient.hxx>
|
#include <simgear/io/HTTPClient.hxx>
|
||||||
|
#include <simgear/package/Catalog.hxx>
|
||||||
|
#include <simgear/structure/subsystem_mgr.hxx>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class FGHTTPClient : public SGSubsystem
|
class FGHTTPClient : public SGSubsystem
|
||||||
|
@ -46,7 +48,7 @@ public:
|
||||||
simgear::HTTP::Client const* client() const { return _http.get(); }
|
simgear::HTTP::Client const* client() const { return _http.get(); }
|
||||||
|
|
||||||
bool isDefaultCatalogInstalled() const;
|
bool isDefaultCatalogInstalled() const;
|
||||||
void addDefaultCatalog();
|
simgear::pkg::CatalogRef addDefaultCatalog();
|
||||||
|
|
||||||
std::string getDefaultCatalogId() const;
|
std::string getDefaultCatalogId() const;
|
||||||
std::string getDefaultCatalogUrl() const;
|
std::string getDefaultCatalogUrl() const;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue