- Добавлено автоматическое построение блока против часовой стрелки. - Добавлено редактирование координат LED через контекстное меню. - Добавлено перемещение светодиодов через Alt + ЛКМ. - Убран лишний столбец "Источник" из таблицы выбранных LED. - Индексы LED сделаны визуальной подсказкой, которая не мешает кликам и перетаскиванию точек. - Добавлены проверки для новых сценариев редактирования, маршрутизации, таблицы и сборочного имени.
143 lines
6.1 KiB
Python
143 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import asdict, dataclass
|
|
from pathlib import Path
|
|
|
|
from PySide6.QtCore import QStandardPaths
|
|
from PySide6.QtGui import QKeySequence
|
|
|
|
|
|
SHORTCUT_DEFINITIONS: list[tuple[str, str, str]] = [
|
|
("open", "Открыть", "Ctrl+O"),
|
|
("save", "Сохранить", "Ctrl+S"),
|
|
("save_as", "Сохранить как", "Ctrl+Shift+S"),
|
|
("generate_script", "Сгенерировать скрипт", "Ctrl+G"),
|
|
("settings", "Параметры", "Ctrl+,"),
|
|
("undo", "Отменить", "Ctrl+Z"),
|
|
("redo", "Повторить", "Ctrl+Y"),
|
|
("delete", "Удалить фиксацию", "Del"),
|
|
("delete_points", "Удалить точки", "Ctrl+Del"),
|
|
("clear_all", "Удалить все", "Ctrl+Shift+Del"),
|
|
("clear_selection", "Очистить выбор", "Esc"),
|
|
("fit_view", "Показать всё", "Ctrl+0"),
|
|
("grid", "Сетка 34 мм", "G"),
|
|
("direction_arrows", "Стрелки направления", "Ctrl+Shift+A"),
|
|
("led_ids", "Отображать id светодиодов", "I"),
|
|
("background", "Подложка", "Ctrl+B"),
|
|
("background_visible", "Показать подложку", "Ctrl+Alt+B"),
|
|
("background_edit", "Редактировать подложку", "Ctrl+Shift+I"),
|
|
("background_reset_scale", "Сбросить масштаб подложки", "Ctrl+Alt+0"),
|
|
("background_clear", "Удалить подложку", "Ctrl+Alt+Backspace"),
|
|
("toggle_sidebar", "Скрыть боковую панель", "F9"),
|
|
("add_line", "Новая линия", "Ctrl+L"),
|
|
("add_led", "Новый светодиод", "N"),
|
|
("add_block", "Блок из выбора", "Ctrl+Shift+L"),
|
|
("auto_blocks", "Автоблоки", "Ctrl+Shift+B"),
|
|
("clockwise", "По часовой", "Ctrl+Alt+R"),
|
|
("counterclockwise", "Против часовой", "Ctrl+Alt+Shift+R"),
|
|
("rebuild_route", "Пересобрать", "Ctrl+Shift+R"),
|
|
("brush", "Кисть", "B"),
|
|
]
|
|
DEFAULT_SHORTCUTS = {key: shortcut for key, _title, shortcut in SHORTCUT_DEFINITIONS}
|
|
SETTINGS_VERSION = 2
|
|
|
|
|
|
@dataclass
|
|
class AppSettings:
|
|
version: int = SETTINGS_VERSION
|
|
theme: str = "system"
|
|
show_grid_by_default: bool = False
|
|
show_direction_arrows_by_default: bool = True
|
|
show_led_ids_by_default: bool = True
|
|
zoom_focus_pull: int = 35
|
|
pan_mode: str = "middle_space"
|
|
instant_space_pan: bool = False
|
|
brush_radius: int = 18
|
|
default_output_dir: str = ""
|
|
confirm_partial_generation: bool = True
|
|
sidebar_inner_sizes: list[int] | None = None
|
|
shortcuts: dict[str, str] | None = None
|
|
|
|
@classmethod
|
|
def load(cls) -> "AppSettings":
|
|
path = settings_path()
|
|
if not path.exists():
|
|
return cls()
|
|
try:
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError):
|
|
return cls()
|
|
settings = cls()
|
|
saved_version = clamp_int(data.get("version", 1), 1, SETTINGS_VERSION, 1)
|
|
for key, value in data.items():
|
|
if hasattr(settings, key):
|
|
setattr(settings, key, value)
|
|
settings.version = SETTINGS_VERSION
|
|
settings.zoom_focus_pull = clamp_int(settings.zoom_focus_pull, 0, 100, cls.zoom_focus_pull)
|
|
settings.brush_radius = clamp_int(settings.brush_radius, 4, 80, cls.brush_radius)
|
|
if settings.theme not in {"system", "light", "dark"}:
|
|
settings.theme = cls.theme
|
|
if settings.pan_mode not in {"middle", "space", "middle_space"}:
|
|
settings.pan_mode = cls.pan_mode
|
|
settings.instant_space_pan = bool(settings.instant_space_pan)
|
|
settings.show_grid_by_default = bool(settings.show_grid_by_default)
|
|
settings.show_direction_arrows_by_default = bool(settings.show_direction_arrows_by_default)
|
|
settings.show_led_ids_by_default = bool(settings.show_led_ids_by_default)
|
|
settings.confirm_partial_generation = bool(settings.confirm_partial_generation)
|
|
settings.default_output_dir = str(settings.default_output_dir or "")
|
|
settings.sidebar_inner_sizes = normalize_splitter_sizes(settings.sidebar_inner_sizes, [360, 420])
|
|
settings.shortcuts = normalize_shortcuts(
|
|
settings.shortcuts,
|
|
fill_new_defaults=saved_version < SETTINGS_VERSION,
|
|
)
|
|
return settings
|
|
|
|
def save(self) -> None:
|
|
path = settings_path()
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(asdict(self), ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
def shortcut_for(self, key: str) -> str:
|
|
if self.shortcuts is None:
|
|
return DEFAULT_SHORTCUTS.get(key, "")
|
|
return self.shortcuts.get(key, DEFAULT_SHORTCUTS.get(key, ""))
|
|
|
|
|
|
def settings_path() -> Path:
|
|
config_dir = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)
|
|
if not config_dir:
|
|
config_dir = str(Path.home() / ".led_mapper")
|
|
return Path(config_dir) / "settings.json"
|
|
|
|
|
|
def clamp_int(value, minimum: int, maximum: int, fallback: int) -> int: # noqa: ANN001
|
|
try:
|
|
number = int(value)
|
|
except (TypeError, ValueError):
|
|
return fallback
|
|
return max(minimum, min(maximum, number))
|
|
|
|
|
|
def normalize_splitter_sizes(value, fallback: list[int]) -> list[int]: # noqa: ANN001
|
|
if not isinstance(value, list) or len(value) != len(fallback):
|
|
return fallback[:]
|
|
sizes = [clamp_int(size, 80, 10000, fallback[index]) for index, size in enumerate(value)]
|
|
if sum(sizes) <= 0:
|
|
return fallback[:]
|
|
return sizes
|
|
|
|
|
|
def normalize_shortcuts(value, *, fill_new_defaults: bool = False) -> dict[str, str]: # noqa: ANN001
|
|
source = value if isinstance(value, dict) else {}
|
|
shortcuts: dict[str, str] = {}
|
|
for key, default in DEFAULT_SHORTCUTS.items():
|
|
raw = source.get(key, default)
|
|
if raw is None or (fill_new_defaults and raw == "" and default):
|
|
raw = ""
|
|
if fill_new_defaults and default:
|
|
raw = default
|
|
sequence = QKeySequence(str(raw))
|
|
shortcuts[key] = sequence.toString(QKeySequence.SequenceFormat.PortableText)
|
|
return shortcuts
|