Custom Settings

Custom settings are built off of the CustomSettings class in extensions.customSettings. They create an interface between the user and the code while avoiding the need for direct coding experience.

Building A Basic Custom Settings Window

Below is a step by step guide on how to create a new custom node.

1 - Initialization

The node must be created and the parent class initialized to make sure that the rest of the class has access to the parent and settings variables. Unlike with the custom nodes, the settings class can be named anything the user wishes, although the format of “NodeClassNameSettings” is used by the WARIO development team for consistancy.

from extensions.customSettings import CustomSettings

class ExampleNodeSettings(CustomSettings):
    def __init__(self, parent, settings):
        super(ExampleNodeSettings, self).__init__(parent, settings)

2 - Constructing The Settings UI

The buildUI function is called as a part of the initialization of the CustomSettings class. As the parent class itself inherets from the PyQt5 QWidget class, we can use widgets included in PyQt5 to build our interface.

The first required component is the layout object

    def buildUI(self, settings):
        self.layout = QtWidgets.QVBoxLayout()
        self.setLayout(self.layout)

In this example we’re using the QVBoxLayout object which creates a vertical arrangement of widgets but other layout types can be found at the Qt documentation site. While this documentation is given in C++, the function calls are effectively identical.

Widgets can be added to this layout to give the required settings interface. In this example we’re adding a single checkbox that enables or disables a graphical output

        self.showGraph = QtWidgets.QCheckBox("Show Graph")
        self.layout.addWidget(self.showGraph)

Combined, this gives

    def buildUI(self, settings):
        self.layout = QtWidgets.QVBoxLayout()
        
        self.showGraph = QtWidgets.QCheckBox("Show Graph")
        self.layout.addWidget(self.showGraph)
        
        self.setLayout(self.layout)      

Alternatively, it is possible to import .ui files created in software like QT-Designer. To import a .ui file use the following code

	uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)),"uiFileRelativeToNodeFile.ui"), self)

The os.path commands generate a path to the ui file relative to the node’s .py file, allowing you to create settings UIs for custom toolkits. This command assigns all widgets to be children of self and thus you can access them by referencing the names set in the designer, e.g.

self.labelWidget.setText("text to set")

3 - Generating The Settings Output

The final required function is genSettings. This is called when the settings window is closed, loses focus, or the WARIO editor saves a file. This function generates two dicts, one for the properties of the settings widgets and the other for the values of the variables used by the code.

First, we need to store the “settingsFile” and “settingsClass” values. These are used to locate the file and class that defines the node’s settings UI when loading a WARIO pipeline.

    def genSettings(self):
        settings = {}
        vars = {}
        
        settings["settingsFile"] = self.settings["settingsFile"]
        settings["settingsClass"] = self.settings["settingsClass"]

With those values saved, the current state of all the window’s widgets must be gathered. These values can be given any key but it is recommended to use consistant, easy to understand names.

        settings["showGraphToggleState"] = self.showGraph.getChecked()

Likewise, the values given by relevant widgets must be stored. The keys of these values must match those used by the node’s code.

        vars["showGraph"] = self.showGraph.getChecked()

Finally, the node that the settings window is attached to must be passed the settings and variable values

        self.parent.settings = settings
        self.parent.variables = vars

This gives the full genSettings function as shown below

    def genSettings(self):
        settings = {}
        vars = {}
        
        settings["settingsFile"] = self.settings["settingsFile"]
        settings["settingsClass"] = self.settings["settingsClass"]
        
        settings["showGraphToggleState"] = self.showGraph.getChecked()
        vars["showGraph"] = self.showGraph.getChecked()
        
        self.parent.settings = settings
        self.parent.variables = vars

4 - Handling Loading

Now that there is a system in place for saving the state of the settings window, code must be added to buildUI to make sure that the widgets are properly updated upon loading a pipeline. As the function must be able to handle both new and loaded settings windows, we must check if to see if each key exists before applying

    def buildUI(self, settings):
        self.layout = QtWidgets.QVBoxLayout()
        
        self.showGraph = QtWidgets.QCheckBox("Show Graph")
        if "showGraphToggleState" in settings.keys():
            self.showGraph.setChecked(settings["showGraphToggleState"])
        self.layout.addWidget(self.showGraph)
        
        self.setLayout(self.layout)              

Finished Settings Window

The above tutorial gives the following settings window:

from extensions.customSettings import CustomSettings
from PyQt5 import QtWidgets

