wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
IC.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "App/Element/IC.h"
5
6#include <algorithm>
7
8#include <QFileInfo>
9#include <QGraphicsSceneMouseEvent>
10#include <QPainter>
11#include <QStyleOptionGraphicsItem>
12
13#include "App/Core/Common.h"
22#include "App/IO/VersionInfo.h"
24#include "App/Wiring/Port.h"
25
26template<>
27struct ElementInfo<IC> {
29 .type = ElementType::IC,
30 .group = ElementGroup::IC,
31 .hasLabel = true,
32 };
33 static_assert(validate(constraints));
34
36 {
38 meta.pixmapPath = []{ return QString(); };
39 meta.titleText = QT_TRANSLATE_NOOP("IC", "INTEGRATED CIRCUIT");
40 meta.translatedName = QT_TRANSLATE_NOOP("IC", "IC");
41 meta.trContext = "IC";
42 meta.defaultAppearances = QStringList();
43 return meta;
44 }
45
46 static inline const bool registered = []() {
48 ElementFactory::registerCreator(constraints.type, [] { return new IC(); });
49 return true;
50 }();
51};
52
53IC::IC(QGraphicsItem *parent)
54 : GraphicElement(ElementType::IC, parent)
55{
56 // ICs display their label vertically alongside the chip body, matching the physical
57 // convention of reading IC labels along the side of a physical DIP package
58 m_label->setRotation(90);
59
60 // Enable hover events so the preview popup can be shown/hidden
61 setAcceptHoverEvents(true);
62}
63
65{
66 // No popup teardown needed here: ICPreviewPopup tracks the pending IC through a
67 // QPointer that auto-nulls on our destruction (and executeShow() guards on it), so a
68 // pending/showing preview can never dereference a deleted IC. Emitting a signal from a
69 // destructor is unsafe — Qt asserts "class destructor may have already run".
70 qDeleteAll(m_internalConnections);
71 qDeleteAll(m_internalElements);
72}
73
74QStringList IC::externalFiles() const
75{
76 QStringList result;
77 if (!isEmbedded() && !m_file.isEmpty()) {
78 result.append(m_file);
79 }
80 return result;
81}
82
83void IC::save(QDataStream &stream, SerializationOptions options) const
84{
85 GraphicElement::save(stream, options);
86
87 QMap<QString, QVariant> map;
88
89 if (isEmbedded()) {
90 map.insert("name", m_blobName);
91 } else if (!m_file.isEmpty()) {
92 map.insert("name", ExternalFilePath::forWriting(m_file, options.purpose));
93 }
94
95 stream << map;
96}
97
98void IC::load(QDataStream &stream, SerializationContext &context)
99{
100 GraphicElement::load(stream, context);
101
102 // Save the portMap keys for each port that GraphicElement::load() just
103 // registered. Loading the sub-circuit below destroys and recreates all
104 // ports, so we must re-register the new ports under the original keys
105 // to keep the outer portMap valid for connection deserialization.
106 // Old format uses ptr values as keys; new format uses serialIds — we
107 // build a reverse map to find each port's key in O(1) instead of scanning.
108 QHash<Port *, quint64> reversePortMap;
109 reversePortMap.reserve(context.portMap.size());
110 for (auto it = context.portMap.constBegin(); it != context.portMap.constEnd(); ++it) {
111 reversePortMap[it.value()] = it.key();
112 }
113
114 QVector<quint64> savedInputKeys, savedOutputKeys;
115 for (auto *port : inputs()) {
116 if (auto it = reversePortMap.constFind(port); it != reversePortMap.constEnd()) {
117 savedInputKeys.append(it.value());
118 }
119 }
120 for (auto *port : outputs()) {
121 if (auto it = reversePortMap.constFind(port); it != reversePortMap.constEnd()) {
122 savedOutputKeys.append(it.value());
123 }
124 }
125
126 // Old format (V_1_2 to V_4_1): IC file path was written as a plain QString
128 m_file = Serialization::readBoundedString(stream);
129
130 // Old files may have stored absolute paths from the original machine; keep only the
131 // filename so we can resolve it relative to the current context directory.
132 // Normalize backslashes first — QFileInfo::fileName() doesn't treat '\' as a
133 // separator on Unix, so a Windows path like "C:\...\sub.panda" would be kept as-is.
134 m_file = QFileInfo(QString(m_file).replace('\\', '/')).fileName();
135
136 loadFile(m_file, context.contextDir);
137 }
138
139 // New format (V_4_1+): IC data stored in a QMap for extensibility
140 if (VersionInfo::hasQMapFormat(context.version)) {
141 QMap<QString, QVariant> map = Serialization::readBoundedMetadata(stream);
142
143 const QString name = map.contains("name") ? map.value("name").toString()
144 : map.contains("fileName") ? map.value("fileName").toString()
145 : map.contains("blobName") ? map.value("blobName").toString()
146 : QString();
147
148 if (name.isEmpty()) {
149 throw PANDACEPTION("IC load: no IC name present in serialized data");
150 }
151
152 // Resolve: try exact name first (embedded blob names may contain dots),
153 // then baseName (file-backed "chain_c.panda" with registry key "chain_c")
154 const QString baseName = QFileInfo(name).baseName();
155 const bool exactMatch = context.blobRegistry && context.blobRegistry->contains(name);
156 const bool baseMatch = !exactMatch && context.blobRegistry && context.blobRegistry->contains(baseName);
157
158 if (exactMatch) {
159 const QByteArray blobBytes = context.blobRegistry->value(name);
160
161 // A cosmetic property edit (e.g. the label field, live on every keystroke —
162 // see ElementEditor::apply()) re-serializes and reloads this IC via UpdateCommand
163 // even though neither the blob reference nor its content actually changed. For a
164 // large embedded sub-circuit, resetInternalState()+loadFromBlob() below is a full
165 // teardown/rebuild of every internal element and connection -- skip it when
166 // nothing this IC depends on has moved. Comparing blob bytes (not just the name)
167 // is required for correctness: UpdateBlobCommand's undo/redo replaces the
168 // registry's bytes under this same name to restore/reapply a sub-circuit edit, and
169 // that must still reload.
170 const bool blobUnchanged = (m_blobName == name) && !m_internalElements.isEmpty()
171 && (m_loadedBlobSnapshot == blobBytes);
172
173 m_blobName = name;
174 if (!blobUnchanged) {
175 loadFromBlob(blobBytes, context.contextDir);
176 }
177 } else if (baseMatch) {
178 m_blobName = baseName;
179 loadFromBlob(context.blobRegistry->value(baseName), context.contextDir);
180 } else {
181 // Don't assign m_file until loadFile() has confirmed name actually resolves to a
182 // real file — ICLoader::loadFile() itself sets ic.m_file, but only once validated,
183 // so a name that resolves to neither a blob nor a file (e.g. an unrelated rename
184 // elsewhere in the registry) throws cleanly without leaving m_file holding a
185 // bogus non-path string.
186 loadFile(name, context.contextDir);
187 }
188 }
189
190 // Re-register the new ports in the outer portMap under the original keys
191 // so that subsequent connection deserialization can find them.
192 //
193 // When the embedded sub-circuit declares fewer ports than the outer
194 // element header did (malformed/fuzzed input), the leftover keys still
195 // resolved to ports that loadFromBlob() destroyed in setPortSize().
196 // Evict those stale entries so a later Connection::load() cannot
197 // dereference the freed pointer. Surfaced by libFuzzer.
198 for (int i = 0; i < savedInputKeys.size(); ++i) {
199 if (i < inputs().size()) {
200 context.portMap[savedInputKeys[i]] = inputs()[i];
201 } else {
202 context.portMap.remove(savedInputKeys[i]);
203 }
204 }
205 for (int i = 0; i < savedOutputKeys.size(); ++i) {
206 if (i < outputs().size()) {
207 context.portMap[savedOutputKeys[i]] = outputs()[i];
208 } else {
209 context.portMap.remove(savedOutputKeys[i]);
210 }
211 }
212}
213
214void IC::resetInternalState()
215{
216 m_internalInputs.clear();
217 m_internalOutputs.clear();
218 // No setInputSize(0)/setOutputSize(0) here: after the first load,
219 // loadBoundaryPorts locks min == max, making those calls silent no-ops —
220 // the boundary port counts are re-established by the next load anyway.
221 // Clear derived simulation state BEFORE freeing the elements those
222 // vectors reference. If we freed first, a simulation tick between the
223 // free and the clear (via Application::notify + QMessageBox spinning
224 // a nested event loop) would dereference dangling pointers.
225 m_sortedInternalElements.clear();
226 m_boundaryInputElements.clear();
227 m_internalHasFeedback = false;
228 qDeleteAll(m_internalConnections);
229 m_internalConnections.clear();
230 qDeleteAll(m_internalElements);
231 m_internalElements.clear();
232 // Bug 1 / Bug 3 postcondition: every owned vector must be cleared atomically
233 // with its parent. If a future change adds a new internal vector and forgets
234 // to clear it here, this assert trips immediately.
235 Q_ASSERT(m_internalElements.isEmpty());
236 Q_ASSERT(m_sortedInternalElements.isEmpty());
237 Q_ASSERT(m_internalConnections.isEmpty());
238 Q_ASSERT(m_internalInputs.isEmpty());
239 Q_ASSERT(m_internalOutputs.isEmpty());
240 Q_ASSERT(m_boundaryInputElements.isEmpty());
241}
242
243void IC::loadFile(const QString &fileName, const QString &contextDir)
244{
245 ICLoader::loadFile(*this, fileName, contextDir);
246}
247
248void IC::loadFromBlob(const QByteArray &blob, const QString &contextDir)
249{
250 ICLoader::loadFromBlob(*this, blob, contextDir);
251 m_loadedBlobSnapshot = blob;
252}
253
254void IC::loadFromDrop(const QString &fileName, const QString &contextDir)
255{
256 loadFile(fileName, contextDir);
257}
258
259IC::PortMetadata IC::buildPortMetadata(const QVector<GraphicElement *> &elements)
260{
261 return ICLoader::buildPortMetadata(elements);
262}
263
264void IC::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
265{
266 // Hide the preview popup immediately on double-click so it doesn't overlap the sub-circuit tab
267 emit previewCancelRequested(this);
268 event->accept();
269 emit requestOpenSubCircuit(id(), m_blobName, m_file);
270}
271
272// --- Hover preview ---
273
274QString IC::displayName() const
275{
276 if (!m_blobName.isEmpty()) {
277 return m_blobName;
278 }
279 return m_file.isEmpty() ? QString() : QFileInfo(m_file).fileName();
280}
281
282bool IC::isCursorOverPort(const QPointF &localPos) const
283{
284 const auto ports = allPorts();
285 return std::any_of(ports.cbegin(), ports.cend(), [&](const Port *port) {
286 return port->contains(mapToItem(port, localPos));
287 });
288}
289
290void IC::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
291{
292 // The preview belongs to the IC body only — the ports are child items that
293 // don't consume hover events, so the IC also receives them over the pins.
294 if (isCursorOverPort(event->pos())) {
296 return;
297 }
298
299 emit previewRequested(this, event->screenPos());
300}
301
302void IC::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
303{
304 if (isCursorOverPort(event->pos())) {
306 return;
307 }
308
309 // Over the body: keep tracking the cursor while a show is pending, but
310 // re-arm the show when the cursor returns from a port within the same hover.
311 // (The "already active" vs. "needs re-arming" distinction is made by whoever
312 // drives the popup off this signal — see SceneUiBinder.)
313 emit previewMoved(this, event->screenPos());
314}
315
316void IC::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
317{
318 Q_UNUSED(event)
320}
321
322QRectF IC::boundingRect() const
323{
324 return renderBodyBounds();
325}
326
327void IC::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
328{
329 Q_UNUSED(widget)
330 Q_UNUSED(option)
331
332 if (isSelected()) {
333 painter->save();
334 painter->setBrush(m_appearance.selectionBrush());
335 painter->setPen(QPen(m_appearance.selectionPen(), 0.5, Qt::SolidLine));
336 painter->drawRoundedRect(boundingRect(), 5, 5);
337 painter->restore();
338 }
339
340 // Draw the body as vectors (crisp at any zoom) rather than blitting a fixed-resolution pixmap.
341 ICRenderer::drawBody(*this, painter);
342}
343
348
350{
352}
353
358
360{
361}
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
Definition Common.h:98
Connection: a wire that connects an output port to an input port in the circuit scene.
Singleton factory for all circuit element types.
Self-registering element trait template and compile-time constraint validation.
ElementMetadata metadataFromConstraints(const ElementConstraints &c)
Converts ElementConstraints to an ElementMetadata with all constraint-derived fields set.
Definition ElementInfo.h:80
constexpr bool validate(const ElementConstraints &c)
Validates element constraints at compile time.
Definition ElementInfo.h:48
Enums::ElementType ElementType
Definition Enums.h:107
Single, shared implementation of how an element's external-file reference (an appearance image,...
ICLoader: loads an IC's sub-circuit from file or embedded blob.
ICRenderer: draws an IC's body and builds its cached pixmaps.
ICSimulation: builds and runs an IC's internal simulation graph.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
Port classes: Port (base), InputPort, and OutputPort.
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
Named version predicates for file-format compatibility checks.
static void registerCreator(ElementType type, std::function< GraphicElement *()> creator)
Registers a creator lambda for type, used by buildElement().
static void registerMetadata(const ElementMetadata &meta)
Registers meta in the global map (called once per element type at startup).
GraphicElement(ElementType type, QGraphicsItem *parent=nullptr)
Constructs a graphic element of the given type, fetching all properties from the metadata registry.
ElementAppearance m_appearance
QVector< Port * > allPorts() const
Returns a combined list of all input and output ports as Port pointers.
QRectF renderBodyBounds() const
Footprint of a "procedural render body" (IC/Mux/Demux/TruthTable): the nominal 64x64 body unioned wit...
virtual void load(QDataStream &stream, SerializationContext &context)
Loads the graphic element through a binary data stream.
QGraphicsSimpleTextItem * m_label
Child text item that displays the label and optional trigger shortcut.
virtual void save(QDataStream &stream, SerializationOptions options) const
const QVector< OutputPort * > & outputs() const
Returns a const reference to the vector of all output ports.
const QVector< InputPort * > & inputs() const
Returns a const reference to the vector of all input ports.
static IC::PortMetadata buildPortMetadata(const QVector< GraphicElement * > &elements)
Scans elements for Input/Output groups, sorts by Y/X position, and builds labels.
Definition ICLoader.cpp:468
static void loadFromBlob(IC &ic, const QByteArray &blob, const QString &contextDir)
Loads ic from in-memory blob bytes (full .panda file format).
Definition ICLoader.cpp:348
static void loadFile(IC &ic, const QString &fileName, const QString &contextDir={})
Loads ic's circuit from fileName and rebuilds the logic mapping.
Definition ICLoader.cpp:88
static void drawBody(IC &ic, QPainter *painter)
static void update(IC &ic)
Simulates ic's internal circuit and propagates results to its boundary.
static void resettle(IC &ic)
static void initialize(IC &ic)
Builds the internal simulation graph (connection graph + topological sort) for ic.
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
Definition IC.cpp:327
~IC() override
Definition IC.cpp:64
void resettleCombinational() override
Definition IC.cpp:354
void previewHideRequested()
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override
Definition IC.cpp:302
void initializeSimulation()
Builds the internal simulation graph (connection graph + sort) for direct simulation.
Definition IC.cpp:344
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
Definition IC.cpp:316
QString displayName() const
Definition IC.cpp:274
void loadFromBlob(const QByteArray &blob, const QString &contextDir)
Loads the IC from in-memory blob bytes (full .panda file format).
Definition IC.cpp:248
bool isEmbedded() const override
Returns true if this element is an embedded IC (not file-backed). Base returns false.
Definition IC.h:81
void refresh() override
Definition IC.cpp:359
void previewRequested(IC *ic, const QPoint &screenPos)
void load(QDataStream &stream, SerializationContext &context) override
Definition IC.cpp:98
void save(QDataStream &stream, SerializationOptions options) const override
Definition IC.cpp:83
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
Definition IC.cpp:264
QStringList externalFiles() const override
Definition IC.cpp:74
void loadFromDrop(const QString &fileName, const QString &contextDir) override
Polymorphic interface for drag-drop initialization (replaces elementType() == IC checks).
Definition IC.cpp:254
void previewMoved(IC *ic, const QPoint &screenPos)
static PortMetadata buildPortMetadata(const QVector< GraphicElement * > &elements)
Scans elements for Input/Output groups, sorts by Y/X position, and builds labels.
Definition IC.cpp:259
void requestOpenSubCircuit(int elementId, const QString &blobName, const QString &filePath)
Emitted on double-click to request opening the sub-circuit in a new tab.
void updateLogic() override
Definition IC.cpp:349
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override
Definition IC.cpp:290
void previewCancelRequested(IC *ic)
QRectF boundingRect() const override
Definition IC.cpp:322
IC(QGraphicsItem *parent=nullptr)
Constructs an IC element without loading a file.
Definition IC.cpp:53
void loadFile(const QString &fileName, const QString &contextDir={})
Loads the IC circuit from fileName and rebuilds the logic mapping.
Definition IC.cpp:243
Abstract base class for circuit element ports (connection endpoints).
Definition Port.h:39
static QString readBoundedString(QDataStream &stream)
Reads a QString from stream, refusing to allocate more bytes than remain in the stream (prevents OOM ...
static QMap< QString, QVariant > readBoundedMetadata(QDataStream &stream)
Reads the file-level metadata QMap<QString,QVariant> from stream without calling QList::reserve() wit...
QString forWriting(const QString &path, SerializationPurpose purpose)
bool hasQMapFormat(const QVersionNumber &v)
V4.1: Format changed from flat binary to keyed QMap; port serial IDs; rotation fix.
Definition VersionInfo.h:60
bool hasLabels(const QVersionNumber &v)
V1.2: Labels added to elements; IC filename field.
Definition VersionInfo.h:24
Compile-time-validatable subset of ElementMetadata.
Definition ElementInfo.h:21
static ElementMetadata metadata()
Definition IC.cpp:35
static constexpr ElementConstraints constraints
Definition IC.cpp:28
static const bool registered
Definition IC.cpp:46
Self-registering element information trait.
Compile-time-registered properties for one element type.
Port count and label metadata extracted from Input/Output elements.
Definition IC.h:96
Bundles all per-deserialization state so that load() overrides receive it through one explicit parame...
QVersionNumber version
File-format version read from the stream header.
QHash< quint64, Port * > & portMap
Accumulated port-pointer map built during deserialization.
QMap< QString, QByteArray > * blobRegistry
Blob registry for resolving embedded IC blobNames during deserialization.
QString contextDir
Directory of the .panda file (for relative path resolution).
Options passed to GraphicElement::save() (and friends); the save-side counterpart of SerializationCon...
SerializationPurpose purpose
What this serialization is for; see SerializationPurpose. Mandatory, no default.