Merge branch 'internal_makeover'

# Conflicts:
#	.gitignore
#	README.md
#	graphics/Buzzer.xcf
#	graphics/icons/startpad.png
#	graphics/icons/user.png
#	graphics/speedclimbing_stopwatch.xcf
#	graphics/startpad.xcf
#	qml/SettingsDialog.qml
#	qml/components/ConnectionDelegate.qml
#	qml/components/ConnectionIcon.qml
#	qml/components/FadeAnimation.qml
#	qml/components/ProgressCircle.qml
#	qml/main.qml
#	qml/qml.qrc
#	shared.qrc
#	sources/appsettings.cpp
#	sources/main.cpp
#	speedclimbing_stopwatch.pro
#	translations/de_DE.qm
#	translations/de_DE.ts
This commit is contained in:
dorian 2019-08-08 19:08:35 +02:00
commit 59bf853f0a
49 changed files with 4250 additions and 694 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/speedclimbing_stopwatch.pro.user
.DS_Store
*.pro.user*

45
CHANGELOG Normal file
View file

@ -0,0 +1,45 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- added profiles dialog
## [0.04] - 2018-08-11
### Added
- buzzer icon in the upper left corner indicating that the buzzer is connected
### Fixed
- start seqnece continues in a buggy way when cancel is being pressed while 'at your marks' or 'ready'
- bug that made the start sequence freeze if a delay of zero or lower or a non valid number was set as delay
### Changed
- increased the size of the back buttons in settings / profiles dialog
## [0.03 - BETA] - 2018-07-29
### Added
- cancel button during start sequence
- new screen in landscape mode
- buttons for settings and profiles
- the screen stays on now
- the volume csontrols control the media volume directly
- settings dialog
- capabilitie to connect to a Buzzer via Wifi
- it is now possible to setup an automatic start sequence that spells the command
'at your marks' and 'ready' with a customizable delay before them
### Fixed
- bug that made a Button freeze when it was pressed and the screen rotated at the same time
## [0.02] - 2018-07-18
### Fixed
- negative time when the stopping starts
- removed delay between the end of the startton an the begin of the stopping
### Changed
- slowed down animations
### Added
- animation for the text "click start to start" between STOPPED and IDLE to
prevent it from getting out of the screen
## [0.01]
### Initial Release

BIN
graphics/BaseStation.xcf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
graphics/icons/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
graphics/icons/ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Binary file not shown.

31
headers/apptheme.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef APPTHEME_H
#define APPTHEME_H
#include <QObject>
#include <QVariant>
#include "appsettings.h"
class AppTheme : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariant style READ getStyle NOTIFY styleChanged)
public:
explicit AppTheme(QObject *parent = nullptr);
private:
QVariant lightTheme;
QVariant darkTheme;
QVariant * currentTheme;
signals:
void styleChanged();
public slots:
QVariant getStyle();
Q_INVOKABLE void changeTheme();
Q_INVOKABLE void refreshTheme();
};
#endif // APPTHEME_H

131
headers/baseconn.h Normal file
View file

@ -0,0 +1,131 @@
#ifndef BASECONN_H
#define BASECONN_H
#include <QObject>
#include <QTcpSocket>
#include <QDataStream>
#include <QDateTime>
#include <QTimer>
#include <QEventLoop>
#include <QSemaphore>
#include <QThread>
#include <QFuture>
#include <QtConcurrent/QtConcurrent>
#include <string.h>
#include "headers/appsettings.h"
#include "headers/speedtimer.h"
class BaseConn : public QObject
{
Q_OBJECT
public:
explicit BaseConn(QObject *parent = nullptr);
// values for the socket connection
int connection_progress;
QString ip;
ushort port = 3563;
int errors;
int errors_until_disconnect = 4;
// the current state
QString state;
// can be:
// - 'disconnected'
// - 'connecting'
// - 'connected'
QVariant connections;
QString latestReadReply;
//---general status values---//
private:
QDateTime *date;
//to get the current time
QTcpSocket *socket;
//socket for communication with the extention
QString readBuffer;
QSemaphore remoteSessions;
int nextConnectionId;
struct waitingRequest {
int id;
QEventLoop * loop;
QJsonObject reply;
};
QList<waitingRequest> waitingRequests;
signals:
void stateChanged();
//is emitted, when the connection state changes
void progressChanged();
//is emmited during the connection process when the progress changes
void gotUnexpectedReply(QString reply);
void connectionsChanged();
void connectionSlotReleased();
void nextRemoteActionChanged();
void nextRemoteActionDelayProgChanged();
void gotError(QString error);
public slots:
Q_INVOKABLE bool connectToHost();
//function to connect to the base station
Q_INVOKABLE bool init();
Q_INVOKABLE void deInit();
Q_INVOKABLE void closeConnection();
void gotError(QAbstractSocket::SocketError err);
// --- socket communication handling ---
Q_INVOKABLE QVariantMap sendCommand(int header, QJsonValue data = "");
// --- helper functions ---
Q_INVOKABLE int writeRemoteSetting(QString key, QString value);
Q_INVOKABLE bool refreshConnections();
// functions for the qml adapter
QString getIP() const;
void setIP(const QString &ipAdress);
QString getState() const;
void setState(QString newState);
int getProgress() const;
QVariant getConnections();
private slots:
void readyRead();
void processSocketMessage(QString message);
void socketReplyRecieved(QString reply);
void socketStateChanged(QAbstractSocket::SocketState socketState);
};
extern BaseConn * pGlobalBaseConn;
#endif // BASECONN_H

109
headers/climbingrace.h Normal file
View file

@ -0,0 +1,109 @@
#ifndef CLIMBINGRACE_H
#define CLIMBINGRACE_H
#include <QObject>
#include <QSound>
#include <QSoundEffect>
#include <QMediaPlayer>
#include "headers/baseconn.h"
#include "headers/appsettings.h"
#include "headers/speedtimer.h"
class ClimbingRace : public QObject
{
Q_OBJECT
Q_PROPERTY(int state READ getState NOTIFY stateChanged)
Q_PROPERTY(int mode READ getMode NOTIFY modeChanged)
Q_PROPERTY(QVariant timers READ getTimerTextList NOTIFY timerTextChanged)
Q_PROPERTY(QString baseStationState READ getBaseStationState NOTIFY baseStationStateChanged)
Q_PROPERTY(QVariant baseStationConnections READ getBaseStationConnections NOTIFY baseStationConnectionsChanged)
Q_PROPERTY(double nextStartActionDelayProgress READ getNextStartActionDelayProgress NOTIFY nextStartActionDelayProgressChanged)
public:
explicit ClimbingRace(QObject *parent = nullptr);
enum raceState { IDLE, STARTING, WAITING, RUNNING, STOPPED };
raceState state;
enum raceMode { LOCAL, REMOTE };
raceMode mode;
private:
AppSettings * appSettings;
BaseConn * baseConn;
QMediaPlayer * player;
QTimer * baseStationSyncTimer;
QTimer * timerTextRefreshTimer;
QTimer * nextStartActionTimer;
QDateTime *date;
QList<SpeedTimer *> speedTimers;
int nextStartAction;
// 0 : 'at your marks'
// 1 : 'ready'
// 2 : 'start'
double nextStartActionDelayProgress;
// helper vars
QVariantList qmlTimers;
const QStringList remoteSettings = {"ready_en", "ready_delay", "at_marks_en", "at_marks_delay"};
const QStringList remoteOnlySettings = {"soundVolume"};
private slots:
// helper functions
void playSoundsAndStartRace();
bool playSound(QString path);
void setState(raceState newState);
void refreshMode();
void refreshTimerText();
bool refreshRemoteTimers();
signals:
void nextStartActionChanged(int nextStartAction);
void nextStartActionDelayProgressChanged();
void stateChanged(int state);
void modeChanged();
void timerTextChanged();
void baseStationStateChanged();
void baseStationConnectionsChanged();
public slots:
Q_INVOKABLE int startRace();
Q_INVOKABLE int stopRace(int type);
Q_INVOKABLE int resetRace();
void syncWithBaseStation();
// functions for qml
Q_INVOKABLE int getState();
Q_INVOKABLE int getMode();
Q_INVOKABLE QVariant getTimerTextList();
Q_INVOKABLE double getNextStartActionDelayProgress();
Q_INVOKABLE void writeSetting(QString key, QVariant value);
Q_INVOKABLE QString readSetting(QString key);
Q_INVOKABLE bool connectBaseStation();
Q_INVOKABLE void disconnectBaseStation();
Q_INVOKABLE QString getBaseStationState();
Q_INVOKABLE QVariant getBaseStationConnections();
// athlete management
Q_INVOKABLE QVariant getAthletes();
Q_INVOKABLE bool createAthlete( QString userName, QString fullName );
Q_INVOKABLE bool deleteAthlete( QString userName );
Q_INVOKABLE bool selectAthlete( QString userName );
Q_INVOKABLE QVariant getResults( QString userName );
Q_INVOKABLE bool reloadBaseStationIpAdress();
};
#endif // CLIMBINGRACE_H

48
headers/speedtimer.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef SPEEDTIMER_H
#define SPEEDTIMER_H
#include <QObject>
#include <QDateTime>
#include <QEventLoop>
#include <QTimer>
#include <QDebug>
class SpeedTimer : public QObject
{
Q_OBJECT
public:
explicit SpeedTimer(QObject *parent = nullptr);
enum timerState { IDLE, STARTING, WAITING, RUNNING, STOPPED, FAILED, CANCELLED };
timerState state;
// variables for capturing the time
double startTime;
double stopTime;
double stoppedTime;
double reactionTime;
signals:
void stateChanged(timerState newState);
void startCanceled(bool falseStart);
public slots:
bool start(bool force = false);
bool stop(int type, bool force = false);
bool reset(bool force = false);
void setState(timerState newState);
QString getState();
double getCurrTime();
QString getText();
//helper functions
void delay(int mSecs);
timerState stateFromString(QString state);
private:
QDateTime *date;
};
#endif // SPEEDTIMER_H

83
qml/ErrorDialog.qml Normal file
View file

