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 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.
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 is a WYSIWYG (What You See is What You Get) editor for placing 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:
-
HAL only 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. Initialization Process
QtVCP macht extra setup für Widgets, die Spezialisierungen (Unterklassen) von _HALWidgetBase
sind, auch bekannt als "HAL-ified" Widgets.
This includes:
-
Injecting important variables,
-
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:
-
Es wird nach allen HAL-ifizierten Widgets gesucht.
-
Er findet das Widget
ScreenOptions
, um Informationen zu sammeln, die es in die anderen Widgets einspeisen muss -
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:-
Fügt Variablen wie die Einstellungsdatei zu jedem HAL-ifizierten Widget hinzu.
-
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
-
The HAL component instance
-
self.HAL_NAME
-
This widget’s name as a 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. Custom 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:
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:
-
Die QtWidgets-Bibliothek von PyQt,
-
LinuxCNC’s HAL library, and
-
QtVCP’s widget
baseclass
's_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._HalToggleBase
, and_HalScaleBase
.
Here is a custom widget based on PyQt’s QGridLayout
widget.
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:
-
Dies definiert den Klassennamen und die Bibliotheken, von denen sie erbt.
Diese Klasse, genanntLcnc_GridLayout
, erbt die Funktionen vonQWidget
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. -
Dies ist die Funktion, die aufgerufen wird, wenn das Widget zum ersten Mal erstellt wird (d.h. instanziiert wird) - das ist ziemlich standardmäßig.
-
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. In The Imports Section
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>
We import
-
pyqtProperty
, damit wir mit dem Qt Designer-Editor interagieren können, -
LED
, weil unser benutzerdefiniertes Widget darauf basiert, -
Status
weil es uns Statusmeldungen von LinuxCNC gibt.
3.2. Im Abschnitt Bibliotheken instanziieren
Here we create the Status
library instance:
########################################### # **** 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.
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
-
Definiert den Namen unseres benutzerdefinierten Widgets und von welcher anderen Klasse es erbt.
In diesem Fall erben wirLED
- ein QtVCP-Widget, das eine Statusleuchte darstellt. -
Typisch für die meisten Widgets - wird aufgerufen, wenn das Widget zum ersten Mal erstellt wird.
-
Typisch für die meisten Widgets - ruft den Initialisierungscode des übergeordneten (Super-)Widgets auf.
Dann setzen wir einige Attribute:
-
Geerbt von
Lcnc_Led
- wir setzen ihn hier, damit kein HAL-Pin erstellt wird. -
Geerbt von
Lcnc_led
- wir setzen es, um sicherzustellen, dass die LED aus ist.
The other attributes are for the selectable options of our widget.
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.
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()+
is called on each widget that inherits +_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 dieser Funktion haben Sie Zugang zu einigen zusätzlichen Informationen (obwohl wir sie in diesem Beispiel nicht verwenden):
-
self.HAL_GCOMP
-
the HAL component instance
-
self.HAL_NAME
-
This widget’s name as a 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 dasGObject
-Nachrichtensystem, um Nachrichten an Widgets zu senden, die sich dafür registrieren.
Diese Zeile ist der Registrierungsprozess. -
state-estop
is the message we wish to listen for and act on. There are many messages available. -
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 Funktionself._flip_state(True)
auf.
Lambda wurde verwendet, um das (w
) Objekt vor dem Aufruf der Funktionself._flip_state
zu strippen.
Es erlaubte auch die Verwendung, umself._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)
The function called looks like this:
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.
It changes:
-
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:
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. 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"
7.2. SystemToolbutton Example
#!/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"
It possible to add an entry to the dialog that pops up when you right click the widget in the layout.
This can do things such as selecting options in a more convenient way.
This is the plugin used for action buttons.
#!/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