A Qt.py extension which aims to expose additional helper utilities to Qt which are common place within coding projects.


Keywords
qute, qt, pyside, pyside2, pyqt
License
MIT
Install
pip install qute==3.0.11

Documentation

#Overview

Qute is a wrapped extension of Marcus Ottosson's Qt.py. The emphasis is on utilising the convience of Qt.py (allowing for use of PyQt, PySide and PySide2 seamlessly) whilst also exposing a set of common pieces of functionality we tend to replicate and utilise in many places.

#Key Features

General Usage

import qute


class MyWidget(qute.QWidget):

    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent=parent)
        
        # -- Create a layout and set it as the base layout. Use 
        # -- qute to slim the layout - removing margins
        self.setLayout(
            qute.utilities.layouts.slimify(qute.QVBoxLayout())
        )
        
        # -- Create some widgets
        self.spinner = qute.QSpinBox()
        self.checker = qute.QCheckBox()
        
        # -- Add these to our layout
        self.layout().addWidget(self.spinner)
        self.layout().addWidget(self.checker)
        
        # -- Finally lets connect some signals and slots without
        # -- caring what it is
        qute.connectBlind(self.spinner, self.do_something)
        qute.connectBlind(self.checker, self.do_something)
        
    def do_something(self, *args, **kwargs):
        print('doing something...')

if __name__ == '__main__':

    # -- Use qute to get or create the QApplication instance
    q_app = qute.utilities.qApp()
    
    widget = MyWidget()
    widget.show()
    
    q_app.exec_()

In this example we see some of the features of qute in use, but most importantly is that it is usable in environments using either PyQt, PySide or PySide2 (thanks to Qt.py), and then utilises the various helper functionality defined within qute which you can read about below.

Cross Application Support

This library is specifically intended for use when in environments where you're actively trying to share/develop tools across multiple applications which support PyQt, PySide or PySide2.

The premise is that you can request the main application window using a common function regardless of the actual application - making it trivial to implement a tool which works in multiple host applications without any bespoke code.

The current list of supported applications are:

* Native Python
* Maya
* 3dsmax
* Motion Builder

Here is an example:

import qute

class MyCrossApplicationTool(qute.QWidget):

    def __init__(self, parent=None):
        super(MyCrossApplicationTool, self).__init__(parent=parent)

        self.setLayout(qute.QVBoxLayout())
        self.layout().addWidget(qute.QLabel('This tool will launch and parent under Max, Maya, Motion Builder or Pure Python'))


# ------------------------------------------------------------------------------
def launch(blocking=False, *args, **kwargs):

    # -- This will return the running QApplication instance, or create
    # -- one if one is not present
    q_app = qute.qApp()

    # -- Create a window and set its parent 'blindly' to what qute
    # -- resolves as the main window.
    window = qute.QMainWindow(parent=qute.utilities.windows.mainWindow())

    # -- Assign our widget to the window
    window.setCentralWidget(MyCrossApplicationTool(*args, **kwargs))

    window.show()

    if blocking:
        q_app.exec_()

launch()

In the example above, we have a (somewhat simple!) tool, and we expose the tool through a launch function which is creating a main window. The crucial part is that the window is asking Qute to return the main application window rather than you relying on an application specific Ui.

In doing this, you can copy/paste the code from the example into Max, Maya or Motion Builder and you will get the same widget, and that widget will be correctly parented under that application, making your Ui incredibly portably and re-usable without an application specific layer.

Styling

Qute gives a convience function for applying stylesheets to Qt widgets. Crucually it also exposes a mechanism allowing you do define variables to be replaced within stylesheets. This helps when wanting to use the same values multiple times across a stylesheet.

For example, if we have a stylesheet such as:

QWidget {
    background-color: rgb(BG_COLOR);
    color: rgb(TEXT_COLOR);
}

QLabel {
    padding-top: 7px;
    padding-bottom: 7px;
    background-color: transparent;
    color: rgb(TEXT_COLOR);
}

