Compare commits

..

10 commits

Author SHA1 Message Date
Carl Philipp Klemm 4c2a4790c0 Add support for reading state 2025-10-14 12:59:55 +02:00
Carl Philipp Klemm 1eac3f6a83 Cleanup python script running 2025-10-13 18:12:32 +02:00
Carl Philipp Klemm 87db38b08e QCodeEditor: update cmake_minimum_required 2025-10-13 17:06:18 +02:00
Carl Philipp Klemm f802708af4 Merge branch 'python' 2025-10-13 17:03:05 +02:00
Carl Philipp Klemm 738c8805f9 Fix missing = in python example 2025-10-13 17:00:02 +02:00
Carl Philipp Klemm 66937e2cfc Implementation using python invocation using qprocess 2025-10-13 16:30:44 +02:00
Carl Philipp Klemm cd67e8ddd7 Add text edit to hold the output of the python script 2025-10-13 15:47:44 +02:00
Carl Philipp Klemm b6fb6ca7d4 Put the file name of the code editor into the satus bar instead of the title bar 2025-10-13 15:32:47 +02:00
Carl Philipp Klemm 73aa61a13b Merge remote-tracking branch 'uvos/actions' 2025-10-13 15:24:02 +02:00
Carl Philipp Klemm 8ad659de76 Add example generation 2025-10-13 15:21:17 +02:00
13 changed files with 329 additions and 127 deletions

View file

@ -28,6 +28,7 @@ find_package(PkgConfig REQUIRED)
pkg_check_modules(EISMULIPLEXER REQUIRED eismuliplexer) pkg_check_modules(EISMULIPLEXER REQUIRED eismuliplexer)
find_package(Qt6 REQUIRED COMPONENTS Widgets) find_package(Qt6 REQUIRED COMPONENTS Widgets)
find_package(Qt6 REQUIRED COMPONENTS Core) find_package(Qt6 REQUIRED COMPONENTS Core)
# find_package(Python3 REQUIRED) - Removed since we're using QProcess instead of embedding Python
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
@ -41,15 +42,17 @@ add_executable(${PROJECT_NAME}
mainwindow.h mainwindow.h
mainwindow.cpp mainwindow.cpp
mainwindow.ui mainwindow.ui
multiplexer.h
multiplexer.cpp
triggerwidget.cpp triggerwidget.cpp
triggerwidget.h triggerwidget.h
pythonrunner.cpp
pythonrunner.h
) )
set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ON) set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ON)
target_compile_options(${PROJECT_NAME} PUBLIC "-Wall") target_compile_options(${PROJECT_NAME} PUBLIC "-Wall")
target_include_directories(${PROJECT_NAME} PUBLIC QCodeEditor) 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) 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
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/resources/eismultiplexerqt.png PROPERTIES QT_RESOURCE_ALIAS eismultiplexerqt.png) 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 ) qt_add_resources(${PROJECT_NAME} "resources" PREFIX "/" FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/eismultiplexerqt.png )
install(TARGETS ${PROJECT_NAME} DESTINATION bin) install(TARGETS ${PROJECT_NAME} DESTINATION bin)

View file

