1. Огляд

Створення власних віджетів дозволяє використовувати редактор Qt Designer для розміщення власного віджета, замість того, щоб робити це вручну у файлі обробника.

Корисні користувацькі віджети були б чудовим способом зробити свій внесок у LinuxCNC.

1.1. Віджети

Віджет — це загальна назва для об’єктів інтерфейсу користувача, таких як кнопки та мітки в PyQt.

Також існують спеціальні віджети, створені для LinuxCNC, які спрощують інтеграцію.

Всі ці віджети можна розмістити за допомогою редактора Qt Designer, що дозволяє побачити результат перед фактичним завантаженням панелі в LinuxCNC.

1.2. Дизайнер Qt

Qt Designer — це WYSIWYG (що бачиш, те й отримуєш) редактор для розміщення віджетів PyQt.

Спочатку його метою було створення графічних віджетів для програм.
Ми використовуємо його для створення екранів та панелей для LinuxCNC.

У Qt Designer, у лівій частині редактора, ви знайдете три категорії віджетів LinuxCNC:

  • Віджети лише для HAL.

  • Віджети контролера LinuxCNC.

  • діалогові віджети.

Щоб Qt Designer міг додавати власні віджети до свого редактора, потрібно додати плагін до потрібної папки.

1.3. Процес ініціалізації

QtVCP виконує додаткове налаштування для віджетів, підкласованих з _HALWidgetBase, тобто віджетів, "підібраних HAL".

Це включає:

  • Введення важливих змінних,

  • Виклик додаткової функції налаштування

  • Виклик функції очищення під час завершення роботи.

Ці функції не викликаються, коли редактор Qt Designer відображає віджети.

Коли QtVCP створює екран з файлу .ui:

  1. Він шукає всі HAL-подібні віджети.

  2. Він знаходить віджет ScreenOptions для збору інформації, необхідної для вставки в інші віджети

  3. Він створює екземпляр кожного віджета, і якщо це HAL-подібний віджет, викликає функцію hal_init().
    Функція hal_init() визначена в базовому класі та:

    1. Додає змінні, такі як файл налаштувань, до кожного HAL-подібного віджета.

    2. Викличте +_hal_init()+ для віджета.
      +_hal_init()+ дозволяє розробнику віджета виконувати налаштування, що потребують доступу до додаткових змінних.

Ось опис додаткових змінних, введених у віджети, "підібрані до HAL":

self.HAL_GCOMP

Екземпляр компонента HAL

self.HAL_NAME

Назва цього віджета у вигляді рядка

self.QT_OBJECT_

Цей екземпляр об’єкта віджета

self.QTVCP_INSTANCE_

Найвищий рівень батьківського елемента екрана

self.PATHS_

Екземпляр бібліотеки шляхів QtVCP

self.PREFS_

Екземпляр додаткового файлу налаштувань

self.SETTINGS_

Екземпляр об’єкта Qsettings

1.4. процес очищення

Коли QtVCP закривається, він викликає функцію +_hal_cleanup()+ на всіх HAL-подібних віджетах.

Базовий клас створює порожню функцію +_hal_cleanup()+, яку можна перевизначити в підкласі користувацького віджета.

Це можна використовувати для таких дій, як налаштування записів тощо.

Ця функція не викликається, коли редактор Qt Designer відображає віджети.

2. Користувацькі віджети HAL

Найпростішим прикладом є віджети HAL.
Файл qtvcp/widgets/simple_widgets.py містить багато віджетів лише HAL.

Давайте розглянемо фрагмент simple_widgets.py:

У розділі «Імпорт»

Тут ми імпортуємо бібліотеки, необхідні нашому класу віджетів.

#!/usr/bin/env python3

###############################
# Імпорт
###############################
from PyQt5 import QtWidgets                     # <1>
from qtvcp.widgets.widget_baseclass \
    import _HalWidgetBase, _HalSensitiveBase    # <2>
import hal                                      # <3>

У цьому випадку нам потрібен доступ до:

  1. Бібліотека QtWidgets від PyQt,

  2. Бібліотека HAL від LinuxCNC та

  3. Віджет QtVCP baseclass 's _HalSensitiveBase для автоматичної настройки контактів HAL та для відключення/включення віджета (також відомого як чутливість вводу).
    У бібліотеці також доступні функції _HalToggleBase та _HalScaleBase.

У розділі «ВІДЖЕТ»

Ось користувацький віджет, заснований на віджеті QGridLayout від PyQt.

QGridLayout дозволяє:

  • Розташуйте об’єкти у вигляді сітки.

  • Увімкнути/вимкнути всі віджети всередині на основі стану виводу HAL.

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

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

Рядок за рядком:

  1. Це визначає ім’я класу та бібліотеки, від яких він успадковує.
    Цей клас, названий Lcnc_GridLayout, успадковує функції QWidget та +_HalSensitiveBase+.
    +_HalSensitiveBase+ є «підкласом» +_HalWidgetBase+, базовим класом більшості віджетів QtVCP, що означає, що він має всі функції +_HalWidgetBase+ плюс функції +_HalSensitiveBase+.
    Він додає функцію, яка дозволяє вмикати або вимикати віджет на основі вхідного BIT-контакту HAL.

  2. Це функція, яка викликається, коли віджет вперше створюється (тобто створюється його екземпляр) – це досить стандартно.

  3. Ця функція ініціалізує Super класи нашого віджета.
    Super означає просто успадковані базові класи, тобто QWidget і _HalSensitiveBase.
    Досить стандартно, за винятком того, що змінюється назва віджета.

3. Віджети користувацького контролера з використанням STATUS

Віджети, що взаємодіють з контролером LinuxCNC, лише трохи складніші та потребують деяких додаткових бібліотек.

У цьому скороченому прикладі ми додамо властивості, які можна змінити в Qt Designer.

Цей віджет світлодіодного індикатора реагуватиме на вибрані стани контролера LinuxCNC.

#!/usr/bin/env python3

###############################
# Імпорт
###############################
from PyQt5.QtCore import pyqtProperty
from qtvcp.widgets.led_widget import LED
from qtvcp.core import Status

###########################################
# **** розділ створення екземплярів бібліотек **** #
###########################################
STATUS = Status()

##########################################
# визначення класу користувацького віджета
##########################################
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
    ########################################################################

    # інвертований статус
    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

    # стан машини зупинено
    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

    # стан машини увімкнено
    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
    #######################################
    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. У розділі «Імпорт»

Тут ми імпортуємо бібліотеки, необхідні нашому класу віджетів.

#!/usr/bin/env python3

###############################
# Імпорт
###############################
from PyQt5.QtCore import pyqtProperty       # <1>
from qtvcp.widgets.led_widget import LED    # <2>
from qtvcp.core import Status               # <3>

Ми імпортуємо

  1. pyqtProperty щоб ми могли взаємодіяти з редактором Qt Designer,

  2. LED оскільки наш власний віджет базується на ньому,

  3. Status тому що він надає нам повідомлення про стан від LinuxCNC.

3.2. У розділі «Створення миттєвих бібліотек»

Тут ми створюємо екземпляр бібліотеки Status:

###########################################
# **** розділ створення екземплярів бібліотек **** #
###########################################
STATUS = Status()

Зазвичай ми створювали екземпляр бібліотеки поза класом віджета, щоб посилання на неї було глобальним, тобто вам не потрібно було використовувати self перед ним.

За домовленістю ми використовуємо всі великі літери в назві для глобальних посилань.

3.3. У розділі «Визначення класу користувацького віджета»

Це суть нашого власного віджета.

Визначення класу та функція ініціалізації екземпляра
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. Визначає ім’я нашого користувацького віджета та від якого іншого класу він успадковується.
    У цьому випадку ми успадковуємо LED - віджет QtVCP, який представляє індикатор стану.

  2. Типово для більшості віджетів — викликається, коли віджет вперше створюється.

  3. Типово для більшості віджетів — викликає код ініціалізації батьківського (супер) віджета.

    Потім ми встановлюємо деякі атрибути:

  4. Успадковано від Lcnc_Led - ми встановлюємо його тут, щоб не створювати контакт HAL.

  5. Успадковано від Lcnc_led — ми встановлюємо його, щоб переконатися, що світлодіод вимкнено.

