ソースを参照

refactor: 移除统一数据点模块

main
suyu 1ヶ月前
コミット
a5d41f476a
25個のファイルの変更532行の追加895行の削除
  1. +0
    -4
      app/integrated_platform.pro
  2. +0
    -37
      app/src/domain/data_point_model.cpp
  3. +0
    -14
      app/src/domain/data_point_model.h
  4. +0
    -23
      app/src/domain/project_model.cpp
  5. +0
    -3
      app/src/domain/project_model.h
  6. +1
    -52
      app/src/infrastructure/json_project_storage.cpp
  7. +0
    -3
      app/src/main.cpp
  8. +0
    -170
      app/src/services/data_point_service.cpp
  9. +0
    -51
      app/src/services/data_point_service.h
  10. +0
    -4
      app/src/services/runtime_mode_service.cpp
  11. +0
    -182
      app/src/ui/main_window.cpp
  12. +0
    -8
      app/src/ui/main_window.h
  13. +511
    -176
      app/src/ui/main_window.ui
  14. +0
    -107
      app/tests/data_point_service_tests.cpp
  15. +0
    -29
      app/tests/data_point_service_tests.pro
  16. +0
    -2
      app/tests/domain_tests.pro
  17. +0
    -2
      app/tests/hmi_editor_service_tests.pro
  18. +0
    -2
      app/tests/logic_editor_service_tests.pro
  19. +6
    -5
      app/tests/main_window_tests.cpp
  20. +0
    -4
      app/tests/main_window_tests.pro
  21. +11
    -2
      app/tests/plc_runtime_tests.cpp
  22. +0
    -2
      app/tests/plc_runtime_tests.pro
  23. +3
    -9
      app/tests/project_management_tests.cpp
  24. +0
    -2
      app/tests/project_management_tests.pro
  25. +0
    -2
      app/tests/runtime_mode_service_tests.pro

+ 0
- 4
app/integrated_platform.pro ファイルの表示

@@ -21,13 +21,11 @@ SOURCES += \
src/domain/register_address.cpp \
src/domain/register_repository.cpp \
src/domain/active_register_repository.cpp \
src/domain/data_point_model.cpp \
src/domain/hmi_model.cpp \
src/domain/control_logic_model.cpp \
src/domain/project_model.cpp \
src/domain/runtime_state.cpp \
src/services/project_service.cpp \
src/services/data_point_service.cpp \
src/services/hmi_editor_service.cpp \
src/services/logic_editor_service.cpp \
src/services/hmi_runtime_service.cpp \
@@ -46,14 +44,12 @@ HEADERS += \
src/domain/register_address.h \
src/domain/register_repository.h \
src/domain/active_register_repository.h \
src/domain/data_point_model.h \
src/domain/hmi_model.h \
src/domain/control_logic_model.h \
src/domain/project_model.h \
src/domain/runtime_state.h \
src/domain/project_storage.h \
src/services/project_service.h \
src/services/data_point_service.h \
src/services/hmi_editor_service.h \
src/services/logic_editor_service.h \
src/services/hmi_runtime_service.h \


+ 0
- 37
app/src/domain/data_point_model.cpp ファイルの表示

@@ -1,37 +0,0 @@
#include "data_point_model.h"

#include <algorithm>
#include <cctype>

namespace {

bool isBlank(const std::string &value)
{
return value.empty()
|| std::all_of(
value.cbegin(), value.cend(),
[](unsigned char character) { return std::isspace(character) != 0; });
}

} // namespace

bool DataPoint::validate(std::string *error) const
{
if (!address.isValid())
{
if (error != nullptr)
{
*error = "data point requires a valid M or D address";
}
return false;
}
if (isBlank(name))
{
if (error != nullptr)
{
*error = "data point name must not be empty";
}
return false;
}
return true;
}

+ 0
- 14
app/src/domain/data_point_model.h ファイルの表示

@@ -1,14 +0,0 @@
#pragma once

#include "register_address.h"

#include <string>

struct DataPoint
{
RegisterAddress address;
std::string name;
std::string comment;

bool validate(std::string *error = nullptr) const;
};

+ 0
- 23
app/src/domain/project_model.cpp ファイルの表示

@@ -52,29 +52,6 @@ bool Project::validate(std::string *error) const
setError(error, "control logic ids must be unique within a project");
return false;
}
for (auto current = dataPoints.cbegin(); current != dataPoints.cend(); ++current)
{
if (!current->validate(error))
{
return false;
}
const auto duplicate = std::find_if(
current + 1,
dataPoints.cend(),
[&current](const DataPoint &candidate)
{
return candidate.address == current->address
|| candidate.name == current->name;
});
if (duplicate != dataPoints.cend())
{
if (error != nullptr)
{
*error = "data point addresses and names must be unique";
}
return false;
}
}
for (const HmiPage &page : hmiPages)
{
// 工程聚合校验会向下委托页面和控件的完整规则


+ 0
- 3
app/src/domain/project_model.h ファイルの表示

@@ -1,7 +1,6 @@
#pragma once

#include "control_logic_model.h"
#include "data_point_model.h"
#include "hmi_model.h"

#include <string>
@@ -27,8 +26,6 @@ struct Project
std::vector<HmiPage> hmiPages;
// 工程包含的控制逻辑集合
std::vector<ControlLogic> controlLogics;
// M/D 地址的工程级名称和注释定义
std::vector<DataPoint> dataPoints;

// 校验工程配置并通过 error 返回失败原因
bool validate(std::string *error = nullptr) const;


+ 1
- 52
app/src/infrastructure/json_project_storage.cpp ファイルの表示

@@ -253,28 +253,6 @@ bool parseAddress(
return true;
}

QJsonObject serializeDataPoint(const DataPoint &data_point)
{
QJsonObject object;
object.insert(QStringLiteral("address"), serializeAddress(data_point.address));
object.insert(QStringLiteral("name"), fromUtf8(data_point.name));
object.insert(QStringLiteral("comment"), fromUtf8(data_point.comment));
return object;
}

bool parseDataPoint(
const QJsonObject &object,
const std::string &context,
DataPoint *data_point,
ParseState *state)
{
QJsonObject address;
return readObject(object, "address", context, &address, state)
&& parseAddress(address, context + ".address", &data_point->address, state)
&& readString(object, "name", context, &data_point->name, state)
&& readString(object, "comment", context, &data_point->comment, state);
}

// 将 HMI 控件类型枚举转换为工程文件中的稳定字符串
QString hmiControlTypeName(HmiControlType type)
{
@@ -1088,19 +1066,12 @@ QJsonObject serializeProject(const Project &project)
logics.append(serializeControlLogic(logic));
}

QJsonArray data_points;
for (const DataPoint &data_point : project.dataPoints)
{
data_points.append(serializeDataPoint(data_point));
}

QJsonObject object;
object.insert(QStringLiteral("formatVersion"), fromUtf8(project.metadata.formatVersion));
object.insert(QStringLiteral("id"), fromUtf8(project.metadata.id));
object.insert(QStringLiteral("name"), fromUtf8(project.metadata.name));
object.insert(QStringLiteral("hmiPages"), pages);
object.insert(QStringLiteral("controlLogics"), logics);
object.insert(QStringLiteral("dataPoints"), data_points);
return object;
}

