Merge branch 'library_migration'

This commit is contained in:
Dorian Zedler 2021-01-15 18:22:09 +01:00
commit 679d072385
Signed by: dorian
GPG Key ID: 989DE36109AFA354
80 changed files with 2956 additions and 4129 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "shared-libraries"]
path = shared-libraries
url = https://git.itsblue.de/ScStw/shared-libraries.git

View File

@ -1,52 +0,0 @@
# 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).
## [1.1]
### Added
- connecting to and controlling a ScStw Basestation is now possible
- creating profiles and saving times is now possible (only when connected to a ScStw Basestation)
- dark mode
### Changed
- new, more modern style
- new start tone which is compliant with the IFSC norm
- new and improved codebase
## [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

View File

@ -20,24 +20,18 @@ DEFINES += QT_DEPRECATED_WARNINGS
TARGET = ScStwApp
# include submodules
CONFIG += ScStwLibraries_QML ScStwLibraries_Styling ScStwLibraries_ClientLibs
include($$PWD/shared-libraries/ScStwLibraries/ScStwLibraries.pri)
SOURCES += \
sources/scstwappbackend.cpp \
sources/main.cpp \
sources/sqlstoragemodel.cpp \
sources/sqlprofilemodel.cpp \
sources/appsettings.cpp \
sources/baseconn.cpp \
sources/speedtimer.cpp \
sources/climbingrace.cpp \
sources/apptheme.cpp
sources/scstwappsettings.cpp
HEADERS += \
headers/sqlstoragemodel.h \
headers/sqlprofilemodel.h \
headers/appsettings.h \
headers/baseconn.h \
headers/speedtimer.h \
headers/climbingrace.h \
headers/apptheme.h
headers/scstwappbackend.h \
headers/scstwappsettings.h
RESOURCES += \
resources/shared/shared.qrc \
@ -59,23 +53,18 @@ else: unix:!android: target.path = /home/pi/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
DISTFILES += \
android-sources/AndroidManifest.xml \
CHANGELOG \
android-sources/src/MainActivity.java
android/AndroidManifest.xml \
android/build.gradle \
android/gradle.properties \
android/gradle/wrapper/gradle-wrapper.jar \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew \
android/gradlew.bat \
android/res/values/libs.xml \
android/src/de/itsblue/scstw/MainActivity.java
android {
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
}
ios {
QMAKE_ASSET_CATALOGS += resources/shared/graphics/Assets.xcassets
xcode_product_bundle_identifier_setting.value="de.itsblue.ScStwApp"
QMAKE_INFO_PLIST = ios/Info.plist
OBJECTIVE_SOURCES += \
sources/sleepprevent.mm
OBJECTIVE_HEADERS += \
headers/sleepprevent.h
QTPLUGIN -= qtaudio_coreaudio
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
ANDROID_ABIS = armeabi-v7a arm64-v8a
}

View File

@ -1,44 +1,54 @@
<?xml version="1.0"?>
<manifest package="com.itsblue.speedclimbing_stopwatch" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="speedclimbing stw" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="speedclimbing stw" android:screenOrientation="unspecified" android:launchMode="singleTop">
<manifest package="com.itsblue.speedclimbing_stwtest" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:extractNativeLibs="true" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="speedclimbing stw"/>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so:plugins/mediaservice/libqtmedia_android.so:lib/libQt5MultimediaQuick.so"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidBearer.jar:jar/QtMultimedia.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value="org.qtproject.qt5.android.multimedia.QtMultimediaUtils"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
@ -46,42 +56,21 @@
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
</manifest>

View File

@ -22,11 +22,9 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="speedclimbing_stw"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
@ -49,12 +47,10 @@
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
@ -62,11 +58,9 @@
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* full - useful QWidget & Quick Controls 1 apps
@ -75,14 +69,11 @@
-->
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
<!-- extract android style -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28"/>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="29"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.

77
android/build.gradle Normal file
View File

@ -0,0 +1,77 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
}
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion '28.0.3'
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion = qtMinSdkVersion
targetSdkVersion = 29
}
}

11
android/gradle.properties Normal file
View File

@ -0,0 +1,11 @@
# Project-wide Gradle settings.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# Gradle caching allows reusing the build artifacts from a previous
# build with the same inputs. However, over time, the cache size will
# grow. Uncomment the following line to enable it.
#org.gradle.caching=true

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
android/gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
android/gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,22 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>
<!-- The following is handled automatically by the deployment tool. It should
not be edited manually. -->
<array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% -->
</array>
<array name="qt_libs">
<!-- %%INSERT_QT_LIBS%% -->
</array>
<array name="load_local_libs">
<!-- %%INSERT_LOCAL_LIBS%% -->
</array>
</resources>

View File

@ -15,12 +15,13 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsblue.speedclimbing_stopwatch;
package de.itsblue.scstw;
public class MainActivity extends org.qtproject.qt5.android.bindings.QtActivity {
@Override
public void onCreate(android.os.Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//this.getWindow().setVolumeControlStream(android.view.AudioManager.STREAM_MUSIC);
this.setVolumeControlStream(android.media.AudioManager.STREAM_MUSIC);
}
}

View File

@ -1,28 +0,0 @@
#ifndef APPSETTINGS_H
#define APPSETTINGS_H
#include <QObject>
#include <QSettings>
#include <QStandardPaths>
class AppSettings : public QObject
{
Q_OBJECT
public:
explicit AppSettings(QObject *parent = nullptr);
~AppSettings();
Q_INVOKABLE QString loadSetting(const QString &key);
Q_INVOKABLE void writeSetting(const QString &key, const QVariant &variant);
Q_INVOKABLE void setDefaultSetting(const QString &key, const QVariant &defaultVariant);
QSettings *settingsManager;
signals:
public slots:
};
extern AppSettings * pGlobalAppSettings;
#endif // APPSETTINGS_H

View File

@ -1,31 +0,0 @@
#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

View File

@ -1,153 +0,0 @@
#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 <QByteArray>
#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;
QVariant connections;
QString latestReadReply;
//---general status values---//
// some meta data of the base
QString firmwareVersion;
bool firmwareUpToDate;
double timeOffset;
// the current state
QString state;
// can be:
// - 'disconnected'
// - 'connecting'
// - 'connected'
private:
QDateTime *date;
//to get the current time
QTcpSocket *socket;
//socket for communication with the extention
QTimer *timeoutTimer;
QString readBuffer;
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 gotUpdate(QVariant data);
void connectionsChanged();
void connectionSlotReleased();
void nextRemoteActionChanged();
void nextRemoteActionDelayProgChanged();
void gotError(QString error);
void propertiesChanged();
public slots:
void connectToHost();
//function to connect to the base station
void connectionTimeout();
bool init();
void deInit();
void closeConnection();
void gotError(QAbstractSocket::SocketError err);
// --- socket communication handling ---
QVariantMap sendCommand(int header, QJsonValue data = "", bool useTerminationKeys = true, int timeout = 3000);
// --- updater functions ---
bool updateTime();
bool updateFirmware();
bool isFirmwareUpToDate();
// --- helper functions ---
int writeRemoteSetting(QString key, QString value);
bool refreshConnections();
void setConnections(QVariantList connections);
// 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

View File

@ -1,79 +0,0 @@
#ifndef BUZZERCONN_H
#define BUZZERCONN_H
#include <QObject>
#include <QObject>
#include <QDir>
#include <QUrl>
#include <QtNetwork>
#include <QAuthenticator>
#include <QDesktopServices>
#include <QDateTime>
#include <QtDebug>
typedef struct strReturnData{
int status_code;
QString text;
}ReturnData_t;
class BuzzerConn : public QObject
{
Q_OBJECT
public:
explicit BuzzerConn(QObject *parent = nullptr, QString ip = "http://192.168.4.1", int port = 80);
double offset;
QList<double> latest_offsets;
double latest_button_pressed;
double starttime;
bool connected;
int connection_progress;
QString ip;
int port;
int errors;
int errors_until_disconnect = 4;
private:
QNetworkAccessManager *networkManager;
QNetworkAccessManager *reloadNetworkManager;
QDateTime *date;
QTcpSocket *socket;
QStringList pending_commands;
//QSemaphore dataPipe(1);
signals:
public slots:
ReturnData_t senddata(QNetworkAccessManager * NetMan, QUrl serviceUrl, int timeout);
//function to communicate with the buzzer
Q_INVOKABLE signed long sendCommand(QString command, int timeout);
//function to send commands to the sensor
//Can be:
//command - return
//GET_TIMESTAMP - timestamp of the sensor
//GET_LASTPRESSED - timestamp of the sensor when it was triggered the last time
//
//error codes:
//(-1) : timeout
//(-2) : invalid data was recieved
Q_INVOKABLE QList<double> gettimes(int timeout);
//function to get the times from the buzzer as a list with the normal network manager
Q_INVOKABLE bool connect();
//function to connect to buzzer
Q_INVOKABLE bool calcoffset(QList<double> times);
//function that calculates the average time offset between the buzzer and the device
Q_INVOKABLE bool buzzer_triggered();
//function that checks ih the buzzer has been pushed since the last call of this function
Q_INVOKABLE bool start();
//syncs the buzzer and the base to make a start possible
Q_INVOKABLE double get(QString key);
//can return some things (offset, lastpressed, currtime, connection_progress, connected)
Q_INVOKABLE QString test();
Q_INVOKABLE bool refresh();
//refreshed the connection to the buzzer
Q_INVOKABLE void appendCommand(QString command);
};
#endif // BUZZERCONN_H

View File

@ -1,120 +0,0 @@
#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)
Q_PROPERTY(int nextStartAction READ getNextStartAction NOTIFY nextStartActionChanged)
Q_PROPERTY(QVariantMap baseStationProperties READ getBaseStationProperties NOTIFY baseStationPropertiesChanged)
public:
explicit ClimbingRace(QObject *parent = nullptr);
enum raceState { IDLE, STARTING, WAITING, RUNNING, STOPPED };
raceState state;
enum raceMode { LOCAL, REMOTE };
raceMode mode;
enum NextStartAction { AtYourMarks, Ready, Start, None };
private:
AppSettings * appSettings;
BaseConn * baseConn;
QMediaPlayer * player;
QTimer * timerTextRefreshTimer;
QTimer * nextStartActionTimer;
QDateTime *date;
QList<SpeedTimer *> speedTimers;
NextStartAction nextStartAction;
double nextStartActionDelayProgress;
// only used in remote mode:
double nextStartActionDelayStartedAt;
double nextStartActionTotalDelay;
// 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(QVariantList timers);
signals:
void nextStartActionChanged();
void nextStartActionDelayProgressChanged();
void stateChanged(int state);
void modeChanged();
void timerTextChanged();
void baseStationStateChanged();
void baseStationConnectionsChanged();
void baseStationPropertiesChanged();
public slots:
Q_INVOKABLE int startRace();
Q_INVOKABLE int stopRace(int type);
Q_INVOKABLE int resetRace();
// base station sync
void handleBaseStationUpdate(QVariant data);
Q_INVOKABLE bool pairConnectedUsbExtensions();
// functions for qml
Q_INVOKABLE int getState();
Q_INVOKABLE int getMode();
Q_INVOKABLE QVariant getTimerTextList();
Q_INVOKABLE double getNextStartActionDelayProgress();
Q_INVOKABLE int getNextStartAction();
Q_INVOKABLE void writeSetting(QString key, QVariant value);
Q_INVOKABLE QString readSetting(QString key);
Q_INVOKABLE void connectBaseStation();
Q_INVOKABLE void disconnectBaseStation();
Q_INVOKABLE QString getBaseStationState();
Q_INVOKABLE QVariant getBaseStationConnections();
Q_INVOKABLE QVariantMap getBaseStationProperties();
Q_INVOKABLE bool updateBasestationFirmware();
Q_INVOKABLE bool updateBasestationTime();
// 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, int timerId );
Q_INVOKABLE QVariant getResults( QString userName );
Q_INVOKABLE bool reloadBaseStationIpAdress();
};
#endif // CLIMBINGRACE_H

42
headers/scstwappbackend.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef SCSTWAPPBACKEND_H
#define SCSTWAPPBACKEND_H
#include <QObject>
#include <scstwclient.h>
#include <scstwrace.h>
#include <ScStw.hpp>
#include <scstwremoterace.h>
#include "headers/scstwappsettings.h"
class ScStwAppBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(ScStwClient *scStwClient READ getScStwClient WRITE setScStwClient NOTIFY scStwClientChanged)
public:
explicit ScStwAppBackend(QObject *parent = nullptr);
private:
ScStwClient * scStwClient;
public slots:
// functions for qml
Q_INVOKABLE ScStwClient *getScStwClient();
Q_INVOKABLE void setScStwClient(ScStwClient *client);
// 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, int timerId );
Q_INVOKABLE QVariant getResults( QString userName );
signals:
void scStwClientChanged();
};
#endif // SCSTWAPPBACKEND_H

View File

@ -0,0 +1,58 @@
#ifndef APPSETTINGS_H
#define APPSETTINGS_H
#include <QObject>
#include <QSettings>
#include <QStandardPaths>
#include "scstwremotesettings.h"
class ScStwAppSettings : public ScStwRemoteSettings
{
Q_OBJECT
public:
explicit ScStwAppSettings(QObject *parent = nullptr);
explicit ScStwAppSettings(ScStwClient * scStwClient, QObject *parent = nullptr);
enum AppInternalSetting {
InvalidSetting = -1,
AppThemeSetting,
BaseStationIpSetting
};
Q_ENUM(AppInternalSetting)
enum KeyLevelEnum {
KeyLevel = 1
};
Q_ENUM(KeyLevelEnum)
using ScStwSettings::readSetting;
Q_INVOKABLE QVariant readSetting(AppInternalSetting key);
using ScStwSettings::writeSetting;
Q_INVOKABLE bool writeSetting(AppInternalSetting key, QVariant value);
using ScStwSettings::setDefaultSetting;
Q_INVOKABLE bool setDefaultSetting(AppInternalSetting key, QVariant defaultValue);
static QString keyToString(int key) {
return QMetaEnum::fromType<AppInternalSetting>().valueToKey(key);
}
static QVariant::Type keyToType(int key) {
QMap<AppInternalSetting, QVariant::Type> types = {
{AppThemeSetting, QVariant::String},
{BaseStationIpSetting, QVariant::String}
};
if(types.contains(AppInternalSetting(key)))
return types[AppInternalSetting(key)];
return QVariant::Invalid;
}
signals:
public slots:
};
extern ScStwAppSettings * pGlobalAppSettings;
#endif // APPSETTINGS_H

