How to mutate list inside a QQmlPropertyMap? - c++

I have a config object called config represented with a QQmlPropertyMap. It has a key called huge_list which is a large list of strings.
QVariantList hugeList = makeHugeList(); // makes a huge list of QStrings
QQmlPropertyMap config;
ownerData.insert("huge_list", hugeList);
How can I add, remove, or edit a particular element of huge_list in C++ without replacing the entire list with a new one? The issue is that config["huge_list"] would return a QVariant which does not seem to support mutation of the underlying data.

Related

How to efficiently lookup items in QAbstractTableModel::data?

From what I have seen, Qt documentation and majority of examples online assume that we are happy with (column, row)-based lookup in data(). But what If my table is based on a custom structure? For instance let's have:
struct MyDrive
{
QString serialNo;
QString user;
QString pc;
QString ipAddress;
QString category;
};
where serialNo is the key. So any operation from outside (imagine the model having implemented a listener) uses it for removing/modifying an item, making QMap as an ideal candidate.
But how to connect this structure with QModelIndex's data? QAbstractTableModel::data asks for data with (column,row) as key, making it more suitable for QVector<QVector>> or something similar (somewhere I read I should avoid using containers with non-constant access time (like map) in data()).
I can imagine using, well, a map with QModelIndex as key and serialNo as value, which would be used as key to my (serialNo-based) map but this looks very inefficient -- QModelIndex addresses concrete entry (serialNo, user, pc, ...) after all so we'd be duplicating the same item over and over again. I was also thinking about having a <serialNo, MyDrive*> map but this is just a workaround to an ugly design decision.
I can't believe I'm the first one with this scenario, so how is it usually solved?
You can use QAbstractItemModel::match to find items by serial
qt help
And enter all the necessary data into the table. This will allow you not to use containers, but how efficient is this a question...
Second solution is subclass AbstractItemModel Reference. Now you can do what you want and use any container by implementing data() function.

Can I set the child of an object to be an object defined separetely in PugiXML?

I want to configure a device using an XML file and was thinking that I can make the individual pugi::xml_nodes first with the values I need and later on make them children of a document or some parent node. However, I seem to be doing something wrong.
Example that works:
#include "pugixml.hpp"
int main(){
pugi::xml_document xml;
pugi::xml_node configRecord = xml.append_child("configrecord");
pugi::xml_node configGroup = configRecord.append_child("configgroup");
configGroup.append_attribute("name") = "ftp server";
}
This works because I first create the parent document and then start branching by adding children. I was thinking that I can first make the node objects, store them into an array and parse that array to add them to the document. But this doesn't work.
#include "pugixml.hpp"
int main(){
pugi::xml_node myNode;
myNode.set_name("value");
myNode.append_child(pugi::node_pcdata).set_value("enable");
pugi::xml_document docu;
docu.set_name("document");
docu.child(myNode); // <- error here, cannot add child to document
}
Can I somehow use the strategy I was planning to or am I constrained to only adding children to an existing parent?
pugixml documentation states that pugi::xml_node is a non-owning pointer to actual node data stored in a pugi::xml_document object:
xml_node is the handle to document node; it can point to any node in
the document, including the document node itself. There is a common
interface for nodes of all types; the actual node type can be queried
via the xml_node::type() method. Note that xml_node is only a handle
to the actual node, not the node itself
Nodes and attributes do not exist without a document tree, so you
can’t create them without adding them to some document.
It seems to me that your code doesn't throw errors when you try to manipulate myNode because default-constructed "null" nodes silently consume operations on them to make chaining easier:
all operations are defined on empty nodes; generally the operations
don’t do anything and return empty nodes/attributes or empty strings
as their result [...] This is useful for chaining calls

QHash of QPair iteration

QHash<QPair<QString N_id, QString A_id>, QString name> info
I have this QHash , and i have the values of N_id and name for a particular index, how can i obtain the value of corresponding A_id. I am trying to use STL-style iterator. I can change QHash to QMap if needed but I cannot use:
QHash<QPair<QString N_id, QString name>, QString A_id>
Edit: N_id and A_id together forms a unique key in my case.
I think the major problem here is that QHash, being a hash table, looks up the values by hashing the keys. Hence, it needs to complete key to be able to look up a value; a "partial" key won't suffice - there's going to be no concrete object to hash then. A similar problem arises with a map: to navigate the BST, you need the complete object in order to make comparisons and left / right decisions. Thus, short of going back to the drawing board and revising your approach, I'd say, maintain a backwards map, be it a QHash or a QMap, with the mapping name -> pair(n_id, a_id). The downside is that you're going to have to keep the two in sync.
However, with the existing data structure, I'd perform a query like this:
#include <algorithm>
QHash<QPair<QString, QString>, QString> info;
QString a_n_id {/*...*/}; // the target N_id
QString a_name {/*...*/}; // the target name
/* ... */
const auto keyList = info.keys(a_name); // QList<QPair<QString, QString> >
std::find_if(keyList.begin(), keyList.end(),
[&](decltype(info)::key_type& key) { return key.first == a_n_id; });
See this question in case decltype(info)::value_type refuses to build on Microsoft VS.
This is of course going to be linear, since, as I've already said, a hash needs the complete object to be able to perform a lookup, hence we can't use the logarithmic complexity lookup in this case.

Possible to get QDomElement from QXmlStreamWriter?

I'm writing a small XMPP server using qxmpp. Now I want to create a QXmppStanza and present it (as if a client had sent it) to the server and my plugins using
void QXmppServer::handleElement(const QDomElement &element)
This function requires a QDomElement and not a QXmppStanza. The only XML realted function I found in QXmppStanza and its derived classes (besides parse(...) ) is the function
void toXml(QXmlStreamWriter *writer)
I don't have experience with XML handling in qt yet, so is there a more performant way than writing the XML to a string/ByteArray, use it as input to create a new QDomElement and return its documentElement?
After doing some further research I have to accept it is not possible.
As stated in QDomDocument's documentation I always require a QDomDocument in order to work with a QDomElement (and other nodes):
Since elements, text nodes, comments, processing instructions, etc., cannot exist outside the context of a document (...)
The QXmlStreamWriter doesn't have a QDomDocument, so I really have to create a QDomDocument (which of course must live as long I want to work with the element) and then parse the text (QDomDocument::setContent).
I had a similar issue and was able to convert from a stream to a DOM element by doing something similar to what is shown below.
The first step is to stream to a byte array.
QByteArray data;
QXmlStreamWriter writer(&data);
object->toXml(&writer);
The second step is to set the content of a DOM document. The document's document element should be the DOM element you need.
QDomDocument temp;
if(temp.setContent(data))
QDomElement element = temp.documentElement(); // do whatever you want with this element

design pattern for "save"

I'm currently working on a "save" mechanism, which allows a user to save the project his working on on hard disc. The output will be a XML file containing all kinds of data.
Now our project structure is about to change and we need to write a new xml file (create a new save method).
So now here comes the challenge: When saving I want the user to be able to choose which file format he will be creating (version1 (old) or version2 (new)).
Does anyone now how to achieve that? Is there a suitable design pattern around?
Remarks:
- The data we are saving can be seen as unrelated blocks, so it would actually be easy to exchange an old block with a new one.
- The whole goal of the thing is, it should be readable again when loading an old project. (I assume this can be done by tags, and just react on tags when loading?)
This sounds like a good application for the Strategy pattern.
You would create an abstract base class FileFormat (the Strategy interface) with two virtual functions, projectToXml and xmlToProject, which are supposed to turn your internal project representation into XML or vice versa.
Then you create two implementing subclasses FileFormatNew and FileFormatLegacy (these are the concrete strategies).
Your save functions would then additionally require an instance of FileFormat, and call the corresponding method of that object to do the data conversion. Your load function could choose the strategy to use by examining the XML tree for something which tells it which version it is.
And when you ever need to support another file format, you just have to create a new class which is a subclass of FileFormat.
Addendum after the exchange in the comments
When you are going to have a lot of versions with very small differences and you still want to use the Strategy pattern, you could make the FileFormat a composite of multiple strategies: A CircleStragegy, a RectangleStrategy, a LineStrategy etc.. In that case I wouldn't use different classes for different versions of the FileFormat. I would create a static factory function for each version which returns a FileFormat with the Strategy objects used in that version.
FileFormat FileFormat::createVersion1_0() {
return new FileFormat(
new LineStrategyOld(),
new CircleStrategyOld(),
new RectangleStragegyOld()
);
}
FileFormat FileFormat::createVersion1_1() {
// the 1.1 version introduced the new way to save lines
return new FileFormat(
new LineStrategyNew(),
new CircleStrategyOld(),
new RectangleStragegyOld()
);
}
FileFormat FileFormat::createVersion1_2() {
// 1.2 uses the new format to save circles
return new FileFormat(
new LineStrategyNew(),
new CircleStrategyNew(),
new RectangleStragegyOld()
);
}
FileFormat FileFormat::createVersion1_3() {
// 1.3 uses a new format to save rectangles, but we realized that
// the new way to save lines wasn't that good after all, so we
// returned to the old way.
return new FileFormat(
new LineStrategyOld(),
new CircleStrategyNew(),
new RectangleStragegyNew()
);
}
Note: In real code you would of course use more descriptive suffixes than "Old" and "New" for your strategy class names.