@@ -1110,7 +1081,6 @@ bool parseProject(
{
QJsonArray pages;
QJsonArray logics;
QJsonArray data_points;
if (!readString(
object,
"formatVersion",
@@ -1131,8 +1101,7 @@ bool parseProject(
if (!readString(object, "id", "project", &project->metadata.id, state)
|| !readString(object, "name", "project", &project->metadata.name, state)
|| !readArray(object, "hmiPages", "project", &pages, state)
|| !readArray(object, "controlLogics", "project", &logics, state)
|| !readArray(object, "dataPoints", "project", &data_points, state))
|| !readArray(object, "controlLogics", "project", &logics, state))
{
return false;
}
@@ -1179,26 +1148,6 @@ bool parseProject(
}
project->controlLogics.push_back(std::move(logic));
}
project->dataPoints.reserve(static_cast<std::size_t>(data_points.size()));
for (int index = 0; index < data_points.size(); ++index)
{
if (!data_points.at(index).isObject())
{
return state->fail(
ProjectStorageError::InvalidField,
"project.dataPoints items must be objects");
}
DataPoint data_point{RegisterAddress{RegisterArea::M, 0}, {}, {}};
if (!parseDataPoint(
data_points.at(index).toObject(),
"project.dataPoints[" + std::to_string(index) + ']',
&data_point,
state))
{
return false;
}
project->dataPoints.push_back(std::move(data_point));
}
return true;
}



+ 0
- 3
app/src/main.cpp ファイルの表示

@@ -13,7 +13,6 @@
#include "infrastructure/plc_register_repository.h"
#include "domain/active_register_repository.h"
#include "services/hmi_editor_service.h"
#include "services/data_point_service.h"
#include "services/hmi_runtime_service.h"
#include "services/logic_editor_service.h"
#include "services/offline_simulation_service.h"
@@ -31,7 +30,6 @@ int main(int argc, char *argv[])
JsonProjectStorage project_storage;
ProjectService project_service(project_storage);
HmiEditorService hmi_editor_service(project_service);
DataPointService data_point_service(project_service);
LogicEditorService logic_editor_service(project_service);
// 当前离线模式使用内存仓库,后续真机模式替换为 PLC 缓存实现
VirtualRegisterRepository virtual_register_repository;
@@ -51,7 +49,6 @@ int main(int argc, char *argv[])
runtime_mode_service,
project_service,
hmi_editor_service,
data_point_service,
logic_editor_service,
hmi_runtime_service);
main_window.show();


+ 0
- 170
app/src/services/data_point_service.cpp ファイルの表示

@@ -1,170 +0,0 @@
#include "data_point_service.h"

#include "project_service.h"

#include <algorithm>

namespace {

bool nodeUsesAddress(const LogicNode &node, const RegisterAddress &address)
{
return std::visit(
[&address](const auto &config) { return config.address == address; },
node.config);
}

} // namespace

DataPointService::DataPointService(ProjectService &project_service)
: project_service_(project_service)
{
}

const std::vector<DataPoint> &DataPointService::dataPoints() const
{
return project_service_.project().dataPoints;
}

const DataPoint *DataPointService::find(const RegisterAddress &address) const
{
const auto &points = dataPoints();
const auto found = std::find_if(
points.cbegin(), points.cend(),
[&address](const DataPoint &point) { return point.address == address; });
return found == points.cend() ? nullptr : &*found;
}

DataPointResult DataPointService::add(const DataPoint &data_point)
{
const DataPointResult validation = validateUnique(data_point, nullptr);
if (!validation.succeeded)
{
return validation;
}
project_service_.editProject().dataPoints.push_back(data_point);
return {true, DataPointError::None, {}};
}

DataPointResult DataPointService::update(
const RegisterAddress &old_address, const DataPoint &data_point)
{
const auto existing = std::find_if(
dataPoints().cbegin(), dataPoints().cend(),
[&old_address](const DataPoint &candidate)
{
return candidate.address == old_address;
});
if (existing == dataPoints().cend())
{
return {false, DataPointError::NotFound, "data point was not found"};
}
const DataPointResult validation = validateUnique(data_point, &old_address);
if (!validation.succeeded)
{
return validation;
}
if (data_point.address != old_address && !references(old_address).empty())
{
return {false, DataPointError::AddressInUse,
"referenced data point address cannot be changed"};
}
Project &project = project_service_.editProject();
const auto point = std::find_if(
project.dataPoints.begin(), project.dataPoints.end(),
[&old_address](const DataPoint &candidate)
{
return candidate.address == old_address;
});
*point = data_point;
return {true, DataPointError::None, {}};
}

DataPointResult DataPointService::remove(const RegisterAddress &address)
{
if (find(address) == nullptr)
{
return {false, DataPointError::NotFound, "data point was not found"};
}
if (!references(address).empty())
{
return {false, DataPointError::AddressInUse,
"data point is still referenced by HMI or ladder logic"};
}
Project &project = project_service_.editProject();
project.dataPoints.erase(
std::remove_if(
project.dataPoints.begin(), project.dataPoints.end(),
[&address](const DataPoint &point) { return point.address == address; }),
project.dataPoints.end());
return {true, DataPointError::None, {}};
}

std::vector<DataPointReference> DataPointService::references(
const RegisterAddress &address) const
{
std::vector<DataPointReference> result;
const Project &project = project_service_.project();
for (const HmiPage &page : project.hmiPages)
{
for (const HmiControl &control : page.controls)
{
if (control.binding.has_value() && *control.binding == address)
{
result.push_back({"HMI/" + page.name + '/' + control.id});
}
}
}
for (const ControlLogic &logic : project.controlLogics)
{
for (const LadderRung &rung : logic.rungs)
{
if (rung.condition.has_value())
{
std::vector<const LogicNode *> nodes;
collectConditionNodes(*rung.condition, &nodes);
for (const LogicNode *node : nodes)
{
if (nodeUsesAddress(*node, address))
{
result.push_back({"LAD/" + logic.name + '/' + rung.name + '/' + node->id});
}
}
}
if (rung.output.has_value() && nodeUsesAddress(*rung.output, address))
{
result.push_back({"LAD/" + logic.name + '/' + rung.name + '/'
+ rung.output->id});
}
}
}
return result;
}

DataPointResult DataPointService::validateUnique(
const DataPoint &data_point,
const RegisterAddress *ignored_address) const
{
std::string error;
if (!data_point.validate(&error))
{
return {false, DataPointError::InvalidDataPoint, error};
}
for (const DataPoint &existing : dataPoints())
{
if (ignored_address != nullptr && existing.address == *ignored_address)
{
continue;
}
if (existing.address == data_point.address)
{
return {false, DataPointError::DuplicateAddress,
"data point address already exists"};
}
if (existing.name == data_point.name)
{
return {false, DataPointError::DuplicateName,
"data point name already exists"};
}
}
return {true, DataPointError::None, {}};
}

+ 0
- 51
app/src/services/data_point_service.h ファイルの表示

@@ -1,51 +0,0 @@
#pragma once

#include "domain/data_point_model.h"

#include <string>
#include <vector>

class ProjectService;

enum class DataPointError
{
None,
InvalidDataPoint,
DuplicateAddress,
DuplicateName,
NotFound,
AddressInUse
};

struct DataPointResult
{
bool succeeded = false;
DataPointError error = DataPointError::None;
std::string message;
};

struct DataPointReference
{
std::string location;
};

class DataPointService
{
public:
explicit DataPointService(ProjectService &project_service);

const std::vector<DataPoint> &dataPoints() const;
const DataPoint *find(const RegisterAddress &address) const;
DataPointResult add(const DataPoint &data_point);
DataPointResult update(
const RegisterAddress &old_address, const DataPoint &data_point);
DataPointResult remove(const RegisterAddress &address);
std::vector<DataPointReference> references(const RegisterAddress &address) const;

private:
DataPointResult validateUnique(
const DataPoint &data_point,
const RegisterAddress *ignored_address) const;

ProjectService &project_service_;
};

+ 0
- 4
app/src/services/runtime_mode_service.cpp ファイルの表示

@@ -180,10 +180,6 @@ PlcCommunicationResult RuntimeModeService::connectPlc(
}
std::vector<RegisterAddress> addresses;
const Project &project = project_service_.project();
for (const DataPoint &point : project.dataPoints)
{
addresses.push_back(point.address);
}
for (const HmiPage &page : project.hmiPages)
{
for (const HmiControl &control : page.controls)


+ 0
- 182
app/src/ui/main_window.cpp ファイルの表示

@@ -4,7 +4,6 @@
#include "logic_editor_widget.h"
#include "plc_connection_dialog.h"
#include "services/hmi_editor_service.h"
#include "services/data_point_service.h"
#include "services/hmi_runtime_service.h"
#include "services/logic_editor_service.h"
#include "services/project_service.h"
@@ -15,7 +14,6 @@
#include <QApplication>
#include <QComboBox>
#include <QFileDialog>
#include <QHeaderView>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
@@ -27,7 +25,6 @@
#include <QStyle>
#include <QTimer>
#include <QTabWidget>
#include <QTableWidget>
#include <QToolButton>
#include <QTreeWidget>
#include <QTreeWidgetItem>
@@ -137,7 +134,6 @@ MainWindow::MainWindow(
RuntimeModeService &runtime_mode_service,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
DataPointService &data_point_service,
LogicEditorService &logic_editor_service,
HmiRuntimeService &hmi_runtime_service,
QWidget *parent)
@@ -146,7 +142,6 @@ MainWindow::MainWindow(
runtime_mode_service_(runtime_mode_service),
project_service_(project_service),
hmi_editor_service_(hmi_editor_service),
data_point_service_(data_point_service),
logic_editor_service_(logic_editor_service),
hmi_runtime_service_(hmi_runtime_service)
{
@@ -154,7 +149,6 @@ MainWindow::MainWindow(
configureAppearance();
configureActions();
configurePropertyEditor();
configureDataPointEditor();
configurePlcConnection();
configureHmiEditor();
configureLogicEditor();
@@ -433,7 +427,6 @@ void MainWindow::configureHmiEditor()
if (runtime_mode_service_.mode() != ApplicationMode::Editing)
{
hmi_editor_widget_->refreshRuntimeValues();
refreshDataPointUi();
updateSimulationUi(false);
}
});
@@ -492,62 +485,9 @@ void MainWindow::configurePropertyEditor()
this, &MainWindow::applySelectedControlProperties);
connect(ui_->applyLogicPropertiesButton, &QPushButton::clicked,
this, &MainWindow::applySelectedLogicNodeProperties);
connect(ui_->hmiDataPointComboBox, qOverload<int>(&QComboBox::currentIndexChanged),
this,
[this](int index)
{
if (index <= 0)
{
return;
}
const QString address = ui_->hmiDataPointComboBox->currentData().toString();
ui_->bindingAreaComboBox->setCurrentIndex(address.startsWith('M') ? 1 : 2);
ui_->bindingIndexSpinBox->setValue(address.mid(1).toInt());
});
connect(ui_->logicDataPointComboBox, qOverload<int>(&QComboBox::currentIndexChanged),
this,
[this](int index)
{
if (index <= 0)
{
return;
}
ui_->logicAddressSpinBox->setValue(
ui_->logicDataPointComboBox->currentData().toString().mid(1).toInt());
});
showControlProperties({});
}

void MainWindow::configureDataPointEditor()
{
ui_->dataPointTable->horizontalHeader()->setSectionResizeMode(
QHeaderView::ResizeToContents);
ui_->dataPointTable->horizontalHeader()->setStretchLastSection(true);
tabifyDockWidget(ui_->outputDock, ui_->dataPointDock);
ui_->viewMenu->addAction(ui_->dataPointDock->toggleViewAction());

connect(ui_->addDataPointButton, &QPushButton::clicked, this, &MainWindow::addDataPoint);
connect(ui_->updateDataPointButton, &QPushButton::clicked,
this, &MainWindow::updateSelectedDataPoint);
connect(ui_->deleteDataPointButton, &QPushButton::clicked,
this, &MainWindow::deleteSelectedDataPoint);
connect(ui_->dataPointTable, &QTableWidget::itemSelectionChanged,
this,
[this]
{
const int row = ui_->dataPointTable->currentRow();
if (row < 0)
{
return;
}
const QString address = ui_->dataPointTable->item(row, 0)->text();
ui_->dataPointAreaComboBox->setCurrentIndex(address.startsWith('M') ? 0 : 1);
ui_->dataPointAddressSpinBox->setValue(address.mid(1).toInt());
ui_->dataPointNameEdit->setText(ui_->dataPointTable->item(row, 2)->text());
ui_->dataPointCommentEdit->setText(ui_->dataPointTable->item(row, 3)->text());
});
}

void MainWindow::configurePlcConnection()
{
connect(ui_->configurePlcAction, &QAction::triggered, this, &MainWindow::connectPlc);
@@ -590,127 +530,6 @@ void MainWindow::refreshProjectUi()
logic_root, {fromUtf8(logic.name) + suffix}));
}
ui_->projectTree->expandAll();
refreshDataPointUi();
}