@ -0,0 +1,83 @@
/*
Speed Climbing Stopwatch - Simple Stopwatch for Climbers
Copyright (C) 2018 Itsblue Development - Dorian Zeder
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import com.itsblue.speedclimbingstopwatch 2.0
Popup {
id: root
x: startButt.x
y: startButt.y
width: startButt.width
height: startButt.height
modal: true
enter: Transition {
NumberAnimation { properties: "scale"; from: 0; to: 1; duration: 300; easing.type: Easing.Linear }
}
exit: Transition {
NumberAnimation { properties: "scale"; from: 1; to: 0; duration: 300; easing.type: Easing.Linear }
}
background: Rectangle {
radius: width * 0.5
color: appTheme.style.viewColor
border.color: appTheme.style.lineColor
border.width: 1
Label {
id: head_text
text: "error"
font.pixelSize: headlineUnderline.width * 0.1
color: enabled ? appTheme.style.textColor:appTheme.style.disabledTextColor
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: headlineUnderline.anchors.topMargin / 2 - height / 2
}
}
Rectangle {
id: headlineUnderline
height: 1
width: parent.width
color: appTheme.style.lineColor
anchors {
top: parent.top
left: parent.left
right: parent.right
topMargin: parent.height * 0.15
rightMargin: parent.radius - Math.sqrt(Math.pow(parent.radius,2)-Math.pow(parent.radius-anchors.topMargin,2))
leftMargin: parent.radius - Math.sqrt(Math.pow(parent.radius,2)-Math.pow(parent.radius-anchors.topMargin,2))
}
}
Image {
id: errorIcon
source: "qrc:/graphics/icons/error.png"
anchors.centerIn: parent
height: parent.height * 0.5
width: height
}
}
}

View file

@ -0,0 +1,67 @@
/*
Speed Climbing Stopwatch - Simple Stopwatch for Climbers
Copyright (C) 2018 - 2019 Itsblue Development - Dorian Zeder
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import com.itsblue.speedclimbingstopwatch 1.0
import "../components"
Column {
property string title: "add profile"
property string secondButt: "ok"
property string newProfileName: ""
Connections {
target: head_add
enabled: true
onClicked: {
if(speedBackend.createAthlete(userNameTf.text, fullNameTf.text)){
profilesStack.get(profilesStack.depth - 2 ).opened()
profilesStack.pop()
}
}
}
TextField {
id: fullNameTf
width: parent.width
placeholderText: "full name"
onTextChanged: {
parent.newProfileName = text
}
Keys.onReturnPressed: {
}
}
TextField {
id: userNameTf
width: parent.width
placeholderText: "username"
onTextChanged: {
parent.newProfileName = text
}
Keys.onReturnPressed: {
}
}
}

View file

@ -0,0 +1,237 @@
/*
Speed Climbing Stopwatch - Simple Stopwatch for Climbers
Copyright (C) 2018 - 2019 Itsblue Development - Dorian Zeder
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import com.itsblue.speedclimbingstopwatch 1.0
import "../components"
RemoteDataListView {
id: profileList
property int currentAthlete: -1
property string title: "profiles"
property string secondButt: "add"
signal opened()
onOpened: {
profileList.loadData()
}
//anchors.fill: parent
//anchors.topMargin: topContainerItm.height * 0.1
loadData: function () {
status = 905
//listData = {}
var retData = speedBackend.getAthletes()
if(retData === undefined){
status = 500
return
}
listData = retData["allAthletes"]
currentAthlete = retData["activeAthlete"]
status = listData.lenght !== false ? 200:0
}
delegate: SwipeDelegate {
id: swipeDelegate
property bool active: profileList.currentAthlete === profileList.listData[index]["id"]
text: profileList.listData[index]["fullName"]
width: profileList.width - (swipeDelegate.x)
height: profileList.height / 5
font.pixelSize: profilesStack.text_pixelSize
function remove() {
removeAnim.start()
}
onClicked: {
profilesStack.openResults(profileList.listData[index]["userName"])
}
contentItem: Text {
visible: false
}
Text {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: swipeDelegate.width * 0.05
right: parent.right
rightMargin: swipeDelegate.rightPadding
}
text: swipeDelegate.text
color: appTheme.style.textColor
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
fontSizeMode: Text.Fit
font.pixelSize: swipeDelegate.height * 0.4
minimumPixelSize: 1
}
background: Rectangle {
color: pressed ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
CheckBox {
id: control
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 7
}
height: parent.height * 0.6
checked: swipeDelegate.active
onCheckedChanged: {
if(checked && !swipeDelegate.active && speedBackend.selectAthlete(profileList.listData[index]["userName"])){
profileList.loadData()
}
}
indicator: Rectangle {
implicitWidth: 26
implicitHeight: 26
height: parent.height
width: height
x: control.leftPadding
y: parent.height / 2 - height / 2
radius: width * 0.2
border.color: control.down ? "#17a81a" : "#21be2b"
color: control.down ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
Rectangle {
width: parent.width * 0.65
height: width
anchors.centerIn: parent
radius: control.checked ? width * 0.2:0
color: control.down ? "#17a81a" : "#21be2b"
opacity: control.checked ? 1:0
scale: control.checked ? 0.9:0
Behavior on color {
ColorAnimation {
duration: 200
}
}
Behavior on radius {
NumberAnimation {
duration: 200
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
Behavior on scale {
NumberAnimation {
duration: 200
}
}
}
}
}
Rectangle {
color: "grey"
height: 1
width: parent.width * 0.9
visible: index > 0
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
}
}
NumberAnimation {
id: removeAnim
target: swipeDelegate
property: "height"
to: 0
easing.type: Easing.InOutQuad
onStopped: profileModel.model.remove(index)
}
swipe.transition: Transition {
SmoothedAnimation { velocity: 3; easing.type: Easing.InOutCubic }
}
swipe.left: Row {
anchors.left: parent.left
height: parent.height
Label {
id: deleteLabel
text: qsTr("Delete")
color: appTheme.style.textColor
verticalAlignment: Label.AlignVCenter
padding: 12
height: parent.height
SwipeDelegate.onClicked: {
profileList.status = 905
if(speedBackend.deleteAthlete(profileList.listData[index]["userName"])){
profileList.loadData()
return
}
profileList.status = 200
}
background: Rectangle {
color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
}
}
}
}
}

View file

@ -0,0 +1,263 @@
/*
Speed Climbing Stopwatch - Simple Stopwatch for Climbers
Copyright (C) 2018 Itsblue Development - Dorian Zeder
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import com.itsblue.speedclimbingstopwatch 1.0
import "../components"
Popup {
id: root
modal: true
dim: false
opacity: 0
enter: Transition {
NumberAnimation { properties: "opacity"; to: 1; duration: 300; easing.type: Easing.InOutQuad }
NumberAnimation { properties: "scale"; from: 0.9; to: 1; duration: 300; easing.type: Easing.InOutQuad }
}
exit: Transition {
NumberAnimation { properties: "opacity"; to: 0; duration: 300; easing.type: Easing.InOutQuad }
NumberAnimation { properties: "scale"; from: 1; to: 0.9; duration: 300; easing.type: Easing.InOutQuad }
}
background: Item {
RectangularGlow {
id: backgroundEffect
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: backgroundRect
cornerRadius: backgroundRect.radius
scale: 1
}
Rectangle {
id: backgroundRect
anchors.fill: parent
radius: width * 0.1
color: appTheme.style.viewColor
}
}
ProfilesStack {
id: profilesStack
width: headlineUnderline.width
anchors {
top: topContainerItm.bottom
left: parent.left
leftMargin: ( parent.width - width ) / 2
topMargin: headlineUnderline.anchors.topMargin * 1.2
bottom: parent.bottom
bottomMargin: topContainerItm.height * 0.3
}
Behavior on opacity {
NumberAnimation {duration: 200}
}
Component.onCompleted: {
profilesStack.init()
}
Connections {
target: root
onOpened: {
profilesStack.init()
}
}
}
Item {
id: topContainerItm
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
height: parent.height * 0.15
width: backgroundRect.width
RectangularGlow {
id: headerUnderlineEffect
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: headlineUnderline
scale: 1
}
Rectangle {
id: headlineUnderline
height: 1
width: parent.width
color: "grey"
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
Canvas {
id: headerBackground
anchors.fill: parent
property color color: appTheme.style.viewColor
onPaint: {
var ctx = getContext("2d");
var topMargin = backgroundRect.radius
ctx.beginPath();
ctx.fillStyle = headerBackground.color
ctx.moveTo(width, topMargin);
//
//ctx.lineTo(width, topMargin);
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.lineTo(0, topMargin)
ctx.arc(topMargin, topMargin, topMargin, 1 * Math.PI, 1.5*Math.PI, false);
ctx.lineTo(width-topMargin, 0)
ctx.arc(width-topMargin, topMargin, topMargin, 1.5*Math.PI, 0, false)
ctx.fill();
}
}
Label {
id: head_text
anchors {
centerIn: parent
}
width: parent.width * 0.8
height: parent.height * 0.8
fontSizeMode: Text.Fit
font.pixelSize: headlineUnderline.width * 0.1
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: appTheme.style.textColor
text: profilesStack.currentItem.title
}
}
FancyButton {
id: head_back
anchors {
left: parent.left
leftMargin: -height * 0.3
top:parent.top
topMargin: anchors.leftMargin
}
height: topContainerItm.height * 0.8
width: height
glowOpacity: Math.pow( root.opacity, 100 )
backgroundColor: appTheme.style.buttonColor
image: appTheme.style.backIcon
onClicked: profilesStack.depth > 1 ? profilesStack.pop():root.close()
}
FancyButton {
id: head_add
anchors {
right: parent.right
rightMargin: -height * 0.3
top:parent.top
topMargin: anchors.rightMargin
}
height: topContainerItm.height * 0.8
width: height
opacity: root.opacity < 1 ? root.opacity : ["ok", "add"].indexOf(profilesStack.currentItem.secondButt) >= 0 ? 1:0
glowOpacity: opacity < 1 ? Math.pow( opacity, 100 ) : Math.pow( opacity, 100 )
backgroundColor: appTheme.style.buttonColor
image: appTheme.style.confirmIcon
imageScale: profilesStack.currentItem.secondButt === "ok" ? 1:0
Label {
anchors {
top: parent.top
topMargin: parent.height/2 - height*0.55
left: parent.left
leftMargin: parent.width/2 - width/2
}
opacity: profilesStack.currentItem.secondButt === "add" ? 1:0
color: appTheme.style.textColor
text: "+"
font.pixelSize: parent.height * 0.8
}
onClicked: {
switch(profilesStack.currentItem.secondButt){
case "add":
profilesStack.createAthlete()
break
case "ok":
//speedBackend.createAthlete(fullNameTf.text, userNameTf.text)
}
}
Behavior on opacity {
enabled: root.opacity === 1
NumberAnimation {
duration: 200
}
}
}
}

View file

@ -0,0 +1,176 @@
/*
Speed Climbing Stopwatch - Simple Stopwatch for Climbers
Copyright (C) 2018 - 2019 Itsblue Development - Dorian Zeder
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import com.itsblue.speedclimbingstopwatch 1.0
import "../components"
StackView {
id: profilesStack
property int text_pixelSize: width * 0.08
//initialItem: profileListComp
onCurrentItemChanged: {
currentItem.opened()
}
function init() {
if(profilesStack.depth === 0){
profilesStack.openAthletes()
}
else {
profilesStack.currentItem.opened()
}
}
function openAthletes() {
var athsComp = profileListComp.createObject(null, {})
profilesStack.push(athsComp)
}
function openResults( userName ){
var resComp = resultViewComp.createObject(null, {"userName": userName})
profilesStack.push(resComp)
}
function createAthlete() {
var createAthleteComp = addProfileComp.createObject(null, {})
profilesStack.push(createAthleteComp)
}
/*-----List of all profiles-----*/
Component {
id: profileListComp
ProfileListPage {}
}
/*-----Option to add a profile-----*/
Component {
id: addProfileComp
AddProfilePage {}
}
// --- Result View ---
Component {
id: resultViewComp
ResultListPage {}
}
/*-----Custom animations-----*/
property int animationDuration: 200
pushEnter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: profilesStack.animationDuration
easing.type: Easing.InOutQuad
}
/*NumberAnimation {
property: "x"
from: width * 0.1
to: 0
duration: 300
}*/
NumberAnimation {
property: "scale"
from: 1.1
to: 1
duration: profilesStack.animationDuration
}
}
pushExit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: profilesStack.animationDuration
easing.type: Easing.InOutQuad
}
/*NumberAnimation {
property: "x"
to: -width * 0.1
from: 0
duration: 300
}*/
NumberAnimation {
property: "scale"
from: 1
to: 0.9
duration: profilesStack.animationDuration
}
}
popExit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: profilesStack.animationDuration
easing.type: Easing.InOutQuad
}
/*NumberAnimation {
property: "x"
to: width * 0.1
from: 0
duration: 300
}*/
NumberAnimation {
property: "scale"
from: 1
to: 1.1
duration: profilesStack.animationDuration
}
}
popEnter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: profilesStack.animationDuration
easing.type: Easing.InOutQuad
}
/*NumberAnimation {
property: "x"
from: -width * 0.1
to: 0
duration: 300
}*/
NumberAnimation {
property: "scale"
from: 0.9
to: 1
duration: profilesStack.animationDuration
}
}
}

View file

