diff --git a/SettingsDialog.qml b/SettingsDialog.qml new file mode 100644 index 0000000..8a60145 --- /dev/null +++ b/SettingsDialog.qml @@ -0,0 +1,90 @@ +import QtQuick 2.9 +import QtMultimedia 5.8 +import QtQuick.Window 2.2 +import QtQuick.Controls 2.2 + +Dialog { + + x: startButt.x + y: startButt.y + width: startButt.width + height: startButt.height + modal: false + enabled: false + + enter: Transition { + NumberAnimation { properties: "scale"; from: 0; to: 1; duration: 500; easing.type: Easing.InCubic } + } + + exit: Transition { + NumberAnimation { properties: "scale"; from: 1; to: 0; duration: 500; easing.type: Easing.OutCubic } + } + + + background: Rectangle { + radius: width * 0.5 + color: "white" + border.color: "grey" + border.width: 1 + Label { + text: "Options" + font.pixelSize: headlineUnderline.width * 0.1 + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: headlineUnderline.anchors.topMargin / 2 - height / 2 + } + } + + Rectangle { + id: headlineUnderline + height: 1 + width: parent.width + color: "grey" + anchors { + top: parent.top + left: parent.left + right: parent.right + topMargin: parent.height * 0.15 + rightMargin: parent.radius - Math.sqrt(Math.pow(parent.radius,2)-Math.pow(parent.radius-anchors.topMargin,2)) + leftMargin: parent.radius - Math.sqrt(Math.pow(parent.radius,2)-Math.pow(parent.radius-anchors.topMargin,2)) + } + } + } + Column { + anchors.fill: parent + + ItemDelegate { + id: connect_del + anchors.fill: parent + text: "connect" + onClicked: { + var i; + if(i === 100){ + i = 0 + running = false + } + + var theUrl = "http://192.168.4.1"; + var xmlHttp = null; + + xmlHttp = new XMLHttpRequest(); + xmlHttp.open( "GET", theUrl, false ); + xmlHttp.setRequestHeader("Content-type", "text/plain;charset=UTF-8"); + xmlHttp.send( null ); + var response = xmlHttp.responseText + var response_status = xmlHttp.status + connect_del.text = response + var res = response.split("
"); + var time_now = new Date().getTime() + console.log(time_now) + console.log(res[1]) + console.log(time_now-res[0]) + root.buzzer_offset = time_now-res[0] + root.last_button_pressed = res[1] + console.log("buzzer_offset: "+root.buzzer_offset) + connect_del.text = response + } + } + } +} diff --git a/buzzerconn.cpp b/buzzerconn.cpp new file mode 100644 index 0000000..e95acf2 --- /dev/null +++ b/buzzerconn.cpp @@ -0,0 +1,94 @@ +#include "buzzerconn.h" + +BuzzerConn::BuzzerConn(QObject *parent) : QObject(parent) +{ + this->networkManager = new QNetworkAccessManager(); + connect(); +} + +bool BuzzerConn::connect() +{ + QList times = gettimes(); + + if(times[0] == 200.0){ + this->connected = true; + calcoffset(); + calcoffset(); + calcoffset(); + return(true); + } + else{ + return(false); + } +} + +void BuzzerConn::calcoffset() +{ + QList times = gettimes(); + QDateTime * date = new QDateTime; + double offset = date->currentMSecsSinceEpoch() - times[2]; + this->latest_offsets.append(offset); + double mem = 0; + for(int i=0;ioffset = mem / double(latest_offsets.length()); +} + +QList BuzzerConn::gettimes() +{ + QList times; + ReturnData_t ret = senddata(QUrl("http://192.168.4.1")); + times.append(double(ret.status_code)); + + if(ret.status_code == 200){ + qDebug()<"); + times.append(times_cache[0].toDouble()); + times.append(times_cache[1].toDouble()); + + return(times); + } + else{ + return(times); + } +} + +ReturnData_t BuzzerConn::senddata(QUrl serviceUrl) +{ + + 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"); + + //set ssl configuration + //send a POST request with the given url and data to the server + QUrlQuery pdata; + QNetworkReply* reply; + + reply = this->networkManager->post(request, pdata.toString(QUrl::FullyEncoded).toUtf8()); + + //wait until the request has finished + QEventLoop loop; + loop.connect(this->networkManager, SIGNAL(finished(QNetworkReply*)), SLOT(quit())); + loop.exec(); + + //get the status code + QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + + ret.status_code = status_code.toInt(); + if(ret.status_code == 0){ //if tehstatus code 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); +} diff --git a/buzzerconn.h b/buzzerconn.h new file mode 100644 index 0000000..1bb22e0 --- /dev/null +++ b/buzzerconn.h @@ -0,0 +1,39 @@ +#ifndef BUZZERCONN_H +#define BUZZERCONN_H + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct strReturnData{ + int status_code; + QString text; +}ReturnData_t; + +class BuzzerConn : public QObject +{ + Q_OBJECT +public: + explicit BuzzerConn(QObject *parent = nullptr); + double offset; + QList latest_offsets; + bool connected; + +private: + QNetworkAccessManager *networkManager; + +signals: + +public slots: + ReturnData_t senddata(QUrl serviceUrl); + QList gettimes(); + bool connect(); + void calcoffset(); +}; + +#endif // BUZZERCONN_H diff --git a/main.cpp b/main.cpp index 259b549..0478a4a 100644 --- a/main.cpp +++ b/main.cpp @@ -6,11 +6,34 @@ #include #include #include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef Q_OS_ANDROID #include #endif #include "sqlstoragemodel.h" #include "sqlprofilemodel.h" +#include "buzzerconn.h" static void connectToDatabase() { @@ -44,11 +67,15 @@ int main(int argc, char *argv[]) QGuiApplication app(argc, argv); - QAndroidJniObject jactivity=QtAndroid::androidActivity(); - if(jactivity.isValid()) - jactivity.callMethod("setVolumeControlStream","(I)V",3); + #ifdef Q_OS_ANDROID + //set the standard volume to media + QAndroidJniObject jactivity=QtAndroid::androidActivity(); + if(jactivity.isValid()) + jactivity.callMethod("setVolumeControlStream","(I)V",3); + #endif connectToDatabase(); + BuzzerConn * pBuzzerConn = new BuzzerConn; //setup the sql storage model as a qml model qmlRegisterType("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlProfileModel"); @@ -59,5 +86,7 @@ int main(int argc, char *argv[]) if (engine.rootObjects().isEmpty()) return -1; + engine.rootContext()->setContextProperty("_cppBuzzerConn", pBuzzerConn); + return app.exec(); } diff --git a/main.qml b/main.qml index 39d2886..11cf8cc 100644 --- a/main.qml +++ b/main.qml @@ -23,6 +23,10 @@ Window { property double stoppedTime: 0 property double currTime: new Date().getTime() + property double buzzer_offset + property double last_button_pressed + + state: "IDLE" Timer { @@ -32,6 +36,33 @@ Window { interval: 1 onTriggered: { root.currTime = new Date().getTime() + + } + } + + Timer { + id: running_refresh_timer + running: root.state === "RUNNING" + repeat: true + interval: 1 + + onTriggered: { + var theUrl = "http://192.168.4.1"; + var xmlHttp = null; + + xmlHttp = new XMLHttpRequest(); + xmlHttp.open( "GET", theUrl, false ); + xmlHttp.send( null ); + var response = xmlHttp.responseText + var res = response.split("
"); + startButt.text = res[0] + if(res[1]>root.last_button_pressed){ + root.last_button_pressed = res[1] + root.stoppedTime = (root.last_button_pressed + root.buzzer_offset) - root.startTime + console.log("STOPPED: "+root.stoppedTime) + time.text = ( root.stoppedTime / 1000 ).toFixed(3) + " sec" + root.state = "STOPPED" + } } } @@ -86,96 +117,100 @@ Window { /*---------------------- Start button ----------------------*/ - Rectangle { - id: startButt + Button { + id : startButt - property string text: "start" + text: "start" property int size: root.landscape() ? parent.width * 0.5:parent.height * 0.5 anchors { bottom: parent.bottom - bottomMargin: parent.height * 0.5 - height * 0.5 + bottomMargin: root.height * 0.5 - height * 0.5 right: parent.right - rightMargin: parent.width * 0.5 - width * 0.5 + rightMargin: root.width * 0.5 - width * 0.5 + } + contentItem: Text { + //make text disappear } - height: root.landscape() ? size > parent.height * 0.9 ? parent.height * 0.9:size : size width: root.landscape() ? size : size > parent.width * 0.9 ? parent.width * 0.9:size - color: "white" - border.color: "grey" - border.width: 1 - radius: width / 2 - - Label { - id: startButt_text - text: parent.text - anchors.centerIn: parent - font.pixelSize: parent.height * 0.16 - font.family: "Helvetica" - } - MouseArea { - enabled: startButt.enabled - anchors.fill: parent - onPressed: parent.color = "lightgrey" - onReleased: parent.color = "white" - onClicked: { - switch(root.state) { - case "": - root.state = "IDLE" - case "IDLE": - root.state = "STARTING" - startSound.play() - break - case "RUNNING": - root.stoppedTime = new Date().getTime() - root.startTime - time.text = ( root.stoppedTime / 1000 ).toFixed(3) + " sec" - root.state = "STOPPED" - break - case "STOPPED": - root.state = "IDLE" - break - } + onPressedChanged: { + if(pressed){ + background.color = "lightgrey" + } + else { + background.color = "white" } } + + background: Rectangle { + color: "white" + border.color: "grey" + border.width: 1 + radius: width / 2 + Label { + id: startButt_text + text: startButt.text + anchors.centerIn: parent + font.pixelSize: parent.height * 0.16 + font.family: "Helvetica" + } + } + + onClicked: { + switch(root.state) { + case "": + root.state = "IDLE" + case "IDLE": + root.state = "STARTING" + startSound.play() + break + case "RUNNING": + root.stoppedTime = new Date().getTime() - root.startTime + time.text = ( root.stoppedTime / 1000 ).toFixed(3) + " sec" + root.state = "STOPPED" + break + case "STOPPED": + root.state = "IDLE" + break + } + } + } /*---------------------- Cancel button ----------------------*/ - Rectangle { + RoundButton { id: cancelButt - property string text: "cancel" + text: "cancel" anchors { right: startButt.right bottom: startButt.bottom } + contentItem: Text { + //make text disappear + } height: startButt.height * 0.3 scale: 0 width: height - color: "white" - border.color: "grey" - border.width: 1 - radius: width / 2 - Label { - id: cancelButt_text - text: parent.text - anchors.centerIn: parent - font.pixelSize: parent.height * 0.16 - font.family: "Helvetica" - } - MouseArea { - enabled: startSound.playing - anchors.fill: parent - onPressed: parent.color = "lightgrey" - onReleased: parent.color = "white" - onClicked: { - startSound.stop() - root.stoppedTime = 0 - time.text = "false start" - root.state = "STOPPED" + enabled: startSound.playing + onPressedChanged: { + if(pressed){ + background.color = "lightgrey" } + else { + background.color = "white" + } + } + + onClicked: { + startSound.stop() + root.stoppedTime = 0 + time.text = "false start" + root.state = "STOPPED" } Behavior on scale { @@ -183,8 +218,29 @@ Window { duration: 200 } } + background: Rectangle { + color: "white" + border.color: "grey" + border.width: 1 + radius: width / 2 + Label { + id: cancelButt_text + text: cancelButt.text + anchors.centerIn: parent + font.pixelSize: parent.height * 0.16 + font.family: "Helvetica" + } + } } + /*------ + Popups + ------*/ + SettingsDialog{ + id:settingsDialog + } + + /*------------------- lower line and menu -------------------*/ @@ -208,10 +264,9 @@ Window { leftMargin: root.landscape() ? parent.width * 0.05:0 } - Rectangle { + RoundButton { id: settingsButt - property string text: "cancel" anchors { //center verticalCenter: root.landscape() ? undefined:parent.verticalCenter @@ -223,45 +278,52 @@ Window { topMargin: root.landscape() ? (parent.height - (height * 2)) / 3:undefined //align in portrait mode leftMargin: root.landscape() ? undefined:(parent.width - width * 2) / 3 - - } + height: root.landscape() ? parent.width * 0.7:parent.height * 0.7 width: height - color: "white" - border.color: "grey" - border.width: 1 - radius: width / 2 - - Image { - id: settungsButt_Image - source: "qrc:/graphics/icons/settings.png" - anchors.centerIn: parent - height: parent.height * 0.7 - width: parent.width * 0.7 - mipmap: true - } - - MouseArea { - enabled: root.state === "IDLE" - anchors.fill: parent - onPressed: parent.color = "lightgrey" - onReleased: parent.color = "white" - onClicked: { + onPressedChanged: { + if(pressed){ + background.color = "lightgrey" + } + else { + background.color = "white" } } - Behavior on scale { - PropertyAnimation { - duration: 200 + onClicked: { + if(settingsDialog.enabled === false){ + settingsDialog.open() + settingsDialog.enabled = true + } + else { + settingsDialog.enabled = false + settingsDialog.close() + } + } + + background: Rectangle { + color: "white" + border.color: "grey" + border.width: 1 + radius: width / 2 + + + Image { + id: settungsButt_Image + source: "qrc:/graphics/icons/settings.png" + anchors.centerIn: parent + height: parent.height * 0.7 + width: parent.width * 0.7 + mipmap: true } } } - Rectangle { + + RoundButton { id: profilesButt - property string text: "cancel" anchors { verticalCenter: root.landscape() ? undefined:parent.verticalCenter horizontalCenter: root.landscape() ? parent.horizontalCenter:undefined @@ -270,39 +332,45 @@ Window { topMargin: root.landscape() ? (parent.height - (height * 2)) / 3:undefined leftMargin: root.landscape() ? undefined:(parent.width - width * 2) / 3 } + height: root.landscape() ? parent.width * 0.7:parent.height * 0.7 - width: height - color: "white" - border.color: "grey" - border.width: 1 - radius: width / 2 - Image { - id: profilesButt_Image - source: "qrc:/graphics/icons/user.png" - anchors.centerIn: parent - height: parent.height * 0.5 - width: parent.width * 0.5 - mipmap: true - } - - MouseArea { - enabled: root.state === "IDLE" - anchors.fill: parent - onPressed: parent.color = "lightgrey" - onReleased: parent.color = "white" - onClicked: { + onPressedChanged: { + if(pressed){ + background.color = "lightgrey" + } + else { + background.color = "white" } } - Behavior on scale { - PropertyAnimation { - duration: 200 + onClicked: { + var theUrl = "http://192.168.4.1"; + var xmlHttp = null; + + xmlHttp = new XMLHttpRequest(); + xmlHttp.open( "GET", theUrl, false ); + xmlHttp.send( null ); + console.log(xmlHttp.responseText) + } + + background: Rectangle { + color: "white" + border.color: "grey" + border.width: 1 + radius: width / 2 + + Image { + id: profilesButt_Image + source: "qrc:/graphics/icons/user.png" + anchors.centerIn: parent + height: parent.height * 0.5 + width: parent.width * 0.5 + mipmap: true } } } - } diff --git a/qml.qrc b/qml.qrc index 11e786b..8272b66 100644 --- a/qml.qrc +++ b/qml.qrc @@ -2,5 +2,6 @@ main.qml FadeAnimation.qml + SettingsDialog.qml diff --git a/speedclimbing_stopwatch.pro b/speedclimbing_stopwatch.pro index 061ba3d..51c0fef 100644 --- a/speedclimbing_stopwatch.pro +++ b/speedclimbing_stopwatch.pro @@ -21,7 +21,8 @@ TARGET = speedclimbing_stw SOURCES += \ main.cpp \ sqlstoragemodel.cpp \ - sqlprofilemodel.cpp + sqlprofilemodel.cpp \ + buzzerconn.cpp RESOURCES += qml.qrc \ shared.qrc @@ -50,4 +51,5 @@ android { HEADERS += \ sqlstoragemodel.h \ - sqlprofilemodel.h + sqlprofilemodel.h \ + buzzerconn.h diff --git a/sqlstoragemodel.h b/sqlstoragemodel.h index cc5eede..d756a92 100644 --- a/sqlstoragemodel.h +++ b/sqlstoragemodel.h @@ -6,6 +6,7 @@ class SqlStorageModel : public QObject { Q_OBJECT + public: explicit SqlStorageModel(QObject *parent = nullptr);