wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
Port.cpp
Go to the documentation of this file.
1// Copyright (c) 2012, STANISLAW ADASZEWSKI
2// SPDX-License-Identifier: BSD-3-Clause
3//
4// Portions Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
5// SPDX-License-Identifier: GPL-3.0-or-later
6
7#include "App/Wiring/Port.h"
8
9#include <QPainter>
10#include <QPen>
11
15
16Port::Port(QGraphicsItem *parent)
17 : QGraphicsPathItem(parent)
18{
19 // ItemSendsScenePositionChanges triggers itemChange(ItemScenePositionHasChanged)
20 // which keeps connected wires redrawn when the parent element moves
21 setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
22 setCacheMode(QGraphicsItem::DeviceCoordinateCache);
23}
24
25QPainterPath Port::shape() const
26{
27 // Hit-area: a small square centred on the port's origin (±kRadius), independent
28 // of the painted glyph — the direction shapes (circle/triangle) would otherwise
29 // shrink the grab target.
30 QPainterPath path;
31 path.addRect(QRectF(QPointF(-kRadius, -kRadius), QPointF(kRadius, kRadius)));
32 return path;
33}
34
35QRectF Port::boundingRect() const
36{
37 // The default QGraphicsPathItem bound is pen-exact (zero slack beyond the stroke), which
38 // gives DeviceCoordinateCache's device-pixel tile no room to antialias the edge -- at high
39 // zoom the pen's edge gets a hard clip instead of a soft fade. 1 local unit of margin (the
40 // port glyph is only ~10 units across) fixes that without perceptibly growing the glyph.
41 return QGraphicsPathItem::boundingRect().adjusted(-1, -1, 1, 1);
42}
43
44void Port::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
45{
46 Q_UNUSED(option)
47 Q_UNUSED(widget)
48
49 // Mirrors QGraphicsPathItem's default paint exactly, except the pen comes from
50 // m_currentPen (see updateTheme()) instead of the item's own, real pen() -- the brush
51 // is unaffected, still the item's real (and cheap to update) brush().
52 painter->setPen(m_currentPen);
53 painter->setBrush(brush());
54 painter->drawPath(path());
55}
56
57const QList<Connection *> &Port::connections() const
58{
59 return m_connections;
60}
61
63{
64 if (!conn) {
65 return;
66 }
67
68 // Guard against duplicate entries; Connection::setStartPort/setEndPort call connect()
69 // and may be called more than once during IC rewiring
70 if (!m_connections.contains(conn)) {
71 m_connections.append(conn);
72 }
73
75}
76
78{
79 m_connections.removeAll(conn);
80
81 // Null out the port reference on the connection so it knows it is detached;
82 // this prevents the connection from calling disconnect() again in its destructor
83 if (conn->startPort() == this) {
84 conn->setStartPort(nullptr);
85 }
86
87 if (conn->endPort() == this) {
88 conn->setEndPort(nullptr);
89 }
90
92}
93
94bool Port::isConnected(Port *otherPort)
95{
96 return std::any_of(m_connections.cbegin(), m_connections.cend(),
97 [&](auto *conn) { return (conn->startPort() == otherPort) || (conn->endPort() == otherPort); });
98}
99
101{
102 // Redraw all wires whose geometry depends on this port's scene position
103 for (auto *conn : std::as_const(m_connections)) {
104 conn->updatePosFromPorts();
105 }
106
107 // A port that violates its validity constraints (e.g. required but unconnected,
108 // or multi-driver) must show Error so the wiring problem is clearly visible
109 if (!isValid()) {
110 setStatus(Status::Error);
111 return;
112 }
113
114 // An unconnected optional input reverts to its default (design-time) signal value
115 // rather than staying at whatever status it last had
116 if (m_connections.empty() && isInput()) {
118 return;
119 }
120
121 // Re-sync from the connection so the port colour is correct after recovering
122 // from a temporarily invalid state (e.g. a second wire was added then removed).
123 // The simulation won't re-push an unchanged signal, so we pull it here.
124 if (isInput() && m_connections.size() == 1) {
125 setStatus(m_connections.constFirst()->status());
126 }
127}
128
129QVariant Port::itemChange(GraphicsItemChange change, const QVariant &value)
130{
131 if (change == ItemScenePositionHasChanged) {
133 }
134
135 return QGraphicsPathItem::itemChange(change, value);
136}
137
138int Port::index() const
139{
140 return m_index;
141}
142
143void Port::setIndex(const int index)
144{
145 m_index = index;
146}
147
149{
150 if (isOutput() && m_graphicElement) {
151 return m_index + m_graphicElement->inputSize();
152 }
153 return m_index;
154}
155
156quint64 Port::makeSerialId(quint64 elementBase, int globalIndex)
157{
158 return (elementBase << 16) | (static_cast<quint64>(globalIndex) & 0xFFFF);
159}
160
161QString Port::name() const
162{
163 return m_name;
164}
165
166void Port::setName(const QString &name)
167{
168 m_name = name;
169 setToolTip(name);
170}
171
172void Port::setDefaultStatus(const Status defaultStatus)
173{
174 m_defaultStatus = defaultStatus;
175 setStatus(defaultStatus);
176}
177
178QBrush Port::currentBrush() const
179{
180 return m_currentBrush;
181}
182
183void Port::setCurrentBrush(const QBrush &currentBrush)
184{
185 m_currentBrush = currentBrush;
186
187 // Qt::yellow is used by hoverEnter() as a transient highlight; don't overwrite it
188 // with the status colour while the user is hovering over the port
189 if (brush().color() != Qt::yellow) {
190 setBrush(currentBrush);
191 }
192}
193
195{
196 return m_required;
197}
198
199void Port::setRequired(const bool required)
200{
201 m_required = required;
202
203 // Requiredness feeds isValid(): re-derive the displayed status so a port
204 // marked optional recovers from Error and a newly required one shows it
206}
207
212
214{
215 setBrush(currentBrush());
216}
217
219{
220 setBrush(QBrush(ThemeManager::attributes().m_portHoverPort));
221}
222
224{
225 const auto &theme = ThemeManager::attributes();
226
227 // m_currentPen (drawn by paint()) is set directly instead of going through the item's
228 // own setPen() -- boundingRect() pads a pen-exact bound (see its own comment) but all
229 // four status pens below share the same implicit default width (theme.m_port*Pen are
230 // bare QColor, converted to QPen here), so the value never actually changes and the
231 // real setPen() would only pay for an unneeded BSP-tree re-index (prepareGeometryChange()).
232 // shape() already uses a fixed hit-area independent of pen width, so unlike Connection
233 // there's no hit-testing reason to ever fall back to the item's real pen either.
234 switch (m_status) {
235 case Status::Unknown: {
236 m_currentPen = theme.m_portUnknownPen;
237 setCurrentBrush(theme.m_portUnknownBrush);
238 break;
239 }
240 case Status::Inactive: {
241 m_currentPen = theme.m_portInactivePen;
242 setCurrentBrush(theme.m_portInactiveBrush);
243 break;
244 }
245 case Status::Active: {
246 m_currentPen = theme.m_portActivePen;
247 setCurrentBrush(theme.m_portActiveBrush);
248 break;
249 }
250 case Status::Error: {
251 m_currentPen = theme.m_portErrorPen;
252 setCurrentBrush(theme.m_portErrorBrush);
253 break;
254 }
255 }
256
257 update();
258}
259
261{
262 while (!m_connections.isEmpty()) {
263 auto *conn = m_connections.constLast();
264 m_connections.removeAll(conn);
265 if (isInput) {
266 conn->setEndPort(nullptr);
267 } else {
268 conn->setStartPort(nullptr);
269 }
270 // No Scene::removeItem call needed: ItemWithId self-unregisters from its
271 // SceneItemRegistry in its own destructor, on any destruction path -- including
272 // this one, where Qt's ~QGraphicsItem cascade dispatches to the non-virtual
273 // QGraphicsScene::removeItem and would otherwise skip our override, leaving a
274 // stale itemById entry pointing at freed memory (the WIREDPANDA-HC family).
275 delete conn;
276 }
277}
278
279InputPort::InputPort(QGraphicsItem *parent)
280 : Port(parent)
281{
282 // Circle: neutral connection point — the signal terminates here
283 QPainterPath path;
284 path.addEllipse(QRectF(-kRadius, -kRadius, 2 * kRadius, 2 * kRadius));
285 setPath(path);
286
287 // A fresh port is unconnected and required by default, so the validity rule
288 // enforced by setStatus()/updateConnections() applies from birth: show Error
289 // until a wire arrives or setRequired(false) relaxes the port. Without this,
290 // elements positioned before entering the scene (file load, scripted
291 // creation) never get a scene-position change to trigger the rule.
292 m_status = InputPort::isValid() ? m_status : Status::Error;
293
294 // Style directly: setStatus() would early-return here because m_status
295 // already holds its final construction-time value
296 updateTheme();
297}
298
300{
301 // An input port owns (and must clean up) all connections that terminate here.
302 // Manually remove from the list before deleting to prevent the connection destructor
303 // from calling disconnect() back into a partially destroyed port.
304 drainConnections(true);
305}
306
308{
309 if (status == m_status) {
310 return;
311 }
312
313 // If the port is invalid due to multiple drivers (bus conflict), emit Error so the
314 // user sees a clear red signal instead of a silent gray Unknown.
315 // Required-but-unconnected ports also become Error to make missing connections visible.
316 m_status = InputPort::isValid() ? status : Status::Error;
317
318 updateTheme();
319}
320
322{
323 return true;
324}
325
327{
328 return false;
329}
330
332{
333 // Valid states: unconnected and optional (default value is safe to use), OR
334 // exactly one connection (multi-driver wiring is not allowed in this simulation model)
335 return m_connections.isEmpty() ? !isRequired() : (m_connections.size() == 1);
336}
337
338OutputPort::OutputPort(QGraphicsItem *parent)
339 : Port(parent)
340{
341 // Right-pointing triangle: tip toward the wire, indicating signal flows outward
342 QPainterPath path;
343 path.moveTo(-kRadius, -kRadius);
344 path.lineTo(+kRadius, 0);
345 path.lineTo(-kRadius, +kRadius);
346 path.closeSubpath();
347 setPath(path);
348
349 // Apply the initial pen/brush directly: setStatus() would early-return here
350 // because m_status already holds the Unknown default
351 updateTheme();
352}
353
355{
356 // Mirror of InputPort destructor: output port also owns the connections that originate
357 // here and must break the back-reference before deletion to avoid re-entrant disconnect()
358 drainConnections(false);
359}
360
362{
363 if (status == m_status) {
364 return;
365 }
366
368 updateTheme();
369
370 // Fan-out: broadcast the new signal status to every wire leaving this output port;
371 // each wire in turn propagates it to the input port at its far end
372 for (auto *conn : connections()) {
373 conn->setStatus(status);
374 }
375}
376
378{
379 return false;
380}
381
383{
384 return true;
385}
386
388{
389 // Output ports have unrestricted fan-out and no connectivity constraints;
390 // their status is the simulation engine's responsibility, not a validity concern
391 return true;
392}
Connection: a wire that connects an output port to an input port in the circuit scene.
Enums::Status Status
Definition Enums.h:106
Abstract base class for all graphical circuit elements.
Port classes: Port (base), InputPort, and OutputPort.
Theme management types and singleton ThemeManager.
A bezier-curve wire connecting an output port to an input port in the scene.
Definition Connection.h:38
OutputPort * startPort() const
Returns the output port this connection originates from.
InputPort * endPort() const
Returns the input port this connection leads to.
void setEndPort(InputPort *port)
Sets the input port this connection leads to.
void setStartPort(OutputPort *port)
Sets the output port this connection originates from.
Abstract base class for all graphical circuit elements in wiRedPanda.
bool isInput() const override
Definition Port.cpp:321
bool isValid() const override
Definition Port.cpp:331
bool isOutput() const override
Definition Port.cpp:326
~InputPort() override
Definition Port.cpp:299
void setStatus(const Status status) override
Definition Port.cpp:307
InputPort(QGraphicsItem *parent=nullptr)
Constructs an input port attached to parent.
Definition Port.cpp:279
void setStatus(const Status status) override
Definition Port.cpp:361
bool isInput() const override
Definition Port.cpp:377
bool isOutput() const override
Definition Port.cpp:382
OutputPort(QGraphicsItem *parent=nullptr)
Constructs an output port attached to parent.
Definition Port.cpp:338
bool isValid() const override
Definition Port.cpp:387
~OutputPort() override
Definition Port.cpp:354
virtual bool isOutput() const =0
Returns true if this is an output port. Pure virtual.
Status m_status
Definition Port.h:213
void setCurrentBrush(const QBrush &currentBrush)
Sets the brush used to fill the port shape.
Definition Port.cpp:183
QRectF boundingRect() const override
Definition Port.cpp:35
int m_index
Definition Port.h:215
void attachConnection(Connection *conn)
Registers conn as a connection attached to this port.
Definition Port.cpp:62
bool m_required
Definition Port.h:214
virtual bool isValid() const =0
Returns true if the port satisfies its connection requirement. Pure virtual.
void hoverEnter()
Applies hover-enter visual feedback.
Definition Port.cpp:218
QString m_name
Definition Port.h:211
void setRequired(const bool required)
Marks whether a wire to this port is mandatory.
Definition Port.cpp:199
virtual void setStatus(Status status)=0
void setGraphicElement(GraphicElement *graphicElement)
Binds this port to graphicElement.
Definition Port.cpp:208
void updateConnections()
Triggers a path update on all attached connections.
Definition Port.cpp:100
void detachConnection(Connection *conn)
Removes conn from this port's connection list.
Definition Port.cpp:77
static constexpr int kRadius
Definition Port.h:199
Status m_defaultStatus
Definition Port.h:212
GraphicElement * graphicElement()
Returns the graphic element that owns this port.
Definition Port.h:66
Status status() const
Returns the current logical status (Active/Inactive/Unknown/Error).
Definition Port.h:79
QPen m_currentPen
Definition Port.h:209
void drainConnections(bool isInput)
Drains all attached connections, breaking back-references before deletion.
Definition Port.cpp:260
int globalIndex() const
Definition Port.cpp:148
QBrush m_currentBrush
Definition Port.h:204
void hoverLeave()
Reverts hover-enter visual feedback.
Definition Port.cpp:213
int index() const
Returns the port's visual/logical index within the element.
Definition Port.cpp:138
Port(QGraphicsItem *parent=nullptr)
Constructs the port with optional parent item.
Definition Port.cpp:16
static quint64 makeSerialId(quint64 elementBase, int globalIndex)
Definition Port.cpp:156
QPainterPath shape() const override
Definition Port.cpp:25
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
Definition Port.cpp:129
Status defaultValue() const
Returns the default status applied when the port is unconnected.
Definition Port.h:76
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
Definition Port.cpp:44
void setDefaultStatus(const Status defaultStatus)
Sets the status applied when the port has no connection.
Definition Port.cpp:172
void updateTheme()
Reapplies the status-based pen and brush from the current ThemeManager palette.
Definition Port.cpp:223
virtual bool isInput() const =0
Returns true if this is an input port. Pure virtual.
bool isConnected(Port *otherPort)
Returns true if this port is connected to otherPort via any wire.
Definition Port.cpp:94
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
Definition Port.cpp:57
GraphicElement * m_graphicElement
Definition Port.h:203
bool isRequired() const
Returns true if a connection to this port is mandatory.
Definition Port.cpp:194
void setName(const QString &name)
Sets the label text shown next to the port.
Definition Port.cpp:166
QString name() const
Returns the port's label text.
Definition Port.cpp:161
void setIndex(const int index)
Sets the port's visual index within the element.
Definition Port.cpp:143
QList< Connection * > m_connections
Definition Port.h:210
static const ThemeAttributes & attributes()
Returns the current ThemeAttributes color set.