Wed, 18 Oct 2023 16:00:06 +0200
Added application icon
/** * CalibrateiSpindel.cpp is part of bmsapp. * * bmsapp is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * bmsapp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "CalibrateiSpindel.h" #include "../ui/ui_CalibrateiSpindel.h" #include "global.h" #include "Utils.h" #include "polyfit.h" #include "MainWindow.h" /* * The following MySQL query produces a table with historic real results: * * SELECT pr.code, * pr.og, * (SELECT angle FROM log_ispindel WHERE code=pr.code ORDER BY datetime LIMIT 1) as og_angle, * pr.fg, * (SELECT angle FROM log_ispindel WHERE code=pr.code ORDER BY datetime DESC LIMIT 1) as fg_angle * FROM products AS pr * WHERE pr.stage=11 AND pr.log_ispindel=1 * ORDER BY pr.fg DESC * ; */ CalibrateiSpindel::CalibrateiSpindel(int id, QWidget *parent) : QDialog(parent), ui(new Ui::CalibrateiSpindel) { QSqlQuery query; #ifdef DEBUG_MONITOR qDebug() << "CalibrateiSpindel record:" << id; #endif ui->setupUi(this); this->recno = id; setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint); WindowTitle(); query.prepare("SELECT node,alias,calibrate FROM mon_ispindels WHERE record = :recno"); query.bindValue(":recno", this->recno); query.exec(); if (query.next()) { _node = query.value("node").toString(); _alias = query.value("alias").toString(); ui->nameEdit->setText(_node+"/"+_alias); QJsonParseError parseError; const auto& json = query.value("calibrate").toString(); if (!json.trimmed().isEmpty()) { const auto& formattedJson = QString("%1").arg(json); QJsonDocument jsonResponse = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); if (parseError.error != QJsonParseError::NoError) { qWarning() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ; } else { QJsonObject jsonObj = jsonResponse.object(); QJsonArray polyData = jsonObj.value("polyData").toArray(); for (int i = 0; i < polyData.size(); i++) { Old[i] = New[i] = polyData.at(i).toDouble(); } _data_old = QString("(%1 * x^3) + (%2 * x^2) + (%3 * x) + %4").arg(Old[0], 0, 'f', 9, '0').arg(Old[1], 0, 'f', 9, '0').arg(Old[2], 0, 'f', 9, '0').arg(Old[3], 0, 'f', 9, '0'); ui->oldEdit->setText(_data_old); QJsonArray calData = jsonObj.value("calData").toArray(); oldtotal = 0; for (int i = 0; i < calData.size(); i++) { QJsonObject calObj = calData.at(i).toObject(); Calibrate c; c.plato = calObj["plato"].toDouble(); c.angle = calObj["angle"].toDouble(); c.sg = Utils::plato_to_sg(c.plato); nCal.append(c); oCal.append(c); oldtotal++; } newtotal = oldtotal; } } } connect(ui->dataTable, SIGNAL(cellChanged(int, int)), this, SLOT(cell_Changed(int, int))); emit refreshTable(); } void CalibrateiSpindel::refreshTable() { QString w; QWidget* pWidget; QHBoxLayout* pLayout; double d, x[12], y[12]; bool gerror, aerror; qDebug() << "refreshTable" << oldtotal << newtotal; /* * During filling the table turn off the cellChanged signal because every cell that is filled * triggers the cellChanged signal. The QTableWidget has no better signal to use. */ this->ignoreChanges = true; const QStringList labels({tr("SG"), tr("°Plato"), tr("Angle"), tr("Del")}); ui->dataTable->setColumnCount(4); ui->dataTable->setColumnWidth(0, 100); /* SG */ ui->dataTable->setColumnWidth(1, 100); /* °Plato */ ui->dataTable->setColumnWidth(2, 100); /* Tilt angle */ ui->dataTable->setColumnWidth(3, 55); /* Del button */ ui->dataTable->setHorizontalHeaderLabels(labels); ui->dataTable->verticalHeader()->hide(); ui->dataTable->setRowCount(newtotal); for (int i = 0; i < 12; i++) { x[i] = y[i] = 0; } std::sort(nCal.begin() , nCal.end(), [=]( const Calibrate& test1 , const Calibrate& test2 )->bool { return test2.angle < test1.angle; }); this->dataHasErrors = false; for (int i = 0; i < newtotal; i++) { y[i] = nCal[i].plato; x[i] = nCal[i].angle; gerror = aerror = false; if ((nCal[i].angle < 10) || (nCal[i].angle > 80)) aerror = true; if (i == 0) { if (nCal[0].plato <= nCal[1].plato) gerror = true; } else if (i == (newtotal -1)) { if (nCal[i].plato != 0) gerror = true; } else { if ((nCal[i].plato <= nCal[i + 1].plato) || (nCal[i].plato >= nCal[i - 1].plato)) gerror = true; } if (gerror || aerror) this->dataHasErrors = true; w = QString("%1").arg(nCal[i].sg, 1, 'f', 4, '0'); QTableWidgetItem *item = new QTableWidgetItem(w); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); if (gerror) item->setForeground(QBrush(QColor(Qt::red))); ui->dataTable->setItem(i, 0, item); w = QString("%1").arg(nCal[i].plato, 1, 'f', 3, '0'); item = new QTableWidgetItem(w); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); if (gerror) item->setForeground(QBrush(QColor(Qt::red))); ui->dataTable->setItem(i, 1, item); w = QString("%1").arg(nCal[i].angle, 1, 'f', 5, '0'); item = new QTableWidgetItem(w); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); if (aerror) item->setForeground(QBrush(QColor(Qt::red))); ui->dataTable->setItem(i, 2, item); /* Add the Delete row button */ pWidget = new QWidget(); QPushButton* btn_del = new QPushButton(); btn_del->setObjectName(QString("%1").arg(i)); /* Send row with the button */ btn_del->setText(tr("Del")); connect(btn_del, SIGNAL(clicked()), this, SLOT(on_deleteRow_clicked())); pLayout = new QHBoxLayout(pWidget); pLayout->addWidget(btn_del); pLayout->setContentsMargins(5, 0, 5, 0); pWidget->setLayout(pLayout); ui->dataTable->setCellWidget(i, 3, pWidget); } int rc = Polyfit::polyfit(newtotal, x, y, 4, New); _data_new = QString("(%1 * x^3) + (%2 * x^2) + (%3 * x) + %4").arg(New[0], 0, 'f', 9, '0').arg(New[1], 0, 'f', 9, '0').arg(New[2], 0, 'f', 9, '0').arg(New[3], 0, 'f', 9, '0'); ui->newEdit->setText(_data_new); /* * Check the new formula against the old formula. */ this->textIsChanged = (_data_old.compare(_data_new) == 0) ? false:true; CalibrateiSpindel::WindowTitle(); new_plot = new QLineSeries(); old_plot = new QLineSeries(); for (int i = 0; i < oldtotal; i++) { old_plot->append(oCal[i].angle, oCal[i].plato); } for (int i = 0; i < newtotal; i++) { new_plot->append(nCal[i].angle, nCal[i].plato); } old_plot->setName(tr("Old")); new_plot->setName(tr("New")); chart = new QChart(); chart->setTitle(tr("Calibration plot")); chart->addSeries(old_plot); chart->addSeries(new_plot); QValueAxis *axisX = new QValueAxis; axisX->setRange(10, 80); axisX->setTickCount(8); axisX->setLabelFormat("%.0f"); axisX->setTitleText(tr("Angle")); axisX->setLabelsFont(QFont("Helvetica", 8, QFont::Normal)); chart->addAxis(axisX, Qt::AlignBottom); old_plot->attachAxis(axisX); new_plot->attachAxis(axisX); QValueAxis *axisY = new QValueAxis; axisY->setRange(0, 20); axisY->setTickCount(11); axisY->setLabelFormat("%.1f"); axisY->setTitleText("Plato"); axisY->setLabelsFont(QFont("Helvetica", 8, QFont::Normal)); chart->addAxis(axisY, Qt::AlignLeft); old_plot->attachAxis(axisY); new_plot->attachAxis(axisY); ui->chartView->setRenderHint(QPainter::Antialiasing); ui->chartView->setChart(chart); this->ignoreChanges = false; } CalibrateiSpindel::~CalibrateiSpindel() { delete ui; emit entry_changed(); } void CalibrateiSpindel::on_quitButton_clicked() { if (this->textIsChanged) { int rc = QMessageBox::warning(this, tr("iSpindel calibrate changed"), tr("The calibration data has been modified. Save changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save); switch (rc) { case QMessageBox::Save: if (this->dataHasErrors) { QMessageBox::warning(this, tr("iSpindel calibrate"), tr("Data is changed but has errors, not saving.")); return; /* Return to the editor page */ } else { SaveData(); } break; /* Saved and then Quit */ case QMessageBox::Discard: break; /* Quit without Save */ case QMessageBox::Cancel: return; /* Return to the editor page */ } } this->close(); this->setResult(1); } void CalibrateiSpindel::SaveData() { QSqlQuery query; if (this->textIsChanged) { /* * Build json data */ QJsonObject calObj; QJsonArray coeff, poly; for (int i = 0; i < 4; i++) { coeff.append(New[i]); } calObj.insert("polyData", coeff); for (int i = 0; i < newtotal; i++) { QJsonObject obj; obj.insert("plato", nCal[i].plato); obj.insert("angle", nCal[i].angle); poly.append(obj); } calObj.insert("calData", poly); query.prepare("UPDATE mon_ispindels SET calibrate=:calibrate WHERE record = :recno"); query.bindValue(":calibrate", QJsonDocument(calObj).toJson(QJsonDocument::Compact) ); query.bindValue(":recno", this->recno); query.exec(); if (query.lastError().isValid()) { qWarning() << "CalibrateiSpindel" << query.lastError(); QMessageBox::warning(this, tr("Database error"), tr("MySQL error: %1\n%2\n%3") .arg(query.lastError().nativeErrorCode()) .arg(query.lastError().driverText()) .arg(query.lastError().databaseText())); } else { qDebug() << "CalibrateiSpindel Saved"; } } } void CalibrateiSpindel::on_deleteRow_clicked() { QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender()); int row = pb->objectName().toInt(); qDebug() << "Delete row" << row << newtotal; if (newtotal < 4) { QMessageBox::warning(this, tr("iSpindel calibrate"), tr("You cannot delete too many rows.")); return; } nCal.removeAt(row); newtotal--; emit refreshTable(); } void CalibrateiSpindel::on_addButton_clicked() { qDebug() << "Add row" << newtotal; this->ignoreChanges = true; Calibrate c; c.plato = 10.0; c.angle = 50.0; c.sg = Utils::plato_to_sg(10.0); nCal.append(c); newtotal++; this->ignoreChanges = false; emit refreshTable(); } void CalibrateiSpindel::cell_Changed(int nRow, int nCol) { QString w; if (this->ignoreChanges) return; qDebug() << "Cell at row " + QString::number(nRow) + " column " + QString::number(nCol) + " was changed."; if (nCol == 0) { // SG changed double d = ui->dataTable->item(nRow, 0)->text().toDouble(); if (d < 1.000 || d > 1.100) { QMessageBox::warning(this, tr("iSpindel calibrate"), tr("The SG must be between 1.000 and 1.100.")); return; } nCal[nRow].sg = d; nCal[nRow].plato = Utils::sg_to_plato(d); } else if (nCol == 1) { double d = ui->dataTable->item(nRow, 1)->text().toDouble(); if (d < 0 || d > 25) { QMessageBox::warning(this, tr("iSpindel calibrate"), tr("Plato must be between 0 and 25.")); return; } nCal[nRow].plato = d; nCal[nRow].sg = Utils::plato_to_sg(d); } else if (nCol == 2) { double d = ui->dataTable->item(nRow, 2)->text().toDouble(); if (d < 10 || d > 80) { QMessageBox::warning(this, tr("iSpindel calibrate"), tr("The tilt angles must be between 10 and 80.")); return; } nCal[nRow].angle = d; } emit refreshTable(); } /* * Window header, mark any change with '**' */ void CalibrateiSpindel::WindowTitle() { QString txt; txt = QString(tr("BMSapp - Calibrate iSpindel %1").arg(this->recno)); if (this->textIsChanged) { txt.append((QString(" **"))); } setWindowTitle(txt); }