@ -108,7 +108,6 @@ void ChannelWidget::onGangComboChanged(int index)
gangedChannelNumber = -1; gangedChannelNumber = -1;
checkbox.setEnabled(true); checkbox.setEnabled(true);
checkbox.setChecked(false); checkbox.setChecked(false);
checkbox.setChecked(false);
} else { } else {
// A ganged channel was selected // A ganged channel was selected
QString currentText = gangcombo.currentText(); QString currentText = gangcombo.currentText();
@ -128,7 +127,7 @@ void ChannelWidget::onGangComboChanged(int index)
void ChannelWidget::onOtherChannelStateChanged(uint16_t deviceSerial, uint16_t channelNumber, bool checked) void ChannelWidget::onOtherChannelStateChanged(uint16_t deviceSerial, uint16_t channelNumber, bool checked)
{ {
// If this channel is ganged to the channel that changed state // If this channel is ganged to the channel that changed state
if (gangedDeviceSerial == deviceSerial && gangedChannelNumber == channelNumber) { if (this->deviceSerial == deviceSerial && this->channelNumber == channelNumber) {
// Update our checkbox state to follow the ganged channel // Update our checkbox state to follow the ganged channel
checkbox.blockSignals(true); checkbox.blockSignals(true);
checkbox.setChecked(checked); checkbox.setChecked(checked);
@ -163,3 +162,9 @@ void ChannelWidget::setGangedChannel(uint16_t gangedDeviceSerial, uint16_t gange
updateCheckboxState(); updateCheckboxState();
} }
void ChannelWidget::replaceMultiplexer(std::shared_ptr<struct eismultiplexer> multiplexer, int serial)
{
if(serial == deviceSerial || serial == -1)
this->multiplexer = multiplexer;
}

View file

@ -33,6 +33,7 @@ public slots:
private slots: private slots:
void onChannelToggled(bool checked); void onChannelToggled(bool checked);
void onGangComboChanged(int index); void onGangComboChanged(int index);
void replaceMultiplexer(std::shared_ptr<struct eismultiplexer> multiplexer, int serial = -1);
signals: signals:
void channelAboutToBeTurnedOn(uint16_t deviceSerial, uint16_t channelNumber); void channelAboutToBeTurnedOn(uint16_t deviceSerial, uint16_t channelNumber);

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.6) cmake_minimum_required(VERSION 3.14)
project(QCodeEditor) project(QCodeEditor)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)

View file

@ -1,6 +1,6 @@
#include <eismultiplexer.h>
#include <QMessageBox> #include <QMessageBox>
#include <QMessageBox> #include <QMessageBox>
#include <set>
#include <QFileDialog> #include <QFileDialog>
#include <QTextStream> #include <QTextStream>
#include <QFile> #include <QFile>
@ -12,11 +12,12 @@ MainWindow::MainWindow(QWidget *parent):
QMainWindow(parent), QMainWindow(parent),
ui(new Ui::MainWindow), ui(new Ui::MainWindow),
codeEditor(this), codeEditor(this),
pythonOutput(this),
pythonRunner(&pythonOutput),
currentFilePath(""), currentFilePath(""),
isFileModified(false) isFileModified(false)
{ {
ui->setupUi(this); ui->setupUi(this);
enumerateDevices();
codeEditor.setAutoIndentation(true); codeEditor.setAutoIndentation(true);
codeEditor.setAutoParentheses(true); codeEditor.setAutoParentheses(true);
@ -26,7 +27,10 @@ MainWindow::MainWindow(QWidget *parent):
font.setStyleHint(QFont::TypeWriter); font.setStyleHint(QFont::TypeWriter);
codeEditor.setFont(font); codeEditor.setFont(font);
ui->codeLayout->addWidget(&codeEditor); ui->codeLayout->addWidget(&codeEditor, 1);
pythonOutput.setReadOnly(true);
ui->codeLayout->addWidget(&pythonOutput);
// Set up keyboard shortcuts // Set up keyboard shortcuts
ui->actionOpen->setShortcut(QKeySequence::Open); ui->actionOpen->setShortcut(QKeySequence::Open);
@ -42,8 +46,15 @@ MainWindow::MainWindow(QWidget *parent):
// Connect text changed signal to track modifications // Connect text changed signal to track modifications
connect(&codeEditor, &QTextEdit::textChanged, this, [this]() { connect(&codeEditor, &QTextEdit::textChanged, this, [this]() {
isFileModified = true; isFileModified = true;
updateTitle(); 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() MainWindow::~MainWindow()
@ -51,16 +62,40 @@ MainWindow::~MainWindow()
delete ui; delete ui;
} }
void MainWindow::updateTitle() 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<channel_t>(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()
{ {
QString windowTitle = "EisMultiplexer-Qt";
if (!currentFilePath.isEmpty()) { if (!currentFilePath.isEmpty()) {
windowTitle = QString("%1 - %2").arg(currentFilePath); QString status = "EisMultiplexer-Qt";
} status = QString("%1").arg(currentFilePath);
if (isFileModified) { if (isFileModified) {
windowTitle += "[*]"; status += " - [Unsaved Changes]";
}
ui->statusbar->showMessage(status);
} }
setWindowTitle(windowTitle);
} }
void MainWindow::onActionOpenTriggered() void MainWindow::onActionOpenTriggered()
@ -86,7 +121,7 @@ void MainWindow::onActionOpenTriggered()
codeEditor.setPlainText(content); codeEditor.setPlainText(content);
currentFilePath = filePath; currentFilePath = filePath;
isFileModified = false; isFileModified = false;
updateTitle(); updateStatus();
} }
void MainWindow::onActionSaveTriggered() void MainWindow::onActionSaveTriggered()
@ -107,7 +142,7 @@ void MainWindow::onActionSaveTriggered()
file.close(); file.close();
isFileModified = false; isFileModified = false;
updateTitle(); updateStatus();
} }
void MainWindow::onActionSaveAsTriggered() void MainWindow::onActionSaveAsTriggered()
@ -120,13 +155,6 @@ void MainWindow::onActionSaveAsTriggered()
return; return;
} }
// Ensure the file has a .py extension if it's a Python file
if (filePath.endsWith(".py", Qt::CaseInsensitive)) {
// File already has .py extension
} else {
filePath += ".py";
}
QFile file(filePath); QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, tr("Error"), tr("Could not save file: %1").arg(filePath)); QMessageBox::warning(this, tr("Error"), tr("Could not save file: %1").arg(filePath));
@ -139,13 +167,16 @@ void MainWindow::onActionSaveAsTriggered()
currentFilePath = filePath; currentFilePath = filePath;
isFileModified = false; isFileModified = false;
updateTitle(); updateStatus();
} }
void MainWindow::enumerateDevices() void MainWindow::enumerateDevices()
{ {
size_t count = 0; size_t count = 0;
uint16_t* serials = eismultiplexer_list_available_devices(&count); 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) if (!serials || count == 0)
{ {
@ -164,6 +195,7 @@ void MainWindow::enumerateDevices()
int ret = eismultiplexer_connect(multiplexer.get(), serial); int ret = eismultiplexer_connect(multiplexer.get(), serial);
if (ret == 0) if (ret == 0)
{ {
multiplexers.push_back({serial, multiplexer});
uint16_t channelCount = 0; uint16_t channelCount = 0;
qDebug()<<"Adding channels from device "<<serial; qDebug()<<"Adding channels from device "<<serial;
if (eismultiplexer_get_channel_count(multiplexer.get(), &channelCount) >= 0) if (eismultiplexer_get_channel_count(multiplexer.get(), &channelCount) >= 0)
@ -173,7 +205,7 @@ void MainWindow::enumerateDevices()
std::shared_ptr<ChannelWidget> widget(new ChannelWidget(serial, channel, multiplexer)); std::shared_ptr<ChannelWidget> widget(new ChannelWidget(serial, channel, multiplexer));
qDebug()<<"Added widget from device "<<serial<<" channel "<<channel; qDebug()<<"Added widget from device "<<serial<<" channel "<<channel;
channels.push_back(widget); channels.push_back(widget);
ui->channelLayout->addWidget(widget.get()); channelLayout->addWidget(widget.get());
} }
} }
@ -187,7 +219,7 @@ void MainWindow::enumerateDevices()
std::shared_ptr<TriggerWidget> triggerWidget(new TriggerWidget(serial, trigger, multiplexer)); std::shared_ptr<TriggerWidget> triggerWidget(new TriggerWidget(serial, trigger, multiplexer));
qDebug()<<"Added trigger widget from device "<<serial<<" trigger "<<trigger; qDebug()<<"Added trigger widget from device "<<serial<<" trigger "<<trigger;
triggers.push_back(triggerWidget); triggers.push_back(triggerWidget);
ui->channelLayout->addWidget(triggerWidget.get()); channelLayout->addWidget(triggerWidget.get());
} }
} }
} }
@ -198,7 +230,8 @@ void MainWindow::enumerateDevices()
qWarning()<<"Failed to connect to device with serial"<<serial<<"eismultiplexer_connect returned"<<ret; qWarning()<<"Failed to connect to device with serial"<<serial<<"eismultiplexer_connect returned"<<ret;
} }
} }
ui->channelLayout->addStretch();
channelLayout->addStretch();
// Second pass: populate gang combos and connect signals for channels // Second pass: populate gang combos and connect signals for channels
for (const auto& widget : channels) { for (const auto& widget : channels) {
@ -222,20 +255,79 @@ void MainWindow::enumerateDevices()
} }
} }
readState();
ui->statusbar->showMessage("Ready"); ui->statusbar->showMessage("Ready");
free(serials); 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() void MainWindow::generateExample()
{ {
QString example = QString example =
"import eismultiplexer\n\n" "# This is an example script to show you how\n# to drive eismultiplexer using the python api\n"
"from time import sleep"; "import eismultiplexer as multi\n"
"from time import sleep\n\n"
"# First initalize the device(s)\n";
for (const auto& channel : channels) std::set<uint16_t> 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("eismultiplexer")); 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);
} }

