products:pyrog:tutorials:dev:main
Различия
Показаны различия между двумя версиями страницы.
| Предыдущая версия справа и слеваПредыдущая версия | |||
| products:pyrog:tutorials:dev:main [2026/07/25 18:47] – [2.3.2 Процедура перевода] ironmesh | products:pyrog:tutorials:dev:main [2026/08/01 23:09] (текущий) – ironmesh | ||
|---|---|---|---|
| Строка 2: | Строка 2: | ||
| Для разработки собственного приложения нужно иметь минимум базовые навыки разработки на Python и PySide6. Если возникли вопросы, | Для разработки собственного приложения нужно иметь минимум базовые навыки разработки на Python и PySide6. Если возникли вопросы, | ||
| + | |||
| ===== 1. Быстрый старт ===== | ===== 1. Быстрый старт ===== | ||
| Строка 30: | Строка 31: | ||
| </ | </ | ||
| - | В файле '' | + | В файле '' |
| <code python> | <code python> | ||
| Строка 88: | Строка 89: | ||
| | **forum_url** | ссылка на форум | " | | **forum_url** | ссылка на форум | " | ||
| | **dependencies** | набор пакетов, | | **dependencies** | набор пакетов, | ||
| - | | **source_language** | локаль оригинального языка, код языка в формате ISO639-1 и код страны согласно стандарту ISO 3166-2, | + | | **source_language** | локаль оригинального языка, код языка в формате |
| - | Папка '' | + | Папка '' |
| Двигаемся дальше. Заглянем в файл '' | Двигаемся дальше. Заглянем в файл '' | ||
| Строка 148: | Строка 149: | ||
| </ | </ | ||
| - | Все настройки помещаются в классе '' | + | Все настройки помещаются в классе '' |
| * **name** - имя свойства, | * **name** - имя свойства, | ||
| Строка 154: | Строка 155: | ||
| * **tooltip** - всплывающая подсказка с описанием, | * **tooltip** - всплывающая подсказка с описанием, | ||
| * **show_reset_btn** - (может быть не у всех Свойств) задает видимость кнопки сброса значения Свойства, | * **show_reset_btn** - (может быть не у всех Свойств) задает видимость кнопки сброса значения Свойства, | ||
| - | * **widget_enabled** - задает активность виджета свойства, | + | * **widget_enabled** - задает активность виджета свойства, |
| + | |||
| + | Добавьте в тело класса атрибуты с нужными Cвойствами. Атрибуты других типов тоже допустимы. Служебные атрибуты имеют префикс '' | ||
| ^ Имя класса свойства ^ Тип данных ^ Пояснение ^ | ^ Имя класса свойства ^ Тип данных ^ Пояснение ^ | ||
| Строка 169: | Строка 172: | ||
| | FilePathProperty | str | Строка, | | FilePathProperty | str | Строка, | ||
| | FilePathListProperty | tuple[tuple[str, | | FilePathListProperty | tuple[tuple[str, | ||
| - | | PlainTextProperty | str | Строка, отличается от StringListProperty тем, что предоставляет многострочное поле ввода | | + | | PlainTextProperty | str | Представляет строковое значение. Отличается от **StringListProperty** тем, что предоставляет многострочное поле ввода | |
| | DateTimeProperty | str | Дата и время в виде строки, | | DateTimeProperty | str | Дата и время в виде строки, | ||
| Строка 193: | Строка 196: | ||
| ==== 2.1 Жизненный цикл плагина ==== | ==== 2.1 Жизненный цикл плагина ==== | ||
| - | Когда пользователь запускает Менеджер, | + | Когда пользователь запускает Менеджер, |
| * '' | * '' | ||
| * '' | * '' | ||
| - | Когда загружается контейнер свойств, | + | Когда загружается контейнер свойств, |
| Если пользователь деактивирует плагин, | Если пользователь деактивирует плагин, | ||
| Строка 204: | Строка 207: | ||
| ==== 2.2 Ваш универсальный помощник ==== | ==== 2.2 Ваш универсальный помощник ==== | ||
| - | Для общения с Менеджером используйте класс | + | Для общения с Менеджером используйте класс |
| - | + | ||
| - | Данный класс предоставляет следующие методы: | + | |
| === 2.2.1 Методы === | === 2.2.1 Методы === | ||
| Строка 219: | Строка 220: | ||
| **plugin_dir_abspath()** | **plugin_dir_abspath()** | ||
| - | + | ||
| Возвращает строку с абсолютным путем к папке плагина | Возвращает строку с абсолютным путем к папке плагина | ||
| **plugin_localstorage_dir_abspath()** | **plugin_localstorage_dir_abspath()** | ||
| - | Возвращает строку с абсолютным путем к индивидуальной папке в локальном хранилище Менеджера ('' | + | Возвращает строку с абсолютным путем к индивидуальной папке в локальном хранилище Менеджера ('' |
| + | **set_menu()** | ||
| + | |||
| + | Устанавливает меню (объект класса [[https:// | ||
| + | |||
| + | Для того чтобы установить собственное меню, передайте аргументу //menu// объект класса **QMenu**, а аргументу // | ||
| + | |||
| + | * ''" | ||
| + | * ''" | ||
| + | * ''" | ||
| + | * ''" | ||
| + | |||
| + | В качестве демонстрации посмотрите на изображение ниже, здесь демонстрируются варианты использовать контекстных меню вкладок\\ {{ https:// | ||
| + | |||
| + | Описания обозначений: | ||
| + | |||
| + | - Встроенное меню | ||
| + | - Переопределенное встроенное меню | ||
| + | - Встроенное меню + Пользовательское меню в начале | ||
| + | - Встроенное меню + Пользовательское меню в конце | ||
| + | - Встроенное меню + Пользовательское меню в начале и в конце | ||
| + | |||
| + | Чтобы удалить ранее установленное меню - передайте аргументу //menu// объект **None**. | ||
| + | |||
| + | **get_menu()** | ||
| + | |||
| + | Возвращает ранее установленное меню в указанное позиции, | ||
| === 2.2.2 Сигналы === | === 2.2.2 Сигналы === | ||
| + | |||
| Helper также имеет ряд сигналов, | Helper также имеет ряд сигналов, | ||
| Строка 238: | Строка 266: | ||
| Весь код в Менеджере выполняется синхронно, | Весь код в Менеджере выполняется синхронно, | ||
| + | |||
| ==== 2.3 Делаем локализацию интерфейса ==== | ==== 2.3 Делаем локализацию интерфейса ==== | ||
| Строка 245: | Строка 274: | ||
| ==== 2.3.1 Языковые константы ==== | ==== 2.3.1 Языковые константы ==== | ||
| - | Языковые константы (ЯК) - это объекты, | + | Языковые константы (ЯК) - это объекты, |
| <code python> | <code python> | ||
| Строка 262: | Строка 291: | ||
| * контекст | * контекст | ||
| * текст константы | * текст константы | ||
| - | * (опционально) строка идентификатор, | + | * (опционально) строка идентификатор, |
| Извлечь перевод (при его наличии, | Извлечь перевод (при его наличии, | ||
| Строка 270: | Строка 299: | ||
| </ | </ | ||
| - | У ЯК переопределен метод '' | + | У ЯК переопределен метод '' |
| <code python> | <code python> | ||
| Строка 284: | Строка 313: | ||
| </ | </ | ||
| - | преобразование производится при вызове экземпляра как функции, | + | преобразование производится при вызове экземпляра как функции, |
| <code python> | <code python> | ||
| Строка 306: | Строка 335: | ||
| </ | </ | ||
| - | этой | + | функции |
| - | Языковые константы поддерживают перевод для множественных форм числительных, | + | Языковые константы поддерживают перевод для множественных форм числительных, |
| <code python> | <code python> | ||
| Строка 325: | Строка 354: | ||
| </ | </ | ||
| - | Обращаю ваше внимание, | + | Обращаю ваше внимание, |
| ==== 2.3.2 Процедура перевода ==== | ==== 2.3.2 Процедура перевода ==== | ||
| - | Теперь, | + | Теперь, |
| Рассмотрим подробнее, | Рассмотрим подробнее, | ||
| + | |||
| - Пользователь изменил язык | - Пользователь изменил язык | ||
| - Менеджер загружает доступные словари для выбранного языка | - Менеджер загружает доступные словари для выбранного языка | ||
| - | - Менеджер через Helper отправляет сигнал plugin_language_changing плагину о том, что язык изменился и ему нужно провести | + | - Менеджер через |
| - Если плагин привязал обработчики к сигналу, | - Если плагин привязал обработчики к сигналу, | ||
| - Менеджер выгружает ранее установленные словари | - Менеджер выгружает ранее установленные словари | ||
| - | Итак, нам как разработчикам плагинов нужно сосредоточиться на шаге 4, когда | + | Итак, нам как разработчикам плагинов нужно сосредоточиться на шаге 4, когда на нужно |
| <code python> | <code python> | ||
| Строка 356: | Строка 386: | ||
| def _on_retranslate(self, | def _on_retranslate(self, | ||
| retranslate_nested_langconstants(lang_consts) | retranslate_nested_langconstants(lang_consts) | ||
| + | |||
| self._file_list_input.set_file_filter(f" | self._file_list_input.set_file_filter(f" | ||
| self._toolBox.setItemText(0, | self._toolBox.setItemText(0, | ||
| Строка 364: | Строка 395: | ||
| self._clear_btn.setText(lang_consts.CLEAR()) | self._clear_btn.setText(lang_consts.CLEAR()) | ||
| self._generate_btn.setText(lang_consts.GENERATE()) | self._generate_btn.setText(lang_consts.GENERATE()) | ||
| - | | + | |
| </ | </ | ||
| - | В нем с помощью функции | + | В нем с помощью функции |
| - | Когда мы используем QtDesigner для создания форм | + | Когда мы используем QtDesigner для создания форм, то в коде формы, которую он генерирует есть метод '' |
| ==== 2.3.3 Подготовка словарей ==== | ==== 2.3.3 Подготовка словарей ==== | ||
| Строка 375: | Строка 406: | ||
| После того как работа над кодом плагина закончена, | После того как работа над кодом плагина закончена, | ||
| - | Для создания TS файлов из исходников есть встроенная утилита TS generator. Перейдите в её настройки и установите адрес папки, в которой будут сохраняться временные файлы, и путь к файлу lupdate, он находится в папке, где устанавливаются пакеты Python, для Windows это '' | + | Для создания TS файлов из исходников есть встроенная утилита TS generator. При первом применении перейдите в её настройки и установите адрес папки, в которой будут сохраняться временные файлы, и путь к файлу |
| * Python файлы ('' | * Python файлы ('' | ||
| * Файл манифеста ('' | * Файл манифеста ('' | ||
| - | * Файлы форм, созданных в QtDesigner ('' | + | * Файлы форм, созданных в QtDesigner ('' |
| - | | + | В разделе |
| - | Итак, мы получили файлы с исходными текстами, | + | <WRAP center round important 80%> |
| + | Утилита импортирует исходные файлы Python и выполняет их код для поиска в них языковых констант, | ||
| + | </ | ||
| + | |||
| + | Итак, мы получили файлы с исходными текстовыми данными, теперь нужно их перевести на целевой язык. Запускаем утилиту Qt Linguist и открываем наши TS-файлы. При первой загрузке нужно выбрать язык оригинала и язык перевода. У утилиты есть одна фишка - можно открыть сразу несколько TS файлов, | ||
| В разделе **Контекст** мы видим, что все текстовые данные сгруппированы в соответствии с тем текстом, | В разделе **Контекст** мы видим, что все текстовые данные сгруппированы в соответствии с тем текстом, | ||
| Строка 391: | Строка 426: | ||
| Также есть возможность скомпилировать файлы переводов утилитой lrelease, можно прочитать информацию по ней [[https:// | Также есть возможность скомпилировать файлы переводов утилитой lrelease, можно прочитать информацию по ней [[https:// | ||
| - | Сейчас можно | + | <WRAP center round tip 80%> |
| + | Можно | ||
| + | </ | ||
| ==== 2.4 Система свойств ==== | ==== 2.4 Система свойств ==== | ||
| Строка 397: | Строка 434: | ||
| ==== 2.4.1 Как работать со Свойствами ==== | ==== 2.4.1 Как работать со Свойствами ==== | ||
| - | Свойство в контексте Pyrog это программная единица, | + | Свойство в контексте Pyrog это программная единица, |
| Рассмотрим строение Свойства на конкретном примере '' | Рассмотрим строение Свойства на конкретном примере '' | ||
| Строка 407: | Строка 444: | ||
| Список аргументов-параметров следующий | Список аргументов-параметров следующий | ||
| - | ^ Параметр ^ Тип ^ Описание | + | ^ Параметр ^ Тип ^ Описание | |
| - | | default_value | float | значение по умолчанию | | | + | | default_value | '' |
| - | | name | str | LangConstant | имя Свойства, | + | | name | '' |
| - | | minimum | float | минимальное значение | | | + | | minimum | '' |
| - | | maximum | float | максимальное значение | | | + | | maximum | '' |
| - | | single_step | float | единичный шаг, шаг изменения значения при нажатии на кнопки виджета QDoubleSpinBox | + | | single_step | '' |
| - | | decimals | int | количество знаков после запятой | + | | decimals | '' |
| - | | tooltip | str | LangConstant | всплывающая подсказка с описание свойства | | + | | tooltip | '' |
| - | | show_reset_btn | bool | флаг того, будет ли показана кнопка сброса значения до дефолтного рядом с виджетом, | + | | show_reset_btn | '' |
| + | |||
| + | Как ранее было сказано, | ||
| + | |||
| + | === Реакция значения Свойства на изменения параметров === | ||
| + | |||
| + | Значение Свойства должно находиться в диапазоне, | ||
| + | |||
| + | === Как работает мягкая валидация === | ||
| + | |||
| + | Вы уже видели, | ||
| - | Как | + | === Сохранение параметров |
| - | **Реакция значения Свойства на изменения параметров** Значение Свойства должно находиться в диапазоне, | + | Если по каким-то причинам |
| - | **Как | + | === Как |
| - | **Сохранение параметров | + | Свойства |
| - | **Как получить | + | === Как получить доступ к графическому интерфейс === |
| - | **Как получить доступ к графическому интерфейсу** | + | Каждое Свойство имеет метод '' |
| ==== 2.4.2 Как работать с контейнером свойств ==== | ==== 2.4.2 Как работать с контейнером свойств ==== | ||
| Строка 454: | Строка 501: | ||
| from .settings import Settings | from .settings import Settings | ||
| - | print(Settings.int_property.value) # вывести значение | + | print(Settings.int_property.value) # вывести значение |
| - | Settings.int_property.value = 7 # изменить значение | + | Settings.int_property.value = 7 # изменить значение |
| - | print(Settings.int_property.default_value) # вывести дефольное значение | + | print(Settings.int_property.default_value) # вывести дефолтное значение |
| - | Settings.int_property.default_value = 9 # измениим дефолтное значение | + | Settings.int_property.default_value = 9 # изменить дефолтное значение |
| </ | </ | ||
| - | Чтобы получить уведомление об изменение значений используйте нотификатор | + | Чтобы получить уведомление об изменении значений используйте нотификатор |
| <code python> | <code python> | ||
| Строка 483: | Строка 530: | ||
| Если нотификатор стал не нужен, то удалите его методом '' | Если нотификатор стал не нужен, то удалите его методом '' | ||
| - | **Как получить доступ к общему графическому интерфейсу Свойств** Контейнер свойств может отрендерить графический интерфейс, | + | === Как получить доступ к общему графическому интерфейсу Свойств |
| + | |||
| + | Контейнер свойств может отрендерить графический интерфейс, | ||
| Дефолтный интерфейс дает возможность фильтровать Свойства по имени, а также сбросить значения до дефолтных для всех Свойств. Данный интерфейс можно переопределить, | Дефолтный интерфейс дает возможность фильтровать Свойства по имени, а также сбросить значения до дефолтных для всех Свойств. Данный интерфейс можно переопределить, | ||
| - | **Как пакетно извлечь/ | + | === Как пакетно извлечь/ |
| + | |||
| + | Например, | ||
| * '' | * '' | ||
| Строка 499: | Строка 550: | ||
| ==== 2.4.3 Как разработать собственное Свойство ==== | ==== 2.4.3 Как разработать собственное Свойство ==== | ||
| - | Может так случиться, | + | Может так случиться, |
| **Шаг 1.** Объявляем класс и создаем сигнал. Имя сигнала не изменять! | **Шаг 1.** Объявляем класс и создаем сигнал. Имя сигнала не изменять! | ||
| Строка 505: | Строка 556: | ||
| <code python> | <code python> | ||
| class BoolListProperty(Property): | class BoolListProperty(Property): | ||
| - | value_changed = Signal(tuple) | + | value_changed = Signal(tuple) |
| </ | </ | ||
| Строка 550: | Строка 601: | ||
| * '' | * '' | ||
| * '' | * '' | ||
| - | * '' | + | * '' |
| Рассмотрим функцию-валидатор | Рассмотрим функцию-валидатор | ||
| Строка 558: | Строка 609: | ||
| </ | </ | ||
| - | параметр '' | + | параметр '' |
| - | **Шаг 3.** Создаем свойства для всех параметров, | + | **Шаг 3.** Создаем свойства для всех параметров, |
| <code python> | <code python> | ||
| items = create_param_property(" | items = create_param_property(" | ||
| - | |||
| tooltip = create_param_property(" | tooltip = create_param_property(" | ||
| - | |||
| name = create_param_property(" | name = create_param_property(" | ||
| - | + | show_reset_btn = create_param_property(" | |
| - | show_reset_btn = create_param_property(" | + | |
| default_value = create_param_property(" | default_value = create_param_property(" | ||
| </ | </ | ||
| Строка 581: | Строка 628: | ||
| * '' | * '' | ||
| - | **Шаг 4.** Пишем конструктор. В модуле '' | + | **Шаг 4.** Пишем конструктор. В модуле '' |
| <code python> | <code python> | ||
| Строка 602: | Строка 649: | ||
| </ | </ | ||
| - | Набор аргументов должен совпадать с набором параметров. | + | Набор аргументов должен совпадать с набором параметров |
| **Шаг 5.** Пишем метод создания виджета ввода | **Шаг 5.** Пишем метод создания виджета ввода | ||
| <code python> | <code python> | ||
| - | | + | def get_input_widget(self) -> QListWidget: |
| - | if hasattr(self, | + | if hasattr(self, |
| - | return self._widget | + | return self._widget |
| - | | + | |
| - | self._widget.child_widget.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Maximum, | + | self._widget.child_widget.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Maximum, |
| - | self._widget.reset_requested.connect(self.reset_value) | + | self._widget.reset_requested.connect(self.reset_value) |
| - | self._widget.child_widget.itemChanged.connect(self._on_widget_value_changed) | + | |
| - | self._update_widget_params() | + | |
| - | self._update_widget_value() | + | self._update_widget_params() |
| + | self._update_widget_value() | ||
| - | | + | |
| </ | </ | ||
| - | На первом шаге проверяем был ли ранее создан виджет, | + | На первом шаге проверяем был ли ранее создан виджет, |
| **Шаг 6.** Пишем методы обновления параметров и значения виджета | **Шаг 6.** Пишем методы обновления параметров и значения виджета | ||
| <code python> | <code python> | ||
| - | def _update_widget_value(self) -> None: | + | def _update_widget_value(self) -> None: |
| - | self._widget.child_widget.blockSignals(True) | + | self._widget.child_widget.blockSignals(True) |
| - | for index, value in enumerate(self.items): | + | for index, value in enumerate(self.items): |
| - | item = self._widget.child_widget.item(index) | + | item = self._widget.child_widget.item(index) |
| - | if item: | + | if item: |
| - | if item.text() != utils.get_lang_const_translation(value): | + | if item.text() != utils.get_lang_const_translation(value): |
| - | item.setText(utils.get_lang_const_translation(value)) | + | item.setText(utils.get_lang_const_translation(value)) |
| - | item_check_state = True if item.checkState() == Qt.CheckState.Checked else False | + | item_check_state = True if item.checkState() == Qt.CheckState.Checked else False |
| - | if self.value[index] != item_check_state: | + | if self.value[index] != item_check_state: |
| - | item.setCheckState(Qt.CheckState.Checked if self.value[index] else Qt.CheckState.Unchecked) | + | item.setCheckState(Qt.CheckState.Checked if self.value[index] else Qt.CheckState.Unchecked) |
| - | else: | + | else: |
| - | item = QListWidgetItem(utils.get_lang_const_translation(value)) | + | item = QListWidgetItem(utils.get_lang_const_translation(value)) |
| - | item.setFlags(item.flags() | Qt.ItemIsUserCheckable) | + | item.setFlags(item.flags() | Qt.ItemIsUserCheckable) |
| - | item_state = Qt.CheckState.Checked if self.value[index] else Qt.CheckState.Unchecked | + | item_state = Qt.CheckState.Checked if self.value[index] else Qt.CheckState.Unchecked |
| - | item.setCheckState(item_state) | + | item.setCheckState(item_state) |
| - | self._widget.child_widget.addItem(item) | + | self._widget.child_widget.addItem(item) |
| - | | + | |
| - | for i in range(len(self.items), | + | for i in range(len(self.items), |
| - | self._widget.child_widget.takeItem(i) | + | self._widget.child_widget.takeItem(i) |
| - | self._adjust_widget_height() | + | self._adjust_widget_height() |
| - | self._widget.set_reset_btn_visibility(self._value != self._default_value and self.show_reset_btn) | + | self._widget.set_reset_btn_visibility(self._value != self._default_value and self.show_reset_btn) |
| - | self._widget.child_widget.blockSignals(False) | + | self._widget.child_widget.blockSignals(False) |
| - | def _update_widget_params(self) -> None: | + | |
| - | if hasattr(self, | + | if hasattr(self, |
| - | self._widget.child_widget.setToolTip(utils.get_lang_const_translation(self.tooltip)) | + | self._widget.child_widget.setToolTip(utils.get_lang_const_translation(self.tooltip)) |
| - | self._update_widget_value() | + | self._update_widget_value() |
| </ | </ | ||
| - | В методе '' | + | В методе '' |
| В методе '' | В методе '' | ||
| Строка 665: | Строка 713: | ||
| <code python> | <code python> | ||
| - | def _on_widget_value_changed(self) -> None: | + | |
| - | self._set_value(tuple(True if (self._widget.child_widget.item(i).checkState() == Qt.CheckState.Checked) else False for i in range(self._widget.child_widget.count()))) | + | self._set_value(tuple(True if (self._widget.child_widget.item(i).checkState() == Qt.CheckState.Checked) else False for i in range(self._widget.child_widget.count()))) |
| - | self._widget.set_reset_btn_visibility(self._value != self._default_value and self.show_reset_btn) | + | self._widget.set_reset_btn_visibility(self._value != self._default_value and self.show_reset_btn) |
| </ | </ | ||
| - | Данный метод вызывается, | + | Данный метод вызывается, |
| Альтернативный способ обновления видимости кнопки сброса; | Альтернативный способ обновления видимости кнопки сброса; | ||
| Строка 682: | Строка 730: | ||
| </ | </ | ||
| - | Метод '' | + | Метод '' |
| + | |||
| + | <code python> | ||
| + | def _on_widget_value_changed(self) -> None: | ||
| + | self._set_value(tuple(True if (self._widget.child_widget.item(i).checkState() == Qt.CheckState.Checked) else False for i in range(self._widget.child_widget.count()))) | ||
| + | self._widget.update_reset_btn_visibility() | ||
| + | </ | ||
| **Шаг 8.** Пишем метод валидации значения | **Шаг 8.** Пишем метод валидации значения | ||
| Строка 707: | Строка 761: | ||
| </ | </ | ||
| - | Примите во внимание, | + | Примите во внимание, |
| - | **Важно!** в процедуре валидации | + | <WRAP center round important 60%> |
| + | В методе // | ||
| + | </ | ||
| + | |||
| + | **Шаг 9 (ситуативный)** Иногда нужно переопределить код свойства '' | ||
| - | **Шаг 9 (ситуативный)** Иногда нужно переопределить код свойства value, например, | ||
| ==== 2.4.4 Разработка кастомного интерфейса для контейнера свойств ==== | ==== 2.4.4 Разработка кастомного интерфейса для контейнера свойств ==== | ||
| Строка 783: | Строка 840: | ||
| Проект будет развиваться, | Проект будет развиваться, | ||
| + | |||
| + | {{section> | ||
products/pyrog/tutorials/dev/main.1784994471.txt.gz · Последнее изменение: — ironmesh