Інші атрибути стосуються опцій, які можна вибрати для нашого віджета.

Функція ініціалізації HAL віджета
    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))

Ця функція підключає STATUS (бібліотеку повідомлень про стан LinuxCNC) до нашого віджета, щоб світлодіод вмикався або вимикався залежно від вибраного стану контролера.

Ми можемо вибрати два стани: is_estopped або is_on.
Залежно від того, який із них активний, наш віджет підключається до відповідних повідомлень STATUS.

+_hal_init()+ викликається для кожного віджета, який успадковує +_HalWidgetBase+, коли QtVCP вперше будує екран.
Ви можете запитати, чому вона викликається для цього віджета, оскільки ми не мали +_HalWidgetBase+ у нашому визначенні класу (class Lcnc_State_Led(Lcnc_Led):) — вона викликається, оскільки Lcnc_Led успадковує +_HalWidgetBase+.

У цій функції ви маєте доступ до деякої додаткової інформації (хоча ми не використовуємо її в цьому прикладі):

self.HAL_GCOMP

екземпляр компонента HAL

self.HAL_NAME

Назва цього віджета у вигляді рядка

self.QT_OBJECT_

Екземпляр об’єкта PyQt цього віджета

self.QTVCP_INSTANCE_

Найвищий рівень батьківського елемента екрана

self.PATHS_

Екземпляр бібліотеки path у QtVCP

self.PREFS_

екземпляр додаткового файлу налаштувань

self.SETTINGS_

об’єкт Qsettings

Ми могли б використовувати цю інформацію для створення HAL-пінів або пошуку шляхів до зображень тощо.

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

Давайте розглянемо цей рядок уважніше:

  • STATUS — дуже поширена тема для створення віджетів.
    STATUS використовує систему повідомлень GObject для надсилання повідомлень віджетам, які реєструються в ньому.
    Цей рядок відповідає за процес реєстрації.

  • «state-estop» – це повідомлення, яке ми хочемо прослухати та вжити заходів. Існує багато доступних повідомлень.

  • lambda w:self._flip_state(True) — це те, що відбувається, коли повідомлення перехоплюється.
    Функція lambda приймає екземпляр віджета (w), який надсилає GObject, а потім викликає функцію self._flip_state(True).
    Lambda використовувалася для видалення об’єкта (w) перед викликом функції self._flip_state.
    Вона також дозволяла надсилати self._flip_state() стан True.

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

Це функція, яка фактично змінює стан світлодіода.
Вона викликається, коли прийнято відповідне повідомлення STATUS.

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

Викликана функція виглядає так:

    def _set_feedrate_text(self, widget, data):

в якому віджет та будь-які дані повинні бути прийняті функцією.

3.3.1. У розділі «Властивості дизайнера: сеттери/отримання/скидання»

    #########################################################################
    # Встановлювачі/геттери/скидання властивостей Qt Designer
    ########################################################################

    # інвертований статус
    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

    # стан машини зупинено
    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

    # стан машини увімкнено
    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 встановлює атрибути віджета.
Це також можна викликати безпосередньо у віджеті.

3.3.2. У розділі «Властивості конструктора»

    #######################################
    # Властивості Qt Designer
    #######################################
    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)

Це реєстрація властивостей у Qt Designer.

Назва власності:

  • це текст, що використовується в Qt Designer,

  • не може бути таким самим, як атрибути, які вони представляють.

Ці властивості відображаються в Qt Designer у порядку, в якому вони тут з’являються.

4. Віджети налаштованого контролера з діями

Ось приклад віджета, який встановлює систему відліку користувача.

Це змінюється:

  • стан контролера машини за допомогою бібліотеки ACTION,

  • чи можна натиснути кнопку, використовуючи бібліотеку STATUS.

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

# Інстанціювання бібліотек із глобальним посиланням
# STATUS надає нам повідомлення про стан від LinuxCNC
# INFO містить деталі INI
# ACTION надає команди 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)

    ##############################
    # код необхідного класу котла #
    ##############################

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

5. Зміни властивостей таблиці стилів на основі подій

