1. Übersicht

Die Erstellung von benutzerdefinierten Widgets ermöglicht es, den Qt Designer-Editor zu verwenden, um ein benutzerdefiniertes Widget zu platzieren, anstatt dies manuell in einer Handler-Datei zu tun.

Ein nützliches benutzerdefiniertes Widget wäre eine großartige Möglichkeit, zu LinuxCNC beizutragen.

1.1. Widgets

Widget ist der allgemeine Name für die UI-Objekte wie Buttons und Beschriftungen in PyQt.

Es gibt auch spezielle Widgets für LinuxCNC zur Erleichterung der Integration.

Alle diese Widgets können mit Qt Designer Editor platziert werden - so dass man das Ergebnis sehen kann, bevor man das Panel in LinuxCNC lädt.

1.2. Qt Designer

Qt Designer ist ein WYSIWYG (What You See is What You Get) Editor zum Platzieren von PyQt-Widgets.

Die ursprüngliche Absicht war es, die grafischen Widgets für Programme zu erstellen.
Wir nutzen es, um Bildschirme und Panels für LinuxCNC zu bauen.

In Qt Designer, auf der linken Seite des Editors, finden Sie drei Kategorien von LinuxCNC Widgets:

  • Nur HAL Widgets.

  • LinuxCNC-Controller-Widgets.

  • Dialog-Widgets.

Damit Qt Designer benutzerdefinierte Widgets zu seinem Editor hinzufügen kann, muss ein Plugin zum richtigen Ordner hinzugefügt werden.

1.3. Initialisierungsprozess

QtVCP macht extra setup für Widgets, die Spezialisierungen (Unterklassen) von _HALWidgetBase sind, auch bekannt als "HAL-ified" Widgets.

Dies umfasst:

  • Injizieren wichtiger Variablen,

  • Aufruf einer extra Setup-Funktion

  • Aufruf einer schließenden Aufräumfunktion beim Herunterfahren.

Diese Funktionen werden nicht aufgerufen, wenn der Qt Designer Editor die Widgets anzeigt.

Wenn QtVCP einen Bildschirm aus der .ui-Datei erstellt:

  1. Es wird nach allen HAL-ifizierten Widgets gesucht.

  2. Er findet das Widget ScreenOptions, um Informationen zu sammeln, die es in die anderen Widgets einspeisen muss

  3. Er instanziiert jedes Widget und ruft, wenn es ein HAL-ifiziertes Widget ist, die Funktion hal_init() auf.
    hal_init() ist in der Basisklasse definiert und sie:

    1. Fügt Variablen wie die Einstellungsdatei zu jedem HAL-ifizierten Widget hinzu.

    2. Ruft +_hal_init()+ für das Widget auf.
      +_hal_init()+ erlaubt es dem Widget-Designer, Einstellungen vorzunehmen, die den Zugriff auf zusätzliche Variablen erfordern.

Hier finden Sie eine Beschreibung der zusätzlichen Variablen, die in "HAL-ifizierte" Widgets eingefügt werden:

self.HAL_GCOMP

Die HAL-Komponenten-Instanz

self.HAL_NAME

Der Name dieses Widgets als String

self.QT_OBJECT_

Diese Objektinstanz des Widgets

self.QTVCP_INSTANCE_

Die übergeordnete Ebene des Bildschirms

self.PATHS_

Die Instanz der QtVCP-Pfadbibliothek

self.PREFS_

Die optionale Instanz der Einstellungsdatei

self.SETTINGS_

Die Qsettings Objektinstanz

1.4. Aufräum-Prozess

Wenn QtVCP geschlossen wird, ruft es die +_hal_cleanup()+ Funktion auf allen HAL-ifizierten Widgets auf.

Die Basisklasse erstellt eine leere Funktion +_hal_cleanup()+, die in der Unterklasse des benutzerdefinierten Widgets neu definiert werden kann.

Damit können Sie z. B. Präferenzen aufzeichnen usw.

Diese Funktion wird nicht aufgerufen, wenn der Qt Designer Editor die Widgets anzeigt.

2. Benutzerdefinierte HAL-Widgets

HAL-Widgets sind die einfachsten, um ein Beispiel zu zeigen.
Die Datei qtvcp/widgets/simple_widgets.py enthält viele reine HAL-Widgets.

Sehen wir uns einen Ausschnitt aus simple_widgets.py an:

Im Abschnitt "Imports"

Hier importieren wir die Bibliotheken, die unsere Widget-Klasse benötigt.

#!/usr/bin/env python3

###############################
# Imports
###############################
from PyQt5 import QtWidgets                     # <1>
from qtvcp.widgets.widget_baseclass \
    import _HalWidgetBase, _HalSensitiveBase    # <2>
import hal                                      # <3>

