From e47c806a2a82ad13d4eab784b22a037a9b83fc7c Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Tue, 14 Oct 2025 14:44:52 +0200 Subject: [PATCH] use venv --- mainwindow.cpp | 80 ++++++++++++++++++-- mainwindow.h | 5 ++ pythonrunner.cpp | 191 ++++++++++++++++++++++++++++++++++++++++++++++- pythonrunner.h | 19 +++++ 4 files changed, 286 insertions(+), 9 deletions(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index b7bd50e..9803186 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -15,7 +15,8 @@ MainWindow::MainWindow(QWidget *parent): pythonOutput(this), pythonRunner(&pythonOutput), currentFilePath(""), - isFileModified(false) + isFileModified(false), + uiBlocked(false) { ui->setupUi(this); @@ -49,10 +50,19 @@ MainWindow::MainWindow(QWidget *parent): 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(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript); connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript); connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript); + connect(&pythonRunner, &PythonRunner::venvPreparationProgress, this, &MainWindow::onVenvPreparationProgress); + enumerateDevices(); generateExample(); } @@ -264,12 +274,42 @@ void MainWindow::enumerateDevices() 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(); + + // 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); + ui->pushButtonRun->setEnabled(false); + ui->pushButtonStop->setEnabled(true); + codeEditor.setEnabled(false); + ui->scrollArea->setEnabled(false); + 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() { @@ -283,6 +323,32 @@ void MainWindow::stopScript() { 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() { for(auto& multiplexer : multiplexers) diff --git a/mainwindow.h b/mainwindow.h index 686c6a2..ac39e0b 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -44,14 +44,19 @@ private slots: void runScript(); void stopScript(); void readState(); + void onVenvPreparationProgress(const QString& message); + void onVenvPreparationFinished(); private: void enumerateDevices(); void generateExample(); void updateStatus(); void disconnectDevices(); + void blockUIForVenvPreparation(); + void unblockUI(); QString currentFilePath; bool isFileModified; + bool uiBlocked; }; #endif // MAINWINDOW_H diff --git a/pythonrunner.cpp b/pythonrunner.cpp index e2ba0ab..99e79d2 100644 --- a/pythonrunner.cpp +++ b/pythonrunner.cpp @@ -1,12 +1,30 @@ #include "pythonrunner.h" #include +#include +#include +#include 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_venvProcess = 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); + 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::of(&QProcess::finished), this, &PythonRunner::onVenvProcessFinished); } PythonRunner::~PythonRunner() { @@ -14,6 +32,10 @@ PythonRunner::~PythonRunner() { m_process->terminate(); m_process->waitForFinished(1000); } + if (m_venvProcess) { + m_venvProcess->terminate(); + m_venvProcess->waitForFinished(1000); + } } bool PythonRunner::runScript(const QString& scriptContent) { @@ -23,7 +45,17 @@ bool PythonRunner::runScript(const QString& scriptContent) { } 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; } @@ -54,3 +86,158 @@ void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStat } 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); +} diff --git a/pythonrunner.h b/pythonrunner.h index 8a8ac15..c160c67 100644 --- a/pythonrunner.h +++ b/pythonrunner.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include class PythonRunner : public QObject { Q_OBJECT @@ -19,13 +22,29 @@ private slots: void onOutputAvailable(); void onErrorAvailable(); void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); signals: void scriptFinished(int code); + void venvPreparationProgress(const QString& message); + void venvPreparationFinished(); private: + void prepareVenv(); + void installEismultiplexerPackage(); + QString getPythonExecutable(); + QString getVenvPath(); + QString getVenvPythonPath(); + +public: + bool isVenvReady(); + QTextEdit* m_outputWidget; QProcess* m_process; + QProcess* m_venvProcess; bool ready; QTemporaryDir dir; + QMutex venvMutex; + QWaitCondition venvCondition; + bool venvPreparing; };