Compare commits

..

1 commit
master ... venv

Author SHA1 Message Date
e47c806a2a use venv 2025-10-14 14:44:52 +02:00
4 changed files with 286 additions and 9 deletions

View file

@ -15,7 +15,8 @@ MainWindow::MainWindow(QWidget *parent):
pythonOutput(this), pythonOutput(this),
pythonRunner(&pythonOutput), pythonRunner(&pythonOutput),
currentFilePath(""), currentFilePath(""),
isFileModified(false) isFileModified(false),
uiBlocked(false)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -49,10 +50,19 @@ MainWindow::MainWindow(QWidget *parent):
updateStatus(); updateStatus();
}); });
// Connect venv preparation signals
connect(&pythonRunner, &PythonRunner::venvPreparationProgress, &pythonOutput, [this](const QString& message) {
pythonOutput.append(message);
pythonOutput.moveCursor(QTextCursor::End);
});
connect(&pythonRunner, &PythonRunner::venvPreparationFinished, this, &MainWindow::onVenvPreparationFinished);
// Connect Run and Stop buttons // Connect Run and Stop buttons
connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript); connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript);
connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript); connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript);
connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript); connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript);
connect(&pythonRunner, &PythonRunner::venvPreparationProgress, this, &MainWindow::onVenvPreparationProgress);
enumerateDevices(); enumerateDevices();
generateExample(); generateExample();
} }
@ -264,12 +274,42 @@ void MainWindow::enumerateDevices()
void MainWindow::runScript() { void MainWindow::runScript() {
QString scriptContent = codeEditor.toPlainText(); QString scriptContent = codeEditor.toPlainText();
// Check if we need to prepare the venv
if (!pythonRunner.isVenvReady()) {
// Block UI for venv preparation
blockUIForVenvPreparation();
pythonRunner.runScript(scriptContent);
} else {
// Normal script execution flow
pythonRunner.runScript(scriptContent); pythonRunner.runScript(scriptContent);
ui->pushButtonRun->setEnabled(false); ui->pushButtonRun->setEnabled(false);
ui->pushButtonStop->setEnabled(true); ui->pushButtonStop->setEnabled(true);
codeEditor.setEnabled(false); codeEditor.setEnabled(false);
ui->scrollArea->setEnabled(false); ui->scrollArea->setEnabled(false);
disconnectDevices(); disconnectDevices();
}
}
void MainWindow::onVenvPreparationFinished() {
// Unblock UI when venv preparation is complete
unblockUI();
// If venv is ready, enable the run button and run the script if needed
if (pythonRunner.isVenvReady()) {
ui->pushButtonRun->setEnabled(true);
// If we have a script to run (from the first runScript call), run it now
if (uiBlocked) {
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() { void MainWindow::stopScript() {
@ -283,6 +323,32 @@ void MainWindow::stopScript() {
enumerateDevices(); enumerateDevices();
} }
void MainWindow::onVenvPreparationProgress(const QString& message) {
// Update the output widget with progress
pythonOutput.append(message);
pythonOutput.moveCursor(QTextCursor::End);
}
void MainWindow::blockUIForVenvPreparation() {
uiBlocked = true;
ui->pushButtonRun->setEnabled(false);
ui->pushButtonStop->setEnabled(false);
codeEditor.setEnabled(false);
ui->scrollArea->setEnabled(false);
pythonOutput.append("Preparing Python environment. Please wait...\n");
pythonOutput.moveCursor(QTextCursor::End);
}
void MainWindow::unblockUI() {
if (uiBlocked) {
uiBlocked = false;
// UI will be re-enabled when script execution starts
}
}
void MainWindow::disconnectDevices() void MainWindow::disconnectDevices()
{ {
for(auto& multiplexer : multiplexers) for(auto& multiplexer : multiplexers)

View file

@ -44,14 +44,19 @@ private slots:
void runScript(); void runScript();
void stopScript(); void stopScript();
void readState(); void readState();
void onVenvPreparationProgress(const QString& message);
void onVenvPreparationFinished();
private: private:
void enumerateDevices(); void enumerateDevices();
void generateExample(); void generateExample();
void updateStatus(); void updateStatus();
void disconnectDevices(); void disconnectDevices();
void blockUIForVenvPreparation();
void unblockUI();
QString currentFilePath; QString currentFilePath;
bool isFileModified; bool isFileModified;
bool uiBlocked;
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

View file

@ -1,12 +1,30 @@
#include "pythonrunner.h" #include "pythonrunner.h"
#include <QDebug> #include <QDebug>
#include <QDir>
#include <QStandardPaths>
#include <QCoreApplication>
PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent) PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent)
: QObject(parent), m_outputWidget(outputWidget), m_process(nullptr) { : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr), m_venvProcess(nullptr),
ready(false), venvPreparing(false) {
m_process = new QProcess(this); m_process = new QProcess(this);
m_venvProcess = new QProcess(this);
connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonRunner::onOutputAvailable); connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonRunner::onOutputAvailable);
connect(m_process, &QProcess::readyReadStandardError, this, &PythonRunner::onErrorAvailable); connect(m_process, &QProcess::readyReadStandardError, this, &PythonRunner::onErrorAvailable);
connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PythonRunner::onProcessFinished); connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PythonRunner::onProcessFinished);
connect(m_venvProcess, &QProcess::readyReadStandardOutput, this, [this]() {
QByteArray output = m_venvProcess->readAllStandardOutput();
m_outputWidget->append(output);
m_outputWidget->moveCursor(QTextCursor::End);
emit venvPreparationProgress(output);
});
connect(m_venvProcess, &QProcess::readyReadStandardError, this, [this]() {
QByteArray error = m_venvProcess->readAllStandardError();
m_outputWidget->append(error);
m_outputWidget->moveCursor(QTextCursor::End);
emit venvPreparationProgress(error);
});
connect(m_venvProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PythonRunner::onVenvProcessFinished);
} }
PythonRunner::~PythonRunner() { PythonRunner::~PythonRunner() {
@ -14,6 +32,10 @@ PythonRunner::~PythonRunner() {
m_process->terminate(); m_process->terminate();
m_process->waitForFinished(1000); m_process->waitForFinished(1000);
} }
if (m_venvProcess) {
m_venvProcess->terminate();
m_venvProcess->waitForFinished(1000);
}
} }
bool PythonRunner::runScript(const QString& scriptContent) { bool PythonRunner::runScript(const QString& scriptContent) {
@ -23,7 +45,17 @@ bool PythonRunner::runScript(const QString& scriptContent) {
} }
m_outputWidget->clear(); m_outputWidget->clear();
m_process->start("python3", QStringList() << "-u" << "-c" << scriptContent);
// Check if venv is ready, if not prepare it
if (!isVenvReady()) {
m_outputWidget->append("Preparing Python environment...\n");
m_outputWidget->moveCursor(QTextCursor::End);
prepareVenv();
return true; // Return early - venv preparation will handle script execution when ready
}
QString pythonPath = getVenvPythonPath();
m_process->start(pythonPath, QStringList() << "-u" << "-c" << scriptContent);
return true; return true;
} }
@ -54,3 +86,158 @@ void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStat
} }
emit scriptFinished(exitCode); emit scriptFinished(exitCode);
} }
void PythonRunner::onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::NormalExit && exitCode == 0) {
// Check if this was venv creation or pip installation
QString venvPythonPath = getVenvPythonPath();
if (!QFile::exists(venvPythonPath)) {
// This was venv creation, now install eismultiplexer package
installEismultiplexerPackage();
} else {
// This was pip installation, mark as ready
ready = true;
m_outputWidget->append("Python environment prepared successfully.\n");
// Signal that venv preparation is complete
{
QMutexLocker locker(&venvMutex);
venvPreparing = false;
venvCondition.wakeAll();
}
emit venvPreparationFinished();
}
} else {
ready = false;
m_outputWidget->append(QString("Failed to prepare Python environment (code %1)\n").arg(exitCode));
// Signal that venv preparation failed
{
QMutexLocker locker(&venvMutex);
venvPreparing = false;
venvCondition.wakeAll();
}
emit venvPreparationFinished();
}
}
void PythonRunner::prepareVenv() {
// Start venv preparation in a non-blocking manner
QMutexLocker locker(&venvMutex);
venvPreparing = true;
// Create virtual environment
QString pythonPath = getPythonExecutable();
QString venvPath = getVenvPath();
m_outputWidget->append(QString("Creating virtual environment at %1...\n").arg(venvPath));
m_outputWidget->moveCursor(QTextCursor::End);
QStringList args;
args << "-m" << "venv" << venvPath;
m_venvProcess->start(pythonPath, args);
if (!m_venvProcess->waitForStarted()) {
m_outputWidget->append("Failed to start virtual environment creation process.\n");
ready = false;
venvPreparing = false;
return;
}
// Don't wait for completion - this will be handled by the finished signal
// The installEismultiplexerPackage will be called in the finished signal handler
}
void PythonRunner::installEismultiplexerPackage() {
QString venvPythonPath = getVenvPythonPath();
QString venvPipPath = QString("%1 -m pip").arg(venvPythonPath);
m_outputWidget->append("Installing eismultiplexer package...\n");
m_outputWidget->moveCursor(QTextCursor::End);
QStringList args;
args << "install" << "eismultiplexer";
m_venvProcess->start(venvPipPath, args);
if (!m_venvProcess->waitForStarted()) {
m_outputWidget->append("Failed to start pip installation process.\n");
ready = false;
// Signal that venv preparation failed
{
QMutexLocker locker(&venvMutex);
venvPreparing = false;
venvCondition.wakeAll();
}
emit venvPreparationFinished();
return;
}
// Don't wait for completion - this will be handled by the finished signal
}
QString PythonRunner::getPythonExecutable() {
QString pythonPath;
// Try to find python3 first, then python
QStringList pythonCandidates;
pythonCandidates << "python3" << "python";
for (const QString& candidate : pythonCandidates) {
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString path = env.value("PATH");
QStringList pathDirs = path.split(';', Qt::SkipEmptyParts);
for (const QString& dir : pathDirs) {
QString pythonExecutable = QDir(dir).filePath(candidate);
if (QFile::exists(pythonExecutable)) {
pythonPath = pythonExecutable;
break;
}
}
if (!pythonPath.isEmpty()) {
break;
}
}
// If still not found, use the default python from QProcessEnvironment
if (pythonPath.isEmpty()) {
pythonPath = "python";
}
return pythonPath;
}
QString PythonRunner::getVenvPath() {
// Create venv in a temporary directory
QString venvDir = dir.path() + QDir::separator() + "venv";
return venvDir;
}
QString PythonRunner::getVenvPythonPath() {
QString venvPath = getVenvPath();
QString venvPython;
// Determine the correct python path for the venv based on platform
QString pythonExecutable = "python";
// On Windows, the python executable is in Scripts directory
// On Linux/macOS, it's in bin directory
QString venvBinDir;
#ifdef Q_OS_WIN
venvBinDir = venvPath + QDir::separator() + "Scripts";
#else
venvBinDir = venvPath + QDir::separator() + "bin";
#endif
venvPython = QDir(venvBinDir).filePath(pythonExecutable);
return venvPython;
}
bool PythonRunner::isVenvReady() {
QString venvPythonPath = getVenvPythonPath();
return QFile::exists(venvPythonPath);
}

View file

@ -4,6 +4,9 @@
#include <QProcess> #include <QProcess>
#include <QObject> #include <QObject>
#include <QTemporaryDir> #include <QTemporaryDir>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
class PythonRunner : public QObject { class PythonRunner : public QObject {
Q_OBJECT Q_OBJECT
@ -19,13 +22,29 @@ private slots:
void onOutputAvailable(); void onOutputAvailable();
void onErrorAvailable(); void onErrorAvailable();
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
void onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
signals: signals:
void scriptFinished(int code); void scriptFinished(int code);
void venvPreparationProgress(const QString& message);
void venvPreparationFinished();
private: private:
void prepareVenv();
void installEismultiplexerPackage();
QString getPythonExecutable();
QString getVenvPath();
QString getVenvPythonPath();
public:
bool isVenvReady();
QTextEdit* m_outputWidget; QTextEdit* m_outputWidget;
QProcess* m_process; QProcess* m_process;
QProcess* m_venvProcess;
bool ready; bool ready;
QTemporaryDir dir; QTemporaryDir dir;
QMutex venvMutex;
QWaitCondition venvCondition;
bool venvPreparing;
}; };