Bladeren bron

fix: 修正梯形图网络注释归属

main
suyu 3 weken geleden
bovenliggende
commit
fbcbe4633b
12 gewijzigde bestanden met toevoegingen van 263 en 47 verwijderingen
  1. +39
    -1
      app/src/domain/control_logic_model.cpp
  2. +1
    -0
      app/src/domain/control_logic_model.h
  3. +77
    -8
      app/src/services/logic_editor_service.cpp
  4. +5
    -1
      app/src/services/logic_editor_service.h
  5. +1
    -15
      app/src/ui/logic_editor_widget.cpp
  6. +9
    -9
      app/src/ui/main_window.cpp
  7. +1
    -1
      app/src/ui/main_window.h
  8. +2
    -2
      app/src/ui/main_window.ui
  9. +22
    -0
      app/tests/domain_tests.cpp
  10. +81
    -1
      app/tests/logic_editor_service_tests.cpp
  11. +16
    -0
      app/tests/project_management_tests.cpp
  12. +9
    -9
      app/tests/runtime_panel_controller_tests.cpp

+ 39
- 1
app/src/domain/control_logic_model.cpp Bestand weergeven

@@ -454,7 +454,7 @@ bool LadderRung::validateStructure(std::string *error) const
if (containsLineBreak(comment)
|| comment.size() > ProjectLimits::kMaximumRungCommentBytes)
{
setError(error, "梯形图行注释必须是最多 128 个 UTF-8 字节的单行文本");
setError(error, "网络注释必须是最多 128 个 UTF-8 字节的单行文本");
return false;
}
if (cells.empty())
@@ -531,6 +531,33 @@ bool LadderRung::validateForRunning(std::string *error) const
return true;
}

std::size_t ControlLogic::networkHeadIndex(std::size_t rung_index) const
{
if (rung_index >= rungs.size())
{
return rungs.size();
}
while (rung_index > 0U)
{
const std::string &upper_id = rungs[rung_index - 1U].id;
const std::string &lower_id = rungs[rung_index].id;
const bool connected_to_previous = std::any_of(
verticalConnections.cbegin(),
verticalConnections.cend(),
[&upper_id, &lower_id](const VerticalConnection &connection)
{
return connection.upperRungId == upper_id
&& connection.lowerRungId == lower_id;
});
if (!connected_to_previous)
{
break;
}
--rung_index;
}
return rung_index;
}

bool ControlLogic::validate(
const ProjectLimitSettings &limits, std::string *error) const
{
@@ -641,6 +668,17 @@ bool ControlLogic::validateStructure(
return false;
}
}
for (std::size_t row = 1U; row < rungs.size(); ++row)
{
if (!rungs[row].comment.empty() && networkHeadIndex(row) != row)
{
setError(
error,
"第 " + std::to_string(row + 1U)
+ " 行不是网络首行,不能保存网络注释");
return false;
}
}
return true;
}



+ 1
- 0
app/src/domain/control_logic_model.h Bestand weergeven

@@ -175,6 +175,7 @@ struct ControlLogic
bool enabled = true;
std::vector<VerticalConnection> verticalConnections;

std::size_t networkHeadIndex(std::size_t rung_index) const;
bool validate(
const ProjectLimitSettings &limits,
std::string *error = nullptr) const;


+ 77
- 8
app/src/services/logic_editor_service.cpp Bestand weergeven

@@ -590,6 +590,45 @@ bool LogicEditorService::rungsEqual(
|| nodesEqual(*left.output, *right.output);
}

bool LogicEditorService::mergeNetworkComments(
ControlLogic *logic, std::string *error)
{
if (logic == nullptr)
{
if (error != nullptr)
{
*error = "未找到控制逻辑";
}
return false;
}
for (std::size_t row = 1U; row < logic->rungs.size(); ++row)
{
const std::size_t head = logic->networkHeadIndex(row);
if (head == row || logic->rungs[row].comment.empty())
{
continue;
}
std::string &head_comment = logic->rungs[head].comment;
std::string &branch_comment = logic->rungs[row].comment;
if (!head_comment.empty() && head_comment != branch_comment)
{
if (error != nullptr)
{
*error = "无法合并网络:第 " + std::to_string(head + 1U)
+ " 行和第 " + std::to_string(row + 1U)
+ " 行存在不同注释,请先统一或清空其中一条";
}
return false;
}
if (head_comment.empty())
{
head_comment = branch_comment;
}
branch_comment.clear();
}
return true;
}

bool LogicEditorService::cellsEqual(
const LadderCell &left, const LadderCell &right)
{
@@ -691,6 +730,19 @@ const LadderRung *LogicEditorService::findRung(
return found == logic->rungs.cend() ? nullptr : &*found;
}

const LadderRung *LogicEditorService::findNetworkHeadRung(
const std::string &logic_id, const std::string &rung_id) const
{
const ControlLogic *logic = findLogic(logic_id);
if (logic == nullptr)
{
return nullptr;
}
const std::size_t row = rungIndex(*logic, rung_id);
const std::size_t head = logic->networkHeadIndex(row);
return head == logic->rungs.size() ? nullptr : &logic->rungs[head];
}

const LadderCell *LogicEditorService::findCell(
const std::string &logic_id,
const std::string &rung_id,
@@ -1217,13 +1269,13 @@ LogicEditorResult LogicEditorService::removeRungs(
return {true, LogicEditorError::None, {}, rung_ids.front()};
}

LogicEditorResult LogicEditorService::updateRungComment(
LogicEditorResult LogicEditorService::updateNetworkComment(
const std::string &logic_id,
const std::string &rung_id,
const std::string &comment)
{
const LadderRung *rung = findRung(logic_id, rung_id);
if (rung == nullptr)
const LadderRung *head = findNetworkHeadRung(logic_id, rung_id);
if (head == nullptr)
{
return failure(LogicEditorError::RungNotFound, "未找到梯形图行");
}
@@ -1232,17 +1284,18 @@ LogicEditorResult LogicEditorService::updateRungComment(
{
return failure(
LogicEditorError::InvalidOperation,
"注释必须是最多 128 个 UTF-8 字节的单行文本");
"网络注释必须是最多 128 个 UTF-8 字节的单行文本");
}
if (rung->comment == comment)
if (head->comment == comment)
{
return {true, LogicEditorError::None, {}, rung_id};
return {true, LogicEditorError::None, {}, head->id};
}
const std::string head_rung_id = head->id;
HistoryState before = captureState();
Project &project = project_service_.editProject();
editableRung(editableLogic(&project, logic_id), rung_id)->comment = comment;
editableRung(editableLogic(&project, logic_id), head_rung_id)->comment = comment;
recordHistory(std::move(before));
return {true, LogicEditorError::None, {}, rung_id};
return {true, LogicEditorError::None, {}, head_rung_id};
}

LogicEditResult LogicEditorService::applyConditionAndAdvance(
@@ -1584,6 +1637,11 @@ LogicEditorResult LogicEditorService::setVerticalConnectionRange(
}
}
std::string error;
if (connected && !mergeNetworkComments(editable, &error))
{
rollbackEdit(std::move(before), modified_before);
return failure(LogicEditorError::InvalidOperation, error);
}
if (!editable->validateStructure(project_service_.projectLimits(), &error))
{
rollbackEdit(std::move(before), modified_before);
@@ -2472,6 +2530,11 @@ LogicClipboardPasteResult LogicEditorService::pasteClipboard(
}
}
std::string error;
if (!mergeNetworkComments(editable, &error))
{
rollbackEdit(std::move(before), modified_before);
return {failure(LogicEditorError::InvalidOperation, error), {}, {}};
}
if (!editable->validateStructure(project_service_.projectLimits(), &error))
{
rollbackEdit(std::move(before), modified_before);
@@ -2687,6 +2750,12 @@ LogicClipboardPasteResult LogicEditorService::pasteClipboard(
}
}
std::string error;
if (!fragment.verticalConnections.empty()
&& !mergeNetworkComments(editable, &error))
{
rollbackEdit(std::move(before), modified_before);
return {failure(LogicEditorError::InvalidOperation, error), {}, {}};
}
if (!editable->validateStructure(project_service_.projectLimits(), &error))
{
rollbackEdit(std::move(before), modified_before);


+ 5
- 1
app/src/services/logic_editor_service.h Bestand weergeven

@@ -165,6 +165,8 @@ public:
const ControlLogic *findLogic(const std::string &logic_id) const;
const LadderRung *findRung(
const std::string &logic_id, const std::string &rung_id) const;
const LadderRung *findNetworkHeadRung(
const std::string &logic_id, const std::string &rung_id) const;
const LadderCell *findCell(
const std::string &logic_id,
const std::string &rung_id,
@@ -209,7 +211,7 @@ public:
LogicEditorResult removeRungs(
const std::string &logic_id,
const std::vector<std::string> &rung_ids);
LogicEditorResult updateRungComment(
LogicEditorResult updateNetworkComment(
const std::string &logic_id,
const std::string &rung_id,
const std::string &comment);
@@ -323,6 +325,8 @@ private:
const ControlLogic &left, const ControlLogic &right);
static bool rungsEqual(
const LadderRung &left, const LadderRung &right);
static bool mergeNetworkComments(
ControlLogic *logic, std::string *error);
static bool cellsEqual(
const LadderCell &left, const LadderCell &right);
static bool nodesEqual(const LogicNode &left, const LogicNode &right);


+ 1
- 15
app/src/ui/logic_editor_widget.cpp Bestand weergeven

@@ -1055,21 +1055,7 @@ void LogicEditorWidget::rebuildRowLayout(const ControlLogic &logic)
qreal next_top = kTop;
for (std::size_t row = 0U; row < logic.rungs.size(); ++row)
{
bool connected_to_previous = false;
if (row > 0U)
{
const std::string &upper_id = logic.rungs[row - 1U].id;
const std::string &lower_id = logic.rungs[row].id;
connected_to_previous = std::any_of(
logic.verticalConnections.cbegin(),
logic.verticalConnections.cend(),
[&upper_id, &lower_id](const VerticalConnection &connection)
{
return connection.upperRungId == upper_id
&& connection.lowerRungId == lower_id;
});
}
const bool network_head = row == 0U || !connected_to_previous;
const bool network_head = logic.networkHeadIndex(row) == row;
if (network_head)
{
next_top += kCommentBandHeight;


+ 9
- 9
app/src/ui/main_window.cpp Bestand weergeven

@@ -878,8 +878,8 @@ void MainWindow::configureActions()
});
connect(ui_->deleteLogicAction, &QAction::triggered,
this, &MainWindow::deleteSelectedLogicObject);
connect(ui_->editRungCommentAction, &QAction::triggered,
this, &MainWindow::editSelectedRungComment);
connect(ui_->editNetworkCommentAction, &QAction::triggered,
this, &MainWindow::editSelectedNetworkComment);
addToolbarMenu(
ui_->logicToolBar,
tr("更多触点"),
@@ -994,7 +994,7 @@ void MainWindow::configureAppearance()
ui_->addAddAction->setIcon(makeUiIcon(UiIcon::Add));
ui_->addSubAction->setIcon(makeUiIcon(UiIcon::Subtract));
ui_->addCompareAction->setIcon(makeUiIcon(UiIcon::Compare));
ui_->editRungCommentAction->setIcon(makeUiIcon(UiIcon::Comment));
ui_->editNetworkCommentAction->setIcon(makeUiIcon(UiIcon::Comment));
ui_->syntaxCheckAction->setIcon(makeUiIcon(UiIcon::SyntaxCheck));
ui_->doubleCoilCheckAction->setIcon(makeUiIcon(UiIcon::Coil));
ui_->deleteLogicAction->setIcon(makeUiIcon(UiIcon::Delete));
@@ -1835,7 +1835,7 @@ void MainWindow::deleteLogicRung()
statusBar()->showMessage(tr("当前行已删除,竖线连接已重新整理"), 3000);
}

void MainWindow::editSelectedRungComment()
void MainWindow::editSelectedNetworkComment()
{
const std::string rung_id = logic_editor_widget_->selectedRungId();
if (rung_id.empty())
@@ -1843,9 +1843,9 @@ void MainWindow::editSelectedRungComment()
showProjectResult(tr("网络注释"), tr("请先选择一个梯形图网络"), false);
return;
}
const LadderRung *rung = logic_editor_service_.findRung(
const LadderRung *head = logic_editor_service_.findNetworkHeadRung(
current_logic_id_, rung_id);
if (rung == nullptr)
if (head == nullptr)
{
return;
}
@@ -1855,13 +1855,13 @@ void MainWindow::editSelectedRungComment()
tr("网络注释"),
tr("说明"),
QLineEdit::Normal,
fromUtf8(rung->comment),
fromUtf8(head->comment),
&accepted);
if (!accepted)
{
return;
}
const LogicEditorResult result = logic_editor_service_.updateRungComment(
const LogicEditorResult result = logic_editor_service_.updateNetworkComment(
current_logic_id_, rung_id, toUtf8(comment));
if (!result.succeeded)
{
@@ -2453,7 +2453,7 @@ void MainWindow::updateModeUi(const QString &message)
ui_->addAddAction->setEnabled(policy.allowsProjectEditing);
ui_->addSubAction->setEnabled(policy.allowsProjectEditing);
ui_->addCompareAction->setEnabled(policy.allowsProjectEditing);
ui_->editRungCommentAction->setEnabled(policy.allowsProjectEditing);
ui_->editNetworkCommentAction->setEnabled(policy.allowsProjectEditing);
ui_->deleteLogicAction->setEnabled(policy.allowsProjectEditing);
ui_->newProjectAction->setEnabled(policy.allowsProjectEditing);
ui_->saveProjectAction->setEnabled(policy.allowsProjectEditing);


+ 1
- 1
app/src/ui/main_window.h Bestand weergeven

@@ -197,7 +197,7 @@ private:
/** 删除当前选中的梯形图行 */
void deleteLogicRung();
/** 编辑当前网络的注释 */
void editSelectedRungComment();
void editSelectedNetworkComment();
/** 规整并检查当前梯形图 */
void runLogicSyntaxCheck();
/** 单独检查当前梯形图中的重复线圈输出 */


+ 2
- 2
app/src/ui/main_window.ui Bestand weergeven

@@ -364,7 +364,7 @@
<addaction name="addNormallyOpenAction"/>
<addaction name="addNormallyClosedAction"/>
<addaction name="addNormalCoilAction"/>
<addaction name="editRungCommentAction"/>
<addaction name="editNetworkCommentAction"/>
<addaction name="separator"/>
<addaction name="syntaxCheckAction"/>
</widget>
@@ -1318,7 +1318,7 @@
<string>在选中的空网格或横线处添加 D 值与常量比较条件</string>
</property>
</action>
<action name="editRungCommentAction">
<action name="editNetworkCommentAction">
<property name="text">
<string>网络注释</string>
</property>


+ 22
- 0
app/tests/domain_tests.cpp Bestand weergeven

@@ -696,6 +696,28 @@ void testEdgeAndCommentBoundaries()
comment_rung.comment = "第一行\r第二行";
require(!comment_rung.validateStructure(), "a rung comment containing CR must be rejected");

ControlLogic commented_logic;
commented_logic.id = "commented-logic";
commented_logic.name = "Commented logic";
LadderRung head;
head.id = "head";
head.name = "Head";
head.comment = "Network comment";
LadderRung branch;
branch.id = "branch";
branch.name = "Branch";
branch.comment = "Hidden branch comment";
commented_logic.rungs = {head, branch};
commented_logic.verticalConnections = {
{"comment-edge", "head", "branch", 0}};
require(
commented_logic.networkHeadIndex(1U) == 0U
&& !commented_logic.validateStructure(),
"a connected branch row must not persist a hidden network comment");
commented_logic.rungs[1].comment.clear();
require(commented_logic.validateStructure(),
"a connected network must be valid when only its head stores the comment");

Project project = makeValidProject();
project.registerComments = {
{RegisterAddress{RegisterArea::M, 0}, "启动按钮"},


+ 81
- 1
app/tests/logic_editor_service_tests.cpp Bestand weergeven

@@ -192,6 +192,85 @@ void testIndependentVerticalConnectionsAndNetworkSplit()
"the model must reject vertical edges between non-adjacent rows");
}

void testNetworkCommentsFollowNetworkHeadsAndMergeAtomically()
{
Fixture fixture;
const std::string first = fixture.addRung();
const std::string second = fixture.addRung();
const std::string third = fixture.addRung();

require(
fixture.editor.updateNetworkComment(
fixture.logicId, second, "下方网络注释").succeeded,
"an independent lower network must accept its own comment");
fixture.editor.clearHistory();
const LogicEditorResult merged = fixture.editor.setVerticalConnection(
fixture.logicId, first, second, 4, true);
const ControlLogic *logic = fixture.editor.findLogic(fixture.logicId);
require(
merged.succeeded && logic->rungs[0].comment == "下方网络注释"
&& logic->rungs[1].comment.empty()
&& fixture.editor.findNetworkHeadRung(fixture.logicId, second)->id
== first,
"merging a commented lower network into an empty upper network must move the comment to the new head");
require(
fixture.editor.undo().succeeded
&& fixture.editor.findRung(fixture.logicId, first)->comment.empty()
&& fixture.editor.findRung(fixture.logicId, second)->comment
== "下方网络注释"
&& fixture.editor.redo().succeeded,
"network merge comment movement must belong to the same undo transaction");

fixture.editor.clearHistory();
const LogicEditorResult edited_from_branch =
fixture.editor.updateNetworkComment(
fixture.logicId, second, "从支路编辑后的注释");
require(
edited_from_branch.succeeded && edited_from_branch.id == first
&& fixture.editor.findRung(fixture.logicId, first)->comment
== "从支路编辑后的注释"
&& fixture.editor.findRung(fixture.logicId, second)->comment.empty(),
"editing from a branch row must update only the network head comment");

require(
fixture.editor.updateNetworkComment(
fixture.logicId, third, "另一个网络注释").succeeded,
"the third independent network must accept a different comment");
fixture.editor.clearHistory();
fixture.projects.restoreModifiedState(false);
const LogicEditorResult conflict = fixture.editor.setVerticalConnection(
fixture.logicId, second, third, 4, true);
logic = fixture.editor.findLogic(fixture.logicId);
require(
!conflict.succeeded
&& conflict.message.find("存在不同注释") != std::string::npos
&& connectionAt(*logic, second, third, 4) == nullptr
&& logic->rungs[0].comment == "从支路编辑后的注释"
&& logic->rungs[2].comment == "另一个网络注释"
&& !fixture.projects.isModified() && !fixture.editor.canUndo(),
"merging differently commented networks must fail without partial topology or history");

require(
fixture.editor.updateNetworkComment(
fixture.logicId, third, "从支路编辑后的注释").succeeded,
"the lower network comment must be editable before a retry");
fixture.editor.clearHistory();
require(
fixture.editor.setVerticalConnection(
fixture.logicId, second, third, 4, true).succeeded
&& fixture.editor.findRung(fixture.logicId, third)->comment.empty(),
"merging equal network comments must keep one comment at the head");
const std::string connection_id = connectionAt(
*fixture.editor.findLogic(fixture.logicId), second, third, 4)->id;
require(
fixture.editor.removeVerticalConnections(
fixture.logicId, {connection_id}).succeeded
&& fixture.editor.findRung(fixture.logicId, first)->comment
== "从支路编辑后的注释"
&& fixture.editor.findRung(fixture.logicId, third)->comment.empty(),
"splitting a network must not duplicate its head comment");
}

void testInsertRowSplitsVerticalEdges()
{
Fixture fixture;
@@ -1149,7 +1228,7 @@ void testWholeRowClipboardInsertionAndLimit()
source_node.succeeded
&& fixture.editor.setHorizontalWireRange(
fixture.logicId, second, 0, 1, true).succeeded
&& fixture.editor.updateRungComment(
&& fixture.editor.updateNetworkComment(
fixture.logicId, first, "整行复制注释").succeeded
&& fixture.editor.setVerticalConnection(
fixture.logicId, first, second, 2, true).succeeded,
@@ -1413,6 +1492,7 @@ int main()
{
testContinuousGridAndIndependentHorizontalWires();
testIndependentVerticalConnectionsAndNetworkSplit();
testNetworkCommentsFollowNetworkHeadsAndMergeAtomically();
testInsertRowSplitsVerticalEdges();
testDeleteRowMergesOnlyContinuousEdges();
testParallelBranchCreatesConnectedVisualRow();


+ 16
- 0
app/tests/project_management_tests.cpp Bestand weergeven

@@ -128,6 +128,7 @@ Project makeExampleProject()
logic.verticalConnections = {
{"vertical-left", "rung-1", "rung-2", 0},
{"vertical-right", "rung-1", "rung-2", 1}};
logic.rungs[1].comment.clear();
project.controlLogics.push_back(std::move(logic));
project.registerComments.push_back({
RegisterAddress{RegisterArea::M, 0}, "Start signal"});
@@ -396,6 +397,21 @@ void testInvalidGridAndConnectionsAreRejected()
== ProjectStorageError::InvalidProject,
"duplicate edges at one row boundary must be rejected");

QJsonObject hidden_comment = original;
logic = firstLogic(&hidden_comment);
rungs = logic.value(QStringLiteral("rungs")).toArray();
rung = rungs.at(1).toObject();
rung.insert(QStringLiteral("comment"), QStringLiteral("hidden branch comment"));
rungs.replace(1, rung);
logic.insert(QStringLiteral("rungs"), rungs);
replaceFirstLogic(&hidden_comment, logic);
const QString hidden_comment_path = directory.filePath(
"hidden-branch-comment.json");
writeBytes(hidden_comment_path, QJsonDocument(hidden_comment).toJson());
require(storage.load(hidden_comment_path.toStdString()).error
== ProjectStorageError::InvalidProject,
"a 2.0 project must reject comments stored on connected branch rows");

QJsonObject non_adjacent = original;
logic = firstLogic(&non_adjacent);
edges = logic.value(QStringLiteral("verticalConnections")).toArray();


+ 9
- 9
app/tests/runtime_panel_controller_tests.cpp Bestand weergeven

@@ -564,11 +564,14 @@ void testLadderLayoutAndDragDeletion()
logic_id, lower, 0, 1, true).succeeded
&& editor.setVerticalConnection(
logic_id, upper, lower, 2, true).succeeded
&& editor.updateRungComment(
logic_id, upper, "主网络注释").succeeded
&& editor.updateRungComment(
logic_id, lower, "支路注释不应重复").succeeded,
&& editor.updateNetworkComment(
logic_id, lower, "主网络注释").succeeded,
"layout fixture must create a connected two-row network");
require(
editor.findNetworkHeadRung(logic_id, lower)->id == upper
&& editor.findRung(logic_id, upper)->comment == "主网络注释"
&& editor.findRung(logic_id, lower)->comment.empty(),
"editing a branch row must store the comment only on the network head");
editor.clearHistory();

LogicEditorWidget widget(editor);
@@ -578,7 +581,6 @@ void testLadderLayoutAndDragDeletion()
QCoreApplication::processEvents(QEventLoop::AllEvents);

bool found_head_comment = false;
bool found_branch_comment = false;
for (QGraphicsItem *item : widget.scene()->items())
{
auto *text = dynamic_cast<QGraphicsSimpleTextItem *>(item);
@@ -588,11 +590,9 @@ void testLadderLayoutAndDragDeletion()
}
found_head_comment = found_head_comment
|| text->text() == QStringLiteral("主网络注释");
found_branch_comment = found_branch_comment
|| text->text() == QStringLiteral("支路注释不应重复");
}
require(found_head_comment && !found_branch_comment,
"only the network-head comment must be rendered");
require(found_head_comment,
"the comment edited from a branch must render above the network head");

constexpr qreal left_bus = 60.0;
constexpr qreal cell_width = 96.0;


Laden…
Annuleren
Opslaan