In diesem Fall benötigen wir Zugang zu:

  1. Die QtWidgets-Bibliothek von PyQt,

  2. LinuxCNCs HAL-Bibliothek, und

  3. QtVCP’s widget baseclass 's _HalSensitiveBase für automatisches HAL-Pin-Setup und zum Deaktivieren/Aktivieren des Widgets (auch bekannt als Eingangsempfindlichkeit).
    Es gibt auch _HalToggleBase, und _HalScaleBase Funktionen in der Bibliothek._HalToggleBase, und _HalScaleBase.

Im Abschnitt WIDGET

Hier ist ein angepasstes Widget (engl. custom widget), das auf dem QGridLayout Widget von PyQt basiert.

QGridLayout erlaubt es,:

  • Objekte in einem Raster zu positionieren.

  • Alle darin enthaltenen Widgets zu aktivieren/deaktivieren, basierend auf einem HAL-Pin-Status.

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

class Lcnc_GridLayout(QtWidgets.QWidget, _HalSensitiveBase):    # <1>
    def __init__(self, parent = None):                          # <2>
        super(GridLayout, self).__init__(parent)                # <3>

Zeile für Zeile:

  1. Dies definiert den Klassennamen und die Bibliotheken, von denen sie erbt.
    Diese Klasse, genannt Lcnc_GridLayout, erbt die Funktionen von QWidget und +_HalSensitiveBase+.
    +_HalSensitiveBase+ ist eine Unterklasse von +_HalWidgetBase+, der Basisklasse der meisten QtVCP-Widgets, d.h. sie hat alle Funktionen von +_HalWidgetBase+ plus die Funktionen von +_HalSensitiveBase+.
    Sie fügt die Funktion hinzu, um das Widget basierend auf einem HAL-Eingangs-BIT-Pin zu aktivieren oder zu deaktivieren.

  2. Dies ist die Funktion, die aufgerufen wird, wenn das Widget zum ersten Mal erstellt wird (d.h. instanziiert wird) - das ist ziemlich standardmäßig.

  3. Diese Funktion initialisiert die Super-Klassen unseres Widgets.
    Super" bedeutet einfach die vererbten Basisklassen, d.h. QWidget und _HalSensitiveBase.
    Außer dem Namen des Widgets wird sich nichts ändern.

3. Benutzerdefinierte Controller-Widgets mit STATUS

Widget, die mit LinuxCNC-Controller interagieren sind nur ein wenig komplizierter und sie erfordern einige extra Bibliotheken.

In diesem reduzierten Beispiel werden wir Eigenschaften hinzufügen, die im Qt Designer geändert werden können.

Dieses LED-Anzeige-Widget reagiert auf wählbare Zustände der LinuxCNC-Steuerung.

#!/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)

    #########################################################################
    # Qt 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

    #######################################
    # Qt 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)

3.1. Im Abschnitt "Imports"

Hier importieren wir die Bibliotheken, die unsere Widget-Klasse benötigt.

#!/usr/bin/env python3

###############################
# Imports
###############################
from PyQt5.QtCore import pyqtProperty       # <1>
from qtvcp.widgets.led_widget import LED    # <2>
from qtvcp.core import Status               # <3>

Wir importieren

  1. pyqtProperty, damit wir mit dem Qt Designer-Editor interagieren können,

  2. LED, weil unser benutzerdefiniertes Widget darauf basiert,

  3. Status weil es uns Statusmeldungen von LinuxCNC gibt.

3.2. Im Abschnitt Bibliotheken instanziieren

Hier erstellen wir die Bibliotheksinstanz Status:

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

Typischerweise haben wir die Bibliothek außerhalb der Widget-Klasse instanziiert, so dass der Verweis auf sie global ist - was bedeutet, dass Sie nicht self. davor verwenden müssen.

Konventionell verwenden wir große Buchstaben im Namen für globale Verweise.

3.3. Im Abschnitt "Benutzerdefinierte Widget-Klassendefinition"

Dies ist das Herzstück unseres benutzerdefinierten Widgets.

Klassendefinition und Instanzinitialisierungsfunktion
class StateLed(LED):                            # <1>
    def __init__(self, parent=None):            # <2>
        super(StateLed, self).__init__(parent)  # <3>
        self.has_hal_pins = False               # <4>
        self.setState(False)                    # <5>
        self.is_estopped = False
        self.is_on = False
        self.invert_state = False
  1. Definiert den Namen unseres benutzerdefinierten Widgets und von welcher anderen Klasse es erbt.
    In diesem Fall erben wir LED - ein QtVCP-Widget, das eine Statusleuchte darstellt.

  2. Typisch für die meisten Widgets - wird aufgerufen, wenn das Widget zum ersten Mal erstellt wird.

  3. Typisch für die meisten Widgets - ruft den Initialisierungscode des übergeordneten (Super-)Widgets auf.

    Dann setzen wir einige Attribute:

  4. Geerbt von Lcnc_Led - wir setzen ihn hier, damit kein HAL-Pin erstellt wird.

  5. Geerbt von Lcnc_led - wir setzen es, um sicherzustellen, dass die LED aus ist.