void MainWindow::refreshDataPointUi()
{
const auto &points = data_point_service_.dataPoints();
ui_->dataPointTable->setRowCount(static_cast<int>(points.size()));
ui_->hmiDataPointComboBox->blockSignals(true);
ui_->logicDataPointComboBox->blockSignals(true);
ui_->hmiDataPointComboBox->clear();
ui_->logicDataPointComboBox->clear();
ui_->hmiDataPointComboBox->addItem(tr("手动输入"));
ui_->logicDataPointComboBox->addItem(tr("手动输入"));
for (int row = 0; row < static_cast<int>(points.size()); ++row)
{
const DataPoint &point = points.at(static_cast<std::size_t>(row));
const QString address = fromUtf8(point.address.toString());
const QString display = tr("%1 - %2").arg(address, fromUtf8(point.name));
ui_->hmiDataPointComboBox->addItem(display, address);
ui_->logicDataPointComboBox->addItem(display, address);
QString value = QStringLiteral("-");
if (runtime_mode_service_.mode() != ApplicationMode::Editing)
{
if (point.address.area() == RegisterArea::M)
{
const BitReadResult read = hmi_runtime_service_.readBit(point.address);
value = read.succeeded ? (read.value ? QStringLiteral("ON") : QStringLiteral("OFF"))
: tr("不可用");
}
else
{
const WordReadResult read = hmi_runtime_service_.readWord(point.address);
value = read.succeeded ? QString::number(read.value) : tr("不可用");
}
}
QStringList references;
for (const DataPointReference &reference : data_point_service_.references(point.address))
{
references.push_back(fromUtf8(reference.location));
}
const QStringList columns{
address,
point.address.area() == RegisterArea::M ? tr("位") : tr("16 位字"),
fromUtf8(point.name),
fromUtf8(point.comment),
value,
references.join(QStringLiteral("; "))};
for (int column = 0; column < columns.size(); ++column)
{
ui_->dataPointTable->setItem(
row, column, new QTableWidgetItem(columns.at(column)));
}
}
ui_->hmiDataPointComboBox->blockSignals(false);
ui_->logicDataPointComboBox->blockSignals(false);
}

