How to use QVariant to convert an enum to a QString - c++

In order to convert an enum in C++ to a QString one can do the following:
template<typename QEnum>
static QString QtEnumToString (const QEnum value)
{
QString valueString = QVariant::fromValue(value).toString();
return valueString;
}
This will generate a QString for an enum declared as:
enum class Type{Text, Html, Image, URL};
Q_ENUM(Type)
Q_DECLARE_METATYPE(ClipboardItem::Type) // this goes outside the class
So for example the output of:
qDebug() << ClipboardItem::QtEnumToString(Type::Html);
is "Html".
Now I would like to do the reverse operation but couldn't find a way. What I have tried is this:
static QVariant::Type QStringToQtEnum (const char *name)
{
return QVariant::nameToType(name);
}
However, if I try to run QStringToQtEnum("Type::Html") the output is always QVariant::Invalid.

Related

How do I make a custom combo box model in QT/C++ that displays the items and lets me pick the item as the class in question not a string? [duplicate]

I'm writing a lexical scanner that generates a stream of tokens from some input. Those tokens have a type and a value. Since I'm using Qt I chose to store the token data as a QVariant. This works pretty well for token data that is of a non-custom type.
Unfortunately, I have several custom types that are stored inside of tokens as well. The tokens have a toString() function that outputs a token description (for debugging), but for all tokens that have data of a custom type this function gives an empty string. The code goes like this:
Test.h:
struct Test
{
QString value_;
Test(const QString& value = "");
QString toString();
};
Q_DECLARE_METATYPE(Test)
Token.h:
struct Token
{
TokenType type_;
QVariant value_;
...
virtual QString toString() const;
};
Token.cpp:
QString Token::toString() const
{
QStringList sl;
sl << "Token(" << ::toString(type_) << ", ";
sl << value_.toString() << ")";
return sl.join("");
}
Example output from scanner:
"Token(TT_TEST, )"
"Token(TT_PLUS, +)"
"Token(TT_NUMBER, 5)"
"Token(TT_end, #)"
The TT_TEST token contains a Test class and I would expect the variant to print it's value. Unfortunately this does not work, and I've tried a lot of solutions that did not work. My current workaround looks like this:
template <typename T>
bool writeToStringList(QStringList& sl, QVariant v)
{
if (!v.canConvert<T>()) return false;
sl << v.value<T>().toString();
return true;
}
and a modified toString() function:
sl << "Token(";
sl << ::toString(type_) << ", ";
if (!writeToStringList<Test>(sl, value_)) {
sl << value_.toString();
}
and I have to do this for all my custom types which just feels pretty clumsy and wrong.
I figure there must be a better solution to this problem. Can anyone of you:
Tell me how to solve the problem with the QVariant in a better way, or
suggest a totally different solution without a QVariant. (I had a template solution earlier but I ran into different problems there, so I would need an example if that is suggested).
?
Q_DECLARE_METATYPE() is in fact sufficient to enable aggregation of a custom type in a QVariant. This does not cover aspects like implicit type conversion and comparison in context of the QVariant though.
Qt5 assumed, to facilitate implicit conversion to QString you may do the following:
#include <QMetaType>
struct Token {
QString _value;
};
Q_DECLARE_METATYPE( Token* );
QString tokenToString( Token* t ) {
return t->_value );
}
int main(int argc, char* argv[]) {
QMetaType::registerConverter<Token*,QString>( tokenToString );
Token t = { QString("hello") };
QVariant value;
value.setValue( &t );
std::cout << value << std::endl;
}
This is of course also possible (and more save) with Q_DECLARE_METATYPE( MyType ) and directly aggregating a Token instance in the QVariant instead of a pointer to Token.
See also this post from the Qt forum
You need to register a QString converter to the meta-object system for the custom type Token
To do that, you have two ways:
Your custom type Token has already a method toString() (or equivalent)
Then you can directly register this method as converter
#include <QDebug>
#include <QMetaType>
#include <functional>
struct Token
{
QString toString() const
{
return _value;
}
QString _value;
};
Q_DECLARE_METATYPE( Token )
int main(int argc, char* argv[])
{
qRegisterMetaType<Token>();
QMetaType::registerConverter(&Token::toString);
Token t {"hello"};
QVariant value;
value.setValue( t );
qDebug() << value.toString();
}
The toString() function is external (not a method of the custom type Token)
Then you can register this external toString function using unary function
#include <QDebug>
#include <QMetaType>
#include <functional>
struct Token
{
QString _value;
};
Q_DECLARE_METATYPE( Token )
QString tokenToString(const Token &t)
{
return t._value;
}
struct toQString : public std::unary_function<Token,QString>
{
QString operator() (const Token &value) const
{
return tokenToString(value);
}
};
int main(int argc, char* argv[])
{
qRegisterMetaType<Token>();
QMetaType::registerConverter<Token, QString, toQString>(toQString());
Token t {"hello"};
QVariant value;
value.setValue( t );
qDebug() << value.toString();
}
PS: If you want to do
qDebug() << value;
you need to implement the QDebug operator in your custom Type,
and register this operator for the custom type Token to the meta-object system.
QMetaType::registerDebugStreamOperator<Token>()

