12#include <QImageReader>
51 m_ui->lineEditTrigger->setValidator(
new QRegularExpressionValidator(QRegularExpression(
"[a-z]| |[A-Z]|[0-9]"),
this));
57 m_ui->checkBoxLocked->installEventFilter(m_tabNavigator);
58 m_ui->comboBoxAudio->installEventFilter(m_tabNavigator);
59 m_ui->comboBoxColor->installEventFilter(m_tabNavigator);
60 m_ui->comboBoxInputSize->installEventFilter(m_tabNavigator);
61 m_ui->comboBoxOutputSize->installEventFilter(m_tabNavigator);
62 m_ui->comboBoxValue->installEventFilter(m_tabNavigator);
63 m_ui->sliderDelay->installEventFilter(m_tabNavigator);
64 m_ui->doubleSpinBoxFrequency->installEventFilter(m_tabNavigator);
65 m_ui->sliderVolume->installEventFilter(m_tabNavigator);
66 m_ui->comboBoxWirelessMode->installEventFilter(m_tabNavigator);
67 m_ui->lineEditElementLabel->installEventFilter(m_tabNavigator);
68 m_ui->lineEditTrigger->installEventFilter(m_tabNavigator);
71 m_tableBox =
new QDialog(
this);
72 m_tableBox->setWindowFlags(Qt::Window);
73 m_tableBox->setWindowTitle(tr(
"Truth Table"));
74 m_tableBox->setModal(
true);
75 auto *tableLayout =
new QGridLayout(m_tableBox);
76 m_table =
new QTableWidget(
this);
77 tableLayout->addWidget(m_table);
78 m_tableBox->setLayout(tableLayout);
80 connect(m_table, &QTableWidget::cellDoubleClicked,
this, &ElementEditor::setTruthTableProposition);
81 connect(m_ui->checkBoxLocked, &QCheckBox::clicked,
this, &ElementEditor::inputLocked);
82 connect(m_ui->comboBoxAudio, qOverload<int>(&QComboBox::currentIndexChanged),
this, &ElementEditor::apply);
83 connect(m_ui->comboBoxColor, qOverload<int>(&QComboBox::currentIndexChanged),
this, &ElementEditor::apply);
84 connect(m_ui->comboBoxInputSize, qOverload<int>(&QComboBox::currentIndexChanged),
this, &ElementEditor::inputIndexChanged);
85 connect(m_ui->comboBoxOutputSize, qOverload<int>(&QComboBox::currentIndexChanged),
this, &ElementEditor::outputIndexChanged);
86 connect(m_ui->comboBoxValue, &QComboBox::currentTextChanged,
this, &ElementEditor::outputValueChanged);
87 connect(m_ui->sliderDelay, qOverload<int>(&QSlider::valueChanged),
this, &ElementEditor::apply);
88 connect(m_ui->doubleSpinBoxFrequency, qOverload<double>(&QDoubleSpinBox::valueChanged),
this, &ElementEditor::apply);
89 connect(m_ui->sliderVolume, qOverload<int>(&QSlider::valueChanged),
this, &ElementEditor::apply);
90 connect(m_ui->comboBoxWirelessMode, qOverload<int>(&QComboBox::currentIndexChanged),
this, &ElementEditor::apply);
91 connect(m_ui->lineEditElementLabel, &QLineEdit::textChanged,
this, &ElementEditor::apply);
92 connect(m_ui->lineEditTrigger, &QLineEdit::textChanged,
this, &ElementEditor::triggerChanged);
93 connect(m_ui->lineEditBlobName, &QLineEdit::editingFinished,
this, &ElementEditor::blobNameEditingFinished);
96 connect(m_ui->pushButtonDefaultAppearance, &QPushButton::clicked,
this, &ElementEditor::defaultAppearance);
105static void setSection(
bool show, QWidget *label, QWidget *widget)
107 label->setVisible(show);
108 widget->setVisible(show);
109 widget->setEnabled(show);
116static bool prepareCombo(QComboBox *cb,
bool hasFeature,
bool hasSame,
const QString &many)
118 if (cb->findText(many) == -1) {
127 cb->removeItem(cb->findText(many));
131 cb->setCurrentText(many);
138 screenPos, itemAtMouse, m_caps, m_elements, m_ui->comboBoxColor, m_scene,
139 [
this](QUndoCommand *cmd) { emit sendCommand(cmd); },
140 [
this] { renameAction(); },
141 [
this] { changeTriggerAction(); },
142 [
this] { updateElementAppearance(); },
143 [
this] { m_isDefaultAppearance = true; m_isUpdatingAppearance = true; apply(); },
144 [
this] { m_ui->doubleSpinBoxFrequency->setFocus(); },
147 if (m_elements.isEmpty()) return;
148 auto *elm = m_elements.first();
149 if (elm->isEmbedded()) {
150 emit editSubcircuitRequested(elm->blobName(), elm->id());
151 }
else if (elm->elementType() == ElementType::IC) {
152 auto *ic = static_cast<IC *>(elm);
153 emit openSubcircuitFileRequested(ic->file());
156 [
this] { emit embedSubcircuitRequested(); },
157 [
this] { emit extractToFileRequested(); }
163 m_ui->lineEditElementLabel->setFocus();
164 m_ui->lineEditElementLabel->selectAll();
169 m_ui->lineEditTrigger->setFocus();
170 m_ui->lineEditTrigger->selectAll();
173void ElementEditor::rebuildAppearanceStateTiles(
const QList<std::pair<int, QString>> &states)
176 while (
auto *item = m_ui->gridLayoutAppearanceStates->takeAt(0)) {
177 delete item->widget();
181 constexpr int columns = 4;
182 constexpr QSize tileSize(32, 32);
183 constexpr QSize iconSize(24, 24);
185 for (
int i = 0; i < states.size(); ++i) {
186 const auto &[index, label] = states[i];
187 auto *tile =
new QToolButton(m_ui->widgetAppearanceStates);
188 tile->setCheckable(
true);
189 tile->setAutoRaise(
true);
190 tile->setFixedSize(tileSize);
191 tile->setIconSize(iconSize);
192 tile->setIcon(QIcon(m_elements[0]->appearancePreviewPixmap(index, iconSize)));
193 tile->setToolTip(label);
194 tile->setProperty(
"appearanceStateIndex", index);
195 m_ui->buttonGroupAppearanceStates->addButton(tile);
196 m_ui->gridLayoutAppearanceStates->addWidget(tile, i / columns, i % columns);
200 if (
auto *item = m_ui->gridLayoutAppearanceStates->itemAtPosition(0, 0)) {
201 if (
auto *firstTile = qobject_cast<QToolButton *>(item->widget())) {
202 firstTile->setChecked(
true);
210 const QString imageFilter = tr(
"Images") +
" (*." + QImageReader::supportedImageFormats().join(
" *.") +
")";
213 if (fileName.isEmpty()) {
217 qCDebug(zero) <<
"File name: " << fileName;
221 if (m_ui->widgetAppearanceStates->isVisible() && m_ui->buttonGroupAppearanceStates->checkedButton()) {
222 const int appearanceIndex = m_ui->buttonGroupAppearanceStates->checkedButton()->property(
"appearanceStateIndex").toInt();
225 const bool needsMacro = m_scene && m_elements.size() > 1;
227 m_scene->undoStack()->beginMacro(tr(
"Change appearance"));
229 for (
auto *elm : std::as_const(m_elements)) {
232 QDataStream stream(&oldData, QIODevice::WriteOnly);
236 elm->setAppearanceAt(appearanceIndex, fileName);
238 m_scene->receiveCommand(
new UpdateCommand({elm}, oldData, m_scene));
242 m_scene->undoStack()->endMacro();
248 if (
auto *checked = qobject_cast<QToolButton *>(m_ui->buttonGroupAppearanceStates->checkedButton())) {
249 checked->setIcon(QIcon(m_elements[0]->appearancePreviewPixmap(appearanceIndex, checked->iconSize())));
254 m_isUpdatingAppearance =
true;
255 m_appearanceName = fileName;
256 m_isDefaultAppearance =
false;
265 disconnect(m_scene, &QGraphicsScene::selectionChanged,
this, &ElementEditor::selectionChanged);
274 connect(m_scene, &QGraphicsScene::selectionChanged,
this, &ElementEditor::selectionChanged);
285 QSignalBlocker blocker(m_ui->comboBoxColor);
286 m_ui->comboBoxColor->clear();
289 m_ui->comboBoxColor->addItem(QIcon(QPixmap(
":/Components/Output/Led/WhiteLed.svg")), tr(
"White"),
"White");
290 m_ui->comboBoxColor->addItem(QIcon(QPixmap(
":/Components/Output/Led/RedLed.svg")), tr(
"Red"),
"Red");
291 m_ui->comboBoxColor->addItem(QIcon(QPixmap(
":/Components/Output/Led/GreenLed.svg")), tr(
"Green"),
"Green");
292 m_ui->comboBoxColor->addItem(QIcon(QPixmap(
":/Components/Output/Led/BlueLed.svg")), tr(
"Blue"),
"Blue");
293 m_ui->comboBoxColor->addItem(QIcon(QPixmap(
":/Components/Output/Led/PurpleLed.svg")), tr(
"Purple"),
"Purple");
298 m_ui->retranslateUi(
this);
310void ElementEditor::setCurrentElements(
const QList<GraphicElement *> &elements)
312 m_elements = elements;
314 if (elements.isEmpty()) {
317 m_ui->lineEditElementLabel->setText(
"");
325 applyCapabilitiesToUi();
329void ElementEditor::applyCapabilitiesToUi()
331 const auto &c = m_caps;
332 auto *firstElement = m_elements.constFirst();
333 auto *firstInput = qobject_cast<GraphicElementInput *>(firstElement);
339 setSection(c.hasLabel, m_ui->labelLabels, m_ui->lineEditElementLabel);
341 m_ui->lineEditElementLabel->setText(c.hasSameLabel ? firstElement->label() : m_manyLabels);
345 setSection(c.hasColors, m_ui->labelColor, m_ui->comboBoxColor);
346 if (
prepareCombo(m_ui->comboBoxColor, c.hasColors, c.hasSameColors, m_manyColors)) {
347 m_ui->comboBoxColor->setCurrentIndex(m_ui->comboBoxColor->findData(firstElement->color()));
351 setSection(c.hasAudio, m_ui->labelAudio, m_ui->comboBoxAudio);
352 if (
prepareCombo(m_ui->comboBoxAudio, c.hasAudio, c.hasSameAudio, m_manyAudios)) {
353 m_ui->comboBoxAudio->setCurrentText(firstElement->audio());
357 setSection(c.hasVolume, m_ui->labelVolume, m_ui->sliderVolume);
358 if (c.hasVolume && c.hasSameVolume) {
359 m_ui->sliderVolume->setValue(
static_cast<int>(firstElement->volume() * 100.0f));
363 setSection(c.hasFrequency, m_ui->labelFrequency, m_ui->doubleSpinBoxFrequency);
364 if (c.hasFrequency) {
366 const bool isBuzzer = (c.hasSameType && c.elementType == ElementType::Buzzer);
367 const double minFreq = isBuzzer ? 20.0 : 0.1;
368 const double maxFreq = isBuzzer ? 20000.0 : 50.0;
369 const double step = isBuzzer ? 10.0 : 0.1;
370 const int decimals = isBuzzer ? 0 : 1;
371 const QString suffix = isBuzzer ? tr(
" Hz") : tr(
" Hz");
373 m_ui->doubleSpinBoxFrequency->setDecimals(decimals);
374 m_ui->doubleSpinBoxFrequency->setSingleStep(step);
375 m_ui->doubleSpinBoxFrequency->setMaximum(maxFreq);
376 m_ui->doubleSpinBoxFrequency->setSuffix(suffix);
378 if (c.hasSameFrequency) {
379 m_ui->doubleSpinBoxFrequency->setMinimum(minFreq);
380 m_ui->doubleSpinBoxFrequency->setSpecialValueText({});
381 m_ui->doubleSpinBoxFrequency->setValue(firstElement->frequency());
383 m_ui->doubleSpinBoxFrequency->setMinimum(0.0);
384 m_ui->doubleSpinBoxFrequency->setSpecialValueText(m_manyFreq);
385 m_ui->doubleSpinBoxFrequency->setValue(0.0);
390 setSection(c.hasDelay, m_ui->labelDelay, m_ui->sliderDelay);
393 m_ui->sliderDelay->setValue(c.hasSameDelay ?
static_cast<int>(firstElement->delay() * 8.0) : 0);
403 QSignalBlocker blocker(m_ui->comboBoxInputSize);
404 m_ui->comboBoxInputSize->clear();
405 setSection(c.canChangeInputSize, m_ui->labelInputs, m_ui->comboBoxInputSize);
406 if (c.canChangeInputSize && c.hasSameType && c.elementType == ElementType::Mux) {
410 for (
int dataInputs = 2; dataInputs <= 8; ++dataInputs) {
412 while ((1 << selectLines) < dataInputs) { ++selectLines; }
413 const int totalInputs = dataInputs + selectLines;
414 if (totalInputs >= c.minimumInputs && totalInputs <= c.maximumInputs) {
415 m_ui->comboBoxInputSize->addItem(QString::number(dataInputs), totalInputs);
419 for (
int port = c.minimumInputs; port <= c.maximumInputs; ++port) {
420 m_ui->comboBoxInputSize->addItem(QString::number(port), port);
423 if (
prepareCombo(m_ui->comboBoxInputSize, c.canChangeInputSize, c.hasSameInputSize, m_manyIS)) {
424 const int idx = m_ui->comboBoxInputSize->findData(firstElement->inputSize());
425 if (idx >= 0) { m_ui->comboBoxInputSize->setCurrentIndex(idx); }
431 QSignalBlocker blocker(m_ui->comboBoxOutputSize);
432 m_ui->comboBoxOutputSize->clear();
433 setSection(c.canChangeOutputSize, m_ui->labelOutputs, m_ui->comboBoxOutputSize);
434 if (c.canChangeOutputSize) {
435 if (c.hasSameType && c.elementType == ElementType::Demux) {
437 for (
int n = c.minimumOutputs; n <= c.maximumOutputs; ++n) {
438 m_ui->comboBoxOutputSize->addItem(QString::number(n), n);
443 for (
int n : {2, 3, 4, 6, 8, 10, 12, 16}) {
444 m_ui->comboBoxOutputSize->addItem(QString::number(n), n);
448 if (
prepareCombo(m_ui->comboBoxOutputSize, c.canChangeOutputSize && !c.hasTruthTable,
449 c.hasSameOutputSize, m_manyOS)) {
450 const int idx = m_ui->comboBoxOutputSize->findData(firstElement->outputSize());
451 if (idx >= 0) { m_ui->comboBoxOutputSize->setCurrentIndex(idx); }
456 m_ui->comboBoxValue->clear();
457 setSection(c.hasLatchedValue, m_ui->labelValue, m_ui->comboBoxValue);
458 if (c.hasLatchedValue) {
461 int maxOut = c.maxCurrentOutputSize;
462 if (maxOut == 1) { ++maxOut; }
463 for (
int val = 0; val < maxOut; ++val) {
464 m_ui->comboBoxValue->addItem(QString::number(val), val);
467 if (
prepareCombo(m_ui->comboBoxValue, c.hasLatchedValue, c.hasSameOutputValue, m_manyOV)) {
468 m_ui->comboBoxValue->setCurrentText(QString::number(firstInput->outputValue()));
472 setSection(c.hasOnlyInputs, m_ui->labelLocked, m_ui->checkBoxLocked);
473 if (c.hasOnlyInputs) {
476 if (c.sameCheckState) {
477 m_ui->checkBoxLocked->setCheckState(firstInput->isLocked() ? Qt::Checked : Qt::Unchecked);
479 m_ui->checkBoxLocked->setCheckState(Qt::PartiallyChecked);
484 setSection(c.hasTrigger, m_ui->labelTrigger, m_ui->lineEditTrigger);
486 m_ui->lineEditTrigger->setText(c.hasSameTrigger ? firstElement->trigger().toString() : m_manyTriggers);
490 m_ui->pushButtonAudioBox->setVisible(c.hasAudioBox);
491 m_ui->pushButtonAudioBox->setEnabled(c.hasAudioBox);
492 m_ui->labelAudioBox->setVisible(c.hasAudioBox);
493 m_ui->lineCurrentAudioBox->setVisible(c.hasAudioBox);
495 m_ui->lineCurrentAudioBox->setText(m_elements.size() > 1 ? m_manyAudios : m_elements[0]->audio());
499 m_ui->pushButtonTruthTable->setVisible(c.hasTruthTable);
500 m_ui->pushButtonTruthTable->setEnabled(c.hasTruthTable);
503 m_ui->pushButtonChangeAppearance->setVisible(c.canChangeAppearance);
504 m_ui->pushButtonDefaultAppearance->setVisible(c.canChangeAppearance);
507 if (c.canChangeAppearance && m_elements.size() == 1) {
508 const auto states = m_elements[0]->appearanceStates();
509 const bool multiState = states.size() > 1;
510 m_ui->labelAppearanceState->setVisible(multiState);
511 m_ui->widgetAppearanceStates->setVisible(multiState);
513 rebuildAppearanceStateTiles(states);
516 m_ui->labelAppearanceState->setVisible(
false);
517 m_ui->widgetAppearanceStates->setVisible(
false);
521 setSection(c.hasWirelessMode, m_ui->labelWirelessMode, m_ui->comboBoxWirelessMode);
522 if (c.hasWirelessMode && !m_elements.isEmpty()) {
523 auto *first = qobject_cast<Node *>(m_elements.constFirst());
524 const bool sameMode = std::all_of(m_elements.cbegin(), m_elements.cend(), [&](
GraphicElement *elm) {
525 const auto *n = qobject_cast<Node *>(elm);
526 return n && n->wirelessMode() == first->wirelessMode();
528 if (
prepareCombo(m_ui->comboBoxWirelessMode,
true, sameMode, m_manyWirelessModes) && first) {
529 QSignalBlocker blocker(m_ui->comboBoxWirelessMode);
530 m_ui->comboBoxWirelessMode->setCurrentIndex(
static_cast<int>(first->wirelessMode()));
535 setSection(c.isEmbedded, m_ui->labelBlobName, m_ui->lineEditBlobName);
536 if (c.isEmbedded && !m_elements.isEmpty()) {
537 const auto *firstElm = m_elements.constFirst();
538 const bool sameBlobName = std::all_of(m_elements.cbegin(), m_elements.cend(), [&](
GraphicElement *elm) {
539 return elm->blobName() == firstElm->blobName();
541 QSignalBlocker blocker(m_ui->lineEditBlobName);
542 m_ui->lineEditBlobName->setText(sameBlobName ? firstElement->blobName() : m_manyLabels);
548 m_ui->groupBoxIdentity->setVisible(c.hasLabel || c.hasColors);
549 m_ui->groupBoxPorts->setVisible(c.canChangeInputSize || c.canChangeOutputSize || c.hasLatchedValue || c.hasOnlyInputs);
550 m_ui->groupBoxTiming->setVisible(c.hasFrequency || c.hasDelay);
551 m_ui->groupBoxSound->setVisible(c.hasAudio || c.hasAudioBox || c.hasVolume);
552 m_ui->groupBoxInteraction->setVisible(c.hasTrigger || c.hasTruthTable || c.hasWirelessMode);
553 m_ui->groupBoxAppearanceSection->setVisible(c.canChangeAppearance || c.isEmbedded);
556void ElementEditor::selectionChanged()
558 setCurrentElements(m_scene->selectedElements());
565 if (m_ui->lineEditElementLabel->text() != m_manyLabels) {
566 elm->
setLabel(m_ui->lineEditElementLabel->text());
570 if (m_ui->comboBoxColor->currentData().isValid()) {
571 elm->
setColor(m_ui->comboBoxColor->currentData().toString());
575 if (m_ui->doubleSpinBoxFrequency->text() != m_manyFreq) {
576 elm->
setFrequency(m_ui->doubleSpinBoxFrequency->value());
581 const double delayFraction = m_ui->sliderDelay->value() / 8.0;
586 if (m_ui->comboBoxAudio->currentText() != m_manyAudios) {
587 elm->
setAudio(m_ui->comboBoxAudio->currentText());
593 if (m_ui->lineEditTrigger->text() != m_manyTriggers && m_ui->lineEditTrigger->text().size() <= 1) {
594 elm->
setTrigger(QKeySequence(m_ui->lineEditTrigger->text()));
598 if (m_isUpdatingAppearance) {
603 if (m_ui->comboBoxWirelessMode->currentText() != m_manyWirelessModes) {
604 if (
auto *node = qobject_cast<Node *>(elm)) {
605 node->setWirelessMode(
static_cast<WirelessMode>(m_ui->comboBoxWirelessMode->currentIndex()));
610 elm->
setVolume(
static_cast<float>(m_ui->sliderVolume->value()) / 100.0f);
619void ElementEditor::apply()
627 if (m_elements.isEmpty() || !isEnabled() || !m_scene) {
634 if (m_scene && m_caps.hasWirelessMode) {
635 const QString newLabel = m_ui->lineEditElementLabel->text();
636 const bool labelChanging = (newLabel != m_manyLabels);
638 const bool modeChanging = (m_ui->comboBoxWirelessMode->currentText() != m_manyWirelessModes);
640 for (
auto *elm : std::as_const(m_elements)) {
641 auto *node = qobject_cast<Node *>(elm);
644 const QString candidateLabel = labelChanging ? newLabel : node->label();
645 const WirelessMode candidateMode = modeChanging ? newMode : node->wirelessMode();
647 if (candidateMode != WirelessMode::Tx || candidateLabel.isEmpty())
continue;
649 for (
auto *other : m_scene->elements()) {
650 if (other == elm)
continue;
651 auto *otherNode = qobject_cast<Node *>(other);
652 if (!otherNode || otherNode->wirelessMode() != WirelessMode::Tx)
continue;
653 if (otherNode->label() == candidateLabel) {
654 QMessageBox::warning(
this,
655 tr(
"Duplicate Wireless Channel"),
656 tr(
"A Tx node with label \"%1\" already exists.\n"
657 "Each wireless channel must have a unique label.").arg(candidateLabel));
668 QList<QGraphicsItem *> wirelessConnsToDelete;
669 if (m_scene && m_caps.hasWirelessMode) {
671 const bool modeChanging = (m_ui->comboBoxWirelessMode->currentText() != m_manyWirelessModes);
674 for (
auto *elm : std::as_const(m_elements)) {
675 auto *node = qobject_cast<Node *>(elm);
678 Port *port = (newMode == WirelessMode::Rx) ?
static_cast<Port *
>(node->inputPort())
679 : (newMode == WirelessMode::Tx) ?
static_cast<Port *
>(node->outputPort())
683 wirelessConnsToDelete.append(
static_cast<QGraphicsItem *
>(conn));
694 QDataStream stream(&oldData, QIODevice::WriteOnly);
697 for (
auto *elm : std::as_const(m_elements)) {
700 applyProperty(elm, prop.type);
705 if (m_isUpdatingAppearance) {
706 m_isUpdatingAppearance =
false;
711 const bool needsMacro = !wirelessConnsToDelete.isEmpty();
713 m_scene->undoStack()->beginMacro(tr(
"Change wireless mode"));
716 m_scene->receiveCommand(
new UpdateCommand(m_elements, oldData, m_scene));
719 m_scene->receiveCommand(
new DeleteItemsCommand(wirelessConnsToDelete, m_scene));
720 m_scene->undoStack()->endMacro();
724void ElementEditor::blobNameEditingFinished()
726 if (m_elements.isEmpty() || !m_caps.isEmbedded) {
730 const QString newBlobName = m_ui->lineEditBlobName->text().trimmed();
731 const QString oldBlobName = m_elements.first()->blobName();
736 if (newBlobName.isEmpty() || newBlobName == oldBlobName || newBlobName == m_manyLabels) {
744 if (m_scene->icRegistry()->hasBlob(newBlobName)) {
745 QMessageBox::warning(
this,
746 tr(
"Duplicate IC Name"),
747 tr(
"An embedded IC named \"%1\" already exists.\n"
748 "Choose a different name.").arg(newBlobName));
755 m_scene->receiveCommand(
new RenameBlobCommand(oldBlobName, newBlobName, m_scene));
758void ElementEditor::inputIndexChanged(
const int index)
760 if (m_elements.isEmpty() || !isEnabled()) {
765 if (m_caps.canChangeInputSize && (m_ui->comboBoxInputSize->currentText() != m_manyIS)) {
766 emit
sendCommand(
new ChangePortSizeCommand(m_elements, m_ui->comboBoxInputSize->currentData().toInt(), m_scene,
true));
769 qCDebug(zero) <<
"Input size changed to " << index;
773void ElementEditor::outputIndexChanged(
const int index)
775 if (m_elements.isEmpty() || !isEnabled()) {
780 if (m_caps.canChangeOutputSize && (m_ui->comboBoxOutputSize->currentText() != m_manyOS)) {
781 emit
sendCommand(
new ChangePortSizeCommand(m_elements, m_ui->comboBoxOutputSize->currentData().toInt(), m_scene,
false));
784 qCDebug(zero) <<
"Output size changed to " << index;
788void ElementEditor::outputValueChanged(
const QString &value)
790 if (m_elements.isEmpty() || !isEnabled()) {
794 const int newValue = value.toInt();
796 for (
auto *elm : std::as_const(m_elements)) {
797 if (elm->
elementType() == ElementType::InputRotary) {
800 if (
auto *input = qobject_cast<InputRotary *>(elm)) {
801 input->setOn(
true, newValue);
805 if (
auto *input = qobject_cast<GraphicElementInput *>(elm)) {
806 input->setOn(
static_cast<bool>(newValue));
814void ElementEditor::inputLocked(
const bool value)
816 if (m_elements.isEmpty() || !isEnabled()) {
820 for (
auto *elm : std::as_const(m_elements)) {
821 if (
auto *input = qobject_cast<GraphicElementInput *>(elm)) {
822 input->setLocked(value);
826 qCDebug(zero) <<
"Input locked.";
830void ElementEditor::triggerChanged(
const QString &cmd)
833 m_ui->lineEditTrigger->setText(cmd.toUpper());
840 if (!m_caps.hasTruthTable || m_elements.isEmpty())
return;
843 auto *truthtable = qobject_cast<TruthTable *>(m_elements[0]);
849 int nInputs = truthtable->inputSize();
850 int nOutputs = truthtable->outputSize();
853 QStringList inputLabels;
854 m_table->setColumnCount(nInputs + nOutputs);
856 m_table->setRowCount(
static_cast<int>(std::pow(2, nInputs)));
858 for (
int i = 0; i < nInputs; ++i) {
859 inputLabels.append(QChar::fromLatin1(
static_cast<char>(
'A' + i)));
862 for (
int i = 0; i < truthtable->outputSize(); ++i) {
863 inputLabels.append(
"S" + QString::number(i));
864 m_table->setColumnWidth(nInputs + i, 14);
867 m_table->setHorizontalHeaderLabels(inputLabels);
869 for (
int i = 0; i < std::pow(2, nInputs); ++i) {
870 for (
int j = 0; j < nInputs; ++j) {
871 m_table->setColumnWidth(j, 14);
873 auto newItemValue = QString::number(i, 2);
875 if (newItemValue.size() < nInputs) {
876 newItemValue = newItemValue.rightJustified(nInputs,
'0');
880 if (m_table->item(i, j) ==
nullptr) {
881 auto *newItem =
new QTableWidgetItem(newItemValue.at(j), QTableWidgetItem::Type);
882 newItem->setTextAlignment(Qt::AlignCenter);
883 m_table->setItem(i, j, newItem);
885 m_table->item(i, j)->setFlags(Qt::ItemIsEnabled);
888 m_table->item(i, j)->setText(newItemValue.at(j));
893 auto bitArray = truthtable->key();
895 for (
int z = 0; z < nOutputs; ++z) {
898 const int output = bitArray.at(256 * z + i);
900 if (m_table->item(i, nInputs + z) ==
nullptr) {
901 auto *newOutItem =
new QTableWidgetItem(QString(QChar::fromLatin1(
static_cast<char>(
'0' + output))));
902 newOutItem->setTextAlignment(Qt::AlignCenter);
903 m_table->setItem(i, nInputs + z, newOutItem);
904 m_table->item(i, nInputs + z)->setFlags(Qt::ItemIsEnabled);
907 m_table->item(i, nInputs + z)->setText(QString::number(output));
914void ElementEditor::setTruthTableProposition(
const int row,
const int column)
918 if (m_elements.size() != 1)
return;
921 if (column < m_elements[0]->inputSize())
return;
925 auto cellItem = m_table->item(row, column);
926 cellItem->setText((cellItem->text() ==
"0") ?
"1" :
"0");
929 auto truthtable = m_elements[0];
930 const int nInputs = truthtable->inputSize();
932 const int positionToChange = 256 * (column - nInputs) + row;
947 if (m_elements.isEmpty()) {
950 auto *audiobox = qobject_cast<AudioBox *>(m_elements[0]);
956 QString(), tr(
"Audio") +
" (*.mp3 *.mp4 *.wav *.ogg)");
958 if (filePath.isEmpty()) {
962 audiobox->setAudio(filePath);
963 m_ui->lineCurrentAudioBox->setText(QFileInfo(filePath).fileName());
966void ElementEditor::defaultAppearance()
971 m_isUpdatingAppearance =
true;
972 m_isDefaultAppearance =
true;
980 setCurrentElements(m_elements);
988 const QString styleSheet =
990 " border: 1px solid rgb(%1,%1,%1);"
991 " border-radius: 2px;"
996 " subcontrol-origin: margin;"
997 " subcontrol-position: top;"
1000 m_ui->groupBox->setStyleSheet(styleSheet.arg(borderColor));
Graphic element for the AudioBox (external audio file player) output.
All QUndoCommand subclasses and the CommandUtils helper namespace.
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Connection: a wire that connects an output port to an input port in the circuit scene.
ElementEditorUi: hand-written UI class for the ElementEditor widget.
static void setSection(bool show, QWidget *label, QWidget *widget)
static bool prepareCombo(QComboBox *cb, bool hasFeature, bool hasSame, const QString &many)
ElementEditor widget for inspecting and modifying selected circuit element properties.
Singleton factory for all circuit element types.
ElementTabNavigator event filter for Tab/Backtab cycling between scene elements.
Enums::WirelessMode WirelessMode
Abstract file dialog interface for testability.
IC definition registry with file watching and embedded blob storage.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
LabeledSlider: QSlider subclass that draws numeric labels beneath each tick mark.
Graphic element for a wire junction node.
Main circuit editing scene with undo/redo and user interaction.
SelectionCapabilities computeCapabilities(const QList< GraphicElement * > &elements)
Computes the SelectionCapabilities for a list of selected elements.
SelectionCapabilities struct and computeCapabilities() free function.
Lightweight Sentry helpers gated behind HAVE_SENTRY.
void sentryBreadcrumb(const char *category, const QString &message)
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
Theme management types and singleton ThemeManager.
Graphic element for a user-programmable truth table.
void setScene(Scene *scene)
Associates the editor with scene to read/write selected elements.
void audioBox()
Opens the audio file selector for the selected element(s).
void update()
Refreshes the editor UI to reflect the current selection.
void sendCommand(QUndoCommand *cmd)
Emitted when a property change should be pushed onto the undo stack.
~ElementEditor() override
void fillColorComboBox()
Repopulates the color combo box with available color options.
void contextMenu(QPoint screenPos, QGraphicsItem *itemAtMouse)
Shows the element context menu at screenPos for the item under itemAtMouse.
void updateElementAppearance()
Applies the chosen appearance to the selected element(s).
void truthTable()
Opens the truth table editor for the selected element(s).
void renameAction()
Opens an inline editor to rename the selected element(s).
ElementEditor(QWidget *parent=nullptr)
Constructs the element editor with optional parent.
void updateTheme()
Applies the current theme colors to the editor widgets.
friend class ElementTabNavigator
void changeTriggerAction()
Opens a dialog to change the trigger key for the selected element(s).
void retranslateUi()
Re-applies translatable strings to all editor widgets.
static QString typeToTitleText(const ElementType type)
Returns the display title string for type.
virtual QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter)=0
Abstract base class for all graphical circuit elements in wiRedPanda.
virtual void setFrequency(const double freq)
Sets the clock frequency to freq (overridden by clock elements).
ElementType elementType() const
Returns the type identifier for this element.
virtual void setColor(const QString &color)
Sets the element color to color and refreshes the pixmap.
virtual void setAudio(const QString &audio)
Sets the audio file associated with this element to audio.
QList< PropertyDescriptor > editableProperties() const
Returns the list of editable properties this element exposes in the ElementEditor.
void setTrigger(const QKeySequence &trigger)
Sets the keyboard shortcut to trigger and updates the label.
void setLabel(const QString &label)
Sets the label text to label and refreshes the display.
virtual void save(QDataStream &stream, SerializationOptions options) const
virtual void setDelay(const double delay)
Sets the clock phase delay to delay (overridden by clock elements).
virtual void setAppearance(const bool defaultAppearance, const QString &fileName)
Switches the element's appearance.
virtual void setVolume(float vol)
Sets the audio playback volume to vol (0.0–1.0).
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
Main circuit editing scene.
void truthTableElementChanged(GraphicElement *element)
Emitted after a TruthTable element's output bit is toggled (redo or undo).
void contextMenuPos(QPoint screenPos, QGraphicsItem *itemAtMouse)
Emitted when a context menu should appear.
void setCircuitUpdateRequired()
Marks the simulation mapping as stale so it is rebuilt on the next tick.
void receiveCommand(QUndoCommand *cmd)
Pushes cmd onto the undo stack (immediately executes its redo()).
static void writePandaHeader(QDataStream &stream)
Writes the .panda circuit file header to stream.
static Theme effectiveTheme()
Returns the effective theme (Light or Dark), resolving System to the OS preference.
Undo command that toggles one output bit in a TruthTable element.
Undo command for property changes (label, color, frequency, appearance, etc.).
FileDialogProvider * provider()
Returns the active provider. Never null.
Type
Identifies which property this descriptor represents.
@ Frequency
Clock oscillation frequency in Hz.
@ Delay
Phase delay as fraction of the clock period.
@ Appearance
Custom pixmap appearance selection.
@ Label
User-visible text label.
@ Audio
Audio note selection (buzzer tone).
@ WirelessModeSelector
Wireless routing mode (None / Tx / Rx) — Node elements only.
@ Volume
Audio playback volume (AudioBox, Buzzer).
@ Color
Color selection (LEDs, displays).
@ AudioBox
AudioBox file selection dialog.
@ Trigger
Keyboard trigger shortcut.
@ TruthTable
Editable truth table dialog.