// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "pyside.h" #include "pipsupport.h" #include "pythonplugin.h" #include "pythontr.h" #include "pythonutils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; using namespace ProjectExplorer; namespace Python::Internal { const char installPySideInfoBarId[] = "Python::InstallPySide"; PySideInstaller *PySideInstaller::instance() { static PySideInstaller *instance = new PySideInstaller; // FIXME: Leaks. return instance; } void PySideInstaller::checkPySideInstallation(const FilePath &python, TextEditor::TextDocument *document) { document->infoBar()->removeInfo(installPySideInfoBarId); const QString pySide = importedPySide(document->plainText()); if (pySide == "PySide2" || pySide == "PySide6") instance()->runPySideChecker(python, pySide, document); } bool PySideInstaller::missingPySideInstallation(const FilePath &pythonPath, const QString &pySide) { QTC_ASSERT(!pySide.isEmpty(), return false); static QMap> pythonWithPyside; if (pythonWithPyside[pythonPath].contains(pySide)) return false; Process pythonProcess; pythonProcess.setCommand({pythonPath, {"-c", "import " + pySide}}); pythonProcess.runBlocking(); const bool missing = pythonProcess.result() != ProcessResult::FinishedWithSuccess; if (!missing) pythonWithPyside[pythonPath].insert(pySide); return missing; } QString PySideInstaller::importedPySide(const QString &text) { static QRegularExpression importScanner("^\\s*(import|from)\\s+(PySide\\d)", QRegularExpression::MultilineOption); const QRegularExpressionMatch match = importScanner.match(text); return match.captured(2); } PySideInstaller::PySideInstaller() : QObject(PythonPlugin::instance()) {} void PySideInstaller::installPyside(const FilePath &python, const QString &pySide, TextEditor::TextDocument *document) { document->infoBar()->removeInfo(installPySideInfoBarId); auto install = new PipInstallTask(python); connect(install, &PipInstallTask::finished, install, &QObject::deleteLater); connect(install, &PipInstallTask::finished, this, [=](bool success){ if (success) emit pySideInstalled(python, pySide); }); install->setPackages({PipPackage(pySide)}); install->run(); } void PySideInstaller::handlePySideMissing(const FilePath &python, const QString &pySide, TextEditor::TextDocument *document) { if (!document || !document->infoBar()->canInfoBeAdded(installPySideInfoBarId)) return; const QString message = Tr::tr("%1 installation missing for %2 (%3)") .arg(pySide, pythonName(python), python.toUserOutput()); InfoBarEntry info(installPySideInfoBarId, message, InfoBarEntry::GlobalSuppression::Enabled); auto installCallback = [=]() { installPyside(python, pySide, document); }; const QString installTooltip = Tr::tr("Install %1 for %2 using pip package installer.") .arg(pySide, python.toUserOutput()); info.addCustomButton(Tr::tr("Install"), installCallback, installTooltip); document->infoBar()->addInfo(info); } void PySideInstaller::runPySideChecker(const FilePath &python, const QString &pySide, TextEditor::TextDocument *document) { using CheckPySideWatcher = QFutureWatcher; QPointer watcher = new CheckPySideWatcher(); // cancel and delete watcher after a 10 second timeout QTimer::singleShot(10000, this, [watcher]() { if (watcher) { watcher->cancel(); watcher->deleteLater(); } }); connect(watcher, &CheckPySideWatcher::resultReadyAt, this, [=, document = QPointer(document)]() { if (watcher->result()) handlePySideMissing(python, pySide, document); watcher->deleteLater(); }); watcher->setFuture(Utils::asyncRun(&missingPySideInstallation, python, pySide)); } } // Python::Internal