[games/chessament] src: Update players ratings from rating list

Manuel Alcaraz Zambrano <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit fc084b50699a1d10ed12984548f6707c1d2507ec by Manuel Alcaraz Zambrano.
Committed on 28/07/2026 at 21:09.
Pushed by manuelal into branch 'master'.

Update players ratings from rating list

M  +1    -0    src/CMakeLists.txt
M  +15   -0    src/playersmodel.cpp
M  +3    -0    src/playersmodel.h
M  +9    -0    src/qml/PlayersPage.qml
A  +41   -0    src/qml/UpdateRatingsDialog.qml     [License: GPL(v3.0+)]
M  +74   -44   src/tournament/ratinglists/ratinglist.cpp
M  +9    -0    src/tournament/ratinglists/ratinglist.h
M  +22   -0    src/tournament/tournament.cpp
M  +2    -0    src/tournament/tournament.h

https://invent.kde.org/games/chessament/-/commit/fc084b50699a1d10ed12984548f6707c1d2507ec

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 95c89ee..cc8d068 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -35,6 +35,7 @@ ecm_add_qml_module(chessament_static
         qml/PairRoundDialog.qml
         qml/PlayersPage.qml
         qml/PlayersPageTableDelegate.qml
+        qml/UpdateRatingsDialog.qml
         qml/WelcomePage.qml
 
         # Player details
diff --git a/src/playersmodel.cpp b/src/playersmodel.cpp
index 432f79e..ada0b85 100644
--- a/src/playersmodel.cpp
+++ b/src/playersmodel.cpp
@@ -300,6 +300,21 @@ void PlayersModel::deletePlayer(const QModelIndex &index)
     endRemoveRows();
 }
 
+void PlayersModel::updateRatings(int listId)
+{
+    m_tournament->updateRatings(listId);
+
+    const int ratingColumn = static_cast<int>(m_columns.indexOf(PlayersModel::Columns::Rating));
+    if (ratingColumn >= 0) {
+        Q_EMIT dataChanged(index(0, ratingColumn), index(rowCount() - 1, ratingColumn));
+    }
+
+    const int nationalRatingColumn = static_cast<int>(m_columns.indexOf(PlayersModel::Columns::NationalRating));
+    if (nationalRatingColumn >= 0) {
+        Q_EMIT dataChanged(index(0, nationalRatingColumn), index(rowCount() - 1, nationalRatingColumn));
+    }
+}
+
 void PlayersModel::updatePlayer(Player *player)
 {
     Q_EMIT dataChanged(index(player->startingRank() - 1, 0), index(player->startingRank() - 1, columnCount() - 1), {});
diff --git a/src/playersmodel.h b/src/playersmodel.h
index fe8ed17..c76dd1d 100644
--- a/src/playersmodel.h
+++ b/src/playersmodel.h
@@ -61,6 +61,9 @@ public:
                                const QString &origin,
                                const QString &gender);
     Q_INVOKABLE void deletePlayer(const QModelIndex &index);
+
+    Q_INVOKABLE void updateRatings(int listId);
+
     void updatePlayer(Player *player);
 
 Q_SIGNALS:
diff --git a/src/qml/PlayersPage.qml b/src/qml/PlayersPage.qml
index 6d92719..5114100 100644
--- a/src/qml/PlayersPage.qml
+++ b/src/qml/PlayersPage.qml
@@ -108,6 +108,15 @@ TablePage {
             enabled: root.tableView.currentRow >= 0
             onTriggered: root.openPlayerDetails()
         },
