From ec3c5bf1880d8af70fd5994832d18cbaa5647b16 Mon Sep 17 00:00:00 2001 From: Neil Horne <15316949+elecpower@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:05:31 +1100 Subject: [PATCH] fix(cpn): inputs and mixes must have source (#5798) --- companion/src/companion.qrc | 3 + companion/src/firmwares/modeldata.cpp | 60 ++++++++++++++ companion/src/firmwares/modeldata.h | 8 ++ companion/src/firmwares/radiodata.cpp | 16 ++++ companion/src/firmwares/radiodata.h | 2 + .../src/images/originals/circle-green.svg | 48 +++++++++++ .../src/images/originals/circle-orange.svg | 48 +++++++++++ companion/src/images/originals/circle-red.svg | 48 +++++++++++ companion/src/images/svg/circle-green.svg | 48 +++++++++++ companion/src/images/svg/circle-orange.svg | 48 +++++++++++ companion/src/images/svg/circle-red.svg | 48 +++++++++++ companion/src/mainwindow.cpp | 6 +- companion/src/mdichild.cpp | 83 +++++++++++++++++-- companion/src/mdichild.h | 16 +++- companion/src/mdichild.ui | 3 + companion/src/modeledit/modeledit.cpp | 3 +- companion/src/modeledit/modeledit.h | 6 +- companion/src/modelslist.cpp | 6 ++ radio/src/thirdparty/FreeRTOS | 2 +- 19 files changed, 481 insertions(+), 21 deletions(-) create mode 100644 companion/src/images/originals/circle-green.svg create mode 100644 companion/src/images/originals/circle-orange.svg create mode 100644 companion/src/images/originals/circle-red.svg create mode 100644 companion/src/images/svg/circle-green.svg create mode 100644 companion/src/images/svg/circle-orange.svg create mode 100644 companion/src/images/svg/circle-red.svg diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index bd395a44389..8712699d382 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -10,6 +10,9 @@ images/track.png images/track0.png images/taranison.png + images/svg/circle-green.svg + images/svg/circle-orange.svg + images/svg/circle-red.svg images/simulator/icons/svg/arrow_click.svg images/simulator/icons/svg/camera.svg images/simulator/icons/svg/camera-active.svg diff --git a/companion/src/firmwares/modeldata.cpp b/companion/src/firmwares/modeldata.cpp index 5f8ee4fc280..8bfac936a8e 100644 --- a/companion/src/firmwares/modeldata.cpp +++ b/companion/src/firmwares/modeldata.cpp @@ -29,6 +29,8 @@ #include "adjustmentreference.h" #include "compounditemmodels.h" +#include + ModelData::ModelData() { clear(); @@ -1924,3 +1926,61 @@ int ModelData::getCustomScreensCount() const return cnt; } + +void ModelData::validate() +{ + modelErrors = false; + + for (int i = 0; i < CPN_MAX_INPUTS; i++) { + if (!expoData[i].isEmpty() && expoData[i].srcRaw == SOURCE_TYPE_NONE) { + modelErrors = true; + return; + } + } + + for (int i = 0; i < CPN_MAX_MIXERS; i++) { + if (!mixData[i].isEmpty() && mixData[i].srcRaw == SOURCE_TYPE_NONE) { + modelErrors = true; + return; + } + } +} + +QStringList ModelData::errorsList() +{ + QStringList list; + + for (int i = 0; i < CPN_MAX_INPUTS; i++) { + if (!expoData[i].isEmpty() && expoData[i].srcRaw == SOURCE_TYPE_NONE) + list.append(tr("Error - Input %1 Line %2 %3").arg(expoData[i].chn + 1).arg(getInputLine(i)).arg(tr("has no source"))); + } + + for (int i = 0; i < CPN_MAX_MIXERS; i++) { + if (!mixData[i].isEmpty() && mixData[i].srcRaw == SOURCE_TYPE_NONE) + list.append(tr("Error - Mix %1 Line %2 %3").arg(mixData[i].destCh).arg(getMixLine(i)).arg(tr("has no source"))); + } + + return list; +} + +int ModelData::getMixLine(int index) const +{ + int cnt = 1; + + for (int i = index - 1; i >= 0 && mixData[i].destCh == mixData[index].destCh; i--) + cnt++; + + return cnt; +} + +int ModelData::getInputLine(int index) const +{ + int cnt = 1; + + for (int i = 0; i < index; i++) { + if (expoData[i].chn == expoData[index].chn) + cnt++; + } + + return cnt; +} diff --git a/companion/src/firmwares/modeldata.h b/companion/src/firmwares/modeldata.h index 6ad033398a9..d4af9c1e1a8 100644 --- a/companion/src/firmwares/modeldata.h +++ b/companion/src/firmwares/modeldata.h @@ -140,6 +140,7 @@ class ModelData { char labels[100]; int modelIndex; // Companion only, temporary index position managed by data model. bool modelUpdated; // Companion only, used to highlight if changed in models list + bool modelErrors; // Companion only, used to highlight if data errors in models list TimerData timers[CPN_MAX_TIMERS]; bool noGlobalFunctions; @@ -366,11 +367,18 @@ class ModelData { static AbstractStaticItemModel * funcSwitchStartItemModel(); int getCustomScreensCount() const; + bool hasErrors() { return modelErrors; } + bool isValid() { return !hasErrors(); } + void validate(); + QStringList errorsList(); protected: void removeGlobalVar(int & var); private: + int getMixLine(int index) const; + int getInputLine(int index) const; + QVector *updRefList = nullptr; struct UpdateReferenceInfo diff --git a/companion/src/firmwares/radiodata.cpp b/companion/src/firmwares/radiodata.cpp index be7f3eeb074..f5e10958346 100644 --- a/companion/src/firmwares/radiodata.cpp +++ b/companion/src/firmwares/radiodata.cpp @@ -343,3 +343,19 @@ AbstractStaticItemModel * RadioData::modelSortOrderItemModel() mdl->loadItemList(); return mdl; } + +void RadioData::validateModels() +{ + for(auto &model: models) + model.validate(); +} + +int RadioData::invalidModels() +{ + int cnt = 0; + + for(auto &model: models) + cnt += model.isValid() ? 0 : 1; + + return cnt; +} diff --git a/companion/src/firmwares/radiodata.h b/companion/src/firmwares/radiodata.h index d8612b602f5..4263092b16b 100644 --- a/companion/src/firmwares/radiodata.h +++ b/companion/src/firmwares/radiodata.h @@ -66,6 +66,8 @@ class RadioData { void setCurrentModel(unsigned int index); void fixModelFilenames(); QString getNextModelFilename(); + void validateModels(); + int invalidModels(); static QString modelSortOrderToString(int value); static AbstractStaticItemModel * modelSortOrderItemModel(); diff --git a/companion/src/images/originals/circle-green.svg b/companion/src/images/originals/circle-green.svg new file mode 100644 index 00000000000..e8e8c790ae7 --- /dev/null +++ b/companion/src/images/originals/circle-green.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/companion/src/images/originals/circle-orange.svg b/companion/src/images/originals/circle-orange.svg new file mode 100644 index 00000000000..f23aaa15c54 --- /dev/null +++ b/companion/src/images/originals/circle-orange.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/companion/src/images/originals/circle-red.svg b/companion/src/images/originals/circle-red.svg new file mode 100644 index 00000000000..7080708e0ad --- /dev/null +++ b/companion/src/images/originals/circle-red.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/companion/src/images/svg/circle-green.svg b/companion/src/images/svg/circle-green.svg new file mode 100644 index 00000000000..e8e8c790ae7 --- /dev/null +++ b/companion/src/images/svg/circle-green.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/companion/src/images/svg/circle-orange.svg b/companion/src/images/svg/circle-orange.svg new file mode 100644 index 00000000000..f23aaa15c54 --- /dev/null +++ b/companion/src/images/svg/circle-orange.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/companion/src/images/svg/circle-red.svg b/companion/src/images/svg/circle-red.svg new file mode 100644 index 00000000000..7080708e0ad --- /dev/null +++ b/companion/src/images/svg/circle-red.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp index bad1760abf1..28729a362d4 100644 --- a/companion/src/mainwindow.cpp +++ b/companion/src/mainwindow.cpp @@ -676,9 +676,9 @@ void MainWindow::updateMenus() saveAsAct->setEnabled(activeChild); closeAct->setEnabled(activeChild); compareAct->setEnabled(activeChild); - writeSettingsAct->setEnabled(activeChild); + writeSettingsAct->setEnabled(activeChild && !activeMdiChild()->invalidModels()); readSettingsAct->setEnabled(true); - writeSettingsSDPathAct->setEnabled(activeChild && isSDPathValid()); + writeSettingsSDPathAct->setEnabled(activeChild && isSDPathValid() && !activeMdiChild()->invalidModels()); readSettingsSDPathAct->setEnabled(isSDPathValid()); writeBUToRadioAct->setEnabled(false); readBUToFileAct->setEnabled(false); @@ -889,10 +889,8 @@ void MainWindow::createActions() // assigned menus in createMenus() recentFilesAct = addAct("recentdocument.png"); closeAct = addAct("clear.png", SLOT(closeFile()) /*, QKeySequence::Close*/); // setting/showing this shortcut interferes with the system one (Ctrl+W/Ctrl-F4) - // TODO change to more appropriate icon sets and uncomment toolbar actions writeSettingsSDPathAct = addAct("folder-tree-write.png", SLOT(writeSettingsSDPath())); readSettingsSDPathAct = addAct("folder-tree-read.png", SLOT(readSettingsSDPath())); - // end TODO exitAct = addAct("exit.png", SLOT(closeAllWindows()), QKeySequence::Quit, qApp); editAppSettingsAct = addAct("apppreferences.png", SLOT(editAppSettings()), QKeySequence::Preferences); diff --git a/companion/src/mdichild.cpp b/companion/src/mdichild.cpp index ab13be5d3e0..28464f5b458 100644 --- a/companion/src/mdichild.cpp +++ b/companion/src/mdichild.cpp @@ -63,6 +63,7 @@ MdiChild::MdiChild(QWidget * parent, QWidget * parentWin, Qt::WindowFlags f): if (parentWindow) parentWindow->setWindowIcon(windowIcon()); + setupStatusBar(); setupNavigation(); initModelsList(); @@ -227,11 +228,12 @@ void MdiChild::setupNavigation() addAct(ACT_MDL_SIM, "simulate.png", SLOT(modelSimulate()), tr("Alt+S")); addAct(ACT_MDL_DUP, "duplicate.png", SLOT(modelDuplicate()), QKeySequence::Underline); - addAct(ACT_MDL_CUT, "cut.png", SLOT(cut()), QKeySequence::Cut); - addAct(ACT_MDL_CPY, "copy.png", SLOT(copy()), QKeySequence::Copy); - addAct(ACT_MDL_PST, "paste.png", SLOT(paste()), QKeySequence::Paste); - addAct(ACT_MDL_INS, "list.png", SLOT(insert()), QKeySequence::Italic); - addAct(ACT_MDL_EXP, "save.png", SLOT(modelExport()), tr("Ctrl+Alt+S")); + addAct(ACT_MDL_CUT, "cut.png", SLOT(cut()), QKeySequence::Cut); + addAct(ACT_MDL_CPY, "copy.png", SLOT(copy()), QKeySequence::Copy); + addAct(ACT_MDL_PST, "paste.png", SLOT(paste()), QKeySequence::Paste); + addAct(ACT_MDL_INS, "list.png", SLOT(insert()), QKeySequence::Italic); + addAct(ACT_MDL_EXP, "save.png", SLOT(modelExport()), tr("Ctrl+Alt+S")); + addAct(ACT_MDL_ERR, "information.png", SLOT(modelShowErrors()), tr("Ctrl+Alt+E")); addAct(ACT_MDL_MOV, "arrow-right.png"); QMenu * catsMenu = new QMenu(this); @@ -349,6 +351,7 @@ void MdiChild::updateNavigation() cboModelSortOrder->setCurrentIndex(radioData.sortOrder); cboModelSortOrder->blockSignals(false); } + action[ACT_GEN_SIM]->setEnabled(!invalidModels()); action[ACT_GEN_SRT]->setVisible(hasLabels); action[ACT_MDL_DEL]->setEnabled(modelsSelected); @@ -369,7 +372,8 @@ void MdiChild::updateNavigation() action[ACT_MDL_WIZ]->setEnabled(singleModelSelected); action[ACT_MDL_DFT]->setEnabled(singleModelSelected && getCurrentModel() != (int)radioData.generalSettings.currModelIndex); action[ACT_MDL_PRT]->setEnabled(singleModelSelected); - action[ACT_MDL_SIM]->setEnabled(singleModelSelected); + action[ACT_MDL_SIM]->setEnabled(singleModelSelected && !invalidModels()); + action[ACT_MDL_ERR]->setEnabled(singleModelSelected && radioData.models[getCurrentModel()].modelErrors); } void MdiChild::retranslateUi() @@ -399,6 +403,7 @@ void MdiChild::retranslateUi() action[ACT_MDL_PRT]->setText(tr("Print Model")); action[ACT_MDL_SIM]->setText(tr("Simulate Model")); action[ACT_MDL_DUP]->setText(tr("Duplicate Model")); + action[ACT_MDL_ERR]->setText(tr("Show Model Errors")); radioToolbar->setWindowTitle(tr("Show Radio Actions Toolbar")); modelsToolbar->setWindowTitle(tr("Show Model Actions Toolbar")); @@ -444,6 +449,7 @@ QList MdiChild::getModelActions() //actGrp.append(getAction(ACT_MDL_RTR)); actGrp.append(getAction(ACT_MDL_PRT)); actGrp.append(getAction(ACT_MDL_SIM)); + actGrp.append(getAction(ACT_MDL_ERR)); return actGrp; } @@ -603,6 +609,7 @@ void MdiChild::refresh() } updateNavigation(); updateTitle(); + updateStatusBar(); } void MdiChild::onItemActivated(const QModelIndex index) @@ -1204,6 +1211,7 @@ void MdiChild::openModelEditWindow(int row) gStopwatch.report("ModelEdit created"); t->setWindowTitle(tr("Editing model %1: ").arg(row+1) + QString(model.name) + QString(" (%1)").arg(userFriendlyCurrentFile())); connect(t, &ModelEdit::modified, this, &MdiChild::setCurrentModelModified); + connect(t, &ModelEdit::closed, this, &MdiChild::onModelEditClosed); gStopwatch.report("STARTING MODEL EDIT"); t->show(); QApplication::restoreOverrideCursor(); @@ -1298,6 +1306,9 @@ bool MdiChild::loadFile(const QString & filename, bool resetCurrentFile) refresh(); } + radioData.validateModels(); + updateStatusBar(); + return true; } @@ -1344,8 +1355,10 @@ bool MdiChild::saveFile(const QString & filename, bool setCurrent) g.eepromDir(QFileInfo(filename).dir().absolutePath()); for (int i = 0; i < (int)radioData.models.size(); i++) { - if (!radioData.models[i].isEmpty()) + if (!radioData.models[i].isEmpty()) { radioData.models[i].modelUpdated = false; + radioData.models[i].validate(); + } } refresh(); @@ -1486,6 +1499,13 @@ int MdiChild::askQuestion(const QString & msg, QMessageBox::StandardButtons butt void MdiChild::writeSettings(StatusDialog * status, bool toRadio) // write to Tx { + // safeguard as the menu actions should be disabled + int cnt = radioData.invalidModels(); + if (cnt) { + QMessageBox::critical(this, tr("Write Models and Settings"), tr("Operation aborted as %1 models have significant errors that may affect model operation.").arg(cnt)); + return; + } + if (g.confirmWriteModelsAndSettings()) { QMessageBox msgbox; msgbox.setText(tr("You are about to overwrite ALL models.")); @@ -1854,3 +1874,52 @@ QAction * MdiChild::actionsSeparator() act->setSeparator(true); return act; } + +bool MdiChild::invalidModels() +{ + return (bool)radioData.invalidModels(); +} + +void MdiChild::modelShowErrors() +{ + ModelData &mdl = radioData.models[getCurrentModel()]; + QMessageBox::critical(this, QString("%1").arg(mdl.name), mdl.errorsList().join("\n")); +} + +void MdiChild::onModelEditClosed(int id) +{ + radioData.models[id].validate(); + refresh(); +} + +void MdiChild::setupStatusBar() +{ + statusBar = new QStatusBar(); + ui->statusBarLayout->addWidget(statusBar); + QLabel *lbl = new QLabel(tr("Models status")); + statusBar->addPermanentWidget(lbl); + statusBarIcon = new QLabel(); + statusBar->addPermanentWidget(statusBarIcon); + statusBarCount = new QLabel(); + statusBar->addPermanentWidget(statusBarCount); +} + +void MdiChild::updateStatusBar() +{ + QPixmap p; + QLabel cnt; + int invalid = radioData.invalidModels(); + + if (!invalidModels()) { + statusBarIcon->setToolTip(tr("No errors")); + p.load(":/images/svg/circle-green.svg"); + } + else { + statusBarIcon->setToolTip(tr("Errors")); + p.load(":/images/svg/circle-red.svg"); + cnt.setText(QString::number(invalid)); + } + + statusBarIcon->setPixmap(p.scaled(QSize(16, 16))); + statusBarCount->setText(cnt.text()); +} diff --git a/companion/src/mdichild.h b/companion/src/mdichild.h index 49f4cbc346c..65e4ec9761b 100644 --- a/companion/src/mdichild.h +++ b/companion/src/mdichild.h @@ -19,8 +19,7 @@ * GNU General Public License for more details. */ -#ifndef _MDICHILD_H_ -#define _MDICHILD_H_ +#pragma once #include "eeprominterface.h" #include "modelslist.h" @@ -33,6 +32,7 @@ #include #include #include +#include class QToolBar; class StatusDialog; @@ -70,6 +70,7 @@ class MdiChild : public QWidget ACT_MDL_DFT, // set as DeFaulT ACT_MDL_PRT, // print ACT_MDL_SIM, + ACT_MDL_ERR, ACT_LBL_ADD, // label actions.. ACT_LBL_DEL, ACT_LBL_MVU, // Move up @@ -89,6 +90,7 @@ class MdiChild : public QWidget QList getModelActions(); QList getLabelsActions(); QAction * getAction(const Actions type); + bool invalidModels(); public slots: void newFile(bool createDefaults = true); @@ -130,6 +132,7 @@ class MdiChild : public QWidget void onCurrentItemChanged(const QModelIndex &, const QModelIndex &); void onDataChanged(const QModelIndex & index); void onInternalModuleChanged(); + void onModelEditClosed(int id); void generalEdit(); void copyGeneralSettings(); @@ -153,6 +156,7 @@ class MdiChild : public QWidget void labelsFault(QString msg); void wizardEdit(); void modelDuplicate(); + void modelShowErrors(); void openModelWizard(int row = -1); void openModelEditWindow(int row = -1); @@ -201,6 +205,7 @@ class MdiChild : public QWidget bool convertStorage(Board::Type from, Board::Type to, bool newFile = false); void showWarning(const QString & msg); int askQuestion(const QString & msg, QMessageBox::StandardButtons buttons = (QMessageBox::Yes | QMessageBox::No), QMessageBox::StandardButton defaultButton = QMessageBox::No); + QDialog * getChildDialog(QRegularExpression & regexp); QDialog * getModelEditDialog(int row); QList * getChildrenDialogsList(QRegularExpression & regexp); @@ -219,6 +224,9 @@ class MdiChild : public QWidget QToolBar * modelsToolbar; QToolBar * labelsToolbar; QLabel *lblLabels; + QStatusBar *statusBar; + QLabel *statusBarIcon; + QLabel *statusBarCount; Firmware * firmware; RadioData radioData; @@ -232,6 +240,8 @@ class MdiChild : public QWidget QComboBox* cboModelSortOrder; void setModelModified(const int modelIndex, bool cascade = true); QAction * actionsSeparator(); + void setupStatusBar(); + void updateStatusBar(); }; // This will draw the drop indicator across all columns of a model View (vs. in just one column), and lets us make the indicator more obvious. @@ -262,5 +272,3 @@ class ItemViewProxyStyle: public QProxyStyle } } }; - -#endif // _MDICHILD_H_ diff --git a/companion/src/mdichild.ui b/companion/src/mdichild.ui index 7ba95757fa0..439caa168b8 100644 --- a/companion/src/mdichild.ui +++ b/companion/src/mdichild.ui @@ -51,6 +51,9 @@ + + + diff --git a/companion/src/modeledit/modeledit.cpp b/companion/src/modeledit/modeledit.cpp index deb753d45dc..49a73d300e7 100644 --- a/companion/src/modeledit/modeledit.cpp +++ b/companion/src/modeledit/modeledit.cpp @@ -140,7 +140,8 @@ ModelEdit::~ModelEdit() void ModelEdit::closeEvent(QCloseEvent *event) { - g.modelEditGeo( saveGeometry() ); + g.modelEditGeo(saveGeometry()); + emit closed(modelId); } void ModelEdit::addTab(GenericPanel *panel, QString text) diff --git a/companion/src/modeledit/modeledit.h b/companion/src/modeledit/modeledit.h index 13c27dce586..f3211bd4a54 100644 --- a/companion/src/modeledit/modeledit.h +++ b/companion/src/modeledit/modeledit.h @@ -19,8 +19,7 @@ * GNU General Public License for more details. */ -#ifndef _MODELEDIT_H_ -#define _MODELEDIT_H_ +#pragma once #include #include "genericpanel.h" @@ -56,6 +55,7 @@ class ModelEdit : public QDialog signals: void modified(); + void closed(int id); private slots: void onTabIndexChanged(int index); @@ -72,5 +72,3 @@ class ModelEdit : public QDialog void launchSimulation(); }; - -#endif // _MODELEDIT_H_ diff --git a/companion/src/modelslist.cpp b/companion/src/modelslist.cpp index 5bb14f5ce56..bea7df78a62 100644 --- a/companion/src/modelslist.cpp +++ b/companion/src/modelslist.cpp @@ -186,6 +186,12 @@ QVariant ModelsListModel::data(const QModelIndex & index, int role) const } if (role == Qt::ForegroundRole && item->isModel()) { + if (index.column() == (hasLabels ? 0 : 1) && radioData->models[item->getModelIndex()].modelErrors) { + QBrush brush; + brush.setColor(Qt::red); + return brush; + } + int col = item->columnCount() - 1; if(hasLabels) col --; diff --git a/radio/src/thirdparty/FreeRTOS b/radio/src/thirdparty/FreeRTOS index dbf70559b27..a4b28e35103 160000 --- a/radio/src/thirdparty/FreeRTOS +++ b/radio/src/thirdparty/FreeRTOS @@ -1 +1 @@ -Subproject commit dbf70559b27d39c1fdb68dfb9a32140b6a6777a0 +Subproject commit a4b28e35103d699edf074dfff4835921b481b301