DeviceShare: Add Android app connector and device manager

Change-Id: I30cb3dc8b71e87fc27482aa503cb53ce98c6bde0
Reviewed-by: Henning Gründl <henning.gruendl@qt.io>
This commit is contained in:
Burak Hancerli
2024-10-22 15:44:01 +02:00
parent 32cad175c2
commit f3046da7d4
9 changed files with 1024 additions and 0 deletions

View File

@@ -34,6 +34,8 @@ add_feature_info("Meta info tracing" ${ENABLE_METAINFO_TRACING} "")
add_subdirectory(libs)
find_package(Qt6 REQUIRED COMPONENTS WebSockets)
add_qtc_plugin(QmlDesigner
PLUGIN_RECOMMENDS QmlPreview
CONDITION TARGET QmlDesignerCore AND TARGET Qt::QuickWidgets AND TARGET Qt::Svg
@@ -728,6 +730,17 @@ extend_qtc_plugin(QmlDesigner
messagemodel.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/devicesharing
DEPENDS
Qt::WebSockets
SOURCES
device.cpp device.h
deviceinfo.cpp deviceinfo.h
devicemanager.cpp devicemanager.h
devicemanagermodel.cpp devicemanagermodel.h
)
add_qtc_plugin(assetexporterplugin
PLUGIN_CLASS AssetExporterPlugin
CONDITION TARGET QmlDesigner

View File

@@ -0,0 +1,198 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "device.h"
#include <QJsonDocument>
#include <QLatin1String>
namespace QmlDesigner::DeviceShare {
// Below are the constants that are used in the communication between the Design Studio and the device.
namespace PackageToDevice {
using namespace Qt::Literals;
constexpr auto designStudioReady = "designStudioReady"_L1;
constexpr auto projectData = "projectData"_L1;
constexpr auto stopRunningProject = "stopRunningProject"_L1;
}; // namespace PackageToDevice
namespace PackageFromDevice {
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 PackageFromDevice
Device::Device(const DeviceInfo &deviceInfo, const DeviceSettings &deviceSettings, QObject *parent)
: QObject(parent)
, m_deviceInfo(deviceInfo)
, m_deviceSettings(deviceSettings)
, m_socket(nullptr)
, m_socketWasConnected(false)
{
qCDebug(deviceSharePluginLog) << "initial device info:" << m_deviceInfo;
m_socket.reset(new QWebSocket());
connect(m_socket.data(), &QWebSocket::textMessageReceived, this, &Device::processTextMessage);
connect(m_socket.data(), &QWebSocket::disconnected, this, [this]() {
m_reconnectTimer.start();
if (!m_socketWasConnected)
return;
m_socketWasConnected = false;
m_pingTimer.stop();
m_pongTimer.stop();
emit disconnected(m_deviceInfo.deviceId());
});
connect(m_socket.data(), &QWebSocket::connected, this, [this]() {
m_socketWasConnected = true;
m_reconnectTimer.stop();
m_pingTimer.start(15000);
sendDesignStudioReady(m_deviceInfo.deviceId());
emit connected(m_deviceInfo.deviceId());
});
m_reconnectTimer.setSingleShot(true);
m_reconnectTimer.setInterval(5000);
connect(&m_reconnectTimer, &QTimer::timeout, this, &Device::reconnect);
initPingPong();
reconnect();
}
Device::~Device()
{
m_socket->close();
m_socket.reset();
}
void Device::initPingPong()
{
connect(&m_pingTimer, &QTimer::timeout, this, [this]() {
m_socket->ping();
m_pongTimer.start(15000);
});
connect(m_socket.data(),
&QWebSocket::pong,
this,
[this](quint64 elapsedTime, [[maybe_unused]] const QByteArray &payload) {
qCDebug(deviceSharePluginLog)
<< "Pong received from Device" << m_deviceInfo.deviceId() << "in" << elapsedTime
<< "ms";
m_pongTimer.stop();
});
connect(&m_pongTimer, &QTimer::timeout, this, [this]() {
qCDebug(deviceSharePluginLog)
<< "Device" << m_deviceInfo.deviceId() << "is not responding. Closing connection.";
m_socket->close();
});
m_pongTimer.setSingleShot(true);
}
void Device::reconnect()
{
if (m_socket->state() == QAbstractSocket::ConnectedState)
m_socket->close();
QUrl url(QStringLiteral("ws://%1:%2").arg(m_deviceSettings.ipAddress()).arg(40000));
m_socket->open(url);
}
DeviceInfo Device::deviceInfo() const
{
return m_deviceInfo;
}
void Device::setDeviceInfo(const DeviceInfo &deviceInfo)
{
m_deviceInfo = deviceInfo;
}
DeviceSettings Device::deviceSettings() const
{
return m_deviceSettings;
}
void Device::setDeviceSettings(const DeviceSettings &deviceSettings)
{
m_deviceSettings = deviceSettings;
reconnect();
}
void Device::sendDesignStudioReady(const QString &uuid) {
sendTextMessage(PackageToDevice::designStudioReady, uuid);
}
void Device::sendProjectNotification()
{
sendTextMessage(PackageToDevice::projectData);
}
void Device::sendProjectData(const QByteArray &data)
{
sendBinaryMessage(data);
}
void Device::sendProjectStopped()
{
sendTextMessage(PackageToDevice::stopRunningProject);
}
bool Device::isConnected() const
{
return m_socket ? m_socket->state() == QAbstractSocket::ConnectedState : false;
}
void Device::sendTextMessage(const QLatin1String &dataType, const QJsonValue &data)
{
if (!isConnected())
return;
QJsonObject message;
message["dataType"] = dataType;
message["data"] = data;
const QString jsonMessage = QString::fromLatin1(
QJsonDocument(message).toJson(QJsonDocument::Compact));
m_socket->sendTextMessage(jsonMessage);
}
void Device::sendBinaryMessage(const QByteArray &data)
{
if (!isConnected())
return;
m_socket->sendBinaryMessage(data);
}
void Device::processTextMessage(const QString &data)
{
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(data.toLatin1(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
qCDebug(deviceSharePluginLog)
<< "Failed to parse JSON message:" << jsonError.errorString() << data;
return;
}
const QJsonObject jsonObj = jsonDoc.object();
const QString dataType = jsonObj.value("dataType").toString();
if (dataType == PackageFromDevice::deviceInfo) {
QJsonObject deviceInfo = jsonObj.value("data").toObject();
m_deviceInfo.setJsonObject(deviceInfo);
emit deviceInfoReady(m_deviceInfo.deviceId(), m_deviceInfo);
} else if (dataType == PackageFromDevice::projectRunning) {
emit projectStarted(m_deviceInfo.deviceId());
} else if (dataType == PackageFromDevice::projectStopped) {
emit projectStopped(m_deviceInfo.deviceId());
} else if (dataType == PackageFromDevice::projectLogs) {
emit projectLogsReceived(m_deviceInfo.deviceId(), jsonObj.value("data").toString());
} else {
qCDebug(deviceSharePluginLog) << "Invalid JSON message:" << jsonObj;
}
}
} // namespace QmlDesigner::DeviceShare

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QTimer>
#include <QWebSocket>
#include "deviceinfo.h"
namespace QmlDesigner::DeviceShare {
class Device : public QObject
{
Q_OBJECT
public:
Device(const DeviceInfo &deviceInfo = {},
const DeviceSettings &deviceSettings = {},
QObject *parent = nullptr);
~Device();
// device management
DeviceInfo deviceInfo() const;
DeviceSettings deviceSettings() const;
void setDeviceInfo(const DeviceInfo &deviceInfo);
void setDeviceSettings(const DeviceSettings &deviceSettings);
// device communication
void sendDesignStudioReady(const QString &uuid);
void sendProjectNotification();
void sendProjectData(const QByteArray &data);
void sendProjectStopped();
// socket
bool isConnected() const;
void reconnect();
private slots:
void processTextMessage(const QString &data);
private:
DeviceInfo m_deviceInfo;
DeviceSettings m_deviceSettings;
QScopedPointer<QWebSocket> m_socket;
bool m_socketWasConnected;
QTimer m_reconnectTimer;
QTimer m_pingTimer;
QTimer m_pongTimer;
void initPingPong();
void sendTextMessage(const QLatin1String &dataType, const QJsonValue &data = QJsonValue());
void sendBinaryMessage(const QByteArray &data);
signals:
void connected(const QString &deviceId);
void disconnected(const QString &deviceId);
void deviceInfoReady(const QString &deviceId, const DeviceInfo &deviceInfo);
void projectStarted(const QString &deviceId);
void projectStopped(const QString &deviceId);
void projectLogsReceived(const QString &deviceId, const QString &logs);
};
} // namespace QmlDesigner::DeviceShare

View File

@@ -0,0 +1,130 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "deviceinfo.h"
#include <QJsonDocument>
namespace QmlDesigner::DeviceShare {
Q_LOGGING_CATEGORY(deviceSharePluginLog, "qtc.designer.deviceSharePluginLog")
IDeviceData::IDeviceData(const QJsonObject &data)
: m_data(data)
{}
QJsonObject IDeviceData::jsonObject() const
{
return m_data;
}
void IDeviceData::setJsonObject(const QJsonObject &data)
{
m_data = data;
}
IDeviceData::operator QString() const
{
return QString::fromLatin1(QJsonDocument(m_data).toJson(QJsonDocument::Compact));
}
bool DeviceSettings::active() const
{
return m_data.value(keyActive).toBool();
}
QString DeviceSettings::alias() const
{
return m_data.value(keyAlias).toString();
}
QString DeviceSettings::ipAddress() const
{
return m_data.value(keyIpAddress).toString();
}
void DeviceSettings::setActive(const bool &active)
{
m_data[keyActive] = active;
}
void DeviceSettings::setAlias(const QString &alias)
{
m_data[keyAlias] = alias;
}
void DeviceSettings::setIpAddress(const QString &ipAddress)
{
m_data[keyIpAddress] = ipAddress;
}
QString DeviceInfo::os() const
{
return m_data.value(keyOs).toString();
}
QString DeviceInfo::osVersion() const
{
return m_data.value(keyOsVersion).toString();
}
QString DeviceInfo::architecture() const
{
return m_data.value(keyArchitecture).toString();
}
int DeviceInfo::screenWidth() const
{
return m_data.value(keyScreenWidth).toInt();
}
int DeviceInfo::screenHeight() const
{
return m_data.value(keyScreenHeight).toInt();
}
QString DeviceInfo::deviceId() const
{
return m_data.value(keyDeviceId).toString();
}
QString DeviceInfo::appVersion() const
{
return m_data.value(keyAppVersion).toString();
}
void DeviceInfo::setOs(const QString &os)
{
m_data[keyOs] = os;
}
void DeviceInfo::setOsVersion(const QString &osVersion)
{
m_data[keyOsVersion] = osVersion;
}
void DeviceInfo::setArchitecture(const QString &architecture)
{
m_data[keyArchitecture] = architecture;
}
void DeviceInfo::setScreenWidth(const int &screenWidth)
{
m_data[keyScreenWidth] = screenWidth;
}
void DeviceInfo::setScreenHeight(const int &screenHeight)
{
m_data[keyScreenHeight] = screenHeight;
}
void DeviceInfo::setDeviceId(const QString &deviceId)
{
m_data[keyDeviceId] = deviceId;
}
void DeviceInfo::setAppVersion(const QString &appVersion)
{
m_data[keyAppVersion] = appVersion;
}
} // namespace QmlDesigner::DeviceShare

View File

@@ -0,0 +1,81 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QJsonObject>
#include <QLoggingCategory>
namespace QmlDesigner::DeviceShare {
Q_DECLARE_LOGGING_CATEGORY(deviceSharePluginLog);
class IDeviceData
{
public:
IDeviceData(const QJsonObject &data = QJsonObject());
virtual ~IDeviceData() = default;
// converters
QJsonObject jsonObject() const;
void setJsonObject(const QJsonObject &data);
operator QString() const;
protected:
QJsonObject m_data;
};
class DeviceSettings : public IDeviceData
{
public:
DeviceSettings() = default;
// Getters
bool active() const;
QString alias() const;
QString ipAddress() const;
// Setters
void setActive(const bool &active);
void setAlias(const QString &alias);
void setIpAddress(const QString &ipAddress);
private:
static constexpr char keyActive[] = "deviceActive";
static constexpr char keyAlias[] = "deviceAlias";
static constexpr char keyIpAddress[] = "ipAddress";
};
class DeviceInfo : public IDeviceData
{
public:
DeviceInfo() = default;
// Getters
QString os() const;
QString osVersion() const;
QString architecture() const;
int screenWidth() const;
int screenHeight() const;
QString deviceId() const;
QString appVersion() const;
// Setters
void setOs(const QString &os);
void setOsVersion(const QString &osVersion);
void setArchitecture(const QString &architecture);
void setScreenWidth(const int &screenWidth);
void setScreenHeight(const int &screenHeight);
void setDeviceId(const QString &deviceId);
void setAppVersion(const QString &appVersion);
private:
static constexpr char keyOs[] = "os";
static constexpr char keyOsVersion[] = "osVersion";
static constexpr char keyScreenWidth[] = "screenWidth";
static constexpr char keyScreenHeight[] = "screenHeight";
static constexpr char keyDeviceId[] = "deviceId";
static constexpr char keyArchitecture[] = "architecture";
static constexpr char keyAppVersion[] = "appVersion";
};
} // namespace QmlDesigner::DeviceShare

View File

@@ -0,0 +1,260 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "devicemanager.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QUdpSocket>
namespace QmlDesigner::DeviceShare {
DeviceManager::DeviceManager(QObject *parent, const QString &settingsPath)
: QObject(parent)
, m_settingsPath(settingsPath)
{
readSettings();
if (m_uuid.isEmpty()) {
m_uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
writeSettings();
}
}
void DeviceManager::writeSettings()
{
QJsonObject root;
QJsonArray devices;
for (const auto &device : m_devices) {
QJsonObject deviceInfo;
deviceInfo.insert("deviceInfo", device->deviceInfo().jsonObject());
deviceInfo.insert("deviceSettings", device->deviceSettings().jsonObject());
devices.append(deviceInfo);
}
root.insert("devices", devices);
root.insert("uuid", m_uuid);
QJsonDocument doc(root);
QFile file(m_settingsPath);
if (!file.open(QIODevice::WriteOnly)) {
qCWarning(deviceSharePluginLog) << "Failed to open settings file" << file.fileName();
return;
}
file.write(doc.toJson());
}
void DeviceManager::readSettings()
{
QFile file(m_settingsPath);
qCDebug(deviceSharePluginLog) << "Reading settings from" << file.fileName();
if (!file.open(QIODevice::ReadOnly)) {
qCWarning(deviceSharePluginLog) << "Failed to open settings file" << file.fileName();
return;
}
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
m_uuid = doc.object()["uuid"].toString();
QJsonArray devices = doc.object()["devices"].toArray();
for (const QJsonValue &deviceInfoJson : devices) {
DeviceInfo deviceInfo;
DeviceSettings deviceSettings;
deviceInfo.setJsonObject(deviceInfoJson.toObject()["deviceInfo"].toObject());
deviceSettings.setJsonObject(deviceInfoJson.toObject()["deviceSettings"].toObject());
auto device = initDevice(deviceInfo, deviceSettings);
m_devices.append(device);
}
}
QList<QSharedPointer<Device>> DeviceManager::devices() const
{
return m_devices;
}
QSharedPointer<Device> DeviceManager::findDevice(const QString &deviceId) const
{
auto it = std::find_if(m_devices.begin(), m_devices.end(), [deviceId](const auto &device) {
return device->deviceInfo().deviceId() == deviceId;
});
return it != m_devices.end() ? *it : nullptr;
}
std::optional<DeviceInfo> DeviceManager::deviceInfo(const QString &deviceId) const
{
auto device = findDevice(deviceId);
if (!device)
return {};
return device->deviceInfo();
}
void DeviceManager::setDeviceAlias(const QString &deviceId, const QString &alias)
{
auto device = findDevice(deviceId);
if (!device)
return;
auto deviceSettings = device->deviceSettings();
deviceSettings.setAlias(alias);
device->setDeviceSettings(deviceSettings);
writeSettings();
}
void DeviceManager::setDeviceActive(const QString &deviceId, const bool active)
{
auto device = findDevice(deviceId);
if (!device)
return;
auto deviceSettings = device->deviceSettings();
deviceSettings.setActive(active);
device->setDeviceSettings(deviceSettings);
writeSettings();
}
void DeviceManager::setDeviceIP(const QString &deviceId, const QString &ip)
{
auto device = findDevice(deviceId);
if (!device)
return;
auto deviceSettings = device->deviceSettings();
deviceSettings.setIpAddress(ip);
device->setDeviceSettings(deviceSettings);
writeSettings();
}
void DeviceManager::addDevice(const QString &ip)
{
if (ip.isEmpty())
return;
const auto trimmedIp = ip.trimmed();
// check regex for xxx.xxx.xxx.xxx
QRegularExpression ipRegex(R"(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)");
if (!ipRegex.match(trimmedIp).hasMatch()) {
qCWarning(deviceSharePluginLog) << "Invalid IP address" << ip;
return;
}
for (const auto &device : m_devices) {
if (device->deviceSettings().ipAddress() == trimmedIp) {
qCWarning(deviceSharePluginLog) << "Device" << trimmedIp << "already exists";
return;
}
}
DeviceSettings deviceSettings;
deviceSettings.setIpAddress(trimmedIp);
auto device = initDevice({}, deviceSettings);
m_devices.append(device);
writeSettings();
emit deviceAdded(device->deviceInfo());
}
QSharedPointer<Device> DeviceManager::initDevice(const DeviceInfo &deviceInfo,
const DeviceSettings &deviceSettings)
{
QSharedPointer<Device> device = QSharedPointer<Device>(new Device{deviceInfo, deviceSettings},
&QObject::deleteLater);
connect(device.data(), &Device::deviceInfoReady, this, &DeviceManager::deviceInfoReceived);
connect(device.data(), &Device::disconnected, this, &DeviceManager::deviceDisconnected);
connect(device.data(), &Device::projectStarted, this, [this](const QString deviceId) {
auto device = findDevice(deviceId);
qCDebug(deviceSharePluginLog) << "Project started on device" << deviceId;
emit projectStarted(device->deviceInfo());
});
connect(device.data(), &Device::projectStopped, this, [this](const QString deviceId) {
auto device = findDevice(deviceId);
qCDebug(deviceSharePluginLog) << "Project stopped on device" << deviceId;
emit projectStopped(device->deviceInfo());
});
connect(device.data(),
&Device::projectLogsReceived,
this,
[this](const QString deviceId, const QString &logs) {
auto device = findDevice(deviceId);
qCDebug(deviceSharePluginLog) << "Log:" << deviceId << logs;
emit projectLogsReceived(device->deviceInfo(), logs);
});
return device;
}
void DeviceManager::deviceInfoReceived(const QString &deviceId, const DeviceInfo &deviceInfo)
{
writeSettings();
qCDebug(deviceSharePluginLog) << "Device" << deviceId << "is online";
emit deviceOnline(deviceInfo);
}
void DeviceManager::deviceDisconnected(const QString &deviceId)
{
auto device = findDevice(deviceId);
if (!device)
return;
qCDebug(deviceSharePluginLog) << "Device" << deviceId << "disconnected";
emit deviceOffline(device->deviceInfo());
}
void DeviceManager::removeDevice(const QString &deviceId)
{
auto device = findDevice(deviceId);
if (!device)
return;
const auto deviceInfo = device->deviceInfo();
m_devices.removeOne(device);
writeSettings();
emit deviceRemoved(deviceInfo);
}
void DeviceManager::removeDeviceAt(int index)
{
if (index < 0 || index >= m_devices.size())
return;
auto deviceInfo = m_devices[index]->deviceInfo();
m_devices.removeAt(index);
writeSettings();
emit deviceRemoved(deviceInfo);
}
void DeviceManager::sendProjectFile(const QString &deviceId, const QString &projectFile)
{
auto device = findDevice(deviceId);
if (!device)
return;
device->sendProjectNotification();
QFile file(projectFile);
if (!file.open(QIODevice::ReadOnly)) {
qCWarning(deviceSharePluginLog) << "Failed to open project file" << projectFile;
return;
}
qCDebug(deviceSharePluginLog) << "Sending project file to device" << deviceId;
QByteArray projectData = file.readAll();
device->sendProjectData(projectData);
qCDebug(deviceSharePluginLog) << "Project file sent to device" << deviceId;
}
void DeviceManager::stopRunningProject(const QString &deviceId)
{
auto device = findDevice(deviceId);
if (!device)
return;
device->sendProjectStopped();
}
} // namespace QmlDesigner::DeviceShare

View File

@@ -0,0 +1,69 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QObject>
#include <QTimer>
#include <QWebSocketServer>
#include "device.h"
namespace QmlDesigner::DeviceShare {
class DeviceManager : public QObject
{
Q_OBJECT
public:
explicit DeviceManager(QObject *parent = nullptr, const QString &settingsPath = "settings.json");
// internal init functions
void addDevice(const QString &ip);
// Getters
QList<QSharedPointer<Device>> devices() const;
std::optional<DeviceInfo> deviceInfo(const QString &deviceId) const;
// Device management functions
void setDeviceAlias(const QString &deviceId, const QString &alias);
void setDeviceActive(const QString &deviceId, const bool active);
void setDeviceIP(const QString &deviceId, const QString &ip);
void removeDevice(const QString &deviceId);
void removeDeviceAt(int index);
void sendProjectFile(const QString &deviceId, const QString &projectFile);
void stopRunningProject(const QString &deviceId);
private:
// Devices management
QList<QSharedPointer<Device>> m_devices;
// settings
const QString m_settingsPath;
QString m_uuid;
private:
// internal slots
void incomingConnection();
void readSettings();
void writeSettings();
QSharedPointer<Device> initDevice(const DeviceInfo &deviceInfo = DeviceInfo(),
const DeviceSettings &deviceSettings = DeviceSettings());
// device signals
void deviceInfoReceived(const QString &deviceId, const DeviceInfo &deviceInfo);
void deviceDisconnected(const QString &deviceId);
QSharedPointer<Device> findDevice(const QString &deviceId) const;
signals:
void deviceAdded(const DeviceInfo &deviceInfo);
void deviceRemoved(const DeviceInfo &deviceInfo);
void deviceOnline(const DeviceInfo &deviceInfo);
void deviceOffline(const DeviceInfo &deviceInfo);
void projectStarted(const DeviceInfo &deviceInfo);
void projectStopped(const DeviceInfo &deviceInfo);
void projectLogsReceived(const DeviceInfo &deviceInfo, const QString &logs);
};
} // namespace QmlDesigner::DeviceShare

View File

@@ -0,0 +1,162 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "devicemanagermodel.h"
#include "devicemanager.h"
namespace QmlDesigner::DeviceShare {
DeviceManagerModel::DeviceManagerModel(DeviceManager &deviceManager, QObject *parent)
: QAbstractTableModel(parent)
, m_deviceManager(deviceManager)
{
connect(&m_deviceManager, &DeviceManager::deviceAdded, this, [this](const DeviceInfo &) {
endResetModel();
});
connect(&m_deviceManager, &DeviceManager::deviceRemoved, this, [this](const DeviceInfo &) {
endResetModel();
});
connect(&m_deviceManager, &DeviceManager::deviceOnline, this, [this](const DeviceInfo &) {
endResetModel();
});
connect(&m_deviceManager, &DeviceManager::deviceOffline, this, [this](const DeviceInfo &) {
endResetModel();
});
}
int DeviceManagerModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_deviceManager.devices().size();
}
int DeviceManagerModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return DeviceColumns::COLUMN_COUNT;
}
QVariant DeviceManagerModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DisplayRole) {
const auto deviceInfo = m_deviceManager.devices()[index.row()]->deviceInfo();
const auto deviceSettings = m_deviceManager.devices()[index.row()]->deviceSettings();
bool isConnected = m_deviceManager.devices()[index.row()]->isConnected();
switch (index.column()) {
case DeviceColumns::Active:
return deviceSettings.active();
case DeviceColumns::Status:
return static_cast<DeviceStatus>(isConnected);
case DeviceColumns::Alias:
return deviceSettings.alias();
case DeviceColumns::IPv4Addr:
return deviceSettings.ipAddress();
case DeviceColumns::OS:
return deviceInfo.os();
case DeviceColumns::OSVersion:
return deviceInfo.osVersion();
case DeviceColumns::Architecture:
return deviceInfo.architecture();
case DeviceColumns::ScreenSize:
return QString("%1x%2").arg(deviceInfo.screenWidth()).arg(deviceInfo.screenHeight());
case DeviceColumns::AppVersion:
return deviceInfo.appVersion();
case DeviceColumns::DeviceId:
return deviceInfo.deviceId();
}
}
if (role == Qt::EditRole) {
const auto deviceSettings = m_deviceManager.devices()[index.row()]->deviceSettings();
switch (index.column()) {
case DeviceColumns::Alias:
return deviceSettings.alias();
case DeviceColumns::Active:
return deviceSettings.active();
}
}
return QVariant();
}
QVariant DeviceManagerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case DeviceColumns::Active:
return "Active";
case DeviceColumns::Status:
return "Status";
case DeviceColumns::Alias:
return "Alias";
case DeviceColumns::IPv4Addr:
return "IPv4 Address";
case DeviceColumns::OS:
return "OS";
case DeviceColumns::OSVersion:
return "OS Version";
case DeviceColumns::Architecture:
return "Architecture";
case DeviceColumns::ScreenSize:
return "Screen Size";
case DeviceColumns::AppVersion:
return "App Version";
case DeviceColumns::DeviceId:
return "Device ID";
case DeviceColumns::Remove:
return "Remove";
}
}
return QVariant();
}
bool DeviceManagerModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid() || role != Qt::EditRole) {
qCWarning(deviceSharePluginLog) << "Invalid index or role";
return false;
}
auto deviceInfo = m_deviceManager.devices()[index.row()]->deviceInfo();
switch (index.column()) {
case DeviceColumns::Alias:
m_deviceManager.setDeviceAlias(deviceInfo.deviceId(), value.toString());
break;
case DeviceColumns::Active:
m_deviceManager.setDeviceActive(deviceInfo.deviceId(), value.toBool());
break;
case DeviceColumns::IPv4Addr:
m_deviceManager.setDeviceIP(deviceInfo.deviceId(), value.toString());
break;
}
emit dataChanged(index, index, {role});
return true;
}
Qt::ItemFlags DeviceManagerModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
if (index.column() == DeviceColumns::Active || index.column() == DeviceColumns::Alias)
flags |= Qt::ItemIsEditable;
return flags;
}
} // namespace QmlDesigner::DeviceShare

View File

@@ -0,0 +1,47 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QAbstractTableModel>
namespace QmlDesigner::DeviceShare {
class DeviceManager;
class DeviceManagerModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DeviceManagerModel(DeviceManager &deviceManager, QObject *parent = nullptr);
enum DeviceStatus { Offline, Online };
Q_ENUM(DeviceStatus)
enum DeviceColumns {
Active,
Status,
Alias,
IPv4Addr,
OS,
OSVersion,
Architecture,
ScreenSize,
AppVersion,
DeviceId,
Remove,
COLUMN_COUNT
};
Q_ENUM(DeviceColumns)
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
DeviceManager &m_deviceManager;
};
} // namespace QmlDesigner::DeviceShare