File indexing completed on 2024-05-05 04:39:49

0001 /*
0002     SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "outputpage.h"
0008 #include "ui_outputlocation.h"
0009 #include "debug.h"
0010 
0011 #include <language/codegen/sourcefiletemplate.h>
0012 #include <language/codegen/templaterenderer.h>
0013 #include <KConfigGroup>
0014 #include <KSharedConfig>
0015 #include <KLocalizedString>
0016 #include <KUrlRequester>
0017 
0018 #include <QSpinBox>
0019 #include <QLabel>
0020 
0021 namespace KDevelop {
0022 
0023 struct OutputPagePrivate
0024 {
0025     explicit OutputPagePrivate(OutputPage* page_)
0026     : page(page_)
0027     , output(nullptr)
0028     { }
0029     OutputPage* page;
0030     Ui::OutputLocationDialog* output;
0031 
0032     QHash<QString, KUrlRequester*> outputFiles;
0033     QHash<QString, QSpinBox*> outputLines;
0034     QHash<QString, QSpinBox*> outputColumns;
0035     QList<QLabel*> labels;
0036 
0037     QHash<QString, QUrl> defaultUrls;
0038     QHash<QString, QUrl> lowerCaseUrls;
0039     QStringList fileIdentifiers;
0040 
0041     void updateRanges(QSpinBox* line, QSpinBox* column, bool enable);
0042     /**
0043      * This implementation simply enables the position widgets on a file that exists.
0044      * Derived classes should overload to set the ranges where class generation should be allowed
0045      *
0046      * @param field the name of the file to be generated (Header, Implementation, etc)
0047      */
0048     void updateFileRange(const QString& field);
0049     void updateFileNames();
0050     void validate();
0051 };
0052 
0053 void OutputPagePrivate::updateRanges(QSpinBox* line, QSpinBox* column, bool enable)
0054 {
0055     qCDebug(PLUGIN_FILETEMPLATES) << "Updating Ranges, file exists: " << enable;
0056     line->setEnabled(enable);
0057     column->setEnabled(enable);
0058 }
0059 
0060 void OutputPagePrivate::updateFileRange(const QString& field)
0061 {
0062     const auto outputFileIt = outputFiles.constFind(field);
0063     if (outputFileIt == outputFiles.constEnd()) {
0064         return;
0065     }
0066 
0067     const QString url = (*outputFileIt)->url().toLocalFile();
0068     QFileInfo info(url);
0069 
0070     updateRanges(outputLines[field], outputColumns[field], info.exists() && !info.isDir());
0071 
0072     validate();
0073 }
0074 
0075 void OutputPagePrivate::updateFileNames()
0076 {
0077     bool lower = output->lowerFilenameCheckBox->isChecked();
0078 
0079     const QHash<QString, QUrl> urls = lower ? lowerCaseUrls : defaultUrls;
0080     for (QHash<QString, KUrlRequester*>::const_iterator it = outputFiles.constBegin();
0081          it != outputFiles.constEnd(); ++it)
0082     {
0083         const QUrl url = urls.value(it.key());
0084         if (!url.isEmpty())
0085         {
0086             it.value()->setUrl(url);
0087         }
0088     }
0089 
0090     //Save the setting for next time
0091     KConfigGroup codegenGroup( KSharedConfig::openConfig(), "CodeGeneration" );
0092     codegenGroup.writeEntry( "LowerCaseFilenames", output->lowerFilenameCheckBox->isChecked() );
0093 
0094     validate();
0095 }
0096 
0097 void OutputPagePrivate::validate()
0098 {
0099     QStringList invalidFiles;
0100     for(QHash< QString, KUrlRequester* >::const_iterator it = outputFiles.constBegin();
0101         it != outputFiles.constEnd(); ++it)
0102     {
0103         if (!it.value()->url().isValid()) {
0104             invalidFiles << it.key();
0105         } else if (it.value()->url().isLocalFile() && !QFileInfo(it.value()->url().adjusted(QUrl::RemoveFilename).toLocalFile()).isWritable()) {
0106             invalidFiles << it.key();
0107         }
0108     }
0109 
0110     bool valid = invalidFiles.isEmpty();
0111     if (valid) {
0112         output->messageWidget->animatedHide();
0113     } else {
0114         std::sort(invalidFiles.begin(), invalidFiles.end());
0115         output->messageWidget->setMessageType(KMessageWidget::Error);
0116         output->messageWidget->setCloseButtonVisible(false);
0117         output->messageWidget->setText(i18np("Invalid output file: %2", "Invalid output files: %2", invalidFiles.count(), invalidFiles.join(QLatin1String(", "))));
0118         output->messageWidget->animatedShow();
0119     }
0120     emit page->isValid(valid);
0121 }
0122 
0123 OutputPage::OutputPage(QWidget* parent)
0124 : QWidget(parent)
0125 , d(new OutputPagePrivate(this))
0126 {
0127     d->output = new Ui::OutputLocationDialog;
0128     d->output->setupUi(this);
0129     d->output->messageWidget->setVisible(false);
0130 
0131     connect(d->output->lowerFilenameCheckBox, &QCheckBox::stateChanged,
0132             this, [&] { d->updateFileNames(); });
0133 }
0134 
0135 OutputPage::~OutputPage()
0136 {
0137     delete d->output;
0138     delete d;
0139 }
0140 
0141 void OutputPage::prepareForm(const SourceFileTemplate& fileTemplate)
0142 {
0143     // First clear any existing file configurations
0144     // This can happen when going back and forth between assistant pages
0145     d->fileIdentifiers.clear();
0146     d->defaultUrls.clear();
0147     d->lowerCaseUrls.clear();
0148 
0149     while (d->output->urlFormLayout->count() > 0)
0150     {
0151         d->output->urlFormLayout->takeAt(0);
0152     }
0153     while (d->output->positionFormLayout->count() > 0)
0154     {
0155         d->output->positionFormLayout->takeAt(0);
0156     }
0157 
0158     qDeleteAll(d->outputFiles);
0159     qDeleteAll(d->outputLines);
0160     qDeleteAll(d->outputColumns);
0161     qDeleteAll(d->labels);
0162 
0163     d->outputFiles.clear();
0164     d->outputLines.clear();
0165     d->outputColumns.clear();
0166     d->labels.clear();
0167 
0168     const auto outputFiles = fileTemplate.outputFiles();
0169 
0170     const int outputFilesCount = outputFiles.count();
0171     d->output->urlGroupBox->setTitle(i18ncp("@title:group", "Output File", "Output Files", outputFilesCount));
0172     d->output->positionGroupBox->setTitle(i18ncp("@title:group", "Location within Existing File", "Location within Existing Files", outputFilesCount));
0173 
0174     for (const SourceFileTemplate::OutputFile& file : outputFiles) {
0175         const QString id = file.identifier;
0176         d->fileIdentifiers << id;
0177 
0178         const QString fileLabelText = i18nc("@label:chooser file name arg", "%1:", file.label);
0179         auto* label = new QLabel(fileLabelText, this);
0180         d->labels << label;
0181         auto* requester = new KUrlRequester(this);
0182         requester->setMode( KFile::File | KFile::LocalOnly );
0183 
0184         connect(requester, &KUrlRequester::textChanged, this, [this, id] () { d->updateFileRange(id); });
0185 
0186         d->output->urlFormLayout->addRow(label, requester);
0187         d->outputFiles.insert(file.identifier, requester);
0188 
0189         label = new QLabel(fileLabelText, this);
0190         d->labels << label;
0191         auto* layout = new QHBoxLayout;
0192 
0193         auto line = new QSpinBox(this);
0194         line->setPrefix(i18n("Line: "));
0195         line->setValue(0);
0196         line->setMinimum(0);
0197         layout->addWidget(line);
0198 
0199         auto column = new QSpinBox(this);
0200         column->setPrefix(i18n("Column: "));
0201         column->setValue(0);
0202         column->setMinimum(0);
0203         layout->addWidget(column);
0204 
0205         d->output->positionFormLayout->addRow(label, layout);
0206         d->outputLines.insert(file.identifier, line);
0207         d->outputColumns.insert(file.identifier, column);
0208     }
0209 }
0210 
0211 void OutputPage::loadFileTemplate(const SourceFileTemplate& fileTemplate,
0212                                    const QUrl& _baseUrl,
0213                                    TemplateRenderer* renderer)
0214 {
0215     QUrl baseUrl = _baseUrl;
0216     if (!baseUrl.path().endsWith(QLatin1Char('/'))) {
0217         baseUrl.setPath(baseUrl.path() + QLatin1Char('/'));
0218     }
0219 
0220     KConfigGroup codegenGroup( KSharedConfig::openConfig(), "CodeGeneration" );
0221     bool lower = codegenGroup.readEntry( "LowerCaseFilenames", true );
0222     d->output->lowerFilenameCheckBox->setChecked(lower);
0223 
0224     const auto outputFiles = fileTemplate.outputFiles();
0225     for (const SourceFileTemplate::OutputFile& file : outputFiles) {
0226         d->fileIdentifiers << file.identifier;
0227 
0228         QUrl url = baseUrl.resolved(QUrl(renderer->render(file.outputName)));
0229         d->defaultUrls.insert(file.identifier, url);
0230 
0231         url = baseUrl.resolved(QUrl(renderer->render(file.outputName).toLower()));
0232         d->lowerCaseUrls.insert(file.identifier, url);
0233     }
0234 
0235     d->updateFileNames();
0236 }
0237 
0238 QHash< QString, QUrl > OutputPage::fileUrls() const
0239 {
0240     QHash<QString, QUrl> urls;
0241     for (QHash<QString, KUrlRequester*>::const_iterator it = d->outputFiles.constBegin(); it != d->outputFiles.constEnd(); ++it)
0242     {
0243         urls.insert(it.key(), it.value()->url());
0244     }
0245     return urls;
0246 }
0247 
0248 QHash< QString, KTextEditor::Cursor > OutputPage::filePositions() const
0249 {
0250     QHash<QString, KTextEditor::Cursor> positions;
0251     for (const QString& identifier : qAsConst(d->fileIdentifiers)) {
0252         positions.insert(identifier, KTextEditor::Cursor(d->outputLines[identifier]->value(), d->outputColumns[identifier]->value()));
0253     }
0254     return positions;
0255 }
0256 
0257 void OutputPage::setFocusToFirstEditWidget()
0258 {
0259     d->output->lowerFilenameCheckBox->setFocus();
0260 }
0261 
0262 }
0263 
0264 #include "moc_outputpage.cpp"