|
|
|
@@ -13,31 +13,34 @@ |
|
|
|
|
|
|
|
namespace { |
|
|
|
|
|
|
|
constexpr qreal kLeftRailX = 64.0; |
|
|
|
constexpr qreal kMinimumSceneWidth = 940.0; |
|
|
|
constexpr qreal kNodeWidth = 116.0; |
|
|
|
constexpr qreal kNodeHeight = 92.0; |
|
|
|
constexpr qreal kNodeTerminalX = 54.0; |
|
|
|
constexpr qreal kSeriesGap = 26.0; |
|
|
|
constexpr qreal kParallelGap = 22.0; |
|
|
|
constexpr qreal kExpressionPadding = 18.0; |
|
|
|
constexpr qreal kRungHeaderHeight = 38.0; |
|
|
|
constexpr qreal kSceneMargin = 28.0; |
|
|
|
constexpr qreal kRailInset = 40.0; |
|
|
|
constexpr qreal kMinimumSceneWidth = 980.0; |
|
|
|
constexpr qreal kCellWidth = 128.0; |
|
|
|
constexpr qreal kCellHeight = 86.0; |
|
|
|
constexpr qreal kNodeTerminalX = 50.0; |
|
|
|
constexpr qreal kRungHeaderHeight = 34.0; |
|
|
|
constexpr qreal kRungGap = 18.0; |
|
|
|
constexpr qreal kOutputWidth = 128.0; |
|
|
|
constexpr qreal kOutputGap = 72.0; |
|
|
|
constexpr qreal kLadderLineWidth = 1.8; |
|
|
|
constexpr int kMinimumLogicColumns = 7; |
|
|
|
const QColor kLadderColor(QStringLiteral("#263842")); |
|
|
|
const QColor kActiveColor(QStringLiteral("#16854f")); |
|
|
|
const QColor kFaultColor(QStringLiteral("#c5362e")); |
|
|
|
const QColor kSelectionColor(QStringLiteral("#dfeef5")); |
|
|
|
const QColor kSelectionBorderColor(QStringLiteral("#277da1")); |
|
|
|
const QColor kGroupBorderColor(QStringLiteral("#8ea4af")); |
|
|
|
const QColor kGridColor(QStringLiteral("#e8edf0")); |
|
|
|
const QColor kPlaceholderColor(QStringLiteral("#81919b")); |
|
|
|
|
|
|
|
struct ExpressionSize |
|
|
|
struct ExpressionMetrics |
|
|
|
{ |
|
|
|
qreal width = kNodeWidth; |
|
|
|
qreal height = kNodeHeight; |
|
|
|
int columns = 1; |
|
|
|
int rows = 1; |
|
|
|
}; |
|
|
|
|
|
|
|
struct RenderResult |
|
|
|
{ |
|
|
|
QPointF input; |
|
|
|
QPointF output; |
|
|
|
}; |
|
|
|
|
|
|
|
QString registerAddressText(const RegisterAddress &address) |
|
|
|
@@ -89,36 +92,33 @@ QString nodeToolTip(const LogicNodeConfig &config) |
|
|
|
config); |
|
|
|
} |
|
|
|
|
|
|
|
ExpressionSize measureExpression(const ConditionExpression &expression) |
|
|
|
ExpressionMetrics measureExpression(const ConditionExpression &expression) |
|
|
|
{ |
|
|
|
if (expression.kind == ConditionExpressionKind::Node) |
|
|
|
{ |
|
|
|
return {}; |
|
|
|
} |
|
|
|
ExpressionSize size{0.0, 0.0}; |
|
|
|
|
|
|
|
ExpressionMetrics metrics{0, 0}; |
|
|
|
if (expression.kind == ConditionExpressionKind::Series) |
|
|
|
{ |
|
|
|
for (const ConditionExpression &child : expression.children) |
|
|
|
{ |
|
|
|
const ExpressionSize child_size = measureExpression(child); |
|
|
|
size.width += child_size.width; |
|
|
|
size.height = std::max(size.height, child_size.height); |
|
|
|
const ExpressionMetrics child_metrics = measureExpression(child); |
|
|
|
metrics.columns += child_metrics.columns; |
|
|
|
metrics.rows = std::max(metrics.rows, child_metrics.rows); |
|
|
|
} |
|
|
|
size.width += kSeriesGap * static_cast<qreal>(expression.children.size() - 1U); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (const ConditionExpression &child : expression.children) |
|
|
|
{ |
|
|
|
const ExpressionSize child_size = measureExpression(child); |
|
|
|
size.width = std::max(size.width, child_size.width); |
|
|
|
size.height += child_size.height; |
|
|
|
const ExpressionMetrics child_metrics = measureExpression(child); |
|
|
|
metrics.columns = std::max(metrics.columns, child_metrics.columns); |
|
|
|
metrics.rows += child_metrics.rows; |
|
|
|
} |
|
|
|
size.height += kParallelGap * static_cast<qreal>(expression.children.size() - 1U); |
|
|
|
} |
|
|
|
size.width += kExpressionPadding * 2.0; |
|
|
|
size.height += kExpressionPadding * 2.0; |
|
|
|
return size; |
|
|
|
return metrics; |
|
|
|
} |
|
|
|
|
|
|
|
QPen ladderPen(bool active) |
|
|
|
@@ -137,6 +137,47 @@ bool traceValue( |
|
|
|
return found != values.end() && found->second; |
|
|
|
} |
|
|
|
|
|
|
|
void addGrid( |
|
|
|
QGraphicsScene &scene, |
|
|
|
qreal left, |
|
|
|
qreal top, |
|
|
|
int columns, |
|
|
|
int rows) |
|
|
|
{ |
|
|
|
QPen pen(kGridColor, 1.0); |
|
|
|
pen.setCosmetic(true); |
|
|
|
for (int column = 0; column <= columns; ++column) |
|
|
|
{ |
|
|
|
const qreal x = left + static_cast<qreal>(column) * kCellWidth; |
|
|
|
QGraphicsLineItem *line = scene.addLine( |
|
|
|
QLineF(x, top, x, top + static_cast<qreal>(rows) * kCellHeight), pen); |
|
|
|
line->setZValue(-10.0); |
|
|
|
} |
|
|
|
for (int row = 0; row <= rows; ++row) |
|
|
|
{ |
|
|
|
const qreal y = top + static_cast<qreal>(row) * kCellHeight; |
|
|
|
QGraphicsLineItem *line = scene.addLine( |
|
|
|
QLineF(left, y, left + static_cast<qreal>(columns) * kCellWidth, y), pen); |
|
|
|
line->setZValue(-10.0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void addPlaceholder(QGraphicsScene &scene, const QRectF &cell, const QString &text) |
|
|
|
{ |
|
|
|
const QRectF bounds = cell.adjusted(12, 18, -12, -18); |
|
|
|
QGraphicsRectItem *item = scene.addRect( |
|
|
|
bounds, |
|
|
|
QPen(kPlaceholderColor, 1.2, Qt::DashLine), |
|
|
|
QBrush(QColor(255, 255, 255, 220))); |
|
|
|
item->setZValue(1.0); |
|
|
|
QGraphicsTextItem *label = scene.addText(text); |
|
|
|
label->setDefaultTextColor(kPlaceholderColor); |
|
|
|
label->setPos( |
|
|
|
bounds.center().x() - label->boundingRect().width() / 2.0, |
|
|
|
bounds.center().y() - label->boundingRect().height() / 2.0); |
|
|
|
label->setZValue(2.0); |
|
|
|
} |
|
|
|
|
|
|
|
} // namespace |
|
|
|
|
|
|
|
class LogicEditorWidget::NodeItem final : public QGraphicsItem |
|
|
|
@@ -145,13 +186,11 @@ public: |
|
|
|
NodeItem( |
|
|
|
const LogicNode &node, |
|
|
|
const std::string &rung_id, |
|
|
|
const std::string &expression_id, |
|
|
|
const QPointF ¢er, |
|
|
|
bool active, |
|
|
|
bool faulted) |
|
|
|
: node_id_(node.id), |
|
|
|
rung_id_(rung_id), |
|
|
|
expression_id_(expression_id), |
|
|
|
config_(node.config), |
|
|
|
configured_(node.configured), |
|
|
|
active_(active), |
|
|
|
@@ -166,7 +205,7 @@ public: |
|
|
|
|
|
|
|
QRectF boundingRect() const override |
|
|
|
{ |
|
|
|
return {-kNodeWidth / 2.0, -kNodeHeight / 2.0, kNodeWidth, kNodeHeight}; |
|
|
|
return {-kCellWidth / 2.0, -kCellHeight / 2.0, kCellWidth, kCellHeight}; |
|
|
|
} |
|
|
|
|
|
|
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override |
|
|
|
@@ -175,8 +214,11 @@ public: |
|
|
|
const bool selected = (option->state & QStyle::State_Selected) != 0; |
|
|
|
if (selected) |
|
|
|
{ |
|
|
|
painter->fillRect(boundingRect(), kSelectionColor); |
|
|
|
painter->fillRect(boundingRect().adjusted(2, 2, -2, -2), kSelectionColor); |
|
|
|
painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine)); |
|
|
|
painter->drawRect(boundingRect().adjusted(3, 3, -3, -3)); |
|
|
|
} |
|
|
|
|
|
|
|
const QColor symbol_color = faulted_ ? kFaultColor |
|
|
|
: selected ? kSelectionBorderColor : active_ ? kActiveColor : kLadderColor; |
|
|
|
painter->setPen(QPen(symbol_color, active_ || faulted_ ? 2.6 : kLadderLineWidth)); |
|
|
|
@@ -186,7 +228,7 @@ public: |
|
|
|
|
|
|
|
if (const auto *contact = std::get_if<ContactNodeConfig>(&config_)) |
|
|
|
{ |
|
|
|
painter->fillRect(QRectF(-24, -21, 48, 42), Qt::white); |
|
|
|
painter->fillRect(QRectF(-25, -22, 50, 44), Qt::white); |
|
|
|
painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-18, 0)); |
|
|
|
painter->drawLine(QPointF(18, 0), QPointF(kNodeTerminalX, 0)); |
|
|
|
painter->drawLine(QPointF(-18, -15), QPointF(-18, 15)); |
|
|
|
@@ -196,13 +238,13 @@ public: |
|
|
|
painter->drawLine(QPointF(-23, 18), QPointF(23, -18)); |
|
|
|
} |
|
|
|
painter->drawText( |
|
|
|
QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18), |
|
|
|
QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), |
|
|
|
Qt::AlignCenter, |
|
|
|
configured_ ? registerAddressText(contact->address) : tr("< M 地址 >")); |
|
|
|
} |
|
|
|
else if (const auto *coil = std::get_if<CoilNodeConfig>(&config_)) |
|
|
|
{ |
|
|
|
painter->fillRect(QRectF(-34, -23, 68, 46), Qt::white); |
|
|
|
painter->fillRect(QRectF(-35, -23, 70, 46), Qt::white); |
|
|
|
painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-16, 0)); |
|
|
|
painter->drawLine(QPointF(16, 0), QPointF(kNodeTerminalX, 0)); |
|
|
|
QPainterPath left; |
|
|
|
@@ -221,7 +263,7 @@ public: |
|
|
|
coil->mode == CoilMode::Set ? QStringLiteral("S") : QStringLiteral("R")); |
|
|
|
} |
|
|
|
painter->drawText( |
|
|
|
QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18), |
|
|
|
QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), |
|
|
|
Qt::AlignCenter, |
|
|
|
configured_ ? registerAddressText(coil->address) : tr("< M 地址 >")); |
|
|
|
} |
|
|
|
@@ -232,102 +274,72 @@ public: |
|
|
|
painter->drawLine(QPointF(-kNodeTerminalX, 0), QPointF(-47, 0)); |
|
|
|
painter->drawLine(QPointF(47, 0), QPointF(kNodeTerminalX, 0)); |
|
|
|
painter->drawRect(box); |
|
|
|
painter->drawText(box, Qt::AlignCenter, |
|
|
|
QStringLiteral("%1 INT").arg(comparisonText(comparison->comparison))); |
|
|
|
painter->drawText( |
|
|
|
QRectF(-kNodeWidth / 2.0, -44, kNodeWidth, 18), Qt::AlignCenter, |
|
|
|
box, Qt::AlignCenter, |
|
|
|
QStringLiteral("%1 INT").arg(comparisonText(comparison->comparison))); |
|
|
|
painter->drawText( |
|
|
|
QRectF(-kCellWidth / 2.0, -40, kCellWidth, 18), |
|
|
|
Qt::AlignCenter, |
|
|
|
configured_ ? registerAddressText(comparison->address) : tr("< D 地址 >")); |
|
|
|
painter->drawText( |
|
|
|
QRectF(-kNodeWidth / 2.0, 22, kNodeWidth, 18), Qt::AlignCenter, |
|
|
|
QRectF(-kCellWidth / 2.0, 22, kCellWidth, 18), |
|
|
|
Qt::AlignCenter, |
|
|
|
configured_ ? QString::number(comparison->value) : tr("< 常量 >")); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const std::string &nodeId() const { return node_id_; } |
|
|
|
const std::string &rungId() const { return rung_id_; } |
|
|
|
const std::string &expressionId() const { return expression_id_; } |
|
|
|
|
|
|
|
private: |
|
|
|
std::string node_id_; |
|
|
|
std::string rung_id_; |
|
|
|
std::string expression_id_; |
|
|
|
LogicNodeConfig config_; |
|
|
|
bool configured_ = true; |
|
|
|
bool active_ = false; |
|
|
|
bool faulted_ = false; |
|
|
|
}; |
|
|
|
|
|
|
|
class LogicEditorWidget::ExpressionItem final : public QGraphicsItem |
|
|
|
{ |
|
|
|
public: |
|
|
|
ExpressionItem( |
|
|
|
std::string expression_id, |
|
|
|
std::string rung_id, |
|
|
|
ConditionExpressionKind kind, |
|
|
|
const QRectF &bounds) |
|
|
|
: expression_id_(std::move(expression_id)), |
|
|
|
rung_id_(std::move(rung_id)), |
|
|
|
kind_(kind), |
|
|
|
bounds_(bounds) |
|
|
|
{ |
|
|
|
setFlag(ItemIsSelectable, true); |
|
|
|
setZValue(1.0); |
|
|
|
setToolTip(kind_ == ConditionExpressionKind::Parallel |
|
|
|
? LogicEditorWidget::tr("并联支路组") |
|
|
|
: LogicEditorWidget::tr("串联条件组")); |
|
|
|
} |
|
|
|
|
|
|
|
QRectF boundingRect() const override { return bounds_; } |
|
|
|
|
|
|
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override |
|
|
|
{ |
|
|
|
if ((option->state & QStyle::State_Selected) == 0) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine)); |
|
|
|
painter->setBrush(QColor(223, 238, 245, 45)); |
|
|
|
painter->drawRect(bounds_.adjusted(2, 2, -2, -2)); |
|
|
|
painter->setPen(kGroupBorderColor); |
|
|
|
painter->drawText( |
|
|
|
bounds_.adjusted(8, 3, -8, -3), |
|
|
|
Qt::AlignRight | Qt::AlignTop, |
|
|
|
kind_ == ConditionExpressionKind::Parallel |
|
|
|
? LogicEditorWidget::tr("并联组") : LogicEditorWidget::tr("串联组")); |
|
|
|
} |
|
|
|
|
|
|
|
const std::string &expressionId() const { return expression_id_; } |
|
|
|
const std::string &rungId() const { return rung_id_; } |
|
|
|
|
|
|
|
private: |
|
|
|
std::string expression_id_; |
|
|
|
std::string rung_id_; |
|
|
|
ConditionExpressionKind kind_ = ConditionExpressionKind::Series; |
|
|
|
QRectF bounds_; |
|
|
|
}; |
|
|
|
|
|
|
|
class LogicEditorWidget::RungItem final : public QGraphicsItem |
|
|
|
{ |
|
|
|
public: |
|
|
|
RungItem(const LadderRung &rung, int number, qreal top, qreal height, qreal width) |
|
|
|
RungItem( |
|
|
|
const LadderRung &rung, |
|
|
|
int number, |
|
|
|
qreal top, |
|
|
|
qreal height, |
|
|
|
qreal width, |
|
|
|
qreal cursor_left) |
|
|
|
: rung_id_(rung.id), |
|
|
|
name_(QString::fromStdString(rung.name)), |
|
|
|
number_(number), |
|
|
|
height_(height), |
|
|
|
width_(width) |
|
|
|
width_(width), |
|
|
|
cursor_left_(cursor_left), |
|
|
|
show_cursor_(!rung.condition.has_value()) |
|
|
|
{ |
|
|
|
setPos(0, top); |
|
|
|
setFlag(ItemIsSelectable, true); |
|
|
|
setZValue(-2.0); |
|
|
|
} |
|
|
|
|
|
|
|
QRectF boundingRect() const override { return {20, 0, width_ - 40, height_}; } |
|
|
|
QRectF boundingRect() const override { return {kSceneMargin, 0, width_, height_}; } |
|
|
|
|
|
|
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) override |
|
|
|
{ |
|
|
|
if ((option->state & QStyle::State_Selected) != 0) |
|
|
|
const bool selected = (option->state & QStyle::State_Selected) != 0; |
|
|
|
if (selected) |
|
|
|
{ |
|
|
|
painter->fillRect(boundingRect(), QColor(QStringLiteral("#f0f7fa"))); |
|
|
|
painter->fillRect(boundingRect(), QColor(240, 247, 250, 90)); |
|
|
|
if (show_cursor_) |
|
|
|
{ |
|
|
|
painter->setPen(QPen(kSelectionBorderColor, 1.4, Qt::DashLine)); |
|
|
|
painter->drawRect(QRectF( |
|
|
|
cursor_left_ + 3, |
|
|
|
kRungHeaderHeight + 3, |
|
|
|
kCellWidth - 6, |
|
|
|
kCellHeight - 6)); |
|
|
|
} |
|
|
|
} |
|
|
|
painter->setPen(QColor(QStringLiteral("#62717b"))); |
|
|
|
QString title = tr("网络 %1").arg(number_); |
|
|
|
@@ -335,9 +347,14 @@ public: |
|
|
|
{ |
|
|
|
title += QStringLiteral(":") + name_; |
|
|
|
} |
|
|
|
painter->drawText(QRectF(28, 6, 320, 22), Qt::AlignLeft | Qt::AlignVCenter, title); |
|
|
|
painter->drawText( |
|
|
|
QRectF(kSceneMargin + 8, 5, 320, 22), |
|
|
|
Qt::AlignLeft | Qt::AlignVCenter, |
|
|
|
title); |
|
|
|
painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1)); |
|
|
|
painter->drawLine(QPointF(28, height_ - 1), QPointF(width_ - 28, height_ - 1)); |
|
|
|
painter->drawLine( |
|
|
|
QPointF(kSceneMargin + 8, height_ - 1), |
|
|
|
QPointF(kSceneMargin + width_ - 8, height_ - 1)); |
|
|
|
} |
|
|
|
|
|
|
|
const std::string &rungId() const { return rung_id_; } |
|
|
|
@@ -348,21 +365,18 @@ private: |
|
|
|
int number_ = 0; |
|
|
|
qreal height_ = 0.0; |
|
|
|
qreal width_ = 0.0; |
|
|
|
qreal cursor_left_ = 0.0; |
|
|
|
bool show_cursor_ = false; |
|
|
|
}; |
|
|
|
|
|
|
|
namespace { |
|
|
|
|
|
|
|
struct RenderResult |
|
|
|
{ |
|
|
|
QPointF input; |
|
|
|
QPointF output; |
|
|
|
}; |
|
|
|
|
|
|
|
RenderResult renderExpression( |
|
|
|
QGraphicsScene &scene, |
|
|
|
const ConditionExpression &expression, |
|
|
|
const std::string &rung_id, |
|
|
|
const QRectF &bounds, |
|
|
|
const QPointF &top_left, |
|
|
|
const ExpressionMetrics &metrics, |
|
|
|
const LogicTraceSnapshot &trace, |
|
|
|
bool trace_enabled, |
|
|
|
const std::string &fault_node_id) |
|
|
|
@@ -371,38 +385,41 @@ RenderResult renderExpression( |
|
|
|
trace, &LogicTraceSnapshot::expressionValues, expression.id); |
|
|
|
if (expression.kind == ConditionExpressionKind::Node) |
|
|
|
{ |
|
|
|
const QPointF center = bounds.center(); |
|
|
|
const QPointF center( |
|
|
|
top_left.x() + kCellWidth / 2.0, |
|
|
|
top_left.y() + kCellHeight / 2.0); |
|
|
|
const bool node_active = trace_enabled && traceValue( |
|
|
|
trace, &LogicTraceSnapshot::nodeValues, expression.node->id); |
|
|
|
scene.addLine( |
|
|
|
QLineF( |
|
|
|
QPointF(top_left.x(), center.y()), |
|
|
|
QPointF(top_left.x() + kCellWidth, center.y())), |
|
|
|
ladderPen(node_active)); |
|
|
|
scene.addItem(new LogicEditorWidget::NodeItem( |
|
|
|
*expression.node, |
|
|
|
rung_id, |
|
|
|
expression.id, |
|
|
|
center, |
|
|
|
trace_enabled && traceValue( |
|
|
|
trace, &LogicTraceSnapshot::nodeValues, expression.node->id), |
|
|
|
node_active, |
|
|
|
expression.node->id == fault_node_id)); |
|
|
|
return {QPointF(bounds.left(), center.y()), QPointF(bounds.right(), center.y())}; |
|
|
|
return { |
|
|
|
QPointF(top_left.x(), center.y()), |
|
|
|
QPointF(top_left.x() + kCellWidth, center.y())}; |
|
|
|
} |
|
|
|
|
|
|
|
scene.addItem(new LogicEditorWidget::ExpressionItem( |
|
|
|
expression.id, rung_id, expression.kind, bounds)); |
|
|
|
if (expression.kind == ConditionExpressionKind::Series) |
|
|
|
{ |
|
|
|
qreal x = bounds.left() + kExpressionPadding; |
|
|
|
qreal x = top_left.x(); |
|
|
|
RenderResult first; |
|
|
|
RenderResult previous; |
|
|
|
for (std::size_t index = 0; index < expression.children.size(); ++index) |
|
|
|
{ |
|
|
|
const ExpressionSize size = measureExpression(expression.children[index]); |
|
|
|
const QRectF child_bounds( |
|
|
|
x, |
|
|
|
bounds.center().y() - size.height / 2.0, |
|
|
|
size.width, |
|
|
|
size.height); |
|
|
|
const ExpressionMetrics child_metrics = measureExpression(expression.children[index]); |
|
|
|
const RenderResult current = renderExpression( |
|
|
|
scene, |
|
|
|
expression.children[index], |
|
|
|
rung_id, |
|
|
|
child_bounds, |
|
|
|
QPointF(x, top_left.y()), |
|
|
|
child_metrics, |
|
|
|
trace, |
|
|
|
trace_enabled, |
|
|
|
fault_node_id); |
|
|
|
@@ -415,29 +432,31 @@ RenderResult renderExpression( |
|
|
|
scene.addLine(QLineF(previous.output, current.input), ladderPen(active)); |
|
|
|
} |
|
|
|
previous = current; |
|
|
|
x += size.width + kSeriesGap; |
|
|
|
x += static_cast<qreal>(child_metrics.columns) * kCellWidth; |
|
|
|
} |
|
|
|
scene.addLine(QLineF(QPointF(bounds.left(), first.input.y()), first.input), ladderPen(active)); |
|
|
|
scene.addLine(QLineF(previous.output, QPointF(bounds.right(), previous.output.y())), ladderPen(active)); |
|
|
|
return {QPointF(bounds.left(), first.input.y()), QPointF(bounds.right(), previous.output.y())}; |
|
|
|
return {first.input, previous.output}; |
|
|
|
} |
|
|
|
|
|
|
|
qreal y = bounds.top() + kExpressionPadding; |
|
|
|
qreal y = top_left.y(); |
|
|
|
std::vector<RenderResult> branches; |
|
|
|
branches.reserve(expression.children.size()); |
|
|
|
for (const ConditionExpression &child : expression.children) |
|
|
|
{ |
|
|
|
const ExpressionSize size = measureExpression(child); |
|
|
|
const QRectF child_bounds( |
|
|
|
bounds.center().x() - size.width / 2.0, |
|
|
|
y, |
|
|
|
size.width, |
|
|
|
size.height); |
|
|
|
const ExpressionMetrics child_metrics = measureExpression(child); |
|
|
|
branches.push_back(renderExpression( |
|
|
|
scene, child, rung_id, child_bounds, trace, trace_enabled, fault_node_id)); |
|
|
|
y += size.height + kParallelGap; |
|
|
|
scene, |
|
|
|
child, |
|
|
|
rung_id, |
|
|
|
QPointF(top_left.x(), y), |
|
|
|
child_metrics, |
|
|
|
trace, |
|
|
|
trace_enabled, |
|
|
|
fault_node_id)); |
|
|
|
y += static_cast<qreal>(child_metrics.rows) * kCellHeight; |
|
|
|
} |
|
|
|
const qreal left_join = bounds.left() + 8.0; |
|
|
|
const qreal right_join = bounds.right() - 8.0; |
|
|
|
|
|
|
|
const qreal left_join = top_left.x(); |
|
|
|
const qreal right_join = top_left.x() + static_cast<qreal>(metrics.columns) * kCellWidth; |
|
|
|
const qreal top_y = branches.front().input.y(); |
|
|
|
const qreal bottom_y = branches.back().input.y(); |
|
|
|
scene.addLine(QLineF(left_join, top_y, left_join, bottom_y), ladderPen(active)); |
|
|
|
@@ -445,7 +464,9 @@ RenderResult renderExpression( |
|
|
|
for (std::size_t index = 0; index < branches.size(); ++index) |
|
|
|
{ |
|
|
|
const bool branch_active = trace_enabled && traceValue( |
|
|
|
trace, &LogicTraceSnapshot::expressionValues, expression.children[index].id); |
|
|
|
trace, |
|
|
|
&LogicTraceSnapshot::expressionValues, |
|
|
|
expression.children[index].id); |
|
|
|
scene.addLine( |
|
|
|
QLineF(QPointF(left_join, branches[index].input.y()), branches[index].input), |
|
|
|
ladderPen(branch_active)); |
|
|
|
@@ -453,17 +474,7 @@ RenderResult renderExpression( |
|
|
|
QLineF(branches[index].output, QPointF(right_join, branches[index].output.y())), |
|
|
|
ladderPen(branch_active)); |
|
|
|
} |
|
|
|
return {QPointF(bounds.left(), top_y), QPointF(bounds.right(), top_y)}; |
|
|
|
} |
|
|
|
|
|
|
|
void addPlaceholder(QGraphicsScene &scene, const QPointF ¢er, const QString &text) |
|
|
|
{ |
|
|
|
const QRectF bounds(center.x() - 58, center.y() - 20, 116, 40); |
|
|
|
scene.addRect(bounds, QPen(kPlaceholderColor, 1.2, Qt::DashLine), QBrush(Qt::white)); |
|
|
|
QGraphicsTextItem *label = scene.addText(text); |
|
|
|
label->setDefaultTextColor(kPlaceholderColor); |
|
|
|
label->setPos(center.x() - label->boundingRect().width() / 2.0, |
|
|
|
center.y() - label->boundingRect().height() / 2.0); |
|
|
|
return {QPointF(left_join, top_y), QPointF(right_join, top_y)}; |
|
|
|
} |
|
|
|
|
|
|
|
} // namespace |
|
|
|
@@ -529,97 +540,105 @@ void LogicEditorWidget::reloadLogic() |
|
|
|
current_rung_id_ = logic->rungs.front().id; |
|
|
|
} |
|
|
|
|
|
|
|
qreal maximum_condition_width = 360.0; |
|
|
|
int condition_columns = 1; |
|
|
|
for (const LadderRung &rung : logic->rungs) |
|
|
|
{ |
|
|
|
if (rung.condition.has_value()) |
|
|
|
{ |
|
|
|
maximum_condition_width = std::max( |
|
|
|
maximum_condition_width, measureExpression(*rung.condition).width); |
|
|
|
condition_columns = std::max( |
|
|
|
condition_columns, |
|
|
|
measureExpression(*rung.condition).columns); |
|
|
|
} |
|
|
|
} |
|
|
|
const qreal scene_width = std::max( |
|
|
|
kMinimumSceneWidth, |
|
|
|
kLeftRailX + maximum_condition_width + kOutputGap + kOutputWidth + 96.0); |
|
|
|
const qreal right_rail_x = scene_width - 64.0; |
|
|
|
const qreal output_center_x = right_rail_x - kOutputWidth / 2.0; |
|
|
|
const int grid_columns = std::max(kMinimumLogicColumns, condition_columns + 2); |
|
|
|
const qreal left_rail_x = kSceneMargin + kRailInset; |
|
|
|
const qreal right_rail_x = left_rail_x + static_cast<qreal>(grid_columns) * kCellWidth; |
|
|
|
const qreal scene_width = std::max(kMinimumSceneWidth, right_rail_x + kSceneMargin); |
|
|
|
|
|
|
|
qreal top = 24.0; |
|
|
|
qreal top = kSceneMargin; |
|
|
|
int number = 1; |
|
|
|
for (const LadderRung &rung : logic->rungs) |
|
|
|
{ |
|
|
|
const ExpressionSize size = rung.condition.has_value() |
|
|
|
? measureExpression(*rung.condition) : ExpressionSize{240.0, kNodeHeight}; |
|
|
|
const qreal height = kRungHeaderHeight + size.height + 28.0; |
|
|
|
const qreal center_y = top + kRungHeaderHeight + size.height / 2.0; |
|
|
|
scene_->addItem(new RungItem(rung, number, top, height, scene_width)); |
|
|
|
const ExpressionMetrics metrics = rung.condition.has_value() |
|
|
|
? measureExpression(*rung.condition) : ExpressionMetrics{}; |
|
|
|
const int grid_rows = std::max(1, metrics.rows); |
|
|
|
const qreal grid_top = top + kRungHeaderHeight; |
|
|
|
const qreal rung_height = kRungHeaderHeight |
|
|
|
+ static_cast<qreal>(grid_rows) * kCellHeight + 12.0; |
|
|
|
scene_->addItem(new RungItem( |
|
|
|
rung, |
|
|
|
number, |
|
|
|
top, |
|
|
|
rung_height, |
|
|
|
scene_width - 2.0 * kSceneMargin, |
|
|
|
left_rail_x)); |
|
|
|
addGrid(*scene_, left_rail_x, grid_top, grid_columns, grid_rows); |
|
|
|
scene_->addLine( |
|
|
|
QLineF(kLeftRailX, top + 30, kLeftRailX, top + height - 12), |
|
|
|
QLineF(left_rail_x, grid_top, left_rail_x, |
|
|
|
grid_top + static_cast<qreal>(grid_rows) * kCellHeight), |
|
|
|
QPen(kLadderColor, 2.4)); |
|
|
|
scene_->addLine( |
|
|
|
QLineF(right_rail_x, top + 30, right_rail_x, top + height - 12), |
|
|
|
QLineF(right_rail_x, grid_top, right_rail_x, |
|
|
|
grid_top + static_cast<qreal>(grid_rows) * kCellHeight), |
|
|
|
QPen(kLadderColor, 2.4)); |
|
|
|
|
|
|
|
QPointF expression_output(kLeftRailX, center_y); |
|
|
|
const qreal main_y = grid_top + kCellHeight / 2.0; |
|
|
|
QPointF expression_output(left_rail_x, main_y); |
|
|
|
if (rung.condition.has_value()) |
|
|
|
{ |
|
|
|
const QRectF bounds( |
|
|
|
kLeftRailX + 24.0, |
|
|
|
center_y - size.height / 2.0, |
|
|
|
size.width, |
|
|
|
size.height); |
|
|
|
const RenderResult rendered = renderExpression( |
|
|
|
*scene_, |
|
|
|
*rung.condition, |
|
|
|
rung.id, |
|
|
|
bounds, |
|
|
|
QPointF(left_rail_x, grid_top), |
|
|
|
metrics, |
|
|
|
trace_, |
|
|
|
runtime_trace_enabled_, |
|
|
|
fault_node_id_); |
|
|
|
const bool active = runtime_trace_enabled_ && traceValue( |
|
|
|
trace_, &LogicTraceSnapshot::rungValues, rung.id); |
|
|
|
scene_->addLine( |
|
|
|
QLineF(QPointF(kLeftRailX, rendered.input.y()), rendered.input), |
|
|
|
ladderPen(active)); |
|
|
|
expression_output = rendered.output; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
addPlaceholder(*scene_, QPointF(kLeftRailX + 120.0, center_y), tr("添加条件")); |
|
|
|
scene_->addLine(QLineF(kLeftRailX, center_y, kLeftRailX + 62.0, center_y), |
|
|
|
ladderPen(false)); |
|
|
|
expression_output = QPointF(kLeftRailX + 178.0, center_y); |
|
|
|
addPlaceholder( |
|
|
|
*scene_, |
|
|
|
QRectF(left_rail_x, grid_top, kCellWidth, kCellHeight), |
|
|
|
tr("添加条件")); |
|
|
|
} |
|
|
|
|
|
|
|
const bool rung_active = runtime_trace_enabled_ && traceValue( |
|
|
|
trace_, &LogicTraceSnapshot::rungValues, rung.id); |
|
|
|
const qreal output_left = right_rail_x - kCellWidth; |
|
|
|
scene_->addLine( |
|
|
|
QLineF(expression_output, QPointF(output_center_x - kNodeTerminalX, expression_output.y())), |
|
|
|
QLineF(expression_output, QPointF(output_left, main_y)), |
|
|
|
ladderPen(rung_active)); |
|
|
|
if (rung.output.has_value()) |
|
|
|
{ |
|
|
|
scene_->addLine( |
|
|
|
QLineF(QPointF(output_left, main_y), QPointF(right_rail_x, main_y)), |
|
|
|
ladderPen(rung_active)); |
|
|
|
scene_->addItem(new NodeItem( |
|
|
|
*rung.output, |
|
|
|
rung.id, |
|
|
|
{}, |
|
|
|
QPointF(output_center_x, expression_output.y()), |
|
|
|
QPointF(output_left + kCellWidth / 2.0, main_y), |
|
|
|
rung_active, |
|
|
|
rung.output->id == fault_node_id_)); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
addPlaceholder(*scene_, QPointF(output_center_x, expression_output.y()), tr("输出线圈")); |
|
|
|
addPlaceholder( |
|
|
|
*scene_, |
|
|
|
QRectF(output_left, grid_top, kCellWidth, kCellHeight), |
|
|
|
tr("输出线圈")); |
|
|
|
scene_->addLine( |
|
|
|
QLineF( |
|
|
|
QPointF(output_left + kCellWidth, main_y), |
|
|
|
QPointF(right_rail_x, main_y)), |
|
|
|
ladderPen(rung_active)); |
|
|
|
} |
|
|
|
scene_->addLine( |
|
|
|
QLineF(output_center_x + kNodeTerminalX, |
|
|
|
expression_output.y(), |
|
|
|
right_rail_x, |
|
|
|
expression_output.y()), |
|
|
|
ladderPen(rung_active)); |
|
|
|
top += height + kRungGap; |
|
|
|
top += rung_height + kRungGap; |
|
|
|
++number; |
|
|
|
} |
|
|
|
scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + 20.0)); |
|
|
|
scene_->setSceneRect(0, 0, scene_width, std::max(400.0, top + kSceneMargin)); |
|
|
|
} |
|
|
|
|
|
|
|
void LogicEditorWidget::selectNode(const std::string &node_id) |
|
|
|
@@ -631,58 +650,74 @@ void LogicEditorWidget::selectNode(const std::string &node_id) |
|
|
|
node->setSelected(node->nodeId() == node_id); |
|
|
|
if (node->nodeId() == node_id) |
|
|
|
{ |
|
|
|
current_rung_id_ = node->rungId(); |
|
|
|
ensureVisible(node); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
item->setSelected(false); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
std::string LogicEditorWidget::selectedNodeId() const |
|
|
|
{ |
|
|
|
for (QGraphicsItem *item : scene_->selectedItems()) |
|
|
|
{ |
|
|
|
if (const NodeItem *node = dynamic_cast<const NodeItem *>(item)) |
|
|
|
{ |
|
|
|
return node->nodeId(); |
|
|
|
} |
|
|
|
} |
|
|
|
return {}; |
|
|
|
const std::vector<std::string> ids = selectedNodeIds(); |
|
|
|
return ids.size() == 1U ? ids.front() : std::string{}; |
|
|
|
} |
|
|
|
|
|
|
|
std::string LogicEditorWidget::selectedExpressionId() const |
|
|
|
std::vector<std::string> LogicEditorWidget::selectedNodeIds() const |
|
|
|
{ |
|
|
|
std::vector<std::pair<QPointF, std::string>> positioned_ids; |
|
|
|
for (QGraphicsItem *item : scene_->selectedItems()) |
|
|
|
{ |
|
|
|
if (const NodeItem *node = dynamic_cast<const NodeItem *>(item)) |
|
|
|
{ |
|
|
|
return node->expressionId(); |
|
|
|
positioned_ids.emplace_back(node->scenePos(), node->nodeId()); |
|
|
|
} |
|
|
|
if (const ExpressionItem *expression = dynamic_cast<const ExpressionItem *>(item)) |
|
|
|
} |
|
|
|
std::sort( |
|
|
|
positioned_ids.begin(), positioned_ids.end(), |
|
|
|
[](const auto &left, const auto &right) |
|
|
|
{ |
|
|
|
return expression->expressionId(); |
|
|
|
} |
|
|
|
if (!qFuzzyCompare(left.first.y(), right.first.y())) |
|
|
|
{ |
|
|
|
return left.first.y() < right.first.y(); |
|
|
|
} |
|
|
|
return left.first.x() < right.first.x(); |
|
|
|
}); |
|
|
|
std::vector<std::string> ids; |
|
|
|
ids.reserve(positioned_ids.size()); |
|
|
|
for (const auto &positioned_id : positioned_ids) |
|
|
|
{ |
|
|
|
ids.push_back(positioned_id.second); |
|
|
|
} |
|
|
|
return {}; |
|
|
|
return ids; |
|
|
|
} |
|
|
|
|
|
|
|
std::string LogicEditorWidget::selectedRungId() const |
|
|
|
{ |
|
|
|
std::string rung_id; |
|
|
|
for (QGraphicsItem *item : scene_->selectedItems()) |
|
|
|
{ |
|
|
|
if (const NodeItem *node = dynamic_cast<const NodeItem *>(item)) |
|
|
|
{ |
|
|
|
return node->rungId(); |
|
|
|
} |
|
|
|
if (const ExpressionItem *expression = dynamic_cast<const ExpressionItem *>(item)) |
|
|
|
{ |
|
|
|
return expression->rungId(); |
|
|
|
if (!rung_id.empty() && rung_id != node->rungId()) |
|
|
|
{ |
|
|
|
return {}; |
|
|
|
} |
|
|
|
rung_id = node->rungId(); |
|
|
|
} |
|
|
|
if (const RungItem *rung = dynamic_cast<const RungItem *>(item)) |
|
|
|
else if (const RungItem *rung = dynamic_cast<const RungItem *>(item)) |
|
|
|
{ |
|
|
|
return rung->rungId(); |
|
|
|
if (rung_id.empty()) |
|
|
|
{ |
|
|
|
rung_id = rung->rungId(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return {}; |
|
|
|
return rung_id; |
|
|
|
} |
|
|
|
|
|
|
|
LogicEditorResult LogicEditorWidget::addRung() |
|
|
|
@@ -701,38 +736,27 @@ LogicEditorResult LogicEditorWidget::addRung() |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
LogicEditorResult LogicEditorWidget::appendCondition(const LogicNodeConfig &config) |
|
|
|
{ |
|
|
|
const LogicEditorResult result = editor_service_.appendCondition( |
|
|
|
logic_id_, currentRungId(), config); |
|
|
|
if (result.succeeded) |
|
|
|
{ |
|
|
|
reloadLogic(); |
|
|
|
selectNode(result.id); |
|
|
|
emit graphChanged(); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
reportFailure(result); |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
LogicEditorResult LogicEditorWidget::insertCondition( |
|
|
|
const LogicNodeConfig &config, SeriesInsertPosition position) |
|
|
|
LogicEditorResult LogicEditorWidget::addCondition(const LogicNodeConfig &config) |
|
|
|
{ |
|
|
|
const std::string rung_id = selectedRungId(); |
|
|
|
const std::string expression_id = selectedExpressionId(); |
|
|
|
const std::vector<std::string> selected_ids = selectedNodeIds(); |
|
|
|
LogicEditorResult result; |
|
|
|
if (rung_id.empty() || expression_id.empty()) |
|
|
|
if (selected_ids.size() > 1U) |
|
|
|
{ |
|
|
|
result = {false, LogicEditorError::InvalidOperation, |
|
|
|
"请先选择串联插入目标节点或支路", {}}; |
|
|
|
"串联插入时只能选择一个节点", {}}; |
|
|
|
} |
|
|
|
else if (selected_ids.size() == 1U) |
|
|
|
{ |
|
|
|
const LogicNode *selected_node = editor_service_.findNode( |
|
|
|
logic_id_, selected_ids.front()); |
|
|
|
result = selected_node != nullptr && selected_node->isCondition() |
|
|
|
? editor_service_.insertConditionAfter( |
|
|
|
logic_id_, currentRungId(), selected_ids.front(), config) |
|
|
|
: editor_service_.appendCondition(logic_id_, currentRungId(), config); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
result = editor_service_.insertCondition( |
|
|
|
logic_id_, rung_id, expression_id, position, config); |
|
|
|
result = editor_service_.appendCondition(logic_id_, currentRungId(), config); |
|
|
|
} |
|
|
|
if (result.succeeded) |
|
|
|
{ |
|
|
|
@@ -747,20 +771,29 @@ LogicEditorResult LogicEditorWidget::insertCondition( |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
LogicEditorResult LogicEditorWidget::addParallelCondition(const LogicNodeConfig &config) |
|
|
|
LogicEditorResult LogicEditorWidget::addParallelBranch(const LogicNodeConfig &config) |
|
|
|
{ |
|
|
|
const std::string rung_id = selectedRungId(); |
|
|
|
const std::string expression_id = selectedExpressionId(); |
|
|
|
const std::vector<std::string> selected_ids = selectedNodeIds(); |
|
|
|
std::vector<std::string> condition_ids; |
|
|
|
for (const std::string &node_id : selected_ids) |
|
|
|
{ |
|
|
|
const LogicNode *node = editor_service_.findNode(logic_id_, node_id); |
|
|
|
if (node != nullptr && node->isCondition()) |
|
|
|
{ |
|
|
|
condition_ids.push_back(node_id); |
|
|
|
} |
|
|
|
} |
|
|
|
LogicEditorResult result; |
|
|
|
if (rung_id.empty() || expression_id.empty()) |
|
|
|
if (rung_id.empty() || condition_ids.empty()) |
|
|
|
{ |
|
|
|
result = {false, LogicEditorError::InvalidOperation, |
|
|
|
"请先选择要并联的节点或整条支路", {}}; |
|
|
|
"请在同一网络中选择要并联的连续节点", {}}; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
result = editor_service_.addParallelCondition( |
|
|
|
logic_id_, rung_id, expression_id, config); |
|
|
|
result = editor_service_.addParallelBranch( |
|
|
|
logic_id_, rung_id, condition_ids, config); |
|
|
|
} |
|
|
|
if (result.succeeded) |
|
|
|
{ |
|
|
|
@@ -794,31 +827,33 @@ LogicEditorResult LogicEditorWidget::setOutput(const LogicNodeConfig &config) |
|
|
|
|
|
|
|
LogicEditorResult LogicEditorWidget::deleteSelected() |
|
|
|
{ |
|
|
|
const std::vector<std::string> node_ids = selectedNodeIds(); |
|
|
|
LogicEditorResult result; |
|
|
|
const std::string node_id = selectedNodeId(); |
|
|
|
const std::string expression_id = selectedExpressionId(); |
|
|
|
const std::string rung_id = selectedRungId(); |
|
|
|
if (!node_id.empty()) |
|
|
|
{ |
|
|
|
result = editor_service_.removeNode(logic_id_, node_id); |
|
|
|
} |
|
|
|
else if (!expression_id.empty()) |
|
|
|
if (!node_ids.empty()) |
|
|
|
{ |
|
|
|
result = editor_service_.removeExpression(logic_id_, rung_id, expression_id); |
|
|
|
for (const std::string &node_id : node_ids) |
|
|
|
{ |
|
|
|
result = editor_service_.removeNode(logic_id_, node_id); |
|
|
|
if (!result.succeeded) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else if (!rung_id.empty()) |
|
|
|
else |
|
|
|
{ |
|
|
|
const std::string rung_id = selectedRungId(); |
|
|
|
if (rung_id.empty()) |
|
|
|
{ |
|
|
|
return {false, LogicEditorError::InvalidOperation, |
|
|
|
"请先选择要删除的逻辑节点或网络", {}}; |
|
|
|
} |
|
|
|
result = editor_service_.removeRung(logic_id_, rung_id); |
|
|
|
if (result.succeeded && current_rung_id_ == rung_id) |
|
|
|
{ |
|
|
|
current_rung_id_ = editor_service_.firstRungId(logic_id_); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
return {false, LogicEditorError::InvalidOperation, |
|
|
|
"请先选择要删除的逻辑节点、支路或网络", {}}; |
|
|
|
} |
|
|
|
if (result.succeeded) |
|
|
|
{ |
|
|
|
reloadLogic(); |
|
|
|
|