Bladeren bron

feat: 增加独立HMI运行窗口

main
suyu 1 maand geleden
bovenliggende
commit
45f8db7780
11 gewijzigde bestanden met toevoegingen van 552 en 11 verwijderingen
  1. +3
    -0
      app/integrated_platform.pro
  2. +134
    -0
      app/src/ui/hmi_runtime_window.cpp
  3. +63
    -0
      app/src/ui/hmi_runtime_window.h
  4. +49
    -0
      app/src/ui/hmi_runtime_window.ui
  5. +14
    -4
      app/src/ui/main_window.cpp
  6. +14
    -2
      app/src/ui/runtime_monitor_widget.cpp
  7. +2
    -0
      app/src/ui/runtime_monitor_widget.h
  8. +103
    -4
      app/src/ui/runtime_panel_controller.cpp
  9. +16
    -1
      app/src/ui/runtime_panel_controller.h
  10. +151
    -0
      app/tests/main_window_tests.cpp
  11. +3
    -0
      app/tests/main_window_tests.pro

+ 3
- 0
app/integrated_platform.pro Bestand weergeven

@@ -22,6 +22,7 @@ SOURCES += \
src/ui/register_comment_dialog.cpp \
src/ui/plc_connection_dialog.cpp \
src/ui/free_monitor_widget.cpp \
src/ui/hmi_runtime_window.cpp \
src/ui/runtime_monitor_widget.cpp \
src/ui/toolbar_icon_factory.cpp \
src/ui/project_workspace_controller.cpp \
@@ -63,6 +64,7 @@ HEADERS += \
src/ui/register_comment_dialog.h \
src/ui/plc_connection_dialog.h \
src/ui/free_monitor_widget.h \
src/ui/hmi_runtime_window.h \
src/ui/runtime_monitor_widget.h \
src/ui/toolbar_icon_factory.h \
src/ui/project_workspace_controller.h \
@@ -108,4 +110,5 @@ FORMS += \
src/ui/register_comment_dialog.ui \
src/ui/plc_connection_dialog.ui \
src/ui/free_monitor_widget.ui \
src/ui/hmi_runtime_window.ui \
src/ui/runtime_monitor_widget.ui

+ 134
- 0
app/src/ui/hmi_runtime_window.cpp Bestand weergeven

@@ -0,0 +1,134 @@
#include "hmi_runtime_window.h"

#include "hmi_editor_widget.h"
#include "services/alarm_service.h"
#include "services/hmi_editor_service.h"
#include "services/hmi_runtime_service.h"
#include "services/project_service.h"
#include "toolbar_icon_factory.h"
#include "ui_hmi_runtime_window.h"

#include <QCloseEvent>
#include <QToolButton>

#include <algorithm>

namespace {

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

} // namespace

HmiRuntimeWindow::HmiRuntimeWindow(
HmiEditorService &hmi_editor_service,
HmiRuntimeService &hmi_runtime_service,
AlarmService &alarm_service,
ProjectService &project_service,
QWidget *parent)
: QMainWindow(parent),
ui_(std::make_unique<Ui::HmiRuntimeWindow>()),
project_service_(project_service)
{
ui_->setupUi(this);
setObjectName(QStringLiteral("hmiRuntimeWindow"));
ui_->exitRuntimeButton->setIcon(makeUiIcon(UiIcon::Exit));
hmi_view_ = new HmiEditorWidget(
hmi_editor_service,
hmi_runtime_service,
alarm_service,
ui_->hmiViewContainer);
hmi_view_->setObjectName(QStringLiteral("hmiRuntimeHmiView"));
hmi_view_->setEditingEnabled(false);
ui_->hmiViewLayout->addWidget(hmi_view_);

connect(hmi_view_, &HmiEditorWidget::pageNavigationRequested,
this, &HmiRuntimeWindow::pageNavigationRequested);
connect(ui_->exitRuntimeButton, &QToolButton::clicked,
this, &HmiRuntimeWindow::exitRequested);
}

HmiRuntimeWindow::~HmiRuntimeWindow() = default;

