First handshake with pica
This commit is contained in:
parent
4da9a275a9
commit
f547170728
9
API/CMakeLists.txt
Normal file
9
API/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
set(HEADERS
|
||||||
|
api.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
api.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(megpie PRIVATE ${SOURCES})
|
93
API/api.cpp
Normal file
93
API/api.cpp
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#include "api.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
constexpr const char* json = "application/json";
|
||||||
|
|
||||||
|
struct NetworkReplyDeleter {
|
||||||
|
void operator () (QNetworkReply* reply) {
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
API::API(const QUrl& address, QObject* parent):
|
||||||
|
QObject(parent),
|
||||||
|
address(address),
|
||||||
|
network()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void API::test(const QString& path, const QJSValue& finished) {
|
||||||
|
qDebug() << "Testing" << path;
|
||||||
|
QNetworkRequest request(path + "/info");
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, json);
|
||||||
|
|
||||||
|
QNetworkReply* reply = network.get(request);
|
||||||
|
connect(reply, &QNetworkReply::finished,
|
||||||
|
std::bind(&API::onTestSuccess, this, reply, finished)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void API::onTestSuccess(QNetworkReply* reply, const QJSValue& finished) const {
|
||||||
|
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
|
||||||
|
QNetworkReply::NetworkError error = reply->error();
|
||||||
|
if (error != QNetworkReply::NoError) {
|
||||||
|
QString err = reply->errorString();
|
||||||
|
qDebug() << "Test for" << reply->url() << "failed:" << err;
|
||||||
|
callCallback(finished, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader);
|
||||||
|
if (!
|
||||||
|
contentType.isValid() ||
|
||||||
|
!contentType.canConvert<QString>() ||
|
||||||
|
contentType.toString() != json
|
||||||
|
) {
|
||||||
|
QString err("wrong response content type");
|
||||||
|
qDebug() << "Test for" << reply->url() << "failed:" << err;
|
||||||
|
callCallback(finished, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
QJsonDocument document = QJsonDocument::fromJson(data);
|
||||||
|
QJsonObject rootObj = document.object();
|
||||||
|
|
||||||
|
QJsonValue type = rootObj.value("type");
|
||||||
|
QJsonValue version = rootObj.value("version");
|
||||||
|
if (!type.isString() || !version.isString()) {
|
||||||
|
QString err("malformed json");
|
||||||
|
qDebug() << "Test for" << reply->url() << "failed:" << err;
|
||||||
|
callCallback(finished, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.toString() != "Pica") {
|
||||||
|
QString err("server of this type (" + type.toString() + ") is not supported");
|
||||||
|
qDebug() << "Test for" << reply->url() << "failed:" << err;
|
||||||
|
callCallback(finished, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version.toString() != "0.0.1") {
|
||||||
|
QString err("server of this version (" + version.toString() + ") is not supported");
|
||||||
|
qDebug() << "Test for" << reply->url() << "failed:" << err;
|
||||||
|
callCallback(finished, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callCallback(finished, QString(), {QJSValue(true)});
|
||||||
|
}
|
||||||
|
|
||||||
|
void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const {
|
||||||
|
if (callback.isCallable()) {
|
||||||
|
if (error.isEmpty())
|
||||||
|
callback.call(QJSValueList({QJSValue(QJSValue::NullValue)}) + arguments);
|
||||||
|
else
|
||||||
|
callback.call({QJSValue(error)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
API/api.h
Normal file
28
API/api.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QJSValue>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
class API : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit API(const QUrl& path = QString(), QObject* parent = nullptr);
|
||||||
|
|
||||||
|
Q_INVOKABLE void test(const QString& path, const QJSValue& finished = QJSValue());
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onTestSuccess(QNetworkReply* reply, const QJSValue& finished) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUrl address;
|
||||||
|
QNetworkAccessManager network;
|
||||||
|
};
|
@ -54,6 +54,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(qml)
|
add_subdirectory(qml)
|
||||||
|
add_subdirectory(API)
|
||||||
|
|
||||||
target_link_libraries(megpie PRIVATE
|
target_link_libraries(megpie PRIVATE
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
|
26
main.cpp
26
main.cpp
@ -4,28 +4,6 @@
|
|||||||
#include "root.h"
|
#include "root.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
Root app(argc, argv);
|
Root app(u"qrc:qml/main.qml"_qs, argc, argv);
|
||||||
|
return app.exec();
|
||||||
std::cout << "Starting" << std::endl;
|
|
||||||
QQmlApplicationEngine engine;
|
|
||||||
const QUrl url(u"qrc:qml/main.qml"_qs);
|
|
||||||
QObject::connect(
|
|
||||||
&engine, &QQmlApplicationEngine::objectCreated, &app,
|
|
||||||
[&url](QObject* obj, const QUrl& objUrl) {
|
|
||||||
if (!obj && url == objUrl)
|
|
||||||
QCoreApplication::exit(-2);
|
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
engine.load(url);
|
|
||||||
if (engine.rootObjects().isEmpty())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
int result;
|
|
||||||
try {
|
|
||||||
result = app.exec();
|
|
||||||
} catch (...) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
21
main_web.cpp
21
main_web.cpp
@ -4,22 +4,13 @@
|
|||||||
#include "root.h"
|
#include "root.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
Root* app = new Root(argc, argv);
|
Root* app;
|
||||||
|
|
||||||
std::cout << "Starting" << std::endl;
|
try {
|
||||||
QQmlApplicationEngine* engine = new QQmlApplicationEngine();
|
app = new Root(u"qrc:qml/main.qml"_qs, argc, argv);
|
||||||
const QUrl url(u"qrc:qml/main.qml"_qs);
|
} catch (const std::exception& exception) {
|
||||||
QObject::connect(
|
std::cerr << "Couldn't start megpie: " << exception.what() << std::endl;
|
||||||
engine, &QQmlApplicationEngine::objectCreated, app,
|
}
|
||||||
[url](QObject* obj, const QUrl& objUrl) {
|
|
||||||
if (!obj && url == objUrl)
|
|
||||||
QCoreApplication::exit(-2);
|
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
engine->load(url);
|
|
||||||
if (engine->rootObjects().isEmpty())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ qt_add_qml_module(megpieQml
|
|||||||
NO_PLUGIN
|
NO_PLUGIN
|
||||||
QML_FILES
|
QML_FILES
|
||||||
main.qml
|
main.qml
|
||||||
|
ServerPick.qml
|
||||||
|
Welcome.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(megpie PRIVATE megpieQml)
|
target_link_libraries(megpie PRIVATE megpieQml)
|
||||||
|
138
qml/ServerPick.qml
Normal file
138
qml/ServerPick.qml
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Page {
|
||||||
|
property string address
|
||||||
|
property bool valid
|
||||||
|
|
||||||
|
signal back()
|
||||||
|
signal success(address: string)
|
||||||
|
|
||||||
|
title: qsTr("Chosing a server")
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
id: label
|
||||||
|
text: qsTr("Type server address in the field below")
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
width: parent.width
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
id: input
|
||||||
|
placeholderText: "https://example.org"
|
||||||
|
text: address
|
||||||
|
onAccepted: modal.check()
|
||||||
|
validator: RegularExpressionValidator {
|
||||||
|
regularExpression: /^(?:http|https)?:\/\/(?:\w*\.)*(?:\w)+(?:\:\d+)?(?:(?:\/\w+)\/?)*$/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
onClicked: back()
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr("Confirm")
|
||||||
|
enabled: input.acceptableInput
|
||||||
|
onClicked: modal.check()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
property bool inProgress: false
|
||||||
|
|
||||||
|
id: modal
|
||||||
|
anchors.centerIn: parent
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
width: column.width + 60
|
||||||
|
height: column.height + 60
|
||||||
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
onClosed: function () {
|
||||||
|
modal.inProgress = false;
|
||||||
|
if (valid)
|
||||||
|
success(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: column
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 10
|
||||||
|
Label {
|
||||||
|
id: status
|
||||||
|
width: 300
|
||||||
|
wrapMode: Label.WordWrap
|
||||||
|
horizontalAlignment: Label.AlignHCenter
|
||||||
|
}
|
||||||
|
BusyIndicator {
|
||||||
|
id: indicator
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: modal.inProgress
|
||||||
|
running: modal.inProgress
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
id: button
|
||||||
|
text: qsTr("Close")
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: !modal.inProgress
|
||||||
|
onClicked: modal.close()
|
||||||
|
focus: true
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
if (!modal.inProgress)
|
||||||
|
modal.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enter: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 0.0
|
||||||
|
to: 1.0
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 1.0
|
||||||
|
to: 0.0
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function check () {
|
||||||
|
valid = false;
|
||||||
|
modal.inProgress = true;
|
||||||
|
status.text = qsTr("Checking") + " " + address + "...";
|
||||||
|
modal.open()
|
||||||
|
|
||||||
|
API.test(input.text, function (err, success) {
|
||||||
|
if (!modal.inProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
modal.inProgress = false;
|
||||||
|
if (err)
|
||||||
|
status.text = err;
|
||||||
|
else
|
||||||
|
status.text = qsTr("Success");
|
||||||
|
|
||||||
|
valid = !!success;
|
||||||
|
if (valid) {
|
||||||
|
address = input.text;
|
||||||
|
modal.close()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
qml/Welcome.qml
Normal file
48
qml/Welcome.qml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Page {
|
||||||
|
property string serverAddress
|
||||||
|
|
||||||
|
signal pickServer(address: string)
|
||||||
|
|
||||||
|
title: qsTr("Welcome")
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: qsTr("Welcome to Megpie!")
|
||||||
|
font {
|
||||||
|
pixelSize: 22
|
||||||
|
bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Label {
|
||||||
|
horizontalAlignment: Label.AlignRight
|
||||||
|
text: qsTr("Current server:")
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
horizontalAlignment: Label.AlignLeft
|
||||||
|
text: serverAddress || qsTr("choose")
|
||||||
|
font {
|
||||||
|
italic: true
|
||||||
|
underline: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: pickServer(serverAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
qml/main.qml
66
qml/main.qml
@ -3,57 +3,43 @@ import QtQuick.Window
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
Window {
|
|
||||||
|
|
||||||
|
ApplicationWindow {
|
||||||
property int counter: 0
|
property int counter: 0
|
||||||
|
|
||||||
id: window
|
id: window
|
||||||
width: 640
|
width: 640
|
||||||
height: 480
|
height: 480
|
||||||
visible: true
|
visible: true
|
||||||
title: qsTr("Hello World")
|
title: "Megpie"
|
||||||
|
|
||||||
Rectangle {
|
header: Label {
|
||||||
id: page
|
text: stack.currentItem.title
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StackView {
|
||||||
|
id: stack
|
||||||
|
initialItem: welcome
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.centerIn: parent
|
}
|
||||||
color: increment.down ? "blue" : decrement.down ? "red" : "lightgrey"
|
|
||||||
|
|
||||||
Behavior on color {
|
Welcome {
|
||||||
ColorAnimation {
|
id: welcome
|
||||||
duration: 100
|
onPickServer: function (address) {
|
||||||
target: page
|
pick.address = address;
|
||||||
easing.type: Easing.InOutQuad
|
stack.push(pick)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GridLayout {
|
ServerPick {
|
||||||
anchors.centerIn: parent
|
visible: false
|
||||||
columns: 2
|
id: pick
|
||||||
|
onBack: stack.pop()
|
||||||
Text {
|
onSuccess: function (address) {
|
||||||
Layout.columnSpan: 2
|
welcome.serverAddress = address;
|
||||||
Layout.alignment: Qt.AlignHCenter
|
stack.pop();
|
||||||
text: qsTr("Hello World")
|
|
||||||
font.pointSize: 24; font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: increment
|
|
||||||
text: "Increment"
|
|
||||||
onClicked: window.counter++
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: decrement
|
|
||||||
text: "Decrement"
|
|
||||||
onClicked: window.counter--
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
Layout.columnSpan: 2
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: "The value is " + window.counter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
root.cpp
24
root.cpp
@ -1,9 +1,24 @@
|
|||||||
#include "root.h"
|
#include "root.h"
|
||||||
|
|
||||||
Root::Root(int& argc, char* argv[]) :
|
Root::Root(const QUrl& root, int& argc, char* argv[]) :
|
||||||
QGuiApplication(argc, argv)
|
QGuiApplication(argc, argv),
|
||||||
|
root(root),
|
||||||
|
engine(),
|
||||||
|
context(engine.rootContext()),
|
||||||
|
api()
|
||||||
{
|
{
|
||||||
|
std::cout << "Starting megpie..." << std::endl;
|
||||||
|
|
||||||
|
connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||||
|
this, &Root::onObjectCreated,
|
||||||
|
Qt::QueuedConnection
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.load(root);
|
||||||
|
if (engine.rootObjects().isEmpty())
|
||||||
|
throw std::runtime_error("Couldn't looad root qml object");
|
||||||
|
|
||||||
|
context->setContextProperty("API", &api);
|
||||||
}
|
}
|
||||||
|
|
||||||
Root::~Root() {
|
Root::~Root() {
|
||||||
@ -25,3 +40,8 @@ bool Root::notify(QObject* receiver, QEvent* e) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Root::onObjectCreated(QObject* obj, const QUrl& objUrl) {
|
||||||
|
if (!obj && objUrl == root)
|
||||||
|
exit(-2);
|
||||||
|
}
|
||||||
|
17
root.h
17
root.h
@ -1,13 +1,28 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QQmlContext>
|
||||||
|
|
||||||
|
#include "API/api.h"
|
||||||
|
|
||||||
class Root : public QGuiApplication {
|
class Root : public QGuiApplication {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Root(int& argc, char* argv[]);
|
Root(const QUrl& root, int& argc, char* argv[]);
|
||||||
~Root();
|
~Root();
|
||||||
bool notify(QObject* receiver, QEvent* e) override;
|
bool notify(QObject* receiver, QEvent* e) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onObjectCreated(QObject* obj, const QUrl& objUrl);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUrl root;
|
||||||
|
QQmlApplicationEngine engine;
|
||||||
|
QQmlContext* context;
|
||||||
|
API api;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user