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;
589 if (m_ui->comboBoxAudio->currentText() != m_manyAudios) {
590 elm->
setAudio(m_ui->comboBoxAudio->currentText());
597 if (m_ui->lineEditTrigger->text() != m_manyTriggers && m_ui->lineEditTrigger->text().size() <= 1) {
598 elm->
setTrigger(QKeySequence(m_ui->lineEditTrigger->text()));
602 if (m_isUpdatingAppearance) {
607 if (m_ui->comboBoxWirelessMode->currentText() != m_manyWirelessModes) {
608 if (
auto *node = qobject_cast<Node *>(elm)) {
609 node->setWirelessMode(
static_cast<WirelessMode>(m_ui->comboBoxWirelessMode->currentIndex()));
614 elm->
setVolume(
static_cast<float>(m_ui->sliderVolume->value()) / 100.0f);
623void ElementEditor::apply()
631 if (m_elements.isEmpty() || !isEnabled() || !m_scene) {
638 if (m_scene && m_caps.hasWirelessMode) {
639 const QString newLabel = m_ui->lineEditElementLabel->text();
640 const bool labelChanging = (newLabel != m_manyLabels);
642 const bool modeChanging = (m_ui->comboBoxWirelessMode->currentText() != m_manyWirelessModes);
644 for (
auto *elm : std::as_const(m_elements)) {
645 auto *node = qobject_cast<Node *>(elm);
648 const QString candidateLabel = labelChanging ? newLabel : node->label();
649 const WirelessMode candidateMode = modeChanging ? newMode : node->wirelessMode();
651 if (candidateMode != WirelessMode::Tx || candidateLabel.isEmpty())
continue;
653 for (
auto *other : m_scene->elements()) {
654 if (other == elm)
continue;
655 auto *otherNode = qobject_cast<Node *>(other);
656 if (!otherNode || otherNode->wirelessMode() != WirelessMode::Tx)
continue;
657 if (otherNode->label() == candidateLabel) {
658 QMessageBox::warning(
this,
659 tr(
"Duplicate Wireless Channel"),
660 tr(
"A Tx node with label \"%1\" already exists.\n"
661 "Each wireless channel must have a unique label.").arg(candidateLabel));
672 QList<QGraphicsItem *> wirelessConnsToDelete;
673 if (m_scene && m_caps.hasWirelessMode) {
675 const bool modeChanging = (m_ui->comboBoxWirelessMode->currentText() != m_manyWirelessModes);
678 for (
auto *elm : std::as_const(m_elements)) {
679 auto *node = qobject_cast<Node *>(elm);
682 Port *port = (newMode == WirelessMode::Rx) ?
static_cast<Port *
>(node->inputPort())
683 : (newMode == WirelessMode::Tx) ?
static_cast<Port *
>(node->outputPort())
687 wirelessConnsToDelete.append(
static_cast<QGraphicsItem *
>(conn));
698 QDataStream stream(&oldData, QIODevice::WriteOnly);
701 for (
auto *elm : std::as_const(m_elements)) {
704 applyProperty(elm, prop.type);
709 if (m_isUpdatingAppearance) {
710 m_isUpdatingAppearance =
false;
715 const bool needsMacro = !wirelessConnsToDelete.isEmpty();
717 m_scene->undoStack()->beginMacro(tr(
"Change wireless mode"));
720 m_scene->receiveCommand(
new UpdateCommand(m_elements, oldData, m_scene));
723 m_scene->receiveCommand(
new DeleteItemsCommand(wirelessConnsToDelete, m_scene));
724 m_scene->undoStack()->endMacro();
728void ElementEditor::blobNameEditingFinished()
730 if (m_elements.isEmpty() || !m_caps.isEmbedded) {
734 const QString newBlobName = m_ui->lineEditBlobName->text().trimmed();
735 const QString oldBlobName = m_elements.first()->blobName();
740 if (newBlobName.isEmpty() || newBlobName == oldBlobName || newBlobName == m_manyLabels) {
748 if (m_scene->icRegistry()->hasBlob(newBlobName)) {
749 QMessageBox::warning(
this,
750 tr(
"Duplicate IC Name"),
751 tr(
"An embedded IC named \"%1\" already exists.\n"
752 "Choose a different name.").arg(newBlobName));
759 m_scene->receiveCommand(
new RenameBlobCommand(oldBlobName, newBlobName, m_scene));
762void ElementEditor::inputIndexChanged(
const int index)
764 if (m_elements.isEmpty() || !isEnabled()) {
769 if (m_caps.canChangeInputSize && (m_ui->comboBoxInputSize->currentText() != m_manyIS)) {
770 emit
sendCommand(
new ChangePortSizeCommand(m_elements, m_ui->comboBoxInputSize->currentData().toInt(), m_scene,
true));
773 qCDebug(zero) <<
"Input size changed to " << index;
777void ElementEditor::outputIndexChanged(
const int index)
779 if (m_elements.isEmpty() || !isEnabled()) {
784 if (m_caps.canChangeOutputSize && (m_ui->comboBoxOutputSize->currentText() != m_manyOS)) {
785 emit
sendCommand(
new ChangePortSizeCommand(m_elements, m_ui->comboBoxOutputSize->currentData().toInt(), m_scene,
false));
788 qCDebug(zero) <<
"Output size changed to " << index;
792void ElementEditor::outputValueChanged(
const QString &value)
794 if (m_elements.isEmpty() || !isEnabled()) {
798 const int newValue = value.toInt();
800 for (
auto *elm : std::as_const(m_elements)) {
801 if (elm->
elementType() == ElementType::InputRotary) {
804 if (
auto *input = qobject_cast<InputRotary *>(elm)) {
805 input->setOn(
true, newValue);
809 if (
auto *input = qobject_cast<GraphicElementInput *>(elm)) {
810 input->setOn(
static_cast<bool>(newValue));
818void ElementEditor::inputLocked(
const bool value)
820 if (m_elements.isEmpty() || !isEnabled()) {
824 for (
auto *elm : std::as_const(m_elements)) {
825 if (
auto *input = qobject_cast<GraphicElementInput *>(elm)) {
826 input->setLocked(value);
830 qCDebug(zero) <<
"Input locked.";
834void ElementEditor::triggerChanged(
const QString &cmd)
837 m_ui->lineEditTrigger->setText(cmd.toUpper());
844 if (!m_caps.hasTruthTable || m_elements.isEmpty())
return;
847 auto *truthtable = qobject_cast<TruthTable *>(m_elements[0]);
853 int nInputs = truthtable->inputSize();
854 int nOutputs = truthtable->outputSize();
857 QStringList inputLabels;
858 m_table->setColumnCount(nInputs + nOutputs);
860 m_table->setRowCount(
static_cast<int>(std::pow(2, nInputs)));
862 for (
int i = 0; i < nInputs; ++i) {
863 inputLabels.append(QChar::fromLatin1(
static_cast<char>(
'A' + i)));
866 for (
int i = 0; i < truthtable->outputSize(); ++i) {
867 inputLabels.append(
"S" + QString::number(i));
868 m_table->setColumnWidth(nInputs + i, 14);
871 m_table->setHorizontalHeaderLabels(inputLabels);
873 for (
int i = 0; i < std::pow(2, nInputs); ++i) {
874 for (
int j = 0; j < nInputs; ++j) {
875 m_table->setColumnWidth(j, 14);
877 auto newItemValue = QString::number(i, 2);
879 if (newItemValue.size() < nInputs) {
880 newItemValue = newItemValue.rightJustified(nInputs,
'0');
884 if (m_table->item(i, j) ==
nullptr) {
885 auto *newItem =
new QTableWidgetItem(newItemValue.at(j), QTableWidgetItem::Type);
886 newItem->setTextAlignment(Qt::AlignCenter);
887 m_table->setItem(i, j, newItem);
889 m_table->item(i, j)->setFlags(Qt::ItemIsEnabled);
892 m_table->item(i, j)->setText(newItemValue.at(j));
897 auto bitArray = truthtable->key();
899 for (
int z = 0; z < nOutputs; ++z) {
902 const int output = bitArray.at(256 * z + i);
904 if (m_table->item(i, nInputs + z) ==
nullptr) {
905 auto *newOutItem =
new QTableWidgetItem(QString(QChar::fromLatin1(
static_cast<char>(
'0' + output))));
906 newOutItem->setTextAlignment(Qt::AlignCenter);
907 m_table->setItem(i, nInputs + z, newOutItem);
908 m_table->item(i, nInputs + z)->setFlags(Qt::ItemIsEnabled);
911 m_table->item(i, nInputs + z)->setText(QString::number(output));
918void ElementEditor::setTruthTableProposition(
const int row,
const int column)
922 if (m_elements.size() != 1)
return;
925 if (column < m_elements[0]->inputSize())
return;
929 auto cellItem = m_table->item(row, column);
930 cellItem->setText((cellItem->text() ==
"0") ?
"1" :
"0");
933 auto truthtable = m_elements[0];
934 const int nInputs = truthtable->inputSize();
936 const int positionToChange = 256 * (column - nInputs) + row;
951 if (m_elements.isEmpty()) {
954 auto *audiobox = qobject_cast<AudioBox *>(m_elements[0]);
960 QString(), tr(
"Audio") +
" (*.mp3 *.mp4 *.wav *.ogg)");
962 if (filePath.isEmpty()) {
966 audiobox->setAudio(filePath);
967 m_ui->lineCurrentAudioBox->setText(QFileInfo(filePath).fileName());
970void ElementEditor::defaultAppearance()
975 m_isUpdatingAppearance =
true;
976 m_isDefaultAppearance =
true;
984 setCurrentElements(m_elements);
992 const QString styleSheet =
994 " border: 1px solid rgb(%1,%1,%1);"
995 " border-radius: 2px;"
1000 " subcontrol-origin: margin;"
1001 " subcontrol-position: top;"
1004 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.