File indexing completed on 2024-05-19 15:46:04

0001 /*
0002     SPDX-FileCopyrightText: 2019 Daniel Mensinger <daniel@mensinger-ka.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mesonrewriterpage.h"
0008 #include "mesonconfig.h"
0009 #include "mesonmanager.h"
0010 #include "mesonoptionbaseview.h"
0011 #include "mesonrewriterinput.h"
0012 #include "mintro/mesonintrospectjob.h"
0013 #include "mintro/mesonprojectinfo.h"
0014 #include "rewriter/mesondefaultopts.h"
0015 #include "rewriter/mesonkwargsinfo.h"
0016 #include "rewriter/mesonkwargsmodify.h"
0017 #include "rewriter/mesonrewriterjob.h"
0018 
0019 #include "ui_mesonrewriterpage.h"
0020 
0021 #include <interfaces/iplugin.h>
0022 #include <interfaces/iproject.h>
0023 #include <util/executecompositejob.h>
0024 
0025 #include <KColorScheme>
0026 #include <QIcon>
0027 #include <QInputDialog>
0028 #include <algorithm>
0029 #include <debug.h>
0030 
0031 using namespace KDevelop;
0032 using namespace std;
0033 
0034 class JobDeleter
0035 {
0036 public:
0037     explicit JobDeleter(QList<KJob*> jobs)
0038         : m_jobs(jobs)
0039     {
0040     }
0041     ~JobDeleter()
0042     {
0043         for (KJob* i : m_jobs) {
0044             delete i;
0045         }
0046     }
0047 
0048 private:
0049     QList<KJob*> m_jobs;
0050 };
0051 
0052 MesonRewriterPage::MesonRewriterPage(IPlugin* plugin, IProject* project, QWidget* parent)
0053     : ConfigPage(plugin, nullptr, parent)
0054     , m_project(project)
0055 {
0056     Q_ASSERT(m_project);
0057 
0058     m_ui = new Ui::MesonRewriterPage;
0059     m_ui->setupUi(this);
0060 
0061     m_projectKwargs = constructPojectInputs();
0062 
0063     // Initialize widgets
0064     for (auto* i : m_projectKwargs) {
0065         m_ui->c_project->addWidget(i);
0066         connect(i, &MesonRewriterInputBase::configChanged, this, &MesonRewriterPage::emitChanged);
0067     }
0068 
0069     recalculateLengths();
0070     reset();
0071 }
0072 
0073 #define STRING_INPUT(name, id) new MesonRewriterInputString(QStringLiteral(name), QStringLiteral(id), this)
0074 
0075 QVector<MesonRewriterInputBase*> MesonRewriterPage::constructPojectInputs()
0076 {
0077     return {
0078         STRING_INPUT("Version", "version"),
0079         STRING_INPUT("License", "license"),
0080         STRING_INPUT("Meson version", "meson_version"),
0081         STRING_INPUT("Subprojects directory", "subproject_dir"),
0082     };
0083 }
0084 
0085 MesonOptContainerPtr MesonRewriterPage::constructDefaultOpt(const QString& name, const QString& value)
0086 {
0087     if (!m_opts) {
0088         return nullptr;
0089     }
0090 
0091     for (auto& i : m_opts->options()) {
0092         if (i->name() != name) {
0093             continue;
0094         }
0095 
0096         if (!value.isNull()) {
0097             i->setFromString(value);
0098         }
0099 
0100         auto optView = MesonOptionBaseView::fromOption(i, this);
0101         if (!optView) {
0102             continue;
0103         }
0104 
0105         auto opt = std::make_shared<MesonRewriterOptionContainer>(optView, this);
0106         if (!opt) {
0107             continue;
0108         }
0109 
0110         connect(opt.get(), &MesonRewriterOptionContainer::configChanged, this, &MesonRewriterPage::emitChanged);
0111         return opt;
0112     }
0113 
0114     return nullptr;
0115 }
0116 
0117 void MesonRewriterPage::setWidgetsDisabled(bool disabled)
0118 {
0119     m_ui->c_tabs->setDisabled(disabled);
0120 }
0121 
0122 void MesonRewriterPage::recalculateLengths()
0123 {
0124     // Calculate the maximum name width to align all widgets
0125     vector<int> lengths;
0126     int maxWidth = 50;
0127     lengths.reserve(m_projectKwargs.size() + m_defaultOpts.size());
0128 
0129     auto input_op = [](MesonRewriterInputBase* x) -> int { return x->nameWidth(); };
0130     auto optCont_op = [](MesonOptContainerPtr x) -> int { return x->view()->nameWidth(); };
0131 
0132     transform(begin(m_projectKwargs), end(m_projectKwargs), back_inserter(lengths), input_op);
0133     transform(begin(m_defaultOpts), end(m_defaultOpts), back_inserter(lengths), optCont_op);
0134 
0135     maxWidth = accumulate(begin(lengths), end(lengths), maxWidth, [](int a, int b) -> int { return max(a, b); });
0136 
0137     // Set widgets width
0138     for (auto* i : m_projectKwargs) {
0139         i->setMinNameWidth(maxWidth);
0140     }
0141 
0142     for (auto& i : m_defaultOpts) {
0143         i->view()->setMinNameWidth(maxWidth);
0144     }
0145 
0146     m_ui->l_dispProject->setMinimumWidth(maxWidth);
0147 }
0148 
0149 void MesonRewriterPage::checkStatus()
0150 {
0151     // Get the config build dir status
0152     auto setStatus = [this](const QString& msg, int color) -> void {
0153         KColorScheme scheme(QPalette::Normal);
0154         KColorScheme::ForegroundRole role;
0155         switch (color) {
0156         case 0:
0157             role = KColorScheme::PositiveText;
0158             setDisabled(false);
0159             break;
0160         case 1:
0161             role = KColorScheme::NeutralText;
0162             setDisabled(true);
0163             break;
0164         case 2:
0165         default:
0166             role = KColorScheme::NegativeText;
0167             setDisabled(true);
0168             break;
0169         }
0170 
0171         QPalette pal = m_ui->l_status->palette();
0172         pal.setColor(QPalette::WindowText, scheme.foreground(role).color());
0173         m_ui->l_status->setPalette(pal);
0174         m_ui->l_status->setText(i18n("Status: %1", msg));
0175     };
0176 
0177     switch (m_state) {
0178     case START:
0179         setStatus(i18n("Initializing GUI"), 1);
0180         break;
0181     case LOADING:
0182         setStatus(i18n("Loading project data..."), 1);
0183         break;
0184     case WRITING:
0185         setStatus(i18n("Writing project data..."), 1);
0186         break;
0187     case READY:
0188         setStatus(i18n("Initializing GUI"), 0);
0189         break;
0190     case ERROR:
0191         setStatus(i18n("Loading meson rewriter data failed"), 2);
0192         break;
0193     }
0194 
0195     // Remove old default options
0196     m_defaultOpts.erase(remove_if(begin(m_defaultOpts), end(m_defaultOpts), [](auto x) { return x->shouldDelete(); }),
0197                         end(m_defaultOpts));
0198 
0199     KColorScheme scheme(QPalette::Normal);
0200     KColorScheme::ForegroundRole role;
0201     int numChanged = 0;
0202 
0203     numChanged += count_if(begin(m_projectKwargs), end(m_projectKwargs), [](auto* x) { return x->hasChanged(); });
0204     numChanged += count_if(begin(m_defaultOpts), end(m_defaultOpts), [](auto x) { return x->hasChanged(); });
0205 
0206     if (numChanged == 0) {
0207         role = KColorScheme::NormalText;
0208         m_ui->l_changed->setText(i18n("No changes"));
0209     } else {
0210         role = KColorScheme::NeutralText;
0211         m_ui->l_changed->setText(i18np("%1 option changed", "%1 options changed", numChanged));
0212     }
0213 
0214     QPalette pal = m_ui->l_changed->palette();
0215     pal.setColor(QPalette::WindowText, scheme.foreground(role).color());
0216     m_ui->l_changed->setPalette(pal);
0217 }
0218 
0219 void MesonRewriterPage::setStatus(MesonRewriterPage::State s)
0220 {
0221     m_state = s;
0222     checkStatus();
0223 }
0224 
0225 void MesonRewriterPage::apply()
0226 {
0227     qCDebug(KDEV_Meson) << "REWRITER GUI: APPLY";
0228 
0229     auto projectSet = make_shared<MesonKWARGSProjectModify>(MesonKWARGSProjectModify::SET);
0230     auto projectDel = make_shared<MesonKWARGSProjectModify>(MesonKWARGSProjectModify::DELETE);
0231     auto defOptsSet = make_shared<MesonRewriterDefaultOpts>(MesonRewriterDefaultOpts::SET);
0232     auto defOptsDel = make_shared<MesonRewriterDefaultOpts>(MesonRewriterDefaultOpts::DELETE);
0233 
0234     auto writer = [](MesonRewriterInputBase* widget, MesonKWARGSModifyPtr set, MesonKWARGSModifyPtr del) {
0235         if (!widget->hasChanged()) {
0236             return;
0237         }
0238 
0239         if (widget->isEnabled()) {
0240             widget->writeToAction(set.get());
0241         } else {
0242             widget->writeToAction(del.get());
0243         }
0244     };
0245 
0246     for_each(begin(m_projectKwargs), end(m_projectKwargs), [&](auto* w) { writer(w, projectSet, projectDel); });
0247 
0248     QStringList deletedOptions = m_initialDefaultOpts;
0249 
0250     for (auto& i : m_defaultOpts) {
0251         auto opt = i->view()->option();
0252 
0253         // Detect deleted options by removing all current present options from the initial option list
0254         deletedOptions.removeAll(opt->name());
0255 
0256         if (opt->isUpdated() || !m_initialDefaultOpts.contains(opt->name())) {
0257             defOptsSet->set(opt->name(), opt->value());
0258         }
0259     }
0260 
0261     for (auto i : deletedOptions) {
0262         defOptsDel->set(i, QString());
0263     }
0264 
0265     QVector<MesonRewriterActionPtr> actions = { projectSet, projectDel, defOptsSet, defOptsDel };
0266 
0267     KJob* rewriterJob = new MesonRewriterJob(m_project, actions, this);
0268 
0269     // Reload the GUI once the data has been written
0270     connect(rewriterJob, &KJob::result, this, &MesonRewriterPage::reset);
0271 
0272     setStatus(WRITING);
0273     rewriterJob->start();
0274 }
0275 
0276 void MesonRewriterPage::reset()
0277 {
0278     qCDebug(KDEV_Meson) << "REWRITER GUI: RESET";
0279 
0280     Meson::BuildDir buildDir = Meson::currentBuildDir(m_project);
0281     if (!buildDir.isValid()) {
0282         setStatus(ERROR);
0283         return;
0284     }
0285 
0286     auto projectInfo = std::make_shared<MesonKWARGSProjectInfo>();
0287 
0288     QVector<MesonRewriterActionPtr> actions = { projectInfo };
0289 
0290     QVector<MesonIntrospectJob::Type> types = { MesonIntrospectJob::PROJECTINFO, MesonIntrospectJob::BUILDOPTIONS };
0291     MesonIntrospectJob::Mode mode = MesonIntrospectJob::MESON_FILE;
0292 
0293     auto introspectJob = new MesonIntrospectJob(m_project, buildDir, types, mode, this);
0294     auto rewriterJob = new MesonRewriterJob(m_project, actions, this);
0295 
0296     QList<KJob*> jobs = { introspectJob, rewriterJob };
0297 
0298     // Don't automatically delete jobs beause they are used in the lambda below
0299     for (KJob* i : jobs) {
0300         i->setAutoDelete(false);
0301     }
0302 
0303     KJob* job = new ExecuteCompositeJob(this, jobs);
0304 
0305     connect(job, &KJob::result, this, [=]() -> void {
0306         JobDeleter deleter(jobs); // Make sure to free all jobs with RAII
0307 
0308         auto prInfo = introspectJob->projectInfo();
0309         m_opts = introspectJob->buildOptions();
0310         if (!prInfo || !m_opts) {
0311             setStatus(ERROR);
0312             return;
0313         }
0314 
0315         m_ui->l_project->setText(QStringLiteral("<html><head/><body><h3>") + prInfo->name()
0316                                  + QStringLiteral("</h3></body></html>"));
0317 
0318         auto setter = [](MesonRewriterInputBase* w, MesonKWARGSInfoPtr i) { w->resetFromAction(i.get()); };
0319 
0320         for_each(begin(m_projectKwargs), end(m_projectKwargs), [=](auto* x) { setter(x, projectInfo); });
0321 
0322         // Updated the default options
0323         m_defaultOpts.clear();
0324         m_initialDefaultOpts.clear();
0325         if (projectInfo->hasKWARG(QStringLiteral("default_options"))) {
0326             auto rawValues = projectInfo->getArray(QStringLiteral("default_options"));
0327             auto options = m_opts->options();
0328 
0329             for (auto i : rawValues) {
0330                 int idx = i.indexOf(QLatin1Char('='));
0331                 if (idx < 0) {
0332                     continue;
0333                 }
0334 
0335                 QString name = i.left(idx);
0336                 QString val = i.mid(idx + 1);
0337 
0338                 auto opt = constructDefaultOpt(name, val);
0339                 if (!opt) {
0340                     continue;
0341                 }
0342 
0343                 m_defaultOpts += opt;
0344                 m_initialDefaultOpts += name;
0345                 m_ui->c_defOpts->addWidget(opt.get());
0346             }
0347         }
0348 
0349         recalculateLengths();
0350         setStatus(READY);
0351         return;
0352     });
0353 
0354     setStatus(LOADING);
0355     job->start();
0356 }
0357 
0358 void MesonRewriterPage::newOption()
0359 {
0360     // Sort by section
0361     QStringList core;
0362     QStringList backend;
0363     QStringList base;
0364     QStringList compiler;
0365     QStringList directory;
0366     QStringList user;
0367     QStringList test;
0368 
0369     for (auto& i : m_opts->options()) {
0370         switch (i->section()) {
0371         case MesonOptionBase::CORE:
0372             core += i->name();
0373             break;
0374         case MesonOptionBase::BACKEND:
0375             backend += i->name();
0376             break;
0377         case MesonOptionBase::BASE:
0378             base += i->name();
0379             break;
0380         case MesonOptionBase::COMPILER:
0381             compiler += i->name();
0382             break;
0383         case MesonOptionBase::DIRECTORY:
0384             directory += i->name();
0385             break;
0386         case MesonOptionBase::USER:
0387             user += i->name();
0388             break;
0389         case MesonOptionBase::TEST:
0390             test += i->name();
0391             break;
0392         }
0393     }
0394 
0395     QStringList total = core + backend + base + compiler + directory + user + test;
0396 
0397     // Remove already existing options
0398     for (auto& i : m_defaultOpts) {
0399         total.removeAll(i->view()->option()->name());
0400     }
0401 
0402     QInputDialog dialog(this);
0403 
0404     dialog.setOption(QInputDialog::UseListViewForComboBoxItems, true);
0405     dialog.setInputMode(QInputDialog::TextInput);
0406     dialog.setWindowTitle(i18nc("@title:window", "Select Additional Meson Option"));
0407     dialog.setLabelText(i18nc("@label:listbox", "Meson option to add:"));
0408     dialog.setComboBoxItems(total);
0409 
0410     if (dialog.exec() != QDialog::Accepted) {
0411         return;
0412     }
0413 
0414     auto opt = constructDefaultOpt(dialog.textValue(), QString());
0415     if (!opt) {
0416         return;
0417     }
0418 
0419     m_defaultOpts += opt;
0420     m_ui->c_defOpts->addWidget(opt.get());
0421     recalculateLengths();
0422 }
0423 
0424 void MesonRewriterPage::defaults()
0425 {
0426     reset();
0427 }
0428 
0429 void MesonRewriterPage::emitChanged()
0430 {
0431     m_configChanged = true;
0432     checkStatus();
0433     emit changed();
0434 }
0435 
0436 QString MesonRewriterPage::name() const
0437 {
0438     return i18nc("@title:tab", "Project");
0439 }
0440 
0441 QString MesonRewriterPage::fullName() const
0442 {
0443     return i18nc("@title:tab", "Meson Project Settings");
0444 }
0445 
0446 QIcon MesonRewriterPage::icon() const
0447 {
0448     return QIcon::fromTheme(QStringLiteral("run-build"));
0449 }
0450 
0451 #include "moc_mesonrewriterpage.cpp"