@ -0,0 +1,119 @@
/*
Speed Climbing Stopwatch - Simple Stopwatch for Climbers
Copyright (C) 2018 - 2019 Itsblue Development - Dorian Zeder
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import com.itsblue.speedclimbingstopwatch 1.0
import "../components"
RemoteDataListView {
id: resultView
property string userName
property string title: userName
property string secondButt: "none"
signal opened()
anchors.margins: 10
clip: true
onOpened: {
loadData()
}
loadData: function () {
status = 905
listData = {}
listData = speedBackend.getResults(userName)
status = listData.lenght !== false ? 200:0
}
delegate: SmoothItemDelegate {
id: resultDel
width: parent.width
height: resultView.height / 4
backgroundRect.radius: 0
function getDateText(){
return new Date(listData[index]["timestamp"]*1000).toLocaleString(Qt.locale(), "dddd, dd.MMM HH:mm")
}
Rectangle {
color: "grey"
height: 1
width: parent.width * 0.9
visible: index > 0
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
}
}
Column {
anchors.fill: parent
anchors.leftMargin: parent.width * 0.05
Label {
id: dateLa
height: parent.height / parent.children.length
font.pixelSize: height * 0.8
fontSizeMode: Text.Fit
color: appTheme.style.textColor
text: resultDel.getDateText()
}
Label {
id: resultLa
height: parent.height / parent.children.length
font.pixelSize: height * 0.8
fontSizeMode: Text.Fit
color: appTheme.style.textColor
text: qsTr("result: ") + (listData[index]["result"] / 1000).toFixed(3) + " s"
}
Label {
id: reactionTimeLa
height: parent.height / parent.children.length
font.pixelSize: height * 0.8
fontSizeMode: Text.Fit
color: appTheme.style.textColor
text: qsTr("reaction time: ") + listData[index]["reactionTime"].toFixed(0) + " ms"
}
}
}
}

View file

@ -20,8 +20,9 @@ import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import "./components"
import "./styles"
Popup {
id: root
@ -31,7 +32,7 @@ Popup {
height: startButt.height
modal: true
dim: false
scale: 0
opacity: 0
property var connections
@ -56,28 +57,106 @@ Popup {
}
enter: Transition {
NumberAnimation { properties: "scale"; to: 1; duration: 300; easing.type: Easing.Linear }
NumberAnimation { properties: "opacity"; to: 1; duration: 300; easing.type: Easing.InOutQuad }
NumberAnimation { properties: "scale"; from: 0.9; to: 1; duration: 300; easing.type: Easing.InOutQuad }
}
exit: Transition {
NumberAnimation { properties: "scale"; to: 0; duration: 300; easing.type: Easing.Linear }
NumberAnimation { properties: "opacity"; to: 0; duration: 300; easing.type: Easing.InOutQuad }
NumberAnimation { properties: "scale"; from: 1; to: 0.9; duration: 300; easing.type: Easing.InOutQuad }
}
background: Rectangle {
radius: width * 0.5
color: StyleSettings.viewColor
border.color: StyleSettings.lineColor
border.width: 1
color: appTheme.style.viewColor
border.color: appTheme.style.lineColor
border.width: 0
Behavior on color {
ColorAnimation {
duration: 200
}
}
RectangularGlow {
id: headerUnderlineEffect
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: headlineUnderline
scale: 1
}
Canvas {
id: headerBackground
Label {
id: head_text
text: options_stack.currentItem.title
font.pixelSize: headlineUnderline.width * 0.1
color: enabled ? StyleSettings.textColor:StyleSettings.disabledTextColor
anchors {
horizontalCenter: parent.horizontalCenter
left: parent.left
right: parent.right
top: parent.top
topMargin: headlineUnderline.anchors.topMargin / 2 - height / 2
bottom: headlineUnderline.bottom
}
height: header.height
width: header.width
property color color: appTheme.style.viewColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
onColorChanged: {
requestPaint()
}
onPaint: {
var ctx = getContext("2d");
ctx.reset();
var centreX = root.width / 2;
var centreY = root.height / 2;
ctx.beginPath();
ctx.fillStyle = headerBackground.color
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, root.width / 2, 1 * Math.PI, 2*Math.PI, false);
//ctx.lineTo(centreX, centreY);
ctx.fill();
}
}
Item {
id: header
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: headlineUnderline.bottom
}
Label {
id: head_text
anchors {
centerIn: parent
}
width: headlineUnderline.width * 0.4
height: parent.height
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: options_stack.currentItem.title
font.pixelSize: headlineUnderline.width * 0.1
color: enabled ? appTheme.style.textColor:appTheme.style.disabledTextColor
}
}
@ -85,7 +164,8 @@ Popup {
id: headlineUnderline
height: 1
width: parent.width
color: StyleSettings.lineColor
color: appTheme.style.lineColor
visible: false
anchors {
top: parent.top
left: parent.left
@ -96,7 +176,7 @@ Popup {
}
}
Button {
FancyButton {
id: head_back
anchors {
left: parent.left
@ -107,24 +187,15 @@ Popup {
height: parent.height * 0.13
width: height
opacity: root.closePolicy === Popup.NoAutoClose ? 0:1
//opacity: root.closePolicy === Popup.NoAutoClose ? 0:1
enabled: opacity > 0
background: Rectangle {
radius: width * 0.5
color: parent.pressed ? StyleSettings.buttonPressedColor:StyleSettings.buttonColor
border.color: StyleSettings.buttonBorderColor
border.width: 1
Image {
anchors.fill: parent
anchors.margins: parent.width * 0.2
source: StyleSettings.backIcon
glowOpacity: Math.pow( root.opacity, 100 )
}
}
image: appTheme.style.backIcon
onClicked: {
options_stack.depth > 1 ? options_stack.pop():root.close()
options_stack.depth > 1 ? options_stack.pop():root.close()
}
Behavior on opacity {
@ -133,12 +204,12 @@ Popup {
}
}
}
}
StackView {
id: options_stack
property int text_pixelSize: root.height * 0.06
property int delegateHeight: height * 0.2
property int rowSpacing: height * 0.01
initialItem: settings
width: headlineUnderline.width
@ -148,7 +219,7 @@ Popup {
top: parent.top
left: parent.left
leftMargin: ( parent.width - headlineUnderline.width ) / 2
topMargin: headlineUnderline.anchors.topMargin * 0.8
topMargin: headlineUnderline.anchors.topMargin * 0.95
bottom: parent.bottom
}
@ -161,138 +232,50 @@ Popup {
id: settings
Column {
property string title: qsTr("Options")
id: settings_col
property string title: qsTr("Options")
spacing: options_stack.rowSpacing
/*----Connect to external devices----*/
ItemDelegate {
NextPageDelegate {
id: connect_del
text: qsTr("connections")
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
}
height: options_stack.delegateHeight
width: parent.width
Image {
id: connect_del_image
source: StyleSettings.backIcon
rotation: 180
height: options_stack.text_pixelSize
width: height
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 10
}
}
text: qsTr("Base Station")
onClicked: {
options_stack.push(connect)
}
}
/*----Automated Start----*/
ItemDelegate {
NextPageDelegate {
id: autostart_del
height: options_stack.delegateHeight
text: qsTr("start sequence")
width: parent.width
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
}
Image {
id: autostart_del_image
source: StyleSettings.backIcon
rotation: 180
height: options_stack.text_pixelSize
width: height
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 10
}
}
onClicked: {
options_stack.push(autostart)
}
}
/*----Style Settings----*/
ItemDelegate {
id: style_del
text: qsTr("change style")
width: parent.width
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
}
Image {
id: style_image
source: StyleSettings.backIcon
rotation: 180
height: options_stack.text_pixelSize
width: height
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 10
}
}
onClicked: {
StyleSettings.setTheme()
}
}
}
}
/*-----Page to connect to extenstions like a startpad or buzzer-----*/
Component {
id: connect
Column {
id: connect_col
property string title: qsTr("connections")
property int delegateHeight: height*0.18
ConnectionDelegate {
id: connect_buzz_del
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
}
status: root.connections.buzzer
connect: root.connect
type: "buzzer"
SmoothSwitchDelegate {
id: styleDel
text: qsTr("dark mode")
width: parent.width
font.pixelSize: options_stack.text_pixelSize
}
height: options_stack.delegateHeight
ConnectionDelegate {
id: connect_stap_del
checked: speedBackend.readSetting("theme") === "Dark"
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
onCheckedChanged: {
speedBackend.writeSetting("theme", checked ? "Dark":"Light")
appTheme.refreshTheme()
}
status: root.connections.startpad
connect: root.connect
type: "startpad"
width: parent.width
font.pixelSize: options_stack.text_pixelSize
}
}
}
@ -302,115 +285,227 @@ Popup {
id: autostart
Column {
id: autostart_col
spacing: options_stack.rowSpacing
property string title: "Autostart"
property int delegateHeight: height*0.18
SwitchDelegate {
id: ready_del
text: qsTr("say 'ready'")
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
}
checked: _cppAppSettings.loadSetting("ready_en") === "true"
width: parent.width
height: parent.delegateHeight
font.pixelSize: options_stack.text_pixelSize
onCheckedChanged: {
_cppAppSettings.writeSetting("ready_en",checked)
}
indicator: SimpleIndicator{}
function updateSetting(key, val, del){
speedBackend.writeSetting(key, val)
}
ItemDelegate {
id: ready_delay_del
text: qsTr("delay (ms)")
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
function loadSetting(key, del){
var val
val = speedBackend.readSetting(key)
return val
}
SmoothSwitchDelegate {
id: ready_del
width: parent.width
height: options_stack.delegateHeight
text: qsTr("say 'ready'")
checked: parent.loadSetting("ready_en", ready_del) === "true"
onCheckedChanged: {
parent.updateSetting("ready_en",checked, ready_del)
}
}
InputDelegate {
id: ready_delay_del
width: parent.width
height: options_stack.delegateHeight
enabled: ready_del.checked
width: parent.width
font.pixelSize: options_stack.text_pixelSize
height: parent.delegateHeight
TextField {
focus: true
placeholderText: qsTr("time")
width: parent.width * 0.3
height: parent.height
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: qsTr("delay (ms)")
inputHint: qsTr("time")
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: _cppAppSettings.loadSetting("ready_delay")
inputText: autostart_col.loadSetting("ready_delay", ready_del)
onTextChanged: {
_cppAppSettings.writeSetting("ready_delay", text)
}
onInputFinished: {
autostart_col.updateSetting("ready_delay", inputText, ready_delay_del)
}
}
SwitchDelegate {
SmoothSwitchDelegate {
id: at_marks_del
text: qsTr("say\n'at your marks'")
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
}
checked: _cppAppSettings.loadSetting("at_marks_en") === "true"
width: parent.width
//height: parent.delegateHeight * 1.5
height: options_stack.delegateHeight
font.pixelSize: options_stack.text_pixelSize
text: qsTr("say 'at your marks'")
checked: autostart_col.loadSetting("at_marks_en", ready_del) === "true"
onCheckedChanged: {
_cppAppSettings.writeSetting("at_marks_en",at_marks_del.checked)
parent.updateSetting("at_marks_en",at_marks_del.checked, at_marks_del)
}
indicator: SimpleIndicator{}
}
ItemDelegate {
InputDelegate {
id: at_marks_delay_del
width: parent.width
height: options_stack.delegateHeight
text: qsTr("delay (ms)")
contentItem: Text {
text: parent.text
color: StyleSettings.textColor
font.pixelSize: options_stack.text_pixelSize
}
inputHint: qsTr("time")
inputMethodHints: Qt.ImhFormattedNumbersOnly
enabled: at_marks_del.checked
inputText: autostart_col.loadSetting("at_marks_delay", at_marks_delay_del)
onInputFinished: {
autostart_col.updateSetting("at_marks_delay", inputText, at_marks_delay_del)
}
}
}
}
/*-----Page to connect to extenstions like a startpad or buzzer-----*/
Component {
id: connect
Column {
id: connectCol
property string title: qsTr("Base Station")
spacing: options_stack.rowSpacing
property bool baseConnected: speedBackend.baseStationState === "connected"
ConnectionDelegate {
id: connectToBaseDel
text: status.status === "connected" ? qsTr("disconnect"): status.status === "disconnected" ? qsTr("connect"):qsTr("connecting...")
status: { "status": speedBackend.baseStationState }
connect: speedBackend.connectBaseStation
disconnect: speedBackend.disconnectBaseStation
type: "baseStation"
width: parent.width
height: parent.delegateHeight
height: options_stack.delegateHeight
font.pixelSize: options_stack.text_pixelSize
font.pixelSize: height * 0.6
}
TextField {
focus: true
placeholderText: qsTr("time")
width: parent.width * 0.3
height: parent.height
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
InputDelegate {
id: baseStationIpDel
text: _cppAppSettings.loadSetting("at_marks_delay")
text: qsTr("IP-Adress")
onTextChanged: {
_cppAppSettings.writeSetting("at_marks_delay",text)
inputHint: "IP"
inputText: speedBackend.readSetting("baseStationIpAdress")
inputTextFieldWidth: width * 0.7
onInputTextChanged: {
speedBackend.writeSetting("baseStationIpAdress", inputText)
speedBackend.reloadBaseStationIpAdress()
}
width: parent.width
height: !connectCol.baseConnected ? options_stack.delegateHeight:0
visible: height > 5
Behavior on height {
NumberAnimation {
duration: 400
easing.type: Easing.Linear
}
}
}
SmoothSliderDelegate {
id: baseStationVolumeDel
text: qsTr("volume")
property bool active: connectCol.baseConnected
width: parent.width
height: active ? options_stack.delegateHeight:0
visible: height > 5
sliderValue: 0
onSliderFinished: {
speedBackend.writeSetting("soundVolume", sliderValue)
}
onActiveChanged: {
if(active){
var val = speedBackend.readSetting("soundVolume")
console.log(val)
if(val !== "false"){
sliderValue = parseFloat(val)
}
}
}
Behavior on height {
NumberAnimation {
duration: 400
easing.type: Easing.Linear
}
}
}
NextPageDelegate {
id: baseStationConnectionsDel
text: qsTr("connected extensions")
width: parent.width
height: connectCol.baseConnected ? options_stack.delegateHeight:0
visible: height > 5
onClicked: {
options_stack.push(baseStationConnections)
}
Behavior on height {
NumberAnimation {
duration: 400
easing.type: Easing.Linear
}
}
}
}
}
/*-----Page to view devices that core connected to the pase startion-----*/
Component{
id: baseStationConnections
ListView {
id: baseStationConnections_list
property string title: qsTr("connections")
spacing: options_stack.rowSpacing
boundsBehavior: Flickable.StopAtBounds
model: speedBackend.baseStationConnections.length
delegate: ConnectionDelegate {
opacity: 1
width: parent.width
height: options_stack.delegateHeight
text: speedBackend.baseStationConnections[index]["name"]
status: {'status': speedBackend.baseStationConnections[index]["state"], 'progress': speedBackend.baseStationConnections[index]["progress"]}
}
}
}
@ -421,18 +516,32 @@ Popup {
property: "opacity"
from: 0
to: 1
duration: 200
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
from: width * 0.1
to: 0
duration: 300
}
}
pushExit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: 200
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
to: -width * 0.1
from: 0
duration: 300
}
}
popExit: Transition {
@ -440,18 +549,30 @@ Popup {
property: "opacity"
from: 1
to: 0
duration: 200
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
to: width * 0.1
from: 0
duration: 300
}
}
popEnter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 200
duration: 300
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
from: -width * 0.1
to: 0
duration: 300
}
}
}

