Qtvcp is an infrastructure to display a custom CNC screen or control panel in LinuxCNC.
It displays a UI file built with the QTDesigner screen editor or combines this
with python programming to create a GUI screen for running a CNC machine.
Qtvcp is completely customizable - you can add different buttons and status LEDs etc.
or add python code for even finer grain customization.

QTDragon Router
Figure 1. qtdragon - 3 or 4 Axis Sample
QTscreen Mill
Figure 2. qtdefault - 3 Axis Sample
QTscreen Qtaxis
Figure 3. Qtaxis - Self Adjusting Axis Sample
QTscreen Blender
Figure 4. Blender - 4 Axis Sample
QTscreen x1mill
Figure 5. X1mill - 4 Axis Sample
QTscreen x1mill
Figure 6. cam_align - Camera alignment VCP
Test Panel
Figure 7. test panel - Test Panel VCP

1. Overview

There are two files that can be used, individually or in combination to add
customization.
A UI file that is made with QT’s Designer graphical editor.
A handler file which is a text file with python code.
Normally qtvcp uses the stock UI and handler file.
You can specify qtvcp to use local UI and handler files.
A local file is one that is in the configuration folder that defines the
rest of the machine’s requirements.
One is not restricted to adding a custom panel on the right or a custom tab.
qtvcp leverages QT Designer (the editor) and PyQT5 (the widget toolkit).
QTvcp has some special widgets and actions added just for LinuxCNC.
There are special widgets to bridge third party widgets to HAL pins.
It’s possible to create widget responses by connecting signals to python
code in the handler file.

1.1. QTvcp Widgets

Qtvcp uses the PyQt5 toolkit’s widgets for linuxcnc integration.
Widget is the general name for objects such as buttons and labels in PyQT5.
You are free to use any available widgets in the QTDesigner editor.
There are also special widgets made for linuxcnc that make integration easier.
This are split in three heading on the left side of the editor.
One is for HAL only widgets.
One is for cnc control widgets.
One is for dialog widgets.
you are free to mix them in any way on your panel.
A very important widget for CNC control is the screenoptions widget.
It does not add anything visually to the screen.
But allows important details to be selected rather then be coded in the handler file.

1.2. INI Settings

If you are using this to make a CNC control screen:
Under the [DISPLAY] heading:

DISPLAY = qtvcp <options> <screen_name>
  options:
    -d debugging on
    -a set window always on top
    -c HAL component name. Default is to use the UI file name.
    -g geometry: WIDTHxHEIGHT+XOFFSET+YOFFSET example: -g 200x400+0+100
    -m maximize window
    -f fullscreen the window
    -t theme. Default is system theme
    -x embed into a X11 window that doesn't support embedding.
    --push_xid send qtvcp's X11 window id number to standard output; for embedding
    -u file path of a substitute handler file
    -o pass a string to qtvcp's handler file under self.w.USEROPTIONS_ list variable. can be multiple -o

    all <options> must be before <screen name>
    <screen_name> is the base name of the .ui and _handler.py files.
    If <screen_name> is missing the default screen will be loaded.

Qtvcp assumes the UI file and the handler file use this same base name.
Qtvcp will search the LinuxCNC configuration file that was launched first for the files,
then in the system skin folder. (The skin folders holds standard screens.)

[DISPLAY]
CYCLE_TIME = 100
GRAPHICS_CYCLE_TIME = 100
HALPIN_CYCLE = 100

Adjust the response rate of the GUI updates in milli seconds. Defaults to 100, useable range 50 - 200
The widgets, graphics and HAL pin update can be set separately.
If the update time is not set right the screen can become unresponsive or very jerky.

1.3. QTDesigner UI File

A designer file is a text file organized in the XML standard that describes the
layout and the widgets of the screen. Pyqt5 uses this file to build the display
and react to those widgets. The QTDesigner editor makes it relatively easy to build
and edit this file.

1.4. Handler Files

A handler file is a file containing python code, which qtvcp adds to it’s
default routines. A handler file allows one to modify defaults, or add logic
to a qtvcp skin without having to modify qtvcp’s core code.
In this way you can have custom behaviour.
If present a handler file will be loaded.
Only one file is allowed.

1.5. Libraries modules

