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_()

QT settings example

# -*- 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: qtSettingExample
Description:

Simple example on how to use qt settings.

The first time you run it, it will say you dint have a last opened project, and set an example path
the second time it will print the value of the example path.


'''

from PySide.QtCore import QSettings ## pip install PySide


if __name__ =="__main__":

    settings = QSettings("Company Name", "Tool Name")

    ## Check if the value already exsited
    stored = settings.value("lastOpenedProject")
    if stored:
        print "We have a last opened project: ", stored
    else:
        print "We dint have a last opened project. Setting the example path."
        ## Saven die bende
        settings.setValue("lastOpenedProject", "C:/some/example.path")

    print "Press enter to exit"
    raw_input()

Show QT widget UI hierarchy

# -*- 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: showQtWidgetHierarchy
Description:

In maya you sometimes want to hack the UI.
Seeng as the UI is made with QT you can hack into it by finding the right node and or widget name.

To make this process easier you can run this script and it will show you the entire hierarchy and return it as a dict

'''


import json

import sys
from PySide.QtGui import * ## pip install PySide

def widgets_recursive(d, widget = None, doPrint =False ):
    if not widget:
        for widget in QApplication.topLevelWidgets():
            get_widget(widget, d, 0, doPrint)
    else:
        get_widget(widget, d, 0, doPrint)
                
def get_widget(w,d, depth = 0, doPrint=False):
    '''
        Recursively searches through all widgets down the tree and prints if desired.
    :param w: the widget to search from
    :param d: the dictionary to add it to
    :param depth: current depth we are at
    :param doPrint: if we need to print
    :return:
    '''
    n = w.objectName()
    n = n if n else str(w)
    if doPrint: print "\t"*depth, n
    newD = {}
    for widget in w.children():
        get_widget(widget, newD, depth +1 )
    d[n] = newD

def get_widget_from_name(name):
    for widget in QApplication.allWidgets():
        try:
            if name in widget.objectName() :
                return widget
        except:
            pass
    return None 

if __name__ =="__main__":

    ## Remove this block if you are running this in maya or something.
    ## Here we make a simple QWindow with a layout and button.
    app = QApplication(sys.argv)
    wid = QWidget()
    wid.setObjectName("myWindow")
    button = QPushButton()
    button.setObjectName("This is my button, there are many like it but this one is mine.")
    lay = QHBoxLayout()
    lay.addWidget(button)
    wid.setLayout(lay)
    wid.show()


    ## Create a simpe dict to hold all the data in the ned.
    widgetHierarchyDict = {}

    ## if you have no idea where to start. Just leave the topWidget argument to None
    ## But if you are in a QT based aplication like Maya you can also start from a widget you know the name of to speed
    ## up the process
    widgetObjectName = None ## "graphEditor1Window"
    topWidget = get_widget_from_name(widgetObjectName)


    ## Recurse over all widgets and store all the information in the provided dict.
    widgets_recursive(widgetHierarchyDict, topWidget)

    ## Print it with json so its nice and clear.
    print json.dumps(widgetHierarchyDict, sort_keys=True, indent = 2 )

Find signals and slots from any QT widget object.

# -*- 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: findSignalsAndSlotsQt

This script gives you all available signals and slots on a qt widget object.
Normally you can just check the documentation, however if custom signals and slots are used its hard to find them.
We do this by using the meta class from the object.

I used this to find the timechanged event on the maya timeControl widget.