+        Kirigami.Action {
+            icon.name: "view-refresh-symbolic"
+            text: KI18n.i18nc("@action Update players ratings", "Update ratings…")
+            enabled: Config.developer && root.tableView.rows > 0
+            onTriggered: {
+                const dialog = Qt.createComponent("org.kde.chessament", "UpdateRatingsDialog").createObject(root.Controls.ApplicationWindow.window, {}) as UpdateRatingsDialog;
+                dialog.open();
+            }
+        },
         Kirigami.Action {
             icon.name: "view-sort-symbolic"
             text: KI18n.i18nc("@action:intoolbar", "Sort Players…")
diff --git a/src/qml/UpdateRatingsDialog.qml b/src/qml/UpdateRatingsDialog.qml
new file mode 100644
index 0000000..3c0f6f4
--- /dev/null
+++ b/src/qml/UpdateRatingsDialog.qml
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: 2026 Manuel Alcaraz Zambrano <[email protected]>
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls as Controls
+
+import org.kde.ki18n
+import org.kde.kirigamiaddons.formcard as FormCard
+
+import org.kde.chessament
+
+FormCard.FormCardDialog {
+    id: root
+
+    title: KI18n.i18nc("@title", "Update Ratings")
+
+    onAccepted: {
+        Controller.playersModel.updateRatings(ratingList.currentValue);
+    }
+
+    FormCard.FormComboBoxDelegate {
+        id: ratingList
+        text: KI18n.i18nc("@label:listbox", "Choose a rating list:")
+        model: RatingListModel {}
+        textRole: "name"
+        valueRole: "listId"
+    }
+
+    footer: Controls.DialogButtonBox {
+        standardButtons: Controls.Dialog.Cancel
+
+        Controls.Button {
+            text: KI18n.i18nc("@action:button Update players ratings", "Update")
+            icon.name: "view-refresh-symbolic"
+            enabled: ratingList.currentValue > 0
+            Controls.DialogButtonBox.buttonRole: Controls.DialogButtonBox.AcceptRole
+        }
+    }
+}
diff --git a/src/tournament/ratinglists/ratinglist.cpp b/src/tournament/ratinglists/ratinglist.cpp
index bfa342d..c2627cb 100644
--- a/src/tournament/ratinglists/ratinglist.cpp
+++ b/src/tournament/ratinglists/ratinglist.cpp
@@ -371,8 +371,6 @@ std::expected<QList<RatingListPlayer>, QString> RatingList::searchPlayers(const
         return std::unexpected(db.error());
     }
 
-    QList<RatingListPlayer> players{};
-
     QSqlQuery query(*db);
     query.prepare(SEARCH_PLAYERS_QUERY);
     query.bindValue(u":search"_s, u"%%1%"_s.arg(text));
@@ -381,54 +379,32 @@ std::expected<QList<RatingListPlayer>, QString> RatingList::searchPlayers(const
         return std::unexpected(query.lastError().text());
     }
 
-    const int idNo = query.record().indexOf("playerId");
-    const int nameNo = query.record().indexOf("name");
-    const int federationNo = query.record().indexOf("federation");
-    const int genderNo = query.record().indexOf("gender");
-    const int titleNo = query.record().indexOf("title");
-    const int birthDayNo = query.record().indexOf("birthday");
-    const int standardNo = query.record().indexOf("standard");
-    const int rapidNo = query.record().indexOf("rapid");
-    const int blitzNo = query.record().indexOf("blitz");
-    const int nationalIdNo = query.record().indexOf("nationalId");
-    const int nationalRatingNo = query.record().indexOf("nationalRating");
-    const int extraNo = query.record().indexOf("extra");
+    return loadPlayers(query);
+}
 
