3

Assume following structure:

MainWindow
--->MySpecialWidget
    |-->QLineEdit
    |-->QSpinBox
    |-->QPushButton
    ---><Basically any other Widget accepting QMouseEvent>

Clicking on any on the above mentioned will cause its respective feature to be activated and the QMouseEvent to be accepted and discarded.

I would like to react inside my MainWindow on any mouseclick in a generic way. More specifically I would like to hinder basic Qt widgets from accepting QMouseEvent while still reacting to it in their usual way. Not accepting should only occur when they are children of MySpecialWidget. Since the QMouseEvent is accepted two layers deep, my MainWindow can not access it by the means of a direct eventFilter.

My current solution (simplified):

void MySpecialWidget::eventFilter(QObject *o, QEvent *e)
{
    if(e->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent *mE = static_cast<QMouseEvent*>(e);
        
        QMouseEvent newMouseEvent = new QMouseEvent(QMouseEvent::MouseButtonPress, mE->pos(), mE->button(), mE->buttons(), mE->modifiers());
        qApp->notify(this, &newMouseEvent);
        // Check how this handled the copied Mouseevent
        return /*result of check*/;
    }
    return Base::eventFilter(o, e);
}

I was also thinking about attaching my MainWindows eventfilter to everything relevant but that sounds like a nightmare to handle in bigger projects.

The question arises: How can I handle and filter events that are accepted and discarded deep inside some hierarchy without having to subclass every component on the way?

Has anyone ever done something similar or do you know if it is even possible in another way? Is my current solution acceptable?

Edit: I noticed for different QWidgets you get different behavior when a QMouseEvent is "eaten". For example MySpecialWidget can filter QMouseEvents of QLineEdit but can not of QSpinBox since it itself contains a QLineEdit. I find this behavior of this library highly irritating.

Edit2 clarification: Although vahanchoos comment would solve the problem, I'm looking for a solution that can work with MySpecialWidget without touching the QApplication or contained classes.

Any comment on my approach or the feasibility of my request would be highly appriciated!

Baumflaum
  • 749
  • 7
  • 20
  • 1
    You might do it globally, i.e. by overriding `QCoreApplication::notify()` function. This assumes sub classing your `QApplication` – vahancho Sep 03 '21 at 11:10
  • I was also thinking about that, but then my notify override would need to know a `QMouseEvent` has been send to a child of `MySpecialWidget`. This wouldn't be optimal. – Baumflaum Sep 03 '21 at 11:52
  • You can determine whether a widget is a child of your `MySpecialWidget` and filter its events if needed. See `QObject::parent()` function. – vahancho Sep 03 '21 at 12:42
  • Well yes, but then some not associated component would have to know about my `MySpecialWidget` and its wish to receive events of its children. In the worst case it is in a completely different module. Assuming I am writing a library extending Qts widgets, this solution isn't even applicable(?). Ofc I will consider it, but only as a last resort. – Baumflaum Sep 03 '21 at 13:15

2 Answers2

2

I hope I understood the keypoint behind this question.

Basically, if your issue is "How to catch the events for a big number of widgets of various type, including those that are made of sub-widgets (like QSpinbox > QLineEdit) without making it a nightmare to manage", you can make use of QObject::findChildren.

More precisely, add this to your window constructor after the call to ui->setupUi() and that will catch everything under the instance of MySpecialWidget:

auto children = ui->mySpecialWidget->findChildren<QWidget*>(QString(), Qt::FindChildrenRecursively);
for (QWidget* w : children)
    w->installEventFilter(ui->mySpecialWidget);

As you probably already understood, this will explore all its children widget (recursively) and install the instance as a common event filter for all.


Side note:

Something is wrong with your implementation of MySpecialWidget::eventFilter. It is supposed to return a boolean. Overriding this method usually is done like this:

bool MySpecialWidget::eventFilter(QObject *obj, QEvent *event)
{
    if(event->type() == QEvent::MouseButtonRelease)
    {
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
        //do something
        return true;
    }
    else // standard event processing
        return QObject::eventFilter(obj, event);
}
Atmo
  • 2,281
  • 1
  • 2
  • 21
  • About, your side note: Ofc you are right. I'm not sure how I messed that up. I'll edit it. Also I will further clarify and improve my question. – Baumflaum Jan 25 '23 at 09:20
  • Sure, I'll look at your edit. What lost me is that you seem to use event filters comfortably so I am unsure where exactly you are stuck. Was it the maintenance effort for big projects? Was it accessing events from grandchildren, great grandchildren, ... of your main window? Was it something else? – Atmo Jan 25 '23 at 09:24
  • @Baumflaum I have seen your edited question. I do think my solution allows to go as deep as desired into the hierarchy under any widget without subclassing; your starting point is to determine a list of widgets by making them children of a `QWidget` subclass but apart from that .... anyway you could have defined the `chidlren` list as children from regular `QWidget` (`children = ui->someWidget->findChildren...`) or a hardcoded list of widgets (`children = { ... };` and installed an event filter like so: `w->installEventFilter(someIndependentObject);`. Am I missing something in my answer? – Atmo Jan 28 '23 at 12:24
  • I guess not, although your answer gives a very obvious solution, I did not realize this was a possibility. But there is a caveat in your approach: There is no easy way to be specific about widgets I want to include/exclude, am I right? Another small question: Do you think my question and the expectations on behavior are reasonable? (just a small sanity check) – Baumflaum Jan 28 '23 at 15:54
  • Short answer: your question is reasonable **but your expectations are not as of now**. Long answer: I do not see anything in your question suggesting you need a way to include/exclude widgets. Quite the contrary, you describe `` or [I would like to react inside my MainWindow on **any** mouseclick in a generic way]. – Atmo Jan 28 '23 at 23:52
  • People cannot come up with an answer for issues you never expressed nor (considering I have hinted at some ways to do it in my previous comments) can I provide a more suitable way of filtering widgets right now; you need to explicitly ask what is needed here and describe it in your question. – Atmo Jan 28 '23 at 23:54
  • Ofc, you are right on that one. In my defense: the question is one year old and my expectations evolved in the time, without being reflected in the question. Also questions and possible caveats arose from having solutions at hand, so don't get me wrong, I just wanted to discuss your solution, not criticise it as bad. – Baumflaum Jan 29 '23 at 07:38
  • I am not saying because I am taking your earlier comment negatively but because you are asking if I think your expectations are reasonable. If you acknowledge some details are missing but won't provide them, then I think they are not. Anyway, if you cannot come up with any sort of logical rule, it means what you are after is probably a hardcoded list of widgets you want to inspect (`auto children = { ... };`); I don't see how to work around having no logical rule except for that. – Atmo Jan 29 '23 at 08:38
  • ... or a mixed approach, that is you nest loops to first process all the widgets in the hardcoded list and inside that loop, process all their children. – Atmo Jan 29 '23 at 08:40
1

I remember doing something similar to simplify massive toggling mouse events. It basically consisted on an "invisible shield" widget hovering the controls I wanted to block. When visible, this view caught all the events; otherwise they were forwarded, "passing through" it.

You may get it implemented using a QStackedLayout, putting the shield on the top of the stack and setting the stacking mode to StackAll. When catching the events, you can both process them and then do not accept the event to let it go through.

The main drawback here is that it will change how your MySpecialWidget is designed due to the stacked layout, but it will depend on your UI architecture.

cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • Actually this could work. When stacking the "ShieldWidget" on top of whatever Widget I'm trying to intercept, I could react in a idiomatic way to at least mouse events. – Baumflaum Jan 25 '23 at 09:13