setProperty() returning false

I'm trying to convert a QVariantMap to a custom class derived from QObject but I'm getting the return value of false from setProperty() when it comes to set the property of my enum type. Code goes below:
The MessageHeader.h file:
// deserialization class header
class MessageHeader : public QObject
{
Q_OBJECT
public:
MessageHeader(QObject *parent = 0);
~MessageHeader();
enum class MessageType
{
none = 0,
foo = 1,
baa = 2
};
Q_ENUM(MessageType)
Q_PROPERTY(MessageType type READ getType WRITE setType)
Q_PROPERTY(int ContentLength READ getContentLength WRITE setContentLength)
void setType(MessageType type);
void setContentLength(int ContentLength);
MessageType getType();
int getContentLength();
QString toString();
MessageType type = MessageType::none;
int ContentLength = 0;
};
The MessageHeader.cpp file:
MessageHeader::MessageHeader(QObject *parent)
: QObject(parent)
{
}
MessageHeader::~MessageHeader()
{
}
MessageType MessageHeader::getType()
{
return type;
}
int MessageHeader::getContentLength()
{
return ContentLength;
}
void MessageHeader::setType(MessageType type)
{
this->type = type;
}
void MessageHeader::setContentLength(int ContentLength)
{
this->ContentLength = ContentLength;
}
QString MessageHeader::toString()
{
return QString("NOT IMPLEMENTED YET");
}
And the deserialize function template helper:
template<typename T>
T* Deserialize(const QString &json)
{
bool status = false;
QJson::Parser parser;
QVariantMap map = parser.parse(json.toUtf8(), &status).toMap();
if(!status)
return NULL;
T *obj = new T(); //don't worry about this, I'll rather take this from paramters once this is working
QObject *p = (QObject *) obj; // cast done so that I see setProperty() method
for(QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
{
const char *name = iter.key().toLatin1();
const QVariant value = iter.value();
qDebug() << "setting " << name << "=" << value;
// the issue goes below. Here setProperty() return false.
// At this point, name = 'type' and value = 2
assert(p->setProperty(name, value));
}
//QJson::QObjectHelper::qvariant2qobject(map, obj);
return obj;
}
The JSON input string to above function is like this:
"{\"ContentLength\": 100, \"type\": 2}"
The enum type is registered in the main funcction before anything else:
qRegisterMetaType<MessageType>("MessageType");
And here's the QJson library used in this example. I build it on Windows with this .pro file
EDIT:
I just found that the type property can't be find by indexOfProperty()
qDebug() << "id = " << meta->indexOfProperty(name); // print -1, name = 'type'
The enum property can only be set if the variant type is either a QString, QInt or QUInt as could be seen here. So to successfully set the enum property, the variant needs to be one of these types and nothing else. QJson parses any unsigned integers as QULongLong as can be seen here, line 84. So one way is to fork QJson and modify the code so the integer values are converted to QInt and QUInt or read/write the enum values as strings.
Also, putting statements within an assert is not a good idea, but I assume you just wrote that code trying to figure out the problem.
Just as a side note, according to Qt documentation,
[qRegisterMetaType] is useful only for registering an alias (typedef) for every other use case Q_DECLARE_METATYPE and qMetaTypeId() should be used instead.
so replacing qRegisterMetaType<MessageHeader::MessageType>("MessageType") with Q_DECLARE_METATYPE(MessageHeader::MessageType) in your header would be a reasonable move.
Building up on Rostislav's answer, if you have no choice but to receive a QULongLong as input, here is a code snippet to convert it if the property to set is an enum:
#include <QMetaProperty>
const QMetaObject* meta = object->metaObject();
const int index = meta->indexOfProperty(propName);
if (index == -1) {/* report error*/}
if (meta->property(index).isEnumType())
// special case for enums properties: they can be set from QInt or QUInt variants,
// but unsigned integers parsed from json are QULongLong
object->setProperty(propName, propVariant.value<unsigned int>());
else
object->setProperty(propName, propVariant);

Output parameter in factory method

I have this class MyClass that, most often, is created by parsing a string. I cannot trust that this string is always correct and therefore I do not want to put the parsing in the constructor. Hence, I have created a static parsing function that returns true if the string is valid and false if it is invalid. See below:
class MyClass
{
private:
Type1 m_memberA;
Type2 m_memberB;
public:
MyClass(const Type1& parameterA, const Type2& parameterB)
: m_memberA(parameterA), m_memberB(paramterB)
static bool parse(const std::string& input, MyClass * result)
{
Type1 paramA;
Type2 paramB;
//Parse input and get values for paramA and paramB.
//Return false if the input is invalid.
MyClass temp(paramA, paramB)
*result = temp;
return true;
}
}
My intent was to use it as follows:
void myFunction(const std::string& input)
{
MyClass * myObject = NULL;
if(MyClass::parse(input, myObject))
{
//Use myObject for something
}
else
{
//Log invalid input string
}
}
Now, this crashes at run time. And I figured that is because I am trying to de-reference a NULL-pointer, which obviously doesn't work. How can I fix this?
You need to pass either a pointer-to-pointer to the function or a pointer reference and allocate with new:
static bool parse(const std::string& input, MyClass *& result)
{
// ......
result = new MyClass(paramA, paramB)
return true;
}

Concatenate strings in function call c++

I'm trying to create a logger function where you can pass in a message which will be logged to a text file. Sometimes I'd like to pass in a variable concatenated with my message so I could do something like:
logger("The variable is: " + variable);
The function is defined as
void logger(std::string message);
I'm using Qt, so I don't know if it's relevant but the variable will always be a QString.
When I tried this it would say that no candidate function for
void logger(const QString);
So I thought why not make a second function where it would expect a concatenation:
void logger(std::string message);
void logger2(const QString message);
It compiled fine when I did
logger2("The variable is: " + variable);
However, when I debugged the passed message variable was an empty string.
How do I get this to work, is it possible?
Why not just do something like this:
QString qs = "hello";
qs.toStdString();
As far as concatenating on the fly, I like to use a simple formatter class:
class Formatter
{
public:
template<class Val> Formatter& operator<<(const Val& val)
{
ss_ << val;
return * this;
}
operator string () const { return ss_.str().c_str(); }
private:
std::stringstream ss_;
};
...which can be used like this:
logger(Formatter() << "The variable is: " << variable);
have you tried logger("The variable is: " + variable.toStdString());
?

How to store enum in SQLite database

I'm trying to store enum in SQLite database using QSql.
I have following class:
partydao.h:
class PartyDao : public QObject
{
public:
enum partyType { typeCompany, typePerson };
private:
Q_OBJECT
Q_ENUMS(partyType)
Q_PROPERTY(partyType type READ type WRITE set_type)
// other declarations
};
Q_DECLARE_METATYPE(PartyDao::partyType)
partydao.cpp:
#include "partydao.h"
static int id = qRegisterMetaType<PartyDao::partyType>("partyType");
I do insert like this:
PartyDao p;
p.setProperty("type", QVariant::fromValue(PartyDao::typePerson));
QSqlQuery query;
query.prepare("INSERT INTO party (type) values (:type)");
qDebug() << p.property("type").isNull();
query.bindValue(":type", p.property("type"));
query.exec();
Although qDebug() prints "false" (i.e. property is not null), null value is stored in db.
I've tried with column of type TEXT and INTEGER with no success.
Can you tell me what I'm doing wrong?
EDIT:
I've just checked and QVariant holding my enum claims it can't be converted to QString or int (canConvert<int>() and canConvert<QString>() return false). Is there a way to add this conversion?
I didn't find any way to use auto conversion of QVariants of user type. Therefore I've created singleton storing rules for conversion.
struct DbConverter
{
virtual ~DbConverter() {}
virtual QVariant toDbValue(const QVariant &value) = 0;
virtual QVariant fromDbValue(const QVariant &value) = 0;
};
class DbConversion
{
public:
static QVariant toDbValue(const QVariant &value)
{
return instance()->m_converters.contains(value.userType()) ?
instance()->m_converters[value.userType()]->toDbValue(value) :
value;
}
static QVariant fromDbValue(int type, const QVariant &value)
{
return instance()->m_converters.contains(type) ?
instance()->m_converters[type]->fromDbValue(value):
value;
}
static int setConverter(int typeId, DbConverter *converter)
{
instance()->m_converters[typeId] = converter;
return typeId;
}
private:
static DbConversion *instance()
{
static DbConversion *inst = new DbConversion;
return inst;
}
QHash<int, DbConverter*> m_converters;
};
I register my custom types using value returned from qRegisterMetaType() and perform conversions on each app<->db transfer.
Please let me know if you find any more elegant solution and I'll be happy to accept your answer.
Just assign initial value to the first element in your enum, and the compiler will auto-increment any further definitions, then save this value to your database? You may have to cast it back and forth between an int, but qvariant should take care of that for you.
ie
enum partyType { typeCompany=1, typePerson };