How to create dynamic signals and slots in Qt? - c++

The signal/slot mechanism in Qt, is a static mechanism. The classes have to be preprocessed by the moc compiler.
Now I want to create signals and slots dynamically at run-time.
I already have a working solution, but it feels to me like a hack, although I am using publicly available methods.
This is the code for dynamic slots:
bool DynamicQObject::connectDynamicSlot(const QString &objectName, QObject *pSourceObject, QMetaMethod signalMethod)
{
QByteArray slotName = signalMethod.name().prepend("on").append("(");
QStringList parameters;
for (int i = 0, j = signalMethod.parameterCount(); i < j; ++i)
{
parameters << QMetaType::typeName(signalMethod.parameterType(i));
}
slotName.append(parameters.join(",")).append(")");
QByteArray theSignal = QMetaObject::normalizedSignature(signalMethod.methodSignature().constData());
QByteArray theSlot = QMetaObject::normalizedSignature(slotName);
if (!QMetaObject::checkConnectArgs(theSignal, theSlot))
{
return false;
}
int signalId = pSourceObject->metaObject()->indexOfSignal(theSignal);
if (signalId < 0)
{
return false;
}
int slotId = slotIndices.value(theSlot, -1);
if (slotId < 0)
{
slotId = slotList.size();
slotIndices[theSlot] = slotId;
slotList.append(createSlot(theSlot, objectName, signalMethod));
}
return QMetaObject::connect(pSourceObject, signalId, this, slotId + metaObject()->methodCount());
}
As you can see, I make intensive use of the QMetaObject, and particularly the index of the slots (method count).
The code for dynamic signals is comparable.
Now my question is: how future proof is this solution, especially because I assume that the index must be at least one bigger than the methodCount()?

Now my question is: how future proof is this solution, especially because I assume that the index must be at least one bigger than the methodCount()?
It should work as of now.
As for future proof... may be. The snippet is using unsupported features which means those might break at any point in the future. It is likely though it will continue working.