Qtvcp as built does little more then display the screen and react to widgets.
For more prebuilt behaviours there are available libraries.
(found in lib/python/qtvcp/lib in RIP linuxcnc install)
libraries are prebuilt python modules that give added features to Qtvcp.
In this way you can select what features you want - yet don’t have to build common ones yourself.
Such libraries include:

audio_player
aux_program_loader
keybindings
message
preferences
notify
virtual_keyboard
machine_log

1.6. Themes

Themes are a way to modify the look and feel of the widgets on the screen.
For instance the color or size of buttons and sliders can be changed using themes.
The Windows theme is default for screens. System theme is default for panels.
to see available themes load qtvcp with -d -t SHOWTHEMES

qtvcp can also be customized with Qt stylesheets using css.

1.7. Local Files

If present, local UI files in the configuration folder will be loaded instead
of the stock UI files. Local UI files allow you to use your customized
designs rather then the default screens.
QTVCP will look for a folder name MYNAME (in the launched configuration folder that holds the INI file).
In that folder QTVCP will load any of the available files; MYNAME.ui, MYNAME_handler.py and MYNAME.qss.

1.8. Modifying Stock Screens

Ther are three ways to customize a screen/panel.

Minor StyleSheet changes:

StyleSheets can be used to set Qt properties.
If a widget uses properties they usually can be modified by stylesheets.
ie:

State_LED #name_0f_led{
qproperty-color: red;
qproperty-diameter: 20;
qproperty-flashRate: 150;
}
Minor python code changes:

A file can be added to add commands to the screen, after the handlerfile is parsed.
In the INI file under the [DISPLAY] heading add USER_COMMAND_FILE = PATH
PATH can be any valid path, it can use ~ for home directory or WORKINGDIRECTORY or
CONFIGDIRECTORY to represent Qtvcp’s idea of those directories.
ie:

[DISPLAY]
USER_COMMAND_FILE = CONFIGDIRECTORY/qtdragon_added_commands

If no entry is found in the INI, Qtvcp will look in the default path.
The default path is in the configuration directory as a hidden file using the screen basename and rc.
ie: CONFIGDIRECTORY/.qtdragonrc

This file will be read and executed as python code in context of the handler file.
Only local functions and local attributes can be referenced.
Global libraries can not be referenced. (usual seen as all capital words with no preceding self.)
What can be used can vary by screen and development cycle.

valid example:

self.w.setWindowTitle('My Title Test')
Full creative control:

If you wish to modify a stock screen with full control, copy it’s UI and handler file to your configuration folder.
There is a QtVCP panel to help with this.
Open a terminal and type qtvcp copy_dialog and a dialog will show to select the screen and
destination folder. This will copy all the file - delete the ones you don’t wish to modify so
that the original files will be used.
It you wish to name your screen differently then the builtin screen’s default name -
change the basename in the edit box.

2. VCP Panels

Qtvcp can be used to create control panels that interface with HAL.

2.1. Builtin panels

There are several builtin HAL panels available.
in a terminal type qtvcp <return> to see a list.

  • test_panel - collect of useful widgets for testing HAL component. Including speech of LED state.

  • cam_align - a camera display widget for rotational alignment

  • sim_panel - a small control panel to simulate MPG jogging controls etc. for simulated configurations

  • vismach_mill_xyz - 3d openGL view of a 3 axis milling machine

QtVismach Mill
Figure 8. qtvismach- 3 Axis Mill Builtin panel
loadusr qtvcp test_panel

You can of course make your own panel and load it.
If you made a ui file named my_panel.ui and name the following HAL file, my_panel.hal
You would then load this from a terminal with halrun -I -f my_panel.hal

# load realtime components
loadrt threads
loadrt classicladder_rt

# load user space programs
loadusr classicladder
loadusr -Wn my_panel qtvcp my_panel.ui

# add components to thread
addf classicladder.0.refresh thread1


# connect pins
net bit-input1     test_panel.checkbox_1        classicladder.0.in-00
net bit-hide       test_panel.checkbox_4        classicladder.0.hide_gui

net bit-output1    test_panel.led_1             classicladder.0.out-00

net s32-in1        test_panel.doublescale_1-s   classicladder.0.s32in-00

# start thread
start

In this case we load qtvcp using -Wn; which waits for the panel to finish loading before
continuing to run the next HAL command. This is so the HAL pins from the panel are finished
in case the are used in the rest of the file.

3. Build a simple clean-sheet custom screen