void HmiRuntimeWindow::setRuntimePage(const std::string &page_id)
{
hmi_view_->setPageId(page_id);
const auto page = std::find_if(
project_service_.project().hmiPages.cbegin(),
project_service_.project().hmiPages.cend(),
[&page_id](const HmiPage &candidate) { return candidate.id == page_id; });
ui_->runtimePageLabel->setText(
page == project_service_.project().hmiPages.cend()
? tr("未选择页面") : fromUtf8(page->name));
}

void HmiRuntimeWindow::reloadRuntimePage()
{
hmi_view_->reloadPage();
}

void HmiRuntimeWindow::setMode(
ApplicationMode mode, PlcConnectionState plc_state)
{
const bool offline = mode == ApplicationMode::OfflineRunning;
const bool online = mode == ApplicationMode::OnlineRunning;
runtime_active_ = offline || online;
hmi_view_->setRuntimeActive(runtime_active_);
if (!runtime_active_)
{
hmi_view_->setRuntimeWriteEnabled(false);
}
ui_->modeLabel->setText(
offline ? tr("离线仿真 · 虚拟 M/D")
: online && plc_state == PlcConnectionState::Connected
? tr("真机运行 · PLC 已连接")
: online ? tr("真机运行 · PLC 通信不可用")
: tr("当前未运行"));
}

void HmiRuntimeWindow::setHmiWriteEnabled(bool enabled)
{
hmi_view_->setRuntimeWriteEnabled(runtime_active_ && enabled);
}

void HmiRuntimeWindow::refreshValues()
{
hmi_view_->refreshRuntimeValues();
}

void HmiRuntimeWindow::showForRuntime()
{
if (!isVisible())
{
showMaximized();
}
else
{
raise();
activateWindow();
}
}

void HmiRuntimeWindow::hideForEditing()
{
hide();
}

void HmiRuntimeWindow::closeForApplicationExit()
{
runtime_active_ = false;
close();
}

void HmiRuntimeWindow::closeEvent(QCloseEvent *event)
{
if (runtime_active_)
{
emit exitRequested();
event->ignore();
return;
}
QMainWindow::closeEvent(event);
}

+ 63
- 0
app/src/ui/hmi_runtime_window.h Bestand weergeven

@@ -0,0 +1,63 @@
/**
* @file hmi_runtime_window.h
* @brief 定义面向操作员的独立 HMI 运行窗口
* @author suyu
*/

#pragma once

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

#include <QMainWindow>

#include <memory>
#include <string>

namespace Ui {
class HmiRuntimeWindow;
}

class AlarmService;
class HmiEditorService;
class HmiEditorWidget;
class HmiRuntimeService;
class ProjectService;
class QCloseEvent;

// 独立显示运行 HMI,工程编辑和运行模式切换仍由外层控制器负责
class HmiRuntimeWindow final : public QMainWindow
{
Q_OBJECT

public:
HmiRuntimeWindow(
HmiEditorService &hmi_editor_service,
HmiRuntimeService &hmi_runtime_service,
AlarmService &alarm_service,
ProjectService &project_service,
QWidget *parent = nullptr);
~HmiRuntimeWindow() override;

void setRuntimePage(const std::string &page_id);
void reloadRuntimePage();
void setMode(ApplicationMode mode, PlcConnectionState plc_state);
void setHmiWriteEnabled(bool enabled);
void refreshValues();
void showForRuntime();
void hideForEditing();
void closeForApplicationExit();

signals:
void pageNavigationRequested(const QString &target_page_id);
void exitRequested();

protected:
void closeEvent(QCloseEvent *event) override;

private:
std::unique_ptr<Ui::HmiRuntimeWindow> ui_;
ProjectService &project_service_;
HmiEditorWidget *hmi_view_ = nullptr;
bool runtime_active_ = false;
};

+ 49
- 0
app/src/ui/hmi_runtime_window.ui Bestand weergeven

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HmiRuntimeWindow</class>
<widget class="QMainWindow" name="HmiRuntimeWindow">
<property name="geometry">
<rect><x>0</x><y>0</y><width>1280</width><height>800</height></rect>
</property>
<property name="windowTitle"><string>HMI 运行</string></property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="centralLayout">
<property name="spacing"><number>0</number></property>
<property name="leftMargin"><number>0</number></property>
<property name="topMargin"><number>0</number></property>
<property name="rightMargin"><number>0</number></property>
<property name="bottomMargin"><number>0</number></property>
<item>
<widget class="QFrame" name="runtimeHeaderFrame">
<property name="minimumSize"><size><width>0</width><height>42</height></size></property>
<property name="maximumSize"><size><width>16777215</width><height>42</height></size></property>
<property name="frameShape"><enum>QFrame::StyledPanel</enum></property>
<layout class="QHBoxLayout" name="runtimeHeaderLayout">
<property name="leftMargin"><number>12</number></property>
<property name="topMargin"><number>4</number></property>
<property name="rightMargin"><number>8</number></property>
<property name="bottomMargin"><number>4</number></property>
<item><widget class="QLabel" name="titleLabel"><property name="text"><string>HMI 运行</string></property><property name="styleSheet"><string notr="true">font-weight: 600;</string></property></widget></item>
<item><spacer name="headerSpacer"><property name="orientation"><enum>Qt::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item>
<item><widget class="QLabel" name="runtimePageLabel"><property name="text"><string>未选择页面</string></property><property name="alignment"><set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set></property></widget></item>
<item><widget class="QLabel" name="modeLabel"><property name="minimumSize"><size><width>170</width><height>0</height></size></property><property name="text"><string>当前未运行</string></property><property name="alignment"><set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set></property></widget></item>
<item><widget class="QToolButton" name="exitRuntimeButton"><property name="toolTip"><string>退出运行</string></property><property name="accessibleName"><string>退出运行</string></property><property name="text"><string>退出运行</string></property><property name="toolButtonStyle"><enum>Qt::ToolButtonIconOnly</enum></property></widget></item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="hmiViewContainer" native="true">
<layout class="QVBoxLayout" name="hmiViewLayout">
<property name="leftMargin"><number>0</number></property>
<property name="topMargin"><number>0</number></property>
<property name="rightMargin"><number>0</number></property>
<property name="bottomMargin"><number>0</number></property>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

+ 14
- 4
app/src/ui/main_window.cpp Bestand weergeven

@@ -325,6 +325,10 @@ void MainWindow::closeEvent(QCloseEvent *event)
event->ignore();
return;
}
if (runtime_panel_controller_ != nullptr)
{
runtime_panel_controller_->closeForApplicationExit();
}
QMainWindow::closeEvent(event);
}