void MainWindow::addDataPoint()
{
const RegisterArea area = ui_->dataPointAreaComboBox->currentIndex() == 0
? RegisterArea::M : RegisterArea::D;
const DataPointResult result = data_point_service_.add({
RegisterAddress{area, ui_->dataPointAddressSpinBox->value()},
toUtf8(ui_->dataPointNameEdit->text()),
toUtf8(ui_->dataPointCommentEdit->text())});
if (!result.succeeded)
{
showProjectResult(tr("新增数据点"), fromUtf8(result.message), false);
return;
}
refreshProjectUi();
statusBar()->showMessage(tr("数据点已新增"), 3000);
}

void MainWindow::updateSelectedDataPoint()
{
const int row = ui_->dataPointTable->currentRow();
if (row < 0)
{
statusBar()->showMessage(tr("请先选择要更新的数据点"), 3000);
return;
}
const QString old_text = ui_->dataPointTable->item(row, 0)->text();
const RegisterAddress old_address{
old_text.startsWith('M') ? RegisterArea::M : RegisterArea::D,
old_text.mid(1).toInt()};
const RegisterArea area = ui_->dataPointAreaComboBox->currentIndex() == 0
? RegisterArea::M : RegisterArea::D;
const DataPointResult result = data_point_service_.update(
old_address,
{RegisterAddress{area, ui_->dataPointAddressSpinBox->value()},
toUtf8(ui_->dataPointNameEdit->text()),
toUtf8(ui_->dataPointCommentEdit->text())});
if (!result.succeeded)
{
showProjectResult(tr("更新数据点"), fromUtf8(result.message), false);
return;
}
refreshProjectUi();
statusBar()->showMessage(tr("数据点已更新"), 3000);
}

void MainWindow::deleteSelectedDataPoint()
{
const int row = ui_->dataPointTable->currentRow();
if (row < 0)
{
statusBar()->showMessage(tr("请先选择要删除的数据点"), 3000);
return;
}
const QString address_text = ui_->dataPointTable->item(row, 0)->text();
const DataPointResult result = data_point_service_.remove({
address_text.startsWith('M') ? RegisterArea::M : RegisterArea::D,
address_text.mid(1).toInt()});
if (!result.succeeded)
{
showProjectResult(tr("删除数据点"), fromUtf8(result.message), false);
return;
}
refreshProjectUi();
statusBar()->showMessage(tr("数据点已删除"), 3000);
}

void MainWindow::connectPlc()
@@ -1224,7 +1043,6 @@ void MainWindow::updateModeUi(const QString &message)
}
ui_->hmiToolBar->setEnabled(policy.allowsProjectEditing);
ui_->logicToolBar->setEnabled(policy.allowsProjectEditing);
ui_->dataPointDock->setEnabled(policy.allowsProjectEditing);
ui_->addButtonAction->setEnabled(policy.allowsProjectEditing);
ui_->addIndicatorAction->setEnabled(policy.allowsProjectEditing);
ui_->addNumericDisplayAction->setEnabled(policy.allowsProjectEditing);


+ 0
- 8
app/src/ui/main_window.h ファイルの表示

@@ -31,7 +31,6 @@ class RuntimeModeService;
class ProjectService;
class HmiEditorService;
class HmiRuntimeService;
class DataPointService;
class HmiEditorWidget;
class LogicEditorService;
class LogicEditorWidget;
@@ -58,7 +57,6 @@ public:
RuntimeModeService &runtime_mode_service,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
DataPointService &data_point_service,
LogicEditorService &logic_editor_service,
HmiRuntimeService &hmi_runtime_service,
QWidget *parent = nullptr);
@@ -76,7 +74,6 @@ private:
void configureLogicEditor();
// 创建并连接控件属性编辑表单
void configurePropertyEditor();
void configureDataPointEditor();
void configurePlcConnection();
// 刷新工程树和当前 HMI 页面信息
void refreshProjectUi();
@@ -102,10 +99,6 @@ private:
void deleteSelectedLogicObject();
// 将逻辑属性表单内容应用到当前选中节点
void applySelectedLogicNodeProperties();
void refreshDataPointUi();
void addDataPoint();
void updateSelectedDataPoint();
void deleteSelectedDataPoint();
void connectPlc();
void disconnectPlc();
// 创建新的工程并刷新编辑界面
@@ -150,7 +143,6 @@ private:
*/
ProjectService &project_service_;
HmiEditorService &hmi_editor_service_;
DataPointService &data_point_service_;
LogicEditorService &logic_editor_service_;
HmiRuntimeService &hmi_runtime_service_;
QActionGroup *mode_action_group_ = nullptr;


+ 511
- 176
app/src/ui/main_window.ui ファイルの表示

