mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-12-10 18:24:11 +01:00
704 lines
28 KiB
C++
704 lines
28 KiB
C++
/**
|
|
******************************************************************************
|
|
*
|
|
* @file uavobjectgeneratorgcs.cpp
|
|
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
|
|
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
|
* @brief produce gcs code for uavobjects
|
|
*
|
|
* @see The GNU Public License (GPL) Version 3
|
|
*
|
|
*****************************************************************************/
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "uavobjectgeneratorgcs.h"
|
|
|
|
#define VERBOSE false
|
|
#define DEPRECATED true
|
|
|
|
#define DEFAULT_ENUM_PREFIX "E_"
|
|
|
|
using namespace std;
|
|
|
|
void error(QString msg)
|
|
{
|
|
cerr << "error: " << msg.toStdString() << endl;
|
|
}
|
|
|
|
void warning(ObjectInfo *object, QString msg)
|
|
{
|
|
cerr << "warning: " << object->filename.toStdString() << ": " << msg.toStdString() << endl;
|
|
}
|
|
|
|
void info(ObjectInfo *object, QString msg)
|
|
{
|
|
if (VERBOSE) {
|
|
cout << "info: " << object->filename.toStdString() << ": " << msg.toStdString() << endl;
|
|
}
|
|
}
|
|
|
|
struct Context {
|
|
ObjectInfo *object;
|
|
// enums
|
|
QString enums;
|
|
QString enumsCount;
|
|
QString registerImpl;
|
|
// interface
|
|
QString fields;
|
|
QString fieldsInfo;
|
|
QString properties;
|
|
QString deprecatedProperties;
|
|
QString getters;
|
|
QString setters;
|
|
QString notifications;
|
|
// implementation
|
|
QString fieldsInit;
|
|
QString fieldsDefault;
|
|
QString propertiesImpl;
|
|
QString notificationsImpl;
|
|
};
|
|
|
|
struct FieldContext {
|
|
FieldInfo *field;
|
|
// field
|
|
QString fieldName;
|
|
QString fieldType;
|
|
// property
|
|
QString propName;
|
|
QString ucPropName;
|
|
QString propType;
|
|
QString propRefType;
|
|
// deprecation
|
|
bool hasDeprecatedProperty;
|
|
bool hasDeprecatedGetter;
|
|
bool hasDeprecatedSetter;
|
|
bool hasDeprecatedNotification;
|
|
};
|
|
|
|
QString fieldTypeStrCPP(int type)
|
|
{
|
|
QStringList fieldTypeStrCPP;
|
|
|
|
fieldTypeStrCPP << "qint8" << "qint16" << "qint32" << "quint8" << "quint16" << "quint32" << "float" << "quint8";
|
|
return fieldTypeStrCPP[type];
|
|
}
|
|
|
|
QString fieldTypeStrCPPClass(int type)
|
|
{
|
|
QStringList fieldTypeStrCPPClass;
|
|
|
|
fieldTypeStrCPPClass << "INT8" << "INT16" << "INT32" << "UINT8" << "UINT16" << "UINT32" << "FLOAT32" << "ENUM";
|
|
return fieldTypeStrCPPClass[type];
|
|
}
|
|
|
|
QString toPropertyName(const QString & name)
|
|
{
|
|
QString str = name;
|
|
|
|
// make sure 1st letter is upper case
|
|
str[0] = str[0].toUpper();
|
|
|
|
// handle underscore
|
|
int p = str.indexOf('_');
|
|
while (p != -1) {
|
|
str.remove(p, 1);
|
|
str[p] = str[p].toUpper();
|
|
p = str.indexOf('_', p);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* Convert a string to lower camel case.
|
|
* Handles following cases :
|
|
* - Property -> property
|
|
* - MyProperty -> myProperty
|
|
* - MYProperty -> myProperty
|
|
* - MY_Property -> my_Property
|
|
* - MY -> my
|
|
*/
|
|
QString toLowerCamelCase(const QString & name)
|
|
{
|
|
QString str = name;
|
|
|
|
for (int i = 0; i < str.length(); ++i) {
|
|
if (str[i].isLower() || !str[i].isLetter()) {
|
|
break;
|
|
}
|
|
if (i > 0 && i < str.length() - 1) {
|
|
// after first, look ahead one
|
|
if (str[i + 1].isLower()) {
|
|
break;
|
|
}
|
|
}
|
|
str[i] = str[i].toLower();
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
QString toEnumName(ObjectInfo *object, FieldInfo *field, int index)
|
|
{
|
|
QString option = field->options[index];
|
|
|
|
if (option.contains(QRegExp(ENUM_SPECIAL_CHARS))) {
|
|
info(object, "Enumeration value \"" + option + "\" contains special chars, cleaning.");
|
|
option.replace(QRegExp(ENUM_SPECIAL_CHARS), "");
|
|
}
|
|
if (option[0].isDigit()) {
|
|
info(object, "Enumeration value \"" + option + "\" starts with a digit, prefixing with \"" + DEFAULT_ENUM_PREFIX "\".");
|
|
option = DEFAULT_ENUM_PREFIX + option;
|
|
}
|
|
if (option == option.toLower()) {
|
|
warning(object, "Enumeration value \"" + option + "\" is all lower case, consider capitalizing.");
|
|
}
|
|
if (option[0].isLower()) {
|
|
warning(object, "Enumeration value \"" + option + "\" does not start with an upper case letter.");
|
|
option[0] = option[0].toUpper();
|
|
}
|
|
if (option == "FALSE") {
|
|
warning(object, "Invalid enumeration name FALSE, converting to False.");
|
|
option = "False";
|
|
}
|
|
if (option == "TRUE") {
|
|
warning(object, "Invalid enumeration name TRUE, converting to True.");
|
|
option = "True";
|
|
}
|
|
return option;
|
|
}
|
|
|
|
QString toEnumStringList(ObjectInfo *object, FieldInfo *field)
|
|
{
|
|
QString enumListString;
|
|
|
|
for (int m = 0; m < field->options.length(); ++m) {
|
|
if (m > 0) {
|
|
enumListString.append(", ");
|
|
}
|
|
QString option = toEnumName(object, field, m);
|
|
enumListString.append(option);
|
|
}
|
|
|
|
return enumListString;
|
|
}
|
|
|
|
|
|
QString generate(Context &ctxt, const QString &fragment)
|
|
{
|
|
QString str = fragment;
|
|
|
|
str.replace(":ClassName", ctxt.object->name);
|
|
str.replace(":className", ctxt.object->namelc);
|
|
|
|
return str;
|
|
}
|
|
|
|
QString generate(Context &ctxt, FieldContext &fieldCtxt, const QString &fragment)
|
|
{
|
|
QString str = generate(ctxt, fragment);
|
|
|
|
str.replace(":PropName", fieldCtxt.ucPropName);
|
|
str.replace(":propName", fieldCtxt.propName);
|
|
str.replace(":propType", fieldCtxt.propType);
|
|
str.replace(":propRefType", fieldCtxt.propRefType);
|
|
|
|
str.replace(":fieldName", fieldCtxt.fieldName);
|
|
str.replace(":fieldType", fieldCtxt.fieldType);
|
|
str.replace(":fieldDesc", fieldCtxt.field->description);
|
|
str.replace(":fieldUnits", fieldCtxt.field->units);
|
|
str.replace(":fieldLimitValues", fieldCtxt.field->limitValues);
|
|
|
|
str.replace(":elementCount", QString::number(fieldCtxt.field->numElements));
|
|
str.replace(":enumCount", QString::number(fieldCtxt.field->numOptions));
|
|
|
|
return str;
|
|
}
|
|
|
|
void generateFieldInfo(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " // :fieldName\n");
|
|
|
|
if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
|
|
QStringList options = fieldCtxt.field->options;
|
|
ctxt.fieldsInfo += " typedef enum { ";
|
|
for (int m = 0; m < options.length(); ++m) {
|
|
if (m > 0) {
|
|
ctxt.fieldsInfo.append(", ");
|
|
}
|
|
ctxt.fieldsInfo += generate(ctxt, fieldCtxt, "%1_%2=%3")
|
|
.arg(fieldCtxt.field->name.toUpper())
|
|
.arg(options[m].toUpper().replace(QRegExp(ENUM_SPECIAL_CHARS), ""))
|
|
.arg(m);
|
|
}
|
|
ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameOptions;\n");
|
|
}
|
|
|
|
// Generate element names (only if field has more than one element)
|
|
if (fieldCtxt.field->numElements > 1 && !fieldCtxt.field->defaultElementNames) {
|
|
QStringList elemNames = fieldCtxt.field->elementNames;
|
|
ctxt.fieldsInfo += " typedef enum { ";
|
|
for (int m = 0; m < elemNames.length(); ++m) {
|
|
if (m > 0) {
|
|
ctxt.fieldsInfo.append(", ");
|
|
}
|
|
ctxt.fieldsInfo += QString("%1_%2=%3")
|
|
.arg(fieldCtxt.field->name.toUpper())
|
|
.arg(elemNames[m].toUpper())
|
|
.arg(m);
|
|
}
|
|
ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameElem;\n");
|
|
}
|
|
|
|
// Generate array information
|
|
if (fieldCtxt.field->numElements > 1) {
|
|
ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " static const quint32 %1_NUMELEM = :elementCount;\n")
|
|
.arg(fieldCtxt.field->name.toUpper());
|
|
}
|
|
}
|
|
|
|
void generateFieldInit(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
ctxt.fieldsInit += generate(ctxt, fieldCtxt, " // :fieldName\n");
|
|
|
|
// Setup element names
|
|
ctxt.fieldsInit += generate(ctxt, fieldCtxt, " QStringList :fieldNameElemNames;\n");
|
|
QStringList elemNames = fieldCtxt.field->elementNames;
|
|
ctxt.fieldsInit += generate(ctxt, fieldCtxt, " :fieldNameElemNames");
|
|
for (int m = 0; m < elemNames.length(); ++m) {
|
|
ctxt.fieldsInit += QString(" << \"%1\"").arg(elemNames[m]);
|
|
}
|
|
ctxt.fieldsInit += ";\n";
|
|
|
|
if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
|
|
ctxt.fieldsInit += generate(ctxt, fieldCtxt, " QStringList :fieldNameEnumOptions;\n");
|
|
QStringList options = fieldCtxt.field->options;
|
|
ctxt.fieldsInit += generate(ctxt, fieldCtxt, " :fieldNameEnumOptions");
|
|
for (int m = 0; m < options.length(); ++m) {
|
|
ctxt.fieldsInit += QString(" << \"%1\"").arg(options[m]);
|
|
}
|
|
ctxt.fieldsInit += ";\n";
|
|
ctxt.fieldsInit += generate(ctxt, fieldCtxt,
|
|
" fields.append(new UAVObjectField(\":fieldName\", tr(\":fieldDesc\"), \":fieldUnits\", UAVObjectField::ENUM, :fieldNameElemNames, :fieldNameEnumOptions, \":fieldLimitValues\"));\n");
|
|
} else {
|
|
ctxt.fieldsInit += generate(ctxt, fieldCtxt,
|
|
" fields.append(new UAVObjectField(\":fieldName\", tr(\":fieldDesc\"), \":fieldUnits\", UAVObjectField::%1, :fieldNameElemNames, QStringList(), \":fieldLimitValues\"));\n")
|
|
.arg(fieldTypeStrCPPClass(fieldCtxt.field->type));
|
|
}
|
|
}
|
|
|
|
void generateFieldDefault(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
if (!fieldCtxt.field->defaultValues.isEmpty()) {
|
|
// For non-array fields
|
|
if (fieldCtxt.field->numElements == 1) {
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " // :fieldName\n");
|
|
if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " data_.:fieldName = %1;\n")
|
|
.arg(fieldCtxt.field->options.indexOf(fieldCtxt.field->defaultValues[0]));
|
|
} else if (fieldCtxt.field->type == FIELDTYPE_FLOAT32) {
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " data_.:fieldName = %1;\n")
|
|
.arg(fieldCtxt.field->defaultValues[0].toFloat());
|
|
} else {
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " data_.:fieldName = %1;\n")
|
|
.arg(fieldCtxt.field->defaultValues[0].toInt());
|
|
}
|
|
} else {
|
|
// Initialize all fields in the array
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " // :fieldName\n");
|
|
for (int idx = 0; idx < fieldCtxt.field->numElements; ++idx) {
|
|
if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " data_.:fieldName[%1] = %2;\n")
|
|
.arg(idx)
|
|
.arg(fieldCtxt.field->options.indexOf(fieldCtxt.field->defaultValues[idx]));
|
|
} else if (fieldCtxt.field->type == FIELDTYPE_FLOAT32) {
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " data_.:fieldName[%1] = %2;\n")
|
|
.arg(idx)
|
|
.arg(fieldCtxt.field->defaultValues[idx].toFloat());
|
|
} else {
|
|
ctxt.fieldsDefault += generate(ctxt, fieldCtxt, " data_.:fieldName[%1] = %2;\n")
|
|
.arg(idx)
|
|
.arg(fieldCtxt.field->defaultValues[idx].toInt());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void generateField(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
if (fieldCtxt.field->numElements > 1) {
|
|
ctxt.fields += generate(ctxt, fieldCtxt, " :fieldType :fieldName[:elementCount];\n");
|
|
} else {
|
|
ctxt.fields += generate(ctxt, fieldCtxt, " :fieldType :fieldName;\n");
|
|
}
|
|
generateFieldInfo(ctxt, fieldCtxt);
|
|
generateFieldInit(ctxt, fieldCtxt);
|
|
generateFieldDefault(ctxt, fieldCtxt);
|
|
}
|
|
|
|
void generateEnum(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
Q_ASSERT(fieldCtxt.field->type == FIELDTYPE_ENUM);
|
|
|
|
QString enumStringList = toEnumStringList(ctxt.object, fieldCtxt.field);
|
|
|
|
ctxt.enums += generate(ctxt, fieldCtxt,
|
|
"class :ClassName_:PropName : public QObject {\n"
|
|
" Q_OBJECT\n"
|
|
"public:\n"
|
|
" enum Enum { %1 };\n"
|
|
" Q_ENUMS(Enum) // TODO switch to Q_ENUM once on Qt 5.5\n"
|
|
"};\n\n").arg(enumStringList);
|
|
|
|
ctxt.enumsCount += generate(ctxt, fieldCtxt, ":PropNameCount = :enumCount, ");
|
|
|
|
ctxt.registerImpl += generate(ctxt, fieldCtxt,
|
|
" qmlRegisterType<:ClassName_:PropName>(\"%1.:ClassName\", 1, 0, \":PropName\");\n").arg("UAVTalk");
|
|
}
|
|
|
|
void generateBaseProperty(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
ctxt.properties += generate(ctxt, fieldCtxt,
|
|
" Q_PROPERTY(:propType :propName READ :propName WRITE set:PropName NOTIFY :propNameChanged)\n");
|
|
|
|
ctxt.getters += generate(ctxt, fieldCtxt, " :propType :propName() const;\n");
|
|
ctxt.setters += generate(ctxt, fieldCtxt, " void set:PropName(const :propRefType value);\n");
|
|
|
|
ctxt.notifications += generate(ctxt, fieldCtxt, " void :propNameChanged(const :propRefType value);\n");
|
|
ctxt.notificationsImpl += generate(ctxt, fieldCtxt, " emit :propNameChanged(:propName());\n");
|
|
|
|
if (DEPRECATED) {
|
|
// generate deprecated property for retro compatibility
|
|
if (fieldCtxt.hasDeprecatedProperty) {
|
|
ctxt.deprecatedProperties += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ Q_PROPERTY(:fieldType :fieldName READ get:fieldName WRITE set:fieldName NOTIFY :fieldNameChanged);\n");
|
|
}
|
|
if (fieldCtxt.hasDeprecatedGetter) {
|
|
ctxt.getters += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ Q_INVOKABLE :fieldType get:fieldName() const { return static_cast<:fieldType>(:propName()); }\n");
|
|
}
|
|
if (fieldCtxt.hasDeprecatedSetter) {
|
|
ctxt.setters += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ void set:fieldName(:fieldType value) { set:PropName(static_cast<:propType>(value)); }\n");
|
|
}
|
|
if (fieldCtxt.hasDeprecatedNotification) {
|
|
ctxt.notifications += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ void :fieldNameChanged(:fieldType value);\n");
|
|
|
|
ctxt.notificationsImpl += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ emit :fieldNameChanged(get:fieldName());\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void generateSimpleProperty(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
|
|
generateEnum(ctxt, fieldCtxt);
|
|
}
|
|
|
|
generateBaseProperty(ctxt, fieldCtxt);
|
|
|
|
// getter implementation
|
|
ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
|
|
":propType :ClassName:::propName() const\n"
|
|
"{\n"
|
|
" QMutexLocker locker(mutex);\n"
|
|
" return static_cast<:propType>(data_.:fieldName);\n"
|
|
"}\n");
|
|
|
|
// emitters
|
|
QString emitters = generate(ctxt, fieldCtxt, "emit :propNameChanged(value);");
|
|
|
|
if (fieldCtxt.hasDeprecatedNotification) {
|
|
emitters += " ";
|
|
emitters += generate(ctxt, fieldCtxt, "emit :fieldNameChanged(static_cast<:fieldType>(value));");
|
|
}
|
|
|
|
// setter implementation
|
|
ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
|
|
"void :ClassName::set:PropName(const :propRefType value)\n"
|
|
"{\n"
|
|
" mutex->lock();\n"
|
|
" bool changed = (data_.:fieldName != static_cast<:fieldType>(value));\n"
|
|
" data_.:fieldName = static_cast<:fieldType>(value);\n"
|
|
" mutex->unlock();\n"
|
|
" if (changed) { %1 }\n"
|
|
"}\n\n").arg(emitters);
|
|
}
|
|
|
|
void generateIndexedProperty(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
|
|
generateEnum(ctxt, fieldCtxt);
|
|
}
|
|
|
|
// indexed getter/setter
|
|
ctxt.getters += generate(ctxt, fieldCtxt, " Q_INVOKABLE :propType :propName(quint32 index) const;\n");
|
|
ctxt.setters += generate(ctxt, fieldCtxt, " Q_INVOKABLE void set:PropName(quint32 index, const :propRefType value);\n");
|
|
|
|
// getter implementation
|
|
ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
|
|
":propType :ClassName:::propName(quint32 index) const\n"
|
|
"{\n"
|
|
" QMutexLocker locker(mutex);\n"
|
|
" return static_cast<:propType>(data_.:fieldName[index]);\n"
|
|
"}\n");
|
|
|
|
// emitters
|
|
QString emitters = generate(ctxt, fieldCtxt, "emit :propNameChanged(index, value);");
|
|
|
|
if (fieldCtxt.hasDeprecatedNotification) {
|
|
emitters += " ";
|
|
emitters += generate(ctxt, fieldCtxt, "emit :fieldNameChanged(index, static_cast<:fieldType>(value));");
|
|
}
|
|
|
|
// setter implementation
|
|
ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
|
|
"void :ClassName::set:PropName(quint32 index, const :propRefType value)\n"
|
|
"{\n"
|
|
" mutex->lock();\n"
|
|
" bool changed = (data_.:fieldName[index] != static_cast<:fieldType>(value));\n"
|
|
" data_.:fieldName[index] = static_cast<:fieldType>(value);\n"
|
|
" mutex->unlock();\n"
|
|
" if (changed) { %1 }\n"
|
|
"}\n\n").arg(emitters);
|
|
|
|
ctxt.notifications += generate(ctxt, fieldCtxt, " void :propNameChanged(quint32 index, const :propRefType value);\n");
|
|
|
|
if (DEPRECATED) {
|
|
// backward compatibility
|
|
if (fieldCtxt.hasDeprecatedGetter) {
|
|
ctxt.getters += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ Q_INVOKABLE :fieldType get:fieldName(quint32 index) const { return static_cast<:fieldType>(:propName(index)); }\n");
|
|
}
|
|
if (fieldCtxt.hasDeprecatedSetter) {
|
|
ctxt.setters += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ void set:fieldName(quint32 index, :fieldType value) { set:PropName(index, static_cast<:propType>(value)); }\n");
|
|
}
|
|
|
|
if (fieldCtxt.hasDeprecatedNotification) {
|
|
ctxt.notifications += generate(ctxt, fieldCtxt,
|
|
" /*DEPRECATED*/ void :fieldNameChanged(quint32 index, :fieldType value);\n");
|
|
}
|
|
}
|
|
|
|
for (int elementIndex = 0; elementIndex < fieldCtxt.field->numElements; elementIndex++) {
|
|
QString elementName = fieldCtxt.field->elementNames[elementIndex];
|
|
|
|
QString sep;
|
|
if (fieldCtxt.propName.right(1)[0].isDigit() && elementName[0].isDigit()) {
|
|
info(ctxt.object, "Property \"" + fieldCtxt.propName + "\" and element \"" + elementName + "\" have digit conflict, consider fixing it.");
|
|
sep = "_";
|
|
}
|
|
|
|
FieldContext elementCtxt;
|
|
elementCtxt.field = fieldCtxt.field;
|
|
elementCtxt.fieldName = fieldCtxt.fieldName + "_" + elementName;
|
|
elementCtxt.fieldType = fieldCtxt.fieldType;
|
|
elementCtxt.propName = fieldCtxt.propName + sep + elementName;
|
|
elementCtxt.ucPropName = fieldCtxt.ucPropName + sep + elementName;
|
|
elementCtxt.propType = fieldCtxt.propType;
|
|
elementCtxt.propRefType = fieldCtxt.propRefType;
|
|
// deprecation
|
|
elementCtxt.hasDeprecatedProperty = (elementCtxt.fieldName != elementCtxt.propName) && DEPRECATED;
|
|
elementCtxt.hasDeprecatedGetter = DEPRECATED;
|
|
elementCtxt.hasDeprecatedSetter = ((elementCtxt.fieldName != elementCtxt.ucPropName) || (elementCtxt.fieldType != elementCtxt.propType)) && DEPRECATED;
|
|
elementCtxt.hasDeprecatedNotification = ((elementCtxt.fieldName != elementCtxt.propName) || (elementCtxt.fieldType != elementCtxt.propType)) && DEPRECATED;
|
|
|
|
|
|
generateBaseProperty(ctxt, elementCtxt);
|
|
|
|
ctxt.propertiesImpl += generate(ctxt, elementCtxt,
|
|
":propType :ClassName:::propName() const { return %1(%2); }\n").arg(fieldCtxt.propName).arg(elementIndex);
|
|
|
|
ctxt.propertiesImpl += generate(ctxt, elementCtxt,
|
|
"void :ClassName::set:PropName(const :propRefType value) { set%1(%2, value); }\n").arg(fieldCtxt.ucPropName).arg(elementIndex);
|
|
}
|
|
}
|
|
|
|
void generateProperty(Context &ctxt, FieldContext &fieldCtxt)
|
|
{
|
|
// do some checks
|
|
QString fieldName = fieldCtxt.fieldName;
|
|
|
|
if (fieldName[0].isLower()) {
|
|
info(ctxt.object, "Field \"" + fieldName + "\" does not start with an upper case letter.");
|
|
}
|
|
|
|
// generate all properties
|
|
if (fieldCtxt.field->numElements > 1) {
|
|
generateIndexedProperty(ctxt, fieldCtxt);
|
|
} else {
|
|
generateSimpleProperty(ctxt, fieldCtxt);
|
|
}
|
|
}
|
|
|
|
bool UAVObjectGeneratorGCS::generate(UAVObjectParser *parser, QString templatepath, QString outputpath)
|
|
{
|
|
gcsCodePath = QDir(templatepath + QString(GCS_CODE_DIR));
|
|
gcsOutputPath = QDir(outputpath);
|
|
gcsOutputPath.mkpath(gcsOutputPath.absolutePath());
|
|
|
|
gcsCodeTemplate = readFile(gcsCodePath.absoluteFilePath("uavobject.cpp.template"));
|
|
gcsIncludeTemplate = readFile(gcsCodePath.absoluteFilePath("uavobject.h.template"));
|
|
QString gcsInitTemplate = readFile(gcsCodePath.absoluteFilePath("uavobjectsinit.cpp.template"));
|
|
|
|
if (gcsCodeTemplate.isEmpty() || gcsIncludeTemplate.isEmpty() || gcsInitTemplate.isEmpty()) {
|
|
error("Error: Failed to read gcs code templates.");
|
|
return false;
|
|
}
|
|
|
|
QString objInc;
|
|
QString gcsObjInit;
|
|
|
|
for (int objidx = 0; objidx < parser->getNumObjects(); ++objidx) {
|
|
ObjectInfo *object = parser->getObjectByIndex(objidx);
|
|
process_object(object);
|
|
|
|
Context ctxt;
|
|
ctxt.object = object;
|
|
|
|
objInc.append(QString("#include \"%1.h\"\n").arg(object->namelc));
|
|
|
|
gcsObjInit += ::generate(ctxt, " objMngr->registerObject( new :ClassName() );\n");
|
|
gcsObjInit += ::generate(ctxt, " :ClassName::registerQMLTypes();\n");
|
|
}
|
|
|
|
// Write the gcs object initialization files
|
|
gcsInitTemplate.replace("$(OBJINC)", objInc);
|
|
gcsInitTemplate.replace("$(OBJINIT)", gcsObjInit);
|
|
|
|
bool res = writeFileIfDifferent(gcsOutputPath.absolutePath() + "/uavobjectsinit.cpp", gcsInitTemplate);
|
|
if (!res) {
|
|
error("Error: Could not write output files");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Generate the GCS object files
|
|
*
|
|
* TODO add getter to get enum names
|
|
*
|
|
* TODO handle "char" unit
|
|
* TODO handle "bool" unit
|
|
* TODO handle "hex" unit
|
|
* TODO handle Vector
|
|
*/
|
|
bool UAVObjectGeneratorGCS::process_object(ObjectInfo *object)
|
|
{
|
|
if (object == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Prepare output strings
|
|
QString outInclude = gcsIncludeTemplate;
|
|
QString outCode = gcsCodeTemplate;
|
|
|
|
// Replace common tags
|
|
replaceCommonTags(outInclude, object);
|
|
replaceCommonTags(outCode, object);
|
|
|
|
// to avoid name conflicts
|
|
QStringList reservedProperties;
|
|
reservedProperties << "Description" << "Metadata";
|
|
|
|
Context ctxt;
|
|
ctxt.object = object;
|
|
|
|
ctxt.registerImpl += ::generate(ctxt,
|
|
" qmlRegisterType<:ClassName>(\"%1.:ClassName\", 1, 0, \":ClassName\");\n").arg("UAVTalk");
|
|
|
|
ctxt.registerImpl += ::generate(ctxt,
|
|
" qmlRegisterType<:ClassNameConstants>(\"%1.:ClassName\", 1, 0, \":ClassNameConstants\");\n").arg("UAVTalk");
|
|
|
|
for (int n = 0; n < object->fields.length(); ++n) {
|
|
FieldInfo *field = object->fields[n];
|
|
|
|
// field context
|
|
FieldContext fieldCtxt;
|
|
fieldCtxt.field = field;
|
|
|
|
// field properties
|
|
fieldCtxt.fieldName = field->name;
|
|
fieldCtxt.fieldType = fieldTypeStrCPP(field->type);
|
|
|
|
fieldCtxt.ucPropName = toPropertyName(field->name);
|
|
fieldCtxt.propName = toLowerCamelCase(fieldCtxt.ucPropName);
|
|
fieldCtxt.propType = fieldCtxt.fieldType;
|
|
if (field->type == FIELDTYPE_INT8) {
|
|
fieldCtxt.propType = fieldTypeStrCPP(FIELDTYPE_INT16);
|
|
} else if (field->type == FIELDTYPE_UINT8) {
|
|
fieldCtxt.propType = fieldTypeStrCPP(FIELDTYPE_UINT16);
|
|
} else if (field->type == FIELDTYPE_ENUM) {
|
|
QString enumClassName = object->name + "_" + fieldCtxt.ucPropName;
|
|
fieldCtxt.propType = enumClassName + "::Enum";
|
|
}
|
|
// reference type
|
|
fieldCtxt.propRefType = fieldCtxt.propType;
|
|
|
|
// deprecation
|
|
fieldCtxt.hasDeprecatedProperty = (fieldCtxt.fieldName != fieldCtxt.propName) && DEPRECATED;
|
|
fieldCtxt.hasDeprecatedGetter = DEPRECATED;
|
|
fieldCtxt.hasDeprecatedSetter = ((fieldCtxt.fieldName != fieldCtxt.ucPropName) || (fieldCtxt.fieldType != fieldCtxt.propType)) && DEPRECATED;
|
|
fieldCtxt.hasDeprecatedNotification = ((fieldCtxt.fieldName != fieldCtxt.propName) || (fieldCtxt.fieldType != fieldCtxt.propType)) && DEPRECATED;
|
|
|
|
generateField(ctxt, fieldCtxt);
|
|
|
|
if (reservedProperties.contains(field->name)) {
|
|
warning(object, "Ignoring reserved property " + field->name + ".");
|
|
continue;
|
|
}
|
|
|
|
generateProperty(ctxt, fieldCtxt);
|
|
}
|
|
|
|
outInclude.replace("$(ENUMS)", ctxt.enums);
|
|
outInclude.replace("$(ENUMS_COUNT)", ctxt.enumsCount);
|
|
outInclude.replace("$(DATAFIELDS)", ctxt.fields);
|
|
outInclude.replace("$(DATAFIELDINFO)", ctxt.fieldsInfo);
|
|
outInclude.replace("$(PROPERTIES)", ctxt.properties);
|
|
outInclude.replace("$(DEPRECATED_PROPERTIES)", ctxt.deprecatedProperties);
|
|
outInclude.replace("$(PROPERTY_GETTERS)", ctxt.getters);
|
|
outInclude.replace("$(PROPERTY_SETTERS)", ctxt.setters);
|
|
outInclude.replace("$(PROPERTY_NOTIFICATIONS)", ctxt.notifications);
|
|
|
|
outCode.replace("$(FIELDSINIT)", ctxt.fieldsInit);
|
|
outCode.replace("$(FIELDSDEFAULT)", ctxt.fieldsDefault);
|
|
outCode.replace("$(PROPERTIES_IMPL)", ctxt.propertiesImpl);
|
|
outCode.replace("$(NOTIFY_PROPERTIES_CHANGED)", ctxt.notificationsImpl);
|
|
outCode.replace("$(REGISTER_QML_TYPES)", ctxt.registerImpl);
|
|
|
|
// Write the GCS code
|
|
bool res = writeFileIfDifferent(gcsOutputPath.absolutePath() + "/" + object->namelc + ".cpp", outCode);
|
|
if (!res) {
|
|
error("Error: Could not write gcs output files");
|
|
return false;
|
|
}
|
|
res = writeFileIfDifferent(gcsOutputPath.absolutePath() + "/" + object->namelc + ".h", outInclude);
|
|
if (!res) {
|
|
error("Error: Could not write gcs output files");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|