View file

@ -1,16 +1,16 @@
#ifndef MAINWINDOW_H #ifndef MAINWINDOW_H
#define MAINWINDOW_H #define MAINWINDOW_H
#include <eismultiplexer.h>
#include <QMainWindow> #include <QMainWindow>
#include <memory> #include <memory>
#include <QCodeEditor> #include <QCodeEditor>
#include <QPythonCompleter> #include <QPythonCompleter>
#include <QPythonHighlighter> #include <QPythonHighlighter>
#include <QProgressBar>
#include "channelwidget.h" #include "channelwidget.h"
#include "triggerwidget.h" #include "triggerwidget.h"
#include "pythonrunner.h"
namespace Ui namespace Ui
{ {
@ -20,12 +20,15 @@ class MainWindow;
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
std::vector<std::shared_ptr<ChannelWidget>> channels;
std::vector<std::shared_ptr<TriggerWidget>> triggers; std::vector<std::shared_ptr<TriggerWidget>> triggers;
std::vector<std::shared_ptr<ChannelWidget>> channels;
std::vector<std::pair<uint16_t, std::shared_ptr<struct eismultiplexer>>> multiplexers;
Ui::MainWindow *ui; Ui::MainWindow *ui;
QCodeEditor codeEditor; QCodeEditor codeEditor;
QPythonHighlighter highligter; QPythonHighlighter highligter;
QPythonCompleter completer; QPythonCompleter completer;
QTextEdit pythonOutput;
PythonRunner pythonRunner;
signals: signals:
void channelStateChanged(uint16_t device, uint16_t channel); void channelStateChanged(uint16_t device, uint16_t channel);
@ -38,11 +41,15 @@ private slots:
void onActionOpenTriggered(); void onActionOpenTriggered();
void onActionSaveTriggered(); void onActionSaveTriggered();
void onActionSaveAsTriggered(); void onActionSaveAsTriggered();
void runScript();
void stopScript();
void readState();
private: private:
void enumerateDevices(); void enumerateDevices();
void generateExample(); void generateExample();
void updateTitle(); void updateStatus();
void disconnectDevices();
QString currentFilePath; QString currentFilePath;
bool isFileModified; bool isFileModified;
}; };

