[games/chessament] src: Add support for sorting tables
Manuel Alcaraz Zambrano <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit b2e24db4ad1abf9856fbb30c94a18dc295a7b139 by Manuel Alcaraz Zambrano.
Committed on 04/08/2026 at 09:45.
Pushed by manuelal into branch 'master'.
Add support for sorting tables
M +1 -0 src/CMakeLists.txt
M +27 -14 src/pairingmodel.cpp
M +1 -0 src/pairingmodel.h
M +33 -24 src/playersmodel.cpp
M +1 -0 src/playersmodel.h
M +14 -3 src/qml/PairingsPage.qml
M +11 -2 src/qml/PlayersPage.qml
M +18 -4 src/qml/StandingsPage.qml
M +17 -3 src/qml/TablePage.qml
A +104 -0 src/qml/components/HeaderDelegate.qml [License: GPL(v3.0+)]
M +22 -15 src/standingsmodel.cpp
M +1 -0 src/standingsmodel.h
https://invent.kde.org/games/chessament/-/commit/b2e24db4ad1abf9856fbb30c94a18dc295a7b139
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cc8d068..8169c11 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,6 +13,7 @@ ecm_add_qml_module(chessament_static
QML_FILES
qml/components/ArbiterDelegate.qml
qml/components/FormCardSearchFieldDelegate.qml
+ qml/components/HeaderDelegate.qml
qml/components/MenuBar.qml
qml/components/RatingField.qml
qml/components/StartingRankField.qml
diff --git a/src/pairingmodel.cpp b/src/pairingmodel.cpp
index d3dfc6a..9901937 100644
--- a/src/pairingmodel.cpp
+++ b/src/pairingmodel.cpp
@@ -87,26 +87,39 @@ QHash<int, QByteArray> PairingModel::roleNames() const
{PairingModel::Roles::HasFinishedRole, "hasFinished"},
{PairingModel::Roles::PairingRole, "pairing"},
{Qt::TextAlignmentRole, "textAlignment"},
+ {PairingModel::Roles::EnableSort, "enableSort"},
};
}
QVariant PairingModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation)
- Q_UNUSED(role)
- switch (m_columns.at(section)) {
- case Board:
- return i18nc("@title:column Board Number", "Board");
- case WhiteStartingRank:
- return i18nc("@title:column White Player Starting Rank Number", "№");
- case WhiteName:
- return i18nc("@title:column Name of the White Player", "White Player");
- case Result:
- return i18nc("@title:column Game Result", "Result");
- case BlackName:
- return i18nc("@title:column Name of the Black Player", "Black Player");
- case BlackStartingRank:
- return i18nc("@title:column Black Player Starting Rank Number", "№");
+
+ const int column = m_columns.at(section);
+
+ if (role == Qt::DisplayRole) {
+ switch (column) {
+ case Board:
+ return i18nc("@title:column Board Number", "Board");
+ case WhiteStartingRank:
+ return i18nc("@title:column White Player Starting Rank Number", "№");
+ case WhiteName:
+ return i18nc("@title:column Name of the White Player", "White Player");
+ case Result:
+ return i18nc("@title:column Game Result", "Result");
+ case BlackName:
+ return i18nc("@title:column Name of the Black Player", "Black Player");
+ case BlackStartingRank:
+ return i18nc("@title:column Black Player Starting Rank Number", "№");
+ }
+ }
+ if (role == PairingModel::Roles::EnableSort) {
+ switch (column) {
+ case Board:
+ return true;
+ default:
+ return false;
+ }
}
return {};
diff --git a/src/pairingmodel.h b/src/pairingmodel.h
index 3b8ac6b..5056805 100644
--- a/src/pairingmodel.h
+++ b/src/pairingmodel.h
@@ -29,6 +29,7 @@ public:
enum Roles {
HasFinishedRole = Qt::UserRole,
PairingRole,
+ EnableSort,
};
Q_ENUM(Roles)
diff --git a/src/playersmodel.cpp b/src/playersmodel.cpp
index ada0b85..e8e7df2 100644
--- a/src/playersmodel.cpp
+++ b/src/playersmodel.cpp
@@ -206,6 +206,7 @@ QHash<int, QByteArray> PlayersModel::roleNames() const
{Qt::TextAlignmentRole, "textAlignment"},
{Roles::PlayerRole, "player"},
{Roles::IconRole, "iconSource"},
+ {Roles::EnableSort, "enableSort"},
};
}
@@ -218,31 +219,39 @@ Qt::ItemFlags PlayersModel::flags(const QModelIndex &index) const
QVariant PlayersModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation)
- Q_UNUSED(role)
- switch (m_columns.at(section)) {
- case PlayersModel::Columns::StartingRank:
- return i18nc("@title:column Player Starting Rank Number", "№");
- case PlayersModel::Columns::Title:
- return i18nc("@title:column Player Title", "Title");
- case PlayersModel::Columns::Name:
- return i18nc("@title:column Player Name", "Name");
- case PlayersModel::Columns::Rating:
- return i18nc("@title:column Player Rating", "Rating");
- case PlayersModel::Columns::NationalRating:
- return i18nc("@title:column Player National Rating", "National Rating");
- case PlayersModel::Columns::PlayerId:
- return i18nc("@title:column", "Player ID");
- case PlayersModel::Columns::NationalId:
- return i18nc("@title:column Player National ID", "National ID");
- case PlayersModel::Columns::BirthDate:
- return i18nc("@title:column", "Birth date");
- case PlayersModel::Columns::Federation:
- return i18nc("@title:column Player Federation", "Federation");
- case PlayersModel::Columns::Origin:
- return i18nc("@title:column Player Origin", "Origin");
- case PlayersModel::Columns::Gender:
- return i18nc("@title:column Player Gender", "Gender");
+
+ const int column = m_columns.at(section);
+
+ if (role == Qt::DisplayRole) {
+ switch (column) {
+ case PlayersModel::Columns::StartingRank:
+ return i18nc("@title:column Player Starting Rank Number", "№");
+ case PlayersModel::Columns::Title:
+ return i18nc("@title:column Player Title", "Title");
+ case PlayersModel::Columns::Name:
+ return i18nc("@title:column Player Name", "Name");
+ case PlayersModel::Columns::Rating:
+ return i18nc("@title:column Player Rating", "Rating");
+ case PlayersModel::Columns::NationalRating:
+ return i18nc("@title:column Player National Rating", "National Rating");
+ case PlayersModel::Columns::PlayerId:
+ return i18nc("@title:column", "Player ID");
+ case PlayersModel::Columns::NationalId:
+ return i18nc("@title:column Player National ID", "National ID");
+ case PlayersModel::Columns::BirthDate:
+ return i18nc("@title:column", "Birth date");
+ case PlayersModel::Columns::Federation:
+ return i18nc("@title:column Player Federation", "Federation");
+ case PlayersModel::Columns::Origin:
+ return i18nc("@title:column Player Origin", "Origin");
+ case PlayersModel::Columns::Gender:
+ return i18nc("@title:column Player Gender", "Gender");
+ }
}
+ if (role == PlayersModel::Roles::EnableSort) {
+ return true;
+ }
+
return {};
}
diff --git a/src/playersmodel.h b/src/playersmodel.h
index c76dd1d..7beb330 100644
--- a/src/playersmodel.h
+++ b/src/playersmodel.h
@@ -34,6 +34,7 @@ public:
enum Roles {
PlayerRole = Qt::UserRole,
IconRole,
+ EnableSort,
};
Q_ENUM(Roles)
diff --git a/src/qml/PairingsPage.qml b/src/qml/PairingsPage.qml
index ceab295..f783d51 100644
--- a/src/qml/PairingsPage.qml
+++ b/src/qml/PairingsPage.qml
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Manuel Alcaraz Zambrano <[email protected]>
+
pragma ComponentBehavior: Bound
import QtCore
@@ -18,9 +19,22 @@ TablePage {
Kirigami.ColumnView.fillWidth: true
+ columnWidths: [55, 55, 300, 120, 300, 55]
+
+ onColumnClicked: function (index: int): void {
+ if (root.sortColumn === index) {
+ root.sortOrder = root.sortOrder === Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder;
+ } else {
+ root.sortColumn = index;
+ }
+ }
+
model: KItemModels.KSortFilterProxyModel {
id: proxyModel
sourceModel: Controller.pairingModel
+
+ sortColumn: root.sortColumn
+ sortOrder: root.sortOrder
filterRowCallback: function (source_row, source_parent) {
if (hideFinishedAction.checked) {
const hasFinished = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PairingModel.HasFinishedRole);
@@ -30,9 +44,6 @@ TablePage {
}
}
- selectionBehavior: TableView.SelectRows
- columnWidths: [55, 55, 300, 120, 300, 55]
-
Dialogs.FileDialog {
id: saveDialog
fileMode: Dialogs.FileDialog.SaveFile
diff --git a/src/qml/PlayersPage.qml b/src/qml/PlayersPage.qml
index 5c6e28e..1d168fe 100644
--- a/src/qml/PlayersPage.qml
+++ b/src/qml/PlayersPage.qml
@@ -24,7 +24,14 @@ TablePage {
Kirigami.ColumnView.fillWidth: true
columnWidths: [55, 55, 300, 60, 90, 90, 100, 100, 150, 150, 50]
- selectionBehavior: TableView.SelectRows
+
+ onColumnClicked: function (index: int): void {
+ if (root.sortColumn === index) {
+ root.sortOrder = root.sortOrder === Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder;
+ } else {
+ root.sortColumn = index;
+ }
+ }
AddPlayerDialog {
id: addPlayerDialog
@@ -44,8 +51,10 @@ TablePage {
model: KItemModels.KSortFilterProxyModel {
id: proxyModel
-
sourceModel: Controller.playersModel
+
+ sortColumn: root.sortColumn
+ sortOrder: root.sortOrder
filterRowCallback: function (source_row: int, source_parent): bool {
const player = sourceModel.data(sourceModel.index(source_row, 0), PlayersModel.PlayerRole) as Player;
return player.name.toLowerCase().includes(root.filterString);
diff --git a/src/qml/StandingsPage.qml b/src/qml/StandingsPage.qml
index 3b23050..0199ebd 100644
--- a/src/qml/StandingsPage.qml
+++ b/src/qml/StandingsPage.qml
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-// SPDX-FileCopyrightText: 2024-2025 Manuel Alcaraz Zambrano <[email protected]>
+// SPDX-FileCopyrightText: 2024-2026 Manuel Alcaraz Zambrano <[email protected]>
pragma ComponentBehavior: Bound
@@ -10,6 +10,7 @@ import QtQuick.Controls as Controls
import QtQuick.Dialogs as Dialogs
import org.kde.ki18n
+import org.kde.kitemmodels as KItemModels
import org.kde.kirigami as Kirigami
import org.kde.chessament
@@ -21,10 +22,23 @@ TablePage {
Kirigami.ColumnView.fillWidth: true
- model: Controller.standingsModel
- content.visible: Controller.tournament.currentRound > 0
+ onColumnClicked: function (index: int): void {
+ if (root.sortColumn === index) {
+ root.sortOrder = root.sortOrder === Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder;
+ } else {
+ root.sortColumn = index;
+ }
+ }
+
+ model: KItemModels.KSortFilterProxyModel {
+ id: proxyModel
+ sourceModel: Controller.standingsModel
- selectionBehavior: TableView.SelectRows
+ sortColumn: root.sortColumn
+ sortOrder: root.sortOrder
+ }
+
+ content.visible: Controller.tournament.currentRound > 0
function defaultColumnWidth(column: int): int {
if (column >= 4) {
diff --git a/src/qml/TablePage.qml b/src/qml/TablePage.qml
index d24acc9..5f3117f 100644
--- a/src/qml/TablePage.qml
+++ b/src/qml/TablePage.qml
@@ -21,10 +21,15 @@ Kirigami.Page {
readonly property alias tableView: tableView
readonly property alias heading: heading
- property var selectionBehavior: TableView.SelectCells
+ property int sortColumn: -1
+ property int sortOrder: Qt.AscendingOrder
+
+ property var selectionBehavior: TableView.SelectRows
property list<int> columnWidths: []
+ signal columnClicked(int index)
+
function defaultColumnWidth(column: int): int {
const columnWidth = root.tableView.implicitColumnWidth(column);
const headingWidth = heading.implicitColumnWidth(column);
@@ -54,6 +59,16 @@ Kirigami.Page {
width: scrollView.width
syncView: tableView
clip: true
+
+ delegate: HeaderDelegate {
+ required property int index
+ required property bool enableSort
+
+ sortSupported: enableSort
+ sortEnabled: root.sortColumn === index
+ sortOrder: root.sortOrder
+ onClicked: root.columnClicked(index)
+ }
}
Controls.ScrollView {
@@ -93,8 +108,7 @@ Kirigami.Page {
return w;
}
if (root.columnWidths[column]) {
- const headingWidth = heading.implicitColumnWidth(column);
- return Math.max(headingWidth, root.columnWidths[column]);
+ return root.columnWidths[column];
}
return root.defaultColumnWidth(column);
}
diff --git a/src/qml/components/HeaderDelegate.qml b/src/qml/components/HeaderDelegate.qml
new file mode 100644
index 0000000..4b98b44
--- /dev/null
+++ b/src/qml/components/HeaderDelegate.qml
@@ -0,0 +1,104 @@
+// SPDX-FileCopyrightText: 2023 Evgeny Chesnokov <[email protected]>
+// SPDX-FileCopyrightText: 2026 Manuel Alcaraz Zambrano <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls as Controls
+import QtQuick.Layouts
+
+import org.kde.kirigami as Kirigami
+
+Controls.Control {
+ id: delegate
+
+ Accessible.role: Accessible.ColumnHeader
+
+ required property string displayText
+
+ property bool sortSupported: true
+ property bool sortEnabled: false
+ property int sortOrder: Qt.AscendingOrder
+
+ signal clicked
+
+ padding: Kirigami.Units.smallSpacing
+
+ background: Rectangle {
+ border.color: "transparent"
+ color: {
+ if (!delegate.enabled) {
+ return "transparent";
+ }
+ if (delegate.hovered && delegate.sortSupported) {
+ return Qt.alpha(Kirigami.Theme.hoverColor, 0.3);
+ }
+ return Kirigami.Theme.backgroundColor;
+ }
+
+ Kirigami.Separator {
+ height: parent.height
+ anchors.right: parent.right
+ }
+
+ Kirigami.Separator {
+ width: parent.width
+ anchors.bottom: parent.bottom
+ }
+ }
+
+ contentItem: RowLayout {
+ anchors.fill: parent
+ spacing: delegate.spacing
+
+ TapHandler {
+ enabled: delegate.sortSupported
+ onTapped: delegate.clicked()
+ }
+
+ Controls.Label {
+ id: label
+ text: delegate.displayText
+ elide: Text.ElideRight
+ verticalAlignment: Qt.AlignVCenter
+ horizontalAlignment: Qt.AlignLeft
+ leftPadding: Kirigami.Units.largeSpacing
+ rightPadding: Kirigami.Units.largeSpacing
+
+ Layout.alignment: Qt.AlignVCenter
+ Layout.fillWidth: true
+
+ Controls.ToolTip.text: text
+ Controls.ToolTip.visible: truncated && delegate.hovered
+ Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
+ }
+
+ Kirigami.Icon {
+ id: sortIndicator
+ visible: delegate.sortEnabled
+ source: "arrow-up-symbolic"
+ implicitWidth: Kirigami.Units.iconSizes.small
+ implicitHeight: Kirigami.Units.iconSizes.small
+
+ Layout.alignment: Qt.AlignVCenter
+ Layout.rightMargin: Kirigami.Units.largeSpacing
+
+ states: State {
+ when: delegate.sortEnabled && delegate.sortOrder === Qt.DescendingOrder
+ PropertyChanges {
+ sortIndicator {
+ rotation: 180
+ }
+ }
+ }
+
+ transitions: Transition {
+ RotationAnimation {
+ duration: Kirigami.Units.longDuration
+ direction: RotationAnimation.Counterclockwise
+ }
+ }
+ }
+ }
+}
diff --git a/src/standingsmodel.cpp b/src/standingsmodel.cpp
index 88ee82d..ce6a81f 100644
--- a/src/standingsmodel.cpp
+++ b/src/standingsmodel.cpp
@@ -70,6 +70,7 @@ QHash<int, QByteArray> StandingsModel::roleNames() const
return {
{Qt::DisplayRole, "displayText"},
{Qt::TextAlignmentRole, "textAlignment"},
+ {StandingsModel::StandingRoles::EnableSort, "enableSort"},
};
}
@@ -82,27 +83,33 @@ Qt::ItemFlags StandingsModel::flags(const QModelIndex &index) const
QVariant StandingsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation)
- Q_UNUSED(role)
Q_ASSERT(m_tournament != nullptr);
- switch (section) {
- case RankRole:
- return i18nc("@title:column Player Ranking", "Rank");
- case StartingRankRole:
- return i18nc("@title:column Player Starting Rank Number", "№");
- case TitleRole:
- return i18nc("@title:column Player Title", "Title");
- case NameRole:
- return i18nc("@title:column Player Name", "Name");
- default: {
- const auto tiebreak = m_tournament->tiebreaks().at(section - 4).get();
- if (!tiebreak->shortName().isNull()) {
- return tiebreak->shortName();
+ if (role == Qt::DisplayRole) {
+ switch (section) {
+ case RankRole:
+ return i18nc("@title:column Player Ranking", "Rank");
+ case StartingRankRole:
+ return i18nc("@title:column Player Starting Rank Number", "№");
+ case TitleRole:
+ return i18nc("@title:column Player Title", "Title");
+ case NameRole:
+ return i18nc("@title:column Player Name", "Name");
+ default: {
+ const auto tiebreak = m_tournament->tiebreaks().at(section - 4).get();
+ if (!tiebreak->shortName().isNull()) {
+ return tiebreak->shortName();
+ }
+ return tiebreak->code();
+ }
}
- return tiebreak->code();
}
+ if (role == StandingsModel::StandingRoles::EnableSort) {
+ return true;
}
+
+ return {};
}
void StandingsModel::setStandings(QList<Standing> standings)
diff --git a/src/standingsmodel.h b/src/standingsmodel.h
index 0e01397..9701e2d 100644
--- a/src/standingsmodel.h
+++ b/src/standingsmodel.h
@@ -22,6 +22,7 @@ public:
StartingRankRole,
TitleRole,
NameRole,
+ EnableSort,
};
Q_ENUM(StandingRoles)