View File

@ -1,48 +0,0 @@
#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, WON, LOST, FAILED, CANCELLED, DISABLED };
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

View File

@ -1,29 +0,0 @@
#ifndef SQLPROFILEMODEL_H
#define SQLPROFILEMODEL_H
#include <QObject>
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
#include <QDateTime>
#include <QSqlRecord>
#include <QSqlTableModel>
class SqlProfileModel : public QSqlTableModel
{
Q_OBJECT
public:
explicit SqlProfileModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
Q_INVOKABLE bool append(QString name);
Q_INVOKABLE void remove(int row);
signals:
public slots:
};
#endif // SQLPROFILEMODEL_H

View File

@ -1,26 +0,0 @@
#ifndef SQLSTORAGEMODEL_H
#define SQLSTORAGEMODEL_H
#include <QObject>
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
#include <QDateTime>
#include <QSqlRecord>
#include <QSqlTableModel>
class SqlStorageModel : public QSqlTableModel
{
Q_OBJECT
public:
explicit SqlStorageModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
signals:
public slots:
};
#endif // SQLSTORAGEMODEL_H

View File

@ -0,0 +1,99 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import "../components"
import "../components/layout"
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
ToolBar {
id: control
property bool connectedToBase: speedBackend.scStwClient.state === ScStwClient.CONNECTED
GridLayout {
id: bottomContentLayout
anchors {
left: parent.left
leftMargin: app.landscape() ? 0:parent.width * 0.1
right: parent.right
rightMargin: app.landscape() ? 0:parent.width * 0.1
top: parent.top
topMargin: app.landscape() ? parent.height * 0.1:0
bottom: parent.bottom
bottomMargin: app.landscape() ? parent.height * 0.1:0
}
columns: app.landscape() ? 1:2
rows: app.landscape() ? 2:1
FancyButton {
id: settingsButt
Layout.alignment: Layout.Center
Layout.preferredHeight: app.landscape() ? width:control.height * 0.8
Layout.preferredWidth: app.landscape() ? control.width * 0.8:height
onClicked: {
settingsDialog.open()
}
image: appTheme.theme.images.settIcon
backgroundColor: parent.pressed ? appTheme.theme.colors.buttonPressed:appTheme.theme.colors.button
}
FancyButton {
id: profilesButt
property double size
state: control.connectedToBase ? "visible":"hidden"
visible: size > 0
Layout.alignment: Layout.Center
Layout.preferredWidth: app.landscape() ? control.width * size:control.height * size
Layout.preferredHeight: width
image: appTheme.theme.images.profilesIcon
backgroundColor: parent.pressed ? appTheme.theme.colors.buttonPressed:appTheme.theme.colors.button
onClicked: {
profilesDialog.open()
}
states: [
State {
name: "hidden"
PropertyChanges {
target: profilesButt
size: 0
}
},
State {
name: "visible"
PropertyChanges {
target: profilesButt
size: 0.8
}
}
]
transitions: [
Transition {
NumberAnimation {
duration: 200
properties: "size"
}
}
]
}
}
}

View File

@ -0,0 +1,395 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.0
import "../components"
import "../components/layout"
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
Item {
id: control
GridLayout {
id: centerLayout
//anchors.centerIn: parent
anchors {
right: parent.right
bottom: parent.bottom
}
width: parent.width
height: parent.height
columns: app.landscape() ? 3:1
rows: app.landscape() ? 1:3
Item {
id: centerExtraContentTop
property double size
Layout.preferredHeight: app.landscape() ? centerLayout.height * 0.6:Math.min(centerLayout.width * size, centerLayout.height * size)
Layout.preferredWidth: app.landscape() ? Math.min(centerLayout.width * size, centerLayout.height * size):centerLayout.width * 0.8
Layout.alignment: Layout.Center
Behavior on size {
NumberAnimation {
duration: 800
easing.type: Easing.InOutQuart
}
}
StackView {
id: centerExtraContentTopStack
anchors.fill: parent
anchors.margins: 1
property QtObject newItem: emptyComp
onNewItemChanged: {
centerExtraContentTopStack.replace(newItem)
}
replaceEnter: Transition {
SequentialAnimation {
PauseAnimation {
duration: 400
}
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 400
easing.type: Easing.InOutQuart
}
}
}
replaceExit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: 400
easing.type: Easing.InOutQuart
}
}
Component {
id: waitingDetailsComp
Column {
opacity: 0
Row {
id: stateIndicatorRow
property int delegateWidth: width / stateIndicatorRepeater.model - (spacing * (stateIndicatorRepeater.model - 1) / stateIndicatorRepeater.model)
property int delegateHeight: height
width: parent.width
height: parent.height * 0.9
spacing: width * 0.1
Repeater {
id: stateIndicatorRepeater
model: scStwRemoteRace.timers.length
delegate: ColumnLayout {
id: timerStatusColumn
property var thisTimer: scStwRemoteRace.timers[index]
width: parent.delegateWidth
height: parent.delegateHeight
Label {
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height * 0.1
font.pixelSize: height * 0.8
fontSizeMode: Text.Fit
minimumPointSize: 1
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: appTheme.theme.colors.text
text: "Lane " + timerStatusColumn.thisTimer["letter"]
}
StateIndicator {
Layout.fillWidth: true
Layout.fillHeight: true
backgroundColor: appTheme.theme.colors.background
successColor: appTheme.theme.colors.success
warningColor: appTheme.theme.colors.warning
state: timerStatusColumn.thisTimer["readyState"] === ScStwTimer.IsReady ?
"success":timerStatusColumn.thisTimer["readyState"] === ScStwTimer.IsDisabled ?
"unknown":"warn"
indicatorSize: 0.8
MouseArea {
anchors.fill: parent
onClicked: {
var disabled = timerStatusColumn.thisTimer["state"] !== ScStwTimer.DISABLED
console.log("setting timer to disabled: " + disabled)
scStwRemoteRace.setTimerDisabled(timerStatusColumn.thisTimer["id"], disabled)
}
}
}
}
}
}
Label {
width: parent.width
height: parent.height * 0.1
font.pixelSize: height * 0.6
fontSizeMode: Text.Fit
minimumPointSize: 1
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: appTheme.theme.colors.text
text: "Tap on a lane to disable it"
}
}
}
Component {
id: incidentDetailsComp
ExtensionOverview {
opacity: 0
backgroundColor: appTheme.theme.colors.background
delegateHeight: centerExtraContentTopStack.height / 5
}
}
Component {
id: emptyComp
Item {}
}
}
}
MainActionButton {
id: mainActionButton
property double size
startProgress: scStwRemoteRace.currentStartDelay["progress"]
Layout.alignment: Layout.Center
Layout.preferredHeight: app.landscape() ? width:Math.min(centerLayout.width * size, centerLayout.height * size)
Layout.preferredWidth: app.landscape() ? Math.min(centerLayout.width * size, centerLayout.height * size):height
onClicked: {
if(progressControlActivated && progress < 1.0)
return
var ret;
switch(scStwRemoteRace.state) {
case ScStwRace.IDLE:
ret = scStwRemoteRace.start()
break;
case ScStwRace.WAITING:
if(!scStwRemoteRace.readySoundEnabled && scStwRemoteRace.isReadyForNextState)
ret = scStwRemoteRace.start()
else
ret = scStwRemoteRace.cancel()
break;
case ScStwRace.PREPAIRING:
case ScStwRace.STARTING:
ret = scStwRemoteRace.cancel()
break;
case ScStwRace.RUNNING:
ret = scStwRemoteRace.stop()
break;
case ScStwRace.STOPPED:
case ScStwRace.INCIDENT:
ret = scStwRemoteRace.reset()
break;
}
if(ret !== 200)
console.log("Error executing main button action: " + ret)
progress = 0
}
Behavior on size {
NumberAnimation {
duration: 800
easing.type: Easing.InOutQuart
}
}
}
Item {
id: centerExtraContentBottom
property double size
Layout.preferredHeight: app.landscape() ? centerLayout.height:Math.min(centerLayout.width * size, centerLayout.height * size)
Layout.preferredWidth: app.landscape() ? Math.min(centerLayout.width * size, centerLayout.height * size):centerLayout.width
Behavior on size {
NumberAnimation {
duration: 400
easing.type: Easing.InOutQuart
}
}
}
}
states: [
State {
name: ScStwRace.IDLE
PropertyChanges {
target: mainActionButton
size: 0.9
text: "start"
}
PropertyChanges {
target: centerLayout
height: app.landscape() ? control.height : Math.max(control.height, app.height * 0.4)
width: app.landscape() ? Math.max(control.width, app.width * 0.4) : control.width
}
},
State {
name: ScStwRace.PREPAIRING
PropertyChanges {
target: mainActionButton
size: 0.9
text: "cancel"
}
},
State {
name: ScStwRace.WAITING
PropertyChanges {
target: mainActionButton
size: scStwRemoteRace.competitionMode ? 0.3:0.9
text: scStwRemoteRace.readySoundEnabled ? "cancel": scStwRemoteRace.isReadyForNextState ? "ready":"cancel"
progressControlActivated: scStwRemoteRace.competitionMode && !scStwRemoteRace.readySoundEnabled && !scStwRemoteRace.isReadyForNextState
}
PropertyChanges {
target: centerExtraContentTop
size: scStwRemoteRace.competitionMode ? 0.7:0
}
PropertyChanges {
target: centerExtraContentBottom
size: scStwRemoteRace.competitionMode ? 0.05:0
}
PropertyChanges {
target: centerExtraContentTopStack
newItem: scStwRemoteRace.competitionMode ? waitingDetailsComp:emptyComp
}
},
State {
name: ScStwRace.STARTING
PropertyChanges {
target: mainActionButton
size: 0.9
text: "cancel"
}
},
State {
name: ScStwRace.RUNNING
PropertyChanges {
target: mainActionButton
size: 0.9
text: scStwRemoteRace.competitionMode ? "cancel":"stop"
progressControlActivated: scStwRemoteRace.competitionMode
}
},
State {
name: ScStwRace.STOPPED
PropertyChanges {
target: mainActionButton
size: 0.5
text: "reset"
}
PropertyChanges {
target: centerLayout
height: app.landscape() ? control.height : Math.max(control.height, app.height * 0.4)
width: app.landscape() ? Math.max(control.width, app.width * 0.4) : control.width
}
},
State {
name: ScStwRace.INCIDENT
PropertyChanges {
target: mainActionButton
size: 0.5
text: "reset"
}
PropertyChanges {
target: centerExtraContentTop
size: scStwRemoteRace.competitionMode ? 0.7:0
}
PropertyChanges {
target: centerExtraContentBottom
size: scStwRemoteRace.competitionMode ? 0.05:0
}
PropertyChanges {
target: centerExtraContentTopStack
newItem: scStwRemoteRace.competitionMode ? incidentDetailsComp:emptyComp
}
}
]
}

View File

