:lang: en
:toc:

[[cha:qtvcp-custom-widgets]]
= QtVCP Building Custom Widgets

== Overview

Building custom widgets allows one to use the Qt Designer editor to place +
a custom widget rather then doing it manually in a handler file. +
A useful custom widgets would be a great way to contribute back to LinuxCNC.

=== Widgets

Widget is the general name for the UI objects such as buttons and labels
in pyQT. +
There are also special widgets made for LinuxCNC that make integration
easier. +
This widgets can be placed with qt Designer editor - allowing one to see
the result before actually loading the panel in LinuxCNC.

=== Designer

Qt Designer is a What You See is What You Get editor for placing pyQT
widgets. +
It's original intend was for building the graphic widgets for programs. +
We leverage it to build screens and panels for LinuxCNC. +
In Qt Designer LinuxCNC widgets are split in three heading on the left side
of the editor. +
One is for HAL only widgets. +
One is for LinuxCNC controller widgets +
And one is for dialog widgets. +
 +
For Designer to add custom widgets to it's editor it must have a plugin
added to the right folder. +

=== initialization process

QtVCP does extra setup for widgets subclassed from _HALWidgetBase. (aka
HAL-ified widgets)+
This includes injecting important variables, calling an extra setup function
and calling a closing cleanup function at shutdown. +
These functions are not called when the Designer editor displays the widgets. +
When QtVCP builds a screen from the .ui file, it searches for all the
HAL-ified widgets. +
Then it finds the ScreenOptions widget, to collect information it needs
to inject into the other widgets. +
It then instantiates each widget and if a HAL-fied widget, it calls the
function hal_init(). +
hal_init() is defined in the base class and it adds variables such as
the preference file to every HAL-ifed widget. +
hal_init() will then call _hal_init() on the widget. _hal_init() allows
the widget designer to do setup that +
requires access to the extra variables. +
Here is a description of the extra variables inject into HAL-ified widgets:

[source,python]
----
self.HAL_GCOMP = the HAL component instance
self.HAL_NAME = This widgets name as a string
self.QT_OBJECT_ = This widgets object instance
self.QTVCP_INSTANCE_ = The very toplevel Parent Of the screen
self.PATHS_ = The instance of QtVCP's path library
self.PREFS_ = the instance of an optional preference file
self.SETTINGS_ = the Qsettings object
----

=== cleanup process

When QtVCP closes, it calls the function _hal_cleanup() on all HAL-ified
widgets. +
The baseclass creates an empty _hal_cleanup() function, which can be
redefined in the custom widget subclass. +
This can be used to do such things as record preferences, etc. +
This function is not called when the Designer editor displays the widgets.

== Custom Hal Widgets

Hal widgets are the simplest to show example of. +
qtvcp/widgets/simple_widgets.py holds many HAL only widgets. +
Lets look at a snippet of simple_widgets.py.

[source,python]
----
#!/usr/bin/env python3

###############################
# Imports
###############################
from PyQt5 import QtWidgets
from qtvcp.widgets.widget_baseclass import _HalWidgetBase, _HalSensitiveBase
import hal

######################
# WIDGET
######################

class Lcnc_GridLayout(QtWidgets.QWidget, _HalSensitiveBase):
    def __init__(self, parent = None):
        super(GridLayout, self).__init__(parent)
----

=== In the 'Imports' section

This is where we import libraries that our widget class needs. +
In this case we need access to pyqt's QtWidgets library, LinuxCNC's HAL
library and qtvcp's widget baseclass _HalSensitiveBase for automatic HAL
pin setup and to disable/enable the widget (also known as input sensitivity). +
There is also _HalToggleBase, and _HalScaleBase functions available in
the library.

=== In the 'WIDGET' section

Ok, here is our custom widget based on pyQT's QGridLayout widget. +
Grid layout allows one to place objects in a grid fashion. +
But this grid_layout also will enable and disable all widgets inside it, +
based on the state of a HAL pin. +
Line by Line:

[source,python]
----
class Lcnc_GridLayout(QtWidgets.QWidget, _HalSensitiveBase):
----

This defines the class name and the libraries in inherits from. +
this class named Lcnc_GridLayout inheriets the functions of QWidget
and _HalSensitiveBase. +
_HalSensitiveBase is 'subclass' of _HalWidgetBase, the base class of most
QtVCP widgets meaning it has all the functions of _HalWidgetBase plus
the functions of _HalSensitiveBase. +
It adds the function to make the widget be enabled or disabled based on
a HAL input BIT pin. +

[source,python]
----
def __init__(self, parent = None):
----

This is the function called when the widget is first made (said instantiated)-
this is pretty standard.

[source,python]
----
super(GridLayout, self).__init__(parent)
----

This function initializes our widget's 'Super' classes. +
'Super' just means the inherited baseclasses; QWidget and _HalSensitiveBase +
Pretty standard other the the widget name will change

== Custom Controller Widgets using STATUS

Widget that interact with LinuxCNC's controller are only a little more
complicated they require some extra libraries. +
In this cut down example we will add properties that can be changed in
Designer. +
This LED indicator widget will respond to selectable LinuxCNC controller
states. +

[source,python]
----
#!/usr/bin/env python3

###############################
# Imports
###############################
from PyQt5.QtCore import pyqtProperty
from qtvcp.widgets.led_widget import LED
from qtvcp.core import Status

###########################################
# **** instantiate libraries section **** #
###########################################
STATUS = Status()

##########################################
# custom widget class definition
##########################################
class StateLED(LED):
    def __init__(self, parent=None):
        super(StateLED, self).__init__(parent)
        self.has_hal_pins = False
        self.setState(False)
        self.is_estopped = False
        self.is_on = False
        self.invert_state = False

    def _hal_init(self):
        if self.is_estopped:
            STATUS.connect('state-estop', lambda w:self._flip_state(True))
            STATUS.connect('state-estop-reset', lambda w:self._flip_state(False))
        elif self.is_on:
            STATUS.connect('state-on', lambda w:self._flip_state(True))
            STATUS.connect('state-off', lambda w:self._flip_state(False))

    def _flip_state(self, data):
            if self.invert_state:
                data = not data
            self.change_state(data)

    #########################################################################
    # Designer properties setter/getters/resetters
    ########################################################################

    # invert status
    def set_invert_state(self, data):
        self.invert_state = data
    def get_invert_state(self):
        return self.invert_state
    def reset_invert_state(self):
        self.invert_state = False

    # machine is estopped status
    def set_is_estopped(self, data):
        self.is_estopped = data
    def get_is_estopped(self):
        return self.is_estopped
    def reset_is_estopped(self):
        self.is_estopped = False

    # machine is on status
    def set_is_on(self, data):
        self.is_on = data
    def get_is_on(self):
        return self.is_on
    def reset_is_on(self):
        self.is_on = False

    #######################################
    # Designer properties
    #######################################
    invert_state_status = pyqtProperty(bool, get_invert_state, set_invert_state, reset_invert_state)
    is_estopped_status = pyqtProperty(bool, get_is_estopped, set_is_estopped, reset_is_estopped)
    is_on_status = pyqtProperty(bool, get_is_on, set_is_on, reset_is_on)
----

=== In the 'Imports' section

This is where we import libraries that our widget class needs. +
We import pyqtProperty so we can interact with the Designer editor. +
we import LED because our custom widget is based on it. +
We import Status because it gives us status messages from LinuxCNC. +

=== In the 'Instantiate Libraries' section
Typically we instantiated the libraries outside of the widget class so
that the reference to it is global - meaning you don't need to use self.
in front of it. +
By convention we use all capital letters in the name. +

=== In the 'custom widget class definition' section

This is the meat and potatoes of our custom widget.

[source,python]
----
class StateLed(LED):
    def __init__(self, parent=None):
        super(StateLed, self).__init__(parent)
        self.has_hal_pins = False
        self.setState(False)
        self.is_estopped = False
        self.is_on = False
        self.invert_state = False
----

This defines the name of our custom widget and what other class it inherits
from, in this case we inherit LED - a QtVCP widget that represents a status
light. +
The __init__ is typical of most widgets, it is called when the widget is
first made. +
The super line is typical of most widgets - it calls the parent (super)
widget's initialization code. +
Then we set some attributes. +
self.has_hal_pins is an attribute inherited from Lcnc_Led - we set it
here so no HAL Pins are made. +
self.setState is inherited from Lcnc_led - we set it to make sure the
LED is off. +
The other attributes are for the selectable options of our widget.