View file

@ -1,21 +1,29 @@
import QtQuick 2.0
import QtQuick.Controls 2.2
ItemDelegate {
SmoothItemDelegate {
id: control
property var status
property var connect
property var disconnect
property string type
text: qsTr(type)
enabled: status.status === "disconnected"
enabled: (status.status === "disconnected" && control.connect !== undefined) || ( status.status === "connected" && control.disconnect !== undefined )
onClicked: {
connect(type)
if(status.status !== "connected"){
statusIndicator.color_override = "red"
shortDelay.start()
if(status.status === "disconnected"){
connect()
if(status.status !== "connected"){
statusIndicator.color_override = "red"
shortDelay.start()
}
}
else {
disconnect()
}
}
@ -33,9 +41,10 @@ ItemDelegate {
id: statusItem
anchors {
right: parent.right
rightMargin: ( height / control.height / 2 ) * height
verticalCenter: parent.verticalCenter
}
height: parent.font.pixelSize
height: control.height * 0.4
width: height
Rectangle {

View file

@ -7,11 +7,12 @@ Image {
source: "qrc:/graphics/icons/buzzer_black.png"
mipmap: true
opacity: status !== "disconnected" ? 1:0
visible: false
opacity: status === "connected" || status === "connecting" ? 1:0
visible: true
width: height
onOpacityChanged: visible = true
SequentialAnimation {
//rotating animation
running: status === "connecting"
@ -29,12 +30,14 @@ Image {
easing.type: Easing.InOutQuad
}
}
Behavior on rotation {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
Behavior on opacity {
NumberAnimation {
duration: 200

View file

@ -29,21 +29,42 @@ SequentialAnimation {
property alias outEasingType: outAnimation.easing.type
property alias inEasingType: inAnimation.easing.type
property string easingType: "Quad"
ParallelAnimation {
NumberAnimation { // in the default case, fade scale to 0
id: outAnimation
target: root.target
property: root.fadeProperty
property: "scale"
duration: root.fadeDuration_in
to: 0.9
easing.type: Easing["In"+root.easingType]
}
NumberAnimation { // in the default case, fade scale to 0
id: outAnimation2
target: root.target
property: "opacity"
duration: root.fadeDuration_in
to: 0
easing.type: Easing["In"+root.easingType]
}
PropertyAction { } // actually change the property targeted by the Behavior between the 2 other animations
NumberAnimation { // in the default case, fade scale back to 1
id: inAnimation
target: root.target
property: root.fadeProperty
duration: root.fadeDuration_out
to: 1
easing.type: Easing["Out"+root.easingType]
}
PropertyAction { } // actually change the property targeted by the Behavior between the 2 other animations
ParallelAnimation {
NumberAnimation { // in the default case, fade scale back to 1
id: inAnimation
target: root.target
property: root.fadeProperty
duration: root.fadeDuration_out
to: 1
easing.type: Easing["Out"+root.easingType]
}
NumberAnimation { // in the default case, fade scale to 0
id: inAnimation2
target: root.target
property: "opacity"
duration: root.fadeDuration_in
to: 1
easing.type: Easing["In"+root.easingType]
}
}
}

View file

@ -0,0 +1,95 @@
import QtQuick 2.3
import QtQuick.Controls 2.4
import QtQuick.Controls.Styles 1.2
BusyIndicator {
id: control
property double animationSpeed: 0.5
contentItem: Item {
implicitWidth: 64
implicitHeight: 64
Item {
id: item
x: parent.width / 2 - 32
y: parent.height / 2 - 32
width: 64
height: 64
opacity: control.running ? 1 : 0
property int currentHeight: 0
onCurrentHeightChanged: {
}
Behavior on opacity {
OpacityAnimator {
duration: 250
}
}
SequentialAnimation {
loops: Animation.Infinite
running: true
NumberAnimation {
target: item
duration: 2000 * 1/control.animationSpeed
to: 1000
properties: "currentHeight"
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: item
duration: 2000 * 1/control.animationSpeed
to: 0
properties: "currentHeight"
easing.type: Easing.InOutQuad
}
}
Row {
anchors.fill: parent
spacing: item.width / 9
Repeater {
id: repeater
model: 5
Rectangle {
property double heightMultiplier: Math.abs( Math.sin(( (item.currentHeight + (index*20))*0.01) * (Math.PI/2) ) )
anchors.verticalCenter: parent.verticalCenter
width: item.width / 9
height: ( heightMultiplier ) * ( item.height - 1 ) + 1
radius: width * 0.5
color: "#21be2b"
}
}
}
}
}
}

View file

@ -0,0 +1,76 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0
Button {
id: control
property string image
property color backgroundColor: appTheme.style.buttonColor
property real imageScale: 1
property double glowRadius: 0.001
property double glowSpread: 0.2
property bool glowVisible: true
property double glowScale: 0.75
property double glowOpacity: Math.pow( control.opacity, 100 )
//scale: control.pressed ? 0.8:1
Behavior on scale {
PropertyAnimation {
duration: 100
}
}
background: Item {
id: controlBackgroundContainer
RectangularGlow {
id: effect
glowRadius: control.glowRadius
spread: control.glowSpread
color: "black"
visible: control.glowVisible
cornerRadius: controlBackground.radius
anchors.fill: controlBackground
scale: control.glowScale
opacity: control.glowOpacity
}
Rectangle {
id: controlBackground
anchors.fill: parent
radius: height * 0.5
color: control.down ? Qt.darker(control.backgroundColor, 1.2) : control.backgroundColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
Image {
id: buttonIcon
source: control.image
anchors.centerIn: parent
height: parent.height * 0.5
width: height
mipmap: true
fillMode: Image.PreserveAspectFit
scale: control.imageScale
}
}
}
}

View file

@ -0,0 +1,67 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
SmoothItemDelegate {
id: control
property string inputText: ""
property string inputHint: ""
signal inputFinished()
property int inputMethodHints: Qt.ImhNone
property int inputTextFieldWidth: control.width * 0.3
onInputTextChanged: {
textField.text = inputText
}
width: parent.width
rightPadding: textField.width + width * 0.02
height: parent.delegateHeight
TextField {
id: textField
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: control.inputTextFieldWidth
height: parent.height
focus: true
placeholderText: control.inputHint
font.pixelSize: height * 0.4
inputMethodHints: control.inputMethodHints
palette.text: appTheme.style.textColor
onTextChanged: {
control.inputText = text
control.inputTextChanged()
}
onActiveFocusChanged: {
if(!activeFocus){
control.inputFinished()
}
}
background: Rectangle {
implicitWidth: 200
implicitHeight: 40
color: "transparent"//control.enabled ? "transparent" : "#353637"
border.color: (textField.activeFocus ? "#21be2b" : "#999999")
radius: height * 0.3
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
}

View file

@ -0,0 +1,22 @@
import QtQuick 2.0
import QtQuick.Controls 2.2
SmoothItemDelegate {
id: control
text: ""
rightPadding: forwardImage.width * 1.1
Image {
id: forwardImage
source: appTheme.style.backIcon
rotation: 180
height: control.height * 0.4
width: height
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: control.width * 0.01
}
}
}

View file

@ -47,7 +47,7 @@ Item {
enabled: true
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.InOutCubic
easing.type: Easing.Linear
}
}
@ -56,7 +56,7 @@ Item {
enabled: true
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.InOutCubic
easing.type: Easing.Linear
}
}

View file

@ -0,0 +1,86 @@
import QtQuick 2.10
import QtQuick.Controls 2.4
Item {
id: control
property var loadData
property var listData: ({})
property Component delegate
property alias view: listView
property int status: -1
property alias contentY: listView.contentY
property alias model: listView.model
signal refresh()
Component.onCompleted: {
}
ListView {
id: listView
model: control.listData.length
anchors.fill: parent
boundsBehavior: Flickable.DragOverBounds
boundsMovement: Flickable.StopAtBounds
anchors.margins: 1
anchors.rightMargin: 14
clip: true
//enabled: status === 200 || status === 902
//opacity: enabled ? 1:0
ScrollBar.vertical: ScrollBar {
parent: listView.parent
anchors {
top: listView.top
left: listView.right
margins: 10
leftMargin: 3
bottom: listView.bottom
}
width: 8
visible: listView.model > 0
active: true
}
delegate: control.delegate
onContentYChanged: {
/*
if(contentY < -listView.height * 0.3 && control.status !== 905){
contentY = 0
control.refresh()
}*/
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
FancyBusyIndicator {
anchors.centerIn: parent
opacity: !(status === 200 || status === 902) ? 1:0
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
}

View file

@ -0,0 +1,59 @@
import QtQuick 2.0
import QtQuick.Controls 2.2
ItemDelegate {
id: control
text: ""
property color textColor: appTheme.style.textColor
property alias backgroundRect: backgroundRect
opacity: enabled ? 1 : 0.2
contentItem: Text {
visible: false
}
Text {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: control.width * 0.02
right: parent.right
rightMargin: control.rightPadding
}
text: control.text
color: appTheme.style.textColor
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
fontSizeMode: Text.Fit
font.pixelSize: control.height * 0.4
minimumPixelSize: 1
}
width: parent.width
background: Rectangle {
id: backgroundRect
color: control.down ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
radius: height * 0.3
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}

View file

@ -0,0 +1,71 @@
import QtQuick 2.0
import QtQuick.Controls 2.4
SmoothItemDelegate {
id: control
property double sliderValue: 0.5
signal sliderFinished()
onSliderValueChanged: {
slider.value = control.sliderValue
}
rightPadding: slider.width + width * 0.02
Slider {
id: slider
anchors {
right: parent.right
rightMargin: parent.width * 0.01
verticalCenter: parent.verticalCenter
}
height: control.height * 0.4
width: parent.width * 0.6
onValueChanged: {
control.sliderValue = value
}
onPressedChanged: {
if(!pressed){
control.sliderFinished()
}
}
background: Rectangle {
x: slider.leftPadding
y: slider.topPadding + slider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 4
width: slider.availableWidth
height: slider.height * 0.2
radius: height * 0.5
color: "#bdbebf"
Rectangle {
width: slider.visualPosition * parent.width
height: parent.height
color: "#21be2b"
radius: height * 0.5
}
}
handle: Rectangle {
x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
y: slider.topPadding + slider.availableHeight / 2 - height / 2
implicitWidth: 26
implicitHeight: 26
width: slider.height
height: width
radius: height * 0.5
color: slider.pressed ? "#f0f0f0" : "#f6f6f6"
border.color: "#bdbebf"
}
}
}

View file

@ -0,0 +1,87 @@
import QtQuick 2.9
import QtQuick.Controls 2.3
SwitchDelegate {
id: control
implicitHeight: 50
implicitWidth: 100
baselineOffset: 100
contentItem: Text {
visible: false
}
Text {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: indicator.left
leftMargin: control.width * 0.02
rightMargin: control.width * 0.01
}
text: control.text
color: appTheme.style.textColor
fontSizeMode: Text.Fit
font.pixelSize: control.height * 0.4
}
indicator: Rectangle {
property bool checked: parent.checked
property bool down: parent.down
height: parent.height * 0.6
width: height * 1.84
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: control.width * 0.02
}
radius: height * 0.5
color: parent.checked ? "#17a81a" : "transparent"
border.color: parent.checked ? "#17a81a" : "#cccccc"
Behavior on color{
ColorAnimation{
duration: 200
}
}
Rectangle {
x: parent.checked ? parent.width - width : 0
width: parent.height
height: parent.height
radius: height * 0.5
color: parent.down ? "#cccccc" : "#ffffff"
border.color: parent.checked ? (parent.down ? "#17a81a" : "#21be2b") : "#999999"
Behavior on x{
NumberAnimation {
property: "x"
duration: 200
easing.type: Easing.InOutQuad
}
}
}
}
background: Rectangle {
opacity: enabled ? 1 : 0.3
color: control.down ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
radius: height * 0.3
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,24 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>ProfilesDialog.qml</file>
<file>SettingsDialog.qml</file>
<file>components/ProgressCircle.qml</file>
<file>components/SimpleIndicator.qml</file>
<file>components/ConnectionDelegate.qml</file>
<file>components/FadeAnimation.qml</file>
<file>connections/BuzzerConn.qml</file>
<file>connections/StartpadConn.qml</file>
<file>styles/StyleSettings.qml</file>
<file>styles/qmldir</file>
<file>styles/Dark.js</file>
<file>styles/Light.js</file>
<file>styles/Default.js</file>
<file>components/ConnectionIcon.qml</file>
<file>components/NextPageDelegate.qml</file>
<file>ErrorDialog.qml</file>
<file>components/FancyButton.qml</file>
<file>components/SmoothItemDelegate.qml</file>
<file>components/SmoothSwitchDelegate.qml</file>
<file>components/InputDelegate.qml</file>
<file>components/SmoothSliderDelegate.qml</file>
<file>components/RemoteDataListView.qml</file>
<file>components/FancyBusyIndicator.qml</file>
<file>ProfilesDialog/ProfilesDialog.qml</file>
<file>ProfilesDialog/ProfilesStack.qml</file>
<file>ProfilesDialog/ProfileListPage.qml</file>
<file>ProfilesDialog/AddProfilePage.qml</file>
<file>ProfilesDialog/ResultListPage.qml</file>
</qresource>
</RCC>

View file

@ -15,10 +15,17 @@
<file>sounds/at_marks_2.wav</file>
<file>graphics/icons/buzzer_black.png</file>
<file>graphics/icons/ok_black.png</file>
<file>translations/german.ts</file>
<file>translations/de_DE.qm</file>
<file>translations/de_DE.ts</file>
<file>graphics/icons/settings_black.png</file>
<file>graphics/icons/startpad_black.png</file>
<file>sounds/false.wav</file>
<file>graphics/icons/error.png</file>
<file>graphics/icons/BaseStation.png</file>
<file>graphics/icons/BaseStation_black.png</file>
<file>graphics/icons/buzzer.png</file>
<file>graphics/icons/startpad.png</file>
<file>graphics/icons/user_black.png</file>
<file>graphics/icons/ok.png</file>
</qresource>
</RCC>

BIN
sounds/false.wav Normal file

Binary file not shown.

View file

@ -17,6 +17,8 @@
#include "headers/appsettings.h"
AppSettings * pGlobalAppSettings = nullptr;
AppSettings::AppSettings(QObject* parent)
:QObject(parent)
{
@ -29,7 +31,11 @@ AppSettings::AppSettings(QObject* parent)
this->setDefaultSetting("at_marks_en", "false");
this->setDefaultSetting("at_marks_delay", 0);
this->setDefaultSetting("theme", "Default");
this->setDefaultSetting("theme", "Light");
this->setDefaultSetting("baseStationIpAdress", "192.168.4.1");
pGlobalAppSettings = this;
}
QString AppSettings::loadSetting(const QString &key)

132
sources/apptheme.cpp Normal file
View file

@ -0,0 +1,132 @@
#include "headers/apptheme.h"
AppTheme::AppTheme(QObject *parent) : QObject(parent)
{
QVariantMap tmpDarkTheme = {
{"backgroundColor", "#2d3037"},
{"buttonColor", "#202227"},
{"buttonPressedColor", "#6ccaf2"},
{"buttonBorderColor", "grey"},
{"disabledButtonColor", "#555555"},
{"viewColor", "#202227"},
{"menuColor", "#292b32"},
{"delegate1Color", "#202227"},
{"delegate2Color", "#202227"},
{"delegateBackgroundColor", "#202227"},
{"delegatePressedColor", "#41454f"},
{"textColor", "#ffffff"},
{"textDarkColor", "#232323"},
{"disabledTextColor", "#777777"},
{"sliderColor", "#6ccaf2"},
{"errorColor", "#ba3f62"},
{"infoColor", "#3fba62"},
{"lineColor", "grey"},
{"backIcon", "qrc:/graphics/icons/back.png"},
{"settIcon", "qrc:/graphics/icons/settings.png"},
{"buzzerIcon", "qrc:/graphics/icons/buzzer.png"},
{"startpadIcon", "qrc:/graphics/icons/startpad.png"},
{"baseStationIcon", "qrc:/graphics/icons/BaseStation.png"},
{"profilesIcon", "qrc:/graphics/icons/user.png"},
{"confirmIcon", "qrc:/graphics/icons/ok.png"}
};
this->darkTheme = tmpDarkTheme;
QVariantMap tmpLightTheme = {
{"backgroundColor", "white"},
{"buttonColor", "white"},
{"buttonPressedColor", "lightgrey"},
{"buttonBorderColor", "grey"},
{"disabledButtonColor", "#d5d5d5"},
{"viewColor", "white"},
{"menuColor", "#f8f8f8"},
{"delegate1Color", "#202227"},
{"delegate2Color", "#202227"},
{"delegateBackgroundColor", "white"},
{"delegatePressedColor", "#dddedf"},
{"textColor", "black"},
{"textDarkColor", "#232323"},
{"disabledTextColor", "grey"},
{"sliderColor", "#6ccaf2"},
{"errorColor", "#ba3f62"},
{"infoColor", "#3fba62"},
{"lineColor", "grey"},
{"backIcon", "qrc:/graphics/icons/back_black.png"},
{"settIcon", "qrc:/graphics/icons/settings_black.png"},
{"buzzerIcon", "qrc:/graphics/icons/buzzer_black.png"},
{"startpadIcon", "qrc:/graphics/icons/startpad_black.png"},
{"baseStationIcon", "qrc:/graphics/icons/BaseStation_black.png"},
{"profilesIcon", "qrc:/graphics/icons/user_black.png"},
{"confirmIcon", "qrc:/graphics/icons/ok_black.png"}
};
this->lightTheme = tmpLightTheme;
QString currentThemeString = pGlobalAppSettings->loadSetting("theme");
if(currentThemeString == "Light"){
this->currentTheme = &this->lightTheme;
}
else if (currentThemeString == "Dark") {
this->currentTheme = &this->darkTheme;
}
else {
this->currentTheme = &this->lightTheme;
}
}
QVariant AppTheme::getStyle() {
return *this->currentTheme;
}
void AppTheme::changeTheme() {
QString currentThemeString = pGlobalAppSettings->loadSetting("theme");
QString newThemeString = "Light";
if(currentThemeString == "Light"){
this->currentTheme = &this->darkTheme;
newThemeString = "Dark";
}
else if (currentThemeString == "Dark") {
this->currentTheme = &this->lightTheme;
newThemeString = "Light";
}
else {
this->currentTheme = &this->lightTheme;
}
pGlobalAppSettings->writeSetting("theme", newThemeString);
emit this->styleChanged();
}
void AppTheme::refreshTheme() {
QString currentThemeString = pGlobalAppSettings->loadSetting("theme");
if(currentThemeString == "Light"){
this->currentTheme = &this->lightTheme;
}
else if (currentThemeString == "Dark") {
this->currentTheme = &this->darkTheme;
}
emit this->styleChanged();
}

373
sources/baseconn.cpp Normal file
View file

@ -0,0 +1,373 @@
#include "headers/baseconn.h"
BaseConn * pGlobalBaseConn = nullptr;
BaseConn::BaseConn(QObject *parent) : QObject(parent)
{
pGlobalBaseConn = this;
socket = new QTcpSocket();
this->setState("disconnected");
connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(gotError(QAbstractSocket::SocketError)));
connect(this->socket, &QAbstractSocket::stateChanged, this, &BaseConn::socketStateChanged);
this->nextConnectionId = 1;
this->connections = QVariantList({});
}
bool BaseConn::connectToHost() {
qDebug() << "connecting";
setState("connecting");
this->connection_progress = 0;
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
// quit the loop when the timer times out
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
//quit the loop when the connection was established
loop.connect(this->socket, SIGNAL(connected()), &loop, SLOT(quit()));
// start the timer before starting to connect
timer.start(3000);
//connect
this->socket->connectToHost(this->ip, this->port);
//wait for the connection to finish (programm gets stuck in here)
loop.exec();
//loop finish
if(timer.remainingTime() == -1){
//the time has been triggered -> timeout
this->socket->abort();
return(false);
}
// stop the timer as the connection has been established
timer.stop();
connect(this->socket, &QTcpSocket::readyRead, this, &BaseConn::readyRead);
this->connection_progress = 50;
if(!this->init()){
this->closeConnection();
return false;
}
this->setState("connected");
return(true);
}
bool BaseConn::init() {
return true;
}
void BaseConn::deInit() {
this->connections.clear();
}
void BaseConn::closeConnection()
{
this->connections = QVariantList({});
emit this->connectionsChanged();
qDebug() << "closing connection";
switch (socket->state())
{
case 0:
socket->disconnectFromHost();
break;
case 2:
socket->abort();
break;
default:
socket->abort();
}
this->deInit();
setState("disconnected");
// for(int i = 0; i < this->waitingRequests.length(); i++){
// this->waitingRequests[i].reply = "ERR_NOT_CONNECTED";
// this->waitingRequests[i].loop->quit();
// return;
// }
}
void BaseConn::gotError(QAbstractSocket::SocketError err)
{
//qDebug() << "got error";
QString strError = "unknown";
switch (err)
{
case 0:
strError = "Connection was refused";
break;
case 1:
strError = "Remote host closed the connection";
this->closeConnection();
break;
case 2:
strError = "Host address was not found";
break;
case 5:
strError = "Connection timed out";
break;
default:
strError = "Unknown error";
}
emit gotError(strError);
qDebug() << "got socket error: " << strError;
}
// -------------------------------------
// --- socket communication handling ---
// -------------------------------------
QVariantMap BaseConn::sendCommand(int header, QJsonValue data){
if(this->state != "connected"){
return {{"status", 910}, {"data", "not connected"}};
}
// generate id and witing requests entry
int thisId = nextConnectionId;
//qDebug() << "sending command: " << header << " with data: " << data << " and id: " << thisId;
nextConnectionId ++;
QEventLoop loop;
QJsonObject reply;
this->waitingRequests.append({thisId, &loop, reply});
QJsonObject requestObj;
requestObj.insert("id", thisId);
requestObj.insert("header", header);
requestObj.insert("data", data);
QString jsonRequest = QJsonDocument(requestObj).toJson();
QTimer timer;
timer.setSingleShot(true);
// quit the loop when the timer times out
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
// quit the loop when the connection was established
// loop.connect(this, &BaseConn::gotReply, &loop, &QEventLoop::quit);
// start the timer before starting to connect
timer.start(3000);
//write data
socket->write(jsonRequest.toLatin1());
//wait for an answer to finish (programm gets stuck in here)
loop.exec();
//loop finished
if(timer.remainingTime() == -1){
//the time has been triggered -> timeout
return {{"status", 911}, {"data", ""}};
}
for(int i = 0; i<this->waitingRequests.length(); i++){
if(this->waitingRequests[i].id == thisId){
reply = this->waitingRequests[i].reply;
}
}
// stop the timer as the connection has been established
timer.stop();
return {{"status", reply.value("header").toInt()}, {"data", reply.value("data").toVariant()}};
}
void BaseConn::readyRead() {
//qDebug() << "ready to ready " << socket->bytesAvailable() << " bytes" ;
QString reply = socket->readAll();
//qWarning() << "socket read: " << reply;
processSocketMessage(reply);
}
void BaseConn::processSocketMessage(QString message){
QString startKey = "<message>";
QString endKey = "</message>";
//qWarning() << "... processing message now ... : " << message;
if(message == ""){
return;
}
if((message.startsWith(startKey) && message.endsWith(endKey)) && (message.count(startKey) == 1 && message.count(endKey) == 1)){
// non-split message ( e.g.: <message>123456789</message>
}
else if(!message.contains(endKey) && (!this->readBuffer.isEmpty() || message.startsWith(startKey))){
// begin of a split message ( e.g.: <message>123 )
// or middle of a split message ( e.g.: 456 )
//qWarning() << "this is a begin or middle of split a message";
this->readBuffer += message;
return;
}
else if(!message.contains(startKey) && message.endsWith(endKey)) {
// end of a split message ( e.g.: 789</message> )
if(!this->readBuffer.isEmpty()){
message = readBuffer + message;
readBuffer.clear();
}
}
else if((message.count(startKey) > 1 || message.count(endKey) > 1) || (message.contains(endKey) && !message.endsWith(endKey) && message.contains(startKey) && !message.startsWith(startKey))) {
// multiple messages in one packet ( e.g.: <message>123456789</message><message>987654321</message> )
// or multiple message fragments in one message ( e.g.: 56789</message><message>987654321</message> or 56789</message><message>98765 )
//qDebug() << "detected multiple messages";
int startOfSecondMessage = message.lastIndexOf(startKey);
// process first part of message
QString firstMessage = message.left(startOfSecondMessage);
this->processSocketMessage(firstMessage);
// process second part of message
QString secondMessage = message.right(message.length() - startOfSecondMessage);
this->processSocketMessage(secondMessage);
return;
}
else {
// invalid message
return;
}
//qWarning() << "... done processing, message: " << message;
this->socketReplyRecieved(message);
}
void BaseConn::socketReplyRecieved(QString reply) {
reply.replace("<message>", "");
reply.replace("</message>", "");
int id = 0;
QJsonDocument jsonReply = QJsonDocument::fromJson(reply.toUtf8());
QJsonObject replyObj = jsonReply.object();
//qDebug() << "got: " << reply;
if(!replyObj.isEmpty()){
id = replyObj.value("id").toInt();
for(int i = 0; i < this->waitingRequests.length(); i++){
if(this->waitingRequests[i].id == id){
this->waitingRequests[i].reply = replyObj;
if(this->waitingRequests[i].loop != nullptr){
this->waitingRequests[i].loop->quit();
}
return;
}
}
}
latestReadReply = reply;
emit gotUnexpectedReply(reply);
}
// ------------------------
// --- helper functions ---
// ------------------------
int BaseConn::writeRemoteSetting(QString key, QString value) {
QJsonArray requestData;
requestData.append(key);
requestData.append(value);
return this->sendCommand(3000, requestData)["status"].toInt();
}
void BaseConn::setIP(const QString &ipAdress){
this->ip = ipAdress;
}
QString BaseConn::getIP() const
{
return(this->ip);
}
QString BaseConn::getState() const
{
return(this->state);
}
void BaseConn::setState(QString newState){
this->state = newState;
emit stateChanged();
}
void BaseConn::socketStateChanged(QAbstractSocket::SocketState socketState) {
switch (socketState) {
case QAbstractSocket::UnconnectedState:
{
this->deInit();
this->setState("disconnected");
break;
}
case QAbstractSocket::ConnectedState:
{
//this->setState("connected");
break;
}
default:
{
qDebug() << "+ --- UNKNOWN SOCKET STATE: " << socketState;
break;
}
}
}
int BaseConn::getProgress() const
{
return(connection_progress);
}
bool BaseConn::refreshConnections() {
QVariantMap reply = this->sendCommand(2006);
if(reply["status"] != 200){
//handle Error!!
if(reply["status"] == 910){
this->connections = QVariantList({});
return true;
}
qDebug() << "+ --- error refreshing connections: " << reply["status"];
return false;
}
QVariantList tmpConnections = reply["data"].toList();
if(this->connections != reply["data"].toList()){
this->connections = reply["data"].toList();
emit this->connectionsChanged();
}
return true;
}
QVariant BaseConn::getConnections() {
return(connections);
/*
"id": "id of the extention (int)",
"type": "type of the extention (can be: 'STARTPAD', 'TOPPAD')",
"name": "name of the extention",
"ip": "ip-adress of he extention (string)",
"state": "state of the extention (can be: 'disconnected', 'connecting', 'connected')"
*/
//QVariantMap conn = {{"id",0}, {"type","STARTPAD"}, {"name", "startpad1"}, {"ip", "192.168.4.11"}, {"state", "connected"}};
//QVariantMap conn1 = {{"id",0}, {"type","TOPPAD"}, {"name", "buzzer1"}, {"ip", "192.168.4.10"}, {"state", "connected"}};
//QVariantList conns = {conn, conn1};
//return conns;
}

637
sources/climbingrace.cpp Normal file
View file

@ -0,0 +1,637 @@
#include "headers/climbingrace.h"
/*
* manages:
* - global state
* - timers
* - sounds
* - next start action
* - next start action delay progress
* - settings (remote and local)
*/
ClimbingRace::ClimbingRace(QObject *parent) : QObject(parent)
{
this->state = IDLE;
this->mode = LOCAL;
this->appSettings = new AppSettings;
this->baseConn = new BaseConn;
this->baseConn->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress"));
connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::baseStationStateChanged);
connect(this->baseConn, &BaseConn::connectionsChanged, this, &ClimbingRace::baseStationConnectionsChanged);
this->speedTimers.append( new SpeedTimer );
this->player = new QMediaPlayer;
this->date = new QDateTime;
this->nextStartActionTimer = new QTimer(this);
nextStartActionTimer->setSingleShot(true);
this->baseStationSyncTimer = new QTimer();
this->baseStationSyncTimer->setInterval(100);
this->baseStationSyncTimer->setSingleShot(true);
this->baseStationSyncTimer->connect(this->baseStationSyncTimer, &QTimer::timeout, this, &ClimbingRace::syncWithBaseStation);
this->baseStationSyncTimer->start();
this->timerTextRefreshTimer = new QTimer();
this->timerTextRefreshTimer->setInterval(1);
this->timerTextRefreshTimer->setSingleShot(true);
this->timerTextRefreshTimer->connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &ClimbingRace::refreshTimerText);
this->refreshTimerText();
}
// --------------------------
// --- Main Functionality ---
// --------------------------
int ClimbingRace::startRace() {
if(this->state != IDLE) {
return 904;
}
this->refreshMode();
qDebug() << "+ --- starting race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
this->setState(STARTING);
this->nextStartAction = -1;
this->playSoundsAndStartRace();
returnCode = 200;
break;
}
case REMOTE:
{
QVariantMap reply = this->baseConn->sendCommand(1000);
if(reply["status"] != 200){
//handle Error!!
returnCode = reply["status"].toInt();
}
else {
returnCode = 200;
}
break;
}
}
return returnCode;
}
int ClimbingRace::stopRace(int type) {
if(this->state != RUNNING && this->state != STARTING) {
return 904;
}
// type can be:
// 0: stopp
// 1: cancel
// 2: fail (fase start)
this->refreshMode();
qDebug() << "+ --- stopping race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
if(type == 1){
this->nextStartActionTimer->stop();
this->player->stop();
this->nextStartAction = -1;
}
returnCode = this->speedTimers[0]->stop(type) ? 200:904;
if(returnCode == 200) {
this->setState(STOPPED);
}
break;
}
case REMOTE:
{
QVariantMap reply = this->baseConn->sendCommand(1001);
if(reply["status"] != 200){
//handle Error!!
returnCode = reply["status"].toInt();
}
else {
returnCode = 200;
}
break;
}
}
return returnCode;
}
int ClimbingRace::resetRace() {
if(this->state != STOPPED) {
return 904;
}
this->refreshMode();
qDebug() << "+ --- resetting race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
returnCode = this->speedTimers[0]->reset() ? 200:904;
if(returnCode == 200){
this->setState(IDLE);
}
break;
}
case REMOTE:
{
QVariantMap reply = this->baseConn->sendCommand(1002);
if(reply["status"] != 200){
//handle Error!!
returnCode = reply["status"].toInt();
}
else {
returnCode = 200;
}
break;
}
}
return returnCode;
}
// -------------------------
// --- Base Station sync ---
// -------------------------
void ClimbingRace::syncWithBaseStation() {
this->refreshMode();
if(this->mode != REMOTE){
this->baseStationSyncTimer->start();
return;
}
this->baseConn->refreshConnections();
QVariantMap tmpReply = this->baseConn->sendCommand(2000);
if(tmpReply["status"] != 200){
this->baseStationSyncTimer->start();
return;
}
this->setState( raceState( tmpReply["data"].toInt() ) );
switch (this->state) {
case 1:
{
// case STARTING
this->refreshRemoteTimers();
tmpReply = this->baseConn->sendCommand(2005);
if(tmpReply["status"] != 200){
//handle error!!
qDebug() << "+ --- getting next start action progress from basestation failed";
this->baseStationSyncTimer->start();
return;
}
else {
this->nextStartActionDelayProgress = tmpReply["data"].toDouble() > 0 ? tmpReply["data"].toDouble():0;
this->nextStartActionDelayProgressChanged();
}
break;
}
default:
{
this->refreshRemoteTimers();
break;
}
}
this->baseStationSyncTimer->start();
}
// ------------------------
// --- helper functions ---
// ------------------------
void ClimbingRace::playSoundsAndStartRace() {
qDebug() << "next Action: " << nextStartAction;
nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
switch (this->nextStartAction) {
case 0:
{
if(!playSound("qrc:/sounds/at_marks_1.wav")){
return;
}
if(appSettings->loadSetting("ready_en") == "true"){
nextStartAction = 1;
nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
}
else{
nextStartAction = 2;
nextStartActionTimer->setInterval(1);
}
break;
}
case 1:
{
if(!playSound("qrc:/sounds/ready_1.wav")){
return;
}
nextStartAction = 2;
nextStartActionTimer->setInterval(1);
break;
}
case 2:
{
if(!playSound("qrc:/sounds/OFFICAL_IFSC_STARTIGNAL.wav")){
return;
}
nextStartAction = -1;
nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
this->setState(RUNNING);
speedTimers[0]->start();
return;
}
default:
{
this->speedTimers[0]->setState(SpeedTimer::STARTING);
if(appSettings->loadSetting("at_marks_en") == "true"){
nextStartAction = 0;
nextStartActionTimer->setInterval(appSettings->loadSetting("at_marks_delay").toInt() <= 0 ? 1:appSettings->loadSetting("at_marks_delay").toInt());
}
else if(appSettings->loadSetting("ready_en") == "true"){
nextStartAction = 1;
nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
}
else{
nextStartAction = 2;
nextStartActionTimer->setInterval(1);
}
break;
}
}
nextStartActionTimer->connect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
nextStartActionTimer->start();
}
bool ClimbingRace::playSound(QString path) {
player->setMedia(QUrl(path));
player->setVolume(50);
player->play();
QTimer timer;
timer.setInterval(1);
timer.setSingleShot(true);
QEventLoop loop;
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
while (player->mediaStatus() == QMediaPlayer::LoadingMedia || player->mediaStatus() == QMediaPlayer::BufferingMedia || player->mediaStatus() == QMediaPlayer::BufferedMedia) {
timer.start();
loop.exec();
}
if(player->mediaStatus() == QMediaPlayer::EndOfMedia){
return true;
}
else {
return false;
}
}
void ClimbingRace::setState(raceState newState) {
if(newState != this->state) {
this->state = newState;
this->stateChanged(newState);
}
}
void ClimbingRace::refreshMode() {
raceMode newMode;
if(this->baseConn->state == "connected"){
newMode = REMOTE;
}
else {
newMode = LOCAL;
}
if(this->mode != newMode){
if(newMode == LOCAL){
// if the new mode is local -> connection to base station has been lost
// go back to one timer
for (int i = 0;i<this->speedTimers.length();i++) {
delete this->speedTimers[i];
}
this->speedTimers.clear();
this->speedTimers.append(new SpeedTimer);
// clear extensions
this->baseConn->connections.clear();
}
this->mode = newMode;
emit this->modeChanged();
}
}
void ClimbingRace::refreshTimerText() {
// --- refresh timer text ---
QVariantList newTimerTextList;
foreach(SpeedTimer * timer, this->speedTimers){
QVariantMap timerMap = {{"text",timer->getText()}, {"reacttime", timer->reactionTime}};
newTimerTextList.append(timerMap);
}
if(newTimerTextList != this->qmlTimers){
this->qmlTimers = newTimerTextList;
emit timerTextChanged();
}
// --- refresh next start action delay progress ---
if(this->mode == LOCAL){
QString totalStr;
if(nextStartAction == 0){
totalStr = appSettings->loadSetting("at_marks_delay");
}
else if (nextStartAction == 1) {
totalStr = appSettings->loadSetting("ready_delay");
}
double remaining = this->nextStartActionTimer->remainingTime();
double total = totalStr.toDouble();
//qDebug() << "DELAY_PROG: " << "total: " << total << " remaining: " << remaining << " prog: " << remaining / total;
if(remaining > 0){
this->nextStartActionDelayProgress = remaining / total;
emit this->nextStartActionDelayProgressChanged();
}
else {
this->nextStartActionDelayProgress = 0;
emit this->nextStartActionDelayProgressChanged();
}
}
else if (this->mode == REMOTE && this->state == IDLE) {
this->nextStartActionDelayProgress = 0;
emit this->nextStartActionDelayProgressChanged();
}
this->timerTextRefreshTimer->start();
}
bool ClimbingRace::refreshRemoteTimers() {
// get current time
QVariantMap tmpReply = this->baseConn->sendCommand(2007);
if(tmpReply["status"].toInt() != 200){
//handle error!!
qDebug() << "+ --- getting timers from basestation failed";
this->baseStationSyncTimer->start();
return false;
}
else {
QVariantList timers = tmpReply["data"].toList();
if(timers.length() != speedTimers.length()){
// local timers are out of sync
// delete all current timers
foreach(SpeedTimer * locTimer, this->speedTimers){
delete locTimer;
}
speedTimers.clear();
foreach(QVariant remTimer, timers){
// create a local timer for each remote timer
this->speedTimers.append(new SpeedTimer);
}
}
foreach(QVariant remTimer, timers){
int currId = remTimer.toMap()["id"].toInt();
speedTimers[currId]->startTime = this->date->currentMSecsSinceEpoch() - remTimer.toMap()["currTime"].toDouble();
speedTimers[currId]->stoppedTime = remTimer.toMap()["currTime"].toDouble();
speedTimers[currId]->reactionTime = remTimer.toMap()["reactTime"].toDouble();
speedTimers[currId]->setState(SpeedTimer::timerState(remTimer.toMap()["state"].toInt()));
}
return true;
}
}
// - athlete management -
QVariant ClimbingRace::getAthletes() {
QVariantMap reply = this->baseConn->sendCommand(4003);
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error getting athletes: " << reply["status"];
return false;
}
QVariantMap tmpAthletes = reply["data"].toMap();
//qDebug() << tmpAthletes;
return tmpAthletes;
}
bool ClimbingRace::createAthlete(QString userName, QString fullName) {
QVariant requestData = QVariantMap({{"fullName", fullName}, {"userName", userName}});
QVariantMap reply = this->baseConn->sendCommand(4001, requestData.toJsonValue());
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error creating athlete: " << reply["status"];
return false;
}
return true;
}
bool ClimbingRace::deleteAthlete( QString userName ){
QVariant requestData = QVariantMap({{"userName", userName}});
QVariantMap reply = this->baseConn->sendCommand(4002, requestData.toJsonValue());
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error deleting athlete: " << reply["status"];
return false;
}
return true;
}
bool ClimbingRace::selectAthlete( QString userName){
QVariant requestData = QVariantMap({{"userName", userName}});
QVariantMap reply = this->baseConn->sendCommand(4000, requestData.toJsonValue());
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error selecting athlete: " << reply["status"];
return false;
}
return true;
}
QVariant ClimbingRace::getResults( QString userName ){
QVariantMap reply = this->baseConn->sendCommand(4004, userName);
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error getting results: " << reply["status"];
return false;
}
QVariantList tmpAthletes = reply["data"].toList();
//qDebug() << tmpAthletes;
return tmpAthletes;
}
// -------------------------
// --- functions for qml ---
// -------------------------
int ClimbingRace::getState() {
return this->state;
}
int ClimbingRace::getMode() {
return this->mode;
}
QVariant ClimbingRace::getTimerTextList() {
return this->qmlTimers;
// QVariantList test;
// QVariantMap test2 = {{"text", "1234"}, {"reacttime", 2.0}};
// test.append(test2);
// return test;
}
double ClimbingRace::getNextStartActionDelayProgress() {
return this->nextStartActionDelayProgress;
}
void ClimbingRace::writeSetting(QString key, QVariant value) {
this->refreshMode();
if(this->mode == REMOTE && ( this->remoteSettings.contains(key) || this->remoteOnlySettings.contains(key) ) ){
this->baseConn->writeRemoteSetting(key, value.toString());
}
else if(!this->remoteOnlySettings.contains(key)){
this->appSettings->writeSetting(key, value);
}
}
QString ClimbingRace::readSetting(QString key) {
this->refreshMode();
if(this->mode == REMOTE && ( this->remoteSettings.contains(key) || this->remoteOnlySettings.contains(key) )){
QVariantMap reply = this->baseConn->sendCommand(3001, key);
if(reply["status"] != 200){
return "false";
}
return reply["data"].toString();
}
else if(!this->remoteOnlySettings.contains(key)){
return this->appSettings->loadSetting(key);
}
else {
return "false";
}
}
bool ClimbingRace::connectBaseStation() {
this->reloadBaseStationIpAdress();
return this->baseConn->connectToHost();
}
void ClimbingRace::disconnectBaseStation() {
this->baseConn->closeConnection();
}
QString ClimbingRace::getBaseStationState() {
return this->baseConn->getState();
}
QVariant ClimbingRace::getBaseStationConnections() {
return baseConn->getConnections();
}
bool ClimbingRace::reloadBaseStationIpAdress() {
if(this->baseConn->state == "disconnected"){
this->baseConn->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress"));
return true;
}
return false;
}