@@ -41,6 +41,9 @@
</property>
<item>
<widget class="QTabWidget" name="editorTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<property name="documentMode">
<bool>true</bool>
</property>
@@ -214,19 +217,19 @@
<x>0</x>
<y>0</y>
<width>1280</width>
<height>23</height>
<height>17</height>
</rect>
</property>
<widget class="QMenu" name="fileMenu">
<property name="title">
<string>文件(&amp;F)</string>
</property>
<addaction name="newProjectAction"/>
<addaction name="saveProjectAction"/>
<addaction name="saveAsProjectAction"/>
<addaction name="loadProjectAction"/>
<addaction name="separator"/>
<addaction name="exitAction"/>
<addaction name="newProjectAction"/>
<addaction name="saveProjectAction"/>
<addaction name="saveAsProjectAction"/>
<addaction name="loadProjectAction"/>
<addaction name="separator"/>
<addaction name="exitAction"/>
</widget>
<widget class="QMenu" name="runMenu">
<property name="title">
@@ -234,10 +237,10 @@
</property>
<addaction name="editingModeAction"/>
<addaction name="offlineModeAction"/>
<addaction name="onlineModeAction"/>
<addaction name="separator"/>
<addaction name="configurePlcAction"/>
<addaction name="disconnectPlcAction"/>
<addaction name="onlineModeAction"/>
<addaction name="separator"/>
<addaction name="configurePlcAction"/>
<addaction name="disconnectPlcAction"/>
</widget>
<widget class="QMenu" name="viewMenu">
<property name="title">
@@ -268,36 +271,52 @@
<addaction name="editingModeAction"/>
<addaction name="offlineModeAction"/>
<addaction name="onlineModeAction"/>
<addaction name="configurePlcAction"/>
<addaction name="disconnectPlcAction"/>
</widget>
<widget class="QToolBar" name="hmiToolBar">
<property name="windowTitle"><string>HMI 控件</string></property>
<property name="toolButtonStyle"><enum>Qt::ToolButtonTextBesideIcon</enum></property>
<attribute name="toolBarArea"><enum>TopToolBarArea</enum></attribute>
<attribute name="toolBarBreak"><bool>false</bool></attribute>
<addaction name="addButtonAction"/>
<addaction name="addIndicatorAction"/>
<addaction name="addNumericDisplayAction"/>
<addaction name="addNumericInputAction"/>
<addaction name="deleteControlAction"/>
</widget>
<widget class="QToolBar" name="logicToolBar">
<property name="windowTitle"><string>控制逻辑节点</string></property>
<property name="toolButtonStyle"><enum>Qt::ToolButtonTextBesideIcon</enum></property>
<attribute name="toolBarArea"><enum>TopToolBarArea</enum></attribute>
<attribute name="toolBarBreak"><bool>false</bool></attribute>
<addaction name="addRungAction"/>
<addaction name="parallelInsertAction"/>
<addaction name="separator"/>
<addaction name="addNormallyOpenAction"/>
<addaction name="addNormallyClosedAction"/>
<addaction name="addNormalCoilAction"/>
<addaction name="addSetCoilAction"/>
<addaction name="addResetCoilAction"/>
<addaction name="addCompareAction"/>
<addaction name="deleteLogicAction"/>
</widget>
<addaction name="configurePlcAction"/>
<addaction name="disconnectPlcAction"/>
</widget>
<widget class="QToolBar" name="hmiToolBar">
<property name="windowTitle">
<string>HMI 控件</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="addButtonAction"/>
<addaction name="addIndicatorAction"/>
<addaction name="addNumericDisplayAction"/>
<addaction name="addNumericInputAction"/>
<addaction name="deleteControlAction"/>
</widget>
<widget class="QToolBar" name="logicToolBar">
<property name="windowTitle">
<string>控制逻辑节点</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="addRungAction"/>
<addaction name="parallelInsertAction"/>
<addaction name="separator"/>
<addaction name="addNormallyOpenAction"/>
<addaction name="addNormallyClosedAction"/>
<addaction name="addNormalCoilAction"/>
<addaction name="addSetCoilAction"/>
<addaction name="addResetCoilAction"/>
<addaction name="addCompareAction"/>
<addaction name="deleteLogicAction"/>
</widget>
<widget class="QDockWidget" name="projectDock">
<property name="minimumSize">
<size>
@@ -327,10 +346,10 @@
</property>
<item>
<widget class="QTreeWidget" name="projectTree">
<property name="headerHidden">
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<property name="headerHidden">
<bool>true</bool>
</property>
<column>
@@ -393,86 +412,315 @@
<rect>
<x>0</x>
<y>0</y>
<width>232</width>
<height>164</height>
<width>234</width>
<height>455</height>
</rect>
</property>
<layout class="QVBoxLayout" name="propertiesPageLayout">
<item>
<layout class="QFormLayout" name="selectionForm">
<item row="0" column="0"><widget class="QLabel" name="selectionCaptionLabel"><property name="text"><string>当前对象</string></property></widget></item>
<item row="0" column="1"><widget class="QLabel" name="selectionValueLabel"><property name="text"><string>未选择</string></property></widget></item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="propertyStack">
<widget class="QWidget" name="hmiPropertiesPage">
<layout class="QFormLayout" name="hmiPropertiesForm">
<property name="fieldGrowthPolicy"><enum>QFormLayout::AllNonFixedFieldsGrow</enum></property>
<item row="0" column="0"><widget class="QLabel" name="controlIdLabel"><property name="text"><string>控件 ID</string></property></widget></item>
<item row="0" column="1"><widget class="QLineEdit" name="controlIdEdit"/></item>
<item row="1" column="0"><widget class="QLabel" name="controlTextLabel"><property name="text"><string>显示文本</string></property></widget></item>
<item row="1" column="1"><widget class="QLineEdit" name="controlTextEdit"/></item>
<item row="2" column="0"><widget class="QLabel" name="controlXLabel"><property name="text"><string>X</string></property></widget></item>
<item row="2" column="1"><widget class="QSpinBox" name="controlXSpinBox"><property name="maximum"><number>4000</number></property></widget></item>
<item row="3" column="0"><widget class="QLabel" name="controlYLabel"><property name="text"><string>Y</string></property></widget></item>
<item row="3" column="1"><widget class="QSpinBox" name="controlYSpinBox"><property name="maximum"><number>4000</number></property></widget></item>
<item row="4" column="0"><widget class="QLabel" name="controlWidthLabel"><property name="text"><string>宽度</string></property></widget></item>
<item row="4" column="1"><widget class="QSpinBox" name="controlWidthSpinBox"><property name="minimum"><number>1</number></property><property name="maximum"><number>4000</number></property></widget></item>
<item row="5" column="0"><widget class="QLabel" name="controlHeightLabel"><property name="text"><string>高度</string></property></widget></item>
<item row="5" column="1"><widget class="QSpinBox" name="controlHeightSpinBox"><property name="minimum"><number>1</number></property><property name="maximum"><number>4000</number></property></widget></item>
<item row="6" column="0"><widget class="QLabel" name="bindingAreaLabel"><property name="text"><string>绑定区域</string></property></widget></item>
<item row="6" column="1"><widget class="QComboBox" name="bindingAreaComboBox">
<item><property name="text"><string>未绑定</string></property><property name="userData"><string notr="true">-1</string></property></item>
<item><property name="text"><string>M</string></property><property name="userData"><string notr="true">0</string></property></item>
<item><property name="text"><string>D</string></property><property name="userData"><string notr="true">1</string></property></item>
</widget></item>
<item row="7" column="0"><widget class="QLabel" name="bindingIndexLabel"><property name="text"><string>绑定地址</string></property></widget></item>
<item row="7" column="1"><widget class="QSpinBox" name="bindingIndexSpinBox"><property name="maximum"><number>4000</number></property></widget></item>
<item row="8" column="0"><widget class="QLabel" name="hmiDataPointLabel"><property name="text"><string>选择数据点</string></property></widget></item>
<item row="8" column="1"><widget class="QComboBox" name="hmiDataPointComboBox"/></item>
<item row="9" column="0" colspan="2"><widget class="QPushButton" name="applyPropertiesButton"><property name="text"><string>应用属性</string></property></widget></item>
</layout>
<layout class="QVBoxLayout" name="propertiesPageLayout">
<item>
<layout class="QFormLayout" name="selectionForm">
<item row="0" column="0">
<widget class="QLabel" name="selectionCaptionLabel">
<property name="text">
<string>当前对象</string>
</property>
</widget>
<widget class="QWidget" name="logicPropertiesPage">
<layout class="QFormLayout" name="logicPropertiesForm">
<property name="fieldGrowthPolicy"><enum>QFormLayout::AllNonFixedFieldsGrow</enum></property>
<item row="0" column="0"><widget class="QLabel" name="logicNodeIdLabel"><property name="text"><string>节点 ID</string></property></widget></item>
<item row="0" column="1"><widget class="QLineEdit" name="logicNodeIdEdit"><property name="readOnly"><bool>true</bool></property></widget></item>
<item row="1" column="0"><widget class="QLabel" name="logicNodeTypeCaption"><property name="text"><string>节点类型</string></property></widget></item>
<item row="1" column="1"><widget class="QLabel" name="logicNodeTypeLabel"><property name="text"><string/></property></widget></item>
<item row="2" column="0"><widget class="QLabel" name="logicAddressAreaCaption"><property name="text"><string>地址区域</string></property></widget></item>
<item row="2" column="1"><widget class="QLabel" name="logicAddressAreaLabel"><property name="text"><string/></property></widget></item>
<item row="3" column="0"><widget class="QLabel" name="logicAddressLabel"><property name="text"><string>地址</string></property></widget></item>
<item row="3" column="1"><widget class="QSpinBox" name="logicAddressSpinBox"><property name="maximum"><number>4000</number></property></widget></item>
<item row="4" column="0"><widget class="QLabel" name="logicDataPointLabel"><property name="text"><string>选择数据点</string></property></widget></item>
<item row="4" column="1"><widget class="QComboBox" name="logicDataPointComboBox"/></item>
<item row="5" column="0"><widget class="QLabel" name="logicModeLabel"><property name="text"><string>工作方式</string></property></widget></item>
<item row="5" column="1"><widget class="QComboBox" name="logicModeComboBox"/></item>
<item row="6" column="0"><widget class="QLabel" name="logicComparisonLabel"><property name="text"><string>比较运算</string></property></widget></item>
<item row="6" column="1"><widget class="QComboBox" name="logicComparisonComboBox">
<item><property name="text"><string>等于</string></property><property name="userData"><string notr="true">0</string></property></item>
<item><property name="text"><string>不等于</string></property><property name="userData"><string notr="true">1</string></property></item>
<item><property name="text"><string>小于</string></property><property name="userData"><string notr="true">2</string></property></item>
<item><property name="text"><string>小于等于</string></property><property name="userData"><string notr="true">3</string></property></item>
<item><property name="text"><string>大于</string></property><property name="userData"><string notr="true">4</string></property></item>
<item><property name="text"><string>大于等于</string></property><property name="userData"><string notr="true">5</string></property></item>
</widget></item>
<item row="7" column="0"><widget class="QLabel" name="logicValueLabel"><property name="text"><string>比较常量</string></property></widget></item>
<item row="7" column="1"><widget class="QSpinBox" name="logicValueSpinBox"><property name="minimum"><number>-32768</number></property><property name="maximum"><number>32767</number></property></widget></item>
<item row="8" column="0" colspan="2"><widget class="QPushButton" name="applyLogicPropertiesButton"><property name="text"><string>应用属性</string></property></widget></item>
</layout>
</item>
<item row="0" column="1">
<widget class="QLabel" name="selectionValueLabel">
<property name="text">
<string>未选择</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="propertyStack">
<widget class="QWidget" name="hmiPropertiesPage">
<layout class="QFormLayout" name="hmiPropertiesForm">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="controlIdLabel">
<property name="text">
<string>控件 ID</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="controlIdEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="controlTextLabel">
<property name="text">
<string>显示文本</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="controlTextEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="controlXLabel">
<property name="text">
<string>X</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="controlXSpinBox">
<property name="maximum">
<number>4000</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="controlYLabel">
<property name="text">
<string>Y</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="controlYSpinBox">
<property name="maximum">
<number>4000</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="controlWidthLabel">
<property name="text">
<string>宽度</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="controlWidthSpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>4000</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="controlHeightLabel">
<property name="text">
<string>高度</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="controlHeightSpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>4000</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="bindingAreaLabel">
<property name="text">
<string>绑定区域</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="bindingAreaComboBox">
<item>
<property name="text">
<string>未绑定</string>
</property>
</item>
<item>
<property name="text">
<string>M</string>
</property>
</item>
<item>
<property name="text">
<string>D</string>
</property>
</item>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="bindingIndexLabel">
<property name="text">
<string>绑定地址</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="bindingIndexSpinBox">
<property name="maximum">
<number>4000</number>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QPushButton" name="applyPropertiesButton">
<property name="text">
<string>应用属性</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="logicPropertiesPage">
<layout class="QFormLayout" name="logicPropertiesForm">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="logicNodeIdLabel">
<property name="text">
<string>节点 ID</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="logicNodeIdEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="logicNodeTypeCaption">
<property name="text">
<string>节点类型</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="logicNodeTypeLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="logicAddressAreaCaption">
<property name="text">
<string>地址区域</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="logicAddressAreaLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="logicAddressLabel">
<property name="text">
<string>地址</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="logicAddressSpinBox">
<property name="maximum">
<number>4000</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="logicModeLabel">
<property name="text">
<string>工作方式</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="logicModeComboBox"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="logicComparisonLabel">
<property name="text">
<string>比较运算</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="logicComparisonComboBox">
<item>
<property name="text">
<string>等于</string>
</property>
</item>
<item>
<property name="text">
<string>不等于</string>
</property>
</item>
<item>
<property name="text">
<string>小于</string>
</property>
</item>
<item>
<property name="text">
<string>小于等于</string>
</property>
</item>
<item>
<property name="text">
<string>大于</string>
</property>
</item>
<item>
<property name="text">
<string>大于等于</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="logicValueLabel">
<property name="text">
<string>比较常量</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="logicValueSpinBox">
<property name="minimum">
<number>-32768</number>
</property>
<property name="maximum">
<number>32767</number>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QPushButton" name="applyLogicPropertiesButton">
<property name="text">
<string>应用属性</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="outputDock">
<widget class="QDockWidget" name="outputDock">
<property name="minimumSize">
<size>
<width>300</width>
@@ -509,74 +757,161 @@
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dataPointDock">
<property name="minimumSize"><size><width>420</width><height>170</height></size></property>
<property name="windowTitle"><string>数据点</string></property>
<attribute name="dockWidgetArea"><number>8</number></attribute>
<widget class="QWidget" name="dataPointDockContents">
<layout class="QVBoxLayout" name="dataPointDockLayout">
<property name="leftMargin"><number>6</number></property>
<property name="topMargin"><number>6</number></property>
<property name="rightMargin"><number>6</number></property>
<property name="bottomMargin"><number>6</number></property>
<item>
<widget class="QTableWidget" name="dataPointTable">
<property name="selectionMode"><enum>QAbstractItemView::SingleSelection</enum></property>
<property name="selectionBehavior"><enum>QAbstractItemView::SelectRows</enum></property>
<property name="editTriggers"><set>QAbstractItemView::NoEditTriggers</set></property>
<column><property name="text"><string>地址</string></property></column>
<column><property name="text"><string>类型</string></property></column>
<column><property name="text"><string>名称</string></property></column>
<column><property name="text"><string>注释</string></property></column>
<column><property name="text"><string>当前值</string></property></column>
<column><property name="text"><string>引用位置</string></property></column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="dataPointEditorLayout">
<item><widget class="QLabel" name="dataPointAreaLabel"><property name="text"><string>区域</string></property></widget></item>
<item><widget class="QComboBox" name="dataPointAreaComboBox"><item><property name="text"><string>M</string></property><property name="userData"><string notr="true">0</string></property></item><item><property name="text"><string>D</string></property><property name="userData"><string notr="true">1</string></property></item></widget></item>
<item><widget class="QLabel" name="dataPointAddressLabel"><property name="text"><string>地址</string></property></widget></item>
<item><widget class="QSpinBox" name="dataPointAddressSpinBox"><property name="maximum"><number>4000</number></property></widget></item>
<item><widget class="QLabel" name="dataPointNameLabel"><property name="text"><string>名称</string></property></widget></item>
<item><widget class="QLineEdit" name="dataPointNameEdit"/></item>
<item><widget class="QLabel" name="dataPointCommentLabel"><property name="text"><string>注释</string></property></widget></item>
<item><widget class="QLineEdit" name="dataPointCommentEdit"/></item>
<item><widget class="QPushButton" name="addDataPointButton"><property name="text"><string>新增</string></property></widget></item>
<item><widget class="QPushButton" name="updateDataPointButton"><property name="text"><string>更新</string></property></widget></item>
<item><widget class="QPushButton" name="deleteDataPointButton"><property name="text"><string>删除</string></property></widget></item>
</layout>
</item>
</layout>
</widget>
</widget>
<action name="exitAction">
</widget>
<action name="exitAction">
<property name="text">
<string>退出(&amp;X)</string>
</property>
</action>
<action name="newProjectAction"><property name="text"><string>新建工程</string></property></action>
<action name="saveProjectAction"><property name="text"><string>保存工程</string></property></action>
<action name="saveAsProjectAction"><property name="text"><string>工程另存为</string></property></action>
<action name="loadProjectAction"><property name="text"><string>加载工程</string></property></action>
<action name="configurePlcAction"><property name="text"><string>PLC 配置</string></property><property name="toolTip"><string>配置参数并连接真实 PLC</string></property></action>
<action name="disconnectPlcAction"><property name="text"><string>断开 PLC</string></property><property name="toolTip"><string>断开当前 PLC 连接</string></property></action>
<action name="addButtonAction"><property name="text"><string>按钮</string></property><property name="toolTip"><string>添加按钮控件</string></property></action>
<action name="addIndicatorAction"><property name="text"><string>指示灯</string></property><property name="toolTip"><string>添加指示灯控件</string></property></action>
<action name="addNumericDisplayAction"><property name="text"><string>数值显示</string></property><property name="toolTip"><string>添加数值显示控件</string></property></action>
<action name="addNumericInputAction"><property name="text"><string>数值输入</string></property><property name="toolTip"><string>添加数值输入控件</string></property></action>
<action name="deleteControlAction"><property name="text"><string>删除</string></property><property name="toolTip"><string>删除当前控件</string></property></action>
<action name="addRungAction"><property name="text"><string>新建网络</string></property><property name="toolTip"><string>在梯形图末尾新增网络</string></property></action>
<action name="parallelInsertAction"><property name="text"><string>并联支路</string></property><property name="toolTip"><string>为画布中选中的连续节点建立并联支路</string></property></action>
<action name="addNormallyOpenAction"><property name="text"><string>常开</string></property><property name="toolTip"><string>向当前网络添加常开触点</string></property></action>
<action name="addNormallyClosedAction"><property name="text"><string>常闭</string></property><property name="toolTip"><string>向当前网络添加常闭触点</string></property></action>
<action name="addNormalCoilAction"><property name="text"><string>线圈</string></property><property name="toolTip"><string>设置当前网络的普通线圈</string></property></action>
<action name="addSetCoilAction"><property name="text"><string>置位</string></property><property name="toolTip"><string>设置当前网络的置位线圈</string></property></action>
<action name="addResetCoilAction"><property name="text"><string>复位</string></property><property name="toolTip"><string>设置当前网络的复位线圈</string></property></action>
<action name="addCompareAction"><property name="text"><string>D 比较</string></property><property name="toolTip"><string>向当前网络添加 D 值与常量比较</string></property></action>
<action name="deleteLogicAction"><property name="text"><string>删除</string></property><property name="toolTip"><string>删除选中的逻辑节点或网络</string></property></action>
</action>
<action name="newProjectAction">
<property name="text">
<string>新建工程</string>
</property>
</action>
<action name="saveProjectAction">
<property name="text">
<string>保存工程</string>
</property>
</action>
<action name="saveAsProjectAction">
<property name="text">
<string>工程另存为</string>
</property>
</action>
<action name="loadProjectAction">
<property name="text">
<string>加载工程</string>
</property>
</action>
<action name="configurePlcAction">
<property name="text">
<string>PLC 配置</string>
</property>
<property name="toolTip">
<string>配置参数并连接真实 PLC</string>
</property>
</action>
<action name="disconnectPlcAction">
<property name="text">
<string>断开 PLC</string>
</property>
<property name="toolTip">
<string>断开当前 PLC 连接</string>
</property>
</action>
<action name="addButtonAction">
<property name="text">
<string>按钮</string>
</property>
<property name="toolTip">
<string>添加按钮控件</string>
</property>
</action>
<action name="addIndicatorAction">
<property name="text">
<string>指示灯</string>
</property>
<property name="toolTip">
<string>添加指示灯控件</string>
</property>
</action>
<action name="addNumericDisplayAction">
<property name="text">
<string>数值显示</string>
</property>
<property name="toolTip">
<string>添加数值显示控件</string>
</property>
</action>
<action name="addNumericInputAction">
<property name="text">
<string>数值输入</string>
</property>
<property name="toolTip">
<string>添加数值输入控件</string>
</property>
</action>
<action name="deleteControlAction">
<property name="text">
<string>删除</string>
</property>
<property name="toolTip">
<string>删除当前控件</string>
</property>
</action>
<action name="addRungAction">
<property name="text">
<string>新建网络</string>
</property>
<property name="toolTip">
<string>在梯形图末尾新增网络</string>
</property>
</action>
<action name="parallelInsertAction">
<property name="text">
<string>并联支路</string>
</property>
<property name="toolTip">
<string>为画布中选中的连续节点建立并联支路</string>
</property>
</action>
<action name="addNormallyOpenAction">
<property name="text">
<string>常开</string>
</property>
<property name="toolTip">
<string>向当前网络添加常开触点</string>
</property>
</action>
<action name="addNormallyClosedAction">
<property name="text">
<string>常闭</string>
</property>
<property name="toolTip">
<string>向当前网络添加常闭触点</string>
</property>
</action>
<action name="addNormalCoilAction">
<property name="text">
<string>线圈</string>
</property>
<property name="toolTip">
<string>设置当前网络的普通线圈</string>
</property>
</action>
<action name="addSetCoilAction">
<property name="text">
<string>置位</string>
</property>
<property name="toolTip">
<string>设置当前网络的置位线圈</string>
</property>
</action>
<action name="addResetCoilAction">
<property name="text">
<string>复位</string>
</property>
<property name="toolTip">
<string>设置当前网络的复位线圈</string>
</property>
</action>
<action name="addCompareAction">
<property name="text">
<string>D 比较</string>
</property>
<property name="toolTip">
<string>向当前网络添加 D 值与常量比较</string>
</property>
</action>
<action name="deleteLogicAction">
<property name="text">
<string>删除</string>
</property>
<property name="toolTip">
<string>删除选中的逻辑节点或网络</string>
</property>
</action>
<action name="editingModeAction">
<property name="checkable">
<bool>true</bool>


+ 0
- 107
app/tests/data_point_service_tests.cpp ファイルの表示

@@ -1,107 +0,0 @@
#include "domain/project_storage.h"
#include "services/data_point_service.h"
#include "services/project_service.h"

#include <iostream>
#include <stdexcept>

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}

void testUniqueDataPointsAndReferences()
{
TestProjectStorage storage;
ProjectService project_service(storage);
DataPointService service(project_service);
const RegisterAddress m0{RegisterArea::M, 0};
const RegisterAddress m1{RegisterArea::M, 1};
const RegisterAddress d0{RegisterArea::D, 0};

require(service.add({m0, "StartCommand", "启动命令"}).succeeded,
"a valid data point must be added");
require(service.add({d0, "Temperature", "温度"}).succeeded,
"M and D data points must coexist");
require(service.add({m0, "DuplicateAddress", {}}).error
== DataPointError::DuplicateAddress,
"duplicate data point addresses must be rejected");
require(service.add({m1, "StartCommand", {}}).error
== DataPointError::DuplicateName,
"duplicate data point names must be rejected");

HmiControl button;
button.id = "start-button";
button.type = HmiControlType::Button;
button.bounds = {0, 0, 100, 40};
button.text = "启动";
button.binding = m0;
HmiPage page;
page.id = "main-page";
page.name = "主画面";
page.controls.push_back(button);
project_service.editProject().hmiPages.push_back(page);

LogicNode contact;
contact.id = "start-contact";
contact.config = ContactNodeConfig{m0, ContactMode::NormallyOpen};
LadderRung rung;
rung.id = "rung-1";
rung.name = "网络 1";
rung.condition = ConditionExpression::fromNode(contact);
ControlLogic logic;
logic.id = "logic-1";
logic.name = "控制逻辑 1";
logic.rungs.push_back(rung);
project_service.editProject().controlLogics.push_back(logic);

const auto references = service.references(m0);
require(references.size() == 2U,
"data point references must include HMI and ladder locations");
require(service.remove(m0).error == DataPointError::AddressInUse,
"referenced data points must not be deleted");
require(service.update(m0, {m1, "StartCommand", "启动命令"}).error
== DataPointError::AddressInUse,
"referenced data point addresses must not change");
require(service.update(m0, {m0, "Start", "启动命令"}).succeeded,
"referenced data point names and comments may change");
require(service.remove(RegisterAddress{RegisterArea::D, 99}).error
== DataPointError::NotFound,
"removing an unknown data point must report not found");
}

} // namespace