View file

@ -16,9 +16,14 @@
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="codeLayout"> <layout class="QVBoxLayout" name="codeLayout">
<property name="leftMargin"> <property name="leftMargin">
<number>10</number> <number>0</number>
</property> </property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
@ -48,9 +53,14 @@
</layout> </layout>
</item> </item>
</layout> </layout>
</item> </widget>
<item>
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="widgetResizable"> <property name="widgetResizable">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -59,15 +69,12 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>595</width> <width>504</width>
<height>537</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3"/>
<item> </widget>
<layout class="QVBoxLayout" name="channelLayout"/>
</item>
</layout>
</widget> </widget>
</widget> </widget>
</item> </item>

View file

@ -1,16 +0,0 @@
#include "multiplexer.h"
Multiplexer::Multiplexer(QObject *parent)
: QObject{parent}
{}
Multiplexer::~Multiplexer()
{
for(auto& multiplexer : multiplexers)
eismultiplexer_disconnect(multiplexer.get());
}
void Multiplexer::probe()
{
}

View file

@ -1,22 +0,0 @@
#ifndef MULTIPLEXER_H
#define MULTIPLEXER_H
#include <QObject>
#include "eismultiplexer.h"
class Multiplexer : public QObject
{
Q_OBJECT
std::vector<std::shared_ptr<struct eismultiplexer>> multiplexers;
std::vector<channel_t> channelStates;
public:
explicit Multiplexer(QObject *parent = nullptr);
~Multiplexer();
void probe();
signals:
void foundDevice(std::shared_ptr<struct eismultiplexer>);
};
#endif // MULTIPLEXER_H

56
pythonrunner.cpp Normal file
View file

@ -0,0 +1,56 @@
#include "pythonrunner.h"
#include <QDebug>
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<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PythonRunner::onProcessFinished);
}
PythonRunner::~PythonRunner() {
if (m_process) {
m_process->terminate();
m_process->waitForFinished(1000);
}
}
bool PythonRunner::runScript(const QString& scriptContent) {
if (m_process->state() == QProcess::Running) {
m_process->terminate();
m_process->waitForFinished(1000);
}
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();
}
}
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);
}

31
pythonrunner.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <QTextEdit>
#include <QProcess>
#include <QObject>
#include <QTemporaryDir>
class PythonRunner : public QObject {
Q_OBJECT
public:
PythonRunner(QTextEdit* outputWidget, QObject* parent = nullptr);
~PythonRunner();
bool 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;
};

View file

@ -70,9 +70,46 @@ void TriggerWidget::onLevelToggled(bool checked)
updateTriggerState(); 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() void TriggerWidget::updateTriggerState()
{ {
trigger_state_t state; trigger_state_t state = TRIGGER_INPUT;
if(inputCheckbox.isChecked()) if(inputCheckbox.isChecked())
{ {
levelCheckbox.blockSignals(true); levelCheckbox.blockSignals(true);

View file

@ -22,6 +22,7 @@ public:
uint16_t getDeviceSerial() const; uint16_t getDeviceSerial() const;
uint16_t getTriggerNumber() const; uint16_t getTriggerNumber() const;
void updateSate(uint16_t serial, uint16_t trigger, trigger_state_t state);
private slots: private slots:
void onInputToggled(bool checked); void onInputToggled(bool checked);