View file

@ -23,7 +23,7 @@
#include <QFile>
#include <QDir>
#include <QStandardPaths>
#include <QtWebView/QtWebView>
//#include <QtWebView/QtWebView>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
@ -44,14 +44,17 @@
#include <QScreen>
#include <QQmlApplicationEngine>
#include <QtQml/QQmlContext>
#include <QtWebView/QtWebView>
//#include <QtWebView/QtWebView>
#ifdef Q_OS_ANDROID
#include <QtAndroidExtras>
#endif
#include "headers/sqlstoragemodel.h"
#include "headers/sqlprofilemodel.h"
#include "headers/buzzerconn.h"
#include "headers/appsettings.h"
#include "headers/baseconn.h"
#include "headers/speedtimer.h"
#include "headers/climbingrace.h"
#include "headers/apptheme.h"
#include <QTranslator>
static void connectToDatabase()
@ -64,7 +67,7 @@ static void connectToDatabase()
}
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
qDebug() << writeDir;
if (!writeDir.mkpath("."))
qFatal("Failed to create writable directory at %s", qPrintable(writeDir.absolutePath()));
@ -105,14 +108,22 @@ int main(int argc, char *argv[])
#endif
connectToDatabase();
BuzzerConn * pBuzzerConn = new BuzzerConn(nullptr, "192.168.4.1", 80);
BuzzerConn * pStartpadConn = new BuzzerConn(nullptr, "192.168.43.150", 80);
AppSettings * pAppSettings = new AppSettings();
//setup the sql storage model as a qml model
qmlRegisterType<SqlProfileModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlProfileModel");
qmlRegisterType<SqlStorageModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlStorageModel");
//setup the startpad and buzzer conn qml objects
//qmlRegisterType<BuzzerConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "BuzzerConn");
//qmlRegisterType<BuzzerConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "StartpadConn");
//qmlRegisterType<BaseConn>("com.itsblue.speedclimbingstopwatch", 1, 0, "BaseStationConn");
//qmlRegisterType<SpeedTimerQmlAdapter>("com.itsblue.speedclimbingstopwatch", 1, 0, "SpeedTimerBackend");
qmlRegisterType<ClimbingRace>("com.itsblue.speedclimbingstopwatch", 2, 0, "SpeedBackend");
qmlRegisterType<AppTheme>("com.itsblue.speedclimbingstopwatch", 2, 0, "AppTheme");
//setup translation engine
//to the language of the system
//if the system language is not found the language is set to english
@ -122,19 +133,15 @@ int main(int argc, char *argv[])
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQmlContext *context = engine.rootContext();
context->setContextProperty("_cppAppSettings", pAppSettings);
if (engine.rootObjects().isEmpty())
return -1;
engine.rootContext()->setContextProperty("_cppBuzzerConn", pBuzzerConn);
engine.rootContext()->setContextProperty("_cppStartpadConn", pStartpadConn);
engine.rootContext()->setContextProperty("_cppAppSettings", pAppSettings);
int iRet = 0;
iRet = app.exec();
delete pBuzzerConn;
delete pStartpadConn;
delete pAppSettings;
return iRet;
}

