1
0
Fork 0
flightgear/src/GUI/CatalogListModel.cxx
2018-04-09 01:29:01 +01:00

328 lines
9.9 KiB
C++

// CatalogListModel.cxx - part of GUI launcher using Qt5
//
// Written by James Turner, started March 2015.
//
// Copyright (C) 2015 James Turner <zakalawe@mac.com>
//
// 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 "CatalogListModel.hxx"
#include <QDebug>
#include <QUrl>
#include <QTimer>
// Simgear
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/package/Install.hxx>
// FlightGear
#include <Main/globals.hxx>
#include <Network/HTTPClient.hxx>
using namespace simgear::pkg;
class CatalogDelegate : public simgear::pkg::Delegate
{
public:
CatalogDelegate(CatalogListModel* outer) : p(outer) {}
void catalogRefreshed(CatalogRef catalog, StatusCode) override
{
p->onCatalogStatusChanged(catalog);
}
void startInstall(InstallRef) override {}
void installProgress(InstallRef, unsigned int, unsigned int) override {}
void finishInstall(InstallRef, StatusCode ) override {}
private:
CatalogListModel* p = nullptr;
};
CatalogListModel::CatalogListModel(QObject* pr, const
simgear::pkg::RootRef& rootRef) :
QAbstractListModel(pr),
m_packageRoot(rootRef)
{
m_delegate = new CatalogDelegate(this);
m_packageRoot->addDelegate(m_delegate);
resetData();
}
CatalogListModel::~CatalogListModel()
{
m_packageRoot->removeDelegate(m_delegate);
}
void CatalogListModel::resetData()
{
CatalogList updatedCatalogs = m_packageRoot->allCatalogs();
std::sort(updatedCatalogs.begin(), updatedCatalogs.end(),
[](const CatalogRef& catA, const CatalogRef& catB)
{ // lexicographic ordering
return catA->name() < catB->name();
});
if (updatedCatalogs == m_catalogs)
return;
beginResetModel();
m_catalogs = updatedCatalogs;
endResetModel();
emit catalogsChanged();
}
int CatalogListModel::rowCount(const QModelIndex& parent) const
{
return m_catalogs.size();
}
QVariant CatalogListModel::data(const QModelIndex& index, int role) const
{
simgear::pkg::CatalogRef cat = m_catalogs.at(index.row());
if (role == Qt::DisplayRole) {
QString name = QString::fromStdString(cat->name());
QString desc;
if (cat->isEnabled()) {
desc = QString::fromStdString(cat->description()).simplified();
} else {
switch (cat->status()) {
case Delegate::FAIL_NOT_FOUND:
desc = tr("The catalog data was not found on the server at the expected location (URL)");
break;
case Delegate::FAIL_VERSION:
desc = tr("The catalog is not comaptible with the version of FlightGear");
break;
case Delegate::FAIL_HTTP_FORBIDDEN:
desc = tr("The catalog server is blocking access from some reason (forbidden)");
break;
default:
desc = tr("disabled due to an internal error");
}
}
return tr("%1 - %2").arg(name).arg(desc);
} else if (role == CatalogDescriptionRole) {
return QString::fromStdString(cat->description());
} else if (role == CatalogNameRole) {
return QString::fromStdString(cat->name());
} else if (role == Qt::ToolTipRole) {
return QString::fromStdString(cat->url());
} else if (role == CatalogUrlRole) {
return QUrl(QString::fromStdString(cat->url()));
} else if (role == CatalogIdRole) {
return QString::fromStdString(cat->id());
} else if (role == CatalogPackageCountRole) {
return static_cast<quint32>(cat->packages().size());
} else if (role == CatalogInstallCountRole) {
return static_cast<quint32>(cat->installedPackages().size());
} else if (role == CatalogStatusRole) {
return translateStatusForCatalog(cat);
} else if (role == CatalogIsNewlyAdded) {
return (cat == m_newlyAddedCatalog);
}
return QVariant();
}
bool CatalogListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
return false;
}
Qt::ItemFlags CatalogListModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags r = Qt::ItemIsSelectable;
const auto cat = m_catalogs.at(index.row());
if (cat->isEnabled()) {
r |= Qt::ItemIsEnabled;
}
return r;
}
QHash<int, QByteArray> CatalogListModel::roleNames() const
{
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
result[CatalogUrlRole] = "url";
result[CatalogIdRole] = "id";
result[CatalogDescriptionRole] = "description";
result[CatalogNameRole] = "name";
result[CatalogStatusRole] = "status";
result[CatalogIsNewlyAdded] = "isNewlyAdded";
return result;
}
void CatalogListModel::removeCatalog(int index)
{
if ((index < 0) || (index >= m_catalogs.size())) {
return;
}
const std::string removeId = m_catalogs.at(index)->id();
m_packageRoot->removeCatalogById(removeId);
resetData();
}
void CatalogListModel::refreshCatalog(int index)
{
if ((index < 0) || (index >= m_catalogs.size())) {
return;
}
m_catalogs.at(index)->refresh();
}
void CatalogListModel::installDefaultCatalog()
{
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
m_newlyAddedCatalog = Catalog::createFromUrl(m_packageRoot, http->getDefaultCatalogUrl());
emit isAddingCatalogChanged();
emit statusOfAddingCatalogChanged();
resetData();
}
void CatalogListModel::addCatalogByUrl(QUrl url)
{
if (m_newlyAddedCatalog) {
qWarning() << Q_FUNC_INFO << "already adding a catalog";
return;
}
m_newlyAddedCatalog = Catalog::createFromUrl(m_packageRoot, url.toString().toStdString());
resetData();
emit isAddingCatalogChanged();
}
int CatalogListModel::indexOf(QUrl url)
{
std::string urlString = url.toString().toStdString();
auto it = std::find_if(m_catalogs.begin(), m_catalogs.end(),
[urlString](simgear::pkg::CatalogRef cat) { return cat->url() == urlString;});
if (it == m_catalogs.end())
return -1;
return std::distance(m_catalogs.begin(), it);
}
void CatalogListModel::finalizeAddCatalog()
{
if (!m_newlyAddedCatalog) {
qWarning() << Q_FUNC_INFO << "no catalog add in progress";
return;
}
auto it = std::find(m_catalogs.begin(), m_catalogs.end(), m_newlyAddedCatalog);
if (it == m_catalogs.end()) {
qWarning() << Q_FUNC_INFO << "couldn't find new catalog in m_catalogs" << QString::fromStdString(m_newlyAddedCatalog->url());
return;
}
const int row = std::distance(m_catalogs.begin(), it);
m_newlyAddedCatalog.clear();
emit isAddingCatalogChanged();
emit statusOfAddingCatalogChanged();
emit dataChanged(index(row), index(row));
}
void CatalogListModel::abandonAddCatalog()
{
if (!m_newlyAddedCatalog)
return;
m_packageRoot->removeCatalog(m_newlyAddedCatalog);
m_newlyAddedCatalog.clear();
emit isAddingCatalogChanged();
emit statusOfAddingCatalogChanged();
resetData();
}
bool CatalogListModel::isAddingCatalog() const
{
return m_newlyAddedCatalog.get() != nullptr;
}
void CatalogListModel::onCatalogStatusChanged(Catalog* cat)
{
if (cat == nullptr) {
resetData();
return;
}
//qInfo() << Q_FUNC_INFO << "for" << QString::fromStdString(cat->url()) << translateStatusForCatalog(cat);
// download the official catalog often fails with a 404 due to how we
// compute the version-specific URL. This is the logic which bounces the UI
// to the fallback URL.
if (cat->status() == Delegate::FAIL_NOT_FOUND) {
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
if (cat->url() == http->getDefaultCatalogUrl()) {
cat->setUrl(http->getDefaultCatalogFallbackUrl());
cat->refresh(); // and trigger another refresh
return;
}
}
if (cat == m_newlyAddedCatalog) {
// defer this signal slightly so that QML calling finalizeAdd or
// abandonAdd in response, doesn't re-enter the package code
QTimer::singleShot(0, this, &CatalogListModel::statusOfAddingCatalogChanged);
return;
}
auto it = std::find(m_catalogs.begin(), m_catalogs.end(), cat);
if (it == m_catalogs.end())
return;
int row = std::distance(m_catalogs.begin(), it);
emit dataChanged(index(row), index(row));
}
CatalogListModel::CatalogStatus CatalogListModel::translateStatusForCatalog(CatalogRef cat) const
{
switch (cat->status()) {
case Delegate::STATUS_SUCCESS:
case Delegate::STATUS_REFRESHED:
return Ok;
case Delegate::FAIL_DOWNLOAD: return NetworkError;
case Delegate::STATUS_IN_PROGRESS: return Refreshing;
case Delegate::FAIL_NOT_FOUND: return NotFoundOnServer;
case Delegate::FAIL_VERSION: return IncomaptbleVersion;
case Delegate::FAIL_HTTP_FORBIDDEN: return HTTPForbidden;
case Delegate::FAIL_VALIDATION: return InvalidData;
default:
return UnknownError;
}
}
CatalogListModel::CatalogStatus CatalogListModel::statusOfAddingCatalog() const
{
if (!m_newlyAddedCatalog.get()) {
return NoAddInProgress;
}
return translateStatusForCatalog(m_newlyAddedCatalog);
}