[source,python]
----
    def _hal_init(self):
        if self.is_estopped:
            STATUS.connect('state-estop', lambda w:self._flip_state(True))
            STATUS.connect('state-estop-reset', lambda w:self._flip_state(False))
        elif self.is_on:
            STATUS.connect('state-on', lambda w:self._flip_state(True))
            STATUS.connect('state-off', lambda w:self._flip_state(False))
----

This function connects STATUS (LinuxCNC status message library) to our
widget so that the LED will on or off based on the selected state of the
controller. We have two states we can choose from is_estopped or is_on. +
Depending on which is active our widget get connected to the appropriate
STATUS messages. +
_hal_int() is called on each widget that inherited _HalWidgetBase, when
QtVCP first builds the screen. +
You might wonder why it's called on this widget since we didn't have
_HalWidgetBase in our class definition (class Lcnc_State_Led(Lcnc_Led):) -
it's called because Lcnc_Led inherits  _HalWidgetBase

in this function you have access to some extra information. (though we
don't use them in this example)

[source,python]
----
        self.HAL_GCOMP = the HAL component instance
        self.HAL_NAME = This widgets name as a string
        self.QT_OBJECT_ = This widgets pyQt object instance
        self.QTVCP_INSTANCE_ = The very toplevel Parent Of the screen
        self.PATHS_ = The instance of QtVCP's path library
        self.PREFS_ = the instance of an optional preference file
----

We could use this information to create HAL pins or look up image paths etc.

[source,python]
----
            STATUS.connect('state-estop', lambda w:self._flip_state(True))
----

lets look at this line more closely. STATUS is very common theme is widget building. +
STATUS use GObject message system to send messages to widgets that register to it. +
This line is the register process. +
'state-estop' is the message we wish to act on. there are many messages available. +
'lambda w:self._flip_state(True)' is what happens when the message is caught. +
the lambda function accepts the widget instance (w) that GObject sends it and then calls the function +
self._flip_state(True) +
Lambda was used to strip the (w) object before calling the self._flip_state function. +
It also allowed use to send self._flip_state() the True state.

[source,python]
----
    def _flip_state(self, data):
            if self.invert_state:
                data = not data
            self.change_state(data)
----

This is the function that actually flips the state of the LED. +
It is what gets called when the appropriate STATUS message is accepted.



[source,python]
----
STATUS.connect('current-feed-rate', self._set_feedrate_text)
----

and the function called looks like this:

[source,python]
----
    def _set_feedrate_text(self, widget, data):
----

in which the widget and any data must be accepted by the function.

==== In the 'Designer properties setter/getters/resetters' section

This is how Designer sets the attributes of the widget. +
This can also be called directly in the widget. +

==== In the 'Designer properties' section

This is the registering of properties in Qt Designer. +
The property name is the text that is used in Qt Designer. +
These property names cannot be the same as the attributes they represent. +
These properties show in Designer in the order they appear here.

== Custom Controller Widgets with actions

Here is an example of a widget that sets the user reference system. +
It changes the machine controller state with the ACTION library. +
It also uses the STATUS library to set whether the button can be clicked +
or not.

[source,python]
----
import os
import hal

from PyQt5.QtWidgets import QWidget, QToolButton, QMenu, QAction
from PyQt5.QtCore import Qt, QEvent, pyqtProperty, QBasicTimer, pyqtSignal
from PyQt5.QtGui import QIcon

from qtvcp.widgets.widget_baseclass import _HalWidgetBase
from qtvcp.widgets.dialog_widget import EntryDialog
from qtvcp.core import Status, Action, Info

# Instantiate the libraries with global reference
# STATUS gives us status messages from LinuxCNC
# INFO holds ini details
# ACTION gives commands to LinuxCNC
STATUS = Status()
INFO = Info()
ACTION = Action()

class SystemToolButton(QToolButton, _HalWidgetBase):
    def __init__(self, parent=None):
        super(SystemToolButton, self).__init__(parent)
        self._joint = 0
        self._last = 0
        self._block_signal = False
        self._auto_label_flag = True
        SettingMenu = QMenu()
        for system in('G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G59.1', 'G59.2', 'G59.3'):

            Button = QAction(QIcon('exit24.png'), system, self)
            Button.triggered.connect(self[system.replace('.','_')])
            SettingMenu.addAction(Button)

        self.setMenu(SettingMenu)
        self.dialog = EntryDialog()

    def _hal_init(self):
        if not self.text() == '':
            self._auto_label_flag = False
        def homed_on_test():
            return (STATUS.machine_is_on()
                    and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED))

        STATUS.connect('state-off', lambda w: self.setEnabled(False))
        STATUS.connect('state-estop', lambda w: self.setEnabled(False))
        STATUS.connect('interp-idle', lambda w: self.setEnabled(homed_on_test()))
        STATUS.connect('interp-run', lambda w: self.setEnabled(False))
        STATUS.connect('all-homed', lambda w: self.setEnabled(True))
        STATUS.connect('not-all-homed', lambda w, data: self.setEnabled(False))
        STATUS.connect('interp-paused', lambda w: self.setEnabled(True))
        STATUS.connect('user-system-changed', self._set_user_system_text)

    def G54(self):
        ACTION.SET_USER_SYSTEM('54')

    def G55(self):
        ACTION.SET_USER_SYSTEM('55')

    def G56(self):
        ACTION.SET_USER_SYSTEM('56')

    def G57(self):
        ACTION.SET_USER_SYSTEM('57')

    def G58(self):
        ACTION.SET_USER_SYSTEM('58')

    def G59(self):
        ACTION.SET_USER_SYSTEM('59')

    def G59_1(self):
        ACTION.SET_USER_SYSTEM('59.1')

    def G59_2(self):
        ACTION.SET_USER_SYSTEM('59.2')

    def G59_3(self):
        ACTION.SET_USER_SYSTEM('59.3')

    def _set_user_system_text(self, w, data):
        convert = { 1:"G54", 2:"G55", 3:"G56", 4:"G57", 5:"G58", 6:"G59", 7:"G59.1", 8:"G59.2", 9:"G59.3"}
        if self._auto_label_flag:
            self.setText(convert[int(data)])

    def ChangeState(self, joint):
        if int(joint) != self._joint:
            self._block_signal = True
            self.setChecked(False)
            self._block_signal = False
            self.hal_pin.set(False)

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

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

