1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
|
/*
SPDX-FileCopyrightText: 2013 Sven Brauch <svenbrauch@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "docfilewizard.h"
#include "docfilemanagerwidget.h"
#include <QGroupBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QBoxLayout>
#include <QPushButton>
#include <QTabWidget>
#include <QScrollBar>
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
#include <KLocalizedString>
#include <KMessageDialog>
#include <KMessageBox>
#include <KProcess>
#include <interfaces/icore.h>
#include <interfaces/iproject.h>
#include <interfaces/iprojectcontroller.h>
#include <project/projectmodel.h>
#include <util/path.h>
DocfileWizard::DocfileWizard(const QString& workingDirectory, QWidget* parent)
: QDialog(parent)
, worker(nullptr)
, workingDirectory(workingDirectory)
{
setLayout(new QVBoxLayout);
// The interpreter group box
QGroupBox* interpreter = new QGroupBox;
interpreter->setTitle(i18n("Configure the Python interpreter to use"));
QFormLayout* interpreterLayout = new QFormLayout;
interpreterField = new QLineEdit(QStringLiteral("python"));
interpreterLayout->addRow(new QLabel(i18n("Python executable")), interpreterField);
interpreter->setLayout(interpreterLayout);
// The module + output file group box
QGroupBox* module = new QGroupBox;
module->setTitle(i18n("Select a python module to generate documentation for"));
QFormLayout* moduleLayout = new QFormLayout;
moduleField = new QLineEdit;
moduleLayout->addRow(new QLabel(i18nc("refers to selecting a python module to perform some operation on",
"Target module (e.g. \"math\")")), moduleField);
outputFilenameField = new QLineEdit;
moduleLayout->addRow(new QLabel(i18n("Output filename")), outputFilenameField);
module->setLayout(moduleLayout);
// Status group box
QGroupBox* status = new QGroupBox;
QTabWidget* tabs = new QTabWidget;
status->setTitle(i18n("Status and output"));
statusField = new QTextEdit();
statusField->setText(i18n("The process has not been run yet."));
statusField->setFontFamily(QStringLiteral("monospace"));
statusField->setLineWrapMode(QTextEdit::NoWrap);
statusField->setReadOnly(true);
statusField->setAcceptRichText(false);
resultField = new QTextEdit();
resultField->setText(i18n("The process has not been run yet."));
resultField->setFontFamily(QStringLiteral("monospace"));
resultField->setLineWrapMode(QTextEdit::NoWrap);
resultField->setReadOnly(true);
resultField->setAcceptRichText(false);
status->setLayout(new QHBoxLayout);
tabs->addTab(statusField, i18n("Script output"));
tabs->addTab(resultField, i18n("Results"));
status->layout()->addWidget(tabs);
// The run / close buttons
QHBoxLayout* buttonsLayout = new QHBoxLayout;
buttonsLayout->setDirection(QBoxLayout::RightToLeft);
QPushButton* closeButton = new QPushButton(i18n("Close"));
closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
saveButton = new QPushButton(i18n("Save and close"));
saveButton->setEnabled(false);
saveButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")));
runButton = new QPushButton(i18n("Generate"));
runButton->setDefault(true);
runButton->setIcon(QIcon::fromTheme(QStringLiteral("tools-wizard")));
buttonsLayout->addWidget(closeButton);
buttonsLayout->addWidget(runButton);
buttonsLayout->addWidget(saveButton);
buttonsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
// connections
QObject::connect(closeButton, &QAbstractButton::clicked, this, &QWidget::close);
QObject::connect(saveButton, &QAbstractButton::clicked, this, &DocfileWizard::saveAndClose);
QObject::connect(moduleField, &QLineEdit::textChanged, this, &DocfileWizard::updateOutputFilename);
QObject::connect(runButton, &QAbstractButton::clicked, this, &DocfileWizard::run);
// putting it all together
layout()->addWidget(interpreter);
layout()->addWidget(module);
layout()->addWidget(status);
layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
qobject_cast<QVBoxLayout*>(layout())->addLayout(buttonsLayout); // TODO ugh
resize(640, 480);
}
const QString DocfileWizard::wasSavedAs() const
{
return savedAs;
}
QString DocfileWizard::fileNameForModule(QString moduleName) const
{
if ( moduleName.isEmpty() ) {
return moduleName;
}
return moduleName.replace(QLatin1Char('.'), QLatin1Char('/')) + QStringLiteral(".py");
}
void DocfileWizard::setModuleName(const QString& moduleName)
{
moduleField->setText(moduleName);
}
bool DocfileWizard::run()
{
// validate input data, setup and program state
if ( worker ) {
// process already running
return false;
}
QString scriptUrl = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevpythonsupport/scripts/introspect.py"));
if ( scriptUrl.isEmpty() ) {
KMessageBox::error(this, i18n("Couldn't find the introspect.py script; check your installation!"));
return false;
}
if ( workingDirectory.isEmpty() ) {
KMessageBox::error(this, i18n("Couldn't find a valid kdev-python data directory; check your installation!"));
return false;
}
QString outputFilename = outputFilenameField->text();
if ( outputFilename.contains(QStringLiteral("..")) ) {
// protect the user from writing outside the data directory accidentally
KMessageBox::error(this, i18n("Invalid output filename"));
return false;
}
runButton->setEnabled(false);
// clean output from previous script runs; since the fields are set to readonly,
// no user data will be lost
statusField->clear();
resultField->clear();
// set up the process and connect relevant slots
QString interpreter = interpreterField->text();
QString module = moduleField->text();
worker = new QProcess(this);
QObject::connect(worker, &QProcess::readyReadStandardError, this, &DocfileWizard::processScriptOutput);
QObject::connect(worker, &QProcess::readyReadStandardOutput, this, &DocfileWizard::processScriptOutput);
QObject::connect(worker, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &DocfileWizard::processFinished);
// can never have too many slashes
outputFile.setFileName(workingDirectory + QStringLiteral("/") + outputFilename);
const QList<KDevelop::IProject*> projs = KDevelop::ICore::self()->projectController()->projects();
QStringList args;
args << scriptUrl;
for (const KDevelop::IProject* proj : std::as_const(projs)) {
if ( proj )
args << proj->path().toLocalFile();
}
args << module;
worker->start(interpreter, args);
return true;
}
void DocfileWizard::saveAndClose()
{
bool mayWrite = true;
if ( outputFile.exists() ) {
//mayWrite = FIXME
KMessageDialog(KMessageDialog::QuestionTwoActions,
i18n("The output file <br/>%1<br/> already exists, do you want to overwrite it?",
outputFile.fileName()), this);
}
if ( mayWrite ) {
auto url = QUrl::fromLocalFile(outputFile.fileName());
Q_ASSERT(url.isLocalFile());
auto basePath = url.url(QUrl::RemoveFilename | QUrl::PreferLocalFile);
// should have been done previously
Q_ASSERT(QDir(basePath).exists());
if ( ! QDir(basePath).exists() ) {
QDir(basePath).mkpath(basePath);
}
outputFile.open(QIODevice::WriteOnly);
QString header = QStringLiteral("\"\"\"") +
i18n("This file contains auto-generated documentation extracted\n"
"from python run-time information. It is analyzed by KDevelop\n"
"to offer features such as code-completion and syntax highlighting.\n"
"If you discover errors in KDevelop's support for this module,\n"
"you can edit this file to correct the errors, e.g. you can add\n"
"additional return statements to functions to control the return\n"
"type to be used for that function by the analyzer.\n"
"Make sure to keep a copy of your changes so you don't accidentally\n"
"overwrite them by re-generating the file.\n") + QStringLiteral("\"\"\"\n\n");
outputFile.write(header.toUtf8() + resultField->toPlainText().toUtf8());
outputFile.close();
savedAs = outputFile.fileName();
close();
}
}
void DocfileWizard::processScriptOutput()
{
statusField->insertPlainText(QString::fromLatin1(worker->readAllStandardError()));
resultField->insertPlainText(QString::fromLatin1(worker->readAllStandardOutput()));
QScrollBar* scrollbar = statusField->verticalScrollBar();
scrollbar->setValue(scrollbar->maximum());
}
void DocfileWizard::processFinished(int, QProcess::ExitStatus)
{
worker = nullptr;
runButton->setEnabled(true);
saveButton->setEnabled(true);
}
void DocfileWizard::updateOutputFilename(const QString& newModuleName)
{
QString newFileName = fileNameForModule(newModuleName);
if ( fileNameForModule(previousModuleName) == outputFilenameField->text() ) {
// the user didn't edit the field, or edited it to what it is anyways, so update the text
// otherwise, do nothing.
outputFilenameField->setText(newFileName);
}
previousModuleName = newModuleName;
}
#include "moc_docfilewizard.cpp"
|