0

I want to inspect something application wide. Specifically I'd like to inspect every widget that is added to the application.

Similar thing can be done in JavaScript/HTML, where you can add DOM mutation listener which fires on DOM changes.

Can I listen on added widgets upon QApplication? Specifically, capture widgets added to QApplication object as children OR widgets added to any of the top level widgets or their children, subchildren, etc...

If not possible, what is best workaround available?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • Do you really need to inspect *every single* widget in the application? – bnaecker Aug 25 '16 at 23:17
  • @bnaecker Yes. I need to be able to "wait for widget" - eg. fire an event when widget that fits some abstract description appears. Only other way would be to loop whole qapplication on an interval and find the widget. That's nasty. I also cache some widget selectors and I'd like to update the cache when widgets are added/removed. – Tomáš Zato Aug 25 '16 at 23:25
  • What is that "abstract description"? Are these widgets of a particular class? E.g., you need to inspect all `QLabel` objects, or any of your own custom classes? Or literally any widget in the whole application? – bnaecker Aug 25 '16 at 23:29
  • @bnaecker Abstract description is a predicate that accepts `QWidget*` and returns true or false. So yes, as I said in my question and in the last reply to your comment, I want to inspect **any** widget added or removed to/from the qapplication. But this question is about additions in particular. – Tomáš Zato Aug 25 '16 at 23:34
  • I wasn't asking for a definition of "abstract description", but what your actual predicate function is. That may help reduce the problem size, and possibly suggest a better solution. – bnaecker Aug 26 '16 at 00:22
  • @bnaecker What I gave you is what I have. I think you just still don't believe I really need it. Trust me. I'm making a test framework and I need to allow the remote side to "wait" (eg. block the test script) until something happens. For example, after clicking button, dialog should happen, but it might not appear instantly. Definitions for those events are abstract, just as I said. – Tomáš Zato Aug 26 '16 at 00:34
  • You say that you need to do XYZ, but you don't say **why** you need to do it. So? Why do you need to wait for a widget that fits some predicate? Why don't you yourself control what widgets appear? What is the predicate? What is the use of it? The purpose would make the question much more complete. – Kuba hasn't forgotten Monica Aug 26 '16 at 18:04
  • @KubaOber Actually not, I think it's generally wrong to press people to answer why are they doing this or that or to press them to do something different. It produces a lot of questions that have answers which only solve OP's problem and not the problem in general. Besides, I said exactly what I'm doing in the comment just above yours. – Tomáš Zato Aug 27 '16 at 11:44
  • **These details matter**. You are arguing against yourself. You don't care at all that a widget is instantiated. Such widgets are of no use since the user doesn't see them! Try it: instantiate a toplevel `QPushBuitton`. Useless, right? You care **that it is shown**. That's essential, and that makes it a trivial task. Think of what happens when a widget is shown. And you've answered your own question now, I hope. No need for polling anything, inspecting any trees, or anything of the sort. **Please edit the question with what you really want: notification that a widget is visible.** – Kuba hasn't forgotten Monica Aug 27 '16 at 23:26
  • I could understand all this effort if this question had no answers and you were trying to make it answerable, but that's not the case. I never said anything about widgets being visible or not, I said I want to know when they become child of QApplication's main widget. That doesn't mean they're visible, in most cases, they're not. To sum up, I think I'm missing something. I'm not sure what you want me to do. If you have time to explain please invite me to chat or post the correct solution to the problem as an answer... – Tomáš Zato Aug 28 '16 at 08:56
  • [MutationSummary](https://github.com/rafaelw/mutation-summary) Could help you. Give it a target and it will give you a summary of everything that's happened (within reason). I.e. A summary of DOM Events/Mutation Events. Hope it helps. There's an example on the github of it's use. – hipkiss Sep 02 '16 at 18:31

2 Answers2

1

The most stable solution is likely to be to walk the object hierarchy every so often, starting with the QApplication as the root, and check each object with your predicate. This is going to be inefficient, which is why I asked for more information about what objects you actually want to query. On the other hand, for a test framework, you may not care so much about efficiency.

It might be possible to do something else, however. You can install event-filtering objects on any QObject, which define how they respond when they receive an event from Qt's event system. You could install an event filter on the root QApplication object (and recursively on any children created) that would check if the event is a QChildEvent and if added() returns true for it. If that's true, meaning a new child was added, you could then install this event filter onto that child as well. All other events would be passed on untouched. You can read more about installing event filters here. Essentially, a filter is a QObject subclass that defines the eventFilter() function, which returns true for events that should be filtered (stopped) and false otherwise. When this returns true, run your predicate on the newly-created object and install the same event filter on it.

One issue to be aware of with this is that the eventFilter() function only receives QObjects. You can learn if the object is a QWidget by calling isWidgetType(), but you don't know more than that. So as long as your predicate can make do with only methods and data defined for these general base classes, that should be OK.

bnaecker
  • 6,152
  • 1
  • 20
  • 33
  • Event filter is a good idea, but wouldn't that override existing event filters within the application? – Tomáš Zato Aug 26 '16 at 01:24
  • You would catch the event you're interested in, do your checks, install the event filter on new children, and then just return false. This means you intercept the event to check its type, but don't actually *filter* out the event. It will be processed as usual by the object. – bnaecker Aug 26 '16 at 01:40
  • I implemented it, it's perfect! Definitely faster and smoother than polling. – Tomáš Zato Aug 26 '16 at 01:45
  • I just found a flaw. According to the docs *in the QEvent::ChildAdded case the child is not yet fully constructed*. Do you have any idea how to wait for when the widget is constructed? – Tomáš Zato Aug 26 '16 at 02:07
  • I think this means that the `QObject` or `QWidget` base class constructor has been called at the time this event is sent, but the full constructor for the derived class hasn't yet been run. So it seems you should be fine treating it as one of those base classes (and calling methods or inspecting data), but cannot treat it as any more derived class. – bnaecker Aug 26 '16 at 16:51
  • I solved it by forwarding the event somewhere else. After forwarding, the object is already constructed. – Tomáš Zato Aug 26 '16 at 17:48
0

As per @bnaecker's answer, here is some code:

AddChildEventFilter.h

#include <QObject>
class QEvent;
class AddChildEventFilter: public QObject {
        Q_OBJECT
    public:
        AddChildEventFilter(QObject* parent=0);
    protected:
        bool eventFilter(QObject *obj, QEvent *event) override;
};

AddChildEventFilter.cpp

#include "AddChildEventFilter.h"
#include <QEvent>
#include <QDebug>
#include <QWidget>
#include <QChildEvent>
AddChildEventFilter::AddChildEventFilter(QObject* parent):QObject(parent) {}

bool AddChildEventFilter::eventFilter(QObject* obj, QEvent* event) {
    if(QWidget* widget = dynamic_cast<QWidget*>(obj)) {
        if(QChildEvent* chevent = dynamic_cast<QChildEvent*>(event)) {
            if(chevent->added()) {
                QObject* child = chevent->child();
                qDebug()<<"Child added: "<<child->metaObject()->className()<<"to"<<widget->metaObject()->className();
                child->installEventFilter(new AddChildEventFilter(child));
            }
        }
    }
    return false;
}

Usage:

#include "AddChildEventFilter.h"
#include <QWidget>
#include <QApplication>
void PrintAllEvents()
{
    for(QWidget* widget: QApplication::allWidgets()) {
        widget->installEventFilter(new AddChildEventFilter(widget));
    }
}
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • This is great, but note that you don't need to do the `dynamic_cast`s in `AddChildEventFilter.cpp`. Each `QEvent` encodes its type, accessible through the `type()` method. So you can just compare with `if (chevent->type() == QEvent::ChildEvent) { ... }`. I'd also do `if (obj->isWidgetType())` instead of the first `dynamic_cast`, too. – bnaecker Aug 26 '16 at 01:54
  • @bnaecker I'm aware of these, but I need to actually access the subclass' (such as `child()`) methods, so I need to cast it. – Tomáš Zato Aug 26 '16 at 01:57
  • Wouldn't be more efficient in your case to check for cases as bnaecker suggests, and then use 'static_cast' instead of 'dynamic_cast'? – Vaidotas Strazdas Jan 11 '17 at 15:18
  • @VaidotasStrazdas Yeah, I was kinda lazy to go through docs and first make IF check for the type of event as per the enumerator used for Qt events. Since I noticed no performance hit with this, I didn't optimize it. – Tomáš Zato Jan 11 '17 at 15:49