@ -0,0 +1,307 @@
import QtQuick 2.9
import QtQuick.Controls 2.9
import "../components"
import "../components/layout"
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
ToolBar {
id: control
property string statusText
Loader {
id: topContentLoader
anchors.fill: parent
Behavior on state {
FadeAnimation {
target: topContentLoader
fadeDuration: 300
}
}
Component {
id: raceStatusLabelComponent
Text {
id: raceStatusLabel
padding: app.landscape() ? width * 0.2 : height * 0.25
text: control.statusText
color: appTheme.theme.colors.text
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: height
minimumPixelSize: 1
Behavior on text {
FadeAnimation {
target: raceStatusLabel
fadeDuration: 100
}
}
}
}
Component {
id: timerColumnComponent
TimerColumn {
id: timerColumn
anchors.fill: parent
anchors.topMargin: app.landscape() ? 0:parent.height * 0.1
anchors.bottomMargin: app.landscape() ? 0:parent.height * 0.1
timers: scStwRemoteRace.timers
colors: appTheme.theme.colors
fontName: appTheme.theme.fonts.timers
showTimerLetter: true
// make text smaller for much better performance
textScale: app.landscape() ? 0.7 : 0.5
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
}
}
Item {
id: connectionIconContainer
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: parent.bottom
bottomMargin: app.landscape() ? parent.height * 0.8:0
rightMargin: app.landscape() ? 0:parent.width * 0.8
}
opacity: scStwRemoteRace.state === ScStwRace.IDLE ? 1:0
ConnectionIcon {
id: baseConnConnIcon
function clientStateToString(state) {
switch(state) {
case ScStwClient.DISCONNECTED:
return "disconnected"
case ScStwClient.CONNECTING:
return "connecting"
case ScStwClient.INITIALISING:
return "connecting"
case ScStwClient.CONNECTED:
return "connected"
}
}
status: clientStateToString(speedBackend.scStwClient.state)
source: appTheme.theme.images.baseStationIcon
anchors {
top: parent.top
topMargin: 10
left: parent.left
leftMargin: 10
}
height: !app.landscape()? parent.height*0.4:parent.width*0.4
}
Row {
id: extensionStatusRow
anchors {
top: parent.top
topMargin: 10
left: baseConnConnIcon.right
leftMargin: 1
}
height: baseConnConnIcon.height * 0.4
spacing: 5
Repeater {
id: extensionStatusRep
model: Object.keys(speedBackend.scStwClient.extensions)
delegate: Rectangle {
id: statusRect
property string thisLetter: modelData
property var thisExtensions: speedBackend.scStwClient.extensions[modelData]
property int thisLaneState: getLaneState(thisExtensions)
width: height
height: parent.height
radius: width * 0.1
color: appTheme.theme.colors.background
border.color: [appTheme.theme.colors.success, appTheme.theme.colors.warning, appTheme.theme.colors.error][thisLaneState]
border.width: width * 0.08
onThisExtensionsChanged: {
thisLaneState = getLaneState(thisExtensions)
}
function getLaneState(extensions) {
var batteryWarning = false;
for(var i = 0; i < extensions.length; i++) {
if(extensions[i]["state"] !== ScStw.ExtensionConnected || extensions[i]["batteryState"] === ScStw.BatteryCritical)
return 2
if(extensions[i]["batteryState"] === ScStw.BatteryWarning)
batteryWarning = true
}
return batteryWarning ? 1:0
}
Behavior on border.color {
ColorAnimation {
duration: 200
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
Text {
anchors.fill: parent
text: parent.thisLetter
fontSizeMode: Text.Fit
font.pixelSize: height * 0.7
font.bold: true
color: statusRect.border.color
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
states: [
State {
name: ScStwRace.IDLE
PropertyChanges {
target: control
statusText: app.landscape() ? "Press\nstart":"Press start"
}
PropertyChanges {
target: topContentLoader
sourceComponent: raceStatusLabelComponent
}
},
State {
name: ScStwRace.PREPAIRING
PropertyChanges {
target: control
statusText: "At your\nmarks"
}
PropertyChanges {
target: topContentLoader
sourceComponent: raceStatusLabelComponent
}
},
State {
name: ScStwRace.WAITING
PropertyChanges {
target: control
statusText: "Ready"
}
PropertyChanges {
target: topContentLoader
sourceComponent: raceStatusLabelComponent
}
},
State {
name: ScStwRace.STARTING
PropertyChanges {
target: control
statusText: "Starting"
}
PropertyChanges {
target: topContentLoader
sourceComponent: raceStatusLabelComponent
}
},
State {
name: ScStwRace.RUNNING
PropertyChanges {
target: topContentLoader
sourceComponent: timerColumnComponent
}
},
State {
name: ScStwRace.STOPPED
PropertyChanges {
target: topContentLoader
sourceComponent: timerColumnComponent
}
},
State {
name: ScStwRace.INCIDENT
PropertyChanges {
target: control
statusText: app.landscape() ? "Technical\nincident!":"Technical incident!"
}
PropertyChanges {
target: topContentLoader
sourceComponent: raceStatusLabelComponent
}
}
]
}

View File

@ -21,7 +21,11 @@ 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 de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"

View File

@ -21,7 +21,11 @@ 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 de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"
@ -91,7 +95,7 @@ RemoteDataListView {
}
text: swipeDelegate.text
color: appTheme.style.textColor
color: appTheme.theme.colors.text
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
@ -104,7 +108,7 @@ RemoteDataListView {
}
background: Rectangle {
color: pressed ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
color: pressed ? appTheme.theme.colors.delegatePressed : appTheme.theme.colors.delegateBackground
Behavior on color {
@ -128,22 +132,22 @@ RemoteDataListView {
Repeater {
id: athleteSelectBoxRep
model: speedBackend.timers.length
model: scStwRemoteRace.timers.length
delegate: CheckBox {
id: control
property bool active: speedBackend.timers[index]["id"] === profileList.listData[swipeDelegate.thisIndex]["active"]
property bool active: scStwRemoteRace.timers[index]["id"] === profileList.listData[swipeDelegate.thisIndex]["active"]
anchors.verticalCenter: parent.verticalCenter
height: parent.height * 0.6
enabled: speedBackend.timers[index]["state"] !== "DISABLED"
enabled: scStwRemoteRace.timers[index]["state"] !== ScStwTimer.DISABLED
checked: control.active
onCheckedChanged: {
if(checked && !control.active && speedBackend.selectAthlete(profileList.listData[swipeDelegate.thisIndex]["userName"], speedBackend.timers[index]["id"])){
if(checked && !control.active && speedBackend.selectAthlete(profileList.listData[swipeDelegate.thisIndex]["userName"], scStwRemoteRace.timers[index]["id"])){
profileList.loadData()
}
}
@ -160,7 +164,7 @@ RemoteDataListView {
radius: width * 0.2
border.color: control.enabled ? control.down ? "#17a81a" : "#21be2b" : "grey"
color: control.down ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
color: control.down ? appTheme.theme.colors.delegatePressed : appTheme.theme.colors.delegateBackground
Rectangle {
width: parent.width * 0.65
@ -233,7 +237,7 @@ RemoteDataListView {
Label {
id: deleteLabel
text: qsTr("Delete")
color: appTheme.style.textColor
color: appTheme.theme.colors.text
verticalAlignment: Label.AlignVCenter
padding: 12
height: parent.height

View File

@ -21,7 +21,12 @@ 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 de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"
@ -59,7 +64,7 @@ Popup {
id: backgroundRect
anchors.fill: parent
radius: width * 0.1
color: appTheme.style.viewColor
color: appTheme.theme.colors.view
}
}
@ -87,7 +92,7 @@ Popup {
Connections {
target: root
onOpened: {
function onOpened() {
profilesStack.init()
}
}
@ -132,7 +137,7 @@ Popup {
anchors.fill: parent
property color color: appTheme.style.viewColor
property color color: appTheme.theme.colors.view
onPaint: {
var ctx = getContext("2d");
@ -173,7 +178,7 @@ Popup {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: appTheme.style.textColor
color: appTheme.theme.colors.text
text: profilesStack.currentItem.title
@ -196,9 +201,9 @@ Popup {
glowOpacity: Math.pow( root.opacity, 100 )
backgroundColor: appTheme.style.buttonColor
backgroundColor: appTheme.theme.colors.button
image: appTheme.style.backIcon
image: appTheme.theme.images.backIcon
onClicked: profilesStack.depth > 1 ? profilesStack.pop():root.close()
@ -221,9 +226,9 @@ Popup {
glowOpacity: opacity < 1 ? Math.pow( opacity, 100 ) : Math.pow( opacity, 100 )
backgroundColor: appTheme.style.buttonColor
backgroundColor: appTheme.theme.colors.button
image: appTheme.style.confirmIcon
image: appTheme.theme.images.confirmIcon
imageScale: profilesStack.currentItem.secondButt === "ok" ? 1:0
Label {
@ -235,7 +240,7 @@ Popup {
}
opacity: profilesStack.currentItem.secondButt === "add" ? 1:0
color: appTheme.style.textColor
color: appTheme.theme.colors.text
text: "+"
font.pixelSize: parent.height * 0.8

View File

@ -21,7 +21,11 @@ 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 de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"

View File

@ -21,7 +21,12 @@ 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 de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"
RemoteDataListView {
@ -83,7 +88,7 @@ RemoteDataListView {
font.pixelSize: height * 0.8
fontSizeMode: Text.Fit
color: appTheme.style.textColor
color: appTheme.theme.colors.text
text: resultDel.getDateText()
}
@ -96,7 +101,7 @@ RemoteDataListView {
font.pixelSize: height * 0.8
fontSizeMode: Text.Fit
color: appTheme.style.textColor
color: appTheme.theme.colors.text
text: qsTr("result: ") + (listData[index]["result"] / 1000).toFixed(3) + " s"
}
@ -109,7 +114,7 @@ RemoteDataListView {
font.pixelSize: height * 0.8
fontSizeMode: Text.Fit
color: appTheme.style.textColor
color: appTheme.theme.colors.text
text: qsTr("reaction time: ") + listData[index]["reactionTime"].toFixed(0) + " ms"
}

View File

@ -1,29 +0,0 @@
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import "../components"
ListView {
id: control
property string title: qsTr("connections")
property var parentObj
spacing: parentObj.rowSpacing
boundsBehavior: Flickable.StopAtBounds
model: speedBackend.baseStationConnections.length
delegate: ConnectionDelegate {
opacity: 1
width: parent.width
height: parentObj.delegateHeight
text: speedBackend.baseStationConnections[index]["name"]
status: {'status': speedBackend.baseStationConnections[index]["state"], 'progress': speedBackend.baseStationConnections[index]["progress"]}
}
}

View File

@ -1,10 +1,17 @@
import QtQuick 2.9
import QtQuick 2.12
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Templates 2.12 as T
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"
Column {
@ -12,105 +19,108 @@ Column {
property string title: qsTr("base station")
property bool baseConnected: speedBackend.baseStationState === "connected"
property bool baseConnected: speedBackend.scStwClient.state === ScStwClient.CONNECTED
property var parentObj
opacity: 0
function doFirmwareUpdate() {
doFirmwareUpdateTimer.start()
}
Timer {
id: doFirmwareUpdateTimer
interval: 10
repeat: false
running: false
onTriggered: {
busyDl.open()
var ret = speedBackend.updateBasestationFirmware()
busyDl.displayMessageAndClose(ret ? "OK":"error", ret ? "#6bd43b":"#e03b2f" )
}
}
ConnectionDelegate {
id: connectToBaseDel
function clientStateToString(state) {
switch(state) {
case ScStwClient.DISCONNECTED:
return "disconnected"
case ScStwClient.CONNECTING:
return "connecting"
case ScStwClient.INITIALISING:
return "connecting"
case ScStwClient.CONNECTED:
return "connected"
}
}
text: status.status === "connected" ? qsTr("disconnect"): status.status === "disconnected" ? qsTr("connect"):qsTr("connecting...")
status: { "status": speedBackend.baseStationState, "progress": 100 }
connect: speedBackend.connectBaseStation
disconnect: speedBackend.disconnectBaseStation
status: { "status": clientStateToString(speedBackend.scStwClient.state), "progress": 100 }
connect: speedBackend.scStwClient.connectToHost
disconnect: speedBackend.scStwClient.closeConnection
type: "baseStation"
width: parent.width
height: parentObj.delegateHeight
}
Loader {
Item {
width: parent.width
height: parent.height * 0.05
Rectangle {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: 2
color: appTheme.theme.colors.line
}
}
StackView {
id: baseStationOptionsLd
property alias parentComp: control
property alias baseConnected: control.baseConnected
width: parent.width
height: parent.height
onBaseConnectedChanged: {
disappearAnim.start()
property alias parentComp: control
property Component sourceComponent: control.baseConnected ? baseStationConnectedOptionsComp : baseStationDisconnectedOptionsComp
onSourceComponentChanged: {
baseStationOptionsLd.replace(sourceComponent)
}
Component.onCompleted: {
baseStationOptionsLd.sourceComponent = control.baseConnected ? baseStationConnectedOptionsComp : baseStationDisconnectedOptionsComp
item.opacity = 1
item.scale = 1
baseStationOptionsLd.replace(sourceComponent)
}
sourceComponent: null
replaceExit: Transition {
ParallelAnimation {
id: disappearAnim
ParallelAnimation {
id: disappearAnim
NumberAnimation {
property: "opacity"
to: 0
duration: 100
}
NumberAnimation {
property: "opacity"
to: 0
duration: 100
target: baseStationOptionsLd.item
}
NumberAnimation {
property: "scale"
to: 0.95
duration: 100
target: baseStationOptionsLd.item
}
onRunningChanged: {
if(!running) {
baseStationOptionsLd.sourceComponent = control.baseConnected ? baseStationConnectedOptionsComp : baseStationDisconnectedOptionsComp
appearAnim.start()
NumberAnimation {
property: "scale"
to: 0.95
duration: 100
}
}
}
ParallelAnimation {
id: appearAnim
replaceEnter: Transition {
ParallelAnimation {
id: appearAnim
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 100
target: baseStationOptionsLd.item
}
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 100
}
NumberAnimation {
property: "scale"
from: 0.95
to: 1
duration: 100
target: baseStationOptionsLd.item
NumberAnimation {
property: "scale"
from: 0.95
to: 1
duration: 100
}
}
}
}
Component {
@ -119,8 +129,6 @@ Column {
Column {
id: baseStationDisconnectedOptions
width: parentComp.width
opacity: 0 // opacity and scale are adjusted by baseStationOptionsLd
scale: 0.95
@ -130,12 +138,11 @@ Column {
text: qsTr("IP")
inputHint: "IP"
inputText: speedBackend.readSetting("baseStationIpAdress")
inputText: scStwAppSettings.readSetting(ScStwAppSettings.BaseStationIpSetting)
inputTextFieldWidth: width * 0.7
onInputTextChanged: {
speedBackend.writeSetting("baseStationIpAdress", inputText)
speedBackend.reloadBaseStationIpAdress()
scStwAppSettings.writeSetting(ScStwAppSettings.BaseStationIpSetting, inputText)
}
width: parent.width
@ -171,258 +178,77 @@ Column {
Component {
id: baseStationConnectedOptionsComp
ScrollView{
id: flickable
contentHeight: baseStationConnectedOptions.childrenRect.height
contentWidth: -1
Column {
id: baseStationConnectedOptions
width: parentComp.width
height: control.height - baseStationOptionsLd.y
clip: true
opacity: 0 // opacity and scale are adjusted by baseStationOptionsLd
scale: 0.95
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical: ScrollBar {
parent: flickable.parent
anchors.top: flickable.top
anchors.left: flickable.right
anchors.bottom: flickable.bottom
policy: ScrollBar.AlwaysOn
interactive: false
}
Column {
id: baseStationConnectedOptions
width: parentComp.width
SmoothSliderDelegate {
id: baseStationVolumeDel
text: qsTr("volume")
width: parent.width
height: parentObj.delegateHeight
sliderValue: 0
onSliderFinished: {
enabled = false
speedBackend.writeSetting("soundVolume", sliderValue)
enabled = true
}
Component.onCompleted: {
var val = speedBackend.readSetting("soundVolume")
if(val !== "false"){
sliderValue = parseFloat(val)
}
}
}
NextPageDelegate {
id: baseStationConnectionsDel
text: qsTr("connected extensions")
width: parent.width
height: parentObj.delegateHeight
visible: height > 5
onClicked: {
parentObj.push(baseStationConnections)
}
}
SmoothItemDelegate {
id: connectUsbExtensionsDel
width: parent.width
height: parentObj.delegateHeight
text: "pair extensions"
onClicked: {
busyDl.open()
var ret = speedBackend.pairConnectedUsbExtensions()
busyDl.displayMessageAndClose(ret ? "OK":"error", ret ? "#6bd43b":"#e03b2f" )
}
}
SmoothItemDelegate {
id: baseStationUpdateDel
// 0: hidden 1: update firmware 2: sync time
property int mode: speedBackend.baseStationProperties["firmware"]["upToDate"] ?
(Math.abs(parseInt(speedBackend.baseStationProperties["timeOffset"])) > 10000 ? 2:0)
:1
width: parent.width
height: mode > 0 ? parentObj.delegateHeight:0
visible: height > 5
text: mode === 2 ? qsTr("sync time"):qsTr("update firmware")
onClicked: {
if(mode === 1) {
control.doFirmwareUpdate()
}
else if(mode === 2){
busyDl.open()
var ret = speedBackend.updateBasestationTime()
busyDl.displayMessageAndClose(ret ? "OK":"error", ret ? "#6bd43b":"#e03b2f" )
}
}
}
Rectangle {
anchors.left: parent.left
width: parent.width
height: 1
color: appTheme.style.lineColor
}
Item {
anchors.left: parent.left
width: parent.width
height: parentObj.delegateHeight * 0.5
Label {
anchors {
top: parent.top
left: parent.left
}
width: parent.width * 0.5
height: parent.height * 0.5
verticalAlignment: Text.AlignTop
horizontalAlignment: Text.AlignLeft
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
color: appTheme.style.lineColor
text: "version: " + speedBackend.baseStationProperties["firmware"]["version"]
}
Label {
property var date: new Date(new Date().getTime() + parseInt(speedBackend.baseStationProperties["timeOffset"]))
anchors {
top: parent.top
right: parent.right
}
width: parent.width * 0.5
height: parent.height
verticalAlignment: Text.AlignTop
horizontalAlignment: Text.AlignRight
fontSizeMode: Text.Fit
minimumPixelSize: 1
font.pixelSize: height
color: appTheme.style.lineColor
text: date.toLocaleDateString() + "\n" + date.toLocaleTimeString()
}
}
}
}
}
Popup {
id: busyDl
property string message: ""
property color messageColor: "transparent"
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
width: app.width
height: app.height
modal: true
dim: true
closePolicy: Dialog.NoAutoClose
function displayMessageAndClose(message, messageColor) {
busyDl.message = message
busyDl.messageColor = messageColor
closeDelayTimer.start()
}
Timer {
id: closeDelayTimer
interval: 1000
repeat: false
running: false
onTriggered: {
busyDl.close()
busyDl.message = ""
busyDl.messageColor = "transparent"
}
}
background: Item {
FancyBusyIndicator {
anchors.centerIn: parent
opacity: busyDl.message === "" ? 1:0
lineColor: "white"
Behavior on opacity { NumberAnimation { duration: 150 } }
}
Label {
anchors.centerIn: parent
id: headerLabel
width: parent.width * 0.5
height: parent.height * 0.5
width: parent.width
height: parentObj.delegateHeight
opacity: busyDl.message === "" ? 0:1
color: busyDl.messageColor
text: busyDl.message
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
font.bold: true
visible: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
Behavior on opacity { NumberAnimation { duration: 150 } }
}
}
fontSizeMode: Text.Fit
font.pixelSize: height
T.Overlay.modal: Rectangle {
id: modalRect
color: "#80404040"
Behavior on opacity { NumberAnimation { duration: 150 } }
minimumPixelSize: 1
color: appTheme.theme.colors.text
text: "ScStwBaseStation"
}
Label {
id: firmwareVersionLabel
width: parent.width
height: parentObj.delegateHeight * 0.3
verticalAlignment: Text.AlignTop
horizontalAlignment: Text.AlignHCenter
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
color: appTheme.theme.colors.line
text: "Firmware: " + speedBackend.scStwClient.getFirmwareVersion() + " API: " + speedBackend.scStwClient.getApiVersion()
}
NextPageDelegate {
text: qsTr("settings")
width: parent.width
height: parentObj.delegateHeight * 0.8
font.pixelSize: height * 0.5
onClicked: {
parentObj.push(baseStationSettings)
}
}
NextPageDelegate {
text: qsTr("extensions")
width: parent.width
height: parentObj.delegateHeight * 0.8
font.pixelSize: height * 0.5
onClicked: {
parentObj.push(extensions)
}
}
}
}
}

View File

@ -0,0 +1,62 @@
import QtQuick 2.12
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"
Column {
id: baseStationSettings
property var parentObj
property string title: "Bs settings"
width: parentObj.width
SmoothSliderDelegate {
id: baseStationVolumeDel
property ScStwSetting setting: scStwAppSettings.getSetting(ScStwSettings.SoundVolumeSetting, ScStwSettings.KeyLevel)
text: qsTr("volume")
width: parent.width
height: parentObj.delegateHeight
font.pixelSize: height * 0.5
sliderValue: setting.readonlyValue
onSliderFinished: {
enabled = false
setting.value = sliderValue
enabled = true
}
}
SmoothSwitchDelegate {
property ScStwSetting setting: scStwAppSettings.getSetting(ScStwSettings.CompetitionModeSetting, ScStwSettings.KeyLevel)
width: parent.width
height: parentObj.delegateHeight
checked: setting.readonlyValue
onClicked: {
enabled = false
setting.value = checked
enabled = true
}
text: qsTr("competition mode")
}
}

View File

@ -41,8 +41,8 @@ Popup {
background: Rectangle {
radius: width * 0.5
color: appTheme.style.viewColor
border.color: appTheme.style.lineColor
color: appTheme.theme.colors.view
border.color: appTheme.theme.colors.line
border.width: 0
Behavior on color {
@ -75,7 +75,7 @@ Popup {
height: header.height
width: header.width
property color color: appTheme.style.viewColor
property color color: appTheme.theme.colors.view
Behavior on color {
ColorAnimation {
@ -129,7 +129,7 @@ Popup {
text: options_stack.currentItem.title
font.pixelSize: headlineUnderline.width * 0.1
color: enabled ? appTheme.style.textColor:appTheme.style.disabledTextColor
color: enabled ? appTheme.theme.colors.text:appTheme.theme.colors.disabledText
}
}
@ -137,7 +137,7 @@ Popup {
id: headlineUnderline
height: 1
width: parent.width
color: appTheme.style.lineColor
color: appTheme.theme.colors.line
visible: false
anchors {
top: parent.top
@ -165,7 +165,7 @@ Popup {
glowOpacity: Math.pow( root.opacity, 100 )
image: appTheme.style.backIcon
image: appTheme.theme.images.backIcon
onClicked: {
options_stack.depth > 1 ? options_stack.pop():root.close()

View File

@ -46,14 +46,22 @@ StackView {
}
/*-----Page to view devices that core connected to the pase startion-----*/
Component{
id: baseStationConnections
Component {
id: extensions
SettingsBaseStationConnectionsPage {
ExtensionOverview {
property string title: qsTr("extensions")
delegateHeight: control.delegateHeight
backgroundColor: appTheme.theme.colors.view
}
}
Component {
id: baseStationSettings
SettingsBaseStationSettingsPage {
parentObj: control
}
}
/*-----Custom animations-----*/

View File

@ -4,6 +4,13 @@ import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"
Column {
@ -16,37 +23,38 @@ Column {
function updateSetting(key, val, del){
del.busy = true
speedBackend.writeSetting(key, val)
scStwAppSettings.writeSetting(key, val, ScStwSettings.KeyLevel)
del.busy = false
}
function loadSetting(key, del){
return speedBackend.readSetting(key)
return scStwAppSettings.readSetting(key, ScStwSettings.KeyLevel)
}
SmoothSwitchDelegate {
id: ready_del
property bool busy: false
property ScStwSetting setting: scStwAppSettings.getSetting(ScStwSettings.ReadySoundEnableSetting, ScStwSettings.KeyLevel)
width: parent.width
height: parentObj.delegateHeight
enabled: !busy
text: qsTr("say 'ready'")
checked: parent.loadSetting("ready_en", ready_del) === "true"
checked: setting.value
onCheckedChanged: {
parent.updateSetting("ready_en", checked, ready_del)
enabled = false
setting.value = checked
enabled = true
}
text: qsTr("say 'ready'")
}
InputDelegate {
id: ready_delay_del
property bool busy: false
property ScStwSetting setting: scStwAppSettings.getSetting(ScStwSettings.ReadySoundDelaySetting, ScStwSettings.KeyLevel)
width: parent.width
height: parentObj.delegateHeight
@ -57,29 +65,32 @@ Column {
inputHint: qsTr("time")
inputMethodHints: Qt.ImhFormattedNumbersOnly
inputText: control.loadSetting("ready_delay", ready_delay_del)
inputText: setting.value
onInputFinished: {
control.updateSetting("ready_delay", inputText, ready_delay_del)
console.log("input finished")
busy = true
setting.value = inputText
busy = false
}
}
SmoothSwitchDelegate {
id: at_marks_del
property bool busy: false
property ScStwSetting setting: scStwAppSettings.getSetting(ScStwSettings.AtYourMarksSoundEnableSetting, ScStwSettings.KeyLevel)
width: parent.width
height: parentObj.delegateHeight
enabled: !busy
text: qsTr("say 'at your marks'")
checked: control.loadSetting("at_marks_en", ready_del) === "true"
checked: setting.value
onCheckedChanged: {
parent.updateSetting("at_marks_en",at_marks_del.checked, at_marks_del)
enabled = false
setting.value = checked
enabled = true
}
}
@ -87,6 +98,7 @@ Column {
id: at_marks_delay_del
property bool busy: false
property ScStwSetting setting: scStwAppSettings.getSetting(ScStwSettings.AtYourMarksSoundDelaySetting, ScStwSettings.KeyLevel)
width: parent.width
height: parentObj.delegateHeight
@ -95,12 +107,14 @@ Column {
inputHint: qsTr("time")
inputMethodHints: Qt.ImhFormattedNumbersOnly
enabled: !busy && at_marks_del.checked
enabled: !busy && at_marks_del.checked
inputText: control.loadSetting("at_marks_delay", at_marks_delay_del)
inputText: setting.value
onInputFinished: {
control.updateSetting("at_marks_delay", inputText, at_marks_delay_del)
busy = true
setting.setValue(inputText)
busy = false
}
}
}

View File

@ -4,6 +4,13 @@ import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
import de.itsblue.ScStwApp 2.0
import "../components"
Column {
@ -48,11 +55,10 @@ Column {
width: parent.width
height: parentObj.delegateHeight
checked: speedBackend.readSetting("theme") === "Dark"
checked: appTheme.setting.value === "Dark"
onCheckedChanged: {
speedBackend.writeSetting("theme", checked ? "Dark":"Light")
appTheme.refreshTheme()
appTheme.setting.setValue(checked ? "Dark":"Light")
}
}
}

View File

@ -0,0 +1,250 @@
import QtQuick 2.0
Rectangle {
id: control
property color chargingColor: "green"
property color fineColor: "green"
property color warningColor: "orange"
property color criticalColor: "red"
property color unknownColor: "grey"
property color notChargingColor: "red"
property color backgroundColor: "white"
property double indicatorSize: 1
height: app.height * 0.9
width: height * 0.8
color: backgroundColor
border.width: Math.min(width * 0.1, height * 0.1) * control.indicatorSize
border.color: "green"
radius: border.width
state: "charging"
Rectangle {
id: outerRect
anchors {
right: parent.left
rightMargin: parent.border.width * 0.25
verticalCenter: parent.verticalCenter
}
width: parent.border.width
height: parent.height * 0.5
radius: width * 0.5
color: parent.border.color
}
Rectangle {
id: innerRect
property double amountFilled: 1
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
margins: parent.border.width * 1.5
}
width: (parent.width - parent.border.width * 3) * amountFilled
radius: control.radius * 0.5
border.width: 0
color: control.border.color
Rectangle {
id: chargingRect
property double amountFilled: 0
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
}
width: parent.width * amountFilled
radius: innerRect.radius
color: Qt.darker(innerRect.color, 1.5)
}
}
Text {
id: questionMarkText
visible: false
anchors.centerIn: parent
height: parent.height * 0.7
width: parent.width * 0.7
color: outerRect.color
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.bold: true
font.pixelSize: height
text: "?"
}
ParallelAnimation {
id: chargingAnimation
running: false
loops: Animation.Infinite
alwaysRunToEnd: true
NumberAnimation {
target: chargingRect
properties: "amountFilled"
from: 0
to: 1
duration: 3000
easing.type: Easing.OutQuad
}
NumberAnimation {
target: chargingRect
properties: "opacity"
from: 1
to: 0
duration: 3000
easing.type: Easing.Linear
}
}
states: [
State {
name: "charging"
PropertyChanges {
target: control
border.color: control.chargingColor
}
PropertyChanges {
target: innerRect
amountFilled: 1
}
PropertyChanges {
target: chargingAnimation
running: true
}
},
State {
name: "notCharging"
PropertyChanges {
target: control
border.color: control.notChargingColor
}
PropertyChanges {
target: innerRect
amountFilled: 0
}
PropertyChanges {
target: questionMarkText
visible: true
}
},
State {
name: "fine"
PropertyChanges {
target: control
border.color: control.fineColor
}
PropertyChanges {
target: innerRect
amountFilled: 1
}
},
State {
name: "warning"
PropertyChanges {
target: control
border.color: control.warningColor
}
PropertyChanges {
target: innerRect
amountFilled: 0.3
}
},
State {
name: "critical"
PropertyChanges {
target: control
border.color: control.criticalColor
}
PropertyChanges {
target: innerRect
amountFilled: 0.1
}
},
State {
name: "unknown"
PropertyChanges {
target: control
border.color: control.unknownColor
}
PropertyChanges {
target: innerRect
amountFilled: 0
}
PropertyChanges {
target: questionMarkText
visible: true
}
}
]
transitions: [
Transition {
NumberAnimation {
properties: "amountFilled"
duration: 400
easing.type: Easing.InOutQuad
}
ColorAnimation {
properties: "color,border.color"
duration: 400
easing.type: Easing.InOutQuad
}
}
]
}

View File

@ -6,27 +6,31 @@ SmoothItemDelegate {
property var status
property string oldState: ""
property var connect
property var connect: null
property var disconnect
property string batteryState: "unknown"
property string type
text: qsTr(type)
enabled: (status.status === "disconnected" && control.connect !== undefined) || ( status.status === "connected" && control.disconnect !== undefined )
onClicked: {
if(status.status === "disconnected"){
if(connect == null)
return;
if(status.status === "disconnected")
connect()
}
else {
else
disconnect()
}
}
onStatusChanged: {
if(oldState !== status.status) {
if(status.status === "disconnected" && oldState === "connecting") {
statusIndicator.color_override = appTheme.style.errorColor
statusIndicator.color_override = "error"
shortDelay.start()
}
oldState = status.status
@ -43,57 +47,59 @@ SmoothItemDelegate {
}
}
Item {
id: statusItem
Row {
anchors {
right: parent.right
rightMargin: ( height / control.height / 2 ) * height
verticalCenter: parent.verticalCenter
}
height: control.height * 0.4
width: height
Rectangle {
spacing: height * 0.4
BatteryIndicator {
id: batteryIndicator
height: control.height * 0.4
width: height
opacity: control.batteryState === "unknown" ? 0:1
backgroundColor: "transparent"
chargingColor: fineColor
fineColor: appTheme.theme.colors.success
warningColor: appTheme.theme.colors.warning
criticalColor: appTheme.theme.colors.error
notChargingColor: warningColor
state: control.batteryState
indicatorSize: 0.8
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
StateIndicator {
id: statusIndicator
property string color_override: ""
anchors.fill: parent
color: color_override === "" ? status.status === "connected" ? appTheme.style.successColor:"transparent":color_override
opacity: status.status === "connecting" ? 0:1
radius: height * 0.5
border.color: appTheme.style.lineColor
border.width: 1
Behavior on color {
ColorAnimation {
duration: 200
}
}
height: control.height * 0.4
width: height
Behavior on opacity {
NumberAnimation {
duration: 800
state: color_override === "" ? status.status === "connected" ? "success": status.status === "connecting" ? "working":"unknown":color_override
}
}
}
indicatorSize: 0.8
ProgressCircle {
id: prog
anchors.fill: parent
opacity: status.status === "connecting" ? 1:0
lineWidth: 1
radius: border.width * 2
arcBegin: 0
arcEnd: 360 * ( status.progress / 100 )
colorCircle: appTheme.style.lineColor
onColorCircleChanged: prog.repaint()
onArcEndChanged: prog.repaint()
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
backgroundColor: appTheme.theme.colors.background
successColor: appTheme.theme.colors.success
errorColor: appTheme.theme.colors.error
}
}
}

View File

@ -0,0 +1,110 @@
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import "../components"
ListView {
id: control
property int delegateHeight: 40
property color backgroundColor: "transparent"
spacing: control.delegateHeight * 0.3
boundsBehavior: Flickable.StopAtBounds
clip: true
model: Object.keys(speedBackend.scStwClient.extensions)
header: Rectangle {
// for top spacing
width: parent.width
height: control.spacing
color: control.backgroundColor
}
delegate: Rectangle {
id: laneContainerRect
property string thisLane: modelData
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width * 0.98
height: control.delegateHeight * extensionsList.model
radius: width * 0.05
border.width: 1
border.color: appTheme.theme.colors.line
color: control.backgroundColor
Rectangle {
id: laneLabelRect
anchors {
left: parent.left
leftMargin: parent.radius * 1
verticalCenter: parent.top
}
height: control.delegateHeight * 0.5
width: laneLabel.font.pixelSize * 4
color: control.backgroundColor
Label {
id: laneLabel
height: parent.height
width: parent.width
leftPadding: laneContainerRect.width * 0.02
rightPadding: laneContainerRect.width * 0.02
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.Fit
font.pixelSize: height * 0.5
color: appTheme.theme.colors.text
text: "Lane " + laneContainerRect.thisLane
}
}
ListView {
id: extensionsList
anchors.fill: parent
anchors.margins: width * 0.04
interactive: false
model: speedBackend.scStwClient.extensions[parent.thisLane].length
delegate: ConnectionDelegate {
property var thisExtension: speedBackend.scStwClient.extensions[laneContainerRect.thisLane][index]
property var stateTranslations: ["disconnected", "connecting", "connecting", "connected"]
property var batteryStateStrings: ["critical", "warning", "fine", "charging", "notCharging"]
color: control.backgroundColor
batteryState: thisExtension["batteryState"] === -1 ? "unknown":batteryStateStrings[thisExtension["batteryState"]]
height: extensionsList.height / extensionsList.model
enabled: true
text: thisExtension["type"] === 0 ? "StartPad":"TopPad" // TODO: make dynamic with ScStw::extensionTypeToString()
status: {'status': stateTranslations[thisExtension["state"]]}
}
}
}
}

View File

@ -1,70 +0,0 @@
/*
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.0
SequentialAnimation {
id: root
property QtObject target
property string fadeProperty: "scale"
property int fadeDuration: 150
property int fadeDuration_in: fadeDuration
property int fadeDuration_out: fadeDuration
property alias outValue: outAnimation.to
property alias inValue: inAnimation.to
property alias outEasingType: outAnimation.easing.type
property alias inEasingType: inAnimation.easing.type
property string easingType: "Quad"
ParallelAnimation {
NumberAnimation { // in the default case, fade scale to 0
id: outAnimation
target: root.target
property: "scale"
duration: root.fadeDuration_in
to: 0.9
easing.type: Easing["In"+root.easingType]
}
NumberAnimation { // in the default case, fade scale to 0
id: outAnimation2
target: root.target
property: "opacity"
duration: root.fadeDuration_in
to: 0
easing.type: Easing["In"+root.easingType]
}
}
PropertyAction { } // actually change the property targeted by the Behavior between the 2 other animations
ParallelAnimation {
NumberAnimation { // in the default case, fade scale back to 1
id: inAnimation
target: root.target
property: root.fadeProperty
duration: root.fadeDuration_out
to: 1
easing.type: Easing["Out"+root.easingType]
}
NumberAnimation { // in the default case, fade scale to 0
id: inAnimation2
target: root.target
property: "opacity"
duration: root.fadeDuration_in
to: 1
easing.type: Easing["In"+root.easingType]
}
}
}

View File

@ -1,64 +0,0 @@
import QtQuick 2.3
import QtQuick.Controls 2.4
BusyIndicator {
id: control
property double animationSpeed: 1000
property double formFactor: 4.5
property color lineColor: "#21be2b"
contentItem: Item {
implicitWidth: 64
implicitHeight: 64
Item {
id: item
anchors.fill: parent
property int currentHeight: 0
SequentialAnimation {
running: control.running
loops: Animation.Infinite
NumberAnimation {
target: item
property: "currentHeight"
from: 0
to: 800
duration: control.animationSpeed
}
}
Row {
anchors.fill: parent
spacing: item.width / 9
Repeater {
id: repeater
model: 5
Rectangle {
property double heightMultiplier: Math.abs( Math.sin( ( ((item.currentHeight/100) + (index*(control.formFactor/repeater.model)))) * (Math.PI/8) ) )
anchors.verticalCenter: parent.verticalCenter
width: item.width / 9
height: ( heightMultiplier ) * ( item.height - 1 ) + 1
radius: width * 0.5
color: control.lineColor
}
}
}
}
}
}

View File

@ -1,12 +1,15 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
Button {
id: control
property string image
property color backgroundColor: appTheme.style.buttonColor
property color backgroundColor: appTheme.theme.colors.button
property real imageScale: 1
property double glowRadius: 0.001
property double glowSpread: 0.2
@ -14,16 +17,16 @@ Button {
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
Behavior on text {
//animate a text change
enabled: true
FadeAnimation {
target: text
}
}
contentItem: Item {}
background: Item {
id: controlBackgroundContainer
@ -56,6 +59,26 @@ Button {
}
}
Text {
id: text
anchors.centerIn: parent
anchors.verticalCenterOffset: -height * 0.05
height: parent.height * 0.6
width: parent.width * 0.6
fontSizeMode: Text.Fit
font.pixelSize: Math.max(parent.height * 0.16, parent.width * 0.16)
font.family: "Helvetica"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control.text
color: enabled ? appTheme.theme.colors.text:appTheme.theme.colors.disabledText
}
Image {
id: buttonIcon
source: control.image

View File

@ -1,5 +1,5 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Controls 2.2
SmoothItemDelegate {
id: control
@ -37,7 +37,7 @@ SmoothItemDelegate {
inputMethodHints: control.inputMethodHints
palette.text: appTheme.style.textColor
palette.text: appTheme.theme.colors.text
onTextChanged: {
control.inputText = text

View File

@ -0,0 +1,129 @@
import QtQuick 2.0
import QtQuick.Controls 2.9
import QtGraphicalEffects 1.0
import de.itsblue.ScStw 2.0
import de.itsblue.ScStw.Styling 2.0
import de.itsblue.ScStw.Styling.Components 1.0
DelayButton {
id : control
text: "start"
property color backgroundColor: appTheme.theme.colors.button
property bool progressControlActivated: false
property double startProgress
property double oldStartProgress: -1
delay: progressControlActivated ? 2000:0
onStartProgressChanged: {
if(startProgress > oldStartProgress)
oldStartProgress = startProgress
else {
startProgressAnimation.from = oldStartProgress
startProgressAnimation.to = startProgress
startProgressAnimation.start()
}
}
Text {
id: labelText
text: control.text
anchors.centerIn: parent
font.pixelSize: parent.height * 0.16
font.family: "Helvetica"
color: enabled ? appTheme.theme.colors.text:appTheme.theme.colors.disabledText
}
NumberAnimation {
id: startProgressAnimation
to: 0
target: control
properties: "oldStartProgress"
duration: 200
}
Behavior on text {
//animate a text change
enabled: true
FadeAnimation {
target: labelText
}
}
contentItem: Text {
}
background: Item {
RectangularGlow {
glowRadius: 0.001
spread: 0.2
color: "black"
visible: true
cornerRadius: background.radius
anchors.fill: background
scale: 0.75
opacity: Math.pow( control.opacity, 100 )
}
Rectangle {
id: background
implicitWidth: 100
implicitHeight: 100
color: control.down ? Qt.darker(control.backgroundColor, 1.2) : control.backgroundColor
radius: size / 2
readonly property real size: Math.min(control.width, control.height)
width: size
height: size
anchors.fill: parent
Behavior on color {
ColorAnimation {
duration: 200
}
}
Canvas {
id: canvas
anchors.fill: parent
Connections {
target: control
function onProgressChanged() {
canvas.requestPaint()
}
function onOldStartProgressChanged() {
canvas.requestPaint()
}
}
onPaint: {
var progress
var showHoldProgress = ((control.oldStartProgress <= 0 || control.oldStartProgress === 1) && control.progressControlActivated)
if(showHoldProgress)
progress = control.progress
else
progress = control.oldStartProgress < 0 ? 0:1-control.oldStartProgress
var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)
ctx.strokeStyle = showHoldProgress ? appTheme.theme.colors.error:"grey"
ctx.lineWidth = parent.width * 0.02
ctx.beginPath()
var startAngle = Math.PI * 1.5
var endAngle = startAngle + progress * Math.PI * 2
ctx.arc(width / 2, height / 2, width / 2 - ctx.lineWidth / 2 - 2, startAngle, endAngle)
ctx.stroke()
}
}
}
}
}

View File

@ -9,7 +9,7 @@ SmoothItemDelegate {
Image {
id: forwardImage
source: appTheme.style.backIcon
source: appTheme.theme.images.backIcon
rotation: 180
height: control.height * 0.4
width: height

View File

@ -1,6 +1,8 @@
import QtQuick 2.10
import QtQuick.Controls 2.4
import de.itsblue.ScStw.Styling.Components 1.0
Item {
id: control
@ -73,8 +75,12 @@ Item {
}
FancyBusyIndicator {
BusyIndicator {
anchors.centerIn: parent
width: app.landscape() ? parent.height * 0.1:parent.width * 0.1
height: width
opacity: !(status === 200 || status === 902) ? 1:0
Behavior on opacity {

View File

@ -4,8 +4,9 @@ import QtQuick.Controls 2.2
ItemDelegate {
id: control
text: ""
property color textColor: appTheme.style.textColor
property color textColor: appTheme.theme.colors.text
property alias backgroundRect: backgroundRect
property color color: appTheme.theme.colors.delegateBackground
font.pixelSize: height * 0.4
@ -26,7 +27,7 @@ ItemDelegate {
}
text: control.text
color: appTheme.style.textColor
color: appTheme.theme.colors.text
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
@ -42,7 +43,7 @@ ItemDelegate {
background: Rectangle {
id: backgroundRect
color: control.down ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
color: control.down ? appTheme.theme.colors.delegatePressed : appTheme.theme.colors.delegateBackground
radius: height * 0.3

View File

@ -1,5 +1,5 @@
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Controls 2.2
SwitchDelegate {
id: control
@ -24,7 +24,7 @@ SwitchDelegate {
}
text: control.text
color: appTheme.style.textColor
color: appTheme.theme.colors.text
fontSizeMode: Text.Fit
@ -60,7 +60,7 @@ SwitchDelegate {
width: parent.height
height: parent.height
radius: height * 0.5
color: parent.down ? appTheme.style.buttonPressedColor:appTheme.style.backgroundColor
color: parent.down ? appTheme.theme.colors.buttonPressed:appTheme.theme.colors.background
border.color: parent.checked ? (parent.down ? "#17a81a" : "#21be2b") : "#999999"
Behavior on x{
NumberAnimation {
@ -74,7 +74,7 @@ SwitchDelegate {
background: Rectangle {
opacity: enabled ? 1 : 0.3
color: control.down ? appTheme.style.delegatePressedColor : appTheme.style.delegateBackgroundColor
color: control.down ? appTheme.theme.colors.delegatePressed : appTheme.theme.colors.delegateBackground
radius: height * 0.3

View File

@ -0,0 +1,316 @@
import QtQuick 2.0
Rectangle {
id: control
property color successColor: "green"
property color warningColor: "orange"
property color errorColor: "red"
property color unknownColor: "grey"
property color backgroundColor: "white"
property color workingColor: "grey"
property double indicatorSize: 1
height: app.height * 0.9
width: height * 0.8
color: backgroundColor
border.width: Math.min(width * 0.1, height * 0.1) * control.indicatorSize
border.color: "green"
radius: border.width * 0.5
state: "success"
clip: true
Rectangle {
id: higherRect
property int totalWidth: Math.abs(Math.sin(rotation * Math.PI / 180) * (height))
property int totalHeight: Math.abs(Math.cos(rotation * Math.PI / 180) * (height))
radius: width * 0.5
border.width: 0
border.color: control.workingColor
color: "green"
}
Rectangle {
id: lowerRect
property int totalWidth: Math.abs(Math.sin(rotation * Math.PI / 180) * (height))
property int totalHeight: Math.abs(Math.cos(rotation * Math.PI / 180) * (height))
radius: width * 0.5
border.width: 0
border.color: control.workingColor
color: "green"
}
ParallelAnimation {
id: workingAnimation
property int from: lowerRect.height
property int to: Math.max(control.width * 1.1, control.height * 1.1)
property bool shouldBeRunning: false
running: false
loops: Animation.Infinite
onShouldBeRunningChanged: {
pauseAnimation.start()
}
NumberAnimation {
target: higherRect
properties: "height"
from: workingAnimation.from
to: workingAnimation.to
duration: 1000
easing.type: Easing.InQuad
}
NumberAnimation {
target: higherRect
properties: "opacity"
from: 1
to: 0.3
duration: 1000
easing.type: Easing.InQuad
}
}
PauseAnimation {
id: pauseAnimation
duration: 400
onStopped: {
workingAnimation.running = workingAnimation.shouldBeRunning
higherRect.opacity = 1
}
}
states: [
State {
name: "working"
PropertyChanges {
target: control
border.color: control.workingColor
}
PropertyChanges {
target: higherRect
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: height
height: Math.min(parent.width * 0.1, parent.height * 0.1) * control.indicatorSize
color: "transparent"
border.color: control.workingColor
border.width: control.border.width
}
PropertyChanges {
target: lowerRect
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: height
height: Math.min(parent.width * 0.2, parent.height * 0.2) * control.indicatorSize
color: control.workingColor
}
PropertyChanges {
target: workingAnimation
shouldBeRunning: true
}
},
State {
name: "unknown"
PropertyChanges {
target: control
border.color: control.unknownColor
}
PropertyChanges {
target: higherRect
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: control.border.width
height: Math.min(parent.width * 0.7, parent.height * 0.7) * control.indicatorSize
color: control.unknownColor
rotation: 90
}
PropertyChanges {
target: lowerRect
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: control.border.width
height: Math.min(parent.width * 0.7, parent.height * 0.7) * control.indicatorSize
color: control.unknownColor
rotation: -90
}
},
State {
id: errorState
name: "error"
property int distance: control.height * 0.1
PropertyChanges {
target: control
border.color: control.errorColor
}
PropertyChanges {
target: higherRect
x: (parent.width - width) / 2
y: control.border.width + parent.height * 0.15
width: control.border.width
height: parent.height * 0.4 * control.indicatorSize
color: control.errorColor
}
PropertyChanges {
target: lowerRect
x: (parent.width - width) / 2
y: higherRect.y + higherRect.height + errorState.distance
width: control.border.width * 1.3
height: width
color: control.errorColor
}
},
State {
name: "warn"
PropertyChanges {
target: control
border.color: control.warningColor
}
PropertyChanges {
target: higherRect
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: control.border.width
height: Math.min(parent.width * 0.7, parent.height * 0.7) * control.indicatorSize
color: control.warningColor
rotation: 45
}
PropertyChanges {
target: lowerRect
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: control.border.width
height: Math.min(parent.width * 0.7, parent.height * 0.7) * control.indicatorSize
color: control.warningColor
rotation: -45
}
},
State {
id: tickState
property int bottomX: ((control.width) / 2 - (higherRect.totalWidth - (lowerRect.totalWidth + higherRect.totalWidth) / 2 )) * 1.055
property int bottomY: ((control.height + higherRect.totalHeight) / 2 - lowerRect.radius) * 1.05
name: "success"
PropertyChanges {
target: control
border.color: control.successColor
}
PropertyChanges {
target: higherRect
x: tickState.bottomX - width / 2 + (Math.sin(rotation * Math.PI / 180) * (height / 2 - radius))
y: tickState.bottomY - height / 2 - (Math.cos(rotation * Math.PI / 180) * (height / 2 - radius))
width: control.border.width
height: Math.min(parent.width * 0.6, parent.height * 0.6) * control.indicatorSize
rotation: 40
color: control.successColor
}
PropertyChanges {
target: lowerRect
x: tickState.bottomX - width / 2 + (Math.sin(rotation * Math.PI / 180) * (height / 2 - radius))
y: tickState.bottomY - height / 2 - (Math.cos(rotation * Math.PI / 180) *(height / 2 - radius))
width: control.border.width
height: Math.min(parent.width * 0.3, parent.height * 0.3) * control.indicatorSize
rotation: -40
color: control.successColor
}
}
]
transitions: [
Transition {
NumberAnimation {
properties: "height,width,rotation,x,y"
duration: 400
easing.type: Easing.InOutQuad
}
ColorAnimation {
properties: "color,border.color"
duration: 400
easing.type: Easing.InOutQuad
}
}
]
}

View File

@ -0,0 +1,48 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.0
Rectangle {
id: control
property double size: sizes[sizeMode]
property string sizeMode: "small"
property var sizes: {
"tiny": 0,
"small": 0.15,
"medium": 0.25,
"large": 0.8
}
Layout.preferredHeight: app.landscape() ? app.height : app.height * size
Layout.preferredWidth: app.landscape() ? app.width * size : app.width
Behavior on size {
NumberAnimation {
duration: 700
easing.type: Easing.InOutQuad
}
}
RectangularGlow {
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: headerBackgroundRectangle
scale: 1
}
Rectangle {
id: headerBackgroundRectangle
anchors.fill: parent
color: appTheme.theme.colors.menu
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,6 @@
<file>main.qml</file>
<file>components/ProgressCircle.qml</file>
<file>components/ConnectionDelegate.qml</file>
<file>components/FadeAnimation.qml</file>
<file>components/ConnectionIcon.qml</file>
<file>components/NextPageDelegate.qml</file>
<file>components/FancyButton.qml</file>
@ -12,7 +11,6 @@
<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>
@ -23,6 +21,14 @@
<file>SettingsDialog/StartPage.qml</file>
<file>SettingsDialog/SettingsStartSequencePage.qml</file>
<file>SettingsDialog/SettingsBaseStationPage.qml</file>
<file>SettingsDialog/SettingsBaseStationConnectionsPage.qml</file>
<file>components/layout/ToolBar.qml</file>
<file>MainPage/TopToolBar.qml</file>
<file>MainPage/CenterContent.qml</file>
<file>MainPage/BottomToolBar.qml</file>
<file>components/MainActionButton.qml</file>
<file>components/StateIndicator.qml</file>
<file>components/BatteryIndicator.qml</file>
<file>components/ExtensionOverview.qml</file>
<file>SettingsDialog/SettingsBaseStationSettingsPage.qml</file>
</qresource>
</RCC>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

View File

@ -25,6 +25,5 @@
<file>graphics/icons/user_black.png</file>
<file>graphics/icons/ok.png</file>
<file>sounds/IFSC_STARTSIGNAL_SINE.wav</file>
<file>ScStwBasestation.sb64</file>
</qresource>
</RCC>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

1
shared-libraries Submodule

@ -0,0 +1 @@
Subproject commit 37bea9105f1c7e0c8f1a209da3d5043c61abfb44

View File

@ -1,69 +0,0 @@
/*
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/>.
*/
#include "headers/appsettings.h"
AppSettings * pGlobalAppSettings = nullptr;
AppSettings::AppSettings(QObject* parent)
:QObject(parent)
{
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
this->settingsManager = new QSettings(path+"/settings.ini", QSettings::IniFormat, this);
this->setDefaultSetting("ready_en", "false");
this->setDefaultSetting("ready_delay", 0);
this->setDefaultSetting("at_marks_en", "false");
this->setDefaultSetting("at_marks_delay", 0);
this->setDefaultSetting("theme", "Light");
this->setDefaultSetting("baseStationIpAdress", "192.168.4.1");
pGlobalAppSettings = this;
}
QString AppSettings::loadSetting(const QString &key)
{
this->settingsManager->beginGroup("AppSettings");
QString value = this->settingsManager->value(key , false).toString();
this->settingsManager->endGroup();
return(value);
}
void AppSettings::writeSetting(const QString &key, const QVariant &variant)
{
this->settingsManager->beginGroup("AppSettings");
this->settingsManager->setValue(key , variant);
this->settingsManager->endGroup();
}
void AppSettings::setDefaultSetting(const QString &key, const QVariant &defaultVariant)
{
QString value = this->loadSetting(key);
if(value == "false"){
this->writeSetting(key, defaultVariant);
}
}
AppSettings::~AppSettings()
{
delete settingsManager;
}

View File

@ -1,132 +0,0 @@
#include "headers/apptheme.h"
AppTheme::AppTheme(QObject *parent) : QObject(parent)
{
QVariantMap tmpDarkTheme = {
{"backgroundColor", "#2d3037"},
{"buttonColor", "#202227"},
{"buttonPressedColor", "#41454f"},
{"buttonBorderColor", "grey"},
{"disabledButtonColor", "#555555"},
{"viewColor", "#202227"},
{"menuColor", "#292b32"},
{"delegate1Color", "#202227"},
{"delegate2Color", "#202227"},
{"delegateBackgroundColor", "#202227"},
{"delegatePressedColor", "#41454f"},
{"textColor", "#ffffff"},
{"textDarkColor", "#232323"},
{"disabledTextColor", "#777777"},
{"sliderColor", "#6ccaf2"},
{"successColor", "#6bd43b"},
{"errorColor", "#e03b2f"},
{"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"},
{"successColor", "#60de26"},
{"errorColor", "#ff0000"},
{"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();
}

View File

@ -1,460 +0,0 @@
#include "headers/baseconn.h"
BaseConn * pGlobalBaseConn = nullptr;
BaseConn::BaseConn(QObject *parent) : QObject(parent)
{
pGlobalBaseConn = this;
socket = new QTcpSocket(this);
this->timeoutTimer = new QTimer(this);
this->timeoutTimer->setSingleShot(true);
this->state = "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({});
}
void BaseConn::connectToHost() {
qDebug() << "connecting";
setState("connecting");
this->connection_progress = 0;
connect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
//connect
this->socket->connectToHost(this->ip, this->port);
timeoutTimer->start(3000);
}
void BaseConn::connectionTimeout() {
this->socket->abort();
disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
}
bool BaseConn::init() {
disconnect(this->timeoutTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
this->timeoutTimer->stop();
connect(this->socket, &QTcpSocket::readyRead, this, &BaseConn::readyRead);
this->connection_progress = 50;
this->setState("connected");
// init remote session
QJsonArray updateSubs = {"onRaceStateChanged", "onTimersChanged", "onExtensionConnectionsChanged", "onNextStartActionChanged"};
QJsonObject sessionParams = {{"updateSubs", updateSubs}, {"init", true}, {"usingTerminationKeys", true}};
QVariantMap initResponse = this->sendCommand(1, sessionParams, false);
if(initResponse["status"] != 200) {
return false;
}
this->firmwareVersion = initResponse["data"].toMap()["version"].toString();
this->timeOffset = initResponse["data"].toMap()["time"].toDouble() - this->date->currentMSecsSinceEpoch();
this->firmwareUpToDate = this->isFirmwareUpToDate();
emit this->propertiesChanged();
qDebug() << "[INFO][BaseStation] Init done! firmware: version: " << this->firmwareVersion << " up-to-date: " << this->firmwareUpToDate << " time offset: " << this->timeOffset;
return true;
}
void BaseConn::deInit() {
this->connections.clear();
emit this->connectionsChanged();
this->setState("disconnected");
}
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();
}
setState("disconnected");
}
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 ---
// -------------------------------------
void BaseConn::socketStateChanged(QAbstractSocket::SocketState socketState) {
switch (socketState) {
case QAbstractSocket::UnconnectedState:
{
this->deInit();
break;
}
case QAbstractSocket::ConnectedState:
{
if(!this->init()) {
this->closeConnection();
}
break;
}
default:
{
//qDebug() << "+ --- UNKNOWN SOCKET STATE: " << socketState;
break;
}
}
}
QVariantMap BaseConn::sendCommand(int header, QJsonValue data, bool useTerminationKeys, int timeout) {
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 = new QEventLoop(this);
QTimer *timer = new QTimer(this);
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();
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(timeout);
//write data
if(useTerminationKeys) {
socket->write("<message>" + jsonRequest.toLatin1() + "</message>");
}
else {
socket->write(jsonRequest.toLatin1());
}
//wait for an answer to finish (programm gets stuck in here)
loop->exec();
bool replyFound = false;
// find reply and delete the request from waiting list
for(int i = 0; i<this->waitingRequests.length(); i++){
if(this->waitingRequests[i].id == thisId){
// request was found
replyFound = true;
// delete event loop
if(this->waitingRequests[i].loop != nullptr) {
delete this->waitingRequests[i].loop;
}
// store reply
reply = this->waitingRequests[i].reply;
// remove reply from waiting list
this->waitingRequests.removeAt(i);
}
}
if(!replyFound) {
// some internal error occured
return {{"status", 900}, {"data", ""}};
}
if(timer->remainingTime() == -1){
//the time has been triggered -> timeout
return {{"status", 911}, {"data", ""}};
}
delete timer;
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();
if(!replyObj.isEmpty()){
id = replyObj.value("id").toInt();
if(id == -1) {
// this message is an update!!
emit this->gotUpdate(replyObj.toVariantMap());
return;
}
// this message is the reply to a command!
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);
}
// -------------------------
// --- updater functions ---
// -------------------------
bool BaseConn::updateTime() {
if(abs(this->timeOffset) < 10000) {
// the time is already up-to-date
return true;
}
QVariantMap ret = this->sendCommand(5001, this->date->currentSecsSinceEpoch());
qDebug() << ret;
return ret["status"].toInt() == 200;
}
bool BaseConn::updateFirmware() {
QString file = ":/ScStwBasestation.sb64";
QFile f(file);
if (!f.open(QFile::ReadOnly)) return false;
QString fileContents = f.readAll();
if(this->firmwareUpToDate) {
return true;
}
QVariantMap ret = this->sendCommand(5000, fileContents, true, 15000);
return ret["status"].toInt() == 200;
}
bool BaseConn::isFirmwareUpToDate() {
QString file = ":/ScStwBasestation.sb64";
QFile f(file);
if (!f.open(QFile::ReadOnly)) return false;
QString fileContents = f.readAll();
QString newFirmwareVersion = fileContents.split("<VER>")[1].split("</VER>")[0];
int newFirmwareVersionMajor = newFirmwareVersion.split(".")[0].toInt();
int newFirmwareVersionMinor = newFirmwareVersion.split(".")[1].toInt();
int newFirmwareVersionPatch = newFirmwareVersion.split(".")[2].toInt();
QString currentFirmwareVersion = this->firmwareVersion;
int currentFirmwareVersionMajor = currentFirmwareVersion.split(".")[0].toInt();
int currentFirmwareVersionMinor = currentFirmwareVersion.split(".")[1].toInt();
int currentFirmwareVersionPatch = currentFirmwareVersion.split(".")[2].toInt();
return newFirmwareVersionMajor < currentFirmwareVersionMajor || newFirmwareVersionMinor < currentFirmwareVersionMinor || newFirmwareVersionPatch <= currentFirmwareVersionPatch;
}
// ------------------------
// --- 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){
if(this->state != newState) {
qDebug() << "+--- BaseConn state changed: " << newState;
this->state = newState;
emit stateChanged();
if(this->state == "disconnected") {
this->deInit();
}
}
}
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;
}
void BaseConn::setConnections(QVariantList connections) {
if(this->connections != connections){
this->connections = connections;
emit this->connectionsChanged();
}
}

View File

@ -1,367 +0,0 @@
/*
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/>.
*/
#include "headers/buzzerconn.h"
BuzzerConn::BuzzerConn(QObject *parent, QString ip, int port) : QObject(parent)
{
this->networkManager = new QNetworkAccessManager();
this->reloadNetworkManager = new QNetworkAccessManager();
this->socket = new QTcpSocket();
this->date = new QDateTime;
this->latest_button_pressed = 0;
this->connected = false;
this->ip = ip;
this->port = port;
// "http://192.168.4.1"
}
bool BuzzerConn::connect()
{
qDebug() << "connecting...";
//wait until the request has finished
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
loop.connect(this->socket, SIGNAL(connected()), &loop, SLOT(quit()));
timer.start(3000);
this->socket->connectToHost(this->ip, this->port);
loop.exec();
timer.stop();
if(timer.remainingTime() == 0){
//the time has been triggered -> timeout
return(false);
}
QList<double> times = gettimes(2000);
qDebug() << times[0];
if(times[0] == 200.0){
this->latest_button_pressed = times[2];
for(int i=0;i<=100;i++){
this->connection_progress = i;
if(!calcoffset(this->gettimes(1000))){
this->connection_progress = 100;
this->connected = false;
return(false);
}
}
this->connected = true;
return(true);
}
else{
this->connected = false;
return(false);
}
}
bool BuzzerConn::calcoffset(QList<double> times)
{
if(times.length() != 3){
return(false);
}
if(times[0] == 200.0){
this->latest_button_pressed = times[2];
double offset = date->currentMSecsSinceEpoch() - times[1];
if(this->latest_offsets.length()>=100){
this->latest_offsets.removeFirst();
}
this->latest_offsets.append(offset);
double mem = 0;
for(int i=0;i<latest_offsets.length();i++){
mem += latest_offsets[i];
}
this->offset = mem / double(latest_offsets.length());
qDebug("%20f", this->offset);
return(true);
}
else {
//this->connected = false;
return(false);
}
}
QList<double> BuzzerConn::gettimes(int timeout)
{
// QList<double> times;
// ReturnData_t ret = senddata(this->networkManager, QUrl(this->ip), timeout);
// times.append(double(ret.status_code));
// if(ret.status_code == 200){
// ret.text.replace("\n","");
// ret.text.replace("\r","");
// QStringList times_cache = ret.text.split("<br>");
// times.append(times_cache[0].toDouble());
// times.append(times_cache[1].toDouble());
// return(times);
// }
// else{
// return(times);
// }
QList<double> times;
signed long ret;
ret = this->sendCommand("GET_TIMESTAMP", timeout);
if(ret >= 0){
times.append(double(200));
times.append(double(ret));
ret = this->sendCommand("GET_LASTPRESSED", timeout);
if(ret >= 0){
times.append(double(ret));
return(times);
}
else {
times[0] = ret;
}
}
else {
times.append(ret);
}
return(times);
}
bool BuzzerConn::buzzer_triggered()
{
if(!this->connected){
return(false);
}
if(pending_commands.length() > 0){
QString command = this->pending_commands.first();
signed long retval = this->sendCommand(command, 800);
if(retval > 0){
this->pending_commands.removeFirst();
}
}
QList<double> times = this->gettimes(1000);
if(times[0] == 200.0){
if(times[2] > this->latest_button_pressed){
this->latest_button_pressed = times[2];
return(true);
}
else {
return(false);
}
}
else{
//this->connected = false;
return(false);
}
}
bool BuzzerConn::start()
{
if(!this->connected){
return(false);
}
QList<double> times = this->gettimes(1000);
if(times[0] == 200.0 && this->connected){
this->latest_button_pressed = times[2];
return(true);
}
else{
this->connected = false;
return(false);
}
}
double BuzzerConn::get(QString key)
{
if(key == "offset"){
return(this->offset);
}
else if (key == "lastpressed") {
return(this->latest_button_pressed);
}
else if( key == "currtime") {
return(this->date->currentMSecsSinceEpoch());
}
else if( key == "connection_progress") {
return(this->connection_progress);
}
else if( key == "connected") {
if(this->connected){
return(1);
}
return(0);
}
}
QString BuzzerConn::test()
{
ReturnData_t ret = this->senddata(this->networkManager, QUrl("http://www.google.de"), 500);
return(ret.text);
}
bool BuzzerConn::refresh()
{
if(!this->connected){
return(false);
}
// QList<double> times;
// ReturnData_t ret = senddata(this->reloadNetworkManager, QUrl(this->ip), 1000);
// times.append(double(ret.status_code));
// if(ret.status_code == 200){
// ret.text.replace("\n","");
// ret.text.replace("\r","");
// QStringList times_cache = ret.text.split("<br>");
// times.append(times_cache[0].toDouble());
// times.append(times_cache[1].toDouble());
// calcoffset(times);
// return(true);
// }
// else{
// //this->connected = false;
// return(false);
// }
if(pending_commands.length() > 0){
QString command = this->pending_commands.first();
signed long retval = this->sendCommand(command, 800);
if(retval > 0){
this->pending_commands.removeFirst();
}
}
//refresh the times
QList<double> ret = this->gettimes(800);
if(ret[0] >= 0){
this->errors = 0;
return(this->calcoffset(ret));
}
else {
this->errors ++;
if(this->errors > errors_until_disconnect){
this->socket->disconnectFromHost();
this->connected = false;
}
return(false);
}
}
ReturnData_t BuzzerConn::senddata(QNetworkAccessManager * NetMan, QUrl serviceUrl, int timeout)
{
ReturnData_t ret; //this is a custom type to store the returned data
// Call the webservice
QNetworkRequest request(serviceUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
//send a POST request with the given url and data to the server
QUrlQuery pdata;
QNetworkReply* reply;
//wait until the request has finished
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
loop.connect(NetMan, SIGNAL(finished(QNetworkReply*)), SLOT(quit()));
timer.start(timeout);
reply = NetMan->post(request, pdata.toString(QUrl::FullyEncoded).toUtf8());
loop.exec();
timer.stop();
//get the status code
QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
ret.status_code = status_code.toInt();
if(ret.status_code == 0){ //if the statuscode is zero, the connecion to the server was not possible
ret.status_code = 444;
}
//get the full text response
ret.text = QString::fromUtf8(reply->readAll());
//return the data
return(ret);
}
signed long BuzzerConn::sendCommand(QString command, int timeout){
//if there is any data in the storage, clear it
if(this->socket->bytesAvailable() > 0){
this->socket->readAll();
}
//send request to the socket server
QByteArray arrBlock;
QDataStream out(&arrBlock, QIODevice::WriteOnly);
//out.setVersion(QDataStream::Qt_5_10);
out << quint16(0) << command;
out.device()->seek(0);
out << quint16(arrBlock.size() - sizeof(quint16));
this->socket->write(arrBlock);
//now wait for the server of the sensor to answer
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
loop.connect(socket, SIGNAL(readyRead()), &loop, SLOT(quit()));
timer.start(timeout);
loop.exec();
//loop finished
timer.stop();
if(timer.remainingTime() == 0){
//the time has been triggered -> timeout
return(-1);
}
//if the data is not 4 bites long if is invalid -> clear and terminate
if(this->socket->bytesAvailable() != 4){
this->socket->readAll();
return(-2);
}
long data = 0;
this->socket->read((char*)&data,4);
qDebug() << data;
qDebug() << this->socket->bytesAvailable();
return data;
}
void BuzzerConn::appendCommand(QString command){
this->pending_commands.append(command);
}

View File

@ -1,649 +0,0 @@
#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);
this->baseConn = new BaseConn(this);
this->baseConn->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress"));
connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::baseStationStateChanged);
connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::refreshMode);
connect(this->baseConn, &BaseConn::connectionsChanged, this, &ClimbingRace::baseStationConnectionsChanged);
connect(this->baseConn, &BaseConn::gotUpdate, this, &ClimbingRace::handleBaseStationUpdate);
connect(this->baseConn, &BaseConn::propertiesChanged, this, &ClimbingRace::baseStationPropertiesChanged);
this->speedTimers.append( new SpeedTimer(this) );
this->player = new QMediaPlayer;
this->date = new QDateTime;
this->nextStartActionTimer = new QTimer(this);
nextStartActionTimer->setSingleShot(true);
this->timerTextRefreshTimer = new QTimer(this);
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;
}
qDebug() << "+ --- starting race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
this->setState(STARTING);
this->nextStartAction = None;
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)
qDebug() << "+ --- stopping race";
int returnCode = 900;
switch (this->mode) {
case LOCAL:
{
if(type == 1){
this->nextStartActionTimer->stop();
this->player->stop();
this->nextStartAction = None;
}
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){
returnCode = reply["status"].toInt();
}
else {
returnCode = 200;
}
break;
}
}
return returnCode;
}
int ClimbingRace::resetRace() {
if(this->state != STOPPED) {
return 904;
}
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 ---
// -------------------------
/**
* @brief ClimbingRace::handleBaseStationUpdate
*
* Function to handle a update, sent by the base station, which indicates
* that some remote value (like a state) has changed
*
* @param data
*/
void ClimbingRace::handleBaseStationUpdate(QVariant data) {
//qDebug() << "got update: " << data;
int header = data.toMap()["header"].toInt();
switch (header) {
case 9000:
{
// the remote race state has changed
this->setState( raceState( data.toMap()["data"].toInt() ) );
break;
}
case 9001:
{
// the remote timers have changed
this->refreshRemoteTimers(data.toMap()["data"].toList());
break;
}
case 9002:
{
// the extension connections have changed
this->baseConn->setConnections(data.toMap()["data"].toList());
break;
}
case 9003:
{
// the next start action has changed
this->nextStartActionTotalDelay = data.toMap()["data"].toMap()["nextActionDelay"].toDouble();
this->nextStartActionDelayStartedAt = this->date->currentMSecsSinceEpoch() - (this->nextStartActionTotalDelay * data.toMap()["data"].toMap()["nextActionDelayProg"].toDouble());
this->nextStartAction = NextStartAction( data.toMap()["data"].toMap()["nextAction"].toInt() );
emit this->nextStartActionChanged();
}
}
}
bool ClimbingRace::refreshRemoteTimers(QVariantList timers) {
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(this));
}
}
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;
}
// ------------------------
// --- helper functions ---
// ------------------------
void ClimbingRace::playSoundsAndStartRace() {
qDebug() << "next Action: " << nextStartAction;
nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
switch (this->nextStartAction) {
case AtYourMarks:
{
if(!playSound("qrc:/sounds/at_marks_1.wav")){
return;
}
if(appSettings->loadSetting("ready_en") == "true"){
nextStartAction = Ready;
nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
}
else{
nextStartAction = Start;
nextStartActionTimer->setInterval(1);
}
break;
}
case Ready:
{
if(!playSound("qrc:/sounds/ready_1.wav")){
return;
}
nextStartAction = Start;
nextStartActionTimer->setInterval(1);
break;
}
case Start:
{
if(!playSound("qrc:/sounds/IFSC_STARTSIGNAL_SINE.wav")){
return;
}
nextStartAction = None;
nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
this->setState(RUNNING);
speedTimers[0]->start();
emit this->nextStartActionChanged();
return;
}
case None:
{
this->speedTimers[0]->setState(SpeedTimer::STARTING);
if(appSettings->loadSetting("at_marks_en") == "true"){
nextStartAction = AtYourMarks;
nextStartActionTimer->setInterval(appSettings->loadSetting("at_marks_delay").toInt() <= 0 ? 1:appSettings->loadSetting("at_marks_delay").toInt());
}
else if(appSettings->loadSetting("ready_en") == "true"){
nextStartAction = Ready;
nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
}
else{
nextStartAction = Start;
nextStartActionTimer->setInterval(1);
}
break;
}
}
emit this->nextStartActionChanged();
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
// reset race
// reset state
this->setState(IDLE);
// reset timers
// 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);
// reset base conn
// 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}, {"state", timer->getState()}, {"id", this->speedTimers.indexOf(timer)}};
newTimerTextList.append(timerMap);
}
if(newTimerTextList != this->qmlTimers){
this->qmlTimers = newTimerTextList;
emit timerTextChanged();
}
// --- refresh next start action delay progress ---
double nextStartActionRemainingDelay = 0;
switch (this->mode) {
case LOCAL: {
// get remaining and total next start action delay time
if(nextStartAction == 0){
this->nextStartActionTotalDelay = appSettings->loadSetting("at_marks_delay").toDouble();
}
else if (nextStartAction == 1) {
this->nextStartActionTotalDelay = appSettings->loadSetting("ready_delay").toDouble();
}
nextStartActionRemainingDelay = this->nextStartActionTimer->remainingTime();
break;
}
case REMOTE: {
// calculate remaining next start action delay time
nextStartActionRemainingDelay = this->nextStartActionTotalDelay - ( this->date->currentMSecsSinceEpoch() - this->nextStartActionDelayStartedAt );
break;
}
}
// calculate next start action delay progress
if(nextStartActionRemainingDelay > 0){
this->nextStartActionDelayProgress = nextStartActionRemainingDelay / this->nextStartActionTotalDelay;
emit this->nextStartActionDelayProgressChanged();
}
else {
this->nextStartActionDelayProgress = 0;
emit this->nextStartActionDelayProgressChanged();
}
/*if (this->mode == REMOTE && this->state == IDLE) {
this->nextStartActionDelayProgress = 0;
emit this->nextStartActionDelayProgressChanged();
}*/
this->timerTextRefreshTimer->start();
}
bool ClimbingRace::pairConnectedUsbExtensions() {
QVariantMap ret = this->baseConn->sendCommand(5002, "", 10000);
qDebug() << ret;
return ret["status"] == 200;
}
// - 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, int timerId ){
QVariant requestData = QVariantMap({{"userName", userName}, {"timerId", timerId}});
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;
}
double ClimbingRace::getNextStartActionDelayProgress() {
return this->nextStartActionDelayProgress;
}
int ClimbingRace::getNextStartAction() {
return this->nextStartAction;
}
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";
}
}
void ClimbingRace::connectBaseStation() {
this->reloadBaseStationIpAdress();
this->baseConn->connectToHost();
}
void ClimbingRace::disconnectBaseStation() {
this->baseConn->closeConnection();
}
QString ClimbingRace::getBaseStationState() {
return this->baseConn->getState();
}
QVariant ClimbingRace::getBaseStationConnections() {
return baseConn->getConnections();
}
QVariantMap ClimbingRace::getBaseStationProperties() {
QVariantMap firmware = {{"version", this->baseConn->firmwareVersion}, {"upToDate", this->baseConn->firmwareUpToDate}};
return {{"firmware", firmware}, {"timeOffset", this->baseConn->timeOffset}};
}
bool ClimbingRace::updateBasestationFirmware() {
return this->baseConn->updateFirmware();
}
bool ClimbingRace::updateBasestationTime() {
return this->baseConn->updateTime();
}
bool ClimbingRace::reloadBaseStationIpAdress() {
if(this->baseConn->state == "disconnected"){
this->baseConn->setIP(pGlobalAppSettings->loadSetting("baseStationIpAdress"));
return true;
}
return false;
}

View File

@ -17,6 +17,7 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlEngine>
#include <QSqlDatabase>
#include <QSqlError>
#include <QDebug>
@ -35,6 +36,8 @@
#include <QQmlApplicationEngine>
#include <QFile>
#include <QDesktopServices>
#include <QScreen>
#include <QRect>
#include <QtCore/QUrl>
#include <QtCore/QCommandLineOption>
@ -44,103 +47,38 @@
#include <QScreen>
#include <QQmlApplicationEngine>
#include <QtQml/QQmlContext>
//#include <QtWebView/QtWebView>
#ifdef Q_OS_ANDROID
#include <QtAndroidExtras>
#endif
#include "headers/sqlstoragemodel.h"
#include "headers/sqlprofilemodel.h"
#include "headers/appsettings.h"
#include "headers/baseconn.h"
#include "headers/speedtimer.h"
#include "headers/climbingrace.h"
#include "headers/apptheme.h"
#include "headers/scstwappsettings.h"
//#include "headers/speedtimer.h"
//#include "headers/climbingrace.h"
#include "headers/scstwappbackend.h"
#include <scstwlibraries.h>
#include <QTranslator>
#if defined(Q_OS_IOS)
#include "headers/sleepprevent.h"
#endif
static void connectToDatabase()
{
QSqlDatabase database = QSqlDatabase::database();
if (!database.isValid()) {
database = QSqlDatabase::addDatabase("QSQLITE");
if (!database.isValid())
qFatal("Cannot add database: %s", qPrintable(database.lastError().text()));
}
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if (!writeDir.mkpath("."))
qFatal("Failed to create writable directory at %s", qPrintable(writeDir.absolutePath()));
// Ensure that we have a writable location on all devices.
const QString fileName = writeDir.absolutePath() + "/chat-database.sqlite3";
//QFile::remove(fileName);
// When using the SQLite driver, open() will create the SQLite database if it doesn't exist.
database.setDatabaseName(fileName);
if (!database.open()) {
QFile::remove(fileName);
qFatal("Cannot open database: %s", qPrintable(database.lastError().text()));
}
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
#ifdef Q_OS_ANDROID
//set the standard volume to media
QAndroidJniObject jactivity=QtAndroid::androidActivity();
if(jactivity.isValid())
jactivity.callMethod<void>("setVolumeControlStream","(I)V",3);
// setup speed backend
qmlRegisterType<ScStwAppBackend>("de.itsblue.ScStwApp", 2, 0, "SpeedBackend");
qmlRegisterType<ScStwAppSettings>("de.itsblue.ScStwApp", 2, 0, "ScStwAppSettings");
//set statusbar color
QtAndroid::runOnAndroidThread([=]()
{
QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
window.callMethod<void>("addFlags", "(I)V", 0x80000000);
window.callMethod<void>("clearFlags", "(I)V", 0x04000000);
//window.callMethod<void>("setStatusBarColor", "(I)V", 0x202227); // Desired statusbar color
//QAndroidJniObject decorView = window.callObjectMethod("getDecorView", "()Landroid/view/View;");
//decorView.callMethod<void>("setSystemUiVisibility", "(I)V", 0x00002000);
});
#endif
connectToDatabase();
AppSettings * pAppSettings = new AppSettings();
//setup the sql storage model as a qml model
qmlRegisterType<SqlProfileModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlProfileModel");
qmlRegisterType<SqlStorageModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlStorageModel");
// setup speed backend and App themes
qmlRegisterType<ClimbingRace>("com.itsblue.speedclimbingstopwatch", 2, 0, "SpeedBackend");
qmlRegisterType<AppTheme>("com.itsblue.speedclimbingstopwatch", 2, 0, "AppTheme");
qRegisterMetaType<ScStwAppSettings::AppInternalSetting>("ScStwAppSettings::BaseStationSetting");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQmlContext *context = engine.rootContext();
ScStwLibraries::init();
ScStwLibraries::initStyling(&engine);
// stup app settings
context->setContextProperty("_cppAppSettings", pAppSettings);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
#if defined(Q_OS_IOS)
SleepPrevent sp;
sp.setTimerDisabled();
#endif
if (engine.rootObjects().isEmpty())
return -1;
int iRet = 0;
iRet = app.exec();
return iRet;
return app.exec();
}

106
sources/scstwappbackend.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "../headers/scstwappbackend.h"
ScStwAppBackend::ScStwAppBackend(QObject *parent) : QObject(parent)
{
}
// ------------------------
// --- helper functions ---
// ------------------------
void ScStwAppBackend::setScStwClient(ScStwClient *client) {
if(client != this->scStwClient) {
this->scStwClient = client;
emit this->scStwClientChanged();
}
}
// - athlete management -
// TODO: move to client
QVariant ScStwAppBackend::getAthletes() {
QVariantMap reply = this->scStwClient->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 ScStwAppBackend::createAthlete(QString userName, QString fullName) {
QVariant requestData = QVariantMap({{"fullName", fullName}, {"userName", userName}});
QVariantMap reply = this->scStwClient->sendCommand(4001, requestData.toJsonValue());
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error creating athlete: " << reply["status"];
return false;
}
return true;
}
bool ScStwAppBackend::deleteAthlete( QString userName ){
QVariant requestData = QVariantMap({{"userName", userName}});
QVariantMap reply = this->scStwClient->sendCommand(4002, requestData.toJsonValue());
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error deleting athlete: " << reply["status"];
return false;
}
return true;
}
bool ScStwAppBackend::selectAthlete( QString userName, int timerId ){
QVariant requestData = QVariantMap({{"userName", userName}, {"timerId", timerId}});
QVariantMap reply = this->scStwClient->sendCommand(4000, requestData.toJsonValue());
if(reply["status"] != 200){
//handle Error!!
qDebug() << "+ --- error selecting athlete: " << reply["status"];
return false;
}
return true;
}
QVariant ScStwAppBackend::getResults( QString userName ){
QVariantMap reply = this->scStwClient->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 ---
// -------------------------
ScStwClient* ScStwAppBackend::getScStwClient() {
return this->scStwClient;
}

View File

@ -0,0 +1,46 @@
/*
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/>.
*/
#include "headers/scstwappsettings.h"
ScStwAppSettings * pGlobalAppSettings = nullptr;
ScStwAppSettings::ScStwAppSettings(QObject *parent) : ScStwAppSettings (nullptr, parent)
{
}
ScStwAppSettings::ScStwAppSettings(ScStwClient * client, QObject* parent)
:ScStwRemoteSettings(client, parent)
{
this->registerKeyLevelConverters(ScStwAppSettings::KeyLevel, &ScStwAppSettings::keyToString, &ScStwAppSettings::keyToType);
this->setDefaultSetting(ScStwAppSettings::AppThemeSetting, "Light");
this->setDefaultSetting(ScStwAppSettings::BaseStationIpSetting, "192.168.4.1");
}
QVariant ScStwAppSettings::readSetting(AppInternalSetting key) {
return this->readSetting(key, 1);
}
bool ScStwAppSettings::writeSetting(AppInternalSetting key, QVariant value) {
return this->writeSetting(key, 1, value);
}
bool ScStwAppSettings::setDefaultSetting(AppInternalSetting key, QVariant defaultValue) {
return this->setDefaultSetting(key, 1, defaultValue);
}

View File

@ -1,211 +0,0 @@
#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(WON);
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 < WON ) && !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 WON:
return "WON";
case LOST:
return "LOST";
case FAILED:
return "FAILED";
case CANCELLED:
return "CANCELLED";
case DISABLED:
return "DISABLED";
}
return "ERROR";
}
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 = "0.000 sec";
break;
case SpeedTimer::STARTING:
newText = "0.000 sec";
break;
case SpeedTimer::WAITING:
newText = "please wait...";
break;
case SpeedTimer::RUNNING:
newText = QString::number( this->getCurrTime() / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::WON:
newText = QString::number( this->stoppedTime / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::LOST:
newText = QString::number( this->stoppedTime / 1000.0, 'f', 3 ) + " sec";
break;
case SpeedTimer::FAILED:
newText = "false start";
break;
case SpeedTimer::CANCELLED:
newText = "cancelled";
break;
case SpeedTimer::DISABLED:
newText = "---";
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 == "WON") {
return WON;
}
else if (state == "LOST") {
return LOST;
}
else if (state == "FAILED") {
return FAILED;
}
else if(state == "DISABLED") {
return DISABLED;
}
else {
return CANCELLED;
}
}

View File

@ -1,96 +0,0 @@
/*
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/>.
*/
#include "headers/sqlprofilemodel.h"
static void createTable()
{
if (QSqlDatabase::database().tables().contains(QStringLiteral("Contacts"))) {
// The table already exists; we don't need to do anything.
return;
}
QSqlQuery query;
//creat eth etable to store the profiles
if (!query.exec(
"CREATE TABLE IF NOT EXISTS `profiles` ( "
" `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,"
" `name` TEXT NOT NULL "
" );")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
//create the table to store the times
if (!query.exec(
"CREATE TABLE IF NOT EXISTS `times` ("
" `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,"
" `profileid` INTEGER NOT NULL,"
" `time` INTEGER NOT NULL, "
" `timestamp` INTEGER NOT NULL"
" );")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
}
SqlProfileModel::SqlProfileModel(QObject *parent) : QSqlTableModel(parent)
{
qDebug("ProfileModel constructor");
createTable();
setTable("profiles");
setEditStrategy(QSqlTableModel::OnManualSubmit);
select();
}
QVariant SqlProfileModel::data(const QModelIndex &index, int role) const
{
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> SqlProfileModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole + 0] = "id";
names[Qt::UserRole + 1] = "name";
return names;
}
bool SqlProfileModel::append(QString name)
{
qDebug() << name;
QSqlRecord newRecord = record();
newRecord.setValue("name", name);
if (!insertRecord(rowCount(), newRecord)) {
qWarning() << "Failed to add profile:" << lastError().text();
return(false);
}
submitAll();
return(true);
}
void SqlProfileModel::remove(int row)
{
removeRows(row, 1);
submitAll();
}

View File

@ -1,43 +0,0 @@
/*
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/>.
*/
#include "headers/sqlstoragemodel.h"
SqlStorageModel::SqlStorageModel(QObject *parent) : QSqlTableModel(parent)
{
qDebug("ProfileModel constructor");
setTable("times");
select();
}
QVariant SqlStorageModel::data(const QModelIndex &index, int role) const
{
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> SqlStorageModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole + 0] = "id";
names[Qt::UserRole + 1] = "name";
return names;
}