QTscreen Mill
Figure 9. Ugly custom screen

3.1. Overview

To build a panel or screen use QTDesigner to build a design you like.
Save this design to your configuration folder with a name of your choice, ending with .ui
modify the configurations INI file to load qtvcp with your new .ui file.
Then connect any required HAL pins in a HAL file

3.2. Get Designer to include linuxcnc widgets

You must have designer installed; These commands should add it:
Or use your package manager to install the same:
sudo apt-get install qttools5-dev-tools
sudo apt-get install qttools5-dev
sudo apt-get install libpython3-dev

Then you must add a link to the qtvcp_plugin.py to the folder that designer will search.

In a RIP version of linuxcnc qtvcp_plugin.py will be in:
~/LINUXCNC_PROJECT_NAME/lib/python/qtvcp/plugins/qtvcp_plugin.py

installed version should be:
usr/lib/python2.7/qtvcp/plugins/qtvcp_plugin.py
or usr/lib/python2.7/dist-packages/qtvcp/plugins/qtvcp_plugin.py

make a link file to the above file and move it to one of the places Designer searches:

Designer searches in these two place for links (pick one):
This can be:
/usr/lib/x86_64-linux-gnu/qt5/plugins/designer/python
or
~/.designer/plugins/python
You may need to add the plugins/python folders

To start Designer:

for a RIP installed:
open a terminal, set the environment for linuxcnc with the command: . scripts/rip-environment
then load designer with : designer -qt=5

otherwise for an installed version, open a terminal and type designer -qt=5

If all goes right you will see the selectable linuxcnc widgets on the left hand side

3.3. build the screen .ui file

When Designer is first started there is a New Form dialog displayed.
Pick Main Window and press the create button.
Do not rename this window - Qtvcp requires the name to be MainWindow

A MainWindow widget is Displayed. Grab the corner of the window and resize to
an appropriate size say 1000x600. right click on the window and click
set minimum size. Do it again and set maximum size.Our sample widget will
now not be resizable.

Drag and drop the screenoption widget onto the main window (anywhere).
This widget doesn’t add anything visually but sets up some common options.
It’s recommended to always add this widget before any other.
Right click on the main window (not the screenoptions widget)
and set the layout as vertical. The screenoption widget will now be fullsized.

On the right hand side there is a panel with tabs for a Property editor and
an object inspector. On the Object inspector click on the screenoption. then
switch to the property Editor. Under the heading ScreenOptions toggle
filedialog_option.

Drag and drop a GCodeGraphics widget and a GcodeEditor widget.
Place and resize them as you see fit leaving some room for buttons.

Now we will add action buttons.
Add 7 action buttons on to the main window. If you double click the button, you
can add text. Edit the button labels for Estop, Machine On, Home, Load,
Run, Pause and stop.
Action buttons default to no action so we must change the properties for defined functions.
You can edit the properties directly in the property editor on the right side of designer.
A convenient alternating is left double clicking on the button This will launch a Dialog
that allows selecting actions while only display relevant data to the action.

We will describe the convenient way first:

  • Right click the Machine On button and select Set Actions. When the Dialog displays,
    use the combobox to navigate to MACHINE CONTROLS - Machine On. In this case there there
    is no option for this action so select ok. Now the button will turn the machine on when pressed

And now the direct way with Designer’s property editor

  • Select the Machine On button. Now go to the Property Editor on the right
    side of Designer. Scroll down until you find the ActionButton heading.
    You will see a list of properties and values. find the machine on action and
    click the checkbox. the button will now control machine on/off.

Do the same for all the other button with the addition of:

  • With the Home button we must also change the joint_number property to -1,
    Which tells the controller to home all the axes rather then a specific axis.

  • With the Pause button under the heading Indicated_PushButton check the
    indicator_option and under the QAbstactButton heading check checkable

designer button property
Figure 10. Qt Designer - Selecting Pause button’s properties

We then need to save this design as tester.ui in the sim/qtvcp folder
We are saving it as tester as that is a file name that qtvcp recognizes and
will use a built in handler file to display it.

3.4. Handler file

a handler file is required. It allows customizations to be written in python.
For instance keyboard controls are usually written in the handler file.

In this example the built in file tester_handler.py is automatically used.
It does the minimum required to display the tester.ui defined screen and do
basic keyboard jogging.

3.5. INI

If you are using qtvcp to make a CNC control screen:
Under the [DISPLAY] heading:

DISPLAY = qtvcp <screen_name>

<screen_name> is the base name of the .ui and _handler.py files.

In our example there is already a sim configuration called tester, that we
will use to display our test screen.

3.6. HAL

If your screen used widgets with HAL pins, then you must connect them in a HAL file.
Qtvcp looks in the INI file, under the heading [HAL] for the entry POSTGUI_HALFILE=<filename>
Typically <filename> would be the screens base name + _postgui + .hal
eg. qtvcp_postgui.hal, but can be any legal filename.
These commands are executed after the screen is built, guaranteeing the widget HAL
pins are available.
You can have multiple line of POSTGUI_HALFILE=<filename> in the INI.
Each will be run one after the other in the order they appear.

Qtvcp also looks in the INI file, under the heading [HAL] for the entry POSTGUI_HALCMD=<command>
<command> would be any valid HAL command.
These commands are executed after the screen is built, after all the POSTGUI_HALFILEs are run,
guaranteeing the widget HAL pins are available.
You can have multiple line of POSTGUI_HALCMD=<command> in the INI.
Each will be run one after the other in the order they appear.

In our example there are no HAl pins to connect.

4. Handler file in detail

handler files are used to create custom controls using python.

4.1. Overview

Here is a sample handler file.
It’s broken up in sections for ease of discussion.

############################
# **** IMPORT SECTION **** #
############################
import sys
import os
import linuxcnc

from PyQt5 import QtCore, QtWidgets

from qtvcp.widgets.mdi_line import MDILine as MDI_WIDGET
from qtvcp.widgets.gcode_editor import GcodeEditor as GCODE
from qtvcp.lib.keybindings import Keylookup
from qtvcp.core import Status, Action

# Set up logging
from qtvcp import logger
LOG = logger.getLogger(__name__)

# Set the log level for this module
#LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL

###########################################
# **** INSTANTIATE LIBRARIES SECTION **** #
###########################################

KEYBIND = Keylookup()
STATUS = Status()
ACTION = Action()
###################################
# **** HANDLER CLASS SECTION **** #
###################################