Можна змінити стиль віджетів у разі зміни подій.

Ви повинні явно "відполірувати" віджет, щоб PyQt переробив стиль.
Це відносно дорога функція, тому її слід використовувати економно.

У цьому прикладі встановлюється властивість isHomed на основі стану homed у LinuxCNC та, у свою чергу, використовується для зміни властивостей таблиці стилів:

У цьому прикладі властивість isHomed буде встановлено на основі стану homed у LinuxCNC.
class HomeLabel(QLabel, _HalWidgetBase):
    def __init__(self, parent=None):
        super(HomeLabel, self).__init__(parent)
        self.joint_number = 0
        # для читання таблиць стилів
        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
    # полірувати віджет, щоб таблиця стилів бачила зміну властивості
    # деякі таблиці стилів забарвлюють текст на 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
    def getisHomed(self):
        return self._isHomed
    def setisHomed(self, data):
        self._isHomed = data

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

Ось зразок таблиці стилів для зміни кольору тексту залежно від штату.

У цьому випадку будь-який віджет, заснований на віджеті HomeLabel вище, змінить колір тексту.
Зазвичай ви вибираєте певні віджети за допомогою HomeLabel #specific_widget_name[homed=true]:

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

6. Використання таблиць стилів для зміни властивостей власного віджета

class Label(QLabel):
    def __init__(self, parent=None):
        super(Label, self).__init__(parent)
        alternateFont0 = self.font

    # Геттер та сеттер Qproperty
    def getFont0(self):
        return self.aleternateFont0
    def setFont0(self, value):
        self.alternateFont0(value)
    # Qproperty
    styleFont0 = pyqtProperty(QFont, getFont0, setFont0)

Зразок таблиці стилів, яка встановлює властивість власного віджета.

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

7. Плагіни віджетів

Ми повинні зареєструвати наш користувацький віджет, щоб Qt Designer міг його використовувати.

Ось типові приклади.
Їх потрібно додати до qtvcp/plugins/.
Потім qtvcp/plugins/qtvcp_plugin.py потрібно буде налаштувати для їх імпорту.

7.1. Приклад сітки

#!/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. Приклад кнопки SystemTool

#!/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. Створення плагіна з діалоговим вікном MenuEntry

Можна додати запис до діалогового вікна, яке з’являється, коли ви клацнете правою кнопкою миші на віджеті в макеті.

Це може робити такі речі, як вибір опцій зручнішим способом.

Це плагін, який використовується для кнопок дій.

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

    # Метод __init__() використовується лише для налаштування плагіна та визначення його
    # ініціалізована змінна.
    def __init__(self, parent=None):
        super(ActionButtonPlugin, self).__init__(parent)
        self.initialized = False

    # Методи initialize() та isInitialized() дозволяють плагіну налаштувати
    # будь-які необхідні ресурси, гарантуючи, що це може статися лише один раз для кожного
    # плагіна.
    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

    # Цей фабричний метод створює нові екземпляри нашого власного віджета
    def createWidget(self, parent):
        return ActionButton(parent)

    # Цей метод повертає назву класу користувацького віджета
    def name(self):
        return "ActionButton"

    # Повертає назву групи у вікні віджета Qt Designer
    def group(self):
        return "LinuxCNC - Controller"

    # Повертає значок
    def icon(self):
        return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('actionbutton')))

    # Повертає короткий опис підказки
    def toolTip(self):
        return "Action button widget"

    # Повертає короткий опис користувацького віджета для використання в розділі «Що
    # "Це?" довідкове повідомлення для віджета.
    def whatsThis(self):
        return ""

    # Повертає значення True, якщо користувацький віджет діє як контейнер для інших віджетів;
    def isContainer(self):
        return False

    # Повертає XML-опис екземпляра користувацького віджета, який описує
    # значення за замовчуванням для його властивостей.
    def domXml(self):
        return '<widget class="ActionButton" name="actionbutton" />\n'

    # Повертає модуль, що містить клас користувацького віджета. Він може містити
    # шлях до модуля.
    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("Налаштування параметрів"))

   def updateWidget(self):

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

клас 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