Skip to content
Snippets Groups Projects
Commit 2e51af59 authored by Burak Hançerli's avatar Burak Hançerli :headphones:
Browse files

QDS-14287 Support interruptable operations

parent 00599908
No related branches found
No related tags found
1 merge request!65QDS-14287 Support interruptable operations
Pipeline #78318 passed
......@@ -47,16 +47,15 @@ execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/3rdparty/zxing-cpp describe -
add_definitions( -DCMAKE_VAR_GIT_VERSION="${CMAKE_VAR_GIT_VERSION}" )
add_definitions( -DCMAKE_VAR_QT_QUICK_COMPONENTS_VERSION="${CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION}" )
add_definitions( -DCMAKE_VAR_ZXING_VERSION="${CMAKE_VAR_ZXING_VERSION}" )
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests)
# this needs to be increased with every new release
if(NOT DEFINED GOOGLE_PLAY_APP_VERSION)
set(GOOGLE_PLAY_APP_VERSION 31)
endif()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests)
message(STATUS "GOOGLE_PLAY_APP_VERSION: ${GOOGLE_PLAY_APP_VERSION}")
message(STATUS "PROJECT VERSION: ${CMAKE_VAR_GIT_VERSION}")
message(STATUS "QT_VERSION: ${QT_VERSION}")
......
......@@ -78,7 +78,7 @@ target_include_directories(qtuiviewerlib PUBLIC
set_target_properties(${PROJECT_NAME} PROPERTIES QT_ANDROID_PACKAGE_NAME "io.qt.qtdesignviewer")
set_property(TARGET ${PROJECT_NAME}
APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
set_property(TARGET ${PROJECT_NAME}
APPEND PROPERTY QT_ANDROID_EXTRA_LIBS
......@@ -86,5 +86,6 @@ set_property(TARGET ${PROJECT_NAME}
${ANDROID_OPENSSL_PATH}/libssl_3.so
)
# CMAKE_VAR_GIT_VERSION (coming from the top-level CMakeLists.txt) and GOOGLE_PLAY_APP_VERSION replaced in the following file
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml.in ${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml)
......@@ -101,7 +101,7 @@ Flickable {
}
Repeater {
model: [qsTr("Open Qt Design Studio"), qsTr("Click Play > Manage Run Targets"), qsTr("In Manage Run Targets window click Add run target button.")]
model: [qsTr("Open up the Qt Design Studio, and a project"), qsTr("Click on the dropdown menu of the Play button in the header"), qsTr("Select Open Device Manager from the menu"), qsTr("In the Device Manager window click on Set Device IP and type in the IP address found from below")]
delegate: RowLayout {
Layout.fillWidth: true
......@@ -150,16 +150,35 @@ Flickable {
Layout.fillWidth: true
text: qsTr("xxx.xxx.xxx.xxx")
font.pixelSize: Constants.lgTextSize
font.bold: true
font.bold: false
wrapMode: Text.WordWrap
Component.onCompleted: {
function updateIpAddresses() {
ipAddress.text = '';
var val = backend.getIpAddresses();
for(var i = 0; i < val.length; i++) {
if(val.length === 0)
ipAddress.text = qsTr("Not connected to any network.");
for(var i = 0; i < val.length; i++)
ipAddress.text += val[i].interface + ': ' + val[i].ip + '\n';
}
Timer {
id: timer
interval: 1000
running: true
repeat: true
onTriggered: {
ipAddress.updateIpAddresses();
}
}
Component.onCompleted: {
ipAddress.updateIpAddresses();
timer.start();
}
}
}
}
......
......@@ -110,7 +110,7 @@ QString Backend::buildInfo() const
// clang-format off
return {
QCoreApplication::applicationVersion() +
"\nTechnology Preview - "+ CMAKE_VAR_GIT_VERSION +
"\n"+ CMAKE_VAR_GIT_VERSION +
"\nQt " + QT_VERSION_STR + " - " + buildType + " Build" +
"\nQt Quick Components " + CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION +
"\nZXing-Cpp: " + CMAKE_VAR_ZXING_VERSION +
......@@ -138,12 +138,21 @@ void Backend::initProjectManager()
{
m_projectManager.reset(new ProjectManager(this));
connect(m_projectManager.get(), &ProjectManager::closingProject, this, [this] {
QMetaObject::invokeMethod(m_dsManager.get(),
"sendProjectStopped",
Qt::QueuedConnection,
Q_ARG(QString, m_lastProjectSenderId));
});
connect(m_projectManager.get(),
&ProjectManager::closingProject,
this,
[this](const QString &sessionId) {
// most likely the project was running from the leftover session
// (DS connected, project started, DS disconnected without stopping the project)
// so we'll stop the project and do not send any signals to the DS
if (sessionId != m_lastSessionId)
return;
QMetaObject::invokeMethod(m_dsManager.get(),
"sendProjectStopped",
Qt::QueuedConnection,
Q_ARG(QString, m_lastProjectSenderId));
});
}
void Backend::initDsManager()
......@@ -175,23 +184,36 @@ void Backend::initDsManager()
connect(m_dsManager.get(),
&DesignStudioManager::projectIncoming,
this,
[this](const int projectSize) {
[this](const QString &id, const int projectSize) {
qDebug() << "Project incoming with size" << projectSize;
emit updatePopupText("Receiving project...");
// we'll use this to notify the correct DS when the project started/stopped
m_lastProjectSenderId = id;
m_lastSessionId = QUuid::createUuid().toString(QUuid::WithoutBraces);
QMetaObject::invokeMethod(m_projectManager.get(),
"stopProject",
Qt::QueuedConnection);
});
// connect(m_dsManager.get(),
// &DesignStudioManager::projectIncomingProgress,
// this,
// [this](const QString &id, const int percentage) { updatePopupProgress(percentage); });
connect(m_dsManager.get(),
&DesignStudioManager::projectIncomingProgress,
this,
[this](const QString &id, const int percentage) { updatePopupProgress(percentage); });
connect(m_dsManager.get(),
&DesignStudioManager::projectStopRequested,
this,
[this](const QString &id) {
qDebug() << "Project stop requested";
emit popupClose();
QMetaObject::invokeMethod(m_projectManager.get(),
"stopProject",
Qt::QueuedConnection);
QMetaObject::invokeMethod(m_dsManager.get(),
"sendProjectStopped",
Qt::QueuedConnection,
Q_ARG(QString, m_lastProjectSenderId));
});
m_dsManager->init();
......@@ -211,20 +233,22 @@ void Backend::runProject(const QString &id, const QByteArray &projectData)
{
emit updatePopupText("Running project...");
// we'll use this to notify the correct DS when the project started/stopped
m_lastProjectSenderId = id;
QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectRunning", Q_ARG(QString, id));
bool retVal;
QMetaObject::invokeMethod(m_projectManager.get(),
"runProject",
Q_RETURN_ARG(bool, retVal),
Q_ARG(QByteArray, projectData),
Q_ARG(bool, autoScaleProject()));
if (!retVal)
QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectStopped", Q_ARG(QString, id));
emit popupClose();
QTimer::singleShot(1000, [this, id, projectData] {
bool retVal;
QMetaObject::invokeMethod(m_projectManager.get(),
"runProject",
Q_RETURN_ARG(bool, retVal),
Q_ARG(QByteArray, projectData),
Q_ARG(bool, autoScaleProject()),
Q_ARG(QString, m_lastSessionId));
if (!retVal)
QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectStopped", Q_ARG(QString, id));
else
QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectRunning", Q_ARG(QString, id));
emit popupClose();
});
}
void Backend::scanQrCode()
......@@ -310,7 +334,6 @@ QJsonArray Backend::getIpAddresses() const
for (const auto &interface : networkInterface.allInterfaces()) {
for (const auto &entry : interface.addressEntries()) {
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) {
qDebug() << "Interface:" << interface.name() << "IP:" << entry.ip().toString();
ipAddresses.append(
QJsonObject{{"interface", interface.name()}, {"ip", entry.ip().toString()}});
}
......
......@@ -51,6 +51,7 @@ private:
QScopedPointer<QrScanner> m_qrScanner;
QString m_lastProjectSenderId;
QString m_lastSessionId;
// Settings
Settings m_settings;
......
......@@ -7,15 +7,48 @@
#include <QJsonObject>
#include <QScreen>
DesignStudio::DesignStudio(QWebSocket *socket, const QString &m_deviceUuid, QObject *parent)
namespace PackageFromDesignStudio {
using namespace Qt::Literals;
constexpr auto designStudioReady = "designStudioReady"_L1;
constexpr auto projectData = "projectData"_L1;
constexpr auto stopRunningProject = "stopRunningProject"_L1;
}; // namespace PackageFromDesignStudio
namespace PackageToDesignStudio {
using namespace Qt::Literals;
constexpr auto deviceInfo = "deviceInfo"_L1;
constexpr auto projectReceivingProgress = "projectReceivingProgress"_L1;
constexpr auto projectStarting = "projectStarting"_L1;
constexpr auto projectRunning = "projectRunning"_L1;
constexpr auto projectStopped = "projectStopped"_L1;
constexpr auto projectLogs = "projectLogs"_L1;
}; // namespace PackageToDesignStudio
DesignStudio::DesignStudio(QWebSocket *socket, const QString &deviceID, QObject *parent)
: QObject(parent)
, m_deviceUuid(m_deviceUuid)
, m_deviceID(deviceID)
, m_socket(socket)
{
initPingPong();
initSocket();
startPingPong();
m_projectStallTimer.setInterval(5000);
m_projectStallTimer.setSingleShot(true);
connect(&m_projectStallTimer, &QTimer::timeout, this, [this]() {
qDebug() << "Project is stalled. Closing the connection.";
m_socket->close();
m_socket->abort();
emit disconnected(m_designStudioID);
});
m_pingTimer.start();
m_speedCalculator.setInterval(1000);
m_speedCalculator.setSingleShot(false);
connect(&m_speedCalculator, &QTimer::timeout, this, [this]() {
quint64 elapsedTime = m_projectNotificationTime.msecsTo(QTime::currentTime()) / 1000;
int receiveSpeed = elapsedTime > 0 ? m_projectData.size() / elapsedTime / 1024 : 0;
qDebug() << "Receive speed" << receiveSpeed << "KB/s (" << receiveSpeed / 1024 << "MB/s )"
<< "percentage" << m_lastPercentage << "%";
});
}
void DesignStudio::initPingPong()
......@@ -27,7 +60,7 @@ void DesignStudio::initPingPong()
connect(&m_pingTimer, &QTimer::timeout, this, [this]() {
m_socket->ping();
m_pongTimer.start();
startPingPong();
});
connect(m_socket.data(),
......@@ -39,20 +72,32 @@ void DesignStudio::initPingPong()
});
connect(&m_pongTimer, &QTimer::timeout, this, [this]() {
qDebug() << "Design Studio" << m_id << "is not responding. Closing the connection.";
qDebug() << "Design Studio" << m_designStudioID
<< "is not responding. Closing the connection.";
m_socket->close();
m_socket->abort();
emit disconnected(m_id);
emit disconnected(m_designStudioID);
});
}
void DesignStudio::stopPingPong()
{
m_pingTimer.stop();
m_pongTimer.stop();
}
void DesignStudio::startPingPong()
{
m_pingTimer.start();
}
void DesignStudio::initSocket()
{
connect(m_socket.data(), &QWebSocket::disconnected, this, [this]() {
qDebug() << "Design Studio" << m_id << "disconnected";
qDebug() << "Design Studio" << m_designStudioID << "disconnected";
m_pingTimer.stop();
m_pongTimer.stop();
emit disconnected(m_id);
emit disconnected(m_designStudioID);
});
connect(m_socket.data(),
......@@ -61,20 +106,9 @@ void DesignStudio::initSocket()
&DesignStudio::processTextMessage);
connect(m_socket.data(),
&QWebSocket::binaryFrameReceived,
&QWebSocket::binaryMessageReceived,
this,
[this](const QByteArray &frame, bool isLastFrame) {
m_projectData.append(frame);
int percentage = m_projectData.size() * 100 / m_incomingProjectSize;
qDebug() << "frame size:" << frame.size() << "current size:" << m_projectData.size()
<< "total size:" << m_incomingProjectSize << "percentage:" << percentage
<< "isLastFrame:" << isLastFrame;
emit projectIncomingProgress(m_id,
m_projectData.size() * 100 / m_incomingProjectSize);
if (isLastFrame) {
emit projectReceived(m_id, m_projectData);
}
});
&DesignStudio::processBinaryMessage);
}
QString DesignStudio::ipv4Addr() const
......@@ -84,7 +118,7 @@ QString DesignStudio::ipv4Addr() const
QString DesignStudio::id() const
{
return m_id;
return m_designStudioID;
}
void DesignStudio::sendDeviceInfo()
......@@ -97,7 +131,7 @@ void DesignStudio::sendDeviceInfo()
deviceInfo["os"] = QSysInfo::prettyProductName();
deviceInfo["osVersion"] = QSysInfo::productVersion();
deviceInfo["architecture"] = QSysInfo::currentCpuArchitecture();
deviceInfo["deviceId"] = m_deviceUuid;
deviceInfo["deviceId"] = m_deviceID;
deviceInfo["appVersion"] = QString(CMAKE_VAR_GIT_VERSION);
qDebug() << "Sending device info to Design Studio" << deviceInfo;
......@@ -131,31 +165,73 @@ void DesignStudio::processTextMessage(const QString &message)
}
const QString dataType = jsonObj.value("dataType").toString();
const QJsonObject data = jsonObj.value("data").toObject();
if (dataType == PackageFromDesignStudio::designStudioReady) {
const QString newDesignStudioId = jsonObj.value("data").toString();
qDebug() << "Design Studio ready with ID" << newDesignStudioId;
m_id = newDesignStudioId;
const QString newDesignStudioId = data["designStudioID"].toString();
const int commVersion = data["commVersion"].toInt();
qDebug() << "Design Studio ready with ID" << newDesignStudioId << "and comm version"
<< commVersion;
m_designStudioID = newDesignStudioId;
sendDeviceInfo();
emit idReceived(newDesignStudioId, ipv4Addr());
} else if (dataType == PackageFromDesignStudio::projectData) {
m_incomingProjectSize = jsonObj.value("data").toInt();
qDebug() << "Project is expected with size" << m_incomingProjectSize;
m_incomingProjectSize = data["projectSize"].toInt();
const QString qtVersion = data["qtVersion"].toString();
auto qcompare = QString::compare(qtVersion, QT_VERSION_STR, Qt::CaseInsensitive);
if (qcompare != 0) {
qDebug() << "Qt version mismatch. Expected" << QT_VERSION_STR " or lower, but got"
<< qtVersion << ". Project may not work correctly.";
emit projectVersionMismatch(QT_VERSION_STR, qtVersion);
}
qDebug() << "Project is expected with size" << m_incomingProjectSize << "and Qt version"
<< qtVersion;
m_projectData.clear();
emit projectIncoming(m_incomingProjectSize);
m_projectNotificationTime = QTime::currentTime();
m_speedCalculator.start();
m_projectStallTimer.start();
stopPingPong();
emit projectIncoming(m_designStudioID, m_incomingProjectSize);
} else if (dataType == PackageFromDesignStudio::stopRunningProject) {
qDebug() << "Stop running project requested";
emit projectStopRequested(m_id);
m_projectData.clear();
m_speedCalculator.stop();
m_projectStallTimer.stop();
emit projectStopRequested(m_designStudioID);
} else {
qDebug() << "Unkown JSON message";
qDebug() << "Unkown JSON message type:" << dataType;
}
}
// this function should only be triggered when the device is receiving a qmlrc project
void DesignStudio::processBinaryMessage(const QByteArray &data)
{
qDebug() << "Binary message received (most probably a project)";
emit projectReceived(m_id, data);
m_projectStallTimer.start();
m_projectData.append(data);
const bool isProjectComplete = m_projectData.size() == m_incomingProjectSize;
int percentage = m_projectData.size() * 100 / m_incomingProjectSize;
if (m_lastPercentage != percentage) {
emit projectIncomingProgress(m_designStudioID, percentage);
m_lastPercentage = percentage;
sendData(PackageToDesignStudio::projectReceivingProgress, percentage);
}
if (isProjectComplete) {
startPingPong();
sendProjectStarting();
emit projectReceived(m_designStudioID, m_projectData);
m_speedCalculator.stop();
m_projectStallTimer.stop();
}
}
void DesignStudio::sendProjectStarting()
{
sendData(PackageToDesignStudio::projectStarting);
}
void DesignStudio::sendProjectRunning()
......
......@@ -7,26 +7,11 @@
#include <QTimer>
#include <QWebSocket>
namespace PackageFromDesignStudio {
using namespace Qt::Literals;
constexpr auto designStudioReady = "designStudioReady"_L1;
constexpr auto projectData = "projectData"_L1;
constexpr auto stopRunningProject = "stopRunningProject"_L1;
}; // namespace PackageFromDesignStudio
namespace PackageToDesignStudio {
using namespace Qt::Literals;
constexpr auto deviceInfo = "deviceInfo"_L1;
constexpr auto projectRunning = "projectRunning"_L1;
constexpr auto projectStopped = "projectStopped"_L1;
constexpr auto projectLogs = "projectLogs"_L1;
}; // namespace PackageToDesignStudio
class DesignStudio : public QObject
{
Q_OBJECT
public:
DesignStudio(QWebSocket *socket, const QString &m_deviceUuid, QObject *parent = nullptr);
DesignStudio(QWebSocket *socket, const QString &deviceID, QObject *parent = nullptr);
// Getters
QString ipv4Addr() const;
......@@ -34,6 +19,7 @@ public:
// Send data
void sendDeviceInfo();
void sendProjectStarting();
void sendProjectRunning();
void sendProjectStopped();
void sendProjectLogs(const QString &logs);
......@@ -47,18 +33,24 @@ private:
QTimer m_pongTimer;
// DS data
QString m_id;
QString m_deviceUuid;
QString m_designStudioID;
QString m_deviceID;
// project
QByteArray m_projectData;
int m_incomingProjectSize;
int m_lastPercentage;
QTime m_projectNotificationTime;
QTimer m_speedCalculator;
QTimer m_projectStallTimer;
// DS comm
void sendData(const QLatin1String &dataType, const QJsonValue &data = QJsonValue());
// Internal
void initPingPong();
void startPingPong();
void stopPingPong();
void initSocket();
private slots:
......@@ -72,7 +64,8 @@ signals:
// DS signals
void idReceived(const QString &id, const QString &ipv4Addr);
void projectIncoming(const int projectSize);
void projectVersionMismatch(const QString &version, const QString &got);
void projectIncoming(const QString &id, const int projectSize);
void projectIncomingProgress(const QString &id, const int percentage);
void projectReceived(const QString &id, const QByteArray &data);
void projectStopRequested(const QString &id);
......
......@@ -76,55 +76,62 @@ void DesignStudioManager::incomingConnection()
this,
&DesignStudioManager::projectIncomingProgress);
connect(designStudio.data(),
&DesignStudio::projectVersionMismatch,
this,
&DesignStudioManager::projectVersionMismatch);
connect(designStudio.data(),
&DesignStudio::projectReceived,
this,
[this, &designStudio](const QString &id, const QByteArray &project) {
emit projectReceived(id, project);
});
&DesignStudioManager::projectReceived);
connect(designStudio.data(),
&DesignStudio::projectStopRequested,
this,
[this](const QString &id) { emit projectStopRequested(id); });
&DesignStudioManager::projectStopRequested);
m_designStudios.append(designStudio);
emit designStudioConnected(designStudio->id(), designStudio->ipv4Addr());
}
void DesignStudioManager::sendProjectStarting(const QString &id)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id)
designStudio->sendProjectStarting();
}
}
void DesignStudioManager::sendProjectRunning(const QString &id)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
if (designStudio->id() == id)
designStudio->sendProjectRunning();
}
}
}
void DesignStudioManager::sendProjectStopped(const QString &id)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
if (designStudio->id() == id)
designStudio->sendProjectStopped();
}
}
}
void DesignStudioManager::sendProjectLogs(const QString &id, const QString &logs)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
if (designStudio->id() == id)
designStudio->sendProjectLogs(logs);
}
}
}
QString DesignStudioManager::getDesignStudioIp(const QString &id) const
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
if (designStudio->id() == id)
return designStudio->ipv4Addr();
}
}
return {};
......
......@@ -16,6 +16,7 @@ public:
void init();
public slots:
void sendProjectStarting(const QString &id);
void sendProjectRunning(const QString &id);
void sendProjectStopped(const QString &id);
void sendProjectLogs(const QString &id, const QString &logs);
......@@ -35,7 +36,8 @@ private:
void incomingConnection();
signals:
void projectIncoming(const int &projectSize);
void projectVersionMismatch(const QString &expected, const QString &got);
void projectIncoming(const QString &id, const int &projectSize);
void projectIncomingProgress(const QString &id, const int percentage);
void projectReceived(const QString &id, const QByteArray &project);
void designStudioConnected(const QString &id, const QString &ipAddr);
......
......@@ -25,6 +25,9 @@ import QtQuick.Studio.MultiText
import QtQuick.Studio.Utils
import QtQuick.Studio.DesignEffects
import QtQuick.Timeline
import QtQuick.Timeline.BlendTrees
import QtQuickUltralite.Extras
import QtQuickUltralite.Layers
......
......@@ -198,9 +198,12 @@ bool ProjectManager::isQt6Project(const QString &qmlProjectFileContent)
return qt6ProjectMatch.hasMatch();
}
bool ProjectManager::runProject(const QByteArray &project, const bool autoScaleProject)
bool ProjectManager::runProject(const QByteArray &project,
const bool autoScaleProject,
const QString &sessionId)
{
m_autoScaleProject = autoScaleProject;
m_sessionId = sessionId;
const QString projectPath = unpackProject(project);
qDebug() << "Project location: " << projectPath;
......@@ -361,7 +364,7 @@ void ProjectManager::showAppWindow()
disconnect(screen, &QScreen::orientationChanged, this, &ProjectManager::orientateWindow);
// this signal is connected to the lambda in the backend
// which will reset the project manager
emit closingProject();
emit closingProject(m_sessionId);
});
m_quickWindow->setFlags(Qt::Window | Qt::WindowStaysOnTopHint);
......@@ -373,6 +376,7 @@ void ProjectManager::stopProject()
if (!m_quickWindow || !m_quickWindow->isVisible())
return;
m_sessionId.clear();
qDebug("Stopping the QML app window");
m_quickWindow->close();
}
......
......@@ -37,13 +37,16 @@ public:
bool isRunning();
public slots:
bool runProject(const QByteArray &project, const bool autoScaleProject);
bool runProject(const QByteArray &project,
const bool autoScaleProject,
const QString &sessionId);
void stopProject();
private:
// Member variables
QByteArray m_projectData;
QString m_projectPath;
QString m_sessionId;
bool m_autoScaleProject;
// Qml related members
......@@ -70,5 +73,5 @@ private:
void showAppWindow();
signals:
void closingProject();
void closingProject(const QString &sessionId);
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment