Parcourir la source

fix: 支持竖线连续向下添加

main
suyu il y a 3 semaines
Parent
révision
ae1c0a3c03
8 fichiers modifiés avec 289 ajouts et 21 suppressions
  1. +107
    -0
      app/src/services/logic_editor_service.cpp
  2. +12
    -0
      app/src/services/logic_editor_service.h
  3. +56
    -19
      app/src/ui/logic_editor_widget.cpp
  4. +1
    -0
      app/src/ui/logic_editor_widget.h
  5. +4
    -1
      app/src/ui/main_window.cpp
  6. +1
    -1
      app/src/ui/main_window.ui
  7. +16
    -0
      app/tests/logic_editor_service_tests.cpp
  8. +92
    -0
      app/tests/runtime_panel_controller_tests.cpp

+ 107
- 0
app/src/services/logic_editor_service.cpp Voir le fichier

@@ -1645,6 +1645,84 @@ LogicEditorResult LogicEditorService::setVerticalConnection(
connected);
}

LogicVerticalEditResult LogicEditorService::applyVerticalConnectionAndAdvance(
const std::string &logic_id,
const std::string &upper_rung_id,
int column_boundary)
{
const ControlLogic *logic = findLogic(logic_id);
if (logic == nullptr)
{
return {
failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"),
{},
-1,
false};
}
if (column_boundary < 0
|| column_boundary > ProjectLimits::kMaximumConditionColumns)
{
return {
failure(
LogicEditorError::InvalidOperation,
"请先选择一个列边界或网格,再插入竖线"),
{},
-1,
false};
}
const std::size_t upper = rungIndex(*logic, upper_rung_id);
if (upper == logic->rungs.size())
{
return {
failure(LogicEditorError::RungNotFound, "未找到梯形图行"),
{},
-1,
false};
}
if (upper + 1U >= logic->rungs.size())
{
return {
failure(
LogicEditorError::InvalidOperation,
"当前已经是末行,无法继续建立竖线"),
{},
-1,
false};
}

const std::string lower_rung_id = logic->rungs[upper + 1U].id;
const bool already_connected = std::any_of(
logic->verticalConnections.cbegin(),
logic->verticalConnections.cend(),
[&upper_rung_id, &lower_rung_id, column_boundary](
const VerticalConnection &connection)
{
return connectionMatches(
connection,
upper_rung_id,
lower_rung_id,
column_boundary);
});
LogicEditorResult edit = setVerticalConnection(
logic_id,
upper_rung_id,
lower_rung_id,
column_boundary,
true);
if (!edit.succeeded)
{
return {std::move(edit), {}, -1, false};
}
edit.message = already_connected
? "竖线已经存在,已移至下一行"
: "竖线连接已建立,已移至下一行";
return {
std::move(edit),
lower_rung_id,
column_boundary,
!already_connected};
}

LogicEditorResult LogicEditorService::setVerticalConnectionRange(
const std::string &logic_id,
const std::string &first_rung_id,
@@ -1672,6 +1750,35 @@ LogicEditorResult LogicEditorService::setVerticalConnectionRange(
std::swap(first, last);
}

bool changed = false;
for (std::size_t row = first; row < last; ++row)
{
const std::string &upper_id = logic->rungs[row].id;
const std::string &lower_id = logic->rungs[row + 1U].id;
const bool exists = std::any_of(
logic->verticalConnections.cbegin(),
logic->verticalConnections.cend(),
[&upper_id, &lower_id, column_boundary](
const VerticalConnection &connection)
{
return connectionMatches(
connection, upper_id, lower_id, column_boundary);
});
if (exists != connected)
{
changed = true;
break;
}
}
if (!changed)
{
return {
true,
LogicEditorError::None,
connected ? "竖线连接已经存在" : "目标位置没有竖线",
first_rung_id};
}

HistoryState before = captureState();
const bool modified_before = project_service_.isModified();
Project &project = project_service_.editProject();


+ 12
- 0
app/src/services/logic_editor_service.h Voir le fichier

@@ -127,6 +127,14 @@ struct LogicEditResult
LogicEditCursor nextCursor;
};

struct LogicVerticalEditResult
{
LogicEditorResult edit;
std::string nextRungId;
int columnBoundary = -1;
bool changed = false;
};

struct LogicSyntaxLocation
{
std::string logicId;
@@ -254,6 +262,10 @@ public:
const std::string &lower_rung_id,
int column_boundary,
bool connected);
LogicVerticalEditResult applyVerticalConnectionAndAdvance(
const std::string &logic_id,
const std::string &upper_rung_id,
int column_boundary);
LogicEditorResult setVerticalConnectionRange(
const std::string &logic_id,
const std::string &first_rung_id,


+ 56
- 19
app/src/ui/logic_editor_widget.cpp Voir le fichier

@@ -2092,6 +2092,49 @@ void LogicEditorWidget::moveToCursor(const LogicEditCursor &cursor)
}
}