'''
import sys
from PySide.QtGui import * ## pip install PySide
from PySide import QtCore
def get_widget(name):
    '''
        Kind of slow method of finding a widget by object name.
    :param name:
    :return:
    '''
    for widget in QApplication.allWidgets():
        try:
            if name in widget.objectName():
                return widget
        except Exception as e:
            print e
            pass
    return None 


def test( *arg, **kwarg):
    '''
        Simple test function to see what the signal sends out.
    :param arg:
    :param kwarg:
    :return:
    '''

    print "The args are: ", arg
    print "The kwargs are: ", kwarg
    print 

if __name__ == "__main__":

    ## Here we make a simple QLineEdit for argument sake ...
    app = QApplication(sys.argv)
    wid = QLineEdit()
    wid.setObjectName("myLineEdit")
    wid.show()

    ## Find the widget by name.
    ## See the qt ui list hierarchy script to find all widgets in a qt ui.
    widgetObjectName = "myLineEdit"
    widgetObject = get_widget(widgetObjectName)
    if not widgetObject:
        raise Exception("Could not find widget: %s" %widgetObjectName)

    ## Sanity check
    if not wid == widgetObject:
        raise Exception("Should not happen.XD")

    ## Get the meta data from this object
    meta = widgetObject.metaObject()

    ## Itterate over the number of methods available
    for methodNr in xrange(meta.methodCount()):
        method = meta.method(methodNr)
        ## If the method is a signal type
        if method.methodType() == QtCore.QMetaMethod.MethodType.Signal:
            ## Print the info.
            print
            print "This is the signal name", method.signature()
            print "These are the signal arguments: ", method.parameterNames()
            ## If the method is a signal type
        if method.methodType() == QtCore.QMetaMethod.MethodType.Slot:
            ## Print the info.
            print
            print "This is the slot name", method.signature()
            print "These are the slot arguments: ", method.parameterNames()

    '''
    output example:
    ...
    This is the signal name textChanged(QString)
    These are the signal arguments:  [PySide.QtCore.QByteArray('')]
    
    This is the signal name textEdited(QString)
    These are the signal arguments:  [PySide.QtCore.QByteArray('')]
    ...
    
    so now you can do
    
    widgetObject.textChanged.connect(test)
    
    any every time the text changes the 'test' function will be called 
    
    '''
    widgetObject.textChanged.connect(test)

    sys.exit(app.exec_())

QT and Maya. How to use a QT Designer UI with Maya. No PyQt

I had several people ask me this and usually I say. Install PyQt … on every machine that needs it and voilla. However this can not always be done and if you want to share your tools online then you just want your tools to work out of the box.

*Now a days I’d say just use pyside. No need for custom PyQt instalations anymore.

So hereby a short tutorial on how to build a QT interface with Designer that work in Maya. Since maya 2012 QT Designer is shipped with the installation of maya. You can find it here “C:\Program Files\Autodesk\<maya version>\bin\designer.exe”. This opens QT designer default interface and you can start creating your own UI’s with simple drag and drop actions. I will not go in depth on how to actually use QT designer in itself as I’ll leave that for a later time.

There are a few standard QT widgets(buttons, sliders etc) that Maya recognizes. As soon as you load the UI in maya, maya will detect these and will replace the widgets with maya’s own version. This means that you can edit, query and delete most of these detectable widgets just by using maya mel or python or pymel. No need for pyqt (even though that does make it a 100 times easier).

Someone on the creative crash website wrote a great introduction to using the QT interface. http://www.creativecrash.com/tutorials/using-qt-designer-for-mel-interfaces Especially the list of controls is very useful to keep handy when you are getting started with QT. Now I will try to skip most of the things that are covered there but some stuff might overlap.

1) Giving commands to button:

In QT Designer you can add a Dynamic Property that gets called every time the button is clicked. You can do this by going into the properties , click the plus icon and click “other… ”
Then in the “Create Dynamic Property” window, write  “-c” without the quotes in the property name section. “-c” resembles the maya mel command for a button. You can add almost all commands like this. But most of the time you will only need “-c” or “-cc” for sliders etc. (*Note if you want to use python commands use a + instead of – )

Now in your property list you will see a new dynamic property at the end of the list. Here you can type any Maya Mel !!! command. However make sure its encapsulated in a string like this.

As you can see the command in itself needs to be in a string. This might cause some confusion as you will have to “escape” any other quotation marks by using a \. Do a search on escaping strings and you will find loads of information on this. As for python code just encapsulate it in a

command.

or use a +c instead of -c.
If you have multiple lines of python code you want to put in one line use the always dreaded and nightmarish ; (semi colon).
They still give me nightmares from the time I was using mel. haha

for example:

 

2) Loading the UI in maya.

This is as straightforward as it can get.  Make ourselves a simple window. Also added a dock control to it so its dock able everywhere.

Or load it with mel : (most simplistic version)

 

3) Getting values from widgets that are not supported by maya.

Now lets do something that we are not supposed to. Now this is where it starts to get interesting for me. Every time I tried to use QT I always ran into this problem. HOW THE HECK DO YOU GET A VALUE FROM A SPINBOX or a DIAL or any other cool custom downloaded widget etc. It always bugged the hell out of me for not being able to find a stable way. But now I seem to have found a solution and the answer is “signals and hidden line edits”.

You can connect any widget to a line edit. To do this create a “line edit” anywhere in the UI. Preferably where it does not edit your layout but you can easily access it.
Go into “Edit Signal / Slots” Mode (F4) and click and drag from your widget to the “line edit”. This opens up a new window that allows you to link a value to the text field (providing the widget support exporting a Qstring)

Now on change of the original widget (a double spin box in this case) the “line edit ” in itself will be updated with the value. However if the user does not change the value the line edit will be empty. Which is why its a good idea to give it the same default value as the widget.

 

4) interacting with the UI from within maya WITHOUT USING PYQT.

So now when we load our QT Ui inside of maya we can query the text of our line edit every time we want to by using the following line of code: