Compare commits

...

17 commits

26 changed files with 653 additions and 707 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.28)
project(itat_challange_olympics)
project(itat_challenge_olympics)
find_package(Qt6 6.2 COMPONENTS Core Quick Quick REQUIRED)
@ -8,12 +8,12 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
qt_add_executable(itat_challange_olympics src/main/main.cpp
qt_add_executable(itat_challenge_olympics src/main/main.cpp
application.qrc
)
qt_add_qml_module(itat_challange_olympics
qt_add_qml_module(itat_challenge_olympics
URI itat
QML_FILES
res/gui/application.qml
@ -23,17 +23,13 @@ qt_add_qml_module(itat_challange_olympics
SOURCES
src/model/Competitor.cpp
src/model/Competitor.h
src/model/CompetitorWithResults.cpp
src/model/CompetitorWithResults.h
src/model/EventInfo.cpp
src/model/EventInfo.h
src/model/FilterModel.cpp
src/model/FilterModel.h
src/model/MedalWinner.cpp
src/model/MedalWinner.h
src/model/Sport.cpp
src/model/Sport.h
src/model/SportModel.cpp
src/model/SportModel.h
RESOURCES
res/pictograms/ARC_small.svg
res/pictograms/ATH_small.svg
@ -51,7 +47,9 @@ qt_add_qml_module(itat_challange_olympics
res/pictograms/CSP_small.svg
res/pictograms/CTR_small.svg
res/pictograms/DIV_small.svg
res/pictograms/EQU_small.svg
res/pictograms/EDR_small.svg
res/pictograms/EJP_small.svg
res/pictograms/EVE_small.svg
res/pictograms/FBL_small.svg
res/pictograms/FEN_small.svg
res/pictograms/GAR_small.svg
@ -81,8 +79,9 @@ qt_add_qml_module(itat_challange_olympics
res/pictograms/WLF_small.svg
res/pictograms/WPO_small.svg
res/pictograms/WRE_small.svg
res/pictograms/WRG_small.svg
)
target_link_libraries(itat_challange_olympics PRIVATE Qt6::Core Qt6::Quick Qt6::Network)
target_link_libraries(itat_challenge_olympics PRIVATE Qt6::Core Qt6::Quick Qt6::Network)
# target_link_libraries(itat_challange_olympics PRIVATE d3d12.lib dxgi.lib d3dcompiler.lib dxguid.lib)
# target_link_libraries(itat_challenge_olympics PRIVATE d3d12.lib dxgi.lib d3dcompiler.lib dxguid.lib)

170
README.md Normal file
View file

@ -0,0 +1,170 @@
# Olympia 2024 Events
> View updated Events with its Competitors and Rankings of all Disciplines
*Olympia 2024 Events* always displays up to date information of the 2024
Olympics in Paris. It achieves this by fetching the `olympics.com` API.
We use the Model-View-Delegate pattern to synchronize the API data in C++ with
the UI-Widgets defined in QML. For this we implement the API data as a Model
which can be seen and interacted with by QML Components.
All code, qml definitions and images, etc are compiled into a single binary that
is not dependent on any resources on relative paths anymore.
On startup the default discipline *Archery* will be fetched and shown on the
EventsPage. From here the user has three options. You can change the discipline
from the Dropdown-Menu (Combobox) in the top left (also note the changing
pictograms of the discipline); Filter the EventNames with the Search field in
the top right; or click on an Event.
When clicking on an Event, the user is redirected to the EventInfoPage. Here you
can see Information about all Competitors that took part in the Event. When you
are done, you can go back to the EventsPage with the button in the top left.
## Galery
<table>
<tr>
<td><img src="doc/events_page_combobox.png"/></td>
<td><img src="doc/events_page_textfield.png"/></td>
<td><img src="doc/event_info.png"/></td>
</tr>
<tr>
<td>Select discipline</td>
<td>Filter by Event</td>
<td>View Competitor</td>
</tr>
</table>
## Getting Started
### Dependencies
- Qt6
### Installation
```sh
git clone git@gitlab.kit.edu:ugmgt/itat_challenge_2024.git
# or download release
cd itat_challenge_2024
cmake -B build
cmake --build build
./build/itat_challenge_olympics
```
## Code Structure
### UML Diagram
```plantuml
@startuml
allowmixing
set namespaceSeparator none
skinparam ranksep 10
package C++ <<Frame>> {
class Application {
QGuiApplication app
QmlComponent component
SportModel model
FilterModel<SportModel> filter
}
class SportModel {
String discipline
<EventInfo> model
request(String discipline)
parseData()
}
class FilterModel {
void setFilterFixedString(String)
}
class EventInfo {
String eventName
List<Competitor> competitors
}
class Competitor {
String name
String code
String noc
}
}
package QML <<Frame>> {
component EventInfoPage {
component [Page] as EIPage {
component [ToolBar] as EIToolBar
component [ListView] as EILisView
}
}
EIToolBar -[hidden]- EILisView
component EventsPage {
component [Page] as EPage {
component [ToolBar] as EToolBar
component [Column] as EColumn {
component [Row] as ERow {
component [ComboBox] as EComboBox
component [TextField] as ETextField
}
component [ListView] as EListView
}
}
}
EToolBar -[hidden]- EColumn
ERow -[hidden]- EListView
component application.qml {
component ApplicationWindow {
component StackView
}
}
}
'application.qml -u- a
'Application -r- a
Application *-- "1" SportModel
Application *-- "1" FilterModel
FilterModel "1" o-- "1" SportModel
SportModel *-- "0..*" EventInfo
EventInfo *-- "0..*" Competitor
Application <.l. application.qml
StackView <.. EventInfoPage
StackView <.. EventsPage
EComboBox "request()" .> SportModel
EComboBox -[hidden]u- ETextField
SportModel "View" .> EListView
FilterModel "View" .> EListView
ETextField "Control" .r.> FilterModel
EILisView <. "View" Competitor
cloud api.olympics.com
() REST
REST - api.olympics.com
SportModel -( REST
application.qml -[hidden]u- Application
@enduml
```
## Authors
- **Silas Stulz** (ugmgt, 2468197) - *Initial Work*
- **Gero Beckmann** (ukpfm, 2409754) - *Initial Work*

BIN
doc/event_info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -4,8 +4,8 @@ import QtQuick.Controls
Page {
id: root
property string eventName
property list<string> competitors
required property string eventName
required property list<QtObject> competitors
header: ToolBar {
ToolButton {
@ -33,15 +33,25 @@ Page {
spacing: 20
model: competitors
delegate: ItemDelegate {
text: modelData
required property string name
required property string noc
required property string mark
required property string statistic
required property string gold
required property string silver
required property string bronze
width: listView.width - listView.leftMargin - listView.rightMargin
height: avatar.implicitHeight + 32
leftPadding: avatar.implicitWidth + 32
Image {
id: avatar
// source: "images/" + modelData.replace(" ", "_") + ".png"
height: 32
Text {
anchors.left: parent.left
text: name + " (" + noc + ")"
}
Text {
anchors.right: parent.right
horizontalAlignment: Text.AlignRight
text: mark + " " + statistic + " | " + gold + "🥇 " + silver + "🥈 " + bronze + "🥉"
}
}
}

View file

@ -254,7 +254,7 @@ Page {
model: filter
delegate: ItemDelegate {
required property string eventName
required property list<string> competitors
required property list<QtObject> competitors
text: eventName
width: listView.width - listView.leftMargin - listView.rightMargin
height: avatar.height

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 720 720">
<defs>
<style>
.cls-1 {
clip-path: url(#clippath);
}
.cls-2 {
fill: none;
}
.cls-2, .cls-3 {
stroke-width: 0px;
}
.cls-3 {
fill: #000;
}
</style>
<clipPath id="clippath">
<rect class="cls-2" width="720" height="720"/>
</clipPath>
</defs>
<g class="cls-1">
<path class="cls-3" d="M0,149.7C0,99.6,31.6,57,75.9,40.5L116.4,0v33.3l99.8,49.9-16.6,33.3h-83.2v149.7c-64.3-.1-116.4-52.2-116.4-116.5ZM215.4,216c30.4-30.4,79.6-30.4,110,0l5.9,5.9,28.7-28.7,11.3,11.3-68,68,35.6,45.1-24.6,24.6-57.4-23.2-24.2,24.2-17.2-17.2c-30.5-30.3-30.5-79.6-.1-110ZM269.2,306.7l41.3,16.8,7-7-25.6-32.5-22.7,22.7ZM603.6,116.4v149.7c64.3,0,116.4-52.1,116.4-116.4s-31.6-92.7-75.9-109.2L603.6,0v33.3l-99.8,49.9,16.6,33.3h83.2ZM603.6,453.9v149.7h-83.2l-16.6,33.3,99.8,49.9v33.2l40.5-40.5c44.3-16.5,75.9-59.1,75.9-109.2s-52.1-116.4-116.4-116.4ZM116.4,603.6v-149.7c-64.3,0-116.4,52.1-116.4,116.4s31.6,92.7,75.9,109.2l40.5,40.5v-33.3l99.8-49.9-16.6-33.3h-83.2ZM490,237.1l64-59c2-1.8,3.3-4.1,3.7-6.2s0-4.2-1.4-5.6l-1.3-1.3-1.3-1.3c-1.4-1.4-3.4-1.8-5.6-1.4s-4.4,1.7-6.2,3.7l-59,64-252.9,252.9-64,59c-2,1.8-3.3,4.1-3.7,6.2s0,4.2,1.4,5.6l1.3,1.3,1.3,1.3c1.4,1.4,3.4,1.8,5.6,1.4s4.4-1.7,6.2-3.7l59-64,252.9-252.9ZM504.6,504c-30.4,30.4-79.6,30.4-110,0l-5.9-5.9-28.7,28.6-11.3-11.3,68-68-35.6-45.1,24.6-24.6,57.4,23.3,24.2-24.2,17.2,17.2c30.5,30.3,30.5,79.6,0,110ZM450.8,413.3l-41.3-16.8-7,7,25.6,32.5,22.7-22.7Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 720 720">
<defs>
<style>
.cls-1 {
clip-path: url(#clippath);
}
.cls-2 {
fill: none;
}
.cls-2, .cls-3 {
stroke-width: 0px;
}
.cls-3 {
fill: #000;
}
</style>
<clipPath id="clippath">
<rect class="cls-2" width="720" height="720"/>
</clipPath>
</defs>
<g class="cls-1">
<path class="cls-3" d="M0,149.7C0,99.6,31.6,57,75.9,40.5L116.4,0v33.3l99.8,49.9-16.6,33.3h-83.2v149.7c-64.3-.1-116.4-52.2-116.4-116.5ZM215.4,216c30.4-30.4,79.6-30.4,110,0l5.9,5.9,28.7-28.7,11.3,11.3-68,68,35.6,45.1-24.6,24.6-57.4-23.2-24.2,24.2-17.2-17.2c-30.5-30.3-30.5-79.6-.1-110ZM269.2,306.7l41.3,16.8,7-7-25.6-32.5-22.7,22.7ZM603.6,116.4v149.7c64.3,0,116.4-52.1,116.4-116.4s-31.6-92.7-75.9-109.2L603.6,0v33.3l-99.8,49.9,16.6,33.3h83.2ZM603.6,453.9v149.7h-83.2l-16.6,33.3,99.8,49.9v33.2l40.5-40.5c44.3-16.5,75.9-59.1,75.9-109.2s-52.1-116.4-116.4-116.4ZM116.4,603.6v-149.7c-64.3,0-116.4,52.1-116.4,116.4s31.6,92.7,75.9,109.2l40.5,40.5v-33.3l99.8-49.9-16.6-33.3h-83.2ZM490,237.1l64-59c2-1.8,3.3-4.1,3.7-6.2s0-4.2-1.4-5.6l-1.3-1.3-1.3-1.3c-1.4-1.4-3.4-1.8-5.6-1.4s-4.4,1.7-6.2,3.7l-59,64-252.9,252.9-64,59c-2,1.8-3.3,4.1-3.7,6.2s0,4.2,1.4,5.6l1.3,1.3,1.3,1.3c1.4,1.4,3.4,1.8,5.6,1.4s4.4-1.7,6.2-3.7l59-64,252.9-252.9ZM504.6,504c-30.4,30.4-79.6,30.4-110,0l-5.9-5.9-28.7,28.6-11.3-11.3,68-68-35.6-45.1,24.6-24.6,57.4,23.3,24.2-24.2,17.2,17.2c30.5,30.3,30.5,79.6,0,110ZM450.8,413.3l-41.3-16.8-7,7,25.6,32.5,22.7-22.7Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 720 720">
<defs>
<style>
.cls-1 {
clip-path: url(#clippath);
}
.cls-2 {
fill: none;
}
.cls-2, .cls-3 {
stroke-width: 0px;
}
.cls-3 {
fill: #000;
}
</style>
<clipPath id="clippath">
<rect class="cls-2" width="720" height="720"/>
</clipPath>
</defs>
<g class="cls-1">
<path class="cls-3" d="M492.6,467.2c39.5-48.8,48.1-114.2,25.8-170.2,10.9-10.3,14-15.4,18.5-25.8.4-.8.7-1.7,1.1-2.6,1-2.2,3.5-5,7.3-8.1h0c40.1,74.6,32,168.4-24.4,235.2l-28.4-28.5ZM486.2,147.4l90.7-58.9,19.5,19.5-23,23c-2.1,2.1-3.2,4.9-3.2,7.8s1.2,5.7,3.2,7.8c2.1,2.1,4.9,3.2,7.8,3.2s5.7-1.2,7.8-3.2l23-23.1,19.5,19.5-58.9,90.7-2.8,1.2c-20.7,8.8-36.8,20.3-40.8,29.4-.4,1-.8,1.8-1.2,2.7-4.9,11.1-6.9,14.7-25.8,31.2-13.5,11.8-28.7,23.7-33.3,26.7l-9.7,6.4-4.8-10.6c-6-13.1-12.7-23-21.3-31.5l-2.3-2.3c-8.4-8.5-18.4-15.2-31.4-21.2l-10.6-4.8,6.4-9.7c3-4.5,14.9-19.7,26.7-33.3,16.5-18.8,20.1-20.9,31.2-25.8.8-.4,1.7-.8,2.7-1.2,9.1-4.1,20.6-20.1,29.4-40.8l1.2-2.7ZM435.3,277.6l13.6-13.6c-10-9.3-21.3-16.9-33.7-22.7-5.9,7.3-10.3,13.2-11.8,15.4,13.3,6.1,23.5,12.9,31.9,20.9ZM478.7,304.8c-5.8-12.4-13.4-23.7-22.7-33.7l-13.6,13.6c8,8.4,14.8,18.6,20.9,31.9,2.2-1.5,8.2-6,15.4-11.8ZM494.2,154.1c-6.5,15.3-19.3,39.3-34.5,46.1-11.9,5.3-13.3,4.9-30.4,24.4-1.2,1.3-2.3,2.7-3.5,4,12.6,6.3,24.2,14.4,34.5,24.1l26.4-26.4,7.1,7.1-26.4,26.4c9.7,10.3,17.7,21.9,24.1,34.5,1.4-1.2,2.7-2.3,4-3.5,19.5-17.1,19.1-18.5,24.4-30.4,6.8-15.2,30.7-28,46.1-34.5l52.8-81.3-6.7-6.7-16,16c-4.1,4.1-9.5,6.2-14.9,6.2s-10.8-2.1-14.9-6.2c-8.2-8.2-8.2-21.6,0-29.8l16-16-6.7-6.7-81.4,52.7ZM448.9,183c.8-.4,1.7-.7,2.6-1.1,2.2-1,5-3.5,8.1-7.3h0c-74.6-40.2-168.3-32.1-235.2,24.4l28.4,28.4c48.8-39.5,114.2-48.1,170.2-25.8,10.4-10.9,15.5-14,25.9-18.6ZM720,360l-131.8-131.8-8.9,13.6h0l118.2,118.2-156,156,11.3,11.3,167.3-167.3ZM0,360l131.8,131.8,8.9-13.6h.1l-118.2-118.2,156-156-11.3-11.3L0,360ZM233.8,572.6l-90.7,58.9-19.5-19.5,23-23c2.1-2.1,3.2-4.9,3.2-7.8s-1.2-5.7-3.2-7.8c-2.1-2.1-4.9-3.2-7.8-3.2s-5.7,1.1-7.8,3.2l-23,23.1-19.5-19.5,58.9-90.7,2.8-1.2c20.7-8.8,36.8-20.3,40.8-29.4.4-1,.8-1.8,1.2-2.7,4.9-11.1,6.9-14.7,25.8-31.2,13.5-11.8,28.7-23.7,33.3-26.7l9.7-6.4,4.8,10.6c6,13.1,12.7,23,21.3,31.5l2.3,2.3c8.4,8.5,18.4,15.2,31.4,21.2l10.6,4.8-6.4,9.7c-3,4.5-14.9,19.7-26.7,33.3-16.5,18.8-20.1,20.9-31.2,25.8-.8.4-1.7.8-2.7,1.2-9.1,4.1-20.6,20.1-29.4,40.8l-1.2,2.7ZM284.7,442.4l-13.7,13.7c10,9.3,21.3,16.9,33.7,22.7,5.9-7.3,10.3-13.2,11.8-15.4-13.2-6.2-23.4-13-31.8-21ZM241.3,415.2c5.8,12.4,13.4,23.7,22.7,33.7l13.6-13.6c-8-8.4-14.8-18.6-20.9-31.9-2.2,1.5-8.2,6-15.4,11.8ZM225.8,565.9c6.5-15.3,19.3-39.3,34.5-46.1,11.9-5.3,13.3-4.9,30.4-24.4,1.2-1.3,2.3-2.7,3.5-4-12.6-6.3-24.2-14.4-34.5-24.1l-26.4,26.4-7.1-7.1,26.4-26.4c-9.7-10.3-17.7-21.9-24.1-34.5-1.4,1.2-2.7,2.3-4,3.5-19.5,17.1-19.1,18.5-24.4,30.4-6.8,15.2-30.7,28-46.1,34.5l-52.8,81.3,6.7,6.7,16-16c4.1-4.1,9.5-6.2,14.9-6.2s10.8,2.1,14.9,6.2c8.2,8.2,8.2,21.6,0,29.8l-16,16,6.7,6.7,81.4-52.7ZM360,697.4l-118.1-118.1h0c0,0-13.6,9-13.6,9l131.7,131.7,167.3-167.3-11.3-11.3-156,156ZM360,22.6l118.1,118.1h0c0-.1,13.6-9,13.6-9L360,0l-167.3,167.3,11.3,11.3L360,22.6ZM199,224.4c-56.4,66.9-64.6,160.6-24.4,235.2h.1c3.8-3.1,6.3-5.9,7.3-8.1.4-.9.8-1.8,1.1-2.6,4.6-10.4,7.6-15.5,18.5-25.8-22.3-56-13.7-121.5,25.8-170.2l-28.4-28.5ZM271.1,537c-.8.4-1.7.7-2.6,1.1-2.2,1-5,3.5-8.1,7.3h0c74.6,40.2,168.3,32.1,235.2-24.3l-28.4-28.4c-48.7,39.5-114.2,48.1-170.2,25.8-10.4,10.8-15.5,13.9-25.9,18.5ZM391.1,379.8l262.6,262.6-11.3,11.3-262.6-262.6c-14.3,9.1-33.4,7.5-45.9-5s-14.1-31.6-5-45.9L66.3,77.7l11.3-11.3,262.6,262.6c14.3-9.1,33.4-7.5,45.9,5s14.1,31.5,5,45.8ZM347.5,336.2l36.3,36.3c5.3-10.1,3.7-23-4.8-31.5s-21.3-10.1-31.5-4.8ZM372.5,383.8l-36.3-36.3c-5.3,10.1-3.7,23,4.8,31.5s21.3,10.1,31.5,4.8Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,140 +0,0 @@
#include "OlympicsAPI.h"
#include <string>
#include <stdexcept>
// networking
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QUrlQuery>
// json parsing
#include <QFile>
#include <QJsonValue>
#include <QJsonDocument>
#include <QJsonObject>
#include <QVariantMap>
#include <QJsonArray>
using namespace std;
/**
* @brief OlympicsAPI::getSportData Requests the current data from the Olympics API of a certain discipline.
* @param sport The discipline to request.
* @return The current information provided by the API endpoint.
*/
QJsonObject OlympicsAPI::getSportData(OlympicsAPI::Disciplines sport) {
string shortName = this->getDisciplineShort(sport);
if (USE_API_REQUEST) {
// create custom temporary event loop on stack
QEventLoop eventLoop;
// "quit()" the event-loop, when the network request "finished()"
QNetworkAccessManager mgr;
QObject::connect(&mgr, SIGNAL(finished(QNetworkReply*)), &eventLoop, SLOT(quit()));
QString endpoint = (API_LINK + shortName).c_str();
// the HTTP request
QNetworkRequest req( (QUrl( endpoint )) );
QNetworkReply *reply = mgr.get(req);
eventLoop.exec(); // blocks stack until "finished()" has been called
if (reply->error() == QNetworkReply::NoError) {
//success
QString strReply = (QString)reply->readAll();
//parse json
QJsonDocument jsonResponse = QJsonDocument::fromJson(strReply.toUtf8());
QJsonObject jsonObj = jsonResponse.object();
delete reply;
return jsonObj;
}
else {
//failure
delete reply;
throw invalid_argument("API request failed.");
}
}
// if API is not used, open locally stored data
QString filePath = ("../../res/mock/" + shortName + ".json").c_str();
QFile file( filePath );
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
throw invalid_argument("Could not open file to read data of the given discipline.");
} else {
QString text = file.readAll();
file.close();
return (QJsonDocument::fromJson(text.toUtf8())).object();
}
}
/**
* @brief OlympicsAPI::getDisciplineShort Get the discipline's short name defined by the IOC (International Olympic Committee)
* @param sport The sport you want to get the name from.
* @return The short name as a string.
*/
string OlympicsAPI::getDisciplineShort(OlympicsAPI::Disciplines sport) {
switch (sport) {
case OlympicsAPI::Disciplines::AquaticsArtisticSwimming: return "SWA";
case OlympicsAPI::Disciplines::AquaticsDiving: return "DIV";
case OlympicsAPI::Disciplines::AquaticsMarathonSwimming: return "OWS";
case OlympicsAPI::Disciplines::AquaticsSwimming: return "SWM";
case OlympicsAPI::Disciplines::AquaticsWaterPolo: return "WPO";
case OlympicsAPI::Disciplines::Archery: return "ARC";
case OlympicsAPI::Disciplines::Athletics: return "ATH";
case OlympicsAPI::Disciplines::Badminton: return "BDM";
case OlympicsAPI::Disciplines::Basketball3v3: return "BK3";
case OlympicsAPI::Disciplines::Basketball: return "BKB";
case OlympicsAPI::Disciplines::Boxing: return "BOX";
case OlympicsAPI::Disciplines::Breaking: return "BKG";
case OlympicsAPI::Disciplines::CanoeingSprint: return "CSP";
case OlympicsAPI::Disciplines::CanoeingSlalom: return "CSL";
case OlympicsAPI::Disciplines::CyclingBMXFreestyle: return "BMF";
case OlympicsAPI::Disciplines::CyclingBMXRacing: return "BMX";
case OlympicsAPI::Disciplines::CyclingMaountainBike: return "MTB";
case OlympicsAPI::Disciplines::CyclingRoad: return "CRD";
case OlympicsAPI::Disciplines::CyclingTrack: return "CTR";
case OlympicsAPI::Disciplines::EquestrianDressage: return "EDR";
case OlympicsAPI::Disciplines::EquestrianEventing: return "EVE";
case OlympicsAPI::Disciplines::EquestrianJumping: return "EJP";
case OlympicsAPI::Disciplines::Fencing: return "FEN";
case OlympicsAPI::Disciplines::FieldHockey: return "HOC";
case OlympicsAPI::Disciplines::Football: return "FBL";
case OlympicsAPI::Disciplines::Golf: return "GLF";
case OlympicsAPI::Disciplines::GymnasticsArtistic: return "GAR";
case OlympicsAPI::Disciplines::GymnasticsRhythmic: return "GRY";
case OlympicsAPI::Disciplines::GymnasticsTrampoline: return "GTR";
case OlympicsAPI::Disciplines::HandballIndoor: return "HBL";
case OlympicsAPI::Disciplines::Judo: return "JUD";
case OlympicsAPI::Disciplines::ModernPentathlon: return "MPN";
case OlympicsAPI::Disciplines::Rowing: return "ROW";
case OlympicsAPI::Disciplines::Rugby7: return "RU7";
case OlympicsAPI::Disciplines::Sailing: return "SAL";
case OlympicsAPI::Disciplines::Shooting: return "SHO";
case OlympicsAPI::Disciplines::Skateboarding: return "SKB";
case OlympicsAPI::Disciplines::SportClimbing: return "CLB";
case OlympicsAPI::Disciplines::Surfing: return "SRF";
case OlympicsAPI::Disciplines::TableTennis: return "TTE";
case OlympicsAPI::Disciplines::Taekwondo: return "TKW";
case OlympicsAPI::Disciplines::Tennis: return "TEN";
case OlympicsAPI::Disciplines::Triathlon: return "TRI";
case OlympicsAPI::Disciplines::VolleyballBeach: return "VBV";
case OlympicsAPI::Disciplines::VolleyballIndoor: return "VVO";
case OlympicsAPI::Disciplines::Weightlifting: return "WLF";
case OlympicsAPI::Disciplines::WrestlingFreestyle: return "WRE";
case OlympicsAPI::Disciplines::WrestlingGrecoRoman: return "WRG";
default: return "ARC"; // default, which should not be possible, because of enum
}
}

View file

@ -1,77 +0,0 @@
#ifndef ITAT_CHALLANGE_OLYMPICS_OLYMPICSAPI_H
#define ITAT_CHALLANGE_OLYMPICS_OLYMPICSAPI_H
#define API_LINK "https://sph-s-api.olympics.com/summer/schedules/api/ENG/schedule/discipline/"
#include <string>
#include <QJsonObject>
// TODO: change this to true to use the olympics api, instead of the mock date in res/mock/
#define USE_API_REQUEST false
using namespace std;
class OlympicsAPI {
public:
enum Disciplines {
AquaticsArtisticSwimming,
AquaticsDiving,
AquaticsMarathonSwimming,
AquaticsSwimming,
AquaticsWaterPolo,
Archery,
Athletics,
Badminton,
Basketball3v3,
Basketball,
Boxing,
Breaking,
CanoeingSprint,
CanoeingSlalom,
CyclingBMXFreestyle,
CyclingBMXRacing,
CyclingMaountainBike,
CyclingRoad,
CyclingTrack,
EquestrianDressage,
EquestrianEventing,
EquestrianJumping,
Fencing,
FieldHockey,
Football,
Golf,
GymnasticsArtistic,
GymnasticsRhythmic,
GymnasticsTrampoline,
HandballIndoor,
Judo,
ModernPentathlon,
Rowing,
Rugby7,
Sailing,
Shooting,
Skateboarding,
SportClimbing,
Surfing,
TableTennis,
Taekwondo,
Tennis,
Triathlon,
VolleyballBeach,
VolleyballIndoor,
Weightlifting,
WrestlingFreestyle,
WrestlingGrecoRoman
};
QJsonObject getSportData(Disciplines sport);
string getDisciplineShort(Disciplines sport);
};
#endif //ITAT_CHALLANGE_OLYMPICS_OLYMPICSAPI_H

View file

@ -20,8 +20,8 @@
// console output
#include <QDebug>
// #include <iostream>
#include "../model/Sport.h"
#include "../model/FilterModel.h"
#include "../model/SportModel.h"
int main(int argc, char *argv[])
{

View file

@ -1,15 +1,126 @@
#include "Competitor.h"
/**
* Reads certain properties from a competitor json object.
* These are: code, name, noc, results
*
* For further information on 'results' see [Competitor::setResult].
*
* Does not set the amounts of medals. For this, call [Competitor::setMedals].
*
* @param competitor The competitor as a QJsonObject.
* @return True if successful.
*/
bool Competitor::setCompetitor(const QJsonObject &competitor) {
if (!competitor.contains("code")
|| !competitor.contains("name")
|| !competitor.contains("noc")) {
throw invalid_argument("Not a competitor object.");
return false;
}
this->code = competitor["code"].toString();
this->name = competitor["name"].toString();
this->noc = competitor["noc"].toString();
setCode(competitor["code"].toInt());
setName(competitor["name"].toString());
setNOC(competitor["noc"].toString());
if (!competitor.contains("results")) return false;
QJsonObject results = competitor["results"].toObject();
return setResults(results);
}
/**
* Copies all values of a given competitor.
*
* @param competitor The competitor to copy.
*/
void Competitor::setCompetitor(const Competitor &competitor) {
setCode(competitor.m_code);
setName(competitor.m_name);
setNOC(competitor.m_noc);
setMark(competitor.m_mark);
setMedalType(competitor.m_medalType);
setStatistic(competitor.m_statistic);
setGold(competitor.m_gold);
setSilver(competitor.m_silver);
setBronze(competitor.m_bronze);
}
/**
* Replaces/sets the results of a competitor.
*
* @param results The results of the competitor.
* @return True, if successful.
*/
bool Competitor::setResults(const QJsonObject &results) {
if (!results.contains("mark")
|| !results.contains("medalType")) {
return false;
}
setMark(results["mark"].toString());
setMedalType(results["medalType"].toString());
return true;
}
/**
* Replaces/sets the won medals of a competitor.
*
* @param medals The won medals with their amount.
* @return True, if successful.
*/
bool Competitor::setMedals(const map<QString, int> &medals) {
if (medals.find("ME_GOLD") == medals.end()
|| medals.find("ME_SILVER") == medals.end()
|| medals.find("ME_BRONZE") == medals.end()) return false;
setGold(medals.find("ME_GOLD")->second);
setSilver(medals.find("ME_SILVER")->second);
setBronze(medals.find("ME_BRONZE")->second);
return true;
}
/**
* Static compare method, which can compare the result times or points of two competitors.
* Returns true, if the left competitor (lComp) got a real lesser score than the right competitor (rComp).
*
* @param lComp First competitor to compare.
* @param rComp Second competitor to compare.
* @return True, if the second competitor got a higher score.
*/
bool Competitor::compareMark(Competitor lComp, Competitor rComp) {
QString l = lComp.mark();
QString r = rComp.mark();
// check if values are numerical (-> not time values)
if (!l.contains(":") || !r.contains(":")) {
return l.toFloat() < r.toFloat();
}
// compare time values if not numerical
QString lTime(""), rTime("");
for (QChar c : l) if (c.isDigit()) lTime.append(c);
for (QChar c : r) if (c.isDigit()) rTime.append(c);
return lTime.compare(rTime) < 0;
}
/**
* Static compare method, which can compare the amount of medals of two competitors.
* Gold has the highest priority, then m_silver and finally m_bronze.
*
* @param lComp First competitor to compare.
* @param rComp Second competitor to compare.
* @return True, if the second competitor got more or higher medals.
*/
bool Competitor::compareMedals(Competitor lComp, Competitor rComp) {
// create difference between medal amounts
int gold = lComp.gold() - rComp.gold();
int silver = lComp.silver() - rComp.silver();
int bronze = lComp.bronze() - rComp.bronze();
// compare medal differences
return gold < 0 || (gold == 0 && (silver < 0 || (silver == 0 && bronze < 0)));
}

View file

@ -1,39 +1,95 @@
#ifndef ITAT_CHALLANGE_OLYMPICS_COMPETITOR_H
#define ITAT_CHALLANGE_OLYMPICS_COMPETITOR_H
#pragma once
#include <QString>
#include <QMap>
#include <QJsonObject>
#include <stdexcept>
#include <QObject>
#include <qqml.h>
using namespace std;
class Competitor {
class Competitor : public QObject {
Q_OBJECT
Q_PROPERTY(int code READ code NOTIFY codeChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString noc READ noc NOTIFY nocChanged)
// results in a certain event/category
Q_PROPERTY(QString mark READ mark NOTIFY markChanged)
Q_PROPERTY(QString medalType READ medalType NOTIFY medalTypeChanged)
Q_PROPERTY(QString statistic READ statistic NOTIFY statisticChanged)
// medal amounts in the whole discipline
Q_PROPERTY(int gold READ gold NOTIFY goldChanged)
Q_PROPERTY(int silver READ silver NOTIFY silverChanged)
Q_PROPERTY(int bronze READ bronze NOTIFY bronzeChanged)
public:
Competitor(const Competitor &competitor) {
this->code = competitor.code;
this->name = competitor.name;
this->noc = competitor.noc;
}
explicit Competitor(QObject *parent) : QObject(parent) {}
Competitor(const QJsonObject &competitor) {
setCompetitor(competitor);
}
// getter
int code() const { return this->m_code; }
QString name() const { return this->m_name; }
QString noc() const { return this->m_noc; }
QString mark() const { return this->m_mark; }
QString medalType() const { return this->m_medalType; }
QString statistic() const { return this->m_statistic; }
int gold() const { return this->m_gold; }
int silver() const { return this->m_silver; }
int bronze() const { return this->m_bronze; }
QString getCode() { return this->code; }
QString getName() { return this->name; }
QString getNOC() { return this->noc; }
// setter
void setCode(int code) { this->m_code = code; }
void setName(QString name) { this->m_name = name; }
void setNOC(QString noc) { this->m_noc = noc; }
void setMark(QString mark) { this->m_mark = mark; }
void setMedalType(QString medalType) { this->m_medalType = medalType; }
void setStatistic(QString stat) { this->m_statistic = stat; }
void setGold(int gold) { this->m_gold = gold; }
void setSilver(int silver) { this->m_silver = silver; }
void setBronze(int bronze) { this->m_bronze = bronze; }
bool setResults(const QJsonObject &results);
bool setMedals(const map<QString, int> &medals);
bool setCompetitor(const QJsonObject &competitor);
void setCompetitor(const Competitor &competitor);
static bool compareName(Competitor lComp, Competitor rComp) {
return lComp.m_name.compare(rComp.m_name) < 0;
}
static bool compareNOC(Competitor lComp, Competitor rComp) {
return lComp.m_noc.compare(rComp.m_noc) < 0;
}
static bool compareMark(Competitor lComp, Competitor rComp);
static bool compareMedals(Competitor lComp, Competitor rComp);
signals:
void codeChanged();
void nameChanged();
void nocChanged();
void markChanged();
void medalTypeChanged();
void statisticChanged();
void goldChanged();
void silverChanged();
void bronzeChanged();
private:
QString code;
QString name;
QString noc;
int m_code;
QString m_name;
QString m_noc;
QString m_mark;
QString m_medalType;
QString m_statistic;
int m_gold;
int m_silver;
int m_bronze;
};
#endif //ITAT_CHALLANGE_OLYMPICS_COMPETITOR_H

View file

@ -1,15 +0,0 @@
#include "CompetitorWithResults.h"
bool CompetitorWithResults::setResults(const QJsonObject &results) {
if (!results.contains("mark")
|| !results.contains("medalType")) {
throw invalid_argument("Results object of competitor is incomplete.");
}
this->results = {
{QString("mark"), results["mark"].toString()},
{QString("medalType"), results["medalType"].toString()}
};
return true;
}

View file

@ -1,28 +0,0 @@
#ifndef ITAT_CHALLANGE_OLYMPICS_COMPETITORWITHRESULTS_H
#define ITAT_CHALLANGE_OLYMPICS_COMPETITORWITHRESULTS_H
#include "Competitor.h"
#include <QString>
#include <QMap>
#include <QJsonObject>
#include <stdexcept>
class CompetitorWithResults : public Competitor {
public:
CompetitorWithResults(const QJsonObject &competitor) : Competitor(competitor) {
if (!competitor.contains("results")) throw invalid_argument("Competitor does not contain results.");
QJsonObject results = competitor["results"].toObject();
setResults(results);
}
bool setResults(const QJsonObject &results);
private:
QMap<QString, QString> results;
};
#endif //ITAT_CHALLANGE_OLYMPICS_COMPETITORWITHRESULTS_H

View file

@ -1,22 +1,21 @@
#include <QObject>
#include "EventInfo.h"
EventInfo::EventInfo(QObject *parent) : QObject(parent) {
}
QString EventInfo::eventName() const {
return m_eventName;
return this->m_eventName;
}
void EventInfo::setEventName(const QString &newEventName) {
m_eventName = newEventName;
}
QList<QString> EventInfo::competitors() const {
QList<Competitor*> EventInfo::competitors() const {
return m_competitors;
}
void EventInfo::setCompetitors(const QList<QString> &newCompetitors) {
m_competitors = newCompetitors;
void EventInfo::setCompetitors(const QList<Competitor*> &newCompetitors) {
this->m_competitors = newCompetitors;
}

View file

@ -1,28 +1,27 @@
#ifndef ITAT_CHALLANGE_OLYMPICS_EVENT_H
#define ITAT_CHALLANGE_OLYMPICS_EVENT_H
#pragma once
#include "Competitor.h"
#include <QObject>
#include <QAbstractListModel>
#include <qqml.h>
class EventInfo : QObject {
class EventInfo : public QObject {
Q_OBJECT
// QML_ELEMENT
Q_PROPERTY(QString eventName READ eventName WRITE setEventName);
Q_PROPERTY(QList<QString> competitors READ competitors WRITE setCompetitors);
Q_PROPERTY(QString eventName READ eventName CONSTANT)
Q_PROPERTY(QList<Competitor*> competitors READ competitors CONSTANT)
public:
public:
explicit EventInfo(QObject *parent = nullptr);
QString eventName() const;
void setEventName(const QString &newEventName);
QList<QString> competitors() const;
void setCompetitors(const QList<QString> &newCompetitors);
QList<Competitor*> competitors() const;
void setCompetitors(const QList<Competitor*> &newCompetitors);
private:
private:
QString m_eventName;
QList<QString> m_competitors;
QList<Competitor*> m_competitors;
};
#endif

View file

@ -1,5 +1,5 @@
#include "FilterModel.h"
#include "Sport.h"
#include "SportModel.h"
FilterModel::FilterModel(QObject *parent)
: QSortFilterProxyModel(parent) {

View file

@ -1,18 +0,0 @@
#include "MedalWinner.h"
bool MedalWinner::setMedals(const QJsonObject &medals) {
if (!medals.contains("ME_GOLD")
|| !medals.contains("ME_SILVER")
|| !medals.contains("ME_BRONZE")) {
throw invalid_argument("Medal object of competitor is incomplete.");
}
this->wonMedals = {
{QString("ME_GOLD"), medals["ME_GOLD"].toString()},
{QString("ME_SILVER"), medals["ME_SILVER"].toString()},
{QString("ME_BRONZE"), medals["ME_BRONZE"].toString()}
};
return true;
}

View file

@ -1,35 +0,0 @@
#ifndef ITAT_CHALLANGE_OLYMPICS_MEDALWINNER_H
#define ITAT_CHALLANGE_OLYMPICS_MEDALWINNER_H
#include "Competitor.h"
#include <QMap>
#include <QJsonObject>
#include <stdexcept>
class MedalWinner : public Competitor {
public:
MedalWinner(const MedalWinner &medalWinner) : Competitor(medalWinner) {
this->wonMedals = {
{QString("ME_GOLD"), medalWinner.wonMedals.value("ME_GOLD")},
{QString("ME_SILVER"), medalWinner.wonMedals.value("ME_SILVER")},
{QString("ME_BRONZE"), medalWinner.wonMedals.value("ME_BRONZE")}
};
}
MedalWinner(const QJsonObject &competitor) : Competitor(competitor) {
if (competitor.contains("medals")) throw invalid_argument("Competitor has no medals.");
QJsonObject medals = competitor["medals"].toObject();
setMedals(medals);
}
bool setMedals(const QJsonObject &medals);
private:
QMap<QString, QString> wonMedals;
};
#endif //ITAT_CHALLANGE_OLYMPICS_MEDALWINNER_H

View file

@ -1,127 +0,0 @@
#ifndef ITAT_CHALLANGE_OLYMPICS_SPORT_H
#define ITAT_CHALLANGE_OLYMPICS_SPORT_H
#include <QAbstractListModel>
#include <QNetworkAccessManager>
#include <qcontainerfwd.h>
#include <set>
#include <QJsonObject>
#include <QJsonDocument>
#include <QString>
#include "EventInfo.h"
using namespace std;
class SportModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(QString discipline READ discipline WRITE setDiscipline NOTIFY disciplineChanged);
public:
enum Role {
EventName = Qt::UserRole + 1,
Competitors
};
explicit SportModel(QObject *parent = nullptr);
virtual int rowCount(const QModelIndex &parent) const override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual QHash<int, QByteArray> roleNames() const override;
QString discipline() const;
void setDiscipline(const QString &discipline);
public slots:
void request(QString discipline);
void parseData();
signals:
void disciplineChanged();
private:
QList<EventInfo*> m_sportList;
QString m_discipline;
QNetworkAccessManager m_networkManager;
QNetworkReply *m_reply = nullptr;
};
class Sport {
public:
Sport(QJsonObject discipline) {
this->discipline = discipline;
}
set<QString> getCategories();
QJsonArray getCompetitorsByCategory(QString category);
QJsonArray getCompetitorsWithMedal();
// filter to change the current competitor array
void lastName(QJsonArray &competitors);
void filterByName(QJsonArray &competitors, QString name);
void filterByCountry(QJsonArray &competitors, QString nocShort);
// sort functions to change the order of the current competitor array
void sortByName(QJsonArray &competitors);
void sortByCountry(QJsonArray &competitors);
void sortByResult(QJsonArray &competitors);
void reverseOrder(QJsonArray &competitors);
// statistic function(s)
void addRelativeToFirst(QJsonArray &competitors);
void setDiscipline(QJsonObject discipline) {
this->discipline = QJsonObject(discipline);
}
private:
/*
* Analysis of provided competitor objects:
*
* Attributes:
* - code
* - noc (national olympics comittee)
* - name (sometimes the country name? mostly the competitors name)
* - order
* [- results] (only if the results are out!)
*
*
* Analysis of provided result objects:
*
* Attributes:
* - position
* - mark
* - medalType
* - irk
* [- winnerLoserTie] (only if provided in the discipline?)
*
* Analysis of where to find the medal winners:
*
* Search for ... in category name.
* - "Bronze"
* - "Gold"
* - "Final"
*
* ! ATTENTION !
* When searching for "Final" there might be "Final A", "Final B", etc.
* The results will only be in ONE of these categories!
* -> which is good... cause then we can count occurences.
*/
QJsonObject discipline;
void filterCompetitors(QJsonArray &competitors, QString attribute, QString filter);
void sortCompetitors(QJsonArray &competitors, function<bool (const QJsonValue &left, const QJsonValue &right)> compare);
bool validateDiscipline();
QJsonObject createCompetitorWithMedals(QJsonObject medalComp);
float calcRelativeStat(QString ref, QString val);
};
#endif

View file

@ -1,4 +1,4 @@
#include "Sport.h"
#include "SportModel.h"
#include "Competitor.h"
// categories
@ -8,8 +8,7 @@
#include <set>
// sorting and filtering
#include <map>
#include <algorithm>
//#include <algorithm>
#include <regex>
// float to string formatting
@ -42,7 +41,7 @@ QVariant SportModel::data(const QModelIndex &index, int role) const {
return event->eventName();
case Competitors:
return event->competitors();
return QVariant::fromValue(event->competitors());
}
}
@ -82,14 +81,14 @@ void SportModel::parseData() {
qDeleteAll(m_sportList);
m_sportList.clear();
QByteArray strReply = m_reply->readAll();
//parse json
// qDebug() << "Response:" << strReply;
QJsonDocument jsonDocument = QJsonDocument::fromJson(strReply);
map<QString, map<QString, int>> medals = getMedalsOfCompetitors();
QJsonArray sports = jsonDocument["units"].toArray();
for (const auto &sport : sports) {
QJsonObject entry = sport.toObject();
@ -97,15 +96,20 @@ void SportModel::parseData() {
EventInfo *event = new EventInfo(this);
event->setEventName(entry["eventUnitName"].toString());
QList<QString> competitors;
QList<Competitor*> competitors;
for (const auto &competitor : entry["competitors"].toArray()) {
competitors << competitor.toObject()["name"].toString();
Competitor *comp = new Competitor(this);
comp->setCompetitor(competitor.toObject());
if (medals.find(comp->name()) != medals.end()) comp->setMedals(medals.find(comp->name())->second);
competitors << comp;
}
addRelativeToFirst(competitors);
event->setCompetitors(competitors);
qDebug() << entry["eventUnitName"].toString();
m_sportList << event;
}
endResetModel();
}
}
@ -124,60 +128,17 @@ QJsonArray filter(QJsonArray input, function<bool (QJsonObject)> eval) {
return output;
}
// static compare function for specific attribute in competitors
function<bool (const QJsonValue &left, const QJsonValue &right)> genCompare(QString attribute) {
return [attribute](const QJsonValue &left, const QJsonValue &right) {
QString l = left.toObject()[attribute].toString();
QString r = right.toObject()[attribute].toString();
return l.compare(r) < 0;
};
}
// static compare function for the results of a competitor in a specific competition (also called mark)
bool compareMark(const QJsonValue &left, const QJsonValue &right) {
// check if one competitor has no mark
QJsonObject l = left.toObject();
if (!l.contains("results")) return true;
QJsonObject r = right.toObject();
if (!r.contains("results")) return false;
QString lMark = l["results"].toObject()["mark"].toString();
QString rMark = r["results"].toObject()["mark"].toString();
// check if the marks are numerical values
if (!lMark.contains(":") && !rMark.contains(":")) return lMark.toFloat() < rMark.toFloat();
// compare time values if not numerical
QString lTime(""), rTime("");
for (QChar c : lMark) if (c.isDigit()) lTime.append(c);
for (QChar c : rMark) if (c.isDigit()) rTime.append(c);
return lTime.compare(rTime) < 0;
}
// static compare function for the amount of medals a competitor has gotten
bool compareMedals(const QJsonValue &left, const QJsonValue &right) {
QJsonObject lMedals = left.toObject()["medals"].toObject();
QJsonObject rMedals = right.toObject()["medals"].toObject();
int gold = lMedals["ME_GOLD"].toInt() - rMedals["ME_GOLD"].toInt();
int silver = lMedals["ME_SILVER"].toInt() - rMedals["ME_SILVER"].toInt();
int bronze = lMedals["ME_BRONZE"].toInt() - rMedals["ME_BRONZE"].toInt();
return gold < 0 || (gold == 0 && (silver < 0 || (silver == 0 && bronze < 0)));
}
/**
* @brief Sport::lastName Reduce the full name to the part that is marked in capital letters (probably last name).
* @param competitors The competitors of one category.
*/
void Sport::lastName(QJsonArray &competitors) {
void SportModel::lastName(QList<Competitor*> &competitors) {
// validate competitors
if (competitors.isEmpty() || !competitors[0].toObject().contains("name")) return;
if (competitors.isEmpty()) return;
for (int i = 0; i < competitors.size(); ++i) {
string fullName = competitors[i].toObject()["name"].toString().toUtf8().constData();
for (int i = 0; i < competitors.size(); i++) {
Competitor* comp = competitors.value(i);
string fullName = comp->name().toUtf8().constData();
// regex to identify names, written in CAPS
regex r("[A-Z']{2,}");
@ -190,15 +151,10 @@ void Sport::lastName(QJsonArray &competitors) {
for (string s : m) lastName = lastName + s + " ";
// remove last space
QJsonValue nameValue = QJsonValue(QString(lastName.substr(0, lastName.size() - 1).c_str()));
QString name = QString(lastName.substr(0, lastName.size() - 1).c_str());
// create new object with replaced name
QJsonObject comp(competitors[i].toObject());
comp.remove("name");
comp.insert("name", nameValue);
// replace competitor in array
competitors.replace(i, comp);
// replace competitor name in list
comp->setName(name);
}
}
@ -206,21 +162,21 @@ void Sport::lastName(QJsonArray &competitors) {
* @brief Sport::validateDiscipline Validates the discipline object. Checks for the units attribute.
* @return True, if discipline contains units.
*/
bool Sport::validateDiscipline() {
return this->discipline.contains("units");
bool SportModel::validateDiscipline() {
return this->o_discipline.contains("units");
}
/**
* @brief Sport::getCategories Reads all possible categories (also called units).
* @return A set of all category names.
*/
set<QString> Sport::getCategories() {
set<QString> SportModel::getCategories() {
set<QString> categoryNames;
if (!validateDiscipline()) return categoryNames;
// search in each unit for the category (named "eventUnitName")
for (const QJsonValueRef &unitRef : this->discipline["units"].toArray()) {
for (const QJsonValueRef &unitRef : this->o_discipline["units"].toArray()) {
QJsonObject unit = unitRef.toObject();
// validate unit
@ -233,44 +189,16 @@ set<QString> Sport::getCategories() {
}
/**
* @brief Sport::getCompetitorsByCategory Searches for all competitors, who took part in the given category.
* @param category The category to search in.
* @return An QJsonArray with all competitors as QJsonValueRef, which can be casted to QJsonObject.
* @brief Sport::getMedalsOfCompetitor Filters all competitors, who have at least one medal. These objects are different from getCompetitorsByCategory !!!
* @return All competitors, who won at least one medal. Structure of one competitor: {code, name, m_noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}}
*/
QJsonArray Sport::getCompetitorsByCategory(QString category) {
QJsonArray competitors;
map<QString, map<QString, int>> SportModel::getMedalsOfCompetitors() {
map<QString, map<QString, int>> competitors;
if (!validateDiscipline()) return competitors;
for (const QJsonValueRef &unitRef : this->discipline["units"].toArray()) {
QJsonObject unit = unitRef.toObject();
// validate unit
if (!unit.contains("eventUnitName") || !unit.contains("competitors")) continue;
// search all units with the same category
if (unit["eventUnitName"].toString().compare(category, Qt::CaseSensitive) != 0) continue;
// add all competitors from one unit
for (const QJsonValueRef &compRef : unit["competitors"].toArray()) {
competitors.push_back(compRef.toObject());
}
}
return QJsonArray(competitors);
}
/**
* @brief Sport::getCompetitorsWithMedal Filters all competitors, who have at least one medal. These objects are different from getCompetitorsByCategory !!!
* @return All competitors, who won at least one medal. Structure of one competitor: {code, name, noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}}
*/
QJsonArray Sport::getCompetitorsWithMedal() {
map<QString, QJsonObject> competitors;
if (!validateDiscipline()) return QJsonArray();
// filter all units, which have medal events
QJsonArray units = filter(this->discipline["units"].toArray(), [](QJsonObject unit){
QJsonArray units = filter(this->o_discipline["units"].toArray(), [](QJsonObject unit){
// search all units with Final, Gold or Bronze in their name, because these are the categories with the medal winners
QString unitName = unit["eventUnitName"].toString();
return unitName.contains("Bronze", Qt::CaseSensitive)
@ -305,56 +233,19 @@ QJsonArray Sport::getCompetitorsWithMedal() {
// check if competitor has other medal(s)
if (competitors.find(name) == competitors.end()) {
competitors.insert({name, createCompetitorWithMedals(medalComp)});
map<QString, int> emptyMedalObject = {
{"ME_GOLD", 0},
{"ME_SILVER", 0},
{"ME_BRONZE", 0}
};
competitors.insert({name, emptyMedalObject});
}
// update the medal count
QJsonObject updatedMedalCount = QJsonObject(competitors.find(name)->second["medals"].toObject());
int amount = updatedMedalCount[medalType].toInt() + 1;
updatedMedalCount.remove(medalType);
updatedMedalCount.insert(medalType, amount);
// create new medals QJsonObject and set it in the map
competitors.find(name)->second["medals"] = updatedMedalCount;
competitors.find(name)->second.find(medalType)->second++;
}
}
// convert map to QJsonArray
QJsonArray output;
for (const pair<QString, QJsonObject> &competitor : competitors) {
output.append(competitor.second);
}
return output;
}
/**
* @brief Sport::createCompetitorWithMedals Creates a competitor QJsonObject with the following attributes: code, name, noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}
* @param comp The original competitor object.
* @return A competitor object with medal counts.
*/
QJsonObject Sport::createCompetitorWithMedals(QJsonObject comp) {
// repair competitor if something is missing
if (!comp.contains("code")) comp.insert("code", "0");
if (!comp.contains("name")) comp.insert("code", "");
if (!comp.contains("noc")) comp.insert("code", "");
// create new competitor QJsonObject and add it to the competitor map
QJsonObject medals {
{"ME_GOLD", 0},
{"ME_SILVER", 0},
{"ME_BRONZE", 0}
};
QJsonObject medalComp {
{"code", comp["code"].toString()},
{"name", comp["name"].toString()},
{"noc", comp["noc"].toString()},
{"medals", medals}
};
return medalComp;
return competitors;
}
/**
@ -362,8 +253,8 @@ QJsonObject Sport::createCompetitorWithMedals(QJsonObject comp) {
* @param competitors The competitors of one category.
* @param name The (part of the) name to search for.
*/
void Sport::filterByName(QJsonArray &competitors, QString name) {
filterCompetitors(competitors, QString("name"), name);
void SportModel::filterByName(QList<Competitor*> &competitors, QString name) {
filterCompetitors(competitors, name);
}
/**
@ -371,8 +262,8 @@ void Sport::filterByName(QJsonArray &competitors, QString name) {
* @param competitors The competitors of one category.
* @param nocShort The (part of the) national olympics comittee short name to search for.
*/
void Sport::filterByCountry(QJsonArray &competitors, QString nocShort) {
filterCompetitors(competitors, QString("noc"), nocShort);
void SportModel::filterByCountry(QList<Competitor*> &competitors, QString nocShort) {
filterCompetitors(competitors, nocShort);
}
/**
@ -381,16 +272,12 @@ void Sport::filterByCountry(QJsonArray &competitors, QString nocShort) {
* @param attribute The attribute to filter by.
* @param filter The string, which should be contained.
*/
void Sport::filterCompetitors(QJsonArray &competitors, QString attribute, QString filter) {
void SportModel::filterCompetitors(QList<Competitor*> &competitors, QString filter) {
for (int i = 0; i < competitors.size(); i++) {
QJsonObject comp = competitors[i].toObject();
if (!comp.contains(attribute) || !comp[attribute].toString().contains(filter, Qt::CaseInsensitive)) {
// remove the competitor, if the attribute does not fit the filter string
competitors.removeAt(i);
if (!competitors.value(i)->noc().contains(filter)) {
competitors.remove(i);
i--;
}
}
}
@ -398,89 +285,77 @@ void Sport::filterCompetitors(QJsonArray &competitors, QString attribute, QStrin
* @brief Sport::sortByName Sort the competitors by their name (alphabetical, ascending).
* @param competitors The competitors of one category.
*/
void Sport::sortByName(QJsonArray &competitors) {
sortCompetitors(competitors, genCompare( QString("name") ));
}
//void SportModel::sortByName(QList<Competitor*> &competitors) {
// if (competitors.isEmpty()) return;
// std::sort(competitors.begin(), competitors.end(), Competitor::compareName);
//}
/**
* @brief Sport::sortByCountry Sort the competitors by their national olympic comittee short name (alphabetical, ascending).
* @param competitors The competitors of one category.
*/
void Sport::sortByCountry(QJsonArray &competitors) {
sortCompetitors(competitors, genCompare( QString("noc") ));
}
//void SportModel::sortByCountry(QList<Competitor*> &competitors) {
// if (competitors.isEmpty()) return;
// std::sort(competitors.begin(), competitors.end(), Competitor::compareNOC);
//}
/**
* @brief Sport::sortByResult Sort the competitors by their results in one specific category (numerical, ascending).
* @param competitors The competitors of one category.
*/
void Sport::sortByResult(QJsonArray &competitors) {
if (competitors.isEmpty()) return;
QJsonObject comp = competitors[0].toObject();
if (comp.contains("results")) sortCompetitors(competitors, compareMark);
else if (comp.contains("medals")) sortCompetitors(competitors, compareMedals);
}
//void SportModel::sortByResult(QList<Competitor*> &competitors) {
// if (competitors.isEmpty()) return;
// std::sort(competitors.begin(), competitors.end(), Competitor::compareMark);
//}
/**
* @brief Sport::sortCompetitors Sorts the given QJsonArray according to the compare function.
* @brief Sport::sortByMedals Sort the competitors by their medal amounts in one specific category (numerical, ascending).
* @param competitors The competitors of one category.
* @param compare A function to compare two competitors with each other. This defines the order.
*/
void Sport::sortCompetitors(QJsonArray &competitors, function<bool (const QJsonValue &left, const QJsonValue &right)> compare) {
make_heap(competitors.begin(), competitors.end(), compare);
sort_heap(competitors.begin(), competitors.end(), compare);
}
//void SportModel::sortByMedals(QList<Competitor*> &competitors) {
// if (competitors.isEmpty()) return;
// std::sort(competitors.begin(), competitors.end(), Competitor::compareMedals);
//}
/**
* @brief Sport::reverseOrder Reverses the order of the competitors.
* @param competitors The competitors of one category.
*/
void Sport::reverseOrder(QJsonArray &competitors) {
void SportModel::reverseOrder(QList<Competitor*> &competitors) {
int iterations = competitors.size() / 2; // automatically rounds down
for (int i = 0; i < iterations; i++) {
QJsonObject temp = competitors[i].toObject();
competitors[i] = competitors[competitors.size() - 1 - i].toObject();
competitors[competitors.size() - 1 - i] = temp;
Competitor *left = competitors.value(i);
Competitor *right = competitors.value(competitors.size() - 1 - i);
competitors.replace(i, right);
competitors.replace(competitors.size() - 1 - i, left);
}
}
/**
* @brief Sport::addRelativeToFirst Adds a relative value to the result of all competitors. Relative to the first competitor in the QJsonArray.
* Stores the statistic in obj->results->stat for each competitor.
* Stores the m_statistic in obj->results->stat for each competitor.
* @param competitors The competitors of one category.
*/
void Sport::addRelativeToFirst(QJsonArray &competitors) {
void SportModel::addRelativeToFirst(QList<Competitor*> &competitors) {
if (competitors.isEmpty()) return;
QJsonObject reference = competitors[0].toObject();
// validate competitors
if (!reference.contains("results")) return;
QString refVal = reference["results"].toObject()["mark"].toString();
QString reference = competitors.value(0)->mark();
for (int i = 0; i < competitors.size(); i++) {
QJsonObject competitor = competitors[i].toObject();
QJsonObject results = competitor["results"].toObject();
Competitor *comp = competitors.value(i);
if (results.contains("stat")) results.remove("stat");
QString result = comp->mark();
// format relative float value to string with 2 digits after decimal point and sign
stringstream sstream;
sstream << fixed << setprecision(2) << calcRelativeStat(refVal, results["mark"].toString());
sstream << fixed << setprecision(2) << calcRelativeStat(reference, result);
QString stat(sstream.str().c_str());
stat.append("%");
if (stat.at(0).isNumber()) stat = QString("+").append(stat);
results.insert("stat", stat);
competitor.remove("results");
competitor.insert("results", results);
competitors.replace(i, competitor);
comp->setStatistic(stat);
}
}
@ -491,7 +366,7 @@ void Sport::addRelativeToFirst(QJsonArray &competitors) {
* @param val The value to calculate the deviation from.
* @return The deviation from ref to val in percent.
*/
float Sport::calcRelativeStat(QString ref, QString val) {
float SportModel::calcRelativeStat(QString ref, QString val) {
// check if the value is not a time
if (!ref.contains(":") && !val.contains(":")) {
float fRef = ref.toFloat();

83
src/model/SportModel.h Normal file
View file

@ -0,0 +1,83 @@
#pragma once
#include <QAbstractListModel>
#include <QNetworkAccessManager>
#include <qcontainerfwd.h>
#include <set>
#include <QJsonObject>
#include <QJsonDocument>
#include <QString>
#include <QList>
#include "EventInfo.h"
using namespace std;
class SportModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString discipline READ discipline WRITE setDiscipline NOTIFY disciplineChanged);
public:
enum Role
{
EventName = Qt::UserRole + 1,
Competitors
};
explicit SportModel(QObject *parent = nullptr);
void setDiscipline(QJsonObject discipline)
{
this->o_discipline = QJsonObject(discipline);
}
virtual int rowCount(const QModelIndex &parent) const override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual QHash<int, QByteArray> roleNames() const override;
set<QString> getCategories();
map<QString, map<QString, int>> getMedalsOfCompetitors();
// filter to change the current competitor list
void lastName(QList<Competitor*> &competitors);
void filterByName(QList<Competitor*> &competitors, QString name);
void filterByCountry(QList<Competitor*> &competitors, QString nocShort);
// sort functions to change the order of the current competitor list
// void sortByName(QList<Competitor*> &competitors);
// void sortByCountry(QList<Competitor*> &competitors);
// void sortByResult(QList<Competitor*> &competitors);
// void sortByMedals(QList<Competitor*> &competitors);
void reverseOrder(QList<Competitor*> &competitors);
// statistic function
void addRelativeToFirst(QList<Competitor*> &competitors);
QString discipline() const;
void setDiscipline(const QString &discipline);
public slots:
void request(QString discipline);
void parseData();
signals:
void disciplineChanged();
private:
QList<EventInfo *> m_sportList;
QString m_discipline;
QNetworkAccessManager m_networkManager;
QNetworkReply *m_reply = nullptr;
// data from api
QJsonObject o_discipline;
bool validateDiscipline();
void filterCompetitors(QList<Competitor*> &competitors, QString filter); // TODO ref instead of obj
// function for statistic calculation
float calcRelativeStat(QString ref, QString val);
};