LED-mapper/background_dialog.py
Danil Iarosh 25ca2101a4 feat: improve LED Mapper settings and image support
- Добавлена сохраняемая настройка размера текста индексов LED с мгновенным предпросмотром и сбросом.
- Исправлено размещение быстрых кнопок в строке меню без перекрытия пунктов на узком окне.
- Усилена проверка OTA-загрузок: права записи, конечный URL, авторизация, Content-Length и отдельная группа updater-процесса.
- Добавлен единый загрузчик подложек с определением формата по содержимому, EXIF-ориентацией и преобразованием CMYK.
- Расширен список форматов подложки и добавлена безопасная обработка крупных изображений.
- Ошибки декодирования теперь показываются пользователю без замены текущей подложки.
- Обновлены smoke-тесты, документация, PyInstaller-конфигурация и версия приложения до 3.4.0.
2026-10-02 09:42:24 +03:00

128 lines
5.0 KiB
Python

from __future__ import annotations
from pathlib import Path
from PySide6.QtWidgets import (
QDialog,
QDialogButtonBox,
QCheckBox,
QDoubleSpinBox,
QFileDialog,
QFormLayout,
QHBoxLayout,
QLineEdit,
QPushButton,
QWidget,
)
try:
from .background_image_loader import supported_image_filter
from .localization import localize_dialog_buttons
from .models import BackgroundImage
except ImportError:
from background_image_loader import supported_image_filter
from localization import localize_dialog_buttons
from models import BackgroundImage
class BackgroundDialog(QDialog):
EMBEDDED_PATH_LABEL = "Встроенная"
def __init__(self, background: BackgroundImage, parent=None) -> None: # noqa: ANN001
super().__init__(parent)
self.setWindowTitle("Подложка")
self.setMinimumWidth(460)
self._source_background = background
self._keep_embedded_image = background.has_embedded_image()
self.path = QLineEdit(self.EMBEDDED_PATH_LABEL if self._keep_embedded_image else background.path)
self.path.setPlaceholderText("Файл изображения")
self.path.setReadOnly(self._keep_embedded_image)
if self._keep_embedded_image:
source_name = background.embedded_image_name or "изображение"
self.path.setToolTip(f"{source_name} встроено в JSON схемы")
browse = QPushButton("Выбрать")
browse.clicked.connect(self._browse)
path_row = QWidget()
path_layout = QHBoxLayout(path_row)
path_layout.setContentsMargins(0, 0, 0, 0)
path_layout.addWidget(self.path, 1)
path_layout.addWidget(browse)
self.offset_x = self._spin(-100000.0, 100000.0, background.offset_x, "")
self.offset_y = self._spin(-100000.0, 100000.0, background.offset_y, "")
self.scale_x = self._spin(1.0, 10000.0, background.scale_x_percent, " %")
self.scale_y = self._spin(1.0, 10000.0, background.scale_y_percent, " %")
self.opacity = self._spin(0.0, 100.0, background.opacity_percent, " %")
self.visible = QCheckBox("Показывать подложку")
self.visible.setChecked(background.visible)
reset_scale = QPushButton("100%")
reset_scale.clicked.connect(self._reset_scale)
scale_row = QWidget()
scale_layout = QHBoxLayout(scale_row)
scale_layout.setContentsMargins(0, 0, 0, 0)
scale_layout.addWidget(self.scale_x, 1)
scale_layout.addWidget(self.scale_y, 1)
scale_layout.addWidget(reset_scale)
form = QFormLayout(self)
form.addRow("Изображение", path_row)
form.addRow("Смещение центра X", self.offset_x)
form.addRow("Смещение центра Y", self.offset_y)
form.addRow("Масштаб X / Y", scale_row)
form.addRow("Прозрачность", self.opacity)
form.addRow("", self.visible)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
localize_dialog_buttons(buttons)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
form.addRow(buttons)
def background(self) -> BackgroundImage:
embedded = self._source_background if self._keep_embedded_image else BackgroundImage()
return BackgroundImage(
path="" if self._keep_embedded_image else self.path.text().strip(),
offset_x=self.offset_x.value(),
offset_y=self.offset_y.value(),
scale_x_percent=self.scale_x.value(),
scale_y_percent=self.scale_y.value(),
opacity_percent=self.opacity.value(),
visible=self.visible.isChecked(),
embedded_image_base64=embedded.embedded_image_base64,
embedded_image_mime_type=embedded.embedded_image_mime_type,
embedded_image_name=embedded.embedded_image_name,
embedded_image_sha256=embedded.embedded_image_sha256,
)
def _reset_scale(self) -> None:
self.scale_x.setValue(100.0)
self.scale_y.setValue(100.0)
def _browse(self) -> None:
start = "" if self._keep_embedded_image else self.path.text().strip()
start_path = Path(start).parent if start else Path.home()
selected, _ = QFileDialog.getOpenFileName(
self,
"Выбрать подложку",
str(start_path),
supported_image_filter(),
)
if selected:
self._set_external_path(selected)
def _set_external_path(self, path: str) -> None:
self._keep_embedded_image = False
self.path.setReadOnly(False)
self.path.setToolTip("")
self.path.setText(path)
@staticmethod
def _spin(minimum: float, maximum: float, value: float, suffix: str) -> QDoubleSpinBox:
spin = QDoubleSpinBox()
spin.setRange(minimum, maximum)
spin.setDecimals(2)
spin.setSingleStep(1.0)
spin.setValue(value)
spin.setSuffix(suffix)
return spin