How to combine multiple signals into a composite slot in PyQt? - concurrency

The problem described in the top diagram: the reader reads an image a and two objects P1 and P2 have to process this image data. The processing in P2 involves both the original image a and the output of P1: b. Both P1 and P2 extend QWidget and the methods are slots: P1.process(a) and P2.process(a, b).
In an ideal world, I would want to have a way to connect two signals, a from Reader and b from P1 connect to the two parameters of the slot (I am thinking of something similar to LabView). But it looks like Qt signals and slot connections do not allow that.
As an workaround I created a dummy class as an intermediary where input to slot_a just stores the input value in dummy._a attribute and input to slot_b sends out a signal with two args, (dummy._a, b) and this is received by P2.slot_ab(a, b) which finished the processing in P2.
But apparently there is no easy way to ensure that Dummy.slot_a will be called before Dummy.slot_b, so the value of a may not get initialized, or be stale.
What will be a good and simple way to pass a and the corresponding value of b to P2 and trigger its processing?
I am using pyqt5 for this.

Related

Too deep hierarchy of signals-slots in Qt

I am writing GUI applicaton in Qt. Currently I waste too much time on routine. It seems something wrong with my architecture. Please tell me how can I change my approach to improve code.
What I am doing:
My program can be decomposed as hierarchy of classes (not inheritance but composition). In example:
class D { /* something */ };
class C { /* something */ };
class B { C c1; C c2; D d1; };
class A { D d1; C c1; };
So, actually it is a tree hierarchy where leaf nodes (class C, class D) are "models" in Qt terminology which hold data. At the top of hierarchy MainWindow (class A) is placed, which holds first level of "views" (class D, i.e. subwidget) and leaf node with data (class C, i.e. text field).
To pass information down from main window to data I use function calls from mainwindow (pushbuttons) to leaf nodes. After that data changes and tells parents about it with signal slot mechanism. Parents continue to pass message up with signaling.
I am really bored by establishing all these communication. Now I have up to 5 levels, however it is not that much in usual code when using composition. Please tell me how can I change my approach. Due to complexity of these connections, development of the code extremely slow, almost stopped.
It is hard to give a concrete example, because there are a lot of code, but the idea of problem which is very difficult to solve is following:
There are two QTreeView, which differently shows data from own model inherited from QAbstractItemModel (tree inside model is not that tree in previous discussion, this tree is only single level of hierarchy). I want to select objects in one QTreeView and change by that selection in second QTreeView. There are total 2 QTreeView, 2 different QAbstractItemModel instances, 2 trees of own objects (for each QAbstractItemModel), and single data.
Sounds like you might have become a victim of going through too many examples. Examples tend to cram functionality where it doesn't belong, creating the possibility to develop bad programming habits.
In actual production things need to be more compartmentalized. The main window should not be the container of the "application logic", all it needs to concern itself with is holding together the main widgets.
But that doesn't seem to be your case, judging by the necessity to delegate things "from mainwindow (pushbuttons) to leaf nodes" as you put it.
On a grander scale, it is not advisable to mix application logic with UI at all, much less cram it all in the main window. The application logic should be its own layer, designed so that it can work without any GUI whatsoever, and then the GUI is another layer that simply hooks up to the logic core.
The logic core should not be monolith either, it should be made of individual components focusing on their particular task.
Your use case doesn't really require any crazy amount of connections, just some basic handlers for the UI elements, which should target the logic core API rather than GUI elements as you appear to be doing now.
Your clarification unfortunately makes absolutely no sense to me, it is still completely unclear what you exactly you want to do.
Let's assume your situation is something like this:
Tree 1 shows a folder structure.
Tree 2 shows the file content of the folder, selected in tree 1.
Data is an editor for the file, assuming a text file, selected in tree 2.
So, in pseudocode, presuming that app is your application core logic object:
Clicking an item in tree 1 says app.setFolder(tree1.selectedItem())
Clicking an item in tree 2 says app.setFile(tree2.selectedItem())
Clicking the editor "save" button says app.save(editorUI.dataField.text())
logic layer gui layer
app mainWindow
folder <-----------select----------- tree1
file <-----------select----------- tree2
save(newData) { editor
if (file) file.rewrite(newData) textField
} saveBtn: app.save(textField.text())
Since there is only a single data source, you could do the following:
Create a general model for that data source. The model should represent the data source generally, without consideration of what the views need.
Create two proxy viewmodels that adapt the general model to the needs of the views.
Couple the selection models of the views that display the viewmodels.
Given the selection models on top of the two proxy models that map to the same source, we can propagate the selection change between them. We leverage the selection mapping provided by the proxy. The QAbstractProxyModel has a functional implementation of mapSelectionxxxx.
void applySel(const QItemSelectionModel *src, const QItemSelection &sel,
const QItemSelection &desel, const QItemSelectionModel *dst) {
// Disallow reentrancy on the selection models
static QHash<QObject*> busySelectionModels;
if (busySelectionModels.contains(src) || busySelectionModels.contains(dst))
return;
busySelectionModels.insert(src);
busySelectionModels.insert(dst);
// The models must be proxies
auto *srcModel = qobject_cast<QAbstractProxyItemModel*>(src->model());
auto *dstModel = qobject_cast<QAbstractProxyItemModel*>(dst->model());
Q_ASSERT(srcModel && dstModel);
// The proxies must refer to the same source model
auto *srcSourceModel = srcModel->sourceModel();
auto *dstSourceModel = dstModel->sourceModel();
Q_ASSERT(srcSourceModel && (srcSourceModel == dstSourceModel));
// Convey the selection
auto const srcSel = srcModel->mapSelectionToSource(sel);
auto const srcDesel = srcModel->mapSelectionToSource(desel);
auto const dstSel = dstModel->mapSelectionFromSource(srcSel);
auto const dstDesel = dstModel->mapSelectionFromSource(srcDesel);
// we would re-enter in the select calls
dst->select(dstSel, QItemSelectionModel::Select);
dst->select(dstDesel, QItemSelectionModel::Deselect);
// Allow re-entrancy
busySelectionModels.remove(src);
busySelectionModels.remove(dst);
}
The above could be easily adapted for a list of destination item selection models, in case you had more than two views.
We can use this translation to couple the selection models of the views:
void coupleSelections(QAbstractItemView *view1, QAbstractItemView *view2) {
auto *sel1 = view1->selectionModel();
auto *sel2 = view2->selectionModel();
Q_ASSERT(sel1 && sel2);
connect(sel1, &QItemSelectionModel::selectionChanged,
[=](const QItemSelection &sel, const QItemSelection &desel){
applySel(sel1, sel, desel, sel2);
});
connect(sel2, &QItemSelectionModel::selectionChanged,
[=](const QItemSelection &sel, const QItemSelection &desel){
applySel(sel2, sel, desel, sel1);
});
}
The above is untested and written from memory, but hopefully will work without much ado.

connecting a basic signal mousepress

I am using QCustomPlot where I am trying to write a code that will rescale my axes once the user press the mouse and drags. I did:
connect(ui->plot, SIGNAL(mousePress(QMouseEvent *event)), this, SLOT(mousedrag(QMouseEvent*)));
and I keep getting:
QObject::connect: No such signal QCustomPlot::mousePress(QMouseEvent
*event)
But mouseWheel(QWheelEvent*) and both mouseWheel and mousePress have signals declared in the QCustomPlot library.
Where am I going wrong? Also if someone has a better signal to trigger my function mousedrag(QMouseEvent*) which rescales the the y2 axis according to y1 axis I am open for suggestions.
The signal signature passed to connect is invalid. The parameter names are not a part of the signature. You should also remove any whitespace so that connect doesn't have to normalize the signatures. A normalized signature has no unnecessary whitespace and outermost const and reference must be removed, e.g. SIGNAL(textChanged(QString)), not SIGNAL(textChanged(const QString &)).
remove
vvvvv
connect(ui->plot, SIGNAL(mousePress(QMouseEvent *event)), this,
SLOT(mousedrag(QMouseEvent*)));
Do the below instead:
// Qt 5
connect(ui->plot, &QCustomPlot::mousePress, this, &MyClass::mousedrag);
// Qt 4
connect(ui->plot, SIGNAL(mousePress(QMouseEvent*)), SLOT(mousedrag(QMouseEvent*));
Sidebar
TL;DR: This sort of API design is essentially a bug.
Events and signal/slot mechanism are different paradigms that the QCustomPlot's design mangles together. The slots connected to these signals can be used in very specific and limited ways only. You have to use them exactly as if they were overloads in a derived class. This means:
Each signal must have either 0 or 1 slots connected to it.
The connections must be direct or automatic to an object in the same thread.
You cannot use queued connections: by the time the control returns to the event loop, the event has been destroyed and the slot/functor will be using a dangling pointer.
When using the "old" signals/slot connection syntax, i.e. the one using the SIGNAL and SLOT macros in the connect() statement, you shall not provide the names of the parameters, only their types.
In other words:
SIGNAL(mousePress(QMouseEvent *event)) // WRONG, parameter name in there!
SIGNAL(mousePress(QMouseEvent *)) // GOOD
SIGNAL(mousePress(QMouseEvent*)) // BETTER: already normalized
So simply change your statement to
connect( ui->plot, SIGNAL(mousePress(QMouseEvent*)),
this, SLOT(mousedrag(QMouseEvent*)) );

What is the gtkmm equivalent to g_signal_handlers_block_by_func()?

I need to block signals that I emit (indirectly) myself.
In C one could use g_signal_handlers_block_by_func() and the sister function unblock.
What can I use in C++ gtkmm?
I have a gtkmm dlna player, that emits the changed signal to a Gtk::HScale Widet each second, because it gets (from the outside) a signal that the song that just plays. And then I seek to the position that just where current, which set the song back a split second...
I would like to block my on changes from the seek, because I saw that a C program did that with g_signal_handlers_block_by_func.
since ptomato asked:
I never realized, that the connect method has a valuable return value:
so if you connect the signals like this:
mywidget_connection = mywidget.signal_value_changed().connect(sigc::mem_fun(*this, &MyClass::on_value_changed ));
In my situation I have 2 ways to change the value:
1.) someone pulls a slider: should update the value and seek
2.) the timer comes along and tells the new position: should update the value but not seek.
then you can block/unblock like this:
mywidget_connection.block();
mywidget.set_value(new_value);
mywidget_connection.unblock();
and this does not emit the changed signal.

C++/Qt - multiple inheritance with QGraphicsItem doesn't work as expected

I recently met a strange problem of my little program and it would be great if you help me to get the reason of this behavior.
My task is quiet simple - I want to use Qt Graphics Framework to show some objects and I want Box2D to calculate bodies position. So my class hierarchy looks like the following:
I have 1 base abstract class B2DObject. It contains some Box2D staff + some common parameters for its successors (names, some flags, etc.). It also has couple of pure virtual functions that will be reimplemented in successor classes.
Then I implement some classes that represent basic shapes: circles, rectangles, polygons, etc. I am doing it in the following way:
class ExtendedPolygon : public B2DObject, public QGraphicsPolygonItem { ... };
class ExtendedCircle : public B2DObject, public QGraphicsEllipseItem { ... };
etc.
(for those who are not familiar with Qt, QGraphics***Item is inherited from QGraphicsItem).
Also I inherited QGraphicsScene and reimplemented its mousePressEvent. In this function I request an object placed at some point on the screen using QGraphicsScene::itemAt function (which returns QGraphicsItem*), convert it to B2DObject* and try to get some internal field from this object:
void TestScene::mousePressEvent (QGraphicsSceneMouseEvent *event)
{
QGraphicsItem* item = itemAt (event->scenePos ());
if (item)
{
B2DObject* obj = reinterpret_cast < B2DObject* > (item);
QString objName = obj->Name(); // just for example,
// getting other internal fields has
// the same effect (described below)
// use retrieved field somehow (e.g. print in the screen)
}
// give the event to the ancestor
}
Unfortunately, dynamic_cast will not work here because these classes are completely unrelated.
Then I create necessary objects and add it to my scene:
ExtendedPolygon* polygon = new ExtendedPolygon (parameters);
polygon->setName (QString ("Object 1"));
...
TestScene scene;
scene.addItem (polygon);
(for those who are not familiar with Qt, here is the prototype of the last function:
void QGraphicsScene::addItem(QGraphicsItem *item);
I guess it just stores all items in internal index storage and calls QGraphicsItem::paint (...) when item needs to be repainted. I suppose QGraphicsScene doesn't make any significant changes to this item).
So my problems start when I run the program and click on an item on the screen. TestScene::mousePressEvent is called (see a piece of code above).
Mouse click position is retrieved, item is found. Casting works fine: in the debugger window (I'm using Qt Creator) I see that obj points to ExtendedPolygon (address is the same as when I add the item to the scene and in the debugger window I can see all the fields). But when I get some field, I receive garbage in any case (and it does not matter, what I'm trying to get - a QString or a pointer to some other structure).
So first of all, I would like to get any advice about my multiple inheritance. In 95% of cases I try to avoid it, but here it is very effective in the programming point of view. So I would appreciate it if you provide me with your point of view about the architecture of the classes hierarchy - does it even suppose to work as I expect it?
If on this level everything is quite fine, then it would be great if someone gets any idea why doesn't it work.
I have some ideas about workaround, but I really would like to solve this problem (just in order not to repeat the same error anymore).
Looks like I've found the root cause of my problem. It was just lack of knowledge regarding how multiple inheritance really works on data layer.
Let's assume that we have 2 basic classes, A and B. Each of them provides some internal data fields and some interfaces.
Then we create a derived class AABB, inheriting both A and B:
class AABB : public A, public B {...}
AABB could add some additional data fields and reimplement some of the interfaces, but it is not necessary.
Let's create and object of class AABB:
AABB* obj = new AABB ();
For example, obj points at address 0x8416e0. At this address starts data from ancestor class A. Data from ancestor class B starts with some offset (it should bw equal to sizeof (A)), for example, at 0x841700.
If we have some function f (B* b), and if we pass a pointer at AABB object to that function (like this: f (obj), obj is created above), actually not obj start address is passed, but rather a pointer at a start of B data section of AABB object.
Thus this misunderstanding of multiple inheritance inner works has led me to the problem I've got.
I guess Qobjects and multiple inheritance has been already treated. As an example: QObject Multiple Inheritance

qt signals/slots in a plugin

I have an app with such structure: all the datatypes (class INode) are stored in plugins (DLLs). Some of the datatypes can be drawn (if they're IDrawable).
To load an object of, e.g. class PointCloudNode: public INode I have a special input plugin (DLL) which is called class PointCloudParser: public IIOPlugin and IIOPlugin is a thread with some specific functionality: class IIOPlugin: public QThread.
All the objects are created by NodeFactory class which is a singleton stored in separate DLL.
And here's the problem:
void PointCloudNode::update()
{
QObject::connect (this,SIGNAL(tmptmp()),this,SLOT(drawObject()));
emit tmptmp();
}
If I do this from any thread (main thread or the Input Plugin thread)
NodeFactory* fab = NodeFactory::getInstance();
boost::shared_ptr<INode> pc(fab->createNode("pointCloud","myPC"));
boost::shared_ptr<IDrawable> dr = boost::dynamic_pointer_cast<IDrawable>(pc);
dr->update();
The update launches, the tmptmp() signal is emitted, and the slot (drawObject()) executes correctly.
BUT
if do just the same, but create the object in my Input Plugin, pass over the shared pointer and execute dr->update() in another function, the slot drawObject() is never entered though all the code is executed (including connect, etc.).
To be more precise, here's the Input Plugin:
void PointCloudParserPlugin::doLoad(const QString& inputName, boost::shared_ptr<INode> container)
{
NodeFactory* factory = NodeFactory::getInstance();
boost::shared_ptr<INode> node = factory->createNode("pointCloud", inputName);
// here goes the loading itself, nothing special...
container->addChild(node); //that's the container where I keep all the objects
//boost::dynamic_pointer_cast<IDrawable>(container->getChild(inputName))->update();
//If I uncomment this line, it all works: the slot is launched.
emit loadingFinished(inputName); // it executes the following function
}
The last emit is connected to this:
void GeomBox::updateVisualization(const QString& fileName)
{
boost::shared_ptr<INode> node = container_->getChild(fileName);
boost::shared_ptr<IDrawable> nodeDrawable = boost::dynamic_pointer_cast<IDrawable>(node);
nodeDrawable->update(); //this is the problem line: update() executes, connect() works, but the slot never runs :(
}
How come? The node object is the same all the way through, it is valid. Every line in code in launched, QObject::connect doesn't write anything to debug window, the signal tmptmp() is emitted, but the slot drawObject() in one case is never reached? Any ideas?
Upd.: If I do not inherit IIOPlugin from QThread, everything works fine (i.e. load the object in the main thread). I expected the signals/slots to work across the threads...
Since you are sending a signal across to a different thread, you might need to explicitly tell Qt that the connection should be a queued one:
QObject::connect(this, SIGNAL(tmptmp()), this, SLOT(drawObject()), Qt::QueuedConnection );
By default Qt will use Qt::AutoConnection as that last parameter, and it will choose whether to use a direct connection (if the slot is in the same thread as the emitter) or a queued connection (if the slot is in a different thread). But since your thread is in a separate library, maybe Qt isn't making the right assumption here.