class HandlerClass:

    ########################
    # **** INITIALIZE **** #
    ########################
    # widgets allows access to  widgets from the qtvcp files
    # at this point the widgets and hal pins are not instantiated
    def __init__(self, halcomp,widgets,paths):
        self.hal = halcomp
        self.w = widgets
        self.PATHS = paths

    ##########################################
    # SPECIAL FUNCTIONS SECTION              #
    ##########################################

    # at this point:
    # the widgets are instantiated.
    # the HAL pins are built but HAL is not set ready
    # This is where you make HAL pins or initialize state of widgets etc
    def initialized__(self):
        pass

    def processed_key_event__(self,receiver,event,is_pressed,key,code,shift,cntrl):
        # when typing in MDI, we don't want keybinding to call functions
        # so we catch and process the events directly.
        # We do want ESC, F1 and F2 to call keybinding functions though
        if code not in(QtCore.Qt.Key_Escape,QtCore.Qt.Key_F1 ,QtCore.Qt.Key_F2,
                    QtCore.Qt.Key_F3,QtCore.Qt.Key_F5,QtCore.Qt.Key_F5):

            # search for the top widget of whatever widget received the event
            # then check if it's one we want the keypress events to go to
            flag = False
            receiver2 = receiver
            while receiver2 is not None and not flag:
                if isinstance(receiver2, QtWidgets.QDialog):
                    flag = True
                    break
                if isinstance(receiver2, MDI_WIDGET):
                    flag = True
                    break
                if isinstance(receiver2, GCODE):
                    flag = True
                    break
                receiver2 = receiver2.parent()

            if flag:
                if isinstance(receiver2, GCODE):
                    # if in manual do our keybindings - otherwise
                    # send events to gcode widget
                    if STATUS.is_man_mode() == False:
                        if is_pressed:
                            receiver.keyPressEvent(event)
                            event.accept()
                        return True
                elif is_pressed:
                    receiver.keyPressEvent(event)
                    event.accept()
                    return True
                else:
                    event.accept()
                    return True

        if event.isAutoRepeat():return True

        # ok if we got here then try keybindings
        try:
            return KEYBIND.call(self,event,is_pressed,shift,cntrl)
        except NameError as e:
            LOG.debug('Exception in KEYBINDING: {}'.format (e))
        except Exception as e:
            LOG.debug('Exception in KEYBINDING:', exc_info=e)
            print 'Error in, or no function for: %s in handler file for-%s'%(KEYBIND.convert(event),key)
            return False

    ########################
    # CALLBACKS FROM STATUS #
    ########################

    #######################
    # CALLBACKS FROM FORM #
    #######################

    #####################
    # GENERAL FUNCTIONS #
    #####################

    # keyboard jogging from key binding calls
    # double the rate if fast is true
    def kb_jog(self, state, joint, direction, fast = False, linear = True):
        if not STATUS.is_man_mode() or not STATUS.machine_is_on():
            return
        if linear:
            distance = STATUS.get_jog_increment()
            rate = STATUS.get_jograte()/60
        else:
            distance = STATUS.get_jog_increment_angular()
            rate = STATUS.get_jograte_angular()/60
        if state:
            if fast:
                rate = rate * 2
            ACTION.JOG(joint, direction, rate, distance)
        else:
            ACTION.JOG(joint, 0, 0, 0)

    #####################
    # KEY BINDING CALLS #
    #####################

    # Machine control
    def on_keycall_ESTOP(self,event,state,shift,cntrl):
        if state:
            ACTION.SET_ESTOP_STATE(STATUS.estop_is_clear())
    def on_keycall_POWER(self,event,state,shift,cntrl):
        if state:
            ACTION.SET_MACHINE_STATE(not STATUS.machine_is_on())
    def on_keycall_HOME(self,event,state,shift,cntrl):
        if state:
            if STATUS.is_all_homed():
                ACTION.SET_MACHINE_UNHOMED(-1)
            else:
                ACTION.SET_MACHINE_HOMING(-1)
    def on_keycall_ABORT(self,event,state,shift,cntrl):
        if state:
            if STATUS.stat.interp_state == linuxcnc.INTERP_IDLE:
                self.w.close()
            else:
                self.cmnd.abort()

    # Linear Jogging
    def on_keycall_XPOS(self,event,state,shift,cntrl):
        self.kb_jog(state, 0, 1, shift)

    def on_keycall_XNEG(self,event,state,shift,cntrl):
        self.kb_jog(state, 0, -1, shift)

    def on_keycall_YPOS(self,event,state,shift,cntrl):
        self.kb_jog(state, 1, 1, shift)

    def on_keycall_YNEG(self,event,state,shift,cntrl):
        self.kb_jog(state, 1, -1, shift)

    def on_keycall_ZPOS(self,event,state,shift,cntrl):
        self.kb_jog(state, 2, 1, shift)

    def on_keycall_ZNEG(self,event,state,shift,cntrl):
        self.kb_jog(state, 2, -1, shift)

    def on_keycall_APOS(self,event,state,shift,cntrl):
        pass
        #self.kb_jog(state, 3, 1, shift, False)

    def on_keycall_ANEG(self,event,state,shift,cntrl):
        pass
        #self.kb_jog(state, 3, -1, shift, linear=False)

    ###########################
    # **** closing event **** #
    ###########################

    ##############################
    # required class boiler code #
    ##############################

    def __getitem__(self, item):
        return getattr(self, item)
    def __setitem__(self, item, value):
        return setattr(self, item, value)

################################
# required handler boiler code #
################################

def get_handlers(halcomp,widgets,paths):
     return [HandlerClass(halcomp,widgets,paths)]

4.2. IMPORT SECTION

This section is for importing library modules required for your screen.
It would be typical to import qtvcp’s keybinding, Status and action
libraries.

4.3. INSTANTIATE LIBRARIES SECTION

By instantiating the libraries here we create global reference.
You can note this by the commands that don’t have self. in front of them.
By convention we capitalize the names of global referenced libraries.

4.4. HANDLER CLASS section

The custom code is placed in a class so qtvcp can utilize it.
This is the definitions on the handler class.

4.5. INITIALIZE section

Like all python libraries the init function is called when the library
is first instantiated. You can set defaults and reference variables here.
The widget references are not available at this point.
The variables halcomp, widgets and paths give access to qtvcp’s HAL component,
widgets, and path info respectably.
This is where you would set up global variables.
Widgets are not actually accessible at this point.

4.6. SPECIAL FUNCTIONS section

