aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt5
-rw-r--r--demo/CMakeLists.txt6
-rw-r--r--demo/Main.qml292
-rw-r--r--mock-server/CMakeLists.txt2
-rw-r--r--mock-server/EchoServer.cpp50
-rw-r--r--mock-server/EchoServer.h24
-rw-r--r--mock-server/main.cpp55
-rw-r--r--src/Xpl2Client.cpp159
-rw-r--r--src/Xpl2Client.h66
9 files changed, 415 insertions, 244 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e49cf1b..6562716 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,10 +10,9 @@ qt_standard_project_setup()
qt_policy(SET QTP0001 NEW)
# QML import path so Qt Creator / qmlls can resolve modules.
-list(APPEND QML_IMPORT_PATH "${PROJECT_BINARY_DIR}/qml")
set(QML_IMPORT_PATH
- "${QML_IMPORT_PATH}"
- CACHE STRING "" FORCE)
+ "${CMAKE_CURRENT_BINARY_DIR}/qml"
+ CACHE STRING "Path to locally built QML modules")
# Generate .qmlls.ini for QML language server
set(QT_QML_GENERATE_QMLLS_INI
diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
index f85639b..dd46473 100644
--- a/demo/CMakeLists.txt
+++ b/demo/CMakeLists.txt
@@ -7,7 +7,11 @@ qt_add_qml_module(
VERSION
1.0
QML_FILES
- Main.qml)
+ Main.qml
+ # IMPORT_PATH sets QT_QML_IMPORT_PATH on the target, which qmlls reads to
+ # resolve imports like "import Xpl2" in .qmlls.ini.
+ IMPORT_PATH
+ "${PROJECT_BINARY_DIR}/qml")
set_target_properties(QtXpl2Demo PROPERTIES RUNTIME_OUTPUT_DIRECTORY
"${PROJECT_BINARY_DIR}/bin")
diff --git a/demo/Main.qml b/demo/Main.qml
index 82b2573..fe9f885 100644
--- a/demo/Main.qml
+++ b/demo/Main.qml
@@ -1,3 +1,5 @@
+pragma ComponentBehavior: Bound
+
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -6,170 +8,232 @@ import Xpl2
ApplicationWindow {
id: root
- width: 800
height: 600
- visible: true
title: "XPL2 Demo"
+ visible: true
+ width: 800
Connections {
- target: Xpl2Client
-
function onConnectedChanged() {
- log(Xpl2Client.connected ? "Connected" : "Disconnected");
+ debugConsole.appendLog(Xpl2Client.connected ? "Connected" :
+ "Disconnected");
}
- function onResponseReceived(response: string) {
- log("← " + response);
- responseModel.append({
- text: response
- });
+ function onErrorOccurred(error: string) {
+ debugConsole.appendLog("ERROR: " + error);
}
- function onErrorOccurred(error: string) {
- log("ERROR: " + error);
+ function onResponseReceived(response: string) {
+ debugConsole.appendLog("Received: " + response);
+ responseModel.append({
+ text: response
+ });
}
function onStatusMessage(message: string) {
- log(message);
+ debugConsole.appendLog(message);
}
- }
- function log(msg: string) {
- let ts = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss.zzz");
- logModel.append({
- text: "[" + ts + "] " + msg
- });
- logView.positionViewAtEnd();
+ target: Xpl2Client
}
ColumnLayout {
anchors.fill: parent
- anchors.margins: 16
- spacing: 12
+ spacing: 0
- // --- Connection ---
- GroupBox {
- title: "Connection"
+ ColumnLayout {
+ Layout.fillHeight: true
Layout.fillWidth: true
+ Layout.margins: 16
+ spacing: 12
- GridLayout {
- columns: 4
- anchors.fill: parent
+ // --- Connection ---
+ GroupBox {
+ Layout.fillWidth: true
+ title: "Connection"
- Label {
- text: "Host:"
- }
- TextField {
- id: hostField
- text: Xpl2Client.host
- Layout.fillWidth: true
- onEditingFinished: Xpl2Client.host = text
- }
+ GridLayout {
+ anchors.fill: parent
+ columns: 2
- Label {
- text: "Port:"
- }
- TextField {
- id: portField
- text: Xpl2Client.port
- implicitWidth: 80
- validator: IntValidator {
- bottom: 1
- top: 65535
+ Label {
+ text: "Host:"
}
- onEditingFinished: Xpl2Client.port = parseInt(text)
- }
- Button {
- text: Xpl2Client.connected ? "Disconnect" : "Connect"
- Layout.columnSpan: 4
- Layout.alignment: Qt.AlignRight
- onClicked: {
- if (Xpl2Client.connected)
+ TextField {
+ id: hostField
+
+ Layout.fillWidth: true
+ text: Xpl2Client.host
+
+ onEditingFinished: Xpl2Client.host = text
+ }
+
+ Label {
+ text: "Command port:"
+ }
+
+ TextField {
+ id: commandPortField
+
+ Layout.fillWidth: true
+ text: Xpl2Client.commandPort
+
+ validator: IntValidator {
+ bottom: 1
+ top: 65535
+ }
+
+ onEditingFinished: Xpl2Client.commandPort = parseInt(
+ text)
+ }
+
+ Label {
+ text: "Imaging port:"
+ }
+
+ TextField {
+ id: imagingPortField
+
+ Layout.fillWidth: true
+ text: Xpl2Client.imagingPort
+
+ validator: IntValidator {
+ bottom: 1
+ top: 65535
+ }
+
+ onEditingFinished: Xpl2Client.imagingPort = parseInt(
+ text)
+ }
+
+ Label {
+ text: "Status port:"
+ }
+
+ TextField {
+ id: statusPortField
+
+ Layout.fillWidth: true
+ text: Xpl2Client.statusPort
+
+ validator: IntValidator {
+ bottom: 1
+ top: 65535
+ }
+
+ onEditingFinished: Xpl2Client.statusPort = parseInt(
+ text)
+ }
+
+ Button {
+ Layout.alignment: Qt.AlignRight
+ Layout.columnSpan: 2
+ text: Xpl2Client.connected ? "Disconnect" : "Connect"
+
+ onClicked: {
+ if (Xpl2Client.connected)
Xpl2Client.disconnectFromServer();
- else
+ else
Xpl2Client.connectToServer();
+ }
}
}
}
- }
- // --- Send Command ---
- GroupBox {
- title: "Command"
- Layout.fillWidth: true
- enabled: Xpl2Client.connected
+ // --- Send Command ---
+ GroupBox {
+ Layout.fillWidth: true
+ enabled: Xpl2Client.connected
+ title: "Command"
- RowLayout {
- anchors.fill: parent
+ RowLayout {
+ anchors.fill: parent
- TextField {
- id: cmdField
- placeholderText: "Enter command…"
- Layout.fillWidth: true
- onAccepted: sendBtn.clicked()
- }
+ TextField {
+ id: cmdField
+
+ Layout.fillWidth: true
+ placeholderText: "Enter command…"
+
+ onAccepted: sendBtn.clicked()
+ }
- Button {
- id: sendBtn
- text: "Send"
- onClicked: {
- if (cmdField.text.length > 0) {
- Xpl2Client.sendCommand(cmdField.text);
- cmdField.text = "";
+ Button {
+ id: sendBtn
+
+ text: "Send"
+
+ onClicked: {
+ if (cmdField.text.length > 0) {
+ Xpl2Client.sendCommand(cmdField.text);
+ cmdField.text = "";
+ }
}
}
}
}
- }
- // --- Responses ---
- GroupBox {
- title: "Responses"
- Layout.fillWidth: true
- Layout.fillHeight: true
+ // --- Responses ---
+ GroupBox {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ title: "Responses"
- ListView {
- id: responseView
- anchors.fill: parent
- clip: true
- model: ListModel {
- id: responseModel
- }
+ ListView {
+ id: responseView
- delegate: Text {
- required property string text
- width: responseView.width
- wrapMode: Text.Wrap
- font.family: "monospace"
- font.pixelSize: 13
- text: model.text
+ anchors.fill: parent
+ clip: true
+
+ delegate: Text {
+ id: responseDelegate
+
+ required property string text
+
+ font.family: "monospace"
+ font.pixelSize: 13
+ text: responseDelegate.text
+ width: responseView.width
+ wrapMode: Text.Wrap
+ }
+ model: ListModel {
+ id: responseModel
+
+ }
}
}
}
- // --- Debug Log ---
- GroupBox {
- title: "Log"
+ // --- Debug Console ---
+ Rectangle {
+ id: debugConsole
+
+ function appendLog(msg) {
+ let ts = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss");
+ debugLog.text += "[" + ts + "] " + msg + "\n";
+ debugLog.cursorPosition = debugLog.text.length;
+ }
+
Layout.fillWidth: true
- Layout.preferredHeight: 160
+ Layout.preferredHeight: 120
+ border.color: "#444"
+ color: "#1e1e1e"
+ radius: 4
- ListView {
- id: logView
+ ScrollView {
anchors.fill: parent
- clip: true
- model: ListModel {
- id: logModel
- }
+ anchors.margins: 4
+
+ TextArea {
+ id: debugLog
- delegate: Text {
- required property string text
- width: logView.width
- wrapMode: Text.Wrap
+ background: null
+ color: "#cccccc"
font.family: "monospace"
- font.pixelSize: 12
- color: "#555"
- text: model.text
+ font.pointSize: 9
+ readOnly: true
+ wrapMode: TextEdit.Wrap
}
}
}
diff --git a/mock-server/CMakeLists.txt b/mock-server/CMakeLists.txt
index 52bfabb..f5d54f8 100644
--- a/mock-server/CMakeLists.txt
+++ b/mock-server/CMakeLists.txt
@@ -1,4 +1,4 @@
-qt_add_executable(Xpl2MockServer main.cpp)
+qt_add_executable(Xpl2MockServer main.cpp EchoServer.h EchoServer.cpp)
set_target_properties(Xpl2MockServer PROPERTIES RUNTIME_OUTPUT_DIRECTORY
"${PROJECT_BINARY_DIR}/bin")
diff --git a/mock-server/EchoServer.cpp b/mock-server/EchoServer.cpp
new file mode 100644
index 0000000..43fc6f9
--- /dev/null
+++ b/mock-server/EchoServer.cpp
@@ -0,0 +1,50 @@
+/**
+ * @file EchoServer.cpp
+ * @brief Simple TCP echo server for a single port.
+ */
+#include "EchoServer.h"
+
+#include <QTcpSocket>
+
+EchoServer::EchoServer (quint16 port, const char *name, QObject *parent)
+ : QTcpServer (parent), m_name (name), m_port (port)
+{
+ connect (this, &QTcpServer::newConnection, this,
+ &EchoServer::onNewConnection);
+
+ if (!listen (QHostAddress::Any, port))
+ qCritical ("Failed to listen on %s port %d: %s", m_name, m_port,
+ qPrintable (errorString ()));
+ else
+ qInfo ("Listening on %s port %d", m_name, m_port);
+}
+
+void
+EchoServer::onNewConnection ()
+{
+ while (auto *sock = nextPendingConnection ())
+ {
+ qInfo ("[%s:%d] client connected", m_name, m_port);
+ connect (sock, &QTcpSocket::readyRead, this,
+ &EchoServer::onClientReadyRead);
+ connect (sock, &QTcpSocket::disconnected, this,
+ &EchoServer::onClientDisconnected);
+ }
+}
+
+void
+EchoServer::onClientReadyRead ()
+{
+ auto *sock = qobject_cast<QTcpSocket *> (sender ());
+ QByteArray data = sock->readAll ();
+ qInfo ("[%s:%d] echo %lld bytes", m_name, m_port, data.size ());
+ sock->write (data);
+}
+
+void
+EchoServer::onClientDisconnected ()
+{
+ auto *sock = qobject_cast<QTcpSocket *> (sender ());
+ qInfo ("[%s:%d] client disconnected", m_name, m_port);
+ sock->deleteLater ();
+}
diff --git a/mock-server/EchoServer.h b/mock-server/EchoServer.h
new file mode 100644
index 0000000..2e51b1e
--- /dev/null
+++ b/mock-server/EchoServer.h
@@ -0,0 +1,24 @@
+/**
+ * @file EchoServer.h
+ * @brief Simple TCP echo server for a single port.
+ */
+#pragma once
+
+#include <QTcpServer>
+
+class EchoServer : public QTcpServer
+{
+ Q_OBJECT
+
+public:
+ EchoServer (quint16 port, const char *name, QObject *parent = nullptr);
+
+private slots:
+ void onNewConnection ();
+ void onClientReadyRead ();
+ void onClientDisconnected ();
+
+private:
+ const char *m_name;
+ quint16 m_port;
+};
diff --git a/mock-server/main.cpp b/mock-server/main.cpp
index 4818d6f..0694e4d 100644
--- a/mock-server/main.cpp
+++ b/mock-server/main.cpp
@@ -2,63 +2,18 @@
* @file main.cpp
* @brief Mock XPL2 server — echoes back on three ports.
*/
-#include <QCoreApplication>
-#include <QTcpServer>
-#include <QTcpSocket>
-
-static QTcpServer *
-listenOn (quint16 port, const char *name, QObject *parent)
-{
- auto *server = new QTcpServer (parent);
-
- QObject::connect (
- server, &QTcpServer::newConnection, server,
- [=] ()
- {
- while (auto *sock = server->nextPendingConnection ())
- {
- qInfo ("[%s:%d] client connected", name, port);
-
- QObject::connect (sock, &QTcpSocket::readyRead, sock,
- [=] ()
- {
- QByteArray data = sock->readAll ();
- qInfo ("[%s:%d] echo %lld bytes", name,
- port, data.size ());
- sock->write (data);
- });
+#include "EchoServer.h"
- QObject::connect (sock, &QTcpSocket::disconnected, sock,
- [=] ()
- {
- qInfo ("[%s:%d] client disconnected", name,
- port);
- sock->deleteLater ();
- });
- }
- });
-
- if (!server->listen (QHostAddress::Any, port))
- {
- qCritical ("Failed to listen on %s port %d: %s", name, port,
- qPrintable (server->errorString ()));
- }
- else
- {
- qInfo ("Listening on %s port %d", name, port);
- }
-
- return server;
-}
+#include <QCoreApplication>
int
main (int argc, char *argv[])
{
QCoreApplication app (argc, argv);
- listenOn (9110, "Command", &app);
- listenOn (9111, "Imaging", &app);
- listenOn (9112, "Status", &app);
+ new EchoServer (9110, "Command", &app);
+ new EchoServer (9111, "Imaging", &app);
+ new EchoServer (9112, "Status", &app);
return app.exec ();
}
diff --git a/src/Xpl2Client.cpp b/src/Xpl2Client.cpp
index db53792..8ed032f 100644
--- a/src/Xpl2Client.cpp
+++ b/src/Xpl2Client.cpp
@@ -4,83 +4,142 @@
*/
#include "Xpl2Client.h"
-Xpl2Client::Xpl2Client(QObject *parent) : QObject(parent) {
- connect(&m_socket, &QTcpSocket::connected, this, &Xpl2Client::onConnected);
- connect(&m_socket, &QTcpSocket::disconnected, this,
- &Xpl2Client::onDisconnected);
- connect(&m_socket, &QTcpSocket::readyRead, this, &Xpl2Client::onReadyRead);
- connect(&m_socket, &QAbstractSocket::errorOccurred, this,
- &Xpl2Client::onErrorOccurred);
+Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent)
+{
+ connect (&m_socket, &QTcpSocket::connected, this, &Xpl2Client::onConnected);
+ connect (&m_socket, &QTcpSocket::disconnected, this,
+ &Xpl2Client::onDisconnected);
+ connect (&m_socket, &QTcpSocket::readyRead, this, &Xpl2Client::onReadyRead);
+ connect (&m_socket, &QAbstractSocket::errorOccurred, this,
+ &Xpl2Client::onErrorOccurred);
}
-QString Xpl2Client::host() const { return m_host; }
+QString
+Xpl2Client::host () const
+{
+ return m_host;
+}
-void Xpl2Client::setHost(const QString &host) {
+void
+Xpl2Client::setHost (const QString &host)
+{
if (m_host == host)
return;
m_host = host;
- emit hostChanged();
+ emit hostChanged ();
}
-quint16 Xpl2Client::port() const { return m_port; }
+quint16
+Xpl2Client::commandPort () const
+{
+ return m_commandPort;
+}
-void Xpl2Client::setPort(quint16 port) {
- if (m_port == port)
+void
+Xpl2Client::setCommandPort (quint16 port)
+{
+ if (m_commandPort == port)
return;
- m_port = port;
- emit portChanged();
+ m_commandPort = port;
+ emit commandPortChanged ();
}
-bool Xpl2Client::isConnected() const {
- return m_socket.state() == QAbstractSocket::ConnectedState;
+quint16
+Xpl2Client::imagingPort () const
+{
+ return m_imagingPort;
}
-void Xpl2Client::connectToServer() {
- if (isConnected()) {
- emit statusMessage(QStringLiteral("Already connected"));
+void
+Xpl2Client::setImagingPort (quint16 port)
+{
+ if (m_imagingPort == port)
return;
- }
- emit statusMessage(
- QStringLiteral("Connecting to %1:%2…").arg(m_host).arg(m_port));
- m_socket.connectToHost(m_host, m_port);
+ m_imagingPort = port;
+ emit imagingPortChanged ();
}
-void Xpl2Client::disconnectFromServer() {
- if (!isConnected())
+quint16
+Xpl2Client::statusPort () const
+{
+ return m_statusPort;
+}
+
+void
+Xpl2Client::setStatusPort (quint16 port)
+{
+ if (m_statusPort == port)
return;
- m_socket.disconnectFromHost();
+ m_statusPort = port;
+ emit statusPortChanged ();
+}
+
+bool
+Xpl2Client::isConnected () const
+{
+ return m_socket.state () == QAbstractSocket::ConnectedState;
+}
+
+void
+Xpl2Client::connectToServer ()
+{
+ if (isConnected ())
+ {
+ emit statusMessage (QStringLiteral ("Already connected"));
+ return;
+ }
+ emit statusMessage (QStringLiteral ("Connecting to %1:%2…")
+ .arg (m_host)
+ .arg (m_commandPort));
+ m_socket.connectToHost (m_host, m_commandPort);
}
-void Xpl2Client::sendCommand(const QString &command) {
- if (!isConnected()) {
- emit errorOccurred(QStringLiteral("Not connected"));
+void
+Xpl2Client::disconnectFromServer ()
+{
+ if (!isConnected ())
return;
- }
- QByteArray data = command.toUtf8() + '\n';
- m_socket.write(data);
- emit statusMessage(QStringLiteral("Sent: %1").arg(command));
+ m_socket.disconnectFromHost ();
+}
+
+void
+Xpl2Client::sendCommand (const QString &command)
+{
+ if (!isConnected ())
+ {
+ emit errorOccurred (QStringLiteral ("Not connected"));
+ return;
+ }
+ QByteArray data = command.toUtf8 () + '\n';
+ m_socket.write (data);
+ emit statusMessage (QStringLiteral ("Sent: %1").arg (command));
}
-void Xpl2Client::onConnected() {
- emit connectedChanged();
- emit statusMessage(QStringLiteral("Connected to %1:%2")
- .arg(m_socket.peerName())
- .arg(m_socket.peerPort()));
+void
+Xpl2Client::onConnected ()
+{
+ emit connectedChanged ();
}
-void Xpl2Client::onDisconnected() {
- emit connectedChanged();
- emit statusMessage(QStringLiteral("Disconnected"));
+void
+Xpl2Client::onDisconnected ()
+{
+ emit connectedChanged ();
}
-void Xpl2Client::onReadyRead() {
- while (m_socket.canReadLine()) {
- QString line = QString::fromUtf8(m_socket.readLine()).trimmed();
- emit responseReceived(line);
- }
+void
+Xpl2Client::onReadyRead ()
+{
+ while (m_socket.canReadLine ())
+ {
+ QString line = QString::fromUtf8 (m_socket.readLine ()).trimmed ();
+ emit responseReceived (line);
+ }
}
-void Xpl2Client::onErrorOccurred(QAbstractSocket::SocketError error) {
- Q_UNUSED(error)
- emit errorOccurred(m_socket.errorString());
+void
+Xpl2Client::onErrorOccurred (QAbstractSocket::SocketError error)
+{
+ Q_UNUSED (error)
+ emit errorOccurred (m_socket.errorString ());
}
diff --git a/src/Xpl2Client.h b/src/Xpl2Client.h
index f052962..d339bc3 100644
--- a/src/Xpl2Client.h
+++ b/src/Xpl2Client.h
@@ -8,46 +8,62 @@
#include <QQmlEngine>
#include <QTcpSocket>
-class Xpl2Client : public QObject {
+class Xpl2Client : public QObject
+{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
- Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
- Q_PROPERTY(quint16 port READ port WRITE setPort NOTIFY portChanged)
- Q_PROPERTY(bool connected READ isConnected NOTIFY connectedChanged)
+ Q_PROPERTY (QString host READ host WRITE setHost NOTIFY hostChanged)
+ Q_PROPERTY (quint16 commandPort READ commandPort WRITE setCommandPort NOTIFY
+ commandPortChanged)
+ Q_PROPERTY (quint16 imagingPort READ imagingPort WRITE setImagingPort NOTIFY
+ imagingPortChanged)
+ Q_PROPERTY (quint16 statusPort READ statusPort WRITE setStatusPort NOTIFY
+ statusPortChanged)
+ Q_PROPERTY (bool connected READ isConnected NOTIFY connectedChanged)
public:
- explicit Xpl2Client(QObject *parent = nullptr);
+ explicit Xpl2Client (QObject *parent = nullptr);
- QString host() const;
- void setHost(const QString &host);
+ QString host () const;
+ void setHost (const QString &host);
- quint16 port() const;
- void setPort(quint16 port);
+ quint16 commandPort () const;
+ void setCommandPort (quint16 port);
- bool isConnected() const;
+ quint16 imagingPort () const;
+ void setImagingPort (quint16 port);
- Q_INVOKABLE void connectToServer();
- Q_INVOKABLE void disconnectFromServer();
- Q_INVOKABLE void sendCommand(const QString &command);
+ quint16 statusPort () const;
+ void setStatusPort (quint16 port);
+
+ bool isConnected () const;
+
+ Q_INVOKABLE void connectToServer ();
+ Q_INVOKABLE void disconnectFromServer ();
+ Q_INVOKABLE void sendCommand (const QString &command);
signals:
- void hostChanged();
- void portChanged();
- void connectedChanged();
- void responseReceived(const QString &response);
- void errorOccurred(const QString &error);
- void statusMessage(const QString &message);
+ void hostChanged ();
+ void commandPortChanged ();
+ void imagingPortChanged ();
+ void statusPortChanged ();
+ void connectedChanged ();
+ void responseReceived (const QString &response);
+ void errorOccurred (const QString &error);
+ void statusMessage (const QString &message);
private slots:
- void onConnected();
- void onDisconnected();
- void onReadyRead();
- void onErrorOccurred(QAbstractSocket::SocketError error);
+ void onConnected ();
+ void onDisconnected ();
+ void onReadyRead ();
+ void onErrorOccurred (QAbstractSocket::SocketError error);
private:
QTcpSocket m_socket;
- QString m_host = QStringLiteral("127.0.0.1");
- quint16 m_port = 5000;
+ QString m_host = QStringLiteral ("127.0.0.1");
+ quint16 m_commandPort = 9110;
+ quint16 m_imagingPort = 9111;
+ quint16 m_statusPort = 9112;
};