----

== Stylesheet Property Changes Based On Events

It's possible to have widgets restyled when events change. +
You must explicitly 'polish' the widget to have PyQt redo the style. +
This is a relatively expensive function so should be used sparingly. +
This example will set the property isHomed based on LinuxCNC's homed state. +
This property can be used in the stylesheet to change stylesheet properties.

[source,python]
----
class HomeLabel(QLabel, _HalWidgetBase):
    def __init__(self, parent=None):
        super(HomeLabel, self).__init__(parent)
        self.joint_number = 0
        # for stylesheet reading
        self._isHomed = False

    def _hal_init(self):
        super(HomeLabel, self)._hal_init()
        STATUS.connect('homed', lambda w,d: self._home_status_polish(int(d), True))
        STATUS.connect('unhomed', lambda w,d: self._home_status_polish(int(d), False))

    # update ishomed property
    # polish widget so stylesheet sees the property change
    # some stylesheets color the text on home/unhome
    def _home_status_polish(self, d, state):
        if self.joint_number = d:
            self.setProperty('isHomed', state)
            self.style().unpolish(self)
            self.style().polish(self)

    # Qproperty getter and setter
    def getisHomed(self):
        return self._isHomed
    def setisHomed(self, data):
        self._isHomed = data

    # Qproperty
    isHomed = QtCore.pyqtProperty(bool, getisHomed, setisHomed)
----

Here is a sample stylesheet to change text color based on home state. +
In this case any widget based on the HomeLabel widget above will change text color. +
You would usually pick specific widgets using 'HomeLabel #specific_widget_name[homed=true]{'

----
HomeLabel[homed=true] {
    color: green;
}
HomeLabel[homed=false] {
    color: red;
}
----

== Use Stylesheets To Change Custom Widget Properties