There are several special functions that qtvcp looks for in the handler file.
If qtvcp finds these it will call them, if not it will silently ignore them.

4.6.1. initialized__(self):

This function is called after the widgets and HAL pins are built
You can manipulate the widgets and HAL pins or add more HAL pins here.
Typically preferences can be checked and set, styles applied to
widgets or status of linuxcnc be connected to functions.
This is also where keybindings would be added.

4.6.2. class_patch__(self):

Class patching allow you to override function calls in an imported module.
Class patching must be done before the module is instantiated and it modifies
all instances made after that.
An example might be patching button calls from the gcode editor to call functions
in the handler file instead.
Class patching is also known as monkey patching.

4.6.3. processed_key_event__(self, receiver,event,is_pressed,key,code,shift,cntrl):

This function is called to facilitate keyboard jogging etc.
By using the keybindings library this can be used to easily add
functions bound to keypresses.

4.6.4. keypress_event__(self,receiver, event)):

This function gives raw key press events. It takes presidence over
the processed_key_event.

4.6.5. keyrelease_event__(receiver, event):

This function gives raw key release events. It takes presidence over
the processed_key_event.

4.6.6. before_loop__(self):

This function is called just before the Qt event loop is entered.
At the point all widgets/libraries/initialization code has completed and the screen is already displayed.

4.6.7. system_shutdown_request__(self):

If present, this function overrides the normal function called when a user selects a total system shutdown.
It could be used to do pre-shutdown housekeeping. The system will not shutdown if using this function, you will
have to do that yourself. qtvcp/linuxcnc will shutdown without a prompt after this function returns

4.6.8. closing_cleanup__(self):

This function is called just before the screen closes. It can be used
to do cleanup before closing.

4.7. STATUS CALLBACKS section

By convention this is where you would put functions that are callbacks
from STATUS definitions.

4.8. CALLBACKS FROM FORM section

By convention this is where you would put functions that are callbacks
from the widgets that you have connected to the MainWindow with the
designer editor.

4.9. GENERAL FUNCTIONS section

By convention this is where you put your general functions

4.10. KEY BINDING section

If you are using the keybinding library this is where you place your
custom key call routines.
The function signature is:

    def on_keycall_KEY(self,event,state,shift,cntrl):
        if state:
            self.do_something_function()

KEY being the code (from the keybindings library) for the desired key.

4.11. CLOSING EVENT section

Putting the close event function here will catch closing events.
This replaces any predefined closeEvent function from qtvcp
It’s usually better to use the special closing_cleanup__ function.

    def closeEvent(self, event):
        self.do_something()
        event.accept()

5. Connecting widgets to python code

It’s possible to connect widgets to python code using signals and slots.
In this way you can give new functions to linuxcnc widgets or utilize
standard widgets to control linuxcnc.

5.1. Overview

In the Designer editor you would create user function slots and connect
them to widgets using signals.
In the handler file you would create the slot’s functions defined in Designer.

5.2. Using Designer to add slots

When you have loaded your screen into designer add a plain PushButton to the screen.
You could change the name of the button to something interesting like test_button
There are two ways to edit connections - This is the graphical way
There is a button in the top tool bar of designer for editing signals.
After pushing it, if you click-and-hold on the button it will show a arrow
(looks like a ground signal from electrical schematic)
Slide this arrow to a part of the main window that does not have widgets on it.
A Configure Connections dialog will pop up.
The list on the left are the available signals from the widget.
The list on the right is the available slots on the main window and you can add to it.

Pick the signal clicked() - this makes the slots side available.
click edit on the slots list.
A Slots/Signals of MainWindow dialog will pop up.
On the slots list at the top there is a plus icon - click it.
you can now edit a new slot name.
Erase the default name slot() and change it to test_button()
press the ok button.
You’ll be back to the Configure Connections dialog.
now you can select your new slot in the slot list.
then press ok and save the file.

QTvcp
Figure 11. Designer signal/slot selection

5.3. Handler file changes

Now you must add the function to the handler file.
The function signature is def slotname(self):
We will add some code to print the widget name.

So for our example:

def test_button(self):
    name = self.w.sender().text()
    print name

Add this code under the section named:

#######################
# callbacks from form #
#######################

In fact it doesn’t matter where in the handler class you put the commands but by convention this is where to put it.
Save the handler file.
Now when you load your screen and press the button it should print the name
of the button in the terminal.