#include #include #include #include #include #include #include "mainwindow.h" #include "ui_mainwindow.h" #include "triggerwidget.h" MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow), codeEditor(this), pythonOutput(this), pythonRunner(&pythonOutput), currentFilePath(""), isFileModified(false) { ui->setupUi(this); codeEditor.setAutoIndentation(true); codeEditor.setAutoParentheses(true); codeEditor.setHighlighter(&highligter); codeEditor.setCompleter(&completer); QFont font("Monospace"); font.setStyleHint(QFont::TypeWriter); codeEditor.setFont(font); ui->codeLayout->addWidget(&codeEditor, 1); pythonOutput.setReadOnly(true); ui->codeLayout->addWidget(&pythonOutput); // Set up keyboard shortcuts ui->actionOpen->setShortcut(QKeySequence::Open); ui->actionSave->setShortcut(QKeySequence::Save); ui->actionSave_As->setShortcut(QKeySequence::SaveAs); ui->actionQuit->setShortcut(QKeySequence::Quit); connect(ui->actionQuit, &QAction::triggered, this, [this]() {close();}); connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onActionOpenTriggered); connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onActionSaveTriggered); connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::onActionSaveAsTriggered); // Connect text changed signal to track modifications connect(&codeEditor, &QTextEdit::textChanged, this, [this]() { isFileModified = true; updateStatus(); }); // Connect Run and Stop buttons connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript); connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript); connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript); enumerateDevices(); generateExample(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::readState() { for (auto& multiplexer : multiplexers) { channel_t channelstate = eismultiplexer_get_connected(multiplexer.second.get()); for (auto& channel : channels) { for(size_t i = 0; i < 16; ++i) { channel_t mask = static_cast(1 << i); channel->onOtherChannelStateChanged(multiplexer.first, i, channelstate & mask); } } int triggerCount = eismultiplexer_get_trigger_count(multiplexer.second.get()); for(int i = 0; i < triggerCount; ++i) { bool level; trigger_state_t state; int ret = eismultiplexer_get_trigger_state(multiplexer.second.get(), i, &state, &level); if (ret < 0) { for (auto& trigger : triggers) trigger->updateSate(multiplexer.first, i, state); } } } } void MainWindow::updateStatus() { if (!currentFilePath.isEmpty()) { QString status = "EisMultiplexer-Qt"; status = QString("%1").arg(currentFilePath); if (isFileModified) { status += " - [Unsaved Changes]"; } ui->statusbar->showMessage(status); } } void MainWindow::onActionOpenTriggered() { QString filePath = QFileDialog::getOpenFileName(this, tr("Open Python Script"), "", tr("Python Files (*.py);;All Files (*)")); if (filePath.isEmpty()) { return; } QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, tr("Error"), tr("Could not open file: %1").arg(filePath)); return; } QTextStream in(&file); QString content = in.readAll(); file.close(); codeEditor.setPlainText(content); currentFilePath = filePath; isFileModified = false; updateStatus(); } void MainWindow::onActionSaveTriggered() { if (currentFilePath.isEmpty()) { onActionSaveAsTriggered(); return; } QFile file(currentFilePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(this, tr("Error"), tr("Could not save file: %1").arg(currentFilePath)); return; } QTextStream out(&file); out << codeEditor.toPlainText(); file.close(); isFileModified = false; updateStatus(); } void MainWindow::onActionSaveAsTriggered() { QString filePath = QFileDialog::getSaveFileName(this, tr("Save Python Script"), "", tr("Python Files (*.py);;All Files (*)")); if (filePath.isEmpty()) { return; } QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(this, tr("Error"), tr("Could not save file: %1").arg(filePath)); return; } QTextStream out(&file); out << codeEditor.toPlainText(); file.close(); currentFilePath = filePath; isFileModified = false; updateStatus(); } void MainWindow::enumerateDevices() { size_t count = 0; uint16_t* serials = eismultiplexer_list_available_devices(&count); if (ui->scrollArea->layout()) delete ui->scrollArea->layout(); QVBoxLayout* channelLayout = new QVBoxLayout(ui->scrollArea); if (!serials || count == 0) { QMessageBox::warning(nullptr, tr("No Devices Found"), tr("No EIS multiplexer devices were found. Please connect a device and try again.")); qWarning() << "No EIS multiplexer devices found"; exit(0); return; } // First pass: create all widgets without connecting signals for (size_t i = 0; i < count; i++) { uint16_t serial = serials[i]; std::shared_ptr multiplexer(new struct eismultiplexer); int ret = eismultiplexer_connect(multiplexer.get(), serial); if (ret == 0) { multiplexers.push_back({serial, multiplexer}); uint16_t channelCount = 0; qDebug()<<"Adding channels from device "<= 0) { for (uint16_t channel = 0; channel < channelCount; channel++) { std::shared_ptr widget(new ChannelWidget(serial, channel, multiplexer)); qDebug()<<"Added widget from device "<addWidget(widget.get()); } } // Add trigger widgets int triggerCount = eismultiplexer_get_trigger_count(multiplexer.get()); if (triggerCount > 0) { qDebug()<<"Adding triggers from device "< triggerWidget(new TriggerWidget(serial, trigger, multiplexer)); qDebug()<<"Added trigger widget from device "<addWidget(triggerWidget.get()); } } } else { QMessageBox::warning(this, tr("Connection Failed"), tr("Failed to connect to device with serial %1").arg(serial)); qWarning()<<"Failed to connect to device with serial"<addStretch(); // Second pass: populate gang combos and connect signals for channels for (const auto& widget : channels) { // Populate gang combo with all other channels for (const auto& otherWidget : channels) { if (widget->getDeviceSerial() != otherWidget->getDeviceSerial() || widget->getChannelNumber() != otherWidget->getChannelNumber()) { QString channelText = QString::asprintf("%04u, %u", otherWidget->getDeviceSerial(), otherWidget->getChannelNumber()); widget->getGangCombo()->addItem(channelText); } } // Connect state change signals for (const auto& otherWidget : channels) { if (widget.get() != otherWidget.get()) { connect(otherWidget.get(), &ChannelWidget::channelStateChanged, widget.get(), &ChannelWidget::onOtherChannelStateChanged); } } } readState(); ui->statusbar->showMessage("Ready"); free(serials); } void MainWindow::runScript() { QString scriptContent = codeEditor.toPlainText(); pythonRunner.runScript(scriptContent); ui->pushButtonRun->setEnabled(false); ui->pushButtonStop->setEnabled(true); codeEditor.setEnabled(false); ui->scrollArea->setEnabled(false); disconnectDevices(); } void MainWindow::stopScript() { pythonRunner.stopScript(); channels.clear(); triggers.clear(); ui->pushButtonRun->setEnabled(true); ui->pushButtonStop->setEnabled(false); codeEditor.setEnabled(true); ui->scrollArea->setEnabled(true); enumerateDevices(); } void MainWindow::disconnectDevices() { for(auto& multiplexer : multiplexers) eismultiplexer_disconnect(multiplexer.second.get()); multiplexers.clear(); } void MainWindow::generateExample() { QString example = "# This is an example script to show you how\n# to drive eismultiplexer using the python api\n" "import eismultiplexer as multi\n" "from time import sleep\n\n" "# First initalize the device(s)\n"; std::set serials; for (size_t i = 0; i < channels.size(); ++i) serials.insert(channels[i]->getDeviceSerial()); size_t i = 0; for (uint16_t serial : serials) { example.append(QString("multiplexer_") + QString::number(i) + " = multi.Multiplexer(serial=" + QString::number(serial) + ")\n"); ++i; } example.append("\nprint('\\nListing the nummber of channels per unit')\n"); for (size_t i = 0; i < serials.size(); ++i) { QString printLine = "print(f'Found unit with serial number {" + QString::number(channels[i]->getDeviceSerial()) + "} and {multiplexer_" + QString::number(i) + ".getChannelCount()} channels')\n"; example.append(printLine); } example.append("\nprint('Connecting the first and second channel on the first unit')\n"); example.append("multiplexer_0.connectChannel(multi.Channel.A)\n"); example.append("multiplexer_0.connectChannel(multi.Channel.B)\n\n"); example.append("print('Waiting for half a second for something to happen')\n"); example.append("sleep(0.5)\n\n"); example.append("print('Disconnect first channel')\n"); example.append("multiplexer_0.disconnectChannel(multi.Channel.A)\n\n"); example.append("print('Waiting up to 5000 milliseconds for a trigger')\n"); example.append("multiplexer_0.setTriggerState(0, multi.TriggerState.INPUT)\n"); example.append("multiplexer_0.waitTrigger(0, multi.TriggerState.HIGHLEVEL, 5000)\n\n"); example.append("print('Disconnecting all channels')\n"); example.append("multiplexer_0.clear()\n"); codeEditor.setText(example); }