int main()
{
try
{
testUniqueDataPointsAndReferences();
}
catch (const std::exception &error)
{
std::cerr << "data point service tests failed: " << error.what() << '\n';
return 1;
}
std::cout << "data point service tests passed\n";
return 0;
}

+ 0
- 29
app/tests/data_point_service_tests.pro ファイルの表示

@@ -1,29 +0,0 @@
TEMPLATE = app
TARGET = data_point_service_tests

CONFIG += console c++17 testcase warn_on
CONFIG -= app_bundle qt

INCLUDEPATH += ../src

SOURCES += \
data_point_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/services/project_service.cpp \
../src/services/data_point_service.cpp

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/services/project_service.h \
../src/services/data_point_service.h

+ 0
- 2
app/tests/domain_tests.pro ファイルの表示

@@ -10,7 +10,6 @@ SOURCES += \
domain_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -19,7 +18,6 @@ SOURCES += \
HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


+ 0
- 2
app/tests/hmi_editor_service_tests.pro ファイルの表示

@@ -10,7 +10,6 @@ SOURCES += \
hmi_editor_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -21,7 +20,6 @@ SOURCES += \
HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


+ 0
- 2
app/tests/logic_editor_service_tests.pro ファイルの表示

@@ -9,7 +9,6 @@ INCLUDEPATH += ../src
SOURCES += \
logic_editor_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/data_point_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/hmi_model.cpp \
@@ -19,7 +18,6 @@ SOURCES += \