[source,python]
----
class Label(QLabel):
    def __init__(self, parent=None):
        super(Label, self).__init__(parent)
        alternateFont0 = self.font

    # Qproperty getter and setter
    def getFont0(self):
        return self.aleternateFont0
    def setFont0(self, value):
        self.alternateFont0(value)
    # Qproperty
    styleFont0 = pyqtProperty(QFont, getFont0, setFont0)
----

Sample stylesheet that sets a custom widget property.

----
Label{
qproperty-styleFont0: "Times,12,-1,0,90,0,0,0,0,0";
}
----

== Widget Plugins

We must register our custom widget for Designer to use them. +
Here is a typical samples +
they would need to be added to qtvcp/plugins/ +
Then qtvcp/plugins/qtvcp_plugin.py would need to be adjusted to import them.

=== Gridlayout example

----
#!/usr/bin/env python3

from PyQt5 import QtCore, QtGui
from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin
from qtvcp.widgets.simple_widgets import Lcnc_GridLayout
from qtvcp.widgets.qtvcp_icons import Icon
ICON = Icon()

####################################
# GridLayout
####################################
class LcncGridLayoutPlugin(QPyDesignerCustomWidgetPlugin):
    def __init__(self, parent = None):
        QPyDesignerCustomWidgetPlugin.__init__(self)
        self.initialized = False
    def initialize(self, formEditor):
        if self.initialized:
            return
        self.initialized = True
    def isInitialized(self):
        return self.initialized
    def createWidget(self, parent):
        return Lcnc_GridLayout(parent)
    def name(self):
        return "Lcnc_GridLayout"
    def group(self):
        return "LinuxCNC - HAL"
    def icon(self):
        return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('lcnc_gridlayout')))
    def toolTip(self):
        return "HAL enable/disable GridLayout widget"
    def whatsThis(self):
        return ""
    def isContainer(self):
        return True
    def domXml(self):
        return '<widget class="Lcnc_GridLayout" name="lcnc_gridlayout" />\n'
    def includeFile(self):
        return "qtvcp.widgets.simple_widgets"
----

=== SystemToolbutton example

[source,python]
----
#!/usr/bin/env python3

from PyQt5 import QtCore, QtGui
from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin
from qtvcp.widgets.system_tool_button import SystemToolButton
from qtvcp.widgets.qtvcp_icons import Icon
ICON = Icon()

####################################
# SystemToolButton
####################################
class SystemToolButtonPlugin(QPyDesignerCustomWidgetPlugin):
    def __init__(self, parent = None):
        super(SystemToolButtonPlugin, self).__init__(parent)
        self.initialized = False
    def initialize(self, formEditor):
        if self.initialized:
            return
        self.initialized = True
    def isInitialized(self):
        return self.initialized
    def createWidget(self, parent):
        return SystemToolButton(parent)
    def name(self):
        return "SystemToolButton"
    def group(self):
        return "LinuxCNC - Controller"
    def icon(self):
        return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('systemtoolbutton')))
    def toolTip(self):
        return "Button for selecting a User Coordinate System"
    def whatsThis(self):
        return ""
    def isContainer(self):
        return False
    def domXml(self):
        return '<widget class="SystemToolButton" name="systemtoolbutton" />\n'
    def includeFile(self):
        return "qtvcp.widgets.system_tool_button"
----

=== Making a plugin with a MenuEntry dialog box

It possible to add an entry to the dialog that pops up when you right +
click the widget in the layout. This can do such things as select options +
in a more convenient way. This is the plugin used for action buttons. +

[source,python]
----
#!/usr/bin/env python3

import sip
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin, \
                QPyDesignerTaskMenuExtension, QExtensionFactory, \
                QDesignerFormWindowInterface, QPyDesignerMemberSheetExtension
from qtvcp.widgets.action_button import ActionButton
from qtvcp.widgets.qtvcp_icons import Icon
ICON = Icon()

Q_TYPEID = {
    'QDesignerContainerExtension':     'org.qt-project.Qt.Designer.Container',
    'QDesignerPropertySheetExtension': 'org.qt-project.Qt.Designer.PropertySheet',
    'QDesignerTaskMenuExtension': 'org.qt-project.Qt.Designer.TaskMenu',
    'QDesignerMemberSheetExtension': 'org.qt-project.Qt.Designer.MemberSheet'
}

