FlightGear next
CatalogListModel.cxx
Go to the documentation of this file.
1// CatalogListModel.cxx - part of GUI launcher using Qt5
2//
3// Written by James Turner, started March 2015.
4//
5// Copyright (C) 2015 James Turner <zakalawe@mac.com>
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#include "CatalogListModel.hxx"
22
23#include <QDebug>
24#include <QUrl>
25#include <QTimer>
26
27// Simgear
28#include <simgear/props/props_io.hxx>
29#include <simgear/structure/exception.hxx>
30#include <simgear/misc/sg_path.hxx>
31
32#include <simgear/package/Package.hxx>
33#include <simgear/package/Install.hxx>
34
35// FlightGear
36#include <Main/globals.hxx>
39
40using namespace simgear::pkg;
41
42class CatalogDelegate : public simgear::pkg::Delegate
43{
44public:
45 CatalogDelegate(CatalogListModel* outer) : p(outer) {}
46
47 void catalogRefreshed(CatalogRef catalog, StatusCode) override
48 {
49 p->onCatalogStatusChanged(catalog);
50 }
51
52 void startInstall(InstallRef) override {}
53 void installProgress(InstallRef, unsigned int, unsigned int) override {}
54 void finishInstall(InstallRef, StatusCode ) override {}
55private:
56 CatalogListModel* p = nullptr;
57};
58
59
61 simgear::pkg::RootRef& rootRef) :
62 QAbstractListModel(pr),
63 m_packageRoot(rootRef)
64{
65 m_delegate = new CatalogDelegate(this);
66 m_packageRoot->addDelegate(m_delegate);
67
68 resetData();
69}
70
72{
73 m_packageRoot->removeDelegate(m_delegate);
74}
75
77{
78 CatalogList updatedCatalogs = m_packageRoot->allCatalogs();
79 std::sort(updatedCatalogs.begin(), updatedCatalogs.end(),
80 [](const CatalogRef& catA, const CatalogRef& catB)
81 { // lexicographic ordering
82 return catA->name() < catB->name();
83 });
84
85 if (updatedCatalogs == m_catalogs)
86 return;
87
88 beginResetModel();
89 m_catalogs = updatedCatalogs;
90 endResetModel();
91
92 emit catalogsChanged();
93}
94
95int CatalogListModel::rowCount(const QModelIndex& parent) const
96{
97 Q_UNUSED(parent)
98 return static_cast<int>(m_catalogs.size());
99}
100
101QVariant CatalogListModel::data(const QModelIndex& index, int role) const
102{
103 const auto cat = m_catalogs.at(static_cast<size_t>(index.row()));
104 if (role == Qt::DisplayRole) {
105 QString name = QString::fromStdString(cat->name());
106 QString desc;
107 if (cat->isEnabled()) {
108 desc = QString::fromStdString(cat->description()).simplified();
109 } else {
110 switch (cat->status()) {
111 case Delegate::FAIL_NOT_FOUND:
112 desc = tr("The catalog data was not found on the server at the expected location (URL)");
113 break;
114 case Delegate::FAIL_VERSION:
115 desc = tr("The catalog is not compatible with the version of FlightGear");
116 break;
117 case Delegate::FAIL_HTTP_FORBIDDEN:
118 desc = tr("The catalog server is blocking access from some reason (forbidden)");
119 break;
120 default:
121 desc = tr("disabled due to an internal error");
122 }
123 }
124 return tr("%1 - %2").arg(name).arg(desc);
125 } else if (role == CatalogDescriptionRole) {
126 return QString::fromStdString(cat->description());
127 } else if (role == CatalogNameRole) {
128 return QString::fromStdString(cat->name());
129 } else if (role == Qt::ToolTipRole) {
130 return QString::fromStdString(cat->url());
131 } else if (role == CatalogUrlRole) {
132 return QUrl(QString::fromStdString(cat->url()));
133 } else if (role == CatalogIdRole) {
134 return QString::fromStdString(cat->id());
135 } else if (role == CatalogPackageCountRole) {
136 return static_cast<quint32>(cat->packages().size());
137 } else if (role == CatalogInstallCountRole) {
138 return static_cast<quint32>(cat->installedPackages().size());
139 } else if (role == CatalogStatusRole) {
140 return translateStatusForCatalog(cat);
141 } else if (role == CatalogIsNewlyAdded) {
142 return (cat == m_newlyAddedCatalog);
143 } else if (role == CatalogEnabled) {
144 return cat->isUserEnabled();
145 }
146
147 return QVariant();
148}
149
150bool CatalogListModel::setData(const QModelIndex &index, const QVariant &value, int role)
151{
152 auto cat = m_catalogs.at(static_cast<size_t>(index.row()));
153 if (role == CatalogEnabled) {
154 cat->setUserEnabled(value.toBool());
155 return true;
156 }
157 return false;
158}
159
160Qt::ItemFlags CatalogListModel::flags(const QModelIndex &index) const
161{
162 Qt::ItemFlags r = Qt::ItemIsSelectable;
163 const auto cat = m_catalogs.at(static_cast<size_t>(index.row()));
164 if (cat->isEnabled()) {
165 r |= Qt::ItemIsEnabled;
166 }
167 return r;
168}
169
170QHash<int, QByteArray> CatalogListModel::roleNames() const
171{
172 QHash<int, QByteArray> result = QAbstractListModel::roleNames();
173 result[CatalogUrlRole] = "url";
174 result[CatalogIdRole] = "id";
175 result[CatalogDescriptionRole] = "description";
176 result[CatalogNameRole] = "name";
177 result[CatalogStatusRole] = "status";
178 result[CatalogIsNewlyAdded] = "isNewlyAdded";
179 result[CatalogEnabled] = "enabled";
180 return result;
181}
182
184{
185 if ((index < 0) || (index >= static_cast<int>(m_catalogs.size()))) {
186 return;
187 }
188
189 const std::string removeId = m_catalogs.at(static_cast<size_t>(index))->id();
190 m_packageRoot->removeCatalogById(removeId);
191 resetData();
192}
193
195{
196 if ((index < 0) || (index >= static_cast<int>(m_catalogs.size()))) {
197 return;
198 }
199 m_catalogs.at(static_cast<size_t>(index))->refresh();
200}
201
203{
204 auto http = globals->get_subsystem<FGHTTPClient>();
205 CatalogRef cat = Catalog::createFromUrl(m_packageRoot, http->getDefaultCatalogUrl());
206 if (showAddFeedback) {
207 m_newlyAddedCatalog = cat;
210 }
211
212 resetData();
213}
214
216{
217 if (m_newlyAddedCatalog) {
218 qWarning() << Q_FUNC_INFO << "already adding a catalog";
219 return;
220 }
221
222 m_newlyAddedCatalog = Catalog::createFromUrl(m_packageRoot, url.toString().toStdString());
223 flightgear::addSentryBreadcrumb("CatalogListModel: Adding catalog " + url.toString().toStdString(), "info");
224 resetData();
226}
227
228int CatalogListModel::indexOf(QUrl url)
229{
230 std::string urlString = url.toString().toStdString();
231 auto it = std::find_if(m_catalogs.begin(), m_catalogs.end(),
232 [urlString](simgear::pkg::CatalogRef cat) { return cat->url() == urlString;});
233 if (it == m_catalogs.end())
234 return -1;
235
236 return static_cast<int>(std::distance(m_catalogs.begin(), it));
237}
238
240{
241 if (!m_newlyAddedCatalog) {
242 qWarning() << Q_FUNC_INFO << "no catalog add in progress";
243 return;
244 }
245
246 auto it = std::find(m_catalogs.begin(), m_catalogs.end(), m_newlyAddedCatalog);
247 if (it == m_catalogs.end()) {
248 qWarning() << Q_FUNC_INFO << "couldn't find new catalog in m_catalogs" << QString::fromStdString(m_newlyAddedCatalog->url());
249 return;
250 }
251
252 flightgear::addSentryBreadcrumb("CatalogListModel: finalziing add of:" + m_newlyAddedCatalog->id(), "info");
253
254 const int row = static_cast<int>(std::distance(m_catalogs.begin(), it));
255 m_newlyAddedCatalog.clear();
258 emit dataChanged(index(row), index(row));
259}
260
262{
263 if (!m_newlyAddedCatalog)
264 return;
265
266 m_packageRoot->removeCatalog(m_newlyAddedCatalog);
267
268 m_newlyAddedCatalog.clear();
271
272 resetData();
273}
274
276{
277 return m_newlyAddedCatalog.get() != nullptr;
278}
279
281{
282 if (cat == nullptr) {
283 resetData();
284 return;
285 }
286
287 //qInfo() << Q_FUNC_INFO << "for" << QString::fromStdString(cat->url()) << translateStatusForCatalog(cat);
288
289 // download the official catalog often fails with a 404 due to how we
290 // compute the version-specific URL. This is the logic which bounces the UI
291 // to the fallback URL.
292 if (cat->status() == Delegate::FAIL_NOT_FOUND) {
293 auto http = globals->get_subsystem<FGHTTPClient>();
294 if (cat->url() == http->getDefaultCatalogUrl()) {
295 cat->setUrl(http->getDefaultCatalogFallbackUrl());
296 cat->refresh(); // and trigger another refresh
297 return;
298 }
299 }
300
301 if (cat == m_newlyAddedCatalog) {
302 // defer this signal slightly so that QML calling finalizeAdd or
303 // abandonAdd in response, doesn't re-enter the package code
304 QTimer::singleShot(0, this, &CatalogListModel::statusOfAddingCatalogChanged);
305 return;
306 }
307
308 auto it = std::find(m_catalogs.begin(), m_catalogs.end(), cat);
309 if (it == m_catalogs.end())
310 return;
311
312 int row = std::distance(m_catalogs.begin(), it);
313 emit dataChanged(index(row), index(row));
314}
315
316CatalogListModel::CatalogStatus CatalogListModel::translateStatusForCatalog(CatalogRef cat) const
317{
318 switch (cat->status()) {
319 case Delegate::STATUS_SUCCESS:
320 case Delegate::STATUS_REFRESHED:
321 return Ok;
322
323 case Delegate::FAIL_DOWNLOAD: return NetworkError;
324 case Delegate::STATUS_IN_PROGRESS: return Refreshing;
325 case Delegate::FAIL_NOT_FOUND: return NotFoundOnServer;
326 case Delegate::FAIL_VERSION: return IncompatibleVersion;
327 case Delegate::FAIL_HTTP_FORBIDDEN: return HTTPForbidden;
328 case Delegate::FAIL_VALIDATION:
329 case Delegate::FAIL_EXTRACT:
330 return InvalidData;
331 default:
332 return UnknownError;
333 }
334}
335
337{
338 if (!m_newlyAddedCatalog.get()) {
339 return NoAddInProgress;
340 }
341
342 return translateStatusForCatalog(m_newlyAddedCatalog);
343}
const int CatalogNameRole
const int CatalogUrlRole
const int CatalogDescriptionRole
const int CatalogIdRole
const int CatalogEnabled
const int CatalogInstallCountRole
const int CatalogIsNewlyAdded
const int CatalogPackageCountRole
const int CatalogStatusRole
#define p(x)
bool catalog
Definition JSBSim.cpp:94
void installProgress(InstallRef, unsigned int, unsigned int) override
void finishInstall(InstallRef, StatusCode) override
void startInstall(InstallRef) override
void catalogRefreshed(CatalogRef catalog, StatusCode) override
CatalogDelegate(CatalogListModel *outer)
Q_INVOKABLE void abandonAddCatalog()
void catalogsChanged()
Q_INVOKABLE void finalizeAddCatalog()
int rowCount(const QModelIndex &parent) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
CatalogListModel(QObject *pr, const simgear::pkg::RootRef &root)
QVariant data(const QModelIndex &index, int role) const override
void isAddingCatalogChanged()
void onCatalogStatusChanged(simgear::pkg::Catalog *cat)
CatalogStatus statusOfAddingCatalog
Q_INVOKABLE void refreshCatalog(int index)
Q_INVOKABLE void installDefaultCatalog(bool showAddFeedback)
QHash< int, QByteArray > roleNames() const override
Q_INVOKABLE void addCatalogByUrl(QUrl url)
Q_INVOKABLE void removeCatalog(int index)
bool setData(const QModelIndex &index, const QVariant &value, int role) override
void statusOfAddingCatalogChanged()
const char * name
FGGlobals * globals
Definition globals.cxx:142
void addSentryBreadcrumb(const std::string &, const std::string &)