diff --git a/Software/PC_Application/Generator/generator.cpp b/Software/PC_Application/Generator/generator.cpp index d0e0bcf..0d31495 100644 --- a/Software/PC_Application/Generator/generator.cpp +++ b/Software/PC_Application/Generator/generator.cpp @@ -2,9 +2,8 @@ #include -Generator::Generator(AppWindow *window) - : Mode(window, "Signal Generator") - , SCPINode("GENerator") +Generator::Generator(AppWindow *window, QString name) + : Mode(window, name, "GENerator") { central = new SignalgeneratorWidget(window->getDevice(), window); diff --git a/Software/PC_Application/Generator/generator.h b/Software/PC_Application/Generator/generator.h index 0897bf4..41f4bb5 100644 --- a/Software/PC_Application/Generator/generator.h +++ b/Software/PC_Application/Generator/generator.h @@ -5,13 +5,15 @@ #include "signalgenwidget.h" #include "scpi.h" -class Generator : public Mode, public SCPINode +class Generator : public Mode { public: - Generator(AppWindow *window); + Generator(AppWindow *window, QString name = "Signal Generator"); void deactivate() override; void initializeDevice() override; + virtual Type getType() override { return Type::SG;} + // Nothing to do for now virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index 5d8d08e..74d5f25 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -45,9 +45,8 @@ #include #include -SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window) - : Mode(window, "Spectrum Analyzer"), - SCPINode("SA"), +SpectrumAnalyzer::SpectrumAnalyzer(AppWindow *window, QString name) + : Mode(window, name, "SA"), central(new TileWidget(traceModel, window)) { averages = 1; @@ -422,6 +421,10 @@ using namespace std; void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d) { + if(Mode::getActiveMode() != this) { + return; + } + if(d.pointNum >= settings.pointNum) { qWarning() << "Ignoring point with too large point number (" << d.pointNum << ")"; return; diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h index 46f033a..95a590f 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h @@ -12,15 +12,17 @@ #include #include -class SpectrumAnalyzer : public Mode, public SCPINode +class SpectrumAnalyzer : public Mode { Q_OBJECT public: - SpectrumAnalyzer(AppWindow *window); + SpectrumAnalyzer(AppWindow *window, QString name = "Spectrum Analyzer"); void deactivate() override; void initializeDevice() override; + virtual Type getType() override { return Type::SA;} + // Only save/load user changeable stuff, no need to save the widgets/mode name etc. virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index 44c4fd9..ce8d311 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -51,9 +51,8 @@ #include #include -VNA::VNA(AppWindow *window) - : Mode(window, "Vector Network Analyzer"), - SCPINode("VNA"), +VNA::VNA(AppWindow *window, QString name) + : Mode(window, name, "VNA"), deembedding(traceModel), deembedding_active(false), central(new TileWidget(traceModel)) @@ -790,6 +789,11 @@ using namespace std; void VNA::NewDatapoint(Protocol::Datapoint d) { + if(Mode::getActiveMode() != this) { + // ignore + return; + } + if(changingSettings) { // already setting new sweep settings, ignore incoming points from old settings return; diff --git a/Software/PC_Application/VNA/vna.h b/Software/PC_Application/VNA/vna.h index 9371b45..fb960b0 100644 --- a/Software/PC_Application/VNA/vna.h +++ b/Software/PC_Application/VNA/vna.h @@ -13,17 +13,19 @@ #include #include -class VNA : public Mode, public SCPINode +class VNA : public Mode { Q_OBJECT public: - VNA(AppWindow *window); + VNA(AppWindow *window, QString name = "Vector Network Analyzer"); void deactivate() override; void initializeDevice() override; void deviceDisconnected() override; void shutdown() override; + virtual Type getType() override { return Type::VNA;} + // Only save/load user changeable stuff, no need to save the widgets/mode name etc. virtual nlohmann::json toJSON() override; virtual void fromJSON(nlohmann::json j) override; diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index 2f46774..ad6adf5 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -134,9 +134,9 @@ AppWindow::AppWindow(QWidget *parent) // Create GUI modes central = new QStackedWidget; setCentralWidget(central); - vna = new VNA(this); - generator = new Generator(this); - spectrumAnalyzer = new SpectrumAnalyzer(this); + auto vna = new VNA(this); + auto generator = new Generator(this); + auto spectrumAnalyzer = new SpectrumAnalyzer(this); // UI connections connect(ui->actionUpdate_Device_List, &QAction::triggered, this, &AppWindow::UpdateDeviceList); @@ -278,9 +278,9 @@ void AppWindow::closeEvent(QCloseEvent *event) if(pref.Startup.UseSetupFile && pref.Startup.AutosaveSetupFile) { SaveSetup(pref.Startup.SetupFile); } - vna->shutdown(); - generator->shutdown(); - spectrumAnalyzer->shutdown(); + for(auto m : Mode::getModes()) { + m->shutdown(); + } delete device; QSettings settings; settings.setValue("geometry", saveGeometry()); @@ -475,27 +475,32 @@ void AppWindow::SetupSCPI() if (params.size() != 1) { return "ERROR"; } + Mode *mode = nullptr; if (params[0] == "VNA") { - vna->activate(); + mode = Mode::findFirstOfType(Mode::Type::VNA); } else if(params[0] == "GEN") { - generator->activate(); + mode = Mode::findFirstOfType(Mode::Type::SG); } else if(params[0] == "SA") { - spectrumAnalyzer->activate(); + mode = Mode::findFirstOfType(Mode::Type::SA); } else { return "INVALID MDOE"; } - return ""; - }, [=](QStringList) -> QString { - auto active = Mode::getActiveMode(); - if(active == vna) { - return "VNA"; - } else if(active == generator) { - return "GEN"; - } else if(active == spectrumAnalyzer) { - return "SA"; + if(mode) { + mode->activate(); + return ""; } else { return "ERROR"; } + }, [=](QStringList) -> QString { + auto active = Mode::getActiveMode(); + if(active) { + switch(active->getType()) { + case Mode::Type::VNA: return "VNA"; + case Mode::Type::SG: return "SG"; + case Mode::Type::SA: return "SA"; + } + } + return "ERROR"; })); auto scpi_status = new SCPINode("STAtus"); scpi_dev->add(scpi_status); @@ -553,10 +558,6 @@ void AppWindow::SetupSCPI() return QString::number(Device::Info(getDevice()).limits_maxFreqHarmonic); })); - scpi.add(vna); - scpi.add(generator); - scpi.add(spectrumAnalyzer); - auto scpi_manual = new SCPINode("MANual"); scpi_manual->add(new SCPICommand("STArt",[=](QStringList) -> QString { StartManualControl(); @@ -798,6 +799,11 @@ void AppWindow::StopTCPServer() server = nullptr; } +SCPI* AppWindow::getSCPI() +{ + return &scpi; +} + int AppWindow::UpdateDeviceList() { deviceActionGroup->setExclusive(true); @@ -963,10 +969,18 @@ void AppWindow::SaveSetup(QString filename) nlohmann::json AppWindow::SaveSetup() { nlohmann::json j; - j["activeMode"] = Mode::getActiveMode()->getName().toStdString(); - j["VNA"] = vna->toJSON(); - j["Generator"] = generator->toJSON(); - j["SpectrumAnalyzer"] = spectrumAnalyzer->toJSON(); + nlohmann::json jm; + for(auto m : Mode::getModes()) { + nlohmann::json jmode; + jmode["type"] = Mode::TypeToName(m->getType()).toStdString(); + jmode["name"] = m->getName().toStdString(); + jmode["settings"] = m->toJSON(); + jm.push_back(jmode); + } + j["Modes"] = jm; + if(Mode::getActiveMode()) { + j["activeMode"] = Mode::getActiveMode()->getName().toStdString(); + } nlohmann::json ref; ref["Mode"] = toolbars.reference.type->currentText().toStdString(); ref["Output"] = toolbars.reference.outFreq->currentText().toStdString(); @@ -1003,20 +1017,36 @@ void AppWindow::LoadSetup(nlohmann::json j) toolbars.reference.type->setCurrentText(QString::fromStdString(j["Reference"].value("Mode", "Int"))); toolbars.reference.outFreq->setCurrentText(QString::fromStdString(j["Reference"].value("Output", "Off"))); } + while(Mode::getModes().size() > 0) { + delete Mode::getModes()[0]; + } + + // old style VNA/Generator/Spectrum Analyzer settings if(j.contains("VNA")) { + auto vna = new VNA(this); vna->fromJSON(j["VNA"]); } if(j.contains("Generator")) { + auto generator = new Generator(this); generator->fromJSON(j["Generator"]); } if(j.contains("SpectrumAnalyzer")) { + auto spectrumAnalyzer = new SpectrumAnalyzer(this); spectrumAnalyzer->fromJSON(j["SpectrumAnalyzer"]); } + if(j.contains("Modes")) { + for(auto jm : j["Modes"]) { + auto type = Mode::TypeFromName(QString::fromStdString(jm.value("type", "Invalid"))); + if(type != Mode::Type::Last && jm.contains("settings")) { + auto m = Mode::createNew(this, QString::fromStdString(jm.value("name", "")), type); + m->fromJSON(jm["settings"]); + } + } + } // activate the correct mode QString modeName = QString::fromStdString(j.value("activeMode", "")); - std::vector modes = {vna, generator, spectrumAnalyzer}; - for(auto m : modes) { + for(auto m : Mode::getModes()) { if(m->getName() == modeName) { m->activate(); break; @@ -1112,6 +1142,4 @@ void AppWindow::UpdateStatusBar(DeviceStatusBar status) // invalid status break; } - - } diff --git a/Software/PC_Application/appwindow.h b/Software/PC_Application/appwindow.h index 95f92c8..daec304 100644 --- a/Software/PC_Application/appwindow.h +++ b/Software/PC_Application/appwindow.h @@ -48,6 +48,8 @@ public: static bool showGUI(); + SCPI* getSCPI(); + protected: void closeEvent(QCloseEvent *event) override; private slots: @@ -100,11 +102,6 @@ private: ManualControlDialog *manual; - // Modes - VNA *vna; - Generator *generator; - SpectrumAnalyzer *spectrumAnalyzer; - // Status bar widgets QLabel lConnectionStatus; QLabel lDeviceInfo; diff --git a/Software/PC_Application/mode.cpp b/Software/PC_Application/mode.cpp index 446503b..3f311c7 100644 --- a/Software/PC_Application/mode.cpp +++ b/Software/PC_Application/mode.cpp @@ -1,44 +1,115 @@ #include "mode.h" +#include "Generator/generator.h" +#include "VNA/vna.h" +#include "SpectrumAnalyzer/spectrumanalyzer.h" +#include "CustomWidgets/informationbox.h" + #include "ui_main.h" #include #include #include #include +#include +std::vector Mode::modes; Mode* Mode::activeMode = nullptr; +QTabBar* Mode::tabbar = nullptr; QWidget* Mode::cornerWidget = nullptr; -QButtonGroup* Mode::modeButtonGroup = nullptr; +//QButtonGroup* Mode::modeButtonGroup = nullptr; -Mode::Mode(AppWindow *window, QString name) +Mode::Mode(AppWindow *window, QString name, QString SCPIname) : QObject(window), + SCPINode(SCPIname), window(window), name(name), central(nullptr) -{ +{ + if(!nameAllowed(name)) { + throw std::runtime_error("Unable to create mode, name already taken"); + } // Create mode switch button - auto modeSwitch = new QPushButton(name); - modeSwitch->setCheckable(true); - modeSwitch->setMaximumHeight(window->menuBar()->height()); if(!cornerWidget) { // this is the first created mode, initialize corner widget and set this mode as active - modeSwitch->setChecked(true); - cornerWidget = new QWidget; + cornerWidget = new QWidget(); cornerWidget->setLayout(new QHBoxLayout); cornerWidget->layout()->setSpacing(0); cornerWidget->layout()->setMargin(0); cornerWidget->layout()->setContentsMargins(0,0,0,0); - window->menuBar()->setCornerWidget(cornerWidget); - modeButtonGroup = new QButtonGroup; -// window->menuBar()->setMaximumHeight(window->menuBar()->height()); - } - cornerWidget->layout()->addWidget(modeSwitch); - modeButtonGroup->addButton(modeSwitch); + cornerWidget->setMaximumHeight(window->menuBar()->height()); - connect(modeSwitch, &QPushButton::clicked, [=](){ - activate(); - }); + tabbar = new QTabBar; + tabbar->setTabsClosable(true); + tabbar->setStyleSheet("QTabBar::tab { height: "+QString::number(window->menuBar()->height())+"px;}"); + cornerWidget->layout()->addWidget(tabbar); + + auto bAdd = new QPushButton(); + QIcon icon; + QString iconThemeName = QString::fromUtf8("list-add"); + if (QIcon::hasThemeIcon(iconThemeName)) { + icon = QIcon::fromTheme(iconThemeName); + } else { + icon.addFile(QString::fromUtf8(":/icons/add.png"), QSize(), QIcon::Normal, QIcon::Off); + } + bAdd->setIcon(icon); + + auto mAdd = new QMenu(); + for(unsigned int i=0;i<(int) Type::Last;i++) { + auto type = (Type) i; + auto action = new QAction(TypeToName(type)); + mAdd->addAction(action); + connect(action, &QAction::triggered, [=](){ + bool ok; + QString text = QInputDialog::getText(window, "Create new "+TypeToName(type)+" tab", + "Name:", QLineEdit::Normal, + TypeToName(type), &ok); + if(ok) { + if(!nameAllowed(text)) { + InformationBox::ShowError("Name collision", "Unable to create tab, no duplicate names allowed"); + } else { + auto mode = Mode::createNew(window, text, type); + mode->activate(); + } + } + }); + } + bAdd->setMenu(mAdd); + bAdd->setMaximumHeight(window->menuBar()->height()); + bAdd->setMaximumWidth(40); + cornerWidget->layout()->addWidget(bAdd); + + window->menuBar()->setCornerWidget(cornerWidget); + + connect(tabbar, &QTabBar::currentChanged, [=](int index){ + modes[index]->activate(); + }); + connect(tabbar, &QTabBar::tabCloseRequested, [=](int index){ + delete modes[index]; + }); + } + modes.push_back(this); + tabbar->blockSignals(true); + tabbar->insertTab(tabbar->count(), name); + tabbar->blockSignals(false); + window->getSCPI()->add(this); +} + +Mode::~Mode() +{ + window->getSCPI()->remove(this); + if(activeMode == this) { + deactivate(); + } + auto index = findTabIndex(); + tabbar->blockSignals(true); + tabbar->removeTab(index); + tabbar->blockSignals(false); + modes.erase(modes.begin() + index); + if(modes.size() > 0) { + modes[tabbar->currentIndex()]->activate(); + } + window->getCentral()->removeWidget(central); } void Mode::activate() @@ -89,14 +160,10 @@ void Mode::activate() } activeMode = this; - // force activation of correct pushbutton in case the mode switch was done via script/setup load. - // This will trigger a second activation of this mode in the signal of the button, but since it is + // force activation of correct tab in case the mode switch was done via script/setup load. + // This will trigger a second activation of this mode in the signal of the tab bar, but since it is // already the active mode, this function will just return -> no recursion - for(auto b : modeButtonGroup->buttons()) { - if(b->text() == name) { - b->click(); - } - } + tabbar->setCurrentIndex(findTabIndex()); if(window->getDevice()) { initializeDevice(); @@ -131,6 +198,9 @@ void Mode::deactivate() } qDebug() << "Deactivated mode" << name; + if(window->getDevice()) { + window->getDevice()->SetIdle(); + } activeMode = nullptr; } @@ -139,6 +209,26 @@ Mode *Mode::getActiveMode() return activeMode; } +QString Mode::TypeToName(Mode::Type t) +{ + switch(t) { + case Type::VNA: return "Vector Network Analyzer"; + case Type::SG: return "Signal Generator"; + case Type::SA: return "Spectrum Analyzer"; + default: return "Invalid"; + } +} + +Mode::Type Mode::TypeFromName(QString s) +{ + for(unsigned int i=0;i<(int)Type::Last;i++) { + if(s == TypeToName((Type) i)) { + return (Type) i; + } + } + return Type::Last; +} + void Mode::saveSreenshot() { auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, QFileDialog::DontUseNativeDialog); @@ -153,6 +243,27 @@ void Mode::saveSreenshot() central->grab().save(filename); } +Mode *Mode::createNew(AppWindow *window, QString name, Mode::Type t) +{ + switch(t) { + case Type::VNA: return new VNA(window, name); + case Type::SG: return new Generator(window, name); + case Type::SA: return new SpectrumAnalyzer(window, name); + default: return nullptr; + } +} + +bool Mode::nameAllowed(QString name) +{ + for(auto m : modes) { + if(m->getName() == name) { + // name already taken, no duplicates allowed + return false; + } + } + return true; +} + void Mode::finalize(QWidget *centralWidget) { central = centralWidget; @@ -176,6 +287,27 @@ void Mode::finalize(QWidget *centralWidget) } } +int Mode::findTabIndex() +{ + auto it = std::find(modes.begin(), modes.end(), this); + return it - modes.begin(); +} + +std::vector Mode::getModes() +{ + return modes; +} + +Mode *Mode::findFirstOfType(Mode::Type t) +{ + for(auto m : modes) { + if(m->getType() == t) { + return m; + } + } + return nullptr; +} + void Mode::setStatusbarMessage(QString msg) { statusbarMsg = msg; @@ -188,3 +320,14 @@ QString Mode::getName() const { return name; } + +void Mode::setName(const QString &value) +{ + if(!nameAllowed(value)) { + // unable to use this name + return; + } + name = value; + tabbar->setTabText(findTabIndex(), name); +} + diff --git a/Software/PC_Application/mode.h b/Software/PC_Application/mode.h index b5f91f2..9d2f065 100644 --- a/Software/PC_Application/mode.h +++ b/Software/PC_Application/mode.h @@ -3,30 +3,50 @@ #include "appwindow.h" #include "savable.h" +#include "scpi.h" #include #include #include #include +#include #include #include -class Mode : public QObject, public Savable +class Mode : public QObject, public Savable, public SCPINode { Q_OBJECT public: - Mode(AppWindow *window, QString name); + enum class Type { + VNA, + SG, + SA, + Last, + }; + + Mode(AppWindow *window, QString name, QString SCPIname); + ~Mode(); virtual void activate(); // derived classes must call Mode::activate before doing anything virtual void deactivate(); // derived classes must call Mode::deactivate before returning virtual void shutdown(){}; // called when the application is about to exit QString getName() const; + void setName(const QString &value); static Mode *getActiveMode(); + static QString TypeToName(Type t); + static Type TypeFromName(QString s); + virtual Type getType() = 0; virtual void initializeDevice() = 0; virtual void deviceDisconnected(){}; virtual void saveSreenshot(); + + static Mode *createNew(AppWindow *window, QString name, Type t); + static bool nameAllowed(QString name); + static std::vector getModes(); + static Mode* findFirstOfType(Type t); + signals: void statusbarMessage(QString msg); protected: @@ -39,10 +59,13 @@ protected: std::set docks; private: + int findTabIndex(); + static std::vector modes; static Mode *activeMode; + static QTabBar *tabbar; static QWidget *cornerWidget; - static QButtonGroup *modeButtonGroup; - const QString name; +// static QButtonGroup *modeButtonGroup; + QString name; QString statusbarMsg; QWidget *central; }; diff --git a/Software/PC_Application/scpi.cpp b/Software/PC_Application/scpi.cpp index 01b31d1..5e5e730 100644 --- a/Software/PC_Application/scpi.cpp +++ b/Software/PC_Application/scpi.cpp @@ -106,6 +106,17 @@ bool SCPINode::add(SCPINode *node) return true; } +bool SCPINode::remove(SCPINode *node) +{ + auto it = std::find(subnodes.begin(), subnodes.end(), node); + if(it != subnodes.end()) { + subnodes.erase(it); + return true; + } else { + return false; + } +} + bool SCPINode::add(SCPICommand *cmd) { if(nameCollision(cmd->name())) { diff --git a/Software/PC_Application/scpi.h b/Software/PC_Application/scpi.h index 57614f2..97ab8b3 100644 --- a/Software/PC_Application/scpi.h +++ b/Software/PC_Application/scpi.h @@ -31,6 +31,7 @@ public: name(name){} bool add(SCPINode *node); + bool remove(SCPINode *node); bool add(SCPICommand *cmd); private: