mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
This reverts commit b25b3e3c61.
While this is useful for development and testing we don't need it
for release versions and it invalidates existing caches causing
additional traffic on CloudDB servers.
1445 lines
48 KiB
C++
1445 lines
48 KiB
C++
/*
|
|
* Copyright (c) 2015 Joern Rischmueller (joern.rm@gmail.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 "CloudDBChart.h"
|
|
#include "CloudDBCommon.h"
|
|
#include "CloudDBStatus.h"
|
|
#include "CloudService.h"
|
|
|
|
#include "LTMChartParser.h"
|
|
#include "GcUpgrade.h"
|
|
#include "Colors.h"
|
|
|
|
#ifdef GC_WANT_R
|
|
#include <RTool.h>
|
|
#endif
|
|
|
|
#ifdef GC_WANT_PYTHON
|
|
#include "PythonEmbed.h"
|
|
#endif
|
|
|
|
#include <QtGlobal>
|
|
#include <QNetworkRequest>
|
|
#include <QNetworkReply>
|
|
#include <QMessageBox>
|
|
#include <QJsonParseError>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QXmlInputSource>
|
|
#include <QXmlSimpleReader>
|
|
#include <QRegularExpressionValidator>
|
|
|
|
CloudDBChartClient::CloudDBChartClient()
|
|
{
|
|
g_nam = new QNetworkAccessManager(this);
|
|
QDir cacheDir(QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation).at(0));
|
|
cacheDir.cdUp();
|
|
g_cacheDir = QString(cacheDir.absolutePath()+"/GoldenCheetahCloudDB");
|
|
QDir newCacheDir(g_cacheDir);
|
|
if (!newCacheDir.exists()) {
|
|
cacheDir.mkdir("GoldenCheetahCloudDB");
|
|
}
|
|
|
|
// general handling for sslErrors
|
|
connect(g_nam, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this,
|
|
SLOT(sslErrors(QNetworkReply*,QList<QSslError>)));
|
|
|
|
// common definitions used
|
|
|
|
g_chart_url_base = g_chart_url_header = g_chartcuration_url_base = g_chartdownloadincr_url_base = CloudDBCommon::cloudDBBaseURL;
|
|
|
|
g_chart_url_base.append("gchart/");
|
|
g_chart_url_header.append("gchartheader");
|
|
g_chartcuration_url_base.append("gchartcuration/");
|
|
g_chartdownloadincr_url_base.append("gchartuse/");
|
|
|
|
}
|
|
CloudDBChartClient::~CloudDBChartClient() {
|
|
delete g_nam;
|
|
}
|
|
|
|
|
|
bool
|
|
CloudDBChartClient::postChart(ChartAPIv1 chart) {
|
|
|
|
// check if Athlete ID is filled
|
|
|
|
if (chart.Header.CreatorId.isEmpty()) return CloudDBCommon::APIresponseOthers;
|
|
|
|
// default where it may be necessary
|
|
if (chart.Header.Language.isEmpty()) chart.Header.Language = "en";
|
|
|
|
// first create the JSON object
|
|
// only a subset of fields is required for POST
|
|
QJsonObject json_header;
|
|
CloudDBCommon::marshallAPIHeaderV1Object(json_header, chart.Header);
|
|
|
|
QJsonObject json;
|
|
json["header"] = json_header;
|
|
json["chartSport"] = chart.ChartSport;
|
|
json["chartType"] = chart.ChartType;
|
|
json["chartView"] = chart.ChartView;
|
|
json["chartDef"] = chart.ChartDef;
|
|
QString image;
|
|
image.append(chart.Image.toBase64());
|
|
json["image"] = image;
|
|
json["creatornick"] = chart.CreatorNick;
|
|
json["creatoremail"] = chart.CreatorEmail;
|
|
QJsonDocument document;
|
|
document.setObject(json);
|
|
|
|
QNetworkRequest request;
|
|
CloudDBCommon::prepareRequest(request, g_chart_url_base);
|
|
g_reply = g_nam->post(request, document.toJson());
|
|
|
|
// wait for reply (synchronously) and process error codes as necessary
|
|
if (!CloudDBCommon::replyReceivedAndOk(g_reply)) return false;
|
|
|
|
CloudDBHeader::setChartHeaderStale(true);
|
|
return true;
|
|
|
|
}
|
|
|
|
bool
|
|
CloudDBChartClient::putChart(ChartAPIv1 chart) {
|
|
|
|
// we assume all field are filled properly / not further check or modification
|
|
|
|
// first create the JSON object / all fields are required for PUT / only LastChanged i
|
|
QJsonObject json_header;
|
|
CloudDBCommon::marshallAPIHeaderV1Object(json_header, chart.Header);
|
|
|
|
QJsonObject json;
|
|
json["header"] = json_header;
|
|
json["chartSport"] = chart.ChartSport;
|
|
json["chartType"] = chart.ChartType;
|
|
json["chartView"] = chart.ChartView;
|
|
json["chartDef"] = chart.ChartDef;
|
|
QString image;
|
|
image.append(chart.Image.toBase64());
|
|
json["image"] = image;
|
|
json["creatornick"] = chart.CreatorNick;
|
|
json["creatoremail"] = chart.CreatorEmail;
|
|
QJsonDocument document;
|
|
document.setObject(json);
|
|
|
|
QNetworkRequest request;
|
|
CloudDBCommon::prepareRequest(request, g_chart_url_base);
|
|
g_reply = g_nam->put(request, document.toJson());
|
|
|
|
// wait for reply (synchronously) and process error codes as necessary
|
|
if (!CloudDBCommon::replyReceivedAndOk(g_reply)) return false;
|
|
|
|
// cache is stale
|
|
deleteChartCache(chart.Header.Id);
|
|
CloudDBHeader::setChartHeaderStale(true);
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
bool
|
|
CloudDBChartClient::getChartByID(qint64 id, ChartAPIv1 *chart) {
|
|
|
|
// read from Cache first
|
|
if (readChartCache(id, chart)) return CloudDBCommon::APIresponseOk;
|
|
|
|
// now from GAE
|
|
QNetworkRequest request;
|
|
CloudDBCommon::prepareRequest(request, g_chart_url_base+QString::number(id, 10));
|
|
g_reply = g_nam->get(request);
|
|
|
|
// wait for reply (synchronously) and process error codes as necessary
|
|
if (!CloudDBCommon::replyReceivedAndOk(g_reply)) return false;
|
|
|
|
// call successfull
|
|
QByteArray result = g_reply->readAll();
|
|
QList<ChartAPIv1>* charts = new QList<ChartAPIv1>;
|
|
unmarshallAPIv1(result, charts);
|
|
if (charts->size() > 0) {
|
|
*chart = charts->value(0);
|
|
writeChartCache(chart);
|
|
charts->clear();
|
|
delete charts;
|
|
return true;
|
|
}
|
|
delete charts;
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CloudDBChartClient::deleteChartByID(qint64 id) {
|
|
|
|
QNetworkRequest request;
|
|
CloudDBCommon::prepareRequest(request, g_chart_url_base+QString::number(id, 10));
|
|
g_reply = g_nam->deleteResource(request);
|
|
|
|
// wait for reply (synchronously) and process error codes as necessary
|
|
if (!CloudDBCommon::replyReceivedAndOk(g_reply)) return false;
|
|
|
|
deleteChartCache(id);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
CloudDBChartClient::curateChartByID(qint64 id, bool newStatus) {
|
|
|
|
QUrlQuery query;
|
|
query.addQueryItem("newStatus", (newStatus ? "true": "false"));
|
|
QNetworkRequest request;
|
|
CloudDBCommon::prepareRequest(request, g_chartcuration_url_base+QString::number(id, 10), &query);
|
|
|
|
// add a payload to "PUT" even though it's not processed
|
|
g_reply = g_nam->put(request, "{ \"id\": \"dummy\" }");
|
|
|
|
// wait for reply (synchronously) and process error codes as necessary
|
|
if (!CloudDBCommon::replyReceivedAndOk(g_reply)) return false;
|
|
|
|
deleteChartCache(id);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CloudDBChartClient::incrementDownloadCounterByID(qint64 id) {
|
|
|
|
QNetworkRequest request;
|
|
CloudDBCommon::prepareRequest(request, g_chartdownloadincr_url_base+QString::number(id, 10));
|
|
g_reply = g_nam->put(request, "{ \"id\": \"dummy\" }");
|
|
|
|
// ignore any errors or reply - user does not need to be informed in case of problems
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
CloudDBChartClient::getAllChartHeader(QList<CommonAPIHeaderV1>* header) {
|
|
bool request_ok = CloudDBHeader::getAllCachedHeader(header, CloudDBHeader::CloudDB_Chart, g_cacheDir, g_chart_url_header, g_nam, g_reply);
|
|
if (request_ok && header->size()>0) {
|
|
cleanChartCache(header);
|
|
}
|
|
return request_ok;
|
|
}
|
|
|
|
|
|
//
|
|
// Trap SSL errors
|
|
//
|
|
void
|
|
CloudDBChartClient::sslErrors(QNetworkReply* reply ,QList<QSslError> errors)
|
|
{
|
|
CloudService::sslErrors(nullptr, reply, errors);
|
|
}
|
|
|
|
// Internal Methods
|
|
|
|
|
|
bool
|
|
CloudDBChartClient::writeChartCache(ChartAPIv1 * chart) {
|
|
|
|
// make sure the subdir exists
|
|
QDir cacheDir(g_cacheDir);
|
|
if (cacheDir.exists()) {
|
|
cacheDir.mkdir("charts");
|
|
} else {
|
|
return false;
|
|
}
|
|
QFile file(g_cacheDir+"/charts/"+QString::number(chart->Header.Id)+".dat");
|
|
if (file.exists()) {
|
|
// overwrite data
|
|
file.resize(0);
|
|
}
|
|
|
|
if (!file.open(QIODevice::WriteOnly)) return false;
|
|
QDataStream out(&file);
|
|
out.setVersion(QDataStream::Qt_4_6);
|
|
// track a version to be able change data structure
|
|
out << chart_magic_string;
|
|
out << chart_cache_version;
|
|
out << chart->Header.LastChanged; // start
|
|
out << chart->Header.CreatorId;
|
|
out << chart->Header.Curated;
|
|
out << chart->Header.Deleted;
|
|
out << chart->Header.Description;
|
|
out << chart->Header.GcVersion;
|
|
out << chart->Header.Id;
|
|
out << chart->Header.Language;
|
|
out << chart->Header.Name;
|
|
out << chart->ChartSport;
|
|
out << chart->ChartType;
|
|
out << chart->ChartView;
|
|
out << chart->ChartDef;
|
|
out << chart->CreatorEmail;
|
|
out << chart->CreatorNick;
|
|
out << chart->Image;
|
|
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool CloudDBChartClient::readChartCache(qint64 id, ChartAPIv1 *chart) {
|
|
|
|
QFile file(g_cacheDir+"/charts/"+QString::number(id)+".dat");
|
|
if (!file.open(QIODevice::ReadOnly)) return false;
|
|
QDataStream in(&file);
|
|
in.setVersion(QDataStream::Qt_4_6);
|
|
// track a version to be able change data structure
|
|
int magic_string;
|
|
int version;
|
|
|
|
in >> magic_string;
|
|
if (magic_string != chart_magic_string) {
|
|
// wrong file format / close and exit
|
|
file.close();
|
|
return false;
|
|
}
|
|
|
|
in >> version;
|
|
if (version != chart_cache_version) {
|
|
// change of version, delete old cache entry
|
|
file.remove();
|
|
return false;
|
|
}
|
|
|
|
in >> chart->Header.LastChanged; // start
|
|
in >> chart->Header.CreatorId;
|
|
in >> chart->Header.Curated;
|
|
in >> chart->Header.Deleted;
|
|
in >> chart->Header.Description;
|
|
in >> chart->Header.GcVersion;
|
|
in >> chart->Header.Id;
|
|
in >> chart->Header.Language;
|
|
in >> chart->Header.Name;
|
|
in >> chart->ChartSport;
|
|
in >> chart->ChartType;
|
|
in >> chart->ChartView;
|
|
in >> chart->ChartDef;
|
|
in >> chart->CreatorEmail;
|
|
in >> chart->CreatorNick;
|
|
in >> chart->Image;
|
|
|
|
file.close();
|
|
return true;
|
|
|
|
}
|
|
|
|
void
|
|
CloudDBChartClient::deleteChartCache(qint64 id) {
|
|
|
|
QFile file(g_cacheDir+"/charts/"+QString::number(id)+".dat");
|
|
if (file.exists()) {
|
|
file.remove();
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
CloudDBChartClient::cleanChartCache(QList<CommonAPIHeaderV1> *objectHeader) {
|
|
// remove Deleted Entries from Cache
|
|
int deleted = 0;
|
|
QMutableListIterator<CommonAPIHeaderV1> it(*objectHeader);
|
|
while (it.hasNext()) {
|
|
CommonAPIHeaderV1 header = it.next();
|
|
if (header.Deleted) {
|
|
deleteChartCache(header.Id);
|
|
it.remove();
|
|
deleted ++;
|
|
}
|
|
}
|
|
if (deleted > 0) {
|
|
// store cache for next time
|
|
CloudDBHeader::writeHeaderCache(objectHeader, CloudDBHeader::CloudDB_Chart, g_cacheDir);
|
|
}
|
|
|
|
}
|
|
|
|
bool
|
|
CloudDBChartClient::unmarshallAPIv1(QByteArray json, QList<ChartAPIv1> *charts) {
|
|
|
|
QJsonParseError parseError;
|
|
QJsonDocument document = QJsonDocument::fromJson(json, &parseError);
|
|
|
|
// all these things should not happen and we have not valid object to return
|
|
if (parseError.error != QJsonParseError::NoError || document.isEmpty() || document.isNull()) {
|
|
return false;
|
|
}
|
|
|
|
// do we have a single object or an array ?
|
|
if (document.isObject()) {
|
|
ChartAPIv1 chart;
|
|
QJsonObject object = document.object();
|
|
unmarshallAPIv1Object(&object, &chart);
|
|
QJsonObject header = object["header"].toObject();
|
|
CloudDBCommon::unmarshallAPIHeaderV1Object(&header, &chart.Header);
|
|
charts->append(chart);
|
|
|
|
} else if (document.isArray()) {
|
|
QJsonArray array(document.array());
|
|
for (int i = 0; i< array.size(); i++) {
|
|
QJsonValue value = array.at(i);
|
|
if (value.isObject()) {
|
|
ChartAPIv1 chart;
|
|
QJsonObject object = value.toObject();
|
|
unmarshallAPIv1Object(&object, &chart);
|
|
QJsonObject header = object["header"].toObject();
|
|
CloudDBCommon::unmarshallAPIHeaderV1Object(&header, &chart.Header);
|
|
charts->append(chart);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CloudDBChartClient::unmarshallAPIv1Object(QJsonObject* object, ChartAPIv1* chart) {
|
|
|
|
chart->ChartSport = object->value("chartSport").toString();
|
|
chart->ChartType = object->value("chartType").toString();
|
|
chart->ChartView = object->value("chartView").toString();
|
|
chart->ChartDef = object->value("chartDef").toString();
|
|
QString imageString = object->value("image").toString();
|
|
QByteArray ba;
|
|
ba.append(imageString.toUtf8());
|
|
chart->Image = QByteArray::fromBase64(ba);
|
|
chart->CreatorNick = object->value("creatorNick").toString();
|
|
chart->CreatorEmail = object->value("creatorEmail").toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------------------------------------
|
|
// Dialog to show and retrieve Shared Charts
|
|
//------------------------------------------------------------------------------------------------------------
|
|
|
|
// define size if image preview at one place
|
|
static const int chartImageWidth = 320;
|
|
static const int chartImageHeight = 240;
|
|
|
|
CloudDBChartListDialog::CloudDBChartListDialog() : const_stepSize(5)
|
|
{
|
|
g_client = new CloudDBChartClient();
|
|
g_currentHeaderList = new QList<CommonAPIHeaderV1>;
|
|
g_fullHeaderList = new QList<CommonAPIHeaderV1>;
|
|
CloudDBHeader::setChartHeaderStale(true); //Force Headers to be loaded
|
|
g_currentPresets = new QList<ChartWorkingStructure>;
|
|
g_textFilterActive = false;
|
|
g_networkrequestactive = false; // don't allow Dialog to close while we are retrieving data
|
|
|
|
showing = new QLabel;
|
|
showingTextTemplate = tr("Showing %1 to %2 of %3 charts for %4 / Total uploaded %5");
|
|
resetToStart = new QPushButton(tr("First"));
|
|
nextSet = new QPushButton(tr("Next %1").arg(QString::number(const_stepSize)));
|
|
prevSet = new QPushButton(tr("Prev %1").arg(QString::number(const_stepSize)));
|
|
resetToStart->setEnabled(true);
|
|
nextSet->setDefault(true);
|
|
nextSet->setEnabled(true);
|
|
prevSet->setEnabled(true);
|
|
|
|
connect(resetToStart, SIGNAL(clicked()), this, SLOT(resetToStartClicked()));
|
|
connect(nextSet, SIGNAL(clicked()), this, SLOT(nextSetClicked()));
|
|
connect(prevSet, SIGNAL(clicked()), this, SLOT(prevSetClicked()));
|
|
|
|
showingLayout = new QHBoxLayout;
|
|
showingLayout->addWidget(showing);
|
|
showingLayout->addStretch();
|
|
showingLayout->addWidget(resetToStart);
|
|
showingLayout->addWidget(prevSet);
|
|
showingLayout->addWidget(nextSet);
|
|
|
|
ownChartsOnly = new QCheckBox(tr("My Charts"));
|
|
ownChartsOnly->setChecked(false);
|
|
|
|
connect(ownChartsOnly, SIGNAL(toggled(bool)), this, SLOT(ownChartsToggled(bool)));
|
|
|
|
curationStateCombo = new QComboBox();
|
|
curationStateCombo->addItem(tr("All"));
|
|
curationStateCombo->addItem(tr("Curated Only"));
|
|
curationStateCombo->addItem(tr("Uncurated Only"));
|
|
|
|
// default to curated charts only
|
|
curationStateCombo->setCurrentIndex(1);
|
|
|
|
connect(curationStateCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(curationStateFilterChanged(int)));
|
|
|
|
sportCombo = new QComboBox();
|
|
sportCombo->addItem(tr("Any Sport"));
|
|
foreach (QString sport, CloudDBCommon::cloudDBSports) {
|
|
sportCombo->addItem(sport);
|
|
}
|
|
|
|
connect(sportCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(sportComboFilterChanged(int)));
|
|
|
|
langCombo = new QComboBox();
|
|
langCombo->addItem(tr("Any Language"));
|
|
foreach (QString lang, CloudDBCommon::cloudDBLangs) {
|
|
langCombo->addItem(lang);
|
|
}
|
|
|
|
textFilterApply = new QPushButton(tr("Search Keyword"));
|
|
textFilterApply->setFixedWidth(180 *dpiXFactor); // To allow proper translation
|
|
textFilter = new QLineEdit;
|
|
g_textFilterActive = false;
|
|
|
|
connect(textFilter, SIGNAL(editingFinished()), this, SLOT(textFilterEditingFinished()));
|
|
connect(langCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(languageFilterChanged(int)));
|
|
connect(textFilterApply, SIGNAL(clicked()), this, SLOT(toggleTextFilterApply()));
|
|
|
|
filterLayout = new QHBoxLayout;
|
|
filterLayout->addWidget(ownChartsOnly);
|
|
filterLayout->addStretch();
|
|
filterLayout->addWidget(curationStateCombo);
|
|
filterLayout->addStretch();
|
|
filterLayout->addWidget(sportCombo);
|
|
filterLayout->addStretch();
|
|
filterLayout->addWidget(langCombo);
|
|
filterLayout->addStretch();
|
|
filterLayout->addWidget(textFilterApply);
|
|
filterLayout->addWidget(textFilter);
|
|
|
|
textFilter->setVisible(true);
|
|
|
|
tableWidget = new QTableWidget(0, 2);
|
|
tableWidget->setContentsMargins(0,0,0,0);
|
|
tableWidget->horizontalHeader()->setStretchLastSection(true);
|
|
tableWidget->horizontalHeader()->setVisible(false);
|
|
tableWidget->verticalHeader()->setVisible(false);
|
|
tableWidget->setShowGrid(true);
|
|
|
|
// the preview shall have a dedicated size
|
|
tableWidget->setColumnWidth(0, chartImageWidth);
|
|
|
|
connect(tableWidget, SIGNAL(cellDoubleClicked(int,int)), this, SLOT(cellDoubleClicked(int,int)));
|
|
|
|
// UserGet Role
|
|
addAndCloseUserGetButton = new QPushButton(tr("Download selected chart(s)"));
|
|
closeUserGetButton = new QPushButton(tr("Close"));
|
|
|
|
addAndCloseUserGetButton->setEnabled(true);
|
|
closeUserGetButton->setEnabled(true);
|
|
closeUserGetButton->setDefault(true);
|
|
|
|
connect(addAndCloseUserGetButton, SIGNAL(clicked()), this, SLOT(addAndCloseClicked()));
|
|
connect(closeUserGetButton, SIGNAL(clicked()), this, SLOT(closeClicked()));
|
|
|
|
buttonUserGetLayout = new QHBoxLayout;
|
|
buttonUserGetLayout->addStretch();
|
|
buttonUserGetLayout->addWidget(addAndCloseUserGetButton);
|
|
buttonUserGetLayout->addWidget(closeUserGetButton);
|
|
|
|
// UserEdit Role
|
|
deleteUserEditButton = new QPushButton(tr("Delete selected chart"));
|
|
editUserEditButton = new QPushButton(tr("Edit selected chart"));
|
|
closeUserEditButton = new QPushButton(tr("Close"));
|
|
|
|
connect(deleteUserEditButton, SIGNAL(clicked()), this, SLOT(deleteUserEdit()));
|
|
connect(editUserEditButton, SIGNAL(clicked()), this, SLOT(editUserEdit()));
|
|
connect(closeUserEditButton, SIGNAL(clicked()), this, SLOT(closeClicked()));
|
|
|
|
buttonUserEditLayout = new QHBoxLayout;
|
|
buttonUserEditLayout->addStretch();
|
|
buttonUserEditLayout->addWidget(deleteUserEditButton);
|
|
buttonUserEditLayout->addWidget(editUserEditButton);
|
|
buttonUserEditLayout->addWidget(closeUserEditButton);
|
|
|
|
// CuratorEdit Role
|
|
curateCuratorEditButton = new QPushButton(tr("Set selected chart 'Curated'"));
|
|
editCuratorEditButton = new QPushButton(tr("Edit selected chart"));
|
|
deleteCuratorEditButton = new QPushButton(tr("Delete selected chart"));
|
|
closeCuratorButton = new QPushButton(tr("Close"));
|
|
|
|
connect(curateCuratorEditButton, SIGNAL(clicked()), this, SLOT(curateCuratorEdit()));
|
|
connect(editCuratorEditButton, SIGNAL(clicked()), this, SLOT(editCuratorEdit()));
|
|
connect(deleteCuratorEditButton, SIGNAL(clicked()), this, SLOT(deleteCuratorEdit()));
|
|
connect(closeCuratorButton, SIGNAL(clicked()), this, SLOT(closeClicked()));
|
|
|
|
buttonCuratorEditLayout = new QHBoxLayout;
|
|
buttonCuratorEditLayout->addStretch();
|
|
buttonCuratorEditLayout->addWidget(curateCuratorEditButton);
|
|
buttonCuratorEditLayout->addWidget(editCuratorEditButton);
|
|
buttonCuratorEditLayout->addWidget(deleteCuratorEditButton);
|
|
buttonCuratorEditLayout->addWidget(closeCuratorButton);
|
|
|
|
// prepare the main layouts - with different buttons layouts
|
|
mainLayout = new QVBoxLayout;
|
|
mainLayout->addLayout(showingLayout);
|
|
mainLayout->addLayout(filterLayout);
|
|
mainLayout->addWidget(tableWidget);
|
|
|
|
mainLayout->addLayout(buttonUserGetLayout);
|
|
mainLayout->addLayout(buttonUserEditLayout);
|
|
mainLayout->addLayout(buttonCuratorEditLayout);
|
|
|
|
setMinimumHeight(500 *dpiYFactor);
|
|
setMinimumWidth(700 *dpiXFactor);
|
|
setLayout(mainLayout);
|
|
|
|
}
|
|
|
|
CloudDBChartListDialog::~CloudDBChartListDialog() {
|
|
delete g_client;
|
|
delete g_currentHeaderList;
|
|
delete g_fullHeaderList;
|
|
delete g_currentPresets;
|
|
}
|
|
|
|
// block DialogWindow close while networkrequest is processed
|
|
void
|
|
CloudDBChartListDialog::closeEvent(QCloseEvent* event) {
|
|
|
|
if (g_networkrequestactive) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
QDialog::closeEvent(event);
|
|
}
|
|
|
|
bool
|
|
CloudDBChartListDialog::prepareData(QString athlete, CloudDBCommon::UserRole role, int chartView) {
|
|
|
|
g_role = role;
|
|
g_chartView = chartView;
|
|
// and now initialize the dialog
|
|
setVisibleButtonsForRole();
|
|
|
|
if (g_role == CloudDBCommon::UserEdit) {
|
|
ownChartsOnly->setChecked(true);
|
|
ownChartsOnly->setEnabled(false);
|
|
setWindowTitle(tr("Chart maintenance - Edit or Delete your Charts"));
|
|
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
} else if (role == CloudDBCommon::CuratorEdit) {
|
|
ownChartsOnly->setChecked(false);
|
|
curationStateCombo->setCurrentIndex(2); // start with uncurated
|
|
setWindowTitle(tr("Curator chart maintenance - Curate, Edit or Delete Charts"));
|
|
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
} else {
|
|
setWindowTitle(tr("Select charts to download"));
|
|
tableWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
}
|
|
|
|
if (CloudDBHeader::isStaleChartHeader()) {
|
|
if (!refreshStaleChartHeader()) return false;
|
|
CloudDBHeader::setChartHeaderStale(false);
|
|
}
|
|
g_currentAthleteId = appsettings->cvalue(athlete, GC_ATHLETE_ID, "").toString();
|
|
applyAllFilters();
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartListDialog::updateCurrentPresets(int index, int count) {
|
|
|
|
// while getting the data (which may take of few seconds), disable the UI
|
|
resetToStart->setEnabled(false);
|
|
nextSet->setEnabled(false);
|
|
prevSet->setEnabled(false);
|
|
closeUserGetButton->setEnabled(false);
|
|
addAndCloseUserGetButton->setEnabled(false);
|
|
curationStateCombo->setEnabled(false);
|
|
ownChartsOnly->setEnabled(false);
|
|
textFilterApply->setEnabled(false);
|
|
sportCombo->setEnabled(false);
|
|
langCombo->setEnabled(false);
|
|
deleteUserEditButton->setEnabled(false);
|
|
editUserEditButton->setEnabled(false);
|
|
closeUserEditButton->setEnabled(false);
|
|
curateCuratorEditButton->setEnabled(false);
|
|
editCuratorEditButton->setEnabled(false);
|
|
deleteCuratorEditButton->setEnabled(false);
|
|
closeCuratorButton->setEnabled(false);
|
|
|
|
// now get the presets
|
|
g_currentPresets->clear();
|
|
ChartAPIv1* chart = new ChartAPIv1;
|
|
g_networkrequestactive = true;
|
|
bool noError = true;
|
|
for (int i = index; i< index+count && i<g_currentHeaderList->count() && noError; i++) {
|
|
if (g_client->getChartByID(g_currentHeaderList->at(i).Id, chart)) {
|
|
|
|
ChartWorkingStructure preset;
|
|
preset.id = chart->Header.Id;
|
|
preset.name = chart->Header.Name;
|
|
preset.description = chart->Header.Description;
|
|
preset.language = chart->Header.Language;
|
|
preset.createdAt = chart->Header.LastChanged;
|
|
preset.image.loadFromData(chart->Image);
|
|
preset.gchartType = chart->ChartType;
|
|
preset.gchartView = chart->ChartView;
|
|
preset.gchartDef = chart->ChartDef;
|
|
preset.creatorNick = chart->CreatorNick;
|
|
preset.gchartSport = chart->ChartSport;
|
|
g_currentPresets->append(preset);
|
|
} else {
|
|
noError = false;
|
|
}
|
|
}
|
|
g_networkrequestactive = false;
|
|
delete chart;
|
|
|
|
// update table with current list
|
|
tableWidget->clearContents();
|
|
tableWidget->setRowCount(0);
|
|
|
|
int chartCount = (g_currentHeaderList->size() == 0) ? 0 : g_currentIndex+1;
|
|
int lastIndex = (g_currentIndex+const_stepSize > g_currentHeaderList->size()) ? g_currentHeaderList->size() : g_currentIndex+const_stepSize;
|
|
|
|
QString view = tr("unknown");
|
|
if (g_role == CloudDBCommon::UserImport ) {
|
|
switch(g_chartView) {
|
|
case 0 : view = tr("Trends"); break;
|
|
case 1 : view = tr("Activities"); break;
|
|
case 2 : view = tr("Plan"); break;
|
|
case 3 : view = tr("Train"); break;
|
|
}
|
|
} else
|
|
{
|
|
view = tr("All Views");
|
|
}
|
|
showing->setText(QString(showingTextTemplate)
|
|
.arg(QString::number(chartCount))
|
|
.arg(QString::number(lastIndex))
|
|
.arg(QString::number(g_currentHeaderList->size()))
|
|
.arg(view)
|
|
.arg(QString::number(g_fullHeaderList->size())));
|
|
|
|
for (int i = 0; i< g_currentPresets->size(); i++ ) {
|
|
tableWidget->insertRow(i);
|
|
ChartWorkingStructure preset = g_currentPresets->at(i);
|
|
|
|
QTableWidgetItem *newPxItem = new QTableWidgetItem("");
|
|
newPxItem->setData(Qt::DecorationRole, QVariant(preset.image.scaled(chartImageWidth, chartImageHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
|
|
newPxItem->setSizeHint(QSize(chartImageWidth, chartImageHeight));
|
|
newPxItem->setFlags(newPxItem->flags() & ~Qt::ItemIsEditable);
|
|
tableWidget->setItem(i, 0, newPxItem);
|
|
tableWidget->item(i,0)->setBackground(Qt::darkGray);
|
|
tableWidget->setRowHeight(i, chartImageHeight+20);
|
|
|
|
QString cellText = QString(tr("<h3>%1</h3><h4>Last Edited At: %2 - Creator: %3</h4>%4"))
|
|
.arg(CloudDBCommon::encodeHTML(preset.name))
|
|
.arg(preset.createdAt.date().toString(Qt::ISODate))
|
|
.arg(CloudDBCommon::encodeHTML(preset.creatorNick))
|
|
.arg(CloudDBCommon::encodeHTML(preset.description));
|
|
|
|
QTextEdit *formattedText = new QTextEdit;
|
|
formattedText->setHtml(cellText);
|
|
formattedText->setReadOnly(true);
|
|
tableWidget->setCellWidget(i,1, formattedText);
|
|
}
|
|
|
|
|
|
// enable UI again
|
|
resetToStart->setEnabled(true);
|
|
nextSet->setEnabled(true);
|
|
prevSet->setEnabled(true);
|
|
closeUserGetButton->setEnabled(true);
|
|
addAndCloseUserGetButton->setEnabled(true);
|
|
curationStateCombo->setEnabled(true);
|
|
ownChartsOnly->setEnabled(true);
|
|
textFilterApply->setEnabled(true);
|
|
sportCombo->setEnabled(true);
|
|
langCombo->setEnabled(true);
|
|
deleteUserEditButton->setEnabled(true);
|
|
editUserEditButton->setEnabled(true);
|
|
closeUserEditButton->setEnabled(true);
|
|
curateCuratorEditButton->setEnabled(true);
|
|
editCuratorEditButton->setEnabled(true);
|
|
deleteCuratorEditButton->setEnabled(true);
|
|
closeCuratorButton->setEnabled(true);
|
|
|
|
// role dependent UI settings
|
|
if (g_role == CloudDBCommon::UserEdit) {
|
|
ownChartsOnly->setEnabled(false);
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::setVisibleButtonsForRole() {
|
|
if (g_role == CloudDBCommon::UserEdit) {
|
|
deleteUserEditButton->setVisible(true);
|
|
editUserEditButton->setVisible(true);
|
|
closeUserEditButton->setVisible(true);
|
|
curateCuratorEditButton->setVisible(false);
|
|
editCuratorEditButton->setVisible(false);
|
|
deleteCuratorEditButton->setVisible(false);
|
|
closeCuratorButton->setVisible(false);
|
|
closeUserGetButton->setVisible(false);
|
|
addAndCloseUserGetButton->setVisible(false);
|
|
} else if (g_role == CloudDBCommon::CuratorEdit) {
|
|
deleteUserEditButton->setVisible(false);
|
|
editUserEditButton->setVisible(false);
|
|
closeUserEditButton->setVisible(false);
|
|
curateCuratorEditButton->setVisible(true);
|
|
editCuratorEditButton->setVisible(true);
|
|
deleteCuratorEditButton->setVisible(true);
|
|
closeCuratorButton->setVisible(true);
|
|
closeUserGetButton->setVisible(false);
|
|
addAndCloseUserGetButton->setVisible(false);
|
|
} else {
|
|
deleteUserEditButton->setVisible(false);
|
|
editUserEditButton->setVisible(false);
|
|
closeUserEditButton->setVisible(false);
|
|
curateCuratorEditButton->setVisible(false);
|
|
editCuratorEditButton->setVisible(false);
|
|
deleteCuratorEditButton->setVisible(false);
|
|
closeCuratorButton->setVisible(false);
|
|
closeUserGetButton->setVisible(true);
|
|
addAndCloseUserGetButton->setVisible(true);
|
|
}
|
|
|
|
}
|
|
|
|
bool
|
|
CloudDBChartListDialog::refreshStaleChartHeader() {
|
|
|
|
g_fullHeaderList->clear();
|
|
g_currentHeaderList->clear();
|
|
g_currentIndex = 0;
|
|
g_networkrequestactive = true;
|
|
if (!g_client->getAllChartHeader(g_fullHeaderList)) {
|
|
g_networkrequestactive = false;
|
|
return false;
|
|
}
|
|
|
|
g_networkrequestactive = false;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CloudDBChartListDialog::addAndCloseClicked() {
|
|
|
|
// check if an item of the table is selected
|
|
|
|
|
|
QList<QTableWidgetItem*> selected = tableWidget->selectedItems();
|
|
if (selected.count()>0)
|
|
{
|
|
g_selected.clear();
|
|
// the selectionMode allows multiple selection so get them all
|
|
for (int i = 0; i< selected.count(); i++) {
|
|
QTableWidgetItem* s = selected.at(i);
|
|
if (s->row() >= 0 && s->row() <= g_currentPresets->count()) {
|
|
#if defined(GC_WANT_R) || defined (GC_WANT_PYTHON)
|
|
int chartType = g_currentPresets->at(s->row()).gchartType.toInt();
|
|
#ifdef GC_WANT_R
|
|
if (chartType == GcWindowTypes::RConsole ||
|
|
chartType == GcWindowTypes::RConsoleSeason ) {
|
|
if (rtool == NULL) {
|
|
QMessageBox::information(0, tr("Chart requires 'R'"), tr("The chart your are downloading requires 'R' to be installed \
|
|
and activated for GoldenCheetah to show any graphics. Either 'R' is not activated \
|
|
in the preferences, or not even installed.<br><br> Please ensure 'R' \
|
|
is installed and activated to be able to use this chart."));
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef GC_WANT_PYTHON
|
|
if (chartType == GcWindowTypes::Python ||
|
|
chartType == GcWindowTypes::PythonSeason ) {
|
|
if (python == NULL) {
|
|
QMessageBox::information(0, tr("Chart requires 'Python'"), tr("The chart your are downloading requires 'Python' to be installed \
|
|
and activated for GoldenCheetah to show any graphics. Either 'Python' is not activated \
|
|
in the preferences, or not even installed.<br><br> Please ensure 'Python' \
|
|
is installed and activated to be able to use this chart."));
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
g_selected << g_currentPresets->at(s->row()).gchartDef;
|
|
|
|
// increment download counter
|
|
g_client->incrementDownloadCounterByID(g_currentPresets->at(s->row()).id);
|
|
}
|
|
}
|
|
accept();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartListDialog::closeClicked() {
|
|
reject();
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartListDialog::resetToStartClicked() {
|
|
|
|
if (g_currentIndex == 0) return;
|
|
|
|
g_currentIndex = 0;
|
|
updateCurrentPresets(g_currentIndex, const_stepSize);
|
|
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::nextSetClicked() {
|
|
|
|
g_currentIndex += const_stepSize;
|
|
if (g_currentIndex >= g_currentHeaderList->size()) {
|
|
g_currentIndex = g_currentHeaderList->size() - const_stepSize;
|
|
}
|
|
if (g_currentIndex < 0) g_currentIndex = 0;
|
|
updateCurrentPresets(g_currentIndex, const_stepSize);
|
|
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::prevSetClicked() {
|
|
|
|
g_currentIndex -= const_stepSize;
|
|
if (g_currentIndex < 0) g_currentIndex = 0;
|
|
updateCurrentPresets(g_currentIndex, const_stepSize);
|
|
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::curateCuratorEdit() {
|
|
|
|
if (tableWidget->selectedItems().size()>0)
|
|
{
|
|
// the selectionMode allows only 1 item to be selected at a time
|
|
QTableWidgetItem* s = tableWidget->selectedItems().at(0);
|
|
if (s->row() >= 0 && s->row() <= g_currentPresets->count()) {
|
|
qint64 id = g_currentPresets->at(s->row()).id;
|
|
if (!g_client->curateChartByID(id, true)) {
|
|
return;
|
|
}
|
|
// refresh header buffer
|
|
refreshStaleChartHeader();
|
|
|
|
// curated chart appears on top of the list / and needs to be filtered
|
|
applyAllFilters();
|
|
}
|
|
}
|
|
|
|
}
|
|
void
|
|
CloudDBChartListDialog::deleteCuratorEdit(){
|
|
// currently same like User
|
|
deleteUserEdit();
|
|
}
|
|
void
|
|
CloudDBChartListDialog::editCuratorEdit(){
|
|
// currently same like User
|
|
editUserEdit();
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::deleteUserEdit(){
|
|
|
|
if (tableWidget->selectedItems().size()>0)
|
|
{
|
|
// chart selected for deletion - but ask again to be sure
|
|
if (QMessageBox::question(0, tr("Chart Maintenance"), QString(tr("Do you really want to delete this chart definition ?"))) != QMessageBox::Yes) return;
|
|
|
|
// the selectionMode allows only 1 item to be selected at a time
|
|
QTableWidgetItem* s = tableWidget->selectedItems().at(0);
|
|
if (s->row() >= 0 && s->row() <= g_currentPresets->count()) {
|
|
qint64 id = g_currentPresets->at(s->row()).id;
|
|
if (!g_client->deleteChartByID(id)) {
|
|
return;
|
|
}
|
|
// set stale for subsequent list dialog calls
|
|
CloudDBHeader::setChartHeaderStale(true);
|
|
|
|
// remove deleted chart from both lists
|
|
QMutableListIterator<CommonAPIHeaderV1> it1(*g_currentHeaderList);
|
|
while (it1.hasNext()) {
|
|
if (it1.next().Id == id) {
|
|
it1.remove();
|
|
break; // there is just one equal entry
|
|
}
|
|
}
|
|
QMutableListIterator<CommonAPIHeaderV1> it2(*g_fullHeaderList);
|
|
while (it2.hasNext()) {
|
|
if (it2.next().Id == id) {
|
|
it2.remove();
|
|
break; // there is just one equal entry
|
|
}
|
|
}
|
|
if (g_currentIndex >= g_currentHeaderList->size()) {
|
|
g_currentIndex = g_currentHeaderList->size() - const_stepSize;
|
|
}
|
|
if (g_currentIndex < 0) g_currentIndex = 0;
|
|
updateCurrentPresets(g_currentIndex, const_stepSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::editUserEdit(){
|
|
|
|
if (tableWidget->selectedItems().size()>0)
|
|
{
|
|
// the selectionMode allows only 1 item to be selected at a time
|
|
QTableWidgetItem* s = tableWidget->selectedItems().at(0);
|
|
if (s->row() >= 0 && s->row() <= g_currentPresets->count()) {
|
|
qint64 id = g_currentPresets->at(s->row()).id;
|
|
ChartAPIv1 chart;
|
|
if (!g_client->getChartByID(id, &chart)) {
|
|
return;
|
|
}
|
|
|
|
// now complete the chart with for the user manually added fields
|
|
CloudDBChartObjectDialog dialog(chart, "", true);
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
if (g_client->putChart(dialog.getChart())) {
|
|
refreshStaleChartHeader();
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
// updated chart appears on top of the list / and needs to be filtered
|
|
applyAllFilters();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::curationStateFilterChanged(int) {
|
|
applyAllFilters();
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::sportComboFilterChanged(int) {
|
|
applyAllFilters();
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::ownChartsToggled(bool) {
|
|
applyAllFilters();
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::toggleTextFilterApply() {
|
|
if (g_textFilterActive) {
|
|
g_textFilterActive = false;
|
|
textFilterApply->setText(tr("Search Keyword"));
|
|
applyAllFilters();
|
|
} else {
|
|
g_textFilterActive = true;
|
|
textFilterApply->setText(tr("Reset Search"));
|
|
applyAllFilters();
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::languageFilterChanged(int) {
|
|
applyAllFilters();
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartListDialog::applyAllFilters() {
|
|
|
|
|
|
// setup to only show charts that are relevant to the current view
|
|
unsigned int mask=0;
|
|
switch(g_chartView) {
|
|
case 0 : mask = VIEW_TRENDS; break;
|
|
default:
|
|
case 1 : mask = VIEW_ANALYSIS; break;
|
|
case 2 : mask = VIEW_PLAN; break;
|
|
case 3 : mask = VIEW_TRAIN; break;
|
|
}
|
|
|
|
QStringList searchList;
|
|
g_currentHeaderList->clear();
|
|
if (!textFilter->text().isEmpty()) {
|
|
// split by any whitespace
|
|
searchList = textFilter->text().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
|
|
}
|
|
foreach (CommonAPIHeaderV1 chart, *g_fullHeaderList) {
|
|
|
|
// list does not contain any deleted chart id's
|
|
|
|
|
|
// first filter based on the current view (Home, Analysis, Plan) - but only on Imp
|
|
bool chartOkForView = false;
|
|
if (g_role == CloudDBCommon::UserImport) {
|
|
int winId = chart.ChartType.toInt();
|
|
for (int i = 0; GcWindows[i].relevance; i++) {
|
|
if (GcWindows[i].id == winId && (GcWindows[i].relevance & mask)) {
|
|
chartOkForView = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// for editing and curation all charts are shown
|
|
chartOkForView = true;
|
|
}
|
|
|
|
int curationState = curationStateCombo->currentIndex();
|
|
if (chartOkForView) {
|
|
|
|
// check curated first
|
|
if (curationState == 0 ||
|
|
(curationState == 1 && chart.Curated) ||
|
|
(curationState == 2 && !chart.Curated ) ) {
|
|
|
|
//check own chart only
|
|
if (!ownChartsOnly->isChecked() ||
|
|
(ownChartsOnly->isChecked() && chart.CreatorId == g_currentAthleteId)) {
|
|
|
|
// then check sport (observe that we have an additional entry "all" in the combo !
|
|
if (sportCombo->currentIndex() == 0 ||
|
|
(sportCombo->currentIndex() > 0 && CloudDBCommon::cloudDBSportIds[sportCombo->currentIndex()-1] == chart.ChartSport )) {
|
|
|
|
// then check language (observe that we have an additional entry "all" in the combo !
|
|
if (langCombo->currentIndex() == 0 ||
|
|
(langCombo->currentIndex() > 0 && CloudDBCommon::cloudDBLangsIds[langCombo->currentIndex()-1] == chart.Language )) {
|
|
|
|
// at last check text filter
|
|
if (g_textFilterActive) {
|
|
// search with filter Keywords
|
|
QString chartInfo = chart.Name + " " + chart.Description;
|
|
foreach (QString search, searchList) {
|
|
if (chartInfo.contains(search, Qt::CaseInsensitive)) {
|
|
g_currentHeaderList->append(chart);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
g_currentHeaderList->append(chart);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
g_currentIndex = 0;
|
|
|
|
// now get the data
|
|
updateCurrentPresets(g_currentIndex, const_stepSize);
|
|
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartListDialog::textFilterEditingFinished() {
|
|
if (g_textFilterActive) {
|
|
applyAllFilters();
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudDBChartListDialog::cellDoubleClicked(int row, int /*column */) {
|
|
ChartAPIv1* chart = new ChartAPIv1;
|
|
if (row >= 0 && row < g_currentHeaderList->size() ) {
|
|
g_networkrequestactive = true;
|
|
if (g_client->getChartByID(g_currentHeaderList->at(g_currentIndex+row).Id, chart)) {
|
|
CloudDBChartShowPictureDialog showImage(chart->Image);
|
|
showImage.exec();
|
|
|
|
}
|
|
}
|
|
g_networkrequestactive = false;
|
|
delete chart;
|
|
}
|
|
|
|
|
|
CloudDBChartShowPictureDialog::CloudDBChartShowPictureDialog(QByteArray imageData) : imageData(imageData) {
|
|
|
|
QRect rec = QApplication::primaryScreen()->geometry();
|
|
imageLabel = new QLabel();
|
|
imageLabel->setMinimumSize(150,100); // not scaled to hi-dpi screens (cloud db constraint not device)
|
|
chartImage.loadFromData(imageData);
|
|
QSize size = chartImage.size();
|
|
int w = rec.width()/2;
|
|
int h = (qreal)size.width()*w/size.height();
|
|
imageLabel->setPixmap(chartImage.scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
|
|
okButton = new QPushButton(tr("Close"));
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
|
buttonLayout->addStretch();
|
|
buttonLayout->addWidget(okButton);
|
|
|
|
connect(okButton, SIGNAL(clicked()), this, SLOT(okClicked()));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
layout->addWidget(imageLabel);
|
|
layout->addLayout(buttonLayout);
|
|
|
|
setLayout(layout);
|
|
|
|
}
|
|
|
|
CloudDBChartShowPictureDialog::~CloudDBChartShowPictureDialog() {
|
|
delete imageLabel;
|
|
delete okButton;
|
|
}
|
|
|
|
void
|
|
CloudDBChartShowPictureDialog::okClicked() {
|
|
accept();
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartShowPictureDialog::resizeEvent(QResizeEvent *) {
|
|
|
|
int w = imageLabel->width();
|
|
int h = imageLabel->height();
|
|
|
|
// set a scaled pixmap to a w x h window keeping its aspect ratio
|
|
imageLabel->setPixmap(chartImage.scaled(w,h,Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------------------------------------
|
|
// Dialog for publishing Chart Details
|
|
//------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
CloudDBChartObjectDialog::CloudDBChartObjectDialog(ChartAPIv1 data, QString athlete, bool update) : data(data), athlete(athlete), update(update) {
|
|
|
|
QLabel *chartName = new QLabel(tr("Chart Name"));
|
|
name = new QLineEdit();
|
|
nameDefault = tr("<Chart Name>");
|
|
name->setMaxLength(50);
|
|
if (update) {
|
|
name->setText(data.Header.Name);
|
|
nameOk = true; // no need to re-check
|
|
} else {
|
|
name->setText(nameDefault);
|
|
nameOk = false;
|
|
}
|
|
QRegularExpression name_rx("^.{5,50}$");
|
|
QValidator *name_validator = new QRegularExpressionValidator(name_rx, this);
|
|
name->setValidator(name_validator);
|
|
|
|
QLabel* sportLabel = new QLabel(tr("Sport"));
|
|
sportCombo = new QComboBox();
|
|
foreach (QString sport, CloudDBCommon::cloudDBSports) {
|
|
sportCombo->addItem(sport);
|
|
}
|
|
if (update) {
|
|
int index = CloudDBCommon::cloudDBSportIds.indexOf(data.ChartSport);
|
|
sportCombo->setCurrentIndex( index<0 ? 0 : index);
|
|
}
|
|
|
|
|
|
QLabel* langLabel = new QLabel(tr("Language"));
|
|
langCombo = new QComboBox();
|
|
foreach (QString lang, CloudDBCommon::cloudDBLangs) {
|
|
langCombo->addItem(lang);
|
|
}
|
|
if (update) {
|
|
int index = CloudDBCommon::cloudDBLangsIds.indexOf(data.Header.Language);
|
|
langCombo->setCurrentIndex( index<0 ? 0 : index);
|
|
}
|
|
|
|
connect(name, SIGNAL(textChanged(QString)), this, SLOT(nameTextChanged(QString)));
|
|
connect(name, SIGNAL(editingFinished()), this, SLOT(nameEditingFinished()));
|
|
|
|
QLabel *nickLabel = new QLabel(tr("Nickname"));
|
|
nickName = new QLineEdit();
|
|
if (update) {
|
|
nickName->setText(data.CreatorNick);
|
|
} else {
|
|
nickName->setText(appsettings->cvalue(athlete, GC_NICKNAME, "").toString());
|
|
}
|
|
|
|
QLabel *emailLabel = new QLabel(tr("E-Mail"));
|
|
email = new QLineEdit();
|
|
email->setMaxLength(100);
|
|
if (update) {
|
|
email->setText(data.CreatorEmail);
|
|
} else {
|
|
|
|
email->setText(appsettings->cvalue(athlete, GC_CLOUDDB_EMAIL, "").toString());
|
|
}
|
|
// regexp: simple e-mail validation / also allow long domain types & subdomains
|
|
QRegularExpression email_rx("^.+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,10}$");
|
|
QValidator *email_validator = new QRegularExpressionValidator(email_rx, this);
|
|
email->setValidator(email_validator);
|
|
emailOk = !email->text().isEmpty(); // email from properties is ok when loaded
|
|
|
|
connect(email, SIGNAL(textChanged(QString)), this, SLOT(emailTextChanged(QString)));
|
|
connect(email, SIGNAL(editingFinished()), this, SLOT(emailEditingFinished()));
|
|
|
|
QLabel* gcVersionLabel = new QLabel(tr("Version Details"));
|
|
QString versionString = VERSION_STRING;
|
|
versionString.append(" : " + data.Header.GcVersion);
|
|
gcVersionString = new QLabel(versionString);
|
|
QLabel* creatorIdLabel = new QLabel(tr("Creator UUid"));
|
|
creatorId = new QLabel(data.Header.CreatorId);
|
|
|
|
QGridLayout *detailsLayout = new QGridLayout;
|
|
detailsLayout->addWidget(chartName, 0, 0, Qt::AlignLeft);
|
|
detailsLayout->addWidget(name, 0, 1);
|
|
|
|
detailsLayout->addWidget(sportLabel, 1, 0, Qt::AlignLeft);
|
|
detailsLayout->addWidget(sportCombo, 1, 1, Qt::AlignLeft);
|
|
detailsLayout->addWidget(langLabel, 1, 2, Qt::AlignLeft);
|
|
detailsLayout->addWidget(langCombo, 1, 3, Qt::AlignLeft);
|
|
|
|
detailsLayout->addWidget(nickLabel, 2, 0, Qt::AlignLeft);
|
|
detailsLayout->addWidget(nickName, 2, 1);
|
|
detailsLayout->addWidget(emailLabel, 2, 2, Qt::AlignLeft);
|
|
detailsLayout->addWidget(email, 2, 3);
|
|
|
|
detailsLayout->addWidget(gcVersionLabel, 3, 0, Qt::AlignLeft);
|
|
detailsLayout->addWidget(gcVersionString, 3, 1);
|
|
detailsLayout->addWidget(creatorIdLabel, 3, 2, Qt::AlignLeft);
|
|
detailsLayout->addWidget(creatorId, 3, 3);
|
|
|
|
description = new QTextEdit();
|
|
description->setAcceptRichText(false);
|
|
descriptionDefault = tr("<Enter the description of the chart here>");
|
|
description->setText(descriptionDefault);
|
|
if (update) {
|
|
description->setText(data.Header.Description);
|
|
} else {
|
|
description->setText(descriptionDefault);
|
|
}
|
|
QPixmap *chartImage = new QPixmap();
|
|
chartImage->loadFromData(data.Image);
|
|
image = new QLabel();
|
|
image->setPixmap(chartImage->scaled(chartImageWidth*2, chartImageHeight*2, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
|
|
publishButton = new QPushButton(tr("Upload"), this);
|
|
cancelButton = new QPushButton(tr("Cancel"), this);
|
|
|
|
publishButton->setEnabled(true);
|
|
|
|
connect(publishButton, SIGNAL(clicked()), this, SLOT(publishClicked()));
|
|
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
|
|
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
|
buttonLayout->addStretch();
|
|
buttonLayout->addWidget(publishButton);
|
|
buttonLayout->addWidget(cancelButton);
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
mainLayout->addLayout(detailsLayout);
|
|
mainLayout->addWidget(description);
|
|
mainLayout->addWidget(image);
|
|
mainLayout->addLayout(buttonLayout);
|
|
|
|
setLayout(mainLayout);
|
|
|
|
}
|
|
|
|
CloudDBChartObjectDialog::~CloudDBChartObjectDialog() {
|
|
|
|
}
|
|
|
|
void
|
|
CloudDBChartObjectDialog::publishClicked() {
|
|
|
|
// check data consistency
|
|
|
|
if (name->text().isEmpty() || name->text() == nameDefault || !nameOk) {
|
|
QMessageBox::warning(0, tr("Upload Chart"), QString(tr("Please enter a valid chart name with min. 5 characters length.")));
|
|
return;
|
|
}
|
|
|
|
if (nickName->text().isEmpty()) {
|
|
QMessageBox::warning(0, tr("Upload Chart"), QString(tr("Please enter a nickname for this athlete.")));
|
|
return;
|
|
}
|
|
if (email->text().isEmpty() || !emailOk) {
|
|
QMessageBox::warning(0, tr("Upload Chart"), QString(tr("Please enter a valid e-mail address.")));
|
|
return;
|
|
}
|
|
|
|
if (description->toPlainText().isEmpty() || description->toPlainText() == descriptionDefault) {
|
|
QMessageBox::warning(0, tr("Upload Chart"), QString(tr("Please enter a sensible chart description.")));
|
|
return;
|
|
}
|
|
|
|
if (QMessageBox::question(0, tr("Cloud Upload"), QString(tr("Do you want to upload this chart definition ?"))) != QMessageBox::Yes) return;
|
|
|
|
data.Header.Name = name->text();
|
|
data.Header.Description = description->toPlainText();
|
|
data.CreatorEmail = email->text();
|
|
data.CreatorNick = nickName->text();
|
|
data.Header.Language = CloudDBCommon::cloudDBLangsIds.at(langCombo->currentIndex());
|
|
data.ChartSport = CloudDBCommon::cloudDBSportIds.at(sportCombo->currentIndex());
|
|
|
|
if (!update) {
|
|
appsettings->setCValue(athlete, GC_NICKNAME, data.CreatorNick);
|
|
appsettings->setCValue(athlete, GC_CLOUDDB_EMAIL, data.CreatorEmail);
|
|
}
|
|
accept();
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartObjectDialog::cancelClicked() {
|
|
reject();
|
|
}
|
|
|
|
|
|
void
|
|
CloudDBChartObjectDialog::emailTextChanged(QString text) {
|
|
|
|
if (text.isEmpty()) {
|
|
QMessageBox::warning(0, tr("Upload Chart"), QString(tr("Please enter a valid e-mail address.")));
|
|
} else {
|
|
emailOk = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudDBChartObjectDialog::emailEditingFinished() {
|
|
// validator check passed
|
|
emailOk = true;
|
|
}
|
|
|
|
void
|
|
CloudDBChartObjectDialog::nameTextChanged(QString text) {
|
|
|
|
if (text.isEmpty()) {
|
|
QMessageBox::warning(0, tr("Upload Chart"), QString(tr("Please enter a valid chart name with min. 5 characters length.")));
|
|
} else {
|
|
nameOk = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudDBChartObjectDialog::nameEditingFinished() {
|
|
// validator check passed
|
|
nameOk = true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|