Release build naming and arrow visibility controls
This commit is contained in:
parent
5895ccc29a
commit
070bb671c8
@ -1,11 +1,41 @@
|
|||||||
# -*- mode: python ; coding: utf-8 -*-
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
spec_dir = Path(SPECPATH)
|
spec_dir = Path(SPECPATH)
|
||||||
|
|
||||||
|
|
||||||
|
def git_build_ref():
|
||||||
|
try:
|
||||||
|
tag = subprocess.check_output(
|
||||||
|
['git', 'describe', '--tags', '--exact-match', 'HEAD'],
|
||||||
|
cwd=spec_dir,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
).strip()
|
||||||
|
if tag:
|
||||||
|
return tag
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
commit = subprocess.check_output(
|
||||||
|
['git', 'rev-parse', '--short', 'HEAD'],
|
||||||
|
cwd=spec_dir,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
).strip()
|
||||||
|
if commit:
|
||||||
|
return commit
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
pass
|
||||||
|
return 'untagged'
|
||||||
|
|
||||||
|
|
||||||
|
app_name = f'LEDMapper-{git_build_ref()}'
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(
|
a = Analysis(
|
||||||
[str(spec_dir / 'main.py')],
|
[str(spec_dir / 'main.py')],
|
||||||
pathex=[str(spec_dir), str(spec_dir.parent)],
|
pathex=[str(spec_dir), str(spec_dir.parent)],
|
||||||
@ -34,7 +64,7 @@ exe = EXE(
|
|||||||
a.binaries,
|
a.binaries,
|
||||||
a.datas,
|
a.datas,
|
||||||
[],
|
[],
|
||||||
name='LEDMapper',
|
name=app_name,
|
||||||
debug=False,
|
debug=False,
|
||||||
bootloader_ignore_signals=False,
|
bootloader_ignore_signals=False,
|
||||||
strip=False,
|
strip=False,
|
||||||
|
|||||||
@ -100,6 +100,7 @@ python -m venv .venv
|
|||||||
| Очистить выбор | `Esc` |
|
| Очистить выбор | `Esc` |
|
||||||
| Показать всю схему | `Ctrl+0` |
|
| Показать всю схему | `Ctrl+0` |
|
||||||
| Сетка 34 мм | `G` |
|
| Сетка 34 мм | `G` |
|
||||||
|
| Стрелки направления | `Ctrl+Shift+A` |
|
||||||
| Скрыть/показать боковую панель | `F9` |
|
| Скрыть/показать боковую панель | `F9` |
|
||||||
| Новая линия | `Ctrl+L` |
|
| Новая линия | `Ctrl+L` |
|
||||||
| Автоблоки | `Ctrl+Shift+B` |
|
| Автоблоки | `Ctrl+Shift+B` |
|
||||||
|
|||||||
22
canvas.py
22
canvas.py
@ -66,6 +66,7 @@ class LedCanvas(QGraphicsView):
|
|||||||
self.items_by_id: dict[int, LedItem] = {}
|
self.items_by_id: dict[int, LedItem] = {}
|
||||||
self.grid_items: list[QGraphicsLineItem] = []
|
self.grid_items: list[QGraphicsLineItem] = []
|
||||||
self.connection_items: list[QGraphicsPathItem] = []
|
self.connection_items: list[QGraphicsPathItem] = []
|
||||||
|
self._direction_arrows_visible = True
|
||||||
self._point_radius = 4.5
|
self._point_radius = 4.5
|
||||||
self._brush_radius = 18.0
|
self._brush_radius = 18.0
|
||||||
self._brush_zone_item: QGraphicsEllipseItem | None = None
|
self._brush_zone_item: QGraphicsEllipseItem | None = None
|
||||||
@ -100,6 +101,15 @@ class LedCanvas(QGraphicsView):
|
|||||||
self._grid_visible = visible
|
self._grid_visible = visible
|
||||||
self._refresh_grid()
|
self._refresh_grid()
|
||||||
|
|
||||||
|
def set_direction_arrows_visible(self, visible: bool) -> None:
|
||||||
|
if self._direction_arrows_visible == visible:
|
||||||
|
return
|
||||||
|
self._direction_arrows_visible = visible
|
||||||
|
self.refresh_styles(redraw_connections=True)
|
||||||
|
|
||||||
|
def direction_arrows_visible(self) -> bool:
|
||||||
|
return self._direction_arrows_visible
|
||||||
|
|
||||||
def set_brush_enabled(self, enabled: bool) -> None:
|
def set_brush_enabled(self, enabled: bool) -> None:
|
||||||
self._brush_enabled = enabled
|
self._brush_enabled = enabled
|
||||||
self._is_brush_stroke = False
|
self._is_brush_stroke = False
|
||||||
@ -537,9 +547,11 @@ class LedCanvas(QGraphicsView):
|
|||||||
ordered_items = [self.items_by_id[point_id] for point_id in block.point_ids if point_id in self.items_by_id]
|
ordered_items = [self.items_by_id[point_id] for point_id in block.point_ids if point_id in self.items_by_id]
|
||||||
for first, second in zip(ordered_items, ordered_items[1:]):
|
for first, second in zip(ordered_items, ordered_items[1:]):
|
||||||
self._append_connection(inner_path, first.pos(), second.pos())
|
self._append_connection(inner_path, first.pos(), second.pos())
|
||||||
self._append_arrow_marker(inner_arrows, first.pos(), second.pos())
|
if self._direction_arrows_visible:
|
||||||
|
self._append_arrow_marker(inner_arrows, first.pos(), second.pos())
|
||||||
self._add_connection_path(inner_path, line.color, inner_width, inner_alpha)
|
self._add_connection_path(inner_path, line.color, inner_width, inner_alpha)
|
||||||
self._add_connection_path(inner_arrows, line.color, arrow_width, arrow_alpha, z_value=2)
|
if self._direction_arrows_visible:
|
||||||
|
self._add_connection_path(inner_arrows, line.color, arrow_width, arrow_alpha, z_value=2)
|
||||||
|
|
||||||
previous_block_end = None
|
previous_block_end = None
|
||||||
bridge_path = QPainterPath()
|
bridge_path = QPainterPath()
|
||||||
@ -551,11 +563,13 @@ class LedCanvas(QGraphicsView):
|
|||||||
block_end = self.items_by_id.get(block.point_ids[-1])
|
block_end = self.items_by_id.get(block.point_ids[-1])
|
||||||
if block_start is not None and previous_block_end is not None:
|
if block_start is not None and previous_block_end is not None:
|
||||||
self._append_connection(bridge_path, previous_block_end.pos(), block_start.pos())
|
self._append_connection(bridge_path, previous_block_end.pos(), block_start.pos())
|
||||||
self._append_arrow_marker(bridge_arrows, previous_block_end.pos(), block_start.pos())
|
if self._direction_arrows_visible:
|
||||||
|
self._append_arrow_marker(bridge_arrows, previous_block_end.pos(), block_start.pos())
|
||||||
if block_end is not None:
|
if block_end is not None:
|
||||||
previous_block_end = block_end
|
previous_block_end = block_end
|
||||||
self._add_connection_path(bridge_path, line.color, bridge_width, bridge_alpha)
|
self._add_connection_path(bridge_path, line.color, bridge_width, bridge_alpha)
|
||||||
self._add_connection_path(bridge_arrows, line.color, arrow_width, bridge_alpha, z_value=2)
|
if self._direction_arrows_visible:
|
||||||
|
self._add_connection_path(bridge_arrows, line.color, arrow_width, bridge_alpha, z_value=2)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _append_connection(path: QPainterPath, start: QPointF, end: QPointF) -> None:
|
def _append_connection(path: QPainterPath, start: QPointF, end: QPointF) -> None:
|
||||||
|
|||||||
1
icons.py
1
icons.py
@ -7,6 +7,7 @@ from PySide6.QtSvg import QSvgRenderer
|
|||||||
|
|
||||||
LUCIDE_PATHS = {
|
LUCIDE_PATHS = {
|
||||||
"archive-restore": '<rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h2"/><path d="M20 8v11a2 2 0 0 1-2 2h-2"/><path d="m9 15 3-3 3 3"/><path d="M12 12v9"/>',
|
"archive-restore": '<rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h2"/><path d="M20 8v11a2 2 0 0 1-2 2h-2"/><path d="m9 15 3-3 3 3"/><path d="M12 12v9"/>',
|
||||||
|
"arrow-right": '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
|
||||||
"brush": '<path d="m9.06 11.9 8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.01 0 1.1-.89 2-2 2 1.4 1.4 3.67 1.4 5.07 0l2.94-2.94a2.93 2.93 0 0 0-4.14-4.14z"/>',
|
"brush": '<path d="m9.06 11.9 8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.01 0 1.1-.89 2-2 2 1.4 1.4 3.67 1.4 5.07 0l2.94-2.94a2.93 2.93 0 0 0-4.14-4.14z"/>',
|
||||||
"chevron-right": '<path d="m9 18 6-6-6-6"/>',
|
"chevron-right": '<path d="m9 18 6-6-6-6"/>',
|
||||||
"eraser": '<path d="m7 21-4.3-4.3c-.9-.9-.9-2.4 0-3.3L14 2l8 8-11 11Z"/><path d="M22 21H7"/><path d="m5 11 8 8"/>',
|
"eraser": '<path d="m7 21-4.3-4.3c-.9-.9-.9-2.4 0-3.3L14 2l8 8-11 11Z"/><path d="M22 21H7"/><path d="m5 11 8 8"/>',
|
||||||
|
|||||||
@ -209,6 +209,16 @@ class MainWindow(QMainWindow):
|
|||||||
add_action("fit_view", "Показать всё", self.canvas.fit_to_content, "eye", QKeySequence("Ctrl+0"))
|
add_action("fit_view", "Показать всё", self.canvas.fit_to_content, "eye", QKeySequence("Ctrl+0"))
|
||||||
grid_action = add_action("grid", "Сетка 34 мм", self.toggle_grid, "grid-2x2", QKeySequence("G"), "Показать или скрыть сетку 34 мм")
|
grid_action = add_action("grid", "Сетка 34 мм", self.toggle_grid, "grid-2x2", QKeySequence("G"), "Показать или скрыть сетку 34 мм")
|
||||||
grid_action.setCheckable(True)
|
grid_action.setCheckable(True)
|
||||||
|
arrows_action = add_action(
|
||||||
|
"direction_arrows",
|
||||||
|
"Стрелки направления",
|
||||||
|
self.toggle_direction_arrows,
|
||||||
|
"arrow-right",
|
||||||
|
QKeySequence("Ctrl+Shift+A"),
|
||||||
|
"Показать или скрыть стрелки направления на линиях",
|
||||||
|
)
|
||||||
|
arrows_action.setCheckable(True)
|
||||||
|
arrows_action.setChecked(True)
|
||||||
add_action("toggle_sidebar", "Скрыть боковую панель", self.toggle_sidebar, "chevron-right", QKeySequence("F9"))
|
add_action("toggle_sidebar", "Скрыть боковую панель", self.toggle_sidebar, "chevron-right", QKeySequence("F9"))
|
||||||
self._build_menu_bar()
|
self._build_menu_bar()
|
||||||
self.addAction(self.delete_action)
|
self.addAction(self.delete_action)
|
||||||
@ -234,6 +244,7 @@ class MainWindow(QMainWindow):
|
|||||||
view_menu = menu_bar.addMenu("Вид")
|
view_menu = menu_bar.addMenu("Вид")
|
||||||
view_menu.addAction(self.actions["fit_view"])
|
view_menu.addAction(self.actions["fit_view"])
|
||||||
view_menu.addAction(self.actions["grid"])
|
view_menu.addAction(self.actions["grid"])
|
||||||
|
view_menu.addAction(self.actions["direction_arrows"])
|
||||||
|
|
||||||
window_menu = menu_bar.addMenu("Окно")
|
window_menu = menu_bar.addMenu("Окно")
|
||||||
window_menu.addAction(self.actions["toggle_sidebar"])
|
window_menu.addAction(self.actions["toggle_sidebar"])
|
||||||
@ -349,6 +360,11 @@ class MainWindow(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
self.collapse_sidebar()
|
self.collapse_sidebar()
|
||||||
|
|
||||||
|
def toggle_direction_arrows(self) -> None:
|
||||||
|
action = self.actions.get("direction_arrows")
|
||||||
|
visible = True if action is None else action.isChecked()
|
||||||
|
self.canvas.set_direction_arrows_visible(visible)
|
||||||
|
|
||||||
def collapse_sidebar(self) -> None:
|
def collapse_sidebar(self) -> None:
|
||||||
if self.sidebar_splitter is None or self.sidebar_stack is None or self._sidebar_collapsed:
|
if self.sidebar_splitter is None or self.sidebar_stack is None or self._sidebar_collapsed:
|
||||||
return
|
return
|
||||||
|
|||||||
59
qa_smoke.py
59
qa_smoke.py
@ -82,6 +82,7 @@ def main() -> None:
|
|||||||
test_grid_toggle_adds_grid_items,
|
test_grid_toggle_adds_grid_items,
|
||||||
test_canvas_connects_new_block_to_previous_non_empty_block,
|
test_canvas_connects_new_block_to_previous_non_empty_block,
|
||||||
test_canvas_draws_direction_arrows_on_connections,
|
test_canvas_draws_direction_arrows_on_connections,
|
||||||
|
test_canvas_can_toggle_direction_arrows,
|
||||||
test_canvas_rebuild_drops_deleted_selection_items,
|
test_canvas_rebuild_drops_deleted_selection_items,
|
||||||
test_canvas_refresh_does_not_accumulate_connection_items,
|
test_canvas_refresh_does_not_accumulate_connection_items,
|
||||||
test_main_window_has_classic_menu_bar,
|
test_main_window_has_classic_menu_bar,
|
||||||
@ -90,6 +91,7 @@ def main() -> None:
|
|||||||
test_undo_redo_roundtrip_for_line_creation,
|
test_undo_redo_roundtrip_for_line_creation,
|
||||||
test_main_window_actions_use_lucide_icons,
|
test_main_window_actions_use_lucide_icons,
|
||||||
test_main_window_action_shortcuts_are_assigned,
|
test_main_window_action_shortcuts_are_assigned,
|
||||||
|
test_main_window_direction_arrow_action_toggles_canvas,
|
||||||
test_lucide_icon_names_are_declared,
|
test_lucide_icon_names_are_declared,
|
||||||
test_table_hides_empty_line_index_columns,
|
test_table_hides_empty_line_index_columns,
|
||||||
test_sidebar_collapses_and_expands,
|
test_sidebar_collapses_and_expands,
|
||||||
@ -794,6 +796,37 @@ def test_canvas_draws_direction_arrows_on_connections() -> None:
|
|||||||
assert sum(path.elementCount() for path in arrow_paths) >= 6
|
assert sum(path.elementCount() for path in arrow_paths) >= 6
|
||||||
|
|
||||||
|
|
||||||
|
def test_canvas_can_toggle_direction_arrows() -> None:
|
||||||
|
if LedCanvas is None or QApplication is None:
|
||||||
|
return
|
||||||
|
app = QApplication.instance() or QApplication([])
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
path = Path(temp_dir) / "arrows-toggle.json"
|
||||||
|
points = [
|
||||||
|
{"id": 0, "x": 0.0, "y": 0.0, "row": 0, "col": 0},
|
||||||
|
{"id": 1, "x": 34.0, "y": 0.0, "row": 0, "col": 1},
|
||||||
|
{"id": 2, "x": 68.0, "y": 0.0, "row": 0, "col": 2},
|
||||||
|
]
|
||||||
|
path.write_text(json.dumps({"circles": points}), encoding="utf-8")
|
||||||
|
doc = MappingDocument()
|
||||||
|
doc.load(path)
|
||||||
|
line = doc.add_line()
|
||||||
|
doc.add_block(line.id, [0, 1, 2])
|
||||||
|
|
||||||
|
canvas = LedCanvas()
|
||||||
|
canvas.set_document(doc)
|
||||||
|
canvas.set_active_line(line.id)
|
||||||
|
assert any(item.zValue() == 2 for item in canvas.connection_items)
|
||||||
|
|
||||||
|
canvas.set_direction_arrows_visible(False)
|
||||||
|
assert canvas.direction_arrows_visible() is False
|
||||||
|
assert not any(item.zValue() == 2 for item in canvas.connection_items)
|
||||||
|
|
||||||
|
canvas.set_direction_arrows_visible(True)
|
||||||
|
assert canvas.direction_arrows_visible() is True
|
||||||
|
assert any(item.zValue() == 2 for item in canvas.connection_items)
|
||||||
|
|
||||||
|
|
||||||
def test_canvas_rebuild_drops_deleted_selection_items() -> None:
|
def test_canvas_rebuild_drops_deleted_selection_items() -> None:
|
||||||
if LedCanvas is None or QApplication is None:
|
if LedCanvas is None or QApplication is None:
|
||||||
return
|
return
|
||||||
@ -855,7 +888,7 @@ def test_classic_menu_contains_expected_actions() -> None:
|
|||||||
expected = {
|
expected = {
|
||||||
"Файл": ["Открыть", "Сохранить", "Сохранить как", "Сгенерировать скрипт"],
|
"Файл": ["Открыть", "Сохранить", "Сохранить как", "Сгенерировать скрипт"],
|
||||||
"Правка": ["Отменить", "Повторить", "Удалить фиксацию", "Удалить все", "Очистить выбор"],
|
"Правка": ["Отменить", "Повторить", "Удалить фиксацию", "Удалить все", "Очистить выбор"],
|
||||||
"Вид": ["Показать всё", "Сетка 34 мм"],
|
"Вид": ["Показать всё", "Сетка 34 мм", "Стрелки направления"],
|
||||||
"Окно": ["Скрыть боковую панель"],
|
"Окно": ["Скрыть боковую панель"],
|
||||||
"Инструменты": ["Новая линия", "Сгенерировать скрипт", "Автоблоки", "Кисть"],
|
"Инструменты": ["Новая линия", "Сгенерировать скрипт", "Автоблоки", "Кисть"],
|
||||||
}
|
}
|
||||||
@ -950,6 +983,7 @@ def test_main_window_actions_use_lucide_icons() -> None:
|
|||||||
"clear_selection",
|
"clear_selection",
|
||||||
"fit_view",
|
"fit_view",
|
||||||
"grid",
|
"grid",
|
||||||
|
"direction_arrows",
|
||||||
"toggle_sidebar",
|
"toggle_sidebar",
|
||||||
}
|
}
|
||||||
missing = [key for key in expected_icon_actions if window.actions[key].icon().isNull()]
|
missing = [key for key in expected_icon_actions if window.actions[key].icon().isNull()]
|
||||||
@ -970,6 +1004,7 @@ def test_main_window_action_shortcuts_are_assigned() -> None:
|
|||||||
"clear_selection": "Esc",
|
"clear_selection": "Esc",
|
||||||
"fit_view": "Ctrl+0",
|
"fit_view": "Ctrl+0",
|
||||||
"grid": "G",
|
"grid": "G",
|
||||||
|
"direction_arrows": "Ctrl+Shift+A",
|
||||||
"toggle_sidebar": "F9",
|
"toggle_sidebar": "F9",
|
||||||
"add_line": "Ctrl+L",
|
"add_line": "Ctrl+L",
|
||||||
"auto_blocks": "Ctrl+Shift+B",
|
"auto_blocks": "Ctrl+Shift+B",
|
||||||
@ -981,9 +1016,31 @@ def test_main_window_action_shortcuts_are_assigned() -> None:
|
|||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_window_direction_arrow_action_toggles_canvas() -> None:
|
||||||
|
if MainWindow is None or QApplication is None:
|
||||||
|
return
|
||||||
|
app = QApplication.instance() or QApplication([])
|
||||||
|
window = MainWindow()
|
||||||
|
app.processEvents()
|
||||||
|
|
||||||
|
action = window.actions["direction_arrows"]
|
||||||
|
assert action.isChecked()
|
||||||
|
assert window.canvas.direction_arrows_visible() is True
|
||||||
|
|
||||||
|
action.trigger()
|
||||||
|
assert not action.isChecked()
|
||||||
|
assert window.canvas.direction_arrows_visible() is False
|
||||||
|
|
||||||
|
action.trigger()
|
||||||
|
assert action.isChecked()
|
||||||
|
assert window.canvas.direction_arrows_visible() is True
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
def test_lucide_icon_names_are_declared() -> None:
|
def test_lucide_icon_names_are_declared() -> None:
|
||||||
expected_names = {
|
expected_names = {
|
||||||
"archive-restore",
|
"archive-restore",
|
||||||
|
"arrow-right",
|
||||||
"chevron-right",
|
"chevron-right",
|
||||||
"eraser",
|
"eraser",
|
||||||
"eye",
|
"eye",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user