void LogicEditorWidget::moveToVerticalTarget(
const LogicVerticalEditResult &result)
{
const bool keep_cell = selected_cell_;
const bool keep_output = selected_output_;
const bool keep_boundary = selected_boundary_;
clearObjectSelection();
selected_rung_id_ = result.nextRungId;
selected_column_ = result.columnBoundary;
selected_cell_ = keep_cell;
selected_output_ = keep_output;
selected_boundary_ = keep_boundary;
rebuildScene();
notifySelectionChanged();

const RowLayout *layout = layoutForRung(result.nextRungId);
if (layout == nullptr)
{
return;
}
qreal center_x = kLeftBus
+ (static_cast<qreal>(result.columnBoundary) + 0.5) * kCellWidth;
qreal target_width = kCellWidth;
if (keep_boundary)
{
center_x = kLeftBus
+ static_cast<qreal>(result.columnBoundary) * kCellWidth;
}
else if (keep_output)
{
center_x = kConditionRight + kOutputWidth / 2.0;
target_width = kOutputWidth;
}
ensureVisible(
QRectF(
center_x - target_width / 2.0,
layout->centerY - kRowHeight / 2.0,
target_width,
kRowHeight),
24,
24);
}

LogicEditorResult LogicEditorWidget::finishCursorEdit(
const LogicEditResult &result)
{
@@ -2412,36 +2455,30 @@ LogicEditorResult LogicEditorWidget::addHorizontalWire()

LogicEditorResult LogicEditorWidget::addVerticalWire()
{
const ControlLogic *logic = editor_service_.findLogic(logic_id_);
if (logic == nullptr || logic->rungs.size() < 2U)
{
return {false, LogicEditorError::InvalidOperation, "至少需要两行才能连接竖线", {}};
}
const std::string upper = selectedRungId();
if (upper.empty() || selected_column_ < 0
|| selected_column_ > ProjectLimits::kMaximumConditionColumns)
|| selected_column_ > ProjectLimits::kMaximumConditionColumns
|| (!selected_cell_ && !selected_output_ && !selected_boundary_))
{
return {false, LogicEditorError::InvalidOperation,
"请先选择一个列边界或网格,再插入竖线", {}};
}
const int index = rowAt(upper);
if (index < 0 || static_cast<std::size_t>(index + 1) >= logic->rungs.size())
{
return {false, LogicEditorError::InvalidOperation, "请选择非末行作为连接起点", {}};
}
const LogicEditorResult result = editor_service_.setVerticalConnection(
logic_id_, upper, logic->rungs[static_cast<std::size_t>(index + 1)].id,
selected_column_, true);
if (result.succeeded)
const LogicVerticalEditResult result =
editor_service_.applyVerticalConnectionAndAdvance(
logic_id_, upper, selected_column_);
if (result.edit.succeeded)
{
rebuildScene();
emit graphChanged();
moveToVerticalTarget(result);
if (result.changed)
{
emit graphChanged();
}
}
else
{
reportFailure(result);
reportFailure(result.edit);
}
return result;
return result.edit;
}

LogicEditorResult LogicEditorWidget::deleteHorizontalWire()


+ 1
- 0
app/src/ui/logic_editor_widget.h Voir le fichier

@@ -121,6 +121,7 @@ private:
const LogicEditCursor &cursor) const;
LogicEditCursor conditionInsertionCursor() const;
void moveToCursor(const LogicEditCursor &cursor);
void moveToVerticalTarget(const LogicVerticalEditResult &result);
LogicEditorResult finishCursorEdit(const LogicEditResult &result);
void rebuildScene();
void selectObject(const Hit &hit, bool extend_node_selection = false);


+ 4
- 1
app/src/ui/main_window.cpp Voir le fichier

@@ -1710,7 +1710,10 @@ void MainWindow::addLogicVerticalWire()
selected_logic_node_id_.clear();
showLogicNodeProperties({});
refreshProjectUi();
statusBar()->showMessage(tr("竖线连接已建立"), 3000);
statusBar()->showMessage(
result.message.empty()
? tr("竖线连接已建立") : fromUtf8(result.message),
3000);
}

void MainWindow::deleteLogicHorizontalWire()


+ 1
- 1
app/src/ui/main_window.ui Voir le fichier

@@ -1206,7 +1206,7 @@
<string>竖线</string>
</property>
<property name="toolTip">
<string>在当前行与下一行的选中列边界建立一段竖线连接</string>
<string>在当前行与下一行的选中列边界建立竖线,成功后自动下移到下一行</string>
</property>
<property name="shortcut">
<string>F12</string>


+ 16
- 0
app/tests/logic_editor_service_tests.cpp Voir le fichier

@@ -136,6 +136,22 @@ void testIndependentVerticalConnectionsAndNetworkSplit()
&& connectionAt(*logic, second, third, 4) != nullptr,
"a long vertical line must be represented by independent segments");

