Browse Source

refactor: 拆分主窗口界面控制职责

main
suyu 1 month ago
parent
commit
d2643d9a91
10 changed files with 1751 additions and 1128 deletions
  1. +6
    -0
      app/integrated_platform.pro
  2. +87
    -1106
      app/src/ui/main_window.cpp
  3. +6
    -22
      app/src/ui/main_window.h
  4. +511
    -0
      app/src/ui/project_workspace_controller.cpp
  5. +104
    -0
      app/src/ui/project_workspace_controller.h
  6. +611
    -0
      app/src/ui/property_panel_controller.cpp
  7. +77
    -0
      app/src/ui/property_panel_controller.h
  8. +255
    -0
      app/src/ui/runtime_panel_controller.cpp
  9. +88
    -0
      app/src/ui/runtime_panel_controller.h
  10. +6
    -0
      app/tests/main_window_tests.pro

+ 6
- 0
app/integrated_platform.pro View File

@@ -23,6 +23,9 @@ SOURCES += \
src/ui/plc_connection_dialog.cpp \
src/ui/free_monitor_widget.cpp \
src/ui/runtime_monitor_widget.cpp \
src/ui/project_workspace_controller.cpp \
src/ui/property_panel_controller.cpp \
src/ui/runtime_panel_controller.cpp \
src/domain/register_address.cpp \
src/domain/register_repository.cpp \
src/domain/active_register_repository.cpp \
@@ -59,6 +62,9 @@ HEADERS += \
src/ui/plc_connection_dialog.h \
src/ui/free_monitor_widget.h \
src/ui/runtime_monitor_widget.h \
src/ui/project_workspace_controller.h \
src/ui/property_panel_controller.h \
src/ui/runtime_panel_controller.h \
src/domain/register_address.h \
src/domain/register_repository.h \
src/domain/active_register_repository.h \


+ 87
- 1106
app/src/ui/main_window.cpp
File diff suppressed because it is too large
View File


+ 6
- 22
app/src/ui/main_window.h View File

@@ -21,7 +21,6 @@ QT_BEGIN_NAMESPACE
class QAction;
class QActionGroup;
class QLabel;
class QTimer;