Die anderen Attribute sind für die auswählbaren Optionen unseres Widgets.

Die HAL-Initialisierungsfunktion des Widgets
    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))

Diese Funktion verbindet STATUS (LinuxCNC-Statusmeldungsbibliothek) mit unserem Widget, so dass die LED je nach dem gewählten Status des Controllers ein- oder ausgeschaltet wird.

Wir haben zwei Zustände, zwischen denen wir wählen können: is_estopped oder is_on.
Je nachdem, welcher Zustand aktiv ist, wird unser Widget mit den entsprechenden STATUS-Meldungen verbunden.

+_hal_init()+ wird _auf jedem Widget aufgerufen, das +_HalWidgetBase+ erbt, wenn QtVCP zum ersten Mal den Bildschirm aufbaut.
Sie fragen sich vielleicht, warum sie bei diesem Widget aufgerufen wird, da wir +_HalWidgetBase+ nicht in unserer Klassendefinition haben (class Lcnc_State_Led(Lcnc_Led):) - es wird aufgerufen, weil Lcnc_Led +_HalWidgetBase+ erbt.

In dieser Funktion haben Sie Zugang zu einigen zusätzlichen Informationen (obwohl wir sie in diesem Beispiel nicht verwenden):

self.HAL_GCOMP

Die HAL-Komponenten-Instanz

self.HAL_NAME

Der Name dieses Widgets als String

self.QT_OBJECT_

dieses Widgets PyQt-Objekt als Instanz

self.QTVCP_INSTANCE_

Die übergeordnete Ebene des Bildschirms

self.PATHS_

Die _Instanz der Pfadbibliothek von QtVCP

self.PREFS_

die Instanz einer optionalen Präferenzdatei

self.SETTINGS_

das Qsettings Objekt

Wir könnten diese Informationen nutzen, um HAL-Pins zu erstellen oder Bildpfade nachzuschlagen usw.

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