class ExampleNodeSettings(CustomSettings):
    def __init__(self, parent, settings):
        super(ExampleNodeSettings, self).__init__(parent, settings)
        
    def buildUI(self, settings):
        self.layout = QtWidgets.QVBoxLayout()
        
        self.showGraph = QtWidgets.QCheckBox("Show Graph")
        if "showGraphToggleState" in settings.keys():
            self.showGraph.setChecked(settings["showGraphToggleState"])
        self.layout.addWidget(self.showGraph)
        
        self.setLayout(self.layout) 

    def genSettings(self):
        settings = {}
        vars = {}
        
        settings["settingsFile"] = self.settings["settingsFile"]
        settings["settingsClass"] = self.settings["settingsClass"]
        
        settings["showGraphToggleState"] = self.showGraph.getChecked()
        vars["showGraph"] = self.showGraph.getChecked()
        
        self.parent.settings = settings
        self.parent.variables = vars

Optional Functions

There are several additional functions included in the CustomSettings class that can be overloaded.

updateGlobals

The updateGlobals function is used for widgets that require access to the global variable values. This could be used for such things as global file naming or changing widget properties. The base version of this function is defined as follows, with “globals” containing the dict of global variables in the same manner as used in the code (see Custom Nodes)

    def updateGlobals(self, globals):
        return

getAttribs

The getAttribs function is used exclusively when defining the attributes for nodes to be used by the “Custom Node” pipeline node. See Custom Nodes for more information.

eventFilter

The eventFilter function is an overload of the built in PyQt5 version of the function that runs the genSettings function when the settings window is either closed or loses focus. This allows for the settings window to directly affect the node’s properties such as its name or attributes.

The default version of this functionis as follows


    def eventFilter(self, object, event):
        if event.type() == QtCore.QEvent.Close:
            self.genSettings()
            event.accept()
        elif event.type() == QtCore.QEvent.WindowDeactivate:
            self.genSettings()
            event.accept()
            
        return False     

Custom Widgets

Sometimes, its easier to create an overloaded PyQt widget class that can be reused for multiple nodes. Several of these have been developed for WARIO and can be found in the extensions.customWidgets file in the WARIO directory. Below are some of the more widely useful ones.

saveWidget/loadWidget

Shows a textbox with a button that opens a dialog allowing save/load locations to be set respectively. Can be passed a string that sets the dialog filter.

Must be passed the parent widget.

    textSave = saveWidget(self, "Text files (*.txt)")

UniqueNameTable

A QTableWidget that forces unique values in the first column. Any attempt to use a value that already exists causes the cell to revert to its previous value. It includes the changedTextHook function that can be overloaded to perform tasks when the user changes the text in any cell of the first column

    def changedTextHook(self, row):
        return

Must be initialized with string containing the error text for when the user attempts to enter an existing name

    uniqueTable = UniqueNameTable("Cannot have identical keys")

LinkedCheckbox

A checkbox that can be linked to another widget to enable or disable it depending on the state of the checkbox. Includes the functions buildLinkedCheckbox and getSettings. The former loads the state of the checkbox and the latter generates the settings and variables for the widget. If the linked widget is either a QComboBox or QSpinBox, they also handle setup and saving for the widget.

    # In self.buildUI
    self.enableWidget = LinkedCheckbox("Enable Widget", existingWidget)
    self.enableWidget.buildLinkedCheckbox("enableWidget", settings)
    
    # In self.genSettings
    self.enableWidget.getSettings("enableWidget", vars, settings)

If a spinbox or combobox, the variable name of the setting is given by the name passed to the getSettings function and its value is set to “None” if the checkbox is left unchecked.

LinkedSpinbox/LinkedDoubleSpinbox

These widgets allow you two create 2 spinboxes of either int or double type that are linked together so as to limit the range of each. The linkWidgets function is used to set the linked widget and how they act relative to the widget calling the function

The LinkedSpinbox widget will force the spinbox values to be no closer than 1 from each other and the LinkedDoubleSpinbox no closer than 0.01

    self.maxValue = LinkedSpinbox()
    self.minValue = LinkedSpinbox()
    
    # We want the min value to always be lower than the max value
    self.maxValue.linkWidgets(self.minValue, "Lower")
    # And the max value to always be higher than the min value
    self.minValue.linkWidgets(self.maxValue, "Higher")

ExpandingTable

This creates a 1 column table that automatically expands when a value is inserted into the final row. It also shrinks whenever the final row is emptied. As with the LinkedCheckbox widget, this widget has a getSettings function that allows for the values stored in the table to be extracted with a single line. The key for these variables is the name passed to the init and getSettings functions (which must be identical)

    # In self.buildUI
    self.table = ExpandingTable("data", settings)
    
    # in self.genSettings
    self.table.getSettings("data", vars, settings)

CentredCellCheckbox

A widget that mimics a checkbox that can be used in QTableWidgets. This creates a centred checkbox that looks more tidy than the default left-aligned one when a cell is simply set as checkable. The functions setChecked and isChecked work in the same way as with a regular QCheckBox widget, and the connect function acts in the same way as the checkbox.toggled.connect call would.

    checkbox = CentredCellCheckbox()
    table.addWidget(0, 0, checkbox)