diff --git a/app/src/services/alarm_service.cpp b/app/src/services/alarm_service.cpp index b04ef82..c6f3c19 100644 --- a/app/src/services/alarm_service.cpp +++ b/app/src/services/alarm_service.cpp @@ -29,21 +29,19 @@ void AlarmService::refresh() records_.begin(), records_.end(), [&definition](const AlarmRecord &record) { - return record.definitionId == definition.id && record.active; + return record.definitionId == definition.id; }); if (*active && current == records_.end()) { records_.insert( records_.begin(), - AlarmRecord{definition.id, definition.message, true, false, now, {}}); + AlarmRecord{definition.id, definition.message, false, now}); } else if (!*active && current != records_.end()) { - current->active = false; - current->clearedAt = now; + records_.erase(current); } } - trimHistory(); } bool AlarmService::acknowledge(const std::string &definition_id) @@ -52,7 +50,7 @@ bool AlarmService::acknowledge(const std::string &definition_id) records_.begin(), records_.end(), [&definition_id](const AlarmRecord &candidate) { - return candidate.definitionId == definition_id && candidate.active; + return candidate.definitionId == definition_id; }); if (record == records_.end()) { @@ -95,20 +93,3 @@ std::optional AlarmService::evaluate( } return std::nullopt; } - -void AlarmService::trimHistory() -{ - std::size_t history_count = static_cast(std::count_if( - records_.cbegin(), records_.cend(), - [](const AlarmRecord &record) { return !record.active; })); - for (auto record = records_.end(); - record != records_.begin() && history_count > kHistoryLimit;) - { - --record; - if (!record->active) - { - record = records_.erase(record); - --history_count; - } - } -} diff --git a/app/src/services/alarm_service.h b/app/src/services/alarm_service.h index e51ceb7..ec82ab8 100644 --- a/app/src/services/alarm_service.h +++ b/app/src/services/alarm_service.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -14,10 +13,8 @@ struct AlarmRecord { std::string definitionId; std::string message; - bool active = true; bool acknowledged = false; std::chrono::system_clock::time_point occurredAt; - std::optional clearedAt; }; class AlarmService @@ -34,9 +31,6 @@ public: private: std::optional evaluate(const AlarmDefinition &definition) const; - void trimHistory(); - - static constexpr std::size_t kHistoryLimit = 20; const ProjectService &project_service_; RegisterRepository &repository_; diff --git a/app/src/ui/hmi_editor_widget.cpp b/app/src/ui/hmi_editor_widget.cpp index 5afa64a..6dafe57 100644 --- a/app/src/ui/hmi_editor_widget.cpp +++ b/app/src/ui/hmi_editor_widget.cpp @@ -224,7 +224,7 @@ public: rect.height() - kAlarmHeaderHeight), Qt::AlignCenter, runtime_active_ ? QObject::tr("暂无报警") - : QObject::tr("运行时显示报警记录")); + : QObject::tr("运行时显示当前报警")); break; } for (int row = 0; row < visible_rows; ++row) @@ -237,18 +237,12 @@ public: kAlarmRowHeight); painter->fillRect( row_rect, - record.active - ? QColor(QStringLiteral("#fde8e8")) - : QColor(QStringLiteral("#f0f2f3"))); + QColor(QStringLiteral("#fde8e8"))); painter->setPen(QColor(QStringLiteral("#d3d8dc"))); painter->drawLine(row_rect.bottomLeft(), row_rect.bottomRight()); - painter->setPen(record.active - ? QColor(QStringLiteral("#8d1f1f")) - : QColor(QStringLiteral("#667078"))); - const QString state = record.active - ? (record.acknowledged ? QObject::tr("已确认") - : QObject::tr("未确认")) - : QObject::tr("已清除"); + painter->setPen(QColor(QStringLiteral("#8d1f1f"))); + const QString state = record.acknowledged + ? QObject::tr("已确认") : QObject::tr("未确认"); painter->drawText( QRectF(row_rect.left() + 5, row_rect.top(), 58, row_rect.height()), Qt::AlignVCenter | Qt::AlignLeft, @@ -341,6 +335,7 @@ public: page_hovered_ = false; button_pressed_ = false; } + updateAlarmVisibility(); update(); } @@ -356,6 +351,7 @@ public: void setAlarmRecords(const std::vector &records) { alarm_records_ = records; + updateAlarmVisibility(); update(); } @@ -394,7 +390,7 @@ protected: if (row >= 0 && row < static_cast(alarm_records_.size())) { const AlarmRecord &record = alarm_records_[static_cast(row)]; - if (record.active && !record.acknowledged && alarm_acknowledge_) + if (!record.acknowledged && alarm_acknowledge_) { alarm_acknowledge_(record.definitionId); } @@ -501,6 +497,17 @@ protected: } private: + void updateAlarmVisibility() + { + if (control_.type != HmiControlType::AlarmList) + { + return; + } + const bool has_active_alarm = !alarm_records_.empty(); + setVisible(editing_enabled_ || !runtime_active_ || has_active_alarm); + setZValue(runtime_active_ && has_active_alarm ? 1000.0 : 0.0); + } + // 控件本体范围保持领域坐标和尺寸语义不变 QRectF controlRect() const { @@ -631,6 +638,7 @@ void HmiEditorWidget::setRuntimeActive(bool active) if (control_item != nullptr) { control_item->setRuntimeValue(false, 0, false); + control_item->setAlarmRecords({}); } } } diff --git a/app/tests/alarm_service_tests.cpp b/app/tests/alarm_service_tests.cpp index fbf679a..0e64c8a 100644 --- a/app/tests/alarm_service_tests.cpp +++ b/app/tests/alarm_service_tests.cpp @@ -34,7 +34,7 @@ void require(bool condition, const std::string &message) } } -const AlarmRecord *findActiveRecord( +const AlarmRecord *findRecord( const AlarmService &service, const std::string &definition_id) { const auto &records = service.records(); @@ -42,7 +42,7 @@ const AlarmRecord *findActiveRecord( records.cbegin(), records.cend(), [&definition_id](const AlarmRecord &candidate) { - return candidate.definitionId == definition_id && candidate.active; + return candidate.definitionId == definition_id; }); return record == records.cend() ? nullptr : &*record; } @@ -92,29 +92,27 @@ void testAlarmDefinitionsAndRuntimeLifecycle() repository.writeWord(RegisterAddress{RegisterArea::D, 0}, 80); repository.writeWord(RegisterAddress{RegisterArea::D, 1}, 10); alarm_service.refresh(); - require(findActiveRecord(alarm_service, m_result.id) != nullptr - && findActiveRecord(alarm_service, high_result.id) != nullptr - && findActiveRecord(alarm_service, low_result.id) != nullptr, + require(findRecord(alarm_service, m_result.id) != nullptr + && findRecord(alarm_service, high_result.id) != nullptr + && findRecord(alarm_service, low_result.id) != nullptr, "all three alarm conditions must create active records"); require(alarm_service.acknowledge(m_result.id) - && findActiveRecord(alarm_service, m_result.id)->acknowledged, + && findRecord(alarm_service, m_result.id)->acknowledged, "an active alarm must be locally acknowledged"); repository.writeBit(RegisterAddress{RegisterArea::M, 0}, false); repository.writeWord(RegisterAddress{RegisterArea::D, 0}, 79); repository.writeWord(RegisterAddress{RegisterArea::D, 1}, 11); alarm_service.refresh(); - require(std::none_of( - alarm_service.records().cbegin(), - alarm_service.records().cend(), - [](const AlarmRecord &record) { return record.active; }), - "cleared conditions must move active alarms into history"); + require(alarm_service.records().empty(), + "cleared conditions must immediately remove active alarms"); repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true); alarm_service.refresh(); - require(findActiveRecord(alarm_service, m_result.id) != nullptr - && alarm_service.records().size() == 4, - "a repeated alarm must create a new occurrence and keep history"); + require(findRecord(alarm_service, m_result.id) != nullptr + && alarm_service.records().size() == 1 + && !alarm_service.records().front().acknowledged, + "a repeated alarm must create a new unacknowledged occurrence"); alarm_service.reset(); require(alarm_service.records().empty(), diff --git a/app/tests/main_window_tests.cpp b/app/tests/main_window_tests.cpp index 02e983a..b368c86 100644 --- a/app/tests/main_window_tests.cpp +++ b/app/tests/main_window_tests.cpp @@ -428,14 +428,20 @@ void testRuntimeAlarmListInteraction() require(alarm_result.succeeded, "runtime alarm test must create an M alarm definition"); - repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true); - alarm_service.refresh(); - require(alarm_service.records().size() == 1 - && alarm_service.records().front().active, - "an active M alarm must be available to AlarmList"); - HmiEditorWidget view(editor_service, runtime_service, alarm_service); view.setPageId(page_id); + QGraphicsItem *alarm_item = nullptr; + for (QGraphicsItem *item : view.scene()->items()) + { + if (item->zValue() >= 0.0) + { + alarm_item = item; + break; + } + } + require(alarm_item != nullptr && alarm_item->isVisible(), + "AlarmList must remain visible while editing"); + view.setEditingEnabled(false); view.setRuntimeActive(true); view.setRuntimeWriteEnabled(false); @@ -443,6 +449,17 @@ void testRuntimeAlarmListInteraction() view.resize(900, 560); view.show(); QApplication::processEvents(); + require(!alarm_item->isVisible(), + "AlarmList must be hidden when runtime has no active alarm"); + + repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true); + alarm_service.refresh(); + view.refreshRuntimeValues(); + QApplication::processEvents(); + require(alarm_service.records().size() == 1, + "an active M alarm must be available to AlarmList"); + require(alarm_item->isVisible() && alarm_item->zValue() > 0.0, + "AlarmList must appear above the page for an active alarm"); const QPoint header_position = view.mapFromScene(QPointF( alarm_list->bounds.x + 20.0, alarm_list->bounds.y + 10.0)); @@ -461,9 +478,16 @@ void testRuntimeAlarmListInteraction() repository.writeBit(RegisterAddress{RegisterArea::M, 0}, false); alarm_service.refresh(); view.refreshRuntimeValues(); - require(!alarm_service.records().front().active - && alarm_service.records().front().clearedAt.has_value(), - "clearing the M condition must retain a cleared history record"); + QApplication::processEvents(); + require(alarm_service.records().empty(), + "clearing the M condition must remove the alarm immediately"); + require(!alarm_item->isVisible(), + "AlarmList must hide after the final alarm recovers"); + + view.setRuntimeActive(false); + view.setEditingEnabled(true); + require(alarm_item->isVisible(), + "AlarmList must become visible again after returning to editing"); } void testModeActionsControlEditingAvailability()