194
sources/speedtimer.cpp Normal file
View file

@ -0,0 +1,194 @@
#include "headers/speedtimer.h"
SpeedTimer::SpeedTimer(QObject *parent) : QObject(parent)
{
this->date = new QDateTime;
this->startTime = 0;
this->stopTime = 0;
this->stoppedTime = 0;
this->reactionTime = 0;
this->state = IDLE;
}
bool SpeedTimer::start(bool force) {
if(this->state != STARTING && !force){
return false;
}
qDebug() << "starting timer";
if(!force){
this->stopTime = 0;
this->stoppedTime = 0;
this->reactionTime = 0;
this->startTime = this->date->currentMSecsSinceEpoch();
}
this->setState(RUNNING);
return true;
}
bool SpeedTimer::stop(int type, bool force) {
// type can be:
// 0: stopped
// 1: cancelled
// 2: failed (fase start)
if( ( this->state != SpeedTimer::STARTING && this->state != SpeedTimer::RUNNING && this->state ) && !force ){
return false;
}
//qDebug() << "Stopping: " << "start Time: " << startTime << " stopTime: " << stopTime << " stoppedTime: " << stoppedTime << " reactionTime: " << reactionTime;
switch (type) {
case 0:
{
this->stopTime = this->date->currentMSecsSinceEpoch();
this->stoppedTime = this->stopTime - this->startTime;
this->setState(STOPPED);
break;
}
case 1:
{
this->stoppedTime = 0;
this->setState(CANCELLED);
break;
}
case 2:
{
this->stoppedTime = this->reactionTime;
this->setState(FAILED);
break;
}
}
qDebug() << "Stopped: " << "start Time: " << startTime << " stopTime: " << stopTime << " stoppedTime: " << stoppedTime << " reactionTime: " << reactionTime;
return true;
//this->startPad->appendCommand("SET_LED_STARTING");
}
bool SpeedTimer::reset(bool force){
if( ( this->state != STOPPED && this->state != FAILED && this->state != CANCELLED ) && !force){
return false;
}
this->startTime = 0;
this->stopTime = 0;
this->stoppedTime = 0;
this->reactionTime = 0;
this->setState(IDLE);
return true;
//this->startPad->appendCommand("SET_LED_STARTING");
}
void SpeedTimer::setState(timerState newState){
if(this->state != newState){
this->state = newState;
qDebug() << "+--- timer state changed: " << newState;
emit this->stateChanged(newState);
}
}
QString SpeedTimer::getState(){
switch(state){
case IDLE:
return("IDLE");
case STARTING:
return("STARTING");
case WAITING:
return ("WAITING");
case RUNNING:
return("RUNNING");
case STOPPED:
return("STOPPED");
case FAILED:
return("FAILED");
case CANCELLED:
return("CANCELLED");
}
}
double SpeedTimer::getCurrTime() {
double currTime;
if(this->state == RUNNING && this->startTime > 0){
currTime = this->date->currentMSecsSinceEpoch() - this->startTime;
}
else {
currTime = this->stoppedTime;
}
return(currTime);
}
QString SpeedTimer::getText() {
//qDebug() << this->getState();
QString newText;
switch (this->state) {
case SpeedTimer::IDLE:
newText = tr("Click Start to start");
break;
case SpeedTimer::STARTING:
newText = "0.000 sec";
break;
case SpeedTimer::WAITING:
newText = tr("Please wait...");
break;
case SpeedTimer::RUNNING:
newText = QString::number( this->getCurrTime() / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::STOPPED:
newText = QString::number( this->stoppedTime / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::FAILED:
newText = tr("False Start");
break;
case SpeedTimer::CANCELLED:
newText = tr("Cancelled");
break;
}
return newText;
}
void SpeedTimer::delay(int mSecs){
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
// quit the loop when the timer times out
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
//quit the loop when the connection was established
// start the timer before starting to connect
timer.start(mSecs);
//connect
//wait for the connection to finish (programm gets stuck in here)
loop.exec();
}
SpeedTimer::timerState SpeedTimer::stateFromString(QString state){
if(state == "IDLE"){
return IDLE;
}
else if (state == "STARTING") {
return STARTING;
}
else if (state == "RUNNING") {
return RUNNING;
}
else if (state == "STOPPED") {
return STOPPED;
}
else if (state == "FAILED") {
return FAILED;
}
else {
return CANCELLED;
}
}

View file

@ -1,9 +1,10 @@
QT += quick sql
QT += quick sql multimedia
android {
QT += androidextras
}
VERSION = 0.04
DEFINES += APP_VERSION=$$VERSION
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
@ -23,14 +24,20 @@ SOURCES += \
sources/main.cpp \
sources/sqlstoragemodel.cpp \
sources/sqlprofilemodel.cpp \
sources/buzzerconn.cpp \
sources/appsettings.cpp
sources/appsettings.cpp \
sources/baseconn.cpp \
sources/speedtimer.cpp \
sources/climbingrace.cpp \
sources/apptheme.cpp
HEADERS += \
headers/sqlstoragemodel.h \
headers/sqlprofilemodel.h \
headers/buzzerconn.h \
headers/appsettings.h
headers/appsettings.h \
headers/baseconn.h \
headers/speedtimer.h \
headers/climbingrace.h \
headers/apptheme.h
RESOURCES += \
shared.qrc \
@ -49,7 +56,8 @@ QTPLUGIN += qtaudio_coreaudio
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
#else: unix:!android: target.path = /opt/$${TARGET}/bin
else: unix:!android: target.path = /home/pi/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
DISTFILES += \

Binary file not shown.

View file

@ -1,106 +1,193 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE" sourcelanguage="en">
<context>
<name>InputDelegate</name>
<message>
<source>delay (ms)</source>
<translation type="vanished">Verzögerung (ms)</translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../qml/SettingsDialog.qml" line="170"/>
<location filename="../qml/SettingsDialog.qml" line="236"/>
<source>Options</source>
<translation>Optionen</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="177"/>
<location filename="../qml/SettingsDialog.qml" line="184"/>
<source>connected to buzzer</source>
<translation>Mit Buzzer verbunden</translation>
<translation type="vanished">Mit Buzzer verbunden</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="177"/>
<location filename="../qml/SettingsDialog.qml" line="184"/>
<source>connect to buzzer</source>
<translation>Mit Buzzer verbinden</translation>
<translation type="vanished">Mit Buzzer verbinden</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="195"/>
<location filename="../qml/SettingsDialog.qml" line="387"/>
<source>connecting...</source>
<translation>verbinde...</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="201"/>
<source>success!</source>
<translation>Erfolg</translation>
<translation type="vanished">Erfolg</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="207"/>
<source>error!</source>
<translation>Fehler</translation>
<translation type="vanished">Fehler</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="232"/>
<location filename="../qml/SettingsDialog.qml" line="245"/>
<location filename="../qml/SettingsDialog.qml" line="379"/>
<source>Base Station</source>
<translation>Base Station</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="257"/>
<source>start sequence</source>
<translation>Start Ablauf</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="275"/>
<location filename="../qml/SettingsDialog.qml" line="267"/>
<source>dark mode</source>
<translation>dunkler Modus</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="310"/>
<source>say &apos;ready&apos;</source>
<translation>sage &apos;ready&apos;</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="291"/>
<location filename="../qml/SettingsDialog.qml" line="332"/>
<location filename="../qml/SettingsDialog.qml" line="327"/>
<location filename="../qml/SettingsDialog.qml" line="359"/>
<source>delay (ms)</source>
<translation>Verzögerung (ms)</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="299"/>
<location filename="../qml/SettingsDialog.qml" line="341"/>
<location filename="../qml/SettingsDialog.qml" line="328"/>
<location filename="../qml/SettingsDialog.qml" line="360"/>
<source>time</source>
<translation>Zeit</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="316"/>
<location filename="../qml/SettingsDialog.qml" line="344"/>
<source>say &apos;at your marks&apos;</source>
<translation>sage &apos;at your marks&apos;</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="387"/>
<source>disconnect</source>
<translation>trennen</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="387"/>
<source>connect</source>
<translation>verbinden</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="403"/>
<source>IP-Adress</source>
<translation>IP-Adresse</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="429"/>
<source>volume</source>
<translation>Lautstärke</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="465"/>
<source>connected extensions</source>
<translation>verbundene Erweiterungen</translation>
</message>
<message>
<location filename="../qml/SettingsDialog.qml" line="492"/>
<source>connections</source>
<translation>Verbindungen</translation>
</message>
<message>
<source>say
&apos;at your marks&apos;</source>
<translation>sage
<translation type="vanished">sage
&apos;at your marks&apos;</translation>
</message>
</context>
<context>
<name>SpeedTimer</name>
<message>
<location filename="../sources/speedtimer.cpp" line="130"/>
<source>Click Start to start</source>
<translation>Tippe start zum Starten</translation>
</message>
<message>
<location filename="../sources/speedtimer.cpp" line="136"/>
<source>Please wait...</source>
<translation>Bitte warten...</translation>
</message>
<message>
<location filename="../sources/speedtimer.cpp" line="145"/>
<source>False Start</source>
<translation>Fehlstart</translation>
</message>
<message>
<location filename="../sources/speedtimer.cpp" line="148"/>
<source>Cancelled</source>
<translation>Abgebrochen</translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../qml/main.qml" line="30"/>
<location filename="../qml/main.qml" line="33"/>
<source>Speedclimbing stw</source>
<translation></translation>
</message>
<message>
<location filename="../qml/main.qml" line="195"/>
<location filename="../qml/main.qml" line="540"/>
<source>Click start to start</source>
<translation>Tippe start zum Starten</translation>
<translation type="vanished">Tippe start zum Starten</translation>
</message>
<message>
<location filename="../qml/main.qml" line="246"/>
<location filename="../qml/main.qml" line="549"/>
<location filename="../qml/main.qml" line="222"/>
<source>reaction time (ms): </source>
<translation>Reaktionszeit (ms): </translation>
</message>
<message>
<location filename="../qml/main.qml" line="349"/>
<location filename="../qml/main.qml" line="629"/>
<source>start</source>
<translation>start</translation>
</message>
<message>
<location filename="../qml/main.qml" line="346"/>
<location filename="../qml/main.qml" line="426"/>
<source>cancel</source>
<translation>Abbruch</translation>
</message>
<message>
<location filename="../qml/main.qml" line="559"/>
<location filename="../qml/main.qml" line="636"/>
<source>Click Start to start</source>
<translation type="unfinished">Tippe start zum Starten</translation>
</message>
<message>
<location filename="../qml/main.qml" line="644"/>
<source>waiting...</source>
<translation>warte...</translation>
</message>
<message>
<location filename="../qml/main.qml" line="652"/>
<source>please wait...</source>
<translation>Bitte warten...</translation>
</message>
<message>
<location filename="../qml/main.qml" line="658"/>
<location filename="../qml/main.qml" line="666"/>
<source>starting...</source>
<translation>starte...</translation>
</message>
<message>
<location filename="../qml/main.qml" line="583"/>
<source>false start</source>
<translation>Fehlstart</translation>
<translation type="vanished">Fehlstart</translation>
</message>
<message>
<location filename="../qml/main.qml" line="589"/>
<location filename="../qml/main.qml" line="686"/>
<source>reset</source>
<translation>reset</translation>
</message>