fixture.editor.clearHistory();
fixture.projects.restoreModifiedState(false);
const LogicVerticalEditResult duplicate =
fixture.editor.applyVerticalConnectionAndAdvance(
fixture.logicId, first, 4);
require(
duplicate.edit.succeeded
&& !duplicate.changed
&& duplicate.nextRungId == second
&& duplicate.columnBoundary == 4
&& fixture.editor.findLogic(fixture.logicId)
->verticalConnections.size() == 2U
&& !fixture.projects.isModified()
&& !fixture.editor.canUndo(),
"an existing vertical edge must advance without dirty state or empty history");

const std::string first_segment =
connectionAt(*logic, first, second, 4)->id;
require(


+ 92
- 0
app/tests/runtime_panel_controller_tests.cpp Voir le fichier

@@ -31,12 +31,14 @@
#include <QComboBox>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QWidget>

#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

namespace {

@@ -781,6 +783,95 @@ void testCursorAdvanceAndInlineCommandInput()
"an inline output must append a row and keep continuous input active");
}

void testVerticalWireShortcutAdvancesDownward()
{
TestProjectStorage storage;
ProjectService project_service(storage);
LogicEditorService editor(project_service);
const std::string logic_id = editor.ensureDefaultLogic().id;
std::vector<std::string> rung_ids;
for (int row = 0; row < 4; ++row)
{
rung_ids.push_back(editor.addRung(logic_id).id);
}
editor.clearHistory();

LogicEditorWidget widget(editor);
widget.setLogicId(logic_id);
widget.resize(1320, 240);
widget.show();
QCoreApplication::processEvents(QEventLoop::AllEvents);

constexpr int boundary = 4;
constexpr qreal left_bus = 60.0;
constexpr qreal cell_width = 96.0;
constexpr qreal first_grid_top = 46.0;
constexpr qreal row_height = 78.0;
const QPoint point = widget.mapFromScene(QPointF(
left_bus + (static_cast<qreal>(boundary) + 0.5) * cell_width,
first_grid_top + row_height / 2.0));
QMouseEvent press(
QEvent::MouseButtonPress,
QPointF(point),
Qt::LeftButton,
Qt::LeftButton,
Qt::NoModifier);
QApplication::sendEvent(widget.viewport(), &press);
QMouseEvent release(
QEvent::MouseButtonRelease,
QPointF(point),
Qt::LeftButton,
Qt::NoButton,
Qt::NoModifier);
QApplication::sendEvent(widget.viewport(), &release);
require(widget.selectedRungId() == rung_ids.front(),
"the vertical shortcut fixture must select the first row");

for (std::size_t row = 0U; row + 1U < rung_ids.size(); ++row)
{
const LogicEditorResult result = widget.addVerticalWire();
require(
result.succeeded
&& widget.selectedRungId() == rung_ids[row + 1U],
"each vertical shortcut must advance to the next row");
const ControlLogic *logic = editor.findLogic(logic_id);
const bool connected = std::any_of(
logic->verticalConnections.cbegin(),
logic->verticalConnections.cend(),
[&rung_ids, row, boundary](const VerticalConnection &connection)
{
return connection.upperRungId == rung_ids[row]
&& connection.lowerRungId == rung_ids[row + 1U]
&& connection.columnBoundary == boundary;
});
require(connected,
"repeated vertical shortcuts must stay on one column boundary");
}
require(
editor.findLogic(logic_id)->verticalConnections.size() == 3U
&& widget.verticalScrollBar()->value() > 0,
"the vertical shortcut must create three segments and keep the target visible");

const LogicEditorResult at_last_row = widget.addVerticalWire();
require(
!at_last_row.succeeded
&& at_last_row.message.find("末行") != std::string::npos
&& editor.findLogic(logic_id)->verticalConnections.size() == 3U
&& widget.selectedRungId() == rung_ids.back(),
"the vertical shortcut must stop cleanly at the last existing row");

for (int remaining = 2; remaining >= 0; --remaining)
{
require(
editor.undo().succeeded
&& editor.findLogic(logic_id)->verticalConnections.size()
== static_cast<std::size_t>(remaining),
"each undo must remove exactly one shortcut-created vertical segment");
}
require(!editor.canUndo(),
"three vertical shortcuts must create exactly three history entries");
}

void testSegmentLevelTraceProjection()
{
TestProjectStorage storage;
@@ -987,6 +1078,7 @@ int main(int argc, char *argv[])
testLogicEditorGridSelectionAndDeletion();
testLadderLayoutAndDragDeletion();
testCursorAdvanceAndInlineCommandInput();
testVerticalWireShortcutAdvancesDownward();
testSegmentLevelTraceProjection();
testLogicClipboardUsesExplicitObjectAndRowSelection();
}


Chargement…
Annuler
Enregistrer