HEADERS += \
../src/domain/register_address.h \
../src/domain/data_point_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/hmi_model.h \


+ 6
- 5
app/tests/main_window_tests.cpp ファイルの表示

@@ -2,7 +2,6 @@
#include "domain/register_repository.h"
#include "services/hmi_editor_service.h"
#include "services/hmi_runtime_service.h"
#include "services/data_point_service.h"
#include "services/logic_editor_service.h"
#include "services/offline_simulation_service.h"
#include "services/project_service.h"
@@ -66,7 +65,6 @@ void testModeActionsControlEditingAvailability()
TestProjectStorage storage;
ProjectService project_service(storage);
HmiEditorService editor_service(project_service);
DataPointService data_point_service(project_service);
LogicEditorService logic_editor_service(project_service);
VirtualRegisterRepository repository;
HmiRuntimeService runtime_service(repository);
@@ -76,13 +74,18 @@ void testModeActionsControlEditingAvailability()
mode_service,
project_service,
editor_service,
data_point_service,
logic_editor_service,
runtime_service);
window.resize(1000, 640);
window.show();
QApplication::processEvents();

require(window.findChild<QWidget *>(QStringLiteral("dataPointDock")) == nullptr,
"the removed data point dock must not remain in the main window");
require(window.findChild<QWidget *>(QStringLiteral("hmiDataPointComboBox")) == nullptr
&& window.findChild<QWidget *>(QStringLiteral("logicDataPointComboBox")) == nullptr,
"HMI and ladder properties must use direct M/D address inputs");

QAction *editing_action = requiredChild<QAction>(window, "editingModeAction");
QAction *offline_action = requiredChild<QAction>(window, "offlineModeAction");
QAction *online_action = requiredChild<QAction>(window, "onlineModeAction");
@@ -238,7 +241,6 @@ void testPlcConfigurationUsesDialog()
TestProjectStorage storage;
ProjectService project_service(storage);
HmiEditorService editor_service(project_service);
DataPointService data_point_service(project_service);
LogicEditorService logic_editor_service(project_service);
VirtualRegisterRepository repository;
HmiRuntimeService runtime_service(repository);
@@ -248,7 +250,6 @@ void testPlcConfigurationUsesDialog()
mode_service,
project_service,
editor_service,
data_point_service,
logic_editor_service,
runtime_service);
require(window.findChild<QComboBox *>(QStringLiteral("serialPortComboBox")) == nullptr,


+ 0
- 4
app/tests/main_window_tests.pro ファイルの表示

@@ -17,13 +17,11 @@ SOURCES += \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/active_register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/runtime_state.cpp \
../src/services/project_service.cpp \
../src/services/data_point_service.cpp \
../src/services/hmi_editor_service.cpp \
../src/services/logic_editor_service.cpp \
../src/services/hmi_runtime_service.cpp \
@@ -39,14 +37,12 @@ HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/active_register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/domain/runtime_state.h \
../src/services/project_service.h \
../src/services/data_point_service.h \
../src/services/hmi_editor_service.h \
../src/services/logic_editor_service.h \
../src/services/hmi_runtime_service.h \


+ 11
- 2
app/tests/plc_runtime_tests.cpp ファイルの表示

@@ -154,8 +154,17 @@ void testRuntimeRepositorySwitchingAndDisconnect()
{
TestProjectStorage storage;
ProjectService project_service(storage);
project_service.editProject().dataPoints.push_back(
{{RegisterArea::M, 5}, "RunState", "运行状态"});
HmiPage page;
page.id = "main-page";
page.name = "Main";
HmiControl indicator;
indicator.id = "run-state";
indicator.type = HmiControlType::Indicator;
indicator.bounds = {0, 0, 80, 40};
indicator.text = "Run";
indicator.binding = RegisterAddress{RegisterArea::M, 5};
page.controls.push_back(indicator);
project_service.editProject().hmiPages.push_back(page);
VirtualRegisterRepository virtual_repository;
PlcRegisterRepository plc_repository;
ActiveRegisterRepository active_repository(virtual_repository);


+ 0
- 2
app/tests/plc_runtime_tests.pro ファイルの表示

@@ -13,7 +13,6 @@ SOURCES += \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/active_register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -28,7 +27,6 @@ HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/active_register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


+ 3
- 9
app/tests/project_management_tests.cpp ファイルの表示

@@ -115,9 +115,6 @@ Project makeExampleProject()
project.metadata = {"example-project", "Example project", "1.0"};
project.hmiPages.push_back(page);
project.controlLogics.push_back(logic);
project.dataPoints = {
{RegisterAddress{RegisterArea::M, 0}, "StartCommand", "启动命令"},
{RegisterAddress{RegisterArea::D, 2}, "Temperature", "当前温度"}};
return project;
}

@@ -179,9 +176,10 @@ void testExampleProjectRoundTrip()
require(saved_json.contains("\"rungs\"")
&& saved_json.contains("\"condition\"")
&& saved_json.contains("\"children\"")
&& saved_json.contains("\"dataPoints\"")
&& saved_json.contains("\"output\""),
"saved project must use structured ladder expressions and data points");
"saved project must use structured ladder expressions");
require(!saved_json.contains("\"dataPoints\""),
"saved project must not contain the removed data point model");
require(!saved_json.contains("\"stages\"")
&& !saved_json.contains("\"branches\""),
"current project format must not contain the removed stage model");
@@ -226,10 +224,6 @@ void testExampleProjectRoundTrip()
rung.condition->children.at(1).node->config);
require(compare.address.index() == 2 && compare.value == 100,
"comparison configuration must survive round trip");
require(project.dataPoints.size() == 2
&& project.dataPoints.front().name == "StartCommand",
"data points must survive round trip");

require(service.saveAs(second_path.toStdString()).succeeded,
"save as must succeed after load");
require(readBytes(first_path) == readBytes(second_path),


+ 0
- 2
app/tests/project_management_tests.pro ファイルの表示

@@ -12,7 +12,6 @@ SOURCES += \
project_management_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -23,7 +22,6 @@ SOURCES += \
HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


+ 0
- 2
app/tests/runtime_mode_service_tests.pro ファイルの表示

@@ -13,7 +13,6 @@ SOURCES += \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/active_register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -27,7 +26,6 @@ HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/active_register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


読み込み中…
キャンセル
保存