QT widgets under mouse

# -*- coding: utf-8 -*-
##  Copyright 2019 Trevor van Hoof and Jan Pijpers.
##  Licensed under the Apache License, Version 2.0
##  Downloaded from https://janpijpers.com or https://gumroad.com/janpijpers
##  See the license file attached or on https://www.janpijpers.com/script-licenses/

'''
Name: widgetUnderMouse
Description:

prints and highlights the widget under your mouse.
note that this is supppaaaaaaa hacky! And it will make any qt ui flash and slow down a lot!
But its helpfull when you want to find out what an object is called. I used this a lot in Maya to hack into the UI and add custom widgets.

If you click with the mouse buttons the overlay will stop.

'''

import sys

## Simple pyside 2 or 1 import check.
try:
    PYSIDE_VERSION = 2
    from PySide2.QtWidgets import *
    from PySide2.QtGui import *#QFont, QIcon, QPixmap, QColor
    from PySide2.QtCore import * 
    from PySide2.QtUiTools import * 
    from pyside2uic import compileUi
except:
    from PySide.QtCore import * 
    from PySide.QtGui import * 



def widgets_at(pos, topOnly = False ):
    """Return ALL widgets at `pos`
        It uses the WA_TransparentForMouseEvents trick to find the underlying widgets.
    Arguments:
        pos (QPoint): Position at which to get widgets
    """

    widgets = []
    ## Ask QT what widget is at this position
    widget_at = QApplication.widgetAt(pos)
    if topOnly:
        return [widget_at]
        
    while widget_at:
        widgets.append(widget_at)
        ## Make widget invisible, so the next time we call the widgetAt function
        ## QT will return the underlying widget.
        widget_at.setAttribute(Qt.WA_TransparentForMouseEvents)
        widget_at = QApplication.widgetAt(pos)

    # Restore attributes else nothing will respond to mouse clicks anymore.
    for widget in widgets:
        widget.setAttribute(Qt.WA_TransparentForMouseEvents, False)

    return widgets
    
class Overlay(QWidget):
    def __init__(self, parent=None):
        '''
            This is an overlay that sits across the entire UI.
            This way its easier to track the mouse position and interact with the widgets below it.
        :param parent:
        '''
        super(Overlay, self).__init__(parent)
        self.setAttribute(Qt.WA_StyledBackground)
        self.setStyleSheet("QWidget { background-color: rgba(0, 255, 0, 0) }")
        self.setMouseTracking(True)
        self._widgetsUnderMouse = set()

    def mouseMoveEvent(self, event):
        '''
            For every 'pixel' we move our mouse this function is called.
        :param event:
        :return:
        '''
        ## query the current position
        pos = QCursor.pos()

        ## Find the widgets below the cursor.
        currentWidgets = set( [ widgets_at(pos)[1] ] )

        ## If we have found new widgets.
        if currentWidgets != self._widgetsUnderMouse:
            ## Remove the old outline of the widgets we had before
            self._removeOutline(self._widgetsUnderMouse)
            ## Add a new outline to our new widgets
            self._addOutline(currentWidgets)

            ## Print all widgets we have under our mouse now.
            for w in currentWidgets:
                n = w.objectName()
                print "Name: ",n, "Widget: ", w
            self._widgetsUnderMouse = currentWidgets
        ## Let qt do the rest of its magic.
        return super(Overlay, self).mouseMoveEvent(event)
        
    def mousePressEvent( self, event ):
        '''
            If we click with the left mouse button the overlay stops.
        :param event:
        :return:
        '''
        self.deleteLater()
        return super(Overlay, self).mousePressEvent(event)
        
        
    def _addOutline( self, wList ):
        for w in wList:
            n = w.objectName()
            ## SUUUPER HACK TRICK
            ## We force an object name on the object
            w.setObjectName("AAAAAAA")
            ## Make the object have a red outline with a stylesheet
            w.setStyleSheet('QWidget#AAAAAAA {border: 4px solid red;outline-offset: -2px;}')
            ## Restore the object name
            w.setObjectName(n)
            

    def _removeOutline( self, wList):
        '''
            Not the best idea because we remove all style info,
            actually we should store the style sheet info before setting the outline buuuuuttt you get the idea :D
        :param wList:
        :return:
        '''
        for w in wList:
            w.setStyleSheet("")
    
    def _clearAll(self):
        self._removeOutline(self._widgetsUnderMouse)
    
    def __del__(self):
        self._clearAll()
        self._widgetsUnderMouse = set()
    
def get_maya_window():
    for widget in QApplication.allWidgets():
        try:
            if widget.objectName() == "MayaWindow":
                return widget
        except:
            pass
    return None 
    
    
window = get_maya_window()
app = None 
if not window:
    '''
        If we are not in Maya, we just make an example window 
    '''
    app = QApplication(sys.argv)
    
    window = QWidget()
    window.setObjectName("Window")
    window.setFixedSize(200, 100)

    button = QPushButton("Button 1", window)
    button.setObjectName("Button 1")
    button.move(10, 10)
    button = QPushButton("Button 2", window)
    button.setObjectName("Button 2")
    button.move(50, 15)
    
overlay = Overlay(window)
overlay.setObjectName("Overlay")
overlay.setFixedSize(window.size())
overlay._clearAll()
overlay.show()

if app:
    window.show()
    app.exec_()

Leave a Reply

Your email address will not be published. Required fields are marked *