diff --git a/language/codegen/sourcefiletemplate.cpp b/language/codegen/sourcefiletemplate.cpp index f59942964..6939ed5f8 100644 --- a/language/codegen/sourcefiletemplate.cpp +++ b/language/codegen/sourcefiletemplate.cpp @@ -1,317 +1,346 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sourcefiletemplate.h" #include "templaterenderer.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; typedef SourceFileTemplate::ConfigOption ConfigOption; class KDevelop::SourceFileTemplatePrivate { public: KArchive* archive; QString descriptionFileName; QStringList searchLocations; ConfigOption readEntry(const QDomElement& element, TemplateRenderer* renderer); }; ConfigOption SourceFileTemplatePrivate::readEntry(const QDomElement& element, TemplateRenderer* renderer) { ConfigOption entry; entry.name = element.attribute(QStringLiteral("name")); entry.type = element.attribute(QStringLiteral("type"), QStringLiteral("String")); + bool isDefaultValueSet = false; for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { QString tag = e.tagName(); if (tag == QLatin1String("label")) { entry.label = e.text(); } else if (tag == QLatin1String("tooltip")) { entry.label = e.text(); } else if (tag == QLatin1String("whatsthis")) { entry.label = e.text(); } else if ( tag == QLatin1String("min") ) { entry.minValue = e.text(); } else if ( tag == QLatin1String("max") ) { entry.maxValue = e.text(); } else if ( tag == QLatin1String("default") ) { entry.value = renderer->render(e.text(), entry.name); + isDefaultValueSet = true; + } + else if (tag == QLatin1String("choices")) { + QStringList values; + QDomNodeList choices = element.elementsByTagName(QStringLiteral("choice")); + for (int j = 0; j < choices.size(); ++j) { + QDomElement choiceElement = choices.at(j).toElement(); + values << choiceElement.attribute(QStringLiteral("name")); + } + Q_ASSERT(!values.isEmpty()); + if (values.isEmpty()) { + qCWarning(LANGUAGE) << "Entry " << entry.name << "has an enum without any choices"; + } + entry.values = values; } } qCDebug(LANGUAGE) << "Read entry" << entry.name << "with default value" << entry.value; + + // preset value for enum if needed + if (!entry.values.isEmpty()) { + if (isDefaultValueSet) { + const bool isSaneDefaultValue = entry.values.contains(entry.value.toString()); + Q_ASSERT(isSaneDefaultValue); + if (!isSaneDefaultValue) { + qCWarning(LANGUAGE) << "Default value" << entry.value << "not in enum" << entry.values; + entry.value = entry.values.at(0); + } + } else { + entry.value = entry.values.at(0); + } + } return entry; } SourceFileTemplate::SourceFileTemplate (const QString& templateDescription) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; setTemplateDescription(templateDescription); } SourceFileTemplate::SourceFileTemplate() : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; } SourceFileTemplate::SourceFileTemplate (const SourceFileTemplate& other) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; *this = other; } SourceFileTemplate::~SourceFileTemplate() { delete d->archive; delete d; } SourceFileTemplate& SourceFileTemplate::operator=(const SourceFileTemplate& other) { if (other.d == d) { return *this; } delete d->archive; if (other.d->archive) { if (other.d->archive->fileName().endsWith(QLatin1String(".zip"))) { d->archive = new KZip(other.d->archive->fileName()); } else { d->archive = new KTar(other.d->archive->fileName()); } d->archive->open(QIODevice::ReadOnly); } else { d->archive = nullptr; } d->descriptionFileName = other.d->descriptionFileName; return *this; } void SourceFileTemplate::setTemplateDescription(const QString& templateDescription) { delete d->archive; d->descriptionFileName = templateDescription; QString archiveFileName; const QString templateBaseName = QFileInfo(templateDescription).baseName(); d->searchLocations.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/kdevfiletemplates/templates/"), QStandardPaths::LocateDirectory)); foreach(const QString& dir, d->searchLocations) { foreach(const auto& entry, QDir(dir).entryInfoList(QDir::Files)) { if (entry.baseName() == templateBaseName) { archiveFileName = entry.absoluteFilePath(); qCDebug(LANGUAGE) << "Found template archive" << archiveFileName; break; } } } if (archiveFileName.isEmpty() || !QFileInfo::exists(archiveFileName)) { qCWarning(LANGUAGE) << "Could not find a template archive for description" << templateDescription << ", archive file" << archiveFileName; d->archive = nullptr; } else { QFileInfo info(archiveFileName); if (info.suffix() == QLatin1String("zip")) { d->archive = new KZip(archiveFileName); } else { d->archive = new KTar(archiveFileName); } d->archive->open(QIODevice::ReadOnly); } } bool SourceFileTemplate::isValid() const { return d->archive; } QString SourceFileTemplate::name() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Name"); } QString SourceFileTemplate::type() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Type", QString()); } QString SourceFileTemplate::languageName() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Language", QString()); } QStringList SourceFileTemplate::category() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Category", QStringList()); } QStringList SourceFileTemplate::defaultBaseClasses() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("BaseClasses", QStringList()); } const KArchiveDirectory* SourceFileTemplate::directory() const { Q_ASSERT(isValid()); return d->archive->directory(); } QList< SourceFileTemplate::OutputFile > SourceFileTemplate::outputFiles() const { QList outputFiles; KConfig templateConfig(d->descriptionFileName); KConfigGroup group(&templateConfig, "General"); QStringList files = group.readEntry("Files", QStringList()); qCDebug(LANGUAGE) << "Files in template" << files; foreach (const QString& fileGroup, files) { KConfigGroup cg(&templateConfig, fileGroup); OutputFile f; f.identifier = cg.name(); f.label = cg.readEntry("Name"); f.fileName = cg.readEntry("File"); f.outputName = cg.readEntry("OutputFile"); outputFiles << f; } return outputFiles; } bool SourceFileTemplate::hasCustomOptions() const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); bool hasOptions = d->archive->directory()->entries().contains(cg.readEntry("OptionsFile", "options.kcfg")); qCDebug(LANGUAGE) << cg.readEntry("OptionsFile", "options.kcfg") << hasOptions; return hasOptions; } QHash< QString, QList > SourceFileTemplate::customOptions(TemplateRenderer* renderer) const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); const KArchiveEntry* entry = d->archive->directory()->entry(cg.readEntry("OptionsFile", "options.kcfg")); QHash > options; if (!entry->isFile()) { return options; } const KArchiveFile* file = static_cast(entry); /* * Copied from kconfig_compiler.kcfg */ QDomDocument doc; QString errorMsg; int errorRow; int errorCol; if ( !doc.setContent( file->data(), &errorMsg, &errorRow, &errorCol ) ) { qCDebug(LANGUAGE) << "Unable to load document."; qCDebug(LANGUAGE) << "Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; return options; } QDomElement cfgElement = doc.documentElement(); if ( cfgElement.isNull() ) { qCDebug(LANGUAGE) << "No document in kcfg file"; return options; } QDomNodeList groups = cfgElement.elementsByTagName(QStringLiteral("group")); for (int i = 0; i < groups.size(); ++i) { QDomElement group = groups.at(i).toElement(); QList optionGroup; QString groupName = group.attribute(QStringLiteral("name")); QDomNodeList entries = group.elementsByTagName(QStringLiteral("entry")); for (int j = 0; j < entries.size(); ++j) { QDomElement entry = entries.at(j).toElement(); optionGroup << d->readEntry(entry, renderer); } options.insert(groupName, optionGroup); } return options; } void SourceFileTemplate::addAdditionalSearchLocation(const QString& location) { if(!d->searchLocations.contains(location)) d->searchLocations.append(location); } diff --git a/language/codegen/sourcefiletemplate.h b/language/codegen/sourcefiletemplate.h index abbcc22ca..ff909b13c 100644 --- a/language/codegen/sourcefiletemplate.h +++ b/language/codegen/sourcefiletemplate.h @@ -1,316 +1,322 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_SOURCEFILETEMPLATE_H #define KDEVPLATFORM_SOURCEFILETEMPLATE_H #include #include #include #include class QStringList; class KArchiveDirectory; namespace KDevelop { class TemplateRenderer; /** * Represents a source file template archive * * @section TemplateStructure Template Archive Structure * * Source file templates in KDevPlatform are archive files. * The archive must contain at least one .desktop file with the template's description. * If multiple such files are present, the one with the same base name as the archive itself will be used. * * The description file must contain a @c [General] section with the following keys: * @li @c Name - The user-visible name of this template * @li @c Comment - A short user-visible comment * @li @c Category - The category of this template. It can be nested, in which case levels are separated * with forward slashes. The top-level category is usually the language followed by the framework. * @li @c Type An optional type, which then adds some more default wizard pages and configuration. * Currently supported are @c Class and @c Test. * @li @c Language The @c X-KDevelop-Language id of a language plugin which is then asked for a custom * createClassHelper. * @li @c Files - List of files generated by this template. These are not actual file names, but names * of config groups describing those files. * @li @c OptionsFile (optional) - If the template uses custom configuration options, specify a path to * the options file here. @ref CustomOptions * * For each file name in the @c Files array, TemplateClassGenerator expects a section with the same name. * this section should contain three keys: * @li @c Name - User-visible name for this file. This will be show the user in the dialog and can be translated. * @li @c File - The input file name in the template archive. The template for this file will be read from here. * @li @c OutputFile - The suggested output file name. This will be renderer as a template, so it can contain variables. * * An example template description is below. It shows all features described above. * * @code * [General] * Name=Example * Comment=Example description for a C++ Class * Category=C++/Basic * Type=Class * Language=C++ * Options=options.kcfg * Files=Header,Implementation * * [Header] * Name=Header * File=class.h * OutputFile={{ name }}.h * * [Implementation] * Name=Implementation * File=class.cpp * OutputFile={{ name }}.cpp * @endcode * * @section CustomOptions * * Templates can expose additional configurations options. * This is done through a file with the same syntax and .kcfg files used by KConfig XT. * The name of this file is specified with the @c OptionsFile key in the [General] section of the description file. * * @note * The options are not parsed by TemplateClassGenerator. * Instead, hasCustomOptions() returns true if the template specifies a custom options file, * and customOptions() returns the full text of that file. * The parsing is done by TemplateOptionsPage. * * The file can (and should) provide a type, name, label and default value for each configuration option. * So far, only variables with types String, Int and Bool are recognized. * Label is the user-visible string that will be shown next to the input field. * The default value will be rendered as a template, so it can contain variables. * * After the user configures the options, they will be available to the template as context variables. * The variable name will match the option name, and its value will be the one set by the user. * * An example options.kcfg file for a class template is included below. * * @code * * * * * * * {{ name }}Private * * * * d * * * * @endcode * * In this example, if the class name is Example, the default value for the private class name will be ExamplePrivate. * After the user accepts the option values, the template will access them through the @c private_class_name * and @c private_member_name variables. * * For more information regarding the XML file format, refer to the KConfig XT documentation. */ class KDEVPLATFORMLANGUAGE_EXPORT SourceFileTemplate { public: /** * Describes a single output file in this template */ struct OutputFile { /** * A unique identifier, equal to the list element in the @c OutputFiles entry */ QString identifier; /** * The name of the input file within the archive, equal to the @c File entry */ QString fileName; /** * User-visible label, equal to the @c Name entry in this file's group in the template description file */ QString label; /** * The default output file name, equal to the @c OutputFile entry * * @note The output name can contain template variables, so make sure to pass it through * TemplateRenderer::render before using it or showing it to the user. */ QString outputName; }; /** * Describes one configuration option */ struct ConfigOption { /** * The type of this option. * - * Currently supported are Int, String and Bool + * Currently supported are Int, String, Enum and Bool */ QString type; /** * A unique identifier for this option */ QString name; /** * User-visible label */ QString label; /** * A context description for the option, shown to the user as a tooltip */ QString context; /** * The default value of this option */ QVariant value; /** * The maximum value of this entry, as a string * * This is applicable only to integers */ QString maxValue; /** * The minimum value of this entry, as a string * * This is applicable only to integers */ QString minValue; + /** + * The possible values of this entry, as a list of strings + * + * This is applicable only to enums + */ + QStringList values; }; /** * Creates a SourceFileTemplate representing the template archive with * description file @p templateDescription. * * @param templateDescription template description file, used to find the * archive and read information */ explicit SourceFileTemplate(const QString& templateDescription); /** * Copy constructor * * Creates a SourceFileTemplate representing the same template archive as @p other. * This new objects shares no data with the @p other, so they can be read and deleted independently. * * @param other the template to copy */ SourceFileTemplate(const SourceFileTemplate& other); /** * Creates an invalid SourceFileTemplate */ SourceFileTemplate(); /** * Destroys this SourceFileTemplate */ ~SourceFileTemplate(); SourceFileTemplate& operator=(const SourceFileTemplate& other); void setTemplateDescription(const QString& templateDescription); /** * Returns true if this SourceFileTemplate represents an actual template archive, and false otherwise */ bool isValid() const; /** * The name of this template, corresponds to the @c Name entry in the description file */ QString name() const; /** * The top-level directory in the template archive * * @sa KArchive::directory() */ const KArchiveDirectory* directory() const; /** * The list of all output files in this template */ QList outputFiles() const; /** * @return true if the template uses any custom options, false otherwise **/ bool hasCustomOptions() const; /** * Return the custom options this template exposes **/ QHash > customOptions(TemplateRenderer* renderer) const; /** * @return The type of this template. * * This can be any string, but TemplateClassAssistant only supports @c Class and @c Test so far. */ QString type() const; /** * aA optional @c X-KDevelop-Language by which a language plugin for this template can be found. */ QString languageName() const; /** * The category of this template. */ QStringList category() const; /** * @return the list of base classes specified by this template. * If this is not a class template, or if it specifies no default base classes, an empty list is returned. * * Each element is the full inheritance description, such as "public QObject". */ QStringList defaultBaseClasses() const; /** * Add an additional search location where the code will look for archives * * @param location Absolute path to a directory with archives. Has to end with a '/' */ void addAdditionalSearchLocation(const QString& location); private: class SourceFileTemplatePrivate* const d; }; } Q_DECLARE_TYPEINFO(KDevelop::SourceFileTemplate::OutputFile, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::SourceFileTemplate::ConfigOption, Q_MOVABLE_TYPE); #endif // KDEVPLATFORM_SOURCEFILETEMPLATE_H diff --git a/plugins/filetemplates/templateoptionspage.cpp b/plugins/filetemplates/templateoptionspage.cpp index 44f99a209..db0c0fc13 100644 --- a/plugins/filetemplates/templateoptionspage.cpp +++ b/plugins/filetemplates/templateoptionspage.cpp @@ -1,156 +1,165 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templateoptionspage.h" #include "templateclassassistant.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include +#include using namespace KDevelop; class KDevelop::TemplateOptionsPagePrivate { public: QList entries; QHash controls; QHash typeProperties; QWidget *firstEditWidget; }; TemplateOptionsPage::TemplateOptionsPage(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new TemplateOptionsPagePrivate) { d->firstEditWidget = nullptr; d->typeProperties.insert(QStringLiteral("String"), "text"); + d->typeProperties.insert(QStringLiteral("Enum"), "currentText"); d->typeProperties.insert(QStringLiteral("Int"), "value"); d->typeProperties.insert(QStringLiteral("Bool"), "checked"); } TemplateOptionsPage::~TemplateOptionsPage() { delete d; } void TemplateOptionsPage::load(const SourceFileTemplate& fileTemplate, TemplateRenderer* renderer) { d->entries.clear(); d->controls.clear(); d->firstEditWidget = nullptr; QLayout* layout = new QVBoxLayout(); QHash > options = fileTemplate.customOptions(renderer); QHash >::const_iterator it; for (it = options.constBegin(); it != options.constEnd(); ++it) { QGroupBox* box = new QGroupBox(this); box->setTitle(it.key()); QFormLayout* formLayout = new QFormLayout; d->entries << it.value(); foreach (const SourceFileTemplate::ConfigOption& entry, it.value()) { QWidget* control = nullptr; const QString type = entry.type; if (type == QLatin1String("String")) { control = new QLineEdit(entry.value.toString(), box); } + else if (type == QLatin1String("Enum")) + { + auto input = new QComboBox(box); + input->addItems(entry.values); + input->setCurrentText(entry.value.toString()); + control = input; + } else if (type == QLatin1String("Int")) { auto input = new QSpinBox(box); input->setValue(entry.value.toInt()); if (!entry.minValue.isEmpty()) { input->setMinimum(entry.minValue.toInt()); } if (!entry.maxValue.isEmpty()) { input->setMaximum(entry.maxValue.toInt()); } control = input; } else if (type == QLatin1String("Bool")) { bool checked = (QString::compare(entry.value.toString(), QStringLiteral("true"), Qt::CaseInsensitive) == 0); QCheckBox* checkBox = new QCheckBox(box); checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked); control = checkBox; } else { qCDebug(PLUGIN_FILETEMPLATES) << "Unrecognized option type" << entry.type; } if (control) { const QString entryLabelText = i18n("%1:", entry.label); QLabel* label = new QLabel(entryLabelText, box); formLayout->addRow(label, control); d->controls.insert(entry.name, control); if (d->firstEditWidget == nullptr) { d->firstEditWidget = control; } } } box->setLayout(formLayout); layout->addWidget(box); } setLayout(layout); } QVariantHash TemplateOptionsPage::templateOptions() const { QVariantHash values; foreach (const SourceFileTemplate::ConfigOption& entry, d->entries) { Q_ASSERT(d->controls.contains(entry.name)); Q_ASSERT(d->typeProperties.contains(entry.type)); values.insert(entry.name, d->controls[entry.name]->property(d->typeProperties[entry.type])); } qCDebug(PLUGIN_FILETEMPLATES) << values.size() << d->entries.size(); return values; } void TemplateOptionsPage::setFocusToFirstEditWidget() { if (d->firstEditWidget) { d->firstEditWidget->setFocus(); } }