wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
Display7.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
5
6#include <QCoreApplication>
7#include <QFile>
8#include <QHash>
9#include <QPainter>
10#include <QRegularExpression>
11#include <QSvgRenderer>
12#include <QThread>
13
14#include "App/Core/Common.h"
19#include "App/IO/VersionInfo.h"
20#include "App/Wiring/Port.h"
21
22namespace {
23
27QByteArray recoloredSegmentSvg(const QByteArray &svgBytes, const QColor &color)
28{
29 static const QRegularExpression fillHex(QStringLiteral("fill:#[0-9a-fA-F]{6}"));
30 QString svg = QString::fromUtf8(svgBytes);
31 svg.replace(fillHex, QStringLiteral("fill:%1").arg(color.name()));
32 return svg.toUtf8();
33}
34
35} // namespace
36
37template<>
40 .type = ElementType::Display7,
41 .group = ElementGroup::Output,
42 .minInputSize = 8,
43 .maxInputSize = 8,
44 .canChangeAppearance = true,
45 .hasColors = true,
46 .hasLabel = true,
47 .rotatesGraphic = false,
48 };
49 static_assert(validate(constraints));
50
52 {
54 meta.pixmapPath = []{ return QStringLiteral(":/Components/Output/Counter/counter_on.svg"); };
55 meta.titleText = QT_TRANSLATE_NOOP("Display7", "7-SEGMENT DISPLAY");
56 meta.translatedName = QT_TRANSLATE_NOOP("Display7", "7-Segment Display");
57 meta.trContext = "Display7";
58 meta.defaultAppearances = QStringList({
59 ":/Components/Output/Counter/counter_off.svg",
60 ":/Components/Output/Counter/counter_a.svg",
61 ":/Components/Output/Counter/counter_b.svg",
62 ":/Components/Output/Counter/counter_c.svg",
63 ":/Components/Output/Counter/counter_d.svg",
64 ":/Components/Output/Counter/counter_e.svg",
65 ":/Components/Output/Counter/counter_f.svg",
66 ":/Components/Output/Counter/counter_g.svg",
67 ":/Components/Output/Counter/counter_dp.svg",
68 });
69 return meta;
70 }
71
72 static inline const bool registered = []() {
74 ElementFactory::registerCreator(constraints.type, [] { return new Display7(); });
75 return true;
76 }();
77};
78
79Display7::Display7(QGraphicsItem *parent)
81{
82 a = cachedSegmentRenderers(m_appearance.defaultAppearances().at(1));
83 b = cachedSegmentRenderers(m_appearance.defaultAppearances().at(2));
84 c = cachedSegmentRenderers(m_appearance.defaultAppearances().at(3));
85 d = cachedSegmentRenderers(m_appearance.defaultAppearances().at(4));
86 e = cachedSegmentRenderers(m_appearance.defaultAppearances().at(5));
87 f = cachedSegmentRenderers(m_appearance.defaultAppearances().at(6));
88 g = cachedSegmentRenderers(m_appearance.defaultAppearances().at(7));
89 dp = cachedSegmentRenderers(m_appearance.defaultAppearances().at(8));
90
92}
93
94QVector<std::shared_ptr<QSvgRenderer>> Display7::cachedSegmentRenderers(const QString &resourcePath)
95{
96 Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread());
97
98 static QHash<QString, QVector<std::shared_ptr<QSvgRenderer>>> cache;
99
100 auto it = cache.find(resourcePath);
101 if (it != cache.end()) {
102 return *it;
103 }
104
105 QFile file(resourcePath);
106 const QByteArray svgBytes = file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
107
108 // Index order matches colorNameToIndex(): 0=White, 1=Red, 2=Green, 3=Blue, 4=Purple
109 static const QColor colors[] = {
110 QColor(255, 255, 255), QColor(255, 0, 0), QColor(0, 255, 0), QColor(0, 0, 255), QColor(255, 0, 255)
111 };
112
113 QVector<std::shared_ptr<QSvgRenderer>> renderers;
114 renderers.reserve(5);
115 for (const QColor &color : colors) {
116 renderers.append(std::make_shared<QSvgRenderer>(recoloredSegmentSvg(svgBytes, color)));
117 }
118
119 cache.insert(resourcePath, renderers);
120 return renderers;
121}
122
124{
125 update();
126}
127
129{
130 // Ports on the left side (x=0) correspond to the left segments (G, F, E, D);
131 // ports on the right side (x=64) correspond to the right segments and DP (A, B, DP, C).
132 // This matches the physical pin layout used by common 7-segment display ICs.
133 if (auto *port = inputPort(0)) { port->setPos( 0, 8); port->setName("G (" + tr("middle") + ")"); }
134 if (auto *port = inputPort(1)) { port->setPos( 0, 24); port->setName("F (" + tr("upper left") + ")"); }
135 if (auto *port = inputPort(2)) { port->setPos( 0, 40); port->setName("E (" + tr("lower left") + ")"); }
136 if (auto *port = inputPort(3)) { port->setPos( 0, 56); port->setName("D (" + tr("bottom") + ")"); }
137 if (auto *port = inputPort(4)) { port->setPos(64, 8); port->setName("A (" + tr("top") + ")"); }
138 if (auto *port = inputPort(5)) { port->setPos(64, 24); port->setName("B (" + tr("upper right") + ")"); }
139 if (auto *port = inputPort(6)) { port->setPos(64, 40); port->setName("DP (" + tr("dot") + ")"); }
140 if (auto *port = inputPort(7)) { port->setPos(64, 56); port->setName("C (" + tr("lower right") + ")"); }
141
142 for (auto *in : inputs()) {
143 // Segments are individually driven; leaving a pin unconnected turns that segment off
144 in->setRequired(false);
145 in->setDefaultStatus(Status::Inactive);
146 }
147
148 // This override doesn't chain to the base updatePortsProperties(), so re-apply the current
149 // rotation/flip orientation to the freshly-positioned ports (keeps flip working on displays).
150 rotatePorts();
151}
152
153void Display7::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
154{
155 // The base class draws the background (off-state SVG).
156 // Active segments are painted on top as vector renders, crisp at any zoom.
157 // All segment renderers share the background's 64x64 box, so they composite correctly.
158 GraphicElement::paint(painter, option, widget);
159
160 const QRectF body(0, 0, 64, 64);
161 if (auto *port = inputPort(0); port && port->status() == Status::Active) { g.at(m_colorNumber)->render(painter, body); }
162 if (auto *port = inputPort(1); port && port->status() == Status::Active) { f.at(m_colorNumber)->render(painter, body); }
163 if (auto *port = inputPort(2); port && port->status() == Status::Active) { e.at(m_colorNumber)->render(painter, body); }
164 if (auto *port = inputPort(3); port && port->status() == Status::Active) { d.at(m_colorNumber)->render(painter, body); }
165 if (auto *port = inputPort(4); port && port->status() == Status::Active) { a.at(m_colorNumber)->render(painter, body); }
166 if (auto *port = inputPort(5); port && port->status() == Status::Active) { b.at(m_colorNumber)->render(painter, body); }
167 if (auto *port = inputPort(6); port && port->status() == Status::Active) { dp.at(m_colorNumber)->render(painter, body); }
168 if (auto *port = inputPort(7); port && port->status() == Status::Active) { c.at(m_colorNumber)->render(painter, body); }
169}
170
171void Display7::setColor(const QString &color)
172{
173 m_color = color;
174 m_colorNumber = colorNameToIndex(color);
175}
176
177QString Display7::color() const
178{
179 return m_color;
180}
181
182void Display7::save(QDataStream &stream, SerializationOptions options) const
183{
184 GraphicElement::save(stream, options);
185
186 QMap<QString, QVariant> map;
187 // PortableFile streams omit the default; fresh-loaded elements start there
188 // anyway. Snapshots write unconditionally (reloaded into live elements).
190 map.insert("color", color());
191 }
192
193 stream << map;
194}
195
196void Display7::load(QDataStream &stream, SerializationContext &context)
197{
198 GraphicElement::load(stream, context);
199 /*
200 * 0, 7, 2, 1, 3, 4, 5, 6
201 * 7, 5, 4, 2, 1, 4, 6, 3, 0
202 * 4, 5, 7, 3, 2, 1, 0, 6
203 * 2, 1, 4, 5, 0, 7, 3, 6
204 */
205
207 // The pin-to-segment mapping changed at v1.6. The permutation
208 // order[i] = j means "the port that was at index i in the old file
209 // should now live at index j in the canonical layout."
210 // Permutation: old[0]→new[2], old[1]→new[1], old[2]→new[4], etc.
211 qCDebug(zero) << "Remapping inputs.";
212 QVector<int> order{2, 1, 4, 5, 0, 7, 3, 6};
213 QVector<InputPort *> aux = inputs();
214
215 if (aux.size() == order.size()) {
216 for (int i = 0; i < aux.size(); ++i) {
217 aux[order[i]] = inputs().value(i);
218 }
219 setInputs(aux);
221 }
222 }
223
225 // A second pin-order change occurred at v1.7, requiring another permutation
226 // applied on top of whatever v1.6 remapping already happened.
227 qCDebug(zero) << "Remapping inputs.";
228 QVector<int> order{2, 5, 4, 0, 7, 3, 6, 1};
229 QVector<InputPort *> aux = inputs();
230
231 if (aux.size() == order.size()) {
232 for (int i = 0; i < aux.size(); ++i) {
233 aux[order[i]] = inputs().value(i);
234 }
235 setInputs(aux);
237 }
238 }
239
241 // v3.1–4.0 stored color as a bare QString
242 const QString color_ = Serialization::readBoundedString(stream);
243 setColor(color_);
244 }
245
246 if (VersionInfo::hasQMapFormat(context.version)) {
247 // v4.1+ uses a key-value map
248 QMap<QString, QVariant> map = Serialization::readBoundedMetadata(stream);
249
250 if (map.contains("color")) {
251 setColor(map.value("color").toString());
252 }
253 }
254}
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Definition Common.h:29
Graphic element for the 7-segment LED display.
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
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.
7-segment LED display output element (7 segments + decimal point = 8 inputs).
Definition Display7.h:25
QString color() const override
Returns the current display color name.
Definition Display7.cpp:177
void load(QDataStream &stream, SerializationContext &context) override
Definition Display7.cpp:196
void save(QDataStream &stream, SerializationOptions options) const override
Serializes the color selection to stream.
Definition Display7.cpp:182
void updatePortsProperties() override
Updates port positions (fixed at 8 inputs).
Definition Display7.cpp:128
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
Definition Display7.cpp:153
void refresh() override
Refreshes the displayed segments based on current input states.
Definition Display7.cpp:123
static constexpr const char * kDefaultColor
Definition Display7.h:31
static QVector< std::shared_ptr< QSvgRenderer > > cachedSegmentRenderers(const QString &resourcePath)
Definition Display7.cpp:94
Display7(QGraphicsItem *parent=nullptr)
Constructs the element with optional parent.
Definition Display7.cpp:79
void setColor(const QString &color) override
Sets the display color.
Definition Display7.cpp:171
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
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
Paints the element onto the scene.
virtual void load(QDataStream &stream, SerializationContext &context)
Loads the graphic element through a binary data stream.
InputPort * inputPort(const int index=0) const
Returns the input port at index (default 0).
void setInputs(const QVector< InputPort * > &inputs)
Replaces the input port vector with inputs.
static int colorNameToIndex(const QString &color)
Maps a color name ("White","Red","Green","Blue","Purple") to indices 0–4.
virtual void save(QDataStream &stream, SerializationOptions options) const
const QVector< InputPort * > & inputs() const
Returns a const reference to the vector of all input ports.
Status status() const
Returns the current logical status (Active/Inactive/Unknown/Error).
Definition Port.h:79
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...
bool hasLockState(const QVersionNumber &v)
V3.1: Lock state for input elements; color for some display/button types.
Definition VersionInfo.h:51
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 hasDisplay7Color(const QVersionNumber &v)
V1.6: Display7 first color support.
Definition VersionInfo.h:33
bool hasDisplay7ExtColor(const QVersionNumber &v)
V1.7: Display7 additional color support.
Definition VersionInfo.h:36
Compile-time-validatable subset of ElementMetadata.
Definition ElementInfo.h:21
static ElementMetadata metadata()
Definition Display7.cpp:51
static constexpr ElementConstraints constraints
Definition Display7.cpp:39
static const bool registered
Definition Display7.cpp:72
Self-registering element information trait.
Compile-time-registered properties for one element type.
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.
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.