@@ -854,6 +858,10 @@ void MainWindow::configureRuntimeMonitor()
[this](const QString &message)
{
appendOutputMessage(message);
},
[this]
{
requestMode(ApplicationMode::Editing);
});
runtime_panel_controller_->configure();
runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget();
@@ -1552,15 +1560,17 @@ void MainWindow::updateModeUi(const QString &message)
statusBar()->showMessage(fromUtf8(navigation.message), 5000);
}
}
runtime_monitor_widget_->setProjectObjects(
hmi_navigation_service_->currentPageId(), current_logic_id_);
runtime_monitor_widget_->setMode(mode, runtime_mode_service_.plcConnectionState());
runtime_panel_controller_->enterRuntime(
hmi_navigation_service_->currentPageId(),
current_logic_id_,
mode,
runtime_mode_service_.plcConnectionState());
ui_->editorTabWidget->setCurrentWidget(ui_->runtimeMonitorTab);
}
else
{
hmi_navigation_service_->stop();
runtime_monitor_widget_->setMode(
runtime_panel_controller_->leaveRuntime(
mode, runtime_mode_service_.plcConnectionState());
if (ui_->editorTabWidget->currentWidget() == ui_->runtimeMonitorTab)
{


+ 14
- 2
app/src/ui/runtime_monitor_widget.cpp Bestand weergeven

@@ -97,7 +97,7 @@ RuntimeMonitorWidget::~RuntimeMonitorWidget() = default;
void RuntimeMonitorWidget::setProjectObjects(
const std::string &page_id, const std::string &logic_id)
{
showRuntimePage(page_id);
setRuntimePage(page_id);

const QSignalBlocker blocker(ui_->runtimeLogicComboBox);
ui_->runtimeLogicComboBox->clear();
@@ -120,6 +120,11 @@ void RuntimeMonitorWidget::setProjectObjects(
: std::string{});
}

void RuntimeMonitorWidget::setRuntimePage(const std::string &page_id)
{
showRuntimePage(page_id);
}

void RuntimeMonitorWidget::setMode(
ApplicationMode mode, PlcConnectionState plc_state)
{
@@ -127,7 +132,10 @@ void RuntimeMonitorWidget::setMode(
const bool online = mode == ApplicationMode::OnlineRunning;
ui_->logicFrame->setVisible(offline);
hmi_view_->setRuntimeActive(offline || online);
setHmiWriteEnabled(offline || (online && plc_state == PlcConnectionState::Connected));
// 工程师诊断视图只投影 HMI,实际运行输入统一由独立 HMI 窗口处理
setHmiWriteEnabled(false);
setFreeMonitorWriteEnabled(offline
|| (online && plc_state == PlcConnectionState::Connected));
if (!offline)
{
logic_view_->clearRuntimeTrace();
@@ -144,6 +152,10 @@ void RuntimeMonitorWidget::setMode(
void RuntimeMonitorWidget::setHmiWriteEnabled(bool enabled)
{
hmi_view_->setRuntimeWriteEnabled(enabled);
}

void RuntimeMonitorWidget::setFreeMonitorWriteEnabled(bool enabled)
{
free_monitor_widget_->setWriteEnabled(enabled);
}



+ 2
- 0
app/src/ui/runtime_monitor_widget.h Bestand weergeven

@@ -41,9 +41,11 @@ public:
~RuntimeMonitorWidget() override;

void setProjectObjects(const std::string &page_id, const std::string &logic_id);
void setRuntimePage(const std::string &page_id);
void setMode(ApplicationMode mode, PlcConnectionState plc_state);
void refreshValues(ApplicationMode mode, PlcConnectionState plc_state);
void setHmiWriteEnabled(bool enabled);
void setFreeMonitorWriteEnabled(bool enabled);
void setLogicTrace(const LogicTraceSnapshot &trace, const std::string &fault_node_id = {});
FreeMonitorWidget *freeMonitorWidget() const;
std::string selectedLogicId() const;


+ 103
- 4
app/src/ui/runtime_panel_controller.cpp Bestand weergeven

@@ -2,6 +2,7 @@

#include "free_monitor_widget.h"
#include "hmi_editor_widget.h"
#include "hmi_runtime_window.h"
#include "logic_editor_widget.h"
#include "runtime_monitor_widget.h"
#include "services/alarm_service.h"
@@ -50,7 +51,8 @@ RuntimePanelController::RuntimePanelController(
std::function<void(const std::string &)> select_logic,
std::function<void(const std::string &)> show_logic_properties,
StatusReporter status_reporter,
OutputReporter output_reporter)
OutputReporter output_reporter,
RuntimeExitRequester runtime_exit_requester)
: parent_(parent),
ui_(ui),
runtime_mode_service_(runtime_mode_service),
@@ -68,10 +70,13 @@ RuntimePanelController::RuntimePanelController(
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))
output_reporter_(std::move(output_reporter)),
runtime_exit_requester_(std::move(runtime_exit_requester))
{
}

RuntimePanelController::~RuntimePanelController() = default;

void RuntimePanelController::configure()
{
runtime_monitor_widget_ = new RuntimeMonitorWidget(
@@ -85,6 +90,35 @@ void RuntimePanelController::configure()
ui_.runtimeMonitorTab);
runtime_monitor_widget_->setObjectName(QStringLiteral("runtimeMonitorWidget"));
ui_.runtimeMonitorLayout->addWidget(runtime_monitor_widget_);
hmi_runtime_window_ = std::make_unique<HmiRuntimeWindow>(
hmi_editor_service_,
hmi_runtime_service_,
alarm_service_,
project_service_);
QObject::connect(hmi_runtime_window_.get(),
&HmiRuntimeWindow::pageNavigationRequested,
&parent_, [this](const QString &target_page_id)
{
const HmiNavigationResult result = hmi_navigation_service_.navigateTo(
target_page_id.toUtf8().toStdString());
if (!result.succeeded)
{
const QString message = fromUtf8(result.message);
status_reporter_(message, 5000);
output_reporter_(QObject::tr("页面导航失败:%1").arg(message));
return;
}
runtime_monitor_widget_->setRuntimePage(result.pageId);
hmi_runtime_window_->setRuntimePage(result.pageId);
});
QObject::connect(hmi_runtime_window_.get(), &HmiRuntimeWindow::exitRequested,
&parent_, [this]
{
if (runtime_exit_requester_)
{
runtime_exit_requester_();
}
});
QObject::connect(runtime_monitor_widget_->freeMonitorWidget(),
&FreeMonitorWidget::monitorAddressesChanged,
&parent_, [this]
@@ -107,6 +141,15 @@ void RuntimePanelController::configure()
status_reporter_(message, 5000);
output_reporter_(QObject::tr("页面导航失败:%1").arg(message));
});
QObject::connect(runtime_monitor_widget_, &RuntimeMonitorWidget::runtimePageChanged,
&parent_, [this](const QString &page_id)
{
if (hmi_runtime_window_ != nullptr)
{
hmi_runtime_window_->setRuntimePage(
page_id.toUtf8().toStdString());
}
});
ui_.editorTabWidget->setTabVisible(2, false);

runtime_refresh_timer_ = new QTimer(&parent_);
@@ -129,6 +172,46 @@ RuntimeMonitorWidget *RuntimePanelController::runtimeMonitorWidget() const
return runtime_monitor_widget_;
}

HmiRuntimeWindow *RuntimePanelController::hmiRuntimeWindow() const
{
return hmi_runtime_window_.get();
}

void RuntimePanelController::enterRuntime(
const std::string &page_id,
const std::string &logic_id,
ApplicationMode mode,
PlcConnectionState plc_state)
{
if (!runtime_session_active_)
{
runtime_monitor_widget_->setProjectObjects(page_id, logic_id);
hmi_runtime_window_->setRuntimePage(page_id);
hmi_runtime_window_->reloadRuntimePage();
runtime_session_active_ = true;
}
runtime_monitor_widget_->setMode(mode, plc_state);
hmi_runtime_window_->setMode(mode, plc_state);
hmi_runtime_window_->showForRuntime();
}

void RuntimePanelController::leaveRuntime(
ApplicationMode mode, PlcConnectionState plc_state)
{
runtime_session_active_ = false;
runtime_monitor_widget_->setMode(mode, plc_state);
hmi_runtime_window_->setMode(mode, plc_state);
hmi_runtime_window_->hideForEditing();
}

void RuntimePanelController::closeForApplicationExit()
{
if (hmi_runtime_window_ != nullptr)
{
hmi_runtime_window_->closeForApplicationExit();
}
}

void RuntimePanelController::updateSimulationUi(bool report_fault)
{
const bool offline = runtime_mode_service_.mode()
@@ -140,14 +223,21 @@ void RuntimePanelController::updateSimulationUi(bool report_fault)
const SimulationState state = runtime_mode_service_.simulationState();
const bool write_enabled = (online && plc_connected)
|| (offline && state == SimulationState::Running);
hmi_editor_widget_.setRuntimeWriteEnabled(write_enabled);
// 操作员输入只允许从独立 HMI 窗口发出,主窗口的画布全部只读
hmi_editor_widget_.setRuntimeWriteEnabled(false);
if (runtime_monitor_widget_ != nullptr)
{
runtime_monitor_widget_->setHmiWriteEnabled(write_enabled);
runtime_monitor_widget_->setHmiWriteEnabled(false);
runtime_monitor_widget_->setFreeMonitorWriteEnabled(write_enabled);
runtime_monitor_widget_->refreshValues(
runtime_mode_service_.mode(),
runtime_mode_service_.plcConnectionState());
}
if (hmi_runtime_window_ != nullptr)
{
hmi_runtime_window_->setHmiWriteEnabled(write_enabled);
hmi_runtime_window_->refreshValues();
}

switch (state)
{
@@ -178,6 +268,11 @@ void RuntimePanelController::updateSimulationUi(bool report_fault)
runtime_monitor_widget_->setProjectObjects(
hmi_navigation_service_.currentPageId(), logic_id);
}
if (hmi_runtime_window_ != nullptr)
{
hmi_runtime_window_->setRuntimePage(
hmi_navigation_service_.currentPageId());
}
logic_editor_widget_.setRuntimeTrace(
runtime_mode_service_.offlineSimulationService()
.traceSnapshot().forLogic(logic_id),
@@ -233,6 +328,10 @@ void RuntimePanelController::handleRuntimeTimer()
runtime_mode_service_.mode(),
runtime_mode_service_.plcConnectionState());
}
if (hmi_runtime_window_ != nullptr)
{
hmi_runtime_window_->refreshValues();
}
updateSimulationUi(false);
}



+ 16
- 1
app/src/ui/runtime_panel_controller.h Bestand weergeven

@@ -12,6 +12,7 @@
class AlarmService;
class HmiEditorService;
class HmiEditorWidget;
class HmiRuntimeWindow;
class HmiNavigationService;
class HmiRuntimeService;
class LogicEditorService;
@@ -35,6 +36,7 @@ public:
using StatusReporter = std::function<void(
const QString &message, int timeout_ms)>;
using OutputReporter = std::function<void(const QString &message)>;
using RuntimeExitRequester = std::function<void()>;

RuntimePanelController(
QWidget &parent,
@@ -54,11 +56,21 @@ public:
std::function<void(const std::string &)> select_logic,
std::function<void(const std::string &)> show_logic_properties,
StatusReporter status_reporter,
OutputReporter output_reporter);
OutputReporter output_reporter,
RuntimeExitRequester runtime_exit_requester);
~RuntimePanelController();

void configure();
void enterRuntime(
const std::string &page_id,
const std::string &logic_id,
ApplicationMode mode,
PlcConnectionState plc_state);
void leaveRuntime(ApplicationMode mode, PlcConnectionState plc_state);
void closeForApplicationExit();
void updateSimulationUi(bool report_fault);
RuntimeMonitorWidget *runtimeMonitorWidget() const;
HmiRuntimeWindow *hmiRuntimeWindow() const;

private:
void handleRuntimeTimer();
@@ -83,6 +95,9 @@ private:
std::function<void(const std::string &)> show_logic_properties_;
StatusReporter status_reporter_;
OutputReporter output_reporter_;
RuntimeExitRequester runtime_exit_requester_;
RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr;
std::unique_ptr<HmiRuntimeWindow> hmi_runtime_window_;
QTimer *runtime_refresh_timer_ = nullptr;
bool runtime_session_active_ = false;
};

+ 151
- 0
app/tests/main_window_tests.cpp Bestand weergeven

@@ -14,6 +14,7 @@
#include "services/register_comment_service.h"
#include "ui/hmi_editor_widget.h"
#include "ui/free_monitor_widget.h"
#include "ui/hmi_runtime_window.h"
#include "ui/logic_editor_widget.h"
#include "ui/main_window.h"
#include "ui/plc_connection_dialog.h"
@@ -278,6 +279,19 @@ ObjectType *requiredChild(MainWindow &window, const char *name)
return child;
}

