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"