527 lines
23 KiB
QML
527 lines
23 KiB
QML
/*
|
|
blueROCK - for digital rock
|
|
Copyright (C) 2019 Dorian Zedler
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import QtQuick 2.10
|
|
import QtQuick.Controls 2.4
|
|
import QtQuick.Layouts 1.3
|
|
import QtQuick.Controls.Material 2.1
|
|
import QtGraphicalEffects 1.0
|
|
|
|
Item {
|
|
id: control
|
|
|
|
property var flowchartData
|
|
property var allFlowchartData
|
|
property int rounds: 0
|
|
|
|
property int refreshes: 0
|
|
property int roundRefreshes: 1
|
|
|
|
property int roundCount: 0
|
|
|
|
onFlowchartDataChanged: {
|
|
prepareData()
|
|
}
|
|
|
|
function prepareData() {
|
|
|
|
if(!control.enabled || control.flowchartData === undefined || control.flowchartData['route_names'] === undefined)
|
|
return
|
|
/*refreshes += 1
|
|
|
|
if(refreshes > 2){
|
|
roundRefreshes += 1
|
|
}
|
|
|
|
console.log("refreshes: " + refreshes + " rounds: " + roundRefreshes)
|
|
|
|
// create competition like data (just testing)
|
|
for(var part in flowchartData['participants']){
|
|
if(flowchartData['participants'].hasOwnProperty(part)){
|
|
|
|
for(var r = 2 + roundRefreshes; r < 7; r++){
|
|
delete flowchartData['participants'][part]["result"+r]
|
|
delete flowchartData['participants'][part]["result_rank"+r]
|
|
}
|
|
|
|
if(parseInt(flowchartData['participants'][part]["result_rank0"]) > 14 + refreshes) {
|
|
delete flowchartData['participants'][part]["result_rank2"]
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
delete flowchartData['route_names'][2]
|
|
delete flowchartData['route_names'][3]
|
|
delete flowchartData['route_names'][4]
|
|
delete flowchartData['route_names'][5]
|
|
delete flowchartData['route_names'][6]
|
|
*/
|
|
|
|
//flowchartData['route_names'] = flowchartData['route_names'].slice(0,)
|
|
|
|
// array to store the restructured data
|
|
var allData = []
|
|
control.allFlowchartData = []
|
|
|
|
control.rounds = Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0
|
|
|
|
//console.log(JSON.stringify(flowchartData['route_names']))
|
|
|
|
for(var round in flowchartData['route_names']){
|
|
if(flowchartData['route_names'].hasOwnProperty(round) && parseInt(round) >= 0){
|
|
//console.log(round)
|
|
|
|
if(parseInt(round) === 0){
|
|
// this is the first round
|
|
|
|
// find pairs (always wors vs. best (1-16; 1-15; ...)) (they are sorted by the rank of the better athlete (1-2-3-4-5-6-7-8)
|
|
|
|
var qualificationResults = []
|
|
|
|
for(var x = 0; x < flowchartData['participants'].length; x++){
|
|
qualificationResults.push(flowchartData['participants'][x])
|
|
}
|
|
|
|
qualificationResults.sort(function(a, b) {
|
|
return parseInt(a["result_rank0"]) - parseInt(b["result_rank0"]);
|
|
});
|
|
|
|
var nextRoundPairs = []
|
|
var totalMatches = (parseInt(Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0) + 2)
|
|
var nextRoundMatches = Math.pow(2, totalMatches-1)
|
|
|
|
for(var i = 0; i < nextRoundMatches; i++){
|
|
nextRoundPairs.push([qualificationResults[i], qualificationResults[nextRoundMatches*2-i-1]])
|
|
}
|
|
|
|
// build second round pairs (sorted by the rank of the better athlete and worst vs. best (1-8; 2-7; ... ))
|
|
|
|
var sortedFirstRoundPairs = []
|
|
|
|
for(i = 0; i < nextRoundMatches; i += 1){
|
|
sortedFirstRoundPairs.push([nextRoundPairs.shift(), nextRoundPairs.pop()])
|
|
}
|
|
|
|
// sort these pairs (containing two pairs of athletes) by the rank of the better athlete (1-4;2-3)
|
|
|
|
var finalSortedFirstRoundPairs = []
|
|
|
|
for(i=0; i < nextRoundMatches/4; i++){
|
|
finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[i])
|
|
finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[nextRoundMatches/2-i-1])
|
|
}
|
|
|
|
// convert the list of pairs of pairs of athletes back to a single list of pairs of athletes
|
|
|
|
var finalFirstRoundPairs = []
|
|
|
|
for(i=0; i < finalSortedFirstRoundPairs.length; i++){
|
|
finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][0])
|
|
finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][1])
|
|
}
|
|
|
|
// push the first round to all data
|
|
finalFirstRoundPairs.push(2)
|
|
finalFirstRoundPairs.push(flowchartData['route_names'][2])
|
|
allData.push(finalFirstRoundPairs)
|
|
|
|
}
|
|
else if(parseInt(round) > 0 ){
|
|
// this is not the first round
|
|
|
|
var nextRound = []
|
|
|
|
// only used when the current round is the 1/2 final
|
|
var smallFinal = []
|
|
var Final = []
|
|
|
|
for(var i = 0; i < allData[allData.length-1].length-2; i+=1){
|
|
|
|
var thisPair = allData[allData.length-1][i]
|
|
var thisWinner
|
|
var thisLooser
|
|
var thisWinnerIsFirstOfNewPair = i%2 === 0
|
|
|
|
if(thisPair[0] === undefined || thisPair[1] === undefined){
|
|
continue
|
|
}
|
|
|
|
if(thisWinnerIsFirstOfNewPair){
|
|
nextRound.push([])
|
|
}
|
|
|
|
//thisPair[0]["result_rank"] = thisPair[0]["result_rank"+round]
|
|
//thisPair[1]["result_rank"] = thisPair[1]["result_rank"+round]
|
|
|
|
if(parseInt(thisPair[0]["result_rank"+round]) < parseInt(thisPair[1]["result_rank"+round])){
|
|
thisWinner = thisPair[0]
|
|
thisLooser = thisPair[1]
|
|
}
|
|
else if(parseInt(thisPair[0]["result_rank"+round]) > parseInt(thisPair[1]["result_rank"+round])) {
|
|
thisWinner = thisPair[1]
|
|
thisLooser = thisPair[0]
|
|
}
|
|
else {
|
|
// no result yet!!
|
|
//console.log("got no winner yet, rank 0: " + thisPair[0]["result_rank"+round] + " rank 1: " + thisPair[1]["result_rank"+round])
|
|
continue
|
|
}
|
|
|
|
//console.log(thisWinner['firstname']+" has won in round " + round)
|
|
|
|
if(round - control.rounds === 2){
|
|
// if we are in the 1/2 final
|
|
|
|
Final.push(thisWinner)
|
|
smallFinal.push(thisLooser)
|
|
}
|
|
else {
|
|
nextRound[nextRound.length-1].push(thisWinner)
|
|
}
|
|
}
|
|
|
|
if(smallFinal.length > 0 && Final.length > 0){
|
|
|
|
// Final
|
|
allData.push([Final, parseInt(round)+2, flowchartData['route_names'][String(parseInt(round)+2)] + " / " + flowchartData['route_names'][String(parseInt(round)+1)] ])
|
|
// small Final
|
|
allData.push([smallFinal, parseInt(round)+1])
|
|
|
|
//break
|
|
}
|
|
else {
|
|
nextRound.push(parseInt(round) + 1 )
|
|
nextRound.push(flowchartData['route_names'][parseInt(round) + 1])
|
|
allData.push(nextRound)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
control.allFlowchartData = allData
|
|
control.roundCount = (parseInt(Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0) + 2)
|
|
//console.log(JSON.stringify(allData))
|
|
}
|
|
|
|
ListView {
|
|
id: roundListView
|
|
|
|
property int columnWidth: height * 0.4
|
|
property int columnHeight: height
|
|
|
|
anchors {
|
|
top: parent.top
|
|
bottom: parent.bottom
|
|
horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
width: Math.min((columnWidth + spacing) * model, control.width)
|
|
|
|
spacing: app.height * 0.05
|
|
|
|
orientation: ListView.LeftToRight
|
|
boundsBehavior: ListView.StopAtBounds
|
|
|
|
model: control.roundCount
|
|
|
|
delegate: Item {
|
|
id: roundItem
|
|
|
|
property int thisIndex: index
|
|
property int thisRound: thisRoundIsValid ? control.allFlowchartData[roundItem.thisIndex][control.allFlowchartData[roundItem.thisIndex].length-2]:-1
|
|
property bool thisRoundIsValid: control.allFlowchartData !== undefined
|
|
&& control.allFlowchartData[roundItem.thisIndex] !== undefined
|
|
&& control.allFlowchartData[roundItem.thisIndex].length > 2
|
|
|
|
property bool thisIsLastRound: thisIndex === control.roundCount - 1
|
|
property bool thisIsSemiFinal: thisIndex === control.roundCount - 2 && rectRep.model === 2
|
|
|
|
property int tileSize: (roundItem.height / 8 - roundNameLa.height) * 1.45
|
|
|
|
width: roundListView.columnWidth
|
|
height: roundListView.columnHeight
|
|
|
|
Column {
|
|
id: roundCol
|
|
|
|
anchors.fill: parent
|
|
|
|
Label {
|
|
id: roundNameLa
|
|
width: parent.width
|
|
height: control.height * 0.05
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
fontSizeMode: Text.Fit
|
|
font.pixelSize: height * 0.6
|
|
minimumPixelSize: 1
|
|
font.bold: true
|
|
text: roundItem.thisRoundIsValid
|
|
&& control.allFlowchartData[roundItem.thisIndex][control.allFlowchartData[roundItem.thisIndex].length-1] !== undefined ?
|
|
control.allFlowchartData[roundItem.thisIndex][control.allFlowchartData[roundItem.thisIndex].length-1] :
|
|
"-"
|
|
}
|
|
|
|
Repeater {
|
|
id: rectRep
|
|
model: Math.max( Math.pow(2, control.roundCount-1) * Math.pow(0.5, (roundItem.thisIndex)), 2)
|
|
delegate: Item {
|
|
id: matchItm
|
|
|
|
property bool lowerPart: (index%2 > 0)
|
|
|
|
property var matchData: roundItem.thisRoundIsValid ?
|
|
control.allFlowchartData[
|
|
thisIsSmallFinal ?
|
|
roundItem.thisIndex+1 :
|
|
roundItem.thisIndex
|
|
][ thisIsSmallFinal ? 0:matchItm.thisIndex]:
|
|
undefined
|
|
|
|
property int thisIndex: index
|
|
property int thisRound: parseInt(roundItem.thisRound) - (thisIsSmallFinal ? 1:0)
|
|
property var thisMatchData: thisMatchDataIsValid ? matchData:[]
|
|
|
|
property bool thisMatchDataIsValid: (matchData !== undefined && matchData !== null && typeof matchData === "object" && matchData.length > 0)
|
|
property bool thisMatchIsOver: thisMatchDataIsValid && thisMatchData[0]['result_rank'+thisRound] !== undefined && thisMatchData[1]['result_rank'+thisRound] !== undefined
|
|
|
|
property bool thisIsFinal: roundItem.thisIsLastRound && thisIndex === rectRep.model - 2
|
|
property bool thisIsSmallFinal: roundItem.thisIsLastRound && thisIndex === rectRep.model - 1
|
|
|
|
property int winnerIndex: thisMatchIsOver ? (parseInt(thisMatchData[0]['result_rank'+thisRound]) < parseInt(thisMatchData[1]['result_rank'+thisRound]) ? 0:1) : -1
|
|
|
|
height: !roundItem.thisIsLastRound ?
|
|
(roundItem.height - roundNameLa.height) / rectRep.model - roundCol.spacing :
|
|
(thisIsFinal ?
|
|
(roundItem.height - roundNameLa.height) * 0.5 + roundItem.tileSize * 0.5 :
|
|
(roundItem.height - roundNameLa.height) - (roundItem.height - roundNameLa.height) * 0.5 + roundItem.tileSize * 0.5
|
|
)
|
|
|
|
width: roundItem.width
|
|
|
|
onMatchDataChanged: {
|
|
fadeInPa.start()
|
|
}
|
|
|
|
ParallelAnimation {
|
|
id: fadeInPa
|
|
NumberAnimation { target: matchItm; property: "opacity"; from: 0; to: 1.0; duration: 400 }
|
|
NumberAnimation { target: matchItm; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
|
|
}
|
|
|
|
Canvas {
|
|
id: lineCanvas
|
|
width: app.width; height: app.height
|
|
contextType: "2d"
|
|
|
|
visible: !roundItem.thisIsLastRound
|
|
|
|
property int targetYFactor: matchItm.lowerPart ? -1:1
|
|
|
|
Path {
|
|
id: firstPartPath
|
|
startX: matchItm.width
|
|
startY: matchItm.height * 0.5
|
|
|
|
PathQuad {
|
|
id: firstPartPathQuad
|
|
relativeX: roundListView.spacing * 0.5
|
|
relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor
|
|
relativeControlX: relativeX
|
|
relativeControlY: 0
|
|
}
|
|
|
|
PathQuad {
|
|
relativeX: roundListView.spacing * 0.5
|
|
relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor
|
|
relativeControlX: 0
|
|
relativeControlY: relativeY
|
|
}
|
|
}
|
|
|
|
onPaint: {
|
|
context.strokeStyle = "lightgrey"
|
|
context.lineWidth = matchRect.height * 0.05
|
|
context.path = firstPartPath;
|
|
context.stroke();
|
|
}
|
|
}
|
|
|
|
RectangularGlow {
|
|
id: effect
|
|
anchors.fill: matchRect
|
|
glowRadius: 0
|
|
spread: 0
|
|
opacity: 0.3
|
|
color: "black"
|
|
cornerRadius: matchRect.radius
|
|
}
|
|
|
|
Rectangle {
|
|
id: matchRect
|
|
|
|
anchors {
|
|
centerIn: !matchItm.thisIsFinal ? parent:undefined
|
|
bottom: matchItm.thisIsFinal ? parent.bottom:undefined
|
|
}
|
|
|
|
//anchors.verticalCenterOffset: matchItm.lowerPart ? 10:10
|
|
width: parent.width
|
|
height: roundItem.tileSize
|
|
//scale: 0.9
|
|
color: Material.dialogColor
|
|
radius: height * 0.2
|
|
|
|
Column {
|
|
spacing: 0
|
|
|
|
anchors.fill: parent
|
|
anchors.margins: matchRect.radius * 0.5
|
|
|
|
Repeater {
|
|
// for the two athletes
|
|
model: 2
|
|
|
|
delegate: RowLayout {
|
|
|
|
height: parent.height / 2 - parent.spacing
|
|
width: parent.width
|
|
|
|
spacing: height * 0.1
|
|
|
|
Label {
|
|
Layout.preferredHeight: parent.height
|
|
Layout.preferredWidth: parent.width * 0.08
|
|
|
|
height: parent.height
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignLeft
|
|
font.pixelSize: height * 0.4
|
|
font.bold: true
|
|
opacity: 0.7
|
|
|
|
text: matchItm.thisMatchData[index] !== undefined ?
|
|
(
|
|
parseInt(matchItm.thisMatchData[index]['result_rank0']) < 10 ?
|
|
matchItm.thisMatchData[index]['result_rank0'] + " ":
|
|
matchItm.thisMatchData[index]['result_rank0']
|
|
):
|
|
""
|
|
}
|
|
|
|
Label {
|
|
Layout.preferredHeight: parent.height
|
|
Layout.maximumWidth: parent.width * 0.6
|
|
|
|
|
|
height: parent.height
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignLeft
|
|
font.pixelSize: height * 0.5
|
|
font.bold: matchItm.winnerIndex === index
|
|
elide: "ElideRight"
|
|
|
|
color: matchItm.winnerIndex === index ? Material.color(Material.Green):Material.primaryTextColor
|
|
|
|
text: matchItm.thisMatchData[index] !== undefined ? matchItm.thisMatchData[index]['firstname'] + " " + matchItm.thisMatchData[index]['lastname'] :"-"
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.preferredHeight: parent.height * 0.8
|
|
Layout.preferredWidth: parent.width * 0.13
|
|
|
|
radius: height / 2
|
|
|
|
border.width: 1
|
|
border.color: Material.frameColor
|
|
color: "transparent"
|
|
|
|
Label {
|
|
anchors.fill: parent
|
|
|
|
padding: parent.parent.height * 0.1
|
|
leftPadding: padding * 2
|
|
rightPadding: leftPadding
|
|
|
|
font.pixelSize: parent.height * 0.5
|
|
font.bold: true
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
|
text: matchItm.thisMatchData[index] !== undefined ? matchItm.thisMatchData[index]["start_number"]:""
|
|
}
|
|
}
|
|
|
|
Item {
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
Label {
|
|
Layout.preferredHeight: parent.height
|
|
Layout.preferredWidth: parent.width * 0.15
|
|
|
|
height: parent.height
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignRight
|
|
font.pixelSize: height * 0.5
|
|
font.bold: matchItm.winnerIndex === index
|
|
|
|
color: matchItm.winnerIndex === index ? Material.color(Material.Green):Material.primaryTextColor
|
|
|
|
text: matchItm.thisMatchData[index] !== undefined && matchItm.thisMatchData[index]['result'+matchItm.thisRound] !== undefined ?
|
|
( parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]) ?
|
|
(parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]).toFixed(2))
|
|
: matchItm.thisMatchData[index]['result'+matchItm.thisRound] )
|
|
: "-"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Loader {
|
|
id: blueRockBadgeLoader
|
|
|
|
x: (parent.width - width) / 2
|
|
y: (parent.height - roundNameLa.height - height) / 2 + roundNameLa.height
|
|
|
|
sourceComponent: roundItem.thisIsSemiFinal ? blueRockBadgeComponent:undefined
|
|
|
|
Component {
|
|
id: blueRockBadgeComponent
|
|
|
|
BlueRockBadge {
|
|
width: roundItem.width
|
|
height: width * 0.25
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|