namespace Ui {
class MainWindow;
@@ -41,6 +40,9 @@ class LogicEditorService;
class LogicEditorWidget;
class RegisterMonitorService;
class RuntimeMonitorWidget;
class ProjectWorkspaceController;
class PropertyPanelController;
class RuntimePanelController;

/**
* @brief 组织主界面区域并将用户操作转交给服务层
@@ -107,15 +109,7 @@ private:
void configureRegisterComments();
// 刷新工程树和当前 HMI 页面信息
void refreshProjectUi();
void handleProjectTreeSelection();
void updateProjectTreeActions();
void addPage();
void addLogic();
void renameProjectItem();
void deleteProjectItem();
void moveProjectItem(int offset);
void setSelectedPageAsInitial();
void toggleSelectedLogicEnabled();
// 显示指定 HMI 控件的可编辑属性
void showControlProperties(const std::string &control_id);
// 显示指定控制逻辑节点的可编辑属性
@@ -124,8 +118,6 @@ private:
void addHmiControl(HmiControlType type);
// 删除当前选中的 HMI 控件
void deleteSelectedControl();
// 将属性表单内容应用到当前选中控件
void applySelectedControlProperties();
// 在当前光标节点后添加串联条件,没有选择时追加到网络末尾
void addLogicCondition(const LogicNodeConfig &config);
// 为当前选中的连续逻辑范围建立并联支路
@@ -138,8 +130,6 @@ private:
void editSelectedRungComment();
// 删除当前选中的逻辑节点或网络
void deleteSelectedLogicObject();
// 将逻辑属性表单内容应用到当前选中节点
void applySelectedLogicNodeProperties();
void connectPlc();
void disconnectPlc();
// 合并同一事件循环内的 PLC 状态通知,避免重复刷新和重复日志
@@ -195,22 +185,16 @@ private:
RegisterMonitorService &register_monitor_service_;
std::unique_ptr<HmiNavigationService> owned_hmi_navigation_service_;
HmiNavigationService *hmi_navigation_service_ = nullptr;
std::unique_ptr<ProjectWorkspaceController> project_workspace_controller_;
std::unique_ptr<PropertyPanelController> property_panel_controller_;
std::unique_ptr<RuntimePanelController> runtime_panel_controller_;
QActionGroup *mode_action_group_ = nullptr;
HmiEditorWidget *hmi_editor_widget_ = nullptr;
LogicEditorWidget *logic_editor_widget_ = nullptr;
RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr;
QTimer *runtime_refresh_timer_ = nullptr;
QLabel *mode_status_label_ = nullptr;
QLabel *register_status_label_ = nullptr;
QLabel *executor_status_label_ = nullptr;
QAction *add_page_action_ = nullptr;
QAction *add_logic_action_ = nullptr;
QAction *rename_project_item_action_ = nullptr;
QAction *delete_project_item_action_ = nullptr;
QAction *move_project_item_up_action_ = nullptr;
QAction *move_project_item_down_action_ = nullptr;
QAction *set_initial_page_action_ = nullptr;
QAction *toggle_logic_enabled_action_ = nullptr;
PlcSerialConfiguration plc_configuration_;
std::string selected_control_id_;
std::string selected_logic_node_id_;


+ 511
- 0
app/src/ui/project_workspace_controller.cpp View File

@@ -0,0 +1,511 @@
#include "project_workspace_controller.h"

#include "hmi_editor_widget.h"
#include "logic_editor_widget.h"
#include "runtime_monitor_widget.h"
#include "services/hmi_editor_service.h"
#include "services/hmi_navigation_service.h"
#include "services/logic_editor_service.h"
#include "services/project_service.h"
#include "services/runtime_mode_service.h"
#include "ui_main_window.h"

#include <QAction>
#include <QInputDialog>
#include <QLineEdit>
#include <QObject>
#include <QSignalBlocker>
#include <QTabWidget>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QWidget>

#include <utility>

namespace {

constexpr int kProjectTreeKindRole = Qt::UserRole;
constexpr int kProjectTreeIdRole = Qt::UserRole + 1;

QString fromUtf8(const std::string &value)
{
return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
}

std::string toUtf8(const QString &value)
{
const QByteArray bytes = value.toUtf8();
return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
}

} // namespace

ProjectWorkspaceController::ProjectWorkspaceController(
QWidget &parent,
Ui::MainWindow &ui,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
LogicEditorService &logic_editor_service,
RuntimeModeService &runtime_mode_service,
HmiNavigationService &hmi_navigation_service,
HmiEditorWidget &hmi_editor_widget,
LogicEditorWidget &logic_editor_widget,
RuntimeMonitorWidget &runtime_monitor_widget,
std::string &current_hmi_page_id,
std::string &current_logic_id,
std::function<void(const std::string &)> show_control_properties,
std::function<void(const std::string &)> show_logic_properties,
ResultReporter result_reporter,
StatusReporter status_reporter)
: parent_(parent),
ui_(ui),
project_service_(project_service),
hmi_editor_service_(hmi_editor_service),
logic_editor_service_(logic_editor_service),
runtime_mode_service_(runtime_mode_service),
hmi_navigation_service_(hmi_navigation_service),
hmi_editor_widget_(hmi_editor_widget),
logic_editor_widget_(logic_editor_widget),
runtime_monitor_widget_(runtime_monitor_widget),
current_hmi_page_id_(current_hmi_page_id),
current_logic_id_(current_logic_id),
show_control_properties_(std::move(show_control_properties)),
show_logic_properties_(std::move(show_logic_properties)),
result_reporter_(std::move(result_reporter)),
status_reporter_(std::move(status_reporter))
{
}

void ProjectWorkspaceController::configure()
{
ui_.projectTree->setContextMenuPolicy(Qt::ActionsContextMenu);

add_page_action_ = new QAction(QObject::tr("新建页面"), &parent_);
add_page_action_->setObjectName(QStringLiteral("addPageAction"));
add_logic_action_ = new QAction(QObject::tr("新建控制逻辑"), &parent_);
add_logic_action_->setObjectName(QStringLiteral("addLogicAction"));
rename_item_action_ = new QAction(QObject::tr("重命名"), &parent_);
rename_item_action_->setObjectName(QStringLiteral("renameProjectItemAction"));
delete_item_action_ = new QAction(QObject::tr("删除"), &parent_);
delete_item_action_->setObjectName(QStringLiteral("deleteProjectItemAction"));
move_item_up_action_ = new QAction(QObject::tr("上移"), &parent_);
move_item_up_action_->setObjectName(QStringLiteral("moveProjectItemUpAction"));
move_item_down_action_ = new QAction(QObject::tr("下移"), &parent_);
move_item_down_action_->setObjectName(
QStringLiteral("moveProjectItemDownAction"));
set_initial_page_action_ = new QAction(QObject::tr("设为初始页面"), &parent_);
set_initial_page_action_->setObjectName(QStringLiteral("setInitialPageAction"));
toggle_logic_enabled_action_ = new QAction(
QObject::tr("禁用控制逻辑"), &parent_);
toggle_logic_enabled_action_->setObjectName(
QStringLiteral("toggleLogicEnabledAction"));

ui_.projectTree->addActions({
add_page_action_,
add_logic_action_,
rename_item_action_,
delete_item_action_,
move_item_up_action_,
move_item_down_action_,
set_initial_page_action_,
toggle_logic_enabled_action_});

QObject::connect(add_page_action_, &QAction::triggered,
&parent_, [this] { addPage(); });
QObject::connect(add_logic_action_, &QAction::triggered,
&parent_, [this] { addLogic(); });
QObject::connect(rename_item_action_, &QAction::triggered,
&parent_, [this] { renameSelectedItem(); });
QObject::connect(delete_item_action_, &QAction::triggered,
&parent_, [this] { deleteSelectedItem(); });
QObject::connect(move_item_up_action_, &QAction::triggered,
&parent_, [this] { moveSelectedItem(-1); });
QObject::connect(move_item_down_action_, &QAction::triggered,
&parent_, [this] { moveSelectedItem(1); });
QObject::connect(set_initial_page_action_, &QAction::triggered,
&parent_, [this] { setSelectedPageAsInitial(); });
QObject::connect(toggle_logic_enabled_action_, &QAction::triggered,
&parent_, [this] { toggleSelectedLogicEnabled(); });
QObject::connect(ui_.projectTree, &QTreeWidget::currentItemChanged,
&parent_, [this](QTreeWidgetItem *, QTreeWidgetItem *)
{
handleSelectionChanged();
});
updateActions();
}

void ProjectWorkspaceController::refresh()
{
if (hmi_editor_service_.findPage(current_hmi_page_id_) == nullptr)
{
const std::string &initial_id = project_service_.project().initialHmiPageId;
current_hmi_page_id_ = hmi_editor_service_.findPage(initial_id) != nullptr
? initial_id : hmi_editor_service_.firstPageId();
show_control_properties_({});
}
if (logic_editor_service_.findLogic(current_logic_id_) == nullptr)
{
current_logic_id_ = logic_editor_service_.firstLogicId();
show_logic_properties_({});
}

hmi_editor_widget_.setPageId(current_hmi_page_id_);
logic_editor_widget_.setLogicId(current_logic_id_);
if (runtime_mode_service_.mode() != ApplicationMode::Editing)
{
runtime_monitor_widget_.setProjectObjects(
hmi_navigation_service_.currentPageId(), current_logic_id_);
}
runtime_mode_service_.refreshPlcPollAddresses();

const QSignalBlocker blocker(ui_.projectTree);
ui_.projectTree->clear();
auto *hmi_root = new QTreeWidgetItem(ui_.projectTree, {QObject::tr("HMI 页面")});
hmi_root->setData(0, kProjectTreeKindRole, static_cast<int>(ItemKind::Root));
QTreeWidgetItem *selected_item = nullptr;
for (const HmiPage &page : project_service_.project().hmiPages)
{
const QString suffix = page.id == project_service_.project().initialHmiPageId
? QObject::tr("(初始)") : QString{};
auto *item = new QTreeWidgetItem(
hmi_root, {fromUtf8(page.name) + suffix});
item->setData(0, kProjectTreeKindRole, static_cast<int>(ItemKind::HmiPage));
item->setData(0, kProjectTreeIdRole, fromUtf8(page.id));
if (page.id == current_hmi_page_id_
&& ui_.editorTabWidget->currentIndex() == 0)
{
selected_item = item;
}
}

const HmiPage *current_page = hmi_editor_service_.findPage(current_hmi_page_id_);
if (current_page != nullptr)
{
ui_.hmiPageTitleLabel->setText(fromUtf8(current_page->name));
ui_.hmiPageSizeLabel->setText(
QObject::tr("%1 x %2").arg(current_page->width).arg(current_page->height));
}
else
{
ui_.hmiPageTitleLabel->setText(QObject::tr("尚未创建 HMI 页面"));
ui_.hmiPageSizeLabel->clear();
}

auto *logic_root = new QTreeWidgetItem(
ui_.projectTree, {QObject::tr("控制逻辑")});
logic_root->setData(0, kProjectTreeKindRole, static_cast<int>(ItemKind::Root));
for (const ControlLogic &logic : project_service_.project().controlLogics)
{
const QString suffix = logic.enabled ? QString{} : QObject::tr("(已禁用)");
auto *item = new QTreeWidgetItem(
logic_root, {fromUtf8(logic.name) + suffix});
item->setData(
0, kProjectTreeKindRole, static_cast<int>(ItemKind::ControlLogic));
item->setData(0, kProjectTreeIdRole, fromUtf8(logic.id));
if (logic.id == current_logic_id_
&& ui_.editorTabWidget->currentIndex() == 1)
{
selected_item = item;
}
}
ui_.projectTree->expandAll();
if (selected_item != nullptr)
{
ui_.projectTree->setCurrentItem(selected_item);
}
updateActions();
}

const std::string &ProjectWorkspaceController::currentHmiPageId() const
{
return current_hmi_page_id_;
}

const std::string &ProjectWorkspaceController::currentLogicId() const
{
return current_logic_id_;
}

void ProjectWorkspaceController::handleSelectionChanged()
{
QTreeWidgetItem *item = ui_.projectTree->currentItem();
if (item == nullptr)
{
updateActions();
return;
}
const auto kind = static_cast<ItemKind>(
item->data(0, kProjectTreeKindRole).toInt());
const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
if (kind == ItemKind::HmiPage && hmi_editor_service_.findPage(id) != nullptr)
{
current_hmi_page_id_ = id;
hmi_editor_widget_.setPageId(id);
hmi_editor_widget_.reloadPage();
ui_.editorTabWidget->setCurrentWidget(ui_.hmiEditorTab);
show_control_properties_({});
const HmiPage *page = hmi_editor_service_.findPage(id);
ui_.hmiPageTitleLabel->setText(fromUtf8(page->name));
ui_.hmiPageSizeLabel->setText(
QObject::tr("%1 x %2").arg(page->width).arg(page->height));
}
else if (kind == ItemKind::ControlLogic
&& logic_editor_service_.findLogic(id) != nullptr)
{
current_logic_id_ = id;
logic_editor_widget_.setLogicId(id);
logic_editor_widget_.reloadLogic();
ui_.editorTabWidget->setCurrentWidget(ui_.logicEditorTab);
show_logic_properties_({});
}
updateActions();
}

void ProjectWorkspaceController::updateActions()
{
if (add_page_action_ == nullptr)
{
return;
}
QTreeWidgetItem *item = ui_.projectTree->currentItem();
const auto kind = item == nullptr ? ItemKind::Root
: static_cast<ItemKind>(item->data(0, kProjectTreeKindRole).toInt());
const bool page_selected = kind == ItemKind::HmiPage;
const bool logic_selected = kind == ItemKind::ControlLogic;
const bool editable = runtime_mode_service_.policy().allowsProjectEditing;
add_page_action_->setEnabled(editable);
add_logic_action_->setEnabled(editable);
rename_item_action_->setEnabled(editable && (page_selected || logic_selected));
delete_item_action_->setEnabled(editable && (page_selected || logic_selected));
move_item_up_action_->setEnabled(editable && (page_selected || logic_selected));
move_item_down_action_->setEnabled(editable && (page_selected || logic_selected));
set_initial_page_action_->setEnabled(editable && page_selected);
toggle_logic_enabled_action_->setEnabled(editable && logic_selected);
if (logic_selected)
{
const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
const ControlLogic *logic = logic_editor_service_.findLogic(id);
toggle_logic_enabled_action_->setText(
logic != nullptr && logic->enabled
? QObject::tr("禁用控制逻辑") : QObject::tr("启用控制逻辑"));
}
}

void ProjectWorkspaceController::addPage()
{
const QString name = QInputDialog::getText(
&parent_, QObject::tr("新建页面"), QObject::tr("页面名称"));
if (name.isEmpty())
{
return;
}
const HmiEditorResult result = hmi_editor_service_.addPage(toUtf8(name));
if (!result.succeeded)
{
reportFailure(QObject::tr("新建页面"), result.message);
return;
}
current_hmi_page_id_ = result.id;
show_control_properties_({});
ui_.editorTabWidget->setCurrentWidget(ui_.hmiEditorTab);
refresh();
hmi_editor_widget_.reloadPage();
}

void ProjectWorkspaceController::addLogic()
{
const QString name = QInputDialog::getText(
&parent_, QObject::tr("新建控制逻辑"), QObject::tr("逻辑名称"));
if (name.isEmpty())
{
return;
}
const LogicEditorResult result = logic_editor_service_.addLogic(toUtf8(name));
if (!result.succeeded)
{
reportFailure(QObject::tr("新建控制逻辑"), result.message);
return;
}
current_logic_id_ = result.id;
show_logic_properties_({});
ui_.editorTabWidget->setCurrentWidget(ui_.logicEditorTab);
refresh();
logic_editor_widget_.reloadLogic();
}

void ProjectWorkspaceController::renameSelectedItem()
{
QTreeWidgetItem *item = ui_.projectTree->currentItem();
if (item == nullptr)
{
return;
}
const auto kind = static_cast<ItemKind>(
item->data(0, kProjectTreeKindRole).toInt());
const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
QString current_name;
if (kind == ItemKind::HmiPage)
{
const HmiPage *page = hmi_editor_service_.findPage(id);
current_name = page == nullptr ? QString{} : fromUtf8(page->name);
}
else if (kind == ItemKind::ControlLogic)
{
const ControlLogic *logic = logic_editor_service_.findLogic(id);
current_name = logic == nullptr ? QString{} : fromUtf8(logic->name);
}
else
{
return;
}
bool accepted = false;
const QString name = QInputDialog::getText(
&parent_, QObject::tr("重命名"), QObject::tr("新名称"),
QLineEdit::Normal, current_name, &accepted);
if (!accepted)
{
return;
}
if (kind == ItemKind::HmiPage)
{
const HmiEditorResult result = hmi_editor_service_.renamePage(id, toUtf8(name));
if (!result.succeeded)
{
reportFailure(QObject::tr("重命名页面"), result.message);
return;
}
}
else
{
const LogicEditorResult result = logic_editor_service_.renameLogic(
id, toUtf8(name));
if (!result.succeeded)
{
reportFailure(QObject::tr("重命名控制逻辑"), result.message);
return;
}
}
refresh();
}

void ProjectWorkspaceController::deleteSelectedItem()
{
QTreeWidgetItem *item = ui_.projectTree->currentItem();
if (item == nullptr)
{
return;
}
const auto kind = static_cast<ItemKind>(
item->data(0, kProjectTreeKindRole).toInt());
const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
if (kind == ItemKind::HmiPage)
{
const HmiEditorResult result = hmi_editor_service_.removePage(id);
if (!result.succeeded)
{
reportFailure(QObject::tr("删除页面"), result.message);
return;
}
}
else if (kind == ItemKind::ControlLogic)
{
const LogicEditorResult result = logic_editor_service_.removeLogic(id);
if (!result.succeeded)
{
reportFailure(QObject::tr("删除控制逻辑"), result.message);
return;
}
}
else
{
return;
}
refresh();
show_control_properties_({});
show_logic_properties_({});
}

void ProjectWorkspaceController::moveSelectedItem(int offset)
{
QTreeWidgetItem *item = ui_.projectTree->currentItem();
if (item == nullptr)
{
return;
}
const auto kind = static_cast<ItemKind>(
item->data(0, kProjectTreeKindRole).toInt());
const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
if (kind == ItemKind::HmiPage)
{
const HmiEditorResult result = hmi_editor_service_.movePage(id, offset);
if (!result.succeeded)
{
status_reporter_(fromUtf8(result.message), 4000);
return;
}
}
else if (kind == ItemKind::ControlLogic)
{
const LogicEditorResult result = logic_editor_service_.moveLogic(id, offset);
if (!result.succeeded)
{
status_reporter_(fromUtf8(result.message), 4000);
return;
}
}
else
{
return;
}
refresh();
}

void ProjectWorkspaceController::setSelectedPageAsInitial()
{
QTreeWidgetItem *item = ui_.projectTree->currentItem();
if (item == nullptr
|| static_cast<ItemKind>(item->data(0, kProjectTreeKindRole).toInt())
!= ItemKind::HmiPage)
{
return;
}
const HmiEditorResult result = hmi_editor_service_.setInitialPage(
toUtf8(item->data(0, kProjectTreeIdRole).toString()));
if (!result.succeeded)
{
reportFailure(QObject::tr("设置初始页面"), result.message);
return;
}
refresh();
}

void ProjectWorkspaceController::toggleSelectedLogicEnabled()
{
QTreeWidgetItem *item = ui_.projectTree->currentItem();
if (item == nullptr
|| static_cast<ItemKind>(item->data(0, kProjectTreeKindRole).toInt())
!= ItemKind::ControlLogic)
{
return;
}
const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
const ControlLogic *logic = logic_editor_service_.findLogic(id);
if (logic == nullptr)
{
return;
}
const LogicEditorResult result = logic_editor_service_.setLogicEnabled(
id, !logic->enabled);
if (!result.succeeded)
{
reportFailure(QObject::tr("启停控制逻辑"), result.message);
return;
}
refresh();
}

void ProjectWorkspaceController::reportFailure(
const QString &action, const std::string &message) const
{
result_reporter_(action, fromUtf8(message), false);
}

+ 104
- 0
app/src/ui/project_workspace_controller.h View File

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

#include "domain/hmi_model.h"
#include "domain/runtime_state.h"

#include <QString>

#include <functional>
#include <string>

class HmiEditorService;
class HmiEditorWidget;
class HmiNavigationService;
class LogicEditorService;
class LogicEditorWidget;
class ProjectService;
class RuntimeModeService;
class RuntimeMonitorWidget;
class QWidget;
class QAction;

namespace Ui {
class MainWindow;
}

// 组织工程树、当前页面/逻辑选择和工程对象的界面刷新
class ProjectWorkspaceController final
{
public:
using ResultReporter = std::function<void(
const QString &action, const QString &message, bool succeeded)>;
using StatusReporter = std::function<void(
const QString &message, int timeout_ms)>;

ProjectWorkspaceController(
QWidget &parent,
Ui::MainWindow &ui,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
LogicEditorService &logic_editor_service,
RuntimeModeService &runtime_mode_service,
HmiNavigationService &hmi_navigation_service,
HmiEditorWidget &hmi_editor_widget,
LogicEditorWidget &logic_editor_widget,
RuntimeMonitorWidget &runtime_monitor_widget,
std::string &current_hmi_page_id,
std::string &current_logic_id,
std::function<void(const std::string &)> show_control_properties,
std::function<void(const std::string &)> show_logic_properties,
ResultReporter result_reporter,
StatusReporter status_reporter);

void configure();
void refresh();
void updateActions();
void addPage();
void addLogic();
void renameSelectedItem();
void deleteSelectedItem();
void moveSelectedItem(int offset);
void setSelectedPageAsInitial();
void toggleSelectedLogicEnabled();

const std::string &currentHmiPageId() const;
const std::string &currentLogicId() const;

private:
enum class ItemKind
{
Root = 0,
HmiPage = 1,
ControlLogic = 2
};

void handleSelectionChanged();
void reportFailure(
const QString &action, const std::string &message) const;

QWidget &parent_;
Ui::MainWindow &ui_;
ProjectService &project_service_;
HmiEditorService &hmi_editor_service_;
LogicEditorService &logic_editor_service_;
RuntimeModeService &runtime_mode_service_;
HmiNavigationService &hmi_navigation_service_;
HmiEditorWidget &hmi_editor_widget_;
LogicEditorWidget &logic_editor_widget_;
RuntimeMonitorWidget &runtime_monitor_widget_;
std::string &current_hmi_page_id_;
std::string &current_logic_id_;
std::function<void(const std::string &)> show_control_properties_;
std::function<void(const std::string &)> show_logic_properties_;
ResultReporter result_reporter_;
StatusReporter status_reporter_;

QAction *add_page_action_ = nullptr;
QAction *add_logic_action_ = nullptr;
QAction *rename_item_action_ = nullptr;
QAction *delete_item_action_ = nullptr;
QAction *move_item_up_action_ = nullptr;
QAction *move_item_down_action_ = nullptr;
QAction *set_initial_page_action_ = nullptr;
QAction *toggle_logic_enabled_action_ = nullptr;
};

+ 611
- 0
app/src/ui/property_panel_controller.cpp View File

@@ -0,0 +1,611 @@
#include "property_panel_controller.h"

#include "hmi_editor_widget.h"
#include "logic_editor_widget.h"
#include "logic_instruction_dialog.h"
#include "services/hmi_editor_service.h"
#include "services/logic_editor_service.h"
#include "services/project_service.h"
#include "ui_main_window.h"

#include <QComboBox>
#include <QLabel>
#include <QPushButton>
#include <QDialog>
#include <QObject>
#include <QSpinBox>
#include <QStackedWidget>
#include <QWidget>

#include <cstdint>
#include <utility>

namespace {

QString fromUtf8(const std::string &value)
{
return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
}

std::string toUtf8(const QString &value)
{
const QByteArray bytes = value.toUtf8();
return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
}

QString controlTypeText(HmiControlType type)
{
switch (type)
{
case HmiControlType::Button:
{
return QObject::tr("按钮");
}
case HmiControlType::Indicator:
{
return QObject::tr("指示灯");
}
case HmiControlType::NumericDisplay:
{
return QObject::tr("数值显示");
}
case HmiControlType::NumericInput:
{
return QObject::tr("数值输入");
}
case HmiControlType::Label:
{
return QObject::tr("文本");
}
case HmiControlType::PageJump:
{
return QObject::tr("页面跳转");
}
case HmiControlType::AlarmList:
{
return QObject::tr("报警列表");
}
default:
{
return QObject::tr("未知控件");
}
}
}

} // namespace

PropertyPanelController::PropertyPanelController(
QWidget &parent,
Ui::MainWindow &ui,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
LogicEditorService &logic_editor_service,
std::function<std::string()> current_page_id,
std::function<std::string()> current_logic_id,
std::string &selected_control_id,
std::string &selected_logic_node_id,
std::function<void()> refresh_project_ui,
ResultReporter result_reporter,
StatusReporter status_reporter)
: parent_(parent),
ui_(ui),
project_service_(project_service),
hmi_editor_service_(hmi_editor_service),
logic_editor_service_(logic_editor_service),
current_page_id_(std::move(current_page_id)),
current_logic_id_(std::move(current_logic_id)),
selected_control_id_(selected_control_id),
selected_logic_node_id_(selected_logic_node_id),
refresh_project_ui_(std::move(refresh_project_ui)),
result_reporter_(std::move(result_reporter)),
status_reporter_(std::move(status_reporter))
{
}

void PropertyPanelController::configure()
{
ui_.bindingAreaComboBox->setItemData(0, -1);
ui_.bindingAreaComboBox->setItemData(1, 0);
ui_.bindingAreaComboBox->setItemData(2, 1);
ui_.buttonOperationComboBox->setItemData(
0, static_cast<int>(HmiButtonOperation::SetOn));
ui_.buttonOperationComboBox->setItemData(
1, static_cast<int>(HmiButtonOperation::SetOff));
ui_.buttonOperationComboBox->setItemData(
2, static_cast<int>(HmiButtonOperation::Toggle));
ui_.buttonOperationComboBox->setItemData(
3, static_cast<int>(HmiButtonOperation::MomentaryOn));
for (int index = 0; index < ui_.logicComparisonComboBox->count(); ++index)
{
ui_.logicComparisonComboBox->setItemData(index, index);
}
QObject::connect(ui_.applyPropertiesButton, &QPushButton::clicked,
&parent_, [this] { applySelectedControlProperties(); });
QObject::connect(ui_.applyLogicPropertiesButton, &QPushButton::clicked,
&parent_, [this] { applySelectedLogicNodeProperties(); });
ui_.targetPageLabel->setVisible(false);
ui_.targetPageComboBox->setVisible(false);
showControlProperties({});
}

void PropertyPanelController::bindEditorWidgets(
HmiEditorWidget &hmi_editor_widget,
LogicEditorWidget &logic_editor_widget)
{
hmi_editor_widget_ = &hmi_editor_widget;
logic_editor_widget_ = &logic_editor_widget;
QObject::connect(&hmi_editor_widget, &HmiEditorWidget::controlSelected,
&parent_, [this](const QString &id)
{
showControlProperties(toUtf8(id));
});
QObject::connect(&hmi_editor_widget, &HmiEditorWidget::controlChanged,
&parent_, [this](const QString &id)
{
showControlProperties(toUtf8(id));
refresh_project_ui_();
});
QObject::connect(&hmi_editor_widget, &HmiEditorWidget::editorError,
&parent_, [this](const QString &message)
{
handleHmiEditorError(message);
});
QObject::connect(&logic_editor_widget, &LogicEditorWidget::nodeSelected,
&parent_, [this](const QString &id)
{
showLogicNodeProperties(toUtf8(id));
});
QObject::connect(&logic_editor_widget, &LogicEditorWidget::graphChanged,
&parent_, [this]
{
refresh_project_ui_();
});
QObject::connect(&logic_editor_widget, &LogicEditorWidget::editorError,
&parent_, [this](const QString &message)
{
handleLogicEditorError(message);
});
}

void PropertyPanelController::showControlProperties(const std::string &control_id)
{
ui_.propertyStack->setCurrentWidget(ui_.hmiPropertiesPage);
selected_control_id_ = control_id;
const HmiControl *control = hmi_editor_service_.findControl(
current_page_id_(), control_id);
const bool has_control = control != nullptr;
ui_.selectionValueLabel->setText(
has_control ? fromUtf8(control->id)
+ (control->isConfigured() ? QString{}
: control->type == HmiControlType::PageJump
? QObject::tr("(未配置)") : QObject::tr("(未绑定)"))
: QObject::tr("未选择"));

for (QWidget *widget : {static_cast<QWidget *>(ui_.controlIdEdit),
static_cast<QWidget *>(ui_.controlTextEdit),
static_cast<QWidget *>(ui_.controlXSpinBox),
static_cast<QWidget *>(ui_.controlYSpinBox),
static_cast<QWidget *>(ui_.controlWidthSpinBox),
static_cast<QWidget *>(ui_.controlHeightSpinBox),
static_cast<QWidget *>(ui_.bindingAreaComboBox),
static_cast<QWidget *>(ui_.bindingIndexSpinBox),
static_cast<QWidget *>(ui_.targetPageComboBox),
static_cast<QWidget *>(ui_.buttonOperationComboBox),
static_cast<QWidget *>(ui_.applyPropertiesButton)})
{
widget->setEnabled(has_control);
}

if (!has_control)
{
ui_.bindingAreaLabel->setVisible(false);
ui_.bindingAreaComboBox->setVisible(false);
ui_.bindingIndexLabel->setVisible(false);
ui_.bindingIndexSpinBox->setVisible(false);
ui_.targetPageLabel->setVisible(false);
ui_.targetPageComboBox->setVisible(false);
ui_.buttonOperationLabel->setVisible(false);
ui_.buttonOperationComboBox->setVisible(false);
return;
}

ui_.controlIdEdit->setText(fromUtf8(control->id));
ui_.controlTextEdit->setText(fromUtf8(control->text));
ui_.controlXSpinBox->setValue(control->bounds.x);
ui_.controlYSpinBox->setValue(control->bounds.y);
ui_.controlWidthSpinBox->setValue(control->bounds.width);
ui_.controlHeightSpinBox->setValue(control->bounds.height);
ui_.bindingAreaComboBox->setCurrentIndex(!control->binding.has_value() ? 0
: control->binding->area() == RegisterArea::M ? 1 : 2);
ui_.bindingIndexSpinBox->setValue(
control->binding.has_value() ? control->binding->index() : 0);
const bool has_binding = control->type != HmiControlType::Label
&& control->type != HmiControlType::PageJump
&& control->type != HmiControlType::AlarmList;
ui_.bindingAreaLabel->setVisible(has_binding);
ui_.bindingAreaComboBox->setVisible(has_binding);
ui_.bindingIndexLabel->setVisible(has_binding);
ui_.bindingIndexSpinBox->setVisible(has_binding);

const bool is_button = control->type == HmiControlType::Button;
ui_.buttonOperationLabel->setVisible(is_button);
ui_.buttonOperationComboBox->setVisible(is_button);
ui_.buttonOperationComboBox->setEnabled(is_button);
ui_.buttonOperationComboBox->setCurrentIndex(
ui_.buttonOperationComboBox->findData(
static_cast<int>(control->buttonOperation)));

const bool is_page_jump = control->type == HmiControlType::PageJump;
ui_.targetPageLabel->setVisible(is_page_jump);
ui_.targetPageComboBox->setVisible(is_page_jump);
ui_.targetPageComboBox->setEnabled(is_page_jump);
ui_.targetPageComboBox->clear();
if (is_page_jump)
{
for (const HmiPage &page : project_service_.project().hmiPages)
{
ui_.targetPageComboBox->addItem(
fromUtf8(page.name), fromUtf8(page.id));
}
const std::string target_id = control->pageJump.has_value()
? control->pageJump->targetPageId : std::string{};
ui_.targetPageComboBox->setCurrentIndex(
ui_.targetPageComboBox->findData(fromUtf8(target_id)));
}
}

void PropertyPanelController::showLogicNodeProperties(const std::string &node_id)
{
ui_.propertyStack->setCurrentWidget(ui_.logicPropertiesPage);
selected_logic_node_id_ = node_id;
const LogicNode *node = logic_editor_service_.findNode(
current_logic_id_(), node_id);
const bool has_node = node != nullptr;
ui_.selectionValueLabel->setText(
has_node ? fromUtf8(node->id) : QObject::tr("未选择"));
for (QWidget *widget : {static_cast<QWidget *>(ui_.logicNodeIdEdit),
static_cast<QWidget *>(ui_.logicAddressSpinBox),
static_cast<QWidget *>(ui_.logicModeComboBox),
static_cast<QWidget *>(ui_.logicComparisonComboBox),
static_cast<QWidget *>(ui_.logicValueSpinBox),
static_cast<QWidget *>(ui_.logicPresetSpinBox),
static_cast<QWidget *>(ui_.applyLogicPropertiesButton)})
{
widget->setEnabled(has_node);
}
ui_.logicNodeTypeLabel->setEnabled(has_node);
ui_.logicAddressAreaLabel->setEnabled(has_node);
if (!has_node)
{
ui_.logicNodeIdEdit->clear();
ui_.logicNodeTypeLabel->clear();
ui_.logicAddressAreaLabel->clear();
ui_.logicModeComboBox->clear();
ui_.logicPresetSpinBox->setValue(TonNodeConfig::kMinimumPresetMs);
return;
}

ui_.logicNodeIdEdit->setText(fromUtf8(node->id));
ui_.logicModeComboBox->clear();
ui_.logicComparisonComboBox->setEnabled(false);
ui_.logicValueSpinBox->setEnabled(false);
ui_.logicPresetSpinBox->setEnabled(false);
if (const auto *contact = std::get_if<ContactNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QObject::tr("触点"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("M"));
ui_.logicAddressSpinBox->setValue(contact->address.index());
ui_.logicModeComboBox->addItem(
QObject::tr("常开"), static_cast<int>(ContactMode::NormallyOpen));
ui_.logicModeComboBox->addItem(
QObject::tr("常闭"), static_cast<int>(ContactMode::NormallyClosed));
ui_.logicModeComboBox->setCurrentIndex(
ui_.logicModeComboBox->findData(static_cast<int>(contact->mode)));
ui_.logicModeComboBox->setEnabled(true);
}
else if (const auto *edge = std::get_if<EdgeContactNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QObject::tr("边沿触点"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("M"));
ui_.logicAddressSpinBox->setValue(edge->address.index());
ui_.logicModeComboBox->addItem(
QObject::tr("上升沿"), static_cast<int>(EdgeMode::Rising));
ui_.logicModeComboBox->addItem(
QObject::tr("下降沿"), static_cast<int>(EdgeMode::Falling));
ui_.logicModeComboBox->setCurrentIndex(
ui_.logicModeComboBox->findData(static_cast<int>(edge->mode)));
ui_.logicModeComboBox->setEnabled(true);
}
else if (const auto *timer = std::get_if<TimerContactNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QObject::tr("T 触点"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("T"));
ui_.logicAddressSpinBox->setValue(timer->address.index());
ui_.logicModeComboBox->addItem(
QObject::tr("常开"), static_cast<int>(ContactMode::NormallyOpen));
ui_.logicModeComboBox->addItem(
QObject::tr("常闭"), static_cast<int>(ContactMode::NormallyClosed));
ui_.logicModeComboBox->setCurrentIndex(
ui_.logicModeComboBox->findData(static_cast<int>(timer->mode)));
ui_.logicModeComboBox->setEnabled(true);
}
else if (const auto *counter_contact =
std::get_if<CounterContactNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QObject::tr("C 触点"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("C"));
ui_.logicAddressSpinBox->setValue(counter_contact->address.index());
ui_.logicModeComboBox->addItem(
QObject::tr("常开"), static_cast<int>(ContactMode::NormallyOpen));
ui_.logicModeComboBox->addItem(
QObject::tr("常闭"), static_cast<int>(ContactMode::NormallyClosed));
ui_.logicModeComboBox->setCurrentIndex(ui_.logicModeComboBox->findData(
static_cast<int>(counter_contact->mode)));
ui_.logicModeComboBox->setEnabled(true);
}
else if (const auto *coil = std::get_if<CoilNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QObject::tr("线圈"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("M"));
ui_.logicAddressSpinBox->setValue(coil->address.index());
ui_.logicModeComboBox->addItem(
QObject::tr("普通"), static_cast<int>(CoilMode::Normal));
ui_.logicModeComboBox->addItem(
QObject::tr("置位"), static_cast<int>(CoilMode::Set));
ui_.logicModeComboBox->addItem(
QObject::tr("复位"), static_cast<int>(CoilMode::Reset));
ui_.logicModeComboBox->setCurrentIndex(
ui_.logicModeComboBox->findData(static_cast<int>(coil->mode)));
ui_.logicModeComboBox->setEnabled(true);
}
else if (const auto *compare = std::get_if<CompareNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QObject::tr("D 值比较"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("D"));
ui_.logicAddressSpinBox->setValue(compare->address.index());
ui_.logicModeComboBox->addItem(QObject::tr("不适用"));
ui_.logicModeComboBox->setEnabled(false);
ui_.logicComparisonComboBox->setEnabled(true);
ui_.logicComparisonComboBox->setCurrentIndex(
ui_.logicComparisonComboBox->findData(
static_cast<int>(compare->comparison)));
ui_.logicValueSpinBox->setEnabled(true);
ui_.logicValueSpinBox->setValue(compare->value);
}
else if (const auto *ton = std::get_if<TonNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QObject::tr("TON"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("T"));
ui_.logicAddressSpinBox->setValue(ton->address.index());
ui_.logicModeComboBox->addItem(QObject::tr("非保持接通延时"));
ui_.logicModeComboBox->setEnabled(false);
ui_.logicPresetSpinBox->setEnabled(true);
ui_.logicPresetSpinBox->setValue(ton->presetMs);
}
else if (const auto *counter = std::get_if<CounterNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(
counter->mode == CounterMode::Up ? QStringLiteral("CTU")
: QStringLiteral("CTD"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("C"));
ui_.logicAddressSpinBox->setValue(counter->address.index());
ui_.logicAddressSpinBox->setEnabled(false);
ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑"));
ui_.logicModeComboBox->setEnabled(false);
}
else if (const auto *move = std::get_if<MoveNodeConfig>(&node->config))
{
ui_.logicNodeTypeLabel->setText(QStringLiteral("MOVE"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("D"));
ui_.logicAddressSpinBox->setValue(move->destination.index());
ui_.logicAddressSpinBox->setEnabled(false);
ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑"));
ui_.logicModeComboBox->setEnabled(false);
}
else
{
const auto &arithmetic = std::get<ArithmeticNodeConfig>(node->config);
ui_.logicNodeTypeLabel->setText(
arithmetic.operation == ArithmeticOperation::Add
? QStringLiteral("ADD") : QStringLiteral("SUB"));
ui_.logicAddressAreaLabel->setText(QStringLiteral("D"));
ui_.logicAddressSpinBox->setValue(arithmetic.destination.index());
ui_.logicAddressSpinBox->setEnabled(false);
ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑"));
ui_.logicModeComboBox->setEnabled(false);
}
if (!node->configured)
{
ui_.logicNodeTypeLabel->setText(QObject::tr("待配置:%1").arg(
ui_.logicNodeTypeLabel->text()));
}
}

void PropertyPanelController::addHmiControl(HmiControlType type)
{
const HmiEditorResult result = hmi_editor_service_.addControl(
current_page_id_(), type);
if (!result.succeeded)
{
reportFailure(QObject::tr("添加控件"), result.message);
return;
}
hmi_editor_widget_->reloadPage();
hmi_editor_widget_->selectControl(result.id);
showControlProperties(result.id);
refresh_project_ui_();
status_reporter_(QObject::tr("已添加%1").arg(controlTypeText(type)), 3000);
}

void PropertyPanelController::deleteSelectedControl()
{
if (selected_control_id_.empty())
{
return;
}
const HmiEditorResult result = hmi_editor_service_.removeControl(
current_page_id_(), selected_control_id_);
if (!result.succeeded)
{
reportFailure(QObject::tr("删除控件"), result.message);
return;
}
selected_control_id_.clear();
hmi_editor_widget_->reloadPage();
showControlProperties({});
refresh_project_ui_();
}

void PropertyPanelController::applySelectedControlProperties()
{
const HmiControl *old_control = hmi_editor_service_.findControl(
current_page_id_(), selected_control_id_);
if (old_control == nullptr)
{
return;
}
HmiControl control = *old_control;
control.id = toUtf8(ui_.controlIdEdit->text());
control.text = toUtf8(ui_.controlTextEdit->text());
control.bounds.x = ui_.controlXSpinBox->value();
control.bounds.y = ui_.controlYSpinBox->value();
control.bounds.width = ui_.controlWidthSpinBox->value();
control.bounds.height = ui_.controlHeightSpinBox->value();
if (control.type == HmiControlType::Label
|| control.type == HmiControlType::PageJump
|| control.type == HmiControlType::AlarmList)
{
control.binding.reset();
}
else
{
const int binding_area = ui_.bindingAreaComboBox->currentData().toInt();
if (binding_area < 0)
{
control.binding.reset();
}
else
{
const RegisterArea area = binding_area == 0
? RegisterArea::M : RegisterArea::D;
control.binding = RegisterAddress{
area, ui_.bindingIndexSpinBox->value()};
}
}
if (control.type == HmiControlType::Button)
{
control.buttonOperation = static_cast<HmiButtonOperation>(
ui_.buttonOperationComboBox->currentData().toInt());
}
if (control.type == HmiControlType::PageJump)
{
control.pageJump = HmiPageJumpConfig{
toUtf8(ui_.targetPageComboBox->currentData().toString())};
}
const HmiEditorResult result = hmi_editor_service_.updateControl(
current_page_id_(), selected_control_id_, control);
if (!result.succeeded)
{
reportFailure(QObject::tr("应用属性"), result.message);
return;
}
selected_control_id_ = result.id;
hmi_editor_widget_->reloadPage();
hmi_editor_widget_->selectControl(selected_control_id_);
refresh_project_ui_();
status_reporter_(QObject::tr("控件属性已更新"), 3000);
}

void PropertyPanelController::applySelectedLogicNodeProperties()
{
const LogicNode *node = logic_editor_service_.findNode(
current_logic_id_(), selected_logic_node_id_);
if (node == nullptr)
{
return;
}

LogicNodeConfig config = node->config;
if (std::holds_alternative<ContactNodeConfig>(config))
{
config = ContactNodeConfig{
RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()},
static_cast<ContactMode>(ui_.logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<EdgeContactNodeConfig>(config))
{
config = EdgeContactNodeConfig{
RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()},
static_cast<EdgeMode>(ui_.logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<TimerContactNodeConfig>(config))
{
config = TimerContactNodeConfig{
TimerAddress{ui_.logicAddressSpinBox->value()},
static_cast<ContactMode>(ui_.logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<CounterContactNodeConfig>(config))
{
config = CounterContactNodeConfig{
CounterAddress{ui_.logicAddressSpinBox->value()},
static_cast<ContactMode>(ui_.logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<CoilNodeConfig>(config))
{
config = CoilNodeConfig{
RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()},
static_cast<CoilMode>(ui_.logicModeComboBox->currentData().toInt())};
}
else if (std::holds_alternative<CompareNodeConfig>(config))
{
config = CompareNodeConfig{
RegisterAddress{RegisterArea::D, ui_.logicAddressSpinBox->value()},
static_cast<ComparisonOperator>(
ui_.logicComparisonComboBox->currentData().toInt()),
static_cast<std::int16_t>(ui_.logicValueSpinBox->value())};
}
else if (std::holds_alternative<TonNodeConfig>(config))
{
config = TonNodeConfig{
TimerAddress{ui_.logicAddressSpinBox->value()},
ui_.logicPresetSpinBox->value()};
}
else
{
LogicInstructionDialog dialog(node->config, &parent_);
if (dialog.exec() != QDialog::Accepted)
{
return;
}
config = dialog.config();
}

const LogicEditorResult result = logic_editor_service_.updateNodeConfig(
current_logic_id_(), selected_logic_node_id_, config);
if (!result.succeeded)
{
reportFailure(QObject::tr("应用逻辑属性"), result.message);
return;
}
logic_editor_widget_->reloadLogic();
logic_editor_widget_->selectNode(selected_logic_node_id_);
showLogicNodeProperties(selected_logic_node_id_);
refresh_project_ui_();
status_reporter_(QObject::tr("逻辑节点属性已更新,节点已配置"), 3000);
}

void PropertyPanelController::handleHmiEditorError(const QString &message) const
{
status_reporter_(message, 5000);
}

void PropertyPanelController::handleLogicEditorError(const QString &message) const
{
status_reporter_(message, 5000);
}

void PropertyPanelController::reportFailure(
const QString &action, const std::string &message) const
{
result_reporter_(action, fromUtf8(message), false);
}

+ 77
- 0
app/src/ui/property_panel_controller.h View File

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

#include "domain/control_logic_model.h"
#include "domain/hmi_model.h"

#include <QString>

#include <functional>
#include <string>

class HmiEditorService;
class HmiEditorWidget;
class LogicEditorService;
class LogicEditorWidget;
class ProjectService;
class QWidget;

namespace Ui {
class MainWindow;
}

// 组织 HMI/梯形图选择状态、属性面板和编辑器信号
class PropertyPanelController final
{
public:
using ResultReporter = std::function<void(
const QString &action, const QString &message, bool succeeded)>;
using StatusReporter = std::function<void(
const QString &message, int timeout_ms)>;

PropertyPanelController(
QWidget &parent,
Ui::MainWindow &ui,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
LogicEditorService &logic_editor_service,
std::function<std::string()> current_page_id,
std::function<std::string()> current_logic_id,
std::string &selected_control_id,
std::string &selected_logic_node_id,
std::function<void()> refresh_project_ui,
ResultReporter result_reporter,
StatusReporter status_reporter);

void configure();
void bindEditorWidgets(
HmiEditorWidget &hmi_editor_widget,
LogicEditorWidget &logic_editor_widget);

void showControlProperties(const std::string &control_id);
void showLogicNodeProperties(const std::string &node_id);
void addHmiControl(HmiControlType type);
void deleteSelectedControl();
void applySelectedControlProperties();
void applySelectedLogicNodeProperties();

private:
void handleHmiEditorError(const QString &message) const;
void handleLogicEditorError(const QString &message) const;
void reportFailure(
const QString &action, const std::string &message) const;

QWidget &parent_;
Ui::MainWindow &ui_;
ProjectService &project_service_;
HmiEditorService &hmi_editor_service_;
LogicEditorService &logic_editor_service_;
std::function<std::string()> current_page_id_;
std::function<std::string()> current_logic_id_;
std::string &selected_control_id_;
std::string &selected_logic_node_id_;
std::function<void()> refresh_project_ui_;
ResultReporter result_reporter_;
StatusReporter status_reporter_;
HmiEditorWidget *hmi_editor_widget_ = nullptr;
LogicEditorWidget *logic_editor_widget_ = nullptr;
};

+ 255
- 0
app/src/ui/runtime_panel_controller.cpp View File

@@ -0,0 +1,255 @@
#include "runtime_panel_controller.h"

#include "free_monitor_widget.h"
#include "hmi_editor_widget.h"
#include "logic_editor_widget.h"
#include "runtime_monitor_widget.h"
#include "services/alarm_service.h"
#include "services/hmi_editor_service.h"
#include "services/hmi_navigation_service.h"
#include "services/hmi_runtime_service.h"
#include "services/logic_editor_service.h"
#include "services/offline_simulation_service.h"
#include "services/project_service.h"
#include "services/register_monitor_service.h"
#include "services/runtime_mode_service.h"
#include "ui_main_window.h"

#include <QListWidget>
#include <QLabel>
#include <QObject>
#include <QTimer>
#include <QWidget>

#include <utility>

namespace {

QString fromUtf8(const std::string &value)
{
return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
}

} // namespace

RuntimePanelController::RuntimePanelController(
QWidget &parent,
Ui::MainWindow &ui,
RuntimeModeService &runtime_mode_service,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
HmiRuntimeService &hmi_runtime_service,
LogicEditorService &logic_editor_service,
HmiNavigationService &hmi_navigation_service,
AlarmService &alarm_service,
RegisterMonitorService &register_monitor_service,
HmiEditorWidget &hmi_editor_widget,
LogicEditorWidget &logic_editor_widget,
QLabel &executor_status_label,
std::function<std::string()> current_logic_id,
std::function<void(const std::string &)> select_logic,
std::function<void(const std::string &)> show_logic_properties,
StatusReporter status_reporter,
OutputReporter output_reporter)
: parent_(parent),
ui_(ui),
runtime_mode_service_(runtime_mode_service),
project_service_(project_service),
hmi_editor_service_(hmi_editor_service),
hmi_runtime_service_(hmi_runtime_service),
logic_editor_service_(logic_editor_service),
hmi_navigation_service_(hmi_navigation_service),
alarm_service_(alarm_service),
register_monitor_service_(register_monitor_service),
hmi_editor_widget_(hmi_editor_widget),
logic_editor_widget_(logic_editor_widget),
executor_status_label_(executor_status_label),
current_logic_id_(std::move(current_logic_id)),
select_logic_(std::move(select_logic)),
show_logic_properties_(std::move(show_logic_properties)),
status_reporter_(std::move(status_reporter)),
output_reporter_(std::move(output_reporter))
{
}

void RuntimePanelController::configure()
{
runtime_monitor_widget_ = new RuntimeMonitorWidget(
hmi_editor_service_,
hmi_runtime_service_,
logic_editor_service_,
project_service_,
hmi_navigation_service_,
alarm_service_,
register_monitor_service_,
ui_.runtimeMonitorTab);
runtime_monitor_widget_->setObjectName(QStringLiteral("runtimeMonitorWidget"));
ui_.runtimeMonitorLayout->addWidget(runtime_monitor_widget_);
QObject::connect(runtime_monitor_widget_->freeMonitorWidget(),
&FreeMonitorWidget::monitorAddressesChanged,
&parent_, [this]
{
runtime_mode_service_.setMonitorAddresses(
register_monitor_service_.addresses());
runtime_monitor_widget_->refreshValues(
runtime_mode_service_.mode(),
runtime_mode_service_.plcConnectionState());
});
QObject::connect(runtime_monitor_widget_->freeMonitorWidget(),
&FreeMonitorWidget::operationMessage,
&parent_, [this](const QString &message)
{
status_reporter_(message, 4000);
});
QObject::connect(runtime_monitor_widget_, &RuntimeMonitorWidget::navigationFailed,
&parent_, [this](const QString &message)
{
status_reporter_(message, 5000);
output_reporter_(QObject::tr("页面导航失败:%1").arg(message));
});
ui_.editorTabWidget->setTabVisible(2, false);

runtime_refresh_timer_ = new QTimer(&parent_);
runtime_refresh_timer_->setInterval(150);
QObject::connect(runtime_refresh_timer_, &QTimer::timeout,
&parent_, [this] { handleRuntimeTimer(); });
runtime_refresh_timer_->start();
QObject::connect(&runtime_mode_service_.offlineSimulationService(),
&OfflineSimulationService::stateChanged,
&parent_, [this] { handleSimulationStateChanged(); },
Qt::QueuedConnection);
QObject::connect(&runtime_mode_service_.offlineSimulationService(),
&OfflineSimulationService::scanCompleted,
&parent_, [this] { handleScanCompleted(); },
Qt::QueuedConnection);
}

RuntimeMonitorWidget *RuntimePanelController::runtimeMonitorWidget() const
{
return runtime_monitor_widget_;
}

void RuntimePanelController::updateSimulationUi(bool report_fault)
{
const bool offline = runtime_mode_service_.mode()
== ApplicationMode::OfflineRunning;
const bool online = runtime_mode_service_.mode()
== ApplicationMode::OnlineRunning;
const bool plc_connected = runtime_mode_service_.plcConnectionState()
== PlcConnectionState::Connected;
const SimulationState state = runtime_mode_service_.simulationState();
const bool write_enabled = (online && plc_connected)
|| (offline && state == SimulationState::Running);
hmi_editor_widget_.setRuntimeWriteEnabled(write_enabled);
if (runtime_monitor_widget_ != nullptr)
{
runtime_monitor_widget_->setHmiWriteEnabled(write_enabled);
runtime_monitor_widget_->refreshValues(
runtime_mode_service_.mode(),
runtime_mode_service_.plcConnectionState());
}

switch (state)
{
case SimulationState::Running:
{
executor_status_label_.setText(
QObject::tr("逻辑执行器:运行(%1 次)")
.arg(runtime_mode_service_.successfulScanCount()));
break;
}
case SimulationState::Faulted:
{
executor_status_label_.setText(QObject::tr("逻辑执行器:故障"));
if (!report_fault)
{
break;
}
const LogicScanResult &error = runtime_mode_service_.simulationError();
if (!error.logicId.empty()
&& logic_editor_service_.findLogic(error.logicId) != nullptr)
{
select_logic_(error.logicId);
}
const std::string logic_id = current_logic_id_();
logic_editor_widget_.setLogicId(logic_id);
if (runtime_monitor_widget_ != nullptr)
{
runtime_monitor_widget_->setProjectObjects(
hmi_navigation_service_.currentPageId(), logic_id);
}
logic_editor_widget_.setRuntimeTrace(
runtime_mode_service_.offlineSimulationService()
.traceSnapshot().forLogic(logic_id),
error.nodeId);
if (runtime_monitor_widget_ != nullptr)
{
runtime_monitor_widget_->setLogicTrace(
runtime_mode_service_.offlineSimulationService().traceSnapshot(),
error.nodeId);
}
if (!error.nodeId.empty())
{
logic_editor_widget_.selectNode(error.nodeId);
show_logic_properties_(error.nodeId);
}
QString message = QObject::tr("离线仿真异常:%1").arg(fromUtf8(error.message));
if (!error.logicId.empty())
{
message += QObject::tr(",逻辑 %1").arg(fromUtf8(error.logicId));
}
if (!error.rungId.empty())
{
message += QObject::tr(",网络 %1").arg(fromUtf8(error.rungId));
}
if (!error.nodeId.empty())
{
message += QObject::tr(",节点 %1").arg(fromUtf8(error.nodeId));
}
status_reporter_(message, 0);
output_reporter_(message);
break;
}
case SimulationState::Stopped:
default:
{
executor_status_label_.setText(QObject::tr("逻辑执行器:停止"));
break;
}
}
}

void RuntimePanelController::handleRuntimeTimer()
{
if (runtime_mode_service_.mode() == ApplicationMode::Editing)
{
return;
}
alarm_service_.refresh();
hmi_editor_widget_.refreshRuntimeValues();
if (runtime_monitor_widget_ != nullptr)
{
runtime_monitor_widget_->refreshValues(
runtime_mode_service_.mode(),
runtime_mode_service_.plcConnectionState());
}
updateSimulationUi(false);
}

void RuntimePanelController::handleSimulationStateChanged()
{
updateSimulationUi(true);
}

void RuntimePanelController::handleScanCompleted()
{
const std::string logic_id = current_logic_id_();
logic_editor_widget_.setRuntimeTrace(
runtime_mode_service_.offlineSimulationService()
.traceSnapshot().forLogic(logic_id));
if (runtime_monitor_widget_ != nullptr)
{
runtime_monitor_widget_->setLogicTrace(
runtime_mode_service_.offlineSimulationService().traceSnapshot());
}
}

+ 88
- 0
app/src/ui/runtime_panel_controller.h View File

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

#include "domain/runtime_state.h"
#include "services/plc_communication_gateway.h"

#include <QString>

#include <functional>
#include <memory>
#include <string>

class AlarmService;
class HmiEditorService;
class HmiEditorWidget;
class HmiNavigationService;
class HmiRuntimeService;
class LogicEditorService;
class LogicEditorWidget;
class QLabel;
class ProjectService;
class RegisterMonitorService;
class RuntimeModeService;
class RuntimeMonitorWidget;
class QTimer;
class QWidget;

namespace Ui {
class MainWindow;
}

// 组织运行监控工作台、运行刷新定时器和离线执行器反馈
class RuntimePanelController final
{
public:
using StatusReporter = std::function<void(
const QString &message, int timeout_ms)>;
using OutputReporter = std::function<void(const QString &message)>;

RuntimePanelController(
QWidget &parent,
Ui::MainWindow &ui,
RuntimeModeService &runtime_mode_service,
ProjectService &project_service,
HmiEditorService &hmi_editor_service,
HmiRuntimeService &hmi_runtime_service,
LogicEditorService &logic_editor_service,
HmiNavigationService &hmi_navigation_service,
AlarmService &alarm_service,
RegisterMonitorService &register_monitor_service,
HmiEditorWidget &hmi_editor_widget,
LogicEditorWidget &logic_editor_widget,
QLabel &executor_status_label,
std::function<std::string()> current_logic_id,
std::function<void(const std::string &)> select_logic,
std::function<void(const std::string &)> show_logic_properties,
StatusReporter status_reporter,
OutputReporter output_reporter);

void configure();
void updateSimulationUi(bool report_fault);
RuntimeMonitorWidget *runtimeMonitorWidget() const;

private:
void handleRuntimeTimer();
void handleSimulationStateChanged();
void handleScanCompleted();

QWidget &parent_;
Ui::MainWindow &ui_;
RuntimeModeService &runtime_mode_service_;
ProjectService &project_service_;
HmiEditorService &hmi_editor_service_;
HmiRuntimeService &hmi_runtime_service_;
LogicEditorService &logic_editor_service_;
HmiNavigationService &hmi_navigation_service_;
AlarmService &alarm_service_;
RegisterMonitorService &register_monitor_service_;
HmiEditorWidget &hmi_editor_widget_;
LogicEditorWidget &logic_editor_widget_;
QLabel &executor_status_label_;
std::function<std::string()> current_logic_id_;
std::function<void(const std::string &)> select_logic_;
std::function<void(const std::string &)> show_logic_properties_;
StatusReporter status_reporter_;
OutputReporter output_reporter_;
RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr;
QTimer *runtime_refresh_timer_ = nullptr;
};

+ 6
- 0
app/tests/main_window_tests.pro View File

@@ -16,6 +16,9 @@ SOURCES += \
../src/ui/plc_connection_dialog.cpp \
../src/ui/free_monitor_widget.cpp \
../src/ui/runtime_monitor_widget.cpp \
../src/ui/project_workspace_controller.cpp \
../src/ui/property_panel_controller.cpp \
../src/ui/runtime_panel_controller.cpp \
../src/ui/hmi_editor_widget.cpp \
../src/ui/logic_editor_widget.cpp \
../src/ui/logic_instruction_dialog.cpp \
@@ -48,6 +51,9 @@ HEADERS += \
../src/ui/plc_connection_dialog.h \
../src/ui/free_monitor_widget.h \
../src/ui/runtime_monitor_widget.h \
../src/ui/project_workspace_controller.h \
../src/ui/property_panel_controller.h \
../src/ui/runtime_panel_controller.h \
../src/ui/hmi_editor_widget.h \
../src/ui/logic_editor_widget.h \
../src/ui/logic_instruction_dialog.h \


Loading…
Cancel
Save