HmiRuntimeWindow *findHmiRuntimeWindow()
{
for (QWidget *widget : QApplication::topLevelWidgets())
{
auto *runtime_window = qobject_cast<HmiRuntimeWindow *>(widget);
if (runtime_window != nullptr)
{
return runtime_window;
}
}
return nullptr;
}

QColor renderedColorAt(HmiEditorWidget &view, const QPoint &viewport_position)
{
QImage image(view.viewport()->size(), QImage::Format_ARGB32_Premultiplied);
@@ -1207,6 +1221,142 @@ void testModeActionsControlEditingAvailability()
"rejected online running must restore the editing action");
}

void testIndependentHmiRuntimeWindowLifecycle()
{
TestProjectStorage storage;
ProjectService project_service(storage);
HmiEditorService editor_service(project_service);
LogicEditorService logic_editor_service(project_service);
const std::string main_page_id = editor_service.ensureDefaultPage().id;
const std::string settings_page_id = editor_service.addPage("Settings").id;
const HmiEditorResult jump_result = editor_service.addControl(
main_page_id, HmiControlType::PageJump);
HmiControl jump = *editor_service.findControl(main_page_id, jump_result.id);
jump.pageJump = HmiPageJumpConfig{settings_page_id};
require(editor_service.updateControl(
main_page_id, jump_result.id, jump).succeeded,
"runtime-window fixture must configure a PageJump target");
const HmiEditorResult return_jump_result = editor_service.addControl(
settings_page_id, HmiControlType::PageJump);
HmiControl return_jump = *editor_service.findControl(
settings_page_id, return_jump_result.id);
return_jump.pageJump = HmiPageJumpConfig{main_page_id};
require(editor_service.updateControl(
settings_page_id, return_jump_result.id, return_jump).succeeded,
"runtime-window fixture must configure a PageJump back to the initial page");
VirtualRegisterRepository repository;
HmiRuntimeService runtime_service(repository);
AlarmEditorService alarm_editor_service(project_service);
AlarmService alarm_service(project_service, repository);
RegisterCommentService register_comment_service(project_service);
OfflineSimulationService simulation_service(repository);
RuntimeModeService mode_service(project_service, simulation_service);
RegisterMonitorService monitor_service(repository);
HmiNavigationService navigation_service(project_service);
MainWindow window(
mode_service,
project_service,
editor_service,
logic_editor_service,
runtime_service,
alarm_editor_service,
alarm_service,
navigation_service,
register_comment_service,
monitor_service);
window.show();
QApplication::processEvents();

requiredChild<QAction>(window, "offlineModeAction")->trigger();
QApplication::processEvents();
HmiRuntimeWindow *runtime_window = findHmiRuntimeWindow();
require(mode_service.mode() == ApplicationMode::OfflineRunning,
"offline mode must remain the authoritative runtime state");
require(runtime_window != nullptr && runtime_window->isVisible(),
"entering offline running must show an independent HMI window");
require(runtime_window->findChild<HmiEditorWidget *>(
QStringLiteral("hmiRuntimeHmiView")) != nullptr,
"independent HMI window must contain a runtime-only HMI view");
HmiEditorWidget *runtime_hmi = runtime_window->findChild<HmiEditorWidget *>(
QStringLiteral("hmiRuntimeHmiView"));
QGraphicsItem *jump_item = nullptr;
for (QGraphicsItem *item : runtime_hmi->scene()->items())
{
if (item->zValue() >= 0.0)
{
jump_item = item;
break;
}
}
require(jump_item != nullptr,
"independent HMI window must project the configured PageJump");
QTest::mouseClick(runtime_hmi->viewport(), Qt::LeftButton, Qt::NoModifier,
runtime_hmi->mapFromScene(
jump_item->mapToScene(jump_item->boundingRect().center())));
QApplication::processEvents();
require(navigation_service.currentPageId() == settings_page_id,
"PageJump in the independent window must update the shared navigation session");
require(runtime_window->findChild<QLabel *>(QStringLiteral("runtimePageLabel"))
->text() == QStringLiteral("Settings"),
"independent window title must follow the navigated runtime page");
require(requiredChild<QLabel>(window, "runtimePageLabel")->text()
== QStringLiteral("Settings"),
"diagnostic runtime view must stay synchronized with the independent HMI page");
QGraphicsItem *return_jump_item = nullptr;
for (QGraphicsItem *item : runtime_hmi->scene()->items())
{
if (item->zValue() >= 0.0)
{
return_jump_item = item;
break;
}
}
require(return_jump_item != nullptr,
"the destination page must expose a PageJump back to the initial page");
QTest::mouseClick(runtime_hmi->viewport(), Qt::LeftButton, Qt::NoModifier,
runtime_hmi->mapFromScene(
return_jump_item->mapToScene(
return_jump_item->boundingRect().center())));
QApplication::processEvents();
require(navigation_service.currentPageId() == main_page_id,
"the independent HMI must return to the initial page before editing");
require(runtime_window->findChild<QLabel *>(QStringLiteral("runtimePageLabel"))
->text() == QStringLiteral("主操作页面"),
"the independent window must keep the initial page selected across sessions");

requiredChild<QAction>(window, "editingModeAction")->trigger();
QApplication::processEvents();
require(mode_service.mode() == ApplicationMode::Editing,
"editing action must return the runtime mode to editing");
require(!runtime_window->isVisible(),
"returning to editing must hide the independent HMI window");

requiredChild<QAction>(window, "addLabelAction")->trigger();
QApplication::processEvents();
require(editor_service.findPage(main_page_id)->controls.size() == 2U,
"editing after a runtime session must add the new control to the page");

requiredChild<QAction>(window, "offlineModeAction")->trigger();
QApplication::processEvents();
runtime_window = findHmiRuntimeWindow();
require(runtime_window != nullptr && runtime_window->isVisible(),
"the HMI window must be reusable for a later runtime session");
runtime_hmi = runtime_window->findChild<HmiEditorWidget *>(
QStringLiteral("hmiRuntimeHmiView"));
require(runtime_hmi != nullptr && runtime_hmi->scene()->items().size() == 3,
"a later runtime session must refresh the independent HMI page after an edit");
require(requiredChild<RuntimeMonitorWidget>(window, "runtimeMonitorWidget")
->findChild<HmiEditorWidget *>(QStringLiteral("runtimeHmiView"))
->scene()->items().size() == 3,
"the diagnostic HMI must keep projecting the edited page");
runtime_window->close();
QApplication::processEvents();
require(mode_service.mode() == ApplicationMode::Editing,
"closing the HMI window must request a transition to editing");
require(!runtime_window->isVisible(),
"closing the HMI window must not leave a visible runtime surface");
}

