17void fillExerciseStepFields(
const QString &
id,
const QJsonObject &stepObj,
ExerciseStep &step)
19 const QString stepKey = stepObj.value(
"key").toString();
20 const QString rawInstruction = stepObj.value(
"instruction").toString();
21 const QString rawHint = stepObj.value(
"hint").toString();
23 if (stepKey.isEmpty()) {
27 const QString keyPrefix =
id + QLatin1Char(
'.') + stepKey + QLatin1Char(
'.');
32 for (
const QJsonValue &elemVal : stepObj.value(
"requiredElements").toArray()) {
33 const QJsonObject elemObj = elemVal.toObject();
35 req.
typeName = elemObj.value(
"type").toString();
36 req.
minCount = qMax(0, elemObj.value(
"minCount").toInt(1));
40 for (
const QJsonValue &connVal : stepObj.value(
"requiredConnections").toArray()) {
41 const QJsonObject connObj = connVal.toObject();
44 req.
toTypeName = connObj.value(
"toType").toString();
45 req.
minCount = qMax(0, connObj.value(
"minCount").toInt(1));
49 step.
click = stepObj.value(
"click").toVariant().toStringList();
50 step.
context = stepObj.value(
"context").toString();
57 , m_core(&fillExerciseStepFields,
65 return m_core.loadFromResource(resourcePath);
74 if (m_scene && m_core.isActive()) {
81 return m_core.currentStepData();
86 if (!m_core.start()) {
108 if (m_core.goToPreviousStep()) {
113void ExerciseEngine::performAdvance()
115 const auto result = m_core.
advance();
116 if (result.reachedEnd) {
120 if (result.advanced) {
133 if (m_core.retranslate()) {
138void ExerciseEngine::onCircuitChanged()
140 if (!validateCurrentStep()) {
146bool ExerciseEngine::validateCurrentStep()
const
148 if (!m_core.isActive() || m_core.totalSteps() == 0 || !m_scene) {
151 const ExerciseStep &step = m_core.currentStepData();
162bool ExerciseEngine::validateElements(
const QVector<ExerciseElementRequirement> &reqs)
const
164 if (reqs.isEmpty()) {
168 const QVector<GraphicElement *> elements = m_scene->elements();
170 for (
const ExerciseElementRequirement &req : reqs) {
172 if (required == ElementType::Unknown) {
173 qWarning() <<
"ExerciseEngine::validateElements: unknown element type" << req.typeName
174 <<
"in exercise" << m_core.id() <<
"-- this step can never be satisfied";
178 for (
const GraphicElement *elm : elements) {
179 if (elm->elementType() == required) {
190bool ExerciseEngine::validateConnections(
const QVector<ExerciseConnectionRequirement> &reqs)
const
192 if (reqs.isEmpty()) {
196 const QVector<GraphicElement *> elements = m_scene->elements();
198 for (
const ExerciseConnectionRequirement &req : reqs) {
201 if (toType == ElementType::Unknown || fromType == ElementType::Unknown) {
202 qWarning() <<
"ExerciseEngine::validateConnections: unknown element type in"
204 <<
"in exercise" << m_core.id() <<
"-- this step can never be satisfied";
209 for (
const GraphicElement *elm : elements) {
210 if (elm->elementType() != toType) {
213 for (
const InputPort *port : elm->inputs()) {
214 for (
const Connection *conn : port->connections()) {
215 if (conn->startPort()
216 && conn->startPort()->graphicElement()
217 && conn->startPort()->graphicElement()->elementType() == fromType) {
230void ExerciseEngine::emitCurrentStep()
235void ExerciseEngine::markCompleted()
237 m_core.markCompleted();
Connection: a wire that connects an output port to an input port in the circuit scene.
Singleton factory for all circuit element types.
Enums::ElementType ElementType
Abstract base class for all graphical circuit elements.
Port classes: Port (base), InputPort, and OutputPort.
Main circuit editing scene with undo/redo and user interaction.
Typed wrappers around QSettings for all application preferences.
static ElementType textToType(const QString &text)
Converts an element type name string to its ElementType enum value.
void setScene(Scene *scene)
bool loadFromResource(const QString &resourcePath)
void advanceStep()
Manually advances one step (used by the overlay Next/Finish button for observe steps).
void stepChanged(int step, int total, const ExerciseStep &data)
void stepCompleted(int step)
const ExerciseStep & currentStepData() const
Returns the data for the current step. Caller must check isActive() first.
ExerciseEngine(QObject *parent=nullptr)
static QString translate(const QString &key, const QString &fallbackEnglish)
Main circuit editing scene.
void circuitHasChanged()
Emitted whenever the circuit changes (element added/removed/moved).
static int exerciseProgress(const QString &exerciseId)
Returns -1 if no record.
static void setCompletedExercises(const QStringList &ids)
static void setExerciseProgress(const QString &exerciseId, int step)
static QStringList completedExercises()
QVector< ExerciseConnectionRequirement > requiredConnections
QVector< ExerciseElementRequirement > requiredElements
QStringList click
Widget/action IDs to activate on step enter.
QString context
"" or "circuit" → overlay on WorkSpace; "bwd" → overlay on BeWavedDolphin.