From b9335d0a2d10692b83d8ce34862effd2aee8ce5d Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 3 Apr 2021 12:51:09 +0200 Subject: [PATCH 001/109] Don't strip all optimization flags. Some basic compiler functionality is dependendent on having optimizations enabled. There shouldn't be any issues with -O2, which is the default for qmake's release builds. --- Desktop_Interface/Labrador.pro | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index 20d61baf..6e079d40 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -250,29 +250,6 @@ QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.10 unix:SOURCES += unixusbdriver.cpp unix:HEADERS += unixusbdriver.h -############################################################# -######## SHARED ANDROID/LINUX GCC FLAGS ######### -########################################################### - -unix:!macx: QMAKE_CXXFLAGS_RELEASE -= -O0 -unix:!macx: QMAKE_CXXFLAGS_RELEASE -= -O1 -unix:!macx: QMAKE_CXXFLAGS_RELEASE -= -O2 -unix:!macx: QMAKE_CXXFLAGS_RELEASE -= -O3 - -android: QMAKE_CXXFLAGS_RELEASE -= -O0 -android: QMAKE_CXXFLAGS_RELEASE -= -O1 -android: QMAKE_CXXFLAGS_RELEASE -= -O2 -android: QMAKE_CXXFLAGS_RELEASE -= -O3 -android: QMAKE_CXXFLAGS_RELEASE -= -Os - - -android: QMAKE_CFLAGS_RELEASE -= -O0 -android: QMAKE_CFLAGS_RELEASE -= -O1 -android: QMAKE_CFLAGS_RELEASE -= -O2 -android: QMAKE_CFLAGS_RELEASE -= -O3 -android: QMAKE_CFLAGS_RELEASE -= -Os - - ############################################################# ################# ANDROID BUILD ONLY ################# ########################################################### From 6b1e3b667157cf4002926c0974f73fc4951acc06 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 16:13:45 +0200 Subject: [PATCH 002/109] add standard shortcut for quit action --- Desktop_Interface/mainwindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index f00d1b71..5fdbb71d 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -36,6 +36,8 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); + ui->actionQuit->setShortcut(Qt::CTRL + Qt::Key_Q); // on macOS CTRL is automaticall translated to the command key + calibrationMessages = new QMessageBox(); ui->psuDisplay->display("4.00"); ui->bufferDisplay->refreshImage(); From 19e0fed1515922437b79cb9c9e909d4badd5fa1d Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 16:14:37 +0200 Subject: [PATCH 003/109] don't crash when failing to load the default list of waveforms --- Desktop_Interface/ui_elements/espocombobox.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/ui_elements/espocombobox.cpp b/Desktop_Interface/ui_elements/espocombobox.cpp index 40217ccc..15c19fb0 100644 --- a/Desktop_Interface/ui_elements/espocombobox.cpp +++ b/Desktop_Interface/ui_elements/espocombobox.cpp @@ -46,7 +46,8 @@ void espoComboBox::readWaveformList(void) char nameBuffer[255]; if(listPtr == NULL){ - qFatal("Could not load _list.wfl"); + qWarning("Could not load _list.wfl"); + return; } while (fgets(nameBuffer,256,listPtr) !=NULL){ From 51bf7de7e24eb9732a31d2434fdeac73df996ff3 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 16:16:21 +0200 Subject: [PATCH 004/109] add some missing keyboard accelerators --- Desktop_Interface/ui_files_desktop/mainwindow.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Desktop_Interface/ui_files_desktop/mainwindow.ui b/Desktop_Interface/ui_files_desktop/mainwindow.ui index b0837793..18075922 100644 --- a/Desktop_Interface/ui_files_desktop/mainwindow.ui +++ b/Desktop_Interface/ui_files_desktop/mainwindow.ui @@ -1512,7 +1512,7 @@ - File + &File @@ -1537,7 +1537,7 @@ - Oscilloscope + &Oscilloscope @@ -1774,7 +1774,7 @@ - Help + &Help From 2805c5e49d7828932359bb4b4791b0daa22ff2d5 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 16:16:42 +0200 Subject: [PATCH 005/109] fix warning of using uninitialized variable --- Desktop_Interface/unixusbdriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 50afe2ee..8a4f67a5 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -136,6 +136,7 @@ int unixUsbDriver::usbIsoInit(void){ } } + qint64 t0 = QDateTime::currentMSecsSinceEpoch(); for(int n=0;n Date: Wed, 14 Apr 2021 16:19:54 +0200 Subject: [PATCH 006/109] feedback when failing to flash/fix warning about unused stored value --- Desktop_Interface/genericusbdriver.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 98643ccb..5e1a5a5f 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -468,7 +468,9 @@ void genericUsbDriver::checkConnection(){ if((firmver != EXPECTED_FIRMWARE_VERSION) || (variant != DEFINED_EXPECTED_VARIANT)){ qDebug() << "Unexpected Firmware!!"; int flashRet = flashFirmware(); - qDebug("flashRet: %d", flashRet); + if (flashRet != 0) { + QMessageBox::warning(window(), tr("Flashing firmware failed"), tr("Flashing firmware failed with error code %1").arg(flashRet)); + } connected = false; connectTimer->start(); return; From 7293fb4988820a0af0dfdbcb3e9bad0e77dc1f7d Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 16:25:33 +0200 Subject: [PATCH 007/109] fix clang warning about using int as bool --- Desktop_Interface/genericusbdriver.cpp | 50 +++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 5e1a5a5f..acd8b260 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -266,46 +266,46 @@ void genericUsbDriver::setDeviceMode(int mode){ //switch on new deviceMode!! switch(deviceMode){ case 0: - if(oldMode > 2) sendClearBuffer(1,0,0); - setVisible_CH2(0); - checkXY(0); + if(oldMode > 2) sendClearBuffer(true,false,false); + setVisible_CH2(false); + checkXY(false); break; case 1: - if(oldMode > 2) sendClearBuffer(1,0,0); - sendClearBuffer(0,1,0); - setVisible_CH2(1); - checkXY(0); + if(oldMode > 2) sendClearBuffer(true,false,false); + sendClearBuffer(false,true,false); + setVisible_CH2(true); + checkXY(false); break; case 2: - if(oldMode > 2) sendClearBuffer(1,0,0); - sendClearBuffer(0,1,0); - setVisible_CH2(1); + if(oldMode > 2) sendClearBuffer(true,false,false); + sendClearBuffer(false,true,false); + setVisible_CH2(true); break; case 3: - if(oldMode != 4) sendClearBuffer(1,0,0); - sendClearBuffer(0,1,0); - setVisible_CH2(0); - checkXY(0); + if(oldMode != 4) sendClearBuffer(true,false,false); + sendClearBuffer(false,true,false); + setVisible_CH2(true); + checkXY(false); break; case 4: - if(oldMode != 3) sendClearBuffer(1,0,0); - sendClearBuffer(0,1,0); - setVisible_CH2(1); - checkXY(0); + if(oldMode != 3) sendClearBuffer(true,false,false); + sendClearBuffer(false,true,false); + setVisible_CH2(true); + checkXY(false); break; case 5: - setVisible_CH2(0); - checkXY(0); + setVisible_CH2(false); + checkXY(false); break; case 6: - sendClearBuffer(0,0,1); - setVisible_CH2(0); - checkXY(0); + sendClearBuffer(false,false,true); + setVisible_CH2(false); + checkXY(false); break; case 7: - sendClearBuffer(1,0,0); + sendClearBuffer(true,false,false); enableMMTimer(); - checkXY(0); + checkXY(false); break; default: qFatal("Error in genericUsbDriver::setDeviceMode. Invalid device mode."); From d1e50afdf8f34d569fd2d3a5646b5c7162026817 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 16:37:20 +0200 Subject: [PATCH 008/109] fix uninitialized value (and remove some magic 'auto' variable) --- Desktop_Interface/genericusbdriver.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index acd8b260..2f6e3db6 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -213,19 +213,21 @@ void genericUsbDriver::sendFunctionGenData(functionGen::ChannelID channelID) } // Timer Setup - int validClockDivs[7] = {1, 2, 4, 8, 64, 256, 1024}; - auto period = [&](int division) -> int - { - return CLOCK_FREQ / (division * channelData.samples.size() * channelData.freq); - }; - - int* clkSettingIt = std::find_if(std::begin(validClockDivs), std::end(validClockDivs), - [&](int division) -> bool { return period(division) < 65535; }); - - int timerPeriod = period(*clkSettingIt); + static const int validClockDivs[7] = {1, 2, 4, 8, 64, 256, 1024}; + + int timerPeriod = 0; + int clkSetting = 0; + for (size_t i=0; i 0xFFFF && i == 0 /* ensure we always have a valid value */) { + continue; + } - // +1 to change from [0:n) to [1:n] - int clkSetting = std::distance(std::begin(validClockDivs), clkSettingIt) + 1; + // +1 to change from [0:n) to [1:n] + clkSetting = i + 1; + timerPeriod = period; + break; + } if(deviceMode == 5) qDebug("DEVICE IS IN MODE 5"); From 7bbf5d15cc630624e1630d4d52ef542365188316 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 16:37:43 +0200 Subject: [PATCH 009/109] fix some minor warnings --- Desktop_Interface/genericusbdriver.cpp | 41 +++++++++++--------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 2f6e3db6..520b6132 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -23,7 +23,6 @@ class GobindarDialog : public QWidget }; GobindarDialog::GobindarDialog() -: QWidget() { setWindowFlags(Qt::Window); @@ -174,9 +173,9 @@ void genericUsbDriver::sendFunctionGenData(functionGen::ChannelID channelID) unsigned char fGenTemp = 0; fGenTemp |= (fGenTriple & 0x01)<<1; fGenTemp |= (fGenTriple & 0x02)>>1; - usbSendControl(0x40, 0xa4, fGenTemp, 0, 0, NULL); + usbSendControl(0x40, 0xa4, fGenTemp, 0, 0, nullptr); #else - usbSendControl(0x40, 0xa4, fGenTriple, 0, 0, NULL); + usbSendControl(0x40, 0xa4, fGenTriple, 0, 0, nullptr); #endif auto applyAmplitudeAndOffset = [&](unsigned char sample) -> unsigned char @@ -237,15 +236,12 @@ void genericUsbDriver::sendFunctionGenData(functionGen::ChannelID channelID) usbSendControl(0x40, 0xa1, timerPeriod, clkSetting, channelData.samples.size(), channelData.samples.data()); else usbSendControl(0x40, 0xa2, timerPeriod, clkSetting, channelData.samples.size(), channelData.samples.data()); - - return; - } void genericUsbDriver::newDig(int digState){ qDebug() << "newDig"; digitalPinState = digState; - usbSendControl(0x40, 0xa6, digState, 0, 0, NULL); + usbSendControl(0x40, 0xa6, digState, 0, 0, nullptr); } /* @@ -257,12 +253,12 @@ void genericUsbDriver::setBufferPtr(bufferControl *newPtr){ void genericUsbDriver::setDeviceMode(int mode){ int oldMode = deviceMode; deviceMode = mode; - usbSendControl(0x40, 0xa5, (mode == 5 ? 0 : mode), gainMask, 0, NULL); + usbSendControl(0x40, 0xa5, (mode == 5 ? 0 : mode), gainMask, 0, nullptr); - if (fGenPtrData[(int)functionGen::ChannelID::CH1] != NULL) + if (fGenPtrData[(int)functionGen::ChannelID::CH1] != nullptr) sendFunctionGenData(functionGen::ChannelID::CH1); - if (fGenPtrData[(int)functionGen::ChannelID::CH2] != NULL) + if (fGenPtrData[(int)functionGen::ChannelID::CH2] != nullptr) sendFunctionGenData(functionGen::ChannelID::CH2); //switch on new deviceMode!! @@ -333,7 +329,7 @@ void genericUsbDriver::psuTick(){ if ((dutyTemp>106) || (dutyTemp<21)){ qDebug("PSU DUTY CYCLE of dutyTemp = %d OUT OF RANGE (could underflow on SOF)!!! ABORTING!!!", dutyTemp); } - usbSendControl(0x40, 0xa3, dutyTemp, 0, 0, NULL); + usbSendControl(0x40, 0xa3, dutyTemp, 0, 0, nullptr); } void genericUsbDriver::setGain(double newGain){ @@ -361,11 +357,11 @@ void genericUsbDriver::setGain(double newGain){ */ qDebug("newGain = %f", newGain); qDebug("gainMask = %x", gainMask); - usbSendControl(0x40, 0xa5, deviceMode, gainMask, 0, NULL); + usbSendControl(0x40, 0xa5, deviceMode, gainMask, 0, nullptr); } void genericUsbDriver::avrDebug(void){ - usbSendControl(0xc0, 0xa0, 0, 0, sizeof(unified_debug), NULL); + usbSendControl(0xc0, 0xa0, 0, 0, sizeof(unified_debug), nullptr); qDebug() << "unified debug is of size" << sizeof(unified_debug); /* @@ -396,16 +392,16 @@ void genericUsbDriver::avrDebug(void){ void genericUsbDriver::kickstartIso(void){ qDebug() << "Attempting to kickstart iso..."; - usbSendControl(0x40, 0xaa, 0, 0, 0, NULL); + usbSendControl(0x40, 0xaa, 0, 0, 0, nullptr); } void genericUsbDriver::requestFirmwareVersion(void){ - usbSendControl(0xc0, 0xa8, 0, 0, 2, NULL); + usbSendControl(0xc0, 0xa8, 0, 0, 2, nullptr); firmver = *((unsigned short *) inBuffer); } void genericUsbDriver::requestFirmwareVariant(void){ - usbSendControl(0xc0, 0xa9, 0, 0, 1, NULL); + usbSendControl(0xc0, 0xa9, 0, 0, 1, nullptr); variant = *((unsigned char *) inBuffer); } @@ -423,7 +419,6 @@ void genericUsbDriver::saveState(int *_out_deviceMode, double *_out_scopeGain, d *(_out_scopeGain) = scopeGain; *(_out_currentPsuVoltage) = currentPsuVoltage; *(_out_digitalPinState) = digitalPinState; - return; } void genericUsbDriver::checkConnection(){ @@ -461,11 +456,11 @@ void genericUsbDriver::checkConnection(){ connectTimer->stop(); requestFirmwareVersion(); - qDebug("BOARD IS RUNNING FIRMWARE VERSION 0x%04hx", firmver); - qDebug("EXPECTING FIRMWARE VERSION 0x%04hx", EXPECTED_FIRMWARE_VERSION); + qDebug("BOARD IS RUNNING FIRMWARE VERSION 0x%04hhx", firmver); + qDebug("EXPECTING FIRMWARE VERSION 0x%04x", EXPECTED_FIRMWARE_VERSION); requestFirmwareVariant(); - qDebug("FIRMWARE VARIANT = 0x%02hx", variant); - qDebug("EXPECTED VARIANT = 0x%02hx", DEFINED_EXPECTED_VARIANT); + qDebug("FIRMWARE VARIANT = 0x%02hhx", variant); + qDebug("EXPECTED VARIANT = 0x%02x", DEFINED_EXPECTED_VARIANT); if((firmver != EXPECTED_FIRMWARE_VERSION) || (variant != DEFINED_EXPECTED_VARIANT)){ qDebug() << "Unexpected Firmware!!"; @@ -502,7 +497,7 @@ void genericUsbDriver::checkConnection(){ psuTimer->start(PSU_PERIOD); connect(psuTimer, SIGNAL(timeout()), this, SLOT(psuTick())); - if(killOnConnect) usbSendControl(0x40, 0xa7, 0, 0, 0, NULL); + if(killOnConnect) usbSendControl(0x40, 0xa7, 0, 0, 0, nullptr); recoveryTimer = new QTimer(); recoveryTimer->setTimerType(Qt::PreciseTimer); @@ -516,6 +511,6 @@ void genericUsbDriver::checkConnection(){ } void genericUsbDriver::bootloaderJump(){ - usbSendControl(0x40, 0xa7, 1, 0, 0, NULL); + usbSendControl(0x40, 0xa7, 1, 0, 0, nullptr); } From 14b3b39a0d4c163ce52935c363bd39088aa0de33 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:19:58 +0200 Subject: [PATCH 010/109] add dialog with pinout for easy access --- .gitignore | 2 +- Desktop_Interface/Labrador.pro | 2 + Desktop_Interface/mainwindow.cpp | 10 +++- Desktop_Interface/mainwindow.h | 4 +- Desktop_Interface/pinoutDialog.cpp | 74 ++++++++++++++++++++++++ Desktop_Interface/pinoutDialog.h | 10 ++++ Desktop_Interface/resources.qrc | 4 ++ Desktop_Interface/resources/pinout.html | 26 +++++++++ Desktop_Interface/resources/pinout.png | Bin 0 -> 279138 bytes 9 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 Desktop_Interface/pinoutDialog.cpp create mode 100644 Desktop_Interface/pinoutDialog.h create mode 100644 Desktop_Interface/resources/pinout.html create mode 100644 Desktop_Interface/resources/pinout.png diff --git a/.gitignore b/.gitignore index c3f5a49a..b2bd967f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ Desktop_Interface/bin/Labrador *.so *.so.* *.qmake.stash -pinout.png +/pinout.png Matlab_Octave_API/___librador/build-librador-Desktop_Qt_5_10_0_GCC_64bit-Release Matlab_Octave_API/___librador/build-librador-Desktop_Qt_5_10_0_GCC_64bit-Debug diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index 6e079d40..01ef1a45 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -36,6 +36,7 @@ SOURCES += main.cpp\ isodriver.cpp \ isobuffer.cpp \ desktop_settings.cpp \ + pinoutDialog.cpp \ scoperangeenterdialog.cpp \ genericusbdriver.cpp \ isobufferbuffer.cpp \ @@ -47,6 +48,7 @@ SOURCES += main.cpp\ HEADERS += mainwindow.h \ functiongencontrol.h \ + pinoutDialog.h \ xmega.h \ isodriver.h \ isobuffer.h \ diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 5fdbb71d..a053ad44 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1,9 +1,11 @@ #include "mainwindow.h" #include "uartstyledecoder.h" #include "daqform.h" +#include "pinoutDialog.h" #include #include "espospinbox.h" + #include #define DO_QUOTE(X) #X @@ -17,7 +19,6 @@ namespace { constexpr uint32_t MAX_CONSOLE_BLOCK_COUNT = 512; constexpr char kDocumentationUrl[] = "https://github.com/EspoTek/Labrador/wiki"; - constexpr char kPinoutUrl[] = "https://github.com/EspoTek/Labrador/wiki/Pinout"; constexpr char kAboutString[] = "

EspoTek Labrador

" "Continuous Release
"\ "Git hash: " QUOTE(GIT_HASH_SHORT) "
"\ @@ -37,6 +38,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); ui->actionQuit->setShortcut(Qt::CTRL + Qt::Key_Q); // on macOS CTRL is automaticall translated to the command key + ui->actionPinout->setShortcut(Qt::Key_F1); calibrationMessages = new QMessageBox(); ui->psuDisplay->display("4.00"); @@ -2458,7 +2460,11 @@ void MainWindow::on_actionDocumentation_triggered() void MainWindow::on_actionPinout_triggered() { - QDesktopServices::openUrl(QUrl(kPinoutUrl, QUrl::TolerantMode)); + if (m_pinoutDialog) { // only allow one to exist + return; + } + m_pinoutDialog = new pinoutDialog; + m_pinoutDialog->show(); // don't exec, allows } void MainWindow::cursorGroupEnabled(bool enabled) diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 8edc9a51..6e35886f 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -29,7 +29,7 @@ //The Main Window object. This has a lot of control information too (keyboard shortcuts etc.)! - +class pinoutDialog; namespace Ui { class MainWindow; @@ -300,6 +300,8 @@ private slots: scopeRangeEnterDialog* scopeRangeSwitch = nullptr; + QPointer m_pinoutDialog; // QPointer automatically resets when object is deleted + #ifdef PLATFORM_ANDROID //Android Special QScreen *screenPtr; diff --git a/Desktop_Interface/pinoutDialog.cpp b/Desktop_Interface/pinoutDialog.cpp new file mode 100644 index 00000000..e3d4a8a6 --- /dev/null +++ b/Desktop_Interface/pinoutDialog.cpp @@ -0,0 +1,74 @@ +#include "pinoutDialog.h" + +#include +#include +#include +#include +#include +#include +#include + +class ImageLabel : public QLabel +{ +public: + ImageLabel(const QString &path) : m_image(path) + { + setPixmap(m_image); + } + + QSize sizeHint() const override { + return m_image.size(); + } +protected: + void resizeEvent(QResizeEvent *event) override + { + setPixmap(m_image.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + QLabel::resizeEvent(event); + } + +private: + QPixmap m_image; +}; + +static QString loadDescription() +{ + QFile file(":/resources/pinout.html"); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to load" << file.fileName(); + return ""; + } + return QString::fromUtf8(file.readAll()); +} + +pinoutDialog::pinoutDialog() +{ + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout(mainLayout); + + QHBoxLayout *contentLayout = new QHBoxLayout; + mainLayout->addLayout(contentLayout); + + QLabel *imageLabel = new ImageLabel(":/resources/pinout.png"); + imageLabel->setMinimumSize(250, 250); + imageLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + contentLayout->addWidget(imageLabel); + + QLabel *description = new QLabel; + description->setWordWrap(true); + description->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + description->setTextFormat(Qt::RichText); + description->setText(loadDescription()); + description->setAlignment(Qt::AlignTop | Qt::AlignLeft); + contentLayout->addWidget(description); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + mainLayout->addLayout(buttonLayout); + buttonLayout->addStretch(); + + QPushButton *closeButton = new QPushButton(tr("Close")); + closeButton->setDefault(true); + buttonLayout->addWidget(closeButton); + + connect(closeButton, &QPushButton::clicked, this, &QDialog::accept); + +} diff --git a/Desktop_Interface/pinoutDialog.h b/Desktop_Interface/pinoutDialog.h new file mode 100644 index 00000000..20ccb45b --- /dev/null +++ b/Desktop_Interface/pinoutDialog.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class pinoutDialog : public QDialog +{ +public: + pinoutDialog(); +}; + diff --git a/Desktop_Interface/resources.qrc b/Desktop_Interface/resources.qrc index aca22c87..862fb2f5 100644 --- a/Desktop_Interface/resources.qrc +++ b/Desktop_Interface/resources.qrc @@ -5,4 +5,8 @@ buffer_2.bmp diagram.png + + resources/pinout.html + resources/pinout.png + diff --git a/Desktop_Interface/resources/pinout.html b/Desktop_Interface/resources/pinout.html new file mode 100644 index 00000000..df0bb4a6 --- /dev/null +++ b/Desktop_Interface/resources/pinout.html @@ -0,0 +1,26 @@ +

AC-coupled pins

+ +

+ The AC coupled pins remove the DC component from your signals, and are meant + mainly for audio purposes and dual-ended opamps. + For general use, you will almost always want to use the DC-coupled pins, + as they do not alter your signal in any way. +

+ +

Duplicate oscilloscope pins

+

+ The duplicate oscilloscope pins are there for measuring resistance and + capacitance on the multimeter. You do not need them otherwise. +

+ +

Fuse bypass pins

+

+ The fuse bypass pins (to the immediate left of the USB connector on the + diagram) are used for bypassing the fuse, if you need to, by shorting them + together. The top pin, as seen on the image, is a 5V pin directly from the USB + connector, and the bottom pin is the 5V output of the fuse. +

+ +

+ The fuse is a PTC resettable fuse, with a maximum current of around 400 mA. +

diff --git a/Desktop_Interface/resources/pinout.png b/Desktop_Interface/resources/pinout.png new file mode 100644 index 0000000000000000000000000000000000000000..b96698dfab340742a0467938c8d6a994c6dddd01 GIT binary patch literal 279138 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�`V}Bpnf?e>; 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%0AW0DyT9Ra=v9A%JeVMv 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 R=Nl=84;^KF$kKi7{{uHdZT0{F literal 0 HcmV?d00001 From 29a76c045b8c7663093106584a89f481c3e3f656 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:30:39 +0200 Subject: [PATCH 011/109] fix some type issues --- Desktop_Interface/i2cdecoder.cpp | 2 +- Desktop_Interface/mainwindow.cpp | 4 ++-- Desktop_Interface/ui_elements/esposlider.cpp | 2 +- Desktop_Interface/unixusbdriver.cpp | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Desktop_Interface/i2cdecoder.cpp b/Desktop_Interface/i2cdecoder.cpp index abccb609..a3d8fd09 100644 --- a/Desktop_Interface/i2cdecoder.cpp +++ b/Desktop_Interface/i2cdecoder.cpp @@ -59,7 +59,7 @@ void i2cDecoder::run() int i2cDecoder::serialDistance(isoBuffer* buffer) { - int back_bit = buffer->m_back * 8; + uint64_t back_bit = buffer->m_back * 8; int bufferEnd_bit = buffer->m_bufferLen * 8; if (back_bit >= serialPtr_bit) return back_bit - serialPtr_bit; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index a053ad44..4d117e52 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -2166,7 +2166,7 @@ void MainWindow::fileLimitReached_CH1(void){ QMessageBox recordingStoppedMessageBox; char recordingStoppedMessage[256]; - sprintf(recordingStoppedMessage, "Maximum file size limit of %uMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000); + sprintf(recordingStoppedMessage, "Maximum file size limit of %lluMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000); recordingStoppedMessageBox.setText(recordingStoppedMessage); recordingStoppedMessageBox.exec(); } @@ -2176,7 +2176,7 @@ void MainWindow::fileLimitReached_CH2(void){ QMessageBox recordingStoppedMessageBox; char recordingStoppedMessage[256]; - sprintf(recordingStoppedMessage, "Maximum file size limit of %uMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000); + sprintf(recordingStoppedMessage, "Maximum file size limit of %lluMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000); recordingStoppedMessageBox.setText(recordingStoppedMessage); recordingStoppedMessageBox.exec(); } diff --git a/Desktop_Interface/ui_elements/esposlider.cpp b/Desktop_Interface/ui_elements/esposlider.cpp index b29a83be..629dd2a3 100644 --- a/Desktop_Interface/ui_elements/esposlider.cpp +++ b/Desktop_Interface/ui_elements/esposlider.cpp @@ -43,7 +43,7 @@ void espoSlider::moveEvent(QMoveEvent *event){ void espoSlider::setTickInterval(int ti){ addressBook.resize(maxTick(ti) + 1, NULL); //Leaky, but not significantly. Old qlabels never deleted. - for (int i=0; i Date: Wed, 14 Apr 2021 17:30:53 +0200 Subject: [PATCH 012/109] fix warning about unhandled enum values --- Desktop_Interface/i2cdecoder.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/i2cdecoder.cpp b/Desktop_Interface/i2cdecoder.cpp index a3d8fd09..9b7866d4 100644 --- a/Desktop_Interface/i2cdecoder.cpp +++ b/Desktop_Interface/i2cdecoder.cpp @@ -117,7 +117,12 @@ void i2cDecoder::runStateMachine() case transmissionState::data: decodeData(sdaEdge, sclEdge); break; - } + case transmissionState::unknown: + default: + throw std::runtime_error("State machine is in an invalid state!"); + return; + + } } edge i2cDecoder::edgeDetection(uint8_t current, uint8_t prev) From a9d04cb13116f9f1e789daabe613d698cadb17d8 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:31:24 +0200 Subject: [PATCH 013/109] don't emit code in all translation units, fix warning --- Desktop_Interface/unixusbdriver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index 16306db7..592ebe65 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -96,7 +96,7 @@ public slots: }; //Callback on iso transfer complete. -static void LIBUSB_CALL isoCallback(struct libusb_transfer * transfer){ +inline void LIBUSB_CALL isoCallback(struct libusb_transfer * transfer){ tcBlockMutex.lock(); //int number = ((tcBlock *)transfer->user_data)->number; //bool completed = ((tcBlock *)transfer->user_data)->completed; From 59607439ec8bd9a9dd5b704843a9b82251b2adc6 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:35:18 +0200 Subject: [PATCH 014/109] let clang-tidy clean up the int/bool issues --- Desktop_Interface/isobuffer.cpp | 2 +- Desktop_Interface/isodriver.cpp | 24 ++--- Desktop_Interface/mainwindow.cpp | 96 +++++++++---------- Desktop_Interface/uartstyledecoder.cpp | 2 +- .../ui_elements/buffercontrol.cpp | 42 ++++---- .../ui_elements/cursorenabler.cpp | 6 +- 6 files changed, 86 insertions(+), 86 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 20eb5519..cc2645be 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -303,7 +303,7 @@ int isoBuffer::capSample(int offset, int target, double seconds, double value, F if (static_cast(m_back) < (samples + offset)) return -1; - short sample = inverseSampleConvert(value, 2048, 0); + short sample = inverseSampleConvert(value, 2048, false); int found = 0; for (int i = samples + offset; i--;) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index c988153c..d4ffb55c 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -369,7 +369,7 @@ void isoDriver::pauseEnable_CH1(bool enabled){ if (autoGainEnabled) autoGain(); } - if(!enabled) clearBuffers(1,0,1); + if(!enabled) clearBuffers(true,false,true); qDebug() << "pauseEnable_CH1" << enabled; } @@ -383,7 +383,7 @@ void isoDriver::pauseEnable_CH2(bool enabled){ if (autoGainEnabled) autoGain(); } - if(!enabled) clearBuffers(0,1,0); + if(!enabled) clearBuffers(false,true,false); } void isoDriver::pauseEnable_multimeter(bool enabled){ @@ -394,7 +394,7 @@ void isoDriver::pauseEnable_multimeter(bool enabled){ delayUpdated(display.delay); } - if(!enabled) clearBuffers(1,0,0); + if(!enabled) clearBuffers(true,false,false); qDebug() << "pauseEnable_multimeter" << enabled; } @@ -493,7 +493,7 @@ void isoDriver::cursorEnableVert(bool enabled){ void isoDriver::udateCursors(void){ if(!(vertCursorEnabled || horiCursorEnabled)){ #if QCP_VER == 1 - cursorTextPtr->setVisible(0); + cursorTextPtr->setVisible(false); #endif return; } @@ -661,7 +661,7 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) } if(singleShotEnabled && (triggerDelay != 0)) - singleShotTriggered(1); + singleShotTriggered(true); readData375_CH1 = internalBuffer375_CH1->readBuffer(display.window,GRAPH_SAMPLES,CH1_mode==2, display.delay + triggerDelay); if(CH2_mode) readData375_CH2 = internalBuffer375_CH2->readBuffer(display.window,GRAPH_SAMPLES,CH2_mode==2, display.delay + triggerDelay); @@ -679,7 +679,7 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) } xmin = (currentVmin < xmin) ? currentVmin : xmin; xmax = (currentVmax > xmax) ? currentVmax : xmax; - broadcastStats(0); + broadcastStats(false); } if (CH1_mode == 2) digitalConvert(readData375_CH1.get(), &CH1); @@ -692,7 +692,7 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) } ymin = (currentVmin < ymin) ? currentVmin : ymin; ymax = (currentVmax > ymax) ? currentVmax : ymax; - broadcastStats(1); + broadcastStats(true); } if (CH2_mode == 2) digitalConvert(readData375_CH2.get(), &CH2); @@ -700,7 +700,7 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) analogConvert(readData750.get(), &CH1, 128, AC_CH1, 1); xmin = (currentVmin < xmin) ? currentVmin : xmin; xmax = (currentVmax > xmax) ? currentVmax : xmax; - broadcastStats(0); + broadcastStats(false); } if(CH1_mode == -2) { @@ -779,12 +779,12 @@ void isoDriver::multimeterAction(){ } if(singleShotEnabled && (triggerDelay != 0)) - singleShotTriggered(1); + singleShotTriggered(true); readData375_CH1 = internalBuffer375_CH1->readBuffer(display.window,GRAPH_SAMPLES, false, display.delay + triggerDelay); QVector x(GRAPH_SAMPLES), CH1(GRAPH_SAMPLES); - analogConvert(readData375_CH1.get(), &CH1, 2048, 0, 1); //No AC coupling! + analogConvert(readData375_CH1.get(), &CH1, 2048, false, 1); //No AC coupling! for (double i=0; i tempBuffer = currentBuffer->readBuffer(seconds, 1024, 0, 0); + std::unique_ptr tempBuffer = currentBuffer->readBuffer(seconds, 1024, false, 0); double sum = 0; double temp; for(int i = 0; i<1024; i++){ - temp = currentBuffer->sampleConvert(tempBuffer[i], TOP, 0); + temp = currentBuffer->sampleConvert(tempBuffer[i], TOP, false); sum += temp; } return sum / 1024; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 4d117e52..2ad2da69 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -75,10 +75,10 @@ MainWindow::MainWindow(QWidget *parent) : ui->controller_iso->internalBuffer375_CH2->m_console2 = ui->console2; initShortcuts(); - ui->debugButton1->setVisible(0); - ui->debugButton2->setVisible(0); - ui->debugButton3->setVisible(0); - ui->debugConsole->setVisible(0); + ui->debugButton1->setVisible(false); + ui->debugButton2->setVisible(false); + ui->debugButton3->setVisible(false); + ui->debugConsole->setVisible(false); // // Set the consoles to be resizeable // for (const auto & console : {ui->console1, ui->console2}) @@ -97,15 +97,15 @@ MainWindow::MainWindow(QWidget *parent) : } #ifndef PLATFORM_ANDROID - ui->kickstartIsoButton->setVisible(0); - ui->console1->setVisible(0); - ui->console2->setVisible(0); + ui->kickstartIsoButton->setVisible(false); + ui->console1->setVisible(false); + ui->console2->setVisible(false); #endif - ui->timeBaseSlider->setVisible(0); + ui->timeBaseSlider->setVisible(false); //ui->pausedLabel_CH2->setVisible(0); - ui->filterLabel_CH1->setVisible(0); - ui->filterLabel_CH2->setVisible(0); + ui->filterLabel_CH1->setVisible(false); + ui->filterLabel_CH2->setVisible(false); //Reset the device to ensure Labrador_libusbk gets handle!! #ifdef PLATFORM_WINDOWS @@ -279,7 +279,7 @@ void MainWindow::initialisePlot() textLabel->setPen(QPen(Qt::white)); textLabel->setBrush(QBrush(Qt::black)); - textLabel->setVisible(0); + textLabel->setVisible(false); ui->controller_iso->cursorTextPtr = textLabel; ui->scopeAxes->yAxis->setAutoTickCount(9); @@ -495,53 +495,53 @@ void MainWindow::menuSetup(){ void MainWindow::on_actionGain0_5_triggered() { ui->controller_iso->driver->setGain(0.5); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGain1_triggered() { ui->controller_iso->driver->setGain(1); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGain2_triggered() { ui->controller_iso->driver->setGain(2); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGain4_triggered() { ui->controller_iso->driver->setGain(4); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGain8_triggered() { ui->controller_iso->driver->setGain(8); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGain16_triggered() { ui->controller_iso->driver->setGain(16); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGain32_triggered() { ui->controller_iso->driver->setGain(32); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGain64_triggered() { ui->controller_iso->driver->setGain(64); - ui->controller_iso->setAutoGain(0); + ui->controller_iso->setAutoGain(false); } void MainWindow::on_actionGainAuto_triggered() { - ui->controller_iso->setAutoGain(1); + ui->controller_iso->setAutoGain(true); } void MainWindow::on_actionCursor_Stats_triggered(bool checked) @@ -562,14 +562,14 @@ void MainWindow::connectDisplaySignals(){ connect(ui->actionRMS, SIGNAL(toggled(bool)), ui->voltageInfoRmsLabel_CH1, SLOT(setVisible(bool))); connect(ui->actionRMS, SIGNAL(toggled(bool)), ui->voltageInfoRmsDisplay_CH1, SLOT(setVisible(bool))); - ui->voltageInfoMaxLabel_CH1->setVisible(0); - ui->voltageInfoMaxDisplay_CH1->setVisible(0); - ui->voltageInfoMinLabel_CH1->setVisible(0); - ui->voltageInfoMinDisplay_CH1->setVisible(0); - ui->VoltageInfoMeanLabel_CH1->setVisible(0); - ui->voltageInfoMeanDisplay_CH1->setVisible(0); - ui->voltageInfoRmsLabel_CH1->setVisible(0); - ui->voltageInfoRmsDisplay_CH1->setVisible(0); + ui->voltageInfoMaxLabel_CH1->setVisible(false); + ui->voltageInfoMaxDisplay_CH1->setVisible(false); + ui->voltageInfoMinLabel_CH1->setVisible(false); + ui->voltageInfoMinDisplay_CH1->setVisible(false); + ui->VoltageInfoMeanLabel_CH1->setVisible(false); + ui->voltageInfoMeanDisplay_CH1->setVisible(false); + ui->voltageInfoRmsLabel_CH1->setVisible(false); + ui->voltageInfoRmsDisplay_CH1->setVisible(false); connect(ui->actionMax_2, SIGNAL(toggled(bool)), ui->voltageInfoMaxLabel_CH2, SLOT(setVisible(bool))); connect(ui->actionMax_2, SIGNAL(toggled(bool)), ui->voltageInfoMaxDisplay_CH2, SLOT(setVisible(bool))); @@ -583,14 +583,14 @@ void MainWindow::connectDisplaySignals(){ connect(ui->actionRMS_2, SIGNAL(toggled(bool)), ui->voltageInfoRmsLabel_CH2, SLOT(setVisible(bool))); connect(ui->actionRMS_2, SIGNAL(toggled(bool)), ui->voltageInfoRmsDisplay_CH2, SLOT(setVisible(bool))); - ui->voltageInfoMaxLabel_CH2->setVisible(0); - ui->voltageInfoMaxDisplay_CH2->setVisible(0); - ui->voltageInfoMinLabel_CH2->setVisible(0); - ui->voltageInfoMinDisplay_CH2->setVisible(0); - ui->VoltageInfoMeanLabel_CH2->setVisible(0); - ui->voltageInfoMeanDisplay_CH2->setVisible(0); - ui->voltageInfoRmsLabel_CH2->setVisible(0); - ui->voltageInfoRmsDisplay_CH2->setVisible(0); + ui->voltageInfoMaxLabel_CH2->setVisible(false); + ui->voltageInfoMaxDisplay_CH2->setVisible(false); + ui->voltageInfoMinLabel_CH2->setVisible(false); + ui->voltageInfoMinDisplay_CH2->setVisible(false); + ui->VoltageInfoMeanLabel_CH2->setVisible(false); + ui->voltageInfoMeanDisplay_CH2->setVisible(false); + ui->voltageInfoRmsLabel_CH2->setVisible(false); + ui->voltageInfoRmsDisplay_CH2->setVisible(false); } @@ -1128,13 +1128,13 @@ void MainWindow::cycleDelayLeft_large(){ void MainWindow::enableLabradorDebugging(){ qDebug() << "DEBUG MODE ACTIVE"; - ui->debugButton1->setVisible(1); - ui->debugButton2->setVisible(1); - ui->debugButton3->setVisible(1); + ui->debugButton1->setVisible(true); + ui->debugButton2->setVisible(true); + ui->debugButton3->setVisible(true); #ifndef PLATFORM_ANDROID - ui->kickstartIsoButton->setVisible(1); + ui->kickstartIsoButton->setVisible(true); #endif - ui->debugConsole->setVisible(1); + ui->debugConsole->setVisible(true); new Q_DebugStream(std::cout, ui->debugConsole); //Redirect Console output to QTextEdit Q_DebugStream::registerQDebugMessageHandler(); //Redirect qDebug() output to QTextEdit @@ -1424,7 +1424,7 @@ void MainWindow::resetUsbState(void){ ui->controller_iso->driver->setFunctionGen(ChannelID::CH1, ui->controller_fg->getChannelController(ChannelID::CH1)); ui->controller_iso->driver->setFunctionGen(ChannelID::CH2, ui->controller_fg->getChannelController(ChannelID::CH2)); - ui->controller_iso->clearBuffers(1,1,1); + ui->controller_iso->clearBuffers(true,true,true); ui->controller_iso->doNotTouchGraph = false; } @@ -1792,7 +1792,7 @@ void MainWindow::on_actionCalibrate_triggered() calibrationMessages->setText("Please disconnect all wires from your Labrador board then press OK to continue."); calibrationMessages->exec(); - ui->controller_iso->clearBuffers(1,1,1); + ui->controller_iso->clearBuffers(true,true,true); QTimer::singleShot(1200, this, SLOT(calibrateStage2())); } @@ -1821,7 +1821,7 @@ void MainWindow::calibrateStage2(){ calibrationMessages->setText("Please connect both oscilloscope channels to the outer shield of the USB connector then press OK to continue."); calibrationMessages->exec(); - ui->controller_iso->clearBuffers(1,1,1); + ui->controller_iso->clearBuffers(true,true,true); QTimer::singleShot(1200, this, SLOT(calibrateStage3())); } @@ -2321,7 +2321,7 @@ void MainWindow::on_actionCalibrate_2_triggered() calibrationMessages->exec(); ui->controller_iso->driver->setPsu(5); - ui->controller_iso->clearBuffers(1,1,1); + ui->controller_iso->clearBuffers(true,true,true); QTimer::singleShot(1800, this, SLOT(calibrate_psu_stage2())); } @@ -2332,7 +2332,7 @@ void MainWindow::calibrate_psu_stage2() if((PSU5 > 6) | (PSU5 < 4) ){ ui->controller_iso->driver->setPsu(4.5); ui->psuSlider->setValue(0); - ui->controller_iso->clearBuffers(1,1,1); + ui->controller_iso->clearBuffers(true,true,true); ui->controller_iso->setAutoGain(true); ui->controller_iso->autoGain(); calibrationMessages->setText("Calibration has been abandoned due to out-of-range values. The oscilloscope should show approximately 5V. Please check all wires on your Labrador board and try again."); @@ -2341,7 +2341,7 @@ void MainWindow::calibrate_psu_stage2() } ui->controller_iso->setGain(1); ui->controller_iso->driver->setPsu(10); - ui->controller_iso->clearBuffers(1,1,1); + ui->controller_iso->clearBuffers(true,true,true); QTimer::singleShot(1800, this, SLOT(calibrate_psu_stage3())); } @@ -2351,7 +2351,7 @@ void MainWindow::calibrate_psu_stage3() qDebug() << "PSU10 =" << PSU10; ui->controller_iso->driver->setPsu(4.5); ui->psuSlider->setValue(0); - ui->controller_iso->clearBuffers(1,1,1); + ui->controller_iso->clearBuffers(true,true,true); ui->controller_iso->setAutoGain(true); ui->controller_iso->autoGain(); diff --git a/Desktop_Interface/uartstyledecoder.cpp b/Desktop_Interface/uartstyledecoder.cpp index cb06e0e4..55c884e8 100644 --- a/Desktop_Interface/uartstyledecoder.cpp +++ b/Desktop_Interface/uartstyledecoder.cpp @@ -185,7 +185,7 @@ bool uartStyleDecoder::jitterCompensationProcedure(bool current_bit) if (left_byte != 0xff) { //Step back, one sample at a time, to the 0->1 transition point - bool temp_bit = 1; + bool temp_bit = true; while(temp_bit) { temp_bit = getNextUartBit(); diff --git a/Desktop_Interface/ui_elements/buffercontrol.cpp b/Desktop_Interface/ui_elements/buffercontrol.cpp index 399b0b8f..c6beb5cb 100644 --- a/Desktop_Interface/ui_elements/buffercontrol.cpp +++ b/Desktop_Interface/ui_elements/buffercontrol.cpp @@ -34,12 +34,12 @@ void bufferControl::scopeIn_CH1(bool state){ //What about DSR!? if (scopeState_CH2){ //Implicitly state is false scopeState_CH2 = false; //updateBuffer(0); - Causes issues because the uncheck below called scopeIn_CH2 (but only when toggle)!!! - scopeUncheck(0); + scopeUncheck(false); } //Turn off the DSR when CH1 is disabled. if(!state && !scopeDsrDisableOverride){ - scopeDsrUncheck(0); + scopeDsrUncheck(false); } scopeDsrOut(state); @@ -80,7 +80,7 @@ void bufferControl::busSnifferIn_CH1(bool state){ if (busSnifferState_CH2){ //Implicitly state is false busSnifferState_CH2 = false; //updateBuffer(0); - Causes issues because the uncheck below called scopeIn_CH2 (but only when toggle)!!! - busSnifferUncheck(0); + busSnifferUncheck(false); } //Signal Gen CH2 doesn't work with bus sniffer. @@ -123,44 +123,44 @@ void bufferControl::updateBuffer(bool decrement, int amount){ //Write new state switch(numBuffers){ case 0: - if(scopeState_CH1 == false) scopeOut_CH1(0); - if(scopeState_CH2 == false) scopeOut_CH2(0); - if(scopeDsrState == false) scopeDsrOut(0); + if(scopeState_CH1 == false) scopeOut_CH1(false); + if(scopeState_CH2 == false) scopeOut_CH2(false); + if(scopeDsrState == false) scopeDsrOut(false); //if(signalGenState == false) signalGenOut(0); - if(busSnifferState_CH1 == false) busSnifferOut_CH1(0); - if(busSnifferState_CH2 == false) busSnifferOut_CH2(0); - if(multimeterState == false) multimeterOut(0); + if(busSnifferState_CH1 == false) busSnifferOut_CH1(false); + if(busSnifferState_CH2 == false) busSnifferOut_CH2(false); + if(multimeterState == false) multimeterOut(false); break; case 1: - scopeOut_CH1(1); + scopeOut_CH1(true); if(scopeState_CH1 == true){ - scopeDsrOut(1); - scopeOut_CH2(1); + scopeDsrOut(true); + scopeOut_CH2(true); } //signalGenOut(1); - busSnifferOut_CH1(1); + busSnifferOut_CH1(true); if(busSnifferState_CH1 == true){ - busSnifferOut_CH2(1); + busSnifferOut_CH2(true); } //busSnifferOut_CH2(1); - multimeterOut(0); + multimeterOut(false); break; case 2: - scopeOut_CH1(1); + scopeOut_CH1(true); //scopeOut_CH2(1); - if(scopeState_CH1 == true) scopeDsrOut(1); + if(scopeState_CH1 == true) scopeDsrOut(true); //signalGenOut(1); - busSnifferOut_CH1(1); + busSnifferOut_CH1(true); //busSnifferOut_CH2(1); - multimeterOut(1); + multimeterOut(true); break; default: qFatal("numBuffers is not equal to 0, 1 or 2"); } if(scopeDsrDisableOverride){ - scopeDsrOut(0); + scopeDsrOut(false); } } @@ -252,6 +252,6 @@ void bufferControl::updateMode(void){ void bufferControl::poke(void){ updateDig(digState); updateMode(); - updateBuffer(0,0); + updateBuffer(false,0); } diff --git a/Desktop_Interface/ui_elements/cursorenabler.cpp b/Desktop_Interface/ui_elements/cursorenabler.cpp index 77a5391b..227ef169 100644 --- a/Desktop_Interface/ui_elements/cursorenabler.cpp +++ b/Desktop_Interface/ui_elements/cursorenabler.cpp @@ -3,7 +3,7 @@ cursorEnabler::cursorEnabler(QWidget *parent) : QLabel(parent) { - this->setVisible(0); + this->setVisible(false); #ifdef PLATFORM_ANDROID this->m_turnedOn = false; #endif @@ -20,11 +20,11 @@ void cursorEnabler::clickDetected(QMouseEvent* event){ if(m_turnedOn){ if (event->button() == Qt::LeftButton) { - tickHori(1); + tickHori(true); } else if (event->button() == Qt::RightButton) { - tickVert(1); + tickVert(true); } } passOnSignal(event); From 319adfa7fb39cee6a9e63dfa5a94d71c47aa1493 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:42:45 +0200 Subject: [PATCH 015/109] fix use of uninitialized memory --- Desktop_Interface/isodriver.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index d4ffb55c..0efc139b 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -840,7 +840,16 @@ void isoDriver::multimeterStats(){ QTimer::singleShot(MULTIMETER_PERIOD, this, SLOT(enableMM())); multimeterShow = false; - bool mvMax, mvMin, mvMean, mvRMS, maMax, maMin, maMean, maRMS, kOhms, uFarads; //We'll let the compiler work out this one. + bool mvMax = false; + bool mvMin = false; + bool mvMean = false; + bool mvRMS = false; + bool maMax = false; + bool maMin = false; + bool maMean = false; + bool maRMS = false; + bool kOhms = false; + bool uFarads = false; //We'll let the compiler work out this one. if(autoMultimeterV){ mvMax = abs(currentVmax) < 1.; From 2c04611b5a828df5df1d270123ec41e06bd21875 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:42:53 +0200 Subject: [PATCH 016/109] fix unused warning --- Desktop_Interface/ui_elements/espospinbox.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/ui_elements/espospinbox.cpp b/Desktop_Interface/ui_elements/espospinbox.cpp index 5e5fb949..245899cf 100644 --- a/Desktop_Interface/ui_elements/espospinbox.cpp +++ b/Desktop_Interface/ui_elements/espospinbox.cpp @@ -64,7 +64,9 @@ void espoSpinBox::changeStepping(double value){ QValidator::State espoSpinBox::validate(QString& text, int& pos) const { - return QValidator::State::Acceptable; + Q_UNUSED(text); + Q_UNUSED(pos); + return QValidator::State::Acceptable; } double espoSpinBox::valueFromText(const QString &text) const From 5772bd10544e6464e3ab6cea0a443a10f3a46c7b Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:43:32 +0200 Subject: [PATCH 017/109] fix invalid format specifier --- Desktop_Interface/isobuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index cc2645be..39267f50 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -62,7 +62,7 @@ void isoBuffer::insertIntoBuffer(short item) short isoBuffer::bufferAt(uint32_t idx) const { if (idx > m_insertedCount) - qFatal("isoBuffer::bufferAt: invalid query, idx = %lu, m_insertedCount = %lu", idx, m_insertedCount); + qFatal("isoBuffer::bufferAt: invalid query, idx = %u, m_insertedCount = %u", idx, m_insertedCount); return m_buffer[(m_back-1) + m_bufferLen - idx]; } From 3b29bfebb6d1d3885facb6d83cb0c748171d8cfa Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:45:10 +0200 Subject: [PATCH 018/109] fix potential memory leak --- Desktop_Interface/ui_elements/espocombobox.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/ui_elements/espocombobox.cpp b/Desktop_Interface/ui_elements/espocombobox.cpp index 15c19fb0..c1f1a56c 100644 --- a/Desktop_Interface/ui_elements/espocombobox.cpp +++ b/Desktop_Interface/ui_elements/espocombobox.cpp @@ -42,14 +42,15 @@ void espoComboBox::readWaveformList(void) qDebug() << "Attempting to open" << dirString; FILE *listPtr = fopen(buffer, "r"); - QStringList *newNames = new QStringList(); - char nameBuffer[255]; if(listPtr == NULL){ qWarning("Could not load _list.wfl"); return; } + QStringList *newNames = new QStringList(); + char nameBuffer[255]; + while (fgets(nameBuffer,256,listPtr) !=NULL){ qDebug() << "nameBuffer = " << nameBuffer; strtok(nameBuffer, "\n\r"); From 91ecafd78733ab32b93079de95ce91701ba6d553 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 17:51:07 +0200 Subject: [PATCH 019/109] fix potential division by zero --- Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp index 30860827..72bffba9 100644 --- a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp +++ b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp @@ -2836,7 +2836,8 @@ QCPLayoutElement *QCPLayoutGrid::elementAt(int index) const /* inherits documentation from base class */ QCPLayoutElement *QCPLayoutGrid::takeAt(int index) { - if (QCPLayoutElement *el = elementAt(index)) + QCPLayoutElement *el = elementAt(index); + if (el && columnCount() > 0) { releaseElement(el); mElements[index / columnCount()][index % columnCount()] = 0; From 1064ac47371d694e5cfa7fd3d2e0b622364590e9 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 18:01:54 +0200 Subject: [PATCH 020/109] let clang-tidy do some minor performance improvements --- Desktop_Interface/functiongencontrol.cpp | 8 ++-- Desktop_Interface/isobuffer_file.cpp | 2 +- Desktop_Interface/isobuffer_file.h | 2 +- Desktop_Interface/ui_elements/esposlider.cpp | 2 +- Desktop_Interface/ui_elements/esposlider.h | 2 +- .../ui_elements/qcp1/qcustomplot.cpp | 47 +++++++------------ .../ui_elements/qcp1/qcustomplot.h | 8 ++-- 7 files changed, 30 insertions(+), 41 deletions(-) diff --git a/Desktop_Interface/functiongencontrol.cpp b/Desktop_Interface/functiongencontrol.cpp index 465d172d..32151f3d 100644 --- a/Desktop_Interface/functiongencontrol.cpp +++ b/Desktop_Interface/functiongencontrol.cpp @@ -1,4 +1,6 @@ #include "functiongencontrol.h" + +#include #include "platformspecific.h" namespace functionGen { @@ -168,7 +170,7 @@ SingleChannelController* DualChannelController::getChannelController(ChannelID c // Hopefuly it can be mostly removed eventually void DualChannelController::waveformName(ChannelID channelID, QString newName) { - getChannelController(channelID)->waveformName(newName); + getChannelController(channelID)->waveformName(std::move(newName)); } void DualChannelController::freqUpdate(ChannelID channelID, double newFreq) @@ -189,7 +191,7 @@ void DualChannelController::offsetUpdate(ChannelID channelID, double newOffset) void DualChannelController::waveformName_CH1(QString newName) { - waveformName(ChannelID::CH1, newName); + waveformName(ChannelID::CH1, std::move(newName)); } void DualChannelController::freqUpdate_CH1(double newFreq) @@ -210,7 +212,7 @@ void DualChannelController::offsetUpdate_CH1(double newOffset) void DualChannelController::waveformName_CH2(QString newName) { - waveformName(ChannelID::CH2, newName); + waveformName(ChannelID::CH2, std::move(newName)); } void DualChannelController::freqUpdate_CH2(double newFreq) diff --git a/Desktop_Interface/isobuffer_file.cpp b/Desktop_Interface/isobuffer_file.cpp index 5c73c625..c7543db7 100644 --- a/Desktop_Interface/isobuffer_file.cpp +++ b/Desktop_Interface/isobuffer_file.cpp @@ -9,7 +9,7 @@ isoBuffer_file::isoBuffer_file(QWidget *parent, int bufferlen, double sampleRate samplesPerSecond = sampleRate_Hz; } -void isoBuffer_file::writeBuffer_float(float* data, int len) +void isoBuffer_file::writeBuffer_float(const float* data, int len) { for (int i=0; i maxTick()){ //qDebug() << "Tried to label tick at position " << position << "but ticks range from 0 to " << maxTick(); diff --git a/Desktop_Interface/ui_elements/esposlider.h b/Desktop_Interface/ui_elements/esposlider.h index aeed7aac..de665316 100644 --- a/Desktop_Interface/ui_elements/esposlider.h +++ b/Desktop_Interface/ui_elements/esposlider.h @@ -15,7 +15,7 @@ class espoSlider : public QSlider Q_OBJECT public: explicit espoSlider(QWidget *parent = 0); - bool setTickLabel(QString label, int position); + bool setTickLabel(const QString& label, int position); void resizeEvent(QResizeEvent *event); void moveEvent(QMoveEvent *event); void setTickInterval(int ti); diff --git a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp index 72bffba9..9cfbeb9c 100644 --- a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp +++ b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp @@ -74,8 +74,7 @@ QCPPainter::QCPPainter(QPaintDevice *device) : } QCPPainter::~QCPPainter() -{ -} += default; /*! Sets the pen of the painter and applies certain fixes to it, depending on the mode of this @@ -919,7 +918,7 @@ void QCPLayer::removeChild(QCPLayerable *layerable) plot does. It is not uncommon to set the QObject-parent to something else in the constructors of QCPLayerable subclasses, to guarantee a working destruction hierarchy. */ -QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer, QCPLayerable *parentLayerable) : +QCPLayerable::QCPLayerable(QCustomPlot *plot, const QString& targetLayer, QCPLayerable *parentLayerable) : QObject(plot), mVisible(true), mParentPlot(plot), @@ -2116,8 +2115,7 @@ int QCPLayoutElement::calculateAutoMargin(QCP::MarginSide side) is an abstract base class, it can't be instantiated directly. */ QCPLayout::QCPLayout() -{ -} += default; /*! First calls the QCPLayoutElement::update base class implementation to update the margins on this @@ -2343,7 +2341,7 @@ void QCPLayout::releaseElement(QCPLayoutElement *el) The return value is a QVector containing the section sizes. */ -QVector QCPLayout::getSectionSizes(QVector maxSizes, QVector minSizes, QVector stretchFactors, int totalSize) const +QVector QCPLayout::getSectionSizes(const QVector& maxSizes, QVector minSizes, QVector stretchFactors, int totalSize) const { if (maxSizes.size() != minSizes.size() || minSizes.size() != stretchFactors.size()) { @@ -3078,8 +3076,7 @@ void QCPLayoutGrid::getMaximumRowColSizes(QVector *maxColWidths, QVector getSectionSizes(QVector maxSizes, QVector minSizes, QVector stretchFactors, int totalSize) const; + QVector getSectionSizes(const QVector& maxSizes, QVector minSizes, QVector stretchFactors, int totalSize) const; private: Q_DISABLE_COPY(QCPLayout) @@ -1503,7 +1503,7 @@ class QCP_LIB_DECL QCPAbstractPlottable : public QCPLayerable class QCP_LIB_DECL QCPItemAnchor { public: - QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString name, int anchorId=-1); + QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString& name, int anchorId=-1); virtual ~QCPItemAnchor(); // getters: @@ -1556,7 +1556,7 @@ class QCP_LIB_DECL QCPItemPosition : public QCPItemAnchor ,ptPlotCoords ///< Dynamic positioning at a plot coordinate defined by two axes (see \ref setAxes). }; - QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString name); + QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString& name); virtual ~QCPItemPosition(); // getters: From 3e1dba07da66d806da82929d9af537ed0608454c Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 14 Apr 2021 18:02:06 +0200 Subject: [PATCH 021/109] final invalid type specifier, I think --- Desktop_Interface/isodriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 0efc139b..6a401929 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -1280,7 +1280,7 @@ void isoDriver::loadFileBuffer(QFile *fileToLoad){ tempList.clear(); } - qDebug("There are %d elements!", numel); + qDebug("There are %llu elements!", numel); //Prompt user for start and end times double defaultSampleRate = 375000; From b75cb30dfdec8736de86511cddcc8e537b02b3a9 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 10:30:00 +0200 Subject: [PATCH 022/109] bundle default waveforms in the application binary --- Desktop_Interface/functiongencontrol.cpp | 70 ++++++------------- Desktop_Interface/resources.qrc | 8 +++ .../ui_elements/espocombobox.cpp | 67 +++++++----------- 3 files changed, 55 insertions(+), 90 deletions(-) diff --git a/Desktop_Interface/functiongencontrol.cpp b/Desktop_Interface/functiongencontrol.cpp index 32151f3d..93499327 100644 --- a/Desktop_Interface/functiongencontrol.cpp +++ b/Desktop_Interface/functiongencontrol.cpp @@ -16,12 +16,31 @@ void SingleChannelController::waveformName(QString newName) int length; + const QStringList potentialDirs = { #ifdef PLATFORM_ANDROID - QString waveformFilePath("assets:/waveforms/"); - waveformFilePath.append(newName); + "assets:", +#else + QCoreApplication::applicationDirPath(), +#endif + ":", // fall back to builtin + }; + + QString filename; + for (const QString &dir : potentialDirs) { + const QString potential = dir + "/waveforms/" + newName; + if (QFileInfo::exists(potential)) { + filename = potential; + break; + } + } + + QString waveformFilePath(filename); QFile fptr(waveformFilePath); - bool success = fptr.open(QIODevice::ReadOnly); + if (!fptr.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open" << newName; + return; + } QByteArray line; char lengthString[16]; @@ -54,51 +73,6 @@ void SingleChannelController::waveformName(QString newName) m_data.samples[i] = static_cast(dummy); } -#else - - QByteArray filePath = QCoreApplication::applicationDirPath() - .append("/waveforms/").append(newName).toLocal8Bit(); - - qDebug() << "opening" << filePath; - - FILE *fptr = fopen(filePath.constData(), "r"); - if (fptr == NULL) - qFatal("%s could not be opened!", filePath.constData()); - - char lengthString[16]; - fgets(lengthString, 5, fptr); - sscanf(lengthString, "%d", &length); - - char divisibilityString[16]; - //Bit of bullshit to deal with CRLF line endings on Mac. - do - { - fgets(divisibilityString, 5, fptr); - } - while ((divisibilityString[0] == '\r') || (divisibilityString[0] == '\n')); - - sscanf(divisibilityString, "%d", &m_data.divisibility); - - qDebug() << "Length = " << length; - qDebug() << "Divisibility = " << m_data.divisibility; - - m_data.samples.resize(length); - - char *dataString = (char *) malloc(length*5+1); - fgets(dataString, length*5+1, fptr); - - int dummy; - char *dataStringCurrent = dataString; - for (int i = 0; i < length; i++) - { - sscanf(dataStringCurrent, "%d", &dummy); - dataStringCurrent += strcspn(dataStringCurrent, "\t") + 1; - m_data.samples[i] = static_cast(dummy); - } - - free(dataString); - fclose(fptr); -#endif double newMaxFreq = DAC_SPS / (length >> (m_data.divisibility - 1)); double newMinFreq = double(CLOCK_FREQ) / 1024.0 / 65535.0 / static_cast(length); diff --git a/Desktop_Interface/resources.qrc b/Desktop_Interface/resources.qrc index 862fb2f5..1f00537f 100644 --- a/Desktop_Interface/resources.qrc +++ b/Desktop_Interface/resources.qrc @@ -9,4 +9,12 @@ resources/pinout.html resources/pinout.png + + bin/waveforms/_list.wfl + bin/waveforms/DC.tlw + bin/waveforms/Sawtooth.tlw + bin/waveforms/Sin.tlw + bin/waveforms/Square.tlw + bin/waveforms/Triangle.tlw + diff --git a/Desktop_Interface/ui_elements/espocombobox.cpp b/Desktop_Interface/ui_elements/espocombobox.cpp index c1f1a56c..2e76f13d 100644 --- a/Desktop_Interface/ui_elements/espocombobox.cpp +++ b/Desktop_Interface/ui_elements/espocombobox.cpp @@ -8,58 +8,41 @@ espoComboBox::espoComboBox(QWidget *parent) : QComboBox(parent) void espoComboBox::readWaveformList(void) { - //This code gets the name of the current directory, regardless of platform. - //This is so the interface knows where to find the waveform data - //QDir *dir = new QDir(); - //qDebug() << dir->currentPath(); + const QStringList potentialDirs = { #ifdef PLATFORM_ANDROID - QFile qt_list("assets:/waveforms/_list.wfl"); - bool success = qt_list.open(QIODevice::ReadOnly | QIODevice::Text); - if(!success){ - qFatal("Could not load _list.wfl"); + "assets:", +#else + QCoreApplication::applicationDirPath(), +#endif + ":/", // fall back to builtin + }; + + QString filename; + for (const QString &dir : potentialDirs) { + const QString potential = dir + "/waveforms/_list.wfl"; + if (QFileInfo::exists(potential)) { + filename = potential; + break; + } } - - char nameBuffer[255]; - QStringList *newNames = new QStringList(); - - while (!qt_list.atEnd()) { - QByteArray line = qt_list.readLine(); - strcpy(nameBuffer, line.data()); - strtok(nameBuffer, "\n\r"); - newNames->append(nameBuffer); - qDebug() << nameBuffer; + if (filename.isEmpty()) { + qWarning() << "Failed to find a waveform list!"; + return; } - this->addItems(*(newNames)); - delete newNames; - qt_list.close(); -#else - QString dirString = QCoreApplication::applicationDirPath(); - dirString.append("/waveforms/_list.wfl"); - QByteArray array = dirString.toLocal8Bit(); - char* buffer = array.data(); - //qDebug() << buffer; - - qDebug() << "Attempting to open" << dirString; - FILE *listPtr = fopen(buffer, "r"); - - if(listPtr == NULL){ + QFile qt_list(filename); + bool success = qt_list.open(QIODevice::ReadOnly | QIODevice::Text); + if(!success){ qWarning("Could not load _list.wfl"); return; } - QStringList *newNames = new QStringList(); - char nameBuffer[255]; + QStringList newNames; - while (fgets(nameBuffer,256,listPtr) !=NULL){ - qDebug() << "nameBuffer = " << nameBuffer; - strtok(nameBuffer, "\n\r"); - newNames->append(nameBuffer); + while (!qt_list.atEnd()) { + this->addItem(qt_list.readLine().trimmed()); } - this->addItems(*(newNames)); - delete newNames; + qt_list.close(); - fclose(listPtr); -#endif qDebug() << "List loaded!!"; } From cc041c329b9ad38c6015185a4e3c0f640a4302e0 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 10:33:25 +0200 Subject: [PATCH 023/109] port away from dangerous strcpy --- Desktop_Interface/functiongencontrol.cpp | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Desktop_Interface/functiongencontrol.cpp b/Desktop_Interface/functiongencontrol.cpp index 93499327..b4ebb91d 100644 --- a/Desktop_Interface/functiongencontrol.cpp +++ b/Desktop_Interface/functiongencontrol.cpp @@ -43,18 +43,22 @@ void SingleChannelController::waveformName(QString newName) } QByteArray line; - char lengthString[16]; - char divisibilityString[16]; - - line = fptr.readLine(); - strcpy(lengthString, line.data()); - sscanf(lengthString, "%d", &length); - qDebug() << "lengthString" << lengthString; - - line = fptr.readLine(); - strcpy(divisibilityString, line.data()); - sscanf(divisibilityString, "%d", &m_data.divisibility); - qDebug() << "divisibilityString" << divisibilityString; + + line = fptr.readLine().trimmed(); + bool ok; + length = line.toInt(&ok); + if (!ok) { + qWarning() << "Invalid length line" << line << "in" << filename; + return; + } + + line = fptr.readLine().trimmed(); + m_data.divisibility = line.toInt(&ok); + if (!ok) { + qWarning() << "Invalid divisibility line" << line << "in" << filename; + return; + } + qDebug() << "Length = " << length; qDebug() << "Divisibility = " << m_data.divisibility; From e661413b1d30d84f89ebb4bd9b682f7dfd09e4ae Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 10:46:17 +0200 Subject: [PATCH 024/109] simplify the waveform reading and make it a bit more robust --- Desktop_Interface/functiongencontrol.cpp | 35 +++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Desktop_Interface/functiongencontrol.cpp b/Desktop_Interface/functiongencontrol.cpp index b4ebb91d..e8716654 100644 --- a/Desktop_Interface/functiongencontrol.cpp +++ b/Desktop_Interface/functiongencontrol.cpp @@ -3,6 +3,8 @@ #include #include "platformspecific.h" +#include + namespace functionGen { ChannelData const& SingleChannelController::getData() const { @@ -51,6 +53,10 @@ void SingleChannelController::waveformName(QString newName) qWarning() << "Invalid length line" << line << "in" << filename; return; } + if (length == 0) { + qWarning() << "No samples in" << filename; + return; + } line = fptr.readLine().trimmed(); m_data.divisibility = line.toInt(&ok); @@ -63,22 +69,31 @@ void SingleChannelController::waveformName(QString newName) qDebug() << "Length = " << length; qDebug() << "Divisibility = " << m_data.divisibility; - QByteArray remainingData = fptr.readAll(); - char *dataString = remainingData.data(); + QString remainingData = QString::fromLatin1(fptr.readAll().simplified()); m_data.samples.resize(length); - int dummy; - char *dataStringCurrent = dataString; - for (int i = 0; i < length; i++) - { - sscanf(dataStringCurrent, "%d", &dummy); - dataStringCurrent += strcspn(dataStringCurrent, "\t") + 1; - m_data.samples[i] = static_cast(dummy); + // Should use tabs for separating, but we support any kind of whitespace + const QStringList values = remainingData.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); + if (values.count() != length) { + qWarning() << "Invalid amount of values" << values.count() << "in" << filename << "expected" << length; + m_data.samples.resize(values.count()); + } + for (int i=0; i 255) { + qWarning() << "Invalid sample value" << values[i]; + } + m_data.samples[i] = uint8_t(sample); } - double newMaxFreq = DAC_SPS / (length >> (m_data.divisibility - 1)); + const unsigned divisor = length >> (m_data.divisibility - 1); + if (divisor == 0) { + qWarning() << "Invalid divisor" << divisor; + return; + } + double newMaxFreq = DAC_SPS / divisor; double newMinFreq = double(CLOCK_FREQ) / 1024.0 / 65535.0 / static_cast(length); setMaxFreq(newMaxFreq); From c97a2cc24934fdebdd3a34338d9941eae839cbc4 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 10:55:53 +0200 Subject: [PATCH 025/109] fix minor compiler warnings --- Desktop_Interface/isodriver.cpp | 3 ++- Desktop_Interface/mainwindow.cpp | 9 +++++++-- Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp | 2 +- Desktop_Interface/ui_elements/qcp1/qcustomplot.h | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 6a401929..1852e3fe 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -778,8 +778,9 @@ void isoDriver::multimeterAction(){ triggerDelay = 0; } - if(singleShotEnabled && (triggerDelay != 0)) + if(singleShotEnabled && (triggerDelay != 0)) { singleShotTriggered(true); + } readData375_CH1 = internalBuffer375_CH1->readBuffer(display.window,GRAPH_SAMPLES, false, display.delay + triggerDelay); diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 2ad2da69..a7059917 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -229,9 +229,9 @@ MainWindow::MainWindow(QWidget *parent) : #endif ui->realTimeButton->setVisible(false); - if ((QApplication::desktop()->availableGeometry().width() < 1520) || (QApplication::desktop()->geometry().height() < 800)) + if ((screen()->availableGeometry().width() < 1520) || (screen()->availableGeometry().height() < 800)) { - qDebug() << "Low resolution detected:" << QApplication::desktop()->availableGeometry().width() << "x" << QApplication::desktop()->availableGeometry().height(); + qDebug() << "Low resolution detected:" << screen()->availableGeometry().width() << "x" << screen()->availableGeometry().height(); this->setMinimumSize(1280, 700); this->resize(1280, 700); } @@ -340,6 +340,8 @@ void MainWindow::labelPsu(){ } void MainWindow::resizeEvent(QResizeEvent *event){ + Q_UNUSED(event); + //ui->scopeAxes->yAxis->setAutoTickCount((ui->scopeAxes->height() + TICK_SEPARATION / 2) / TICK_SEPARATION); //ui->scopeAxes->xAxis->setAutoTickCount((ui->scopeAxes->width() + TICK_SEPARATION / 2) / TICK_SEPARATION); @@ -1297,6 +1299,7 @@ void MainWindow::readSettingsFile(){ void MainWindow::on_actionRecord_triggered(bool checked) { + Q_UNUSED(checked); /* if(!checked){ ui->controller_iso->internalBuffer375_CH1->disableFileIO(); @@ -2375,11 +2378,13 @@ void MainWindow::calibrate_psu_stage3() void MainWindow::on_actionSerial_triggered(bool checked) { + Q_UNUSED(checked); ui->controller_iso->setSerialType(0); } void MainWindow::on_actionI2C_triggered(bool checked) { + Q_UNUSED(checked); ui->controller_iso->setSerialType(1); } diff --git a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp index 9cfbeb9c..d377759c 100644 --- a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp +++ b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp @@ -3110,7 +3110,7 @@ Qt::Alignment QCPLayoutInset::insetAlignment(int index) const else { qDebug() << Q_FUNC_INFO << "Invalid element index:" << index; - return 0; + return Qt::Alignment(); } } diff --git a/Desktop_Interface/ui_elements/qcp1/qcustomplot.h b/Desktop_Interface/ui_elements/qcp1/qcustomplot.h index 2abc0ef3..2553ff0e 100644 --- a/Desktop_Interface/ui_elements/qcp1/qcustomplot.h +++ b/Desktop_Interface/ui_elements/qcp1/qcustomplot.h @@ -2482,7 +2482,7 @@ Q_DECLARE_TYPEINFO(QCPData, Q_MOVABLE_TYPE); This is the container in which QCPGraph holds its data. \see QCPData, QCPGraph::setData */ -typedef QMap QCPDataMap; +typedef QMultiMap QCPDataMap; typedef QMapIterator QCPDataMapIterator; typedef QMutableMapIterator QCPDataMutableMapIterator; @@ -3137,7 +3137,7 @@ Q_DECLARE_TYPEINFO(QCPFinancialData, Q_MOVABLE_TYPE); This is the container in which QCPFinancial holds its data. \see QCPFinancial, QCPFinancial::setData */ -typedef QMap QCPFinancialDataMap; +typedef QMultiMap QCPFinancialDataMap; typedef QMapIterator QCPFinancialDataMapIterator; typedef QMutableMapIterator QCPFinancialDataMutableMapIterator; From d37e184dd38dc6e3325b7b50ee0ad4ba8a44f2f8 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 11:28:18 +0200 Subject: [PATCH 026/109] port away from some deprecated APIs --- Desktop_Interface/isodriver.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 1852e3fe..178748dc 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -264,20 +264,20 @@ void isoDriver::setVoltageRange(QWheelEvent* event) void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes) { - if (!(event->modifiers() == Qt::ControlModifier) && event->orientation() == Qt::Orientation::Vertical) { + if (!(event->modifiers() == Qt::ControlModifier) && qAbs(event->angleDelta().y()) > qAbs(event->angleDelta().x())) { double c = (topRange - botRange) / (double)400; QCPRange range = axes->yAxis->range(); - double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(event->y())-range.lower) / range.size())); + double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(event->position().y())-range.lower) / range.size())); if (pixPct < 0) pixPct = 0; if (pixPct > 100) pixPct = 100; qDebug() << "WHEEL @ " << pixPct << "%"; qDebug() << range.upper; - topRange -= event->delta() / 120.0 * c * pixPct; - botRange += event->delta() / 120.0 * c * (100.0 - pixPct); + topRange -= event->angleDelta().y() / 120.0 * c * pixPct; + botRange += event->angleDelta().y() / 120.0 * c * (100.0 - pixPct); if (topRange > (double)20) topRange = (double)20; if (botRange < -(double)20) botRange = (double)-20; @@ -289,7 +289,7 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double c = (window) / (double)200; QCPRange range = axes->xAxis->range(); - double pixPct = (double)100 * ((double)axes->xAxis->pixelToCoord(event->x()) - range.lower); + double pixPct = (double)100 * ((double)axes->xAxis->pixelToCoord(event->position().x()) - range.lower); pixPct /= isProperlyPaused ? (double)(range.upper - range.lower) : (double)(window); @@ -301,7 +301,7 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, pixPct = 100; qDebug() << "WHEEL @ " << pixPct << "%"; - qDebug() << event->delta(); + qDebug() << event->angleDelta().x(); if (! isProperlyPaused) { @@ -312,8 +312,8 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, qDebug() << c * ((double)100 - (double)pixPct) * pixPct / 100; } - window -= event->delta() / 120.0 * c * pixPct; - delay += event->delta() / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; + window -= event->angleDelta().x() / 120.0 * c * pixPct; + delay += event->angleDelta().x() / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; // NOTE: delayUpdated and timeWindowUpdated are called more than once beyond here, // maybe they should only be called once at the end? From 6f9b06216521222e4c7e66d43a05804729e983b4 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 11:34:32 +0200 Subject: [PATCH 027/109] fix using up/down keys in the spinboxes --- Desktop_Interface/mainwindow.cpp | 38 +++++++++++++++++++++++++------- Desktop_Interface/mainwindow.h | 7 +++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index a7059917..d6461f44 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -991,10 +991,6 @@ void MainWindow::initShortcuts(){ shortcut_snapshot_CH1 = new QShortcut(QKeySequence("c"), this); shortcut_snapshot_CH2 = new QShortcut(QKeySequence("v"), this); - shortcut_ArrowUp = new QShortcut(QKeySequence("Up"), ui->menuBar); - shortcut_ArrowDown = new QShortcut(QKeySequence("Down"), ui->menuBar); - shortcut_CtrlArrowUp = new QShortcut(QKeySequence("Ctrl+Up"), ui->menuBar); - shortcut_CtrlArrowDown = new QShortcut(QKeySequence("Ctrl+Down"), ui->menuBar); shortcut_w = new QShortcut(QKeySequence("w"), ui->menuBar); shortcut_s = new QShortcut(QKeySequence("s"), ui->menuBar); shortcut_ctrlW = new QShortcut(QKeySequence("Ctrl+w"), ui->menuBar); @@ -1020,10 +1016,6 @@ void MainWindow::initShortcuts(){ connect(shortcut_snapshot_CH1, SIGNAL(activated()), this, SLOT(on_actionSnapshot_CH1_triggered())); connect(shortcut_snapshot_CH2, SIGNAL(activated()), this, SLOT(on_actionSnapshot_CH2_triggered())); - connect(shortcut_ArrowUp, SIGNAL(activated()), this, SLOT(arrowUpTriggered())); - connect(shortcut_ArrowDown, SIGNAL(activated()), this, SLOT(arrowDownTriggered())); - connect(shortcut_CtrlArrowUp, SIGNAL(activated()), this, SLOT(ctrlArrowUpTriggered())); - connect(shortcut_CtrlArrowDown, SIGNAL(activated()), this, SLOT(ctrlArrowDownTriggered())); connect(shortcut_w, SIGNAL(activated()), this, SLOT(arrowUpTriggered())); connect(shortcut_s, SIGNAL(activated()), this, SLOT(arrowDownTriggered())); connect(shortcut_ctrlW, SIGNAL(activated()), this, SLOT(ctrlArrowUpTriggered())); @@ -2524,3 +2516,33 @@ void MainWindow::on_actionHide_Widget_LogicAnalyzer_triggered(bool checked) ui->busSnifferGroup_CH2->setVisible(!checked); ui->digitalOutputGroup->setVisible(!checked); } + +void MainWindow::keyPressEvent(QKeyEvent *event) +{ + if(!(ui->scopeAxes->underMouse())) { + QMainWindow::keyPressEvent(event); + return; + } + switch(event->key()) { + case Qt::Key_Down: + if (event->modifiers() & Qt::ControlModifier) { + ctrlArrowDownTriggered(); + } else { + arrowDownTriggered(); + } + break; + case Qt::Key_Up: + if (event->modifiers() & Qt::ControlModifier) { + ctrlArrowUpTriggered(); + } else { + arrowUpTriggered(); + } + break; + default: + QMainWindow::keyPressEvent(event); + return; + } + + event->setAccepted(true); + +} diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 6e35886f..e7758a01 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -227,6 +227,9 @@ private slots: void on_actionHide_Widget_LogicAnalyzer_triggered(bool checked); +protected: + void keyPressEvent(QKeyEvent *event); + private: //Generic Vars Ui::MainWindow *ui; @@ -271,10 +274,6 @@ private slots: QShortcut *shortcut_cycleBaudRateBackwards_CH1; QShortcut *shortcut_cycleBaudRate_CH2; QShortcut *shortcut_cycleBaudRateBackwards_CH2; - QShortcut *shortcut_ArrowUp; - QShortcut *shortcut_ArrowDown; - QShortcut *shortcut_CtrlArrowUp; - QShortcut *shortcut_CtrlArrowDown; QShortcut *shortcut_w; QShortcut *shortcut_ctrlW; QShortcut *shortcut_s; From 3c203325a2e36e5794b643b9b9b1d8a066e96d52 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 11:34:57 +0200 Subject: [PATCH 028/109] port away from deprecated APIs, stop leaking memory --- Desktop_Interface/mainwindow.cpp | 36 ++++++++++++++++++++++++-------- Desktop_Interface/mainwindow.h | 1 - 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index d6461f44..bed443ea 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1053,22 +1053,40 @@ void MainWindow::on_actionForce_Square_triggered(bool checked) this->resize(tempWidth, tempHeight); } +// In Qt 6 they have gone insane +static QWheelEvent createWheelEvent(const bool negative, const QPoint &point, const Qt::KeyboardModifier modifier = Qt::NoModifier) +{ + return QWheelEvent(point, // pos + QCursor::pos(), // globalpos + QPoint(0, negative ? -120 : 120), // pixelDelta + QPoint(0, negative ? -10 : 10), // angleDelta + Qt::NoButton, // buttons + modifier, // keyboard modifiers + Qt::NoScrollPhase, // scroll phase + false // inverted + ); +} + void MainWindow::arrowUpTriggered(){ qDebug() << "Boy UP!"; if(!(ui->scopeAxes->underMouse())) return; QPoint point = ui->scopeAxes->mapFromGlobal(QCursor::pos()); - wheelEmu = new QWheelEvent(point, 120, 0, 0, Qt::Vertical); - ui->controller_iso->setVoltageRange(wheelEmu); + + QWheelEvent wheelEmu = createWheelEvent(false, point); + qApp->sendEvent(ui->scopeAxes, &wheelEmu); } void MainWindow::arrowDownTriggered(){ qDebug() << "Boy DOWN!"; - if(!(ui->scopeAxes->underMouse())) return; + if(!(ui->scopeAxes->underMouse())){ + qDebug() << "Not under mouse"; + return; + } QPoint point = ui->scopeAxes->mapFromGlobal(QCursor::pos()); - wheelEmu = new QWheelEvent(point, -120, 0, 0, Qt::Vertical); - ui->controller_iso->setVoltageRange(wheelEmu); + QWheelEvent wheelEmu = createWheelEvent(true, point); + qApp->sendEvent(ui->scopeAxes, &wheelEmu); } void MainWindow::ctrlArrowUpTriggered(){ @@ -1076,8 +1094,8 @@ void MainWindow::ctrlArrowUpTriggered(){ if(!(ui->scopeAxes->underMouse())) return; QPoint point = ui->scopeAxes->mapFromGlobal(QCursor::pos()); - wheelEmu = new QWheelEvent(point, 120, 0, Qt::ControlModifier, Qt::Vertical); - ui->controller_iso->setVoltageRange(wheelEmu); + QWheelEvent wheelEmu = createWheelEvent(false, point, Qt::ControlModifier); + qApp->sendEvent(ui->scopeAxes, &wheelEmu); } void MainWindow::ctrlArrowDownTriggered(){ @@ -1085,8 +1103,8 @@ void MainWindow::ctrlArrowDownTriggered(){ if(!(ui->scopeAxes->underMouse())) return; QPoint point = ui->scopeAxes->mapFromGlobal(QCursor::pos()); - wheelEmu = new QWheelEvent(point, -120, 0, Qt::ControlModifier, Qt::Vertical); - ui->controller_iso->setVoltageRange(wheelEmu); + QWheelEvent wheelEmu = createWheelEvent(true, point, Qt::ControlModifier); + qApp->sendEvent(ui->scopeAxes, &wheelEmu); } void MainWindow::cycleDelayRight(){ diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index e7758a01..9474f571 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -233,7 +233,6 @@ private slots: private: //Generic Vars Ui::MainWindow *ui; - QWheelEvent *wheelEmu; bool forceSquare = false; QCPItemText *textLabel; QFile *output375_CH1, *output375_CH2, *output750; From 39f0c9f9ec93beb9ad0c03a3574dbd89c64f33e6 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 15 Apr 2021 12:48:25 +0200 Subject: [PATCH 029/109] add button to automatically set ranges for what is on the screen --- Desktop_Interface/isodriver.cpp | 1 + Desktop_Interface/isodriver.h | 5 +++++ Desktop_Interface/mainwindow.cpp | 15 +++++++++++++++ Desktop_Interface/mainwindow.h | 2 ++ Desktop_Interface/scoperangeenterdialog.cpp | 8 ++++++++ Desktop_Interface/scoperangeenterdialog.h | 2 ++ 6 files changed, 33 insertions(+) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 178748dc..d86090ac 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -242,6 +242,7 @@ void isoDriver::clearBuffers(bool ch3751, bool ch3752, bool ch750){ if(ch3751) internalBuffer375_CH1->clearBuffer(); if(ch3752) internalBuffer375_CH2->clearBuffer(); if(ch750) internalBuffer750->clearBuffer(); + total_read = 0; } void isoDriver::setVisible_CH2(bool visible){ diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index cabb288e..277f93eb 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -88,6 +88,11 @@ class isoDriver : public QLabel //DAQ bool fileModeEnabled = false; double daq_maxWindowSize; + + double vMax() const { return currentVmax; } + double vMin() const { return currentVmin; } + long numSamples() const { return total_read; } + private: //Those bloody bools that just Enable/Disable a single property bool paused_CH1 = false; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index bed443ea..10b0c783 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -2410,6 +2410,7 @@ void MainWindow::on_actionShow_Range_Dialog_on_Main_Page_triggered(bool checked) connect(scopeRangeSwitch, SIGNAL(yBotUpdated(double)), ui->controller_iso, SLOT(setBotRange(double))); connect(scopeRangeSwitch, SIGNAL(windowUpdated(double)), ui->controller_iso, SLOT(setTimeWindow(double))); connect(scopeRangeSwitch, SIGNAL(delayUpdated(double)), ui->controller_iso, SLOT(setDelay(double))); + connect(scopeRangeSwitch, &scopeRangeEnterDialog::autoClicked, this, &MainWindow::on_setAutoScopeRange); connect(ui->controller_iso, SIGNAL(topRangeUpdated(double)), scopeRangeSwitch, SLOT(yTopChanged(double))); connect(ui->controller_iso, SIGNAL(botRangeUpdated(double)), scopeRangeSwitch, SLOT(yBotChanged(double))); @@ -2535,6 +2536,20 @@ void MainWindow::on_actionHide_Widget_LogicAnalyzer_triggered(bool checked) ui->digitalOutputGroup->setVisible(!checked); } +void MainWindow::on_setAutoScopeRange() +{ + if (ui->controller_iso->numSamples() <= 0) { + return; + } + + const double max = ui->controller_iso->vMax(); + const double min = ui->controller_iso->vMin(); + ui->controller_iso->setTopRange(max + max * 0.1); // 10% extra above + ui->controller_iso->setBotRange(min - min * 0.1); // 10% extra below + + +} + void MainWindow::keyPressEvent(QKeyEvent *event) { if(!(ui->scopeAxes->underMouse())) { diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 9474f571..3638669e 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -227,6 +227,8 @@ private slots: void on_actionHide_Widget_LogicAnalyzer_triggered(bool checked); + void on_setAutoScopeRange(); + protected: void keyPressEvent(QKeyEvent *event); diff --git a/Desktop_Interface/scoperangeenterdialog.cpp b/Desktop_Interface/scoperangeenterdialog.cpp index c6cd00c6..08f37720 100644 --- a/Desktop_Interface/scoperangeenterdialog.cpp +++ b/Desktop_Interface/scoperangeenterdialog.cpp @@ -1,5 +1,6 @@ #include "scoperangeenterdialog.h" #include "ui_scoperangeenterdialog.h" +#include scopeRangeEnterDialog::scopeRangeEnterDialog(QWidget *parent, bool buttonVisible, double yTop, double yBot, double window, double delay) : QDialog(parent), @@ -15,6 +16,13 @@ scopeRangeEnterDialog::scopeRangeEnterDialog(QWidget *parent, bool buttonVisible ui->timeWindowBox->setValue(window); ui->buttonBox->setVisible(buttonVisible); + if (!buttonVisible) { + QPushButton *autoButton = new QPushButton(tr("&Auto")); + ui->verticalLayout->addWidget(autoButton); + + connect(autoButton, &QPushButton::clicked, this, &scopeRangeEnterDialog::autoClicked); + } + for (espoSpinBox* spinBox : {ui->vMaxBox, ui->vMinBox, ui->timeWindowBox, ui->delayBox}) { spinBox->changeStepping(spinBox->value()); diff --git a/Desktop_Interface/scoperangeenterdialog.h b/Desktop_Interface/scoperangeenterdialog.h index c6c20cdb..e4f13aae 100644 --- a/Desktop_Interface/scoperangeenterdialog.h +++ b/Desktop_Interface/scoperangeenterdialog.h @@ -32,6 +32,8 @@ class scopeRangeEnterDialog : public QDialog void yBotUpdated(double val); void windowUpdated(double val); void delayUpdated(double val); + void autoClicked(); + private slots: void toUpdateYTop(double val); void toUpdateYBot(double val); From a66ad5f8dded6dd23bcb79eca89aaf7736741d09 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 11:18:43 +0200 Subject: [PATCH 030/109] mis-groked the code --- Desktop_Interface/isodriver.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index 277f93eb..ce83ad75 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -89,8 +89,8 @@ class isoDriver : public QLabel bool fileModeEnabled = false; double daq_maxWindowSize; - double vMax() const { return currentVmax; } - double vMin() const { return currentVmin; } + double vMax() const { return qMax(xmax, ymax); } + double vMin() const { return qMin(xmin, ymin); } long numSamples() const { return total_read; } private: From 403a98943bcf1aa3a25c2aec65aa2a05ad2ab9bc Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 11:20:52 +0200 Subject: [PATCH 031/109] remove some dead code --- Desktop_Interface/isobuffer.cpp | 2 -- Desktop_Interface/isodriver.cpp | 17 ----------------- 2 files changed, 19 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 39267f50..0e862f9b 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -23,7 +23,6 @@ constexpr auto fX0Comp = std::less {}; constexpr auto fX1X2Comp = std::greater {}; #endif -constexpr auto kTopMultimeter = 2048; constexpr double kTriggerSensitivityMultiplier = 4; } @@ -249,7 +248,6 @@ void isoBuffer::disableFileIO() m_fileIOEnabled = false; m_currentColumn = 0; m_currentFile->close(); - return; } diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index d86090ac..e784b7b3 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -634,23 +634,6 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) } } - if(!paused_CH1) - { - int offset = -2; //No trigger! - - int backLength = length/750; - backLength *= (CH1_mode == -1) ? VALID_DATA_PER_750 : VALID_DATA_PER_375; - - if(offset>0){ - int temp_offset = offset % 750; - offset /= 750; - offset *= (CH1_mode == -1) ? VALID_DATA_PER_750 : VALID_DATA_PER_375; - offset += temp_offset; - } - - //qDebug() << "Now offset = " << offset; - } - double triggerDelay = 0; if (triggerEnabled) { From d4dc2ee2f01751299297b31478f8e1d6b340feec Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 11:21:08 +0200 Subject: [PATCH 032/109] minor issues --- Desktop_Interface/functiongencontrol.cpp | 2 +- Desktop_Interface/isobuffer.cpp | 8 ++++---- Desktop_Interface/isodriver.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Desktop_Interface/functiongencontrol.cpp b/Desktop_Interface/functiongencontrol.cpp index e8716654..53f9b96a 100644 --- a/Desktop_Interface/functiongencontrol.cpp +++ b/Desktop_Interface/functiongencontrol.cpp @@ -223,5 +223,5 @@ void DualChannelController::offsetUpdate_CH2(double newOffset) offsetUpdate(ChannelID::CH2, newOffset); } -} +} // namespace functionGen diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 0e862f9b..b5a6f522 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -16,11 +16,11 @@ constexpr char const* fileHeaderFormat = constexpr auto kSamplesSeekingCap = 20; #ifdef INVERT_MM -constexpr auto fX0Comp = std::greater {}; -constexpr auto fX1X2Comp = std::less {}; +constexpr auto fX0Comp = std::greater<> {}; +constexpr auto fX1X2Comp = std::less<> {}; #else -constexpr auto fX0Comp = std::less {}; -constexpr auto fX1X2Comp = std::greater {}; +constexpr auto fX0Comp = std::less<> {}; +constexpr auto fX1X2Comp = std::greater<> {}; #endif constexpr double kTriggerSensitivityMultiplier = 4; diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index e784b7b3..b312fe86 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -692,7 +692,7 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) } - for (double i=0; i0) { CH1[i] = 0; @@ -771,7 +771,7 @@ void isoDriver::multimeterAction(){ QVector x(GRAPH_SAMPLES), CH1(GRAPH_SAMPLES); analogConvert(readData375_CH1.get(), &CH1, 2048, false, 1); //No AC coupling! - for (double i=0; i0) { CH1[i] = 0; From 526fdfb7c4659bfcc7bb430b340fe898c2dfbc57 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 11:35:02 +0200 Subject: [PATCH 033/109] replace magic numbers with enum --- Desktop_Interface/isodriver.cpp | 46 ++++++++++++++++----------------- Desktop_Interface/isodriver.h | 11 +++++++- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index b312fe86..979eb7ea 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -75,7 +75,7 @@ void isoDriver::timerTick(void){ if (deviceMode_prev != 0 && deviceMode_prev != 1 && deviceMode_prev != 2) clearBuffers(true, false, false); - frameActionGeneric(1,0); + frameActionGeneric(ChannelMode::Analog, ChannelMode::Off); break; case 1: if (deviceMode_prev != 0 && deviceMode_prev != 1 && deviceMode_prev != 2) @@ -85,7 +85,7 @@ void isoDriver::timerTick(void){ clearBuffers(false, true, false); internalBuffer375_CH2->m_channel = 1; - frameActionGeneric(1,2); + frameActionGeneric(ChannelMode::Analog, ChannelMode::Digital); if(serialDecodeEnabled_CH1 && serialType == 0){ internalBuffer375_CH2->serialManage(baudRate_CH1, parity_CH1, hexDisplay_CH1); } @@ -96,13 +96,13 @@ void isoDriver::timerTick(void){ if (deviceMode_prev != 2) clearBuffers(false, true, false); - frameActionGeneric(1,1); + frameActionGeneric(ChannelMode::Analog, ChannelMode::Analog); break; case 3: if (deviceMode_prev != 3 && deviceMode_prev != 4) clearBuffers(true, false, false); - frameActionGeneric(2,0); + frameActionGeneric(ChannelMode::Digital, ChannelMode::Off); if(serialDecodeEnabled_CH1 && serialType == 0){ internalBuffer375_CH1->serialManage(baudRate_CH1, parity_CH1, hexDisplay_CH1); } @@ -114,7 +114,7 @@ void isoDriver::timerTick(void){ clearBuffers(false, true, false); internalBuffer375_CH2->m_channel = 2; - frameActionGeneric(2,2); + frameActionGeneric(ChannelMode::Digital, ChannelMode::Digital); if(serialDecodeEnabled_CH1 && serialType == 0){ internalBuffer375_CH1->serialManage(baudRate_CH1, parity_CH1, hexDisplay_CH1); } @@ -143,7 +143,7 @@ void isoDriver::timerTick(void){ case 6: if (deviceMode_prev != 6) clearBuffers(false, false, true); - frameActionGeneric(-1,0); + frameActionGeneric(ChannelMode::Analog750, ChannelMode::Off); break; case 7: if (deviceMode_prev != 7) @@ -613,22 +613,22 @@ void isoDriver::setTriggerMode(int newMode) } //0 for off, 1 for ana, 2 for dig, -1 for ana750, -2 for file -void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) +void isoDriver::frameActionGeneric(const ChannelMode CH1_mode, const ChannelMode CH2_mode) { //qDebug() << "made it to frameActionGeneric"; - if(!paused_CH1 && CH1_mode == - 1){ + if(!paused_CH1 && CH1_mode == ChannelMode::Analog750) { for (unsigned int i=0;i<(length/ADC_SPF);i++){ internalBuffer750->writeBuffer_char(&isoTemp[ADC_SPF*i], VALID_DATA_PER_750); } } - if(!paused_CH1 && CH1_mode > 0){ + if(!paused_CH1 && (CH1_mode == ChannelMode::Analog || CH1_mode == ChannelMode::Digital)){ for (unsigned int i=0;i<(length/ADC_SPF);i++){ internalBuffer375_CH1->writeBuffer_char(&isoTemp[ADC_SPF*i], VALID_DATA_PER_375); } } - if(!paused_CH2 && CH2_mode > 0){ + if(!paused_CH2 && (CH2_mode == ChannelMode::Analog || CH2_mode == ChannelMode::Digital)){ for (unsigned int i=0;i<(length/ADC_SPF);i++){ internalBuffer375_CH2->writeBuffer_char(&isoTemp[ADC_SPF*i+ADC_SPF/2], VALID_DATA_PER_375); //+375 to get the second half of the packet } @@ -637,7 +637,7 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) double triggerDelay = 0; if (triggerEnabled) { - isoBuffer* internalBuffer_CH1 = (CH1_mode == -1) ? internalBuffer750 : internalBuffer375_CH1; + isoBuffer* internalBuffer_CH1 = (CH1_mode == ChannelMode::Analog750) ? internalBuffer750 : internalBuffer375_CH1; triggerDelay = (triggerMode < 2) ? internalBuffer_CH1->getDelayedTriggerPoint(display.window) - display.window : internalBuffer375_CH2->getDelayedTriggerPoint(display.window) - display.window; if (triggerDelay < 0) @@ -647,14 +647,14 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) if(singleShotEnabled && (triggerDelay != 0)) singleShotTriggered(true); - readData375_CH1 = internalBuffer375_CH1->readBuffer(display.window,GRAPH_SAMPLES,CH1_mode==2, display.delay + triggerDelay); - if(CH2_mode) readData375_CH2 = internalBuffer375_CH2->readBuffer(display.window,GRAPH_SAMPLES,CH2_mode==2, display.delay + triggerDelay); - if(CH1_mode == -1) readData750 = internalBuffer750->readBuffer(display.window,GRAPH_SAMPLES,false, display.delay + triggerDelay); - if(CH1_mode == -2) readDataFile = internalBufferFile->readBuffer(display.window,GRAPH_SAMPLES,false, display.delay); + readData375_CH1 = internalBuffer375_CH1->readBuffer(display.window,GRAPH_SAMPLES,CH1_mode==ChannelMode::Digital, display.delay + triggerDelay); + if(CH2_mode != ChannelMode::Off) readData375_CH2 = internalBuffer375_CH2->readBuffer(display.window,GRAPH_SAMPLES, CH2_mode==ChannelMode::Digital, display.delay + triggerDelay); + if(CH1_mode == ChannelMode::Analog750) readData750 = internalBuffer750->readBuffer(display.window,GRAPH_SAMPLES,false, display.delay + triggerDelay); + if(CH1_mode == ChannelMode::File) readDataFile = internalBufferFile->readBuffer(display.window,GRAPH_SAMPLES,false, display.delay); QVector x(GRAPH_SAMPLES), CH1(GRAPH_SAMPLES), CH2(GRAPH_SAMPLES); - if (CH1_mode == 1){ + if (CH1_mode == ChannelMode::Analog){ analogConvert(readData375_CH1.get(), &CH1, 128, AC_CH1, 1); for (int i=0; i < GRAPH_SAMPLES; i++) { @@ -665,9 +665,9 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) xmax = (currentVmax > xmax) ? currentVmax : xmax; broadcastStats(false); } - if (CH1_mode == 2) digitalConvert(readData375_CH1.get(), &CH1); + if (CH1_mode == ChannelMode::Digital) digitalConvert(readData375_CH1.get(), &CH1); - if (CH2_mode == 1){ + if (CH2_mode == ChannelMode::Analog){ analogConvert(readData375_CH2.get(), &CH2, 128, AC_CH2, 2); for (int i=0; i < GRAPH_SAMPLES; i++) { @@ -678,16 +678,16 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) ymax = (currentVmax > ymax) ? currentVmax : ymax; broadcastStats(true); } - if (CH2_mode == 2) digitalConvert(readData375_CH2.get(), &CH2); + if (CH2_mode == ChannelMode::Digital) digitalConvert(readData375_CH2.get(), &CH2); - if(CH1_mode == -1) { + if(CH1_mode == ChannelMode::Analog750) { analogConvert(readData750.get(), &CH1, 128, AC_CH1, 1); xmin = (currentVmin < xmin) ? currentVmin : xmin; xmax = (currentVmax > xmax) ? currentVmax : xmax; broadcastStats(false); } - if(CH1_mode == -2) { + if(CH1_mode == ChannelMode::File) { fileStreamConvert(readDataFile, &CH1); } @@ -709,7 +709,7 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) axes->yAxis->setRange(ymin, ymax); }else{ axes->graph(0)->setData(x,CH1); - if(CH2_mode) axes->graph(1)->setData(x,CH2); + if(CH2_mode != ChannelMode::Off) axes->graph(1)->setData(x,CH2); axes->xAxis->setRange(-display.window - display.delay, -display.delay); axes->yAxis->setRange(display.topRange, display.botRange); } @@ -1368,7 +1368,7 @@ void isoDriver::daqLoad_endChanged(double newEnd){ void isoDriver::fileTimerTick(){ //qDebug() << "isoDriver::fileTimerTick()"; - frameActionGeneric(-2,0); + frameActionGeneric(ChannelMode::File, ChannelMode::Off); } void isoDriver::enableFileMode(){ diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index ce83ad75..29cc5d0e 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -52,6 +52,15 @@ class isoDriver : public QLabel { Q_OBJECT public: + enum class ChannelMode { + Off = 0, + Analog = 1, + Digital = 2, + Analog750 = -1, + File = -2 + }; + Q_ENUM(ChannelMode); + explicit isoDriver(QWidget *parent = 0); void autoGain(void); //Generic Vars @@ -137,7 +146,7 @@ class isoDriver : public QLabel short reverseFrontEnd(double voltage); void multimeterAction(); void broadcastStats(bool CH2); - void frameActionGeneric(char CH1_mode, char CH2_mode); + void frameActionGeneric(const ChannelMode CH1_mode, const ChannelMode CH2_mode); void triggerStateChanged(); //Variables that are just pointers to other classes/vars QCustomPlot *axes; // TODO: move into DisplayControl From f0f5fc89f8561b61c67140b227f5111e172de947 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 11:54:27 +0200 Subject: [PATCH 034/109] fix some direct memory leak (should ideally be rewritten away from raw memory buffers) --- Desktop_Interface/genericusbdriver.cpp | 2 ++ Desktop_Interface/isodriver.cpp | 15 +++++++++++++++ Desktop_Interface/isodriver.h | 2 ++ Desktop_Interface/mainwindow.cpp | 8 ++++---- .../ui_elements/qcp1/qcustomplot.cpp | 4 ++-- Desktop_Interface/unixusbdriver.cpp | 4 ++++ 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 520b6132..66537f43 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -105,6 +105,8 @@ genericUsbDriver::~genericUsbDriver(void){ } } qDebug() << "genericUsbDriver dectructor completed"; + free(outBuffers[0]); + free(outBuffers[1]); } diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 979eb7ea..2901b3e5 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -35,6 +35,21 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) connect(slowTimer, SIGNAL(timeout()), this, SLOT(slowTimerTick())); } +isoDriver::~isoDriver() +{ + free(isoTemp); + + delete v0; + delete v1; + delete dv; + + delete t0; + delete t1; + delete dt; + + delete f; +} + void isoDriver::setDriver(genericUsbDriver *newDriver){ driver = newDriver; qDebug() << "driver = " << driver; diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index 29cc5d0e..c2ca6d55 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -62,6 +62,8 @@ class isoDriver : public QLabel Q_ENUM(ChannelMode); explicit isoDriver(QWidget *parent = 0); + ~isoDriver(); + void autoGain(void); //Generic Vars isoBuffer *internalBuffer375_CH1; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 10b0c783..9c28ba35 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -297,15 +297,15 @@ void MainWindow::initialisePlot() ui->scopeAxes->yAxis->setTicker(yTicker); #endif - QPen *dashPen = new QPen(Qt::white, 2); - dashPen->setStyle(Qt::DashLine); + QPen dashPen(Qt::white, 2); + dashPen.setStyle(Qt::DashLine); ui->scopeAxes->graph(0)->setPen(QPen(Qt::yellow, 1)); ui->scopeAxes->graph(1)->setPen(QPen(Qt::cyan, 1)); ui->scopeAxes->graph(2)->setPen(QPen(Qt::white, 2)); - ui->scopeAxes->graph(3)->setPen(*(dashPen)); + ui->scopeAxes->graph(3)->setPen(dashPen); ui->scopeAxes->graph(4)->setPen(QPen(Qt::white, 2)); - ui->scopeAxes->graph(5)->setPen(*(dashPen)); + ui->scopeAxes->graph(5)->setPen(dashPen); ui->scopeAxes->xAxis->setBasePen(QPen(Qt::white, 1)); diff --git a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp index d377759c..5d4d6bc2 100644 --- a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp +++ b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp @@ -7457,12 +7457,12 @@ QCPItemAnchor::QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentIte QCPItemAnchor::~QCPItemAnchor() { // unregister as parent at children: - foreach (QCPItemPosition *child, mChildrenX.toList()) + foreach (QCPItemPosition *child, mChildrenX.values()) { if (child->parentAnchorX() == this) child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX } - foreach (QCPItemPosition *child, mChildrenY.toList()) + foreach (QCPItemPosition *child, mChildrenY.values()) { if (child->parentAnchorY() == this) child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 14446cf9..e769fe93 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -55,6 +55,10 @@ unixUsbDriver::~unixUsbDriver(void){ } //unixDriverDeleteMutex.unlock(); qDebug() << "unixUsbDriver destructor completed!\n\n"; + for (unsigned char k=0; k Date: Sun, 18 Apr 2021 12:04:42 +0200 Subject: [PATCH 035/109] fix remaining memory leaks, mostly by letting Qt handle it --- Desktop_Interface/genericusbdriver.cpp | 2 +- Desktop_Interface/genericusbdriver.h | 2 +- Desktop_Interface/isodriver.cpp | 5 ++++- Desktop_Interface/isodriver.h | 2 +- Desktop_Interface/mainwindow.cpp | 4 ++-- Desktop_Interface/ui_elements/timedtickbox.cpp | 2 +- Desktop_Interface/unixusbdriver.cpp | 4 ++-- Desktop_Interface/unixusbdriver.h | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 66537f43..aa6f546e 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -75,7 +75,7 @@ genericUsbDriver::genericUsbDriver(QWidget *parent) : QLabel(parent) qDebug() << "pipeID" << k << "=" << pipeID[k]; } - connectTimer = new QTimer(); + connectTimer = new QTimer(this); connectTimer->setTimerType(Qt::PreciseTimer); connectTimer->start(USB_RECONNECT_PERIOD); connect(connectTimer, SIGNAL(timeout()), this, SLOT(checkConnection())); diff --git a/Desktop_Interface/genericusbdriver.h b/Desktop_Interface/genericusbdriver.h index 623d8599..99c41642 100644 --- a/Desktop_Interface/genericusbdriver.h +++ b/Desktop_Interface/genericusbdriver.h @@ -71,7 +71,7 @@ class genericUsbDriver : public QLabel bool connected = false; bool calibrateOnConnect = false; //Generic Functions - explicit genericUsbDriver(QWidget *parent = 0); + explicit genericUsbDriver(QWidget *parent); ~genericUsbDriver(); virtual char *isoRead(unsigned int *newLength) = 0; //void setBufferPtr(bufferControl *newPtr); diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 2901b3e5..549fc2a8 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -29,7 +29,7 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) startTimer(); - slowTimer = new QTimer; + slowTimer = new QTimer(this); slowTimer->setTimerType(Qt::PreciseTimer); slowTimer->start(MULTIMETER_PERIOD); connect(slowTimer, SIGNAL(timeout()), this, SLOT(slowTimerTick())); @@ -51,6 +51,9 @@ isoDriver::~isoDriver() } void isoDriver::setDriver(genericUsbDriver *newDriver){ + if (driver) { + driver->deleteLater(); + } driver = newDriver; qDebug() << "driver = " << driver; } diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index c2ca6d55..c9a615c4 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -73,7 +73,7 @@ class isoDriver : public QLabel #if QCP_VER == 1 QCPItemText *cursorTextPtr; #endif - genericUsbDriver *driver; + QPointer driver; bool doNotTouchGraph = true; double ch1_ref = 1.65; double ch2_ref = 1.65; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 9c28ba35..2bd4ef25 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -51,7 +51,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->psuDisplay->display("4.50"); - ui->controller_iso->setDriver(new _PLATFORM_DEPENDENT_USB_OBJECT()); + ui->controller_iso->setDriver(new _PLATFORM_DEPENDENT_USB_OBJECT(this)); ui->controller_iso->setAxes(ui->scopeAxes); ui->timeBaseSlider->setMaximum(10*log10(MAX_WINDOW_SIZE)); @@ -1387,7 +1387,7 @@ void MainWindow::reinitUsbStage2(void){ qDebug() << "ReinitUsb entering stage 2"; delete(ui->controller_iso->driver); qDebug() << "Reinitialising USB driver!"; - ui->controller_iso->driver = new _PLATFORM_DEPENDENT_USB_OBJECT(); + ui->controller_iso->setDriver(new _PLATFORM_DEPENDENT_USB_OBJECT(this)); //Reconnect the other objects. //ui->controller_iso->driver->setBufferPtr(ui->bufferDisplay); diff --git a/Desktop_Interface/ui_elements/timedtickbox.cpp b/Desktop_Interface/ui_elements/timedtickbox.cpp index 76ad242f..f3f88b8b 100644 --- a/Desktop_Interface/ui_elements/timedtickbox.cpp +++ b/Desktop_Interface/ui_elements/timedtickbox.cpp @@ -2,7 +2,7 @@ timedTickBox::timedTickBox(QWidget *parent) : QCheckBox(parent) { - timer = new QTimer(); + timer = new QTimer(this); timer->setTimerType(Qt::PreciseTimer); timer->start(timerLength); connect(timer, SIGNAL(timeout()), this, SLOT(timerTick())); diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index e769fe93..c4209b40 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -164,7 +164,7 @@ int unixUsbDriver::usbIsoInit(void){ } } - isoTimer = new QTimer(); + isoTimer = new QTimer(this); isoTimer->setTimerType(Qt::PreciseTimer); isoTimer->start(ISO_TIMER_PERIOD); connect(isoTimer, SIGNAL(timeout()), this, SLOT(isoTimerTick())); @@ -172,7 +172,7 @@ int unixUsbDriver::usbIsoInit(void){ qDebug() << "Setup successful!"; isoHandler = new worker(); - workerThread = new QThread(); + workerThread = new QThread(this); isoHandler->ctx = ctx; isoHandler->moveToThread(workerThread); diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index 592ebe65..12934e20 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -61,7 +61,7 @@ class unixUsbDriver : public genericUsbDriver { Q_OBJECT public: - explicit unixUsbDriver(QWidget *parent = 0); + explicit unixUsbDriver(QWidget *parent); ~unixUsbDriver(); void usbSendControl(uint8_t RequestType, uint8_t Request, uint16_t Value, uint16_t Index, uint16_t Length, unsigned char *LDATA); char *isoRead(unsigned int *newLength); From 23b0b2109599fd2142791361a51a4826bf991293 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:15:18 +0200 Subject: [PATCH 036/109] fix slot signature --- Desktop_Interface/ui_files_desktop/mainwindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/ui_files_desktop/mainwindow.ui b/Desktop_Interface/ui_files_desktop/mainwindow.ui index 18075922..4b63e8f0 100644 --- a/Desktop_Interface/ui_files_desktop/mainwindow.ui +++ b/Desktop_Interface/ui_files_desktop/mainwindow.ui @@ -4052,7 +4052,7 @@ lockPsuCheckBox toggled(bool) lockPsuCheckBox - resetTimer(bool) + resetTimer() 1424 From 654a2f57abd161fd4c1aa57f9063da6bb2b652ad Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:21:25 +0200 Subject: [PATCH 037/109] remove some dead code, fixes warnings --- Desktop_Interface/isodriver.h | 1 - Desktop_Interface/mainwindow.cpp | 9 -- Desktop_Interface/mainwindow.h | 1 - .../ui_elements/buffercontrol.cpp | 8 -- Desktop_Interface/ui_elements/buffercontrol.h | 1 - .../ui_files_desktop/mainwindow.ui | 82 ------------------- 6 files changed, 102 deletions(-) diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index c9a615c4..3c6013a3 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -215,7 +215,6 @@ class isoDriver : public QLabel void sendMultimeterLabel2(QString); void sendMultimeterLabel3(QString); void sendMultimeterLabel4(QString); - void changeTimeAxis(bool positive); void sendTriggerValue(double); void sendVmax_CH1(double); void sendVmin_CH1(double); diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 2bd4ef25..54f810e8 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -54,8 +54,6 @@ MainWindow::MainWindow(QWidget *parent) : ui->controller_iso->setDriver(new _PLATFORM_DEPENDENT_USB_OBJECT(this)); ui->controller_iso->setAxes(ui->scopeAxes); - ui->timeBaseSlider->setMaximum(10*log10(MAX_WINDOW_SIZE)); - //ui->controller_iso->driver->setBufferPtr(ui->bufferDisplay); ui->cursorStatsLabel->hide(); initialisePlot(); @@ -101,7 +99,6 @@ MainWindow::MainWindow(QWidget *parent) : ui->console1->setVisible(false); ui->console2->setVisible(false); #endif - ui->timeBaseSlider->setVisible(false); //ui->pausedLabel_CH2->setVisible(0); ui->filterLabel_CH1->setVisible(false); @@ -1037,12 +1034,6 @@ void MainWindow::initShortcuts(){ } -void MainWindow::timeBaseNeedsChanging(bool positive){ - int tempVal = ui->timeBaseSlider->value(); - tempVal += positive ? 1 : -1; - ui->timeBaseSlider->setValue(tempVal); -} - void MainWindow::on_actionForce_Square_triggered(bool checked) { forceSquare = checked; diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 3638669e..65afab40 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -104,7 +104,6 @@ private slots: void cycleBaudRateBackwards_CH2(); //Deprecated/Unsupported - void timeBaseNeedsChanging(bool positive); void on_actionForce_Square_triggered(bool checked); void helloWorld(); diff --git a/Desktop_Interface/ui_elements/buffercontrol.cpp b/Desktop_Interface/ui_elements/buffercontrol.cpp index c6beb5cb..97650c1f 100644 --- a/Desktop_Interface/ui_elements/buffercontrol.cpp +++ b/Desktop_Interface/ui_elements/buffercontrol.cpp @@ -66,14 +66,6 @@ void bufferControl::scopeDsrIn(bool state){ updateMode(); } -/*void bufferControl::signalGenIn(bool state){ - signalGenState = state; - - updateBuffer(state,1); - qDebug() << "signalGenIn" << state; - updateMode(); -}*/ - void bufferControl::busSnifferIn_CH1(bool state){ busSnifferState_CH1 = state; diff --git a/Desktop_Interface/ui_elements/buffercontrol.h b/Desktop_Interface/ui_elements/buffercontrol.h index 83bd857e..d210f1a0 100644 --- a/Desktop_Interface/ui_elements/buffercontrol.h +++ b/Desktop_Interface/ui_elements/buffercontrol.h @@ -46,7 +46,6 @@ public slots: void scopeIn_CH1(bool state); void scopeIn_CH2(bool state); void scopeDsrIn(bool state); - //void signalGenIn(bool state); void busSnifferIn_CH1(bool state); void busSnifferIn_CH2(bool state); void multimeterIn(bool state); diff --git a/Desktop_Interface/ui_files_desktop/mainwindow.ui b/Desktop_Interface/ui_files_desktop/mainwindow.ui index 4b63e8f0..8df42963 100644 --- a/Desktop_Interface/ui_files_desktop/mainwindow.ui +++ b/Desktop_Interface/ui_files_desktop/mainwindow.ui @@ -81,37 +81,6 @@ - - - - -60 - - - 10 - - - 10 - - - -20 - - - -20 - - - true - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 10 - - - @@ -2664,7 +2633,6 @@ setDSR(bool) scopeIn_CH1(bool) scopeIn_CH2(bool) - signalGenIn(bool) busSnifferIn_CH1(bool) busSnifferIn_CH2(bool) multimeterIn(bool) @@ -2748,7 +2716,6 @@ multimeterREnabled(int) multimeterRMS(double) sendMultimeterLabel4(QString) - setWindow(int) setVoltageRange(QWheelEvent*) pauseEnable_CH1(bool) pauseEnable_CH2(bool) @@ -2848,22 +2815,6 @@ - - signalGenGroup_CH2 - toggled(bool) - bufferDisplay - signalGenIn(bool) - - - 1250 - 853 - - - 1356 - 891 - - - bufferDisplay busSnifferOut_CH2(bool) @@ -3328,22 +3279,6 @@ - - timeBaseSlider - valueChanged(int) - controller_iso - setWindow(int) - - - 441 - 559 - - - 1251 - 892 - - - scopeAxes mouseWheel(QWheelEvent*) @@ -3776,22 +3711,6 @@ - - controller_iso - changeTimeAxis(bool) - MainWindow - timeBaseNeedsChanging(bool) - - - 1251 - 892 - - - 800 - 696 - - - xyDisplayLabel toggled(bool) @@ -4466,7 +4385,6 @@ - timeBaseNeedsChanging(bool) reinitUsb() rSourceIndexChanged(int) multimeterStateChange(bool) From d987273de3d3cd4ccd7a327c0056b2eccc14845f Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:23:43 +0200 Subject: [PATCH 038/109] fix another couple of warnings from Qt about unused slots --- Desktop_Interface/mainwindow.cpp | 59 -------------------------------- Desktop_Interface/mainwindow.h | 2 -- 2 files changed, 61 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 54f810e8..18190cb6 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1298,65 +1298,6 @@ void MainWindow::readSettingsFile(){ } } -void MainWindow::on_actionRecord_triggered(bool checked) -{ - Q_UNUSED(checked); - /* - if(!checked){ - ui->controller_iso->internalBuffer375_CH1->disableFileIO(); - ui->controller_iso->internalBuffer375_CH2->disableFileIO(); - ui->controller_iso->internalBuffer750->disableFileIO(); - - delete(output375_CH1); - delete(output375_CH2); - delete(output750); - return; - } - QDateTime now = QDateTime::currentDateTime(); - QString dateString = now.toString("yyyyMMddhhmmsszzz"); - qDebug() << dateString; - - qDebug() << "QStandardPaths::DocumentsLocation" << QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - outputDir = new QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); - outputDir->mkdir("EspoTek"); - outputDir->cd("EspoTek"); - outputDir->mkdir("recordings"); - outputDir->cd("recordings"); - outputDir->mkdir(dateString); - outputDir->cd(dateString); - - qDebug() << outputDir->absolutePath(); - - output375_CH1 = new QFile(outputDir->filePath("375_CH1.csv")); - output375_CH2 = new QFile(outputDir->filePath("375_CH2.csv")); - output750 = new QFile(outputDir->filePath("750.csv")); - - ui->controller_iso->internalBuffer375_CH1->enableFileIO(output375_CH1); - ui->controller_iso->internalBuffer375_CH2->enableFileIO(output375_CH2); - ui->controller_iso->internalBuffer750->enableFileIO(output750); - - delete(outputDir); - return; - */ -} - -void MainWindow::on_actionTake_Snapshot_triggered() -{ - /* - QString fileName; - showFileDialog(&fileName); - qDebug() << fileName; - int len = fileName.length(); - - if(len==0) return; //User cancelled - - qDebug() << len; - fileName.remove(len-4, 4); - qDebug() << fileName; - ui->controller_iso->takeSnapshot(&fileName); - */ -} - void MainWindow::reinitUsb(void){ ui->controller_iso->doNotTouchGraph = true; ui->controller_iso->driver->saveState(&reinitdeviceMode, &reinitScopeGain, &reinitCurrentPsuVoltage, &reinitDigitalPinState); diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 65afab40..b1b3c134 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -122,8 +122,6 @@ private slots: void on_actionAuto_Lock_toggled(bool arg1); //File/other - void on_actionRecord_triggered(bool checked); - void on_actionTake_Snapshot_triggered(); void reinitUsb(void); void reinitUsbStage2(void); void resetUsbState(void); From 92453ad2c25744f30cfeafdd5081e1a17eae4d67 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:31:33 +0200 Subject: [PATCH 039/109] don't use raw pointers, fixes a race condition crash at exit --- Desktop_Interface/genericusbdriver.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.h b/Desktop_Interface/genericusbdriver.h index 99c41642..5da86a32 100644 --- a/Desktop_Interface/genericusbdriver.h +++ b/Desktop_Interface/genericusbdriver.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "functiongencontrol.h" #include "xmega.h" @@ -93,9 +94,9 @@ class genericUsbDriver : public QLabel //bufferControl *bufferPtr = NULL; QTimer *psuTimer = nullptr; unsigned char pipeID[3]; - QTimer *isoTimer = nullptr; - QTimer *connectTimer = nullptr; - QTimer *recoveryTimer; + QPointer isoTimer; + QPointer connectTimer; + QPointer recoveryTimer; unsigned char currentWriteBuffer = 0; unsigned long timerCount = 0; unsigned char inBuffer[256]; From 4eaeffc88c634602edb7fa639494230005bfc019 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:31:39 +0200 Subject: [PATCH 040/109] fix hanging on exit --- Desktop_Interface/unixusbdriver.cpp | 7 ++++--- Desktop_Interface/unixusbdriver.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index c4209b40..ddc4a3ba 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -22,13 +22,14 @@ unixUsbDriver::~unixUsbDriver(void){ if(connected){ if (workerThread) { - workerThread->deleteLater(); + workerThread->requestInterruption(); + workerThread->quit(); while(workerThread->isRunning()){ - workerThread->quit(); qDebug() << "isRunning?" << workerThread->isFinished(); QThread::msleep(100); } - } + workerThread->deleteLater(); + } if (isoHandler) delete(isoHandler); //delete(workerThread); diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index 12934e20..1c39de70 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -37,7 +37,7 @@ class worker : public QObject public slots: void handle(){ qDebug() << "SUB THREAD ID" << QThread::currentThreadId(); - while(cleanupRemaining){ + while(cleanupRemaining && !QThread::currentThread()->isInterruptionRequested()){ //qDebug() << cleanupRemaining; if(libusb_event_handling_ok(ctx)){ libusb_handle_events_timeout(ctx, &tv); From 9a5e133c31edba5c049ebfec70ddb16a70c8011c Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:35:31 +0200 Subject: [PATCH 041/109] don't busyloop --- Desktop_Interface/unixusbdriver.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index ddc4a3ba..565062ec 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -24,10 +24,7 @@ unixUsbDriver::~unixUsbDriver(void){ { workerThread->requestInterruption(); workerThread->quit(); - while(workerThread->isRunning()){ - qDebug() << "isRunning?" << workerThread->isFinished(); - QThread::msleep(100); - } + workerThread->wait(); workerThread->deleteLater(); } if (isoHandler) From facfe3d86c622e6230a23726221e6465b2594a58 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:47:41 +0200 Subject: [PATCH 042/109] fix deprecated warnings in bundled version of qcustomplot --- .../ui_elements/qcp1/qcustomplot.cpp | 70 +++++++++---------- .../ui_elements/qcp1/qcustomplot.h | 9 +-- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp index 5d4d6bc2..73e64381 100644 --- a/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp +++ b/Desktop_Interface/ui_elements/qcp1/qcustomplot.cpp @@ -7635,12 +7635,12 @@ QCPItemPosition::~QCPItemPosition() // unregister as parent at children: // Note: this is done in ~QCPItemAnchor again, but it's important QCPItemPosition does it itself, because only then // the setParentAnchor(0) call the correct QCPItemPosition::pixelPoint function instead of QCPItemAnchor::pixelPoint - foreach (QCPItemPosition *child, mChildrenX.toList()) + for (QCPItemPosition *child : mChildrenX.values()) { if (child->parentAnchorX() == this) child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX } - foreach (QCPItemPosition *child, mChildrenY.toList()) + for (QCPItemPosition *child : mChildrenY.values()) { if (child->parentAnchorY() == this) child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY @@ -9022,7 +9022,7 @@ QCustomPlot::QCustomPlot(QWidget *parent) : mAutoAddPlottableToLegend(true), mAntialiasedElements(QCP::aeNone), mNotAntialiasedElements(QCP::aeNone), - mInteractions(0), + mInteractions(QCP::iNoInteraction), mSelectionTolerance(8), mNoAntialiasingOnDrag(false), mBackgroundBrush(Qt::white, Qt::SolidPattern), @@ -10855,7 +10855,7 @@ void QCustomPlot::wheelEvent(QWheelEvent *event) emit mouseWheel(event); // call event of affected layout element: - if (QCPLayoutElement *el = layoutElementAt(event->pos())) + if (QCPLayoutElement *el = layoutElementAt(event->position())) el->wheelEvent(event); QWidget::wheelEvent(event); @@ -12580,18 +12580,18 @@ void QCPAxisRect::wheelEvent(QWheelEvent *event) if (mRangeZoom != 0) { double factor; - double wheelSteps = event->delta()/120.0; // a single step delta is +/-120 usually + QPointF wheelSteps = event->angleDelta() / 120.; // a single step delta is +/-120 usually if (mRangeZoom.testFlag(Qt::Horizontal)) { - factor = qPow(mRangeZoomFactorHorz, wheelSteps); + factor = qPow(mRangeZoomFactorHorz, wheelSteps.x()); if (mRangeZoomHorzAxis.data()) - mRangeZoomHorzAxis.data()->scaleRange(factor, mRangeZoomHorzAxis.data()->pixelToCoord(event->pos().x())); + mRangeZoomHorzAxis.data()->scaleRange(factor, mRangeZoomHorzAxis.data()->pixelToCoord(event->position().x())); } if (mRangeZoom.testFlag(Qt::Vertical)) { - factor = qPow(mRangeZoomFactorVert, wheelSteps); + factor = qPow(mRangeZoomFactorVert, wheelSteps.y()); if (mRangeZoomVertAxis.data()) - mRangeZoomVertAxis.data()->scaleRange(factor, mRangeZoomVertAxis.data()->pixelToCoord(event->pos().y())); + mRangeZoomVertAxis.data()->scaleRange(factor, mRangeZoomVertAxis.data()->pixelToCoord(event->position().y())); } mParentPlot->replot(); } @@ -14012,7 +14012,7 @@ void QCPColorScale::setRangeDrag(bool enabled) if (enabled) mAxisRect.data()->setRangeDrag(QCPAxis::orientation(mType)); else - mAxisRect.data()->setRangeDrag(0); + mAxisRect.data()->setRangeDrag(QFlags()); } /*! @@ -14032,7 +14032,7 @@ void QCPColorScale::setRangeZoom(bool enabled) if (enabled) mAxisRect.data()->setRangeZoom(QCPAxis::orientation(mType)); else - mAxisRect.data()->setRangeZoom(0); + mAxisRect.data()->setRangeZoom(QFlags()); } /*! @@ -14544,7 +14544,7 @@ void QCPGraph::setData(const QVector &key, const QVector &value) { newData.key = key[i]; newData.value = value[i]; - mData->insertMulti(newData.key, newData); + mData->insert(newData.key, newData); } } @@ -14570,7 +14570,7 @@ void QCPGraph::setDataValueError(const QVector &key, const QVectorinsertMulti(key[i], newData); + mData->insert(key[i], newData); } } @@ -14597,7 +14597,7 @@ void QCPGraph::setDataValueError(const QVector &key, const QVectorinsertMulti(key[i], newData); + mData->insert(key[i], newData); } } @@ -14623,7 +14623,7 @@ void QCPGraph::setDataKeyError(const QVector &key, const QVector newData.value = value[i]; newData.keyErrorMinus = keyError[i]; newData.keyErrorPlus = keyError[i]; - mData->insertMulti(key[i], newData); + mData->insert(key[i], newData); } } @@ -14650,7 +14650,7 @@ void QCPGraph::setDataKeyError(const QVector &key, const QVector newData.value = value[i]; newData.keyErrorMinus = keyErrorMinus[i]; newData.keyErrorPlus = keyErrorPlus[i]; - mData->insertMulti(key[i], newData); + mData->insert(key[i], newData); } } @@ -14679,7 +14679,7 @@ void QCPGraph::setDataBothError(const QVector &key, const QVectorinsertMulti(key[i], newData); + mData->insert(key[i], newData); } } @@ -14710,7 +14710,7 @@ void QCPGraph::setDataBothError(const QVector &key, const QVectorinsertMulti(key[i], newData); + mData->insert(key[i], newData); } } @@ -14871,7 +14871,7 @@ void QCPGraph::addData(const QCPDataMap &dataMap) */ void QCPGraph::addData(const QCPData &data) { - mData->insertMulti(data.key, data); + mData->insert(data.key, data); } /*! \overload @@ -14887,7 +14887,7 @@ void QCPGraph::addData(double key, double value) QCPData newData; newData.key = key; newData.value = value; - mData->insertMulti(newData.key, newData); + mData->insert(newData.key, newData); } /*! \overload @@ -14906,7 +14906,7 @@ void QCPGraph::addData(const QVector &keys, const QVector &value { newData.key = keys[i]; newData.value = values[i]; - mData->insertMulti(newData.key, newData); + mData->insert(newData.key, newData); } } @@ -16870,7 +16870,7 @@ void QCPCurve::setData(const QVector &t, const QVector &key, con newData.t = t[i]; newData.key = key[i]; newData.value = value[i]; - mData->insertMulti(newData.t, newData); + mData->insert(newData.t, newData); } } @@ -16890,7 +16890,7 @@ void QCPCurve::setData(const QVector &key, const QVector &value) newData.t = i; // no t vector given, so we assign t the index of the key/value pair newData.key = key[i]; newData.value = value[i]; - mData->insertMulti(newData.t, newData); + mData->insert(newData.t, newData); } } @@ -16933,7 +16933,7 @@ void QCPCurve::addData(const QCPCurveDataMap &dataMap) */ void QCPCurve::addData(const QCPCurveData &data) { - mData->insertMulti(data.t, data); + mData->insert(data.t, data); } /*! \overload @@ -16946,7 +16946,7 @@ void QCPCurve::addData(double t, double key, double value) newData.t = t; newData.key = key; newData.value = value; - mData->insertMulti(newData.t, newData); + mData->insert(newData.t, newData); } /*! \overload @@ -16966,7 +16966,7 @@ void QCPCurve::addData(double key, double value) newData.t = 0; newData.key = key; newData.value = value; - mData->insertMulti(newData.t, newData); + mData->insert(newData.t, newData); } /*! \overload @@ -16984,7 +16984,7 @@ void QCPCurve::addData(const QVector &ts, const QVector &keys, c newData.t = ts[i]; newData.key = keys[i]; newData.value = values[i]; - mData->insertMulti(newData.t, newData); + mData->insert(newData.t, newData); } } @@ -18575,7 +18575,7 @@ void QCPBars::setData(const QVector &key, const QVector &value) { newData.key = key[i]; newData.value = value[i]; - mData->insertMulti(newData.key, newData); + mData->insert(newData.key, newData); } } @@ -18660,7 +18660,7 @@ void QCPBars::addData(const QCPBarDataMap &dataMap) */ void QCPBars::addData(const QCPBarData &data) { - mData->insertMulti(data.key, data); + mData->insert(data.key, data); } /*! \overload @@ -18672,7 +18672,7 @@ void QCPBars::addData(double key, double value) QCPBarData newData; newData.key = key; newData.value = value; - mData->insertMulti(newData.key, newData); + mData->insert(newData.key, newData); } /*! \overload @@ -18688,7 +18688,7 @@ void QCPBars::addData(const QVector &keys, const QVector &values { newData.key = keys[i]; newData.value = values[i]; - mData->insertMulti(newData.key, newData); + mData->insert(newData.key, newData); } } @@ -20699,7 +20699,7 @@ void QCPFinancial::setData(const QVector &key, const QVector &op n = qMin(n, close.size()); for (int i=0; iinsertMulti(key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i])); + mData->insert(key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i])); } } @@ -20815,7 +20815,7 @@ void QCPFinancial::addData(const QCPFinancialDataMap &dataMap) */ void QCPFinancial::addData(const QCPFinancialData &data) { - mData->insertMulti(data.key, data); + mData->insert(data.key, data); } /*! \overload @@ -20830,7 +20830,7 @@ void QCPFinancial::addData(const QCPFinancialData &data) */ void QCPFinancial::addData(double key, double open, double high, double low, double close) { - mData->insertMulti(key, QCPFinancialData(key, open, high, low, close)); + mData->insert(key, QCPFinancialData(key, open, high, low, close)); } /*! \overload @@ -20851,7 +20851,7 @@ void QCPFinancial::addData(const QVector &key, const QVector &op n = qMin(n, close.size()); for (int i=0; iinsertMulti(key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i])); + mData->insert(key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i])); } } diff --git a/Desktop_Interface/ui_elements/qcp1/qcustomplot.h b/Desktop_Interface/ui_elements/qcp1/qcustomplot.h index 2553ff0e..70e61b35 100644 --- a/Desktop_Interface/ui_elements/qcp1/qcustomplot.h +++ b/Desktop_Interface/ui_elements/qcp1/qcustomplot.h @@ -152,7 +152,8 @@ Q_DECLARE_FLAGS(PlottingHints, PlottingHint) \see QCustomPlot::setInteractions */ -enum Interaction { iRangeDrag = 0x001 ///< 0x001 Axis ranges are draggable (see \ref QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeDragAxes) +enum Interaction { iNoInteraction = 0, + iRangeDrag = 0x001 ///< 0x001 Axis ranges are draggable (see \ref QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeDragAxes) ,iRangeZoom = 0x002 ///< 0x002 Axis ranges are zoomable with the mouse wheel (see \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeZoomAxes) ,iMultiSelect = 0x004 ///< 0x004 The user can select multiple objects by holding the modifier set by \ref QCustomPlot::setMultiSelectModifier while clicking ,iSelectPlottables = 0x008 ///< 0x008 Plottables are selectable (e.g. graphs, curves, bars,... see QCPAbstractPlottable) @@ -2579,7 +2580,7 @@ class QCP_LIB_DECL QCPGraph : public QCPAbstractPlottable protected: // property members: - QCPDataMap *mData; + QMultiMap *mData; QPen mErrorPen; LineStyle mLineStyle; QCPScatterStyle mScatterStyle; @@ -2652,7 +2653,7 @@ Q_DECLARE_TYPEINFO(QCPCurveData, Q_MOVABLE_TYPE); \see QCPCurveData, QCPCurve::setData */ -typedef QMap QCPCurveDataMap; +typedef QMultiMap QCPCurveDataMap; typedef QMapIterator QCPCurveDataMapIterator; typedef QMutableMapIterator QCPCurveDataMutableMapIterator; @@ -2815,7 +2816,7 @@ Q_DECLARE_TYPEINFO(QCPBarData, Q_MOVABLE_TYPE); This is the container in which QCPBars holds its data. \see QCPBarData, QCPBars::setData */ -typedef QMap QCPBarDataMap; +typedef QMultiMap QCPBarDataMap; typedef QMapIterator QCPBarDataMapIterator; typedef QMutableMapIterator QCPBarDataMutableMapIterator; From ad64f1fe10d00e0b39f6880ad14bc433f6508489 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:56:21 +0200 Subject: [PATCH 043/109] fix potential double free (really should port away from raw C buffers) --- Desktop_Interface/isodriver.cpp | 5 +++-- Desktop_Interface/isodriver.h | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 549fc2a8..02c639c2 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -13,7 +13,8 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) internalBuffer375_CH2 = new isoBuffer(this, MAX_WINDOW_SIZE*ADC_SPS/20*21, this, 2); internalBuffer750 = new isoBuffer(this, MAX_WINDOW_SIZE*ADC_SPS/10*21, this, 1); - isoTemp = (char *) malloc(TIMER_PERIOD*ADC_SPF + 8); //8-byte header contains (unsigned long) length + myIsoTemp = (char *) malloc(TIMER_PERIOD*ADC_SPF + 8); //8-byte header contains (unsigned long) length + isoTemp = myIsoTemp; char volts[2] = "V"; char seconds[2] = "s"; @@ -37,7 +38,7 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) isoDriver::~isoDriver() { - free(isoTemp); + free(myIsoTemp); delete v0; delete v1; diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index 3c6013a3..c4f3b460 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -156,8 +156,9 @@ class isoDriver : public QLabel std::unique_ptr readData375_CH2; std::unique_ptr readData750; float *readDataFile; - char *isoTemp = NULL; - short *isoTemp_short = NULL; + char *myIsoTemp = nullptr; + char *isoTemp = nullptr; + short *isoTemp_short = nullptr; siprint *v0; siprint *v1; siprint *dv; From 9fb61b5c6f26ee64beabc695d74fde30f40fdc4d Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 12:56:44 +0200 Subject: [PATCH 044/109] fix libusb shutdown --- Desktop_Interface/unixusbdriver.cpp | 1 + Desktop_Interface/unixusbdriver.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 565062ec..2af6354f 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -19,6 +19,7 @@ unixUsbDriver::unixUsbDriver(QWidget *parent) : genericUsbDriver(parent) unixUsbDriver::~unixUsbDriver(void){ qDebug() << "\n\nunixUsbDriver destructor ran!"; //unixDriverDeleteMutex.lock(); + shutdownMode = true; if(connected){ if (workerThread) { diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index 1c39de70..c3646147 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -37,13 +37,13 @@ class worker : public QObject public slots: void handle(){ qDebug() << "SUB THREAD ID" << QThread::currentThreadId(); - while(cleanupRemaining && !QThread::currentThread()->isInterruptionRequested()){ + while(cleanupRemaining){ //qDebug() << cleanupRemaining; if(libusb_event_handling_ok(ctx)){ libusb_handle_events_timeout(ctx, &tv); //qDebug() << "HANDLED"; } - if(stopTime){ + if(stopTime || QThread::currentThread()->isInterruptionRequested()){ if(cleanupRemaining){ cleanupRemaining--; qDebug("Cleaning... #%hhu phases remain.\n", cleanupRemaining); From 2cd9f51263a23cd40a073a0112e7ebba4f04fd98 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 18 Apr 2021 13:16:09 +0200 Subject: [PATCH 045/109] port to C++ smart pointers, avoids raw memory juggling and associated mess --- Desktop_Interface/genericusbdriver.cpp | 6 ++---- Desktop_Interface/genericusbdriver.h | 4 ++-- Desktop_Interface/isodriver.cpp | 8 +++----- Desktop_Interface/isodriver.h | 3 +-- Desktop_Interface/unixusbdriver.cpp | 6 +++--- Desktop_Interface/unixusbdriver.h | 2 +- Desktop_Interface/winusbdriver.cpp | 8 +++----- Desktop_Interface/winusbdriver.h | 2 +- 8 files changed, 16 insertions(+), 23 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index aa6f546e..3fa189a6 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -63,8 +63,8 @@ genericUsbDriver::genericUsbDriver(QWidget *parent) : QLabel(parent) this->hide(); //Double buffers are used to send the transfers to isoDriver. outBuffers and bufferLengths store the actual data from each transfer as well as length. They are read by isoDriver when it calls isoRead(). - outBuffers[0] = (unsigned char *) calloc(ISO_PACKET_SIZE*ISO_PACKETS_PER_CTX*NUM_ISO_ENDPOINTS + 8, 1); - outBuffers[1] = (unsigned char *) calloc(ISO_PACKET_SIZE*ISO_PACKETS_PER_CTX*NUM_ISO_ENDPOINTS + 8, 1); + outBuffers[0].reset((char *) calloc(ISO_PACKET_SIZE*ISO_PACKETS_PER_CTX*NUM_ISO_ENDPOINTS + 8, 1), free); + outBuffers[1].reset((char *) calloc(ISO_PACKET_SIZE*ISO_PACKETS_PER_CTX*NUM_ISO_ENDPOINTS + 8, 1), free); bufferLengths[0] = 0; bufferLengths[1] = 0; @@ -105,8 +105,6 @@ genericUsbDriver::~genericUsbDriver(void){ } } qDebug() << "genericUsbDriver dectructor completed"; - free(outBuffers[0]); - free(outBuffers[1]); } diff --git a/Desktop_Interface/genericusbdriver.h b/Desktop_Interface/genericusbdriver.h index 5da86a32..c99c67f4 100644 --- a/Desktop_Interface/genericusbdriver.h +++ b/Desktop_Interface/genericusbdriver.h @@ -67,14 +67,14 @@ class genericUsbDriver : public QLabel int dutyTemp = 21; bool killOnConnect = false; //Generic Vars - unsigned char *outBuffers[2]; + std::shared_ptr outBuffers[2]; unsigned int bufferLengths[2]; bool connected = false; bool calibrateOnConnect = false; //Generic Functions explicit genericUsbDriver(QWidget *parent); ~genericUsbDriver(); - virtual char *isoRead(unsigned int *newLength) = 0; + virtual std::shared_ptr isoRead(unsigned int *newLength) = 0; //void setBufferPtr(bufferControl *newPtr); void saveState(int *_out_deviceMode, double *_out_scopeGain, double *_out_currentPsuVoltage, int *_out_digitalPinState); virtual void usbSendControl(uint8_t RequestType, uint8_t Request, uint16_t Value, uint16_t Index, uint16_t Length, unsigned char *LDATA) = 0; diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 02c639c2..bfd60308 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -13,8 +13,8 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) internalBuffer375_CH2 = new isoBuffer(this, MAX_WINDOW_SIZE*ADC_SPS/20*21, this, 2); internalBuffer750 = new isoBuffer(this, MAX_WINDOW_SIZE*ADC_SPS/10*21, this, 1); - myIsoTemp = (char *) malloc(TIMER_PERIOD*ADC_SPF + 8); //8-byte header contains (unsigned long) length - isoTemp = myIsoTemp; + // use malloc, it gives us aligned data + isoTemp.reset((char *) malloc(TIMER_PERIOD*ADC_SPF + 8), free); //8-byte header contains (unsigned long) length char volts[2] = "V"; char seconds[2] = "s"; @@ -38,8 +38,6 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) isoDriver::~isoDriver() { - free(myIsoTemp); - delete v0; delete v1; delete dv; @@ -765,7 +763,7 @@ void isoDriver::frameActionGeneric(const ChannelMode CH1_mode, const ChannelMode } void isoDriver::multimeterAction(){ - isoTemp_short = (short *)isoTemp; + isoTemp_short = (short *)isoTemp.get(); if(!paused_multimeter){ for (unsigned int i=0;i<(length/ADC_SPF);i++){ internalBuffer375_CH1->writeBuffer_short(&isoTemp_short[ADC_SPF/2*i], ADC_SPF/2-1); //Offset because the first 8 bytes of the array contain the length (no samples!!)! diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index c4f3b460..a4233611 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -156,8 +156,7 @@ class isoDriver : public QLabel std::unique_ptr readData375_CH2; std::unique_ptr readData750; float *readDataFile; - char *myIsoTemp = nullptr; - char *isoTemp = nullptr; + std::shared_ptr isoTemp; short *isoTemp_short = nullptr; siprint *v0; siprint *v1; diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 2af6354f..6588e567 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -251,7 +251,7 @@ void unixUsbDriver::isoTimerTick(void){ }else{ srcPtr = &midBuffer_prev[k][ISO_PACKET_SIZE * (ISO_PACKETS_PER_CTX + current_offset)]; } - memcpy(&(outBuffers[currentWriteBuffer][packetLength]), srcPtr, ISO_PACKET_SIZE); + memcpy(&outBuffers[currentWriteBuffer].get()[packetLength], srcPtr, ISO_PACKET_SIZE); packetLength += ISO_PACKET_SIZE; } } @@ -288,12 +288,12 @@ void unixUsbDriver::isoTimerTick(void){ return; } -char *unixUsbDriver::isoRead(unsigned int *newLength){ +std::shared_ptr unixUsbDriver::isoRead(unsigned int *newLength){ //*(newLength) = 0; //return (char*) NULL; //qDebug() << "unixUsbDriver::isoRead"; *(newLength) = bufferLengths[!currentWriteBuffer]; - return (char*) outBuffers[(unsigned char) !currentWriteBuffer]; + return outBuffers[(unsigned char) !currentWriteBuffer]; } void unixUsbDriver::recoveryTick(void){ diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index c3646147..cbfa415f 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -64,7 +64,7 @@ class unixUsbDriver : public genericUsbDriver explicit unixUsbDriver(QWidget *parent); ~unixUsbDriver(); void usbSendControl(uint8_t RequestType, uint8_t Request, uint16_t Value, uint16_t Index, uint16_t Length, unsigned char *LDATA); - char *isoRead(unsigned int *newLength); + std::shared_ptr isoRead(unsigned int *newLength); void manualFirmwareRecovery(void); protected: //USB Vars diff --git a/Desktop_Interface/winusbdriver.cpp b/Desktop_Interface/winusbdriver.cpp index 13368b5e..b3fc4ed3 100644 --- a/Desktop_Interface/winusbdriver.cpp +++ b/Desktop_Interface/winusbdriver.cpp @@ -25,8 +25,6 @@ winUsbDriver::~winUsbDriver(void){ UsbK_AbortPipe(handle, pipeID[k]); } OvlK_Free(ovlPool); - free(outBuffers[0]); - free(outBuffers[1]); UsbK_Free(handle); } @@ -247,7 +245,7 @@ void winUsbDriver::isoTimerTick(void){ for(int i=0;iNumberOfPackets;i++){ for(unsigned char k=0; kIsoPackets[i].Offset; - memcpy(&(outBuffers[currentWriteBuffer][packetLength]), &dataBuffer[k][earliest][dataBufferOffset], isoCtx[k][earliest]->IsoPackets[i].Length); + memcpy(&(outBuffers[currentWriteBuffer].get()[packetLength]), &dataBuffer[k][earliest][dataBufferOffset], isoCtx[k][earliest]->IsoPackets[i].Length); packetLength += isoCtx[k][earliest]->IsoPackets[i].Length; } } @@ -300,10 +298,10 @@ void winUsbDriver::isoTimerTick(void){ return; } -char *winUsbDriver::isoRead(unsigned int *newLength){ +std::shared_ptr winUsbDriver::isoRead(unsigned int *newLength){ //This will be called almost immediately after the upTick() signal is sent. Make sure bufferLengths[] abd outBuffers[] are ready! *(newLength) = bufferLengths[!currentWriteBuffer]; - return (char*) outBuffers[(unsigned char) !currentWriteBuffer]; + return outBuffers[(unsigned char) !currentWriteBuffer]; } bool winUsbDriver::allEndpointsComplete(int n){ diff --git a/Desktop_Interface/winusbdriver.h b/Desktop_Interface/winusbdriver.h index 259f2f68..c57a1eac 100644 --- a/Desktop_Interface/winusbdriver.h +++ b/Desktop_Interface/winusbdriver.h @@ -20,7 +20,7 @@ class winUsbDriver : public genericUsbDriver explicit winUsbDriver(QWidget *parent = 0); ~winUsbDriver(); void usbSendControl(uint8_t RequestType, uint8_t Request, uint16_t Value, uint16_t Index, uint16_t Length, unsigned char *LDATA); - char *isoRead(unsigned int *newLength); + std::shared_ptr isoRead(unsigned int *newLength); void manualFirmwareRecovery(void); private: //USB Vars From aefa58396a900c04be1c602e4df6a7a446bc8afb Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Tue, 20 Apr 2021 10:37:07 +0200 Subject: [PATCH 046/109] fix build with qcustomplot2 --- Desktop_Interface/mainwindow.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 18190cb6..a9beab93 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -256,7 +256,11 @@ void MainWindow::initialisePlot() { QCPCurve *xyCurve = new QCPCurve(ui->scopeAxes->xAxis, ui->scopeAxes->yAxis); xyCurve->setPen(QPen(Qt::yellow, 1)); + +#if QCP_VER == 1 ui->scopeAxes->addPlottable(xyCurve); +#endif + ui->scopeAxes->addGraph(); ui->scopeAxes->addGraph(); ui->scopeAxes->addGraph(); From 446a1ca755c03b4fb3e5bfeb1f377dab8e2acf89 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Tue, 20 Apr 2021 10:37:37 +0200 Subject: [PATCH 047/109] allow building with system version of qcustomplot --- Desktop_Interface/Labrador.pro | 10 +++++++++- Desktop_Interface/ui_elements.pri | 10 ++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index 01ef1a45..e0716465 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -18,13 +18,21 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport TARGET = Labrador TEMPLATE = app +BUILTIN_QCP = true QCP_VER = 1 + DEFINES += "QCP_VER=$${QCP_VER}" equals(QCP_VER,"2"){ DEFINES += QCUSTOMPLOT_USE_OPENGL - LIBS += -lOpenGL32 + win32: LIBS += -lOpenGL32 message("Using QCP2 with OpenGL support") } +equals(BUILTIN_QCP,"true"){ + message("Using bundled version of qcustomplot") +} else { + message("Using system version of qcustomplot") + LIBS += -lqcustomplot +} include(ui_elements.pri) diff --git a/Desktop_Interface/ui_elements.pri b/Desktop_Interface/ui_elements.pri index c515cb67..ea7e2616 100644 --- a/Desktop_Interface/ui_elements.pri +++ b/Desktop_Interface/ui_elements.pri @@ -1,8 +1,12 @@ @INCLUDEPATH += $$PWD/ui_elements @DEPENDPATH += $$PWD/ui_elements -INCLUDEPATH += $$PWD/ui_elements/qcp$${QCP_VER} -DEPENDPATH += $$PWD/ui_elements/qcp$${QCP_VER} +equals(BUILTIN_QCP,"true"){ + INCLUDEPATH += $$PWD/ui_elements/qcp$${QCP_VER} + DEPENDPATH += $$PWD/ui_elements/qcp$${QCP_VER} + SOURCES += ui_elements/qcp$${QCP_VER}/qcustomplot.cpp + HEADERS += ui_elements/qcp$${QCP_VER}/qcustomplot.h +} SOURCES += ui_elements/buffercontrol.cpp \ @@ -12,7 +16,6 @@ SOURCES += ui_elements/buffercontrol.cpp \ ui_elements/esposlider.cpp \ ui_elements/espospinbox.cpp \ ui_elements/noclosemenu.cpp \ - ui_elements/qcp$${QCP_VER}/qcustomplot.cpp \ ui_elements/siprint.cpp \ ui_elements/timedtickbox.cpp \ ui_elements/voltagespinbox.cpp \ @@ -26,7 +29,6 @@ HEADERS += ui_elements/buffercontrol.h \ ui_elements/esposlider.h \ ui_elements/espospinbox.h \ ui_elements/noclosemenu.h \ - ui_elements/qcp$${QCP_VER}/qcustomplot.h \ ui_elements/siprint.h \ ui_elements/timedtickbox.h \ ui_elements/voltagespinbox.h \ From 259461851cfe4f13c7b558db800d83956eb94eef Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Tue, 20 Apr 2021 11:15:37 +0200 Subject: [PATCH 048/109] fix missing emits --- Desktop_Interface/scoperangeenterdialog.cpp | 8 ++++---- Desktop_Interface/uartstyledecoder.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Desktop_Interface/scoperangeenterdialog.cpp b/Desktop_Interface/scoperangeenterdialog.cpp index 08f37720..f9f819dc 100644 --- a/Desktop_Interface/scoperangeenterdialog.cpp +++ b/Desktop_Interface/scoperangeenterdialog.cpp @@ -41,7 +41,7 @@ void scopeRangeEnterDialog::toUpdateYTop(double val){ if (yTop != val) { yTop = val; - yTopUpdated(val); + emit yTopUpdated(val); } } @@ -51,7 +51,7 @@ void scopeRangeEnterDialog::toUpdateYBot(double val){ if (yBot != val) { yBot = val; - yBotUpdated(val); + emit yBotUpdated(val); } } @@ -63,7 +63,7 @@ void scopeRangeEnterDialog::toUpdateWindow(double val){ ui->delayBox->setMax(((double)MAX_WINDOW_SIZE) - ui->timeWindowBox->value()); qDebug() << "delayBox updating to" << ui->delayBox->maximum(); timeWindow = val; - windowUpdated(val); + emit windowUpdated(val); } } @@ -75,7 +75,7 @@ void scopeRangeEnterDialog::toUpdateDelay(double val){ ui->timeWindowBox->setMax(((double)MAX_WINDOW_SIZE) - ui->delayBox->value()); qDebug() << "timeWindowBox updating max to" << ui->timeWindowBox->maximum(); delay = val; - delayUpdated(val); + emit delayUpdated(val); } } diff --git a/Desktop_Interface/uartstyledecoder.cpp b/Desktop_Interface/uartstyledecoder.cpp index 55c884e8..22ebe252 100644 --- a/Desktop_Interface/uartstyledecoder.cpp +++ b/Desktop_Interface/uartstyledecoder.cpp @@ -82,7 +82,7 @@ void uartStyleDecoder::serialDecode() if (allZeroes) { qDebug() << "Wire Disconnect detected!"; - wireDisconnected(m_parent->m_channel); + emit wireDisconnected(m_parent->m_channel); m_parent->m_isDecoding = false; m_updateTimer.stop(); } From 7460151e0156994f6f61551b155cd44490dd9578 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Tue, 20 Apr 2021 11:16:13 +0200 Subject: [PATCH 049/109] fix unused --- Desktop_Interface/scoperangeenterdialog.cpp | 1 + Desktop_Interface/ui_elements/esposlider.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Desktop_Interface/scoperangeenterdialog.cpp b/Desktop_Interface/scoperangeenterdialog.cpp index f9f819dc..647e8be2 100644 --- a/Desktop_Interface/scoperangeenterdialog.cpp +++ b/Desktop_Interface/scoperangeenterdialog.cpp @@ -11,6 +11,7 @@ scopeRangeEnterDialog::scopeRangeEnterDialog(QWidget *parent, bool buttonVisible ui->vMaxBox->setMinimum(yBot); ui->vMinBox->setMaximum(yTop); + ui->delayBox->setValue(delay); ui->vMaxBox->setValue(yTop); ui->vMinBox->setValue(yBot); ui->timeWindowBox->setValue(window); diff --git a/Desktop_Interface/ui_elements/esposlider.cpp b/Desktop_Interface/ui_elements/esposlider.cpp index fbaa6371..87cd3cd4 100644 --- a/Desktop_Interface/ui_elements/esposlider.cpp +++ b/Desktop_Interface/ui_elements/esposlider.cpp @@ -29,12 +29,14 @@ bool espoSlider::setTickLabel(const QString& label, int position) } void espoSlider::resizeEvent(QResizeEvent *event){ + Q_UNUSED(event); this->debug_var++; //qDebug() << "move/resize event" << debug_var; rearrange(); } void espoSlider::moveEvent(QMoveEvent *event){ + Q_UNUSED(event); this->debug_var++; //qDebug() << "move/resize event" << debug_var; rearrange(); @@ -71,6 +73,8 @@ void espoSlider::rearrange(){ int c = 5; int left = this->geometry().left(); + Q_UNUSED(left); + int right = this->geometry().right(); int top = this->geometry().top(); int bottom = this->geometry().bottom(); From b3a8722d141e5191bfad969ac1ea6e65fd0d29c3 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Tue, 20 Apr 2021 11:16:30 +0200 Subject: [PATCH 050/109] fix signedness --- Desktop_Interface/uartstyledecoder.cpp | 2 +- Desktop_Interface/uartstyledecoder.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/uartstyledecoder.cpp b/Desktop_Interface/uartstyledecoder.cpp index 22ebe252..5b4464c0 100644 --- a/Desktop_Interface/uartstyledecoder.cpp +++ b/Desktop_Interface/uartstyledecoder.cpp @@ -90,7 +90,7 @@ void uartStyleDecoder::serialDecode() int uartStyleDecoder::serialDistance() const { - int back_bit = m_parent->m_back * 8; + unsigned back_bit = m_parent->m_back * 8; int bufferEnd_bit = (m_parent->m_bufferLen-1) * 8; if (back_bit >= serialPtr_bit) return back_bit - serialPtr_bit; diff --git a/Desktop_Interface/uartstyledecoder.h b/Desktop_Interface/uartstyledecoder.h index 999f16cd..bed47167 100644 --- a/Desktop_Interface/uartstyledecoder.h +++ b/Desktop_Interface/uartstyledecoder.h @@ -27,7 +27,7 @@ class uartStyleDecoder : public QObject isoBuffer *m_parent; // Indicates the current bit being decoded. - int serialPtr_bit; + unsigned serialPtr_bit; bool uartTransmitting = false; bool newUartSymbol = false; From 81570aa3fc98e83957dc15267582b316625b6143 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Tue, 20 Apr 2021 11:16:38 +0200 Subject: [PATCH 051/109] implement baudot decoding --- Desktop_Interface/uartstyledecoder.cpp | 84 +++++++++++++++++++++++++- Desktop_Interface/uartstyledecoder.h | 5 +- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/Desktop_Interface/uartstyledecoder.cpp b/Desktop_Interface/uartstyledecoder.cpp index 5b4464c0..76415aeb 100644 --- a/Desktop_Interface/uartstyledecoder.cpp +++ b/Desktop_Interface/uartstyledecoder.cpp @@ -200,7 +200,7 @@ bool uartStyleDecoder::jitterCompensationProcedure(bool current_bit) } //Basically scaffolding to add character maps for other modes (5 bit, for example). -char uartStyleDecoder::decodeDatabit(int mode, short symbol) const +char uartStyleDecoder::decodeDatabit(int mode, short symbol) { switch(mode) { @@ -216,9 +216,87 @@ char uartStyleDecoder::decodeDatabit(int mode, short symbol) const } } -char uartStyleDecoder::decodeBaudot(short symbol) const +char uartStyleDecoder::decodeBaudot(short symbol) { - return 'a'; + // Baudot-Murray code, ITA2 variant + + if (m_baudotFigures) { + switch(symbol) { + case 1: return '3'; + case 3: return '-'; + case 5: return '\''; + case 6: return '8'; + case 7: return '7'; + case 9: return 0x5; // Enquiry + case 11: return '\a'; // Bell + case 12: return ','; + case 13: return '!'; + case 14: return ':'; + case 15: return '('; + case 16: return '5'; + case 17: return '+'; + case 18: return ')'; + case 19: return '2'; + case 20: return '$'; // or £ + case 21: return '6'; + case 22: return '0'; + case 23: return '1'; + case 24: return '9'; + case 25: return '?'; + case 26: return '&'; // or @ + case 28: return '.'; + case 29: return '/'; + case 30: return ';'; + + case 27: return ' '; // Switch to figures in letter mode + case 31: m_baudotFigures = false; return -1; // Switch to letters + + default: + // Either a common one, or unknown + break; + } + } + + // Letter mode: + switch(symbol) { + case 0: return '\0'; + case 2: return '\n'; + case 8: return '\r'; + + case 1: return 'E'; + case 3: return 'A'; + case 4: return ' '; + case 5: return 'S'; + case 6: return 'I'; + case 7: return 'U'; + case 9: return 'D'; + case 10: return 'R'; + case 11: return 'J'; + case 12: return 'N'; + case 13: return 'F'; + case 14: return 'C'; + case 15: return 'K'; + case 16: return 'T'; + case 17: return 'Z'; + case 18: return 'L'; + case 19: return 'W'; + case 20: return 'H'; + case 21: return 'Y'; + case 22: return 'P'; + case 23: return 'Q'; + case 24: return 'O'; + case 25: return 'B'; + case 26: return 'G'; + case 28: return 'M'; + case 29: return 'X'; + case 30: return 'V'; + + case 27: m_baudotFigures = true; return ' '; + case 31: return 0x8; // Delete/erase, we return backspace + default: + qWarning() << "Invalid baudot:" << symbol; + return -1; + } } void uartStyleDecoder::setParityMode(UartParity newParity) diff --git a/Desktop_Interface/uartstyledecoder.h b/Desktop_Interface/uartstyledecoder.h index bed47167..084b6def 100644 --- a/Desktop_Interface/uartstyledecoder.h +++ b/Desktop_Interface/uartstyledecoder.h @@ -61,8 +61,8 @@ public slots: void setHexDisplay(bool enabled); private: - char decodeDatabit(int mode, short symbol) const; - char decodeBaudot(short symbol) const; + char decodeDatabit(int mode, short symbol); + char decodeBaudot(short symbol); std::mutex mutex; UartParity parity = UartParity::None; @@ -71,6 +71,7 @@ public slots: UartParity parityOf(uint32_t bitField) const; bool parityCheckFailed = false; + bool m_baudotFigures = false; }; From e99f00335d4feedc758e9bd63c8b43a610bdc7d3 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 22 Apr 2021 15:12:20 +0200 Subject: [PATCH 052/109] fix some crashing --- Desktop_Interface/Labrador.pro | 70 ++++++++++++++++----------------- Desktop_Interface/isobuffer.cpp | 4 ++ Desktop_Interface/isodriver.h | 2 +- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index e0716465..cbc5782c 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -130,54 +130,55 @@ win32{ unix:!android:!macx{ INCLUDEPATH += $$PWD/build_linux + LIBS += -lusb-1.0 ##make sure you have the libusb-1.0-0-dev package! + LIBS += -ldfuprog-0.9 + contains(QT_ARCH, arm) { message("Building for Raspberry Pi") - #libusb include - unix:!android:!macx:LIBS += -lusb-1.0 ##make sure you have the libusb-1.0-0-dev package! - unix:!android:!macx:INCLUDEPATH += build_linux/libusb - unix:!android:!macx:DEPENDPATH += build_linux/libusb - - #libdfuprog include - unix:!android:!macx:LIBS += -L$$PWD/build_linux/libdfuprog/lib/arm -ldfuprog-0.9 - unix:!android:!macx:INCLUDEPATH += $$PWD/build_linux/libdfuprog/include - unix:!android:!macx:DEPENDPATH += $$PWD/build_linux/libdfuprog/include + + #All ARM-Linux GCC treats char as unsigned by default??? QMAKE_CFLAGS += -fsigned-char QMAKE_CXXFLAGS += -fsigned-char DEFINES += "PLATFORM_RASPBERRY_PI" - #All ARM-Linux GCC treats char as unsigned by default??? - lib_deploy.files = $$PWD/build_linux/libdfuprog/lib/arm/libdfuprog-0.9.so - lib_deploy.path = /usr/lib + + !contains(CONFIG, "USE_SYSTEM_LIBS") { + LIBS += -L$$PWD/build_linux/libdfuprog/lib/arm + lib_deploy.files = $$PWD/build_linux/libdfuprog/lib/arm/libdfuprog-0.9.so + } } else { contains(QT_ARCH, i386) { message("Building for Linux (x86)") - unix:!android:!macx:LIBS += -lusb-1.0 ##make sure you have the libusb-1.0-0-dev package! - unix:!android:!macx:INCLUDEPATH += build_linux/libusb - unix:!android:!macx:DEPENDPATH += build_linux/libusb - - #libdfuprog include - unix:!android:!macx:LIBS += -L$$PWD/build_linux/libdfuprog/lib/x86 -ldfuprog-0.9 - unix:!android:!macx:INCLUDEPATH += $$PWD/build_linux/libdfuprog/include - unix:!android:!macx:DEPENDPATH += $$PWD/build_linux/libdfuprog/include - lib_deploy.files = $$PWD/build_linux/libdfuprog/lib/x86/libdfuprog-0.9.so - lib_deploy.path = /usr/lib + !contains(CONFIG, "USE_SYSTEM_LIBS") { + LIBS += -L$$PWD/build_linux/libdfuprog/lib/x86 + lib_deploy.files = $$PWD/build_linux/libdfuprog/lib/x86/libdfuprog-0.9.so + } } else { message("Building for Linux (x64)") - #libusb include - unix:!android:!macx:LIBS += -Lbuild_linux/libusb -lusb-1.0 ##I suspect the -L here does nothing! - unix:!android:!macx:INCLUDEPATH += build_linux/libusb - unix:!android:!macx:DEPENDPATH += build_linux/libusb - - #libdfuprog include - unix:!android:!macx:LIBS += -L$$PWD/build_linux/libdfuprog/lib/x64 -ldfuprog-0.9 - unix:!android:!macx:INCLUDEPATH += $$PWD/build_linux/libdfuprog/include - unix:!android:!macx:DEPENDPATH += $$PWD/build_linux/libdfuprog/include - lib_deploy.files = $$PWD/build_linux/libdfuprog/lib/x64/libdfuprog-0.9.so - lib_deploy.path = /usr/lib + !contains(CONFIG, "USE_SYSTEM_LIBS") { + LIBS += -Lbuild_linux/libusb ##I suspect the -L here does nothing! + #libdfuprog include + LIBS += -L$$PWD/build_linux/libdfuprog/lib/x64 + lib_deploy.files = $$PWD/build_linux/libdfuprog/lib/x64/libdfuprog-0.9.so + } } } - + + contains(CONFIG, "USE_SYSTEM_LIBS") { + INCLUDEPATH += $$[QT_INSTALL_PREFIX]/include/libusb-1.0 + } else { + INCLUDEPATH += build_linux/libusb + DEPENDPATH += build_linux/libusb + + #libdfuprog include + INCLUDEPATH += $$PWD/build_linux/libdfuprog/include + DEPENDPATH += $$PWD/build_linux/libdfuprog/include + + lib_deploy.path = /usr/lib + INSTALLS += lib_deploy + } + other.files += bin/firmware other.files += bin/waveforms other.path = /usr/bin/EspoTek-Labrador @@ -215,7 +216,6 @@ unix:!android:!macx{ } INSTALLS += target - INSTALLS += lib_deploy INSTALLS += other INSTALLS += udev INSTALLS += icon48 diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index b5a6f522..4e8ebcd6 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -121,6 +121,10 @@ std::unique_ptr isoBuffer::readBuffer(double sampleWindow, int numSampl double itr = delaySamples; for (int i = 0; i < numSamples && itr < m_insertedCount; i++) { + if (int(itr) < 0 || ((m_back-1) + m_bufferLen - itr) >= m_bufferLen * 2) { + qWarning() << "itr out of range" << itr << numSamples << m_insertedCount << i << timeBetweenSamples; + break; + } assert(int(itr) >= 0); readData[i] = bufferAt(int(itr)); diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index a4233611..4acef1a0 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -151,7 +151,7 @@ class isoDriver : public QLabel void frameActionGeneric(const ChannelMode CH1_mode, const ChannelMode CH2_mode); void triggerStateChanged(); //Variables that are just pointers to other classes/vars - QCustomPlot *axes; // TODO: move into DisplayControl + QPointer axes; // TODO: move into DisplayControl std::unique_ptr readData375_CH1; std::unique_ptr readData375_CH2; std::unique_ptr readData750; From ec4c5e927efb1555a947428fffb977f7cf95af1b Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Thu, 22 Apr 2021 17:19:44 +0200 Subject: [PATCH 053/109] port away from deprecated libusb API --- Desktop_Interface/unixusbdriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 6588e567..f51ef00b 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -72,7 +72,7 @@ unsigned char unixUsbDriver::usbInit(unsigned long VIDin, unsigned long PIDin){ return 1; } else qDebug() << "Libusb context initialised"; - libusb_set_debug(ctx, 3); + libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); } if(handle == NULL){ From 3f5604bb325b0f2f1da5d759c339e6c6f1dd4cc1 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 11:49:54 +0200 Subject: [PATCH 054/109] more usage of enums and flags to make code understandable --- Desktop_Interface/isodriver.cpp | 93 +++++++++++++++++++++------------ Desktop_Interface/isodriver.h | 23 +++++++- 2 files changed, 81 insertions(+), 35 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index bfd60308..cd671614 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -88,18 +88,21 @@ void isoDriver::timerTick(void){ // TODO: Do we need to invalidate state when the device is reconnected? bool invalidateTwoWireState = true; switch(driver->deviceMode){ - case 0: - if (deviceMode_prev != 0 && deviceMode_prev != 1 && deviceMode_prev != 2) - clearBuffers(true, false, false); + case DeviceCH1Analog: + if (deviceMode_prev != DeviceCH1Analog && deviceMode_prev != DeviceCH1AnalogCH2Digital && deviceMode_prev != DeviceCH1AnalogCH2Analog) { + clearBuffers(Channel3751); + } frameActionGeneric(ChannelMode::Analog, ChannelMode::Off); break; - case 1: - if (deviceMode_prev != 0 && deviceMode_prev != 1 && deviceMode_prev != 2) - clearBuffers(true, false, false); + case DeviceCH1AnalogCH2Digital: + if (deviceMode_prev != DeviceCH1Analog && deviceMode_prev != 1 && deviceMode_prev != DeviceCH1AnalogCH2Analog) { + clearBuffers(Channel3751); + } - if (deviceMode_prev != 1) - clearBuffers(false, true, false); + if (deviceMode_prev != DeviceCH1AnalogCH2Digital) { + clearBuffers(Channel3752); + } internalBuffer375_CH2->m_channel = 1; frameActionGeneric(ChannelMode::Analog, ChannelMode::Digital); @@ -107,28 +110,33 @@ void isoDriver::timerTick(void){ internalBuffer375_CH2->serialManage(baudRate_CH1, parity_CH1, hexDisplay_CH1); } break; - case 2: - if (deviceMode_prev != 0 && deviceMode_prev != 1 && deviceMode_prev != 2) - clearBuffers(true, false, false); - if (deviceMode_prev != 2) - clearBuffers(false, true, false); + case DeviceCH1AnalogCH2Analog: + if (deviceMode_prev != DeviceCH1Analog && deviceMode_prev != DeviceCH1AnalogCH2Digital && deviceMode_prev != DeviceCH1AnalogCH2Analog) { + clearBuffers(Channel3751); + } + if (deviceMode_prev != DeviceCH1AnalogCH2Analog) { + clearBuffers(Channel3752); + } frameActionGeneric(ChannelMode::Analog, ChannelMode::Analog); break; - case 3: - if (deviceMode_prev != 3 && deviceMode_prev != 4) - clearBuffers(true, false, false); + case DeviceCH1Digital: + if (deviceMode_prev != DeviceCH1Digital && deviceMode_prev != DeviceCH1DigitalCH2Digital) { + clearBuffers(Channel3751); + } frameActionGeneric(ChannelMode::Digital, ChannelMode::Off); if(serialDecodeEnabled_CH1 && serialType == 0){ internalBuffer375_CH1->serialManage(baudRate_CH1, parity_CH1, hexDisplay_CH1); } break; - case 4: - if (deviceMode_prev != 3 && deviceMode_prev != 4) - clearBuffers(true, false, false); - if (deviceMode_prev != 4) - clearBuffers(false, true, false); + case DeviceCH1DigitalCH2Digital: + if (deviceMode_prev != DeviceCH1Digital && deviceMode_prev != DeviceCH1DigitalCH2Digital) { + clearBuffers(Channel3751); + } + if (deviceMode_prev != DeviceCH1DigitalCH2Digital) { + clearBuffers(Channel3752); + } internalBuffer375_CH2->m_channel = 2; frameActionGeneric(ChannelMode::Digital, ChannelMode::Digital); @@ -157,14 +165,16 @@ void isoDriver::timerTick(void){ break; case 5: break; - case 6: - if (deviceMode_prev != 6) - clearBuffers(false, false, true); + case DeviceCH1Analog750: + if (deviceMode_prev != DeviceCH1Analog750) { + clearBuffers(Channel750); + } frameActionGeneric(ChannelMode::Analog750, ChannelMode::Off); break; - case 7: - if (deviceMode_prev != 7) - clearBuffers(true, false, false); + case DeviceMultimeter: + if (deviceMode_prev != DeviceMultimeter) { + clearBuffers(Channel3751); + } multimeterAction(); break; default: @@ -255,10 +265,18 @@ void isoDriver::startTimer(){ //qFatal("ISO TIMER STARTED");*/ } -void isoDriver::clearBuffers(bool ch3751, bool ch3752, bool ch750){ - if(ch3751) internalBuffer375_CH1->clearBuffer(); - if(ch3752) internalBuffer375_CH2->clearBuffer(); - if(ch750) internalBuffer750->clearBuffer(); +void isoDriver::clearBuffers(const Channels channels) +{ + if (channels & Channel3751) { + internalBuffer375_CH1->clearBuffer(); + } + if (channels & Channel3752) { + internalBuffer375_CH2->clearBuffer(); + } + if (channels & Channel750) { + internalBuffer750->clearBuffer(); + } + total_read = 0; } @@ -387,7 +405,9 @@ void isoDriver::pauseEnable_CH1(bool enabled){ if (autoGainEnabled) autoGain(); } - if(!enabled) clearBuffers(true,false,true); + if (!enabled) { + clearBuffers(Channel3751 | Channel750); + } qDebug() << "pauseEnable_CH1" << enabled; } @@ -401,7 +421,10 @@ void isoDriver::pauseEnable_CH2(bool enabled){ if (autoGainEnabled) autoGain(); } - if(!enabled) clearBuffers(false,true,false); + if (!enabled) { + clearBuffers(Channel3752); + } + qDebug() << "pauseEnable_CH2" << enabled; } void isoDriver::pauseEnable_multimeter(bool enabled){ @@ -412,7 +435,9 @@ void isoDriver::pauseEnable_multimeter(bool enabled){ delayUpdated(display.delay); } - if(!enabled) clearBuffers(true,false,false); + if (!enabled) { + clearBuffers(Channel3751); + } qDebug() << "pauseEnable_multimeter" << enabled; } diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index 4acef1a0..37762d5d 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -52,6 +52,14 @@ class isoDriver : public QLabel { Q_OBJECT public: + enum Channel { + Channel3751 = 1 << 0, + Channel3752 = 1 << 1, + Channel750 = 1 << 2, + }; + Q_DECLARE_FLAGS(Channels, Channel); + Q_FLAG(Channel); + enum class ChannelMode { Off = 0, Analog = 1, @@ -61,6 +69,17 @@ class isoDriver : public QLabel }; Q_ENUM(ChannelMode); + enum DeviceMode { + DeviceCH1Analog = 0, + DeviceCH1AnalogCH2Digital = 1, + DeviceCH1AnalogCH2Analog = 2, + DeviceCH1Digital = 3, + DeviceCH1DigitalCH2Digital = 4, + DeviceCH1Analog750 = 6, + DeviceMultimeter = 7 + }; + Q_ENUM(DeviceMode); + explicit isoDriver(QWidget *parent = 0); ~isoDriver(); @@ -239,7 +258,7 @@ public slots: void pauseEnable_CH2(bool enabled); void pauseEnable_multimeter(bool enabled); void startTimer(); - void clearBuffers(bool ch3751, bool ch3752, bool ch750); + void clearBuffers(const isoDriver::Channels channels); void setVisible_CH2(bool visible); void gainBuffers(double multiplier); void gainTick(void); @@ -298,4 +317,6 @@ public slots: void setHexDisplay_CH2(bool enabled); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(isoDriver::Channels); + #endif // ISODRIVER_H From 35bed4ca082cf7aef1299676db7803dabf2bc122 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 11:50:03 +0200 Subject: [PATCH 055/109] fix memory leak --- Desktop_Interface/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index a9beab93..3bb89dfb 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1244,7 +1244,7 @@ void MainWindow::helloWorld(){ #define QSETTINGS_DEFAULT_RETURN 42069 void MainWindow::readSettingsFile(){ - settings = new QSettings(); + settings = new QSettings(this); int connectionType = settings->value("ConnectionType", QSETTINGS_DEFAULT_RETURN).toInt(); double calibrate_vref_ch1 = settings->value("CalibrateVrefCH1", 1.65).toDouble(); double calibrate_vref_ch2 = settings->value("CalibrateVrefCH2", 1.65).toDouble(); From f394ccde1381590f44044e42f54ff7c79f3e214c Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 11:50:41 +0200 Subject: [PATCH 056/109] clean up manual firmware recovery UI code a bit --- Desktop_Interface/unixusbdriver.cpp | 82 +++++++++++++---------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index f51ef00b..808bc7f0 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -391,37 +391,28 @@ int unixUsbDriver::flashFirmware(void){ libusb_exit(ctx); qDebug() << "Libusb exited"; connected = false; - handle = NULL; - ctx = NULL; + handle = nullptr; + ctx = nullptr; return 0; #endif } +// TODO: port to QWizard, less annoying than an endless list of popup dialogs void unixUsbDriver::manualFirmwareRecovery(void){ #ifndef PLATFORM_ANDROID //Get location of firmware file - char fname[128]; - sprintf(fname, "/firmware/labrafirm_%04x_%02x.hex", EXPECTED_FIRMWARE_VERSION, DEFINED_EXPECTED_VARIANT); - - QString dirString = QCoreApplication::applicationDirPath(); - dirString.append(fname); - QByteArray array = dirString.toLocal8Bit(); - char* buffer = array.data(); + const QString fname = QString::asprintf("/firmware/labrafirm_%04x_%02x.hex", EXPECTED_FIRMWARE_VERSION, DEFINED_EXPECTED_VARIANT); + const QString dirString = QCoreApplication::applicationDirPath() + fname; //Vars QMessageBox manualFirmwareMessages; - int messageBoxReturn; - - char leaveBootloaderCommand[256]; - sprintf(leaveBootloaderCommand, "dfu-programmer atxmega32a4u launch"); + QByteArray leaveBootloaderCommand = "dfu-programmer atxmega32a4u launch"; int exit_code; - char eraseCommand[256]; - sprintf(eraseCommand, "dfu-programmer atxmega32a4u erase --force"); - char flashCommand[256]; - sprintf(flashCommand, "dfu-programmer atxmega32a4u flash %s", buffer); + QByteArray eraseCommand = "dfu-programmer atxmega32a4u erase --force"; + QByteArray flashCommand = "dfu-programmer atxmega32a4u flash " + dirString.toLocal8Bit(); @@ -436,14 +427,13 @@ void unixUsbDriver::manualFirmwareRecovery(void){ manualFirmwareMessages.exec(); manualFirmwareMessages.setStandardButtons(QMessageBox::Yes|QMessageBox::No); manualFirmwareMessages.setText("Did that fix things?"); - messageBoxReturn = manualFirmwareMessages.exec(); - manualFirmwareMessages.setStandardButtons(QMessageBox::Ok); - if(messageBoxReturn == 16384){ //"Yes" is 16384, no is 65536 - manualFirmwareMessages.setText("Awesome! Have fun!"); - messageBoxReturn = manualFirmwareMessages.exec(); + if(manualFirmwareMessages.exec() == QMessageBox::Yes){ + QMessageBox::information(this, tr("Success"), tr("Awesome! Have fun!")); return; } + manualFirmwareMessages.setStandardButtons(QMessageBox::Ok); + //Real troubleshooting begins here..... //USB Problems. @@ -461,34 +451,36 @@ void unixUsbDriver::manualFirmwareRecovery(void){ manualFirmwareMessages.setText("If that doesn't fix it, please open an issue on github.com/espotek/labrador, or contact me at admin@espotek.com."); manualFirmwareMessages.exec(); return; - } else { - exit_code = dfuprog_virtual_cmd(leaveBootloaderCommand); - manualFirmwareMessages.setText("No Labrador board could be detected.\n\nIt's possible that you're stuck in booloader mode.\n\nI've attempted to launch the firmware manually."); + } + exit_code = dfuprog_virtual_cmd(leaveBootloaderCommand.data()); + manualFirmwareMessages.setText("No Labrador board could be detected.\n\nIt's possible that you're stuck in booloader mode.\n\nI've attempted to launch the firmware manually."); + manualFirmwareMessages.exec(); + if(exit_code){ + manualFirmwareMessages.setText("Command failed. This usually means that no device is detected.\n\nPlease Ensure that the cable you're using can carry data (for example, by using it to transfer data to your phone).\n\nSome cables are for charging only, and not physically contain data lines.\n\nAlso note that the red light on the Labrador board is a power indicator for the PSU output pins.\nIt will turn on even if no data lines are present."); + manualFirmwareMessages.exec(); + return; + } + + //Firmware launch failed, but bootloader preset + if(!connected){ + exit_code = dfuprog_virtual_cmd(eraseCommand.data()); + exit_code += dfuprog_virtual_cmd(flashCommand.data()); + manualFirmwareMessages.setText("The bootloader is present, but firmware launch failed. I've attempted to reprogram it."); manualFirmwareMessages.exec(); - if(exit_code){ - manualFirmwareMessages.setText("Command failed. This usually means that no device is detected.\n\nPlease Ensure that the cable you're using can carry data (for example, by using it to transfer data to your phone).\n\nSome cables are for charging only, and not physically contain data lines.\n\nAlso note that the red light on the Labrador board is a power indicator for the PSU output pins.\nIt will turn on even if no data lines are present."); + + if(!exit_code){ //Reprogramming was successful, but board is still in bootloader mode. + exit_code = dfuprog_virtual_cmd(leaveBootloaderCommand.data()); + Q_UNUSED(exit_code); + manualFirmwareMessages.setText("Reprogramming was successful! Attempting to launch the board.\n\nIf it does not start working immediately, please wait 10 seconds and then reconnect the board."); manualFirmwareMessages.exec(); - return; - } - //Firmware launch failed, but bootloader preset - if(!connected){ - exit_code = dfuprog_virtual_cmd(eraseCommand); - exit_code += dfuprog_virtual_cmd(flashCommand); - manualFirmwareMessages.setText("The bootloader is present, but firmware launch failed. I've attempted to reprogram it."); + } else { //Programming failed. + manualFirmwareMessages.setText("Automatic Reprogramming failed.\n\nPlease contact me at admin@espotek.com for further support."); manualFirmwareMessages.exec(); - - if(!exit_code){ //Reprogramming was successful, but board is still in bootloader mode. - exit_code = dfuprog_virtual_cmd(leaveBootloaderCommand); - manualFirmwareMessages.setText("Reprogramming was successful! Attempting to launch the board.\n\nIf it does not start working immediately, please wait 10 seconds and then reconnect the board."); - manualFirmwareMessages.exec(); - } else { //Programming failed. - manualFirmwareMessages.setText("Automatic Reprogramming failed.\n\nPlease contact me at admin@espotek.com for further support."); - manualFirmwareMessages.exec(); - } - - return; } + + return; } + // ??? #endif } From 551c450b3ad42f1af79fbef5f22def02b6699f42 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 11:51:52 +0200 Subject: [PATCH 057/109] try to fix some issues with broken zooming --- Desktop_Interface/isodriver.cpp | 51 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index cd671614..619b4736 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -301,7 +301,8 @@ void isoDriver::setVoltageRange(QWheelEvent* event) void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes) { if (!(event->modifiers() == Qt::ControlModifier) && qAbs(event->angleDelta().y()) > qAbs(event->angleDelta().x())) { - double c = (topRange - botRange) / (double)400; + + double c = (qFuzzyCompare(topRange, botRange) ? 1. : (topRange - botRange)) / double(400); QCPRange range = axes->yAxis->range(); @@ -315,20 +316,27 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, topRange -= event->angleDelta().y() / 120.0 * c * pixPct; botRange += event->angleDelta().y() / 120.0 * c * (100.0 - pixPct); - if (topRange > (double)20) topRange = (double)20; - if (botRange < -(double)20) botRange = (double)-20; + topRange = qMin(topRange, 20.); + botRange = qMin(botRange, 20.); + + // Never allow 0 delta between top and bottom + if (qFuzzyCompare(topRange, botRange)) { + topRange += 0.01; + botRange -= 0.01; + } + topRangeUpdated(topRange); botRangeUpdated(botRange); } else { - double c = (window) / (double)200; + double c = (window) / 200.; QCPRange range = axes->xAxis->range(); - double pixPct = (double)100 * ((double)axes->xAxis->pixelToCoord(event->position().x()) - range.lower); + double pixPct = 100. * (double(axes->xAxis->pixelToCoord(event->position().x())) - range.lower); - pixPct /= isProperlyPaused ? (double)(range.upper - range.lower) - : (double)(window); + pixPct /= isProperlyPaused ? double(range.upper - range.lower) + : double(window); if (pixPct < 0) pixPct = 0; @@ -349,32 +357,23 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, } window -= event->angleDelta().x() / 120.0 * c * pixPct; + delay += event->angleDelta().x() / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; + delay = qBound(0., delay, maxWindowSize - window); + + if (window <= 0.) { + window = 0.01; + } else if (window > maxWindowSize) { + window = maxWindowSize; + } // NOTE: delayUpdated and timeWindowUpdated are called more than once beyond here, // maybe they should only be called once at the end? - delayUpdated(delay); - timeWindowUpdated(window); - qDebug() << window << delay; - if (window > maxWindowSize) - { - window = maxWindowSize; - timeWindowUpdated(window); - } - if ((window + delay) > maxWindowSize) - { - delay = maxWindowSize - window; - delayUpdated(delay); - } - if (delay < 0) - { - delay = 0; - delayUpdated(delay); - } - + delayUpdated(delay); + timeWindowUpdated(window); } } From ae6fb8e999b237e40f146fcaece4ac3d2352418f Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 11:54:04 +0200 Subject: [PATCH 058/109] .... old style signals and slots --- Desktop_Interface/isodriver.cpp | 6 ++++++ Desktop_Interface/isodriver.h | 1 + 2 files changed, 7 insertions(+) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 619b4736..2753d640 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -265,6 +265,12 @@ void isoDriver::startTimer(){ //qFatal("ISO TIMER STARTED");*/ } +void isoDriver::clearBuffers(bool ch3751, bool ch3752, bool ch750){ + if(ch3751) internalBuffer375_CH1->clearBuffer(); + if(ch3752) internalBuffer375_CH2->clearBuffer(); + if(ch750) internalBuffer750->clearBuffer(); + total_read = 0; +} void isoDriver::clearBuffers(const Channels channels) { if (channels & Channel3751) { diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index 37762d5d..bab04910 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -258,6 +258,7 @@ public slots: void pauseEnable_CH2(bool enabled); void pauseEnable_multimeter(bool enabled); void startTimer(); + void clearBuffers(bool ch3751, bool ch3752, bool ch750); void clearBuffers(const isoDriver::Channels channels); void setVisible_CH2(bool visible); void gainBuffers(double multiplier); From 70c970794d49d58fa8f20049d2b9eea6f495fa9c Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 12:00:58 +0200 Subject: [PATCH 059/109] more porting away from boolean traps --- Desktop_Interface/isodriver.cpp | 3 ++- Desktop_Interface/isodriver.h | 2 +- Desktop_Interface/mainwindow.cpp | 18 +++++++++--------- .../ui_files_desktop/mainwindow.ui | 2 +- .../ui_files_mobile/mainwindow.ui | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 2753d640..8e0234fd 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -265,12 +265,13 @@ void isoDriver::startTimer(){ //qFatal("ISO TIMER STARTED");*/ } -void isoDriver::clearBuffers(bool ch3751, bool ch3752, bool ch750){ +void isoDriver::clearChannelBuffers(bool ch3751, bool ch3752, bool ch750){ if(ch3751) internalBuffer375_CH1->clearBuffer(); if(ch3752) internalBuffer375_CH2->clearBuffer(); if(ch750) internalBuffer750->clearBuffer(); total_read = 0; } + void isoDriver::clearBuffers(const Channels channels) { if (channels & Channel3751) { diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index bab04910..d46939ea 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -258,7 +258,7 @@ public slots: void pauseEnable_CH2(bool enabled); void pauseEnable_multimeter(bool enabled); void startTimer(); - void clearBuffers(bool ch3751, bool ch3752, bool ch750); + void clearChannelBuffers(bool ch3751, bool ch3752, bool ch750); void clearBuffers(const isoDriver::Channels channels); void setVisible_CH2(bool visible); void gainBuffers(double multiplier); diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 3bb89dfb..71227cc2 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -158,7 +158,7 @@ MainWindow::MainWindow(QWidget *parent) : //Set the settings again! connect(ui->controller_iso->driver, SIGNAL(gainBuffers(double)), ui->controller_iso, SLOT(gainBuffers(double))); connect(ui->controller_iso->driver, SIGNAL(disableWindow(bool)), this, SLOT(setEnabled(bool))); - connect(ui->controller_iso->driver, SIGNAL(sendClearBuffer(bool,bool,bool)), ui->controller_iso, SLOT(clearBuffers(bool,bool,bool))); + connect(ui->controller_iso->driver, SIGNAL(sendClearBuffer(bool,bool,bool)), ui->controller_iso, SLOT(clearChannelBuffers(bool,bool,bool))); //connect(ui->controller_iso->driver, SIGNAL(startIsoTimer()), ui->controller_iso, SLOT(startTimer())); connect(ui->controller_iso->driver, SIGNAL(setVisible_CH2(bool)), ui->controller_iso, SLOT(setVisible_CH2(bool))); //connect(ui->controller_iso->driver, SIGNAL(enableMMTimer()), ui->controller_iso, SLOT(enableMM())); @@ -1342,7 +1342,7 @@ void MainWindow::reinitUsbStage2(void){ //Set the settings again! connect(ui->controller_iso->driver, SIGNAL(gainBuffers(double)), ui->controller_iso, SLOT(gainBuffers(double))); connect(ui->controller_iso->driver, SIGNAL(disableWindow(bool)), this, SLOT(setEnabled(bool))); - connect(ui->controller_iso->driver, SIGNAL(sendClearBuffer(bool,bool,bool)), ui->controller_iso, SLOT(clearBuffers(bool,bool,bool))); + connect(ui->controller_iso->driver, SIGNAL(sendClearBuffer(bool,bool,bool)), ui->controller_iso, SLOT(clearChannelBuffers(bool,bool,bool))); //connect(ui->controller_iso->driver, SIGNAL(startIsoTimer()), ui->controller_iso, SLOT(startTimer())); connect(ui->controller_iso->driver, SIGNAL(setVisible_CH2(bool)), ui->controller_iso, SLOT(setVisible_CH2(bool))); //connect(ui->controller_iso->driver, SIGNAL(enableMMTimer()), ui->controller_iso, SLOT(enableMM())); @@ -1373,7 +1373,7 @@ void MainWindow::resetUsbState(void){ ui->controller_iso->driver->setFunctionGen(ChannelID::CH1, ui->controller_fg->getChannelController(ChannelID::CH1)); ui->controller_iso->driver->setFunctionGen(ChannelID::CH2, ui->controller_fg->getChannelController(ChannelID::CH2)); - ui->controller_iso->clearBuffers(true,true,true); + ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); ui->controller_iso->doNotTouchGraph = false; } @@ -1741,7 +1741,7 @@ void MainWindow::on_actionCalibrate_triggered() calibrationMessages->setText("Please disconnect all wires from your Labrador board then press OK to continue."); calibrationMessages->exec(); - ui->controller_iso->clearBuffers(true,true,true); + ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); QTimer::singleShot(1200, this, SLOT(calibrateStage2())); } @@ -1770,7 +1770,7 @@ void MainWindow::calibrateStage2(){ calibrationMessages->setText("Please connect both oscilloscope channels to the outer shield of the USB connector then press OK to continue."); calibrationMessages->exec(); - ui->controller_iso->clearBuffers(true,true,true); + ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); QTimer::singleShot(1200, this, SLOT(calibrateStage3())); } @@ -2270,7 +2270,7 @@ void MainWindow::on_actionCalibrate_2_triggered() calibrationMessages->exec(); ui->controller_iso->driver->setPsu(5); - ui->controller_iso->clearBuffers(true,true,true); + ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); QTimer::singleShot(1800, this, SLOT(calibrate_psu_stage2())); } @@ -2281,7 +2281,7 @@ void MainWindow::calibrate_psu_stage2() if((PSU5 > 6) | (PSU5 < 4) ){ ui->controller_iso->driver->setPsu(4.5); ui->psuSlider->setValue(0); - ui->controller_iso->clearBuffers(true,true,true); + ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); ui->controller_iso->setAutoGain(true); ui->controller_iso->autoGain(); calibrationMessages->setText("Calibration has been abandoned due to out-of-range values. The oscilloscope should show approximately 5V. Please check all wires on your Labrador board and try again."); @@ -2290,7 +2290,7 @@ void MainWindow::calibrate_psu_stage2() } ui->controller_iso->setGain(1); ui->controller_iso->driver->setPsu(10); - ui->controller_iso->clearBuffers(true,true,true); + ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); QTimer::singleShot(1800, this, SLOT(calibrate_psu_stage3())); } @@ -2300,7 +2300,7 @@ void MainWindow::calibrate_psu_stage3() qDebug() << "PSU10 =" << PSU10; ui->controller_iso->driver->setPsu(4.5); ui->psuSlider->setValue(0); - ui->controller_iso->clearBuffers(true,true,true); + ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); ui->controller_iso->setAutoGain(true); ui->controller_iso->autoGain(); diff --git a/Desktop_Interface/ui_files_desktop/mainwindow.ui b/Desktop_Interface/ui_files_desktop/mainwindow.ui index 8df42963..d3e1f546 100644 --- a/Desktop_Interface/ui_files_desktop/mainwindow.ui +++ b/Desktop_Interface/ui_files_desktop/mainwindow.ui @@ -2720,7 +2720,7 @@ pauseEnable_CH1(bool) pauseEnable_CH2(bool) startTimer() - clearBuffers(bool,bool,bool) + clearChannelBuffers(bool,bool,bool) setVisible_CH2(bool) gainBuffers(double) setAutoGain(bool) diff --git a/Desktop_Interface/ui_files_mobile/mainwindow.ui b/Desktop_Interface/ui_files_mobile/mainwindow.ui index 3a6dc41d..c6b0b84b 100644 --- a/Desktop_Interface/ui_files_mobile/mainwindow.ui +++ b/Desktop_Interface/ui_files_mobile/mainwindow.ui @@ -2780,7 +2780,7 @@ pauseEnable_CH1(bool) pauseEnable_CH2(bool) startTimer() - clearBuffers(bool,bool,bool) + clearChannelBuffers(bool,bool,bool) setVisible_CH2(bool) gainBuffers(double) setAutoGain(bool) From f0205eac6c32deb20746c8de0b0c70967e742aca Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 12:01:09 +0200 Subject: [PATCH 060/109] fix some minor warnings --- Desktop_Interface/isodriver.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 8e0234fd..4ec974d2 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -1267,9 +1267,7 @@ void isoDriver::loadFileBuffer(QFile *fileToLoad){ disableFileMode(); - if(internalBufferFile != NULL){ - delete internalBufferFile; - } + delete internalBufferFile; //Load the file if (!fileToLoad->open(QIODevice::ReadOnly)) { @@ -1392,9 +1390,7 @@ void isoDriver::loadFileBuffer(QFile *fileToLoad){ qDebug() << "Initialising timer"; //Initialise the file timer. - if (fileTimer != NULL){ - delete fileTimer; - } + delete fileTimer; fileTimer = new QTimer(); fileTimer->setTimerType(Qt::PreciseTimer); fileTimer->start(TIMER_PERIOD); From c6fbd05548a97dee9321fbad8457d6b5f532e0d6 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 12:04:38 +0200 Subject: [PATCH 061/109] some memory safety --- Desktop_Interface/isodriver.cpp | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 4ec974d2..6a5aabc4 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -582,7 +582,8 @@ void isoDriver::udateCursors(void){ #endif if (!cursorStatsEnabled) return; - QString *cursorStatsString = new QString(); + // shouldn't this be moved into the QCP_VER == 1? + QString cursorStatsString; v0->value = display.y0; v1->value = display.y1; @@ -592,22 +593,15 @@ void isoDriver::udateCursors(void){ dt->value = fabs(display.x0 - display.x1); f->value = 1 / (display.x1 - display.x0); - char temp_hori[64]; - char temp_vert[64]; - char temp_separator[2]; - sprintf(temp_hori, "V0=%s, V1=%s, ΔV=%s", v0->printVal(), v1->printVal(), dv->printVal()); - sprintf(temp_vert, "t0=%s, t1=%s, Δt=%s, f=%s", t0->printVal(), t1->printVal(), dt->printVal(), f->printVal()); - sprintf(temp_separator, "\n"); - - //sprintf(temp, "hello!"); - if(horiCursorEnabled) cursorStatsString->append(temp_hori); - if(horiCursorEnabled && vertCursorEnabled) cursorStatsString->append(temp_separator); - if(vertCursorEnabled) cursorStatsString->append(temp_vert); - //qDebug() << temp; + const QString temp_hori = QString::asprintf("V0=%s, V1=%s, ΔV=%s", v0->printVal(), v1->printVal(), dv->printVal()); + const QString temp_vert = QString::asprintf("t0=%s, t1=%s, Δt=%s, f=%s", t0->printVal(), t1->printVal(), dt->printVal(), f->printVal()); + + if(horiCursorEnabled) cursorStatsString.append(temp_hori); + if(horiCursorEnabled && vertCursorEnabled) cursorStatsString.append("\n"); + if(vertCursorEnabled) cursorStatsString.append(temp_vert); #if QCP_VER == 1 - cursorTextPtr->setText(*(cursorStatsString)); + cursorTextPtr->setText(cursorStatsString); #endif - delete cursorStatsString; } short isoDriver::reverseFrontEnd(double voltage){ From 0c788514f64abb64605d2bfd2060a71f5b0a3cea Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:04:02 +0200 Subject: [PATCH 062/109] fix more magic numbers --- Desktop_Interface/genericusbdriver.cpp | 16 ++++++------ Desktop_Interface/isobuffer.cpp | 8 +++--- Desktop_Interface/isodriver.cpp | 35 ++++++++++++++++++-------- Desktop_Interface/isodriver.h | 11 -------- Desktop_Interface/mainwindow.cpp | 8 +++--- Desktop_Interface/xmega.h | 17 +++++++++++-- 6 files changed, 56 insertions(+), 39 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 3fa189a6..1510d76f 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -263,29 +263,29 @@ void genericUsbDriver::setDeviceMode(int mode){ //switch on new deviceMode!! switch(deviceMode){ - case 0: - if(oldMode > 2) sendClearBuffer(true,false,false); + case DeviceCH1Analog: + if(oldMode > DeviceCH1AnalogCH2Analog) sendClearBuffer(true,false,false); setVisible_CH2(false); checkXY(false); break; - case 1: + case DeviceCH1AnalogCH2Digital: if(oldMode > 2) sendClearBuffer(true,false,false); sendClearBuffer(false,true,false); setVisible_CH2(true); checkXY(false); break; - case 2: + case DeviceCH1AnalogCH2Analog: if(oldMode > 2) sendClearBuffer(true,false,false); sendClearBuffer(false,true,false); setVisible_CH2(true); break; - case 3: + case DeviceCH1Digital: if(oldMode != 4) sendClearBuffer(true,false,false); sendClearBuffer(false,true,false); setVisible_CH2(true); checkXY(false); break; - case 4: + case DeviceCH1DigitalCH2Digital : if(oldMode != 3) sendClearBuffer(true,false,false); sendClearBuffer(false,true,false); setVisible_CH2(true); @@ -295,12 +295,12 @@ void genericUsbDriver::setDeviceMode(int mode){ setVisible_CH2(false); checkXY(false); break; - case 6: + case DeviceCH1Analog750 : sendClearBuffer(false,false,true); setVisible_CH2(false); checkXY(false); break; - case 7: + case DeviceMultimeter: sendClearBuffer(true,false,false); enableMMTimer(); checkXY(false); diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 4e8ebcd6..49e4e18f 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -260,9 +260,9 @@ double isoBuffer::sampleConvert(short sample, int TOP, bool AC) const double scope_gain = (double)(m_virtualParent->driver->scopeGain); double voltageLevel = (sample * (vcc/2)) / (m_frontendGain*scope_gain*TOP); - if (m_virtualParent->driver->deviceMode != 7) voltageLevel += m_voltage_ref; + if (m_virtualParent->driver->deviceMode != DeviceMultimeter) voltageLevel += m_voltage_ref; #ifdef INVERT_MM - if (m_virtualParent->driver->deviceMode == 7) voltageLevel *= -1; + if (m_virtualParent->driver->deviceMode == DeviceMultimeter) voltageLevel *= -1; #endif if (AC) @@ -286,9 +286,9 @@ short isoBuffer::inverseSampleConvert(double voltageLevel, int TOP, bool AC) con } #ifdef INVERT_MM - if (m_virtualParent->driver->deviceMode == 7) voltageLevel *= -1; + if (m_virtualParent->driver->deviceMode == DeviceMultimeter) voltageLevel *= -1; #endif - if (m_virtualParent->driver->deviceMode != 7) voltageLevel -= m_voltage_ref; + if (m_virtualParent->driver->deviceMode != DeviceMultimeter) voltageLevel -= m_voltage_ref; // voltageLevel = (sample * (vcc/2)) / (frontendGain*scope_gain*TOP); short sample = (voltageLevel * (m_frontendGain*scope_gain*TOP))/(vcc/2); diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 6a5aabc4..1c6def3f 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -201,9 +201,9 @@ void isoDriver::analogConvert(short *shortPtr, QVector *doublePtr, int T double *data = doublePtr->data(); for (int i=0;ideviceMode != 7) data[i] += ref; + if (driver->deviceMode != DeviceMultimeter) data[i] += ref; #ifdef INVERT_MM - if(driver->deviceMode == 7) data[i] *= -1; + if(driver->deviceMode == DeviceMultimeter) data[i] *= -1; #endif accumulated += data[i]; @@ -390,7 +390,7 @@ bool isoDriver::properlyPaused(){ qDebug() << "Properly paused"; return true; } - if ((driver->deviceMode == 0) || (driver->deviceMode == 3) || (driver->deviceMode == 6)){ + if ((driver->deviceMode == DeviceCH1Analog) || (driver->deviceMode == DeviceCH1Digital) || (driver->deviceMode == DeviceCH1Analog750)){ if(paused_CH1) qDebug() << "Properly paused"; else qDebug() << "Not properly paused"; return paused_CH1; } @@ -473,9 +473,24 @@ void isoDriver::gainTick(void){ #warning: "gainTick does nothing on Android!!" #else qDebug() << "Multiplying by " << multi; - if (driver->deviceMode <5) internalBuffer375_CH1->gainBuffer(log2(multi)); - if ((driver->deviceMode == 1) | (driver->deviceMode == 2) | (driver->deviceMode == 4)) internalBuffer375_CH2->gainBuffer(log2(multi)); - if ((driver->deviceMode == 6) | (driver->deviceMode == 7)) internalBuffer750->gainBuffer(log2(multi)); + if (driver->deviceMode <= DeviceCH1DigitalCH2Digital) { + internalBuffer375_CH1->gainBuffer(log2(multi)); + } + + switch(driver->deviceMode) { + case DeviceCH1AnalogCH2Digital: + case DeviceCH1AnalogCH2Analog: + case DeviceCH1DigitalCH2Digital: + internalBuffer375_CH2->gainBuffer(log2(multi)); + break; + case DeviceCH1Analog750: + case DeviceMultimeter: + internalBuffer750->gainBuffer(log2(multi)); + break; + default: + qWarning() << "Invalid device mode" << driver->deviceMode; + break; + } #endif } @@ -607,15 +622,15 @@ void isoDriver::udateCursors(void){ short isoDriver::reverseFrontEnd(double voltage){ //qFatal("reverseFrontEnd driver mode 7"); #ifdef INVERT_MM - if(driver->deviceMode == 7) voltage *= -1; + if(driver->deviceMode == DeviceMultimeter) voltage *= -1; #endif double vn = vcc * (R2/(R1+R2)); double vx = vn + (voltage - vn) * (R4 / (R3+R4)); - double TOP = (driver->deviceMode == 7) ? 2048 : 128; + double TOP = (driver->deviceMode == DeviceMultimeter) ? 2048 : 128; - if (driver->deviceMode == 7){ + if (driver->deviceMode == DeviceMultimeter){ qDebug() << "SEEEKING"; qDebug() << ((vx - vn)/vref * (double)driver->scopeGain * (double)TOP + (double)0.5); qDebug() << "SEEEKING"; @@ -636,7 +651,7 @@ void isoDriver::setTriggerEnabled(bool enabled) void isoDriver::setTriggerLevel(double level) { - internalBuffer375_CH1->setTriggerLevel(level, (driver->deviceMode == 7 ? 2048 : 128), AC_CH1); + internalBuffer375_CH1->setTriggerLevel(level, (driver->deviceMode == DeviceMultimeter ? 2048 : 128), AC_CH1); internalBuffer375_CH2->setTriggerLevel(level, 128, AC_CH2); internalBuffer750->setTriggerLevel(level, 128, AC_CH1); triggerStateChanged(); diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index d46939ea..ba162e51 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -69,17 +69,6 @@ class isoDriver : public QLabel }; Q_ENUM(ChannelMode); - enum DeviceMode { - DeviceCH1Analog = 0, - DeviceCH1AnalogCH2Digital = 1, - DeviceCH1AnalogCH2Analog = 2, - DeviceCH1Digital = 3, - DeviceCH1DigitalCH2Digital = 4, - DeviceCH1Analog750 = 6, - DeviceMultimeter = 7 - }; - Q_ENUM(DeviceMode); - explicit isoDriver(QWidget *parent = 0); ~isoDriver(); diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 71227cc2..35b40a7a 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1693,7 +1693,7 @@ void MainWindow::on_actionCalibrate_triggered() calibrationMessages->exec(); return; } - if(ui->controller_iso->driver->deviceMode!=4){ + if(ui->controller_iso->driver->deviceMode != DeviceCH1DigitalCH2Digital){ calibrationMessages->setStandardButtons(QMessageBox::Ok|QMessageBox::Cancel); calibrationMessages->setText("The calibration sequence requires all devices to be turned off, except for the oscilloscope CH1 and CH2. Is it OK for me to change your workspace?"); int choice = calibrationMessages->exec(); @@ -1992,7 +1992,7 @@ void MainWindow::on_actionRecord_CH1_triggered(bool checked) qDebug() << "on_actionRecord_CH1_triggered(bool checked)"; qDebug() << daq_max_file_size; if(!checked){ - if(ui->controller_iso->driver->deviceMode!=6){ + if(ui->controller_iso->driver->deviceMode != DeviceCH1Analog750) { ui->controller_iso->internalBuffer375_CH1->disableFileIO(); delete(output375_CH1); } else { @@ -2014,7 +2014,7 @@ void MainWindow::on_actionRecord_CH1_triggered(bool checked) return; //User cancelled } #endif - if(ui->controller_iso->driver->deviceMode!=6){ + if(ui->controller_iso->driver->deviceMode != DeviceCH1Analog750) { output375_CH1 = new QFile(fileName); ui->controller_iso->internalBuffer375_CH1->enableFileIO(output375_CH1, daq_num_to_average, daq_max_file_size); } else { @@ -2048,7 +2048,7 @@ void MainWindow::on_actionRecord_CH2_triggered(bool checked) output375_CH2 = new QFile(fileName); ui->controller_iso->internalBuffer375_CH2->enableFileIO(output375_CH2, daq_num_to_average, daq_max_file_size); - if((checked) && (ui->controller_iso->driver->deviceMode != 4)) + if((checked) && (ui->controller_iso->driver->deviceMode != DeviceCH1DigitalCH2Digital)) QMessageBox::warning(nullptr, "Warning", "CH2 is disabled. The DAQ will NOT RECORD any data from from CH2 until it has been enabled!"); } diff --git a/Desktop_Interface/xmega.h b/Desktop_Interface/xmega.h index 3fe8ae51..9990cade 100644 --- a/Desktop_Interface/xmega.h +++ b/Desktop_Interface/xmega.h @@ -17,8 +17,6 @@ #define R2 (double)1000 #define R1 (double)1000 -#define INIT_DEVICE_MODE 0 - #define PSU_STEP 5 #define PSU_PERIOD 100 #define PSU_ADC_TOP 128 @@ -26,4 +24,19 @@ #define INVERT_TRIPLE #define INVERT_MM +enum DeviceMode { + DeviceCH1Analog = 0, + DeviceCH1AnalogCH2Digital = 1, + DeviceCH1AnalogCH2Analog = 2, + + DeviceCH1Digital = 3, + DeviceCH1DigitalCH2Digital = 4, + + DeviceCH1Analog750 = 6, + + DeviceMultimeter = 7 +}; + +#define INIT_DEVICE_MODE DeviceCH1Analog + #endif // XMEGA_H From 6e9f46122f5386c781b122ac271594022635f66f Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:09:27 +0200 Subject: [PATCH 063/109] move DisplayControl class to separate files --- Desktop_Interface/DisplayControl.cpp | 85 ++++++++++++++++++++++++++ Desktop_Interface/DisplayControl.h | 29 +++++++++ Desktop_Interface/Labrador.pro | 2 + Desktop_Interface/isodriver.cpp | 80 ------------------------ Desktop_Interface/isodriver.h | 24 +------- Desktop_Interface/uartstyledecoder.cpp | 2 +- 6 files changed, 118 insertions(+), 104 deletions(-) create mode 100644 Desktop_Interface/DisplayControl.cpp create mode 100644 Desktop_Interface/DisplayControl.h diff --git a/Desktop_Interface/DisplayControl.cpp b/Desktop_Interface/DisplayControl.cpp new file mode 100644 index 00000000..6702d90e --- /dev/null +++ b/Desktop_Interface/DisplayControl.cpp @@ -0,0 +1,85 @@ +#include "DisplayControl.h" + +#include +#include +#include + +void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes) +{ + if (!(event->modifiers() == Qt::ControlModifier) && qAbs(event->angleDelta().y()) > qAbs(event->angleDelta().x())) { + + double c = (qFuzzyCompare(topRange, botRange) ? 1. : (topRange - botRange)) / double(400); + + QCPRange range = axes->yAxis->range(); + + double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(event->position().y())-range.lower) / range.size())); + if (pixPct < 0) pixPct = 0; + if (pixPct > 100) pixPct = 100; + + qDebug() << "WHEEL @ " << pixPct << "%"; + qDebug() << range.upper; + + topRange -= event->angleDelta().y() / 120.0 * c * pixPct; + botRange += event->angleDelta().y() / 120.0 * c * (100.0 - pixPct); + + topRange = qMin(topRange, 20.); + botRange = qMin(botRange, 20.); + + // Never allow 0 delta between top and bottom + if (qFuzzyCompare(topRange, botRange)) { + topRange += 0.01; + botRange -= 0.01; + } + + topRangeUpdated(topRange); + botRangeUpdated(botRange); + } + else + { + double c = (window) / 200.; + QCPRange range = axes->xAxis->range(); + + double pixPct = 100. * (double(axes->xAxis->pixelToCoord(event->position().x())) - range.lower); + + pixPct /= isProperlyPaused ? double(range.upper - range.lower) + : double(window); + + if (pixPct < 0) + pixPct = 0; + + if (pixPct > 100) + pixPct = 100; + + qDebug() << "WHEEL @ " << pixPct << "%"; + qDebug() << event->angleDelta().x(); + + if (! isProperlyPaused) + { + qDebug() << "TIGGERED"; + qDebug() << "upper = " << range.upper << "lower = " << range.lower; + qDebug() << "window = " << window; + qDebug() << c * ((double)pixPct); + qDebug() << c * ((double)100 - (double)pixPct) * pixPct / 100; + } + + window -= event->angleDelta().x() / 120.0 * c * pixPct; + + delay += event->angleDelta().x() / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; + delay = qBound(0., delay, maxWindowSize - window); + + if (window <= 0.) { + window = 0.01; + } else if (window > maxWindowSize) { + window = maxWindowSize; + } + + // NOTE: delayUpdated and timeWindowUpdated are called more than once beyond here, + // maybe they should only be called once at the end? + + qDebug() << window << delay; + + delayUpdated(delay); + timeWindowUpdated(window); + } + +} diff --git a/Desktop_Interface/DisplayControl.h b/Desktop_Interface/DisplayControl.h new file mode 100644 index 00000000..2b69f8c3 --- /dev/null +++ b/Desktop_Interface/DisplayControl.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class QWheelEvent; +class QCustomPlot; + +class DisplayControl : public QObject +{ + Q_OBJECT +public: + + double delay = 0; + double window = 0.01; + double y0 = 0; + double y1 = 0; + double x0 = 0; + double x1 = 0; + double topRange = 2.5; + double botRange = -0.5; + + void setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); + +signals: + void topRangeUpdated(double); + void botRangeUpdated(double); + void timeWindowUpdated(double); + void delayUpdated(double); +}; diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index cbc5782c..5478a39f 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -39,6 +39,7 @@ include(ui_elements.pri) MOC_DIR = $$PWD/moc SOURCES += main.cpp\ + DisplayControl.cpp \ mainwindow.cpp \ functiongencontrol.cpp \ isodriver.cpp \ @@ -55,6 +56,7 @@ SOURCES += main.cpp\ i2cdecoder.cpp HEADERS += mainwindow.h \ + DisplayControl.h \ functiongencontrol.h \ pinoutDialog.h \ xmega.h \ diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 1c6def3f..2c698eda 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -305,86 +305,6 @@ void isoDriver::setVoltageRange(QWheelEvent* event) autoGain(); } -void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes) -{ - if (!(event->modifiers() == Qt::ControlModifier) && qAbs(event->angleDelta().y()) > qAbs(event->angleDelta().x())) { - - double c = (qFuzzyCompare(topRange, botRange) ? 1. : (topRange - botRange)) / double(400); - - QCPRange range = axes->yAxis->range(); - - double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(event->position().y())-range.lower) / range.size())); - if (pixPct < 0) pixPct = 0; - if (pixPct > 100) pixPct = 100; - - qDebug() << "WHEEL @ " << pixPct << "%"; - qDebug() << range.upper; - - topRange -= event->angleDelta().y() / 120.0 * c * pixPct; - botRange += event->angleDelta().y() / 120.0 * c * (100.0 - pixPct); - - topRange = qMin(topRange, 20.); - botRange = qMin(botRange, 20.); - - // Never allow 0 delta between top and bottom - if (qFuzzyCompare(topRange, botRange)) { - topRange += 0.01; - botRange -= 0.01; - } - - topRangeUpdated(topRange); - botRangeUpdated(botRange); - } - else - { - double c = (window) / 200.; - QCPRange range = axes->xAxis->range(); - - double pixPct = 100. * (double(axes->xAxis->pixelToCoord(event->position().x())) - range.lower); - - pixPct /= isProperlyPaused ? double(range.upper - range.lower) - : double(window); - - if (pixPct < 0) - pixPct = 0; - - if (pixPct > 100) - pixPct = 100; - - qDebug() << "WHEEL @ " << pixPct << "%"; - qDebug() << event->angleDelta().x(); - - if (! isProperlyPaused) - { - qDebug() << "TIGGERED"; - qDebug() << "upper = " << range.upper << "lower = " << range.lower; - qDebug() << "window = " << window; - qDebug() << c * ((double)pixPct); - qDebug() << c * ((double)100 - (double)pixPct) * pixPct / 100; - } - - window -= event->angleDelta().x() / 120.0 * c * pixPct; - - delay += event->angleDelta().x() / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; - delay = qBound(0., delay, maxWindowSize - window); - - if (window <= 0.) { - window = 0.01; - } else if (window > maxWindowSize) { - window = maxWindowSize; - } - - // NOTE: delayUpdated and timeWindowUpdated are called more than once beyond here, - // maybe they should only be called once at the end? - - qDebug() << window << delay; - - delayUpdated(delay); - timeWindowUpdated(window); - } - -} - bool isoDriver::properlyPaused(){ if(paused_CH1 & paused_CH2){ qDebug() << "Properly paused"; diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index ba162e51..b6030751 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -11,6 +11,7 @@ #include "siprint.h" #include "i2cdecoder.h" #include "uartstyledecoder.h" +#include "DisplayControl.h" class isoBuffer; class isoBuffer_file; @@ -25,29 +26,6 @@ class isoBuffer_file; // That is one of the things I plan on fixing, and in fact // the reason why I began the commenting! -class DisplayControl : public QObject -{ - Q_OBJECT -public: - - double delay = 0; - double window = 0.01; - double y0 = 0; - double y1 = 0; - double x0 = 0; - double x1 = 0; - double topRange = 2.5; - double botRange = -0.5; - - void setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); - -signals: - void topRangeUpdated(double); - void botRangeUpdated(double); - void timeWindowUpdated(double); - void delayUpdated(double); -}; - class isoDriver : public QLabel { Q_OBJECT diff --git a/Desktop_Interface/uartstyledecoder.cpp b/Desktop_Interface/uartstyledecoder.cpp index 76415aeb..24eb2c82 100644 --- a/Desktop_Interface/uartstyledecoder.cpp +++ b/Desktop_Interface/uartstyledecoder.cpp @@ -211,7 +211,7 @@ char uartStyleDecoder::decodeDatabit(int mode, short symbol) return symbol; break; default: - qDebug() << "uartStyleDecoder::decodeDatabit is failing..."; + qDebug() << "uartStyleDecoder::decodeDatabit: invalid mode" << mode; return -1; // Garbage } } From 8c1d355fc2169ef6dcba954b553ff95c8b538684 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:22:16 +0200 Subject: [PATCH 064/109] move zooming code out into separate functions --- Desktop_Interface/DisplayControl.cpp | 33 ++++++++++++++++++---------- Desktop_Interface/DisplayControl.h | 3 +++ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Desktop_Interface/DisplayControl.cpp b/Desktop_Interface/DisplayControl.cpp index 6702d90e..3f3b6a37 100644 --- a/Desktop_Interface/DisplayControl.cpp +++ b/Desktop_Interface/DisplayControl.cpp @@ -7,20 +7,30 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes) { if (!(event->modifiers() == Qt::ControlModifier) && qAbs(event->angleDelta().y()) > qAbs(event->angleDelta().x())) { + zoomVertically(event->angleDelta().y(), event->position().y(), axes); + } + else + { + zoomHorizontally(event->angleDelta().x(), event->position().x(), isProperlyPaused, maxWindowSize, axes); + } +} + +void DisplayControl::zoomVertically(const double delta, const double y, QCustomPlot *axes) +{ double c = (qFuzzyCompare(topRange, botRange) ? 1. : (topRange - botRange)) / double(400); QCPRange range = axes->yAxis->range(); - double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(event->position().y())-range.lower) / range.size())); + double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(y)-range.lower) / range.size())); if (pixPct < 0) pixPct = 0; if (pixPct > 100) pixPct = 100; qDebug() << "WHEEL @ " << pixPct << "%"; qDebug() << range.upper; - topRange -= event->angleDelta().y() / 120.0 * c * pixPct; - botRange += event->angleDelta().y() / 120.0 * c * (100.0 - pixPct); + topRange -= delta / 120.0 * c * pixPct; + botRange += delta / 120.0 * c * (100.0 - pixPct); topRange = qMin(topRange, 20.); botRange = qMin(botRange, 20.); @@ -33,13 +43,15 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, topRangeUpdated(topRange); botRangeUpdated(botRange); - } - else - { + +} + +void DisplayControl::zoomHorizontally(const double delta, const double x, bool isProperlyPaused, double maxWindowSize, QCustomPlot *axes) +{ double c = (window) / 200.; QCPRange range = axes->xAxis->range(); - double pixPct = 100. * (double(axes->xAxis->pixelToCoord(event->position().x())) - range.lower); + double pixPct = 100. * (double(axes->xAxis->pixelToCoord(x)) - range.lower); pixPct /= isProperlyPaused ? double(range.upper - range.lower) : double(window); @@ -51,7 +63,7 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, pixPct = 100; qDebug() << "WHEEL @ " << pixPct << "%"; - qDebug() << event->angleDelta().x(); + qDebug() << delta; if (! isProperlyPaused) { @@ -62,9 +74,9 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, qDebug() << c * ((double)100 - (double)pixPct) * pixPct / 100; } - window -= event->angleDelta().x() / 120.0 * c * pixPct; + window -= delta / 120.0 * c * pixPct; - delay += event->angleDelta().x() / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; + delay += delta / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; delay = qBound(0., delay, maxWindowSize - window); if (window <= 0.) { @@ -80,6 +92,5 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, delayUpdated(delay); timeWindowUpdated(window); - } } diff --git a/Desktop_Interface/DisplayControl.h b/Desktop_Interface/DisplayControl.h index 2b69f8c3..234ae849 100644 --- a/Desktop_Interface/DisplayControl.h +++ b/Desktop_Interface/DisplayControl.h @@ -21,6 +21,9 @@ class DisplayControl : public QObject void setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); + void zoomVertically(const double delta, const double y, QCustomPlot *axes); + void zoomHorizontally(const double delta, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); + signals: void topRangeUpdated(double); void botRangeUpdated(double); From 6e5894e0df1d45be2f1cc383aab841522b643500 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:23:37 +0200 Subject: [PATCH 065/109] fix whitespace, in separate commit for better history --- Desktop_Interface/DisplayControl.cpp | 107 ++++++++++++++------------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/Desktop_Interface/DisplayControl.cpp b/Desktop_Interface/DisplayControl.cpp index 3f3b6a37..97518185 100644 --- a/Desktop_Interface/DisplayControl.cpp +++ b/Desktop_Interface/DisplayControl.cpp @@ -18,79 +18,82 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, void DisplayControl::zoomVertically(const double delta, const double y, QCustomPlot *axes) { - double c = (qFuzzyCompare(topRange, botRange) ? 1. : (topRange - botRange)) / double(400); - - QCPRange range = axes->yAxis->range(); + double c; + if (qFuzzyIsNull(topRange) || qFuzzyIsNull(botRange) || qFuzzyCompare(topRange, botRange)) { + c = 1. / 400.; + } else { + c = (topRange - botRange) / 400.; + } - double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(y)-range.lower) / range.size())); - if (pixPct < 0) pixPct = 0; - if (pixPct > 100) pixPct = 100; + QCPRange range = axes->yAxis->range(); - qDebug() << "WHEEL @ " << pixPct << "%"; - qDebug() << range.upper; + double pixPct = (double)100 - ((double)100 * (((double)axes->yAxis->pixelToCoord(y)-range.lower) / range.size())); + if (pixPct < 0) pixPct = 0; + if (pixPct > 100) pixPct = 100; - topRange -= delta / 120.0 * c * pixPct; - botRange += delta / 120.0 * c * (100.0 - pixPct); + qDebug() << "WHEEL @ " << pixPct << "%"; + qDebug() << range.upper; - topRange = qMin(topRange, 20.); - botRange = qMin(botRange, 20.); + topRange -= delta / 120.0 * c * pixPct; + botRange += delta / 120.0 * c * (100.0 - pixPct); - // Never allow 0 delta between top and bottom - if (qFuzzyCompare(topRange, botRange)) { - topRange += 0.01; - botRange -= 0.01; - } + topRange = qMin(topRange, 20.); + botRange = qMin(botRange, 20.); - topRangeUpdated(topRange); - botRangeUpdated(botRange); + // Never allow 0 delta between top and bottom + if (qFuzzyCompare(topRange, botRange)) { + topRange += 0.01; + botRange -= 0.01; + } + topRangeUpdated(topRange); + botRangeUpdated(botRange); } void DisplayControl::zoomHorizontally(const double delta, const double x, bool isProperlyPaused, double maxWindowSize, QCustomPlot *axes) { - double c = (window) / 200.; - QCPRange range = axes->xAxis->range(); - - double pixPct = 100. * (double(axes->xAxis->pixelToCoord(x)) - range.lower); + double c = (window) / 200.; + QCPRange range = axes->xAxis->range(); - pixPct /= isProperlyPaused ? double(range.upper - range.lower) - : double(window); + double pixPct = 100. * (double(axes->xAxis->pixelToCoord(x)) - range.lower); - if (pixPct < 0) - pixPct = 0; + pixPct /= isProperlyPaused ? double(range.upper - range.lower) + : double(window); - if (pixPct > 100) - pixPct = 100; + if (pixPct < 0) + pixPct = 0; - qDebug() << "WHEEL @ " << pixPct << "%"; - qDebug() << delta; + if (pixPct > 100) + pixPct = 100; - if (! isProperlyPaused) - { - qDebug() << "TIGGERED"; - qDebug() << "upper = " << range.upper << "lower = " << range.lower; - qDebug() << "window = " << window; - qDebug() << c * ((double)pixPct); - qDebug() << c * ((double)100 - (double)pixPct) * pixPct / 100; - } + qDebug() << "WHEEL @ " << pixPct << "%"; + qDebug() << delta; - window -= delta / 120.0 * c * pixPct; + if (! isProperlyPaused) + { + qDebug() << "TIGGERED"; + qDebug() << "upper = " << range.upper << "lower = " << range.lower; + qDebug() << "window = " << window; + qDebug() << c * ((double)pixPct); + qDebug() << c * ((double)100 - (double)pixPct) * pixPct / 100; + } - delay += delta / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; - delay = qBound(0., delay, maxWindowSize - window); + window -= delta / 120.0 * c * pixPct; - if (window <= 0.) { - window = 0.01; - } else if (window > maxWindowSize) { - window = maxWindowSize; - } + delay += delta / 120.0 * c * (100.0 - pixPct) * pixPct / 100.0; + delay = qBound(0., delay, maxWindowSize - window); - // NOTE: delayUpdated and timeWindowUpdated are called more than once beyond here, - // maybe they should only be called once at the end? + if (window <= 0.) { + window = 0.01; + } else if (window > maxWindowSize) { + window = maxWindowSize; + } - qDebug() << window << delay; + // NOTE: delayUpdated and timeWindowUpdated are called more than once beyond here, + // maybe they should only be called once at the end? - delayUpdated(delay); - timeWindowUpdated(window); + qDebug() << window << delay; + delayUpdated(delay); + timeWindowUpdated(window); } From 8ff4c8695e4ce06d9067290b8d1296a17bee842d Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:31:12 +0200 Subject: [PATCH 066/109] more protection against division by zero --- Desktop_Interface/DisplayControl.cpp | 33 ++++++++++++++++++---------- Desktop_Interface/DisplayControl.h | 2 +- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Desktop_Interface/DisplayControl.cpp b/Desktop_Interface/DisplayControl.cpp index 97518185..2276f22f 100644 --- a/Desktop_Interface/DisplayControl.cpp +++ b/Desktop_Interface/DisplayControl.cpp @@ -18,6 +18,10 @@ void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, void DisplayControl::zoomVertically(const double delta, const double y, QCustomPlot *axes) { + if (qFuzzyIsNull(delta)) { + return; + } + double c; if (qFuzzyIsNull(topRange) || qFuzzyIsNull(botRange) || qFuzzyCompare(topRange, botRange)) { c = 1. / 400.; @@ -52,19 +56,29 @@ void DisplayControl::zoomVertically(const double delta, const double y, QCustomP void DisplayControl::zoomHorizontally(const double delta, const double x, bool isProperlyPaused, double maxWindowSize, QCustomPlot *axes) { - double c = (window) / 200.; + if (qFuzzyIsNull(delta)) { + return; + } + + double c; + if (qFuzzyIsNull(window)) { + c = 1. / 200.; + } else { + c = window / 200.; + } + QCPRange range = axes->xAxis->range(); double pixPct = 100. * (double(axes->xAxis->pixelToCoord(x)) - range.lower); - pixPct /= isProperlyPaused ? double(range.upper - range.lower) - : double(window); - - if (pixPct < 0) - pixPct = 0; + if (qFuzzyIsNull(window) || qFuzzyCompare(range.upper, range.lower)) { + pixPct = 1; + } else { + pixPct /= isProperlyPaused ? double(range.upper - range.lower) + : double(window); + } - if (pixPct > 100) - pixPct = 100; + pixPct = qBound(0., pixPct, 100.); qDebug() << "WHEEL @ " << pixPct << "%"; qDebug() << delta; @@ -89,9 +103,6 @@ void DisplayControl::zoomHorizontally(const double delta, const double x, bool i window = maxWindowSize; } - // NOTE: delayUpdated and timeWindowUpdated are called more than once beyond here, - // maybe they should only be called once at the end? - qDebug() << window << delay; delayUpdated(delay); diff --git a/Desktop_Interface/DisplayControl.h b/Desktop_Interface/DisplayControl.h index 234ae849..5806df1a 100644 --- a/Desktop_Interface/DisplayControl.h +++ b/Desktop_Interface/DisplayControl.h @@ -22,7 +22,7 @@ class DisplayControl : public QObject void setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); void zoomVertically(const double delta, const double y, QCustomPlot *axes); - void zoomHorizontally(const double delta, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); + void zoomHorizontally(const double delta, const double x, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); signals: void topRangeUpdated(double); From ed40bd7433af3f9e8b11348d37441402ab91d198 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:39:48 +0200 Subject: [PATCH 067/109] avoid including QWheelEvent in displaycontrol --- Desktop_Interface/DisplayControl.cpp | 16 +++++++--------- Desktop_Interface/DisplayControl.h | 3 +-- Desktop_Interface/isodriver.cpp | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Desktop_Interface/DisplayControl.cpp b/Desktop_Interface/DisplayControl.cpp index 2276f22f..fa9fde94 100644 --- a/Desktop_Interface/DisplayControl.cpp +++ b/Desktop_Interface/DisplayControl.cpp @@ -1,19 +1,17 @@ #include "DisplayControl.h" -#include #include #include -void DisplayControl::setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes) +void DisplayControl::setVoltageRange (const QPointF &position, const QPoint zoomDelta, const Qt::KeyboardModifiers modifiers, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes) { - if (!(event->modifiers() == Qt::ControlModifier) && qAbs(event->angleDelta().y()) > qAbs(event->angleDelta().x())) { - zoomVertically(event->angleDelta().y(), event->position().y(), axes); + const bool isVertical = qAbs(zoomDelta.y()) > qAbs(zoomDelta.x()); + const bool isHorizontal = qAbs(zoomDelta.x()) > qAbs(zoomDelta.y()); + if (!(modifiers == Qt::ControlModifier) && isVertical) { + zoomVertically(zoomDelta.y(), position.y(), axes); + } else if (isHorizontal) { + zoomHorizontally(zoomDelta.x(), position.x(), isProperlyPaused, maxWindowSize, axes); } - else - { - zoomHorizontally(event->angleDelta().x(), event->position().x(), isProperlyPaused, maxWindowSize, axes); - } - } void DisplayControl::zoomVertically(const double delta, const double y, QCustomPlot *axes) diff --git a/Desktop_Interface/DisplayControl.h b/Desktop_Interface/DisplayControl.h index 5806df1a..f4379194 100644 --- a/Desktop_Interface/DisplayControl.h +++ b/Desktop_Interface/DisplayControl.h @@ -2,7 +2,6 @@ #include -class QWheelEvent; class QCustomPlot; class DisplayControl : public QObject @@ -19,7 +18,7 @@ class DisplayControl : public QObject double topRange = 2.5; double botRange = -0.5; - void setVoltageRange (QWheelEvent* event, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); + void setVoltageRange (const QPointF &position, const QPoint zoomDelta, const Qt::KeyboardModifiers modifiers, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); void zoomVertically(const double delta, const double y, QCustomPlot *axes); void zoomHorizontally(const double delta, const double x, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 2c698eda..55abbfe0 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -298,7 +298,7 @@ void isoDriver::setVoltageRange(QWheelEvent* event) bool isProperlyPaused = properlyPaused(); double maxWindowSize = fileModeEnabled ? daq_maxWindowSize : ((double)MAX_WINDOW_SIZE); - display.setVoltageRange(event, isProperlyPaused, maxWindowSize, axes); + display.setVoltageRange(event->position(), event->angleDelta(), event->modifiers(), isProperlyPaused, maxWindowSize, axes); if (!(event->modifiers() == Qt::ControlModifier)) if (autoGainEnabled && !isProperlyPaused) From d529080ca1c12e55ffdb3b0e3483f0b34822baf3 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:44:32 +0200 Subject: [PATCH 068/109] better naming --- Desktop_Interface/isodriver.cpp | 2 +- Desktop_Interface/isodriver.h | 2 +- Desktop_Interface/mainwindow.cpp | 2 +- Desktop_Interface/ui_files_desktop/mainwindow.ui | 4 ++-- Desktop_Interface/ui_files_mobile/mainwindow.ui | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 55abbfe0..081a3db6 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -291,7 +291,7 @@ void isoDriver::setVisible_CH2(bool visible){ axes->graph(1)->setVisible(visible); } -void isoDriver::setVoltageRange(QWheelEvent* event) +void isoDriver::onWheelEvent(QWheelEvent* event) { if(doNotTouchGraph && !fileModeEnabled) return; diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index b6030751..e8717a2b 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -219,7 +219,7 @@ class isoDriver : public QLabel void delayUpdated(double); void enableCursorGroup(bool); public slots: - void setVoltageRange(QWheelEvent *event); + void onWheelEvent(QWheelEvent *event); void timerTick(void); void pauseEnable_CH1(bool enabled); void pauseEnable_CH2(bool enabled); diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 35b40a7a..3d082b0e 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1652,7 +1652,7 @@ bool MainWindow::gestureFilter(QGestureEvent *event){ } else{ wheelEmu = new QWheelEvent(point, (embiggen ? 120 : -120), 0, 0, Qt::Vertical); } - ui->controller_iso->setVoltageRange(wheelEmu); + ui->controller_iso->onWheelEvent(wheelEmu); return true; } else { diff --git a/Desktop_Interface/ui_files_desktop/mainwindow.ui b/Desktop_Interface/ui_files_desktop/mainwindow.ui index d3e1f546..f7bbad58 100644 --- a/Desktop_Interface/ui_files_desktop/mainwindow.ui +++ b/Desktop_Interface/ui_files_desktop/mainwindow.ui @@ -2716,7 +2716,7 @@ multimeterREnabled(int) multimeterRMS(double) sendMultimeterLabel4(QString) - setVoltageRange(QWheelEvent*) + onWheelEvent(QWheelEvent*) pauseEnable_CH1(bool) pauseEnable_CH2(bool) startTimer() @@ -3283,7 +3283,7 @@ scopeAxes mouseWheel(QWheelEvent*) controller_iso - setVoltageRange(QWheelEvent*) + onWheelEvent(QWheelEvent*) 499 diff --git a/Desktop_Interface/ui_files_mobile/mainwindow.ui b/Desktop_Interface/ui_files_mobile/mainwindow.ui index c6b0b84b..9e35287d 100644 --- a/Desktop_Interface/ui_files_mobile/mainwindow.ui +++ b/Desktop_Interface/ui_files_mobile/mainwindow.ui @@ -2776,7 +2776,7 @@ multimeterRMS(double) sendMultimeterLabel4(QString) setWindow(int) - setVoltageRange(QWheelEvent*) + onWheelEvent(QWheelEvent*) pauseEnable_CH1(bool) pauseEnable_CH2(bool) startTimer() @@ -3381,7 +3381,7 @@ scopeAxes mouseWheel(QWheelEvent*) controller_iso - setVoltageRange(QWheelEvent*) + onWheelEvent(QWheelEvent*) 482 From 760f67bbc99f0f35f3ba640d2173b3a9a6682d79 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:44:40 +0200 Subject: [PATCH 069/109] goddamn pixel/angle delta --- Desktop_Interface/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 3d082b0e..8efa75a6 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1053,8 +1053,8 @@ static QWheelEvent createWheelEvent(const bool negative, const QPoint &point, co { return QWheelEvent(point, // pos QCursor::pos(), // globalpos - QPoint(0, negative ? -120 : 120), // pixelDelta - QPoint(0, negative ? -10 : 10), // angleDelta + QPoint(0, negative ? -0 : 0), // pixelDelta + QPoint(0, negative ? -120 : 120), // angleDelta Qt::NoButton, // buttons modifier, // keyboard modifiers Qt::NoScrollPhase, // scroll phase From a400004beb7fe7a9146373b5a6dec34d2648c72b Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:49:32 +0200 Subject: [PATCH 070/109] goddamn qt and stupid deprecations --- Desktop_Interface/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 8efa75a6..42b6d411 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1053,8 +1053,8 @@ static QWheelEvent createWheelEvent(const bool negative, const QPoint &point, co { return QWheelEvent(point, // pos QCursor::pos(), // globalpos - QPoint(0, negative ? -0 : 0), // pixelDelta - QPoint(0, negative ? -120 : 120), // angleDelta + QPoint(negative ? -0 : 0, 0), // pixelDelta + QPoint(negative ? -120 : 120, 0), // angleDelta Qt::NoButton, // buttons modifier, // keyboard modifiers Qt::NoScrollPhase, // scroll phase From 586622362736da5db0cbd4b681df92d04970a7a2 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 13:53:22 +0200 Subject: [PATCH 071/109] add some missing emits --- Desktop_Interface/DisplayControl.cpp | 8 ++++---- Desktop_Interface/isodriver.cpp | 24 ++++++++++++------------ Desktop_Interface/mainwindow.cpp | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Desktop_Interface/DisplayControl.cpp b/Desktop_Interface/DisplayControl.cpp index fa9fde94..681c513e 100644 --- a/Desktop_Interface/DisplayControl.cpp +++ b/Desktop_Interface/DisplayControl.cpp @@ -48,8 +48,8 @@ void DisplayControl::zoomVertically(const double delta, const double y, QCustomP botRange -= 0.01; } - topRangeUpdated(topRange); - botRangeUpdated(botRange); + emit topRangeUpdated(topRange); + emit botRangeUpdated(botRange); } void DisplayControl::zoomHorizontally(const double delta, const double x, bool isProperlyPaused, double maxWindowSize, QCustomPlot *axes) @@ -103,6 +103,6 @@ void DisplayControl::zoomHorizontally(const double delta, const double x, bool i qDebug() << window << delay; - delayUpdated(delay); - timeWindowUpdated(window); + emit delayUpdated(delay); + emit timeWindowUpdated(window); } diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 081a3db6..16cf1be4 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -327,7 +327,7 @@ void isoDriver::pauseEnable_CH1(bool enabled){ if(!properlyPaused()) { display.delay = 0; - delayUpdated(display.delay); + emit delayUpdated(display.delay); if (autoGainEnabled) autoGain(); } @@ -343,7 +343,7 @@ void isoDriver::pauseEnable_CH2(bool enabled){ if(!properlyPaused()){ display.delay = 0; - delayUpdated(display.delay); + emit delayUpdated(display.delay); if (autoGainEnabled) autoGain(); } @@ -358,7 +358,7 @@ void isoDriver::pauseEnable_multimeter(bool enabled){ if(!properlyPaused()) { display.delay = 0; - delayUpdated(display.delay); + emit delayUpdated(display.delay); } if (!enabled) { @@ -1123,24 +1123,24 @@ void isoDriver::setTopRange(double newTop) { // NOTE: Should this be clamped to 20? display.topRange = newTop; - topRangeUpdated(display.topRange); + emit topRangeUpdated(display.topRange); } void isoDriver::setBotRange(double newBot) { // NOTE: Should this be clamped to 20? display.botRange = newBot; - botRangeUpdated(display.botRange); + emit botRangeUpdated(display.botRange); } void isoDriver::setTimeWindow(double newWindow){ display.window = newWindow; - timeWindowUpdated(display.window); + emit timeWindowUpdated(display.window); } void isoDriver::setDelay(double newDelay){ display.delay = newDelay; - delayUpdated(display.delay); + emit delayUpdated(display.delay); } void isoDriver::takeSnapshot(QString *fileName, unsigned char channel){ @@ -1347,12 +1347,12 @@ void isoDriver::fileTimerTick(){ void isoDriver::enableFileMode(){ fileModeEnabled = true; daq_maxWindowSize = daqLoad_endTime - daqLoad_startTime; - showRealtimeButton(true); + emit showRealtimeButton(true); } void isoDriver::disableFileMode(){ fileModeEnabled = false; - showRealtimeButton(false); + emit showRealtimeButton(false); if(fileTimer != NULL){ fileTimer->stop(); } @@ -1362,17 +1362,17 @@ void isoDriver::disableFileMode(){ if (display.window > mws) { display.window = mws; - timeWindowUpdated(display.window); + emit timeWindowUpdated(display.window); } if ((display.window + display.delay) > mws) { display.delay -= display.window + display.delay - mws; - delayUpdated(display.delay); + emit delayUpdated(display.delay); } if (display.delay < 0) { display.delay = 0; - delayUpdated(display.delay); + emit delayUpdated(display.delay); } } diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 42b6d411..5490a52c 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1106,7 +1106,7 @@ void MainWindow::cycleDelayRight(){ qDebug() << "RIGHT"; ui->controller_iso->display.delay -= ui->controller_iso->display.window/10; if(ui->controller_iso->display.delay < 0) ui->controller_iso->display.delay = 0; - ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); + emit ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); } void MainWindow::cycleDelayLeft(){ @@ -1114,14 +1114,14 @@ void MainWindow::cycleDelayLeft(){ double mws = ui->controller_iso->fileModeEnabled ? ui->controller_iso->daq_maxWindowSize : ((double)MAX_WINDOW_SIZE); ui->controller_iso->display.delay += ui->controller_iso->display.window/10; if(ui->controller_iso->display.delay > (mws - ui->controller_iso->display.window)) ui->controller_iso->display.delay = (mws - ui->controller_iso->display.window); - ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); + emit ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); } void MainWindow::cycleDelayRight_large(){ qDebug() << "RIGHT"; ui->controller_iso->display.delay -= ui->controller_iso->display.window/2; if(ui->controller_iso->display.delay < 0) ui->controller_iso->display.delay = 0; - ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); + emit ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); } void MainWindow::cycleDelayLeft_large(){ @@ -1129,7 +1129,7 @@ void MainWindow::cycleDelayLeft_large(){ double mws = ui->controller_iso->fileModeEnabled ? ui->controller_iso->daq_maxWindowSize : ((double)MAX_WINDOW_SIZE); ui->controller_iso->display.delay += ui->controller_iso->display.window/2; if(ui->controller_iso->display.delay > (mws - ui->controller_iso->display.window)) ui->controller_iso->display.delay = (mws - ui->controller_iso->display.window); - ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); + emit ui->controller_iso->delayUpdated(ui->controller_iso->display.delay); } void MainWindow::enableLabradorDebugging(){ From 502c733900006f38bf27164df731ac5d34d541ee Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 14:10:49 +0200 Subject: [PATCH 072/109] fix some connections --- Desktop_Interface/mainwindow.cpp | 212 +++++++++++----------- Desktop_Interface/scoperangeenterdialog.h | 10 +- 2 files changed, 112 insertions(+), 110 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 5490a52c..455ebe8b 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -179,7 +179,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->menuAndroid_Special->menuAction()->setVisible(false); #endif - connect(ui->controller_iso->driver, SIGNAL(killMe()), this, SLOT(reinitUsb())); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::killMe, this, &MainWindow::reinitUsb); ui->console1->setMaximumBlockCount(MAX_CONSOLE_BLOCK_COUNT); ui->console2->setMaximumBlockCount(MAX_CONSOLE_BLOCK_COUNT); //ui->frequencyValue_CH2->setValue(369); @@ -191,32 +191,32 @@ MainWindow::MainWindow(QWidget *parent) : ui->multimeterRComboBox->setVisible(false); #endif - connect(ui->controller_iso, SIGNAL(multimeterREnabled(int)), this, SLOT(rSourceIndexChanged(int))); - connect(ui->controller_iso, SIGNAL(multimeterRMS(double)), ui->multimeterRmsDisplay, SLOT(display(double))); - connect(ui->controller_iso, SIGNAL(sendMultimeterLabel4(QString)), ui->multimeterRmsLabel, SLOT(setText(QString))); - connect(ui->controller_iso, SIGNAL(sendVRMS_CH1(double)), ui->voltageInfoRmsDisplay_CH1, SLOT(display(double))); - connect(ui->controller_iso, SIGNAL(sendVRMS_CH2(double)), ui->voltageInfoRmsDisplay_CH2, SLOT(display(double))); + connect(ui->controller_iso, &isoDriver::multimeterREnabled, this, &MainWindow::rSourceIndexChanged); + connect(ui->controller_iso, &isoDriver::multimeterRMS, ui->multimeterRmsDisplay, qOverload(&QLCDNumber::display)); + connect(ui->controller_iso, &isoDriver::sendMultimeterLabel4, ui->multimeterRmsLabel, &QLabel::setText); + connect(ui->controller_iso, &isoDriver::sendVRMS_CH1, ui->voltageInfoRmsDisplay_CH1, qOverload(&QLCDNumber::display)); + connect(ui->controller_iso, &isoDriver::sendVRMS_CH2, ui->voltageInfoRmsDisplay_CH2, qOverload(&QLCDNumber::display)); - connect(ui->controller_iso, SIGNAL(mainWindowPleaseDisableSerial(int)), this, SLOT(serialEmergencyDisable(int))); + connect(ui->controller_iso, &isoDriver::mainWindowPleaseDisableSerial, this, &MainWindow::serialEmergencyDisable); - connect(ui->controller_iso->driver, SIGNAL(signalFirmwareFlash(void)), ui->deviceConnected, SLOT(flashingFirmware(void))); - connect(ui->controller_iso->internalBuffer375_CH1, SIGNAL(fileIOinternalDisable()), this, SLOT(fileLimitReached_CH1())); - connect(ui->controller_iso->internalBuffer750, SIGNAL(fileIOinternalDisable()), this, SLOT(fileLimitReached_CH1())); - connect(ui->controller_iso->internalBuffer375_CH2, SIGNAL(fileIOinternalDisable()), this, SLOT(fileLimitReached_CH2())); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::signalFirmwareFlash, ui->deviceConnected, &deviceConnectedDisplay::flashingFirmware); + connect(ui->controller_iso->internalBuffer375_CH1, &isoBuffer::fileIOinternalDisable, this, &MainWindow::fileLimitReached_CH1); + connect(ui->controller_iso->internalBuffer750, &isoBuffer::fileIOinternalDisable, this, &MainWindow::fileLimitReached_CH1); + connect(ui->controller_iso->internalBuffer375_CH2, &isoBuffer::fileIOinternalDisable, this, &MainWindow::fileLimitReached_CH2); - connect(ui->controller_iso, SIGNAL(showRealtimeButton(bool)), ui->realTimeButton, SLOT(setVisible(bool))); - connect(ui->realTimeButton, SIGNAL(pressed()), ui->controller_iso, SLOT(disableFileMode())); + connect(ui->controller_iso, &isoDriver::showRealtimeButton, ui->realTimeButton, &QWidget::setVisible); + connect(ui->realTimeButton, &QAbstractButton::pressed, ui->controller_iso, &isoDriver::disableFileMode); - connect(ui->pausedLabeL_CH1, SIGNAL(toggled(bool)), this, SLOT(paused(bool))); - connect(ui->pausedLabel_CH2, SIGNAL(toggled(bool)), this, SLOT(paused(bool))); - connect(ui->pause_LA, SIGNAL(toggled(bool)), this, SLOT(paused(bool))); - connect(ui->multimeterPauseCheckBox, SIGNAL(toggled(bool)), this, SLOT(paused(bool))); + connect(ui->pausedLabeL_CH1, &QAbstractButton::toggled, this, &MainWindow::paused); + connect(ui->pausedLabel_CH2, &QAbstractButton::toggled, this, &MainWindow::paused); + connect(ui->pause_LA, &QAbstractButton::toggled, this, &MainWindow::paused); + connect(ui->multimeterPauseCheckBox, &QAbstractButton::toggled, this, &MainWindow::paused); #ifndef PLATFORM_ANDROID - connect(ui->hideCH1Box, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(hideCH1(bool))); - connect(ui->hideCH2Box, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(hideCH2(bool))); + connect(ui->hideCH1Box, &QAbstractButton::toggled, ui->controller_iso, &isoDriver::hideCH1); + connect(ui->hideCH2Box, &QAbstractButton::toggled, ui->controller_iso, &isoDriver::hideCH2); connect(ui->actionHexDisplay, &QAction::toggled, ui->controller_iso, &isoDriver::setHexDisplay_CH1); connect(ui->actionHexDisplay_2, &QAction::toggled, ui->controller_iso, &isoDriver::setHexDisplay_CH2); @@ -239,10 +239,10 @@ MainWindow::MainWindow(QWidget *parent) : } #ifndef PLATFORM_ANDROID - connect(ui->offsetSpinBox_CH1, SIGNAL(valueChanged(double)), ui->controller_iso, SLOT(offsetChanged_CH1(double))); - connect(ui->offsetSpinBox_CH2, SIGNAL(valueChanged(double)), ui->controller_iso, SLOT(offsetChanged_CH2(double))); - connect(ui->attenuationComboBox_CH1, SIGNAL(currentIndexChanged(int)), ui->controller_iso, SLOT(attenuationChanged_CH1(int))); - connect(ui->attenuationComboBox_CH2, SIGNAL(currentIndexChanged(int)), ui->controller_iso, SLOT(attenuationChanged_CH2(int))); + connect(ui->offsetSpinBox_CH1, qOverload(&QDoubleSpinBox::valueChanged), ui->controller_iso, &isoDriver::offsetChanged_CH1); + connect(ui->offsetSpinBox_CH2, qOverload(&QDoubleSpinBox::valueChanged), ui->controller_iso, &isoDriver::offsetChanged_CH2); + connect(ui->attenuationComboBox_CH1, qOverload(&QComboBox::currentIndexChanged), ui->controller_iso, &isoDriver::attenuationChanged_CH1); + connect(ui->attenuationComboBox_CH2, qOverload(&QComboBox::currentIndexChanged), ui->controller_iso, &isoDriver::attenuationChanged_CH2); #endif connect(ui->controller_iso, &isoDriver::enableCursorGroup, this, &MainWindow::cursorGroupEnabled); } @@ -418,18 +418,18 @@ void MainWindow::menuSetup(){ #endif - connect(ui->actionAutoV, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setAutoMultimeterV(bool))); - connect(ui->actionV, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceVolts(bool))); - connect(ui->actionMV, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceMillivolts(bool))); - connect(ui->actionAutoI, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setAutoMultimeterI(bool))); - connect(ui->actionA, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceAmps(bool))); - connect(ui->actionMA, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceMilliamps(bool))); - connect(ui->actionAutoR, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setAutoMultimeterR(bool))); - connect(ui->actionOhm, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceOhms(bool))); - connect(ui->actionKOhm, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceKiloOhms(bool))); - connect(ui->actionAutoC, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setAutoMultimeterC(bool))); - connect(ui->actionNF, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceNFarads(bool))); - connect(ui->action_F, SIGNAL(toggled(bool)), ui->controller_iso, SLOT(setForceUFarads(bool))); + connect(ui->actionAutoV, &QAction::toggled, ui->controller_iso, &isoDriver::setAutoMultimeterV); + connect(ui->actionV, &QAction::toggled, ui->controller_iso, &isoDriver::setForceVolts); + connect(ui->actionMV, &QAction::toggled, ui->controller_iso, &isoDriver::setForceMillivolts); + connect(ui->actionAutoI, &QAction::toggled, ui->controller_iso, &isoDriver::setAutoMultimeterI); + connect(ui->actionA, &QAction::toggled, ui->controller_iso, &isoDriver::setForceAmps); + connect(ui->actionMA, &QAction::toggled, ui->controller_iso, &isoDriver::setForceMilliamps); + connect(ui->actionAutoR, &QAction::toggled, ui->controller_iso, &isoDriver::setAutoMultimeterR); + connect(ui->actionOhm, &QAction::toggled, ui->controller_iso, &isoDriver::setForceOhms); + connect(ui->actionKOhm, &QAction::toggled, ui->controller_iso, &isoDriver::setForceKiloOhms); + connect(ui->actionAutoC, &QAction::toggled, ui->controller_iso, &isoDriver::setAutoMultimeterC); + connect(ui->actionNF, &QAction::toggled, ui->controller_iso, &isoDriver::setForceNFarads); + connect(ui->action_F, &QAction::toggled, ui->controller_iso, &isoDriver::setForceUFarads); uartBaudGroup_CH1 = new QActionGroup(this); @@ -553,17 +553,17 @@ void MainWindow::on_actionCursor_Stats_triggered(bool checked) } void MainWindow::connectDisplaySignals(){ - connect(ui->actionMax, SIGNAL(toggled(bool)), ui->voltageInfoMaxLabel_CH1, SLOT(setVisible(bool))); - connect(ui->actionMax, SIGNAL(toggled(bool)), ui->voltageInfoMaxDisplay_CH1, SLOT(setVisible(bool))); + connect(ui->actionMax, &QAction::toggled, ui->voltageInfoMaxLabel_CH1, &QWidget::setVisible); + connect(ui->actionMax, &QAction::toggled, ui->voltageInfoMaxDisplay_CH1, &QWidget::setVisible); - connect(ui->actionMin, SIGNAL(toggled(bool)), ui->voltageInfoMinLabel_CH1, SLOT(setVisible(bool))); - connect(ui->actionMin, SIGNAL(toggled(bool)), ui->voltageInfoMinDisplay_CH1, SLOT(setVisible(bool))); + connect(ui->actionMin, &QAction::toggled, ui->voltageInfoMinLabel_CH1, &QWidget::setVisible); + connect(ui->actionMin, &QAction::toggled, ui->voltageInfoMinDisplay_CH1, &QWidget::setVisible); - connect(ui->actionMean, SIGNAL(toggled(bool)), ui->VoltageInfoMeanLabel_CH1, SLOT(setVisible(bool))); - connect(ui->actionMean, SIGNAL(toggled(bool)), ui->voltageInfoMeanDisplay_CH1, SLOT(setVisible(bool))); + connect(ui->actionMean, &QAction::toggled, ui->VoltageInfoMeanLabel_CH1, &QWidget::setVisible); + connect(ui->actionMean, &QAction::toggled, ui->voltageInfoMeanDisplay_CH1, &QWidget::setVisible); - connect(ui->actionRMS, SIGNAL(toggled(bool)), ui->voltageInfoRmsLabel_CH1, SLOT(setVisible(bool))); - connect(ui->actionRMS, SIGNAL(toggled(bool)), ui->voltageInfoRmsDisplay_CH1, SLOT(setVisible(bool))); + connect(ui->actionRMS, &QAction::toggled, ui->voltageInfoRmsLabel_CH1, &QWidget::setVisible); + connect(ui->actionRMS, &QAction::toggled, ui->voltageInfoRmsDisplay_CH1, &QWidget::setVisible); ui->voltageInfoMaxLabel_CH1->setVisible(false); ui->voltageInfoMaxDisplay_CH1->setVisible(false); @@ -574,17 +574,17 @@ void MainWindow::connectDisplaySignals(){ ui->voltageInfoRmsLabel_CH1->setVisible(false); ui->voltageInfoRmsDisplay_CH1->setVisible(false); - connect(ui->actionMax_2, SIGNAL(toggled(bool)), ui->voltageInfoMaxLabel_CH2, SLOT(setVisible(bool))); - connect(ui->actionMax_2, SIGNAL(toggled(bool)), ui->voltageInfoMaxDisplay_CH2, SLOT(setVisible(bool))); + connect(ui->actionMax_2, &QAction::toggled, ui->voltageInfoMaxLabel_CH2, &QWidget::setVisible); + connect(ui->actionMax_2, &QAction::toggled, ui->voltageInfoMaxDisplay_CH2, &QWidget::setVisible); - connect(ui->actionMin_2, SIGNAL(toggled(bool)), ui->voltageInfoMinLabel_CH2, SLOT(setVisible(bool))); - connect(ui->actionMin_2, SIGNAL(toggled(bool)), ui->voltageInfoMinDisplay_CH2, SLOT(setVisible(bool))); + connect(ui->actionMin_2, &QAction::toggled, ui->voltageInfoMinLabel_CH2, &QWidget::setVisible); + connect(ui->actionMin_2, &QAction::toggled, ui->voltageInfoMinDisplay_CH2, &QWidget::setVisible); - connect(ui->actionMean_2, SIGNAL(toggled(bool)), ui->VoltageInfoMeanLabel_CH2, SLOT(setVisible(bool))); - connect(ui->actionMean_2, SIGNAL(toggled(bool)), ui->voltageInfoMeanDisplay_CH2, SLOT(setVisible(bool))); + connect(ui->actionMean_2, &QAction::toggled, ui->VoltageInfoMeanLabel_CH2, &QWidget::setVisible); + connect(ui->actionMean_2, &QAction::toggled, ui->voltageInfoMeanDisplay_CH2, &QWidget::setVisible); - connect(ui->actionRMS_2, SIGNAL(toggled(bool)), ui->voltageInfoRmsLabel_CH2, SLOT(setVisible(bool))); - connect(ui->actionRMS_2, SIGNAL(toggled(bool)), ui->voltageInfoRmsDisplay_CH2, SLOT(setVisible(bool))); + connect(ui->actionRMS_2, &QAction::toggled, ui->voltageInfoRmsLabel_CH2, &QWidget::setVisible); + connect(ui->actionRMS_2, &QAction::toggled, ui->voltageInfoRmsDisplay_CH2, &QWidget::setVisible); ui->voltageInfoMaxLabel_CH2->setVisible(false); ui->voltageInfoMaxDisplay_CH2->setVisible(false); @@ -1010,31 +1010,31 @@ void MainWindow::initShortcuts(){ shortcut_Esc = new QShortcut(QKeySequence("Esc"), this); - connect(shortcut_cycleBaudRate_CH1, SIGNAL(activated()), this, SLOT(cycleBaudRate_CH1())); - connect(shortcut_cycleBaudRateBackwards_CH1, SIGNAL(activated()), this, SLOT(cycleBaudRateBackwards_CH1())); - connect(shortcut_cycleBaudRate_CH2, SIGNAL(activated()), this, SLOT(cycleBaudRate_CH2())); - connect(shortcut_cycleBaudRateBackwards_CH2, SIGNAL(activated()), this, SLOT(cycleBaudRateBackwards_CH2())); - connect(shortcut_snapshot_CH1, SIGNAL(activated()), this, SLOT(on_actionSnapshot_CH1_triggered())); - connect(shortcut_snapshot_CH2, SIGNAL(activated()), this, SLOT(on_actionSnapshot_CH2_triggered())); + connect(shortcut_cycleBaudRate_CH1, &QShortcut::activated, this, &MainWindow::cycleBaudRate_CH1); + connect(shortcut_cycleBaudRateBackwards_CH1, &QShortcut::activated, this, &MainWindow::cycleBaudRateBackwards_CH1); + connect(shortcut_cycleBaudRate_CH2, &QShortcut::activated, this, &MainWindow::cycleBaudRate_CH2); + connect(shortcut_cycleBaudRateBackwards_CH2, &QShortcut::activated, this, &MainWindow::cycleBaudRateBackwards_CH2); + connect(shortcut_snapshot_CH1, &QShortcut::activated, this, &MainWindow::on_actionSnapshot_CH1_triggered); + connect(shortcut_snapshot_CH2, &QShortcut::activated, this, &MainWindow::on_actionSnapshot_CH2_triggered); - connect(shortcut_w, SIGNAL(activated()), this, SLOT(arrowUpTriggered())); - connect(shortcut_s, SIGNAL(activated()), this, SLOT(arrowDownTriggered())); - connect(shortcut_ctrlW, SIGNAL(activated()), this, SLOT(ctrlArrowUpTriggered())); - connect(shortcut_ctrlS, SIGNAL(activated()), this, SLOT(ctrlArrowDownTriggered())); + connect(shortcut_w, &QShortcut::activated, this, &MainWindow::arrowUpTriggered); + connect(shortcut_s, &QShortcut::activated, this, &MainWindow::arrowDownTriggered); + connect(shortcut_ctrlW, &QShortcut::activated, this, &MainWindow::ctrlArrowUpTriggered); + connect(shortcut_ctrlS, &QShortcut::activated, this, &MainWindow::ctrlArrowDownTriggered); - connect(shortcut_a, SIGNAL(activated()), this, SLOT(cycleDelayLeft())); - connect(shortcut_d, SIGNAL(activated()), this, SLOT(cycleDelayRight())); + connect(shortcut_a, &QShortcut::activated, this, &MainWindow::cycleDelayLeft); + connect(shortcut_d, &QShortcut::activated, this, &MainWindow::cycleDelayRight); - connect(shortcut_ArrowLeft, SIGNAL(activated()), this, SLOT(cycleDelayLeft())); - connect(shortcut_ArrowRight, SIGNAL(activated()), this, SLOT(cycleDelayRight())); - connect(shortcut_CtrlArrowLeft, SIGNAL(activated()), this, SLOT(cycleDelayLeft_large())); - connect(shortcut_CtrlArrowRight, SIGNAL(activated()), this, SLOT(cycleDelayRight_large())); + connect(shortcut_ArrowLeft, &QShortcut::activated, this, &MainWindow::cycleDelayLeft); + connect(shortcut_ArrowRight, &QShortcut::activated, this, &MainWindow::cycleDelayRight); + connect(shortcut_CtrlArrowLeft, &QShortcut::activated, this, &MainWindow::cycleDelayLeft_large); + connect(shortcut_CtrlArrowRight, &QShortcut::activated, this, &MainWindow::cycleDelayRight_large); - connect(shortcut_snapScopeToCursors, SIGNAL(activated()), this, SLOT(on_actionSnap_to_Cursors_triggered())); - connect(shortcut_manualRange, SIGNAL(activated()), this, SLOT(on_actionEnter_Manually_triggered())); + connect(shortcut_snapScopeToCursors, &QShortcut::activated, this, &MainWindow::on_actionSnap_to_Cursors_triggered); + connect(shortcut_manualRange, &QShortcut::activated, this, &MainWindow::on_actionEnter_Manually_triggered); - connect(shortcut_Debug, SIGNAL(activated()), this, SLOT(enableLabradorDebugging())); - connect(shortcut_Esc, SIGNAL(activated()), this, SLOT(reinitUsb())); + connect(shortcut_Debug, &QShortcut::activated, this, &MainWindow::enableLabradorDebugging); + connect(shortcut_Esc, &QShortcut::activated, this, &MainWindow::reinitUsb); } @@ -1231,10 +1231,10 @@ void MainWindow::on_actionEnter_Manually_triggered() ui->controller_iso->display.delay = 0; scopeRangeEnterDialog dialog(this, ui->controller_iso->display.topRange, ui->controller_iso->display.botRange, ui->controller_iso->display.window, ui->controller_iso->display.delay); dialog.setModal(true); - connect(&dialog, SIGNAL(yTopUpdated(double)), ui->controller_iso, SLOT(setTopRange(double))); - connect(&dialog, SIGNAL(yBotUpdated(double)), ui->controller_iso, SLOT(setBotRange(double))); - connect(&dialog, SIGNAL(windowUpdated(double)), ui->controller_iso, SLOT(setTimeWindow(double))); - connect(&dialog, SIGNAL(delayUpdated(double)), ui->controller_iso, SLOT(setDelay(double))); + connect(&dialog, &scopeRangeEnterDialog::yTopUpdated, ui->controller_iso, &isoDriver::setTopRange); + connect(&dialog, &scopeRangeEnterDialog::yBotUpdated, ui->controller_iso, &isoDriver::setBotRange); + connect(&dialog, &scopeRangeEnterDialog::windowUpdated, ui->controller_iso, &isoDriver::setTimeWindow); + connect(&dialog, &scopeRangeEnterDialog::delayUpdated, ui->controller_iso, &isoDriver::setDelay); dialog.exec(); } @@ -1327,39 +1327,39 @@ void MainWindow::reinitUsbStage2(void){ //Reconnect the other objects. //ui->controller_iso->driver->setBufferPtr(ui->bufferDisplay); - connect(ui->debugButton1, SIGNAL(clicked()), ui->controller_iso->driver, SLOT(avrDebug())); - connect(ui->debugButton3, SIGNAL(clicked()), ui->controller_iso->driver, SLOT(bootloaderJump())); - connect(ui->psuSlider, SIGNAL(voltageChanged(double)), ui->controller_iso->driver, SLOT(setPsu(double))); - connect(ui->controller_iso, SIGNAL(setGain(double)), ui->controller_iso->driver, SLOT(setGain(double))); + connect(ui->debugButton1, &QAbstractButton::clicked, ui->controller_iso->driver.data(), &genericUsbDriver::avrDebug); + connect(ui->debugButton3, &QAbstractButton::clicked, ui->controller_iso->driver.data(), &genericUsbDriver::bootloaderJump); + connect(ui->psuSlider, &espoSlider::voltageChanged, ui->controller_iso->driver.data(), &genericUsbDriver::setPsu); + connect(ui->controller_iso, &isoDriver::setGain, ui->controller_iso->driver.data(), &genericUsbDriver::setGain); connect(ui->controller_fg, &functionGenControl::functionGenToUpdate, ui->controller_iso->driver, &genericUsbDriver::setFunctionGen); - connect(ui->bufferDisplay, SIGNAL(modeChange(int)), ui->controller_iso->driver, SLOT(setDeviceMode(int))); + connect(ui->bufferDisplay, &bufferControl::modeChange, ui->controller_iso->driver.data(), &genericUsbDriver::setDeviceMode); connect(ui->bufferDisplay, &bufferControl::modeChange, this, [this](){ // Force a trigger refresh ui->controller_iso->setTriggerLevel(ui->triggerLevelValue->value()); }); - connect(ui->bufferDisplay, SIGNAL(updateDig(int)), ui->controller_iso->driver, SLOT(newDig(int))); + connect(ui->bufferDisplay, &bufferControl::updateDig, ui->controller_iso->driver.data(), &genericUsbDriver::newDig); //Set the settings again! - connect(ui->controller_iso->driver, SIGNAL(gainBuffers(double)), ui->controller_iso, SLOT(gainBuffers(double))); - connect(ui->controller_iso->driver, SIGNAL(disableWindow(bool)), this, SLOT(setEnabled(bool))); - connect(ui->controller_iso->driver, SIGNAL(sendClearBuffer(bool,bool,bool)), ui->controller_iso, SLOT(clearChannelBuffers(bool,bool,bool))); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::gainBuffers, ui->controller_iso, &isoDriver::gainBuffers); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::disableWindow, this, &QWidget::setEnabled); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::sendClearBuffer, ui->controller_iso, &isoDriver::clearChannelBuffers); //connect(ui->controller_iso->driver, SIGNAL(startIsoTimer()), ui->controller_iso, SLOT(startTimer())); - connect(ui->controller_iso->driver, SIGNAL(setVisible_CH2(bool)), ui->controller_iso, SLOT(setVisible_CH2(bool))); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::setVisible_CH2, ui->controller_iso, &isoDriver::setVisible_CH2); //connect(ui->controller_iso->driver, SIGNAL(enableMMTimer()), ui->controller_iso, SLOT(enableMM())); - connect(ui->controller_iso->driver, SIGNAL(checkXY(bool)), ui->xyDisplayLabel, SLOT(setChecked(bool))); - connect(ui->controller_iso->driver, SIGNAL(disableWindow(bool)), ui->deviceConnected, SLOT(connectedStatusChanged(bool))); - connect(ui->controller_iso->driver, SIGNAL(upTick()), ui->controller_iso, SLOT(timerTick())); - connect(ui->controller_iso->driver, SIGNAL(killMe()), this, SLOT(reinitUsb())); - connect(ui->controller_iso->driver, SIGNAL(connectedStatus(bool)), ui->deviceConnected, SLOT(connectedStatusChanged(bool))); - connect(ui->controller_iso->driver, SIGNAL(signalFirmwareFlash(void)), ui->deviceConnected, SLOT(flashingFirmware(void))); - connect(ui->controller_iso->driver, SIGNAL(initialConnectComplete()), this, SLOT(resetUsbState())); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::checkXY, ui->xyDisplayLabel, &QAbstractButton::setChecked); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::disableWindow, ui->deviceConnected, &deviceConnectedDisplay::connectedStatusChanged); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::upTick, ui->controller_iso, &isoDriver::timerTick); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::killMe, this, &MainWindow::reinitUsb); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::connectedStatus, ui->deviceConnected, &deviceConnectedDisplay::connectedStatusChanged); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::signalFirmwareFlash, ui->deviceConnected, &deviceConnectedDisplay::flashingFirmware); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::initialConnectComplete, this, &MainWindow::resetUsbState); ui->controller_iso->driver->setGain(reinitScopeGain); ui->controller_iso->driver->psu_offset = psu_voltage_calibration_offset; readSettingsFile(); ui->controller_iso->driver->calibrateOnConnect = (dt_userWantsToCalibrate == 16384); //Yes/No are 16384/65536 for some reason. I think 0/1 are reserved for OK/Cancel. - connect(ui->controller_iso->driver, SIGNAL(calibrateMe()), this, SLOT(on_actionCalibrate_triggered())); + connect(ui->controller_iso->driver.data(), &genericUsbDriver::calibrateMe, this, &MainWindow::on_actionCalibrate_triggered); qDebug() << "ReinitUsbStage2 is returning"; } @@ -2103,9 +2103,9 @@ void MainWindow::on_actionDAQ_Settings_triggered() qDebug() << "on_actionDAQ_Settings_triggered()"; daqForm df(this, daq_num_to_average, daq_max_file_size); df.setModal(true); - connect(&df, SIGNAL(updatedAveraging(int)), this, SLOT(daq_updatedAveraging(int))); - connect(&df, SIGNAL(updatedMaxFileSize(qulonglong)), this, SLOT(daq_updatedMaxFileSize(qulonglong))); - connect(&df, SIGNAL(saveButtonPressed()), this, SLOT(daq_saveButtonPressed())); + connect(&df, &daqForm::updatedAveraging, this, &MainWindow::daq_updatedAveraging); + connect(&df, &daqForm::updatedMaxFileSize, this, &MainWindow::daq_updatedMaxFileSize); + connect(&df, &daqForm::saveButtonPressed, this, &MainWindow::daq_saveButtonPressed); df.exec(); } @@ -2259,7 +2259,7 @@ void MainWindow::on_actionCalibrate_2_triggered() ui->multimeterPauseCheckBox->setChecked(false); ui->controller_iso->setAutoGain(false); - ui->controller_iso->setGain(4); + emit ui->controller_iso->setGain(4); //Remove the offset before setting it again; don't want them to stack! ui->controller_iso->driver->psu_offset = 0; @@ -2288,7 +2288,7 @@ void MainWindow::calibrate_psu_stage2() calibrationMessages->exec(); return; } - ui->controller_iso->setGain(1); + emit ui->controller_iso->setGain(1); ui->controller_iso->driver->setPsu(10); ui->controller_iso->clearBuffers(isoDriver::Channel3751 | isoDriver::Channel3752 | isoDriver::Channel750); QTimer::singleShot(1800, this, SLOT(calibrate_psu_stage3())); @@ -2342,16 +2342,16 @@ void MainWindow::on_actionShow_Range_Dialog_on_Main_Page_triggered(bool checked) scopeRangeSwitch = new scopeRangeEnterDialog(nullptr, false, ui->controller_iso->display.topRange, ui->controller_iso->display.botRange, ui->controller_iso->display.window, ui->controller_iso->display.delay); scopeRangeSwitch->setWindowFlags(Qt::Widget); ui->verticalLayout_5->insertWidget(2, scopeRangeSwitch); - connect(scopeRangeSwitch, SIGNAL(yTopUpdated(double)), ui->controller_iso, SLOT(setTopRange(double))); - connect(scopeRangeSwitch, SIGNAL(yBotUpdated(double)), ui->controller_iso, SLOT(setBotRange(double))); - connect(scopeRangeSwitch, SIGNAL(windowUpdated(double)), ui->controller_iso, SLOT(setTimeWindow(double))); - connect(scopeRangeSwitch, SIGNAL(delayUpdated(double)), ui->controller_iso, SLOT(setDelay(double))); + connect(scopeRangeSwitch, &scopeRangeEnterDialog::yTopUpdated, ui->controller_iso, &isoDriver::setTopRange); + connect(scopeRangeSwitch, &scopeRangeEnterDialog::yBotUpdated, ui->controller_iso, &isoDriver::setBotRange); + connect(scopeRangeSwitch, &scopeRangeEnterDialog::windowUpdated, ui->controller_iso, &isoDriver::setTimeWindow); + connect(scopeRangeSwitch, &scopeRangeEnterDialog::delayUpdated, ui->controller_iso, &isoDriver::setDelay); connect(scopeRangeSwitch, &scopeRangeEnterDialog::autoClicked, this, &MainWindow::on_setAutoScopeRange); - connect(ui->controller_iso, SIGNAL(topRangeUpdated(double)), scopeRangeSwitch, SLOT(yTopChanged(double))); - connect(ui->controller_iso, SIGNAL(botRangeUpdated(double)), scopeRangeSwitch, SLOT(yBotChanged(double))); - connect(ui->controller_iso, SIGNAL(timeWindowUpdated(double)), scopeRangeSwitch, SLOT(windowChanged(double))); - connect(ui->controller_iso, SIGNAL(delayUpdated(double)), scopeRangeSwitch, SLOT(delayChanged(double))); + connect(ui->controller_iso, &isoDriver::topRangeUpdated, scopeRangeSwitch, &scopeRangeEnterDialog::yTopChanged); + connect(ui->controller_iso, &isoDriver::botRangeUpdated, scopeRangeSwitch, &scopeRangeEnterDialog::yBotChanged); + connect(ui->controller_iso, &isoDriver::timeWindowUpdated, scopeRangeSwitch, &scopeRangeEnterDialog::windowChanged); + connect(ui->controller_iso, &isoDriver::delayUpdated, scopeRangeSwitch, &scopeRangeEnterDialog::delayChanged); } qDebug() << "on_actionShow_Range_Dialog_on_Main_Page_triggered" << checked; settings->setValue("ShowRangeDialog", checked); diff --git a/Desktop_Interface/scoperangeenterdialog.h b/Desktop_Interface/scoperangeenterdialog.h index e4f13aae..c7f9f271 100644 --- a/Desktop_Interface/scoperangeenterdialog.h +++ b/Desktop_Interface/scoperangeenterdialog.h @@ -20,6 +20,12 @@ class scopeRangeEnterDialog : public QDialog explicit scopeRangeEnterDialog(QWidget *parent = 0, bool buttonVisible = true, double yTop = 20, double yBot = -20, double window = -10, double delay = 0); ~scopeRangeEnterDialog(); +public slots: + void yTopChanged(double val); + void yBotChanged(double val); + void windowChanged(double val); + void delayChanged(double val); + private: Ui::scopeRangeEnterDialog *ui; double delay = 130065506; @@ -39,10 +45,6 @@ private slots: void toUpdateYBot(double val); void toUpdateWindow(double val); void toUpdateDelay(double val); - void yTopChanged(double val); - void yBotChanged(double val); - void windowChanged(double val); - void delayChanged(double val); }; #endif // SCOPERANGEENTERDIALOG_H From d76ee6b3b715df85cebf57fc229b859bdd61ed28 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 14:15:36 +0200 Subject: [PATCH 073/109] last of the connects (except the android code) --- Desktop_Interface/daqform.cpp | 14 +++++++------- Desktop_Interface/daqloadprompt.cpp | 4 ++-- Desktop_Interface/genericusbdriver.cpp | 6 +++--- Desktop_Interface/isodriver.cpp | 8 ++++---- Desktop_Interface/scoperangeenterdialog.cpp | 2 +- Desktop_Interface/ui_elements/timedtickbox.cpp | 2 +- Desktop_Interface/unixusbdriver.cpp | 4 ++-- Desktop_Interface/winusbdriver.cpp | 2 +- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Desktop_Interface/daqform.cpp b/Desktop_Interface/daqform.cpp index afaa083d..c3372945 100644 --- a/Desktop_Interface/daqform.cpp +++ b/Desktop_Interface/daqform.cpp @@ -26,17 +26,17 @@ daqForm::daqForm(QWidget *parent, int initialAverage, qulonglong initialMaxFileS updateLabels(); //Internal Signal Connect //Changed values - connect(ui->fileSizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateValues())); - connect(ui->numberOfPointsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateValues())); - connect(ui->limitFileSizeGroupBox, SIGNAL(toggled(bool)), this, SLOT(updateValues())); - connect(ui->sampleAveragingGroup, SIGNAL(toggled(bool)), this, SLOT(updateValues())); + connect(ui->fileSizeSpinBox, &QSpinBox::textChanged, this, &daqForm::updateValues); + connect(ui->numberOfPointsSpinBox, &QSpinBox::textChanged, this, &daqForm::updateValues); + connect(ui->limitFileSizeGroupBox, &QGroupBox::toggled, this, &daqForm::updateValues); + connect(ui->sampleAveragingGroup, &QGroupBox::toggled, this, &daqForm::updateValues); //Label - connect(ui->numberOfPointsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateLabels())); - connect(ui->fileSizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateLabels())); + connect(ui->numberOfPointsSpinBox, &QSpinBox::textChanged, this, &daqForm::updateLabels); + connect(ui->fileSizeSpinBox, &QSpinBox::textChanged, this, &daqForm::updateLabels); //Save Button - connect(ui->saveAsDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(trigger_saveButtonPressed())); + connect(ui->saveAsDefaultsButton, &QAbstractButton::clicked, this, &daqForm::trigger_saveButtonPressed); } diff --git a/Desktop_Interface/daqloadprompt.cpp b/Desktop_Interface/daqloadprompt.cpp index e06d7522..7b6fd295 100644 --- a/Desktop_Interface/daqloadprompt.cpp +++ b/Desktop_Interface/daqloadprompt.cpp @@ -18,8 +18,8 @@ daqLoadPrompt::daqLoadPrompt(QWidget *parent, double minTime, double maxTime) : ui->endTimeDoubleSpinBox->setValue(maxTime); //Internal signals - connect(ui->startTimeDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(valueChange())); - connect(ui->endTimeDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(valueChange())); + connect(ui->startTimeDoubleSpinBox, &QDoubleSpinBox::textChanged, this, &daqLoadPrompt::valueChange); + connect(ui->endTimeDoubleSpinBox, &QDoubleSpinBox::textChanged, this, &daqLoadPrompt::valueChange); valueChange(); } diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 1510d76f..b9f055e4 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -78,7 +78,7 @@ genericUsbDriver::genericUsbDriver(QWidget *parent) : QLabel(parent) connectTimer = new QTimer(this); connectTimer->setTimerType(Qt::PreciseTimer); connectTimer->start(USB_RECONNECT_PERIOD); - connect(connectTimer, SIGNAL(timeout()), this, SLOT(checkConnection())); + connect(connectTimer.data(), &QTimer::timeout, this, &genericUsbDriver::checkConnection); qDebug()<< "Generic Usb Driver setup complete"; messageBox = new QMessageBox(); } @@ -495,14 +495,14 @@ void genericUsbDriver::checkConnection(){ psuTimer = new QTimer(); psuTimer->setTimerType(Qt::PreciseTimer); psuTimer->start(PSU_PERIOD); - connect(psuTimer, SIGNAL(timeout()), this, SLOT(psuTick())); + connect(psuTimer, &QTimer::timeout, this, &genericUsbDriver::psuTick); if(killOnConnect) usbSendControl(0x40, 0xa7, 0, 0, 0, nullptr); recoveryTimer = new QTimer(); recoveryTimer->setTimerType(Qt::PreciseTimer); recoveryTimer->start(RECOVERY_PERIOD); - connect(recoveryTimer, SIGNAL(timeout()), this, SLOT(recoveryTick())); + connect(recoveryTimer.data(), &QTimer::timeout, this, &genericUsbDriver::recoveryTick); initialConnectComplete(); if(!killOnConnect && calibrateOnConnect){ diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 16cf1be4..84086b9e 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -33,7 +33,7 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) slowTimer = new QTimer(this); slowTimer->setTimerType(Qt::PreciseTimer); slowTimer->start(MULTIMETER_PERIOD); - connect(slowTimer, SIGNAL(timeout()), this, SLOT(slowTimerTick())); + connect(slowTimer, &QTimer::timeout, this, &isoDriver::slowTimerTick); } isoDriver::~isoDriver() @@ -1252,8 +1252,8 @@ void isoDriver::loadFileBuffer(QFile *fileToLoad){ qDebug() << "maxTime =" << maxTime; daqLoadPrompt dlp(this, minTime, maxTime); - connect(&dlp, SIGNAL(startTime(double)), this, SLOT(daqLoad_startChanged(double))); - connect(&dlp, SIGNAL(endTime(double)), this, SLOT(daqLoad_endChanged(double))); + connect(&dlp, &daqLoadPrompt::startTime, this, &isoDriver::daqLoad_startChanged); + connect(&dlp, &daqLoadPrompt::endTime, this, &isoDriver::daqLoad_endChanged); //Defaults daqLoad_startTime = minTime; @@ -1323,7 +1323,7 @@ void isoDriver::loadFileBuffer(QFile *fileToLoad){ fileTimer = new QTimer(); fileTimer->setTimerType(Qt::PreciseTimer); fileTimer->start(TIMER_PERIOD); - connect(fileTimer, SIGNAL(timeout()), this, SLOT(fileTimerTick())); + connect(fileTimer, &QTimer::timeout, this, &isoDriver::fileTimerTick); qDebug() << "File Buffer loaded!"; enableFileMode(); qDebug() << "File Mode Enabled"; diff --git a/Desktop_Interface/scoperangeenterdialog.cpp b/Desktop_Interface/scoperangeenterdialog.cpp index 647e8be2..1329a65c 100644 --- a/Desktop_Interface/scoperangeenterdialog.cpp +++ b/Desktop_Interface/scoperangeenterdialog.cpp @@ -27,7 +27,7 @@ scopeRangeEnterDialog::scopeRangeEnterDialog(QWidget *parent, bool buttonVisible for (espoSpinBox* spinBox : {ui->vMaxBox, ui->vMinBox, ui->timeWindowBox, ui->delayBox}) { spinBox->changeStepping(spinBox->value()); - connect(spinBox, SIGNAL(valueChanged(double)), spinBox, SLOT(changeStepping(double))); + connect(spinBox, qOverload(&QDoubleSpinBox::valueChanged), spinBox, &espoSpinBox::changeStepping); } } diff --git a/Desktop_Interface/ui_elements/timedtickbox.cpp b/Desktop_Interface/ui_elements/timedtickbox.cpp index f3f88b8b..5ec972bf 100644 --- a/Desktop_Interface/ui_elements/timedtickbox.cpp +++ b/Desktop_Interface/ui_elements/timedtickbox.cpp @@ -5,7 +5,7 @@ timedTickBox::timedTickBox(QWidget *parent) : QCheckBox(parent) timer = new QTimer(this); timer->setTimerType(Qt::PreciseTimer); timer->start(timerLength); - connect(timer, SIGNAL(timeout()), this, SLOT(timerTick())); + connect(timer, &QTimer::timeout, this, &timedTickBox::timerTick); } diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 808bc7f0..14a6be9f 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -166,7 +166,7 @@ int unixUsbDriver::usbIsoInit(void){ isoTimer = new QTimer(this); isoTimer->setTimerType(Qt::PreciseTimer); isoTimer->start(ISO_TIMER_PERIOD); - connect(isoTimer, SIGNAL(timeout()), this, SLOT(isoTimerTick())); + connect(isoTimer.data(), &QTimer::timeout, this, &unixUsbDriver::isoTimerTick); qDebug() << "Setup successful!"; @@ -175,7 +175,7 @@ int unixUsbDriver::usbIsoInit(void){ isoHandler->ctx = ctx; isoHandler->moveToThread(workerThread); - connect(workerThread, SIGNAL(started()), isoHandler, SLOT(handle())); + connect(workerThread, &QThread::started, isoHandler, &worker::handle); workerThread->start(); diff --git a/Desktop_Interface/winusbdriver.cpp b/Desktop_Interface/winusbdriver.cpp index b3fc4ed3..c8983339 100644 --- a/Desktop_Interface/winusbdriver.cpp +++ b/Desktop_Interface/winusbdriver.cpp @@ -197,7 +197,7 @@ int winUsbDriver::usbIsoInit(void){ isoTimer = new QTimer(); isoTimer->setTimerType(Qt::PreciseTimer); isoTimer->start(ISO_TIMER_PERIOD); - connect(isoTimer, SIGNAL(timeout()), this, SLOT(isoTimerTick())); + connect(isoTimer.data(), &QTimer::timeout, this, &winUsbDriver::isoTimerTick); qDebug() << "Setup successful!"; return 0; From 685bfecfc3c5424a4e1122800eb65f459bd73ed2 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 14:27:09 +0200 Subject: [PATCH 074/109] fix cursor text with QCP 2 --- Desktop_Interface/isodriver.cpp | 9 ++------- Desktop_Interface/isodriver.h | 4 +--- Desktop_Interface/mainwindow.cpp | 5 ++++- Desktop_Interface/mainwindow.h | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 84086b9e..83860a8b 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -476,9 +476,7 @@ void isoDriver::cursorEnableVert(bool enabled){ void isoDriver::udateCursors(void){ if(!(vertCursorEnabled || horiCursorEnabled)){ -#if QCP_VER == 1 cursorTextPtr->setVisible(false); -#endif return; } @@ -512,12 +510,11 @@ void isoDriver::udateCursors(void){ axes->graph(4)->setData(hori0x, hori0y); axes->graph(5)->setData(hori1x, hori1y); } -#if QCP_VER == 1 + cursorTextPtr->setVisible(cursorStatsEnabled); -#endif + if (!cursorStatsEnabled) return; - // shouldn't this be moved into the QCP_VER == 1? QString cursorStatsString; v0->value = display.y0; @@ -534,9 +531,7 @@ void isoDriver::udateCursors(void){ if(horiCursorEnabled) cursorStatsString.append(temp_hori); if(horiCursorEnabled && vertCursorEnabled) cursorStatsString.append("\n"); if(vertCursorEnabled) cursorStatsString.append(temp_vert); -#if QCP_VER == 1 cursorTextPtr->setText(cursorStatsString); -#endif } short isoDriver::reverseFrontEnd(double voltage){ diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index e8717a2b..fbc9f419 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -5,7 +5,7 @@ #include #include #include -#include "qcustomplot.h" +#include #include "genericusbdriver.h" #include "desktop_settings.h" #include "siprint.h" @@ -56,9 +56,7 @@ class isoDriver : public QLabel isoBuffer *internalBuffer375_CH2; isoBuffer *internalBuffer750; isoBuffer_file *internalBufferFile = NULL; -#if QCP_VER == 1 QCPItemText *cursorTextPtr; -#endif QPointer driver; bool doNotTouchGraph = true; double ch1_ref = 1.65; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 455ebe8b..c245584c 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -268,9 +268,11 @@ void MainWindow::initialisePlot() ui->scopeAxes->addGraph(); ui->scopeAxes->addGraph(); -#if QCP_VER == 1 textLabel = new QCPItemText(ui->scopeAxes); +#if QCP_VER == 1 ui->scopeAxes->addItem(textLabel); +#endif + textLabel->setPositionAlignment(Qt::AlignTop|Qt::AlignRight); textLabel->position->setType(QCPItemPosition::ptAxisRectRatio); textLabel->position->setCoords(0.99, 0); // place position at center/top of axis rect @@ -283,6 +285,7 @@ void MainWindow::initialisePlot() textLabel->setVisible(false); ui->controller_iso->cursorTextPtr = textLabel; +#if QCP_VER == 1 ui->scopeAxes->yAxis->setAutoTickCount(9); ui->scopeAxes->xAxis->setAutoTickCount(9); #endif diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index b1b3c134..abe5ccf1 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -16,7 +16,7 @@ #include #include -#include "qcustomplot.h" +#include #include "platformspecific.h" #include "qcustomplot.h" From cb86884595b9f90cdfbf51d04dcac7eb3e1b0db9 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 14:27:32 +0200 Subject: [PATCH 075/109] fix build with system version of QCP --- Desktop_Interface/Labrador.pro | 19 +++++++++++-------- Desktop_Interface/ui_elements.pri | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index 5478a39f..904f5890 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -18,8 +18,17 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport TARGET = Labrador TEMPLATE = app -BUILTIN_QCP = true -QCP_VER = 1 + +!contains(CONFIG, "USE_SYSTEM_QCP") { + message("Using bundled version of qcustomplot") + QCP_VER = 1 +} else { + message("Using system version of qcustomplot") + DEFINES += QCUSTOMPLOT_USE_OPENGL + QCP_VER = 2 + LIBS += -lqcustomplot +} + DEFINES += "QCP_VER=$${QCP_VER}" equals(QCP_VER,"2"){ @@ -27,12 +36,6 @@ equals(QCP_VER,"2"){ win32: LIBS += -lOpenGL32 message("Using QCP2 with OpenGL support") } -equals(BUILTIN_QCP,"true"){ - message("Using bundled version of qcustomplot") -} else { - message("Using system version of qcustomplot") - LIBS += -lqcustomplot -} include(ui_elements.pri) diff --git a/Desktop_Interface/ui_elements.pri b/Desktop_Interface/ui_elements.pri index ea7e2616..446c846d 100644 --- a/Desktop_Interface/ui_elements.pri +++ b/Desktop_Interface/ui_elements.pri @@ -1,7 +1,7 @@ @INCLUDEPATH += $$PWD/ui_elements @DEPENDPATH += $$PWD/ui_elements -equals(BUILTIN_QCP,"true"){ +!contains(CONFIG, "USE_SYSTEM_QCP") { INCLUDEPATH += $$PWD/ui_elements/qcp$${QCP_VER} DEPENDPATH += $$PWD/ui_elements/qcp$${QCP_VER} SOURCES += ui_elements/qcp$${QCP_VER}/qcustomplot.cpp From 3f5f8a99ac0e90daa816dbac60c82bda2c66fb3b Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 14:48:23 +0200 Subject: [PATCH 076/109] start porting stuff away from shitty STL APIs so it is possible to debug --- Desktop_Interface/isobuffer.cpp | 44 ++++++++++----------------------- Desktop_Interface/isobuffer.h | 2 +- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 49e4e18f..6e5a120d 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -395,50 +395,32 @@ void isoBuffer::checkTriggered() double isoBuffer::getDelayedTriggerPoint(double delay) { - if (m_triggerPositionList.size() == 0) + if (m_triggerPositionList.isEmpty()) { return 0; + } + // Holy fuck the STL APIs suck, + // TODO: port to something sane that is readable + const uint32_t delaySamples = delay * m_samplesPerSecond; - auto isValid = [=](uint32_t index)->bool + QVector::reverse_iterator it = std::find_if(m_triggerPositionList.rbegin(), m_triggerPositionList.rend(), [=](uint32_t index) { if (m_back > delaySamples) return (index < m_back - delaySamples) || (index >= m_back); else return (index < m_bufferLen + m_back - delaySamples) && (index >= m_back); - }; + }); - auto getDelay = [=](uint32_t index)->double + if (it != m_triggerPositionList.rend()) { + // NOTE: vector::erase does not remove the element pointed to by the second iterator. + m_triggerPositionList.erase(m_triggerPositionList.begin(), std::prev(it.base())); + const uint32_t index = m_triggerPositionList[0]; if (m_back > index) - return (m_back - index) / static_cast(m_samplesPerSecond); + return (m_back - index) / double(m_samplesPerSecond); else - return (m_bufferLen + (m_back-1) - index) / static_cast(m_samplesPerSecond); - }; - - // Like std::find_if but returns the last element matching the predicate instead of the first one - // TODO: Move this elsewhere (maybe a utils / algorithms file??) - // requires first and last to be Bidirectional iters, and form a valid range - // requires p to be a valid unaryPredicate - // Full signature would be: - // template - // It find_last_if(It begin, It end, Predicate p) - auto find_last_if = [](auto begin, auto end, auto p) - { - using It = decltype(begin); // TODO: remove this line once this is a proper function - std::reverse_iterator rlast(begin), rfirst(end); - auto found = std::find_if(rfirst, rlast, p); - return found == rlast - ? end - : std::prev(found.base()); - }; - - auto it = find_last_if(m_triggerPositionList.begin(), m_triggerPositionList.end(), isValid); - if (it != m_triggerPositionList.end()) - { - // NOTE: vector::erase does not remove the element pointed to by the second iterator. - m_triggerPositionList.erase(m_triggerPositionList.begin(), it); - return getDelay(m_triggerPositionList[0]); + return (m_bufferLen + (m_back-1) - index) / double(m_samplesPerSecond); } return 0; diff --git a/Desktop_Interface/isobuffer.h b/Desktop_Interface/isobuffer.h index 07c506d2..ea70c453 100644 --- a/Desktop_Interface/isobuffer.h +++ b/Desktop_Interface/isobuffer.h @@ -113,7 +113,7 @@ class isoBuffer : public QWidget TriggerSeekState m_triggerSeekState = TriggerSeekState::BelowTriggerLevel; short m_triggerLevel = 0; short m_triggerSensitivity = 0; - std::vector m_triggerPositionList = {}; + QVector m_triggerPositionList = {}; // UARTS decoding uartStyleDecoder* m_decoder = NULL; bool m_isDecoding = true; From 8cc496ecc543eec6a04eab66a241da808e31dc3e Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 15:16:16 +0200 Subject: [PATCH 077/109] clang-tidy --- Desktop_Interface/isobuffer.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 6e5a120d..072773b8 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -406,10 +406,11 @@ double isoBuffer::getDelayedTriggerPoint(double delay) QVector::reverse_iterator it = std::find_if(m_triggerPositionList.rbegin(), m_triggerPositionList.rend(), [=](uint32_t index) { - if (m_back > delaySamples) + if (m_back > delaySamples) { return (index < m_back - delaySamples) || (index >= m_back); - else - return (index < m_bufferLen + m_back - delaySamples) && (index >= m_back); + } + + return (index < m_bufferLen + m_back - delaySamples) && (index >= m_back); }); if (it != m_triggerPositionList.rend()) @@ -417,10 +418,12 @@ double isoBuffer::getDelayedTriggerPoint(double delay) // NOTE: vector::erase does not remove the element pointed to by the second iterator. m_triggerPositionList.erase(m_triggerPositionList.begin(), std::prev(it.base())); const uint32_t index = m_triggerPositionList[0]; - if (m_back > index) + + if (m_back > index) { return (m_back - index) / double(m_samplesPerSecond); - else - return (m_bufferLen + (m_back-1) - index) / double(m_samplesPerSecond); + } + + return (m_bufferLen + (m_back-1) - index) / double(m_samplesPerSecond); } return 0; From 1062db63dd628bbb73354d675592ab9ef16a758e Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 15:20:44 +0200 Subject: [PATCH 078/109] fix spin boxes not updating --- Desktop_Interface/isodriver.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 83860a8b..b65489c4 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -34,6 +34,11 @@ isoDriver::isoDriver(QWidget *parent) : QLabel(parent) slowTimer->setTimerType(Qt::PreciseTimer); slowTimer->start(MULTIMETER_PERIOD); connect(slowTimer, &QTimer::timeout, this, &isoDriver::slowTimerTick); + + connect(&display, &DisplayControl::delayUpdated, this, &isoDriver::delayUpdated); + connect(&display, &DisplayControl::timeWindowUpdated, this, &isoDriver::timeWindowUpdated); + connect(&display, &DisplayControl::topRangeUpdated, this, &isoDriver::topRangeUpdated); + connect(&display, &DisplayControl::botRangeUpdated, this, &isoDriver::botRangeUpdated); } isoDriver::~isoDriver() From e04b5c5e20bdd058c1c520f4fae0541a63edd4ce Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 15:36:12 +0200 Subject: [PATCH 079/109] clean up the shortcuts --- Desktop_Interface/mainwindow.cpp | 82 +++++++++++--------------------- Desktop_Interface/mainwindow.h | 21 +------- 2 files changed, 28 insertions(+), 75 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index c245584c..9001958d 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -37,9 +37,6 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); - ui->actionQuit->setShortcut(Qt::CTRL + Qt::Key_Q); // on macOS CTRL is automaticall translated to the command key - ui->actionPinout->setShortcut(Qt::Key_F1); - calibrationMessages = new QMessageBox(); ui->psuDisplay->display("4.00"); ui->bufferDisplay->refreshImage(); @@ -986,58 +983,33 @@ void MainWindow::cycleBaudRateBackwards_CH2(){ } void MainWindow::initShortcuts(){ - shortcut_cycleBaudRate_CH1 = new QShortcut(QKeySequence("Ctrl+B"), ui->menuBar); - shortcut_cycleBaudRateBackwards_CH1 = new QShortcut(QKeySequence("Ctrl+Shift+B"), ui->menuBar); - shortcut_cycleBaudRate_CH2 = new QShortcut(QKeySequence("Ctrl+Alt+B"), ui->menuBar); - shortcut_cycleBaudRateBackwards_CH2 = new QShortcut(QKeySequence("Ctrl+Shift+Alt+B"), ui->menuBar); - shortcut_snapScopeToCursors = new QShortcut(QKeySequence("Z"), ui->menuBar); - shortcut_manualRange = new QShortcut(QKeySequence("M"), ui->menuBar); - shortcut_snapshot_CH1 = new QShortcut(QKeySequence("c"), this); - shortcut_snapshot_CH2 = new QShortcut(QKeySequence("v"), this); - - shortcut_w = new QShortcut(QKeySequence("w"), ui->menuBar); - shortcut_s = new QShortcut(QKeySequence("s"), ui->menuBar); - shortcut_ctrlW = new QShortcut(QKeySequence("Ctrl+w"), ui->menuBar); - shortcut_ctrlS = new QShortcut(QKeySequence("Ctrl+s"), ui->menuBar); - - shortcut_a = new QShortcut(QKeySequence("a"), this); - shortcut_d = new QShortcut(QKeySequence("d"), this); - shortcut_ArrowLeft = new QShortcut(QKeySequence("Left"), this); - shortcut_ArrowRight = new QShortcut(QKeySequence("Right"), this); - shortcut_CtrlArrowLeft = new QShortcut(QKeySequence("Ctrl+Left"), this); - shortcut_CtrlArrowRight = new QShortcut(QKeySequence("Ctrl+Right"), this); - - - - shortcut_Debug = new QShortcut(QKeySequence("Home"), this); - shortcut_Esc = new QShortcut(QKeySequence("Esc"), this); - - - connect(shortcut_cycleBaudRate_CH1, &QShortcut::activated, this, &MainWindow::cycleBaudRate_CH1); - connect(shortcut_cycleBaudRateBackwards_CH1, &QShortcut::activated, this, &MainWindow::cycleBaudRateBackwards_CH1); - connect(shortcut_cycleBaudRate_CH2, &QShortcut::activated, this, &MainWindow::cycleBaudRate_CH2); - connect(shortcut_cycleBaudRateBackwards_CH2, &QShortcut::activated, this, &MainWindow::cycleBaudRateBackwards_CH2); - connect(shortcut_snapshot_CH1, &QShortcut::activated, this, &MainWindow::on_actionSnapshot_CH1_triggered); - connect(shortcut_snapshot_CH2, &QShortcut::activated, this, &MainWindow::on_actionSnapshot_CH2_triggered); - - connect(shortcut_w, &QShortcut::activated, this, &MainWindow::arrowUpTriggered); - connect(shortcut_s, &QShortcut::activated, this, &MainWindow::arrowDownTriggered); - connect(shortcut_ctrlW, &QShortcut::activated, this, &MainWindow::ctrlArrowUpTriggered); - connect(shortcut_ctrlS, &QShortcut::activated, this, &MainWindow::ctrlArrowDownTriggered); - - connect(shortcut_a, &QShortcut::activated, this, &MainWindow::cycleDelayLeft); - connect(shortcut_d, &QShortcut::activated, this, &MainWindow::cycleDelayRight); - - connect(shortcut_ArrowLeft, &QShortcut::activated, this, &MainWindow::cycleDelayLeft); - connect(shortcut_ArrowRight, &QShortcut::activated, this, &MainWindow::cycleDelayRight); - connect(shortcut_CtrlArrowLeft, &QShortcut::activated, this, &MainWindow::cycleDelayLeft_large); - connect(shortcut_CtrlArrowRight, &QShortcut::activated, this, &MainWindow::cycleDelayRight_large); - - connect(shortcut_snapScopeToCursors, &QShortcut::activated, this, &MainWindow::on_actionSnap_to_Cursors_triggered); - connect(shortcut_manualRange, &QShortcut::activated, this, &MainWindow::on_actionEnter_Manually_triggered); - - connect(shortcut_Debug, &QShortcut::activated, this, &MainWindow::enableLabradorDebugging); - connect(shortcut_Esc, &QShortcut::activated, this, &MainWindow::reinitUsb); + new QShortcut(QKeySequence("Ctrl+B"), this, SLOT(cycleBaudRate_CH1())); + new QShortcut(QKeySequence("Ctrl+Shift+B"), this, SLOT(cycleBaudRateBackwards_CH1())); + new QShortcut(QKeySequence("Ctrl+Alt+B"), this, SLOT(cycleBaudRate_CH2())); + new QShortcut(QKeySequence("Ctrl+Shift+Alt+B"), this, SLOT(cycleBaudRateBackwards_CH2())); + new QShortcut(QKeySequence("Z"), this, SLOT(on_actionSnap_to_Cursors_triggered())); + new QShortcut(QKeySequence("M"), this, SLOT(on_actionEnter_Manually_triggered())); + new QShortcut(QKeySequence("c"), this, SLOT(on_actionSnapshot_CH1_triggered())); + new QShortcut(QKeySequence("v"), this, SLOT(on_actionSnapshot_CH2_triggered())); + + new QShortcut(QKeySequence("w"), this, SLOT(arrowUpTriggered())); + new QShortcut(QKeySequence("s"), this, SLOT(arrowDownTriggered())); + new QShortcut(QKeySequence("Ctrl+w"), this, SLOT(ctrlArrowUpTriggered())); + new QShortcut(QKeySequence("Ctrl+s"), this, SLOT(ctrlArrowDownTriggered())); + + new QShortcut(QKeySequence("a"), this, SLOT(cycleDelayLeft())); + new QShortcut(QKeySequence("d"), this, SLOT(cycleDelayRight())); + new QShortcut(QKeySequence("Left"), this, SLOT(cycleDelayLeft())); + new QShortcut(QKeySequence("Right"), this, SLOT(cycleDelayRight())); + + new QShortcut(QKeySequence("Ctrl+Left"), this, SLOT(cycleDelayLeft_large())); + new QShortcut(QKeySequence("Ctrl+Right"), this, SLOT(cycleDelayRight_large())); + + new QShortcut(QKeySequence("Home"), this, SLOT(enableLabradorDebugging())); + new QShortcut(QKeySequence("Esc"), this, SLOT(reinitUsb())); + + ui->actionQuit->setShortcut(Qt::CTRL + Qt::Key_Q); // on macOS CTRL is automaticall translated to the command key + ui->actionPinout->setShortcut(Qt::Key_F1); } diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index abe5ccf1..f91a5d8e 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -174,6 +174,7 @@ private slots: +public slots: void on_actionAbout_triggered(); void on_actionOpen_DAQ_File_triggered(); @@ -268,26 +269,6 @@ private slots: QActionGroup *fpsGroup; QActionGroup *connectionTypeGroup; QActionGroup *serialProtocolGroup; - QShortcut *shortcut_cycleBaudRate_CH1; - QShortcut *shortcut_cycleBaudRateBackwards_CH1; - QShortcut *shortcut_cycleBaudRate_CH2; - QShortcut *shortcut_cycleBaudRateBackwards_CH2; - QShortcut *shortcut_w; - QShortcut *shortcut_ctrlW; - QShortcut *shortcut_s; - QShortcut *shortcut_ctrlS; - QShortcut *shortcut_a; - QShortcut *shortcut_d; - QShortcut *shortcut_ArrowLeft; - QShortcut *shortcut_ArrowRight; - QShortcut *shortcut_CtrlArrowLeft; - QShortcut *shortcut_CtrlArrowRight; - QShortcut *shortcut_snapScopeToCursors;\ - QShortcut *shortcut_manualRange; - QShortcut *shortcut_snapshot_CH1; - QShortcut *shortcut_snapshot_CH2; - QShortcut *shortcut_Debug; - QShortcut *shortcut_Esc; //Duct Tape bool dt_AlreadyAskedAboutCalibration = false; From bc5141699ec6765abd2d848d4912630297a005f8 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 16:05:14 +0200 Subject: [PATCH 080/109] fix division by zero --- Desktop_Interface/isodriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index b65489c4..903c3b95 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -528,7 +528,7 @@ void isoDriver::udateCursors(void){ t0->value = display.x0; t1->value = display.x1; dt->value = fabs(display.x0 - display.x1); - f->value = 1 / (display.x1 - display.x0); + f->value = display.x1 != display.x0 ? 1 / (display.x1 - display.x0) : 0.; const QString temp_hori = QString::asprintf("V0=%s, V1=%s, ΔV=%s", v0->printVal(), v1->printVal(), dv->printVal()); const QString temp_vert = QString::asprintf("t0=%s, t1=%s, Δt=%s, f=%s", t0->printVal(), t1->printVal(), dt->printVal(), f->printVal()); From e119cfe0c27acd954159d657f9ade1b716207e2a Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 16:05:24 +0200 Subject: [PATCH 081/109] proper default window size --- Desktop_Interface/DisplayControl.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/DisplayControl.h b/Desktop_Interface/DisplayControl.h index f4379194..cc204ef2 100644 --- a/Desktop_Interface/DisplayControl.h +++ b/Desktop_Interface/DisplayControl.h @@ -2,6 +2,8 @@ #include +#include "desktop_settings.h" + class QCustomPlot; class DisplayControl : public QObject @@ -10,7 +12,7 @@ class DisplayControl : public QObject public: double delay = 0; - double window = 0.01; + double window = MAX_WINDOW_SIZE; double y0 = 0; double y1 = 0; double x0 = 0; From 35aa228d9d95e269feaa8284e53303a26f82ed15 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 16:05:33 +0200 Subject: [PATCH 082/109] add shortcut to zoom back out again --- Desktop_Interface/mainwindow.cpp | 12 +++++++++++- Desktop_Interface/mainwindow.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 9001958d..a5aebdf0 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -987,7 +987,8 @@ void MainWindow::initShortcuts(){ new QShortcut(QKeySequence("Ctrl+Shift+B"), this, SLOT(cycleBaudRateBackwards_CH1())); new QShortcut(QKeySequence("Ctrl+Alt+B"), this, SLOT(cycleBaudRate_CH2())); new QShortcut(QKeySequence("Ctrl+Shift+Alt+B"), this, SLOT(cycleBaudRateBackwards_CH2())); - new QShortcut(QKeySequence("Z"), this, SLOT(on_actionSnap_to_Cursors_triggered())); + new QShortcut(QKeySequence("z"), this, SLOT(on_actionSnap_to_Cursors_triggered())); + new QShortcut(QKeySequence("Shift+Z"), this, SLOT(on_actionResetDisplay())); new QShortcut(QKeySequence("M"), this, SLOT(on_actionEnter_Manually_triggered())); new QShortcut(QKeySequence("c"), this, SLOT(on_actionSnapshot_CH1_triggered())); new QShortcut(QKeySequence("v"), this, SLOT(on_actionSnapshot_CH2_triggered())); @@ -2085,6 +2086,15 @@ void MainWindow::on_actionDAQ_Settings_triggered() df.exec(); } +void MainWindow::on_actionResetDisplay() +{ + ui->controller_iso->setBotRange(-0.5); + ui->controller_iso->setTopRange(2.5); + ui->controller_iso->setDelay(0.); + ui->controller_iso->setTimeWindow(MAX_WINDOW_SIZE); + +} + void MainWindow::fileLimitReached_CH1(void){ ui->actionRecord_CH1->setChecked(false); diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index f91a5d8e..825dc0fb 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -164,6 +164,7 @@ private slots: void on_actionShow_Debug_Console_triggered(); void on_actionDAQ_Settings_triggered(); + void on_actionResetDisplay(); void fileLimitReached_CH1(void); void fileLimitReached_CH2(void); From 06c23f6cb9decc262a3239d5edc6aea0ad195ed7 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 16:46:55 +0200 Subject: [PATCH 083/109] I never which color is which --- Desktop_Interface/mainwindow.cpp | 23 +++++++++++++++++++ Desktop_Interface/mainwindow.h | 2 ++ .../ui_elements/buffercontrol.cpp | 6 ++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index a5aebdf0..3a7b5b43 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -242,6 +242,8 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->attenuationComboBox_CH2, qOverload(&QComboBox::currentIndexChanged), ui->controller_iso, &isoDriver::attenuationChanged_CH2); #endif connect(ui->controller_iso, &isoDriver::enableCursorGroup, this, &MainWindow::cursorGroupEnabled); + + connect(ui->scopeGroup_CH2, &QGroupBox::toggled, this, &MainWindow::onChannel2Toggled); } MainWindow::~MainWindow() @@ -282,6 +284,21 @@ void MainWindow::initialisePlot() textLabel->setVisible(false); ui->controller_iso->cursorTextPtr = textLabel; + QCPItemText *ch1Label = new QCPItemText(ui->scopeAxes); + ch1Label->setText("Channel 1"); + ch1Label->setPositionAlignment(Qt::AlignLeft|Qt::AlignTop); // holy fuck qcustomplot is a clusterfuck + ch1Label->position->setType(QCPItemPosition::ptAxisRectRatio); + ch1Label->position->setCoords(0.01, 0); + ch1Label->setColor(Qt::yellow); + + ch2Label = new QCPItemText(ui->scopeAxes); + ch2Label->setPositionAlignment(Qt::AlignLeft|Qt::AlignTop); + ch2Label->setText("Channel 2"); + ch2Label->position->setParentAnchorY(ch1Label->bottom); // this API is horribly confusing + ch2Label->position->setParentAnchorX(ch1Label->left); + ch2Label->setColor(Qt::cyan); + ch2Label->setVisible(false); + #if QCP_VER == 1 ui->scopeAxes->yAxis->setAutoTickCount(9); ui->scopeAxes->xAxis->setAutoTickCount(9); @@ -2131,6 +2148,12 @@ void MainWindow::daq_saveButtonPressed(){ settings->setValue("daq_defaultFileSize", daq_max_file_size); } +void MainWindow::onChannel2Toggled() +{ + ch2Label->setVisible(ui->scopeGroup_CH2->isChecked()); + ui->scopeAxes->replot(); // what the fuck qcustomplot +} + void MainWindow::on_actionAbout_triggered() { QMessageBox aboutDialog(this); diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 825dc0fb..0548e38e 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -173,6 +173,7 @@ private slots: void daq_updatedMaxFileSize(qulonglong newVal); void daq_saveButtonPressed(); + void onChannel2Toggled(); public slots: @@ -236,6 +237,7 @@ public slots: Ui::MainWindow *ui; bool forceSquare = false; QCPItemText *textLabel; + QCPItemText *ch2Label = nullptr; QFile *output375_CH1, *output375_CH2, *output750; unsigned char caibrateStage; QMessageBox *calibrationMessages; diff --git a/Desktop_Interface/ui_elements/buffercontrol.cpp b/Desktop_Interface/ui_elements/buffercontrol.cpp index 97650c1f..88f5a55d 100644 --- a/Desktop_Interface/ui_elements/buffercontrol.cpp +++ b/Desktop_Interface/ui_elements/buffercontrol.cpp @@ -34,7 +34,7 @@ void bufferControl::scopeIn_CH1(bool state){ //What about DSR!? if (scopeState_CH2){ //Implicitly state is false scopeState_CH2 = false; //updateBuffer(0); - Causes issues because the uncheck below called scopeIn_CH2 (but only when toggle)!!! - scopeUncheck(false); + emit scopeUncheck(false); } //Turn off the DSR when CH1 is disabled. @@ -42,8 +42,8 @@ void bufferControl::scopeIn_CH1(bool state){ //What about DSR!? scopeDsrUncheck(false); } - scopeDsrOut(state); - scopeOut_CH2(state); + emit scopeDsrOut(state); + emit scopeOut_CH2(state); updateBuffer(state,1); // Do this last to ensure anything accidentally enabled is immediately switched off qDebug() << "scopeIn_CH1" << state; From 1c211a5cfff2df6161901bfe990c8da8e622463c Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 17:05:58 +0200 Subject: [PATCH 084/109] try to improve the plotting --- Desktop_Interface/mainwindow.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 3a7b5b43..9e4845ab 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -267,6 +267,18 @@ void MainWindow::initialisePlot() ui->scopeAxes->addGraph(); ui->scopeAxes->addGraph(); + // TODO: port to QWT to get rid of the horrible flickering issues etc. +// ui->scopeAxes->graph(0)->setAntialiased(true); +// ui->scopeAxes->graph(1)->setAntialiased(true); +// ui->scopeAxes->graph(2)->setAntialiased(true); +// ui->scopeAxes->graph(3)->setAntialiased(true); +// ui->scopeAxes->graph(4)->setAntialiased(true); +// ui->scopeAxes->graph(5)->setAntialiased(true); + +// ui->scopeAxes->graph(0)->setAdaptiveSampling(false); +// ui->scopeAxes->graph(1)->setAdaptiveSampling(false); +// ui->scopeAxes->graph(2)->setAdaptiveSampling(false); + textLabel = new QCPItemText(ui->scopeAxes); #if QCP_VER == 1 ui->scopeAxes->addItem(textLabel); @@ -305,7 +317,7 @@ void MainWindow::initialisePlot() #endif #if QCP_VER == 2 - ui->scopeAxes->setOpenGl(true); +// ui->scopeAxes->setOpenGl(true); QSharedPointer xTicker(new QCPAxisTicker); xTicker->setTickCount(9); ui->scopeAxes->xAxis->setTicker(xTicker); From 3c063f611cb38650088838631b4138255b56da26 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 18:08:11 +0200 Subject: [PATCH 085/109] need a better downsampling --- Desktop_Interface/Labrador.pro | 2 ++ Desktop_Interface/isobuffer.cpp | 52 ++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index 904f5890..dbb5b72e 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -138,6 +138,8 @@ unix:!android:!macx{ LIBS += -lusb-1.0 ##make sure you have the libusb-1.0-0-dev package! LIBS += -ldfuprog-0.9 + QMAKE_CXXFLAGS+=-O2 + contains(QT_ARCH, arm) { message("Building for Raspberry Pi") diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 072773b8..2040456f 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "isodriver.h" #include "uartstyledecoder.h" @@ -119,19 +120,62 @@ std::unique_ptr isoBuffer::readBuffer(double sampleWindow, int numSampl std::fill (readData.get(), readData.get() + numSamples, short(0)); double itr = delaySamples; - for (int i = 0; i < numSamples && itr < m_insertedCount; i++) + short *data = &m_buffer[(m_back-1) + m_bufferLen]; + for (int pos = 0; pos < numSamples && itr < m_insertedCount; pos++) { if (int(itr) < 0 || ((m_back-1) + m_bufferLen - itr) >= m_bufferLen * 2) { - qWarning() << "itr out of range" << itr << numSamples << m_insertedCount << i << timeBetweenSamples; + qWarning() << "itr out of range" << itr << numSamples << m_insertedCount << pos << timeBetweenSamples; break; } assert(int(itr) >= 0); - readData[i] = bufferAt(int(itr)); + + // What we do: We loop over the bytes we would skip, and count the number of negative and positive values. + // If there are more negative than positive we select the lowest we found, otherwise we pick the highest. + // It's not perfect, but eliminates some of the worst artifacts. + // An average would be easier, but you miss the peaks, which are the interesting parts of the data. + + // TODO: to optimize: + // - reverse the loop (I thought the prefetching would be smart enough, but maybe not) + // - Split loop into blocks with constant length, to avoid the pshuflw and whatnot + if (timeBetweenSamples > 1) { + int numPositive = 0; + int numNegative = 0; + short minimum = std::numeric_limits::max(); + short maximum = std::numeric_limits::min(); + const int end = qMin(m_insertedCount, itr + timeBetweenSamples); + for (int i=itr; i= 0; + } + readData[pos] = maximum; +// readData[pos] = (minimum < 0 && qAbs(minimum)) > qAbs(maximum) ? minimum: maximum;// numPositive > numNegative ? maximum: minimum; +// readData[pos] = numPositive > numNegative ? maximum: minimum; + } else { + readData[pos] = bufferAt(int(itr)); + } if (singleBit) { int subIdx = 8*(-itr-floor(-itr)); - readData[i] &= (1 << subIdx); + readData[pos] &= (1 << subIdx); } itr += timeBetweenSamples; From cfe26344db981cf2571bdbbe9320fd3341b3a986 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 24 Apr 2021 19:26:08 +0200 Subject: [PATCH 086/109] minor fixes --- Desktop_Interface/genericusbdriver.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Desktop_Interface/genericusbdriver.h b/Desktop_Interface/genericusbdriver.h index c99c67f4..911bb685 100644 --- a/Desktop_Interface/genericusbdriver.h +++ b/Desktop_Interface/genericusbdriver.h @@ -29,19 +29,23 @@ #define NUM_ISO_ENDPOINTS (1) #endif -#ifdef PLATFORM_WINDOWS +#ifdef Q_OS_WINDOWS #define ISO_PACKETS_PER_CTX 17 #define NUM_FUTURE_CTX 40 #elif defined PLATFORM_RASPBERRY_PI #define ISO_PACKETS_PER_CTX 66 // 15fps... #define NUM_FUTURE_CTX 4 -#elif defined PLATFORM_LINUX +#elif defined Q_OS_LINUX #define ISO_PACKETS_PER_CTX 17 #define NUM_FUTURE_CTX 20 -#else +#elif defined Q_OS_MACOS // A real Mac may be capable of higher refresh rates and more parallel contexts, but these settings work on a hackintosh too. #define ISO_PACKETS_PER_CTX 33 #define NUM_FUTURE_CTX 4 +#else + #warning "Unknown OS" + #define ISO_PACKETS_PER_CTX 17 + #define NUM_FUTURE_CTX 20 #endif #define ISO_TIMER_PERIOD 1 From 4b4cd0d5ce558c0e32018c4dfdaa5eb3dcae4d6b Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 14:08:00 +0200 Subject: [PATCH 087/109] fix building with old libusb versions --- Desktop_Interface/unixusbdriver.cpp | 4 ++++ Desktop_Interface/unixusbdriver.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 14a6be9f..4026eef7 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -72,7 +72,11 @@ unsigned char unixUsbDriver::usbInit(unsigned long VIDin, unsigned long PIDin){ return 1; } else qDebug() << "Libusb context initialised"; +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000108) libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); +#else + libusb_set_debug(ctx, 3); +#endif } if(handle == NULL){ diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index cbfa415f..25bf15d2 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -7,7 +7,7 @@ #include #include "genericusbdriver.h" -#include "libusb.h" +#include extern "C" { #include "libdfuprog.h" From 8b01c52f4c4bf0baca5f55408cf0b901a0c9e8e8 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Mon, 26 Apr 2021 17:02:25 +0200 Subject: [PATCH 088/109] fix assert on exit in libusb --- Desktop_Interface/unixusbdriver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index 25bf15d2..d56561e9 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -33,7 +33,7 @@ class worker : public QObject ~worker(){}; libusb_context *ctx; bool stopTime = false; - unsigned char cleanupRemaining = 4; + unsigned char cleanupRemaining = NUM_FUTURE_CTX ; public slots: void handle(){ qDebug() << "SUB THREAD ID" << QThread::currentThreadId(); From ad417bc85baad03dbe1a433e27ab736e26493d5d Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Mon, 26 Apr 2021 17:10:06 +0200 Subject: [PATCH 089/109] tradeoff a tiny bit of performance (5-10% on a 1.9GHz core) --- Desktop_Interface/desktop_settings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Desktop_Interface/desktop_settings.cpp b/Desktop_Interface/desktop_settings.cpp index ea9a61aa..1f018a2e 100644 --- a/Desktop_Interface/desktop_settings.cpp +++ b/Desktop_Interface/desktop_settings.cpp @@ -6,7 +6,8 @@ int MAX_PENDING_TRANSFERS = 512; int MAX_PENDING_IO = 16; //Plot settings -int GRAPH_SAMPLES = 1024; +// this should be int GRAPH_SAMPLES = 375000 * 10; to avoid downsampling +int GRAPH_SAMPLES = 8192; int TIMER_PERIOD = 17; int ISO_RECOVERY_TIME = (200); int MAX_WINDOW_SIZE = 10; From 7d72b921e85e23c96b0f789c96f7f746fab6ea44 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Mon, 26 Apr 2021 17:29:30 +0200 Subject: [PATCH 090/109] better default --- Desktop_Interface/DisplayControl.h | 2 +- Desktop_Interface/mainwindow.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/DisplayControl.h b/Desktop_Interface/DisplayControl.h index cc204ef2..54048f4a 100644 --- a/Desktop_Interface/DisplayControl.h +++ b/Desktop_Interface/DisplayControl.h @@ -17,7 +17,7 @@ class DisplayControl : public QObject double y1 = 0; double x0 = 0; double x1 = 0; - double topRange = 2.5; + double topRange = 5.1; double botRange = -0.5; void setVoltageRange (const QPointF &position, const QPoint zoomDelta, const Qt::KeyboardModifiers modifiers, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 9e4845ab..ee42deba 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -2118,7 +2118,7 @@ void MainWindow::on_actionDAQ_Settings_triggered() void MainWindow::on_actionResetDisplay() { ui->controller_iso->setBotRange(-0.5); - ui->controller_iso->setTopRange(2.5); + ui->controller_iso->setTopRange(5.1); ui->controller_iso->setDelay(0.); ui->controller_iso->setTimeWindow(MAX_WINDOW_SIZE); From 128a93c439b36a4d0b268e4ca8fa23abda3ead84 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 1 May 2021 13:24:38 +0200 Subject: [PATCH 091/109] memory safety --- Desktop_Interface/mainwindow.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index ee42deba..a33ef5bc 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -357,12 +357,10 @@ void MainWindow::initialisePlot() } void MainWindow::labelPsu(){ - char tempString[4]; int tempInt = ui->psuSlider->maximum(); int tempCounter = 0; while(tempInt>90){ - sprintf(tempString, "%dV", tempInt/20); - ui->psuSlider->setTickLabel(tempString, tempCounter); + ui->psuSlider->setTickLabel(QString::asprintf("%dV", tempInt/20), tempCounter); tempInt -= 20; tempCounter++; } From 1fa80e58307133935e336b30164280a6d9346aa1 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 1 May 2021 13:25:34 +0200 Subject: [PATCH 092/109] don't use zero for pointers --- Desktop_Interface/unixusbdriver.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index d56561e9..20ed8562 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -68,7 +68,7 @@ class unixUsbDriver : public genericUsbDriver void manualFirmwareRecovery(void); protected: //USB Vars - libusb_context *ctx = NULL; + libusb_context *ctx = nullptr; libusb_device_handle *handle = NULL; //USBIso Vars unsigned char *midBuffer_current[NUM_ISO_ENDPOINTS]; @@ -78,7 +78,7 @@ class unixUsbDriver : public genericUsbDriver tcBlock transferCompleted[NUM_ISO_ENDPOINTS][NUM_FUTURE_CTX]; unsigned char dataBuffer[NUM_ISO_ENDPOINTS][NUM_FUTURE_CTX][ISO_PACKET_SIZE*ISO_PACKETS_PER_CTX]; worker *isoHandler = nullptr; - QThread *workerThread = nullptr; + QPointer workerThread; int cumulativeFramePhaseErrors = 0; //Generic Functions virtual unsigned char usbInit(unsigned long VIDin, unsigned long PIDin); From 7fdae93dd423a5873c307cb55436e6d25163fd7e Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 5 May 2021 10:33:17 +0200 Subject: [PATCH 093/109] fix the key event override when hovering the scope --- Desktop_Interface/mainwindow.cpp | 44 ++++++++++++++++++++++++++------ Desktop_Interface/mainwindow.h | 6 +++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index a33ef5bc..25a571b8 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -236,6 +236,11 @@ MainWindow::MainWindow(QWidget *parent) : } #ifndef PLATFORM_ANDROID + // So sue me + for (QWidget *child : findChildren()) { + child->installEventFilter(this); + } + connect(ui->offsetSpinBox_CH1, qOverload(&QDoubleSpinBox::valueChanged), ui->controller_iso, &isoDriver::offsetChanged_CH1); connect(ui->offsetSpinBox_CH2, qOverload(&QDoubleSpinBox::valueChanged), ui->controller_iso, &isoDriver::offsetChanged_CH2); connect(ui->attenuationComboBox_CH1, qOverload(&QComboBox::currentIndexChanged), ui->controller_iso, &isoDriver::attenuationChanged_CH1); @@ -1056,8 +1061,8 @@ static QWheelEvent createWheelEvent(const bool negative, const QPoint &point, co { return QWheelEvent(point, // pos QCursor::pos(), // globalpos - QPoint(negative ? -0 : 0, 0), // pixelDelta - QPoint(negative ? -120 : 120, 0), // angleDelta + QPoint(0, negative ? -0 : 0), // pixelDelta + QPoint(0, negative ? -120 : 120), // angleDelta Qt::NoButton, // buttons modifier, // keyboard modifiers Qt::NoScrollPhase, // scroll phase @@ -2506,10 +2511,35 @@ void MainWindow::on_setAutoScopeRange() void MainWindow::keyPressEvent(QKeyEvent *event) { - if(!(ui->scopeAxes->underMouse())) { - QMainWindow::keyPressEvent(event); + if (maybeHandleKeypress(event)) { + event->setAccepted(true); return; } + + QMainWindow::keyPressEvent(event); +} + +bool MainWindow::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + if (maybeHandleKeypress(static_cast(event))) { + QWidget *original = qobject_cast(obj); + if (original) { + original->clearFocus(); + } + event->setAccepted(true); + return true; + } + } + + return QObject::eventFilter(obj, event); +} + +bool MainWindow::maybeHandleKeypress(QKeyEvent *event) +{ + if (!(ui->scopeAxes->underMouse())) { + return false; + } switch(event->key()) { case Qt::Key_Down: if (event->modifiers() & Qt::ControlModifier) { @@ -2526,10 +2556,8 @@ void MainWindow::keyPressEvent(QKeyEvent *event) } break; default: - QMainWindow::keyPressEvent(event); - return; + return false; } - event->setAccepted(true); - + return true; } diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 0548e38e..b0626e00 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -42,7 +42,7 @@ class MainWindow : public QMainWindow public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event) override; void showFileDialog(QString *fileName); void openFileDialog(QString *fileName); private slots: @@ -230,7 +230,9 @@ public slots: void on_setAutoScopeRange(); protected: - void keyPressEvent(QKeyEvent *event); + void keyPressEvent(QKeyEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override; + bool maybeHandleKeypress(QKeyEvent *event); private: //Generic Vars From 98bbf818faf43d8df10ce01b67a75228f2c9cb0e Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 14:35:36 +0200 Subject: [PATCH 094/109] don't hardcode default values everywhere --- Desktop_Interface/DisplayControl.h | 4 ++-- Desktop_Interface/desktop_settings.h | 2 ++ Desktop_Interface/mainwindow.cpp | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Desktop_Interface/DisplayControl.h b/Desktop_Interface/DisplayControl.h index 54048f4a..7c3fce23 100644 --- a/Desktop_Interface/DisplayControl.h +++ b/Desktop_Interface/DisplayControl.h @@ -17,8 +17,8 @@ class DisplayControl : public QObject double y1 = 0; double x0 = 0; double x1 = 0; - double topRange = 5.1; - double botRange = -0.5; + double topRange = DEFAULT_TOP_RANGE; + double botRange = DEFAULT_BOTTOM_RANGE; void setVoltageRange (const QPointF &position, const QPoint zoomDelta, const Qt::KeyboardModifiers modifiers, bool isProperlyPaused, double maxWindowSize, QCustomPlot* axes); diff --git a/Desktop_Interface/desktop_settings.h b/Desktop_Interface/desktop_settings.h index 5b8ad610..dfc2bb93 100644 --- a/Desktop_Interface/desktop_settings.h +++ b/Desktop_Interface/desktop_settings.h @@ -17,6 +17,8 @@ extern int TIMER_PERIOD; extern int ISO_RECOVERY_TIME; extern int MAX_WINDOW_SIZE; extern int TICK_SEPARATION; +static constexpr double DEFAULT_TOP_RANGE = 5.1; +static constexpr double DEFAULT_BOTTOM_RANGE = -0.5; //Multimeter settings extern int MULTIMETER_PERIOD; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 25a571b8..2d3290b2 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -2120,8 +2120,8 @@ void MainWindow::on_actionDAQ_Settings_triggered() void MainWindow::on_actionResetDisplay() { - ui->controller_iso->setBotRange(-0.5); - ui->controller_iso->setTopRange(5.1); + ui->controller_iso->setBotRange(DEFAULT_BOTTOM_RANGE); + ui->controller_iso->setTopRange(DEFAULT_TOP_RANGE); ui->controller_iso->setDelay(0.); ui->controller_iso->setTimeWindow(MAX_WINDOW_SIZE); From b909385f2e955d6c70f59c0e2a6048cc854e722d Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 14:35:53 +0200 Subject: [PATCH 095/109] use just a single CONFIG option --- Desktop_Interface/Labrador.pro | 3 ++- Desktop_Interface/ui_elements.pri | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index dbb5b72e..4fe650d5 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -19,7 +19,7 @@ TARGET = Labrador TEMPLATE = app -!contains(CONFIG, "USE_SYSTEM_QCP") { +!contains(CONFIG, "USE_SYSTEM_LIBS") { message("Using bundled version of qcustomplot") QCP_VER = 1 } else { @@ -173,6 +173,7 @@ unix:!android:!macx{ } contains(CONFIG, "USE_SYSTEM_LIBS") { + message("Using system version of libusb") INCLUDEPATH += $$[QT_INSTALL_PREFIX]/include/libusb-1.0 } else { INCLUDEPATH += build_linux/libusb diff --git a/Desktop_Interface/ui_elements.pri b/Desktop_Interface/ui_elements.pri index 446c846d..2f997737 100644 --- a/Desktop_Interface/ui_elements.pri +++ b/Desktop_Interface/ui_elements.pri @@ -1,7 +1,7 @@ @INCLUDEPATH += $$PWD/ui_elements @DEPENDPATH += $$PWD/ui_elements -!contains(CONFIG, "USE_SYSTEM_QCP") { +!contains(CONFIG, "USE_SYSTEM_LIBS") { INCLUDEPATH += $$PWD/ui_elements/qcp$${QCP_VER} DEPENDPATH += $$PWD/ui_elements/qcp$${QCP_VER} SOURCES += ui_elements/qcp$${QCP_VER}/qcustomplot.cpp From 562613dd0f0dbe4439513c72d0154ea1894a4b66 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 14:36:34 +0200 Subject: [PATCH 096/109] I think I finally found a usable method of downsampling with an OK tradeoff between performance and representative display result --- Desktop_Interface/isobuffer.cpp | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 2040456f..fea7fdeb 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -129,45 +129,22 @@ std::unique_ptr isoBuffer::readBuffer(double sampleWindow, int numSampl } assert(int(itr) >= 0); - // What we do: We loop over the bytes we would skip, and count the number of negative and positive values. - // If there are more negative than positive we select the lowest we found, otherwise we pick the highest. - // It's not perfect, but eliminates some of the worst artifacts. - // An average would be easier, but you miss the peaks, which are the interesting parts of the data. - // TODO: to optimize: // - reverse the loop (I thought the prefetching would be smart enough, but maybe not) // - Split loop into blocks with constant length, to avoid the pshuflw and whatnot if (timeBetweenSamples > 1) { - int numPositive = 0; - int numNegative = 0; short minimum = std::numeric_limits::max(); short maximum = std::numeric_limits::min(); const int end = qMin(m_insertedCount, itr + timeBetweenSamples); + double average = 0.; for (int i=itr; i= 0; + average += val; } - readData[pos] = maximum; -// readData[pos] = (minimum < 0 && qAbs(minimum)) > qAbs(maximum) ? minimum: maximum;// numPositive > numNegative ? maximum: minimum; -// readData[pos] = numPositive > numNegative ? maximum: minimum; + average /= end - itr; + readData[pos] = qAbs(maximum - average) > qAbs(average - minimum) ? maximum : minimum; } else { readData[pos] = bufferAt(int(itr)); } From a45b82cf02827b6ff743396a5358f750e1f6ffb1 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 14:59:31 +0200 Subject: [PATCH 097/109] expose selecting downsampling method in the UI --- Desktop_Interface/isobuffer.cpp | 48 +++++++++++++++---- Desktop_Interface/isobuffer.h | 10 ++++ Desktop_Interface/isodriver.cpp | 7 +++ Desktop_Interface/isodriver.h | 2 + Desktop_Interface/mainwindow.cpp | 27 +++++++++++ Desktop_Interface/mainwindow.h | 5 ++ .../ui_files_desktop/mainwindow.ui | 43 +++++++++++++++++ 7 files changed, 133 insertions(+), 9 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index fea7fdeb..6fb166f5 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -132,19 +132,49 @@ std::unique_ptr isoBuffer::readBuffer(double sampleWindow, int numSampl // TODO: to optimize: // - reverse the loop (I thought the prefetching would be smart enough, but maybe not) // - Split loop into blocks with constant length, to avoid the pshuflw and whatnot - if (timeBetweenSamples > 1) { + if (timeBetweenSamples > 1 && m_downsamplingMethod != DownsamplingMethod::Decimate) { short minimum = std::numeric_limits::max(); short maximum = std::numeric_limits::min(); const int end = qMin(m_insertedCount, itr + timeBetweenSamples); - double average = 0.; - for (int i=itr; i qAbs(average - minimum) ? maximum : minimum; + break; } - average /= end - itr; - readData[pos] = qAbs(maximum - average) > qAbs(average - minimum) ? maximum : minimum; + case DownsamplingMethod::Bottom: + for (int i=itr; i @@ -111,6 +120,7 @@ class isoBuffer : public QWidget int m_sampleRate_bit; TriggerType m_triggerType = TriggerType::Disabled; TriggerSeekState m_triggerSeekState = TriggerSeekState::BelowTriggerLevel; + DownsamplingMethod m_downsamplingMethod = DownsamplingMethod::Decimate; short m_triggerLevel = 0; short m_triggerSensitivity = 0; QVector m_triggerPositionList = {}; diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 903c3b95..a8120ec8 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -1491,3 +1491,10 @@ void isoDriver::setHexDisplay_CH2(bool enabled) { hexDisplay_CH2 = enabled; } + +void isoDriver::setDownsampleMethod(const DownsamplingMethod method) +{ + internalBuffer375_CH1->setDownsampleMethod(method); + internalBuffer375_CH2->setDownsampleMethod(method); + internalBuffer750->setDownsampleMethod(method); +} diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index fbc9f419..7be3e712 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -281,6 +281,8 @@ public slots: void attenuationChanged_CH2(int attenuationIndex); void setHexDisplay_CH1(bool enabled); void setHexDisplay_CH2(bool enabled); + + void setDownsampleMethod(const DownsamplingMethod method); }; Q_DECLARE_OPERATORS_FOR_FLAGS(isoDriver::Channels); diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 2d3290b2..748221d0 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -415,6 +415,13 @@ void MainWindow::menuSetup(){ gainGroup->addAction(ui->actionGain64); ui->actionGainAuto->setChecked(true); + QActionGroup *downsampleGroup = new QActionGroup(this); + downsampleGroup->addAction(ui->actionDownsampleAverageDelta); + downsampleGroup->addAction(ui->actionDownsamplePeak); + downsampleGroup->addAction(ui->actionDownsampleBottom); + downsampleGroup->addAction(ui->actionDownsampleDecimate); + ui->actionDownsampleAverageDelta->setChecked(true); + rangeGroupV = new QActionGroup(this); rangeGroupV->addAction(ui->actionAutoV); rangeGroupV->addAction(ui->actionMV); @@ -1246,6 +1253,26 @@ void MainWindow::on_actionEnter_Manually_triggered() dialog.exec(); } +void MainWindow::on_actionDownsampleDecimate_triggered() +{ + ui->controller_iso->setDownsampleMethod(DownsamplingMethod::Decimate); +} + +void MainWindow::on_actionDownsamplePeak_triggered() +{ + ui->controller_iso->setDownsampleMethod(DownsamplingMethod::Peak); +} + +void MainWindow::on_actionDownsampleBottom_triggered() +{ + ui->controller_iso->setDownsampleMethod(DownsamplingMethod::Bottom); +} + +void MainWindow::on_actionDownsampleAverageDelta_triggered() +{ + ui->controller_iso->setDownsampleMethod(DownsamplingMethod::AverageDelta); +} + void MainWindow::helloWorld(){ qDebug() << "Hello World!"; } diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index b0626e00..5197ed5d 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -67,6 +67,11 @@ private slots: void on_actionSnap_to_Cursors_triggered(); void on_actionEnter_Manually_triggered(); + void on_actionDownsampleDecimate_triggered(); + void on_actionDownsamplePeak_triggered(); + void on_actionDownsampleBottom_triggered(); + void on_actionDownsampleAverageDelta_triggered(); + void connectDisplaySignals(); void calibrateStage2(); void calibrateStage3(); diff --git a/Desktop_Interface/ui_files_desktop/mainwindow.ui b/Desktop_Interface/ui_files_desktop/mainwindow.ui index f7bbad58..2723137e 100644 --- a/Desktop_Interface/ui_files_desktop/mainwindow.ui +++ b/Desktop_Interface/ui_files_desktop/mainwindow.ui @@ -1522,6 +1522,15 @@ + + + &Downsampling method + + + + + + &CH1 Stats @@ -1564,6 +1573,8 @@ + + @@ -1850,6 +1861,38 @@ QAction::TextHeuristicRole + + + true + + + &Decimate + + + + + true + + + &Peak + + + + + true + + + &Bottom + + + + + true + + + &Maximum delta + + Ca&librate From 1132f816e70f8c57c022871d17160d260fe9e6ea Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 15:04:19 +0200 Subject: [PATCH 098/109] whoops, should be in sync with UI --- Desktop_Interface/isobuffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/isobuffer.h b/Desktop_Interface/isobuffer.h index 094fc543..39f459b2 100644 --- a/Desktop_Interface/isobuffer.h +++ b/Desktop_Interface/isobuffer.h @@ -120,7 +120,7 @@ class isoBuffer : public QWidget int m_sampleRate_bit; TriggerType m_triggerType = TriggerType::Disabled; TriggerSeekState m_triggerSeekState = TriggerSeekState::BelowTriggerLevel; - DownsamplingMethod m_downsamplingMethod = DownsamplingMethod::Decimate; + DownsamplingMethod m_downsamplingMethod = DownsamplingMethod::AverageDelta; short m_triggerLevel = 0; short m_triggerSensitivity = 0; QVector m_triggerPositionList = {}; From c1c9eca4a0f089eee3617fefc8e4eeff16eae6cd Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 15:10:26 +0200 Subject: [PATCH 099/109] I'm not a smart person --- Desktop_Interface/isobuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 6fb166f5..88470371 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -149,7 +149,7 @@ std::unique_ptr isoBuffer::readBuffer(double sampleWindow, int numSampl average += val; } average /= end - itr; - result = qAbs(maximum - average) > qAbs(average - minimum) ? maximum : minimum; + result = qAbs(average - maximum) > qAbs(average - minimum) ? maximum : minimum; break; } case DownsamplingMethod::Bottom: From 1d0e2823f091172364d634e3da6bf4f1fa5b49f8 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 15:49:01 +0200 Subject: [PATCH 100/109] trigger works a bit more --- Desktop_Interface/isobuffer.cpp | 6 ++++-- Desktop_Interface/isodriver.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 88470371..77383515 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -411,10 +411,12 @@ void isoBuffer::setTriggerType(TriggerType newType) { qDebug() << "Trigger Type: " << (uint8_t)newType; m_triggerType = newType; + m_triggerPositionList.clear(); } void isoBuffer::setTriggerLevel(double voltageLevel, uint16_t top, bool acCoupled) { + m_triggerPositionList.clear(); m_triggerLevel = inverseSampleConvert(voltageLevel, top, acCoupled); m_triggerSensitivity = static_cast(1 + abs(voltageLevel * kTriggerSensitivityMultiplier * static_cast(top) / 128.)); qDebug() << "Trigger Level: " << m_triggerLevel; @@ -447,7 +449,7 @@ void isoBuffer::checkTriggered() double isoBuffer::getDelayedTriggerPoint(double delay) { if (m_triggerPositionList.isEmpty()) { - return 0; + return -1; } // Holy fuck the STL APIs suck, @@ -477,5 +479,5 @@ double isoBuffer::getDelayedTriggerPoint(double delay) return (m_bufferLen + (m_back-1) - index) / double(m_samplesPerSecond); } - return 0; + return -1; } diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index a8120ec8..d03ba4e3 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -616,14 +616,14 @@ void isoDriver::frameActionGeneric(const ChannelMode CH1_mode, const ChannelMode { isoBuffer* internalBuffer_CH1 = (CH1_mode == ChannelMode::Analog750) ? internalBuffer750 : internalBuffer375_CH1; triggerDelay = (triggerMode < 2) ? internalBuffer_CH1->getDelayedTriggerPoint(display.window) - display.window : internalBuffer375_CH2->getDelayedTriggerPoint(display.window) - display.window; - - if (triggerDelay < 0) - triggerDelay = 0; } - if(singleShotEnabled && (triggerDelay != 0)) + if(singleShotEnabled && (triggerDelay > 0)) singleShotTriggered(true); + if (triggerDelay < 0) + triggerDelay = 0; + readData375_CH1 = internalBuffer375_CH1->readBuffer(display.window,GRAPH_SAMPLES,CH1_mode==ChannelMode::Digital, display.delay + triggerDelay); if(CH2_mode != ChannelMode::Off) readData375_CH2 = internalBuffer375_CH2->readBuffer(display.window,GRAPH_SAMPLES, CH2_mode==ChannelMode::Digital, display.delay + triggerDelay); if(CH1_mode == ChannelMode::Analog750) readData750 = internalBuffer750->readBuffer(display.window,GRAPH_SAMPLES,false, display.delay + triggerDelay); From 0bb3bbc44f0d8d7c360317d47b52bb2a2c881ecd Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 15:49:13 +0200 Subject: [PATCH 101/109] tiny performance improvement for trigger --- Desktop_Interface/isobuffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 77383515..771e7562 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -430,14 +430,14 @@ void isoBuffer::checkTriggered() if (m_triggerType == TriggerType::Disabled) return; - if ((bufferAt(0) >= (m_triggerLevel + m_triggerSensitivity)) && (m_triggerSeekState == TriggerSeekState::BelowTriggerLevel)) + if ((m_triggerSeekState == TriggerSeekState::BelowTriggerLevel) && (bufferAt(0) >= (m_triggerLevel + m_triggerSensitivity))) { // Rising Edge m_triggerSeekState = TriggerSeekState::AboveTriggerLevel; if (m_triggerType == TriggerType::Rising) m_triggerPositionList.push_back(m_back - 1); } - else if ((bufferAt(0) < (m_triggerLevel - m_triggerSensitivity)) && (m_triggerSeekState == TriggerSeekState::AboveTriggerLevel)) + else if ((m_triggerSeekState == TriggerSeekState::AboveTriggerLevel) && (bufferAt(0) < (m_triggerLevel - m_triggerSensitivity))) { // Falling Edge m_triggerSeekState = TriggerSeekState::BelowTriggerLevel; From 0e202650c7c6a3fa6e2a7a1208a5658186dd3b32 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 16:01:15 +0200 Subject: [PATCH 102/109] fix trigger randomly not working with auto gain/changing gain --- Desktop_Interface/isobuffer.cpp | 17 +++++++++++++++-- Desktop_Interface/isobuffer.h | 4 ++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 771e7562..28090622 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -219,6 +219,7 @@ void isoBuffer::gainBuffer(int gain_log) m_buffer[i+m_bufferLen] >>= gain_log; } } + updateTriggerLevel(); } @@ -417,10 +418,22 @@ void isoBuffer::setTriggerType(TriggerType newType) void isoBuffer::setTriggerLevel(double voltageLevel, uint16_t top, bool acCoupled) { m_triggerPositionList.clear(); - m_triggerLevel = inverseSampleConvert(voltageLevel, top, acCoupled); - m_triggerSensitivity = static_cast(1 + abs(voltageLevel * kTriggerSensitivityMultiplier * static_cast(top) / 128.)); + + // Because inverseSampleConvert() depends on e. g. gain, we have to store this and update the level when we change gain + m_triggerVoltage = voltageLevel; + m_triggerTop = top; + m_triggerACCoupled = acCoupled; + + updateTriggerLevel(); +} + +void isoBuffer::updateTriggerLevel() +{ + m_triggerLevel = inverseSampleConvert(m_triggerVoltage, m_triggerTop, m_triggerACCoupled); + m_triggerSensitivity = static_cast(1 + abs(m_triggerVoltage * kTriggerSensitivityMultiplier * static_cast(m_triggerTop) / 128.)); qDebug() << "Trigger Level: " << m_triggerLevel; qDebug() << "Trigger sensitivity:" << m_triggerSensitivity; + } // TODO: Clear trigger diff --git a/Desktop_Interface/isobuffer.h b/Desktop_Interface/isobuffer.h index 39f459b2..801c2e3d 100644 --- a/Desktop_Interface/isobuffer.h +++ b/Desktop_Interface/isobuffer.h @@ -87,6 +87,7 @@ class isoBuffer : public QWidget template int capSample(int offset, int target, double seconds, double value, Function comp); void checkTriggered(); + void updateTriggerLevel(); public: int cap_x0fromLast(double seconds, double vbot); int cap_x1fromLast(double seconds, int x0, double vbot); @@ -121,6 +122,9 @@ class isoBuffer : public QWidget TriggerType m_triggerType = TriggerType::Disabled; TriggerSeekState m_triggerSeekState = TriggerSeekState::BelowTriggerLevel; DownsamplingMethod m_downsamplingMethod = DownsamplingMethod::AverageDelta; + double m_triggerVoltage = 0.; + uint16_t m_triggerTop = 0; + bool m_triggerACCoupled = false; short m_triggerLevel = 0; short m_triggerSensitivity = 0; QVector m_triggerPositionList = {}; From 079e08140724f0b2c1ac9d606a569f4361ed7326 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 16:16:03 +0200 Subject: [PATCH 103/109] the lack of emit markings was really confusing --- Desktop_Interface/daqform.cpp | 10 +- Desktop_Interface/daqloadprompt.cpp | 4 +- Desktop_Interface/functiongencontrol.cpp | 16 +-- Desktop_Interface/genericusbdriver.cpp | 68 +++++++------ Desktop_Interface/isobuffer.cpp | 2 +- Desktop_Interface/isodriver.cpp | 98 +++++++++---------- .../ui_elements/buffercontrol.cpp | 68 ++++++------- .../ui_elements/cursorenabler.cpp | 6 +- Desktop_Interface/ui_elements/esposlider.cpp | 6 +- Desktop_Interface/unixusbdriver.cpp | 8 +- 10 files changed, 145 insertions(+), 141 deletions(-) diff --git a/Desktop_Interface/daqform.cpp b/Desktop_Interface/daqform.cpp index c3372945..519341ec 100644 --- a/Desktop_Interface/daqform.cpp +++ b/Desktop_Interface/daqform.cpp @@ -68,17 +68,17 @@ void daqForm::updateValues(){ //Averaging if(ui->sampleAveragingGroup->isChecked()){ - updatedAveraging(ui->numberOfPointsSpinBox->value()); - } else updatedAveraging(1); + emit updatedAveraging(ui->numberOfPointsSpinBox->value()); + } else emit updatedAveraging(1); //File Size if(ui->limitFileSizeGroupBox->isChecked()){ - updatedMaxFileSize(ui->fileSizeSpinBox->value() * 1000000); - } else updatedMaxFileSize(0); + emit updatedMaxFileSize(ui->fileSizeSpinBox->value() * 1000000); + } else emit updatedMaxFileSize(0); } void daqForm::trigger_saveButtonPressed(){ - saveButtonPressed(); + emit saveButtonPressed(); } diff --git a/Desktop_Interface/daqloadprompt.cpp b/Desktop_Interface/daqloadprompt.cpp index 7b6fd295..edce0b95 100644 --- a/Desktop_Interface/daqloadprompt.cpp +++ b/Desktop_Interface/daqloadprompt.cpp @@ -33,8 +33,8 @@ void daqLoadPrompt::valueChange(){ ui->startTimeDoubleSpinBox->setMaximum(ui->endTimeDoubleSpinBox->value() - min_interval); ui->endTimeDoubleSpinBox->setMinimum(ui->startTimeDoubleSpinBox->value() + min_interval); - startTime(ui->startTimeDoubleSpinBox->value()); - endTime(ui->endTimeDoubleSpinBox->value()); + emit startTime(ui->startTimeDoubleSpinBox->value()); + emit endTime(ui->endTimeDoubleSpinBox->value()); char units[2] = "B"; double contig_ram_required = ((ui->endTimeDoubleSpinBox->value() - ui->startTimeDoubleSpinBox->value()) / min_interval) * 4 + 512; //4 bytes per sample (float), each sample is stored only once. 512 is just a bullshit value to represent the overhead required to store the other variables in the buffer object diff --git a/Desktop_Interface/functiongencontrol.cpp b/Desktop_Interface/functiongencontrol.cpp index 53f9b96a..295f0c83 100644 --- a/Desktop_Interface/functiongencontrol.cpp +++ b/Desktop_Interface/functiongencontrol.cpp @@ -96,31 +96,31 @@ void SingleChannelController::waveformName(QString newName) double newMaxFreq = DAC_SPS / divisor; double newMinFreq = double(CLOCK_FREQ) / 1024.0 / 65535.0 / static_cast(length); - setMaxFreq(newMaxFreq); - setMinFreq(newMinFreq); + emit setMaxFreq(newMaxFreq); + emit setMinFreq(newMinFreq); - notifyUpdate(this); + emit notifyUpdate(this); } void SingleChannelController::freqUpdate(double newFreq) { qDebug() << "newFreq = " << newFreq; m_data.freq = newFreq; - notifyUpdate(this); + emit notifyUpdate(this); } void SingleChannelController::amplitudeUpdate(double newAmplitude) { qDebug() << "newAmplitude = " << newAmplitude; m_data.amplitude = newAmplitude; - notifyUpdate(this); + emit notifyUpdate(this); } void SingleChannelController::offsetUpdate(double newOffset) { qDebug() << "newOffset = " << newOffset; m_data.offset = newOffset; - notifyUpdate(this); + emit notifyUpdate(this); } @@ -132,7 +132,7 @@ DualChannelController::DualChannelController(QWidget *parent) : QLabel(parent) SingleChannelController* controller2 = getChannelController(ChannelID::CH2); connect(controller1, &SingleChannelController::notifyUpdate, - this, [=](SingleChannelController* ptr){ this->functionGenToUpdate(ChannelID::CH1, ptr); }); + this, [=](SingleChannelController* ptr){ emit this->functionGenToUpdate(ChannelID::CH1, ptr); }); connect(controller1, &SingleChannelController::setMaxFreq, this, &DualChannelController::setMaxFreq_CH1); @@ -142,7 +142,7 @@ DualChannelController::DualChannelController(QWidget *parent) : QLabel(parent) connect(controller2, &SingleChannelController::notifyUpdate, - this, [=](SingleChannelController* ptr){ this->functionGenToUpdate(ChannelID::CH2, ptr); }); + this, [=](SingleChannelController* ptr){ emit this->functionGenToUpdate(ChannelID::CH2, ptr); }); connect(controller1, &SingleChannelController::setMaxFreq, this, &DualChannelController::setMaxFreq_CH2); diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index b9f055e4..4f1a72b0 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -58,7 +58,6 @@ GobindarDialog::GobindarDialog() genericUsbDriver::genericUsbDriver(QWidget *parent) : QLabel(parent) { - connectedStatus(false); qDebug() << "Making USB Driver invisible!!"; this->hide(); @@ -81,6 +80,11 @@ genericUsbDriver::genericUsbDriver(QWidget *parent) : QLabel(parent) connect(connectTimer.data(), &QTimer::timeout, this, &genericUsbDriver::checkConnection); qDebug()<< "Generic Usb Driver setup complete"; messageBox = new QMessageBox(); + + // Emitting in a constructor does not work + QMetaObject::invokeMethod(this, [this]() { + emit this->connectedStatus(false); + }); } genericUsbDriver::~genericUsbDriver(void){ @@ -264,46 +268,46 @@ void genericUsbDriver::setDeviceMode(int mode){ //switch on new deviceMode!! switch(deviceMode){ case DeviceCH1Analog: - if(oldMode > DeviceCH1AnalogCH2Analog) sendClearBuffer(true,false,false); - setVisible_CH2(false); - checkXY(false); + if(oldMode > DeviceCH1AnalogCH2Analog) emit sendClearBuffer(true,false,false); + emit setVisible_CH2(false); + emit checkXY(false); break; case DeviceCH1AnalogCH2Digital: - if(oldMode > 2) sendClearBuffer(true,false,false); - sendClearBuffer(false,true,false); - setVisible_CH2(true); - checkXY(false); + if(oldMode > 2) emit sendClearBuffer(true,false,false); + emit sendClearBuffer(false,true,false); + emit setVisible_CH2(true); + emit checkXY(false); break; case DeviceCH1AnalogCH2Analog: - if(oldMode > 2) sendClearBuffer(true,false,false); - sendClearBuffer(false,true,false); - setVisible_CH2(true); + if(oldMode > 2) emit sendClearBuffer(true,false,false); + emit sendClearBuffer(false,true,false); + emit setVisible_CH2(true); break; case DeviceCH1Digital: - if(oldMode != 4) sendClearBuffer(true,false,false); - sendClearBuffer(false,true,false); - setVisible_CH2(true); - checkXY(false); + if(oldMode != 4) emit sendClearBuffer(true,false,false); + emit sendClearBuffer(false,true,false); + emit setVisible_CH2(true); + emit checkXY(false); break; case DeviceCH1DigitalCH2Digital : - if(oldMode != 3) sendClearBuffer(true,false,false); - sendClearBuffer(false,true,false); - setVisible_CH2(true); - checkXY(false); + if(oldMode != 3) emit sendClearBuffer(true,false,false); + emit sendClearBuffer(false,true,false); + emit setVisible_CH2(true); + emit checkXY(false); break; case 5: - setVisible_CH2(false); - checkXY(false); + emit setVisible_CH2(false); + emit checkXY(false); break; case DeviceCH1Analog750 : - sendClearBuffer(false,false,true); - setVisible_CH2(false); - checkXY(false); + emit sendClearBuffer(false,false,true); + emit setVisible_CH2(false); + emit checkXY(false); break; case DeviceMultimeter: - sendClearBuffer(true,false,false); - enableMMTimer(); - checkXY(false); + emit sendClearBuffer(true,false,false); + emit enableMMTimer(); + emit checkXY(false); break; default: qFatal("Error in genericUsbDriver::setDeviceMode. Invalid device mode."); @@ -334,7 +338,7 @@ void genericUsbDriver::psuTick(){ void genericUsbDriver::setGain(double newGain){ if (newGain == scopeGain) return; //No update! - gainBuffers(scopeGain/newGain); + emit gainBuffers(scopeGain/newGain); scopeGain = newGain; //See XMEGA_AU Manual, page 359. ADC.CTRL.GAIN. if(newGain==0.5) gainMask = 0x07; @@ -427,7 +431,7 @@ void genericUsbDriver::checkConnection(){ unsigned char initReturnValue; if(!connected){ - connectedStatus(false); + emit connectedStatus(false); qDebug() << "CHECKING CONNECTION!"; initReturnValue = usbInit(BOARD_VID, BOARD_PID); connected = !(initReturnValue); @@ -479,7 +483,7 @@ void genericUsbDriver::checkConnection(){ connectTimer->stop(); delete(connectTimer); - connectedStatus(true); + emit connectedStatus(true); setDeviceMode(deviceMode); @@ -503,10 +507,10 @@ void genericUsbDriver::checkConnection(){ recoveryTimer->setTimerType(Qt::PreciseTimer); recoveryTimer->start(RECOVERY_PERIOD); connect(recoveryTimer.data(), &QTimer::timeout, this, &genericUsbDriver::recoveryTick); - initialConnectComplete(); + emit initialConnectComplete(); if(!killOnConnect && calibrateOnConnect){ - calibrateMe(); + emit calibrateMe(); } } diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index 28090622..a5fb0c81 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -268,7 +268,7 @@ void isoBuffer::maybeOutputSampleToFile(double convertedSample) if (m_fileIO_numBytesWritten >= m_fileIO_maxFileSize) { m_fileIOEnabled = false; // Just in case signalling fails. - fileIOinternalDisable(); + emit fileIOinternalDisable(); } } } diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index d03ba4e3..0a50680c 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -382,7 +382,7 @@ void isoDriver::autoGain(){ for (int i=0;i<8;i++){ if (maxgain>snap[i]){ - setGain(snap[i]); + emit setGain(snap[i]); return; } } @@ -619,7 +619,7 @@ void isoDriver::frameActionGeneric(const ChannelMode CH1_mode, const ChannelMode } if(singleShotEnabled && (triggerDelay > 0)) - singleShotTriggered(true); + emit singleShotTriggered(true); if (triggerDelay < 0) triggerDelay = 0; @@ -740,7 +740,7 @@ void isoDriver::multimeterAction(){ } if(singleShotEnabled && (triggerDelay != 0)) { - singleShotTriggered(true); + emit singleShotTriggered(true); } readData375_CH1 = internalBuffer375_CH1->readBuffer(display.window,GRAPH_SAMPLES, false, display.delay + triggerDelay); @@ -778,13 +778,13 @@ void isoDriver::setMultimeterType(int type){ switch (type){ case R: - multimeterREnabled(multimeterRsource); + emit multimeterREnabled(multimeterRsource); break; case C: - multimeterREnabled(254); + emit multimeterREnabled(254); break; default: - multimeterREnabled(255); + emit multimeterREnabled(255); } qDebug() << "multimeterType = " << multimeterType; @@ -867,57 +867,57 @@ void isoDriver::multimeterStats(){ if(multimeterType == V){ if(mvMax){ currentVmax *= 1000; - sendMultimeterLabel1("Max (mV)"); - }else sendMultimeterLabel1("Max (V)"); + emit sendMultimeterLabel1("Max (mV)"); + }else emit sendMultimeterLabel1("Max (V)"); if(mvMin){ currentVmin *= 1000; - sendMultimeterLabel2("Min (mV)"); - }else sendMultimeterLabel2("Min (V)"); + emit sendMultimeterLabel2("Min (mV)"); + }else emit sendMultimeterLabel2("Min (V)"); if(mvMean){ currentVmean *= 1000; - sendMultimeterLabel3("Mean (mV)"); - }else sendMultimeterLabel3("Mean (V)"); + emit sendMultimeterLabel3("Mean (mV)"); + }else emit sendMultimeterLabel3("Mean (V)"); if(mvRMS){ currentVRMS *= 1000; - sendMultimeterLabel4("RMS (mV)"); - }else sendMultimeterLabel4("RMS (V)"); + emit sendMultimeterLabel4("RMS (mV)"); + }else emit sendMultimeterLabel4("RMS (V)"); - multimeterMax(currentVmax); - multimeterMin(currentVmin); - multimeterMean(currentVmean); - multimeterRMS(currentVRMS); + emit multimeterMax(currentVmax); + emit multimeterMin(currentVmin); + emit multimeterMean(currentVmean); + emit multimeterRMS(currentVRMS); return; } if(multimeterType == I){ if(maMax){ currentVmax *= 1000; - sendMultimeterLabel1("Max (mA)"); - }else sendMultimeterLabel1("Max (A)"); + emit sendMultimeterLabel1("Max (mA)"); + }else emit sendMultimeterLabel1("Max (A)"); if(maMin){ currentVmin *= 1000; - sendMultimeterLabel2("Min (mA)"); - }else sendMultimeterLabel2("Min (A)"); + emit sendMultimeterLabel2("Min (mA)"); + }else emit sendMultimeterLabel2("Min (A)"); if(maMean){ currentVmean *= 1000; - sendMultimeterLabel3("Mean (mA)"); - }else sendMultimeterLabel3("Mean (A)"); + emit sendMultimeterLabel3("Mean (mA)"); + }else emit sendMultimeterLabel3("Mean (A)"); if(maRMS){ currentVRMS *= 1000; - sendMultimeterLabel4("RMS (mA)"); - }else sendMultimeterLabel4("RMS (A)"); + emit sendMultimeterLabel4("RMS (mA)"); + }else emit sendMultimeterLabel4("RMS (A)"); - multimeterMax(currentVmax / seriesResistance); - multimeterMin(currentVmin / seriesResistance); - multimeterMean(currentVmean / seriesResistance); - multimeterRMS(currentVRMS / seriesResistance); + emit multimeterMax(currentVmax / seriesResistance); + emit multimeterMin(currentVmin / seriesResistance); + emit multimeterMean(currentVmean / seriesResistance); + emit multimeterRMS(currentVRMS / seriesResistance); return; } @@ -939,9 +939,9 @@ void isoDriver::multimeterStats(){ //qDebug() << "Vrat = " << Vrat; //qDebug() << "Rp = " << Rp; //qDebug() << "estimated_resistance = " << estimated_resistance; - multimeterMax(0); - multimeterMin(0); - multimeterMean(0); + emit multimeterMax(0); + emit multimeterMin(0); + emit multimeterMean(0); if(autoMultimeterR){ kOhms = (estimated_resistance) > 1000; @@ -949,9 +949,9 @@ void isoDriver::multimeterStats(){ if(kOhms){ estimated_resistance /= 1000; - sendMultimeterLabel4("Resistance (kΩ)"); - }else sendMultimeterLabel4("Resistance (Ω)"); - multimeterRMS(estimated_resistance); + emit sendMultimeterLabel4("Resistance (kΩ)"); + }else emit sendMultimeterLabel4("Resistance (Ω)"); + emit multimeterRMS(estimated_resistance); } if(multimeterType == C){ double cap_vbot = 0.8; @@ -986,13 +986,13 @@ void isoDriver::multimeterStats(){ } if(uFarads){ - sendMultimeterLabel4("Capacitance (μF)"); + emit sendMultimeterLabel4("Capacitance (μF)"); Cm = Cm*1000000; } else { - sendMultimeterLabel4("Capacitance (nF)"); + emit sendMultimeterLabel4("Capacitance (nF)"); Cm = Cm*1000000000; } - multimeterRMS(Cm); + emit multimeterRMS(Cm); } } @@ -1093,24 +1093,24 @@ void isoDriver::setXYmode(bool enabled){ } void isoDriver::triggerGroupStateChange(bool enabled){ - if(enabled) sendTriggerValue((currentVmax-currentVmin)*0.85 + currentVmin); + if(enabled) emit sendTriggerValue((currentVmax-currentVmin)*0.85 + currentVmin); } void isoDriver::broadcastStats(bool CH2){ if(CH2){ if(!update_CH2) return; update_CH2 = false; - sendVmax_CH2(currentVmax); - sendVmin_CH2(currentVmin); - sendVmean_CH2(currentVmean); - sendVRMS_CH2(currentVRMS); + emit sendVmax_CH2(currentVmax); + emit sendVmin_CH2(currentVmin); + emit sendVmean_CH2(currentVmean); + emit sendVRMS_CH2(currentVRMS); } else{ if(!update_CH1) return; update_CH1 = false; - sendVmax_CH1(currentVmax); - sendVmin_CH1(currentVmin); - sendVmean_CH1(currentVmean); - sendVRMS_CH1(currentVRMS); + emit sendVmax_CH1(currentVmax); + emit sendVmin_CH1(currentVmin); + emit sendVmean_CH1(currentVmean); + emit sendVRMS_CH1(currentVRMS); } } @@ -1187,7 +1187,7 @@ void isoDriver::rSourceChanged(int newSource){ void isoDriver::serialNeedsDisabling(int channel){ qDebug("isoDriver acknowledges disconnect from channel %d", channel); - mainWindowPleaseDisableSerial(channel); + emit mainWindowPleaseDisableSerial(channel); } //Thank you https://stackoverflow.com/questions/27318631/parsing-through-a-csv-file-in-qt diff --git a/Desktop_Interface/ui_elements/buffercontrol.cpp b/Desktop_Interface/ui_elements/buffercontrol.cpp index 88f5a55d..9168bb1e 100644 --- a/Desktop_Interface/ui_elements/buffercontrol.cpp +++ b/Desktop_Interface/ui_elements/buffercontrol.cpp @@ -39,7 +39,7 @@ void bufferControl::scopeIn_CH1(bool state){ //What about DSR!? //Turn off the DSR when CH1 is disabled. if(!state && !scopeDsrDisableOverride){ - scopeDsrUncheck(false); + emit scopeDsrUncheck(false); } emit scopeDsrOut(state); @@ -72,13 +72,13 @@ void bufferControl::busSnifferIn_CH1(bool state){ if (busSnifferState_CH2){ //Implicitly state is false busSnifferState_CH2 = false; //updateBuffer(0); - Causes issues because the uncheck below called scopeIn_CH2 (but only when toggle)!!! - busSnifferUncheck(false); + emit busSnifferUncheck(false); } //Signal Gen CH2 doesn't work with bus sniffer. - signalGenOut(!state); + emit signalGenOut(!state); - busSnifferOut_CH2(state); + emit busSnifferOut_CH2(state); updateBuffer(state,1); qDebug() << "busSnifferIn_CH1" << state; @@ -115,44 +115,44 @@ void bufferControl::updateBuffer(bool decrement, int amount){ //Write new state switch(numBuffers){ case 0: - if(scopeState_CH1 == false) scopeOut_CH1(false); - if(scopeState_CH2 == false) scopeOut_CH2(false); - if(scopeDsrState == false) scopeDsrOut(false); + if(scopeState_CH1 == false) emit scopeOut_CH1(false); + if(scopeState_CH2 == false) emit scopeOut_CH2(false); + if(scopeDsrState == false) emit scopeDsrOut(false); //if(signalGenState == false) signalGenOut(0); - if(busSnifferState_CH1 == false) busSnifferOut_CH1(false); - if(busSnifferState_CH2 == false) busSnifferOut_CH2(false); - if(multimeterState == false) multimeterOut(false); + if(busSnifferState_CH1 == false) emit busSnifferOut_CH1(false); + if(busSnifferState_CH2 == false) emit busSnifferOut_CH2(false); + if(multimeterState == false) emit multimeterOut(false); break; case 1: - scopeOut_CH1(true); + emit scopeOut_CH1(true); if(scopeState_CH1 == true){ - scopeDsrOut(true); - scopeOut_CH2(true); + emit scopeDsrOut(true); + emit scopeOut_CH2(true); } //signalGenOut(1); - busSnifferOut_CH1(true); + emit busSnifferOut_CH1(true); if(busSnifferState_CH1 == true){ - busSnifferOut_CH2(true); + emit busSnifferOut_CH2(true); } //busSnifferOut_CH2(1); - multimeterOut(false); + emit multimeterOut(false); break; case 2: - scopeOut_CH1(true); + emit scopeOut_CH1(true); //scopeOut_CH2(1); - if(scopeState_CH1 == true) scopeDsrOut(true); + if(scopeState_CH1 == true) emit scopeDsrOut(true); //signalGenOut(1); - busSnifferOut_CH1(true); + emit busSnifferOut_CH1(true); //busSnifferOut_CH2(1); - multimeterOut(true); + emit multimeterOut(true); break; default: qFatal("numBuffers is not equal to 0, 1 or 2"); } if(scopeDsrDisableOverride){ - scopeDsrOut(false); + emit scopeDsrOut(false); } } @@ -164,7 +164,7 @@ void bufferControl::digIn_CH1(bool state){ else{ digState &= (unsigned char) 0xff - mask; } - updateDig(digState); + emit updateDig(digState); } void bufferControl::digIn_CH2(bool state){ @@ -174,7 +174,7 @@ void bufferControl::digIn_CH2(bool state){ } else{ digState &= (unsigned char) 0xff - mask; - } updateDig(digState); + } emit updateDig(digState); } void bufferControl::digIn_CH3(bool state){ @@ -184,7 +184,7 @@ void bufferControl::digIn_CH3(bool state){ } else{ digState &= (unsigned char) 0xff - mask; - } updateDig(digState); + } emit updateDig(digState); } void bufferControl::digIn_CH4(bool state){ @@ -194,55 +194,55 @@ void bufferControl::digIn_CH4(bool state){ } else{ digState &= (unsigned char) 0xff - mask; - } updateDig(digState); + } emit updateDig(digState); } void bufferControl::updateMode(void){ if(multimeterState){ - modeChange(7); + emit modeChange(7); qDebug() << "Changed to mode 7"; return; } if(scopeDsrState){ - modeChange(6); + emit modeChange(6); qDebug() << "Changed to mode 6"; return; } if (busSnifferState_CH2){ - modeChange(4); + emit modeChange(4); qDebug() << "Changed to mode 4"; return; } if(busSnifferState_CH1 && scopeState_CH1){ - modeChange(1); + emit modeChange(1); qDebug() << "Changed to mode 1"; return; } if (scopeState_CH2){ - modeChange(2); + emit modeChange(2); qDebug() << "Changed to mode 2"; return; } if(busSnifferState_CH1){ - modeChange(3); + emit modeChange(3); qDebug() << "Changed to mode 3"; return; } if(scopeState_CH1){ - modeChange(0); + emit modeChange(0); qDebug() << "Changed to mode 0"; return; } - modeChange(5); + emit modeChange(5); qDebug() << "Changed to mode 5"; return; } void bufferControl::poke(void){ - updateDig(digState); + emit updateDig(digState); updateMode(); updateBuffer(false,0); } diff --git a/Desktop_Interface/ui_elements/cursorenabler.cpp b/Desktop_Interface/ui_elements/cursorenabler.cpp index 227ef169..f71ac948 100644 --- a/Desktop_Interface/ui_elements/cursorenabler.cpp +++ b/Desktop_Interface/ui_elements/cursorenabler.cpp @@ -20,12 +20,12 @@ void cursorEnabler::clickDetected(QMouseEvent* event){ if(m_turnedOn){ if (event->button() == Qt::LeftButton) { - tickHori(true); + emit tickHori(true); } else if (event->button() == Qt::RightButton) { - tickVert(true); + emit tickVert(true); } } - passOnSignal(event); + emit passOnSignal(event); } diff --git a/Desktop_Interface/ui_elements/esposlider.cpp b/Desktop_Interface/ui_elements/esposlider.cpp index 87cd3cd4..45c90eec 100644 --- a/Desktop_Interface/ui_elements/esposlider.cpp +++ b/Desktop_Interface/ui_elements/esposlider.cpp @@ -98,13 +98,13 @@ void espoSlider::selfMoved(int newval) QString newstring; newstring.setNum((double) newval/20, 'f', 2); //qDebug() << newstring; - voltageChanged(((double) newval) / 20); - lcdOut(newstring); + emit voltageChanged(((double) newval) / 20); + emit lcdOut(newstring); return; } void espoSlider::poke(void){ //qDebug() << "Refreshing to voltage" << ((double) (this->value())) / 20; - voltageChanged(((double) (this->value())) / 20); + emit voltageChanged(((double) (this->value())) / 20); } diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 4026eef7..fc722228 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -124,8 +124,8 @@ void unixUsbDriver::usbSendControl(uint8_t RequestType, uint8_t Request, uint16_ } //else qDebug() << "unixUsbDriver::usbSendControl SUCCESS"; if((error == LIBUSB_ERROR_NO_DEVICE) && (Request!=0xa7)){ //Bootloader Jump won't return; this is expected behaviour. qDebug() << "Device not found. Becoming an hero."; - connectedStatus(false); - killMe(); + emit connectedStatus(false); + emit killMe(); } return; } @@ -288,7 +288,7 @@ void unixUsbDriver::isoTimerTick(void){ tcBlockMutex.unlock(); //qDebug() << "Calling upTick()"; - upTick(); + emit upTick(); return; } @@ -335,7 +335,7 @@ int unixUsbDriver::flashFirmware(void){ sprintf(fname, "/firmware/labrafirm_%04x_%02x.hex", EXPECTED_FIRMWARE_VERSION, DEFINED_EXPECTED_VARIANT); qDebug() << "FLASHING " << fname; - signalFirmwareFlash(); + emit signalFirmwareFlash(); QApplication::processEvents(); //Go to bootloader mode From f5219d0ade682f3b2324b16f41b1f5c4b3c395e9 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 16:33:48 +0200 Subject: [PATCH 104/109] use nullptr --- Desktop_Interface/functiongencontrol.h | 2 +- Desktop_Interface/genericusbdriver.h | 2 +- Desktop_Interface/isobuffer.cpp | 2 +- Desktop_Interface/isobuffer.h | 4 ++-- Desktop_Interface/isobuffer_file.cpp | 2 +- Desktop_Interface/isobuffer_file.h | 2 +- Desktop_Interface/isodriver.cpp | 2 +- Desktop_Interface/isodriver.h | 10 +++++----- Desktop_Interface/mainwindow.h | 2 +- Desktop_Interface/scoperangeenterdialog.h | 2 +- Desktop_Interface/uartstyledecoder.h | 2 +- Desktop_Interface/ui_elements/buffercontrol.h | 2 +- Desktop_Interface/ui_elements/cursorenabler.h | 2 +- .../ui_elements/deviceconnecteddisplay.h | 2 +- Desktop_Interface/ui_elements/espocombobox.h | 2 +- Desktop_Interface/ui_elements/esposlider.cpp | 4 ++-- Desktop_Interface/ui_elements/esposlider.h | 2 +- Desktop_Interface/ui_elements/espospinbox.h | 2 +- Desktop_Interface/ui_elements/noclosemenu.h | 2 +- Desktop_Interface/ui_elements/swipeystack.h | 2 +- Desktop_Interface/ui_elements/timedtickbox.h | 4 ++-- Desktop_Interface/ui_elements/voltagespinbox.h | 2 +- Desktop_Interface/unixusbdriver.cpp | 14 +++++++------- Desktop_Interface/unixusbdriver.h | 2 +- 24 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Desktop_Interface/functiongencontrol.h b/Desktop_Interface/functiongencontrol.h index 9e22011b..6b813c75 100644 --- a/Desktop_Interface/functiongencontrol.h +++ b/Desktop_Interface/functiongencontrol.h @@ -56,7 +56,7 @@ class DualChannelController : public QLabel { Q_OBJECT public: - explicit DualChannelController(QWidget *parent = 0); + explicit DualChannelController(QWidget *parent = nullptr); public: SingleChannelController* getChannelController(ChannelID channelID); diff --git a/Desktop_Interface/genericusbdriver.h b/Desktop_Interface/genericusbdriver.h index 911bb685..7cdcd9ef 100644 --- a/Desktop_Interface/genericusbdriver.h +++ b/Desktop_Interface/genericusbdriver.h @@ -88,7 +88,7 @@ class genericUsbDriver : public QLabel //State Vars unsigned char fGenTriple=0; unsigned short gainMask = 2056; - functionGen::SingleChannelController* fGenPtrData[2] = {NULL, NULL}; + functionGen::SingleChannelController* fGenPtrData[2] = {nullptr, nullptr}; int dutyPsu = 0; double currentPsuVoltage; int digitalPinState = 0; diff --git a/Desktop_Interface/isobuffer.cpp b/Desktop_Interface/isobuffer.cpp index a5fb0c81..1d240edf 100644 --- a/Desktop_Interface/isobuffer.cpp +++ b/Desktop_Interface/isobuffer.cpp @@ -392,7 +392,7 @@ int isoBuffer::cap_x2fromLast(double seconds, int x1, double vtop) void isoBuffer::serialManage(double baudRate, UartParity parity, bool hexDisplay) { - if (m_decoder == NULL) + if (m_decoder == nullptr) { m_decoder = new uartStyleDecoder(baudRate, this); } diff --git a/Desktop_Interface/isobuffer.h b/Desktop_Interface/isobuffer.h index 801c2e3d..6a41192b 100644 --- a/Desktop_Interface/isobuffer.h +++ b/Desktop_Interface/isobuffer.h @@ -55,7 +55,7 @@ class isoBuffer : public QWidget { Q_OBJECT public: - isoBuffer(QWidget* parent = 0, int bufferLen = 0, isoDriver* caller = 0, unsigned char channel_value = 0); + isoBuffer(QWidget* parent = nullptr, int bufferLen = 0, isoDriver* caller = nullptr, unsigned char channel_value = 0); ~isoBuffer() = default; // Basic buffer operations @@ -129,7 +129,7 @@ class isoBuffer : public QWidget short m_triggerSensitivity = 0; QVector m_triggerPositionList = {}; // UARTS decoding - uartStyleDecoder* m_decoder = NULL; + uartStyleDecoder* m_decoder = nullptr; bool m_isDecoding = true; private: // File I/O diff --git a/Desktop_Interface/isobuffer_file.cpp b/Desktop_Interface/isobuffer_file.cpp index c7543db7..acac4d6e 100644 --- a/Desktop_Interface/isobuffer_file.cpp +++ b/Desktop_Interface/isobuffer_file.cpp @@ -41,7 +41,7 @@ float *isoBuffer_file::readBuffer(double sampleWindow, int numSamples, bool sing qDebug() << "front" << front; */ int idx, subIdx; - if(readData!=NULL) free(readData); + if(readData!=nullptr) free(readData); readData = (float *) calloc(numSamples, sizeof(float)); diff --git a/Desktop_Interface/isobuffer_file.h b/Desktop_Interface/isobuffer_file.h index 047feea0..1a73276f 100644 --- a/Desktop_Interface/isobuffer_file.h +++ b/Desktop_Interface/isobuffer_file.h @@ -16,7 +16,7 @@ class isoBuffer_file : public QWidget void clearBuffer(); double samplesPerSecond; int bufferEnd, back = 0; - float *buffer, *readData = NULL; + float *buffer, *readData = nullptr; signals: public slots: diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 0a50680c..5788ae37 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -1353,7 +1353,7 @@ void isoDriver::enableFileMode(){ void isoDriver::disableFileMode(){ fileModeEnabled = false; emit showRealtimeButton(false); - if(fileTimer != NULL){ + if(fileTimer != nullptr){ fileTimer->stop(); } diff --git a/Desktop_Interface/isodriver.h b/Desktop_Interface/isodriver.h index 7be3e712..60d66211 100644 --- a/Desktop_Interface/isodriver.h +++ b/Desktop_Interface/isodriver.h @@ -47,7 +47,7 @@ class isoDriver : public QLabel }; Q_ENUM(ChannelMode); - explicit isoDriver(QWidget *parent = 0); + explicit isoDriver(QWidget *parent = nullptr); ~isoDriver(); void autoGain(void); @@ -55,7 +55,7 @@ class isoDriver : public QLabel isoBuffer *internalBuffer375_CH1; isoBuffer *internalBuffer375_CH2; isoBuffer *internalBuffer750; - isoBuffer_file *internalBufferFile = NULL; + isoBuffer_file *internalBufferFile = nullptr; QCPItemText *cursorTextPtr; QPointer driver; bool doNotTouchGraph = true; @@ -174,9 +174,9 @@ class isoDriver : public QLabel i2c::i2cDecoder* twoWire = nullptr; bool twoWireStateInvalid = true; //Generic Vars - QTimer* isoTimer = NULL; - QTimer *slowTimer = NULL; - QTimer *fileTimer = NULL; + QTimer* isoTimer = nullptr; + QTimer *slowTimer = nullptr; + QTimer *fileTimer = nullptr; long total_read = 0; unsigned int length; QFile *snapshotFile_CH1; diff --git a/Desktop_Interface/mainwindow.h b/Desktop_Interface/mainwindow.h index 5197ed5d..65087b82 100644 --- a/Desktop_Interface/mainwindow.h +++ b/Desktop_Interface/mainwindow.h @@ -40,7 +40,7 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); void resizeEvent(QResizeEvent *event) override; void showFileDialog(QString *fileName); diff --git a/Desktop_Interface/scoperangeenterdialog.h b/Desktop_Interface/scoperangeenterdialog.h index c7f9f271..a71c49b8 100644 --- a/Desktop_Interface/scoperangeenterdialog.h +++ b/Desktop_Interface/scoperangeenterdialog.h @@ -17,7 +17,7 @@ class scopeRangeEnterDialog : public QDialog Q_OBJECT public: - explicit scopeRangeEnterDialog(QWidget *parent = 0, bool buttonVisible = true, double yTop = 20, double yBot = -20, double window = -10, double delay = 0); + explicit scopeRangeEnterDialog(QWidget *parent = nullptr, bool buttonVisible = true, double yTop = 20, double yBot = -20, double window = -10, double delay = 0); ~scopeRangeEnterDialog(); public slots: diff --git a/Desktop_Interface/uartstyledecoder.h b/Desktop_Interface/uartstyledecoder.h index 084b6def..9a2e8a6f 100644 --- a/Desktop_Interface/uartstyledecoder.h +++ b/Desktop_Interface/uartstyledecoder.h @@ -19,7 +19,7 @@ class uartStyleDecoder : public QObject { Q_OBJECT public: - explicit uartStyleDecoder(double baudRate, QObject *parent = NULL); + explicit uartStyleDecoder(double baudRate, QObject *parent = nullptr); ~uartStyleDecoder() = default; diff --git a/Desktop_Interface/ui_elements/buffercontrol.h b/Desktop_Interface/ui_elements/buffercontrol.h index d210f1a0..34b38ffd 100644 --- a/Desktop_Interface/ui_elements/buffercontrol.h +++ b/Desktop_Interface/ui_elements/buffercontrol.h @@ -11,7 +11,7 @@ class bufferControl : public QLabel { Q_OBJECT public: - explicit bufferControl(QWidget *parent = 0); + explicit bufferControl(QWidget *parent = nullptr); void refreshImage(void); bool busSnifferState_CH1 = false; bool scopeDsrDisableOverride = false; diff --git a/Desktop_Interface/ui_elements/cursorenabler.h b/Desktop_Interface/ui_elements/cursorenabler.h index 6674deb0..c1f84c63 100644 --- a/Desktop_Interface/ui_elements/cursorenabler.h +++ b/Desktop_Interface/ui_elements/cursorenabler.h @@ -11,7 +11,7 @@ class cursorEnabler : public QLabel { Q_OBJECT public: - explicit cursorEnabler(QWidget *parent = 0); + explicit cursorEnabler(QWidget *parent = nullptr); bool turnedOn() {return m_turnedOn;} private: bool m_turnedOn = true; diff --git a/Desktop_Interface/ui_elements/deviceconnecteddisplay.h b/Desktop_Interface/ui_elements/deviceconnecteddisplay.h index 20dc29b4..c892d3f4 100644 --- a/Desktop_Interface/ui_elements/deviceconnecteddisplay.h +++ b/Desktop_Interface/ui_elements/deviceconnecteddisplay.h @@ -11,7 +11,7 @@ class deviceConnectedDisplay : public QLabel { Q_OBJECT public: - explicit deviceConnectedDisplay(QWidget *parent = 0); + explicit deviceConnectedDisplay(QWidget *parent = nullptr); signals: public slots: void connectedStatusChanged(bool status); diff --git a/Desktop_Interface/ui_elements/espocombobox.h b/Desktop_Interface/ui_elements/espocombobox.h index ff3cba27..9a4737bc 100644 --- a/Desktop_Interface/ui_elements/espocombobox.h +++ b/Desktop_Interface/ui_elements/espocombobox.h @@ -14,7 +14,7 @@ class espoComboBox : public QComboBox { Q_OBJECT public: - explicit espoComboBox(QWidget *parent = 0); + explicit espoComboBox(QWidget *parent = nullptr); void readWaveformList(void); private: signals: diff --git a/Desktop_Interface/ui_elements/esposlider.cpp b/Desktop_Interface/ui_elements/esposlider.cpp index 45c90eec..7f329cf6 100644 --- a/Desktop_Interface/ui_elements/esposlider.cpp +++ b/Desktop_Interface/ui_elements/esposlider.cpp @@ -44,9 +44,9 @@ void espoSlider::moveEvent(QMoveEvent *event){ void espoSlider::setTickInterval(int ti){ - addressBook.resize(maxTick(ti) + 1, NULL); //Leaky, but not significantly. Old qlabels never deleted. + addressBook.resize(maxTick(ti) + 1, nullptr); //Leaky, but not significantly. Old qlabels never deleted. for (size_t i=0; i Date: Sun, 9 May 2021 16:40:23 +0200 Subject: [PATCH 105/109] remove redundant void arguments --- Desktop_Interface/Labrador.pro | 2 +- Desktop_Interface/genericusbdriver.cpp | 10 +++++----- Desktop_Interface/isodriver.cpp | 6 +++--- Desktop_Interface/mainwindow.cpp | 10 +++++----- Desktop_Interface/ui_elements/buffercontrol.cpp | 6 +++--- .../ui_elements/deviceconnecteddisplay.cpp | 2 +- Desktop_Interface/ui_elements/espocombobox.cpp | 2 +- Desktop_Interface/ui_elements/esposlider.cpp | 2 +- Desktop_Interface/unixusbdriver.cpp | 12 ++++++------ 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Desktop_Interface/Labrador.pro b/Desktop_Interface/Labrador.pro index 4fe650d5..ff406d05 100644 --- a/Desktop_Interface/Labrador.pro +++ b/Desktop_Interface/Labrador.pro @@ -11,7 +11,7 @@ QT += core gui -CONFIG += c++14 +CONFIG += c++17 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 4f1a72b0..22fc893c 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -87,7 +87,7 @@ genericUsbDriver::genericUsbDriver(QWidget *parent) : QLabel(parent) }); } -genericUsbDriver::~genericUsbDriver(void){ +genericUsbDriver::~genericUsbDriver(){ qDebug() << "genericUsbDriver dectructor entering"; if(connected){ if (psuTimer) @@ -364,7 +364,7 @@ void genericUsbDriver::setGain(double newGain){ usbSendControl(0x40, 0xa5, deviceMode, gainMask, 0, nullptr); } -void genericUsbDriver::avrDebug(void){ +void genericUsbDriver::avrDebug(){ usbSendControl(0xc0, 0xa0, 0, 0, sizeof(unified_debug), nullptr); qDebug() << "unified debug is of size" << sizeof(unified_debug); @@ -394,17 +394,17 @@ void genericUsbDriver::avrDebug(void){ */ } -void genericUsbDriver::kickstartIso(void){ +void genericUsbDriver::kickstartIso(){ qDebug() << "Attempting to kickstart iso..."; usbSendControl(0x40, 0xaa, 0, 0, 0, nullptr); } -void genericUsbDriver::requestFirmwareVersion(void){ +void genericUsbDriver::requestFirmwareVersion(){ usbSendControl(0xc0, 0xa8, 0, 0, 2, nullptr); firmver = *((unsigned short *) inBuffer); } -void genericUsbDriver::requestFirmwareVariant(void){ +void genericUsbDriver::requestFirmwareVariant(){ usbSendControl(0xc0, 0xa9, 0, 0, 1, nullptr); variant = *((unsigned char *) inBuffer); } diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index 5788ae37..f01cd909 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -67,7 +67,7 @@ void isoDriver::setAxes(QCustomPlot *newAxes){ qDebug() << "axes = " << axes; } -void isoDriver::timerTick(void){ +void isoDriver::timerTick(){ //qDebug() << "isoDriver SEZ Tick!"; if(firstFrame){ autoGain(); @@ -393,7 +393,7 @@ void isoDriver::gainBuffers(double multiplier){ QTimer::singleShot(TIMER_PERIOD*4, this, SLOT(gainTick())); } -void isoDriver::gainTick(void){ +void isoDriver::gainTick(){ #ifdef PLATFORM_ANDROID #warning: "gainTick does nothing on Android!!" #else @@ -479,7 +479,7 @@ void isoDriver::cursorEnableVert(bool enabled){ axes->graph(3)->setVisible(enabled); } -void isoDriver::udateCursors(void){ +void isoDriver::udateCursors(){ if(!(vertCursorEnabled || horiCursorEnabled)){ cursorTextPtr->setVisible(false); return; diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 748221d0..7957f72c 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -1337,7 +1337,7 @@ void MainWindow::readSettingsFile(){ } } -void MainWindow::reinitUsb(void){ +void MainWindow::reinitUsb(){ ui->controller_iso->doNotTouchGraph = true; ui->controller_iso->driver->saveState(&reinitdeviceMode, &reinitScopeGain, &reinitCurrentPsuVoltage, &reinitDigitalPinState); @@ -1354,7 +1354,7 @@ void MainWindow::reinitUsb(void){ qDebug() << "ReinitUsb Stage 1 complete"; } -void MainWindow::reinitUsbStage2(void){ +void MainWindow::reinitUsbStage2(){ qDebug() << "ReinitUsb entering stage 2"; delete(ui->controller_iso->driver); qDebug() << "Reinitialising USB driver!"; @@ -1398,7 +1398,7 @@ void MainWindow::reinitUsbStage2(void){ qDebug() << "ReinitUsbStage2 is returning"; } -void MainWindow::resetUsbState(void){ +void MainWindow::resetUsbState(){ using functionGen::ChannelID; //ui->controller_iso->driver->setDeviceMode(deviceMode); //ui->controller_iso->driver->setPsu(currentPsuVoltage); @@ -2154,7 +2154,7 @@ void MainWindow::on_actionResetDisplay() } -void MainWindow::fileLimitReached_CH1(void){ +void MainWindow::fileLimitReached_CH1(){ ui->actionRecord_CH1->setChecked(false); QMessageBox recordingStoppedMessageBox; @@ -2164,7 +2164,7 @@ void MainWindow::fileLimitReached_CH1(void){ recordingStoppedMessageBox.exec(); } -void MainWindow::fileLimitReached_CH2(void){ +void MainWindow::fileLimitReached_CH2(){ ui->actionRecord_CH2->setChecked(false); QMessageBox recordingStoppedMessageBox; diff --git a/Desktop_Interface/ui_elements/buffercontrol.cpp b/Desktop_Interface/ui_elements/buffercontrol.cpp index 9168bb1e..5bc01004 100644 --- a/Desktop_Interface/ui_elements/buffercontrol.cpp +++ b/Desktop_Interface/ui_elements/buffercontrol.cpp @@ -5,7 +5,7 @@ bufferControl::bufferControl(QWidget *parent) : QLabel(parent) //refreshImage(); } -void bufferControl::refreshImage(void){ +void bufferControl::refreshImage(){ //qDebug() << "Trying to show bitmap " << numBuffers; switch(numBuffers){ @@ -197,7 +197,7 @@ void bufferControl::digIn_CH4(bool state){ } emit updateDig(digState); } -void bufferControl::updateMode(void){ +void bufferControl::updateMode(){ if(multimeterState){ emit modeChange(7); qDebug() << "Changed to mode 7"; @@ -241,7 +241,7 @@ void bufferControl::updateMode(void){ } -void bufferControl::poke(void){ +void bufferControl::poke(){ emit updateDig(digState); updateMode(); updateBuffer(false,0); diff --git a/Desktop_Interface/ui_elements/deviceconnecteddisplay.cpp b/Desktop_Interface/ui_elements/deviceconnecteddisplay.cpp index 6980cbbd..46d24abc 100644 --- a/Desktop_Interface/ui_elements/deviceconnecteddisplay.cpp +++ b/Desktop_Interface/ui_elements/deviceconnecteddisplay.cpp @@ -22,7 +22,7 @@ void deviceConnectedDisplay::connectedStatusChanged(bool status){ #endif } -void deviceConnectedDisplay::flashingFirmware(void){ +void deviceConnectedDisplay::flashingFirmware(){ qDebug() << "deviceConnectedDisplay::flashingFirmware"; this->setText("Flashing Device Firmware"); this->setStyleSheet("QLabel { color:green; }"); diff --git a/Desktop_Interface/ui_elements/espocombobox.cpp b/Desktop_Interface/ui_elements/espocombobox.cpp index 2e76f13d..c8a60d71 100644 --- a/Desktop_Interface/ui_elements/espocombobox.cpp +++ b/Desktop_Interface/ui_elements/espocombobox.cpp @@ -6,7 +6,7 @@ espoComboBox::espoComboBox(QWidget *parent) : QComboBox(parent) } -void espoComboBox::readWaveformList(void) +void espoComboBox::readWaveformList() { const QStringList potentialDirs = { #ifdef PLATFORM_ANDROID diff --git a/Desktop_Interface/ui_elements/esposlider.cpp b/Desktop_Interface/ui_elements/esposlider.cpp index 7f329cf6..5cdb3042 100644 --- a/Desktop_Interface/ui_elements/esposlider.cpp +++ b/Desktop_Interface/ui_elements/esposlider.cpp @@ -103,7 +103,7 @@ void espoSlider::selfMoved(int newval) return; } -void espoSlider::poke(void){ +void espoSlider::poke(){ //qDebug() << "Refreshing to voltage" << ((double) (this->value())) / 20; emit voltageChanged(((double) (this->value())) / 20); } diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index f9d7f9a1..3e522c10 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -16,7 +16,7 @@ unixUsbDriver::unixUsbDriver(QWidget *parent) : genericUsbDriver(parent) } } -unixUsbDriver::~unixUsbDriver(void){ +unixUsbDriver::~unixUsbDriver(){ qDebug() << "\n\nunixUsbDriver destructor ran!"; //unixDriverDeleteMutex.lock(); shutdownMode = true; @@ -130,7 +130,7 @@ void unixUsbDriver::usbSendControl(uint8_t RequestType, uint8_t Request, uint16_ return; } -int unixUsbDriver::usbIsoInit(void){ +int unixUsbDriver::usbIsoInit(){ int error; for(int n=0;n unixUsbDriver::isoRead(unsigned int *newLength){ return outBuffers[(unsigned char) !currentWriteBuffer]; } -void unixUsbDriver::recoveryTick(void){ +void unixUsbDriver::recoveryTick(){ //This should not be called in shutdown mode since it cause double deletion! if(!shutdownMode){ avrDebug(); @@ -328,7 +328,7 @@ void unixUsbDriver::backupCleanup(){ isoHandler->stopTime = true; } -int unixUsbDriver::flashFirmware(void){ +int unixUsbDriver::flashFirmware(){ #ifndef PLATFORM_ANDROID char fname[128]; qDebug() << "\n\n\n\n\n\n\n\nFIRMWARE MISMATCH!!!! FLASHING....\n\n\n\n\n\n\n"; @@ -404,7 +404,7 @@ int unixUsbDriver::flashFirmware(void){ // TODO: port to QWizard, less annoying than an endless list of popup dialogs -void unixUsbDriver::manualFirmwareRecovery(void){ +void unixUsbDriver::manualFirmwareRecovery(){ #ifndef PLATFORM_ANDROID //Get location of firmware file const QString fname = QString::asprintf("/firmware/labrafirm_%04x_%02x.hex", EXPECTED_FIRMWARE_VERSION, DEFINED_EXPECTED_VARIANT); From 867ffab36fd246c1dd384c3e324efdec5b2b4949 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sun, 9 May 2021 16:53:53 +0200 Subject: [PATCH 106/109] fix some minor clang warnings --- Desktop_Interface/daqform.cpp | 2 +- Desktop_Interface/functiongencontrol.cpp | 2 +- Desktop_Interface/genericusbdriver.cpp | 2 +- Desktop_Interface/isobuffer_file.cpp | 2 +- Desktop_Interface/isodriver.cpp | 3 +++ Desktop_Interface/ui_elements/esposlider.cpp | 12 ++++++++---- Desktop_Interface/ui_elements/esposlider.h | 2 +- Desktop_Interface/ui_elements/espospinbox.cpp | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Desktop_Interface/daqform.cpp b/Desktop_Interface/daqform.cpp index 519341ec..42034856 100644 --- a/Desktop_Interface/daqform.cpp +++ b/Desktop_Interface/daqform.cpp @@ -20,7 +20,7 @@ daqForm::daqForm(QWidget *parent, int initialAverage, qulonglong initialMaxFileS ui->limitFileSizeGroupBox->setChecked(false); } else { ui->limitFileSizeGroupBox->setChecked(true); - ui->fileSizeSpinBox->setValue(initialMaxFileSize/1000000); + ui->fileSizeSpinBox->setValue(initialMaxFileSize/1000000ull); } updateLabels(); diff --git a/Desktop_Interface/functiongencontrol.cpp b/Desktop_Interface/functiongencontrol.cpp index 295f0c83..28d6f72f 100644 --- a/Desktop_Interface/functiongencontrol.cpp +++ b/Desktop_Interface/functiongencontrol.cpp @@ -93,7 +93,7 @@ void SingleChannelController::waveformName(QString newName) qWarning() << "Invalid divisor" << divisor; return; } - double newMaxFreq = DAC_SPS / divisor; + double newMaxFreq = double(DAC_SPS) / divisor; double newMinFreq = double(CLOCK_FREQ) / 1024.0 / 65535.0 / static_cast(length); emit setMaxFreq(newMaxFreq); diff --git a/Desktop_Interface/genericusbdriver.cpp b/Desktop_Interface/genericusbdriver.cpp index 22fc893c..a98eabfc 100644 --- a/Desktop_Interface/genericusbdriver.cpp +++ b/Desktop_Interface/genericusbdriver.cpp @@ -27,7 +27,7 @@ GobindarDialog::GobindarDialog() setWindowFlags(Qt::Window); QPalette palette; - palette.setColor(QPalette::Background, Qt::white); + palette.setColor(QPalette::Window, Qt::white); setPalette(palette); QFont largeFont; diff --git a/Desktop_Interface/isobuffer_file.cpp b/Desktop_Interface/isobuffer_file.cpp index acac4d6e..b2aa2749 100644 --- a/Desktop_Interface/isobuffer_file.cpp +++ b/Desktop_Interface/isobuffer_file.cpp @@ -40,7 +40,7 @@ float *isoBuffer_file::readBuffer(double sampleWindow, int numSamples, bool sing qDebug() << "back" << back; qDebug() << "front" << front; */ - int idx, subIdx; + int idx; if(readData!=nullptr) free(readData); readData = (float *) calloc(numSamples, sizeof(float)); diff --git a/Desktop_Interface/isodriver.cpp b/Desktop_Interface/isodriver.cpp index f01cd909..2c0bcbba 100644 --- a/Desktop_Interface/isodriver.cpp +++ b/Desktop_Interface/isodriver.cpp @@ -1169,6 +1169,9 @@ double isoDriver::meanVoltageLast(double seconds, unsigned char channel, int TOP case 3: currentBuffer = internalBuffer750; break; + default: + qWarning() << "Invalid channel!" << channel; + return 0; } std::unique_ptr tempBuffer = currentBuffer->readBuffer(seconds, 1024, false, 0); diff --git a/Desktop_Interface/ui_elements/esposlider.cpp b/Desktop_Interface/ui_elements/esposlider.cpp index 5cdb3042..2eeaa0f9 100644 --- a/Desktop_Interface/ui_elements/esposlider.cpp +++ b/Desktop_Interface/ui_elements/esposlider.cpp @@ -44,10 +44,14 @@ void espoSlider::moveEvent(QMoveEvent *event){ void espoSlider::setTickInterval(int ti){ - addressBook.resize(maxTick(ti) + 1, nullptr); //Leaky, but not significantly. Old qlabels never deleted. - for (size_t i=0; ideleteLater(); +// } + addressBook.resize(maxTick(ti) + 1); //Leaky, but not significantly. Old qlabels never deleted. + for (int i=0; i addressBook; + QVector addressBook; QWidget* windowPointer; int labelMargin; signals: diff --git a/Desktop_Interface/ui_elements/espospinbox.cpp b/Desktop_Interface/ui_elements/espospinbox.cpp index 245899cf..ecc245cb 100644 --- a/Desktop_Interface/ui_elements/espospinbox.cpp +++ b/Desktop_Interface/ui_elements/espospinbox.cpp @@ -31,12 +31,12 @@ QString espoSpinBox::textFromValue(double value) const{ lastValidValue = value; return windowText; } - if (abs(value) >= 1/1000){ + if (abs(value) >= 1./1000){ QTextStream(&windowText) << value * 1000 << "m"; lastValidValue = value; return windowText; } - if (abs(value) >= 1/1000000){ + if (abs(value) >= 1./1000000){ QTextStream(&windowText) << value * 1000000 << "u"; lastValidValue = value; return windowText; From 0ac03232600c84cfdac5c214d5e1218eeb4780ec Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Wed, 26 May 2021 10:04:01 +0200 Subject: [PATCH 107/109] add some translucency to the plots, makes it a bit easier to see overlapping --- Desktop_Interface/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 7957f72c..95b6f5ed 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -335,8 +335,8 @@ void MainWindow::initialisePlot() QPen dashPen(Qt::white, 2); dashPen.setStyle(Qt::DashLine); - ui->scopeAxes->graph(0)->setPen(QPen(Qt::yellow, 1)); - ui->scopeAxes->graph(1)->setPen(QPen(Qt::cyan, 1)); + ui->scopeAxes->graph(0)->setPen(QPen(QColor(255, 255, 0, 192), 1)); + ui->scopeAxes->graph(1)->setPen(QPen(QColor(0, 255, 255, 192), 1)); ui->scopeAxes->graph(2)->setPen(QPen(Qt::white, 2)); ui->scopeAxes->graph(3)->setPen(dashPen); ui->scopeAxes->graph(4)->setPen(QPen(Qt::white, 2)); From 3af25e7548d1a631a8ad81e9005ce69564206f70 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Fri, 30 Jul 2021 15:44:21 +0200 Subject: [PATCH 108/109] accelerator --- Desktop_Interface/ui_files_desktop/mainwindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_Interface/ui_files_desktop/mainwindow.ui b/Desktop_Interface/ui_files_desktop/mainwindow.ui index 2723137e..27744199 100644 --- a/Desktop_Interface/ui_files_desktop/mainwindow.ui +++ b/Desktop_Interface/ui_files_desktop/mainwindow.ui @@ -168,7 +168,7 @@ - Paused + &Paused From 536da41dffbb1fdd933616485f0cab08c52c45c0 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Sat, 9 Oct 2021 20:20:20 +0200 Subject: [PATCH 109/109] some cleanup --- Desktop_Interface/daqform.cpp | 4 ++-- Desktop_Interface/mainwindow.cpp | 12 ++++-------- Desktop_Interface/uartstyledecoder.cpp | 2 +- Desktop_Interface/unixusbdriver.cpp | 14 ++------------ Desktop_Interface/unixusbdriver.h | 26 +++++++++++++------------- 5 files changed, 22 insertions(+), 36 deletions(-) diff --git a/Desktop_Interface/daqform.cpp b/Desktop_Interface/daqform.cpp index 42034856..ae847d1c 100644 --- a/Desktop_Interface/daqform.cpp +++ b/Desktop_Interface/daqform.cpp @@ -46,8 +46,8 @@ daqForm::~daqForm() } void daqForm::updateLabels(){ - double effective_sample_rate_single = 375000/((double)ui->numberOfPointsSpinBox->value()); - double effective_sample_rate_double = 750000/((double)ui->numberOfPointsSpinBox->value()); + double effective_sample_rate_single = 375000/(double(ui->numberOfPointsSpinBox->value())); + double effective_sample_rate_double = 750000/(double(ui->numberOfPointsSpinBox->value())); int num_samples_stored = (ui->fileSizeSpinBox->value() * 1000000) / NUM_BYTES_STORED_PER_DAQ_SAMPLE; //Print in SI units diff --git a/Desktop_Interface/mainwindow.cpp b/Desktop_Interface/mainwindow.cpp index 95b6f5ed..a41a6cfb 100644 --- a/Desktop_Interface/mainwindow.cpp +++ b/Desktop_Interface/mainwindow.cpp @@ -2041,10 +2041,9 @@ void MainWindow::on_actionRecord_CH1_triggered(bool checked) QString fileName; showFileDialog(&fileName); qDebug() << fileName; - int len = fileName.length(); #ifndef PLATFORM_ANDROID - if(len==0){ + if(fileName.isEmpty()){ ui->actionRecord_CH1->setChecked(false); return; //User cancelled } @@ -2072,10 +2071,9 @@ void MainWindow::on_actionRecord_CH2_triggered(bool checked) QString fileName; showFileDialog(&fileName); qDebug() << fileName; - int len = fileName.length(); #ifndef PLATFORM_ANDROID - if(len==0){ + if(fileName.isEmpty()){ ui->actionRecord_CH2->setChecked(false); return; //User cancelled } @@ -2159,7 +2157,7 @@ void MainWindow::fileLimitReached_CH1(){ QMessageBox recordingStoppedMessageBox; char recordingStoppedMessage[256]; - sprintf(recordingStoppedMessage, "Maximum file size limit of %lluMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000); + sprintf(recordingStoppedMessage, "Maximum file size limit of %lluMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000ull); recordingStoppedMessageBox.setText(recordingStoppedMessage); recordingStoppedMessageBox.exec(); } @@ -2168,9 +2166,7 @@ void MainWindow::fileLimitReached_CH2(){ ui->actionRecord_CH2->setChecked(false); QMessageBox recordingStoppedMessageBox; - char recordingStoppedMessage[256]; - sprintf(recordingStoppedMessage, "Maximum file size limit of %lluMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000); - recordingStoppedMessageBox.setText(recordingStoppedMessage); + recordingStoppedMessageBox.setText(QString::asprintf("Maximum file size limit of %lluMB reached. Data Acquisition Stopped.", daq_max_file_size/1000000ull)); recordingStoppedMessageBox.exec(); } diff --git a/Desktop_Interface/uartstyledecoder.cpp b/Desktop_Interface/uartstyledecoder.cpp index 24eb2c82..2f9382f5 100644 --- a/Desktop_Interface/uartstyledecoder.cpp +++ b/Desktop_Interface/uartstyledecoder.cpp @@ -68,7 +68,7 @@ void uartStyleDecoder::serialDecode() else { // Uart starts transmitting after start bit (logic low). - uartTransmitting = uart_bit == false; + uartTransmitting = !uart_bit; jitterCompensationNeeded = true; } diff --git a/Desktop_Interface/unixusbdriver.cpp b/Desktop_Interface/unixusbdriver.cpp index 3e522c10..3bd1f607 100644 --- a/Desktop_Interface/unixusbdriver.cpp +++ b/Desktop_Interface/unixusbdriver.cpp @@ -127,7 +127,6 @@ void unixUsbDriver::usbSendControl(uint8_t RequestType, uint8_t Request, uint16_ emit connectedStatus(false); emit killMe(); } - return; } int unixUsbDriver::usbIsoInit(){ @@ -192,14 +191,6 @@ int unixUsbDriver::usbIsoInit(){ void unixUsbDriver::isoTimerTick(){ timerCount++; - char subString[3] = "th"; - if(timerCount%10 == 1) strcpy(subString, "st"); - if(timerCount%10 == 2) strcpy(subString, "nd"); - if(timerCount%10 == 3) strcpy(subString, "rd"); - if((timerCount<20) && (timerCount > 10)) strcpy(subString, "th"); - - //qDebug("\n\nThis is the %d%s Tick!", timerCount, subString); - int n, error, earliest = MAX_OVERLAP; qint64 minFrame = 9223372036854775807; //max value for 64 bit signed @@ -289,7 +280,6 @@ void unixUsbDriver::isoTimerTick(){ tcBlockMutex.unlock(); //qDebug() << "Calling upTick()"; emit upTick(); - return; } std::shared_ptr unixUsbDriver::isoRead(unsigned int *newLength){ @@ -297,7 +287,7 @@ std::shared_ptr unixUsbDriver::isoRead(unsigned int *newLength){ //return (char*) NULL; //qDebug() << "unixUsbDriver::isoRead"; *(newLength) = bufferLengths[!currentWriteBuffer]; - return outBuffers[(unsigned char) !currentWriteBuffer]; + return outBuffers[uint8_t(!currentWriteBuffer)]; } void unixUsbDriver::recoveryTick(){ @@ -319,7 +309,7 @@ bool unixUsbDriver::allEndpointsComplete(int n){ void unixUsbDriver::shutdownProcedure(){ shutdownMode = true; - QTimer::singleShot(100, this, SLOT(backupCleanup())); + QTimer::singleShot(100, this, &unixUsbDriver::backupCleanup); } //On physical disconnect, isoTimerTick will not assert stopTime. Hence this duct-tape function. diff --git a/Desktop_Interface/unixusbdriver.h b/Desktop_Interface/unixusbdriver.h index 6a26fae6..77e6f0ac 100644 --- a/Desktop_Interface/unixusbdriver.h +++ b/Desktop_Interface/unixusbdriver.h @@ -29,9 +29,9 @@ class worker : public QObject Q_OBJECT public: - worker(){}; - ~worker(){}; - libusb_context *ctx; + worker() = default; + ~worker() override {} + libusb_context *ctx = nullptr; bool stopTime = false; unsigned char cleanupRemaining = NUM_FUTURE_CTX ; public slots: @@ -62,10 +62,10 @@ class unixUsbDriver : public genericUsbDriver Q_OBJECT public: explicit unixUsbDriver(QWidget *parent); - ~unixUsbDriver(); - void usbSendControl(uint8_t RequestType, uint8_t Request, uint16_t Value, uint16_t Index, uint16_t Length, unsigned char *LDATA); - std::shared_ptr isoRead(unsigned int *newLength); - void manualFirmwareRecovery(void); + ~unixUsbDriver() override; + void usbSendControl(uint8_t RequestType, uint8_t Request, uint16_t Value, uint16_t Index, uint16_t Length, unsigned char *LDATA) override; + std::shared_ptr isoRead(unsigned int *newLength) override; + void manualFirmwareRecovery(void) override; protected: //USB Vars libusb_context *ctx = nullptr; @@ -81,17 +81,17 @@ class unixUsbDriver : public genericUsbDriver QPointer workerThread; int cumulativeFramePhaseErrors = 0; //Generic Functions - virtual unsigned char usbInit(unsigned long VIDin, unsigned long PIDin); - int usbIsoInit(void); - virtual int flashFirmware(void); + virtual unsigned char usbInit(unsigned long VIDin, unsigned long PIDin) override; + int usbIsoInit(void) override; + virtual int flashFirmware(void) override; bool allEndpointsComplete(int n); bool shutdownMode = false; int numCancelled = 0; signals: public slots: - void isoTimerTick(void); - void recoveryTick(void); - void shutdownProcedure(void); + void isoTimerTick(void) override; + void recoveryTick(void) override; + void shutdownProcedure(void) override; void backupCleanup(void); };