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
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/speedclimbing_stopwatch.pro.user
|
/speedclimbing_stopwatch.pro.user
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.pro.user*
|
||||||
|
|
45
CHANGELOG
Normal 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
BIN
graphics/icons/BaseStation.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
graphics/icons/BaseStation_black.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
graphics/icons/error.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
graphics/icons/ok.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 18 KiB |
BIN
graphics/icons/user_black.png
Normal file
After Width: | Height: | Size: 11 KiB |
31
headers/apptheme.h
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
qml/ProfilesDialog/AddProfilePage.qml
Normal 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: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
237
qml/ProfilesDialog/ProfileListPage.qml
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
263
qml/ProfilesDialog/ProfilesDialog.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
176
qml/ProfilesDialog/ProfilesStack.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
qml/ProfilesDialog/ResultListPage.qml
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,8 +20,9 @@ import QtMultimedia 5.8
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window 2.2
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import QtQuick.Controls.Styles 1.4
|
||||||
import "./components"
|
import "./components"
|
||||||
import "./styles"
|
|
||||||
|
|
||||||
Popup {
|
Popup {
|
||||||
id: root
|
id: root
|
||||||
|
@ -31,7 +32,7 @@ Popup {
|
||||||
height: startButt.height
|
height: startButt.height
|
||||||
modal: true
|
modal: true
|
||||||
dim: false
|
dim: false
|
||||||
scale: 0
|
opacity: 0
|
||||||
|
|
||||||
property var connections
|
property var connections
|
||||||
|
|
||||||
|
@ -56,28 +57,106 @@ Popup {
|
||||||
}
|
}
|
||||||
|
|
||||||
enter: Transition {
|
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 {
|
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 {
|
background: Rectangle {
|
||||||
radius: width * 0.5
|
radius: width * 0.5
|
||||||
color: StyleSettings.viewColor
|
color: appTheme.style.viewColor
|
||||||
border.color: StyleSettings.lineColor
|
border.color: appTheme.style.lineColor
|
||||||
border.width: 1
|
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
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
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 {
|
Label {
|
||||||
id: head_text
|
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
|
text: options_stack.currentItem.title
|
||||||
font.pixelSize: headlineUnderline.width * 0.1
|
font.pixelSize: headlineUnderline.width * 0.1
|
||||||
color: enabled ? StyleSettings.textColor:StyleSettings.disabledTextColor
|
color: enabled ? appTheme.style.textColor:appTheme.style.disabledTextColor
|
||||||
anchors {
|
|
||||||
horizontalCenter: parent.horizontalCenter
|
|
||||||
top: parent.top
|
|
||||||
topMargin: headlineUnderline.anchors.topMargin / 2 - height / 2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +164,8 @@ Popup {
|
||||||
id: headlineUnderline
|
id: headlineUnderline
|
||||||
height: 1
|
height: 1
|
||||||
width: parent.width
|
width: parent.width
|
||||||
color: StyleSettings.lineColor
|
color: appTheme.style.lineColor
|
||||||
|
visible: false
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top
|
top: parent.top
|
||||||
left: parent.left
|
left: parent.left
|
||||||
|
@ -96,7 +176,7 @@ Popup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
FancyButton {
|
||||||
id: head_back
|
id: head_back
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
|
@ -107,21 +187,12 @@ Popup {
|
||||||
|
|
||||||
height: parent.height * 0.13
|
height: parent.height * 0.13
|
||||||
width: height
|
width: height
|
||||||
opacity: root.closePolicy === Popup.NoAutoClose ? 0:1
|
//opacity: root.closePolicy === Popup.NoAutoClose ? 0:1
|
||||||
enabled: opacity > 0
|
enabled: opacity > 0
|
||||||
|
|
||||||
background: Rectangle {
|
glowOpacity: Math.pow( root.opacity, 100 )
|
||||||
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
|
|
||||||
|
|
||||||
}
|
image: appTheme.style.backIcon
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
options_stack.depth > 1 ? options_stack.pop():root.close()
|
options_stack.depth > 1 ? options_stack.pop():root.close()
|
||||||
|
@ -133,12 +204,12 @@ Popup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
id: options_stack
|
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
|
initialItem: settings
|
||||||
width: headlineUnderline.width
|
width: headlineUnderline.width
|
||||||
|
|
||||||
|
@ -148,7 +219,7 @@ Popup {
|
||||||
top: parent.top
|
top: parent.top
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: ( parent.width - headlineUnderline.width ) / 2
|
leftMargin: ( parent.width - headlineUnderline.width ) / 2
|
||||||
topMargin: headlineUnderline.anchors.topMargin * 0.8
|
topMargin: headlineUnderline.anchors.topMargin * 0.95
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,138 +232,50 @@ Popup {
|
||||||
id: settings
|
id: settings
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
property string title: qsTr("Options")
|
|
||||||
id: settings_col
|
id: settings_col
|
||||||
|
|
||||||
|
property string title: qsTr("Options")
|
||||||
|
spacing: options_stack.rowSpacing
|
||||||
|
|
||||||
/*----Connect to external devices----*/
|
/*----Connect to external devices----*/
|
||||||
ItemDelegate {
|
NextPageDelegate {
|
||||||
id: connect_del
|
id: connect_del
|
||||||
text: qsTr("connections")
|
|
||||||
|
|
||||||
contentItem: Text {
|
height: options_stack.delegateHeight
|
||||||
text: parent.text
|
|
||||||
color: StyleSettings.textColor
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
|
||||||
}
|
|
||||||
|
|
||||||
width: parent.width
|
text: qsTr("Base Station")
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
options_stack.push(connect)
|
options_stack.push(connect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*----Automated Start----*/
|
/*----Automated Start----*/
|
||||||
ItemDelegate {
|
NextPageDelegate {
|
||||||
id: autostart_del
|
id: autostart_del
|
||||||
|
|
||||||
|
height: options_stack.delegateHeight
|
||||||
|
|
||||||
text: qsTr("start sequence")
|
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: {
|
onClicked: {
|
||||||
options_stack.push(autostart)
|
options_stack.push(autostart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*----Style Settings----*/
|
/*----Style Settings----*/
|
||||||
ItemDelegate {
|
SmoothSwitchDelegate {
|
||||||
id: style_del
|
id: styleDel
|
||||||
text: qsTr("change style")
|
text: qsTr("dark mode")
|
||||||
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"
|
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
height: options_stack.delegateHeight
|
||||||
|
|
||||||
|
checked: speedBackend.readSetting("theme") === "Dark"
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
speedBackend.writeSetting("theme", checked ? "Dark":"Light")
|
||||||
|
appTheme.refreshTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionDelegate {
|
|
||||||
id: connect_stap_del
|
|
||||||
|
|
||||||
contentItem: Text {
|
|
||||||
text: parent.text
|
|
||||||
color: StyleSettings.textColor
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
|
||||||
}
|
|
||||||
|
|
||||||
status: root.connections.startpad
|
|
||||||
connect: root.connect
|
|
||||||
type: "startpad"
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,116 +285,228 @@ Popup {
|
||||||
id: autostart
|
id: autostart
|
||||||
Column {
|
Column {
|
||||||
id: autostart_col
|
id: autostart_col
|
||||||
property string title: "Autostart"
|
|
||||||
property int delegateHeight: height*0.18
|
|
||||||
|
|
||||||
SwitchDelegate {
|
spacing: options_stack.rowSpacing
|
||||||
id: ready_del
|
|
||||||
text: qsTr("say 'ready'")
|
property string title: "Autostart"
|
||||||
contentItem: Text {
|
|
||||||
text: parent.text
|
function updateSetting(key, val, del){
|
||||||
color: StyleSettings.textColor
|
speedBackend.writeSetting(key, val)
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checked: _cppAppSettings.loadSetting("ready_en") === "true"
|
function loadSetting(key, del){
|
||||||
width: parent.width
|
var val
|
||||||
height: parent.delegateHeight
|
|
||||||
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
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: {
|
onCheckedChanged: {
|
||||||
_cppAppSettings.writeSetting("ready_en",checked)
|
parent.updateSetting("ready_en",checked, ready_del)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
indicator: SimpleIndicator{}
|
InputDelegate {
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
id: ready_delay_del
|
id: ready_delay_del
|
||||||
text: qsTr("delay (ms)")
|
|
||||||
contentItem: Text {
|
width: parent.width
|
||||||
text: parent.text
|
height: options_stack.delegateHeight
|
||||||
color: StyleSettings.textColor
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled: ready_del.checked
|
enabled: ready_del.checked
|
||||||
width: parent.width
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
|
||||||
height: parent.delegateHeight
|
|
||||||
|
|
||||||
TextField {
|
text: qsTr("delay (ms)")
|
||||||
focus: true
|
inputHint: qsTr("time")
|
||||||
placeholderText: qsTr("time")
|
|
||||||
width: parent.width * 0.3
|
|
||||||
height: parent.height
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||||
|
|
||||||
text: _cppAppSettings.loadSetting("ready_delay")
|
inputText: autostart_col.loadSetting("ready_delay", ready_del)
|
||||||
|
|
||||||
onTextChanged: {
|
onInputFinished: {
|
||||||
_cppAppSettings.writeSetting("ready_delay", text)
|
autostart_col.updateSetting("ready_delay", inputText, ready_delay_del)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchDelegate {
|
SmoothSwitchDelegate {
|
||||||
id: at_marks_del
|
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
|
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: {
|
onCheckedChanged: {
|
||||||
_cppAppSettings.writeSetting("at_marks_en",at_marks_del.checked)
|
parent.updateSetting("at_marks_en",at_marks_del.checked, at_marks_del)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
indicator: SimpleIndicator{}
|
InputDelegate {
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
id: at_marks_delay_del
|
id: at_marks_delay_del
|
||||||
text: qsTr("delay (ms)")
|
|
||||||
contentItem: Text {
|
|
||||||
text: parent.text
|
|
||||||
color: StyleSettings.textColor
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled: at_marks_del.checked
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.delegateHeight
|
height: options_stack.delegateHeight
|
||||||
|
|
||||||
font.pixelSize: options_stack.text_pixelSize
|
text: qsTr("delay (ms)")
|
||||||
|
inputHint: qsTr("time")
|
||||||
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
|
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||||
|
|
||||||
text: _cppAppSettings.loadSetting("at_marks_delay")
|
enabled: at_marks_del.checked
|
||||||
|
|
||||||
onTextChanged: {
|
inputText: autostart_col.loadSetting("at_marks_delay", at_marks_delay_del)
|
||||||
_cppAppSettings.writeSetting("at_marks_delay",text)
|
|
||||||
|
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: options_stack.delegateHeight
|
||||||
|
|
||||||
|
font.pixelSize: height * 0.6
|
||||||
|
}
|
||||||
|
|
||||||
|
InputDelegate {
|
||||||
|
id: baseStationIpDel
|
||||||
|
|
||||||
|
text: qsTr("IP-Adress")
|
||||||
|
|
||||||
|
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"]}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-----Custom animations-----*/
|
/*-----Custom animations-----*/
|
||||||
|
@ -421,18 +516,32 @@ Popup {
|
||||||
property: "opacity"
|
property: "opacity"
|
||||||
from: 0
|
from: 0
|
||||||
to: 1
|
to: 1
|
||||||
duration: 200
|
duration: 300
|
||||||
easing.type: Easing.InOutQuad
|
easing.type: Easing.InOutQuad
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
property: "x"
|
||||||
|
from: width * 0.1
|
||||||
|
to: 0
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pushExit: Transition {
|
pushExit: Transition {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
property: "opacity"
|
property: "opacity"
|
||||||
from: 1
|
from: 1
|
||||||
to: 0
|
to: 0
|
||||||
duration: 200
|
duration: 300
|
||||||
easing.type: Easing.InOutQuad
|
easing.type: Easing.InOutQuad
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
property: "x"
|
||||||
|
to: -width * 0.1
|
||||||
|
from: 0
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
popExit: Transition {
|
popExit: Transition {
|
||||||
|
@ -440,18 +549,30 @@ Popup {
|
||||||
property: "opacity"
|
property: "opacity"
|
||||||
from: 1
|
from: 1
|
||||||
to: 0
|
to: 0
|
||||||
duration: 200
|
duration: 300
|
||||||
easing.type: Easing.InOutQuad
|
easing.type: Easing.InOutQuad
|
||||||
}
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
property: "x"
|
||||||
|
to: width * 0.1
|
||||||
|
from: 0
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
}
|
}
|
||||||
popEnter: Transition {
|
popEnter: Transition {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
property: "opacity"
|
property: "opacity"
|
||||||
from: 0
|
from: 0
|
||||||
to: 1
|
to: 1
|
||||||
duration: 200
|
duration: 300
|
||||||
easing.type: Easing.InOutQuad
|
easing.type: Easing.InOutQuad
|
||||||
}
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
property: "x"
|
||||||
|
from: -width * 0.1
|
||||||
|
to: 0
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,31 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
ItemDelegate {
|
SmoothItemDelegate {
|
||||||
id: control
|
id: control
|
||||||
|
|
||||||
property var status
|
property var status
|
||||||
property var connect
|
property var connect
|
||||||
|
property var disconnect
|
||||||
|
|
||||||
property string type
|
property string type
|
||||||
text: qsTr(type)
|
text: qsTr(type)
|
||||||
|
|
||||||
enabled: status.status === "disconnected"
|
enabled: (status.status === "disconnected" && control.connect !== undefined) || ( status.status === "connected" && control.disconnect !== undefined )
|
||||||
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
connect(type)
|
|
||||||
|
if(status.status === "disconnected"){
|
||||||
|
connect()
|
||||||
if(status.status !== "connected"){
|
if(status.status !== "connected"){
|
||||||
statusIndicator.color_override = "red"
|
statusIndicator.color_override = "red"
|
||||||
shortDelay.start()
|
shortDelay.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: shortDelay
|
id: shortDelay
|
||||||
|
@ -33,9 +41,10 @@ ItemDelegate {
|
||||||
id: statusItem
|
id: statusItem
|
||||||
anchors {
|
anchors {
|
||||||
right: parent.right
|
right: parent.right
|
||||||
|
rightMargin: ( height / control.height / 2 ) * height
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
height: parent.font.pixelSize
|
height: control.height * 0.4
|
||||||
width: height
|
width: height
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
|
@ -7,11 +7,12 @@ Image {
|
||||||
source: "qrc:/graphics/icons/buzzer_black.png"
|
source: "qrc:/graphics/icons/buzzer_black.png"
|
||||||
mipmap: true
|
mipmap: true
|
||||||
|
|
||||||
opacity: status !== "disconnected" ? 1:0
|
opacity: status === "connected" || status === "connecting" ? 1:0
|
||||||
visible: false
|
visible: true
|
||||||
|
|
||||||
width: height
|
width: height
|
||||||
onOpacityChanged: visible = true
|
onOpacityChanged: visible = true
|
||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
//rotating animation
|
//rotating animation
|
||||||
running: status === "connecting"
|
running: status === "connecting"
|
||||||
|
@ -29,12 +30,14 @@ Image {
|
||||||
easing.type: Easing.InOutQuad
|
easing.type: Easing.InOutQuad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on rotation {
|
Behavior on rotation {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 200
|
duration: 200
|
||||||
easing.type: Easing.OutQuad
|
easing.type: Easing.OutQuad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 200
|
duration: 200
|
||||||
|
|
|
@ -29,15 +29,26 @@ SequentialAnimation {
|
||||||
property alias outEasingType: outAnimation.easing.type
|
property alias outEasingType: outAnimation.easing.type
|
||||||
property alias inEasingType: inAnimation.easing.type
|
property alias inEasingType: inAnimation.easing.type
|
||||||
property string easingType: "Quad"
|
property string easingType: "Quad"
|
||||||
|
ParallelAnimation {
|
||||||
NumberAnimation { // in the default case, fade scale to 0
|
NumberAnimation { // in the default case, fade scale to 0
|
||||||
id: outAnimation
|
id: outAnimation
|
||||||
target: root.target
|
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
|
duration: root.fadeDuration_in
|
||||||
to: 0
|
to: 0
|
||||||
easing.type: Easing["In"+root.easingType]
|
easing.type: Easing["In"+root.easingType]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
PropertyAction { } // actually change the property targeted by the Behavior between the 2 other animations
|
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
|
NumberAnimation { // in the default case, fade scale back to 1
|
||||||
id: inAnimation
|
id: inAnimation
|
||||||
target: root.target
|
target: root.target
|
||||||
|
@ -46,4 +57,14 @@ SequentialAnimation {
|
||||||
to: 1
|
to: 1
|
||||||
easing.type: Easing["Out"+root.easingType]
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
95
qml/components/FancyBusyIndicator.qml
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
qml/components/FancyButton.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
67
qml/components/InputDelegate.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
qml/components/NextPageDelegate.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,7 +47,7 @@ Item {
|
||||||
enabled: true
|
enabled: true
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: root.animationDuration
|
||||||
easing.type: Easing.InOutCubic
|
easing.type: Easing.Linear
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ Item {
|
||||||
enabled: true
|
enabled: true
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: root.animationDuration
|
||||||
easing.type: Easing.InOutCubic
|
easing.type: Easing.Linear
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
86
qml/components/RemoteDataListView.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
qml/components/SmoothItemDelegate.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
qml/components/SmoothSliderDelegate.qml
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
87
qml/components/SmoothSwitchDelegate.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
779
qml/main.qml
23
qml/qml.qrc
|
@ -1,19 +1,24 @@
|
||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>main.qml</file>
|
<file>main.qml</file>
|
||||||
<file>ProfilesDialog.qml</file>
|
|
||||||
<file>SettingsDialog.qml</file>
|
<file>SettingsDialog.qml</file>
|
||||||
<file>components/ProgressCircle.qml</file>
|
<file>components/ProgressCircle.qml</file>
|
||||||
<file>components/SimpleIndicator.qml</file>
|
|
||||||
<file>components/ConnectionDelegate.qml</file>
|
<file>components/ConnectionDelegate.qml</file>
|
||||||
<file>components/FadeAnimation.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/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>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -15,10 +15,17 @@
|
||||||
<file>sounds/at_marks_2.wav</file>
|
<file>sounds/at_marks_2.wav</file>
|
||||||
<file>graphics/icons/buzzer_black.png</file>
|
<file>graphics/icons/buzzer_black.png</file>
|
||||||
<file>graphics/icons/ok_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.qm</file>
|
||||||
<file>translations/de_DE.ts</file>
|
<file>translations/de_DE.ts</file>
|
||||||
<file>graphics/icons/settings_black.png</file>
|
<file>graphics/icons/settings_black.png</file>
|
||||||
<file>graphics/icons/startpad_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>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
BIN
sounds/false.wav
Normal file
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
#include "headers/appsettings.h"
|
#include "headers/appsettings.h"
|
||||||
|
|
||||||
|
AppSettings * pGlobalAppSettings = nullptr;
|
||||||
|
|
||||||
AppSettings::AppSettings(QObject* parent)
|
AppSettings::AppSettings(QObject* parent)
|
||||||
:QObject(parent)
|
:QObject(parent)
|
||||||
{
|
{
|
||||||
|
@ -29,7 +31,11 @@ AppSettings::AppSettings(QObject* parent)
|
||||||
this->setDefaultSetting("at_marks_en", "false");
|
this->setDefaultSetting("at_marks_en", "false");
|
||||||
this->setDefaultSetting("at_marks_delay", 0);
|
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)
|
QString AppSettings::loadSetting(const QString &key)
|
||||||
|
|
132
sources/apptheme.cpp
Normal 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
|
@ -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
|
@ -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;
|
||||||
|
}
|
|
@ -23,7 +23,7 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QtWebView/QtWebView>
|
//#include <QtWebView/QtWebView>
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
|
@ -44,14 +44,17 @@
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QtQml/QQmlContext>
|
#include <QtQml/QQmlContext>
|
||||||
#include <QtWebView/QtWebView>
|
//#include <QtWebView/QtWebView>
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include <QtAndroidExtras>
|
#include <QtAndroidExtras>
|
||||||
#endif
|
#endif
|
||||||
#include "headers/sqlstoragemodel.h"
|
#include "headers/sqlstoragemodel.h"
|
||||||
#include "headers/sqlprofilemodel.h"
|
#include "headers/sqlprofilemodel.h"
|
||||||
#include "headers/buzzerconn.h"
|
|
||||||
#include "headers/appsettings.h"
|
#include "headers/appsettings.h"
|
||||||
|
#include "headers/baseconn.h"
|
||||||
|
#include "headers/speedtimer.h"
|
||||||
|
#include "headers/climbingrace.h"
|
||||||
|
#include "headers/apptheme.h"
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
static void connectToDatabase()
|
static void connectToDatabase()
|
||||||
|
@ -64,7 +67,7 @@ static void connectToDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
qDebug() << writeDir;
|
|
||||||
if (!writeDir.mkpath("."))
|
if (!writeDir.mkpath("."))
|
||||||
qFatal("Failed to create writable directory at %s", qPrintable(writeDir.absolutePath()));
|
qFatal("Failed to create writable directory at %s", qPrintable(writeDir.absolutePath()));
|
||||||
|
|
||||||
|
@ -105,14 +108,22 @@ int main(int argc, char *argv[])
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connectToDatabase();
|
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();
|
AppSettings * pAppSettings = new AppSettings();
|
||||||
|
|
||||||
//setup the sql storage model as a qml model
|
//setup the sql storage model as a qml model
|
||||||
qmlRegisterType<SqlProfileModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlProfileModel");
|
qmlRegisterType<SqlProfileModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlProfileModel");
|
||||||
qmlRegisterType<SqlStorageModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlStorageModel");
|
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
|
//setup translation engine
|
||||||
//to the language of the system
|
//to the language of the system
|
||||||
//if the system language is not found the language is set to english
|
//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;
|
QQmlApplicationEngine engine;
|
||||||
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
|
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
|
||||||
|
|
||||||
|
QQmlContext *context = engine.rootContext();
|
||||||
|
context->setContextProperty("_cppAppSettings", pAppSettings);
|
||||||
|
|
||||||
if (engine.rootObjects().isEmpty())
|
if (engine.rootObjects().isEmpty())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
engine.rootContext()->setContextProperty("_cppBuzzerConn", pBuzzerConn);
|
|
||||||
engine.rootContext()->setContextProperty("_cppStartpadConn", pStartpadConn);
|
|
||||||
engine.rootContext()->setContextProperty("_cppAppSettings", pAppSettings);
|
|
||||||
|
|
||||||
int iRet = 0;
|
int iRet = 0;
|
||||||
iRet = app.exec();
|
iRet = app.exec();
|
||||||
|
|
||||||
delete pBuzzerConn;
|
|
||||||
delete pStartpadConn;
|
|
||||||
delete pAppSettings;
|
|
||||||
|
|
||||||
return iRet;
|
return iRet;
|
||||||
}
|
}
|
||||||
|
|
194
sources/speedtimer.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
QT += quick sql
|
QT += quick sql multimedia
|
||||||
|
|
||||||
android {
|
android {
|
||||||
QT += androidextras
|
QT += androidextras
|
||||||
}
|
}
|
||||||
VERSION = 0.04
|
VERSION = 0.04
|
||||||
|
DEFINES += APP_VERSION=$$VERSION
|
||||||
CONFIG += c++11
|
CONFIG += c++11
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
|
@ -23,14 +24,20 @@ SOURCES += \
|
||||||
sources/main.cpp \
|
sources/main.cpp \
|
||||||
sources/sqlstoragemodel.cpp \
|
sources/sqlstoragemodel.cpp \
|
||||||
sources/sqlprofilemodel.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 += \
|
||||||
headers/sqlstoragemodel.h \
|
headers/sqlstoragemodel.h \
|
||||||
headers/sqlprofilemodel.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 += \
|
RESOURCES += \
|
||||||
shared.qrc \
|
shared.qrc \
|
||||||
|
@ -49,7 +56,8 @@ QTPLUGIN += qtaudio_coreaudio
|
||||||
|
|
||||||
# Default rules for deployment.
|
# Default rules for deployment.
|
||||||
qnx: target.path = /tmp/$${TARGET}/bin
|
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
|
!isEmpty(target.path): INSTALLS += target
|
||||||
|
|
||||||
DISTFILES += \
|
DISTFILES += \
|
||||||
|
|
|
@ -1,106 +1,193 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.1" language="de_DE" sourcelanguage="en">
|
<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>
|
<context>
|
||||||
<name>SettingsDialog</name>
|
<name>SettingsDialog</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="170"/>
|
<location filename="../qml/SettingsDialog.qml" line="236"/>
|
||||||
<source>Options</source>
|
<source>Options</source>
|
||||||
<translation>Optionen</translation>
|
<translation>Optionen</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="177"/>
|
|
||||||
<location filename="../qml/SettingsDialog.qml" line="184"/>
|
|
||||||
<source>connected to buzzer</source>
|
<source>connected to buzzer</source>
|
||||||
<translation>Mit Buzzer verbunden</translation>
|
<translation type="vanished">Mit Buzzer verbunden</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="177"/>
|
|
||||||
<location filename="../qml/SettingsDialog.qml" line="184"/>
|
|
||||||
<source>connect to buzzer</source>
|
<source>connect to buzzer</source>
|
||||||
<translation>Mit Buzzer verbinden</translation>
|
<translation type="vanished">Mit Buzzer verbinden</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="195"/>
|
<location filename="../qml/SettingsDialog.qml" line="387"/>
|
||||||
<source>connecting...</source>
|
<source>connecting...</source>
|
||||||
<translation>verbinde...</translation>
|
<translation>verbinde...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="201"/>
|
|
||||||
<source>success!</source>
|
<source>success!</source>
|
||||||
<translation>Erfolg</translation>
|
<translation type="vanished">Erfolg</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="207"/>
|
|
||||||
<source>error!</source>
|
<source>error!</source>
|
||||||
<translation>Fehler</translation>
|
<translation type="vanished">Fehler</translation>
|
||||||
</message>
|
</message>
|
||||||
<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>
|
<source>start sequence</source>
|
||||||
<translation>Start Ablauf</translation>
|
<translation>Start Ablauf</translation>
|
||||||
</message>
|
</message>
|
||||||
<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 'ready'</source>
|
<source>say 'ready'</source>
|
||||||
<translation>sage 'ready'</translation>
|
<translation>sage 'ready'</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="291"/>
|
<location filename="../qml/SettingsDialog.qml" line="327"/>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="332"/>
|
<location filename="../qml/SettingsDialog.qml" line="359"/>
|
||||||
<source>delay (ms)</source>
|
<source>delay (ms)</source>
|
||||||
<translation>Verzögerung (ms)</translation>
|
<translation>Verzögerung (ms)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="299"/>
|
<location filename="../qml/SettingsDialog.qml" line="328"/>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="341"/>
|
<location filename="../qml/SettingsDialog.qml" line="360"/>
|
||||||
<source>time</source>
|
<source>time</source>
|
||||||
<translation>Zeit</translation>
|
<translation>Zeit</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/SettingsDialog.qml" line="316"/>
|
<location filename="../qml/SettingsDialog.qml" line="344"/>
|
||||||
|
<source>say 'at your marks'</source>
|
||||||
|
<translation>sage 'at your marks'</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
|
<source>say
|
||||||
'at your marks'</source>
|
'at your marks'</source>
|
||||||
<translation>sage
|
<translation type="vanished">sage
|
||||||
'at your marks'</translation>
|
'at your marks'</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</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>
|
<context>
|
||||||
<name>main</name>
|
<name>main</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/main.qml" line="30"/>
|
<location filename="../qml/main.qml" line="33"/>
|
||||||
<source>Speedclimbing stw</source>
|
<source>Speedclimbing stw</source>
|
||||||
<translation></translation>
|
<translation></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/main.qml" line="195"/>
|
|
||||||
<location filename="../qml/main.qml" line="540"/>
|
|
||||||
<source>Click start to start</source>
|
<source>Click start to start</source>
|
||||||
<translation>Tippe start zum Starten</translation>
|
<translation type="vanished">Tippe start zum Starten</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/main.qml" line="246"/>
|
<location filename="../qml/main.qml" line="222"/>
|
||||||
<location filename="../qml/main.qml" line="549"/>
|
<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>
|
<source>start</source>
|
||||||
<translation>start</translation>
|
<translation>start</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/main.qml" line="346"/>
|
<location filename="../qml/main.qml" line="426"/>
|
||||||
<source>cancel</source>
|
<source>cancel</source>
|
||||||
<translation>Abbruch</translation>
|
<translation>Abbruch</translation>
|
||||||
</message>
|
</message>
|
||||||
<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>
|
<source>starting...</source>
|
||||||
<translation>starte...</translation>
|
<translation>starte...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/main.qml" line="583"/>
|
|
||||||
<source>false start</source>
|
<source>false start</source>
|
||||||
<translation>Fehlstart</translation>
|
<translation type="vanished">Fehlstart</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/main.qml" line="589"/>
|
<location filename="../qml/main.qml" line="686"/>
|
||||||
<source>reset</source>
|
<source>reset</source>
|
||||||
<translation>reset</translation>
|
<translation>reset</translation>
|
||||||
</message>
|
</message>
|
||||||
|
|