Changing and removing values from deeply nested QVariant - qt

I'm using QVariant to manage the project settings of our In-House application.
For this I'm using a nested QVariantMap recursively containing QVariantMaps and leaves holding the actual values.
Now, I found it quite cumbersome to set and remove nodes from this tree like structure. Especially, if the nesting reaches a certain depth.
Part of the problem is, that value<T> returns a copy instead of a reference. (I'm wondering, why Qt is lacking this feature??)
#include <QVariantMap>
#include <QDebug>
int main(int argc, char** args) {
QVariantMap map = { {"A", QVariantMap{ {"B",5.} }} };
{
// Change value
auto nested = map["A"].value<QVariantMap>();
nested["B"] = 6;
map["A"] = nested;
qDebug() << map;
// What it should be
// map["A"]["B"] = 5;
}
{
// Remove value
auto nested = map["A"].value<QVariantMap>();
nested.remove("B");
map["A"] = nested;
qDebug() << map;
// What it should be
// map["A"].remove("B");
}
}
What might be the easiest way to directly set and remove values and to make my function a one-liner? Performance is not critical, but ease of usability is definitely an issue for me.

After some thought I came up to the idea to use a path to my desired value. This path should be unique.
The following code recursively finds the value and removes it. Changing a value should be quite similar. I'm not sure, if there might an easier approach.
bool remove(QVariantMap& map, const QString& path, QString sep = ".") {
auto elems = path.split(sep);
if (elems.size() > 1) {
if (!map.contains(elems.first())) return false;
auto tmp = elems;
tmp.pop_front();
auto childMap = map[elems.first()].value<QVariantMap>();
bool ret = remove(childMap, tmp.join("."));
if (!ret) return false;
map[elems.first()] = childMap;
return true;
}
else if (elems.size() == 1) {
return map.remove(elems[0]) >= 1;
}
else {
return false;
}
}
Remark
This solution should not be used, if there is a lot of data, as there is quite a lot of copying of maps.

Related

Correct Assignment for Pointers

I am shifting from Python to C so bit rusty on the semantics as well as coding habit. In Python everything is treated as an object and objects are passed to functions. This is not the case in C so I want to increment an integer using pointers. What is the correct assignment to do so. I want to do it the following way but have the assignments wrong:
#include <stdio.h>
int i = 24;
int increment(*i){
*i++;
return i;
}
int main() {
increment(&i);
printf("i = %d, i);
return 0;
}
I fixed your program:
#include <stdio.h>
int i = 24;
// changed from i to j in order to avoid confusion.
// note you could declare the return type as void instead
int increment(int *j){
(*j)++;
return *j;
}
int main() {
increment(&i);
printf("i = %d", i);
return 0;
}
Your main error was the missing int in the function's argument (also a missing " in the printf).
Also I would prefer using parentheses in expressions as *j++ and specify exactly the precedence like I did in (*j)++, because I want to increment the content of the variable in the 'j' location not to increment the pointer - meaning to point it on the next memory cell - and then use its content.

QT container, with specified order and no repetitions

I need somthing similar to QSet, but I need the items to be saved on the order I inserted them
is there such thing?
I am not aware of anything like that out of the box in neither Qt nor STL. Boost has something like that I think but it is not that hard to do this yourself.
You could do a wrapper around QHash like this:
template<typename T>
class MySet : QHash<T, int>
{
public:
using QHash<T, int>::QHash;
QVector<T> values() //this 'hides' the base QHash::values() of QHash
{
QVector<T> vec(count());
for(auto it = cbegin(); it != end(); ++it)
{
vec[it.value()] = it.key();
}
return vec;
}
void insert(const T &value)
{
if(!contains(value))
{
insert(value, m_Data.count());
}
}
};
The usage is quite similar to QSet:
MySet<QString> set;
set.insert("1");
set.insert("2");
set.insert("3");
qDebug() << set.values();
And that prints the values in order. If you need more complete support like iterators also iterating in your desired order you would have to reimplement more functionality but the gist of it would be the same. After all QSet is internally QHash as well. Note that the above does not support removal without modification.
Maybe a QList or a QVector could help.
QList<QString> stringList;
//By the way, Qt provides QStringList as a typedef for QList<QString>
stringList.append("A");
stringList.append("B");
qDebug() << stringList.at(0); //A
qDebug() << stringList.at(1); //B

QMargins in QSS

Is it possible to set a custom QMargins Q_PROPERTY via QSS? Does it follow the margins syntax? Maybe I missed something obvious, but I didn't find any specific reference for this usage.
It was already mentioned in the previous answer that there's no specific parsing method for QMargins, but the property data is set as QStringList so we can add converters to make QMargins variable accept QStringList object.
const char QCssCustomValue_Margin[] = "qmargins";
QStringList marginsToStringList(const QMargins &margins) {
return {QLatin1String(QCssCustomValue_Margin), QString::asprintf("%dpx", margins.left()),
QString::asprintf("%dpx", margins.top()), QString::asprintf("%dpx", margins.right()),
QString::asprintf("%dpx", margins.bottom())};
}
QMargins stringListToMargins(const QStringList &stringList) {
QMargins res;
if (stringList.size() == 2 &&
!stringList.front().compare(QLatin1String(QCssCustomValue_Margin), Qt::CaseInsensitive)) {
QStringList valueList = stringList.back().split(",");
QVector<int> x;
for (int i = 0; i < qMin(valueList.size(), 4); ++i) {
QString str = valueList.at(i).simplified();
if (str.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) {
str.chop(2);
}
bool isNum;
int num = str.toInt(&isNum);
if (isNum) {
x.push_back(num);
} else {
x.push_back(0);
}
}
res.setLeft(x[0]);
res.setTop(x[1]);
res.setRight(x[2]);
res.setBottom(x[3]);
}
return res;
}
Then you should register these two functions to QApplication, make sure the codes below run before the property is set when the stylesheet is loaded.
// Implement outside
Q_DECLARE_METATYPE(QMargins)
// Execute after QApplication is created
QMetaType::registerConverter<QStringList, QMargins>(stringListToMargins);
QMetaType::registerConverter<QMargins, QStringList>(marginsToStringList);
After such preparation, when you have a Q_PROPERTY like this in a custom widget,
Q_PROPERTY(QMargins margins READ margins WRITE setMargins NOTIFY marginsChanged)
you can add property in stylesheet like,
MyWidget {
qproperty-margins: qmargins(1px, 1px, 1px, 1px);
}
You may need to remove any margins set by the layout. As far as I know you need to do this by code.
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(0);
In qcssparser.cpp (version 5.4) they have
QVariant v;
const QVariant value = w->property(property.toLatin1());
switch (value.type()) {
case QVariant::Icon: v = decl.iconValue(); break;
case QVariant::Image: v = QImage(decl.uriValue()); break;
case QVariant::Pixmap: v = QPixmap(decl.uriValue()); break;
case QVariant::Rect: v = decl.rectValue(); break;
case QVariant::Size: v = decl.sizeValue(); break;
case QVariant::Color: v = decl.colorValue(); break;
case QVariant::Brush: v = decl.brushValue(); break;
#ifndef QT_NO_SHORTCUT
case QVariant::KeySequence: v = QKeySequence(decl.d->values.at(0).variant.toString()); break;
#endif
default: v = decl.d->values.at(0).variant; break;
}
w->setProperty(property.toLatin1(), v);
So it's no special reader for margin.

Program fails when trying to add a pointer to an array inside a function (C)

I cannot get this code to work properly. When I try to compile it, one of three things will happen: Either I'll get no errors, but when I run the program, it immediately locks up; or it'll compile fine, but says 'Segmentation fault' and exits when I run it; or it gives warnings when compiled:
"conflicting types for ‘addObjToTree’
previous implicit declaration of ‘addObjToTree’ was here"
but then says 'Segmentation fault' and exits when I try to run it.
I'm on Mac OS X 10.6 using gcc.
game-obj.h:
typedef struct itemPos {
float x;
float y;
} itemPos;
typedef struct gameObject {
itemPos loc;
int uid;
int kind;
int isEmpty;
...
} gameObject;
internal-routines.h:
void addObjToTree (gameObject *targetObj, gameObject *destTree[]) {
int i = 0;
int stop = 1;
while (stop) {
if ((*destTree[i]).isEmpty == 0)
i++;
else if ((*destTree[i]).isEmpty == 1)
stop = 0;
else
;/*ERROR*/
}
if (stop == 0) {
destTree[i] = targetObj;
}
else
{
;/*ERROR*/
}
}
/**/
void initFS_LA (gameObject *target, gameObject *tree[], itemPos destination) {
addObjToTree(target, tree);
(*target).uid = 12981;
(*target).kind = 101;
(*target).isEmpty = 0;
(*target).maxHealth = 100;
(*target).absMaxHealth = 200;
(*target).curHealth = 100;
(*target).skill = 1;
(*target).isSolid = 1;
(*target).factionID = 555;
(*target).loc.x = destination.x;
(*target).loc.y = destination.y;
}
main.c:
#include "game-obj.h"
#include "internal-routines.h"
#include <stdio.h>
int main()
{
gameObject abc;
gameObject jkl;
abc.kind = 101;
abc.uid = 1000;
itemPos aloc;
aloc.x = 10;
aloc.y = 15;
gameObject *masterTree[3];
masterTree[0] = &(abc);
initFS_LA(&jkl, masterTree, aloc);
printf("%d\n",jkl.factionID);
return 0;
}
I don't understand why it doesn't work. I just want addObjToTree(...) to add a pointer to a gameObject in the next free space of masterTree, which is an array of pointers to gameObject structures. even weirder, if I remove the line addObjToTree(target, tree); from initFS_LA(...) it works perfectly. I've already created a function that searches masterTree by uid and that also works fine, even if I initialize a new gameObject with initFS_LA(...) (without the addObjToTree line.) I've tried rearranging the functions within the header file, putting them into separate header files, prototyping them, rearranging the order of #includes, explicitly creating a pointer variable instead of using &jkl, but absolutely nothing works. Any ideas? I appreciate any help
If I see this correctly, then you don't initialize elements 1 and 2 of the masterTree array anywhere. Then, your addObjToTree() function searches the - uninitialized - array for a free element.
Declaring a variable like gameObject *masterTree[3]; in C does not zero-initialize the array. Add some memset (masterTree, 0, sizeof (masterTree)); to initialize.
Note that you're declaring an array of pointers to structs here, not an array of structs (see also here), so you also need to adjust your addObjToTree() to check for a NULL-pointer instead of isEmpty.
It would also be good practice to pass the length of that array to that function to avoid buffer overruns.
If you want an array of structs, then you need to declare it as gameObject masterTree[3]; and the parameter in your addObjToTree() becomes gameObject *tree.

How to use a QFile with std::iostream?

Is it possible to use a QFile like a std::iostream? I'm quite sure there must be a wrapper out there. The question is where?
I have another libs, which requires a std::istream as input parameter, but in my program i only have a QFile at this point.
I came up with my own solution using the following code:
#include <ios>
#include <QIODevice>
class QStdStreamBuf : public std::streambuf
{
public:
QStdStreamBuf(QIODevice *dev) : std::streambuf(), m_dev(dev)
{
// Initialize get pointer. This should be zero so that underflow is called upon first read.
this->setg(0, 0, 0);
}
protected:
virtual std::streamsize xsgetn(std::streambuf::char_type *str, std::streamsize n)
{
return m_dev->read(str, n);
}
virtual std::streamsize xsputn(const std::streambuf::char_type *str, std::streamsize n)
{
return m_dev->write(str, n);
}
virtual std::streambuf::pos_type seekoff(std::streambuf::off_type off, std::ios_base::seekdir dir, std::ios_base::openmode /*__mode*/)
{
switch(dir)
{
case std::ios_base::beg:
break;
case std::ios_base::end:
off = m_dev->size() - off;
break;
case std::ios_base::cur:
off = m_dev->pos() + off;
break;
}
if(m_dev->seek(off))
return m_dev->pos();
else
return std::streambuf::pos_type(std::streambuf::off_type(-1));
}
virtual std::streambuf::pos_type seekpos(std::streambuf::pos_type off, std::ios_base::openmode /*__mode*/)
{
if(m_dev->seek(off))
return m_dev->pos();
else
return std::streambuf::pos_type(std::streambuf::off_type(-1));
}
virtual std::streambuf::int_type underflow()
{
// Read enough bytes to fill the buffer.
std::streamsize len = sgetn(m_inbuf, sizeof(m_inbuf)/sizeof(m_inbuf[0]));
// Since the input buffer content is now valid (or is new)
// the get pointer should be initialized (or reset).
setg(m_inbuf, m_inbuf, m_inbuf + len);
// If nothing was read, then the end is here.
if(len == 0)
return traits_type::eof();
// Return the first character.
return traits_type::not_eof(m_inbuf[0]);
}
private:
static const std::streamsize BUFFER_SIZE = 1024;
std::streambuf::char_type m_inbuf[BUFFER_SIZE];
QIODevice *m_dev;
};
class QStdIStream : public std::istream
{
public:
QStdIStream(QIODevice *dev) : std::istream(m_buf = new QStdStreamBuf(dev)) {}
virtual ~QStdIStream()
{
rdbuf(0);
delete m_buf;
}
private:
QStdStreamBuf * m_buf;
};
I works fine for reading local files. I haven't tested it for writing files. This code is surely not perfect but it works.
I came up with my own solution (which uses the same idea Stephen Chu suggested)
#include <iostream>
#include <fstream>
#include <cstdio>
#include <QtCore>
using namespace std;
void externalLibFunction(istream & input_stream) {
copy(istream_iterator<string>(input_stream),
istream_iterator<string>(),
ostream_iterator<string>(cout, " "));
}
ifstream QFileToifstream(QFile & file) {
Q_ASSERT(file.isReadable());
return ifstream(::_fdopen(file.handle(), "r"));
}
int main(int argc, char ** argv)
{
QFile file("a file");
file.open(QIODevice::WriteOnly);
file.write(QString("some string").toLatin1());
file.close();
file.open(QIODevice::ReadOnly);
std::ifstream ifs(QFileToifstream(file));
externalLibFunction(ifs);
}
Output:
some string
This code uses std::ifstream move constructor (C++x0 feature) specified in 27.9.1.7 basic_ifstream constructors section of Working Draft, Standard for Programming Language C++:
basic_ifstream(basic_ifstream&& rhs);
Effects: Move constructs from the
rvalue rhs. This is accomplished by
move constructing the base class, and
the contained basic_filebuf. Next
basic_istream::set_rdbuf(&sb) is called to install the contained
basic_filebuf.
See How to return an fstream (C++0x) for discussion on this subject.
If the QFile object you get is not open for read already, you can get filename from it and open an ifstream object.
If it's already open, you can get file handle/descriptor with handle() and go from there. There's no portable way of getting a fstream from platform handle. You will have to find a workaround for your platforms.
Here's a good guide for subclassing std::streambuf to provide a non-seekable read-only std::istream: https://stackoverflow.com/a/14086442/316578
Here is a simple class based on that approach which adapts a QFile into an std::streambuf which can then be wrapped in an std::istream.
#include <iostream>
#include <QFile>
constexpr qint64 ERROR = -1;
constexpr qint64 BUFFER_SIZE = 1024;
class QFileInputStreamBuffer final : public std::streambuf {
private:
QFile &m_file;
QByteArray m_buffer;
public:
explicit QFileInputStreamBuffer(QFile &file)
: m_file(file),
m_buffer(BUFFER_SIZE, Qt::Uninitialized) {
}
virtual int underflow() override {
if (atEndOfBuffer()) {
// try to get more data
const qint64 bytesReadIntoBuffer = m_file.read(m_buffer.data(), BUFFER_SIZE);
if (bytesReadIntoBuffer != ERROR) {
setg(m_buffer.data(), m_buffer.data(), m_buffer.data() + bytesReadIntoBuffer);
}
}
if (atEndOfBuffer()) {
// no more data available
return std::char_traits<char>::eof();
}
else {
return std::char_traits<char>::to_int_type(*gptr());
}
}
private:
bool atEndOfBuffer() const {
return gptr() == egptr();
}
};
If you want to be able to more things like seek, write, etc., then you'd need one of the other more complex solutions here which override more streambuf functions.
If you don't care much for performance you can always read everything from the file and dump it into an std::stringstream and then pass that to your library. (or the otherway, buffer everything to a stringstream and then write to a QFile)
Other than that, it doesn't look like the two can inter-operate. At any rate, Qt to STL inter operations are often a cause for obscure bugs and subtle inconsistencies if the version of STL that Qt was compiled with is different in any way from the version of STL you are using. This can happen for instance if you change the version of Visual Studio.

Resources