views:

93

answers:

2

Fist, apologies for the length of the question.

I am trying to propagate custom Qt event from child widgets to a top parent widget in order to trigger some action based on event type instead of linking signals.

Qt docs suggests that every event posted with postEvent() that have accept() and ignore() methods can be propagated (meaning each QEvent subclass).

I have tried to override customEvents method instead of events but to no avail.

Python

I've tried this in Python using PyQt4 (Qt version is 4.6).

from PyQt4.QtGui import *
from PyQt4.QtCore import *

class Foo(QWidget):
    def doEvent(self):
        QApplication.postEvent(self, QEvent(12345))

    def event(self, event):
        event.ignore()
        return False

class Bar(QWidget):
    def __init__(self, *args, **kwargs):
        super(Bar, self).__init__(*args, **kwargs)
        self.foo = Foo(self)
        layout = QHBoxLayout()
        layout.addWidget(self.foo)
        self.setLayout(layout)

    def event(self, event):
        if event.type() == 12345:
            self.someEventHandler()
        return True

    def someEventHandler(self):
        print 'Handler in {0}'.format(self.__class__.__name__)

if __name__=='__main__':
    app = QApplication([''])
    bar = Bar()
    bar.show()
    bar.foo.doEvent()
    app.exec_()

In this example Bar.someEventHandler() would only trigger if event was posted with self.parent() as the first argument like so:

def doEvent(self):
    QApplication.postEvent(self.parent(), QEvent(12345))

Which is understandable since the event is passed directly to receiving object.

C++

Similar example in C++:

foobar.h

#ifndef FOOBAR_H
#define FOOBAR_H

#include <QtGui>

class Foo : public QWidget
{
    Q_OBJECT

public:
    Foo(QWidget *parent = 0);
    void doEvent();
    bool event(QEvent *);
};


class Bar : public QWidget
{
    Q_OBJECT

public:
    Bar(QWidget *parent = 0);
    Foo *foo;
    bool event(QEvent *);
};

#endif // FOOBAR_H

foobar.cpp

#include "foobar.h"

Foo::Foo(QWidget *parent)
     : QWidget(parent) {}

void Foo::doEvent() {
    QEvent *event = new QEvent(QEvent::User);
    QApplication::postEvent(this, event);
}

bool Foo::event(QEvent *event)
{
    event->ignore();
    return QWidget::event(event);
}

Bar::Bar(QWidget *parent)
     : QWidget(parent)
 {
    foo = new Foo(this);
}

bool Bar::event(QEvent *event)
{
    if (event->type() == QEvent::User) {
        qDebug() << "Handler triggered";
        return true;
    }
    return QWidget::event(event);
}

main.cpp

#include <QtGui>
#include "foobar.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Bar bar(0);
    bar.show();
    bar.foo->doEvent();
    return app.exec();
}

Same as in python this only works if event is passed directly to an object.

void Foo::doEvent() {
    QEvent *event = new QEvent(QEvent::User);
    QApplication::postEvent(this->parentWidget(), event);
}

Perhaps I missed the point, is it possible that only Key and Mouse events are propagated upwards?

+2  A: 

I've spent some time browsing the Qt source in order to answer your question, and what I finally came up with is this: propagation of events to parent widgets is performed by QApplication::notify, basically a long switch statement with all sorts of events. For example, this is how it's done for QEvent::WhatsThisClicked:

...
case QEvent::WhatsThisClicked:
        {
            QWidget *w = static_cast<QWidget *>(receiver);
            while (w) {
                res = d->notify_helper(w, e);
                if ((res && e->isAccepted()) || w->isWindow())
                    break;
                w = w->parentWidget();
            }
        }
...

Now, the salient point: this is not performed for user defined events (and many other explicitly handled standard Qt events, as well), since the default clause is:

 default:
        res = d->notify_helper(receiver, e);
        break;

And notify_helper doesn't propagate events. So, my answer is: apparently, user defined events are not propagated to parent widgets, you will have to do it yourself (or better: override QApplication::notify (it's a virtual public member) and add event propagation for your event(s)).

I hope that helps.

Greg S
Most helpful, thank you.
rebus
+1  A: 

Re-implementation of QApplication.notify in Python which would propagate custom events.

In Qt notfy_helper makes sure the event filters are called (both QApplications and receivers) but I skipped that as I don't need them and notfy_helper is private member.

from PyQt4.QtCore import QEvent
from PyQt4.QtGui import QApplication

class MyApp(QApplication):
    def notify(self, receiver, event):
        if event.type() > QEvent.User:
            w = receiver
            while(w):
                # Note that this calls `event` method directly thus bypassing
                # calling qApplications and receivers event filters
                res = w.event(event);
                if res and event.isAccepted():
                    return res
                w = w.parent()
        return super(MyApp, self).notify(receiver, event)

And instead of using instance of QApplication we use instance of our subclass.

import sys
if __name__=='__main__':
    app = MyApp(sys.argv)
rebus