This can be assigned to a widget using:

import qute

qute.utilities.styling.apply(
    css_str,
    apply_to=widget,
    BG_COLOR='50, 50, 50',
    TEXT_COLOR='255, 0, 0',
)

In this example we pass a CSS string and the widget we want to apply to. Any additional keywords will be used as search and replace elements. This is handy when wanting to change sections of your stylesheet easily. Your replacements can be numbers, strings or filepaths (just ensure your slashing is / and not \). The space example stylesheet demonstrates this by using png files for widget backgrounds.

Equally, you can pass the full path to a css/qss file too:

qute.utilities.styling.apply(
    '/usr/styles/my_style.qss',
    widget,
)

Alternatively you can have a library of style sheets and set the environment variable QUTE_STYLE_PATH to that location. By doing this you can pass the name of the style rather than the absolute path. Qute comes with one example stylesheet called space which can be used to demonstrate this as below:

qute.utilities.styling.apply(
    'space',
    widget,
)   

This is an example of the space stylesheet:

alt text

Menu Generation

Generating menu's can be tedious and involve a lot of repetative code. In many cases a menu is made up of either actions, sseperators or sub-menus.

Each of these are supported by the menu generation function qute.utilities.menus.menuFromDictionary. The format of the dictionary you provide must conform to:

{'Label': function} or {'Label': dict} or {'Label': None}

If a function is given then the function is set as the callable when the item is clicked. If a dictionary is given as the value a submenu is generated (this is recusive, so you can nest menus). If the value is None then a Seperator will be added regardless of the key.

Here is an example:

import qute

def foo():
    print('calling foo')

def bar():
    print('calling bar')

menu_definition = {
    'Foo': foo,
    '-': None,
    'More': dict(bar=bar)
}

menu = qute.utilities.menus.menuFromDictionary(menu_definition)

In this example we define some functions and add them as keys, we can then generate a QMenu from that dictionary. This is especially useful when you're dynamically generating menu from variable data.

You can also define icons for your menu. To utilise this mechanism your icons must have the same name as the label and end in .png. You can then define the path to the icons during the menu call as shown here:

menu = qute.utilities.menus.menuFromDictionary(
    structure=menu_definition,
    icon_paths=[
        os.path.dirname(__file__),
    ]
)

Derive

Derive is all about dynamically generating ui elements based on data types and being able to extract values from widgets without having to know what they are. This is particularly useful when generating ui elements on the fly without knowing what they are up front.

A good example of this is the exposure of options or attributes on a class without knowing exactly what those options are. We can see an example of that here:

import qute

class Node:
    """
    Define a base class for something
    """
    
    def __init__(self):
        self.options=dict()

class Circle(Node):

    def __init__(self):
        self.options['radius'] = 5
        self.options['closed'] = True
   
class Quadtrilateral(Node):

    def __init__(self):
        self.options['force_rectangle']

def example_callback(*args, **kwargs):
    print('In Callback')
    
nodes = [
    Circle(),
    Quadtrilateral(),
    Quadtrilateral(),
    Circle(),
]

for node in nodes:
    for option, value in node.options:
    
        # -- Blindly create a widget to represent the widget
        widget = qute.utilities.derive.deriveWidget(
        value=value,
        label=option,
        )
        
        # -- Connect the change event of the widget
        # -- to our callback - without knowing what 
        # -- the widget is or what to connect
        qute.utilities.derive.connectBlind(widget, example_callback)

We can also ask for the value from a widget without knowing what the widget is. This can be done using:

import qute

value = qute.utilities.derive.deriveValue(widget)

This mechanism makes it easier to create dynamic ui's, especially when you're trying to expose data which can be manipulated on code objects.

Compatability

This has been tested under Python 2.7.13 and Python 3.6.6 under both Windows and Ubuntu.

Contribute

If you would like to contribute thoughts, ideas, fixes or features please get in touch! mike@twisted.space