As a separate option, if all your things are very similar in nature (like in a vector or something), consider connecting to a lambda function. eg:
QObject::connect(iter->checkbox, &QCheckBox::stateChanged,
[&, iter->startup_status](int new_val) {
if (new_val == Qt::CheckState::Checked) {
startup_status = true;
} else {
startup_status = false;
}
});
where iter is a struct/class that has public fields
QCheckBox *checkbox;
bool startup_status;
by this method, it is possible to have a variable number of very similar "slots" (which aren't actually slots, but act like slots)

Related

How to create and use a QVector in other functions or is there an easier way to do this?

I am using multiple QSpinBoxes that all need to do the exact same thing when interacted with. I am able to get them to do what I want with no issue but I thought I could optimize them by just having them call on 1 function. The only issue was that I would have to have an if statement in order to set a new minimum every time they are interacted with. If I could put them into some sort of array or list then I would be able to easily just make the change based off of one number. Using various previous threads and websites I tried to make a QVector of QSpinboxes in order to use as I previously described. After a lot of trial and errors I've reached a point where I'm not sure why I am getting the error that I am.
Here is my code:
MainWindow::~MainWindow()
{
QVector <QSpinBox*> boxes(0);
QSpinBox* sqlBox=new QSpinBox(this);
QSpinBox* perlBox=new QSpinBox(this);
QSpinBox* rustBox=new QSpinBox(this);
QSpinBox* cSharpBox=new QSpinBox(this);
QSpinBox* htmlBox=new QSpinBox(this);
QSpinBox* cssBox=new QSpinBox(this);
QSpinBox* javaBox=new QSpinBox(this);
QSpinBox* pythonBox=new QSpinBox(this);
QSpinBox* cPlusBox=new QSpinBox(this);
boxes.append(sqlBox);
boxes.append(perlBox);
boxes.append(rustBox);
boxes.append(cSharpBox);
boxes.append(htmlBox);
boxes.append(cssBox);
boxes.append(javaBox);
boxes.append(pythonBox);
boxes.append(cPlusBox);
for (int i = 0; i <= 8; i++)
{
boxes[i]->setRange(0, 1000);
boxes[i]->accessibleName();
if (i < 3)
{
boxes[i]->setGeometry(100, 105+(90*i), 71, 21);
}
else if (i >= 3 && i < 6)
{
boxes[i]->setGeometry(290, 105+(90*(i%3)), 71, 21);
}
else
{
boxes[i]->setGeometry(460, 105+(90*(i%3)), 71, 21);
}
}
delete ui;
}
void MainWindow::buyPrgm(short unsigned int input,short unsigned int prgm)
{
int first = 0, second = 0;
first = revButton();
if (code >= prgmCostOG[prgm] + pow(prgmCost[prgm], 0.1*prgmAmount[prgm]))
{
prgmAmount[prgm] = input;
prgmCost[prgm] = prgmCostOG[prgm] + pow(prgmCost[prgm], 0.1*prgmAmount[prgm]);
ui->boxes[prgm]->setMinimum(prgmAmount);
}
else
{
ui->noBuy->setText("You don't have enough code!");
for(int i = 0; i > 1000; i++)
{
second = revButton();
if ((second - first) > 5)
{
ui->noBuy->setText("");
break;
}
}
}
}
The two errors I'm getting are:
"error: Member reference type 'QVector' (aka 'QList') is not a pointer; did you mean to use '.'? (fix available)"
// I can fix this error by changing
boxes[prgm]->setMinimum(prgmAmount[prgm]);
// into
boxes[prgm].setMinimum(prgmAmount[prgm]);
Even after making this fix I still have this error:
"error: No member named 'setMinimum' in 'QList'"
I assume this means that it only wants me to use the built in functions that work with QVectors for example "append' or "capacity" despite being able to use the built in functions for QSpinBoxes in the function that they were declared in.
Once again everything I've learned about QVectors has been through posts that people have already made so I may be making awful mistakes. Any help is welcome.

Create a new object from pointer reference C++

So, I've this code below:
foreach (QLineSeries* series, lineSeriesMap.values())
{
// ...
}
And I will modify series objects in this loop and I don't want to modify the original one, but create a new edited one. I'm extremely new to C++ and Qt so, I want something as the Java code below:
QLineSeries editedSeries = new QLineSeries(series);
I'm deleting elements, editing and re-ordering them from series by the way. But, as I said I need them both.
EDIT:
I've tried your answers but best way I believe is putting the code. This is a project made by some co-worker who changed jobs so its not my code, as i said I dont know C++.
chartwidget.h
void fillAreaSeries();
//...
QHash<QString,QLineSeries*> lineSeriesEntersMap;
QHash<QString,QLineSeries*> lineSeriesExitsMap;
chartwidget.cpp
void ChartWidget::fillAreaSeries() {
foreach (QLineSeries* seriesEnter, lineSeriesEntersMap.values())
{
if (lineSeriesExitsMap.contains(seriesEnter->name())) {
QLineSeries* seriesExit = lineSeriesExitsMap.value(seriesEnter->name());
if (!((seriesEnter->points().size() == 1) && (seriesExit->points().size() == 1))) {
for(int i = seriesEnter->points().size() - 1; i > 0; i--)
{
if (seriesEnter->points().at(i - 1).y() > seriesEnter->points().at(i).y())
{
seriesEnter->removePoints(i, 1);
}
}
for (int i = seriesExit->points().size() - 1; i > 0; i--)
{
if (seriesExit->points().at(i - 1).y() < seriesExit->points().at(i).y())
{
seriesExit->removePoints(i-1, 1);
}
}
QVector<QPointF> editPoints = seriesExit->pointsVector();
std::sort(editPoints.begin(),editPoints.end(), [] (const QPointF & p1, const QPointF & p2)
{
return p1.y() < p2.y();
});
seriesExit->replace(editPoints);
qDebug() << "__Swap:__";
qDebug() << seriesEnter->points().at(0).y();
qDebug() << seriesExit->points().at(0).y();
qDebug() << seriesEnter->points().at(1).y();
qDebug() << seriesExit->points().at(1).y();
QAreaSeries* series = new QAreaSeries(seriesEnter, seriesExit);
series->setName(seriesEnter->name());
series->setOpacity(0.50);
series->setPen(Qt::NoPen);
series->setPointLabelsFormat(seriesEnter->name().split("-").at(0));
areaSeriesMap.insert(series->name(), series);
}
}
}
}
Edit 3:
So, QLineSeries contains QPointF list. I've the code below:
foreach (QLineSeries* seriesEnter, lineSeriesEntersMap.values())
{
QLineSeries* entersToBeEdited = new QLineSeries(chart);
entersToBeEdited->setName(seriesEnter->name());
entersToBeEdited->points().append(seriesEnter->points());
//...
append doesnt work and returns 0 points. But I can set a name. I also tried appending by looping through items and adding it by
entersToBeEdited->points().push_back(seriesEnter->points().at(i));
and still nothing. I also tried << and += but no luck.
Looking at the class definition of QLineSeries, I don't see any simple way to copy your instance in order to duplicate it.
Thus you will have first to create a new instance :
QLineSeries editedSeries;
and manually copy the content of your original series in it.
editedSeries.append(originalSeries.points());
As you cannot modify the data once it is in the QLineSeries object, I would recommend to subclass QLineSeries or modify the QList<QPointF> obtained via originalSeries.points() before adding it to your new chart.
QLineSeries is not copyable, so you can't do what you want by modifying a copy. You will need to create a new QLineSeries from scratch.

Best way to store data that's constantly being accessed

Until now I've been using structs to store data and then putting these structs inside a QHash.
struct Event {
QString languageCode;
QString message;
//QMap<QString, QString> messages; Ignore it. Won't be using it.
QString prop;
qint64 dateSec;
qint64 dateUsec;
qint64 startDateSec;
qint64 startDateUsec;
};
QHash<QString, Event> eventList;
Event event;
event.languageCode = "en";
event.message = "";
event.prop = "www.google.com";
event.dateSec = 1429554399;
event.dateUsec = 0;
event.startDateSec = 0;
event.startDateUsec = 0;
eventList.insert("ab1443c323956", event);
// Test...
qDebug() << eventList.value("ab1443c323956").prop;
Is this the best approach to store data that's constantly being accessed and sometimes being modified? This is small but there won't be only one key in the Hash.
From code you posted:
eventList.insert("ab1443c323956", event);
// Test...
qDebug() << eventList.value("ab1443c323956").prop;
I assume, that you either hard-code identifiers of events (which are of type QString now) or save them somewhere. In this case, I would use QVector:
extern int ab1443c323956_index;
QVector<Event> eventList;
Event event;
//fill data
eventList.push_back(event);
ab1443c323956_index = eventList.size() - 1;
// Test...
qDebug() << eventList[ab1443c323956_index].prop;
Now, since you always append events (QVector::push_back() is equivalent to QVector::append()), you have following complexities:
insertion: constant amortized ( Amort. O(1) )
index lookup: constant ( O(1) )
For data, that is constantly accessed - nothing can beat this in terms of speed.
Of course, this solution is valid, if you can use integers as your "keys", but when it comes to operations like this one, even QHash can introduce significant overhead.

Extract menu action data in receiving function or slot

In my menu, I am setting data to the menu actions. How can I extract that data in my slot? Or even better, instead of connecting a slot, can I also connect a member function that is able to extract the action data (like in the 1st connect)? The action data is meant to identify each action. As a sidenode, I am not sure if I can use several menu action entries on only one openNote-action.
void Traymenu::createMainContextMenu() {
QAction *actionNewNote = m_mainContextMenu.addAction("Neue Notiz");
actionNewNote->setIcon(QIcon("C:\\new.ico"));
actionNewNote->setIconVisibleInMenu(true);
QObject::connect(actionNewNote,&QAction::triggered,this,&Traymenu::newNote);
QString menuEntryName;
QAction *openNote;
QVariant noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
connect(openNote,SIGNAL(triggered()),this,SLOT(s_showNote()));
noteID.setValue(m_noteList[i].data()->getID());
openNote->setData(noteID);
}
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
And the slot:
void Traymenu::s_showNote() {
QObject* obj = sender();
//int noteID = data.toInt();
//Search all notes in noteList for that ID and show it
}
Using QObject::sender()
You can use QObject::sender() to get the signal's sender, followed by qobject_cast to cast the sender pointer to the right type.
void Traymenu::s_showNote()
{
QAction* act = qobject_cast<QAction *>(sender());
if (act != 0)
{
QVariant data = act->data();
int noteID = data.toInt();
showNote(noteID); // isolate showNote logic from "get my ID" stuff
}
}
void Traymenu::showNote(int noteID)
{
// Do the real work here, now that you have the ID ...
}
As the Qt documentation warns, "This function violates the object-oriented principle of modularity." It's still a fairly safe and standard practice, though — just one with some shortcomings. In particular, note that you're committing to having a s_showNote method that only works when it's accessed as a slot (otherwise sender is 0).
Using QSignalMapper
Alternatively, you can use the QSignalMapper class to return a pointer to teh item or to associate a unique identifier (int or QString) with each item.
Something like this:
void Traymenu::createMainContextMenu()
{
signalMapper = new QSignalMapper(this); // (or initialize elsewhere)
// ... (create your newNote here same as before) ...
QString menuEntryName;
QAction *openNote;
int noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
noteID = m_noteList[i].data()->getID();
openNote->setData(QVariant(noteID)); // (if you still need data in the QActions)
signalMapper->setMapping(openNote, noteID);
}
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(showNote(int)));
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
void Traymenu::showNote(int noteID) {
// Now you have the ID ...
}
This pattern has the benefit of isolating all the ugly "Wait, how do I get my identifier?" stuff in one spot, instead of having both the initialization code and the slot function having code for associating actions and IDs.
I would write it like:
void Traymenu::s_showNote() {
QObject* obj = sender();
QAction *action = qobject_cast<QAction *>(obj);
int id = action->data().toInt();
for (int i = 0; i < m_noteList.count(); i++) {
if (m_noteList[i].data()->getID() == id) {
[..]
}
}
}

Qt QMetaObjects and casting from QWidget to QObject

Im trying to make the code which reads xml files and deserialize various qt controls from this xml, and im doing this using QDomDocument, and i want to get the QLlist from my deserealization method. And im having a bit of troubles, here is some code of the template class (.h) file:
QList<T*> deserialize(QIODevice *input)
{
QList<T*> objects = QList<T*>();
if(_deserializeObject(input, objects))
return objects;
}
bool _deserializeObjects(QIODevice* input, QList<QObject*>& list);
and my .cpp file with deserialize method, here im reading the control tags from file:
bool Serializer::_deserializeObjects(QIODevice* input, QList<QObject *> &objects)
{
QDomDocument doc;
if (!doc.setContent(input))
return false;
QDomElement root= doc.documentElement();
for(int j = 0; j < root.childNodes().length();j++)
{
QObject* object;
qDebug() << root.tagName();
if(root.tagName().contains("QGroupBox")) // <------- Here i need to determine which control i need to process.
{
????
}
qDebug () << object->metaObject()->className();
qDebug() << object->metaObject()->propertyCount();
for(int i = 0; i < object->metaObject()->propertyCount(); i++)
{
object->metaObject()->cast()
QMetaProperty prop = object->metaObject()->property(i);
QString propName = prop.name();
if(propName == "objectName")
continue;
QDomNodeList nodeList = root.elementsByTagName(propName);
if(nodeList.length() < 1)
continue;
QDomNode node = nodeList.at(0);
QVariant value = object->property(propName.toLatin1().data());
QString v = node.toElement().text();
if(propName == "x")
{
x = v.toInt();
}
else if(propName == "y")
{
y = v.toInt();
}
else if(propName == "width")
{
width = v.toInt();
}
else if(propName == "height")
{
height = v.toInt();
}
if(propName == "geometry")
{
continue;
}
object->setProperty(propName.toLatin1().data(), QVariant(v));
}
object->setProperty("geometry",QVariant(QRect(x,y,width,height)));
objects.push_back(object);
}
return true;
}
In this part
if(root.tagName().contains("QGroupBox")) // <------- Here i need to determine which control i need to process.
{
????
}
qDebug () << object->metaObject()->className();
qDebug() << object->metaObject()->propertyCount();
for(int i = 0; i < object->metaObject()->propertyCount(); i++)
{
...
}
I want to actually somehow get the type of the control by name, so the question is, can i cast QGroupBox to QObject saving the QGroupBox properties so QObject metaObject class name would be QGroupBox, so i can pass all this properties? Because i don't want to make the loops for each control type. Also i when i got the result like so:
QList<QObject *> ds = s.deserialize<Object>((QIODevice*)&f);
Can i then just pass all QObjects in a loop and using QMetaObject class name and using qobject_cast cast each object to QPushButton,QLabel etc.?
QGroupBox is a subclass of QObject; therefore every QGroupBox is also a QObject, so you can treat it as one whenever you like. An explicit cast isn't necessary.
Iterating over all the diffent objects-derived-from-QObject in a loop will do what you want, provided that the methods you call on them are virtual methods (which they presumably will be -- in particular, QObject::metaObject() is a virtual method, so your loop will get the appropriate QMetaObject returned even if it is calling them method through a QObject pointer).
(As an aside, the annoying part of the process will probably be the part where you have read the name of the object's type from the XML and now need to instantiate an object of that type. AFAIK there is no good automatic way to do that in C++, so the best you can do is a factory function containing a giant switch statement with a separate case for every type you might want to instantiate)
Alternatively, use a right tool for a right job. Chances are that what you are really building here is some XML thing for defining widget layouts etc. Qt already has a tool for that, the Qt Designer which uses an XML format for the UI definitions and a C++ code generator for actually producing a C++ code during compile time.