Schauen wir uns diese Zeile genauer an:

  • STATUS ist ein sehr verbreitetes Thema beim Erstellen von Widgets.
    STATUS verwendet das GObject-Nachrichtensystem, um Nachrichten an Widgets zu senden, die sich dafür registrieren.
    Diese Zeile ist der Registrierungsprozess.

  • state-estop ist die Nachricht, auf die wir hören und auf die wir reagieren wollen. Es sind viele Nachrichten verfügbar.

  • lambda w:self._flip_state(True)` ist das, was passiert, wenn die Nachricht abgefangen wird.
    Die Lambda-Funktion akzeptiert die Widget-Instanz (w), die GObject ihr sendet und ruft dann die Funktion self._flip_state(True) auf.
    Lambda wurde verwendet, um das (w) Objekt vor dem Aufruf der Funktion self._flip_state zu strippen.
    Es erlaubte auch die Verwendung, um self._flip_state() den Zustand True zu senden.

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

Dies ist die Funktion, die den Zustand der LED tatsächlich umschaltet.
Sie wird aufgerufen, wenn die entsprechende STATUS-Meldung akzeptiert wird.

STATUS.connect('current-feed-rate', self._set_feedrate_text)

Die aufgerufene Funktion sieht wie folgt aus:

    def _set_feedrate_text(self, widget, data):

in dem das Widget und alle Daten von der Funktion akzeptiert werden müssen.

3.3.1. Im Abschnitt Designer Properties Setter/Getters/Resetters

    #########################################################################
    # Qt 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

Dies ist die Art und Weise, wie Qt Designer die Attribute des Widgets setzt.
Dies kann auch direkt im Widget aufgerufen werden.

3.3.2. Im Abschnitt "Designereigenschaften"

    #######################################
    # Qt 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)

Dies ist die Registrierung von Eigenschaften in Qt Designer.

Der Eigenschaftsname:

  • ist der Text, der in Qt Designer verwendet wird,

  • kann nicht mit den Attributen identisch sein, die sie darstellen.

Diese Eigenschaften werden im Qt Designer in der Reihenfolge angezeigt, in der sie hier erscheinen.

4. Benutzerdefinierte Controller-Widgets mit Aktionen

Hier ist ein Beispiel für ein Widget, welches das Benutzerreferenzsystem festlegt.

Es ändert sich:

  • den Zustand der Maschinensteuerung mit Hilfe der Bibliothek ACTION,

  • ob die Schaltfläche mit Hilfe der STATUS-Bibliothek angeklickt werden kann oder nicht.

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

# Instanziieren Sie die Bibliotheken mit einer globalen Referenz.
# STATUS gibt uns Statusmeldungen von LinuxCNC
# INFO enthält INI-Details
# ACTION gibt Befehle an LinuxCNC
STATUS = Status()
INFO = Info()
ACTION = Aktion()

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)

5. Stylesheet-Eigenschaftsänderungen auf der Grundlage von Ereignissen

Es ist möglich, Widgets bei Ereignisänderungen neu zu gestalten.

Sie müssen das Widget explizit _"polieren", damit PyQt den Stil wiederherstellt.
Dies ist eine relativ teure Funktion und sollte daher sparsam verwendet werden.

Dieses Beispiel setzt eine "isHomed"-Eigenschaft basierend auf dem "homed"-Zustand von LinuxCNC und verwendet diese wiederum, um Stylesheet-Eigenschaften zu ändern:

Dieses Beispiel setzt die Eigenschaft isHomed basierend auf LinuxCNC’s homed (engl. für referenziert) Zustandsangabe.
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))

    # ishomed-Eigenschaft aktualisieren
    # Widget polieren, damit Stylesheet die Änderung der Eigenschaft sieht
    # einige Stylesheets färben den Text bei 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)

Hier ist ein Beispiel-Stylesheet zum Ändern der Textfarbe basierend auf dem Home-Status.

In diesem Fall wird jedes Widget, das auf dem obigen HomeLabel-Widget basiert, die Textfarbe ändern.
Normalerweise würden Sie bestimmte Widgets mit HomeLabel #specific_widget_name[homed=true] auswählen:

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

6. Verwenden von Stylesheets zum Ändern benutzerdefinierter Widget-Eigenschaften

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)

Beispiel-Stylesheet, das eine benutzerdefinierte Widget-Eigenschaft festlegt.

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

7. Widget-Plugins

Wir müssen unsere benutzerdefinierten Widgets registrieren, damit Qt Designer sie verwenden kann.

Hier sind ein paar typische Beispiele.
Sie müssten zu qtvcp/plugins/ hinzugefügt werden.
Dann müsste qtvcp/plugins/qtvcp_plugin.py angepasst werden, um sie zu importieren.

7.1. Beispiel für ein Raster (engl. grid)-Layout

#!/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"

7.2. SystemToolbutton Beispiel

#!/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"

7.3. Erstellen eines Plugins mit einem Dialogfeld "MenuEntry"

Es ist möglich, einen Eintrag in den Dialog einzufügen, der erscheint, wenn Sie mit der rechten Maustaste auf das Widget im Layout klicken.

So können Sie beispielsweise Optionen auf bequemere Weise auswählen.

Dies ist das Plugin, das für action buttons verwendet wird.

#!/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):

    # Die Methode __init__() wird nur verwendet, um das Plugin einzurichten und seine
    # initialisierte Variable zu definieren.
    def __init__(self, parent=None):
        super(ActionButtonPlugin, self).__init__(parent)
        self.initialized = False

    # Die Methoden initialize() und isInitialized() erlauben es dem Plugin, # alle benötigten Ressourcen einzurichten,
    # wobei sichergestellt wird, dass dies nur einmal für jedes
    # Plugin geschehen kann.
    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

    # Diese Factory-Methode erstellt neue Instanzen unseres benutzerdefinierten Widgets
    def createWidget(self, parent):
        return ActionButton(parent)

    # Diese Methode gibt den Namen der benutzerdefinierten Widget-Klasse zurück.
    def name(self):
        return "ActionButton"

    # Gibt den Namen der Gruppe in der Widgetbox von Qt Designer zurück
    def group(self):
        return "LinuxCNC - Controller"

    # Gibt das Icon zurück
    def icon(self):
        return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('actionbutton')))

    # Gibt eine Kurzbeschreibung des Tooltips zurück
    def toolTip(self):
        return "Aktionsschaltflächen-Widget"

    # Gibt eine kurze Beschreibung des benutzerdefinierten Widgets zur Verwendung in einer
    # "Was ist das?"-Hilfemitteilung für das Widget.
    def whatsThis(self):
        return ""

    # Gibt True zurück, wenn das benutzerdefinierte Widget als Container für andere Widgets fungiert;
    def isContainer(self):
        return False

    # Gibt eine XML-Beschreibung einer benutzerdefinierten Widget-Instanz zurück, welche die
    # Standardwerte für seine Eigenschaften beschreibt.
    def domXml(self):
        return '<widget class="ActionButton" name="actionbutton" />\n'

    # Gibt das Modul zurück, das die benutzerdefinierte Widget-Klasse enthält. Es kann
    # einen Modulpfad enthalten.
    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