diff --git a/.clang-tidy b/.clang-tidy index fd21b3531..543d5ca53 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -40,6 +40,7 @@ Checks: -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-use-enum-class, -hicpp-avoid-c-arrays, -hicpp-avoid-goto, -hicpp-braces-around-statements, diff --git a/.github/actions/cmake/action.yml b/.github/actions/cmake/action.yml index e822fb209..04c549162 100644 --- a/.github/actions/cmake/action.yml +++ b/.github/actions/cmake/action.yml @@ -66,6 +66,7 @@ runs: -B '${{github.workspace}}/build' \ -DCMAKE_BUILD_TYPE=Release \ -DQF_BUILD_QML_PLUGINS=ON \ + -DQF_WITH_LIBSHV=ON \ -DBUILD_TESTING=OFF \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DUSE_QT6=${{ inputs.use_qt6 }} \ diff --git a/.github/actions/run-linter/action.yml b/.github/actions/run-linter/action.yml index 93baab419..7d5cd38b4 100644 --- a/.github/actions/run-linter/action.yml +++ b/.github/actions/run-linter/action.yml @@ -20,11 +20,18 @@ runs: modules: qtserialport qtmultimedia additional_cmake_args: -DCMAKE_GLOBAL_AUTOGEN_TARGET=ON -DCMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF + # https://bugs.launchpad.net/ubuntu/+source/clazy/+bug/2086665/comments/4 + - name: Workaround for clazy + shell: bash + run: | + sudo apt-get remove gcc-14 + sudo apt-get autoremove + # workaround for clang-tidy false positive - uses: mjp41/workaround8649@c8550b715ccdc17f89c8d5c28d7a48eeff9c94a8 if: runner.os == 'Linux' with: - os: ubuntu-latest + os: ubuntu-24.04 - name: Build autogenerated stuff shell: bash diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d41591f4c..312183ca7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,8 +8,8 @@ on: jobs: clang-tidy: - name: clang-tidy / Ubuntu 22.04 - runs-on: ubuntu-22.04 + name: clang-tidy / Ubuntu 24.04 + runs-on: ubuntu-24.04 env: CC: clang CXX: clang++ @@ -26,8 +26,8 @@ jobs: exclude_files_pattern: ^quickevent/app/quickevent/plugins/Receipts/src/thirdparty/qrcodegen\.cpp$ clazy: - name: clazy / Ubuntu 22.04 - runs-on: ubuntu-22.04 + name: clazy / Ubuntu 24.04 + runs-on: ubuntu-24.04 env: CC: clang CXX: clang++ diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index ea681f520..fa02121e1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,6 +37,7 @@ jobs: -B '${{github.workspace}}/build' \ -DCMAKE_BUILD_TYPE=Release \ -DQF_BUILD_QML_PLUGINS=ON \ + -DQF_WITH_LIBSHV=ON \ -DBUILD_TESTING=OFF \ -DUSE_QT6=ON \ -DCOMMIT_SHA=${{ env.COMMIT_SHA }} \ diff --git a/.gitmodules b/.gitmodules index 6e2bec6bb..5006631e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "3rdparty/necrolog"] path = 3rdparty/necrolog url = https://github.com/fvacek/necrolog.git +[submodule "3rdparty/libshv"] + path = 3rdparty/libshv + url = https://github.com/silicon-heaven/libshv.git diff --git a/3rdparty/libshv b/3rdparty/libshv new file mode 160000 index 000000000..b1c3721d5 --- /dev/null +++ b/3rdparty/libshv @@ -0,0 +1 @@ +Subproject commit b1c3721d53f7390598e8f4fd8e5a3b225b58e5d0 diff --git a/CMakeLists.txt b/CMakeLists.txt index e511bd8f6..a59ce7871 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.18.4) set(QF_BUILD_QML_PLUGINS ON CACHE BOOL "Build with QML Plugins support") +set(QF_WITH_LIBSHV OFF CACHE BOOL "Build with libshv") project(quickbox LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) @@ -34,6 +35,9 @@ endif (WIN32) if (NOT TARGET libnecrolog) add_subdirectory(3rdparty/necrolog) endif() +if (QF_WITH_LIBSHV) + add_subdirectory(3rdparty/libshv) +endif() set(USE_QT6 ON) find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui Sql Qml Xml LinguistTools PrintSupport Svg SerialPort Multimedia Network) diff --git a/libqf/libqfcore/src/sql/qxrecchng.cpp b/libqf/libqfcore/src/sql/qxrecchng.cpp index fc0ae36b2..b534d9ed5 100644 --- a/libqf/libqfcore/src/sql/qxrecchng.cpp +++ b/libqf/libqfcore/src/sql/qxrecchng.cpp @@ -48,6 +48,7 @@ QString QxRecChng::recopToString(RecOp op) case RecOp::Update: return "Update"; case RecOp::Delete: return "Delete"; } + return QString(); } } diff --git a/libqf/libqfcore/src/sql/qxsql.cpp b/libqf/libqfcore/src/sql/qxsql.cpp index 844ffbf2d..45e55d111 100644 --- a/libqf/libqfcore/src/sql/qxsql.cpp +++ b/libqf/libqfcore/src/sql/qxsql.cpp @@ -1,6 +1,8 @@ #include "qxsql.h" #include +#include +#include #include #include @@ -11,6 +13,49 @@ namespace qf::core::sql { //================================================================ // QxSqlApi //================================================================ +namespace { + +class Transaction +{ +public: + Transaction(QSqlDatabase db) : m_db(db) { + if (!m_db.transaction()) { + qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); + throw std::runtime_error("BEGIN transaction error"); + } + } + ~Transaction() { + if (m_inTransaction) { + m_db.rollback(); + } + } + void commit() { + if (!m_db.commit()) { + qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); + throw std::runtime_error("COMMIT transaction error"); + } + m_inTransaction = false; + } +private: + QSqlDatabase m_db; + bool m_inTransaction = true; +}; +} +void QxSqlApi::transaction(const QString &query, const QVariantList ¶ms, QSqlDatabase db) +{ + Transaction transaction(db); + qf::core::sql::Query q(db); + q.prepare(query, qf::core::Exception::Throw); + for (const auto ¶m : params) { + auto m = param.toMap(); + for (const auto &[k, v] : m.asKeyValueRange()) { + q.bindValue(':' + k, v); + } + q.exec(qf::core::Exception::Throw); + } + transaction.commit(); +} + qint64 QxSqlApi::createRecord(const QString &table, const Record &record, const QString &issuer) { Q_UNUSED(issuer) @@ -63,7 +108,7 @@ bool QxSqlApi::updateRecord(const QString &table, qint64 id, const Record &recor .arg(table, keyVals.join(", "), QString::number(id)); ExecResult res = exec(qs, record); - return res.rowsAffected == 1; + return res.numRowsAffected == 1; } bool QxSqlApi::deleteRecord(const QString &table, qint64 id, const QString &issuer) @@ -72,7 +117,7 @@ bool QxSqlApi::deleteRecord(const QString &table, qint64 id, const QString &issu QString qs = QString("DELETE FROM %1 WHERE id = %2").arg(table, QString::number(id)); ExecResult res = exec(qs, {}); - return res.rowsAffected == 1; + return res.numRowsAffected == 1; } QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::optional &fields, const std::optional &id, const std::optional &limit) @@ -106,18 +151,15 @@ QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::op } //================================================================ -// QxSql +// QxSqlApiImpl //================================================================ -QxSql::QxSql(const QSqlDatabase &db) - : m_db(db) -{ - -} - -QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) +QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) { + // qDebug() << query << params; QSqlQuery q(m_db); - q.prepare(query); + if (!q.prepare(query)) { + throw qf::core::Exception(q.lastError().text()); + } for (const auto &[key, val] : params.asKeyValueRange()) { q.bindValue(QStringLiteral(":%1").arg(key), val); } @@ -126,7 +168,7 @@ QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) } QueryResult result; for (int i = 0; i < q.record().count(); ++i) { - result.columns.append(q.record().fieldName(i).toLower()); + result.fields.append(DbField{.name = q.record().fieldName(i).toLower()}); } while (q.next()) { QList row; @@ -138,10 +180,12 @@ QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) return result; } -ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) +ExecResult QxSqlApiImpl::exec(const QString &query, const QVariantMap ¶ms) { QSqlQuery q(m_db); - q.prepare(query); + if (!q.prepare(query)) { + throw qf::core::Exception(q.lastError().text()); + } for (const auto &[key, val] : params.asKeyValueRange()) { q.bindValue(QStringLiteral(":%1").arg(key), val); } @@ -149,44 +193,129 @@ ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) throw qf::core::Exception(q.lastError().text()); } ExecResult result; - result.rowsAffected = q.numRowsAffected(); + result.numRowsAffected = q.numRowsAffected(); + if (auto id = q.lastInsertId().toLongLong(); id > 0) { + result.lastInsertId = id; + } return result; } -qint64 QxSql::createRecord(const QString &table, const Record &record, const QString &issuer) +void QxSqlApiImpl::transaction(const QString &query, const QVariantList ¶ms) +{ + QxSqlApi::transaction(query, params, m_db); +} + +//================================================================ +// QxSql +//================================================================ +QxSql::QxSql(const QString &issuer, const QSqlDatabase &db, QObject *parent) + : QObject(parent) + , m_issuer(issuer) + , m_sqlApi(db) { - auto id = QxSqlApi::createRecord(table, record, issuer); - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, +} + +QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) +{ + return m_sqlApi.query(query, params); +} + +ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) +{ + return m_sqlApi.exec(query, params); +} + +QList QxSql::listRecords(const QString &table, const std::optional &fields, const std::optional &fromId, const std::optional &limit) +{ + return m_sqlApi.listRecords(table, fields, fromId, limit); +} + +void QxSql::transaction(const QString &query, const QVariantList ¶ms) +{ + m_sqlApi.transaction(query, params); +} + +qint64 QxSql::createRecord(const QString &table, const Record &record, QObject *source) +{ + auto id = m_sqlApi.createRecord(table, record, m_issuer); + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = record, .op = qf::core::sql::RecOp::Insert, - .issuer = issuer}); + .issuer = m_issuer}, + source); return id; } -bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer) +std::optional QxSql::readRecord(const QString &table, qint64 id, const std::optional &fields) { - auto ok = QxSqlApi::updateRecord(table, id, record, issuer); + return m_sqlApi.readRecord(table, id, fields); +} + +bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, QObject *source) +{ + auto ok = m_sqlApi.updateRecord(table, id, record, m_issuer); if (ok) { - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = record, .op = qf::core::sql::RecOp::Update, - .issuer = issuer}); + .issuer = m_issuer}, + source); } return ok; } -bool QxSql::deleteRecord(const QString &table, qint64 id, const QString &issuer) +bool QxSql::deleteRecord(const QString &table, qint64 id, QObject *source) { - auto ok = QxSqlApi::deleteRecord(table, id, issuer); + auto ok = m_sqlApi.deleteRecord(table, id, m_issuer); if (ok) { - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = {}, .op = qf::core::sql::RecOp::Delete, - .issuer = issuer}); + .issuer = m_issuer}, + source); } return ok; } + +void QxSql::emitRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = record, + .op = qf::core::sql::RecOp::Insert, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = record, + .op = qf::core::sql::RecOp::Update, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecDeleted(const QString &table, qint64 id, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = {}, + .op = qf::core::sql::RecOp::Delete, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecChng(const core::sql::QxRecChng &recchng, QObject *source) +{ + emit recChng(recchng, source); +} + } diff --git a/libqf/libqfcore/src/sql/qxsql.h b/libqf/libqfcore/src/sql/qxsql.h index 51e7c6fd0..04d493eeb 100644 --- a/libqf/libqfcore/src/sql/qxsql.h +++ b/libqf/libqfcore/src/sql/qxsql.h @@ -15,9 +15,14 @@ namespace qf::core::sql { using Record = QVariantMap; +struct DbField +{ + QString name; +}; + struct QFCORE_DECL_EXPORT QueryResult { - QStringList columns; + QList fields; QList> rows; std::optional record(int i) const @@ -27,8 +32,8 @@ struct QFCORE_DECL_EXPORT QueryResult } Record r; const auto &row = rows[i]; - for (int j = 0; j < columns.size(); ++j) { - r[columns[j]] = row.value(j); + for (int j = 0; j < fields.size(); ++j) { + r[fields[j].name] = row.value(j); } return r; } @@ -36,7 +41,8 @@ struct QFCORE_DECL_EXPORT QueryResult struct QFCORE_DECL_EXPORT ExecResult { - qint64 rowsAffected = 0; + qint64 numRowsAffected = 0; + std::optional lastInsertId = 0; }; class QFCORE_DECL_EXPORT QxSqlApi @@ -54,11 +60,12 @@ class QFCORE_DECL_EXPORT QxSqlApi { return listOneOrMoreRecords(table, fields, fromId, limit); } + void transaction(const QString &query, const QVariantList ¶ms, QSqlDatabase db); - virtual qint64 createRecord(const QString &table, const Record &record, const QString &issuer); - virtual std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); - virtual bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer); - virtual bool deleteRecord(const QString &table, qint64 id, const QString &issuer); + qint64 createRecord(const QString &table, const Record &record, const QString &issuer); + std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); + bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer); + bool deleteRecord(const QString &table, qint64 id, const QString &issuer); protected: QList listOneOrMoreRecords( const QString &table, @@ -67,23 +74,48 @@ class QFCORE_DECL_EXPORT QxSqlApi const std::optional &limit); }; -class QFCORE_DECL_EXPORT QxSql : public QObject, public QxSqlApi +class QxSqlApiImpl : public QxSqlApi { - Q_OBJECT public: - QxSql(const QSqlDatabase &db = QSqlDatabase()); - ~QxSql() override = default; - - Q_SIGNAL void dbRecChng(const qf::core::sql::QxRecChng &recchng); + QxSqlApiImpl(QSqlDatabase db) : m_db(db) {} QueryResult query(const QString &query, const QVariantMap ¶ms) override; ExecResult exec(const QString &query, const QVariantMap ¶ms) override; - - qint64 createRecord(const QString &table, const Record &record, const QString &issuer) override; - bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer) override; - bool deleteRecord(const QString &table, qint64 id, const QString &issuer) override; + void transaction(const QString &query, const QVariantList ¶ms); private: QSqlDatabase m_db; }; +class QFCORE_DECL_EXPORT QxSql : public QObject +{ + Q_OBJECT +public: + QxSql(const QString &issuer, const QSqlDatabase &db = {}, QObject *parent = nullptr); + ~QxSql() override = default; + + Q_SIGNAL void recChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + + QueryResult query(const QString &query, const QVariantMap ¶ms); + ExecResult exec(const QString &query, const QVariantMap ¶ms); + QList listRecords( + const QString &table, + const std::optional &fields = std::nullopt, + const std::optional &fromId = std::nullopt, + const std::optional &limit = std::nullopt); + void transaction(const QString &query, const QVariantList ¶ms); + + qint64 createRecord(const QString &table, const Record &record, QObject *source); + std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); + bool updateRecord(const QString &table, qint64 id, const Record &record, QObject *source); + bool deleteRecord(const QString &table, qint64 id, QObject *source); + + void emitRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + void emitRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + void emitRecDeleted(const QString &table, qint64 id, QObject *source); + void emitRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); +private: + QString m_issuer; + QxSqlApiImpl m_sqlApi; +}; + } // namespace qf::core::sql diff --git a/libqf/libqfcore/src/utils/table.cpp b/libqf/libqfcore/src/utils/table.cpp index bd7a7a759..deadd881b 100644 --- a/libqf/libqfcore/src/utils/table.cpp +++ b/libqf/libqfcore/src/utils/table.cpp @@ -1311,6 +1311,7 @@ QVariant Table::sumValue(int field_ix) const } return ret; } + namespace { void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QString &str) { @@ -1325,6 +1326,7 @@ void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QString & } } } + QDomElement Table::toHtmlElement(QDomDocument &owner_doc, const QString & col_names, TextExportOptions opts) const { QList ixs; diff --git a/libqf/libqfcore/src/utils/treetable.cpp b/libqf/libqfcore/src/utils/treetable.cpp index c18b2fdee..5641dea4f 100644 --- a/libqf/libqfcore/src/utils/treetable.cpp +++ b/libqf/libqfcore/src/utils/treetable.cpp @@ -581,8 +581,8 @@ QVariantMap TreeTable::keyvals(int row_ix) const { return rows().value(row_ix).toMap().value(KEY_KEYVALS).toMap(); } - -static inline QString line_indent(const QString &ind, int level) +namespace { +QString line_indent(const QString &ind, int level) { QString s; for(int i=0; i Application::readDbRecord(const QString &table, qint64 id, const std::optional &fields) const -{ - using namespace qf::core::sql; - QxSql sql; - return sql.readRecord(table, id, fields); -} - -bool Application::updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.updateRecord(table, id, record, uuidString()); -} - -bool Application::deleteDbRecord(const QString &table, qint64 id, QObject *source) -{ - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.deleteRecord(table, id, uuidString()); -} + if (m_qxSql == nullptr) { + m_qxSql = new qf::core::sql::QxSql(uuidString(), {}, this); + } + return m_qxSql; +} + +// qint64 Application::createDbRecord(const QString &table, const QVariantMap &record, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.createRecord(table, record, uuidString()); +// } + +// std::optional Application::readDbRecord(const QString &table, qint64 id, const std::optional &fields) const +// { +// using namespace qf::core::sql; +// QxSql sql; +// return sql.readRecord(table, id, fields); +// } + +// bool Application::updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.updateRecord(table, id, record, uuidString()); +// } + +// bool Application::deleteDbRecord(const QString &table, qint64 id, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.deleteRecord(table, id, uuidString()); +// } QString Application::versionString() const { return QCoreApplication::applicationVersion(); } -void Application::emitDbRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = record, - .op = qf::core::sql::RecOp::Insert, - .issuer = uuidString() - }, source); -} - -void Application::emitDbRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = record, - .op = qf::core::sql::RecOp::Update, - .issuer = uuidString() - }, source); -} - -void Application::emitDbRecDeleted(const QString &table, qint64 id, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = {}, - .op = qf::core::sql::RecOp::Delete, - .issuer = uuidString() - }, source); -} - -void Application::emitQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) -{ - emit qxRecChng(recchng, source); -} - Application *Application::instance(bool must_exist) { auto *ret = qobject_cast(Super::instance()); diff --git a/libqf/libqfgui/src/framework/application.h b/libqf/libqfgui/src/framework/application.h index 6b3893a8c..513007e7d 100644 --- a/libqf/libqfgui/src/framework/application.h +++ b/libqf/libqfgui/src/framework/application.h @@ -12,6 +12,8 @@ class QQmlEngine; +namespace qf::core::sql { class QxSql; } + namespace qf { namespace gui { namespace framework { @@ -28,15 +30,13 @@ class QFGUI_DECL_EXPORT Application : public QApplication explicit Application(int & argc, char ** argv); ~Application() override = default; - Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); - qint64 createDbRecord(const QString &table, const QVariantMap &record, QObject *source); - std::optional readDbRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt) const; - bool updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - bool deleteDbRecord(const QString &table, qint64 id, QObject *source); - void emitDbRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - void emitDbRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - void emitDbRecDeleted(const QString &table, qint64 id, QObject *source); - void emitQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + qf::core::sql::QxSql *qxSql(); + + // Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + // qint64 createDbRecord(const QString &table, const QVariantMap &record, QObject *source); + // std::optional readDbRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt) const; + // bool updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + // bool deleteDbRecord(const QString &table, qint64 id, QObject *source); Q_INVOKABLE QString versionString() const; public: @@ -53,6 +53,7 @@ class QFGUI_DECL_EXPORT Application : public QApplication static QString uuidString(); protected: MainWindow* m_frameWork = nullptr; + qf::core::sql::QxSql *m_qxSql = nullptr; }; }}} diff --git a/libqf/libqfgui/src/framework/datadialogwidget.h b/libqf/libqfgui/src/framework/datadialogwidget.h index 37c4c1d91..ec4cca8e0 100644 --- a/libqf/libqfgui/src/framework/datadialogwidget.h +++ b/libqf/libqfgui/src/framework/datadialogwidget.h @@ -26,7 +26,7 @@ class QFGUI_DECL_EXPORT DataDialogWidget : public DialogWidget qf::gui::model::DataDocument* dataDocument(bool throw_exc = qf::core::Exception::Throw); - Q_SLOT virtual bool load(const QVariant &id = QVariant(), int mode = qf::gui::model::DataDocument::ModeEdit); + virtual bool load(const QVariant &id = QVariant(), int mode = qf::gui::model::DataDocument::ModeEdit); bool acceptDialogDone(int result) Q_DECL_OVERRIDE; diff --git a/libqf/libqfgui/src/framework/dialogwidget.cpp b/libqf/libqfgui/src/framework/dialogwidget.cpp index f46cd8f23..8d5769997 100644 --- a/libqf/libqfgui/src/framework/dialogwidget.cpp +++ b/libqf/libqfgui/src/framework/dialogwidget.cpp @@ -9,13 +9,13 @@ using namespace qf::gui::framework; -DialogWidget::DialogWidget(QWidget *parent) : - Super(parent), IPersistentSettings(this) +DialogWidget::DialogWidget(QWidget *parent) + : Super(parent) + , IPersistentSettings(this) { } -DialogWidget::~DialogWidget() -= default; +DialogWidget::~DialogWidget() = default; bool DialogWidget::acceptDialogDone(int result) { @@ -23,12 +23,7 @@ bool DialogWidget::acceptDialogDone(int result) Q_UNUSED(result); return true; } -/* -QVariant DialogWidget::acceptDialogDone_qml(const QVariant &result) -{ - return acceptDialogDone(result.toBool()); -} -*/ + void DialogWidget::settleDownInDialog_qml(const QVariant &dlg) { auto *o = dlg.value(); diff --git a/libqf/libqfgui/src/framework/dialogwidget.h b/libqf/libqfgui/src/framework/dialogwidget.h index 13893c8a0..611be6b95 100644 --- a/libqf/libqfgui/src/framework/dialogwidget.h +++ b/libqf/libqfgui/src/framework/dialogwidget.h @@ -32,7 +32,7 @@ class QFGUI_DECL_EXPORT DialogWidget : public Frame, public IPersistentSettings typedef Frame Super; public: explicit DialogWidget(QWidget *parent = nullptr); - ~DialogWidget() Q_DECL_OVERRIDE; + ~DialogWidget() override; QF_PROPERTY_IMPL(QString, t, T, itle) QF_PROPERTY_IMPL(QString, i, I, conSource) diff --git a/libqf/libqfgui/src/framework/dockwidget.h b/libqf/libqfgui/src/framework/dockwidget.h index 1364ec5ee..bc4067236 100644 --- a/libqf/libqfgui/src/framework/dockwidget.h +++ b/libqf/libqfgui/src/framework/dockwidget.h @@ -22,12 +22,12 @@ class QFGUI_DECL_EXPORT DockWidget : public QDockWidget, public framework::IPers public: explicit DockWidget(const QString &window_title, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); explicit DockWidget(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) : DockWidget(QString(), parent, flags) {} - ~DockWidget() Q_DECL_OVERRIDE; + ~DockWidget() override; // visibilityChanged() exists already in QDockWidget //Q_SIGNAL void visibleChanged(bool visible); protected: - bool event(QEvent *ev) Q_DECL_OVERRIDE; + bool event(QEvent *ev) override; //void showEvent(QShowEvent *ev) Q_DECL_OVERRIDE; private: void setQmlWidget(QWidget *w); diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.cpp b/libqf/libqfgui/src/framework/ipersistentsettings.cpp index c639c374c..2d66f0235 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.cpp +++ b/libqf/libqfgui/src/framework/ipersistentsettings.cpp @@ -5,7 +5,7 @@ #include #include -using namespace qf::gui::framework; +namespace qf::gui::framework { IPersistentSettings::IPersistentSettings(QObject *controlled_object) : m_controlledObject(controlled_object) @@ -39,7 +39,8 @@ QString IPersistentSettings::persistentSettingsPath() return m_persistentSettingsPath; } -static void callMethodRecursively(QObject *obj, const char *method_name) +namespace { +void callMethodRecursively(QObject *obj, const char *method_name) { if(!obj) return; @@ -48,7 +49,7 @@ static void callMethodRecursively(QObject *obj, const char *method_name) QMetaMethod mm = obj->metaObject()->method(ix); mm.invoke(obj); } - Q_FOREACH(auto *o, obj->children()) { + for (auto *o : obj->children()) { //static int level = 0; //level++; //QString indent = QString(level, ' '); @@ -57,6 +58,7 @@ static void callMethodRecursively(QObject *obj, const char *method_name) //level--; } } +} void IPersistentSettings::loadPersistentSettingsRecursively() { @@ -103,28 +105,24 @@ QString IPersistentSettings::rawPersistentSettingsPath() QString persistent_id = persistentSettingsId(); QStringList raw_path; if(!persistent_id.isEmpty()) { - for(QObject *obj=m_controlledObject->parent(); obj!=nullptr; obj=obj->parent()) { + for(QObject* obj = m_controlledObject->parent(); obj != nullptr; obj = obj->parent()) { auto *ps = dynamic_cast(obj); if(ps) { QString pp = ps->rawPersistentSettingsPath(); - if(!pp.isEmpty()) + if(!pp.isEmpty()) { raw_path.insert(0, pp); - //qfWarning() << "reading property 'persistentSettingsId' error" << obj << "casted to IPersistentSettings" << ps; - //qfWarning() << "\tcorrect value should be:" << parent_id; + } break; } - QVariant vid = obj->property("persistentSettingsId"); - QString parent_id = vid.toString(); - if(!parent_id.isEmpty()) { - raw_path.insert(0, parent_id); - } - - // reading property using QQmlProperty is crashing my app Qt 5.3.1 commit a83826dad0f62d7a96f5a6093240e4c8f7f2e06e - //QQmlProperty p(obj, "persistentSettingsId"); - //QVariant v2 = p.read(); + QVariant vid = obj->property("persistentSettingsId"); + QString parent_id = vid.toString(); + if(!parent_id.isEmpty()) { + raw_path.insert(0, parent_id); + } } raw_path.append(persistent_id); } return raw_path.join('/'); } +} diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.h b/libqf/libqfgui/src/framework/ipersistentsettings.h index 239dae588..272bd7efd 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.h +++ b/libqf/libqfgui/src/framework/ipersistentsettings.h @@ -7,9 +7,7 @@ class QObject; -namespace qf { -namespace gui { -namespace framework { +namespace qf::gui::framework { class QFGUI_DECL_EXPORT IPersistentSettings { @@ -38,6 +36,6 @@ class QFGUI_DECL_EXPORT IPersistentSettings QString m_persistentSettingsPath; }; -}}} +} #endif diff --git a/libqf/libqfgui/src/model/sqldatadocument.h b/libqf/libqfgui/src/model/sqldatadocument.h index ee56ac5b5..2a2b755e1 100644 --- a/libqf/libqfgui/src/model/sqldatadocument.h +++ b/libqf/libqfgui/src/model/sqldatadocument.h @@ -3,9 +3,7 @@ #include "datadocument.h" #include "sqltablemodel.h" -namespace qf { -namespace gui { -namespace model { +namespace qf::gui::model { class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument { @@ -21,7 +19,7 @@ class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument qf::core::sql::QueryBuilder queryBuilder(); void setQueryBuilder(const qf::core::sql::QueryBuilder &qb); protected: - SqlTableModel* createModel(QObject *parent) Q_DECL_OVERRIDE; + SqlTableModel* createModel(QObject *parent) override; ///! load model persistent storage via model bool loadData() Q_DECL_OVERRIDE; @@ -35,5 +33,5 @@ class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument */ }; -}}} +} diff --git a/libqf/libqfgui/src/model/sqltablemodel.cpp b/libqf/libqfgui/src/model/sqltablemodel.cpp index 794a0b83e..e2d3d25dc 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.cpp +++ b/libqf/libqfgui/src/model/sqltablemodel.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -162,8 +164,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) int serial_ix = -1; bool serial_ix_explicitly_set = false; int primary_ix = -1; - //QSqlIndex pri_ix = ti.primaryIndex(); - //bool has_blob_field = false; + QVariantMap qx_record; for (const auto &fld : row_ref.fields()) { i++; if(fld.tableId() != table_id) @@ -198,6 +199,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) new_fld.setValue(v); //qfInfo() << "\t\t" << "val is QString:" << (v.metaType().id() == QMetaType::QString); rec.append(new_fld); + qx_record[fld.shortName().toLower()] = v; } } @@ -213,43 +215,26 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) } else { qs = sqldrv->sqlStatement(QSqlDriver::InsertStatement, table, rec, false); - //qs = fixSerialDefaultValue(qs, serial_ix, rec); } - if(qs.isEmpty()) + if(qs.isEmpty()) { continue; - /* - qfDebug() << "\texecuting prepared query:" << qs; - bool ok = q.prepare(qs); - if(!ok) { - qfError() << "Cannot prepare query:" << qs; } - else { - for(int i=0; i= 0 && !serial_ix_explicitly_set) { - QVariant v = q.lastInsertId(); - qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << v; - if(v.isValid()) { - row_ref.setValue(serial_ix, v); + auto insert_id = q.lastInsertId(); + qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << insert_id; + if(insert_id.isValid()) { + row_ref.setValue(serial_ix, insert_id); row_ref.setDirty(serial_ix, false); if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecInserted(table_id, v.value(), record_to_map(rec), this); + app->qxSql()->emitRecInserted(table_id, insert_id.value(), record_to_map(rec), this); } } else { @@ -283,7 +268,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) QSqlDriver *sqldrv = sql_conn.driver(); for (const auto &table_id : tableIds(m_table.fields())) { qfDebug() << "\ttableid:" << table_id; - //table = conn.fullTableNameToQtDriverTableName(table); + QVariantMap qx_record; QSqlRecord edit_rec; int i = -1; for (const auto &fld : row_ref.fields()) { @@ -301,12 +286,13 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) //qfDebug() << "\ttableid:" << tableid << "fullTableName:" << fld.fullTableName(); qfDebug() << "\tdirty field" << fld.name() << "type:" << fld.type().id() << "orig val:" << row_ref.origValue(i).toString() << "new val:" << v.toString(); //qfDebug().noSpace() << "\tdirty value: '" << v.toString() << "' isNull(): " << v.isNull() << " type(): " << v.type(); - QSqlField sqlfld(fld.shortName(), fld.type()); + QSqlField sqlfld(fld.shortName().toLower(), fld.type()); sqlfld.setValue(v); //if(sqlfld.type() == QVariant::ByteArray) // has_blob_field = true; qfDebug() << "\tfield is null: " << sqlfld.isNull(); edit_rec.append(sqlfld); + qx_record[fld.shortName()] = v; } if(!edit_rec.isEmpty()) { qfDebug() << "updating table edits:" << table_id; @@ -315,6 +301,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) query_str += " "; QSqlRecord where_rec; qfDebug() << "looking for primary index of table:" << table_id; + std::optional id_pri_key_value; for(const auto &fld_name : sql_conn.primaryIndexFieldNames(table_id)) { QString full_fld_name = table_id + '.' + fld_name; qfDebug() << "\t checking value of field:" << full_fld_name; @@ -328,6 +315,9 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) sqlfld.setValue(row_ref.origValue(fld_ix)); qfDebug() << "\tpri index field" << full_fld_name << "type:" << sqlfld.metaType().id() << "orig val:" << row_ref.origValue(fld_ix) << "current val:" << row_ref.value(fld_ix); where_rec.append(sqlfld); + if (auto id = sqlfld.value().toInt(); id > 0) { + id_pri_key_value = id; + } } QF_ASSERT(!where_rec.isEmpty(), QString("pri keys values not generated for table '%1'").arg(table_id), @@ -348,7 +338,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) if (num_rows_affected == 1) { if (where_rec.count() == 1) { if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecUpdated(table_id, where_rec.value(0).value(), record_to_map(edit_rec), this); + app->qxSql()->emitRecUpdated(table_id, where_rec.value(0).value(), record_to_map(edit_rec), this); } } } @@ -457,7 +447,7 @@ bool SqlTableModel::removeTableRow(int row_no, bool throw_exc) if (num_rows_affected == 1) { if (where_rec.count() == 1) { if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecDeleted(table_id, where_rec.value(0).value(), this); + app->qxSql()->emitRecDeleted(table_id, where_rec.value(0).value(), this); } } } else { diff --git a/libqf/libqfgui/src/model/sqltablemodel.h b/libqf/libqfgui/src/model/sqltablemodel.h index 439b87c6f..ce9e2313f 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.h +++ b/libqf/libqfgui/src/model/sqltablemodel.h @@ -10,12 +10,10 @@ #include #include -namespace qf { -namespace gui { -namespace sql { -class Connection; -} -namespace model { +namespace qf::core::sql { struct QxRecChng; } +namespace qf::gui::sql { class Connection; } + +namespace qf::gui::model { class QFGUI_DECL_EXPORT SqlTableModel : public TableModel { @@ -100,5 +98,5 @@ class QFGUI_DECL_EXPORT SqlTableModel : public TableModel QMap m_foreignKeyDependencies; }; -}}} +} diff --git a/libqf/libqfgui/src/model/tablemodel.cpp b/libqf/libqfgui/src/model/tablemodel.cpp index 2fff88940..1eebdbfca 100644 --- a/libqf/libqfgui/src/model/tablemodel.cpp +++ b/libqf/libqfgui/src/model/tablemodel.cpp @@ -672,7 +672,7 @@ QColor TableModel::contrastTextColor(const QColor &background_color) return (qGray(background_color.rgb()) < 128)? QColor(Qt::white) : QColor(Qt::black); } -void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) +void TableModel::applyQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) { // qfInfo() << __FUNCTION__ << source; if (source == this) { @@ -685,12 +685,28 @@ void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *s return i; } } + qfMessage() << "cannot find table row with id:" << id; } else { - qfWarning() << "cannot find table column:" << (table_name + ".id"); + // not every change is addressing every table model + qfMessage() << metaObject()->className() << objectName() << "cannot find table column:" << (table_name + ".id"); } return -1; }; - if (recchng.op == qf::core::sql::RecOp::Update) { + if (recchng.op == qf::core::sql::RecOp::Insert) { + if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { + // row exists already + return; + } + if (auto fld_ix = table().fields().fieldIndex(recchng.table + ".id"); fld_ix >= 0) { + beginInsertRows({}, 0, 0); + auto row = insertTableRow(0); + tableRowRef(row).setValue(fld_ix, recchng.id); + endInsertRows(); + qfMessage() << "reloading inserted row:" << row << "table:" << recchng.table; + reloadRow(row); + } + } + else if (recchng.op == qf::core::sql::RecOp::Update) { if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { for (const auto &[k, v] : recchng.record.asKeyValueRange()) { if (auto col_ix = columnIndexOfTableField(recchng.table + '.' + k); col_ix.has_value()) { @@ -707,12 +723,18 @@ void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *s } } else { // calculated column - qfInfo() << "reloading calculated field, row:" << row << (recchng.table + '.' + k) << "-->" << v; + qfMessage() << "reloading calculated field, row:" << row << (recchng.table + '.' + k) << "-->" << v; reloadRow(row); } } } } + else if (recchng.op == qf::core::sql::RecOp::Delete) { + if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { + qfMessage() << "deleteing row:" << row << (recchng.table + ".id") << recchng.id; + removeRowNoOverload(row, !qf::core::Exception::Throw); + } + } } QVariant TableModel::rawValueToEdit(int column_index, const QVariant &val) const diff --git a/libqf/libqfgui/src/model/tablemodel.h b/libqf/libqfgui/src/model/tablemodel.h index fc2c4b805..fded27d00 100644 --- a/libqf/libqfgui/src/model/tablemodel.h +++ b/libqf/libqfgui/src/model/tablemodel.h @@ -209,7 +209,7 @@ class QFGUI_DECL_EXPORT TableModel : public QAbstractTableModel static QColor contrastTextColor(const QColor &background_color); - void handleQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + void applyQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); protected: virtual void checkColumns(); void createColumnsFromTableFields(); diff --git a/libqf/libqfgui/src/tableview.cpp b/libqf/libqfgui/src/tableview.cpp index 3dabbcadf..17e40e82a 100644 --- a/libqf/libqfgui/src/tableview.cpp +++ b/libqf/libqfgui/src/tableview.cpp @@ -633,40 +633,37 @@ void TableView::paste() void TableView::setValueInSelection_helper(const QVariant &new_val) { + auto *sql_m = qobject_cast(tableModel()); + if (!sql_m) { + return; + } QModelIndexList lst = selectedIndexes(); QMap row_selections; foreach(const QModelIndex &ix, lst) { row_selections[ix.row()] << ix; } - QList selected_row_indexes = row_selections.keys(); - if(selected_row_indexes.count() == 1) { - int row_ix = selected_row_indexes.value(0); - foreach(const QModelIndex &ix, row_selections.value(row_ix)) { - model()->setData(ix, new_val); - } - } - else if(selected_row_indexes.count() > 1) { - qfc::sql::Connection conn; - auto *sql_m = qobject_cast(tableModel()); - if(sql_m) { - try { - conn = sql_m->sqlConnection(); - qfc::sql::Transaction transaction(conn); - QF_TIME_SCOPE(QString("Saving %1 rows").arg(selected_row_indexes.count())); - foreach(int row_ix, selected_row_indexes) { - foreach(const QModelIndex &ix, row_selections.value(row_ix)) { - model()->setData(ix, new_val); - } - sql_m->postRow(toTableModelRowNo(row_ix), qf::core::Exception::Throw); - } - transaction.commit(); - } - catch(qfc::Exception &e) { - dialogs::MessageBox::showException(this, e); + auto *proxy_m = qobject_cast(lastProxyModel()); + Q_ASSERT(proxy_m); + auto is_dynamic_sort_filter = proxy_m->dynamicSortFilter(); + proxy_m->setDynamicSortFilter(false); + qfc::sql::Connection conn; + try { + conn = sql_m->sqlConnection(); + qfc::sql::Transaction transaction(conn); + QF_TIME_SCOPE(QString("Saving %1 rows").arg(selected_row_indexes.count())); + for(const auto &[row_ix, row_indexes] : row_selections.asKeyValueRange()) { + for(const auto &ix : row_indexes) { + proxy_m->setData(ix, new_val); } + sql_m->postRow(toTableModelRowNo(row_ix), qf::core::Exception::Throw); } - update(); + transaction.commit(); + } + catch(qfc::Exception &e) { + dialogs::MessageBox::showException(this, e); } + proxy_m->setDynamicSortFilter(is_dynamic_sort_filter); + update(); } void TableView::setValueInSelection() @@ -1154,7 +1151,7 @@ void TableView::rowExternallySaved(const QVariant &id) qf::core::sql::Query q; bool ok = q.exec(query_str); if (!ok) { - qfInfo() << "Query:" << query_str; + qfMessage() << "Query:" << query_str; qfWarning() << "SQL error:" << q.lastErrorText(); return; } diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.h b/libquickevent/libquickeventgui/src/reportoptionsdialog.h index e25ef7ee2..e13fd6dad 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.h +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.h @@ -87,13 +87,13 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: explicit ReportOptionsDialog(QWidget *parent = nullptr); ~ReportOptionsDialog() override; - int exec() Q_DECL_OVERRIDE; + int exec() override; void setStartListPrintVacantsVisible(bool b); void setStartListForRelays(); - QString persistentSettingsPath() Q_DECL_OVERRIDE; - bool setPersistentSettingsId(const QString &id) Q_DECL_OVERRIDE; + QString persistentSettingsPath() override; + bool setPersistentSettingsId(const QString &id) override; Q_SIGNAL void persistentSettingsIdChanged(const QString &id); void setOptions(const Options &options); @@ -129,7 +129,7 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: static QString sqlWhereExpression(const Options &opts, const int stage_id); static QString getClassesForStartNumber(const int number, const int stage_id); protected: - //void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; + //void showEvent(QShowEvent *event) override; private: Ui::ReportOptionsDialog *ui; }; diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 5368f5fe7..219207bab 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -60,20 +60,18 @@ add_executable(quickevent plugins/Core/src/widgets/settingsdialog.cpp plugins/Core/src/widgets/settingsdialog.ui + plugins/Event/src/stageconfig.cpp plugins/Event/src/stageconfig.h plugins/Event/src/connectdbdialogwidget.cpp plugins/Event/src/connectdbdialogwidget.ui plugins/Event/src/connectionsettings.cpp plugins/Event/src/dbschema.cpp - plugins/Event/src/eventconfig.cpp + plugins/Event/src/appdbconfig.cpp plugins/Event/src/appdbconfig.h + plugins/Event/src/eventconfig.cpp plugins/Event/src/eventconfig.h plugins/Event/src/eventdialogwidget.cpp plugins/Event/src/eventdialogwidget.ui plugins/Event/src/eventplugin.cpp plugins/Event/src/openeventdialog.cpp plugins/Event/src/openeventdialog.ui - plugins/Event/src/stage.cpp - plugins/Event/src/stagedocument.cpp - plugins/Event/src/stagewidget.cpp - plugins/Event/src/stagewidget.ui plugins/Event/src/findregistrationedit.cpp plugins/Event/src/lentcardssettingspage.cpp plugins/Event/src/lentcardssettingspage.ui @@ -85,7 +83,10 @@ add_executable(quickevent plugins/Event/src/services/oresultsclient.cpp plugins/Event/src/services/oresultsclientwidget.cpp plugins/Event/src/services/oresultsclientwidget.ui + plugins/Event/src/services/oresultsconfig.cpp plugins/Event/src/services/oresultsconfig.h + plugins/Event/src/services/ofeed/ofeedclient.cpp + plugins/Event/src/services/ofeed/ofeedconfig.cpp plugins/Event/src/services/ofeed/ofeedconfig.h plugins/Event/src/services/ofeed/ofeedclientwidget.cpp plugins/Event/src/services/ofeed/ofeedclientwidget.ui plugins/Event/src/services/ofeed/ofeedwelcomedialog.h @@ -99,15 +100,15 @@ add_executable(quickevent plugins/Event/src/services/serviceswidget.cpp plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui - plugins/Event/src/services/qx/qxclientservice.cpp - plugins/Event/src/services/qx/qxclientservice.h - plugins/Event/src/services/qx/qxclientservicewidget.cpp - plugins/Event/src/services/qx/qxclientservicewidget.h - plugins/Event/src/services/qx/qxclientservicewidget.ui - plugins/Event/src/services/qx/qxlateregistrationswidget.h - plugins/Event/src/services/qx/qxlateregistrationswidget.cpp - plugins/Event/src/services/qx/qxlateregistrationswidget.ui - plugins/Event/src/services/qx/runchangedialog.h plugins/Event/src/services/qx/runchangedialog.cpp plugins/Event/src/services/qx/runchangedialog.ui + + plugins/Event/src/services/qx/qxnode.cpp + plugins/Event/src/services/qx/sqlapinode.cpp + plugins/Event/src/services/qx/sqlapi.cpp plugins/Event/src/services/qx/sqlapi.h + plugins/Event/src/services/qx/nodes.cpp + plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h + plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui + plugins/Event/src/services/qx/qxlateentrieswidget.h plugins/Event/src/services/qx/qxlateentrieswidget.cpp plugins/Event/src/services/qx/qxlateentrieswidget.ui + plugins/Event/src/services/qx/lateentrydialog.h plugins/Event/src/services/qx/lateentrydialog.cpp plugins/Event/src/services/qx/lateentrydialog.ui plugins/Event/src/services/qx/runchange.h plugins/Event/src/services/qx/runchange.cpp plugins/Event/src/services/punchingtest/punchingtestservice.h plugins/Event/src/services/punchingtest/punchingtestservice.cpp plugins/Event/src/services/punchingtest/punchingtestservicewidget.h plugins/Event/src/services/punchingtest/punchingtestservicewidget.cpp @@ -120,6 +121,7 @@ add_executable(quickevent plugins/Oris/src/txtimporter.cpp plugins/Oris/src/xmlimporter.cpp + plugins/Receipts/src/receiptsconfig.cpp plugins/Receipts/src/receiptsconfig.h plugins/Receipts/src/receiptsplugin.cpp plugins/Receipts/src/receiptsprinter.cpp plugins/Receipts/src/receiptsprinteroptions.cpp @@ -128,10 +130,11 @@ add_executable(quickevent plugins/Receipts/src/receiptssettings.cpp plugins/Receipts/src/receiptssettingspage.cpp plugins/Receipts/src/receiptssettingspage.ui - plugins/Receipts/src/thirdparty/qrcodegen.cpp plugins/Receipts/src/receiptswidget.cpp plugins/Receipts/src/receiptswidget.ui + plugins/Receipts/src/thirdparty/qrcodegen.cpp + plugins/Relays/src/addlegdialogwidget.cpp plugins/Relays/src/addlegdialogwidget.ui plugins/Relays/src/relaydocument.cpp @@ -274,6 +277,10 @@ qt6_add_lupdate(quickevent TS_FILES ${TS_FILES}) target_sources(quickevent PRIVATE ${QM_FILES}) target_include_directories(quickevent PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src) target_link_libraries(quickevent PUBLIC libquickeventcore libquickeventgui libqfgui libsiut) +if (QF_WITH_LIBSHV) + target_link_libraries(quickevent PUBLIC libshviotqt) + target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) +endif() # Extract version from appversion.h file(READ "${CMAKE_CURRENT_SOURCE_DIR}/src/appversion.h" APP_VERSION_H) diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardchecker.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardchecker.cpp index bbdf0d63b..4ca899c17 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardchecker.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardchecker.cpp @@ -82,7 +82,7 @@ int CardChecker::startTimeSec(int run_id) std::optional CardChecker::maximumCardCheckAdvanceSec() { - return getPlugin()->eventConfig()->maximumCardCheckAdvanceSec(); + return getPlugin()->eventConfig().maximumCardCheckAdvanceSec(); } quickevent::core::CourseDef CardChecker::courseCodesForRunId(int run_id) diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp index 11148b97a..04eff1545 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -115,7 +116,7 @@ int CardReaderPlugin::findRunId(int si_id, int si_finish_time, QString *err_msg) *err_msg = QString(); int si_finish_time_msec = getPlugin()->msecToStageStartAM(si_finish_time); qf::core::sql::Query q; - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if(is_relays) { q.exec("SELECT id, leg, startTimeMs, finishTimeMs FROM runs WHERE siId=" QF_IARG(si_id) " AND isRunning" @@ -280,7 +281,7 @@ int CardReaderPlugin::saveCardToSql(const quickevent::core::si::ReadCard &read_c {"runIdAssignError", read_card.runIdAssignError()}, {"data", qf::core::Utils::qvariantToJson(read_card.data())}, }; - auto id = app->createDbRecord("cards", rec, this); + auto id = app->qxSql()->createRecord("cards", rec, this); return id; } catch (const std::exception &e) { @@ -333,7 +334,7 @@ int CardReaderPlugin::savePunchRecordToSql(const quickevent::core::si::PunchReco {"timeMs", punch.timems()}, {"runTimeMs", punch.runtimems_isset()? punch.runtimems(): QVariant()}, }; - auto id = app->createDbRecord("punches", rec, this); + auto id = app->qxSql()->createRecord("punches", rec, this); return id; } catch (const std::exception &e) { @@ -406,7 +407,7 @@ void CardReaderPlugin::updateCheckedCardValuesSql(int card_id, const quickevent: {"notStart", false}, {"penaltyTimeMs", {}}, }; - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } if (auto missing_codes = checked_card.missingCodes(); !missing_codes.isEmpty()) { QStringList missing_str; @@ -417,7 +418,7 @@ void CardReaderPlugin::updateCheckedCardValuesSql(int card_id, const quickevent: QVariantMap rec { {"runIdAssignError", tr("Missing codes: %1").arg(missing_str.join(','))}, }; - app->updateDbRecord("cards", card_id, rec, this); + app->qxSql()->updateRecord("cards", card_id, rec, this); } } catch (const std::exception &e) { @@ -463,7 +464,7 @@ bool CardReaderPlugin::saveCardAssignedRunnerIdSql(int card_id, int run_id) {"runId", run_id}, {"runIdAssignTS", QDateTime::currentDateTime()}, }; - app->updateDbRecord("cards", card_id, rec, this); + app->qxSql()->updateRecord("cards", card_id, rec, this); return true; } catch (const std::exception &e) { @@ -540,7 +541,7 @@ void CardReaderPlugin::setStartTime(int relay_id, int leg, int start_time) { {"startTimeMs", start_time}, }; auto run_id = q.value(0).toInt(); - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } catch (const std::exception &e) { qfError() << "setStartTime(): Update runs error, query:" << e.what(); @@ -551,7 +552,7 @@ void CardReaderPlugin::setStartTime(int relay_id, int leg, int start_time) { bool CardReaderPlugin::processCardToRunAssignment(int card_id, int run_id) { qfLogFuncFrame(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if(is_relays) { qf::core::sql::Query q; q.execThrow("SELECT relayId, leg, startTimeMs FROM runs WHERE id=" + QString::number(run_id)); diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 45ec6ea0d..5b5b2534b 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -111,6 +112,7 @@ class Model : public quickevent::gui::og::SqlTableModel Model::Model(QObject *parent) : Super(parent) { + setIdColumnName("cards.id"); clearColumns(col_COUNT); setColumn(col_cards_id, ColumnDefinition("cards.id", "id").setReadOnly(true)); setColumn(col_cards_siId, ColumnDefinition("cards.siId", tr("SI")).setReadOnly(true).setCastType(qMetaTypeId())); @@ -261,7 +263,7 @@ CardReaderWidget::CardReaderWidget(QWidget *parent) } }, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &CardReaderWidget::onQxRecChng, Qt::QueuedConnection); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &CardReaderWidget::onQxRecChng, Qt::QueuedConnection); } CardReaderWidget::~CardReaderWidget() @@ -474,7 +476,7 @@ void CardReaderWidget::settleDownInPartWidget(::PartWidget *part_widget) a_tools->addActionInto(a); } } - connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &CardReaderWidget::onDbEventNotify, Qt::QueuedConnection); + // connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &CardReaderWidget::onDbEventNotify, Qt::QueuedConnection); } void CardReaderWidget::reset() @@ -488,7 +490,7 @@ void CardReaderWidget::reset() void CardReaderWidget::reload() { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); //QString driver_name = m_cardsModel->sqlConnection().driverName(); int current_stage = getPlugin()->currentStageId(); qfs::QueryBuilder qb; @@ -520,20 +522,20 @@ void CardReaderWidget::reload() void CardReaderWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { if(isVisible()) { - m_cardsModel->handleQxRecChng(recchng, source); + m_cardsModel->applyQxRecChng(recchng, source); } } -void CardReaderWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) -{ - Q_UNUSED(connection_id) - if(domain == QLatin1String(Event::EventPlugin::DBEVENT_CARD_READ)) { - int card_id = data.toInt(); - if(isVisible()) { - updateTableView(card_id); - } - } -} +// void CardReaderWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) +// { +// Q_UNUSED(connection_id) +// if(domain == QLatin1String(Event::EventPlugin::DBEVENT_CARD_READ)) { +// int card_id = data.toInt(); +// if(isVisible()) { +// updateTableView(card_id); +// } +// } +// } void CardReaderWidget::createActions() { @@ -817,7 +819,7 @@ void CardReaderWidget::assignRunnerToSelectedCard() QVariantMap rec { {"isRunning", true}, }; - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); // QString qs = "UPDATE runs SET isRunning=true WHERE competitorId=" QF_IARG(competitor_id) " AND stageId=" QF_IARG(stage_id); // q.execThrow(qs); } @@ -834,7 +836,7 @@ void CardReaderWidget::assignRunnerToSelectedCard() QVariantMap rec { {"siId", si_id}, }; auto run_id = q.value(0).toInt(); if (n++ == 0 || use_si_in_next_stages) { - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } } // QString qs = "UPDATE runs SET siId=" QF_IARG(si_id) " WHERE competitorId=" QF_IARG(competitor_id) " AND stageId=" QF_IARG(stage_id); diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h index 03b7a6aa5..8911758d9 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h @@ -68,8 +68,8 @@ class CardReaderWidget : public QFrame Q_SLOT void reload(); private: - void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); - void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); + void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + // void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); void appendLog(NecroLog::Level level, const QString &msg); void processDriverInfo(NecroLog::Level level, const QString &msg); void logDriverRawData(const QByteArray &data); diff --git a/quickevent/app/quickevent/plugins/Classes/src/classesplugin.cpp b/quickevent/app/quickevent/plugins/Classes/src/classesplugin.cpp index 148b738c4..4fb77506d 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/classesplugin.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/classesplugin.cpp @@ -93,7 +93,7 @@ void ClassesPlugin::createCourses(int stage_id, const QList &co { qfLogFuncFrame(); try { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); qf::core::sql::Transaction transaction(qf::core::sql::Connection::forName()); qf::core::sql::Query q; if (delete_current) diff --git a/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp b/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp index 8fc4af4be..a5929b302 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp @@ -50,6 +50,8 @@ using qf::gui::framework::getPlugin; using Event::EventPlugin; using Classes::ClassesPlugin; +namespace { + class CourseCodesTableModel : public qfm::SqlTableModel { Q_OBJECT @@ -98,6 +100,7 @@ class CourseCodesTableModel : public qfm::SqlTableModel return Super::data(index, role); } }; +} ClassesWidget::ClassesWidget(QWidget *parent) : Super(parent), @@ -156,9 +159,9 @@ ClassesWidget::ClassesWidget(QWidget *parent) : connect(ui->tblClasses, &qf::gui::TableView::currentRowChanged, this, &ClassesWidget::reloadCourseCodes); connect(ui->chkUseAllMaps, &QCheckBox::toggled, this, [this](bool checked) { auto evplugin = getPlugin(); - auto data = evplugin->stageData(selectedStageId()); - data.setUseAllMaps(checked); - evplugin->setStageData(selectedStageId(), data); + auto stage_config = evplugin->stageConfig(selectedStageId()); + stage_config.useAllMaps = checked; + evplugin->appDbConfig().setStageConfig(selectedStageId(), stage_config); }); } @@ -353,7 +356,7 @@ void ClassesWidget::reload() } m_courseItemDelegate->setCourses(courses); } - ui->chkUseAllMaps->setChecked(getPlugin()->stageData(stage_id).isUseAllMaps()); + ui->chkUseAllMaps->setChecked(getPlugin()->stageConfig(stage_id).useAllMaps); reloadCourseCodes(); } @@ -392,7 +395,8 @@ void ClassesWidget::importCourses(const QList &course_defs, con reload(); } -static QString normalize_course_name(const QString &course_name) +namespace { +QString normalize_course_name(const QString &course_name) { QString ret = qf::core::Collator::toAscii7(QLocale::Czech, course_name, false); ret.replace(' ', QString()); @@ -402,10 +406,11 @@ static QString normalize_course_name(const QString &course_name) ret.replace('-', '+'); return ret; } +} void ClassesWidget::import_ocad_txt() { - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->eventConfig().isRelays()) { QMessageBox::warning(this,tr("Warning"),tr("Import does not yet support relays.")); return; } @@ -493,7 +498,7 @@ void ClassesWidget::import_ocad_v8() lines << QString::fromUtf8(ba).trimmed(); } try { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QMap defined_courses_map; for(const auto &line : lines) { // [classname];coursename;[relay.leg];lenght_km;climb;S1;dist_1;code_1[;dist_n;code_n];dist_finish;F1 @@ -568,7 +573,8 @@ void ClassesWidget::import_ocad_v8() } } -static QString element_text(const QDomElement &parent, const QString &tag_name) +namespace { +QString element_text(const QDomElement &parent, const QString &tag_name) { QDomElement el = parent.firstChildElement(tag_name); if(el.isNull()) @@ -576,17 +582,18 @@ static QString element_text(const QDomElement &parent, const QString &tag_name) return el.text(); } -static QString dump_element(const QDomElement &el) +QString dump_element(const QDomElement &el) { QString ret; QTextStream s(&ret); el.save(s, QDomNode::EncodingFromDocument); return ret; } +} void ClassesWidget::import_ocad_iofxml_2() { - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->eventConfig().isRelays()) { QMessageBox::warning(this,tr("Warning"),tr("Import does not yet support relays.")); return; } @@ -735,7 +742,7 @@ void ClassesWidget::import_ocad_iofxml_3() coursedef.addClass(class_name); } } - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->eventConfig().isRelays()) { QDomNodeList ndlst = xdoc.elementsByTagName(QStringLiteral("TeamCourseAssignment")); QMap relay_courses; for (int i = 0; i < ndlst.count(); ++i) { diff --git a/quickevent/app/quickevent/plugins/Classes/src/drawing/classitem.cpp b/quickevent/app/quickevent/plugins/Classes/src/drawing/classitem.cpp index 07be89988..b59720aed 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/drawing/classitem.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/drawing/classitem.cpp @@ -305,8 +305,8 @@ QList ClassItem::findClashes(const QSet &clash_types) QList ret; auto *git = ganttItem(); for (int i = 0; i < git->startSlotItemCount(); ++i) { - StartSlotItem *slot_it = git->startSlotItemAt(i); - if(slot_it->data().isIgnoreClassClashCheck()) + auto *slot_it = git->startSlotItemAt(i); + if(slot_it->config().ignoreClassClashCheck) continue; for (int j = 0; j < slot_it->classItemCount(); ++j) { ClassItem *class_it = slot_it->classItemAt(j); @@ -508,9 +508,9 @@ void ClassItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) if(slot_it->classItemIndex(this) == 0) { // the edited start time was written to the database by the dialog, // move the whole slot to keep the layout consistent with a reload - StartSlotData sd = slot_it->data(); - sd.setStartOffset(qMax(dt.startTimeMin(), 0)); - slot_it->setData(sd); + auto sd = slot_it->config(); + sd.startOffset = qMax(dt.startTimeMin(), 0); + slot_it->setConfig(sd); } if(slot_it->classItemCount() > 1) { // start times of the classes chained after this one shift with its new size diff --git a/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.cpp b/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.cpp index eb70c0f76..08ab2602b 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.cpp @@ -63,9 +63,8 @@ StartSlotItem *GanttItem::addStartSlotItem() void GanttItem::load(int stage_id) { qfLogFuncFrame(); - Event::StageData stage_data = getPlugin()->stageData(stage_id); - DrawingConfig dc(stage_data.drawingConfig()); - QVariantList start_slot_list = dc.startSlots(); + const auto &stage_data = getPlugin()->stageConfig(stage_id); + // const auto &start_slot_list = stage_data.drawingConfig.startSlots; qfs::Query q(qfs::Connection::forName()); qf::core::sql::QueryBuilder qb1; @@ -110,11 +109,11 @@ void GanttItem::load(int stage_id) if(slot_ix > curr_slot_ix) { slot_item = addStartSlotItem(); curr_slot_ix = slot_ix; - StartSlotData sd(start_slot_list.value(slot_ix).toMap()); + auto sd = stage_data.drawingConfig.startSlotConfig(slot_ix); if(!cd.value(QStringLiteral("startTimeMin")).isNull()) - sd.setStartOffset(start_time_min); - slot_item->setData(sd); - curr_slot_end_min = sd.startOffset(); + sd.startOffset = start_time_min; + slot_item->setConfig(sd); + curr_slot_end_min = sd.startOffset; curr_slot_is_drawn = true; curr_course_id = -1; } @@ -129,9 +128,9 @@ void GanttItem::load(int stage_id) && (start_time_min == 0 || start_time_min == curr_slot_end_min); if(!chains_to_curr_slot) { slot_item = addStartSlotItem(); - StartSlotData sd; - sd.setStartOffset(start_time_min); - slot_item->setData(sd); + Event::StartSlotConfig sd; + sd.startOffset = start_time_min; + slot_item->setConfig(sd); curr_slot_end_min = start_time_min; curr_slot_is_drawn = false; } @@ -154,24 +153,15 @@ void GanttItem::save(int stage_id) { qfLogFuncFrame(); { - Event::StageData stage = getPlugin()->stageData(stage_id); - DrawingConfig dc(stage.drawingConfig()); - QVariantList start_slots; + auto *event = getPlugin(); + // auto event_config = config->eventConfig(); + auto stage_config = event->stageConfig(stage_id); + stage_config.drawingConfig.startSlots.clear(); for (int i = 0; i < startSlotItemCount(); ++i) { StartSlotItem *slot_it = startSlotItemAt(i); - StartSlotData sd = slot_it->data(); - start_slots << sd; + stage_config.drawingConfig.startSlots.push_back(slot_it->config()); } - dc.setStartSlots(start_slots); - auto dc_str = qf::core::Utils::qvariantToJson(dc); - - QString qs = "UPDATE stages SET drawingConfig=:drawingConfig WHERE id=:id"; - qfs::Query q(qfs::Connection::forName()); - q.prepare(qs, qf::core::Exception::Throw); - q.bindValue(":drawingConfig", dc_str); - q.bindValue(":id", stage_id); - q.exec(qf::core::Exception::Throw); - getPlugin()->clearStageDataCache(); + event->appDbConfig().setStageConfig(stage_id, stage_config); } { QString qs = "UPDATE classdefs SET" @@ -235,7 +225,7 @@ void GanttItem::checkClassClash() QList clashes; for (int i = 0; i < startSlotItemCount(); ++i) { StartSlotItem *slot_it = startSlotItemAt(i); - if(slot_it->data().isIgnoreClassClashCheck()) + if(slot_it->config().ignoreClassClashCheck) continue; for (int j = 0; j < slot_it->classItemCount(); ++j) { ClassItem *class_it = slot_it->classItemAt(j); @@ -243,7 +233,7 @@ void GanttItem::checkClassClash() for(ClassItem *other : clashing_class_list) { // every pair is found from both sides, record it once if(quintptr(class_it) < quintptr(other)) - clashes << ClassClash{class_it, other, class_it->clashWith(other)}; + clashes << ClassClash{.class1=class_it, .class2=other, .type=class_it->clashWith(other)}; } } } @@ -304,8 +294,3 @@ void GanttItem::moveStartSlotItem(int from_slot_ix, int to_slot_ix) checkClassClash(); } } - - - - - diff --git a/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.h b/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.h index 57148dd89..9be8b0437 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.h +++ b/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttitem.h @@ -3,6 +3,8 @@ #include "classitem.h" +#include + #include #include @@ -18,14 +20,6 @@ class StartSlotItem; class ClassItem; class GanttRuler; -class DrawingConfig : public QVariantMap -{ -public: - explicit DrawingConfig(const QVariantMap &m = QVariantMap()) : QVariantMap(m) {} - - QF_VARIANTMAP_FIELD(QVariantList, s, setS, tartSlots) -}; - class GanttItem : public QGraphicsRectItem, public IGanttItem { private: @@ -53,7 +47,7 @@ class GanttItem : public QGraphicsRectItem, public IGanttItem StartSlotItem* addStartSlotItem(); private: QList m_startSlotItems; - DrawingConfig m_drawingConfig; + Event::DrawingConfig m_drawingConfig; GanttRuler *m_ganttRuler; QSet m_clashTypesToCheck; }; diff --git a/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttscene.cpp b/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttscene.cpp index 1777644a1..ef7f94eb2 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttscene.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/drawing/ganttscene.cpp @@ -23,8 +23,8 @@ GanttScene::GanttScene(QObject * parent) void GanttScene::load(int stage_id) { clear(); - Event::StageData stage_data = getPlugin()->stageData(stage_id); - m_useAllMaps = stage_data.isUseAllMaps(); + const auto &stage_data = getPlugin()->stageConfig(stage_id); + m_useAllMaps = stage_data.useAllMaps; m_ganttItem = new GanttItem(); addItem(m_ganttItem); m_ganttItem->load(stage_id); diff --git a/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotheader.cpp b/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotheader.cpp index c1dd9236b..070ab25b3 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotheader.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotheader.cpp @@ -51,8 +51,6 @@ void paintItemRecursively(QGraphicsItem *it, QPainter *painter, QStyleOptionGrap painter->restore(); } -} - class LockItem : public QGraphicsRectItem { Q_DECLARE_TR_FUNCTIONS(drawing::ClassdefsLockItem) @@ -328,6 +326,7 @@ class SpinnerItem : public QGraphicsRectItem SpinButton *m_btnInc; SpinValueItem *m_value; }; +} StartSlotHeader::StartSlotHeader(StartSlotItem *parent) : Super(parent), IGanttItem(this) @@ -388,7 +387,7 @@ void StartSlotHeader::updateGeometry() m_lockItem->setPos(0, row_h); auto *start_spinner = static_cast(m_startSpinner); start_spinner->setGeometry(QRectF(col1_w, 0, 2 * row_h, row_h)); - start_spinner->setValue(QString::number(slot_it->data().startOffset())); + start_spinner->setValue(QString::number(slot_it->config().startOffset)); auto *interval_spinner = static_cast(m_intervalSpinner); interval_spinner->setGeometry(QRectF(col1_w, row_h, 2 * row_h, row_h)); if(slot_it->isStartIntervalUniform()) { @@ -416,8 +415,8 @@ void StartSlotHeader::updateGeometry() void StartSlotHeader::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - const StartSlotData &dt = startSlotItem()->data(); - if(dt.isIgnoreClassClashCheck()) { + const auto &dt = startSlotItem()->config(); + if(dt.ignoreClassClashCheck) { QColor c("khaki"); c.setAlpha(128); QRectF r = rect(); @@ -436,7 +435,7 @@ void StartSlotHeader::paint(QPainter *painter, const QStyleOptionGraphicsItem *o void StartSlotHeader::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { - StartSlotData dt = startSlotItem()->data(); + const auto &dt = startSlotItem()->config(); QMenu menu; QAction *a_append_start_slot = menu.addAction(tr("Append start slot")); QAction *a_set_start = menu.addAction(tr("Set slot start offset")); @@ -445,7 +444,7 @@ void StartSlotHeader::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) //a_locked->setCheckable(true); QAction *a_clash = menu.addAction(tr("Ignore class clash check")); a_clash->setCheckable(true); - a_clash->setChecked(dt.isIgnoreClassClashCheck()); + a_clash->setChecked(dt.ignoreClassClashCheck); QAction *a = menu.exec(event->screenPos()); if(a == a_append_start_slot) { auto *gi = ganttItem(); @@ -463,7 +462,7 @@ void StartSlotHeader::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) o = o->parent(); } bool ok; - int i = QInputDialog::getInt(w, tr("InputDialog"), tr("Start slot offset [min]:"), dt.startOffset(), 0, 1000000, 1, &ok); + int i = QInputDialog::getInt(w, tr("InputDialog"), tr("Start slot offset [min]:"), dt.startOffset, 0, 1000000, 1, &ok); if(ok) { startSlotItem()->setStartOffset(i); } diff --git a/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.cpp b/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.cpp index 1b24cc949..426a7ccb5 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.cpp @@ -61,10 +61,10 @@ ClassItem *StartSlotItem::takeClassItemAt(int ix) void StartSlotItem::setStartOffset(int start_offset) { start_offset = std::max(start_offset, 0); - StartSlotData dt = data(); - if(dt.startOffset() != start_offset) { - dt.setStartOffset(start_offset); - setData(dt); + auto dt = config(); + if(dt.startOffset != start_offset) { + dt.startOffset = start_offset; + setConfig(dt); ganttScene()->setDirty(true); updateGeometry(); ganttItem()->checkClassClash(); @@ -73,8 +73,8 @@ void StartSlotItem::setStartOffset(int start_offset) int StartSlotItem::startOffset() const { - StartSlotData dt = data(); - return dt.startOffset(); + auto dt = config(); + return dt.startOffset; } void StartSlotItem::setStartInterval(int interval_min) @@ -112,16 +112,16 @@ bool StartSlotItem::isStartIntervalUniform() const bool StartSlotItem::isIgnoreClassClashCheck() const { - return data().isIgnoreClassClashCheck(); + return config().ignoreClassClashCheck; } void StartSlotItem::setIgnoreClassClashCheck(bool b) { - auto dt = data(); - if(dt.isIgnoreClassClashCheck() == b) + auto dt = config(); + if(dt.ignoreClassClashCheck == b) return; - dt.setIgnoreClassClashCheck(b); - setData(dt); + dt.ignoreClassClashCheck = b; + setConfig(dt); ganttScene()->setDirty(true); updateGeometry(); ganttItem()->checkClassClash(); @@ -160,22 +160,22 @@ ClassItem *StartSlotItem::addClassItem() return it; } -const StartSlotData& StartSlotItem::data() const +const Event::StartSlotConfig& StartSlotItem::config() const { - return m_data; + return m_config; } -void StartSlotItem::setData(const StartSlotData &data) +void StartSlotItem::setConfig(const Event::StartSlotConfig &data) { - m_data = data; + m_config = data; } void StartSlotItem::updateGeometry() { qfLogFuncFrame(); - auto dt = data(); - int pos_x = minToPx(dt.startOffset()); + const auto &dt = config(); + int pos_x = minToPx(dt.startOffset); double h = m_header->minHeight(); for (int i = 0; i < classItemCount(); ++i) { ClassItem *it = classItemAt(i); @@ -197,13 +197,6 @@ void StartSlotItem::updateGeometry() //qfInfo() << r.left() << r.top() << r.width() << r.height(); setRect(r); m_header->updateGeometry(); - /* - setAcceptDrops(!isLocked()); - for (int i = 0; i < classItemCount(); ++i) { - ClassItem *it = classItemAt(i); - it->setAcceptDrops(!isLocked()); - } - */ } void StartSlotItem::setClassAreaWidth(int px) @@ -251,4 +244,3 @@ void StartSlotItem::dropEvent(QGraphicsSceneDragDropEvent *event) m_dragIn = false; update(); } - diff --git a/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.h b/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.h index 9da1b2be5..472af5805 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.h +++ b/quickevent/app/quickevent/plugins/Classes/src/drawing/startslotitem.h @@ -3,6 +3,8 @@ #include "iganttitem.h" +#include + #include #include @@ -14,22 +16,12 @@ class ClassItem; class GanttItem; class StartSlotHeader; -class StartSlotData : public QVariantMap -{ -public: - explicit StartSlotData(const QVariantMap &m = QVariantMap()) : QVariantMap(m) {} - - QF_VARIANTMAP_FIELD(int, s, setS, tartOffset) - //QF_VARIANTMAP_FIELD(bool, is, set, Locked) - QF_VARIANTMAP_FIELD(bool, is, set, IgnoreClassClashCheck) -}; - class StartSlotItem : public QGraphicsRectItem, public IGanttItem { private: typedef QGraphicsRectItem Super; public: - StartSlotItem(QGraphicsItem * parent = 0); + StartSlotItem(QGraphicsItem * parent = nullptr); QF_FIELD_IMPL2(int, s, S, lotNumber, 0) @@ -59,15 +51,15 @@ class StartSlotItem : public QGraphicsRectItem, public IGanttItem void updateGeometry(); void setClassAreaWidth(int px); - const StartSlotData& data() const; - void setData(const StartSlotData &data); + const Event::StartSlotConfig& config() const; + void setConfig(const Event::StartSlotConfig &data); void dragEnterEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE; void dragMoveEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE; void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE; void dropEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE; private: - StartSlotData m_data; + Event::StartSlotConfig m_config; QList m_classItems; StartSlotHeader *m_header; bool m_dragIn = false; diff --git a/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp b/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp index 05d6ebe35..f3f92745a 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp @@ -19,6 +19,7 @@ namespace qfw = qf::gui; namespace qfm = qf::gui::model; namespace qfs = qf::core::sql; +namespace { class CoursesTableModel : public qfm::SqlTableModel { Q_OBJECT @@ -66,6 +67,7 @@ class CoursesTableModel : public qfm::SqlTableModel return Super::data(index, role); } }; +} EditCoursesWidget::EditCoursesWidget(int stage_id, QWidget *parent) : Super(parent) @@ -94,7 +96,7 @@ EditCoursesWidget::EditCoursesWidget(int stage_id, QWidget *parent) QHeaderView *hh = ui->tblCourses->horizontalHeader(); hh->setSectionHidden(CoursesTableModel::Col_runCount, true); - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->appDbConfig().eventConfig().isRelays()) { ui->cbRunnersCount->setVisible(false); } else { connect (ui->cbRunnersCount,&QCheckBox::clicked,this,&EditCoursesWidget::updateQuery); @@ -135,7 +137,7 @@ void EditCoursesWidget::updateQuery() qfs::QueryBuilder qb; - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->appDbConfig().eventConfig().isRelays()) { qb.select2("courses", "*") .select("0 AS run_count") .select(code_list_query + "AS code_list") @@ -151,7 +153,7 @@ void EditCoursesWidget::updateQuery() .join("courses.id", "classdefs.courseId", qf::core::sql::QueryBuilder::INNER_JOIN) .join("classdefs.classId", "classes.id") .join("classes.id", "competitors.classId") - .joinRestricted("competitors.id", "runs.competitorId", "runs.isRunning") + .joinRestricted("competitors.id", "runs.competitorId", "runs.isRunning AND runs.stageId=" QF_IARG(m_stageId)) .where("classdefs.stageId=" QF_IARG(m_stageId)) .groupBy("courses.id") .orderBy("courses.name"); diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp index 58d837025..cae50bfde 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp @@ -1,5 +1,6 @@ #include "competitordocument.h" +#include #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include @@ -92,7 +94,7 @@ bool CompetitorDocument::saveData() getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_RUN_CHANGED, QVariantList {run_id, rec}); // new update API auto *app = qf::gui::framework::Application::instance(); - app->emitDbRecInserted( "runs", run_id, rec, this); + app->qxSql()->emitRecInserted( "runs", run_id, rec, this); } } } @@ -103,7 +105,7 @@ bool CompetitorDocument::saveData() auto *app = qf::gui::framework::Application::instance(); QVariantMap rec { {"siId", siid()}, }; for (const auto &[run_id, _] : old_records.asKeyValueRange()) { - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } // int competitor_id = dataId().toInt(); // qf::core::sql::Query q(sqlModel()->connectionName()); @@ -175,7 +177,7 @@ bool CompetitorDocument::dropData() getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_RUN_CHANGED, QVariantList {run_id, {}}); // new update API auto *app = qf::gui::framework::Application::instance(); - app->emitDbRecDeleted("runs", run_id, this); + app->qxSql()->emitRecDeleted("runs", run_id, this); } } } @@ -212,4 +214,101 @@ QVariant CompetitorDocument::siid() const return value(SIID); } +QList CompetitorDocument::possibleStartTimesMs(int run_id) +{ + using namespace qf::core::sql; + + int class_id; + int stage_id; + int start_time; + QString club_abbr; + { + qf::core::sql::QueryBuilder qb; + qb.select2("runs", "competitorId, stageId, startTimeMs") + .select2("competitors", "classId, registration") + .from("runs") + .joinRestricted("runs.competitorId", "competitors.id", "runs.id=" QF_IARG(run_id), qf::core::sql::QueryBuilder::INNER_JOIN) + .where("runs.isRunning"); + Query q; + q.exec(qb.toString(), qf::core::Exception::Throw); + if (q.next()) { + class_id = q.value("classId").toInt(); + stage_id = q.value("stageId").toInt(); + start_time = q.value("startTimeMs").toInt(); + club_abbr = q.value("registration").toString().mid(0, 3).toUpper(); + } + else { + return {}; + } + } + + Classes::ClassDef class_def; + class_def.load(class_id, stage_id, false); + + if (class_def.classInterval == 0) { + return {}; + } + + struct S { + QString club; + int start_time = 0; + + bool operator<(const S &o) const { return start_time < o.start_time; } + }; + QList runs; + { + qf::core::sql::QueryBuilder qb; + qb.select2("runs", "startTimeMs") + .select2("competitors", "registration") + .from("runs") + .joinRestricted("runs.competitorId", "competitors.id", "competitors.classId=" QF_IARG(class_id), qf::core::sql::QueryBuilder::INNER_JOIN) + .where("runs.stageId=" QF_IARG(stage_id)) + .where("runs.isRunning") + .orderBy("runs.startTimeMs"); + //qfInfo() << qb.toString(); + Query q; + q.exec(qb.toString(), qf::core::Exception::Throw); + while (q.next()) { + //auto start_time = q.value("startTimeMs").toInt(); + //if (start_time < class_def.classStartFirst) { + // continue; + //} + runs << S { + .club = q.value("registration").toString().mid(0, 3).toUpper(), + .start_time = q.value("startTimeMs").toInt() + }; + } + std::sort(runs.begin(), runs.end()); + } + QList ret; + auto start_interval_ms = class_def.classInterval * 60 * 1000; + for (auto stime = class_def.classStartFirst; stime <= class_def.classStartLast; stime += class_def.classInterval) { + S r { .club = {}, .start_time = stime }; + auto [lo, up] = std::equal_range(runs.begin(), runs.end(), r); + if (lo == up || stime == start_time) { + // stime does not exist or it is my current start_time, should be inserted before up + // check next club + // qfInfo() << "free stime:" << quickevent::core::og::TimeMs(stime).toString(); + if (up != runs.end()) { + // qfInfo() << "next club:" << up->club; + if (up->club == club_abbr && (up->start_time - stime) <= start_interval_ms) { + // qfInfo() << "same next club:" << club_abbr; + continue; + } + } + // check prev club + if (up != runs.begin()) { + up--; + // qfInfo() << "prev club:" << up->club; + if (up->club == club_abbr && (stime - up->start_time) <= start_interval_ms) { + // qfInfo() << "same prev club:" << club_abbr; + continue; + } + } + ret << stime; + } + } + return ret; +} + diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h index a394edbe4..8894d63d3 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h @@ -1,5 +1,4 @@ -#ifndef COMPETITORS_COMPETITORDOCUMENT_H -#define COMPETITORS_COMPETITORDOCUMENT_H +#pragma once #include @@ -15,13 +14,13 @@ class CompetitorDocument : public qf::gui::model::SqlDataDocument public: CompetitorDocument(QObject *parent = nullptr); - //bool isSaveSiidToRuns() const {return m_saveSiidToRuns;} void setEmitDbEventsOnSave(bool b) {m_isEmitDbEventsOnSave = b;} - //void setSiid(const QVariant &siid, bool save_siid_to_runs); void setSiid(const QVariant &siid); QVariant siid() const; const QVector& runsIds() const {return m_runsIds;} + + static QList possibleStartTimesMs(int run_id); protected: bool loadData() override; bool saveData() override; @@ -33,4 +32,3 @@ class CompetitorDocument : public qf::gui::model::SqlDataDocument } -#endif // COMPETITORS_COMPETITORDOCUMENT_H diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp index 854300cf1..df64ef73c 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp @@ -155,7 +155,7 @@ CompetitorWidget::CompetitorWidget(QWidget *parent) : ui(new Ui::CompetitorWidget) { qfLogFuncFrame(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); setPersistentSettingsId("CompetitorWidget"); ui->setupUi(this); @@ -205,7 +205,7 @@ CompetitorWidget::CompetitorWidget(QWidget *parent) : connect(ui->tblRuns, &qfw::TableView::editCellRequest, this, [this](QModelIndex index) { auto col = index.column(); if(col == CompetitorRunsModel::col_runs_startTimeMs) { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if (!is_relays) { saveData(); auto row = ui->tblRuns->tableRow(); @@ -376,97 +376,7 @@ QString CompetitorWidget::guessClassFromRegistration(const QString ®istration QList CompetitorWidget::possibleStartTimesMs(int run_id) { - int class_id; - int stage_id; - int start_time; - QString club_abbr; - { - qf::core::sql::QueryBuilder qb; - qb.select2("runs", "competitorId, stageId, startTimeMs") - .select2("competitors", "classId, registration") - .from("runs") - .joinRestricted("runs.competitorId", "competitors.id", "runs.id=" QF_IARG(run_id), qf::core::sql::QueryBuilder::INNER_JOIN) - .where("runs.isRunning"); - qfs::Query q; - q.exec(qb.toString(), qf::core::Exception::Throw); - if (q.next()) { - class_id = q.value("classId").toInt(); - stage_id = q.value("stageId").toInt(); - start_time = q.value("startTimeMs").toInt(); - club_abbr = q.value("registration").toString().mid(0, 3).toUpper(); - } - else { - return {}; - } - } - - Classes::ClassDef class_def; - class_def.load(class_id, stage_id, false); - - if (class_def.classInterval == 0) { - return {}; - } - - struct S { - QString club; - int start_time = 0; - - bool operator<(const S &o) const { return start_time < o.start_time; } - }; - QList runs; - { - qf::core::sql::QueryBuilder qb; - qb.select2("runs", "startTimeMs") - .select2("competitors", "registration") - .from("runs") - .joinRestricted("runs.competitorId", "competitors.id", "competitors.classId=" QF_IARG(class_id), qf::core::sql::QueryBuilder::INNER_JOIN) - .where("runs.stageId=" QF_IARG(stage_id)) - .where("runs.isRunning") - .orderBy("runs.startTimeMs"); - //qfInfo() << qb.toString(); - qfs::Query q; - q.exec(qb.toString(), qf::core::Exception::Throw); - while (q.next()) { - //auto start_time = q.value("startTimeMs").toInt(); - //if (start_time < class_def.classStartFirst) { - // continue; - //} - runs << S { - .club = q.value("registration").toString().mid(0, 3).toUpper(), - .start_time = q.value("startTimeMs").toInt() - }; - } - std::sort(runs.begin(), runs.end()); - } - QList ret; - auto start_interval_ms = class_def.classInterval * 60 * 1000; - for (auto stime = class_def.classStartFirst; stime <= class_def.classStartLast; stime += class_def.classInterval) { - S r { .club = {}, .start_time = stime }; - auto [lo, up] = std::equal_range(runs.begin(), runs.end(), r); - if (lo == up || stime == start_time) { - // stime does not exist or it is my current start_time, should be inserted before up - // check next club - // qfInfo() << "free stime:" << quickevent::core::og::TimeMs(stime).toString(); - if (up != runs.end()) { - qfInfo() << "next club:" << up->club; - if (up->club == club_abbr && (up->start_time - stime) <= start_interval_ms) { - // qfInfo() << "same next club:" << club_abbr; - continue; - } - } - // check prev club - if (up != runs.begin()) { - up--; - // qfInfo() << "prev club:" << up->club; - if (up->club == club_abbr && (stime - up->start_time) <= start_interval_ms) { - // qfInfo() << "same prev club:" << club_abbr; - continue; - } - } - ret << stime; - } - } - return ret; + return Competitors::CompetitorDocument::possibleStartTimesMs(run_id); } void CompetitorWidget::onRegistrationSelected(const QVariantMap &values) @@ -477,7 +387,7 @@ void CompetitorWidget::onRegistrationSelected(const QVariantMap &values) qfDebug() << "\t" << s << "->" << values.value(s); doc->setValue(s, values.value(s)); } - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if(!is_relays) { // if no class is set, guess class from registration if(ui->cbxClass->currentText().isEmpty()) { @@ -517,7 +427,7 @@ void CompetitorWidget::save() bool CompetitorWidget::saveData() { try { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); auto *doc = qobject_cast(dataController()->document()); if(!is_relays && doc->value(QStringLiteral("classId")).toInt() == 0) { qf::gui::dialogs::MessageBox::showWarning(this, tr("Class should be entered.")); @@ -537,7 +447,7 @@ bool CompetitorWidget::acceptDialogDone(int result) { if (result == QDialog::Accepted) { auto *doc = qobject_cast(dataController()->document()); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if (doc->mode() != Competitors::CompetitorDocument::ModeDelete) { if (doc->value("classId").toInt() == 0 && !is_relays) { QMessageBox::information(this, tr("Competitor form check"), tr("Class must be set.")); diff --git a/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.cpp b/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.cpp index 7d605b9ba..28efcbfc2 100644 --- a/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.cpp +++ b/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.cpp @@ -1,6 +1,7 @@ #include "appstatusbar.h" #include +#include #include namespace Core { @@ -16,10 +17,11 @@ AppStatusBar::AppStatusBar(QWidget *parent) m_lblEvent->setFrameShadow(QFrame::Plain); addPermanentWidget(m_lblEvent); - m_lblStage = new QLabel(this); - m_lblStage->setFrameShape(QFrame::Panel); - m_lblStage->setFrameShadow(QFrame::Plain); - addPermanentWidget(m_lblStage); + m_btCurrentStage = new QPushButton(this); + m_btCurrentStage->setToolTip(tr("Set current stage")); + m_btCurrentStage->setStyleSheet("background: lime;"); + connect(m_btCurrentStage, &QPushButton::clicked, this, &AppStatusBar::stageClicked); + addPermanentWidget(m_btCurrentStage); m_progress = new QProgressBar(this); m_progress->setMinimumWidth(200); @@ -37,16 +39,20 @@ AppStatusBar::AppStatusBar(QWidget *parent) AppStatusBar::~AppStatusBar() = default; -void AppStatusBar::setEventName(const QString &event_name) +QString AppStatusBar::eventDbName() const +{ + return m_lblEvent->text(); +} + +void AppStatusBar::setEventDbName(const QString &event_name) { - m_eventName = event_name; m_lblEvent->setText(event_name); } void AppStatusBar::setStageNo(int stage_no) { m_stageNo = stage_no; - m_lblStage->setText(tr("E%1").arg(m_stageNo)); + m_btCurrentStage->setText(tr("E%1").arg(m_stageNo)); } void AppStatusBar::showProgress(const QString &msg, int completed, int total) diff --git a/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.h b/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.h index 869dc0b70..33cdbe31a 100644 --- a/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.h +++ b/quickevent/app/quickevent/plugins/Core/src/widgets/appstatusbar.h @@ -6,39 +6,34 @@ class QLabel; class QProgressBar; +class QPushButton; namespace Core { class AppStatusBar : public qf::gui::StatusBar { Q_OBJECT - - Q_PROPERTY(QString eventName READ eventName WRITE setEventName NOTIFY eventNameChanged) - Q_PROPERTY(int stageNo READ stageNo WRITE setStageNo NOTIFY stageNoChanged) private: typedef qf::gui::StatusBar Super; public: AppStatusBar(QWidget *parent = nullptr); ~AppStatusBar() Q_DECL_OVERRIDE; - QString eventName() const {return m_eventName;} - Q_SLOT void setEventName(const QString &event_name); - Q_SIGNAL void eventNameChanged(); + QString eventDbName() const; + void setEventDbName(const QString &event_name); int stageNo() const {return m_stageNo;} - Q_SLOT void setStageNo(int stage_no); - Q_SIGNAL void stageNoChanged(); + void setStageNo(int stage_no); + Q_SIGNAL void stageClicked(); void showProgress(const QString &msg, int completed, int total) Q_DECL_OVERRIDE; private: QLabel *m_lblMessage; QProgressBar *m_progress; QLabel *m_lblEvent; - QLabel *m_lblStage; + QPushButton *m_btCurrentStage; - QString m_eventName; int m_stageNo = 0; }; } - diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index c06846d00..f3fd50ba9 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -31,22 +31,6 @@ Schema { Index { fields: ['ckey']; primary: true } ] }, - Table { name: 'stages' - fields: [ - Field { name: 'id'; type: Int {} }, - Field { name: 'startDateTime'; type: DateTime {} }, - Field { name: 'useAllMaps' - type: Boolean {} - defaultValue: false - notNull: true - }, - Field { name: 'drawingConfig'; type: String {} }, - Field { name: 'qxApiToken'; type: String {} } - ] - indexes: [ - Index {fields: ['id']; primary: true } - ] - }, Table { name: 'courses' fields: [ Field { name: 'id'; type: Serial { primaryKey: true } }, @@ -150,7 +134,7 @@ Schema { Field { name: 'relayLegCount'; type: Int { } } ] indexes: [ - Index {fields: ['stageId']; references: ForeignKeyReference {table: 'stages'; fields: ['id']; } }, + Index {fields: ['stageId']}, Index {fields: ['classId']; references: ForeignKeyReference {table: 'classes'; fields: ['id']; } }, Index {fields: ['courseId']; references: ForeignKeyReference {table: 'courses'; fields: ['id']; } } ] @@ -294,7 +278,6 @@ Schema { onDelete: 'RESTRICT'; } }, - Index {fields: ['stageId']; references: ForeignKeyReference {table: 'stages'; fields: ['id']; } }, Index {fields: ['relayId', 'leg']; unique: false }, Index {fields: ['stageId', 'siId']; unique: false } // might be duplicate to enable SI card sharing in not overlapping runs ] @@ -494,22 +477,31 @@ Schema { fields: [ Field { name: 'id'; type: Serial { primaryKey: true } }, Field { name: 'stage_id'; type: Int { } }, - Field { name: 'change_id'; type: Int { } }, - Field { name: 'data_id'; type: Int { } }, + Field { name: 'foreign_table'; type: String { } }, + Field { name: 'foreign_id'; type: Int { } }, Field { name: 'data_type'; type: String { } }, Field { name: 'data'; type: String { } }, Field { name: 'orig_data'; type: String { } comment: 'Store data overriden by change here to enable change rollback.' }, - Field { name: 'source'; type: String { } }, Field { name: 'user_id'; type: String { } }, Field { name: 'status'; type: String { } }, Field { name: 'status_message'; type: String { } }, - Field { name: 'created'; type: DateTime { } }, + Field { name: 'created'; type: DateTime { } + notNull: true + defaultValue: 'CURRENT_TIMESTAMP' + }, Field { name: 'lock_number'; type: Int { } } ] indexes: [ - Index {fields: ['stage_id', 'change_id']; unique: true } + Index {fields: ['stage_id', 'data_type', 'foreign_id']; unique: false }, + Index {fields: ['stage_id', 'status']; unique: false }, + Index {fields: ['stage_id', 'foreign_id'] + // The WHERE clause means the index only contains rows that satisfy where condition + where: "status = 'Pending' AND data_type = 'LateEntry' AND foreign_table='runs' AND foreign_id IS NOT NULL" + comment: 'Only one pending late entry can be present for single runs.id (foreign_id).' + unique: true + } ] } ] diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml index 6d43184fe..c3fae0e09 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml @@ -21,6 +21,8 @@ QtObject { def += ' DEFAULT '; if(typeof defaultValue === 'boolean' && driver_name.endsWith("SQLITE")) def += (defaultValue)? 1: 0; + else if(defaultValue === 'CURRENT_TIMESTAMP') + def += defaultValue; else if(typeof defaultValue === 'string') def += "'" + defaultValue + "'"; else diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml index bc72921c5..12986f898 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml @@ -9,6 +9,7 @@ QtObject property bool primary: false property bool unique: false property ForeignKeyReference references: null + property string where property string comment function indexName(cnt, table_name) @@ -55,21 +56,29 @@ QtObject else if(primary) { ret += '\tCONSTRAINT ' + indexName(constr_no, options.tableName) + ' PRIMARY KEY (' + fields.join(', ') + ')'; } - else if(unique) { + else if(unique && !where) { ret += '\tCONSTRAINT ' + indexName(constr_no, options.tableName) + ' UNIQUE (' + fields.join(', ') + ')'; } } return ret; } - function createSqlIndexScript(options) + function createSqlIndexScript(index_name, table_name, options) { - var ret = ''; if(fields) { - if(!primary && !unique && !references) { - ret = '(' + fields.join(', ') + ')'; + if (where) { + return 'CREATE ' + + (unique? 'UNIQUE ': ' ') + +'INDEX ' + index_name + + ' ON ' + table_name + ' (' + fields.join(', ') + ')' + + ' WHERE ' + where; + } + else if(!primary && !unique && !references) { + return 'CREATE INDEX ' + index_name + + ' ON ' + table_name + + ' (' + fields.join(', ') + ')'; } } - return ret; + return ''; } } diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml index ab5b750ca..a50ad1766 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml @@ -20,8 +20,8 @@ QtObject { var field_defs = []; options.tableName = name; var fieldNames = [] - for(var i=0; i +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace Event; + +namespace { + +constexpr auto EVENT = "event"; +constexpr auto STAGE = "stage"; +constexpr auto RECEIPTS = "receipts"; +constexpr auto OFEED = "ofeed"; +constexpr auto ORESULTS = "oresults"; +constexpr auto QX = "qx"; + +QVariantMap changedValues(const QVariantMap &old_values, const QVariantMap &new_values) +{ + QVariantMap ret; + for (const auto &[key, val] : new_values.asKeyValueRange()) { + if(old_values.value(key) != val) + ret.insert(key, val); + } + return ret; +} + +} + +QxConfig QxConfig::fromVariantMap(const QVariantMap &map) +{ + QxConfig ret; + ret.apiToken = map.value("apiToken").toString(); + return ret; +} + +QVariantMap QxConfig::toVariantMap() const +{ + QVariantMap ret; + ret["apiToken"] = apiToken; + return ret; +} + +void AppDbConfig::load() +{ + using namespace qf::core::sql; + Connection conn = Connection::forName(); + Q_ASSERT(conn.isOpen()); + + QVariantMap config; + const auto set_group_value = [&config](const QString &group, const QString &key, const QVariant &value) { + auto m = config.value(group).toMap(); + m[key] = value; + config[group] = m; + }; + const auto set_group_stage_value = [&config](const QString &group, int stage_id, const QString &key, const QVariant &value) { + auto stage = QString::number(stage_id); + auto m1 = config.value(group).toMap(); + auto m2 = m1.value(stage).toMap(); + m2[key] = value; + m1[stage] = m2; + config[group] = m1; + }; + + Query q(conn); + QueryBuilder qb; + qb.select("ckey, cvalue, ctype").from("config"); + if(q.exec(qb.toString(), qf::core::Exception::Throw)) { + while(q.next()) { + QString key = q.value(0).toString(); + QVariant val = q.value(1); + QString type = q.value(2).toString(); + QVariant v = qf::core::Utils::retypeStringValue(val.toString(), type); + const auto group = key.section('.', 0, 0); + if (group == EVENT || group == QX) { + set_group_value(group, key.section('.', 1), v); + } else if (group == STAGE || group == RECEIPTS || group == ORESULTS || group == OFEED) { + auto stage_id = key.section('.', 1, 1).toInt(); + set_group_stage_value(group, stage_id, key.section('.', 2), v); + } else { + config[key] = v; + } + + } + } + m_dbVersion = config.value("db.version", 0).toInt(); + Q_ASSERT(m_dbVersion > 0); + + m_qxConfig = QxConfig::fromVariantMap(config.value(QX).toMap()); + m_eventConfig = EventConfig::fromVariantMap(config.value(EVENT).toMap()); + + const auto load_stage_config = [&config](const QString &group, auto &target, auto fromVariantMap) { + for ( const auto &[stage, val] : config.value(group).toMap().asKeyValueRange()) { + target[stage.toInt()] = fromVariantMap(val.toMap()); + } + }; + { + auto stages = config.value(STAGE).toMap(); + Query stages_q(conn); + stages_q.exec("SELECT * FROM stages ORDER BY id", !qf::core::Exception::Throw); + while(stages_q.next()) { + const auto stage_id = stages_q.value("id").toString(); + if(stages.contains(stage_id)) + continue; + set_group_stage_value(STAGE, stage_id.toInt(), "startDateTime", stages_q.value("startDateTime").toDateTime()); + set_group_stage_value(STAGE, stage_id.toInt(), "useAllMaps", stages_q.value("useAllMaps").toBool()); + set_group_stage_value(STAGE, stage_id.toInt(), "drawingConfig", stages_q.value("drawingConfig").toString()); + } + } + load_stage_config(STAGE, m_stagesConfig, StageConfig::fromVariantMap); + load_stage_config(RECEIPTS, m_receiptsConfig, Receipts::ReceiptsConfig::fromVariantMap); + load_stage_config(ORESULTS, m_oresultsConfig, services::OResultsConfig::fromVariantMap); + load_stage_config(OFEED, m_ofeedConfig, services::OFeedConfig::fromVariantMap); +} + +void AppDbConfig::save(const QString &prefix, int stage_id, const QVariantMap &data) +{ + save(prefix + "." + QString::number(stage_id), data); +} + +void AppDbConfig::save(const QString &prefix, const QVariantMap &data) +{ + if(data.isEmpty()) { + return; + } + + using namespace qf::core::sql; + Connection conn = Connection::forName(); + + Query q_up(conn); + q_up.prepare("UPDATE config SET cvalue=:val WHERE ckey=:key", qf::core::Exception::Throw); + Query q_ins(conn); + q_ins.prepare("INSERT INTO config (ckey, cvalue, ctype) VALUES (:key, :val, :type)", qf::core::Exception::Throw); + for (const auto &[key, val] : data.asKeyValueRange()) { + QString val_str; + if(val.typeId() == qMetaTypeId()) + val_str = val.toDate().toString(Qt::ISODate); + else if(val.typeId() == qMetaTypeId()) + val_str = val.toTime().toString(Qt::ISODate); + else if(val.typeId() == qMetaTypeId()) + val_str = val.toDateTime().toString(Qt::ISODate); + else if(val.typeId() == qMetaTypeId()) + val_str = qf::core::Utils::qvariantToJson(val); + else + val_str = val.toString(); + auto full_key = prefix.isEmpty() ? key : prefix + "." + key; + q_up.bindValue(":key", full_key); + q_up.bindValue(":val", val_str); + q_up.exec(qf::core::Exception::Throw); + if(q_up.numRowsAffected() < 1) { + QString type = val.typeName(); + q_ins.bindValue(":key", full_key); + q_ins.bindValue(":type", type); + q_ins.bindValue(":val", val_str); + q_ins.exec(qf::core::Exception::Throw); + } + } +} + +void AppDbConfig::setEventConfig(const EventConfig &config) +{ + const QVariantMap changed_values = changedValues(m_eventConfig.toVariantMap(), config.toVariantMap()); + m_eventConfig = config; + save(EVENT, changed_values); +} + +const services::OResultsConfig& AppDbConfig::oresultsConfig(int stage_id) const +{ + if (m_oresultsConfig.contains(stage_id)) { + return m_oresultsConfig.at(stage_id); + } + static services::OResultsConfig defaultCfg; + return defaultCfg; +} + +const StageConfig& AppDbConfig::stageConfig(int stage_id) const +{ + if (m_stagesConfig.contains(stage_id)) { + return m_stagesConfig.at(stage_id); + } + static StageConfig defaultCfg; + return defaultCfg; +} + +void AppDbConfig::setStageConfig(int stage_id, const StageConfig &cfg) +{ + const QVariantMap changed_values = changedValues(stageConfig(stage_id).toVariantMap(), cfg.toVariantMap()); + m_stagesConfig[stage_id] = cfg; + save(STAGE, stage_id, changed_values); +} + +void AppDbConfig::setOresultsConfig(int stage_id, const services::OResultsConfig &cfg) +{ + const QVariantMap changed_values = changedValues(oresultsConfig(stage_id).toVariantMap(), cfg.toVariantMap()); + m_oresultsConfig[stage_id] = cfg; + save(ORESULTS, stage_id, changed_values); +} + +const Receipts::ReceiptsConfig& AppDbConfig::receiptsConfig(int stage_id) const +{ + if (m_receiptsConfig.contains(stage_id)) { + return m_receiptsConfig.at(stage_id); + } + static Receipts::ReceiptsConfig defaultCfg; + return defaultCfg; +} + +void AppDbConfig::setReceiptsConfig(int stage_id, const Receipts::ReceiptsConfig &cfg) +{ + const QVariantMap changed_values = changedValues(receiptsConfig(stage_id).toVariantMap(), cfg.toVariantMap()); + m_receiptsConfig[stage_id] = cfg; + save(RECEIPTS, stage_id, changed_values); +} + +const services::OFeedConfig& AppDbConfig::ofeedConfig(int stage_id) const +{ + if (m_ofeedConfig.contains(stage_id)) { + return m_ofeedConfig.at(stage_id); + } + static services::OFeedConfig defaultCfg; + return defaultCfg; +} + +void AppDbConfig::setOfeedConfig(int stage_id, const services::OFeedConfig &cfg) +{ + const QVariantMap changed_values = changedValues(ofeedConfig(stage_id).toVariantMap(), cfg.toVariantMap()); + m_ofeedConfig[stage_id] = cfg; + save(OFEED, stage_id, changed_values); +} + +void AppDbConfig::setQxConfig(const QxConfig &config) +{ + const QVariantMap changed_values = changedValues(m_qxConfig.toVariantMap(), config.toVariantMap()); + m_qxConfig = config; + save(QX, changed_values); +} diff --git a/quickevent/app/quickevent/plugins/Event/src/appdbconfig.h b/quickevent/app/quickevent/plugins/Event/src/appdbconfig.h new file mode 100644 index 000000000..331079931 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/appdbconfig.h @@ -0,0 +1,59 @@ +#pragma once + +#include "eventconfig.h" +#include "plugins/Event/src/stageconfig.h" +#include "services/oresultsconfig.h" +#include "services/ofeed/ofeedconfig.h" +#include + +#include +#include +#include +#include +#include + +namespace Event { + +struct QxConfig { + static QxConfig fromVariantMap(const QVariantMap &map); + QVariantMap toVariantMap() const; + + QString apiToken; +}; + +class AppDbConfig { +public: + explicit AppDbConfig() = default; + +public: + void load(); + + int dbVersion() const { return m_dbVersion; } + const EventConfig &eventConfig() const { return m_eventConfig; } + void setEventConfig(const EventConfig &config); + const StageConfig &stageConfig(int stage_id) const; + void setStageConfig(int stage_id, const StageConfig &config); + const services::OResultsConfig &oresultsConfig(int stage_id) const; + void setOresultsConfig(int stage_id, const services::OResultsConfig &config); + const Receipts::ReceiptsConfig &receiptsConfig(int stage_id) const; + void setReceiptsConfig(int stage_id, const Receipts::ReceiptsConfig &config); + const services::OFeedConfig &ofeedConfig(int stage_id) const; + void setOfeedConfig(int stage_id, const services::OFeedConfig &config); + const QxConfig &qxConfig() const { return m_qxConfig; } + void setQxConfig(const QxConfig &config); + +private: + void save(const QString &prefix, const QVariantMap &data); + void save(const QString &prefix, int stage_id, const QVariantMap &data); + +private: + std::map m_stagesConfig; + std::map m_ofeedConfig; + std::map m_receiptsConfig; + std::map m_oresultsConfig; + EventConfig m_eventConfig; + QxConfig m_qxConfig; + int m_dbVersion = 0; +}; + +} // namespace Event diff --git a/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp b/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp index 0e3358385..c14eac7cd 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp @@ -1,23 +1,8 @@ #include "eventconfig.h" -#include "eventplugin.h" -#include #include -#include -#include -#include -#include -#include -#include - -#include - -using namespace Event; - -namespace { -const auto EVENT_NAME = QStringLiteral("event.name"); -} +namespace Event { std::optional EventConfig::disciplineFromInt(int i) { @@ -40,289 +25,64 @@ std::optional EventConfig::disciplineFromInt(int i) return {}; } -EventConfig::EventConfig(QObject *parent) - : QObject(parent) -{ -} - -QVariant EventConfig::value(const QStringList &path, const QVariant &default_value) const -{ - //QF_ASSERT(knownKeys().contains(key), "Key " + key + " is not known key!", return QVariant()); - QVariant ret = default_value; - if(!path.isEmpty()) { - QVariantMap m = m_data; - for (int i = 0; i < path.count() - 1; ++i) { - const QString &key = path[i]; - m = m.value(key).toMap(); - } - ret = m.value(path.last(), default_value); - } - return ret; -} - -void EventConfig::setValue(const QStringList &path, const QVariant &val) -{ - QF_ASSERT(!path.isEmpty(), "Empty path!", return); - m_data = setValue_helper(m_data, path, val); -} - -void EventConfig::load() -{ - using namespace qf::core::sql; - Connection conn = Connection::forName(); - - // Check connection existence / validity - Q_ASSERT(conn.isOpen()); - // if(!conn.isOpen()) { - // qfWarning() << "EventConfig::load(): database connection is not open:" - // << conn.errorString(); - // return; - // } - - Query q(conn); - QueryBuilder qb; - qb.select("ckey, cvalue, ctype").from("config").orderBy("ckey"); - if(q.exec(qb.toString(), qf::core::Exception::Throw)) { - while(q.next()) { - QString key = q.value(0).toString(); - /* - if(!knownKeys().contains(key)) { - qfWarning() << "Config key" << key << "is not known to the QuickEvent config system"; - } - */ - QVariant val = q.value(1); - QString type = q.value(2).toString(); - QVariant v = qf::core::Utils::retypeStringValue(val.toString(), type); - setValue(key, v); - } - } - // checkApiKey(); -} - -void EventConfig::save(const QString &path_to_save) -{ - QVariantMap m; - save_helper(m, QString(), m_data); - using namespace qf::core::sql; - Connection conn = Connection::forName(); - - try { - Query q_up(conn); - q_up.prepare("UPDATE config SET cvalue=:val WHERE ckey=:key", qf::core::Exception::Throw); - Query q_ins(conn); - q_ins.prepare("INSERT INTO config (ckey, cvalue, ctype) VALUES (:key, :val, :type)", qf::core::Exception::Throw); - QMapIterator it(m); - while(it.hasNext()) { - it.next(); - QString key = it.key(); - if(!path_to_save.isEmpty()) { - if(!key.startsWith(path_to_save)) - continue; - if(key.length() > path_to_save.length() && key[path_to_save.length()] != '.') - continue; - } - QVariant val = it.value(); - QString val_str; - if(val.typeId() == qMetaTypeId()) - val_str = val.toDate().toString(Qt::ISODate); - else if(val.typeId() == qMetaTypeId()) - val_str = val.toTime().toString(Qt::ISODate); - else if(val.typeId() == qMetaTypeId()) - val_str = val.toDateTime().toString(Qt::ISODate); - else - val_str = val.toString(); - q_up.bindValue(":key", key); - q_up.bindValue(":val", val_str); - q_up.exec(qf::core::Exception::Throw); - if(q_up.numRowsAffected() < 1) { - QString type = val.typeName(); - q_ins.bindValue(":key", key); - q_ins.bindValue(":type", type); - q_ins.bindValue(":val", val_str); - q_ins.exec(qf::core::Exception::Throw); - } - } - } - catch(const qf::core::Exception &e) { - qf::gui::framework::MainWindow *fwk = qf::gui::framework::MainWindow::frameWork(); - qf::gui::dialogs::MessageBox::showException(fwk, e); - } - -} - -void EventConfig::save_helper(QVariantMap &ret, const QString ¤t_path, const QVariant &val) -{ - if(val.typeId() == qMetaTypeId()) { - QVariantMap m = val.toMap(); - QMapIterator it(m); - while(it.hasNext()) { - it.next(); - QString cp = it.key(); - if(!current_path.isEmpty()) - cp = current_path + '.' + cp; - save_helper(ret, cp, it.value()); - } - } - else { - ret[current_path] = val; - } -} - -QVariantMap EventConfig::setValue_helper(const QVariantMap &m, const QStringList &path, const QVariant &val) -{ - QVariantMap ret; - QF_ASSERT(!path.isEmpty(), "Empty path!", return ret); - if(path.count() == 1) { - ret = m; - ret[path.first()] = val; - } - else { - QStringList p = path; - QString key = p.takeFirst(); - ret = m; - ret[key] = setValue_helper(m.value(key).toMap(), p, val); - } - return ret; -} - -int EventConfig::stageCount() const -{ - return value(QStringLiteral("event.stageCount")).toInt(); -} - -int EventConfig::currentStageId() const -{ - int ret = value(QStringLiteral("event.currentStageId")).toInt(); - if(ret == 0) - ret = 1; - return ret; -} - -int EventConfig::sportId() const -{ - return value(QStringLiteral("event.sportId")).toInt(); -} - -EventConfig::Discipline EventConfig::discipline() const -{ - auto di = value(QStringLiteral("event.disciplineId")).toInt(); - return disciplineFromInt(di).value_or(Discipline::LongDistance); -} - -int EventConfig::importId() const -{ - return value(QStringLiteral("event.importId")).toInt(); -} - -int EventConfig::handicapLength() const -{ - return value(QStringLiteral("event.handicapLength")).toInt(); -} - -QString EventConfig::eventName() const -{ - return value(QStringLiteral("event.name")).toString(); -} - -// namespace { -// auto const API_KEY_CONFIG_PATH = QStringLiteral("event.apiKey"); -// } -// QString EventConfig::apiKey() const -// { -// return value(API_KEY_CONFIG_PATH).toString(); -// } - -// void EventConfig::checkApiKey() -// { -// if (apiKey().isEmpty()) { -// auto api_key = EventPlugin::createApiKey(10); -// setValue(API_KEY_CONFIG_PATH, api_key); -// save(API_KEY_CONFIG_PATH); -// } -// } - -QString EventConfig::eventPlace() const -{ - return value(QStringLiteral("event.place")).toString(); -} - -QString EventConfig::director() const -{ - return value(QStringLiteral("event.director")).toString(); -} - -QString EventConfig::mainReferee() const -{ - return value(QStringLiteral("event.mainReferee")).toString(); -} - -QDateTime EventConfig::eventDateTime() const -{ - QDateTime dt; - QVariant v = value(QStringLiteral("event.dateTime")); - if(v.isValid()) { - dt = v.toDateTime(); - } - else { - QVariant v = value(QStringLiteral("event.date")); - dt.setDate(v.toDate()); - v = value(QStringLiteral("event.time")); - QTime tm = v.toTime(); - if(tm.isValid()) - dt.setTime(tm); - else - dt = dt.addSecs(10 * 60 * 60); // start at 10:00 if not specified - } - return dt; -} - -int EventConfig::dbVersion() const -{ - return value(QStringLiteral("db.version")).toInt(); +EventConfig EventConfig::fromVariantMap(const QVariantMap &values) +{ + EventConfig data; + data.stageCount = values.value("stageCount", 1).toInt(); + data.name = values.value("name").toString(); + data.date = values.value("date").toDate(); + data.time = values.value("time").toTime(); + data.description = values.value("description").toString(); + data.place = values.value("place").toString(); + data.mainReferee = values.value("mainReferee").toString(); + data.director = values.value("director").toString(); + data.handicapLength = values.value("handicapLength").toInt(); + data.sportId = values.value("sportId").toInt(); + data.disciplineId = values.value("disciplineId").toInt(); + data.importId = values.value("importId").toInt(); + data.orisEventKey = values.value("orisEventKey").toString(); + data.cardCheckTimeSec = values.value("cardChechCheckTimeSec").toInt(); + data.oneTenthSecResults = values.value("oneTenthSecResults").toInt(); + data.iofRace = values.value("iofRace").toBool(); + data.iofXmlRaceNumber = values.value("iofXmlRaceNumber").toInt(); + data.currentStageId = values.value("currentStageId", 1).toInt(); + return data; } std::optional EventConfig::maximumCardCheckAdvanceSec() const { - if(auto sec = value(QStringLiteral("event.cardChechCheckTimeSec")).toInt(); sec > 0) + if(auto sec = cardCheckTimeSec; sec > 0) return sec; return {}; } -bool EventConfig::isOneTenthSecResults() const -{ - return static_cast(value(QStringLiteral("oneTenthSecResults")).toInt()); -} - -bool EventConfig::isIofRace() const -{ - return value(QStringLiteral("event.iofRace")).toInt() != 0; -} - -int EventConfig::iofXmlRaceNumber() const +bool EventConfig::isRelays() const { - return value(QStringLiteral("event.iofXmlRaceNumber")).toInt(); + return disciplineId == 5 || disciplineId == 6 || disciplineId == 15; } -QString EventConfig::orisEventKey() const +QVariantMap EventConfig::toVariantMap() const { - return value(QStringLiteral("event.orisEventKey")).toString(); + QVariantMap values; + values.insert("stageCount", stageCount); + values.insert("name", name); + values.insert("date", date); + values.insert("time", time); + values.insert("description", description); + values.insert("place", place); + values.insert("mainReferee", mainReferee); + values.insert("director", director); + values.insert("handicapLength", handicapLength); + values.insert("sportId", sportId); + values.insert("disciplineId", disciplineId); + values.insert("importId", importId); + values.insert("orisEventKey", orisEventKey); + values.insert("cardChechCheckTimeSec", cardCheckTimeSec); + values.insert("oneTenthSecResults", oneTenthSecResults); + values.insert("iofRace", iofRace); + values.insert("iofXmlRaceNumber", iofXmlRaceNumber); + values.insert("currentStageId", currentStageId); + return values; } -/* -const QSet &EventConfig::knownKeys() -{ - static QSet s; - if(s.isEmpty()) { - s << "event.stageCount" - << EVENT_NAME - << "event.date" - << "event.description" - << "event.place" - << "event.mainReferee" - << "event.director" - << "event.importId"; - } - return s; } -*/ - diff --git a/quickevent/app/quickevent/plugins/Event/src/eventconfig.h b/quickevent/app/quickevent/plugins/Event/src/eventconfig.h index a4d9c4ea5..6987399c5 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventconfig.h +++ b/quickevent/app/quickevent/plugins/Event/src/eventconfig.h @@ -1,27 +1,15 @@ -#ifndef EVENTCONFIG_H -#define EVENTCONFIG_H +#pragma once -#include +#include +#include +#include +#include #include -#include -#include - -#include namespace Event { -class EventConfig : public QObject +struct EventConfig { - Q_OBJECT - Q_PROPERTY(int stageCount READ stageCount) - Q_PROPERTY(bool isHandicap READ isHandicap) - Q_PROPERTY(bool isRelays READ isRelays) - Q_PROPERTY(bool isIofRace READ isIofRace) - Q_PROPERTY(bool iofXmlRaceNumber READ iofXmlRaceNumber) - Q_PROPERTY(QString director READ director) - Q_PROPERTY(QString mainReferee READ mainReferee) - //Q_PROPERTY(QString eventName READ eventName) -public: enum class Sport {OB = 1, LOB, MTBO, TRAIL}; enum class Discipline {LongDistance = 1, ShortDistance = 2, @@ -38,52 +26,35 @@ class EventConfig : public QObject KnocOutSprint = 16, Indoor = 19, }; + static std::optional disciplineFromInt(int i); -public: - explicit EventConfig(QObject *parent = nullptr); -public: - QVariantMap values() const {return m_data;} - QVariant value(const QStringList &path, const QVariant &default_value = QVariant()) const; - QVariant value(const QString &path, const QVariant &default_value = QVariant()) const { - return value(path.split('.'), default_value); - } - void setValue(const QStringList &path, const QVariant &val); - void setValue(const QString &path, const QVariant &val) {setValue(path.split('.'), val);} - void load(); - void save(const QString &path_to_save = QString()); - int stageCount() const; - int currentStageId() const; - int sportId() const; - Discipline discipline() const; - int importId() const; - int handicapLength() const; - bool isHandicap() const {return handicapLength() > 0;} - bool isRelays() const { - return discipline() == Discipline::Relays - || discipline() == Discipline::Teams - || discipline() == Discipline::SprintRelays; - } - bool isIofRace() const; - int iofXmlRaceNumber() const; - QString eventName() const; - // QString apiKey() const; - QString eventPlace() const; - QString director() const; - QString mainReferee() const; - QDateTime eventDateTime() const; - int dbVersion() const; + bool isHandicap() const {return handicapLength > 0;} std::optional maximumCardCheckAdvanceSec() const; - bool isOneTenthSecResults() const; - QString orisEventKey() const; -private: - // void checkApiKey(); - void save_helper(QVariantMap &ret, const QString ¤t_path, const QVariant &val); - QVariantMap setValue_helper(const QVariantMap &m, const QStringList &path, const QVariant &val); -private: - QVariantMap m_data; + + bool isRelays() const; + + static EventConfig fromVariantMap(const QVariantMap &values); + QVariantMap toVariantMap() const; + + int stageCount = 1; + QString name; + QDate date; + QTime time; + QString description; + QString place; + QString mainReferee; + QString director; + int handicapLength = 0; + int sportId = 0; + int disciplineId = 0; + int importId = 0; + QString orisEventKey; + int cardCheckTimeSec = 0; + int oneTenthSecResults = 0; + bool iofRace = false; + int iofXmlRaceNumber = 0; + int currentStageId = 1; }; } - -#endif // EVENTCONFIG_H diff --git a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp index 9ed6bc57a..a6081717c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp @@ -1,10 +1,14 @@ #include "eventdialogwidget.h" #include "ui_eventdialogwidget.h" -#include "eventconfig.h" - #include +#include +#include +#include + + + EventDialogWidget::EventDialogWidget(QWidget *parent) : Super(parent), ui(new Ui::EventDialogWidget) @@ -12,6 +16,13 @@ EventDialogWidget::EventDialogWidget(QWidget *parent) : setPersistentSettingsId("EventDialogWidget"); ui->setupUi(this); + ui->stageStartTimesTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + ui->stageStartTimesTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->stageStartTimesTable->verticalHeader()->hide(); + connect(ui->ed_stageCount, QOverload::of(&QSpinBox::valueChanged), this, [this]() { + updateStageStartTimeEditors(saveParams()); + }); + connect(ui->ed_iofRace, &QAbstractButton::toggled, ui->frameIofRace, &QWidget::setVisible); ui->frameIofRace->hide(); @@ -60,64 +71,94 @@ void EventDialogWidget::setEventIdEditable(bool b) ui->ed_eventId->setReadOnly(!b); } -void EventDialogWidget::loadParams(const QVariantMap ¶ms) +void EventDialogWidget::updateStageStartTimeEditors(const EventDialogWidget::Params ¶ms) { - ui->ed_stageCount->setValue(params.value("stageCount").toInt()); - //ui->ed_currentStage->setValue(params.value("currentStageId").toInt()); - ui->ed_name->setText(params.value("name").toString()); - QDate date = params.value("date").toDate(); - if(!date.isValid()) - date = QDate::currentDate(); - ui->ed_date->setDate(date); - QTime time = params.value("time").toTime(); - if(time.isValid()) - ui->ed_time->setTime(time); - ui->ed_description->setText(params.value("description").toString()); - ui->ed_place->setText(params.value("place").toString()); - ui->ed_mainReferee->setText(params.value("mainReferee").toString()); - ui->ed_director->setText(params.value("director").toString()); - ui->ed_handicapLength->setValue(params.value("handicapLength").toInt()); - if (auto ix = ui->cbxSportId->findData(params.value("sportId").toInt()); ix < 0) { + QDateTime event_start(params.eventConfig.date, params.eventConfig.time); + + QTableWidget *table = ui->stageStartTimesTable; + table->setRowCount(params.eventConfig.stageCount); + for(int row = 0; row < params.eventConfig.stageCount; ++row) { + auto stage_id = row + 1; + auto *stage_item = new QTableWidgetItem(QString::number(stage_id)); + stage_item->setTextAlignment(Qt::AlignCenter); + table->setItem(row, 0, stage_item); + + auto stage_start = params.stageStarts.value(row, event_start.addDays(row)); + auto *editor = new QDateTimeEdit(stage_start, table); + editor->setCalendarPopup(true); + editor->setDisplayFormat(QStringLiteral("dd.MM.yyyy HH:mm:ss")); + table->setCellWidget(row, 1, editor); + } + table->resizeRowsToContents(); + const int visible_row_count = qMin(table->rowCount(), 5); + int height = table->horizontalHeader()->height() + 2 * table->frameWidth(); + for(int row = 0; row < visible_row_count; ++row) { + height += table->rowHeight(row); + } + table->setFixedHeight(height); +} + +void EventDialogWidget::loadParams(const EventDialogWidget::Params ¶ms) +{ + ui->ed_name->setText(params.eventConfig.name); + ui->ed_date->setDate(params.eventConfig.date.isValid() ? params.eventConfig.date : QDate::currentDate()); + if(params.eventConfig.time.isValid()) + ui->ed_time->setTime(params.eventConfig.time); + + ui->ed_stageCount->setValue(params.eventConfig.stageCount); + updateStageStartTimeEditors(params); + ui->ed_description->setText(params.eventConfig.description); + ui->ed_place->setText(params.eventConfig.place); + ui->ed_mainReferee->setText(params.eventConfig.mainReferee); + ui->ed_director->setText(params.eventConfig.director); + ui->ed_handicapLength->setValue(params.eventConfig.handicapLength); + if(auto ix = ui->cbxSportId->findData(params.eventConfig.sportId); ix < 0) ui->cbxSportId->setCurrentIndex(0); - } else { + else ui->cbxSportId->setCurrentIndex(ix); - } - if (auto ix = ui->cbxDisciplineId->findData(params.value("disciplineId").toInt()); ix < 0) { + if(auto ix = ui->cbxDisciplineId->findData(params.eventConfig.disciplineId); ix < 0) ui->cbxDisciplineId->setCurrentIndex(0); - } else { + else ui->cbxDisciplineId->setCurrentIndex(ix); - } - ui->ed_orisImportId->setText(params.value("importId").toString()); - ui->ed_orisRace->setChecked(!ui->ed_orisImportId->text().isEmpty()); - ui->ed_orisEventKey->setText(params.value("orisEventKey").toString()); - ui->ed_cardChecCheckTimeSec->setValue(params.value("cardChechCheckTimeSec").toInt()); - ui->ed_oneTenthSecResults->setCurrentIndex(params.value("oneTenthSecResults").toInt()); - ui->ed_iofRace->setChecked(params.value("iofRace").toInt() != 0); - ui->ed_xmlRaceNumber->setValue(params.value("iofXmlRaceNumber").toInt()); + ui->ed_orisImportId->setText(params.eventConfig.importId > 0 ? QString::number(params.eventConfig.importId) : QString()); + ui->ed_orisRace->setChecked(params.eventConfig.importId > 0); + ui->ed_orisEventKey->setText(params.eventConfig.orisEventKey); + ui->ed_cardChecCheckTimeSec->setValue(params.eventConfig.cardCheckTimeSec); + ui->ed_oneTenthSecResults->setCurrentIndex(params.eventConfig.oneTenthSecResults); + ui->ed_iofRace->setChecked(params.eventConfig.iofRace); + ui->ed_xmlRaceNumber->setValue(params.eventConfig.iofXmlRaceNumber); } -QVariantMap EventDialogWidget::saveParams() +EventDialogWidget::Params EventDialogWidget::saveParams() { - QVariantMap ret; - ret["stageCount"] = ui->ed_stageCount->value(); - //ret["currentStageId"] = ui->ed_currentStage->value(); - ret["name"] = ui->ed_name->text(); - ret["date"] = ui->ed_date->date(); - ret["time"] = ui->ed_time->time(); - ret["description"] = ui->ed_description->text(); - ret["place"] = ui->ed_place->text(); - ret["mainReferee"] = ui->ed_mainReferee->text(); - ret["director"] = ui->ed_director->text(); - ret["handicapLength"] = ui->ed_handicapLength->value(); - ret["sportId"] = ui->cbxSportId->currentData().isNull() ? static_cast(Event::EventConfig::Sport::OB) : ui->cbxSportId->currentData().toInt(); - ret["disciplineId"] = (ui->cbxDisciplineId->currentIndex() <= 0) ? static_cast(Event::EventConfig::Discipline::LongDistance) : ui->cbxDisciplineId->currentData(); - ret["importId"] = ui->ed_orisImportId->text().toInt(); - ret["orisEventKey"] = ui->ed_orisEventKey->text(); - ret["cardChechCheckTimeSec"] = ui->ed_cardChecCheckTimeSec->value(); - ret["oneTenthSecResults"] = ui->ed_oneTenthSecResults->currentIndex(); - ret["iofRace"] = (int)ui->ed_iofRace->isChecked(); - ret["iofXmlRaceNumber"] = ui->ed_xmlRaceNumber->value(); - return ret; + EventDialogWidget::Params params; + params.eventConfig.stageCount = ui->ed_stageCount->value(); + for(int row = 0; row < ui->stageStartTimesTable->rowCount(); ++row) { + if(auto *editor = qobject_cast(ui->stageStartTimesTable->cellWidget(row, 1))) { + params.stageStarts << editor->dateTime(); + } + } + params.eventConfig.name = ui->ed_name->text(); + params.eventConfig.date = ui->ed_date->date(); + params.eventConfig.time = ui->ed_time->time(); + params.eventConfig.description = ui->ed_description->text(); + params.eventConfig.place = ui->ed_place->text(); + params.eventConfig.mainReferee = ui->ed_mainReferee->text(); + params.eventConfig.director = ui->ed_director->text(); + params.eventConfig.handicapLength = ui->ed_handicapLength->value(); + params.eventConfig.sportId = ui->cbxSportId->currentData().isNull() + ? static_cast(Event::EventConfig::Sport::OB) + : ui->cbxSportId->currentData().toInt(); + params.eventConfig.disciplineId = ui->cbxDisciplineId->currentIndex() <= 0 + ? static_cast(Event::EventConfig::Discipline::LongDistance) + : ui->cbxDisciplineId->currentData().toInt(); + params.eventConfig.importId = ui->ed_orisImportId->text().toInt(); + params.eventConfig.orisEventKey = ui->ed_orisEventKey->text(); + params.eventConfig.cardCheckTimeSec = ui->ed_cardChecCheckTimeSec->value(); + params.eventConfig.oneTenthSecResults = ui->ed_oneTenthSecResults->currentIndex(); + params.eventConfig.iofRace = ui->ed_iofRace->isChecked(); + params.eventConfig.iofXmlRaceNumber = ui->ed_xmlRaceNumber->value(); + return params; } QString EventDialogWidget::disciplineName(int disc_id) diff --git a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.h b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.h index 35b11dce8..2dd9b4f49 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.h @@ -1,6 +1,9 @@ #ifndef EVENTDIALOGWIDGET_H #define EVENTDIALOGWIDGET_H +#include "eventconfig.h" + +#include #include namespace Ui { @@ -10,8 +13,13 @@ class EventDialogWidget; class EventDialogWidget : public qf::gui::framework::DialogWidget { Q_OBJECT -private: - typedef qf::gui::framework::DialogWidget Super; + + using Super = qf::gui::framework::DialogWidget; +public: + struct Params { + Event::EventConfig eventConfig; + QList stageStarts; + }; public: explicit EventDialogWidget(QWidget *parent = nullptr); ~EventDialogWidget() Q_DECL_OVERRIDE; @@ -20,12 +28,14 @@ class EventDialogWidget : public qf::gui::framework::DialogWidget QString eventId() const; void setEventIdEditable(bool b); - void loadParams(const QVariantMap ¶ms); - QVariantMap saveParams(); + void loadParams(const EventDialogWidget::Params ¶ms); + EventDialogWidget::Params saveParams(); static QString disciplineName(int disc_id); static QString sportName(int sport_id); private: + void updateStageStartTimeEditors(const EventDialogWidget::Params ¶ms); + Ui::EventDialogWidget *ui; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui index 5d6f37dda..41ada4458 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui @@ -6,8 +6,8 @@ 0 0 - 596 - 619 + 590 + 766 @@ -16,19 +16,6 @@ - - - - - - - &Date - - - ed_date - - - @@ -39,27 +26,23 @@ - - - - h:mm:ss + + + + &Date - - + + ed_date - - + + - D&irector + Main r&eferee - ed_director + ed_mainReferee @@ -73,64 +56,82 @@ - - + + - &Place + D&irector - ed_place + ed_director - - + + + + + Disabled + + + + + Enabled + + + - - - - dd.MM.yyyy + + + + Des&cription - - true + + ed_description - - + + - Event ID + Card check - ed_name + ed_handicapLength - - + + - Main r&eferee + 1/10 sec results - ed_mainReferee + ed_handicapLength - - + + + + 1 + + + + + - Discipline + &Handicap length ed_handicapLength - - + + @@ -142,33 +143,46 @@ - - + + - Des&cription + Event ID - ed_description + ed_name + + + - - + + - - + + - Card check + &Place + + + ed_place + + + + + + + Discipline ed_handicapLength - + Maximal distance between card CHECK and START time @@ -190,40 +204,6 @@ - - - - 1/10 sec results - - - ed_handicapLength - - - - - - - - Disabled - - - - - Enabled - - - - - - - - &Handicap length - - - ed_handicapLength - - - @@ -246,6 +226,43 @@ + + + + Unique event name + + + + + + + + + + h:mm:ss + + + + + + + + + + dd.MM.yyyy + + + true + + + + + + @@ -256,21 +273,39 @@ - - - - 1 - - - - - - - - - - Unique event name - + + + + + 0 + 0 + + + + + 16777215 + 120 + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + QAbstractItemView::SelectionMode::NoSelection + + + 2 + + + + # + + + + + Stage start time + + @@ -299,6 +334,12 @@ + + + 0 + 0 + + @@ -380,19 +421,6 @@ - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index 32e500ba3..386c86cd0 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -4,21 +4,24 @@ #include "eventdialogwidget.h" #include "openeventdialog.h" #include "dbschema.h" +#include "plugins/Event/src/appdbconfig.h" +#include "plugins/Event/src/eventconfig.h" +#include "plugins/Event/src/stageconfig.h" #include "registrationswidget.h" #include "lentcardssettingspage.h" -#include "stagewidget.h" -#include "stagedocument.h" #include "../../Core/src/widgets/appstatusbar.h" #include "services/serviceswidget.h" #include "services/emmaclient.h" -#include "services/qx/qxclientservice.h" +#include "services/qx/qxeventservice.h" #include "services/punchingtest/punchingtestservice.h" #include #include #include +#include +#include #include #include @@ -32,15 +35,16 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include -#include #include #include @@ -49,7 +53,6 @@ #include #include #include -#include #include #include #include @@ -73,6 +76,8 @@ using qff::getPlugin; namespace Event { +namespace { + class DbEventPayload : public QVariantMap { private: @@ -108,7 +113,6 @@ QByteArray DbEventPayload::toJson() const return jsd.toJson(QJsonDocument::Compact); } -namespace { const auto QBE_EXT = QStringLiteral(".qbe"); QString singleFileStorageDir() @@ -191,58 +195,60 @@ EventPlugin::EventPlugin(QObject *parent) { connect(this, &EventPlugin::installed, this, &EventPlugin::onInstalled);//, Qt::QueuedConnection); connect(this, &EventPlugin::currentStageIdChanged, this, &EventPlugin::saveCurrentStageId); - connect(this, &EventPlugin::eventNameChanged, [this](const QString &event_name) { + connect(this, &EventPlugin::eventDbNameChanged, [this](const QString &event_name) { setEventOpen(!event_name.isEmpty()); }); connect(this, &Event::EventPlugin::dbEventNotify, this, &Event::EventPlugin::onDbEventNotify, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &EventPlugin::onRecChng); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &EventPlugin::onRecChng); } -void EventPlugin::initEventConfig() +EventPlugin::~EventPlugin() = default; + +Event::AppDbConfig &EventPlugin::appDbConfig() { - if(m_eventConfig == nullptr) { - m_eventConfig = new Event::EventConfig(this); - } - else { - qfWarning() << "Event config exists already!"; - } + return m_appDbConfig; } -Event::EventConfig *EventPlugin::eventConfig(bool reload) +const Event::AppDbConfig &EventPlugin::appDbConfig() const { - if(m_eventConfig == nullptr) { - m_eventConfig = new Event::EventConfig(this); - reload = true; - } - if(reload) { - m_eventConfig->load(); - //emit eventNameChanged(m_eventConfig->eventName()); - } - return m_eventConfig; + return m_appDbConfig; +} + +const EventConfig &EventPlugin::eventConfig() const +{ + return appDbConfig().eventConfig(); } -int EventPlugin::stageCount() +int EventPlugin::stageCount() const { - if(eventName().isEmpty()) + if(eventDbName().isEmpty()) { return 0; - return eventConfig()->stageCount(); + } + return appDbConfig().eventConfig().stageCount; +} + +const Event::StageConfig &EventPlugin::stageConfig(int stage_id) const +{ + return appDbConfig().stageConfig(stage_id); } void EventPlugin::setCurrentStageId(int stage_id) { - if(m_currentStageId == stage_id) { + if(currentStageId() == stage_id) { return; } - m_currentStageId = stage_id; + auto c = eventConfig(); + c.currentStageId = stage_id; + appDbConfig().setEventConfig(c); emit currentStageIdChanged(stage_id); } int EventPlugin::currentStageId() const { - return m_currentStageId; + return eventConfig().currentStageId; } -int EventPlugin::stageIdForRun(int run_id) +int EventPlugin::stageIdForRun(int run_id) const { int ret = 0; qfs::QueryBuilder qb; @@ -258,31 +264,29 @@ int EventPlugin::stageIdForRun(int run_id) return ret; } -int EventPlugin::stageStartMsec(int stage_id) +int EventPlugin::stageStartMsec(int stage_id) const { QTime start_time = stageStartTime(stage_id); int ret = start_time.msecsSinceStartOfDay(); return ret; } -QDate EventPlugin::stageStartDate(int stage_id) +QDate EventPlugin::stageStartDate(int stage_id) const { return stageStartDateTime(stage_id).date(); } -QTime EventPlugin::stageStartTime(int stage_id) +QTime EventPlugin::stageStartTime(int stage_id) const { return stageStartDateTime(stage_id).time(); } -QDateTime EventPlugin::stageStartDateTime(int stage_id) +QDateTime EventPlugin::stageStartDateTime(int stage_id) const { - Event::StageData stage_data = stageData(stage_id); - QDateTime dt = stage_data.startDateTime(); - return dt; + return appDbConfig().stageConfig(stage_id).startDateTime; } -int EventPlugin::msecToStageStartAM(int si_am_time_sec, int msec, int stage_id) +int EventPlugin::msecToStageStartAM(int si_am_time_sec, int msec, int stage_id) const { if(si_am_time_sec == 0xEEEE) return quickevent::core::og::TimeMs::UNREAL_TIME_MSEC; @@ -293,43 +297,7 @@ int EventPlugin::msecToStageStartAM(int si_am_time_sec, int msec, int stage_id) return time_msec; } -StageData EventPlugin::stageData(int stage_id) -{ - if(!m_stageCache.contains(stage_id)) { - StageDocument doc; - doc.load(stage_id); - StageData data; - for (const auto &[k, v] : doc.values().asKeyValueRange()) { - auto key = k.toLower(); - if (key == "drawingconfig" && v.userType() == qMetaTypeId()) { - // convert json to variantmap - data[key] = qf::core::Utils::jsonToQVariant(v.toString()); - } - else { - data[key] = v; - } - } - m_stageCache[stage_id] = StageData(data); - } - return m_stageCache.value(stage_id); -} -void EventPlugin::setStageData(int stage_id, const StageData &data) -{ - StageDocument doc; - doc.load(stage_id); - for (const auto &[k, v] : data.asKeyValueRange()) { - doc.setValue(k, v); - m_stageCache[stage_id][k.toLower()] = v; - } - doc.save(); -} - -void EventPlugin::clearStageDataCache() -{ - qfInfo() << "stages data cache cleared"; - m_stageCache.clear(); -} void EventPlugin::onInstalled() { @@ -346,15 +314,20 @@ void EventPlugin::onInstalled() m_actCreateEvent = new qfw::Action(tr("Create eve&nt")); //m_actCreateEvent->setShortcut("Ctrl+N"); - connect(m_actCreateEvent, &QAction::triggered, this, [this]() { createEvent(); }); + connect(m_actCreateEvent, &QAction::triggered, this, [this]() { createEvent({}, {}); }); m_actEditEvent = new qfw::Action(tr("E&dit event")); m_actEditEvent->setEnabled(false); connect(m_actEditEvent, &QAction::triggered, this, &EventPlugin::editEvent); - connect(this, &EventPlugin::eventNameChanged, [this](const QString &event_name) { + connect(this, &EventPlugin::eventDbNameChanged, [this](const QString &event_name) { this->m_actEditEvent->setEnabled(!event_name.isEmpty()); }); + m_actSetCurrentStage = new qfw::Action(tr("Set current &stage")); + m_actSetCurrentStage->setEnabled(false); + connect(m_actSetCurrentStage, &QAction::triggered, this, &EventPlugin::setCurrentStage); + connect(this, &EventPlugin::eventOpenChanged, m_actSetCurrentStage, &QAction::setEnabled); + m_actExportEvent_qbe = new qfw::Action(tr("Event (*.qbe)")); m_actExportEvent_qbe->setEnabled(false); connect(m_actExportEvent_qbe, &QAction::triggered, this, &EventPlugin::exportEvent_qbe); @@ -363,25 +336,29 @@ void EventPlugin::onInstalled() connect(m_actImportEvent_qbe, &QAction::triggered, this, &EventPlugin::importEvent_qbe); if(auto *sb = qobject_cast(fwk->statusBar())) { - connect(this, &EventPlugin::eventNameChanged, sb, &Core::AppStatusBar::setEventName); + connect(this, &EventPlugin::eventDbNameChanged, sb, &Core::AppStatusBar::setEventDbName); connect(this, &EventPlugin::currentStageIdChanged, sb, &Core::AppStatusBar::setStageNo); + connect(sb, &Core::AppStatusBar::stageClicked, this, &EventPlugin::setCurrentStage); } - connect(this, &EventPlugin::eventNameChanged, this, &EventPlugin::updateWindowTitle); + connect(this, &EventPlugin::eventDbNameChanged, this, &EventPlugin::updateWindowTitle); connect(this, &EventPlugin::currentStageIdChanged, this, &EventPlugin::updateWindowTitle); connect(fwk, &qff::MainWindow::applicationLaunched, this, &EventPlugin::connectToSqlServer); - connect(this, &EventPlugin::eventOpenChanged, this, &EventPlugin::onEventOpened); qfw::Action *a_import = fwk->menuBar()->actionForPath("file/import", false); Q_ASSERT(a_import); a_import->addActionBefore(m_actConnectDb); a_import->addSeparatorBefore(); - m_actEvent = m_actConnectDb->addMenuAfter("file.event", tr("&Event")); + qfw::Action *a_file = fwk->menuBar()->actionForPath("file", false); + Q_ASSERT(a_file); + m_actEvent = a_file->addMenuAfter("event", tr("&Event")); m_actEvent->setEnabled(false); m_actEvent->addActionInto(m_actCreateEvent); m_actEvent->addActionInto(m_actOpenEvent); m_actEvent->addActionInto(m_actEditEvent); + m_actEvent->addActionInto(m_actSetCurrentStage); + m_actSetCurrentStage->addSeparatorBefore(); m_actImport = fwk->menuBar()->actionForPath("file/import"); m_actImport->addActionInto(m_actImportEvent_qbe); @@ -394,35 +371,19 @@ void EventPlugin::onInstalled() qfw::ToolBar *tb = fwk->toolBar("Event", true); tb->setObjectName("EventToolbar"); tb->setWindowTitle(tr("Event")); + if(auto *part_switch = fwk->findChild(QStringLiteral("partSwitch"))) + fwk->insertToolBar(part_switch, tb); { auto *bt_stage = new QToolButton(); - //bt_stage->setFlat(true); bt_stage->setAutoRaise(true); - bt_stage->setCheckable(true); + bt_stage->setEnabled(false); + bt_stage->setToolTip(tr("Set current stage")); tb->addWidget(bt_stage); - m_cbxStage = new QComboBox(); - connect(m_cbxStage, &QComboBox::activated, this, [this](int ix) { - setCurrentStageId(ix + 1); - }); - connect(this, &EventPlugin::currentStageIdChanged, bt_stage, [this, bt_stage](int stage_id) { - QSignalBlocker b(m_cbxStage); - m_cbxStage->setCurrentIndex(stage_id - 1); + connect(bt_stage, &QToolButton::clicked, this, &EventPlugin::setCurrentStage); + connect(this, &EventPlugin::eventOpenChanged, bt_stage, &QToolButton::setEnabled); + connect(this, &EventPlugin::currentStageIdChanged, bt_stage, [bt_stage](int stage_id) { bt_stage->setText(tr("Current stage E%1").arg(stage_id)); }); - QAction *act_stage = tb->addWidget(m_cbxStage); - act_stage->setVisible(false); - - - QIcon ico(qf::gui::Style::icon("settings")); - m_actEditStage = new qfw::Action(ico, "Stage settings"); - m_actEditStage->setVisible(false); - connect(m_actEditStage, &QAction::triggered, this, &EventPlugin::editStage); - tb->addAction(m_actEditStage); - - connect(bt_stage, &QPushButton::clicked, this, [this, act_stage](bool checked) { - act_stage->setVisible(checked); - m_actEditStage->setVisible(checked); - }); } fwk->menuBar()->actionForPath("view/toolbar")->addActionInto(tb->toggleViewAction()); @@ -435,7 +396,7 @@ void EventPlugin::onInstalled() auto *emma_client = new services::EmmaClient(this); services::Service::addService(emma_client); - auto shvapi_client = new services::qx::QxClientService(this); + auto shvapi_client = new services::qx::QxEventService(this); services::Service::addService(shvapi_client); auto *punching_test = new services::PunchingTestService(this); @@ -478,41 +439,36 @@ void EventPlugin::onInstalled() void EventPlugin::updateWindowTitle() const { - QString title = QStringLiteral("%1 E%2").arg(eventName()).arg(currentStageId()); + QString title = QStringLiteral("%1 E%2").arg(eventDbName()).arg(currentStageId()); qff::MainWindow *fwk = qff::MainWindow::frameWork(); fwk->setWindowTitle(title); } -void EventPlugin::loadCurrentStageId() -{ - int stage_id = 1; - if(!eventName().isEmpty()) { - stage_id = eventConfig()->currentStageId(); - } - setCurrentStageId(stage_id); -} - void EventPlugin::saveCurrentStageId(int current_stage) { - if(current_stage != eventConfig()->currentStageId()) { - eventConfig()->setValue("event.currentStageId", current_stage); - eventConfig()->save("event"); + if(current_stage != appDbConfig().eventConfig().currentStageId) { + auto event_cfg = appDbConfig().eventConfig(); + event_cfg.currentStageId = current_stage; + appDbConfig().setEventConfig(event_cfg); } } -void EventPlugin::editStage() +void EventPlugin::setCurrentStage() { - //qfLogFuncFrame();// << "id:" << id << "mode:" << mode; - int stage_id = currentStageId(); - auto *w = new Event::StageWidget(); - auto *fwk = qf::gui::framework::MainWindow::frameWork(); - qfd::Dialog dlg(QDialogButtonBox::Save | QDialogButtonBox::Cancel, fwk); - dlg.setDefaultButton(QDialogButtonBox::Save); - dlg.setCentralWidget(w); - w->load(stage_id); - if(dlg.exec()) { - emitDbEvent(Event::EventPlugin::DBEVENT_STAGE_START_CHANGED, stage_id, true); - } + QStringList stages; + for(int stage_id = 1; stage_id <= stageCount(); ++stage_id) + stages.append(QStringLiteral("E%1").arg(stage_id)); + if(stages.isEmpty()) + return; + + QInputDialog dialog(qff::MainWindow::frameWork()); + dialog.setWindowTitle(tr("Set current stage")); + dialog.setLabelText(tr("Stage:")); + dialog.setComboBoxItems(stages); + dialog.setComboBoxEditable(false); + dialog.setTextValue(stages.value(currentStageId() - 1, stages.first())); + if(dialog.exec() == QDialog::Accepted) + setCurrentStageId(stages.indexOf(dialog.textValue()) + 1); } void EventPlugin::emitDbEvent(const QString &domain, const QVariant &data, bool loopback) @@ -529,7 +485,7 @@ void EventPlugin::emitDbEvent(const QString &domain, const QVariant &data, bool return; } DbEventPayload dbpl; - dbpl.setEventName(eventName()); + dbpl.setEventName(eventDbName()); dbpl.setDomain(domain); dbpl.setData(data); dbpl.setconnectionId(connection_id); @@ -573,7 +529,7 @@ QString EventPlugin::classNameById(int class_id) QString EventPlugin::shvApiEventId() const { - return eventName() + "-" + QString::number(m_eventConfig->importId()); + return eventDbName() + "-" + QString::number(eventConfig().importId); } QString EventPlugin::createApiKey(int length) @@ -602,9 +558,9 @@ QString EventPlugin::createApiKey(int length) return key; } -QString EventPlugin::fileNameWithStageAndEventName(const QString &fn, std::optional stage_id) +QString EventPlugin::fileNameWithStageAndEventName(const QString &fn, std::optional stage_id) const { - auto ret = eventName(); + auto ret = eventDbName(); if(stageCount() > 1) { ret += QStringLiteral(".e%1").arg(stage_id.value_or(currentStageId())); } @@ -612,12 +568,12 @@ QString EventPlugin::fileNameWithStageAndEventName(const QString &fn, std::optio return ret; } -QString EventPlugin::startListIofXml3FileName(std::optional stage_id) +QString EventPlugin::startListIofXml3FileName(std::optional stage_id) const { return fileNameWithStageAndEventName(START_LIST_IOFXML3_FILE, stage_id); } -QString EventPlugin::resultsIofXml3FileName(std::optional stage_id) +QString EventPlugin::resultsIofXml3FileName(std::optional stage_id) const { return fileNameWithStageAndEventName(RESULTS_IOFXML3_FILE, stage_id); } @@ -664,7 +620,7 @@ void EventPlugin::onDbEvent(const QString &name, QSqlDriver::NotificationSource qfWarning() << "DbNotify with invalid event name, payload:" << event_name << payload.toString(); return; } - if(event_name == eventName()) { + if(event_name == eventDbName()) { QVariant data = dbpl.data(); if (dbpl.domain() == DBEVENT_QX_RECCHNG) { qfMessage() << "from postgres:" << data; @@ -673,7 +629,7 @@ void EventPlugin::onDbEvent(const QString &name, QSqlDriver::NotificationSource qfWarning() << "RecChng loopback detected, issuer:" << recchng.issuer; return; } - qf::gui::framework::Application::instance()->emitQxRecChng(recchng, this); + qf::gui::framework::Application::instance()->qxSql()->emitRecChng(recchng, this); return; } qfMessage() << "emitting domain:" << domain << "data:" << data; @@ -697,37 +653,35 @@ void EventPlugin::onRegistrationsDockVisibleChanged(bool on) void EventPlugin::repairStageStarts(const qf::core::sql::Connection &from_conn, const qf::core::sql::Connection &to_conn) { qfs::Query to_q(to_conn); + to_q.prepare("INSERT INTO config (ckey, cvalue, ctype) VALUES (:key, :value, :type)"); + qfs::Query from_q(from_conn); - from_q.exec("SELECT * FROM stages ORDER BY id"); + from_q.exec("SELECT * FROM stages ORDER BY id", !qf::core::Exception::Throw); while(from_q.next()) { - int ix = from_q.fieldIndex(QStringLiteral("startDate")); + int ix = from_q.fieldIndex(QStringLiteral("startDateTime")); if(ix < 0) break; - QDate d = from_q.value(ix).toDate(); - QTime t = from_q.value("startTime").toTime(); - QDateTime dt(d, t); - int id = from_q.value("id").toInt(); - to_q.exec("UPDATE stages SET startDateTime=" QF_SARG(dt.toString(Qt::ISODate)) " WHERE id=" QF_IARG(id)); + auto stage_id = from_q.value("id").toInt(); + StageConfig stage_config; + stage_config.startDateTime = from_q.value(ix).toDateTime(); + stage_config.drawingConfig = DrawingConfig::fromString(from_q.value("drawingConfig").toString()); + stage_config.useAllMaps = from_q.value("useAllMaps").toBool(); + auto make_key = [stage_id](const QString& key) {return QStringLiteral("stage.%1.%2").arg(stage_id).arg(key);}; + to_q.bindValue(":key", make_key("startDateTime")); + to_q.bindValue(":value", stage_config.startDateTime); + to_q.bindValue(":type", "QDateTime"); + to_q.exec(); + to_q.bindValue(":key", make_key("drawingConfig")); + to_q.bindValue(":value", qf::core::Utils::qvariantToJson(stage_config.drawingConfig.toVariantMap())); + to_q.bindValue(":type", "QString"); + to_q.exec(); + to_q.bindValue(":key", make_key("useAllMaps")); + to_q.bindValue(":value", stage_config.useAllMaps); + to_q.bindValue(":type", "bool"); + to_q.exec(); } } -void EventPlugin::onEventOpened() -{ - if(!isEventOpen()) - return; - qfLogFuncFrame() << "stage count:" << stageCount(); - { - QSignalBlocker b(m_cbxStage); - m_cbxStage->clear(); - int stage_cnt = stageCount(); - for (int i = 0; i < stage_cnt; ++i) { - m_cbxStage->addItem("E" + QString::number(i + 1), i + 1); - } - m_cbxStage->setCurrentIndex(-1); - } - loadCurrentStageId(); -} - EventPlugin::ConnectionType EventPlugin::connectionType() const { ConnectionSettings connection_settings; @@ -871,7 +825,7 @@ void EventPlugin::connectToSqlServer() m_actEvent->setEnabled(connect_ok); m_actExport->setEnabled(connect_ok); m_actImport->setEnabled(connect_ok); - m_actEditStage->setEnabled(connect_ok); + if(connect_ok) { closeEvent(); openEvent(conn_w->eventName()); @@ -897,19 +851,20 @@ bool run_sql_script(qf::core::sql::Query &q, const QStringList &sql_lines) return true; } } -bool EventPlugin::createEvent(const QString &event_name, const QVariantMap &event_params) +bool EventPlugin::createEvent(const QString &event_name, const EventConfig &event_params) { qfLogFuncFrame(); qff::MainWindow *fwk = qff::MainWindow::frameWork(); EventPlugin::ConnectionType connection_type = connectionType(); QStringList existing_event_ids; - if(connection_type == ConnectionType::SingleFile) + if(connection_type == ConnectionType::SingleFile) { existing_event_ids = existingFileEventNames(); - else + } else { existing_event_ids = existingSqlEventNames(); + } QString event_id = event_name; - QVariantMap new_params = event_params; + EventDialogWidget::Params new_params {.eventConfig = event_params, .stageStarts = {}}; do { qfd::Dialog dlg(fwk); dlg.setButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); @@ -936,13 +891,9 @@ bool EventPlugin::createEvent(const QString &event_name, const QVariantMap &even closeEvent(); - Event::EventConfig event_config; bool ok = false; //ConnectionSettings connection_settings; - event_config.setValue("event", new_params); - int stage_count = event_params.value("stageCount").toInt(); - if(stage_count == 0) - stage_count = event_config.stageCount(); + int stage_count = new_params.eventConfig.stageCount; qfInfo() << "createEvent, stage_count:" << stage_count; QF_ASSERT(stage_count > 0, "Stage count have to be greater than 0", return false); @@ -979,22 +930,18 @@ bool EventPlugin::createEvent(const QString &event_name, const QVariantMap &even if(connection_type == ConnectionType::SqlServer) { stage_table_name = event_id + '.' + stage_table_name; } - QDateTime start_dt = event_config.eventDateTime(); - // FIXME: handle SQL errors here and below in q.exec() - q.prepare("INSERT INTO " + stage_table_name + " (id, startDateTime) VALUES (:id, :startDateTime)"); + // Stage details are stored in config, but stage rows are still required by foreign keys. + q.prepare("INSERT INTO " + stage_table_name + " (id) VALUES (:id)"); for(int i=0; isetWindowTitle(tr("Edit event")); - event_w->setEventId(eventName()); + event_w->setEventId(eventDbName()); event_w->setEventIdEditable(false); - event_w->loadParams(eventConfig()->value("event").toMap()); + EventDialogWidget::Params params; + params.eventConfig = eventConfig(); + for(int i=0; iloadParams(params); dlg.setCentralWidget(event_w); if(!dlg.exec()) return; - eventConfig()->setValue("event", event_w->saveParams()); - eventConfig()->save("event"); + auto new_params = event_w->saveParams(); + appDbConfig().setEventConfig(new_params.eventConfig); + for(int i=0; i no change + if(!eventDbName().isEmpty() && db_event_names.contains(eventDbName()) && !ok) { + // dialog canceled and event is already open => no change return true; - + } closeEvent(); if(ok) { @@ -1145,29 +1109,31 @@ bool EventPlugin::openEvent(const QString &_event_name) } } if(ok) { - EventConfig *evc = eventConfig(true); - if(evc->dbVersion() < dbVersion()) { + m_appDbConfig = AppDbConfig(); + m_appDbConfig.load(); + if(m_appDbConfig.dbVersion() < dbVersion()) { qfd::MessageBox::showError(fwk, tr("Event data version (%1) is too low, minimal version is (%2).\nUse: File --> Import --> Event (*.qbe) to convert event to current version.") - .arg(qf::core::Utils::intToVersionString(evc->dbVersion())) + .arg(qf::core::Utils::intToVersionString(m_appDbConfig.dbVersion())) .arg(qf::core::Utils::intToVersionString(dbVersion()))); closeEvent(); ok = false; } - else if(evc->dbVersion() > dbVersion()) { + else if(m_appDbConfig.dbVersion() > dbVersion()) { qfd::MessageBox::showError(fwk, tr("Event was created in more recent QuickEvent version (%1) and the application might not work as expected. Download latest QuickEvent is strongly recommended.") - .arg(qf::core::Utils::intToVersionString(evc->dbVersion()))); + .arg(qf::core::Utils::intToVersionString(m_appDbConfig.dbVersion()))); } } if(ok) { connection_settings.setEventName(event_name); - setEventName(event_name); + setEventDbName(event_name); //emit reloadDataRequest(); } - m_actEditStage->setEnabled(ok); + m_actOpenEvent->setEnabled(ok || !db_event_names.isEmpty()); m_actEditEvent->setEnabled(ok); m_actExportEvent_qbe->setEnabled(ok); setEventOpen(ok); + emit currentStageIdChanged(currentStageId()); return ok; } @@ -1351,7 +1317,7 @@ void EventPlugin::exportEvent_qbe() void EventPlugin::deleteEvent(const QString &event_name) { qff::MainWindow *fwk = qff::MainWindow::frameWork(); - if(event_name == eventName()) + if(event_name == eventDbName()) closeEvent(); if(connectionType() == ConnectionType::SingleFile) { const QString fn = eventNameToFileName(event_name); @@ -1366,8 +1332,8 @@ void EventPlugin::deleteEvent(const QString &event_name) .arg(event_name, q.lastErrorText())); } } - -static void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) +namespace { +void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) { dst.setHostName(src.hostName()); dst.setPort(src.port()); @@ -1375,7 +1341,7 @@ static void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) dst.setPassword(src.password()); dst.setDatabaseName(src.databaseName()); } - +} bool EventPlugin::importEventFromFile(const QString &src_file, const QString &dest_event_name) { qfLogFuncFrame(); @@ -1457,7 +1423,7 @@ bool EventPlugin::convertSqlEvent(const QString &from_event, const QString &to_e } QString EventPlugin::copyEventSchema(qfs::Connection &imp_conn, qfs::Connection &exp_conn, - const QString &dest_schema_name) + const QString &dest_schema_name) { qff::MainWindow *fwk = qff::MainWindow::frameWork(); QString err_str; @@ -1486,12 +1452,10 @@ QString EventPlugin::copyEventSchema(qfs::Connection &imp_conn, qfs::Connection err_str = copy_sql_table(table_name, rec, imp_conn, exp_conn); if(!err_str.isEmpty()) break; - if(table_name == QLatin1String("stages")) { - repairStageStarts(imp_conn, exp_conn); - } } if(!err_str.isEmpty()) break; + repairStageStarts(imp_conn, exp_conn); transaction.commit(); } while(false); return err_str; @@ -1543,11 +1507,7 @@ void EventPlugin::onDbEventNotify(const QString &domain, int connection_id, cons { Q_UNUSED(connection_id) qfLogFuncFrame() << "domain:" << domain << "payload:" << data; - if(domain == QLatin1String(Event::EventPlugin::DBEVENT_STAGE_START_CHANGED)) { - //int stage_id = data.toInt(); - clearStageDataCache(); - } - else if(domain == QLatin1String(Event::EventPlugin::DBEVENT_REGISTRATIONS_IMPORTED)) { + if(domain == QLatin1String(Event::EventPlugin::DBEVENT_REGISTRATIONS_IMPORTED)) { reloadRegistrationsModel(); } } @@ -1611,4 +1571,3 @@ const qf::core::utils::Table &EventPlugin::registrationsTable() } } - diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.h b/quickevent/app/quickevent/plugins/Event/src/eventplugin.h index 50a8e077e..fbb7821ab 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.h +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.h @@ -1,8 +1,8 @@ #ifndef EVENTPLUGIN_H #define EVENTPLUGIN_H -#include "eventconfig.h" -#include "stage.h" +#include "appdbconfig.h" +#include "plugins/Event/src/stageconfig.h" #include @@ -17,22 +17,20 @@ namespace qf::gui { class Action; } namespace qf::gui::framework { class DockWidget; } namespace qf::gui::model { class SqlTableModel; } -class QComboBox; class DbSchema; namespace Event { - static constexpr auto START_LIST_IOFXML3_FILE = "startlist-iof3.xml"; static constexpr auto RESULTS_IOFXML3_FILE = "results-iof3.xml"; class EventPlugin : public qf::gui::framework::Plugin { Q_OBJECT - Q_PROPERTY(QObject* eventConfig READ eventConfig) + Q_PROPERTY(int currentStageId READ currentStageId NOTIFY currentStageIdChanged) Q_PROPERTY(int stageCount READ stageCount) - Q_PROPERTY(QString eventName READ eventName WRITE setEventName NOTIFY eventNameChanged) + Q_PROPERTY(QString eventDbName READ eventDbName WRITE setEventDbName NOTIFY eventDbNameChanged) Q_PROPERTY(bool eventOpen READ isEventOpen WRITE setEventOpen NOTIFY eventOpenChanged) Q_PROPERTY(bool sqlServerConnected READ isSqlServerConnected NOTIFY sqlServerConnectedChanged) private: @@ -41,9 +39,10 @@ class EventPlugin : public qf::gui::framework::Plugin enum class ConnectionType : int {SqlServer = 0, SingleFile}; public: EventPlugin(QObject *parent = nullptr); + ~EventPlugin() override; QF_PROPERTY_BOOL_IMPL(e, E, ventOpen) - QF_PROPERTY_IMPL(QString, e, E, ventName) + QF_PROPERTY_IMPL(QString, e, E, ventDbName) /// strange is that 'quickboxDbEvent' just doesn't work without any error /// from psql doc: Commonly, the channel name is the same as the name of some table in the database @@ -60,36 +59,33 @@ class EventPlugin : public qf::gui::framework::Plugin static constexpr auto DBEVENT_PUNCH_RECEIVED = "punchReceived"; static constexpr auto DBEVENT_REGISTRATIONS_IMPORTED = "registrationsImported"; static constexpr auto DBEVENT_STAGE_START_CHANGED = "stageStartChanged"; - static constexpr auto DBEVENT_QX_CHANGE_RECEIVED = "qxChangeReceived"; static constexpr auto DBEVENT_QX_RECCHNG = "qxRecChng"; - Q_INVOKABLE void initEventConfig(); - Event::EventConfig* eventConfig(bool reload = false); - int stageCount(); + Event::AppDbConfig& appDbConfig(); + const Event::AppDbConfig& appDbConfig() const; + const Event::EventConfig& eventConfig() const; + int stageCount() const; + const Event::StageConfig& stageConfig(int stage_id) const; Q_SLOT void setCurrentStageId(int stage_id); int currentStageId() const; Q_SIGNAL void currentStageIdChanged(int current_stage); - Q_INVOKABLE int stageIdForRun(int run_id); + Q_INVOKABLE int stageIdForRun(int run_id) const; - Q_INVOKABLE int stageStartMsec(int stage_id); - Q_INVOKABLE QDate stageStartDate(int stage_id); - Q_INVOKABLE QTime stageStartTime(int stage_id); - Q_INVOKABLE QDateTime stageStartDateTime(int stage_id); + int stageStartMsec(int stage_id) const; + QDate stageStartDate(int stage_id) const; + QTime stageStartTime(int stage_id) const; + QDateTime stageStartDateTime(int stage_id) const; //Q_INVOKABLE int currentStageStartMsec(); - int msecToStageStartAM(int si_am_time_sec, int msec = 0, int stage_id = 0); - - StageData stageData(int stage_id); - void setStageData(int stage_id, const StageData &data); - Q_SLOT void clearStageDataCache(); + int msecToStageStartAM(int si_am_time_sec, int msec = 0, int stage_id = 0) const; - Q_SLOT bool createEvent(const QString &event_name = QString(), const QVariantMap &event_params = QVariantMap()); - Q_SLOT void editEvent(); - Q_SLOT bool closeEvent(); - Q_SLOT bool openEvent(const QString &event_name = QString()); - Q_SLOT void exportEvent_qbe(); - Q_SLOT void importEvent_qbe(); + bool createEvent(const QString &event_name, const EventConfig &event_params); + void editEvent(); + bool closeEvent(); + bool openEvent(const QString &event_name = QString()); + void exportEvent_qbe(); + void importEvent_qbe(); void emitReloadDataRequest() { emit reloadDataRequest(); } Q_SIGNAL void reloadDataRequest(); @@ -108,9 +104,9 @@ class EventPlugin : public qf::gui::framework::Plugin QString shvApiEventId() const; static QString createApiKey(int length); - QString startListIofXml3FileName(std::optional stage_id = std::nullopt); - QString resultsIofXml3FileName(std::optional stage_id = std::nullopt); - QString fileNameWithStageAndEventName(const QString &fn, std::optional stage_id); + QString startListIofXml3FileName(std::optional stage_id = std::nullopt) const; + QString resultsIofXml3FileName(std::optional stage_id = std::nullopt) const; + QString fileNameWithStageAndEventName(const QString &fn, std::optional stage_id) const; DbSchema* dbSchema(); static int dbVersion(); @@ -130,11 +126,9 @@ class EventPlugin : public qf::gui::framework::Plugin QStringList existingSqlEventNames() const; QStringList existingFileEventNames(const QString &dir = QString()) const; - void onEventOpened(); void connectToSqlServer(); - void loadCurrentStageId(); void saveCurrentStageId(int current_stage); - void editStage(); + void setCurrentStage(); void onDbEvent(const QString & name, QSqlDriver::NotificationSource source, const QVariant & payload); void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); @@ -151,8 +145,7 @@ class EventPlugin : public qf::gui::framework::Plugin bool importEventFromFile(const QString &src_file, const QString &dest_event_name); bool convertSqlEvent(const QString &from_event, const QString &to_event); void deleteEvent(const QString &event_name); - QString copyEventSchema(qf::core::sql::Connection &imp_conn, qf::core::sql::Connection &exp_conn, - const QString &dest_schema_name); + QString copyEventSchema(qf::core::sql::Connection &imp_conn, qf::core::sql::Connection &exp_conn, const QString &dest_schema_name); void onServiceDockVisibleChanged(bool on = true); private: @@ -163,14 +156,11 @@ class EventPlugin : public qf::gui::framework::Plugin qf::gui::Action *m_actCreateEvent = nullptr; qf::gui::Action *m_actOpenEvent = nullptr; qf::gui::Action *m_actEditEvent = nullptr; + qf::gui::Action *m_actSetCurrentStage = nullptr; qf::gui::Action *m_actExportEvent_qbe = nullptr; qf::gui::Action *m_actImportEvent_qbe = nullptr; - qf::gui::Action *m_actEditStage = nullptr; - Event::EventConfig *m_eventConfig = nullptr; + Event::AppDbConfig m_appDbConfig; bool m_sqlServerConnected = false; - QComboBox *m_cbxStage = nullptr; - int m_currentStageId = 0; - QMap m_stageCache; QMap m_classNameCache; qf::gui::framework::DockWidget *m_servicesDockWidget = nullptr; diff --git a/quickevent/app/quickevent/plugins/Event/src/openeventdialog.cpp b/quickevent/app/quickevent/plugins/Event/src/openeventdialog.cpp index fa363e2f0..67e10264a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/openeventdialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/openeventdialog.cpp @@ -1,6 +1,6 @@ #include "openeventdialog.h" #include "ui_openeventdialog.h" -#include "eventconfig.h" +#include "appdbconfig.h" #include "eventdialogwidget.h" #include diff --git a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp index 308cba100..b40a05a26 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp @@ -90,7 +90,7 @@ void EmmaClient::exportRadioCodesRacomTxt() QTextStream ts_codes(&f_splitcodes); int current_stage = getPlugin()->currentStageId(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if (is_relays) { qfs::QueryBuilder qb_classes; @@ -193,7 +193,7 @@ void EmmaClient::exportResultsIofXml3() QString export_dir = ss.exportDir(); QString file_name = export_dir + '/' + ss.fileNameBase() + ".results.xml"; int current_stage = getPlugin()->currentStageId(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QString str = is_relays ? getPlugin()->resultsIofXml30() @@ -213,7 +213,7 @@ void EmmaClient::exportStartListIofXml3() QString export_dir = ss.exportDir(); QString file_name = export_dir + '/' + ss.fileNameBase() + ".startlist.xml"; int current_stage = getPlugin()->currentStageId(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QString str = is_relays ? getPlugin()->startListIofXml30() @@ -405,7 +405,7 @@ void EmmaClient::exportStartListRacomTxt() const QTextStream ts(&f); ts.setGenerateByteOrderMark(true); // BOM - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); int current_stage = getPlugin()->currentStageId(); qfs::QueryBuilder qb; qb.select2("runs", "startTimeMs, siId, competitorId, isrunning, leg, finishTimeMs") @@ -526,7 +526,7 @@ void EmmaClient::exportStartListRacomCsv() const #else ts.setCodec("UTF-8"); #endif - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); int current_stage = getPlugin()->currentStageId(); const QString separator(";"); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp index 48833f987..a6094fbe6 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp @@ -32,7 +32,7 @@ EmmaClientWidget::EmmaClientWidget(QWidget *parent) ui->chExportStartListXml30->setCheckState((ss.exportStartListTypeXml3()) ? Qt::Checked : Qt::Unchecked); ui->chExportResultsXml30->setCheckState((ss.exportResultTypeXml3()) ? Qt::Checked : Qt::Unchecked); if (ui->edFileNameBase->text().isEmpty()) - ui->edFileNameBase->setText(getPlugin()->eventName()); + ui->edFileNameBase->setText(getPlugin()->eventDbName()); if (ss.startExportType() == EmmaClientSettings::StartExportType::CSV) ui->rbRacomStartCsv->setChecked(true); } @@ -106,7 +106,7 @@ bool EmmaClientWidget::saveSettings() type = EmmaClientSettings::StartExportType::CSV; ss.setStartExportType(type); if (ss.fileNameBase().isEmpty()) - ss.setFileNameBase(getPlugin()->eventName()); + ss.setFileNameBase(getPlugin()->eventDbName()); svc->setSettings(ss); if(!dir.isEmpty()) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp index 3cc6cb3d7..641e40765 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp @@ -109,10 +109,6 @@ namespace Event::services { return base_url.toString(); } - QString stageConfigKey(const QString &prefix, const QString &suffix, int stage) - { - return prefix + QLatin1Char('.') + suffix + QStringLiteral(".E") + QString::number(stage); - } } OFeedClient::OFeedClient(QObject *parent) @@ -164,7 +160,7 @@ void OFeedClient::exportResultsIofXml3() m_resultsExportInProgress = true; int current_stage = getPlugin()->currentStageId(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QString str = is_relays ? getPlugin()->resultsIofXml30() @@ -184,7 +180,7 @@ void OFeedClient::exportStartListIofXml3(std::function on_success) m_startListExportInProgress = true; int current_stage = getPlugin()->currentStageId(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QString str = is_relays ? getPlugin()->startListIofXml30() @@ -304,7 +300,7 @@ void OFeedClient::onDbEventNotify(const QString &domain, int connection_id, cons auto lst = data.toList(); int run_id = lst.value(0).toInt(); auto dirty_vals = lst.value(1).toMap(); - qfInfo() << serviceName().toStdString() + " DB event RUN CHANGED received, run_id: " << run_id << ", dirty keys: " << dirty_vals.keys().join(", "); + // qfInfo() << serviceName().toStdString() << "DB event RUN CHANGED received, run_id: " << run_id << ", dirty keys: " << dirty_vals.keys().join(", "); if (!dirty_vals.isEmpty()) { static const QSet relevant_fields = { // Run fields (finishTimeMs and timeMs are covered by DBEVENT_CARD_PROCESSED_AND_ASSIGNED) @@ -319,7 +315,7 @@ void OFeedClient::onDbEventNotify(const QString &domain, int connection_id, cons QStringLiteral("classid"), }; bool has_relevant = false; - for (const auto &key : dirty_vals.keys()) { + for (const auto &[key, _] : dirty_vals.asKeyValueRange()) { if (relevant_fields.contains(key.section('.', -1).toLower())) { has_relevant = true; break; @@ -340,127 +336,118 @@ void OFeedClient::onDbEventNotify(const QString &domain, int connection_id, cons QString OFeedClient::hostUrl() const { int current_stage = getPlugin()->currentStageId(); - QString key = serviceName().toLower() + ".hostUrl.E" + QString::number(current_stage); - return normalized_base_host_url(getPlugin()->eventConfig()->value(key, k_default_host_url).toString()); + OFeedConfig cfg = getPlugin()->appDbConfig().ofeedConfig(current_stage); + return normalized_base_host_url(cfg.hostUrl); } QString OFeedClient::eventId() const { int current_stage = getPlugin()->currentStageId(); - return getPlugin()->eventConfig()->value(serviceName().toLower() + ".eventId.E" + QString::number(current_stage)).toString(); + OFeedConfig cfg = getPlugin()->appDbConfig().ofeedConfig(current_stage); + return cfg.eventId; } QString OFeedClient::eventPassword() const { int current_stage = getPlugin()->currentStageId(); - return getPlugin()->eventConfig()->value(serviceName().toLower() + ".eventPassword.E" + QString::number(current_stage)).toString(); + OFeedConfig cfg = getPlugin()->appDbConfig().ofeedConfig(current_stage); + return cfg.eventPassword; } QString OFeedClient::changelogOrigin() const { int current_stage = getPlugin()->currentStageId(); - QString key = serviceName().toLower() + ".changelogOrigin.E" + QString::number(current_stage); - return getPlugin()->eventConfig()->value(key, "START").toString(); + OFeedConfig cfg = getPlugin()->appDbConfig().ofeedConfig(current_stage); + return cfg.changelogOrigin; } bool OFeedClient::isInsertFromOFeed = false; QDateTime OFeedClient::lastChangelogCall() { int current_stage = getPlugin()->currentStageId(); - QString key = serviceName().toLower() + ".lastChangelogCall.E" + QString::number(current_stage); - + auto config = getPlugin()->appDbConfig().ofeedConfig(current_stage); // Retrieve the stored value from the configuration - QVariant value = getPlugin()->eventConfig()->value(key); + auto last_call = config.lastChangelogCall; // Check if the value exists - if (!value.isValid() || value.toString().isEmpty()) { + if (!last_call.isValid() || last_call.toString().isEmpty()) { // No valid value exists, set the initial value - QDateTime initialValue = QDateTime::fromSecsSinceEpoch(0); // Default to Unix epoch (1970-01-01T00:00:00Z) - getPlugin()->eventConfig()->setValue(key, initialValue.toString(Qt::ISODate)); - getPlugin()->eventConfig()->save(serviceName().toLower()); - // qDebug() << "No lastChangelogCall found. Setting initial value to:" << initialValue.toString(Qt::ISODate); - return initialValue; - } - - // Convert the stored string to QDateTime - QDateTime lastChangelog = QDateTime::fromString(value.toString(), Qt::ISODate); - - // Check if the conversion was successful - if (!lastChangelog.isValid()) { - // If invalid, set the default value - QDateTime initialValue = QDateTime::fromSecsSinceEpoch(0); // Default to Unix epoch - getPlugin()->eventConfig()->setValue(key, initialValue.toString(Qt::ISODate)); - getPlugin()->eventConfig()->save(serviceName().toLower()); - // qDebug() << "Invalid lastChangelogCall found. Setting initial value to:" << initialValue.toString(Qt::ISODate); - return initialValue; + config.lastChangelogCall = QDateTime::fromSecsSinceEpoch(0); // Default to Unix epoch (1970-01-01T00:00:00Z) + getPlugin()->appDbConfig().setOfeedConfig(current_stage, config); + // qDebug() << "No lastChangelogCall found. Setting initial value to:" << config.lastChangelogCall.toString(Qt::ISODate); } - - return lastChangelog; + return config.lastChangelogCall; } bool OFeedClient::runXmlValidation() { - int current_stage = getPlugin()->currentStageId(); - QString key = serviceName().toLower() + ".runXmlValidation.E" + QString::number(current_stage); - return getPlugin()->eventConfig()->value(key, "true").toBool(); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.ofeedConfig(current_stage).runXmlValidation; } bool OFeedClient::runChangesProcessing () { - int current_stage = getPlugin()->currentStageId(); - QString key = serviceName().toLower() + ".runChangesProcessing.E" + QString::number(current_stage); - return getPlugin()->eventConfig()->value(key, "false").toBool(); -}; - -QString OFeedClient::receiptConfigKey(const QString &suffix) const -{ - const int current_stage = getPlugin()->currentStageId(); - return stageConfigKey(k_event_config_prefix, suffix, current_stage); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.ofeedConfig(current_stage).runChangesProcessing; } -QVariant OFeedClient::receiptConfigValue(const QString &suffix, const QVariant &default_value) const -{ - return getPlugin()->eventConfig()->value(receiptConfigKey(suffix), default_value); -} +// QString OFeedClient::receiptConfigKey(const QString &suffix) const +// { +// const int current_stage = getPlugin()->currentStageId(); +// return stageConfigKey(k_event_config_prefix, suffix, current_stage); +// } -void OFeedClient::setReceiptConfigValue(const QString &suffix, const QVariant &value) -{ - auto *event_config = getPlugin()->eventConfig(); - event_config->setValue(receiptConfigKey(suffix), value); - event_config->save(k_event_config_prefix); -} +// QVariant OFeedClient::receiptConfigValue(const QString &suffix, const QVariant &default_value) const +// { +// return getPlugin()->appDbConfig().value(receiptConfigKey(suffix), default_value); +// } + +// void OFeedClient::setReceiptConfigValue(const QString &suffix, const QVariant &value) +// { +// auto *event_config = getPlugin()->appDbConfig(); +// event_config.setValue(receiptConfigKey(suffix), value); +// event_config.save(k_event_config_prefix); +// } bool OFeedClient::printEventImageOnReceipt() const { - return receiptConfigValue(QStringLiteral("receiptPrintEventImage"), false).toBool(); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.receiptsConfig(current_stage).printImage; } void OFeedClient::setPrintEventImageOnReceipt(bool on) { - setReceiptConfigValue(QStringLiteral("receiptPrintEventImage"), on); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + auto print_config = config.receiptsConfig(current_stage); + print_config.printImage = on; + config.setReceiptsConfig(current_stage, print_config); } bool OFeedClient::printEventQrCodeOnReceipt() const { - return receiptConfigValue(QStringLiteral("receiptPrintEventQrCode"), false).toBool(); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.receiptsConfig(current_stage).printQrCode; } void OFeedClient::setPrintEventQrCodeOnReceipt(bool on) { - setReceiptConfigValue(QStringLiteral("receiptPrintEventQrCode"), on); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + auto print_config = config.receiptsConfig(current_stage); + print_config.printQrCode = on; + config.setReceiptsConfig(current_stage, print_config); } int OFeedClient::receiptImageHeightMm() const { - bool ok = false; - int height_mm = receiptConfigValue(QStringLiteral("receiptImageHeightMm"), 18).toInt(&ok); - if(!ok) - return 18; - if(height_mm < 10) - return 10; - if(height_mm > 60) - return 60; - return height_mm; + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.receiptsConfig(current_stage).imageHeightMm; } void OFeedClient::setReceiptImageHeightMm(int height_mm) @@ -469,12 +456,18 @@ void OFeedClient::setReceiptImageHeightMm(int height_mm) height_mm = 10; else if(height_mm > 60) height_mm = 60; - setReceiptConfigValue(QStringLiteral("receiptImageHeightMm"), height_mm); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + auto rc = config.receiptsConfig(current_stage); + rc.imageHeightMm = height_mm; + config.setReceiptsConfig(current_stage, rc); } QString OFeedClient::receiptEventLinkUrl() const { - const QString custom_url = receiptConfigValue(QStringLiteral("receiptEventLinkUrl")).toString().trimmed(); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + const QString custom_url = config.receiptsConfig(current_stage).linkUrl; if(!custom_url.isEmpty()) return custom_url; return defaultReceiptEventLinkUrl(); @@ -496,12 +489,18 @@ QString OFeedClient::defaultReceiptEventLinkUrl() const void OFeedClient::setReceiptEventLinkUrl(QString link_url) { link_url = link_url.trimmed(); - setReceiptConfigValue(QStringLiteral("receiptEventLinkUrl"), link_url); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + auto rc = config.receiptsConfig(current_stage); + rc.linkUrl = link_url; + config.setReceiptsConfig(current_stage, rc); } QString OFeedClient::receiptEventQrCodeCaption() const { - return receiptConfigValue(QStringLiteral("receiptPrintEventQrCodeCaption"), defaultReceiptEventQrCodeCaption()).toString().trimmed(); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.receiptsConfig(current_stage).qrCodeCaption; } QString OFeedClient::defaultReceiptEventQrCodeCaption() const @@ -512,7 +511,11 @@ QString OFeedClient::defaultReceiptEventQrCodeCaption() const void OFeedClient::setReceiptEventQrCodeCaption(QString caption) { caption = caption.trimmed(); - setReceiptConfigValue(QStringLiteral("receiptPrintEventQrCodeCaption"), caption); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + auto rc = config.receiptsConfig(current_stage); + rc.qrCodeCaption = caption; + config.setReceiptsConfig(current_stage, rc); } bool OFeedClient::hasCachedEventImage() const @@ -522,28 +525,36 @@ bool OFeedClient::hasCachedEventImage() const QString OFeedClient::cachedEventImageBase64() const { - return receiptConfigValue(QStringLiteral("receiptImageDataBase64")).toString(); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.receiptsConfig(current_stage).imageBase64; } QString OFeedClient::cachedEventImageFormat() const { - return receiptConfigValue(QStringLiteral("receiptImageFormat"), QStringLiteral("png")).toString().toLower(); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + return config.receiptsConfig(current_stage).imageFormat; } void OFeedClient::setCachedEventImage(const QByteArray &raw_data, const QString &format) { - auto *event_config = getPlugin()->eventConfig(); - event_config->setValue(receiptConfigKey(QStringLiteral("receiptImageDataBase64")), QString::fromLatin1(raw_data.toBase64())); - event_config->setValue(receiptConfigKey(QStringLiteral("receiptImageFormat")), format.toLower()); - event_config->save(k_event_config_prefix); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + auto rc = config.receiptsConfig(current_stage); + rc.imageBase64 = QString::fromLatin1(raw_data.toBase64()); + rc.imageFormat = format.toLower(); + config.setReceiptsConfig(current_stage, rc); } void OFeedClient::clearCachedEventImage() { - auto *event_config = getPlugin()->eventConfig(); - event_config->setValue(receiptConfigKey(QStringLiteral("receiptImageDataBase64")), QString()); - event_config->setValue(receiptConfigKey(QStringLiteral("receiptImageFormat")), QString()); - event_config->save(k_event_config_prefix); + auto &config = getPlugin()->appDbConfig(); + auto current_stage = config.eventConfig().currentStageId; + auto rc = config.receiptsConfig(current_stage); + rc.imageBase64.clear(); + rc.imageFormat.clear(); + config.setReceiptsConfig(current_stage, rc); } void OFeedClient::ensureEventImageCachedAtStartup() @@ -660,67 +671,84 @@ void OFeedClient::refreshEventImageCache(std::function()->currentStageId(); - getPlugin()->eventConfig()->setValue(serviceName().toLower() + ".hostUrl.E" + QString::number(current_stage), hostUrl); - getPlugin()->eventConfig()->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.hostUrl = hostUrl; + config.setOfeedConfig(current_stage, cfg); m_eventImageStartupAttempted = false; } void OFeedClient::setEventId(QString eventId) { - int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue(serviceName().toLower() + ".eventId.E" + QString::number(current_stage), eventId); - getPlugin()->eventConfig()->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.eventId = eventId; + config.setOfeedConfig(current_stage, cfg); m_eventImageStartupAttempted = false; } void OFeedClient::setEventPassword(QString eventPassword) { - int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue(serviceName().toLower() + ".eventPassword.E" + QString::number(current_stage), eventPassword); - getPlugin()->eventConfig()->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.eventPassword = eventPassword; + config.setOfeedConfig(current_stage, cfg); m_eventImageStartupAttempted = false; } void OFeedClient::setChangelogOrigin(QString changelogOrigin) { - int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue(serviceName().toLower() + ".changelogOrigin.E" + QString::number(current_stage), changelogOrigin); - getPlugin()->eventConfig()->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.changelogOrigin = changelogOrigin; + config.setOfeedConfig(current_stage, cfg); } void OFeedClient::setLastChangelogCall(QDateTime lastChangelogCall) { - int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue(serviceName().toLower() + ".lastChangelogCall.E" + QString::number(current_stage), lastChangelogCall); - getPlugin()->eventConfig()->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.lastChangelogCall = lastChangelogCall; + config.setOfeedConfig(current_stage, cfg); } void OFeedClient::setRunXmlValidation(bool runXmlValidation) { - int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue(serviceName().toLower() + ".runXmlValidation.E" + QString::number(current_stage), runXmlValidation); - getPlugin()->eventConfig()->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.runXmlValidation = runXmlValidation; + config.setOfeedConfig(current_stage, cfg); } void OFeedClient::setRunChangesProcessing(bool runChangesProcessing) { - int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue(serviceName().toLower() + ".runChangesProcessing.E" + QString::number(current_stage), runChangesProcessing); - getPlugin()->eventConfig()->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.runChangesProcessing = runChangesProcessing; + config.setOfeedConfig(current_stage, cfg); } bool OFeedClient::introTourShowed() const { - return getPlugin()->eventConfig()->value( - serviceName().toLower() + QStringLiteral(".introTourShowed"), false).toBool(); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + return config.ofeedConfig(current_stage).introTourShowed; } void OFeedClient::setIntroTourShowed(bool shown) { - auto *event_config = getPlugin()->eventConfig(); - event_config->setValue(serviceName().toLower() + QStringLiteral(".introTourShowed"), shown); - event_config->save(serviceName().toLower()); + auto &config = getPlugin()->appDbConfig(); + const int current_stage = getPlugin()->currentStageId(); + auto cfg = config.ofeedConfig(current_stage); + cfg.introTourShowed = shown; + config.setOfeedConfig(current_stage, cfg); } void OFeedClient::testConnection(const QString &host_url, @@ -758,7 +786,7 @@ void OFeedClient::testConnection(const QString &host_url, const QByteArray response_bytes = reply->readAll(); const QString response_text = QString::fromUtf8(response_bytes).trimmed(); - auto callback_error = [&](const QString &msg) { + auto callback_error = [http_status, response_text, callback](const QString &msg) { const QString status_text = http_status > 0 ? QStringLiteral("HTTP %1").arg(http_status) : QStringLiteral("HTTP status unavailable"); const QString full_message = response_text.isEmpty() ? QStringLiteral("%1: %2").arg(status_text, msg) @@ -1373,7 +1401,7 @@ void OFeedClient::markChangelogEntryAsProcessed(int protocolId) variables["processedByType"] = QString("SYSTEM"); variables["processedBySource"] = QString("IT"); - sendGraphQLRequest(mutation, variables, [this, protocolId](QJsonObject data) { + sendGraphQLRequest(mutation, variables, [protocolId](QJsonObject data) { if (data.isEmpty()) { qfWarning() << serviceName().toStdString() + " Failed to mark changelog entry " << protocolId << " as processed"; } else { @@ -1522,7 +1550,7 @@ void OFeedClient::processNewRunner(int ofeed_competitor_id) // Save emits db event doc.save(); - auto competitor_id = doc.value("competitors.id"); + // auto competitor_id = doc.value("competitors.id"); // Get runs.id for current stage int current_stage = getPlugin()->currentStageId(); @@ -1563,8 +1591,8 @@ void OFeedClient::storeChange(const QJsonObject &change) QString firstname = competitor["firstname"].isString() ? competitor["firstname"].toString() : no_data; QString lastname = competitor["lastname"].isString() ? competitor["lastname"].toString() : no_data; - QJsonDocument change_json_doc(change); - QString change_json = QString::fromUtf8(change_json_doc.toJson(QJsonDocument::Compact)); + // QJsonDocument change_json_doc(change); + // QString change_json = QString::fromUtf8(change_json_doc.toJson(QJsonDocument::Compact)); int change_id = change["id"].toInt(); auto created = QDateTime::fromString(change["createdAt"].toString(), Qt::ISODate); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.h b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.h index 4a0d036b9..773ec3400 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.h @@ -9,8 +9,8 @@ class QTimer; class QNetworkAccessManager; -namespace Event { -namespace services { + +namespace Event::services { class OFeedClientSettings : public ServiceSettings { @@ -108,9 +108,9 @@ class OFeedClient : public Service void onExportTimerTimeOut(); void init(); void ensureEventImageCachedAtStartup(); - QString receiptConfigKey(const QString &suffix) const; - QVariant receiptConfigValue(const QString &suffix, const QVariant &default_value = QVariant()) const; - void setReceiptConfigValue(const QString &suffix, const QVariant &value); + // QString receiptConfigKey(const QString &suffix) const; + // QVariant receiptConfigValue(const QString &suffix, const QVariant &default_value = QVariant()) const; + // void setReceiptConfigValue(const QString &suffix, const QVariant &value); void setCachedEventImage(const QByteArray &raw_data, const QString &format); void clearCachedEventImage(); void checkCredentials(); @@ -134,6 +134,6 @@ class OFeedClient : public Service QByteArray zlibCompress(QByteArray data); }; -}} +} #endif // OFEEDCLIENT_H diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h index b91980d60..242409451 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h @@ -40,9 +40,9 @@ class OFeedClientWidget : public qf::gui::framework::DialogWidget QString defaultReceiptEventLink() const; OFeedClient* service(); bool saveSettings(); + bool acceptDialogDone(int result) override; private: Ui::OFeedClientWidget *ui; - bool acceptDialogDone(int result) override; bool m_isTestConnectionRunning = false; bool m_isImageRefreshRunning = false; QString m_lastAutoReceiptEventLink; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedconfig.cpp b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedconfig.cpp new file mode 100644 index 000000000..6c50a6f5c --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedconfig.cpp @@ -0,0 +1,40 @@ +#include "ofeedconfig.h" + +namespace Event::services { + +OFeedConfig::OFeedConfig() +{ + hostUrl = "https://api.orienteerfeed.com"; + changelogOrigin = "START"; + // lastChangelogCall = QDateTime::fromSecsSinceEpoch(0); +} + +OFeedConfig OFeedConfig::fromVariantMap(const QVariantMap& map) +{ + OFeedConfig config; + config.hostUrl = map.value("hostUrl", config.hostUrl).toString(); + config.eventId = map.value("eventId", config.eventId).toString(); + config.eventPassword = map.value("eventPassword", config.eventPassword).toString(); + config.changelogOrigin = map.value("changelogOrigin", config.changelogOrigin).toString(); + config.lastChangelogCall = map.value("lastChangelogCall", config.lastChangelogCall).toDateTime(); + config.runXmlValidation = map.value("runXmlValidation", config.runXmlValidation).toBool(); + config.runChangesProcessing = map.value("runChangesProcessing", config.runChangesProcessing).toBool(); + config.introTourShowed = map.value("introTourShowed", config.introTourShowed).toBool(); + return config; +} + +QVariantMap OFeedConfig::toVariantMap() const +{ + QVariantMap map; + map["hostUrl"] = hostUrl; + map["eventId"] = eventId; + map["eventPassword"] = eventPassword; + map["changelogOrigin"] = changelogOrigin; + map["lastChangelogCall"] = lastChangelogCall; + map["runXmlValidation"] = runXmlValidation; + map["runChangesProcessing"] = runChangesProcessing; + map["introTourShowed"] = introTourShowed; + return map; +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedconfig.h b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedconfig.h new file mode 100644 index 000000000..21259262c --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedconfig.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace Event::services { + +struct OFeedConfig +{ + OFeedConfig(); + static OFeedConfig fromVariantMap(const QVariantMap& map); + QVariantMap toVariantMap() const; + + QString hostUrl; + QString eventId; + QString eventPassword; + QString changelogOrigin; + QDateTime lastChangelogCall; + bool runXmlValidation = true; + bool runChangesProcessing = false; + // Non-stage-specific field, stored as ofeed.introTourShowed + bool introTourShowed = false; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp index 61c99f7fb..a322af9bf 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp @@ -60,7 +60,7 @@ void OResultsClient::stop() { void OResultsClient::exportResultsIofXml3() { int current_stage = getPlugin()->currentStageId(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QString str = is_relays ? getPlugin()->resultsIofXml30() @@ -72,7 +72,7 @@ void OResultsClient::exportResultsIofXml3() void OResultsClient::exportStartListIofXml3(std::function on_success) { int current_stage = getPlugin()->currentStageId(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QString str = is_relays ? getPlugin()->startListIofXml30() @@ -169,27 +169,31 @@ void OResultsClient::onDbEventNotify(const QString &domain, int connection_id, c QString OResultsClient::apiKey() const { int current_stage = getPlugin()->currentStageId(); - return getPlugin()->eventConfig()->value("oresults.apiKey.E" + QString::number(current_stage)).toString(); + return getPlugin()->appDbConfig().oresultsConfig(current_stage).apiKey; } void OResultsClient::setApiKey(QString apiKey) { int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue("oresults.apiKey.E" + QString::number(current_stage), apiKey); - getPlugin()->eventConfig()->save("oresults"); + auto &db = getPlugin()->appDbConfig(); + auto cfg = db.oresultsConfig(current_stage); + cfg.apiKey = apiKey; + db.setOresultsConfig(current_stage, cfg); } QString OResultsClient::eventName() const { int current_stage = getPlugin()->currentStageId(); - return getPlugin()->eventConfig()->value("oresults.eventName.E" + QString::number(current_stage)).toString(); + return getPlugin()->appDbConfig().oresultsConfig(current_stage).eventName; } void OResultsClient::setEventName(const QString &name) { int current_stage = getPlugin()->currentStageId(); - getPlugin()->eventConfig()->setValue("oresults.eventName.E" + QString::number(current_stage), name); - getPlugin()->eventConfig()->save("oresults"); + auto &db = getPlugin()->appDbConfig(); + auto cfg = db.oresultsConfig(current_stage); + cfg.eventName = name; + db.setOresultsConfig(current_stage, cfg); } void OResultsClient::sendCompetitorChange(QString xml) { @@ -214,20 +218,20 @@ void OResultsClient::sendCompetitorChange(QString xml) { reply->deleteLater(); }); } - -static void append_list(QVariantList &lst, const QVariantList &new_lst) +namespace { +void append_list(QVariantList &lst, const QVariantList &new_lst) { lst.insert(lst.count(), new_lst); } -static int mop_start(int runner_start_ms) { +int mop_start(int runner_start_ms) { int stage_id = getPlugin()->currentStageId(); QDateTime event_start = getPlugin()->stageStartDateTime(stage_id); QDateTime runner_start = event_start.addMSecs(runner_start_ms); return event_start.date().startOfDay().msecsTo(runner_start) / 100; } -static int mop_run_status_code( +int mop_run_status_code( int time, bool isDisq, bool isDisqByOrganizer, @@ -251,7 +255,7 @@ static int mop_run_status_code( return 1; // OK return 0; // Unknown } - +} void OResultsClient::onCompetitorChanged(int competitor_id) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsconfig.cpp b/quickevent/app/quickevent/plugins/Event/src/services/oresultsconfig.cpp new file mode 100644 index 000000000..a3b657588 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsconfig.cpp @@ -0,0 +1,21 @@ +#include "oresultsconfig.h" + +namespace Event::services { + +OResultsConfig OResultsConfig::fromVariantMap(const QVariantMap &map) +{ + OResultsConfig ret; + ret.apiKey = map.value("apiKey").toString(); + ret.eventName = map.value("eventName").toString(); + return ret; +} + +QVariantMap OResultsConfig::toVariantMap() const +{ + QVariantMap ret; + ret["apiKey"] = apiKey; + ret["eventName"] = eventName; + return ret; +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsconfig.h b/quickevent/app/quickevent/plugins/Event/src/services/oresultsconfig.h new file mode 100644 index 000000000..e9e75b36c --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsconfig.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace Event::services { + +struct OResultsConfig +{ + static OResultsConfig fromVariantMap(const QVariantMap &map); + QVariantMap toVariantMap() const; + + QString apiKey; + QString eventName; +}; + +} // namespace Event::services diff --git a/quickevent/app/quickevent/plugins/Event/src/services/punchingtest/punchingtestservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/punchingtest/punchingtestservice.cpp index 0f228fec4..f02b587d6 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/punchingtest/punchingtestservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/punchingtest/punchingtestservice.cpp @@ -139,7 +139,7 @@ void PunchingTestService::onTimerTick() // When enabled: 1/badCheckRate chance the runner checked too early (triggers bad-check). // When disabled: natural 1–2 minute window, no bad-check possible. int check_offset_sec; - if (auto cfg = event_plugin->eventConfig()->maximumCardCheckAdvanceSec(); cfg.has_value()) { + if (auto cfg = event_plugin->appDbConfig().eventConfig().maximumCardCheckAdvanceSec(); cfg.has_value()) { int max_sec = cfg.value(); if (rng.bounded(static_cast(ss.badCheckRate())) == 0) { // Bad check: checked too early — [max+1, max*3] seconds before start diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp new file mode 100644 index 000000000..b21873fd7 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -0,0 +1,410 @@ +#include "lateentrydialog.h" +#include "ui_lateentrydialog.h" + +#include "qxeventservice.h" +#include "runchange.h" + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace quickevent::core::og; + +namespace Event::services::qx { + +LateEntryDialog::LateEntryDialog(int change_id, int stage_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent) + : Super(parent) + , ui(new Ui::LateEntryDialog) + , m_changeId(change_id) + , m_stageId(stage_id) + , m_lateEntryId(late_entry.id) + , m_status(qxChangeStatusFromString(status)) +{ + Q_ASSERT(change_id > 0); + + ui->setupUi(this); + + ui->edStatusMessage->setPlainText(status_message); + + ui->btAccept->setDisabled(true); + ui->btReject->setDisabled(true); + + connect(ui->chkForce, &QCheckBox::checkStateChanged, this, [this]() { + updateButtonsEnabled(); + }); + connect(ui->btAccept, &QPushButton::clicked, this, [this]() { + resolveChangesAndClose(true); + done(QDialog::Accepted); + }); + connect(ui->btAcceptAndEdit, &QPushButton::clicked, this, [this]() { + resolveChangesAndClose(true); + emit editCompetitor(m_competitorId); + done(QDialog::Accepted); + }); + connect(ui->btReject, &QPushButton::clicked, this, [this]() { + resolveChangesAndClose(false); + done(QDialog::Accepted); + }); + connect(ui->btSendMessage, &QPushButton::clicked, this, &LateEntryDialog::updateQxChangeMessage); + + ui->edNote->setText(late_entry.note.value_or(QString())); + + ui->edChangeId->setValue(change_id); + + auto update_background = [](QWidget *widget) { + bool is_set = false; + if (auto *le = qobject_cast(widget)) { + is_set = !le->text().isEmpty(); + } + else if (auto *spin_box = qobject_cast(widget)) { + is_set = spin_box->value() > 0; + } + if (is_set) { + widget->setStyleSheet("background: palegreen"); + } + }; + + ui->grpFirstName->setChecked(late_entry.first_name.has_value()); + ui->edFirstName->setText(late_entry.first_name.value_or(QString())); + update_background(ui->edFirstName); + + ui->grpLastName->setChecked(late_entry.last_name.has_value()); + ui->edLastName->setText(late_entry.last_name.value_or(QString())); + update_background(ui->edLastName); + + ui->grpRegistration->setChecked(late_entry.registration.has_value()); + ui->edRegistration->setText(late_entry.registration.value_or(QString())); + update_background(ui->edRegistration); + + ui->grpSiCard->setChecked(late_entry.si_id.has_value()); + ui->edSiCard->setValue(late_entry.si_id.value_or(0)); + update_background(ui->edSiCard); + + ui->grpStartTime->setChecked(late_entry.start_time_ms.has_value()); + ui->edStartTime->setText(late_entry.start_time_ms.has_value()? TimeMs(late_entry.start_time_ms.value()).toString(): QString{}); + update_background(ui->edStartTime); + + connect(ui->edStartTime, &QLineEdit::textEdited, this, &LateEntryDialog::checkStartTimeIsValid); + + if (auto *run_id = std::get_if(&late_entry.id)) { + auto id = run_id->id; + ui->edRunId->setValue(id); + loadOrigValues(id); + checkStartTimeIsValid(); + } + else if (auto *id = std::get_if(&late_entry.id)) { + auto class_id = id->id; + loadClassName(class_id); + } + + if (late_entry.paid.value_or(false)) { + ui->lblPaid->setText(tr("Paid")); + ui->lblPaid->setStyleSheet("background:green;color:white"); + } else { + ui->lblPaid->setText(tr("Not Paid")); + ui->lblPaid->setStyleSheet("background:red;color:white"); + } + + checkDuplicitRegistration(); + checkDuplicitName(); + lockChange(); +} + +LateEntryDialog::~LateEntryDialog() +{ + delete ui; +} + +QxEventService *LateEntryDialog::service() +{ + auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); + Q_ASSERT(svc); + return svc; +} + +void LateEntryDialog::setMessage(const QString &msg, bool error) +{ + if (msg.isEmpty()) { + ui->lblError->setStyleSheet({}); + } + else if (error) { + ui->lblError->setStyleSheet("background: salmon"); + } + else { + ui->lblError->setStyleSheet({}); + } + ui->lblError->setText(msg); +} + +void LateEntryDialog::loadOrigValues(int run_id) +{ + Q_ASSERT(run_id > 0); + + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT classes.name AS class_name, competitors.id AS competitor_id" + " FROM runs" + " JOIN competitors ON competitors.id=runs.competitorId AND runs.id=%1" + " LEFT JOIN classes ON competitors.classId=classes.id") + .arg(run_id) + ); + if (q.next()) { + ui->edClassName->setText(q.value("class_name").toString()); + m_competitorId = q.value("competitor_id").toInt(); + } + + Competitors::CompetitorDocument doc; + doc.load(m_competitorId, qf::gui::model::DataDocument::ModeView); + + m_origValues.first_name = doc.value("firstName").toString(); + m_origValues.last_name = doc.value("lastName").toString(); + m_origValues.registration = doc.value("registration").toString(); + { + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT startTimeMs" + " FROM runs" + " WHERE id=%1") + .arg(run_id) + ); + if (q.next()) { + m_origValues.start_time_ms = q.value("startTimeMs").toInt(); + } + } + m_origValues.si_id = doc.value("siId").toInt(); + + ui->edFirstNameOrig->setText(m_origValues.first_name); + ui->edLastNameOrig->setText(m_origValues.last_name); + ui->edRegistrationOrig->setText(m_origValues.registration); + ui->edStartTimeOrig->setText(TimeMs(m_origValues.start_time_ms).toString()); + ui->edSiCardOrig->setValue(m_origValues.si_id); +} + +void LateEntryDialog::loadClassName(int class_id) +{ + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT name FROM classes WHERE id=%1") + .arg(class_id) ); + if (q.next()) { + ui->edClassName->setText(q.value(0).toString()); + } +} + +void LateEntryDialog::unlockChange() const +{ + if (m_lockNumber > 0 || ui->chkForce->isChecked()) { + qf::core::sql::Query q; + q.execThrow(QStringLiteral("UPDATE qxchanges SET lock_number=NULL WHERE id=%1") + .arg(m_changeId) ); + } +} + +void LateEntryDialog::lockChange() +{ + auto lock_number = QxEventService::currentConnectionId(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("UPDATE qxchanges SET lock_number=%1 WHERE id=%2 AND (lock_number IS NULL OR lock_number=%1)") + .arg(lock_number) + .arg(m_changeId) + ); + if (q.numRowsAffected() == 0) { + m_lockNumber = 0; + setMessage(tr("Change is already locked by other user."), true); + } + else { + m_lockNumber = lock_number; + } + updateButtonsEnabled(); +} + +void LateEntryDialog::updateButtonsEnabled() +{ + ui->btAccept->setEnabled(m_status == QxChangeStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); + ui->btReject->setEnabled(m_status == QxChangeStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); +} + +void LateEntryDialog::resolveChangesAndClose(bool is_accepted) +{ + using namespace qf::gui::model; + + if (is_accepted) { + auto class_id = classId(); + bool is_insert = class_id.has_value() && !m_setIsRunning; + Competitors::CompetitorDocument doc; + doc.load(m_competitorId, is_insert? DataDocument::ModeInsert: DataDocument::ModeEdit); + if (is_insert) { + doc.setValue("classId", class_id.value()); + } + if (ui->grpFirstName->isChecked()) { + doc.setValue("firstName", ui->edFirstName->text()); + } + if (ui->grpLastName->isChecked()) { + doc.setValue("lastName", ui->edLastName->text()); + } + if (ui->grpRegistration->isChecked()) { + doc.setValue("registration", ui->edRegistration->text()); + } + if (ui->grpSiCard->isChecked()) { + if (is_insert) { + doc.setValue("siId", ui->edSiCard->value()); + } + } + doc.save(); + m_competitorId = doc.dataId().toInt(); + qf::core::sql::Record rec; + if (ui->grpSiCard->isChecked()) { + rec["siId"] = ui->edSiCard->value(); + } + if (ui->grpStartTime->isChecked()) { + rec["starttimems"] = TimeMs::fromString(ui->edStartTime->text()).msec(); + } + if (m_setIsRunning) { + rec["isRunning"] = true; + } + if (!rec.isEmpty()) { + auto competitor_id = doc.value("id").toInt(); + Q_ASSERT(competitor_id > 0); + int run_id = 0; + { + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT id FROM runs WHERE competitorId=%1 AND stageId=%2") + .arg(competitor_id) + .arg(m_stageId) + ); + if (q.next()) { + run_id = q.value(0).toInt(); + } + } + Q_ASSERT(run_id > 0); + qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", run_id, rec, this); + } + } + + qf::core::sql::Record rec { + {"status", is_accepted? "Accepted": "Rejected"}, + {"status_message", ui->edStatusMessage->toPlainText()}, + {"orig_data", qf::core::Utils::qvariantToJson(is_accepted? m_origValues.toVariantMap(): QVariantMap{})}, + }; + qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); +} + +void LateEntryDialog::updateQxChangeMessage() +{ + qf::core::sql::Record rec { + { "status_message", ui->edStatusMessage->toPlainText() }, + }; + qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); +} + +void LateEntryDialog::checkDuplicitRegistration() +{ + auto class_id = classId(); + if (!ui->grpRegistration->isChecked() || !class_id.has_value()) { + return; + } + auto reg = ui->edRegistration->text().trimmed().toUpper(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT id, firstName, lastName FROM competitors WHERE registration='%1' AND classId=%2") + .arg(reg).arg(class_id.value()) ); + if (q.next()) { + setMessage(tr("Competitor %1 %2 %3 is registered in this class already, run will be updated.") + .arg(q.value("firstName").toString()) + .arg(q.value("lastName").toString()) + .arg(reg) + , true); + auto competitor_id = q.value("id").toInt(); + Q_ASSERT(competitor_id > 0); + changeEventEntryToStageEntry(competitor_id); + } +} + +void LateEntryDialog::checkDuplicitName() +{ + auto class_id = classId(); + if (!ui->grpFirstName->isChecked() || !ui->grpLastName->isChecked() || !class_id.has_value()) { + return; + } + auto first_name = ui->edFirstName->text().trimmed(); + auto last_name = ui->edLastName->text().trimmed(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT id, registration FROM competitors WHERE firstName='%1' AND lastName='%2' AND classId=%3") + .arg(first_name) + .arg(last_name) + .arg(class_id.value())); + if (q.next()) { + setMessage(tr("Competitor %1 %2 %3 is registered in this class already, run will be updated.") + .arg(first_name) + .arg(last_name) + .arg(q.value("registration").toString()) + , true); + auto competitor_id = q.value("id").toInt(); + Q_ASSERT(competitor_id > 0); + changeEventEntryToStageEntry(competitor_id); + } +} + +void LateEntryDialog::changeEventEntryToStageEntry(int competitor_id) +{ + m_setIsRunning = true; + m_competitorId = competitor_id; + + ui->grpFirstName->setChecked(false); + ui->grpLastName->setChecked(false); + ui->grpRegistration->setChecked(false); +} + +void LateEntryDialog::checkStartTimeIsValid() +{ + if (!ui->grpStartTime->isChecked()) { + ui->edStartTime->setStyleSheet({}); + return; + } + if (auto run_id = runId(); run_id.has_value()) { + auto stime = TimeMs::fromString(ui->edStartTime->text()); + if (stime.isValid()) { + auto possible_stimes = Competitors::CompetitorDocument::possibleStartTimesMs(run_id.value()); + if (possible_stimes.contains(stime.msec())) { + ui->edStartTime->setStyleSheet({}); + } else { + ui->edStartTime->setStyleSheet("background: red"); + } + } + } +} + +void LateEntryDialog::done(int result) +{ + unlockChange(); + Super::done(result); +} + +std::optional LateEntryDialog::runId() const +{ + if (auto *id = std::get_if(&m_lateEntryId)) { + return id->id; + } + return std::nullopt; +} + +std::optional LateEntryDialog::classId() const +{ + if (auto *id = std::get_if(&m_lateEntryId)) { + return id->id; + } + return std::nullopt; +} + +} // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h new file mode 100644 index 000000000..fee286b76 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -0,0 +1,66 @@ +#ifndef LATEENTRYDIALOG_H +#define LATEENTRYDIALOG_H + +#include "runchange.h" + +#include + +class QNetworkReply; + +namespace Event::services::qx { + +namespace Ui { +class LateEntryDialog; +} + +struct LateEntry; +class QxEventService; + +class LateEntryDialog : public QDialog +{ + Q_OBJECT + using Super = QDialog; +public: + explicit LateEntryDialog(int change_id, int stage_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent = nullptr); + ~LateEntryDialog() override; + + Q_SIGNAL void editCompetitor(int competitor_id); + + void done(int result) override; +private: + std::optional runId() const; + std::optional classId() const; + QxEventService* service(); + void setMessage(const QString &msg, bool error); + + void loadOrigValues(int run_id); + void loadClassName(int class_id); + + void lockChange(); + void unlockChange() const; + void updateButtonsEnabled(); + + void resolveChangesAndClose(bool is_accepted); + void updateQxChangeMessage(); + + void checkDuplicitRegistration(); + void checkDuplicitName(); + void checkStartTimeIsValid(); + void changeEventEntryToStageEntry(int competitor_id); +private: + Ui::LateEntryDialog *ui; + int m_changeId = 0; + int m_stageId = 0; + LateEntryForeignId m_lateEntryId = RunId{}; + int m_competitorId = 0; + int m_lockNumber = 0; + QxChangeStatus m_status = QxChangeStatus::Rejected; + OrigRunRecord m_origValues; + bool m_setIsRunning = false; +}; + + +} // namespace Event::services::qx + + +#endif // LATEENTRYDIALOG_H diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui similarity index 79% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index c18f157b3..6e2cee6ef 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -1,19 +1,19 @@ - Event::services::qx::RunChangeDialog - + Event::services::qx::LateEntryDialog + 0 0 712 - 569 + 696 Dialog - + @@ -101,35 +101,6 @@ - - - - Lock number - - - - - - - - 0 - 0 - - - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - - - true - - - None - - - 999999999 - - - @@ -142,7 +113,11 @@ - + + + true + + @@ -170,7 +145,11 @@ - + + + true + + @@ -198,7 +177,11 @@ - + + + true + + @@ -290,6 +273,38 @@ + + + + Start time + + + true + + + + + + true + + + + + + + -> + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + @@ -308,6 +323,40 @@ + + + + + + Message + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Send + + + + + + + + @@ -322,18 +371,23 @@ - - - - - Message - - - - - - - + + + + 0 + 0 + + + + QFrame::Shape::Box + + + Not Paid + + + Qt::AlignmentFlag::AlignCenter + + @@ -351,7 +405,7 @@ - 40 + 0 20 @@ -378,6 +432,13 @@ + + + + Accept and Edit + + + @@ -386,7 +447,6 @@ edClassName edRunId edChangeId - edLockNumber grpFirstName edFirstNameOrig edFirstName @@ -403,6 +463,7 @@ edSiCardRent edNote edStatusMessage + btSendMessage chkForce btAccept btReject @@ -413,7 +474,7 @@ btCancel clicked() - Event::services::qx::RunChangeDialog + Event::services::qx::LateEntryDialog reject() diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp new file mode 100644 index 000000000..6a4db4533 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp @@ -0,0 +1,51 @@ +#include "nodes.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace qf::core::sql; +using namespace shv::chainpack; + +namespace Event::services::qx { + +//========================================================= +// DotAppNode +//========================================================= +namespace { +auto METH_NAME = "name"; +} +const std::vector &DotAppNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {Rpc::METH_PING, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Browse}, + {METH_NAME, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Browse}, + }; + return meta_methods; +} + +RpcValue DotAppNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == Rpc::METH_PING) { + return nullptr; + } + if(method == METH_NAME) { + return "QuickEvent"; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h new file mode 100644 index 000000000..58cf1125e --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h @@ -0,0 +1,22 @@ +#pragma once + +#include "qxnode.h" + +#include + +namespace Event::services::qx { + +class DotAppNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit DotAppNode(shv::iotqt::node::ShvNode *parent) : Super(".app", parent) {} +private: + //shv::chainpack::RpcValue callMethodRq(const shv::chainpack::RpcRequest &rq) override; + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp deleted file mode 100644 index e9a04902e..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "qxclientservicewidget.h" -#include "ui_qxclientservicewidget.h" -#include "qxclientservice.h" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using qf::gui::framework::getPlugin; - -namespace Event::services::qx { - -QxClientServiceWidget::QxClientServiceWidget(QWidget *parent) - : Super(parent) - , ui(new Ui::QxClientServiceWidget) -{ - setPersistentSettingsId("QxClientServiceWidget"); - ui->setupUi(this); - connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxClientServiceWidget::updateOCheckListPostUrl); - connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxClientServiceWidget::updateOCheckListPostUrl); - - setMessage(""); - - auto *svc = service(); - Q_ASSERT(svc); - auto *event_plugin = getPlugin(); - auto current_stage = event_plugin->currentStageId(); - auto settings = svc->settings(); - ui->edServerUrl->setText(settings.exchangeServerUrl()); - ui->edApiToken->setText(svc->apiToken()); - ui->edCurrentStage->setValue(current_stage); - connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxClientServiceWidget::testConnection); - connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportEventInfo); - connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportStartList); - connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportRuns); -} - -QxClientServiceWidget::~QxClientServiceWidget() -{ - delete ui; -} - -void QxClientServiceWidget::setMessage(const QString &msg, MessageType msg_type) -{ - if (msg.isEmpty()) { - ui->lblStatus->setStyleSheet({}); - } - else { - switch (msg_type) { - case MessageType::Ok: - ui->lblStatus->setStyleSheet("background: lightgreen"); - break; - case MessageType::Error: - ui->lblStatus->setStyleSheet("background: salmon"); - break; - case MessageType::Progress: - ui->lblStatus->setStyleSheet("background: orange"); - break; - } - } - ui->lblStatus->setText(msg); -} - -bool QxClientServiceWidget::acceptDialogDone(int result) -{ - if(result == QDialog::Accepted) { - if(!saveSettings()) { - return false; - } - } - return true; -} - -QxClientService *QxClientServiceWidget::service() -{ - auto *svc = qobject_cast(Service::serviceByName(QxClientService::serviceId())); - QF_ASSERT(svc, QxClientService::serviceId() + " doesn't exist", return nullptr); - return svc; -} - -bool QxClientServiceWidget::saveSettings() -{ - auto *svc = service(); - if(svc) { - auto ss = svc->settings(); - ss.setExchangeServerUrl(ui->edServerUrl->text()); - svc->setSettings(ss); - auto *event_plugin = getPlugin(); - - auto current_stage = event_plugin->currentStageId(); - auto stage_data = event_plugin->stageData(current_stage); - stage_data.setQxApiToken(ui->edApiToken->text()); - event_plugin->setStageData(current_stage, stage_data); - } - return true; -} - -void QxClientServiceWidget::updateOCheckListPostUrl() -{ - auto url = QStringLiteral("%1/api/event/current/oc").arg(ui->edServerUrl->text()); - ui->edOChecklistUrl->setText(url); - ui->edOChecklistUrlHeader->setText(QStringLiteral("qx-api-token=%1").arg(ui->edApiToken->text())); -} - -void QxClientServiceWidget::testConnection() -{ - auto *svc = service(); - Q_ASSERT(svc); - auto *reply = svc->getRemoteEventInfo(ui->edServerUrl->text(), ui->edApiToken->text()); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto data = reply->readAll(); - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - ui->edEventId->setValue(event_info.id()); - setMessage(tr("Connected OK")); - } - else { - setMessage(tr("Connection error: %1").arg(reply->errorString()), MessageType::Error); - } - reply->deleteLater(); - }); -} - -void QxClientServiceWidget::exportEventInfo() -{ - auto *svc = service(); - Q_ASSERT(svc); - auto *reply = svc->postEventInfo(ui->edServerUrl->text(), ui->edApiToken->text()); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - auto data = reply->readAll(); - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - ui->edEventId->setValue(event_info.id()); - setMessage(tr("Event info updated OK")); - } - else { - setMessage(tr("Event info update error: %1\n%2").arg(reply->errorString()).arg(QString::fromUtf8(data)), MessageType::Error); - } - reply->deleteLater(); - }); -} - -void QxClientServiceWidget::exportStartList() -{ - auto *svc = service(); - Q_ASSERT(svc); - saveSettings(); - setMessage(tr("Start list export started ..."), MessageType::Progress); - svc->postStartListIofXml3(this, [this](auto err) { - if (err.isEmpty()) { - setMessage(tr("Start list exported Ok")); - } - else { - setMessage(err, MessageType::Error); - } - }); -} - -void QxClientServiceWidget::exportRuns() -{ - auto *svc = service(); - Q_ASSERT(svc); - saveSettings(); - setMessage(tr("Runs export started ..."), MessageType::Progress); - svc->postRuns(this, [this](auto err) { - if (err.isEmpty()) { - setMessage(tr("Runs exported Ok")); - } - else { - setMessage(err, MessageType::Error); - } - }); -} - -} - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp similarity index 52% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index e80607c83..ac24a0b23 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -1,5 +1,7 @@ -#include "qxclientservice.h" -#include "qxclientservicewidget.h" +#include "qxeventservice.h" +#include "qxeventservicewidget.h" +#include "nodes.h" +#include "sqlapinode.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -9,6 +11,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -24,6 +30,10 @@ #include #include +using namespace shv::chainpack; +using namespace shv::iotqt::rpc; +using namespace shv::iotqt::node; + using namespace qf::core; using namespace qf::gui; using namespace qf::gui::dialogs; @@ -33,89 +43,92 @@ using Event::EventPlugin; using Runs::RunsPlugin; namespace Event::services::qx { -//=============================================== -// QxClientServiceSettings -//=============================================== -// QString QxClientServiceSettings::eventKey() const -// { -// auto *event_plugin = getPlugin(); -// auto *cfg = event_plugin->eventConfig(); -// auto key = cfg->apiKey(); -// auto current_stage = cfg->currentStageId(); -// return QStringLiteral("%1%2").arg(key).arg(current_stage); -// } //=============================================== // QxClientService //=============================================== -QxClientService::QxClientService(QObject *parent) - : Super(QxClientService::serviceId(), parent) +QxEventService::QxEventService(QObject *parent) + : Super(QxEventService::serviceId(), parent) + , m_rootNode(new shv::iotqt::node::ShvRootNode(this)) { - auto *event_plugin = getPlugin(); - connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxClientService::onDbEventNotify, Qt::QueuedConnection); + new DotAppNode(m_rootNode); + new SqlApiNode(m_rootNode); + + connect(m_rootNode, &shv::iotqt::node::ShvNode::sendRpcMessage, this, &QxEventService::sendRpcMessage); } -QString QxClientService::serviceDisplayName() const +QString QxEventService::serviceDisplayName() const { - return tr("QE Exchange"); + return tr("QX Event"); } -QString QxClientService::serviceId() +QString QxEventService::serviceId() { return QStringLiteral("qx"); } -void QxClientService::run() { +void QxEventService::run() { + using namespace shv::iotqt::rpc; + auto ss = settings(); - auto *reply = getRemoteEventInfo(ss.exchangeServerUrl(), apiToken()); - connect(reply, &QNetworkReply::finished, this, [this, reply, ss]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto data = reply->readAll(); - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - setStatusMessage(event_info.name() + (event_info.stage_count() > 1? QStringLiteral(" E%1").arg(event_info.stage()): QString())); - m_eventId = event_info.id(); - connectToSSE(m_eventId); - if (!m_pollChangesTimer) { - m_pollChangesTimer = new QTimer(this); - connect(m_pollChangesTimer, &QTimer::timeout, this, &QxClientService::pollQxChanges); - } - pollQxChanges(); - m_pollChangesTimer->start(10000); - Super::run(); - } - else { - qfWarning() << "Cannot run QX service, network error:" << reply->errorString(); - } - }); + + delete m_rpcConnection; + m_eventId = 0; + + auto api_token = apiToken(); + if (api_token.isEmpty()) { + setStatus(Status::Stopped); + setStatusMessage(tr("API token is not set.")); + } + + m_rpcConnection = new DeviceConnection("QuickEvent", this); + m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); + if (ss.isExportDatabase()) { + RpcValue::Map opts; + RpcValue::Map device; + device["deviceId"] = api_token.toStdString(); + opts["device"] = device; + m_rpcConnection->setConnectionOptions(opts); + } + + connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxEventService::onBrokerConnectedChanged); + connect(m_rpcConnection, &ClientConnection::socketError, this, &QxEventService::onBrokerSocketError); + connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); + connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); + + // connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &QxEventService::sendRecchgShvSignal); + + m_rpcConnection->open(); } -void QxClientService::stop() +void QxEventService::stop() { - disconnectSSE(); + delete m_rpcConnection; + m_rpcConnection = nullptr; + m_eventId = 0; if (m_pollChangesTimer) { m_pollChangesTimer->stop(); } Super::stop(); } -qf::gui::framework::DialogWidget *QxClientService::createDetailWidget() +qf::gui::framework::DialogWidget *QxEventService::createDetailWidget() { - auto *w = new QxClientServiceWidget(); + auto *w = new QxEventServiceWidget(); return w; } -void QxClientService::loadSettings() +void QxEventService::loadSettings() { Super::loadSettings(); auto ss = settings(); - if (ss.exchangeServerUrl().isEmpty()) { - ss.setExchangeServerUrl("http://localhost:8000"); + if (ss.shvBrokerUrl().isEmpty()) { + ss.setShvBrokerUrl("tcp://localhost?user=test&password=test"); } m_settings = ss; } -void QxClientService::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) +void QxEventService::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) { Q_UNUSED(connection_id) Q_UNUSED(data) @@ -162,7 +175,7 @@ void QxClientService::onDbEventNotify(const QString &domain, int connection_id, } } -QNetworkAccessManager *QxClientService::networkManager() +QNetworkAccessManager *QxEventService::networkManager() { if (!m_networkManager) { m_networkManager = new QNetworkAccessManager(this); @@ -170,7 +183,7 @@ QNetworkAccessManager *QxClientService::networkManager() return m_networkManager; } -QNetworkReply *QxClientService::getRemoteEventInfo(const QString &qxhttp_host, const QString &api_token) +QNetworkReply *QxEventService::getRemoteEventInfo(const QString &qxhttp_host, const QString &api_token) { auto *nm = networkManager(); QNetworkRequest request; @@ -181,7 +194,7 @@ QNetworkReply *QxClientService::getRemoteEventInfo(const QString &qxhttp_host, c return nm->get(request); } -QNetworkReply *QxClientService::postEventInfo(const QString &qxhttp_host, const QString &api_token) +QNetworkReply *QxEventService::postEventInfo(const QString &qxhttp_host, const QString &api_token) { auto *nm = networkManager(); QNetworkRequest request; @@ -197,22 +210,22 @@ QNetworkReply *QxClientService::postEventInfo(const QString &qxhttp_host, const return nm->post(request, data); } -void QxClientService::postStartListIofXml3(QObject *context, std::function call_back) +void QxEventService::postStartListIofXml3(QObject *context, std::function call_back) { auto *ep = getPlugin(); int current_stage = ep->currentStageId(); - bool is_relays = ep->eventConfig()->isRelays(); + bool is_relays = ep->appDbConfig().eventConfig().isRelays(); if (!is_relays) { auto xml = getPlugin()->startListStageIofXml30(current_stage, quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners); uploadSpecFile(SpecFile::StartListIofXml3, xml.toUtf8(), context, call_back); } } -void QxClientService::postRuns(QObject *context, std::function call_back) +void QxEventService::postRuns(QObject *context, std::function call_back) { auto *ep = getPlugin(); int current_stage = ep->currentStageId(); - bool is_relays = ep->eventConfig()->isRelays(); + bool is_relays = ep->appDbConfig().eventConfig().isRelays(); if (!is_relays) { auto runs = getPlugin()->qxExportRunsCsvJson(current_stage); auto json = qf::core::Utils::qvariantToJsonUtf8(runs, false); @@ -220,9 +233,9 @@ void QxClientService::postRuns(QObject *context, std::function c } } -void QxClientService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) +void QxEventService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path); url.setQuery(query); // qfInfo() << url.toString(); @@ -249,9 +262,9 @@ void QxClientService::getHttpJson(const QString &path, const QUrlQuery &query, Q }); } -QNetworkReply* QxClientService::getQxChangesReply(int from_id) +QNetworkReply* QxEventService::getQxChangesReply(int from_id) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(QStringLiteral("/api/event/%1/changes").arg(eventId())); url.setQuery(QStringLiteral("from_id=%1").arg(from_id)); @@ -261,32 +274,29 @@ QNetworkReply* QxClientService::getQxChangesReply(int from_id) return networkManager()->get(request); } -int QxClientService::eventId() const +int QxEventService::eventId() const { - if (m_eventId == 0) { - throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); - } + // if (m_eventId == 0) { + // throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); + // } return m_eventId; } -QByteArray QxClientService::apiToken() const +QString QxEventService::apiToken() const { - // API token must not be cached to enable service point - // always to current stage event on qxhttpd auto *event_plugin = getPlugin(); - auto current_stage = event_plugin->currentStageId(); - return event_plugin->stageData(current_stage).qxApiToken().toUtf8(); + return event_plugin->appDbConfig().qxConfig().apiToken; } -QUrl QxClientService::exchangeServerUrl() const +QUrl QxEventService::shvBrokerUrl() const { auto ss = settings(); - return QUrl(ss.exchangeServerUrl()); + return QUrl(ss.shvBrokerUrl()); } -void QxClientService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) +void QxEventService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path.value_or("/api/event/current/file")); if (name.has_value()) { @@ -294,7 +304,7 @@ void QxClientService::postFileCompressed(std::optional path, std::optio } QNetworkRequest request; request.setUrl(url); - request.setRawHeader(QX_API_TOKEN, apiToken()); + request.setRawHeader(QX_API_TOKEN, apiToken().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/zip")); auto zdata = zlibCompress(data); QNetworkReply *reply = networkManager()->post(request, zdata); @@ -312,7 +322,7 @@ void QxClientService::postFileCompressed(std::optional path, std::optio }); } -void QxClientService::uploadSpecFile(SpecFile file, QByteArray data, QObject *context, const std::function &call_back) +void QxEventService::uploadSpecFile(SpecFile file, QByteArray data, QObject *context, const std::function &call_back) { switch (file) { case SpecFile::StartListIofXml3: @@ -324,7 +334,7 @@ void QxClientService::uploadSpecFile(SpecFile file, QByteArray data, QObject *co } } -QByteArray QxClientService::zlibCompress(QByteArray data) +QByteArray QxEventService::zlibCompress(QByteArray data) { QByteArray compressedData = qCompress(data); // strip the 4-byte length put on by qCompress @@ -333,19 +343,19 @@ QByteArray QxClientService::zlibCompress(QByteArray data) return compressedData; } -void QxClientService::httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context, const std::function &call_back) +void QxEventService::httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context, const std::function &call_back) { if (!isRunning()) { return; } - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path); url.setQuery(query); QNetworkRequest request; request.setUrl(url); - request.setRawHeader(QX_API_TOKEN, apiToken()); + request.setRawHeader(QX_API_TOKEN, apiToken().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); auto data = QJsonDocument::fromVariant(json).toJson(QJsonDocument::Compact); qfInfo() << "HTTP POST JSON:" << url.toString() << "data:" << QString::fromUtf8(data); @@ -373,122 +383,15 @@ void QxClientService::httpPostJson(const QString &path, const QString &query, QV } } -void QxClientService::connectToSSE(int event_id) -{ - Q_UNUSED(event_id); - // auto url = exchangeServerUrl(); - // url.setPath(QStringLiteral("/api/event/%1/run/changes/sse").arg(event_id)); - // QNetworkRequest request(url); - // request.setRawHeader(QByteArray("Accept"), QByteArray("text/event-stream")); - // request.setHeader(QNetworkRequest::UserAgentHeader, "QuickEvent"); - // request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); // Events shouldn't be cached - - // qfInfo() << "Connecting to SSE:" << url.toString(); - // m_replySSE = networkManager()->get(request); - // qfInfo() << "Connected"; - // connect(m_replySSE, &QNetworkReply::readyRead, this, [this]() { - // auto data = m_replySSE->readAll(); - // qfInfo() << "DATA:" << data.toStdString(); - // }); - // connect(m_replySSE, &QNetworkReply::finished, this, [this]() { - // qfInfo() << "SSE finished:" << m_replySSE->errorString(); - // }); -} - -void QxClientService::disconnectSSE() -{ - if (m_replySSE) { - qfInfo() << "Disconnecting SSE:" << m_replySSE; - m_replySSE->deleteLater(); - m_replySSE = nullptr; - } -} - -void QxClientService::pollQxChanges() -{ - auto event_plugin = getPlugin(); - if(!getPlugin()->isEventOpen()) { - return; - } - int stage_id = event_plugin->currentStageId(); - try { - int max_change_id = 0; - qf::core::sql::Query q; - q.execThrow("SELECT MAX(change_id) FROM qxchanges WHERE stage_id=" + QString::number(event_plugin->currentStageId())); - if (q.next()) { - max_change_id = q.value(0).toInt(); - } - auto *reply = getQxChangesReply(max_change_id + 1); - connect(reply, &QNetworkReply::finished, this, [reply, stage_id]() { - QString err; - if(reply->error()) { - err = reply->errorString(); - qfWarning() << "Load qxchanges error:" << err; - } - else { - QJsonParseError err; - auto data = reply->readAll(); - auto json = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) { - qfWarning() << "Parse qxchanges error:" << err.errorString(); - } - else { - auto records = json.array().toVariantList(); - qf::core::sql::Query q; - q.prepare("INSERT INTO qxchanges (data_type, data, data_id, source, user_id, status, status_message, stage_id, change_id, created)" - " VALUES (:data_type, :data, :data_id, :source, :user_id, :status, :status_message, :stage_id, :change_id, :created)" - " RETURNING id"); - for (const auto &v : records) { - auto rec = v.toMap(); - auto ba = QJsonDocument::fromVariant(rec.value("data")).toJson(QJsonDocument::Compact); - auto data = QString::fromUtf8(ba); - q.bindValue(":data_type", rec.value("data_type")); - q.bindValue(":data", data); - q.bindValue(":data_id", rec.value("data_id")); - q.bindValue(":source", rec.value("source")); - q.bindValue(":user_id", rec.value("user_id")); - q.bindValue(":status", rec.value("status")); - q.bindValue(":status_message", rec.value("status_message")); - q.bindValue(":stage_id", stage_id); - auto change_id = rec.value("id").toInt(); - Q_ASSERT(change_id > 0); - q.bindValue(":change_id", change_id); - auto created = QDateTime::fromString(rec.value("created").toString(), Qt::ISODate); - qfDebug() << "created:" << created.toString(Qt::ISODate); - q.bindValue(":created", created); - // may fail on prikey violation if more clients are inserting simultaneously - if (q.exec()) { - if (q.next()) { - auto id = q.value(0).toInt(); - qfDebug() << "insert ID:" << id; - getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED, id, true); - } - } - else { - qfInfo() << "sql error:" << q.lastErrorText(); - - } - } - } - } - reply->deleteLater(); - }); - } - catch (const qf::core::Exception &e) { - qfWarning() << "Load qxchanges error:" << e.message(); - } -} - -EventInfo QxClientService::eventInfo() const +EventInfo QxEventService::eventInfo() const { auto *event_plugin = getPlugin(); - auto *event_config = event_plugin->eventConfig(); + const auto &event_config = event_plugin->appDbConfig().eventConfig(); EventInfo ei; ei.set_stage(event_plugin->currentStageId()); ei.set_stage_count(event_plugin->stageCount()); - ei.set_name(event_config->eventName()); - ei.set_place(event_config->eventPlace()); + ei.set_name(event_config.name); + ei.set_place(event_config.place); ei.set_start_time(event_plugin->stageStartDateTime(event_plugin->currentStageId()).toString(Qt::ISODate)); qf::core::sql::Query q; @@ -537,35 +440,134 @@ EventInfo QxClientService::eventInfo() const // qfInfo() << qf::core::Utils::qvariantToJson(ei, false); return ei; } -/* -namespace { -auto query_to_json_csv(QSqlQuery &q) +//namespace { +//auto query_to_json_csv(QSqlQuery &q) +//{ +// QVariantList csv; +// { +// // QStringList columns{"name", "control_count", "length", "climb", "start_time", "interval", "start_slot_count"}; +// QStringList columns; +// auto rec = q.record(); +// for (auto i = 0; i < rec.count(); ++i) { +// columns << rec.field(i).name(); +// } +// csv.insert(csv.length(), columns); +// } +// while (q.next()) { +// QVariantList values; +// auto rec = q.record(); +// for (auto i = 0; i < rec.count(); ++i) { +// values << q.value(i); +// } +// csv.insert(csv.length(), values); +// } +// return csv; +//} +//} +int QxEventService::currentConnectionId() { - QVariantList csv; - { - // QStringList columns{"name", "control_count", "length", "climb", "start_time", "interval", "start_slot_count"}; - QStringList columns; - auto rec = q.record(); - for (auto i = 0; i < rec.count(); ++i) { - columns << rec.field(i).name(); - } - csv.insert(csv.length(), columns); + return qf::core::sql::Connection::forName().connectionId(); +} + +const char *QxEventService::eventctlShvPath() const +{ + return "qx/qxeventd/eventctl"; +} + +void QxEventService::onBrokerConnectedChanged(bool is_connected) +{ + if(is_connected) { + auto *event_plugin = getPlugin(); + const auto &app_config = event_plugin->appDbConfig(); + auto api_token = app_config.qxConfig().apiToken.toStdString(); + auto *rpc_call = shv::iotqt::rpc::RpcCall::create(m_rpcConnection) + ->setShvPath(eventctlShvPath()) + ->setMethod("openEventApiKey") + ->setParams(RpcValue(api_token)); + connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if (error.isValid()) { + setStatus(Status::Stopped); + setStatusMessage(tr("Client info discovery error: %1").arg(error.toString())); + } + else { + const auto &info = result.asList(); + m_eventId = info.value(0).toInt(); + m_eventMountPoint = info.value(1).to(); + setStatus(Status::Running); + setStatusMessage(tr("Event ID: %1").arg(m_eventId)); + subscribeChanges(); + } + }); + rpc_call->start(); + } else { + setStatus(Status::Stopped); } - while (q.next()) { - QVariantList values; - auto rec = q.record(); - for (auto i = 0; i < rec.count(); ++i) { - values << q.value(i); + +} + +void QxEventService::onBrokerSocketError(const QString &err) +{ + qfWarning() << "onBrokerSocketError:" << err; + setStatusMessage(tr("Broker socket error: %1").arg(err)); +} + +void QxEventService::onBrokerLoginError(const shv::chainpack::RpcError &err) +{ + qfWarning() << "onBrokerLoginError:" << err.toString(); + setStatusMessage(tr("Broker login error: %1").arg(err.toString())); +} + +void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) +{ +// shvLogFuncFrame() << msg.toCpon(); + if(msg.isRequest()) { + RpcRequest rq(msg); + if (rq.shvPath().asString().starts_with(".broker/")) { + // ignore broker discovery messages + return; } - csv.insert(csv.length(), values); + qfMessage() << "RPC request received:" << rq.toPrettyString(); + m_rootNode->handleRpcRequest(rq); + } + else if(msg.isResponse()) { + RpcResponse rp(msg); + qfMessage() << "RPC response received:" << rp.toPrettyString(); + } + else if(msg.isSignal()) { + RpcSignal nt(msg); + qfMessage() << "RPC signal received:" << nt.toPrettyString(); } - return csv; } + +void QxEventService::sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg) +{ + if(m_rpcConnection && m_rpcConnection->isBrokerConnected()) { + m_rpcConnection->sendRpcMessage(rpc_msg); + } } -*/ -int QxClientService::currentConnectionId() + +void QxEventService::subscribeChanges() { - return qf::core::sql::Connection::forName().connectionId(); + // Q_ASSERT(m_rpcConnection); + // QString shv_path = "test"; + // QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; + // auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); + // connect(rpc_call, &RpcCall::maybeResult, this, [shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + // if(error.isValid()) { + // qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); + // } + // else { + // qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); + // } + // }); + // rpc_call->start(); } +// void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) +// { +// if (isRunning()) { +// m_rpcConnection->sendShvSignal("sql", "recchng", ::qx::qxRecChngToRpcValue(chng)); +// } +// } + } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h similarity index 66% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 636a54c4d..e78831898 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -7,15 +7,22 @@ class QNetworkReply; class QUrlQuery; class QTimer; +namespace shv::iotqt::node { class ShvNodeTree; class ShvRootNode; } +namespace shv::iotqt::rpc { class DeviceConnection; } +namespace shv::chainpack { class RpcMessage; class RpcError; } +namespace qf::core::sql { struct QxRecChng; } + namespace Event::services::qx { -class QxClientServiceSettings : public ServiceSettings +class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; - QF_VARIANTMAP_FIELD2(QString, e, setE, xchangeServerUrl, "http://localhost:8000") + QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "tcp://localhost?user=qe&password=test") + QF_VARIANTMAP_FIELD2(bool, is, set, LocalBroker, false) + QF_VARIANTMAP_FIELD2(bool, is, set, ExportDatabase, false) public: - QxClientServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} + QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; class EventInfo : public QVariantMap @@ -34,7 +41,7 @@ class EventInfo : public QVariantMap EventInfo(const QVariantMap &data = QVariantMap()) : QVariantMap(data) {} }; -class QxClientService : public Service +class QxEventService : public Service { Q_OBJECT @@ -42,14 +49,14 @@ class QxClientService : public Service public: static constexpr auto QX_API_TOKEN = "qx-api-token"; public: - QxClientService(QObject *parent); + QxEventService(QObject *parent); static QString serviceId(); QString serviceDisplayName() const override; void run() override; void stop() override; - QxClientServiceSettings settings() const {return QxClientServiceSettings(m_settings);} + QxEventServiceSettings settings() const {return QxEventServiceSettings(m_settings);} void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); QNetworkAccessManager* networkManager(); @@ -63,11 +70,20 @@ class QxClientService : public Service QNetworkReply* getQxChangesReply(int from_id); - QByteArray apiToken() const; + QString apiToken() const; static int currentConnectionId(); - QUrl exchangeServerUrl() const; + QUrl shvBrokerUrl() const; int eventId() const; +private: // shv + void onBrokerConnectedChanged(bool is_connected); + void onRpcMessageReceived(const shv::chainpack::RpcMessage &msg); + void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); + void onBrokerSocketError(const QString &err); + void onBrokerLoginError(const shv::chainpack::RpcError &err); + + const char* eventctlShvPath() const; + void subscribeChanges(); private: void loadSettings() override; qf::gui::framework::DialogWidget *createDetailWidget() override; @@ -78,16 +94,15 @@ class QxClientService : public Service void httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context = nullptr, const std::function &call_back = nullptr); - void connectToSSE(int event_id); - void disconnectSSE(); - - void pollQxChanges(); - EventInfo eventInfo() const; +private: // shv + shv::iotqt::rpc::DeviceConnection *m_rpcConnection = nullptr; + shv::iotqt::node::ShvRootNode *m_rootNode; private: QNetworkAccessManager *m_networkManager = nullptr; QNetworkReply *m_replySSE = nullptr; int m_eventId = 0; + QString m_eventMountPoint; QTimer *m_pollChangesTimer = nullptr; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp new file mode 100644 index 000000000..5801437e7 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -0,0 +1,191 @@ +#include "qxeventservicewidget.h" +#include "ui_qxeventservicewidget.h" + +#include "qxeventservice.h" + +#include "../../eventplugin.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using qf::gui::framework::getPlugin; + +namespace Event::services::qx { + +QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) + : Super(parent) + , ui(new Ui::QxEventServiceWidget) +{ + setPersistentSettingsId("QxEventServiceWidget"); + ui->setupUi(this); + + setMessage(""); + + auto *svc = service(); + Q_ASSERT(svc); + auto *event_plugin = getPlugin(); + auto current_stage = event_plugin->currentStageId(); + auto settings = svc->settings(); + ui->edServerUrl->setText(settings.shvBrokerUrl()); + ui->chkExportDatabase->setChecked(settings.isExportDatabase()); + ui->edApiToken->setText(svc->apiToken()); + ui->edCurrentStage->setValue(current_stage); + ui->edEventId->setValue(svc->eventId()); + connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); + + connect(ui->btQxQxOrg, &QRadioButton::toggled, this, &QxEventServiceWidget::setConnectionType); + connect(ui->btLocalhost, &QRadioButton::toggled, this, &QxEventServiceWidget::setConnectionType); + ui->btQxQxOrg->setChecked(!settings.isLocalBroker()); +} + +QxEventServiceWidget::~QxEventServiceWidget() +{ + delete ui; +} + +void QxEventServiceWidget::setMessage(const QString &msg, MessageType msg_type) +{ + if (msg.isEmpty()) { + ui->lblStatus->setStyleSheet({}); + } + else { + switch (msg_type) { + case MessageType::Ok: + ui->lblStatus->setStyleSheet("background: lightgreen"); + break; + case MessageType::Error: + ui->lblStatus->setStyleSheet("background: salmon"); + break; + case MessageType::Progress: + ui->lblStatus->setStyleSheet("background: orange"); + break; + } + } + ui->lblStatus->setText(msg); +} + +bool QxEventServiceWidget::acceptDialogDone(int result) +{ + // delete testing connection if any + delete findChild(); + if(result == QDialog::Accepted) { + if(!saveSettings()) { + return false; + } + } + return true; +} + +QxEventService *QxEventServiceWidget::service() +{ + auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); + QF_ASSERT(svc, QxEventService::serviceId() + " doesn't exist", return nullptr); + return svc; +} + +bool QxEventServiceWidget::saveSettings() +{ + auto *svc = service(); + if(svc) { + auto ss = svc->settings(); + bool is_localhost = ui->btLocalhost->isChecked(); + if (is_localhost) { + ss.setShvBrokerUrl(ui->edServerUrl->text()); + } + ss.setLocalBroker(is_localhost); + ss.setExportDatabase(ui->chkExportDatabase->isChecked()); + svc->setSettings(ss); + + auto &app_config = getPlugin()->appDbConfig(); + auto qx_config = app_config.qxConfig(); + qx_config.apiToken = ui->edApiToken->text().trimmed(); + app_config.setQxConfig(qx_config); + } + return true; +} + +void QxEventServiceWidget::testConnection() +{ + using namespace shv::iotqt::rpc; + using namespace shv::chainpack; + + delete findChild(); + + auto *rpc = new DeviceConnection("QuickEventTest", this); + rpc->setConnectionString(ui->edServerUrl->text()); + if (ui->chkExportDatabase->isChecked()) { + RpcValue::Map opts; + RpcValue::Map device; + device["deviceId"] = ui->edApiToken->text().toStdString(); + opts["device"] = device; + rpc->setConnectionOptions(opts); + } + + connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { + if (is_connected) { + setMessage(tr("Broker connected OK")); + auto *rpc_call = shv::iotqt::rpc::RpcCall::create(rpc) + ->setShvPath(".broker/currentClient") + ->setMethod("info"); + connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if (error.isValid()) { + setMessage(tr("Client info discovery error: %1").arg(error.toString()), MessageType::Error); + } + else { + const auto &info = result.asMap(); + auto mount_point = info.value("mountPoint").to(); + auto event_id = mount_point.section('/', -1, -1).toInt(); + ui->edEventId->setValue(event_id); + setMessage(tr("Event mounted at: %1, event id: %2").arg(mount_point).arg(event_id)); + } + }); + rpc_call->start(); + } + }); + connect(rpc, &ClientConnection::socketError, this, [this](const QString &error) { + setMessage(tr("Connection error: %1").arg(error), MessageType::Error); + }); + connect(rpc, &ClientConnection::brokerLoginError, this, [this](const auto &error) { + setMessage(tr("Login error: %1").arg(QString::fromStdString(error.toString())), MessageType::Error); + }); + rpc->open(); +} + +void QxEventServiceWidget::setConnectionType() +{ + bool is_localhost = ui->btLocalhost->isChecked(); + if (is_localhost) { + ui->lblUrl->setVisible(true); + ui->edServerUrl->setVisible(true); + } else { + ui->lblUrl->setVisible(false); + ui->edServerUrl->setVisible(false); + } +} + +QString QxEventServiceWidget::brokerUrl() const +{ + bool is_localhost = ui->btLocalhost->isChecked(); + if (is_localhost) { + return ui->edServerUrl->text(); + } + auto url = QStringLiteral("tcp://%1?user=qe&%2=%3"); + url = url.arg("qxqx.org"); + url = url.arg("password"); + url = url.arg("2ddf6394ea6"); + return url; +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h similarity index 52% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h index f1b108a30..0ae762934 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h @@ -5,32 +5,31 @@ namespace Event::services::qx { namespace Ui { -class QxClientServiceWidget; +class QxEventServiceWidget; } -class QxClientService; +class QxEventService; -class QxClientServiceWidget : public qf::gui::framework::DialogWidget +class QxEventServiceWidget : public qf::gui::framework::DialogWidget { Q_OBJECT using Super = qf::gui::framework::DialogWidget; public: - explicit QxClientServiceWidget(QWidget *parent = nullptr); - ~QxClientServiceWidget() override; + explicit QxEventServiceWidget(QWidget *parent = nullptr); + ~QxEventServiceWidget() override; + + bool acceptDialogDone(int result) override; private: enum class MessageType { Ok, Error, Progress }; void setMessage(const QString &msg = {}, MessageType msg_type = MessageType::Ok); - QxClientService* service(); + QxEventService* service(); bool saveSettings(); - void updateOCheckListPostUrl(); void testConnection(); - void exportEventInfo(); - void exportStartList(); - void exportRuns(); + void setConnectionType(); + QString brokerUrl() const; private: - Ui::QxClientServiceWidget *ui; - bool acceptDialogDone(int result) override; + Ui::QxEventServiceWidget *ui; }; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui similarity index 65% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index d8824707e..b6bc76c50 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -1,7 +1,7 @@ - Event::services::qx::QxClientServiceWidget - + Event::services::qx::QxEventServiceWidget + 0 @@ -14,43 +14,62 @@ SHV API service + + + + SHV broker connection + + + + + + + 0 + 0 + + + + Url + + + + + + + localhost + + + true + + + + + + + qxqx.org + + + false + + + + + + + + + - - - - Exchange server url - - - - - - - - - - Fill this url into HTTP POST synchonization method in O-CheckList - - - API token - - - - - - - - - - Fill this url into HTTP POST synchonization method in O-CheckList - + + - Current stage + Export database - - + + false @@ -60,12 +79,15 @@ true - - 1 + + Not loaded + + + 0 - + Fill this url into HTTP POST synchonization method in O-CheckList @@ -75,8 +97,11 @@ + + + - + false @@ -87,52 +112,41 @@ true - Not loaded + Not defined + + + 0 0 - - + + Fill this url into HTTP POST synchonization method in O-CheckList - OCheckList Url - - - - - - - true + API token - - + + Fill this url into HTTP POST synchonization method in O-CheckList - Header - - - - - - - true + Current stage - - + + Qt::Orientation::Horizontal @@ -145,31 +159,17 @@ - - - - Export event info - - - - + Test connection - - - - Export start list - - - - - + + - Export runs + Archive event @@ -200,10 +200,6 @@ - - edServerUrl - edOChecklistUrl - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp new file mode 100644 index 000000000..49b59528e --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -0,0 +1,339 @@ +#include "qxlateentrieswidget.h" +#include "ui_qxlateentrieswidget.h" + +#include "qxeventservice.h" +#include "lateentrydialog.h" +#include "runchange.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace qfm = qf::gui::model; +namespace qfs = qf::core::sql; +namespace qfw = qf::gui; +using qf::gui::framework::getPlugin; + +namespace Event::services::qx { + +namespace { +enum Columns { + col_id = 0, + col_stage_id, + col_data_type, + col_foreign_id, + col_foreign_table, + col_data, + col_orig_data, + col_status, + col_status_message, + col_user_id, + col_created, + col_lock_number, + col_COUNT +}; + +constexpr auto COL_ID = "id"; +constexpr auto COL_STAGE_ID = "stage_id"; +constexpr auto COL_DATA_TYPE = "data_type"; +constexpr auto COL_FOREIGN_ID = "foreign_id"; +constexpr auto COL_FOREIGN_TABLE = "foreign_table"; +constexpr auto COL_DATA = "data"; +constexpr auto COL_ORIG_DATA = "orig_data"; +constexpr auto COL_STATUS = "status"; +constexpr auto COL_STATUS_MESSAGE = "status_message"; +constexpr auto COL_USER_ID = "user_id"; +constexpr auto COL_CREATED = "created"; +constexpr auto COL_LOCK_NUMBER = "lock_number"; + +class LateEntriesModel : public qfm::SqlTableModel +{ +public: + using qfm::SqlTableModel::SqlTableModel; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (role == Qt::BackgroundRole) { + if (index.column() == col_data) { + auto data = index.data().toString(); + auto qxdata = QxChangeData::fromJson(data); + if (auto le = qxdata.lateEntry; le.has_value()) { + if (le.value().paid.value_or(false)) { + return QColor("lightgreen"); + } + } + } + else if (index.column() == col_status) { + auto status = qxChangeStatusFromString(index.data().toString()); + switch (status) { + case QxChangeStatus::Pending: return {}; + case QxChangeStatus::Accepted: return QColor("lightgreen"); + case QxChangeStatus::Rejected: return QColor("salmon"); + } + } + } + return qfm::SqlTableModel::data(index, role); + } +}; +} // namespace + +QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::QxLateEntriesWidget) +{ + ui->setupUi(this); + + ui->tableView->setReadOnly(true); + ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->tableView, &qfw::TableView::customContextMenuRequested, this, &QxLateEntriesWidget::onTableCustomContextMenuRequest); + connect(ui->tableView, &qfw::TableView::doubleClicked, this, &QxLateEntriesWidget::onTableDoubleClicked); + + ui->tableView->setPersistentSettingsId("tblQxLateEntries"); + ui->tableView->setInsertRowEnabled(false); + ui->tableView->setCloneRowEnabled(false); + ui->tableView->setRemoveRowEnabled(false); + ui->tableView->setDirtyRowsMenuSectionEnabled(false); + + ui->toolbar->setTableView(ui->tableView); + m_model = new LateEntriesModel(this); + //m->setObjectName("classes.classesModel"); + + m_model->clearColumns(col_COUNT); + m_model->setColumn(col_id, qf::gui::model::TableModel::ColumnDefinition(COL_ID).setReadOnly(true).setAlignment(Qt::AlignRight)); + m_model->setColumn(col_stage_id, qf::gui::model::TableModel::ColumnDefinition(COL_STAGE_ID).setReadOnly(true).setAlignment(Qt::AlignRight)); + m_model->setColumn(col_data_type, qf::gui::model::SqlTableModel::ColumnDefinition(COL_DATA_TYPE).setReadOnly(true)); + m_model->setColumn(col_foreign_id, qf::gui::model::SqlTableModel::ColumnDefinition(COL_FOREIGN_ID).setReadOnly(true).setAlignment(Qt::AlignRight)); + m_model->setColumn(col_foreign_table, qf::gui::model::SqlTableModel::ColumnDefinition(COL_FOREIGN_TABLE).setReadOnly(true)); + m_model->setColumn(col_data, qf::gui::model::SqlTableModel::ColumnDefinition(COL_DATA).setReadOnly(true)); + m_model->setColumn(col_orig_data, qf::gui::model::SqlTableModel::ColumnDefinition(COL_ORIG_DATA).setReadOnly(true)); + m_model->setColumn(col_status, qf::gui::model::SqlTableModel::ColumnDefinition(COL_STATUS).setReadOnly(true)); + m_model->setColumn(col_status_message, qf::gui::model::SqlTableModel::ColumnDefinition(COL_STATUS_MESSAGE).setReadOnly(true)); + m_model->setColumn(col_user_id, qf::gui::model::SqlTableModel::ColumnDefinition(COL_USER_ID).setReadOnly(true)); + m_model->setColumn(col_created, qf::gui::model::SqlTableModel::ColumnDefinition(COL_CREATED).setReadOnly(true)); + m_model->setColumn(col_lock_number, qf::gui::model::SqlTableModel::ColumnDefinition(COL_LOCK_NUMBER).setReadOnly(true)); + ui->tableView->setTableModel(m_model); + + ui->tableView->setColumnHidden(col_stage_id, true); + + showMessage(tr("QxEvent service is disabled"), true); + setEnabled(false); + + auto *svc = qxEventService(); + connect(svc, &Service::statusChanged, this, [this](Service::Status new_status){ + switch (new_status) { + case Service::Status::Unknown: + case Service::Status::Stopped: + showMessage(tr("QxEvent service is disabled"), true); + setEnabled(false); + break; + case Service::Status::Running: + showMessage({}); + setEnabled(true); + reload(); + break; + } + }); + + { + auto *lst = ui->lstType; + lst->addItem("All"); + lst->addItem("RunUpdateRequest"); + lst->addItem("RunUpdated"); + lst->addItem("OcChange"); + lst->addItem("RadioPunch"); + lst->addItem("CardReadout"); + lst->setCurrentIndex(0); + connect(lst, &QComboBox::currentIndexChanged, this, &QxLateEntriesWidget::reload); + } + connect(ui->chkPending, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkAccepted, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkRejected, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + + connect(ui->btAll, &QPushButton::clicked, this, [this]() { + QSignalBlocker sb1(ui->lstType); + ui->lstType->setCurrentIndex(0); + + QSignalBlocker sb3(ui->chkPending); + ui->chkPending->setChecked(true); + QSignalBlocker sb5(ui->chkAccepted); + ui->chkAccepted->setChecked(true); + QSignalBlocker sb6(ui->chkRejected); + ui->chkRejected->setChecked(true); + + reload(); + }); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &QxLateEntriesWidget::onQxRecChng, Qt::QueuedConnection); +} + +QxLateEntriesWidget::~QxLateEntriesWidget() +{ + delete ui; +} + +void QxLateEntriesWidget::onVisibleChanged(bool is_visible) +{ + if (is_visible && isEnabled()) { + reload(); + } +} + +QxEventService *QxLateEntriesWidget::qxEventService() +{ + auto *svc = qobject_cast(Event::services::Service::serviceByName(QxEventService::serviceId())); + Q_ASSERT(svc); + return svc; +} + +void QxLateEntriesWidget::resizeColumns() +{ + auto *tv = ui->tableView; + tv->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); +} + +void QxLateEntriesWidget::showMessage(const QString &msg, bool is_error) +{ + if (msg.isEmpty()) { + ui->lblErrorMsg->hide(); + } + else { + if (is_error) { + ui->lblErrorMsg->setStyleSheet("background: salmon"); + } + else { + ui->lblErrorMsg->setStyleSheet("background: lightgreen"); + } + ui->lblErrorMsg->show(); + } + ui->lblErrorMsg->setText(msg); +} + +void QxLateEntriesWidget::reload() +{ + if(!isEnabled()) { + return; + } + auto event_plugin = getPlugin(); + if(!getPlugin()->isEventOpen()) { + return; + } + int stage_id = event_plugin->currentStageId(); + qfs::QueryBuilder qb; + qb.select2("qxchanges", "*") + .from("qxchanges") + .where("stage_id=" + QString::number(stage_id)) + .orderBy("id"); + QStringList status_cond_list; + if (ui->chkPending->isChecked()) { + status_cond_list << "status='Pending'"; + } + if (ui->chkAccepted->isChecked()) { + status_cond_list << "status='Accepted'"; + } + if (ui->chkRejected->isChecked()) { + status_cond_list << "status='Rejected'"; + } + if (!status_cond_list.isEmpty()) { + qb.where('(' + status_cond_list.join(" OR ") + ')'); + } + if (auto ix = ui->lstType->currentIndex(); ix > 0) { + auto data_type = ui->lstType->currentText(); + qb.where(QStringLiteral("data_type='%1'").arg(data_type)); + } + qfDebug() << qb.toString(); + m_model->setQueryBuilder(qb, false); + m_model->reload(); +} + +void QxLateEntriesWidget::applyQxChange(int sql_id) +{ + qfDebug() << "reloading qxchanges row id:" << sql_id << "col id:" << COL_ID; + if(sql_id <= 0) { + return; + } + + auto qb = m_model->queryBuilder(); + qb.where(QStringLiteral("id=%1").arg(sql_id)); + qf::core::sql::Query q; + q.execThrow(qb.toString()); + if (!q.next()) { + // inserted row is filtered out + return; + } + + m_model->insertRow(0); + m_model->setValue(0, COL_ID, sql_id); + int cnt = m_model->reloadRow(0); + if(cnt != 1) { + qfWarning() << "Inserted qx change row id:" << sql_id << "reloaded in" << cnt << "instances."; + return; + } +} + +void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) +{ + QAction a_edit_competitor(tr("Edit competitor"), nullptr); + QList lst; + lst << &a_edit_competitor; + QAction *a = QMenu::exec(lst, ui->tableView->viewport()->mapToGlobal(pos)); + if(a == &a_edit_competitor) { + if (auto ix = ui->tableView->indexAt(pos); ix.isValid()) { + if (auto t = ix.sibling(ix.row(), col_foreign_table).data().toString(); t == "runs") { + auto run_id = ix.sibling(ix.row(), col_foreign_id).data().toInt(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT competitorId FROM runs WHERE id=%1") + .arg(run_id) + ); + if (q.next()) { + auto competitor_id = q.value(0).toInt(); + RunsWidget::showEditCompetitorDialog(competitor_id, this); + return; + } + } + } + QMessageBox::information(this, tr("Edit competitor"), tr("No competitor found for this run.")); + } +} + +void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) +{ + auto row = ui->tableView->toTableModelIndex(ix).row(); + auto data = m_model->value(row, COL_DATA).toString(); + auto qxdata = QxChangeData::fromJson(data); + if (qxdata.lateEntry.has_value()) { + auto status = m_model->value(row, COL_STATUS).toString(); + auto status_message = m_model->value(row, COL_STATUS_MESSAGE).toString(); + auto change_id = m_model->value(row, COL_ID).toInt(); + auto stage_id = m_model->value(row, COL_STAGE_ID).toInt(); + LateEntryDialog dlg(change_id, stage_id, qxdata.lateEntry.value(), status, status_message, this); + connect(&dlg, &LateEntryDialog::editCompetitor, this, [this](int competitor_id) { + RunsWidget::showEditCompetitorDialog(competitor_id, this); + }, Qt::QueuedConnection); + dlg.exec(); + ui->tableView->reloadRow(row); + } +} + +void QxLateEntriesWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) +{ + if (recchng.table == "qxchanges" && recchng.record.contains("status")) { + // status affect filter, cannot be solved by applyQxRecChng + m_model->reload(); + return; + } + m_model->applyQxRecChng(recchng, source); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h similarity index 52% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h index 6c972c739..85dd3265e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h @@ -2,40 +2,40 @@ #include +namespace qf::core::sql { struct QxRecChng; } namespace qf::gui::model { class SqlTableModel; } namespace Event::services::qx { namespace Ui { -class QxLateRegistrationsWidget; +class QxLateEntriesWidget; } -class QxClientService; +class QxEventService; -class QxLateRegistrationsWidget : public QWidget +class QxLateEntriesWidget : public QWidget { Q_OBJECT public: - explicit QxLateRegistrationsWidget(QWidget *parent = nullptr); - ~QxLateRegistrationsWidget() override; + explicit QxLateEntriesWidget(QWidget *parent = nullptr); + ~QxLateEntriesWidget() override; - void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); private: - QxClientService* service(); + QxEventService* qxEventService(); void reload(); - void addQxChangeRow(int sql_id); + void applyQxChange(int sql_id); void resizeColumns(); void showMessage(const QString &msg, bool is_error = false); - void applyCurrentChange(); void onTableCustomContextMenuRequest(const QPoint &pos); void onTableDoubleClicked(const QModelIndex &ix); + void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); private: - Ui::QxLateRegistrationsWidget *ui; + Ui::QxLateEntriesWidget *ui; qf::gui::model::SqlTableModel *m_model; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui similarity index 85% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui index 07bf69ed4..9ac4e2a67 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui @@ -1,7 +1,7 @@ - Event::services::qx::QxLateRegistrationsWidget - + Event::services::qx::QxLateEntriesWidget + 0 @@ -11,7 +11,7 @@ - Form + Late entries @@ -87,13 +87,6 @@ 0 - - - - Locked - - - @@ -108,13 +101,6 @@ - - - - Null - - - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp deleted file mode 100644 index f0747e77a..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp +++ /dev/null @@ -1,307 +0,0 @@ -#include "qxlateregistrationswidget.h" -#include "ui_qxlateregistrationswidget.h" - -#include "qxclientservice.h" -#include "runchangedialog.h" -#include "runchange.h" - -#include - -#include -#include -#include -#include - -#include -#include - -namespace qfm = qf::gui::model; -namespace qfs = qf::core::sql; -namespace qfw = qf::gui; -using qf::gui::framework::getPlugin; - -namespace Event::services::qx { - -constexpr auto COL_ID = "id"; -constexpr auto COL_CHANGE_ID = "change_id"; -constexpr auto COL_DATA_TYPE = "data_type"; -constexpr auto COL_DATA_ID = "data_id"; -constexpr auto COL_DATA = "data"; -constexpr auto COL_ORIG_DATA = "orig_data"; -constexpr auto COL_STATUS = "status"; -constexpr auto COL_STATUS_MESSAGE = "status_message"; -constexpr auto COL_SOURCE = "source"; -constexpr auto COL_USER_ID = "user_id"; -constexpr auto COL_CREATED = "created"; -constexpr auto COL_LOCK_NUMBER = "lock_number"; - -constexpr auto STATUS_PENDING = "Pending"; -constexpr auto STATUS_LOCKED = "Locked"; - -constexpr auto DATA_TYPE_RUN_UPDATE_REQUEST = "RunUpdateRequest"; - -constexpr auto SOURCE_WWW = "www"; - -QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : - QWidget(parent), - ui(new Ui::QxLateRegistrationsWidget) -{ - ui->setupUi(this); - - ui->tableView->setReadOnly(true); - ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->tableView, &qfw::TableView::customContextMenuRequested, this, &QxLateRegistrationsWidget::onTableCustomContextMenuRequest); - connect(ui->tableView, &qfw::TableView::doubleClicked, this, &QxLateRegistrationsWidget::onTableDoubleClicked); - - ui->tableView->setPersistentSettingsId("tblQxLateRegistrations"); - ui->tableView->setInsertRowEnabled(false); - ui->tableView->setCloneRowEnabled(false); - ui->tableView->setRemoveRowEnabled(false); - ui->tableView->setDirtyRowsMenuSectionEnabled(false); - - ui->toolbar->setTableView(ui->tableView); - m_model = new qfm::SqlTableModel(this); - //m->setObjectName("classes.classesModel"); - m_model->addColumn(COL_ID).setReadOnly(true).setAlignment(Qt::AlignLeft); - m_model->addColumn(COL_STATUS, tr("Status")); - m_model->addColumn(COL_DATA_TYPE, tr("Type")); - m_model->addColumn(COL_DATA_ID, tr("Data ID")).setAlignment(Qt::AlignLeft); - m_model->addColumn(COL_SOURCE, tr("Source")); - m_model->addColumn(COL_USER_ID, tr("User")); - m_model->addColumn(COL_STATUS_MESSAGE, tr("Status message")); - m_model->addColumn(COL_CREATED, tr("Created")); - m_model->addColumn(COL_CHANGE_ID, tr("Change ID")); - m_model->addColumn(COL_LOCK_NUMBER, tr("Lock")); - m_model->addColumn(COL_DATA, tr("Data"));//.setToolTip(tr("Locked for drawing")); - m_model->addColumn(COL_ORIG_DATA, tr("Orig data"));//.setToolTip(tr("Locked for drawing")); - ui->tableView->setTableModel(m_model); - - showMessage({}); - setEnabled(false); - - auto *svc = service(); - connect(svc, &Service::statusChanged, this, [this](Service::Status new_status){ - switch (new_status) { - case Service::Status::Unknown: - case Service::Status::Stopped: - setEnabled(false); - break; - case Service::Status::Running: - setEnabled(true); - reload(); - break; - } - }); - - connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &QxLateRegistrationsWidget::onDbEventNotify, Qt::QueuedConnection); - - { - auto *lst = ui->lstType; - lst->addItem("All"); - lst->addItem("RunUpdateRequest"); - lst->addItem("RunUpdated"); - lst->addItem("OcChange"); - lst->addItem("RadioPunch"); - lst->addItem("CardReadout"); - lst->setCurrentIndex(0); - connect(lst, &QComboBox::currentIndexChanged, this, &QxLateRegistrationsWidget::reload); - } - connect(ui->chkNull, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkPending, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkLocked, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkAccepted, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkRejected, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - - connect(ui->btAll, &QPushButton::clicked, this, [this]() { - QSignalBlocker sb1(ui->lstType); - ui->lstType->setCurrentIndex(0); - - QSignalBlocker sb2(ui->chkNull); - ui->chkNull->setChecked(true); - QSignalBlocker sb3(ui->chkPending); - ui->chkPending->setChecked(true); - QSignalBlocker sb4(ui->chkLocked); - ui->chkLocked->setChecked(true); - QSignalBlocker sb5(ui->chkAccepted); - ui->chkAccepted->setChecked(true); - QSignalBlocker sb6(ui->chkRejected); - ui->chkRejected->setChecked(true); - - reload(); - }); -} - -QxLateRegistrationsWidget::~QxLateRegistrationsWidget() -{ - delete ui; -} - -void QxLateRegistrationsWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload) -{ - Q_UNUSED(connection_id) - if(domain == QLatin1String(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED)) { - int sql_id = payload.toInt(); - addQxChangeRow(sql_id); - } -} - -void QxLateRegistrationsWidget::onVisibleChanged(bool is_visible) -{ - if (is_visible && isEnabled()) { - reload(); - } -} - -QxClientService *QxLateRegistrationsWidget::service() -{ - auto *svc = qobject_cast(Event::services::Service::serviceByName(QxClientService::serviceId())); - Q_ASSERT(svc); - return svc; -} - -void QxLateRegistrationsWidget::resizeColumns() -{ - auto *tv = ui->tableView; - tv->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); -} - -void QxLateRegistrationsWidget::showMessage(const QString &msg, bool is_error) -{ - if (msg.isEmpty()) { - ui->lblErrorMsg->hide(); - } - else { - if (is_error) { - ui->lblErrorMsg->setStyleSheet("background: salmon"); - } - else { - ui->lblErrorMsg->setStyleSheet("background: lightgreen"); - } - ui->lblErrorMsg->show(); - } - ui->lblErrorMsg->setText(msg); -} - -void QxLateRegistrationsWidget::reload() -{ - if(!isEnabled()) { - return; - } - auto event_plugin = getPlugin(); - if(!getPlugin()->isEventOpen()) { - return; - } - int stage_id = event_plugin->currentStageId(); - qfs::QueryBuilder qb; - qb.select2("qxchanges", "*") - .from("qxchanges") - .where("stage_id=" + QString::number(stage_id)) - .orderBy("id"); - QStringList status_cond_list; - if (ui->chkNull->isChecked()) { - status_cond_list << "status IS NULL"; - } - if (ui->chkPending->isChecked()) { - status_cond_list << "status='Pending'"; - } - if (ui->chkLocked->isChecked()) { - status_cond_list << "status LIKE 'Locked%'"; - } - if (ui->chkAccepted->isChecked()) { - status_cond_list << "status='Accepted'"; - } - if (ui->chkRejected->isChecked()) { - status_cond_list << "status='Rejected'"; - } - if (!status_cond_list.isEmpty()) { - qb.where('(' + status_cond_list.join(" OR ") + ')'); - } - if (auto ix = ui->lstType->currentIndex(); ix > 0) { - auto data_type = ui->lstType->currentText(); - qb.where(QStringLiteral("data_type='%1'").arg(data_type)); - } - qfDebug() << qb.toString(); - m_model->setQueryBuilder(qb, false); - m_model->reload(); -} - -void QxLateRegistrationsWidget::addQxChangeRow(int sql_id) -{ - qfDebug() << "reloading qxchanges row id:" << sql_id << "col id:" << COL_ID; - if(sql_id <= 0) { - return; - } - - auto qb = m_model->queryBuilder(); - qb.where(QStringLiteral("id=%1").arg(sql_id)); - qf::core::sql::Query q; - q.execThrow(qb.toString()); - if (!q.next()) { - // inserted row is filtered out - return; - } - - m_model->insertRow(0); - m_model->setValue(0, COL_ID, sql_id); - int cnt = m_model->reloadRow(0); - if(cnt != 1) { - qfWarning() << "Inserted qx change row id:" << sql_id << "reloaded in" << cnt << "instances."; - return; - } -} - - -void QxLateRegistrationsWidget::applyCurrentChange() -{ - auto row = ui->tableView->currentIndex().row(); - if (row < 0) { - return; - } - auto status = m_model->value(row, COL_STATUS).toString(); - if (status != STATUS_PENDING) { - return; - } - //auto run_id = m_model->value(row, COL_RUN_ID).toInt(); - //int competitor_id = 0; - // int result = getPlugin()->editCompetitor(competitor_id, run_id == 0? Mode::Insert: Mode::Edit); - -} - -void QxLateRegistrationsWidget::onTableCustomContextMenuRequest(const QPoint &pos) -{ - QAction a_neco(tr("Neco"), nullptr); - QList lst; - lst << &a_neco; - QAction *a = QMenu::exec(lst, ui->tableView->viewport()->mapToGlobal(pos)); - if(a == &a_neco) { - //printSelectedCards(); - } -} - -void QxLateRegistrationsWidget::onTableDoubleClicked(const QModelIndex &ix) -{ - auto row = ix.row(); - auto data_type = m_model->value(row, COL_DATA_TYPE).toString(); - if (data_type != DATA_TYPE_RUN_UPDATE_REQUEST) { - return; - } - auto source = m_model->value(row, COL_SOURCE).toString(); - if (source != SOURCE_WWW) { - return; - } - auto change_id = m_model->value(row, COL_CHANGE_ID).toInt(); - auto status = m_model->value(row, COL_STATUS).toString(); - auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); - auto data_id = m_model->value(row, COL_DATA_ID).toInt(); - auto data = m_model->value(row, COL_DATA).toString().toUtf8(); - auto change_rec = QJsonDocument::fromJson(data).toVariant().toMap(); - auto run_change = RunChange::fromVariantMap(change_rec.value(DATA_TYPE_RUN_UPDATE_REQUEST).toMap()); - if (status == STATUS_PENDING || status == STATUS_LOCKED) { - RunChangeDialog dlg(change_id, data_id, lock_number, run_change, this); - dlg.exec(); - ui->tableView->reloadRow(row); - } -} - -} - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp new file mode 100644 index 000000000..dfcea61fd --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp @@ -0,0 +1,36 @@ +#include "qxnode.h" + +#include +#include + +#include + +using namespace shv::chainpack; + +namespace Event::services::qx { + +QxNode::QxNode(const std::string &name, shv::iotqt::node::ShvNode *parent) + : Super(name, parent) +{ + +} + +size_t QxNode::methodCount(const StringViewList &shv_path) +{ + if(shv_path.empty()) { + return metaMethods().size(); + } + return Super::methodCount(shv_path); +} + +const MetaMethod *QxNode::metaMethod(const StringViewList &shv_path, size_t ix) +{ + if(shv_path.empty()) { + if(metaMethods().size() <= ix) + QF_EXCEPTION("Invalid method index: " + QString::number(ix) + " of: " + QString::number(metaMethods().size())); + return &(metaMethods()[ix]); + } + return Super::metaMethod(shv_path, ix); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h new file mode 100644 index 000000000..79e42c174 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace Event::services::qx { + +class QxNode : public shv::iotqt::node::ShvNode +{ + Q_OBJECT + + using Super = shv::iotqt::node::ShvNode; +public: + explicit QxNode(const std::string &name, shv::iotqt::node::ShvNode *parent); + + size_t methodCount(const StringViewList &shv_path) override; + const shv::chainpack::MetaMethod* metaMethod(const StringViewList &shv_path, size_t ix) override; +protected: + //shv::chainpack::RpcValue callMethodRq(const shv::chainpack::RpcRequest &rq) override; + //shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; + virtual const std::vector &metaMethods() = 0; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp index c9a84e23b..8e4cdd5c2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp @@ -1,5 +1,7 @@ #include "runchange.h" +#include + #include #include @@ -11,17 +13,25 @@ namespace Event::services::qx { // return ret; // } -RunChange RunChange::fromVariantMap(const QVariantMap &map) +LateEntry LateEntry::fromVariantMap(const QVariantMap &map) { - RunChange ret; + LateEntry ret; - if (auto v = map.value("class_name"); v.isValid()) { ret.class_name = v.toString(); } - if (auto v = map.value("first_name"); v.isValid()) { ret.first_name = v.toString(); } - if (auto v = map.value("last_name"); v.isValid()) { ret.last_name = v.toString(); } + auto id_map = map.value("id").toMap(); + if (auto id = id_map.value("RunId").toInt(); id > 0) { + ret.id = RunId{id}; + } + if (auto id = id_map.value("ClassId").toInt(); id > 0) { + ret.id = ClassId{id}; + } + if (auto v = map.value("firstname"); v.isValid()) { ret.first_name = v.toString(); } + if (auto v = map.value("lastname"); v.isValid()) { ret.last_name = v.toString(); } if (auto v = map.value("registration"); v.isValid()) { ret.registration = v.toString(); } - if (auto v = map.value("si_id"); v.isValid()) { ret.si_id = v.toInt(); } + if (auto v = map.value("starttimems"); v.isValid()) { ret.start_time_ms = v.toInt(); } + if (auto v = map.value("siid"); v.isValid()) { ret.si_id = v.toInt(); } // if (auto v = map.value("si_id_rent"); v.isValid()) { ret.si_id_rent = v.toBool(); } - ret.note = map.value("note").toString(); + if (auto v = map.value("note"); v.isValid()) { ret.note = v.toString(); } + if (auto v = map.value("paid"); v.isValid()) { ret.paid = v.toBool(); } return ret; } @@ -29,13 +39,37 @@ RunChange RunChange::fromVariantMap(const QVariantMap &map) QVariantMap OrigRunRecord::toVariantMap() const { QVariantMap ret; - ret["first_name"] = first_name; - ret["last_name"] = last_name; + ret["firstname"] = first_name; + ret["lastname"] = last_name; ret["registration"] = registration; - ret["si_id"] = si_id; + ret["siid"] = si_id; + ret["starttimems"] = start_time_ms; return ret; } -} // namespace Event::services::qx +QxChangeData QxChangeData::fromJson(const QString &json) +{ + QxChangeData ret; + auto map = QJsonDocument::fromJson(json.toUtf8()).toVariant().toMap(); + if (auto v = map.value(DATA_TYPE_LATE_ENTRY); v.isValid()) { + ret.lateEntry = LateEntry::fromVariantMap(v.toMap()); + } + return ret; +} +QxChangeStatus qxChangeStatusFromString(const QString &status) +{ + if (status.compare(QStringLiteral("Pending"), Qt::CaseInsensitive) == 0) { + return QxChangeStatus::Pending; + } + if (status.compare(QStringLiteral("Accepted"), Qt::CaseInsensitive) == 0) { + return QxChangeStatus::Accepted; + } + if (status.compare(QStringLiteral("Rejected"), Qt::CaseInsensitive) == 0) { + return QxChangeStatus::Rejected; + } + qfWarning() << "Unknown late entry status:" << status; + return QxChangeStatus::Rejected; +} +} // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 83ab57062..6fe5aef7a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -4,20 +4,39 @@ #include #include +#include +#include + namespace Event::services::qx { -struct RunChange +constexpr auto DATA_TYPE_LATE_ENTRY = "LateEntry"; + +struct RunId { int id = 0; }; +struct ClassId { int id = 0; }; + +using LateEntryForeignId = std::variant; + +struct LateEntry { - std::optional class_name; + LateEntryForeignId id = RunId{}; std::optional first_name; std::optional last_name; std::optional registration; std::optional si_id; + std::optional start_time_ms; // std::optional si_id_rent; - QString note; + std::optional note; + std::optional paid; + + // static LateEntry fromJsonString(const QString &json); + static LateEntry fromVariantMap(const QVariantMap &map); +}; + +struct QxChangeData +{ + std::optional lateEntry; - // static RunChange fromJsonString(const QString &json); - static RunChange fromVariantMap(const QVariantMap &map); + static QxChangeData fromJson(const QString &json); }; struct OrigRunRecord @@ -25,7 +44,8 @@ struct OrigRunRecord QString first_name; QString last_name; QString registration; - int si_id; + int start_time_ms = 0; + int si_id = 0; // bool si_id_rent; QVariantMap toVariantMap() const; @@ -38,27 +58,28 @@ enum class DataType { RadioPunch, CardReadout, }; -enum class ChangeStatus { +enum class QxChangeStatus { Pending, - Locked, Accepted, Rejected, }; -struct EventChange -{ - int64_t id; - QString source; - DataType data_type; - int64_t data_id; - QVariant data; - QString user_id; - ChangeStatus status; - QString status_message; - QDateTime created; - int64_t lock_number; +QxChangeStatus qxChangeStatusFromString(const QString &status); - QVariantMap toVariantMap() const; -}; +// struct EventChange +// { +// int64_t id; +// QString source; +// DataType data_type; +// int64_t data_id; +// QVariant data; +// QString user_id; +// QxChangeStatus status; +// QString status_message; +// QDateTime created; +// int64_t lock_number; + +// QVariantMap toVariantMap() const; +// }; // struct ChangeRecord // { @@ -74,6 +95,3 @@ struct EventChange // }; } // namespace Event::services::qx - - - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp deleted file mode 100644 index c44c38a94..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include "runchangedialog.h" -#include "ui_runchangedialog.h" - -#include "qxclientservice.h" -#include "runchange.h" - -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace Event::services::qx { - -RunChangeDialog::RunChangeDialog(int change_id, int run_id, int lock_number, const RunChange &run_change, QWidget *parent) - : QDialog(parent) - , ui(new Ui::RunChangeDialog) - , m_changeId(change_id) - , m_runId(run_id) -{ - ui->setupUi(this); - - ui->btAccept->setDisabled(true); - ui->btReject->setDisabled(true); - - connect(ui->chkForce, &QCheckBox::checkStateChanged, this, [this]() { - ui->btAccept->setDisabled(!ui->chkForce->isChecked()); - ui->btReject->setDisabled(!ui->chkForce->isChecked()); - }); - connect(ui->btAccept, &QPushButton::clicked, this, [this]() { - resolveChanges(true); - }); - connect(ui->btReject, &QPushButton::clicked, this, [this]() { - resolveChanges(false); - }); - - ui->edClassName->setText(run_change.class_name.value_or(QString())); - ui->edNote->setText(run_change.note); - - ui->edRunId->setValue(run_id); - ui->edChangeId->setValue(change_id); - ui->edLockNumber->setValue(lock_number); - - ui->grpFirstName->setChecked(run_change.first_name.has_value()); - ui->edFirstName->setText(run_change.first_name.has_value()? run_change.first_name.value(): QString()); - - ui->grpLastName->setChecked(run_change.last_name.has_value()); - ui->edLastName->setText(run_change.last_name.has_value()? run_change.last_name.value(): QString()); - - ui->grpRegistration->setChecked(run_change.registration.has_value()); - ui->edRegistration->setText(run_change.registration.has_value()? run_change.registration.value(): QString()); - - ui->grpSiCard->setChecked(run_change.si_id.has_value()); - ui->edSiCard->setValue(run_change.si_id.has_value()? run_change.si_id.value(): 0); - - if (run_id > 0) { - loadOrigValues(); - } - else { - loadClassId(); - } - lockChange(); -} - -RunChangeDialog::~RunChangeDialog() -{ - delete ui; -} - -QxClientService *RunChangeDialog::service() -{ - auto *svc = qobject_cast(Service::serviceByName(QxClientService::serviceId())); - Q_ASSERT(svc); - return svc; -} - -void RunChangeDialog::setMessage(const QString &msg, bool error) -{ - if (msg.isEmpty()) { - ui->lblError->setStyleSheet({}); - } - else if (error) { - ui->lblError->setStyleSheet("background: salmon"); - } - else { - ui->lblError->setStyleSheet({}); - } - ui->lblError->setText(msg); -} - -void RunChangeDialog::loadOrigValues() -{ - Q_ASSERT(m_runId > 0); - - qf::core::sql::Query q; - q.execThrow(QStringLiteral("SELECT classes.name AS class_name, competitors.id AS competitor_id" - " FROM runs" - " JOIN competitors ON competitors.id=runs.competitorId AND runs.id=%1" - " LEFT JOIN classes ON competitors.classId=classes.id") - .arg(m_runId) - ); - if (q.next()) { - ui->edClassName->setText(q.value("class_name").toString()); - m_competitorId = q.value("competitor_id").toInt(); - } - - Competitors::CompetitorDocument doc; - doc.load(m_competitorId, qf::gui::model::DataDocument::ModeView); - - m_origValues.first_name = doc.value("firstName").toString(); - m_origValues.last_name = doc.value("lastName").toString(); - m_origValues.registration = doc.value("registration").toString(); - m_origValues.si_id = doc.value("siId").toInt(); - - ui->edFirstNameOrig->setText(m_origValues.first_name); - ui->edLastNameOrig->setText(m_origValues.last_name); - ui->edRegistrationOrig->setText(m_origValues.registration); - ui->edSiCardOrig->setValue(m_origValues.si_id); -} - -void RunChangeDialog::loadClassId() -{ - auto class_name = ui->edClassName->text(); - if (class_name.isEmpty()) { - return; - } - qf::core::sql::Query q; - q.execThrow(QStringLiteral("SELECT id" - " FROM classes" - " WHERE name='%1'") - .arg(class_name) - ); - if (q.next()) { - m_classId = q.value("id").toInt(); - } -} - -void RunChangeDialog::lockChange() -{ - // NIY - // auto *svc = service(); - // auto *nm = svc->networkManager(); - - // auto path = QStringLiteral("/api/event/%1/changes").arg(svc->eventId()); - // QUrlQuery query; - // query.addQueryItem("from_id", QString::number(m_changeId)); - // query.addQueryItem("limit", QString::number(1)); - // svc->getHttpJson(path, query, this, [this](auto data, auto error) { - // if (!error.isEmpty()) { - // setMessage(error, true); - // return; - // } - // auto rec = data.toList().value(0).toMap(); - // }); - - - // QNetworkRequest request; - // auto url = svc->exchangeServerUrl(); - // // qfInfo() << "url " << url.toString(); - // url.setPath("/api/event/current/changes/lock-change"); - - // QUrlQuery query; - // query.addQueryItem("change_id", QString::number(m_changeId)); - // auto connection_id = QxClientService::currentConnectionId(); - // query.addQueryItem("lock_number", QString::number(connection_id)); - // url.setQuery(query); - // qfInfo() << "GET " << url.toString(); - - // request.setUrl(url); - // request.setRawHeader(QxClientService::QX_API_TOKEN, svc->apiToken()); - // auto *reply = nm->get(request); - // connect(reply, &QNetworkReply::finished, this, [this, reply, connection_id]() { - // auto data = reply->readAll(); - // if (reply->error() == QNetworkReply::NetworkError::NoError) { - // m_lockNumber = data.toInt(); - // ui->edLockNumber->setValue(m_lockNumber); - // if (m_lockNumber == connection_id) { - // ui->btAccept->setDisabled(false); - // ui->btReject->setDisabled(false); - - // qf::core::sql::Query q; - // q.execThrow(QStringLiteral("UPDATE qxchanges SET lock_number=%1, status='Locked' WHERE id=%2") - // .arg(connection_id) - // .arg(m_changeId) - // ); - // } - // else { - // setMessage(tr("Change is locked already by other client: %1, current client id:.%2").arg(m_lockNumber).arg(connection_id), false); - // } - // } - // else { - // setMessage(tr("Lock change error: %1\n%2").arg(reply->errorString()).arg(QString::fromUtf8(data)), true); - // } - // reply->deleteLater(); - // }); -} - -void RunChangeDialog::resolveChanges(bool is_accepted) -{ - if (is_accepted) { - applyLocalChanges(is_accepted); - } - auto *svc = service(); - auto *nm = svc->networkManager(); - QNetworkRequest request; - auto url = svc->exchangeServerUrl(); - // qfInfo() << "url " << url.toString(); - url.setPath("/api/event/current/changes/resolve-change"); - - QUrlQuery query; - query.addQueryItem("change_id", QString::number(m_changeId)); - query.addQueryItem("lock_number", QString::number(m_lockNumber)); - query.addQueryItem("accepted", is_accepted? "true": "false"); - query.addQueryItem("status_message", ui->edStatusMessage->text()); - url.setQuery(query); - - request.setUrl(url); - request.setRawHeader(QxClientService::QX_API_TOKEN, svc->apiToken()); - auto *reply = nm->get(request); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - accept(); - } - else { - QMessageBox::warning(this, - QCoreApplication::applicationName(), - tr("Update change error: %1").arg(reply->errorString())); - } - reply->deleteLater(); - }); -} - -void RunChangeDialog::applyLocalChanges(bool is_accepted) -{ - using namespace qf::gui::model; - - bool is_insert = m_runId == 0; - Competitors::CompetitorDocument doc; - doc.load(m_competitorId, is_insert? DataDocument::ModeInsert: DataDocument::ModeEdit); - if (is_insert) { - doc.setValue("classId", m_classId); - } - if (ui->grpFirstName->isChecked()) { - doc.setValue("firstName", ui->edFirstName->text()); - } - if (ui->grpLastName->isChecked()) { - doc.setValue("lastName", ui->edLastName->text()); - } - if (ui->grpRegistration->isChecked()) { - doc.setValue("registration", ui->edRegistration->text()); - } - if (ui->grpSiCard->isChecked()) { - if (is_insert) { - doc.setValue("siId", ui->edSiCard->value()); - } - else { - qf::core::sql::Query q; - q.execThrow(QStringLiteral("UPDATE runs SET siId=%1 WHERE id=%2") - .arg(ui->edSiCard->value()) - .arg(m_runId) - ); - } - } - doc.save(); - { - qf::core::sql::Query q; - q.execThrow(QStringLiteral("UPDATE qxchanges SET status='%1' WHERE id=%2") - .arg(is_accepted? "Accepted": "Rejected") - .arg(m_changeId) - ); - } - { - auto dc_str = qf::core::Utils::qvariantToJson(m_origValues.toVariantMap()); - QString qs = "UPDATE qxchanges SET orig_data=:orig_data WHERE id=:id"; - qf::core::sql::Query q; - q.prepare(qs, qf::core::Exception::Throw); - q.bindValue(":orig_data", dc_str); - q.bindValue(":id", m_changeId); - q.exec(qf::core::Exception::Throw); - } -} - -bool RunChangeDialog::checkHttpError(QNetworkReply *reply) -{ - if (reply->error() != QNetworkReply::NetworkError::NoError) { - setMessage(tr("Http error: %1\n%2") - .arg(reply->request().url().toString()) - .arg(reply->errorString()), true); - return false; - } - return true; -} - -} // namespace Event::services::qx - - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h deleted file mode 100644 index d704a1d48..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef RUNCHANGEDIALOG_H -#define RUNCHANGEDIALOG_H - -#include "runchange.h" - -#include - -class QNetworkReply; - -namespace Event::services::qx { - -namespace Ui { -class RunChangeDialog; -} - -struct RunChange; -class QxClientService; - -class RunChangeDialog : public QDialog -{ - Q_OBJECT - -public: - explicit RunChangeDialog(int change_id, int run_id, int lock_number, const RunChange &run_change, QWidget *parent = nullptr); - ~RunChangeDialog() override; - -private: - QxClientService* service(); - void setMessage(const QString &msg, bool error); - - void loadOrigValues(); - void loadClassId(); - - void lockChange(); - - void resolveChanges(bool is_accepted); - void applyLocalChanges(bool is_accepted); - - bool checkHttpError(QNetworkReply *reply); -private: - Ui::RunChangeDialog *ui; - int m_changeId = 0; - int m_runId = 0; - int m_competitorId = 0; - int m_classId = 0; - int m_lockNumber = 0; - OrigRunRecord m_origValues; -}; - - -} // namespace Event::services::qx - - -#endif // RUNCHANGEDIALOG_H diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp new file mode 100644 index 000000000..25a68b6fc --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -0,0 +1,262 @@ +#include "sqlapi.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace shv::chainpack; + +namespace Event::services::qx { + +RpcValue dbFieldToRpcValue(const DbField &fld) +{ + RpcValue::Map ret; + ret["name"] = fld.name.toStdString(); + return RpcValue(std::move(ret)); +} + +RpcValue execResultToRpcValue(const ExecResult &res) +{ + RpcValue::Map ret; + ret["numRowsAffected"] = res.numRowsAffected; + ret["lastInsertId"] = res.lastInsertId.has_value()? RpcValue(res.lastInsertId.value()): RpcValue(nullptr); + return ret; +} + +RpcValue queryResultToRpcValue(const QueryResult &res) +{ + RpcValue::Map ret; + RpcValue::List flds; + for(const auto &fld : res.fields) { + flds.push_back(dbFieldToRpcValue(fld)); + } + RpcValue::List rpc_rows; + for (const auto &row : res.rows) { + RpcValue::List rpc_row; + for (const auto &cell : row) { + rpc_row.push_back(shv::coreqt::rpc::qVariantToRpcValue(cell)); + } + rpc_rows.push_back(std::move(rpc_row)); + } + ret["fields"] = flds; + ret["rows"] = std::move(rpc_rows); + return ret; +} + +namespace { +RpcValue::List toShvRecordList(const QList &records) +{ + RpcValue::List ret; + for (const auto &rec : records) { + ret.push_back(shv::coreqt::rpc::qVariantToRpcValue(rec)); + } + return ret; +} +QStringList toQStringList(const std::vector &sl) +{ + QStringList qsl; + for (const auto &s : sl) { + qsl << QString::fromStdString(s); + } + return qsl; +} +} + +SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + const auto &lst = rv.asList(); + auto sql_query = QString::fromStdString(lst.valref(0).asString()); + const auto &sql_params = lst.valref(1); + return SqlQueryAndParams { .query = sql_query, .params = shv::coreqt::rpc::rpcValueToQVariant(sql_params).toMap() }; +} + +RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) +{ + auto m = chng.toVariantMap(); + return shv::coreqt::rpc::qVariantToRpcValue(m); +} + +//============================================== +// SqlApi +//============================================== +SqlApi::SqlApi(QObject *parent) + : QObject{parent} +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + connect(qxsql, &qf::core::sql::QxSql::recChng, this, &SqlApi::recChng); +} + +// void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) +// { +// qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); +// emit recchng(chng); +// } + +// namespace { + +// class Transaction +// { +// public: +// Transaction(QSqlDatabase db) : m_db(db) { +// if (!m_db.transaction()) { +// qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); +// throw std::runtime_error("BEGIN transaction error"); +// } +// } +// ~Transaction() { +// if (m_inTransaction) { +// m_db.rollback(); +// } +// } +// void commit() { +// if (!m_db.commit()) { +// qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); +// throw std::runtime_error("COMMIT transaction error"); +// } +// m_inTransaction = false; +// } +// private: +// QSqlDatabase m_db; +// bool m_inTransaction = true; +// }; + +// void bindParams(qf::core::sql::Query &q, const Record ¶ms) +// { +// for (const auto &[k, v] : params.asKeyValueRange()) { +// q.bindValue(':' + k, v); +// } +// } + +// QueryResult sqlQuery(const SqlQueryAndParams ¶ms) +// { +// qf::core::sql::Query q; +// q.prepare(params.query, qf::core::Exception::Throw); +// bindParams(q, params.params); +// q.exec(qf::core::Exception::Throw); + +// QueryResult ret; +// QSqlRecord rec = q.record(); +// for (int i = 0; i < rec.count(); ++i) { +// QSqlField fld = rec.field(i); +// DbField rfld; +// rfld.name = fld.name(); +// // rfld.name.replace("__", "."); +// ret.fields.push_back(rfld); +// } +// while(q.next()) { +// QueryResult::Row row; +// for (int i = 0; i < rec.count(); ++i) { +// row.push_back(q.value(i)); +// //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); +// } +// ret.rows.insert(ret.rows.size(), row); +// } +// return ret; +// } + +// ExecResult sqlExec(const SqlQueryAndParams ¶ms) +// { +// qf::core::sql::Query q; +// q.prepare(params.query, qf::core::Exception::Throw); +// bindParams(q, params.params); +// q.exec(qf::core::Exception::Throw); + +// ExecResult ret; +// ret.numRowsAffected = q.numRowsAffected(); +// ret.lastInsertId = q.lastInsertId().toInt(); +// return ret; +// } + +// } + +ExecResult SqlApi::exec(const SqlQueryAndParams ¶ms) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->exec(params.query, params.params); +} + +QueryResult SqlApi::query(const SqlQueryAndParams ¶ms) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->query(params.query, params.params); +} + +void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + QVariantList qparams; + for (const auto &p : params) { + qparams << shv::coreqt::rpc::rpcValueToQVariant(p); + } + qxsql->transaction(QString::fromStdString(query), qparams); +} + +RpcValue::List SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + auto records = qxsql->listRecords(QString::fromStdString(table), toQStringList(fields), ids_above, limit); + return toShvRecordList(records); +} +namespace { +std::string to_lower(const std::string &s) +{ + std::string result; + result.reserve(s.size()); + + std::ranges::transform(s, std::back_inserter(result), + [](unsigned char c) { return std::tolower(c); }); + + return result; +} + +[[maybe_unused]] Record normalizeFieldNames(const Record &rec) +{ + Record ret; + for (const auto &[k, v] : rec.asKeyValueRange()) { + ret[QString::fromStdString(to_lower(k.toStdString()))] = v; + } + return ret; +} +} +int64_t SqlApi::create(const std::string &table, const RpcValue::Map &record) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->createRecord(QString::fromStdString(table), shv::coreqt::rpc::rpcValueToQVariant(record).toMap(), this); +} + +std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + if (auto rec = qxsql->readRecord(QString::fromStdString(table), id, toQStringList(fields)); rec.has_value()) { + return shv::coreqt::rpc::qVariantToRpcValue(rec.value()).asMap(); + } + return {}; +} + +bool SqlApi::update(const std::string &table, int64_t id, const RpcValue::Map &record) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->updateRecord(QString::fromStdString(table), id, shv::coreqt::rpc::rpcValueToQVariant(record).toMap(), this); +} + +bool SqlApi::drop(const std::string &table, int64_t id) +{ + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->deleteRecord(QString::fromStdString(table), id, this); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h new file mode 100644 index 000000000..4e3294683 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace qf::core::sql { struct QxRecChng; } + +namespace Event::services::qx { + +using DbField = qf::core::sql::DbField; +using ExecResult = qf::core::sql::ExecResult; +using QueryResult = qf::core::sql::QueryResult; + +shv::chainpack::RpcValue dbFieldToRpcValue(const DbField &fld); +shv::chainpack::RpcValue execResultToRpcValue(const ExecResult &res); +shv::chainpack::RpcValue queryResultToRpcValue(const QueryResult &res); + +using Record = QVariantMap; + +struct SqlQueryAndParams +{ + QString query; + Record params; + + static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); +}; + +shv::chainpack::RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng); + + +class SqlApi : public QObject +{ + Q_OBJECT +public: + explicit SqlApi(QObject *parent = nullptr); + + Q_SIGNAL void recChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + + ExecResult exec(const SqlQueryAndParams ¶ms); + QueryResult query(const SqlQueryAndParams ¶ms); + void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); + shv::chainpack::RpcValue::List list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); + std::optional read(const std::string &table, int64_t id, const std::vector &fields); + bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); + bool drop(const std::string &table, int64_t id); +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp new file mode 100644 index 000000000..063e5d516 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -0,0 +1,137 @@ +#include "sqlapinode.h" +#include "sqlapi.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace shv::chainpack; +using namespace shv::coreqt::data; + +namespace Event::services::qx { + +SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) + : Super("sql", parent) + , m_sqlApi(new SqlApi(this)) +{ + connect(m_sqlApi, &SqlApi::recChng, this, [this](const qf::core::sql::QxRecChng &recchng, QObject *source) { + Q_UNUSED(source); + RpcSignal sig; + sig.setSignal("recchng"); + sig.setShvPath(shvPath().asString()); + sig.setParams(shv::coreqt::rpc::qVariantToRpcValue(recchng.toVariantMap())); + qfInfo() << "emit recchng:" << recchng.table; + emitSendRpcMessage(sig); + }); + +} + +namespace { +auto METH_QUERY = "query"; +auto METH_EXEC = "exec"; +auto METH_TRANSACTION = "transaction"; +auto METH_LIST = "list"; +auto METH_CREATE = "create"; +auto METH_READ = "read"; +auto METH_UPDATE = "update"; +auto METH_DELETE = "delete"; +} + +const std::vector &SqlApiNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_QUERY, MetaMethod::Flag::LargeResultHint, "[s:query,{s|i|b|t|n}:params]", "{{s:name}:fields,[[s|i|b|t|n]]:rows}", AccessLevel::Read}, + {METH_EXEC, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "{i:rows_affected,i|n:insert_id}", AccessLevel::Write}, + {METH_TRANSACTION, MetaMethod::Flag::None, "[s:query,[{s|i|b|t|n}]:params]", "n", AccessLevel::Write}, + {METH_LIST, MetaMethod::Flag::LargeResultHint, "{s:table,[s]|n:fields,i|n:ids_above,i|n:limit}", "[{s|i|b|t|n}]", AccessLevel::Write}, + {METH_CREATE, MetaMethod::Flag::None, "{s:table,{s|i|b|t|n}:record}", "i", AccessLevel::Write}, + {METH_READ, MetaMethod::Flag::None, "{s:table,i:id,{s}|n:fields}", "{s|i|b|t|n}|n", AccessLevel::Read}, + {METH_UPDATE, MetaMethod::Flag::None, "{s:table,i:id,{s|i|b|t|n}:record}", "b", AccessLevel::Write}, + {METH_DELETE, MetaMethod::Flag::None, "{s:table,i:id}", "b", AccessLevel::Write}, + }; + return meta_methods; +} + +RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == METH_EXEC) { + auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); + return execResultToRpcValue(res); + } + if(method == METH_QUERY) { + auto res = m_sqlApi->query(SqlQueryAndParams::fromRpcValue(params)); + return queryResultToRpcValue(res); + } + if(method == METH_TRANSACTION) { + auto sql_query = params.asList().valref(0).asString(); + const auto &sql_params = params.asList().valref(0); + m_sqlApi->transaction(sql_query, sql_params.asList()); + return RpcValue(nullptr); + } + if(method == METH_LIST) { + const auto &map = params.asMap(); + const auto &table = map.value("table").asString(); + std::vector fields; + for (const auto &fn : map.valref("fields").asList()) { + fields.push_back(fn.asString()); + } + auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); + auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); + auto res = m_sqlApi->list(table, fields, ids_above, limit); + return res; + } + if(method == METH_CREATE) { + const auto &map = params.asMap(); + const auto &table = map.value("table").asString(); + const auto &record = map.valref("record").asMap(); + auto res = m_sqlApi->create(table, record); + return res; + } + if(method == METH_READ) { + const auto &map = params.asMap(); + const auto &table = map.value("table").asString(); + auto id = map.value("id").toInt64(); + std::vector fields; + for (const auto &fn : map.valref("fields").asList()) { + fields.push_back(fn.asString()); + } + auto res = m_sqlApi->read(table, id, fields); + if (res.has_value()) { + return res.value(); + } + return RpcValue(nullptr); + } + if(method == METH_UPDATE) { + const auto &map = params.asMap(); + const auto &table = map.value("table").asString(); + auto id = map.value("id").toInt64(); + const auto &record = map.valref("record").asMap(); + auto res = m_sqlApi->update(table, id, record); + return res; + } + if(method == METH_DELETE) { + const auto &map = params.asMap(); + const auto &table = map.value("table").asString(); + auto id = map.valref("id").toInt(); + auto res = m_sqlApi->drop(table, id); + return res; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h new file mode 100644 index 000000000..d461ae689 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h @@ -0,0 +1,28 @@ +#pragma once + +#include "qxnode.h" + +class QSqlQuery; +class QSqlRecord; + +namespace shv::coreqt::data { class RpcSqlResult; } + +namespace Event::services::qx { + +class SqlApi; + +class SqlApiNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit SqlApiNode(shv::iotqt::node::ShvNode *parent); +protected: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +private: + SqlApi *m_sqlApi = nullptr; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/service.cpp b/quickevent/app/quickevent/plugins/Event/src/services/service.cpp index 0c469327c..45e49ade8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/service.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/service.cpp @@ -50,7 +50,7 @@ void Service::onEventOpen() { auto *event_plugin = getPlugin(); if(event_plugin->isEventOpen()) { - loadSettingsForEvent(event_plugin->eventName()); + loadSettingsForEvent(event_plugin->eventDbName()); ServiceSettings ss = settings(); if(ss.isAutoStart()) { run(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp index e4c9a604a..324dd0424 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp @@ -35,12 +35,7 @@ void ServicesWidget::reload() Service *svc = Service::serviceAt(i); auto *sw = new ServiceWidget(); - sw->setStatus(svc->status()); - connect(svc, &Service::statusChanged, sw, &ServiceWidget::setStatus); - sw->setServiceId(svc->serviceId(), svc->serviceDisplayName()); - sw->setMessage(svc->statusMessage()); - connect(svc, &Service::statusMessageChanged, sw, &ServiceWidget::setMessage); - connect(sw, &ServiceWidget::setRunningRequest, svc, &Service::setRunning); + sw->setService(svc); ly2->addWidget(sw); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp index dd4bf8233..3069eb848 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp @@ -45,10 +45,15 @@ void ServiceWidget::setStatus(Service::Status st) } } -void ServiceWidget::setServiceId(const QString &id, const QString &display_name) +void ServiceWidget::setService(Service *service) { - m_serviceId = id; - ui->lblServiceName->setText(display_name.isEmpty()? id: display_name); + m_serviceId = service->serviceId(); + ui->lblServiceName->setText(service->serviceDisplayName()); + setStatus(service->status()); + connect(service, &Service::statusChanged, this, &ServiceWidget::setStatus); + setMessage(service->statusMessage()); + connect(service, &Service::statusMessageChanged, this, &ServiceWidget::setMessage); + connect(this, &ServiceWidget::setRunningRequest, service, &Service::setRunning); } QString ServiceWidget::serviceId() const diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h index f659cafde..4d589d2fe 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h @@ -21,7 +21,7 @@ class ServiceWidget : public QWidget ~ServiceWidget(); void setStatus(Service::Status st); - void setServiceId(const QString &id, const QString &display_name); + void setService(Service *service); QString serviceId() const; void setMessage(const QString &m); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui index cdff60fa1..510d437a7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui @@ -87,6 +87,9 @@ neco neco + + true + diff --git a/quickevent/app/quickevent/plugins/Event/src/stage.cpp b/quickevent/app/quickevent/plugins/Event/src/stage.cpp deleted file mode 100644 index 1bd53d5e7..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/stage.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "stage.h" - -#include - -using namespace Event; - -StageData::StageData(const QVariantMap &data) - : Super(data) -{ -} - -QDateTime StageData::startDateTime() const -{ - auto dt = value(QStringLiteral("startdatetime")).toDateTime(); - dt = dt.toTimeZone(QTimeZone::systemTimeZone()); - return dt; -} - - - - diff --git a/quickevent/app/quickevent/plugins/Event/src/stage.h b/quickevent/app/quickevent/plugins/Event/src/stage.h deleted file mode 100644 index f52c79399..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/stage.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -namespace Event { - - -class StageData : public QVariantMap -{ -private: - typedef QVariantMap Super; - - QF_VARIANTMAP_KEY_FIELD(bool, useallmaps, is, set, UseAllMaps) - QF_VARIANTMAP_KEY_FIELD(QVariantMap, drawingconfig, d, setD, rawingConfig) - QF_VARIANTMAP_KEY_FIELD(QString, qxapitoken, qx, setQx, ApiToken) -public: - StageData(const QVariantMap &data = QVariantMap()); - - QDateTime startDateTime() const; -}; - -} - diff --git a/quickevent/app/quickevent/plugins/Event/src/stageconfig.cpp b/quickevent/app/quickevent/plugins/Event/src/stageconfig.cpp new file mode 100644 index 000000000..bff47a2d8 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/stageconfig.cpp @@ -0,0 +1,97 @@ +#include "stageconfig.h" + +#include +#include + +#include +#include +#include + +namespace Event { + +StartSlotConfig StartSlotConfig::fromVariantMap(const QVariantMap& map) +{ + StartSlotConfig config; + config.startOffset = map.value("startOffset").toInt(); + config.ignoreClassClashCheck = map.value("ignoreClassClashCheck").toBool(); + return config; +} + +QVariantMap StartSlotConfig::toVariantMap() const +{ + QVariantMap map; + map["startOffset"] = startOffset; + map["ignoreClassClashCheck"] = ignoreClassClashCheck; + return map; +} + +DrawingConfig DrawingConfig::fromString(const QString& str) +{ + DrawingConfig config; + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(str.toUtf8(), &parseError); + if (parseError.error == QJsonParseError::NoError) { + if (doc.isObject()) { + config = fromVariantMap(doc.object().toVariantMap()); + } + } else { + qfWarning() << "Failed to parse drawing config JSON:" << parseError.errorString(); + } + return config; +} + +DrawingConfig DrawingConfig::fromVariantMap(const QVariantMap& map) +{ + DrawingConfig config; + auto lst = map.value("startSlots").toList(); + for (const auto& slot : lst) { + config.startSlots.push_back(StartSlotConfig::fromVariantMap(slot.toMap())); + } + return config; +} + +QVariantMap DrawingConfig::toVariantMap() const +{ + QVariantMap map; + QVariantList lst; + for (const auto& slot : startSlots) { + lst.append(slot.toVariantMap()); + } + map["startSlots"] = lst; + return map; +} + +const StartSlotConfig& DrawingConfig::startSlotConfig(int index) const +{ + if (index < 0 || index >= static_cast(startSlots.size())) { + static StartSlotConfig defaultSlot; + return defaultSlot; + } + return startSlots[index]; +} + +StageConfig StageConfig::fromVariantMap(const QVariantMap& map) +{ + StageConfig config; + config.startDateTime = map.value("startDateTime").toDateTime(); + config.useAllMaps = map.value("useAllMaps").toBool(); + auto v = map.value("drawingConfig"); + if (v.userType() == QMetaType::QVariantMap) { + config.drawingConfig = DrawingConfig::fromVariantMap(v.toMap()); + } + else if (v.userType() == QMetaType::QString) { + config.drawingConfig = DrawingConfig::fromString(v.toString()); + } + return config; +} + +QVariantMap StageConfig::toVariantMap() const +{ + QVariantMap map; + map["startDateTime"] = startDateTime; + map["useAllMaps"] = useAllMaps; + map["drawingConfig"] = drawingConfig.toVariantMap(); + return map; +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/stageconfig.h b/quickevent/app/quickevent/plugins/Event/src/stageconfig.h new file mode 100644 index 000000000..c286f534e --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/stageconfig.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +namespace Event { + +struct StartSlotConfig +{ + static StartSlotConfig fromVariantMap(const QVariantMap& map); + QVariantMap toVariantMap() const; + + int startOffset; + bool ignoreClassClashCheck = false; +}; + +struct DrawingConfig +{ + static DrawingConfig fromString(const QString& str); + static DrawingConfig fromVariantMap(const QVariantMap& map); + QVariantMap toVariantMap() const; + const StartSlotConfig& startSlotConfig(int index) const; + + std::vector startSlots; +}; + +struct StageConfig +{ + static StageConfig fromVariantMap(const QVariantMap& map); + QVariantMap toVariantMap() const; + + QDateTime startDateTime; + bool useAllMaps = false; + DrawingConfig drawingConfig; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/stagedocument.cpp b/quickevent/app/quickevent/plugins/Event/src/stagedocument.cpp deleted file mode 100644 index 45462be9b..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/stagedocument.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "stagedocument.h" - -#include - -using namespace Event; - -StageDocument::StageDocument(QObject *parent) - : Super(parent) -{ - qf::core::sql::QueryBuilder qb; - qb.select2("stages", "*") - .from("stages") - .where("stages.id={{ID}}"); - setQueryBuilder(qb); -} - diff --git a/quickevent/app/quickevent/plugins/Event/src/stagedocument.h b/quickevent/app/quickevent/plugins/Event/src/stagedocument.h deleted file mode 100644 index 5c2b07992..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/stagedocument.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef EVENT_STAGEDOCUMENT_H -#define EVENT_STAGEDOCUMENT_H - -#include - -namespace Event { - -class StageDocument : public qf::gui::model::SqlDataDocument -{ - Q_OBJECT -private: - typedef qf::gui::model::SqlDataDocument Super; -public: - StageDocument(QObject *parent = nullptr); -}; - -} - -#endif // EVENT_STAGEDOCUMENT_H diff --git a/quickevent/app/quickevent/plugins/Event/src/stagewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/stagewidget.cpp deleted file mode 100644 index 8156d0142..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/stagewidget.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "stagewidget.h" -#include "ui_stagewidget.h" - -#include "stagedocument.h" - -#include - -using namespace Event; - -StageWidget::StageWidget(QWidget *parent) : - Super(parent), - ui(new Ui::StageWidget) -{ - setPersistentSettingsId("StageWidget"); - ui->setupUi(this); - - setTitle(tr("Stage")); - setWindowTitle(tr("Edit Stage")); - dataController()->setDocument(new StageDocument(this)); -} - -StageWidget::~StageWidget() -{ - delete ui; -} - -bool StageWidget::load(const QVariant &id, int mode) -{ - bool ok = Super::load(id, mode); - if(ok) { - QDateTime dt; - qf::gui::model::DataDocument *doc = dataDocument(); - const auto START_DATE_TIME = QStringLiteral("startDateTime"); - dt = doc->value(START_DATE_TIME).toDateTime().toLocalTime(); - /* - if(!dt.isValid()) { - // try old DB version - QDate d = doc->value(QStringLiteral("startDate")).toDate(); - QTime t = doc->value(QStringLiteral("startTime")).toTime(); - dt = QDateTime(d, t); - } - */ - ui->dateEdit->setDate(dt.date()); - ui->timeEdit->setTime(dt.time()); - } - return ok; -} - -bool StageWidget::saveData() -{ - QDate d = ui->dateEdit->date(); - QTime t = ui->timeEdit->time(); - QDateTime dt(d, t, QTimeZone::systemTimeZone()); - qf::gui::model::DataDocument *doc = dataDocument(); - const auto START_DATE_TIME = QStringLiteral("startDateTime"); - doc->setValue(START_DATE_TIME, dt); - /* - if(doc->isValidFieldName(START_DATE_TIME)) { - doc->setValue(START_DATE_TIME, dt); - } - else { - // old DB version - doc->setValue(QStringLiteral("startDate"), d); - doc->setValue(QStringLiteral("startTime"), t); - } - */ - return Super::saveData(); -} diff --git a/quickevent/app/quickevent/plugins/Event/src/stagewidget.h b/quickevent/app/quickevent/plugins/Event/src/stagewidget.h deleted file mode 100644 index 1361b80a7..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/stagewidget.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef EVENT_STAGEWIDGET_H -#define EVENT_STAGEWIDGET_H - -#include - -#include - -namespace Event { - -namespace Ui { -class StageWidget; -} - -class StageWidget : public qf::gui::framework::DataDialogWidget -{ - Q_OBJECT -private: - using Super = qf::gui::framework::DataDialogWidget; -public: - explicit StageWidget(QWidget *parent = 0); - ~StageWidget(); - - bool load(const QVariant &id = QVariant(), int mode = qf::gui::model::DataDocument::ModeEdit) Q_DECL_OVERRIDE; -protected: - bool saveData() Q_DECL_OVERRIDE; -private: - Ui::StageWidget *ui; -}; - -} - -#endif diff --git a/quickevent/app/quickevent/plugins/Event/src/stagewidget.ui b/quickevent/app/quickevent/plugins/Event/src/stagewidget.ui deleted file mode 100644 index 1b532ce4c..000000000 --- a/quickevent/app/quickevent/plugins/Event/src/stagewidget.ui +++ /dev/null @@ -1,70 +0,0 @@ - - - Event::StageWidget - - - - 0 - 0 - 413 - 313 - - - - Form - - - - - - &Date - - - dateEdit - - - - - - - dd.MM. yyyy - - - true - - - - - - - &Time - - - timeEdit - - - - - - - H:mm:ss - - - - - - - - qf::gui::TimeEdit - QTimeEdit -
qf/gui/timeedit.h
-
- - qf::gui::DateEdit - QDateEdit -
qf/gui/dateedit.h
-
-
- - -
diff --git a/quickevent/app/quickevent/plugins/Oris/src/orisimporter.cpp b/quickevent/app/quickevent/plugins/Oris/src/orisimporter.cpp index f17a5a91e..9cacd324e 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/orisimporter.cpp +++ b/quickevent/app/quickevent/plugins/Oris/src/orisimporter.cpp @@ -1,4 +1,5 @@ #include "chooseoriseventdialog.h" +#include "plugins/Event/src/eventconfig.h" #include "orisimporter.h" #include @@ -140,7 +141,7 @@ void OrisImporter::syncCurrentEventEntries(std::function success_callba { qf::gui::framework::MainWindow *fwk = qf::gui::framework::MainWindow::frameWork(); //if(!qf::gui::dialogs::MessageBox::askYesNo(fwk, tr("All runners entries imported from Oris will be synchronized, manual changes will be lost!"))) - int oris_id = getPlugin()->eventConfig()->importId(); + int oris_id = getPlugin()->eventConfig().importId; if(oris_id == 0) { qf::gui::dialogs::MessageBox::showError(fwk, tr("Cannot find Oris import ID.")); return; @@ -354,22 +355,22 @@ void OrisImporter::importEvent(int event_id, std::function success_call } auto discipline = discipline_id_opt.value_or(Event::EventConfig::Discipline::LongDistance); qfInfo() << "pocet etap:" << stage_count << "sport id:" << sport_id << "discipline id:" << di; - QVariantMap ecfg; - ecfg["stageCount"] = stage_count; - ecfg["name"] = data.value(QStringLiteral("Name")).toString(); - ecfg["description"] = QString(); - ecfg["date"] = QDate::fromString(data.value(QStringLiteral("Date")).toString(), Qt::ISODate); - ecfg["place"] = data.value(QStringLiteral("Place")).toString(); - ecfg["mainReferee"] = jsonObjectToFullName(data, QStringLiteral("MainReferee")); - ecfg["director"] = jsonObjectToFullName(data, QStringLiteral("Director")); - ecfg["sportId"] = sport_id; - ecfg["disciplineId"] = static_cast(discipline); - ecfg["importId"] = event_id; - ecfg["time"] = QTime::fromString(data.value(QStringLiteral("StartTime")).toString(), QStringLiteral("hh:mm")); + Event::EventConfig ecfg; + ecfg.stageCount = stage_count; + ecfg.name = data.value(QStringLiteral("Name")).toString(); + ecfg.description = QString(); + ecfg.date = QDate::fromString(data.value(QStringLiteral("Date")).toString(), Qt::ISODate); + ecfg.place = data.value(QStringLiteral("Place")).toString(); + ecfg.mainReferee = jsonObjectToFullName(data, QStringLiteral("MainReferee")); + ecfg.director = jsonObjectToFullName(data, QStringLiteral("Director")); + ecfg.sportId = sport_id; + ecfg.disciplineId = static_cast(discipline); + ecfg.importId = event_id; + ecfg.time = QTime::fromString(data.value(QStringLiteral("StartTime")).toString(), QStringLiteral("hh:mm")); if(!getPlugin()->createEvent(QString(), ecfg)) return; - bool is_relay = getPlugin()->eventConfig()->isRelays(); + bool is_relay = getPlugin()->eventConfig().isRelays(); //QString event_name = getPlugin()->eventName(); qfLogScope("importEvent"); qf::core::sql::Transaction transaction; @@ -467,7 +468,7 @@ const char KEY_ORIG_RUNS[] = "origRuns"; void OrisImporter::syncEventEntries(int event_id, std::function success_callback) { - if(getPlugin()->eventConfig()->isRelays()) { + if(getPlugin()->eventConfig().isRelays()) { syncRelaysEntries(event_id, success_callback); return; } @@ -769,7 +770,7 @@ void OrisImporter::syncEventEntries(int event_id, std::function success void OrisImporter::importRegistrations(std::function success_callback) { - int sport_id = getPlugin()->eventConfig()->sportId(); + int sport_id = getPlugin()->eventConfig().sportId; int year = QDate::currentDate().addMonths(-2).year(); bool ok; @@ -898,7 +899,7 @@ void OrisImporter::getAndImportClub(const QString &club, const QString &key) void OrisImporter::importMissingOneTimeClubs() { auto *fwk = qf::gui::framework::MainWindow::frameWork(); - auto event_key = getPlugin()->eventConfig()->orisEventKey(); + auto event_key = getPlugin()->eventConfig().orisEventKey; if (event_key.isEmpty()) { QMessageBox::warning(fwk,tr("Warning"),tr("For import one-time clubs, you need to fill ORIS Event Key in File->Event->Edit event")); return; @@ -925,7 +926,7 @@ void OrisImporter::importMissingOneTimeClubs() } } - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->eventConfig().isRelays()) { // also from relays table q.exec("SELECT id, club FROM relays ORDER BY id", qf::core::Exception::Throw); while(q.next()) { @@ -959,5 +960,3 @@ void OrisImporter::importMissingOneTimeClubs() qf::gui::dialogs::MessageBox::showException(fwk, e); } } - - diff --git a/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp index 7f0181aea..2afc4d9d2 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp +++ b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp @@ -185,8 +185,8 @@ bool XmlImporter::importEntries(QXmlStreamReader &reader, const XmlCreators crea } } - Event::EventConfig *event_config = getPlugin()->eventConfig(); - int selected_race = event_config->iofXmlRaceNumber(); // if entries has more races in (defined in Event), selected race + const auto &event_config = getPlugin()->eventConfig(); + int selected_race = event_config.iofXmlRaceNumber; // if entries has more races in (defined in Event), selected race // load from XML & insert to db int items_processed = 0; int relays_processed = 0; @@ -476,7 +476,7 @@ bool XmlImporter::importStartlist(QXmlStreamReader &reader, const XmlCreators cr bool XmlImporter::importClasses(QXmlStreamReader &reader, const XmlCreators creator) { // load data from XML - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); qf::core::sql::Transaction transaction; int items_processed = 0; while(reader.readNextStartElement()) { @@ -806,20 +806,20 @@ bool XmlImporter::importEvent(QXmlStreamReader &reader, const XmlCreators creato else return false; try { - QVariantMap ecfg; - ecfg["stageCount"] = 1; - ecfg["name"] = (event_race.name.isEmpty()) ? race_name : event_race.name; - ecfg["description"] = (event_race.name.isEmpty())? QString() : race_name; - ecfg["date"] = event_race.datetime.date(); - ecfg["place"] = QString(); - ecfg["mainReferee"] = QString(); - ecfg["director"] = QString(); - ecfg["sportId"] = static_cast(Event::EventConfig::Sport::OB); - ecfg["disciplineId"] = static_cast(discipline); - ecfg["importId"] = event_id; - ecfg["time"] = event_race.datetime.time(); - ecfg["iofRace"] = 1; - ecfg["iofXmlRaceNumber"] = (races.size() > 1) ? event_race.number : 0; + Event::EventConfig ecfg; + ecfg.stageCount = 1; + ecfg.name = (event_race.name.isEmpty()) ? race_name : event_race.name; + ecfg.description = (event_race.name.isEmpty())? QString() : race_name; + ecfg.date = event_race.datetime.date(); + ecfg.place = QString(); + ecfg.mainReferee = QString(); + ecfg.director = QString(); + ecfg.sportId = static_cast(Event::EventConfig::Sport::OB); + ecfg.disciplineId = static_cast(discipline); + ecfg.importId = event_id; + ecfg.time = event_race.datetime.time(); + ecfg.iofRace = true; + ecfg.iofXmlRaceNumber = (races.size() > 1) ? event_race.number : 0; return getPlugin()->createEvent(QString(), ecfg); } catch (qf::core::Exception &e) { diff --git a/quickevent/app/quickevent/plugins/Receipts/src/receiptsconfig.cpp b/quickevent/app/quickevent/plugins/Receipts/src/receiptsconfig.cpp new file mode 100644 index 000000000..cd96fb522 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Receipts/src/receiptsconfig.cpp @@ -0,0 +1,39 @@ +#include "receiptsconfig.h" + +#include + +namespace Receipts { + +ReceiptsConfig::ReceiptsConfig() +{ + qrCodeCaption = "Live Results"; + imageFormat = "png"; +} + +ReceiptsConfig ReceiptsConfig::fromVariantMap(const QVariantMap &map) +{ + ReceiptsConfig config; + config.printQrCode = map.value("printQrCode", config.printQrCode).toBool(); + config.linkUrl = map.value("linkUrl", config.linkUrl).toString(); + config.qrCodeCaption = map.value("qrCodeCaption", config.qrCodeCaption).toString(); + config.printImage = map.value("printImage", config.printImage).toBool(); + config.imageHeightMm = map.value("imageHeightMm", config.imageHeightMm).toInt(); + config.imageBase64 = map.value("imageBase64", config.imageBase64).toString(); + config.imageFormat = map.value("imageFormat", config.imageFormat).toString(); + return config; +} + +QVariantMap ReceiptsConfig::toVariantMap() const +{ + QVariantMap ret; + ret["printQrCode"] = printQrCode; + ret["linkUrl"] = linkUrl; + ret["qrCodeCaption"] = qrCodeCaption; + ret["printImage"] = printImage; + ret["imageHeightMm"] = imageHeightMm; + ret["imageBase64"] = imageBase64; + ret["imageFormat"] = imageFormat; + return ret; +} + +} diff --git a/quickevent/app/quickevent/plugins/Receipts/src/receiptsconfig.h b/quickevent/app/quickevent/plugins/Receipts/src/receiptsconfig.h new file mode 100644 index 000000000..35e774cb2 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Receipts/src/receiptsconfig.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace Receipts { + +struct ReceiptsConfig +{ + ReceiptsConfig(); + static ReceiptsConfig fromVariantMap(const QVariantMap &map); + QVariantMap toVariantMap() const; + + bool printQrCode = false; + QString linkUrl; + QString qrCodeCaption; // Default "Live Results" applied by AppDbConfig::receiptsConfig() + bool printImage = false; + int imageHeightMm = 18; // Clamped to [10, 60] + QString imageBase64; + QString imageFormat; // Default "png" applied by AppDbConfig::receiptsConfig() +}; + +} diff --git a/quickevent/app/quickevent/plugins/Receipts/src/receiptsplugin.cpp b/quickevent/app/quickevent/plugins/Receipts/src/receiptsplugin.cpp index d8546c457..52214d912 100644 --- a/quickevent/app/quickevent/plugins/Receipts/src/receiptsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Receipts/src/receiptsplugin.cpp @@ -55,25 +55,21 @@ using CardReader::CardReaderPlugin; namespace Receipts { namespace { -QString eventConfigKey(const QString &suffix) +ReceiptsConfig currentReceiptsConfig() { const int current_stage = qMax(getPlugin()->currentStageId(), 1); - return QStringLiteral("event.") + suffix + QStringLiteral(".E") + QString::number(current_stage); + return getPlugin()->appDbConfig().receiptsConfig(current_stage); } QString configuredReceiptEventLinkUrl() { - return getPlugin()->eventConfig()->value(eventConfigKey(QStringLiteral("receiptEventLinkUrl"))).toString().trimmed(); + return currentReceiptsConfig().linkUrl; } -QString defaultReceiptQrCodeCaption() -{ - return QStringLiteral("Live Results"); -} QString configuredReceiptQrCodeCaption() { - return getPlugin()->eventConfig()->value(eventConfigKey(QStringLiteral("receiptPrintEventQrCodeCaption")), defaultReceiptQrCodeCaption()).toString().trimmed(); + return currentReceiptsConfig().qrCodeCaption; } QString receiptLinkWithCompetitorClass(const QString &base_url, const QString &class_name) @@ -100,35 +96,27 @@ QString receiptLinkWithCompetitorClass(const QString &base_url, const QString &c bool printReceiptImageEnabled() { - return getPlugin()->eventConfig()->value(eventConfigKey(QStringLiteral("receiptPrintEventImage")), false).toBool(); + return currentReceiptsConfig().printImage; } bool printReceiptQrCodeEnabled() { - return getPlugin()->eventConfig()->value(eventConfigKey(QStringLiteral("receiptPrintEventQrCode")), false).toBool(); + return currentReceiptsConfig().printQrCode; } QString configuredReceiptImageBase64() { - return getPlugin()->eventConfig()->value(eventConfigKey(QStringLiteral("receiptImageDataBase64"))).toString(); + return currentReceiptsConfig().imageBase64; } QString configuredReceiptImageFormat() { - return getPlugin()->eventConfig()->value(eventConfigKey(QStringLiteral("receiptImageFormat")), QStringLiteral("png")).toString().trimmed().toLower(); + return currentReceiptsConfig().imageFormat; } int configuredReceiptImageHeightMm() { - bool ok = false; - int image_height_mm = getPlugin()->eventConfig()->value(eventConfigKey(QStringLiteral("receiptImageHeightMm")), 18).toInt(&ok); - if(!ok) - return 18; - if(image_height_mm < 10) - return 10; - if(image_height_mm > 60) - return 60; - return image_height_mm; + return currentReceiptsConfig().imageHeightMm; } QString receiptImageCacheDirPath() @@ -403,7 +391,7 @@ QVariantMap ReceiptsPlugin::receiptTablesData(int card_id) { qfLogFuncFrame() << card_id; QF_TIME_SCOPE("receiptTablesData()"); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QVariantMap ret; quickevent::core::si::ReadCard read_card = getPlugin()->readCard(card_id); quickevent::core::si::CheckedCard checked_card = getPlugin()->checkCard(read_card); diff --git a/quickevent/app/quickevent/plugins/Receipts/src/receiptssettingspage.cpp b/quickevent/app/quickevent/plugins/Receipts/src/receiptssettingspage.cpp index 57300219f..7f8f97174 100644 --- a/quickevent/app/quickevent/plugins/Receipts/src/receiptssettingspage.cpp +++ b/quickevent/app/quickevent/plugins/Receipts/src/receiptssettingspage.cpp @@ -389,21 +389,16 @@ void ReceiptsSettingsPage::load() } auto *event_plugin = qf::gui::framework::getPlugin(); Q_ASSERT(event_plugin); - auto *event_config = event_plugin->eventConfig(); - Q_ASSERT(event_config); - m_stageId = qMax(event_config->currentStageId(), 1); - ui->chkPrintReceiptQrCode->setChecked(event_config->value(eventConfigKey(QStringLiteral("receiptPrintEventQrCode")), false).toBool()); - ui->edReceiptQrCodeBaseUrl->setText(event_config->value(eventConfigKey(QStringLiteral("receiptEventLinkUrl"))).toString().trimmed()); - ui->edReceiptQrCodeCaption->setText(event_config->value(eventConfigKey(QStringLiteral("receiptPrintEventQrCodeCaption")), defaultReceiptQrCodeCaption()).toString().trimmed()); - ui->chkPrintReceiptImage->setChecked(event_config->value(eventConfigKey(QStringLiteral("receiptPrintEventImage")), false).toBool()); - int image_height_mm = event_config->value(eventConfigKey(QStringLiteral("receiptImageHeightMm")), 18).toInt(); - if(image_height_mm < 10) - image_height_mm = 10; - else if(image_height_mm > 60) - image_height_mm = 60; - ui->edReceiptImageHeight->setValue(image_height_mm); - m_receiptImageBase64 = event_config->value(eventConfigKey(QStringLiteral("receiptImageDataBase64"))).toString(); - m_receiptImageFormat = event_config->value(eventConfigKey(QStringLiteral("receiptImageFormat"))).toString().trimmed().toLower(); + auto &event_config = event_plugin->appDbConfig(); + m_stageId = qMax(event_config.eventConfig().currentStageId, 1); + const auto &rc = event_config.receiptsConfig(m_stageId); + ui->chkPrintReceiptQrCode->setChecked(rc.printQrCode); + ui->edReceiptQrCodeBaseUrl->setText(rc.linkUrl); + ui->edReceiptQrCodeCaption->setText(rc.qrCodeCaption); + ui->chkPrintReceiptImage->setChecked(rc.printImage); + ui->edReceiptImageHeight->setValue(rc.imageHeightMm); + m_receiptImageBase64 = rc.imageBase64; + m_receiptImageFormat = rc.imageFormat; if(m_receiptImageBase64.isEmpty()) { m_receiptImageFormat.clear(); ui->edReceiptImageFile->clear(); @@ -427,16 +422,16 @@ void ReceiptsSettingsPage::save() auto *event_plugin = qf::gui::framework::getPlugin(); Q_ASSERT(event_plugin); - auto *event_config = event_plugin->eventConfig(); - Q_ASSERT(event_config); - event_config->setValue(eventConfigKey(QStringLiteral("receiptPrintEventQrCode")), ui->chkPrintReceiptQrCode->isChecked()); - event_config->setValue(eventConfigKey(QStringLiteral("receiptEventLinkUrl")), ui->edReceiptQrCodeBaseUrl->text().trimmed()); - event_config->setValue(eventConfigKey(QStringLiteral("receiptPrintEventQrCodeCaption")), ui->edReceiptQrCodeCaption->text().trimmed()); - event_config->setValue(eventConfigKey(QStringLiteral("receiptPrintEventImage")), ui->chkPrintReceiptImage->isChecked()); - event_config->setValue(eventConfigKey(QStringLiteral("receiptImageHeightMm")), ui->edReceiptImageHeight->value()); - event_config->setValue(eventConfigKey(QStringLiteral("receiptImageDataBase64")), m_receiptImageBase64); - event_config->setValue(eventConfigKey(QStringLiteral("receiptImageFormat")), m_receiptImageFormat); - event_config->save(QStringLiteral("event")); + auto &event_config = event_plugin->appDbConfig(); + ReceiptsConfig rc; + rc.printQrCode = ui->chkPrintReceiptQrCode->isChecked(); + rc.linkUrl = ui->edReceiptQrCodeBaseUrl->text().trimmed(); + rc.qrCodeCaption = ui->edReceiptQrCodeCaption->text().trimmed(); + rc.printImage = ui->chkPrintReceiptImage->isChecked(); + rc.imageHeightMm = ui->edReceiptImageHeight->value(); + rc.imageBase64 = m_receiptImageBase64; + rc.imageFormat = m_receiptImageFormat; + event_config.setReceiptsConfig(m_stageId, rc); } QVariantMap ReceiptsSettingsPage::currentTestReceiptData() const @@ -445,8 +440,7 @@ QVariantMap ReceiptsSettingsPage::currentTestReceiptData() const auto *event_plugin = qf::gui::framework::getPlugin(); Q_ASSERT(event_plugin); - auto *event_config = event_plugin->eventConfig(); - Q_ASSERT(event_config); + auto &event_config = event_plugin->appDbConfig(); const int stage_id = qMax(m_stageId, 1); const bool print_logo = ui->chkPrintReceiptImage->isChecked(); @@ -514,7 +508,7 @@ QVariantMap ReceiptsSettingsPage::currentTestReceiptData() const int stage_start_time_ms = event_plugin->stageStartMsec(stage_id); if(stage_start_time_ms <= 0) { - QTime tm = event_config->eventDateTime().time(); + QTime tm = event_config.eventConfig().time; stage_start_time_ms = tm.isValid() ? tm.msecsSinceStartOfDay() : (10 * 60 * 60 * 1000); } diff --git a/quickevent/app/quickevent/plugins/Receipts/src/receiptswidget.cpp b/quickevent/app/quickevent/plugins/Receipts/src/receiptswidget.cpp index 79bad5063..2ba315bbf 100644 --- a/quickevent/app/quickevent/plugins/Receipts/src/receiptswidget.cpp +++ b/quickevent/app/quickevent/plugins/Receipts/src/receiptswidget.cpp @@ -117,7 +117,7 @@ void ReceiptsWidget::reset() void ReceiptsWidget::reload() { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); int current_stage = currentStageId(); qfs::QueryBuilder qb; qb.select2("cards", "id, siId, printerConnectionId") diff --git a/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp b/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp index a34241215..463e1450b 100644 --- a/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp +++ b/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp @@ -26,6 +26,7 @@ #include #include +#include #include @@ -80,8 +81,7 @@ void RelaysPlugin::onInstalled() return; auto *ep = getPlugin(); bool event_open = ep->isEventOpen(); - auto *cfg = event_open ? ep->eventConfig() : nullptr; - bool is_relays = cfg && cfg->isRelays(); + bool is_relays = event_open && ep->appDbConfig().eventConfig().isRelays(); scw->setPartVisible(featureId(), is_relays); if(event_open && !is_relays && m_partWidget && m_partWidget->isActive()) scw->setActivePart(QStringLiteral("Runs"), true); @@ -188,7 +188,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsResultsTable(const QString &where_ { qfLogFuncFrame() << "leg cnt:" << leg_count; qf::core::utils::TreeTable tt; - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(1)); tt.appendColumn("className", QMetaType(QMetaType::QString)); qfs::QueryBuilder qb; @@ -220,7 +220,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsResultsTable(const QString &where_ qfDebug() << "nLegsResultsTable table:" << wt(); return tt; } - +namespace { QPair getClubFromName(QString name) { qf::core::sql::Query q; @@ -229,7 +229,7 @@ QPair getClubFromName(QString name) return qMakePair(q.value(0).toInt(), q.value(1).toString()); return qMakePair(0,""); } - +} qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, int leg_count, int max_places, bool exclude_not_finish) { @@ -248,8 +248,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in qfError() << "Leg count not defined for class id:" << class_id; return qf::core::utils::TreeTable(); } - if(leg_count > max_leg) - leg_count = max_leg; + leg_count = std::min(leg_count, max_leg); QList relays; //QStringList relay_ids; @@ -513,7 +512,7 @@ QVariant RelaysPlugin::startListByClassesTableData(const QString &class_filter, //console.info("currentStageTable query:", reportModel.effectiveQuery()); model.reload(); qf::core::utils::TreeTable tt = model.toTreeTable(); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(1)); { qf::core::sql::QueryBuilder qb; @@ -579,8 +578,8 @@ QString RelaysPlugin::resultsIofXml30() { QDateTime start00 = getPlugin()->stageStartDateTime(1); qfDebug() << "creating table"; - Event::EventConfig *event_config = getPlugin()->eventConfig(); - bool is_iof_race = event_config->isIofRace(); + const auto &event_config = getPlugin()->eventConfig(); + bool is_iof_race = event_config.iofRace; //auto tt_classes = getPlugin()->nLegsResultsTable("classes.name='D105'", 999, 999999, false); auto tt_classes = getPlugin()->nLegsResultsTable(QString(), 999, 999999, false); QVariantList result_list{ @@ -863,8 +862,8 @@ QString RelaysPlugin::startListIofXml30() { QDateTime start00 = getPlugin()->stageStartDateTime(1); qfDebug() << "creating table"; - Event::EventConfig *event_config = getPlugin()->eventConfig(); - bool is_iof_race = event_config->isIofRace(); + const auto &event_config = getPlugin()->eventConfig(); + bool is_iof_race = event_config.iofRace; qf::core::utils::TreeTable tt_classes = startListByClassesTableData(QString(), false); QVariantList start_list{ "StartList", diff --git a/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp b/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp index f128fb23e..61b90d3f1 100644 --- a/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp +++ b/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp @@ -392,7 +392,7 @@ QVariant RelaysWidget::startListByClubsTableData(bool with_vacants) //console.info("currentStageTable query:", reportModel.effectiveQuery()); model.reload(); qf::core::utils::TreeTable tt = model.toTreeTable(); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(1)); { qf::core::sql::QueryBuilder qb; diff --git a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple.qml b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple.qml index e80bdac46..d6191578d 100644 --- a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple.qml +++ b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple.qml @@ -114,7 +114,7 @@ Report { halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold //text: root.eventConfig.event.mainReferee - text: root.eventConfig.mainReferee() + text: root.eventConfig.mainReferee } Para { width: "%" @@ -139,7 +139,7 @@ Report { width: "%" halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold - text: root.eventConfig.director() + text: root.eventConfig.director } Para { width: "%" @@ -158,5 +158,3 @@ Report { } } } - - diff --git a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple2.qml b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple2.qml index 5247d40b3..db6b6be86 100644 --- a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple2.qml +++ b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-apple2.qml @@ -116,7 +116,7 @@ Report { halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold //text: root.eventConfig.event.mainReferee - text: root.eventConfig.mainReferee() + text: root.eventConfig.mainReferee } Para { width: "%" @@ -141,7 +141,7 @@ Report { width: "%" halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold - text: root.eventConfig.director() + text: root.eventConfig.director } Para { width: "%" @@ -161,5 +161,3 @@ Report { } } } - - diff --git a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-covid.qml b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-covid.qml index 25da2d8cc..4e0f1d00e 100644 --- a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-covid.qml +++ b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-covid.qml @@ -137,7 +137,7 @@ Report { halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold //text: root.eventConfig.event.mainReferee - text: root.eventConfig.mainReferee() + text: root.eventConfig.mainReferee } Para { width: "%" @@ -163,7 +163,7 @@ Report { width: "%" halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold - text: root.eventConfig.director() + text: root.eventConfig.director } Para { width: "%" @@ -183,5 +183,3 @@ Report { } } } - - diff --git a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-hsh.qml b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-hsh.qml index 33e45d9fe..e6a85f9d8 100644 --- a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-hsh.qml +++ b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards-hsh.qml @@ -127,7 +127,7 @@ Report { halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold //text: root.eventConfig.event.mainReferee - text: root.eventConfig.mainReferee() + text: root.eventConfig.mainReferee } Para { width: "%" @@ -152,7 +152,7 @@ Report { width: "%" halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold - text: root.eventConfig.director() + text: root.eventConfig.director } Para { width: "%" diff --git a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards.qml b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards.qml index ff02358c1..e61e125a3 100644 --- a/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards.qml +++ b/quickevent/app/quickevent/plugins/Runs/qml/reports/awards/results_stage_awards.qml @@ -114,7 +114,7 @@ Report { halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold //text: root.eventConfig.event.mainReferee - text: root.eventConfig.mainReferee() + text: root.eventConfig.mainReferee } Para { width: "%" @@ -139,7 +139,7 @@ Report { width: "%" halign: Frame.AlignHCenter textStyle: myStyle.textStyleBold - text: root.eventConfig.director() + text: root.eventConfig.director } Para { width: "%" @@ -158,5 +158,3 @@ Report { } } } - - diff --git a/quickevent/app/quickevent/plugins/Runs/src/printawardsoptionsdialogwidget.h b/quickevent/app/quickevent/plugins/Runs/src/printawardsoptionsdialogwidget.h index aa2796ccd..4de289604 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/printawardsoptionsdialogwidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/printawardsoptionsdialogwidget.h @@ -15,7 +15,7 @@ class PrintAwardsOptionsDialogWidget : public qf::gui::framework::DialogWidget private: using Super = qf::gui::framework::DialogWidget; public: - explicit PrintAwardsOptionsDialogWidget(QWidget *parent = 0); + explicit PrintAwardsOptionsDialogWidget(QWidget *parent = nullptr); ~PrintAwardsOptionsDialogWidget(); QVariantMap printOptions() const; diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index b5e3ccbe7..9fb5808e0 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -11,7 +11,7 @@ // #include "../../Competitors/src/competitorwidget.h" #include "../../CardReader/src/cardreaderplugin.h" #include "../../Event/src/eventplugin.h" -#include "../../Event/src/services/qx/qxlateregistrationswidget.h" +#include "../../Event/src/services/qx/qxlateentrieswidget.h" #include #include @@ -43,6 +43,7 @@ #include #include +#include #include namespace qff = qf::gui::framework; @@ -143,15 +144,15 @@ void RunsPlugin::onInstalled() } { auto *dw = new qff::DockWidget(nullptr); - dw->setObjectName("qxLateRegistrationsDockWidget"); - dw->setPersistentSettingsId("Runs/qxLateRegistrations"); - dw->setWindowTitle(tr("Late registrations")); - auto *ew = new Event::services::qx::QxLateRegistrationsWidget(); + dw->setObjectName("qxLateEntriesDockWidget"); + dw->setPersistentSettingsId("Runs/qxLateEntries"); + dw->setWindowTitle(tr("Late entries")); + auto *ew = new Event::services::qx::QxLateEntriesWidget(); dw->setWidget(ew); fwk->addDockWidget(Qt::RightDockWidgetArea, dw); dw->hide(); - connect(dw, &qff::DockWidget::visibilityChanged, ew, &Event::services::qx::QxLateRegistrationsWidget::onVisibleChanged); + connect(dw, &qff::DockWidget::visibilityChanged, ew, &Event::services::qx::QxLateEntriesWidget::onVisibleChanged); auto *a = dw->toggleViewAction(); // a->setShortcut(QKeySequence("ctrl+shift+E")); @@ -168,7 +169,7 @@ int RunsPlugin::courseForRun(int run_id) { // TODO: implementation should be dependend on event type and exposed to QML // see: CardCheck pattern - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if(is_relays) { return courseForRun_Relays(run_id); } @@ -758,7 +759,7 @@ qf::core::utils::TreeTable RunsPlugin::stageResultsTable(int stage_id, const QSt model.reload(); qf::core::utils::TreeTable tt = model.toTreeTable(); tt.setValue("stageId", stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); { @@ -959,9 +960,9 @@ QString RunsPlugin::resultsIofXml30Stage(int stage_id) { QDateTime stage_start_date_time = getPlugin()->stageStartDateTime(stage_id);//.toTimeSpec(Qt::OffsetFromUTC); qf::core::utils::TreeTable tt1 = stageResultsTable(stage_id, QString(), 0, false, true); - Event::EventConfig *event_config = getPlugin()->eventConfig(); - bool is_iof_race = event_config->isIofRace(); - int iof_xml_race_number = event_config->iofXmlRaceNumber(); + const auto &event_config = getPlugin()->eventConfig(); + bool is_iof_race = event_config.iofRace; + int iof_xml_race_number = event_config.iofXmlRaceNumber; QVariantList result_list{ "ResultList", @@ -1009,7 +1010,7 @@ QString RunsPlugin::resultsIofXml30Stage(int stage_id) event_lst.insert(event_lst.count(), QVariantList{"Race", QVariantList{"RaceNumber", iof_xml_race_number}, - QVariantList{"Name", event_config->eventName()} + QVariantList{"Name", event_config.name} } ); } @@ -1178,17 +1179,17 @@ void RunsPlugin::writeCSOSHeader(QTextStream &ts) const auto *evp = getPlugin(); int stage_id = selectedStageId(); QDateTime start_dt = evp->stageStartDateTime(stage_id); - Event::EventConfig *ec = evp->eventConfig(); + const auto &ec = evp->appDbConfig().eventConfig(); static constexpr int HWIDTH = -19; - ts << make_width("Kod zavodu", HWIDTH) << ": " << ec->importId() << "\r\n"; - ts << make_width("Nazev zavodu", HWIDTH) << ": " << ec->eventName() << "\r\n"; + ts << make_width("Kod zavodu", HWIDTH) << ": " << ec.importId << "\r\n"; + ts << make_width("Nazev zavodu", HWIDTH) << ": " << ec.name << "\r\n"; ts << make_width("Zarazeni do soutezi", HWIDTH) << ": " << "" << "\r\n"; ts << make_width("Datum konani", HWIDTH) << ": " << start_dt.toString(Qt::ISODate) << "\r\n"; - ts << make_width("Misto konani", HWIDTH) << ": " << ec->eventPlace() << "\r\n"; + ts << make_width("Misto konani", HWIDTH) << ": " << ec.place << "\r\n"; ts << make_width("Poradatel", HWIDTH) << ": " << "" << "\r\n"; ts << make_width("Mapa", HWIDTH) << ": " << "" << "\r\n"; - ts << make_width("Reditel zavodu", HWIDTH) << ": " << ec->director() << "\r\n"; - ts << make_width("Hlavni rozhodci", HWIDTH) << ": " << ec->mainReferee() << "\r\n"; + ts << make_width("Reditel zavodu", HWIDTH) << ": " << ec.director << "\r\n"; + ts << make_width("Hlavni rozhodci", HWIDTH) << ": " << ec.mainReferee << "\r\n"; ts << make_width("Stavitel trati", HWIDTH) << ": " << "" << "\r\n"; ts << make_width("JURY", HWIDTH) << ": " << "" << "\r\n"; ts << make_width("Protokol", HWIDTH) << ": " << "\r\n"; @@ -1294,11 +1295,11 @@ bool RunsPlugin::exportResultsCsosOverall(int stage_count, const QString &file_n qf::core::sql::QueryBuilder RunsPlugin::runsQuery(int stage_id, int class_id, bool show_offrace) { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); qfs::QueryBuilder qb; qb.select2("runs", "*") .select2("classes", "name") - .select2("competitors", "id, iofId, registration, licence, ranking, siId, note") + .select2("competitors", "id, firstName, lastName, iofId, registration, licence, ranking, siId, note") .select("COALESCE(lastName, '') || ' ' || COALESCE(firstName, '') AS competitorName") .select("lentcards.siid IS NOT NULL AS cardInLentTable") @@ -1428,7 +1429,7 @@ qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &wher m.reload(); auto tt = m.toTreeTable(); tt.setValue("stageId", stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); tt.appendColumn("courses.numberOfControls", QMetaType(QMetaType::Int)); tt.appendColumn("courses.startNumber", QMetaType(QMetaType::Int)); @@ -1527,9 +1528,7 @@ qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &wher int mapCount = tt_row.value(QStringLiteral("mapCount")).toInt(); int cnt = tt2.rowCount(); int total_vacants = mapCount - cnt; - if (total_vacants < 0) { - total_vacants = 0; - } + total_vacants = std::max(total_vacants, 0); for (int k = 0; k < total_vacants; ++k) { int ix = tt2.appendRow(); qf::core::utils::TreeTableRow tt2_row = tt2.row(ix); @@ -1564,7 +1563,7 @@ qf::core::utils::TreeTable RunsPlugin::startListClubsTable(const quickevent::gui m.reload(); auto tt = m.toTreeTable(); tt.setValue("stageId", stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); { qf::core::utils::TreeTableColumn c = tt.column(0); @@ -1633,7 +1632,7 @@ qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &whe qf::core::utils::TreeTable tt; tt.setValue("stageId", stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); tt.appendColumn("competitors.registration", QMetaType(QMetaType::QString)); @@ -1658,7 +1657,7 @@ qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &whe for(int j=0; j()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(sel_stage_id)); QVector start00_epoch_sec; for(int stage_id = 1; stage_id <= stages_count; stage_id++) { @@ -1781,7 +1780,7 @@ qf::core::utils::TreeTable RunsPlugin::startListClubsNStagesTable(const int stag m.reload(); auto tt = m.toTreeTable(); tt.setValue("stageId", sel_stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); tt.setValue("stageStart", getPlugin()->stageStartDateTime(sel_stage_id)); { qf::core::utils::TreeTableColumn c = tt.column(0); @@ -2046,7 +2045,7 @@ void RunsPlugin::report_resultsAwards() return; QVariantMap props; - props["eventConfig"] = QVariant::fromValue(getPlugin()->eventConfig()); + props["eventConfig"] = getPlugin()->eventConfig().toVariantMap(); auto tt = stageResultsTable(opts.value("stageId").toInt(), QString(), opts.value("numPlaces").toInt()); qf::gui::reports::ReportViewWidget::showReport(fwk , findReportFile(rep_path) @@ -2070,13 +2069,13 @@ void RunsPlugin::report_resultsNStages() return; auto opts = dlg.options(); auto tt = nstagesResultsTable(dlg.sqlWhereExpression(), dlg.stagesCount(), opts.resultNumPlaces(), opts.isResultExcludeDisq()); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); //tt.setValue("stageStart", getPlugin()->stageStartDateTime(stages_count)); QVariantMap props; props["stagesCount"] = dlg.stagesCount(); props["options"] = opts; qf::gui::reports::ReportViewWidget::showReport(fwk - , findReportFile("results_nstages.qml") + , findReportFile("results_nstages.qml") , tt.toVariant() , tr("Results after %n stage(s)", "", dlg.stagesCount()) , "printResultsNStages" @@ -2097,13 +2096,13 @@ void RunsPlugin::report_resultsNStagesSpeaker() return; auto opts = dlg.options(); auto tt = nstagesResultsTable(dlg.sqlWhereExpression(), dlg.stagesCount(), opts.resultNumPlaces(), opts.isResultExcludeDisq()); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); //tt.setValue("stageStart", getPlugin()->stageStartDateTime(stages_count)); QVariantMap props; props["stagesCount"] = dlg.stagesCount(); props["options"] = opts; qf::gui::reports::ReportViewWidget::showReport(fwk - , findReportFile("results_nstagesSpeaker.qml") + , findReportFile("results_nstagesSpeaker.qml") , tt.toVariant() , tr("Results after %n stage(s)", "", dlg.stagesCount()) , "printResultsNStagesWide" @@ -2122,7 +2121,7 @@ void RunsPlugin::report_nStagesAwards() return; QVariantMap props; - props["eventConfig"] = QVariant::fromValue(getPlugin()->eventConfig()); + props["eventConfig"] = getPlugin()->eventConfig().toVariantMap(); auto tt = nstagesResultsTable(QString(), opts.value("stageId").toInt(), opts.value("numPlaces").toInt()); qf::gui::reports::ReportViewWidget::showReport(fwk , findReportFile(rep_path) @@ -2473,7 +2472,7 @@ void RunsPlugin::export_resultsHtmlNStages() qff::MainWindow *fwk = qff::MainWindow::frameWork(); auto tt1 = nstagesResultsTable(sql_where_expr, stage_id, num_places, exclude_disq); - tt1.setValue("event", getPlugin()->eventConfig()->value("event")); + tt1.setValue("event", getPlugin()->eventConfig().toVariantMap()); //tt.setValue("stageStart", getPlugin()->stageStartDateTime(stages_count)); QString file_dir = QDir::tempPath() + "/quickevent/overall-e" + QString::number(stage_id); @@ -2738,11 +2737,11 @@ QString RunsPlugin::getClubAbbrFromName(QString name) QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) { QDateTime start00 = getPlugin()->stageStartDateTime(stage_id); - Event::EventConfig *event_config = getPlugin()->eventConfig(); + const auto &event_config = getPlugin()->eventConfig(); //console.debug("print_vacants", print_vacants); auto tt1 = startListClassesTable("", vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart); - bool is_iof_race = event_config->isIofRace(); - int iof_xml_race_number = event_config->iofXmlRaceNumber(); + bool is_iof_race = event_config.iofRace; + int iof_xml_race_number = event_config.iofXmlRaceNumber; QVariantList xml_root{"StartList" , QVariantMap { @@ -2756,7 +2755,7 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::Report QVariantMap event = tt1.value("event").toMap(); QVariantList xml_event{"Event"}; append_list(xml_event, QVariantList{"Id", QVariantMap{{"type", "ORIS"}}, event.value("importId")}); - append_list(xml_event, QVariantList{"Name", event_config->eventName()}); + append_list(xml_event, QVariantList{"Name", event_config.name}); append_list(xml_event, QVariantList{"StartTime", QVariantList{"Date", start00.date().toString(Qt::ISODate)}, @@ -2786,11 +2785,11 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::Report } ); - if (iof_xml_race_number != 0) { - append_list(xml_event, + if(iof_xml_race_number != 0) { + xml_event.insert(xml_event.count(), QVariantList{"Race", QVariantList{"RaceNumber", iof_xml_race_number}, - QVariantList{"Name", event_config->eventName()} + QVariantList{"Name", event_config.name} } ); } @@ -2814,9 +2813,9 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::Report for(int j=0; j()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); m_classDef.load(m_highlightedClassId, m_stageId, is_relays); //qfInfo() << "interval:" << m_classInterval << "first:" << m_classStartFirst << "last:" << m_classStartLast; } @@ -50,7 +50,7 @@ void RunsTableItemDelegate::paintBackground(QPainter *painter, const QStyleOptio auto *tm = qobject_cast(v->tableModel()); if(!(m && tm)) return; - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if(m_highlightedClassId > 0) { if(is_relays) { // check that start time in classes is the same diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index 858272e09..e9c2fa810 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -1,13 +1,15 @@ #include "runstablemodel.h" +#include "../../Event/src/eventplugin.h" + #include #include -#include "../../Event/src/eventplugin.h" #include #include #include +#include #include #include #include @@ -50,7 +52,7 @@ RunsTableModel::RunsTableModel(QObject *parent) setColumn(col_competitors_note, ColumnDefinition("competitors.note", tr("Note"))); connect(this, &RunsTableModel::dataChanged, this, &RunsTableModel::onDataChanged, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &RunsTableModel::onQxRecChng, Qt::QueuedConnection); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &RunsTableModel::onQxRecChng, Qt::QueuedConnection); } Qt::ItemFlags RunsTableModel::flags(const QModelIndex &index) const @@ -62,7 +64,7 @@ Qt::ItemFlags RunsTableModel::flags(const QModelIndex &index) const //qfInfo() << flgs; } if(index.column() == col_course_id) { - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->appDbConfig().eventConfig().isRelays()) { flgs &= ~Qt::ItemIsEditable; } } @@ -72,7 +74,7 @@ Qt::ItemFlags RunsTableModel::flags(const QModelIndex &index) const QVariant RunsTableModel::data(const QModelIndex &index, int role) const { if(index.column() == col_course_id && role == Qt::DisplayRole) { - if (getPlugin()->eventConfig()->isRelays()) { + if (getPlugin()->appDbConfig().eventConfig().isRelays()) { auto start_number = value(index.row(), "startNumber").toInt(); auto leg = value(index.row(), "runs.leg").toInt(); return QStringLiteral("%1.%2").arg(start_number).arg(leg); @@ -101,6 +103,12 @@ QVariant RunsTableModel::data(const QModelIndex &index, int role) const QVariant RunsTableModel::value(int row_ix, int column_ix) const { + if(column_ix == col_competitorName) { + qf::core::utils::TableRow row = tableRow(row_ix); + auto first_name = row.value(QStringLiteral("firstName")).toString(); + auto last_name = row.value(QStringLiteral("lastName")).toString(); + return last_name + ' ' + first_name; + } if(column_ix == col_runFlags) { qf::core::utils::TableRow row = tableRow(row_ix); bool mis_punch = row.value(QStringLiteral("runs.misPunch")).toBool(); @@ -355,7 +363,11 @@ void RunsTableModel::onDataChanged(const QModelIndex &top_left, const QModelInde void RunsTableModel::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { - handleQxRecChng(recchng, source); + if (recchng.op == qf::core::sql::RecOp::Insert) { + // inserts might be in filterred out class or stage, this cannot be solved by applyQxRecChng + return; + } + applyQxRecChng(recchng, source); } bool RunsTableModel::postRow(int row_no, bool throw_exc) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h index ed54e71b6..08bc5a67c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h @@ -1,5 +1,4 @@ -#ifndef RUNSTABLEMODEL_H -#define RUNSTABLEMODEL_H +#pragma once #include @@ -67,4 +66,3 @@ class RunsTableModel : public quickevent::gui::og::SqlTableModel void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); }; -#endif // RUNSTABLEMODEL_H diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index c40d4b517..66b65dc84 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -8,9 +8,9 @@ #include "cardflagsdialog.h" #include -#include #include #include +#include #include #include @@ -20,6 +20,8 @@ #include #include +#include + #include #include #include @@ -58,7 +60,7 @@ RunsTableWidget::RunsTableWidget(QWidget *parent) : ui->tblRuns->setItemDelegate(m_runsTableItemDelegate); auto *event_plugin = getPlugin(); connect(event_plugin, &EventPlugin::eventOpenChanged, this, [this, event_plugin](bool is_open) { - if (is_open && !event_plugin->eventConfig()->isRelays() && !m_courseItemDelegate) { + if (is_open && !event_plugin->appDbConfig().eventConfig().isRelays() && !m_courseItemDelegate) { m_courseItemDelegate = new CourseItemDelegate(ui->tblRuns); m_courseItemDelegate->setNullText(tr("Implicit")); ui->tblRuns->setItemDelegateForColumn(RunsTableModel::col_course_id, m_courseItemDelegate); @@ -173,7 +175,7 @@ void RunsTableWidget::reload(int stage_id, int class_id, bool show_offrace, cons ui->lblClassStart->setText(class_start_time_min >= 0? QString::number(class_start_time_min): "---"); ui->lblClassInterval->setText(class_start_interval_min >= 0? QString::number(class_start_interval_min): "---"); } - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if (!is_relays && m_courseItemDelegate) { m_courseItemDelegate->setCourses(definedCourses()); } @@ -433,4 +435,3 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) qf::gui::dialogs::MessageBox::showError(this, message); } - diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h index 8ba2e0964..263ddbd5c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h @@ -7,6 +7,7 @@ class RunsTableItemDelegate; class CourseItemDelegate; namespace qf::gui { class TableView; } +namespace qf::core::sql { struct QxRecChng; } namespace Ui { class RunsTableWidget; diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp index 53793196f..4eec22b5b 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp @@ -73,7 +73,7 @@ RunsWidget::RunsWidget(QWidget *parent) : ui->cbxDrawMethod->clear(); if(!getPlugin()->isEventOpen()) return; - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if(is_relays) { ui->cbxDrawMethod->addItem(tr("Relays first leg"), static_cast(DrawMethod::RelaysFirstLeg)); } @@ -119,7 +119,7 @@ void RunsWidget::reset() } /// Note: You should use QAction::setVisible() to change the visibility of the widget. /// Using QWidget::setVisible(), QWidget::show() and QWidget::hide() does not work. - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); m_toolbarActionLabelLeg->setVisible(is_relays); m_toolbarActionComboLeg->setVisible(is_relays); //qfWarning() << "is relays:" << is_relays << "legs visible:" << m_cbxLeg->isVisible(); @@ -131,7 +131,7 @@ void RunsWidget::reset() void RunsWidget::reload() { qfLogFuncFrame(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); int stage_id = is_relays? m_cbxLeg->currentData().toInt(): selectedStageId(); int class_id = m_cbxClasses->currentData().toInt(); ui->wRunsTableWidget->reload(stage_id, class_id, m_chkShowOffRace->isChecked()); @@ -425,8 +425,7 @@ void RunsWidget::settleDownInPartWidget(::PartWidget *part_widget) auto updateMenuEnabled = [part_widget]() { auto *ep = getPlugin(); - auto *cfg = ep->isEventOpen() ? ep->eventConfig() : nullptr; - bool enabled = !(cfg && cfg->isRelays()); + bool enabled = !(ep->isEventOpen() && ep->appDbConfig().eventConfig().isRelays()); for(const char *path : {"print", "import", "export"}) { if(auto *a = part_widget->menuBar()->actionForPath(path)) { a->setEnabled(enabled); @@ -508,7 +507,7 @@ QList RunsWidget::runsForClass(int stage_id, int class_id, const QString &e QList RunsWidget::competitorsForClass(int stage_id, int class_id, const QString &extra_where_condition, const QString &order_by) { qfLogFuncFrame() << "stage:" << stage_id << "class:" << class_id; - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); QList ret; qf::core::sql::QueryBuilder qb; qb.select2("runs", "id, competitorId") @@ -690,11 +689,11 @@ void RunsWidget::saveLockedForDrawing(int class_id, int stage_id, bool is_locked void RunsWidget::onDrawClicked() { qfLogFuncFrame(); - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); int stage_id = selectedStageId(); DrawMethod draw_method = DrawMethod(ui->cbxDrawMethod->currentData().toInt()); - Event::StageData stage_data = getPlugin()->stageData(stage_id); - bool use_all_maps = stage_data.isUseAllMaps(); + const auto &stage_data = getPlugin()->stageConfig(stage_id); + bool use_all_maps = stage_data.useAllMaps; qfDebug() << "DrawMethod:" << (int)draw_method << "use_all_maps:" << use_all_maps; QList class_ids; int class_id = m_cbxClasses->currentData().toInt(); @@ -724,7 +723,7 @@ void RunsWidget::onDrawClicked() try { qf::core::sql::Transaction transaction; for(int class_id : class_ids) { - int handicap_length_ms = getPlugin()->eventConfig()->handicapLength() * 60 * 1000; + int handicap_length_ms = getPlugin()->eventConfig().handicapLength * 60 * 1000; QVector handicap_times; qf::core::sql::QueryBuilder qb; qb.select2("classdefs", "startTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter, mapCount") @@ -772,7 +771,7 @@ void RunsWidget::onDrawClicked() runners_draw_ids = group1 + group2 + group3; } else if(draw_method == DrawMethod::Handicap) { - int stage_count = getPlugin()->eventConfig()->stageCount(); + int stage_count = getPlugin()->eventConfig().stageCount; qf::core::utils::Table results = getPlugin()->nstagesClassResultsTable(stage_count - 1, class_id); QMap competitor_to_run; for(const auto &cc : competitorsForClass(stage_count, class_id)) @@ -1057,7 +1056,7 @@ void RunsWidget::export_results_stage_csv() if(fn.isEmpty()) return; - bool is_iof_race = getPlugin()->eventConfig()->isIofRace(); + bool is_iof_race = getPlugin()->eventConfig().iofRace; quickevent::core::exporters::StageResultsCsvExporter exp(is_iof_race); QFileInfo fi(fn); exp.setOutDir(fi.absolutePath()); @@ -1140,7 +1139,7 @@ void RunsWidget::report_competitorsStatistics() m.setQueryBuilder(qb); m.reload(); qfu::TreeTable tt = m.toTreeTable(); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("event", getPlugin()->eventConfig().toVariantMap()); for (int stage_id = 1; stage_id <= stage_cnt; ++stage_id) { QString prefix = "e" + QString::number(stage_id) + "_"; QString col_runs_count = prefix + "runCount"; @@ -1210,7 +1209,7 @@ void RunsWidget::editCompetitor_helper(const QVariant &id, int mode, int siid) QF_ASSERT(doc != nullptr, "Document is null!", return); if(mode == qfm::DataDocument::ModeInsert) { if(siid == 0) { - bool is_relays = getPlugin()->eventConfig()->isRelays(); + bool is_relays = getPlugin()->eventConfig().isRelays(); if(!is_relays) { int class_id = m_cbxClasses->currentData().toInt(); if(class_id > 0) @@ -1243,6 +1242,18 @@ void RunsWidget::editCompetitor_helper(const QVariant &id, int mode, int siid) } } +void RunsWidget::showEditCompetitorDialog(int competitor_id, QWidget *parent) +{ + auto *w = new CompetitorWidget(); + w->setWindowTitle(tr("Edit Competitor")); + qfd::Dialog dlg(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, parent); + dlg.setDefaultButton(QDialogButtonBox::Ok); + + dlg.setCentralWidget(w); + w->load(competitor_id, qf::gui::model::DataDocument::ModeEdit); + dlg.exec(); +} + void RunsWidget::updateClassComboBox() { m_cbxClasses->blockSignals(true); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h index 6e882180b..df6e7fd9b 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h @@ -13,19 +13,11 @@ class QLabel; class QToolButton; namespace qf { -namespace core { -namespace model { -class SqlTableModel; -} -} -namespace gui { -class ForeignKeyComboBox; -} +namespace core { namespace model { class SqlTableModel; } } +namespace gui { class ForeignKeyComboBox; } } -namespace Event { -class EventPlugin; -} +namespace Event { class EventPlugin; } namespace Ui { class RunsWidget; @@ -59,6 +51,7 @@ class RunsWidget : public QFrame void export_results_stage_csv(); public: + static void showEditCompetitorDialog(int competitor_id, QWidget *parent); void editCompetitor(const QVariant &id, int mode) {editCompetitor_helper(id, mode, 0);} void editCompetitors(int mode); void editCompetitorOnPunch(int siid); @@ -71,7 +64,6 @@ class RunsWidget : public QFrame void onDrawRemoveClicked(); void onCbxStageCurrentIndexChanged(); private: - /** * @brief runnersInClubsHistogram * @return list of runs.id for each club sorted by their count, longest list of runners is first diff --git a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp index 5f006eac1..99b1e6a8b 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp @@ -72,8 +72,8 @@ bool ResultsExporter::exportResults() const } if(ss.outputFormat() == static_cast(ResultsExporterSettings::OutputFormat::IofXml3)) { int current_stage = getPlugin()->currentStageId(); - QString file_name = ss.exportDir() + "/" + getPlugin()->eventName() + "." + Event::RESULTS_IOFXML3_FILE; - bool is_relays = getPlugin()->eventConfig()->isRelays(); + QString file_name = ss.exportDir() + "/" + getPlugin()->eventDbName() + "." + Event::RESULTS_IOFXML3_FILE; + bool is_relays = getPlugin()->eventConfig().isRelays(); QString str = is_relays ? getPlugin()->resultsIofXml30() @@ -94,7 +94,7 @@ bool ResultsExporter::exportResults() const return true; } if(ss.outputFormat() == static_cast(ResultsExporterSettings::OutputFormat::CSVMulti)) { - bool is_iof_race = getPlugin()->eventConfig()->isIofRace(); + bool is_iof_race = getPlugin()->eventConfig().iofRace; quickevent::core::exporters::StageResultsCsvExporter exp(is_iof_race); exp.setOutDir(ss.exportDir()); if (!ss.csvSeparator().isNull()) @@ -106,7 +106,7 @@ bool ResultsExporter::exportResults() const return true; } if(ss.outputFormat() == static_cast(ResultsExporterSettings::OutputFormat::CSV)) { - bool is_iof_race = getPlugin()->eventConfig()->isIofRace(); + bool is_iof_race = getPlugin()->eventConfig().iofRace; quickevent::core::exporters::StageResultsCsvExporter exp(is_iof_race); exp.setOutDir(ss.exportDir()); if (!ss.csvSeparator().isNull()) diff --git a/quickevent/app/quickevent/src/appversion.h b/quickevent/app/quickevent/src/appversion.h index d62b2834f..a08b78be1 100644 --- a/quickevent/app/quickevent/src/appversion.h +++ b/quickevent/app/quickevent/src/appversion.h @@ -1,4 +1,4 @@ #pragma once -#define APP_VERSION "3.5.8" +#define APP_VERSION "3.6.0" diff --git a/quickevent/app/quickevent/src/main.cpp b/quickevent/app/quickevent/src/main.cpp index 9d6040ced..04d873e64 100644 --- a/quickevent/app/quickevent/src/main.cpp +++ b/quickevent/app/quickevent/src/main.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -197,7 +198,8 @@ int main(int argc, char *argv[]) main_window.show(); emit main_window.applicationLaunched(); - QObject::connect(&app, &Application::qxRecChng, &app, [](const qf::core::sql::QxRecChng &recchng) { +#ifdef QT_DEBUG + QObject::connect(app.qxSql(), &qf::core::sql::QxSql::recChng, &app, [](const qf::core::sql::QxRecChng &recchng) { auto dump_map = [](const QVariantMap &m) { QStringList rows; for (const auto &[k, v] : m.asKeyValueRange()) { @@ -210,6 +212,7 @@ int main(int argc, char *argv[]) << "id:" << recchng.id << "record:" << dump_map(recchng.record); }); +#endif int ret = Application::exec(); diff --git a/quickevent/app/quickevent/src/partwidget.cpp b/quickevent/app/quickevent/src/partwidget.cpp index 7140fa89c..1d2accb73 100644 --- a/quickevent/app/quickevent/src/partwidget.cpp +++ b/quickevent/app/quickevent/src/partwidget.cpp @@ -10,13 +10,11 @@ PartWidget::PartWidget(const QString& title, const QString &feature_id, QWidget *parent) : Super(feature_id, parent) { - using namespace qf::gui::framework; - setPersistentSettingsId(featureId()); setTitle(title); connect(this, &PartWidget::activeChanged, this, &PartWidget::onActiveChanged); - auto *event_plugin = getPlugin(); + auto *event_plugin = qf::gui::framework::getPlugin(); connect(event_plugin, &Event::EventPlugin::currentStageIdChanged, this, &PartWidget::resetPartRequest); connect(event_plugin, &Event::EventPlugin::eventOpenChanged, this, &PartWidget::resetPartRequest); connect(event_plugin, &Event::EventPlugin::reloadDataRequest, this, &PartWidget::resetPartRequest); @@ -27,12 +25,11 @@ void PartWidget::onActiveChanged() if(isActive()) { qf::gui::framework::MainWindow *fwk = qf::gui::framework::MainWindow::frameWork(); QF_ASSERT(fwk != nullptr, "Invalid FrameWork", return); - qf::gui::framework::Plugin *event_plugin = fwk->plugin("Event", qf::core::Exception::Throw); - bool sql_connected = event_plugin->property("sqlServerConnected").toBool(); - QString event_name = event_plugin->property("eventName").toString(); + auto *event_plugin = qf::gui::framework::getPlugin(); + bool sql_connected = event_plugin->isSqlServerConnected(); + QString event_name = event_plugin->eventDbName(); if(sql_connected && !event_name.isEmpty()) { emit reloadPartRequest(); } } } -