void testEdgeTimerPropertyEditingAndParallelMenu()
{
TestProjectStorage storage;
@@ -1733,6 +1883,7 @@ int main(int argc, char *argv[])
testRuntimeAlarmListInteraction();
testWindowTitleTracksUnsavedProjectChanges();
testModeActionsControlEditingAvailability();
testIndependentHmiRuntimeWindowLifecycle();
testEdgeTimerPropertyEditingAndParallelMenu();
testPlcConfigurationUsesDialog();
testRepeatedPlcStatusNotificationsAreCoalesced();


+ 3
- 0
app/tests/main_window_tests.pro Bestand weergeven

@@ -15,6 +15,7 @@ SOURCES += \
../src/ui/register_comment_dialog.cpp \
../src/ui/plc_connection_dialog.cpp \
../src/ui/free_monitor_widget.cpp \
../src/ui/hmi_runtime_window.cpp \
../src/ui/runtime_monitor_widget.cpp \
../src/ui/toolbar_icon_factory.cpp \
../src/ui/project_workspace_controller.cpp \
@@ -52,6 +53,7 @@ HEADERS += \
../src/ui/register_comment_dialog.h \
../src/ui/plc_connection_dialog.h \
../src/ui/free_monitor_widget.h \
../src/ui/hmi_runtime_window.h \
../src/ui/runtime_monitor_widget.h \
../src/ui/toolbar_icon_factory.h \
../src/ui/project_workspace_controller.h \
@@ -94,5 +96,6 @@ FORMS += \
../src/ui/register_comment_dialog.ui \
../src/ui/plc_connection_dialog.ui \
../src/ui/free_monitor_widget.ui \
../src/ui/hmi_runtime_window.ui \
../src/ui/runtime_monitor_widget.ui \
../src/ui/logic_instruction_dialog.ui

Laden…
Annuleren
Opslaan