-    while (query.next()) {
-        const auto id = query.value(idNo).toString();
-        const auto name = query.value(nameNo).toString();
-        const auto federation = query.value(federationNo).toString();
-        const auto gender = query.value(genderNo).toString();
-        const auto title = query.value(titleNo).toString();
-        const auto birthDate = query.value(birthDayNo).toString();
-        const auto standardRating = query.value(standardNo).toInt();
-        const auto rapidRating = query.value(rapidNo).toInt();
-        const auto blitzRating = query.value(blitzNo).toInt();
-        const auto nationalId = query.value(nationalIdNo).toString();
-        const auto nationalRating = query.value(nationalRatingNo).toInt();
+std::optional<RatingListPlayer> RatingList::searchPlayer(const QString &playerId, int listId)
+{
+    auto db = getDb();
+    if (!db) {
+        return std::nullopt;
+    }
 
-        const auto extra = query.value(extraNo).toByteArray();
-        const auto extraJson = QJsonDocument::fromJson(extra);
+    QSqlQuery query(*db);
+    query.prepare(SEARCH_PLAYER_QUERY);
+    query.bindValue(u":playerId"_s, playerId);
+    query.bindValue(u":listId"_s, listId);
 
-        const auto player = RatingListPlayer{
-            id,
-            name,
-            federation,
-            gender,
-            title,
-            birthDate,
-            standardRating,
-            rapidRating,
-            blitzRating,
-            nationalId,
-            nationalRating,
-            extraJson.object(),
-        };
+    if (!query.exec()) {
+        return std::nullopt;
+    }
 
-        players << player;
+    const auto players = loadPlayers(query);
+
+    if (players.isEmpty()) {
+        return std::nullopt;
     }
 
-    return players;
+    return players.first();
 }
 
 std::expected<void, QString> RatingList::savePlayers(const QList<RatingListPlayer> &players)
@@ -498,4 +474,58 @@ std::expected<void, QString> RatingList::savePlayers(const QList<RatingListPlaye
     return {};
 }
 
+QList<RatingListPlayer> RatingList::loadPlayers(QSqlQuery &query)
+{
+    QList<RatingListPlayer> players{};
+
+    const int idNo = query.record().indexOf("playerId");
+    const int nameNo = query.record().indexOf("name");
+    const int federationNo = query.record().indexOf("federation");
+    const int genderNo = query.record().indexOf("gender");
+    const int titleNo = query.record().indexOf("title");
+    const int birthDayNo = query.record().indexOf("birthday");
+    const int standardNo = query.record().indexOf("standard");
+    const int rapidNo = query.record().indexOf("rapid");
+    const int blitzNo = query.record().indexOf("blitz");
+    const int nationalIdNo = query.record().indexOf("nationalId");
+    const int nationalRatingNo = query.record().indexOf("nationalRating");
+    const int extraNo = query.record().indexOf("extra");
+
+    while (query.next()) {
+        const auto id = query.value(idNo).toString();
+        const auto name = query.value(nameNo).toString();
+        const auto federation = query.value(federationNo).toString();
+        const auto gender = query.value(genderNo).toString();
+        const auto title = query.value(titleNo).toString();
+        const auto birthDate = query.value(birthDayNo).toString();
+        const auto standardRating = query.value(standardNo).toInt();
+        const auto rapidRating = query.value(rapidNo).toInt();
+        const auto blitzRating = query.value(blitzNo).toInt();
+        const auto nationalId = query.value(nationalIdNo).toString();
+        const auto nationalRating = query.value(nationalRatingNo).toInt();
+
+        const auto extra = query.value(extraNo).toByteArray();
+        const auto extraJson = QJsonDocument::fromJson(extra);
+
+        const auto player = RatingListPlayer{
+            id,
+            name,
+            federation,
+            gender,
+            title,
+            birthDate,
+            standardRating,
+            rapidRating,
+            blitzRating,
+            nationalId,
+            nationalRating,
+            extraJson.object(),
+        };
+
+        players << player;
+    }
+
+    return players;
+}
+
 #include "moc_ratinglist.cpp"
diff --git a/src/tournament/ratinglists/ratinglist.h b/src/tournament/ratinglists/ratinglist.h
index 18e48ca..1fef506 100644
--- a/src/tournament/ratinglists/ratinglist.h
+++ b/src/tournament/ratinglists/ratinglist.h
@@ -14,6 +14,7 @@
 #include "ratinglistplayer.h"
 
 class QSqlDatabase;
+class QSqlQuery;
 class RatingListReader;
 
 using namespace Qt::StringLiterals;
@@ -65,6 +66,10 @@ constexpr auto ADD_RATING_LIST_PLAYER_QUERY =
 static const auto SEARCH_PLAYERS_QUERY =
     u"SELECT playerId, name, federation, gender, title, birthday, standard, rapid, blitz, nationalId, nationalRating, json(extra) as extra FROM players WHERE name LIKE :search LIMIT 20;"_s;
 
+static const QString SEARCH_PLAYER_QUERY =
+    u"SELECT playerId, name, federation, gender, title, birthday, standard, rapid, blitz, nationalId, nationalRating, json(extra) as extra "
+    "FROM players WHERE list = :listId AND playerId = :playerId LIMIT 1;"_s;
+
 static constexpr auto RATING_LISTS_DB_CONNECTION_NAME = "rating-lists"_L1;
 static constexpr auto RATING_LISTS_DB_CONNECTION_NAME_WRITER = "rating-lists-writer"_L1;
 
@@ -93,6 +98,8 @@ public:
 
     static std::expected<QList<RatingListPlayer>, QString> searchPlayers(const QString &text);
 
+    static std::optional<RatingListPlayer> searchPlayer(const QString &playerId, int listId);
+
 public Q_SLOTS:
     void setExtra(const QByteArray &extra);
 
@@ -107,6 +114,8 @@ private:
     std::expected<uint, QString> processFile(QByteArray content, const QMimeType &mime);
     std::expected<void, QString> savePlayers(const QList<RatingListPlayer> &players);
 
+    static QList<RatingListPlayer> loadPlayers(QSqlQuery &query);
+
     int m_id{};
     QString m_name;
     QString m_url;
diff --git a/src/tournament/tournament.cpp b/src/tournament/tournament.cpp
index 08bb980..128a361 100644
--- a/src/tournament/tournament.cpp
+++ b/src/tournament/tournament.cpp
@@ -12,6 +12,7 @@
 
 #include "db.h"
 #include "event.h"
+#include "ratinglists/ratinglist.h"
 #include "state.h"
 #include "tiebreaks/aob.h"
 #include "tiebreaks/buchholz.h"
@@ -333,6 +334,27 @@ void Tournament::sortPlayers()
     }
 }
 
+void Tournament::updateRatings(int listId)
+{
+    const auto &players = m_players;
+    for (const auto &player : players) {
+        const auto listPlayer = RatingList::searchPlayer(player->playerId(), listId);
+
+        if (!listPlayer) {
+            continue;
+        }
+
+        if (listPlayer->standardRating() == player->rating() && listPlayer->nationalRating() == player->nationalRating()) {
+            continue;
+        }
+
+        player->setRating(listPlayer->standardRating());
+        player->setNationalRating(listPlayer->nationalRating());
+
+        savePlayer(player.get());
+    }
+}
+
 int Tournament::changePlayerStartingRank(Player *player, int startingRank)
 {
     Q_ASSERT(startingRank >= 1);
diff --git a/src/tournament/tournament.h b/src/tournament/tournament.h
index 7572819..1654ce2 100644
--- a/src/tournament/tournament.h
+++ b/src/tournament/tournament.h
@@ -140,6 +140,8 @@ public:
      */
     void sortPlayers();
 
+    void updateRatings(int listId);
+
     /*!
      * Changes the starting rank of \a player to \a startingRank.
      *
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.