Files
GoldenCheetah/src/FileIO/FixPyScriptsDialog.cpp
Joachim Kohlhammer 000a76983d "Automated only"-flagging for DataProcessors
* Options > Data Fields > Processors & Automation (renamed from
  "Processing")
  * Modernized the UI
  * Added UI to set Automation (None, On Import, On Save) for all
    processors
  * Added UI to set Automated execution only for all processors
  * Showing the processors description
  * Showing the processors setting (if available) in a more userfriendly
    way
  * Added option to add / delete / edit custom Python processors
  * Enabled editing of Python processors via double click
  * Added option to hide code processors
* Removed the submenu to manage Python Fixes from the Edit-menu
* Renamed the Edit-menu to "Process"
* Hiding "automated only"-processors from Process-menu and
  Batchprocessing-dialog
* DataProcessors
  * Turned configuration of all core processors into forms
  * Changed the technical name of all core processors to match their
    classname
  * Added legacy technical name to all core processors to support existing
    scripts
  * Moved description from config-class to DataProcessor
  * Implemented a migration path for existing persisted processor
    configurations
* GSettings
  * Added method to remove settings by key
2024-11-26 20:04:33 -03:00

378 lines
10 KiB
C++

#include <QGroupBox>
#include "FixPyScriptsDialog.h"
#include "MainWindow.h"
#include "Colors.h"
#include "AbstractView.h"
#include "PythonEmbed.h"
#include "PythonSyntax.h"
ManageFixPyScriptsWidget::ManageFixPyScriptsWidget
(Context *context, QWidget *parent)
: QGroupBox(tr("Select a Python Fix to manage"), parent), context(context)
{
scripts = new QListWidget;
scripts->setAlternatingRowColors(true);
QDialogButtonBox *manageButtonBox = new QDialogButtonBox();
manageButtonBox->setOrientation(Qt::Vertical);
QPushButton *newButton = manageButtonBox->addButton(tr("New"), QDialogButtonBox::ActionRole);
edit = manageButtonBox->addButton(tr("Edit"), QDialogButtonBox::ActionRole);
del = manageButtonBox->addButton(tr("Delete"), QDialogButtonBox::ActionRole);
QHBoxLayout *manageBox = new QHBoxLayout(this);
manageBox->addWidget(scripts);
manageBox->addWidget(manageButtonBox);
connect(newButton, &QPushButton::clicked, this, &ManageFixPyScriptsWidget::newClicked);
connect(scripts, &QListWidget::itemDoubleClicked, this, &ManageFixPyScriptsWidget::editClicked);
connect(scripts, &QListWidget::itemSelectionChanged, this, &ManageFixPyScriptsWidget::scriptSelected);
connect(edit, &QPushButton::clicked, this, &ManageFixPyScriptsWidget::editClicked);
connect(del, &QPushButton::clicked, this, &ManageFixPyScriptsWidget::delClicked);
reload(0);
}
void
ManageFixPyScriptsWidget::newClicked
()
{
EditFixPyScriptDialog editDlg(context, nullptr, this);
if (editDlg.exec() == QDialog::Accepted) {
reload(editDlg.getName());
}
}
void
ManageFixPyScriptsWidget::editClicked
()
{
if (scripts->currentItem() == nullptr) {
return;
}
FixPyScript *script = fixPySettings->getScript(scripts->currentItem()->text());
EditFixPyScriptDialog editDlg(context, script, this);
if (editDlg.exec() == QDialog::Accepted) {
reload(editDlg.getName());
}
}
void
ManageFixPyScriptsWidget::delClicked
()
{
if (scripts->currentItem() == nullptr) {
return;
}
QString name = scripts->currentItem()->text();
QString msg = QString(tr("Are you sure you want to delete %1?")).arg(name);
QMessageBox::StandardButtons result = QMessageBox::question(this, "GoldenCheetah", msg);
if (result == QMessageBox::No) {
return;
}
fixPySettings->deleteScript(name);
reload(scripts->currentRow());
}
void
ManageFixPyScriptsWidget::scriptSelected
()
{
edit->setEnabled(scripts->currentItem() != nullptr);
del->setEnabled(scripts->currentItem() != nullptr);
}
void
ManageFixPyScriptsWidget::reload
()
{
scripts->clear();
QList<FixPyScript*> fixes = fixPySettings->getScripts();
for (int i = 0; i < fixes.size(); i++) {
QListWidgetItem *item = new QListWidgetItem();
item->setText(fixes[i]->name);
scripts->addItem(item);
}
edit->setEnabled(false);
del->setEnabled(false);
}
void
ManageFixPyScriptsWidget::reload
(int selRow)
{
reload();
QList<FixPyScript*> fixes = fixPySettings->getScripts();
if (scripts->count() > 0) {
scripts->setCurrentRow(std::max(0, std::min(selRow, scripts->count() - 1)));
}
}
void
ManageFixPyScriptsWidget::reload
(const QString &selName)
{
reload();
QList<FixPyScript*> fixes = fixPySettings->getScripts();
for (int i = 0; i < fixes.size(); i++) {
if (fixes[i]->name == selName) {
scripts->setCurrentRow(i);
return;
}
}
}
ManageFixPyScriptsDialog::ManageFixPyScriptsDialog
(Context *context)
{
setWindowTitle(tr("Manage Python Fixes"));
setMinimumSize(QSize(700 * dpiXFactor, 600 * dpiYFactor));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(new ManageFixPyScriptsWidget(context));
mainLayout->addWidget(buttonBox);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
EditFixPyScriptDialog::EditFixPyScriptDialog(Context *context, FixPyScript *fix, QWidget *parent)
: QDialog(parent), context(context), pyFixScript(fix)
{
setWindowTitle(tr("Edit Python Fix"));
setMinimumSize(QSize(1200 * dpiXFactor, 650 * dpiYFactor));
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QSplitter *outerSplitter = new QSplitter(Qt::Horizontal);
outerSplitter->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
outerSplitter->setHandleWidth(1);
QSplitter *splitter = new QSplitter(Qt::Vertical);
splitter->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
splitter->setHandleWidth(1);
// LHS
QFrame *scriptBox = new QFrame;
scriptBox->setFrameStyle(QFrame::NoFrame);
QFormLayout *scriptLayout = newQFormLayout();
scriptLayout->setContentsMargins(0, 0, 0, 10);
scriptBox->setLayout(scriptLayout);
scriptName = new QLineEdit(fix ? fix->name : "");
script = new QTextEdit;
script->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
script->setFrameStyle(QFrame::NoFrame);
script->setAcceptRichText(false);
QFont courier("Courier", QFont().pointSize());
script->setFont(courier);
QPalette p = palette();
p.setColor(QPalette::Base, GColor(CPLOTBACKGROUND));
p.setColor(QPalette::Text, GCColor::invertColor(GColor(CPLOTBACKGROUND)));
script->setPalette(p);
script->setStyleSheet(AbstractView::ourStyleSheet());
// syntax highlighter
setScript(fix ? fix->source : "");
QPushButton *run = new QPushButton(tr("Run"));
scriptLayout->addRow(tr("Name"), scriptName);
scriptLayout->addRow(script);
scriptLayout->addRow(run);
splitter->addWidget(scriptBox);
console = new PythonConsole(context, this, this);
console->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
splitter->addWidget(console);
outerSplitter->addWidget(splitter);
// ride editor
GcChartWindow *win = GcWindowRegistry::newGcWindow(GcWindowTypes::MetadataWindow, context);
if (win) {
win->setProperty("nomenu", true);
RideItem *notconst = (RideItem*)context->currentRideItem();
win->setProperty("ride", QVariant::fromValue<RideItem*>(notconst));
DateRange dr = context->currentDateRange();
win->setProperty("dateRange", QVariant::fromValue<DateRange>(dr));
outerSplitter->addWidget(win);
}
mainLayout->addWidget(outerSplitter);
QPushButton *save = new QPushButton(tr("Save and close"));
QPushButton *cancel = new QPushButton(tr("Cancel"));
QHBoxLayout *buttons = new QHBoxLayout();
buttons->addStretch();
buttons->addWidget(cancel);
buttons->addWidget(save);
mainLayout->addLayout(buttons);
connect(run, SIGNAL(clicked()), this, SLOT(runClicked()));
connect(save, SIGNAL(clicked()), this, SLOT(saveClicked()));
connect(cancel, SIGNAL(clicked()), this, SLOT(reject()));
}
QString
EditFixPyScriptDialog::getName
() const
{
if (pyFixScript != nullptr) {
return pyFixScript->name;
} else {
return QString();
}
}
void EditFixPyScriptDialog::closeEvent(QCloseEvent *event)
{
if (!isModified()) {
event->accept();
return;
}
QMessageBox msgBox;
msgBox.setText(tr("The Python Fix has been modified."));
msgBox.setInformativeText(tr("Do you want to save your changes?"));
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int result = msgBox.exec();
switch (result) {
case QMessageBox::Save:
event->ignore();
saveClicked();
break;
case QMessageBox::Discard:
event->accept();
break;
case QMessageBox::Cancel:
event->ignore();
break;
}
}
void EditFixPyScriptDialog::setScript(QString string)
{
if (python && script) {
script->setText(string);
new PythonSyntax(script->document());
}
text = string;
}
bool EditFixPyScriptDialog::isModified()
{
return scriptName->isModified()
|| script->document()->isModified();
}
void EditFixPyScriptDialog::saveClicked()
{
QString name = scriptName->text().trimmed();
if (name.isEmpty()) {
QMessageBox::critical(this, "GoldenCheetah", tr("Please specify a name for the Python Fix."));
return;
}
QString illegal = "<>:\"|?*";
foreach (const QChar& c, name)
{
// Check for illegal characters
if (illegal.contains(c)) {
QMessageBox::critical(this, "GoldenCheetah",
QString(tr("The Python Fix name may not contain any of the following characters: %1")).arg(illegal));
return;
}
}
QList<FixPyScript *> fixScripts = fixPySettings->getScripts();
foreach (FixPyScript *fixScript, fixScripts) {
if (fixScript == pyFixScript) {
continue;
}
if (name == fixScript->name) {
QMessageBox::critical(this, "GoldenCheetah",
tr("A Python Fix with that name exists already. Please choose another name."));
return;
}
}
// ensure path is unique
QString path(name);
path = path.replace(" ", "_").toLower() + ".py";
for (int i = 0; i < fixScripts.size(); i++) {
FixPyScript *fixScript = fixScripts[i];
if (fixScript == pyFixScript) {
continue;
}
if (path == fixScript->path) {
path = "_" + path;
i = 0;
}
}
if (pyFixScript == nullptr) {
pyFixScript = fixPySettings->createScript(name);
}
// remember old path, for clean up
if (!pyFixScript->path.isEmpty() && pyFixScript->path != path) {
pyFixScript->oldPath = pyFixScript->path;
}
// remember old name, for clean up
if (!pyFixScript->name.isEmpty() && pyFixScript->name != name) {
pyFixScript->oldName = pyFixScript->name;
}
pyFixScript->name = name;
pyFixScript->source = script->toPlainText();
pyFixScript->path = path;
pyFixScript->changed = true;
fixPySettings->save();
accept();
}
void EditFixPyScriptDialog::runClicked()
{
QString key = QUuid::createUuid().toString();
key = key.replace("-", "_");
setUpdatesEnabled(false);
QString errText;
FixPyRunner pyRunner(context);
pyRunner.run(script->toPlainText(), "_" + key, errText);
if (!errText.isEmpty()) {
console->putData(errText);
}
setUpdatesEnabled(true);
}