diff --git a/CMakeLists.txt b/CMakeLists.txt index 11ab008..8d6f54b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(EISMULIPLEXER REQUIRED eismuliplexer) find_package(Qt6 REQUIRED COMPONENTS Widgets) find_package(Qt6 REQUIRED COMPONENTS Core) -# find_package(Python3 REQUIRED) - Removed since we're using QProcess instead of embedding Python +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) @@ -42,17 +42,19 @@ add_executable(${PROJECT_NAME} mainwindow.h mainwindow.cpp mainwindow.ui + multiplexer.h + multiplexer.cpp triggerwidget.cpp triggerwidget.h pythonrunner.cpp pythonrunner.h + pythonembed.cpp ) set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ON) target_compile_options(${PROJECT_NAME} PUBLIC "-Wall") target_include_directories(${PROJECT_NAME} PUBLIC QCodeEditor) -# target_include_directories(${PROJECT_NAME} PRIVATE ${Python3_INCLUDE_DIRS}) - Removed since we're using QProcess instead of embedding Python -target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets Qt6::Core ${EISMULIPLEXER_LIBRARIES} QCodeEditor) -# ${Python3_LIBRARIES} - Removed since we're using QProcess instead of embedding Python +target_include_directories(${PROJECT_NAME} PRIVATE ${Python3_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets Qt6::Core ${EISMULIPLEXER_LIBRARIES} QCodeEditor ${Python3_LIBRARIES}) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/resources/eismultiplexerqt.png PROPERTIES QT_RESOURCE_ALIAS eismultiplexerqt.png) qt_add_resources(${PROJECT_NAME} "resources" PREFIX "/" FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/eismultiplexerqt.png ) install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/channelwidget.cpp b/channelwidget.cpp index 2f6a02e..5c32f45 100644 --- a/channelwidget.cpp +++ b/channelwidget.cpp @@ -108,6 +108,7 @@ void ChannelWidget::onGangComboChanged(int index) gangedChannelNumber = -1; checkbox.setEnabled(true); checkbox.setChecked(false); + checkbox.setChecked(false); } else { // A ganged channel was selected QString currentText = gangcombo.currentText(); @@ -127,7 +128,7 @@ void ChannelWidget::onGangComboChanged(int index) void ChannelWidget::onOtherChannelStateChanged(uint16_t deviceSerial, uint16_t channelNumber, bool checked) { // If this channel is ganged to the channel that changed state - if (this->deviceSerial == deviceSerial && this->channelNumber == channelNumber) { + if (gangedDeviceSerial == deviceSerial && gangedChannelNumber == channelNumber) { // Update our checkbox state to follow the ganged channel checkbox.blockSignals(true); checkbox.setChecked(checked); @@ -162,9 +163,3 @@ void ChannelWidget::setGangedChannel(uint16_t gangedDeviceSerial, uint16_t gange updateCheckboxState(); } -void ChannelWidget::replaceMultiplexer(std::shared_ptr multiplexer, int serial) -{ - if(serial == deviceSerial || serial == -1) - this->multiplexer = multiplexer; -} - diff --git a/channelwidget.h b/channelwidget.h index f149df0..959ad86 100644 --- a/channelwidget.h +++ b/channelwidget.h @@ -33,7 +33,6 @@ public slots: private slots: void onChannelToggled(bool checked); void onGangComboChanged(int index); - void replaceMultiplexer(std::shared_ptr multiplexer, int serial = -1); signals: void channelAboutToBeTurnedOn(uint16_t deviceSerial, uint16_t channelNumber); diff --git a/external/QCodeEditor/CMakeLists.txt b/external/QCodeEditor/CMakeLists.txt index f0b611b..63918e8 100644 --- a/external/QCodeEditor/CMakeLists.txt +++ b/external/QCodeEditor/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.6) project(QCodeEditor) set(CMAKE_CXX_STANDARD 11) diff --git a/mainwindow.cpp b/mainwindow.cpp index b7bd50e..14a525f 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -13,11 +14,12 @@ MainWindow::MainWindow(QWidget *parent): ui(new Ui::MainWindow), codeEditor(this), pythonOutput(this), - pythonRunner(&pythonOutput), currentFilePath(""), - isFileModified(false) + isFileModified(false), + pythonRunner(nullptr) { ui->setupUi(this); + enumerateDevices(); codeEditor.setAutoIndentation(true); codeEditor.setAutoParentheses(true); @@ -50,11 +52,9 @@ MainWindow::MainWindow(QWidget *parent): }); // 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(); + pythonRunner = new PythonRunner(&pythonOutput, this); + connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::onPushButtonRunClicked); + connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::onPushButtonStopClicked); } MainWindow::~MainWindow() @@ -62,30 +62,6 @@ 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()) { @@ -174,9 +150,6 @@ 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) { @@ -195,7 +168,6 @@ void MainWindow::enumerateDevices() 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) @@ -205,7 +177,7 @@ void MainWindow::enumerateDevices() std::shared_ptr widget(new ChannelWidget(serial, channel, multiplexer)); qDebug()<<"Added widget from device "<addWidget(widget.get()); + ui->channelLayout->addWidget(widget.get()); } } @@ -219,7 +191,7 @@ void MainWindow::enumerateDevices() std::shared_ptr triggerWidget(new TriggerWidget(serial, trigger, multiplexer)); qDebug()<<"Added trigger widget from device "<addWidget(triggerWidget.get()); + ui->channelLayout->addWidget(triggerWidget.get()); } } } @@ -230,8 +202,7 @@ void MainWindow::enumerateDevices() qWarning()<<"Failed to connect to device with serial"<addStretch(); + ui->channelLayout->addStretch(); // Second pass: populate gang combos and connect signals for channels for (const auto& widget : channels) { @@ -255,39 +226,23 @@ void MainWindow::enumerateDevices() } } - readState(); - ui->statusbar->showMessage("Ready"); free(serials); + generateExample(); } -void MainWindow::runScript() { +void MainWindow::onPushButtonRunClicked() { QString scriptContent = codeEditor.toPlainText(); - pythonRunner.runScript(scriptContent); + 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(); +void MainWindow::onPushButtonStopClicked() { + pythonRunner->stopScript(); 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() @@ -305,7 +260,7 @@ void MainWindow::generateExample() size_t i = 0; for (uint16_t serial : serials) { - example.append(QString("multiplexer_") + QString::number(i) + " = multi.Multiplexer(serial=" + QString::number(serial) + ")\n"); + example.append(QString("multiplexer_") + QString::number(i) + " multi.Multiplexer(serial=" + QString::number(serial) + ")\n"); ++i; } diff --git a/mainwindow.h b/mainwindow.h index 686c6a2..df74472 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,13 +1,14 @@ + + #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include #include #include #include #include #include -#include + #include "channelwidget.h" #include "triggerwidget.h" #include "pythonrunner.h" @@ -19,16 +20,15 @@ class MainWindow; class MainWindow : public QMainWindow { - Q_OBJECT - std::vector> triggers; - std::vector> channels; - std::vector>> multiplexers; - Ui::MainWindow *ui; + Q_OBJECT + std::vector> channels; + std::vector> triggers; + Ui::MainWindow *ui; QCodeEditor codeEditor; QPythonHighlighter highligter; QPythonCompleter completer; QTextEdit pythonOutput; - PythonRunner pythonRunner; + PythonRunner* pythonRunner; signals: void channelStateChanged(uint16_t device, uint16_t channel); @@ -41,15 +41,13 @@ private slots: void onActionOpenTriggered(); void onActionSaveTriggered(); void onActionSaveAsTriggered(); - void runScript(); - void stopScript(); - void readState(); + void onPushButtonRunClicked(); + void onPushButtonStopClicked(); private: void enumerateDevices(); void generateExample(); void updateStatus(); - void disconnectDevices(); QString currentFilePath; bool isFileModified; }; diff --git a/mainwindow.ui b/mainwindow.ui index c858820..d32da49 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -16,65 +16,58 @@ - - - Qt::Orientation::Horizontal + + + 10 - - - - 0 - + + - - - - - false - - - Stop - - - - - - - - - - Run - - - - - - - + + + false + + + Stop + + + + + + + + + + Run + + + + + - - - - - 400 - 0 - + + + + + + + true + + + + + 0 + 0 + 591 + 533 + - - true - - - - - 0 - 0 - 504 - 537 - - - - + + + + + diff --git a/multiplexer.cpp b/multiplexer.cpp new file mode 100644 index 0000000..3e1c8e0 --- /dev/null +++ b/multiplexer.cpp @@ -0,0 +1,16 @@ +#include "multiplexer.h" + +Multiplexer::Multiplexer(QObject *parent) + : QObject{parent} +{} + +Multiplexer::~Multiplexer() +{ + for(auto& multiplexer : multiplexers) + eismultiplexer_disconnect(multiplexer.get()); +} + +void Multiplexer::probe() +{ + +} diff --git a/multiplexer.h b/multiplexer.h new file mode 100644 index 0000000..32a04ec --- /dev/null +++ b/multiplexer.h @@ -0,0 +1,22 @@ +#ifndef MULTIPLEXER_H +#define MULTIPLEXER_H + +#include +#include "eismultiplexer.h" + +class Multiplexer : public QObject +{ + Q_OBJECT + std::vector> multiplexers; + std::vector channelStates; + +public: + explicit Multiplexer(QObject *parent = nullptr); + ~Multiplexer(); + void probe(); + +signals: + void foundDevice(std::shared_ptr); +}; + +#endif // MULTIPLEXER_H diff --git a/pythonembed.cpp b/pythonembed.cpp new file mode 100644 index 0000000..c1fa924 --- /dev/null +++ b/pythonembed.cpp @@ -0,0 +1,73 @@ +#include "pythonembed.h" +#pragma push_macro("slots") +#undef slots +#include +#pragma pop_macro("slots") +#include + +PythonEmbed::PythonEmbed(QTextEdit* outputWidget, QObject* parent) + : QObject(parent), m_outputWidget(outputWidget) { + // Initialize Python + Py_Initialize(); + + m_initialized = true; + + // Initialize the output widget with Python prompt + m_outputWidget->clear(); + m_outputWidget->append("Python 3.11.2 (main, Oct 5 2023, 17:20:59) [GCC 11.4.0] on linux\n"); + m_outputWidget->append("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n\n"); + m_outputWidget->append(">>> "); +} + +PythonEmbed::~PythonEmbed() { + if (m_initialized) { + Py_Finalize(); + } +} + +void PythonEmbed::runScript(const QString& scriptContent) { + if (!m_initialized) { + m_outputWidget->append("Python not initialized\n"); + return; + } + + // Convert QString to const char* + QByteArray scriptBytes = scriptContent.toUtf8(); + const char* script = scriptBytes.constData(); + + // Run the script + PyRun_SimpleString(script); + + // Check for any errors + if (PyErr_Occurred()) { + handlePythonError("Script execution error"); + } else { + m_outputWidget->append("\n>>> "); + } +} + +void PythonEmbed::stopScript() { + // In this simple implementation, we don't have a running Python thread + // to stop, but we could add that functionality later +} + +void PythonEmbed::handlePythonError(const char* context) { + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + if (type) { + PyObject *str = PyObject_Str(value); + PyObject *bytes = PyUnicode_AsEncodedString(str, "utf-8", "strict"); + const char *error_msg = PyBytes_AS_STRING(bytes); + + QString errorString = QString::fromUtf8(context) + ": " + QString::fromUtf8(error_msg); + m_outputWidget->append(errorString); + + Py_DecRef(str); + Py_DecRef(bytes); + Py_DecRef(type); + Py_DecRef(value); + Py_DecRef(traceback); + } else { + m_outputWidget->append(QString::fromUtf8(context) + ": No error information available"); + } +} diff --git a/pythonembed.h b/pythonembed.h new file mode 100644 index 0000000..4c72337 --- /dev/null +++ b/pythonembed.h @@ -0,0 +1,25 @@ +#ifndef PYTHONEMBED_H +#define PYTHONEMBED_H + +#include +#include + +class PythonEmbed : public QObject { + Q_OBJECT + +public: + PythonEmbed(QTextEdit* outputWidget, QObject* parent = nullptr); + ~PythonEmbed(); + + void runScript(const QString& scriptContent); + void stopScript(); + +private: + void handlePythonError(const char* context); + + QTextEdit* m_outputWidget; + bool m_initialized = false; +}; + +#endif // PYTHONEMBED_H + diff --git a/pythonrunner.cpp b/pythonrunner.cpp index e2ba0ab..03fdd03 100644 --- a/pythonrunner.cpp +++ b/pythonrunner.cpp @@ -1,56 +1,24 @@ + #include "pythonrunner.h" -#include +#include "pythonembed.h" PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent) - : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr) { - m_process = new QProcess(this); - connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonRunner::onOutputAvailable); - connect(m_process, &QProcess::readyReadStandardError, this, &PythonRunner::onErrorAvailable); - connect(m_process, QOverload::of(&QProcess::finished), this, &PythonRunner::onProcessFinished); + : QObject(parent), m_outputWidget(outputWidget), m_pythonEmbed(nullptr) { + m_pythonEmbed = new PythonEmbed(m_outputWidget, this); } PythonRunner::~PythonRunner() { - if (m_process) { - m_process->terminate(); - m_process->waitForFinished(1000); - } + delete m_pythonEmbed; } -bool PythonRunner::runScript(const QString& scriptContent) { - if (m_process->state() == QProcess::Running) { - m_process->terminate(); - m_process->waitForFinished(1000); +void PythonRunner::runScript(const QString& scriptContent) { + if (m_pythonEmbed) { + m_pythonEmbed->runScript(scriptContent); } - - m_outputWidget->clear(); - m_process->start("python3", QStringList() << "-u" << "-c" << scriptContent); - - return true; } void PythonRunner::stopScript() { - if (m_process && m_process->state() == QProcess::Running) { - m_process->terminate(); + if (m_pythonEmbed) { + m_pythonEmbed->stopScript(); } } - -void PythonRunner::onOutputAvailable() { - QByteArray output = m_process->readAllStandardOutput(); - m_outputWidget->append(output); - m_outputWidget->moveCursor(QTextCursor::End); -} - -void PythonRunner::onErrorAvailable() { - QByteArray error = m_process->readAllStandardError(); - m_outputWidget->append(error); - m_outputWidget->moveCursor(QTextCursor::End); -} - -void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { - if (exitStatus == QProcess::NormalExit && exitCode != 0) { - m_outputWidget->append(QString("Process exited with code %1\n").arg(exitCode)); - } else if (exitStatus == QProcess::CrashExit) { - m_outputWidget->append("Python was stopped\n"); - } - emit scriptFinished(exitCode); -} diff --git a/pythonrunner.h b/pythonrunner.h index 8a8ac15..b5067cb 100644 --- a/pythonrunner.h +++ b/pythonrunner.h @@ -1,31 +1,26 @@ -#pragma once -#include -#include +#ifndef PYTHONRUNNER_H +#define PYTHONRUNNER_H + #include -#include +#include + +class PythonEmbed; class PythonRunner : public QObject { Q_OBJECT public: - PythonRunner(QTextEdit* outputWidget, QObject* parent = nullptr); + explicit PythonRunner(QTextEdit* outputWidget, QObject* parent = nullptr); ~PythonRunner(); - bool runScript(const QString& scriptContent); +public slots: + void runScript(const QString& scriptContent); void stopScript(); -private slots: - void onOutputAvailable(); - void onErrorAvailable(); - void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); - -signals: - void scriptFinished(int code); - private: QTextEdit* m_outputWidget; - QProcess* m_process; - bool ready; - QTemporaryDir dir; + PythonEmbed* m_pythonEmbed; }; + +#endif // PYTHONRUNNER_H diff --git a/triggerwidget.cpp b/triggerwidget.cpp index f051768..efa6627 100644 --- a/triggerwidget.cpp +++ b/triggerwidget.cpp @@ -70,46 +70,9 @@ void TriggerWidget::onLevelToggled(bool checked) updateTriggerState(); } -void TriggerWidget::updateSate(uint16_t serial, uint16_t trigger, trigger_state_t state) -{ - if (serial == deviceSerial && trigger == triggerNumber) { - switch (state) - { - case TRIGGER_INPUT: - levelCheckbox.blockSignals(true); - levelCheckbox.setChecked(false); - levelCheckbox.setEnabled(false); - levelCheckbox.blockSignals(false); - inputCheckbox.blockSignals(true); - inputCheckbox.setChecked(true); - inputCheckbox.blockSignals(false); - break; - case TRIGGER_HIGH: - inputCheckbox.blockSignals(true); - inputCheckbox.setChecked(false); - inputCheckbox.blockSignals(false); - levelCheckbox.blockSignals(true); - levelCheckbox.setChecked(true); - levelCheckbox.setEnabled(true); - levelCheckbox.blockSignals(false); - break; - case TRIGGER_LOW: - inputCheckbox.blockSignals(true); - inputCheckbox.setChecked(false); - inputCheckbox.blockSignals(false); - levelCheckbox.blockSignals(true); - levelCheckbox.setChecked(true); - levelCheckbox.setEnabled(false); - levelCheckbox.blockSignals(false); - default: - break; - } - } -} - void TriggerWidget::updateTriggerState() { - trigger_state_t state = TRIGGER_INPUT; + trigger_state_t state; if(inputCheckbox.isChecked()) { levelCheckbox.blockSignals(true); diff --git a/triggerwidget.h b/triggerwidget.h index 0fe17cb..718ed1b 100644 --- a/triggerwidget.h +++ b/triggerwidget.h @@ -22,7 +22,6 @@ public: uint16_t getDeviceSerial() const; uint16_t getTriggerNumber() const; - void updateSate(uint16_t serial, uint16_t trigger, trigger_state_t state); private slots: void onInputToggled(bool checked);