introspeqt / introspeqt

Simple introspector for PySide applications
GNU General Public License v3.0
3 stars 0 forks source link

allWidgets() vs. widgetAt(pos()) #3

Open ghost opened 10 years ago

ghost commented 10 years ago

Here is one suggested by Marcus Ottosson,

Instead of getting a list of QWidgets from a given QApplication, it might be a good idea to get the widget under the cursor when the mouse is clicked. By doing that, we don't have to worry about overlapping widgets and/or events not being handled. Currently unhandled QEvents are affecting the output as specified in #1.

In order to implement that, we could use widgetAt(pos) which will also be a step in the right direction if we want to aim for heatmaps as explained in #2.

I'll do a few tests to see if this is a feasible option. Let me know what you think.

ghost commented 10 years ago

I've done a quick test with some promising results (avoiding duplicates when events are unhandled).

Here is what I've done:

Created a custom mousePressEvent function

def mousePressEvent(self, QMouseEvent):
    mouse_button = None

    if QMouseEvent.button() == QtCore.Qt.LeftButton:
        mouse_button = 'left'
    if QMouseEvent.button() == QtCore.Qt.RightButton:
        mouse_button = 'right'
    if QMouseEvent.button() == QtCore.Qt.MiddleButton:
        mouse_button = 'middle'

    cursor = QtGui.QCursor()
    app = QtCore.QCoreApplication.instance()
    widget = app.widgetAt(cursor.pos()).objectName()
    if not widget:
        widget = '<Unknown objectName>'

    print mouse_button + ' mouse button pressed.'
    print 'Coordinates:', cursor.pos().x(), cursor.pos().y()
    print 'Widget Name', widget

Overloading the method for all widgets

def log_usage(app):
    handler = Handler(app)
    for widget in QtGui.QApplication.allWidgets():
        newMousePressEvent = functools.partial(mousePressEvent, widget)
        widget.mousePressEvent = newMousePressEvent

Output

The output that I get when using this looks like this:

left mouse button pressed.
Coordinates: 592 372
Widget Name Btn00

How to avoid breaking existing functionality

The next step will be to see how to avoid breaking functionality in the case that the widgets already overloaded the mousePressEvent method. What I have in mind is this:

I'm not sure if this will work. I'll keep you updated.

ghost commented 10 years ago

For the record, those are not part of the repository yet... It's a local test that I'm doing. :-)

mottosso commented 10 years ago

This is close to what I had in mind, but there is a slight difference.

This, and what you've done previously, is a push method. You instruct each widget to push the given information to you, so that you can make a note of it. What I meant was for you to instead try a pull method. I.e. instead of telling each widget to let you know when an event has happened to them, you ask for it.

In the case of generating a heat map, you could do something like this.

# Pulling events from a Qt application
from PySide import QtCore, QtGui

timer = QtCore.QTimer()
timer.setInterval(100) # 10/sec

heatmap = {}
def record_hovering():
    cursor = QtGui.QCursor()
    app = QtCore.QCoreApplication.instance()
    widget = app.widgetAt(cursor.pos()).objectName()
    if not widget:
        widget = '<Unknown objectName>'

    # Is this the first time we hover this widget?
    if not widget in heatmap:
        heatmap[widget] = 0

    # Keep track of how long we're hovering this widget.
    heatmap[widget] += 1

timer.timeout.connect(record_hovering)
timer.start()

Let it run for a few seconds, then collect the results. E.g., here's what it looks like from Maya.

for key in sorted(heatmap, key=heatmap.__getitem__, reverse=True):
    print "%s = %s" % (key, heatmap[key])
<Unknown objectName> = 150
mayaLayoutInternalWidget = 37
qt_scrollarea_viewport = 33
MayaWindow = 22
m_menubar_OptionBoxWindow = 21
check1 = 14
field = 13
OptionBoxWindow = 11
flowLayout6 = 10
menuMode = 9
modelPanel4 = 8
slider = 8
toolBar4 = 5
radio2 = 4

Looks like I spent most of my time hovering unnamed widget(s).

Not particularly helpful of course, a heatmap will probably be better off graphically visualised, but the point is that here, we're pulling information from widgets, as opposed to pushing information from them, and in doing so we leave widgets un-altered and thus aren't affecting performance, are guaranteed a widget with the highest z-order and can also capture widgets that wouldn't normally allow you to override their mousePressedEvent - which may possibly be the majority of Maya's or any other native GUI widgets (as they're coming from C++).

ghost commented 10 years ago

Excellent write-up Marcus!

we're pulling information from widgets, as opposed to pushing information from them, and in doing so we leave widgets un-altered and thus aren't affecting performance, are guaranteed a widget with the highest z-order and can also capture widgets that wouldn't normally allow you to override their mousePressedEvent - which may possibly be the majority of Maya's or any other native GUI widgets (as they're coming from C++)

I see what you mean and it makes a lot of sense. I will keep experimenting with the idea and keep you updated about my progress.