####################################
# ActionBUTTON
####################################
class ActionButtonPlugin(QPyDesignerCustomWidgetPlugin):

    # The __init__() method is only used to set up the plugin and define its
    # initialized variable.
    def __init__(self, parent=None):
        super(ActionButtonPlugin, self).__init__(parent)
        self.initialized = False

    # The initialize() and isInitialized() methods allow the plugin to set up
    # any required resources, ensuring that this can only happen once for each
    # plugin.
    def initialize(self, formEditor):

        if self.initialized:
            return
        manager = formEditor.extensionManager()
        if manager:
            self.factory = ActionButtonTaskMenuFactory(manager)
            manager.registerExtensions(self.factory, Q_TYPEID['QDesignerTaskMenuExtension'])
        self.initialized = True

    def isInitialized(self):
        return self.initialized

    # This factory method creates new instances of our custom widget
    def createWidget(self, parent):
        return ActionButton(parent)

    # This method returns the name of the custom widget class
    def name(self):
        return "ActionButton"

    # Returns the name of the group in Qt Designer's widget box
    def group(self):
        return "LinuxCNC - Controller"

    # Returns the icon
    def icon(self):
        return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('actionbutton')))

    # Returns a tool tip short description
    def toolTip(self):
        return "Action button widget"

    # Returns a short description of the custom widget for use in a "What's
    # This?" help message for the widget.
    def whatsThis(self):
        return ""

    # Returns True if the custom widget acts as a container for other widgets;
    def isContainer(self):
        return False

    # Returns an XML description of a custom widget instance that describes
    # default values for its properties.
    def domXml(self):
        return '<widget class="ActionButton" name="actionbutton" />\n'

    # Returns the module containing the custom widget class. It may include
    # a module path.
    def includeFile(self):
        return "qtvcp.widgets.action_button"


class ActionButtonDialog(QtWidgets.QDialog):

   def __init__(self, widget, parent = None):

      QtWidgets.QDialog.__init__(self, parent)

      self.widget = widget

      self.previewWidget = ActionButton()

      buttonBox = QtWidgets.QDialogButtonBox()
      okButton = buttonBox.addButton(buttonBox.Ok)
      cancelButton = buttonBox.addButton(buttonBox.Cancel)

      okButton.clicked.connect(self.updateWidget)
      cancelButton.clicked.connect(self.reject)

      layout = QtWidgets.QGridLayout()
      self.c_estop = QtWidgets.QCheckBox("Estop Action")
      self.c_estop.setChecked(widget.estop )
      layout.addWidget(self.c_estop)

      layout.addWidget(buttonBox, 5, 0, 1, 2)
      self.setLayout(layout)

      self.setWindowTitle(self.tr("Set Options"))

   def updateWidget(self):

      formWindow = QDesignerFormWindowInterface.findFormWindow(self.widget)
      if formWindow:
          formWindow.cursor().setProperty("estop_action",
              QtCore.QVariant(self.c_estop.isChecked()))
      self.accept()

class ActionButtonMenuEntry(QPyDesignerTaskMenuExtension):

    def __init__(self, widget, parent):
        super(QPyDesignerTaskMenuExtension, self).__init__(parent)
        self.widget = widget
        self.editStateAction = QtWidgets.QAction(
          self.tr("Set Options..."), self)
        self.editStateAction.triggered.connect(self.updateOptions)

    def preferredEditAction(self):
        return self.editStateAction

    def taskActions(self):
        return [self.editStateAction]

    def updateOptions(self):
        dialog = ActionButtonDialog(self.widget)
        dialog.exec_()

class ActionButtonTaskMenuFactory(QExtensionFactory):
    def __init__(self, parent = None):
        QExtensionFactory.__init__(self, parent)

    def createExtension(self, obj, iid, parent):

        if not isinstance(obj, ActionButton):
            return None
        if iid == Q_TYPEID['QDesignerTaskMenuExtension']:
            return ActionButtonMenuEntry(obj, parent)
        elif iid == Q_TYPEID['QDesignerMemberSheetExtension']:
            return ActionButtonMemberSheet(obj, parent)
        return None
----

// vim: set syntax=asciidoc:
