Add tree context menu hotkeys and support renaming lines in GUI
- Добавлены локальные горячие клавиши для действий в дереве: F2 для переименования, Ctrl+Del для удаления, Ctrl+M для объединения блоков. - Изменен дефолтный хоткей для удаления всех фиксаций (clear_all) на Shift+Del. - Реализована функция rename_line для переименования линий по аналогии с блоками. - В контекстное меню дерева добавлено отображение горячих клавиш справа от названий команд. - Добавлены интеграционные тесты для верификации переименования линий и регистрации хоткеев.
This commit is contained in:
parent
c15fe6e510
commit
65d7580977
@ -245,6 +245,27 @@ class MainWindow(QMainWindow):
|
||||
self.tree.customContextMenuRequested.connect(self._show_tree_menu)
|
||||
self.tree.itemDoubleClicked.connect(self._on_tree_item_double_clicked)
|
||||
|
||||
self.tree_rename_action = QAction(self)
|
||||
self.tree_rename_action.setShortcut(QKeySequence("F2"))
|
||||
self.tree_rename_action.setShortcutContext(Qt.WidgetShortcut)
|
||||
self.tree_rename_action.triggered.connect(self.rename_selected_tree_item)
|
||||
self.tree.addAction(self.tree_rename_action)
|
||||
self.actions["tree_rename"] = self.tree_rename_action
|
||||
|
||||
self.tree_delete_action = QAction(self)
|
||||
self.tree_delete_action.setShortcut(QKeySequence("Ctrl+Del"))
|
||||
self.tree_delete_action.setShortcutContext(Qt.WidgetShortcut)
|
||||
self.tree_delete_action.triggered.connect(self.remove_selected_tree_item)
|
||||
self.tree.addAction(self.tree_delete_action)
|
||||
self.actions["tree_delete"] = self.tree_delete_action
|
||||
|
||||
self.tree_merge_action = QAction(self)
|
||||
self.tree_merge_action.setShortcut(QKeySequence("Ctrl+M"))
|
||||
self.tree_merge_action.setShortcutContext(Qt.WidgetShortcut)
|
||||
self.tree_merge_action.triggered.connect(self.merge_selected_blocks)
|
||||
self.tree.addAction(self.tree_merge_action)
|
||||
self.actions["tree_merge"] = self.tree_merge_action
|
||||
|
||||
self.table = QTableWidget(0, 8)
|
||||
self.table.setHorizontalHeaderLabels(
|
||||
["ID", "X", "Y", "Ряд", "Кол.", "Линия", "Блок", "UID"]
|
||||
@ -360,7 +381,7 @@ class MainWindow(QMainWindow):
|
||||
QKeySequence("Ctrl+Del"),
|
||||
"Полностью удалить выбранные LED-точки из схемы",
|
||||
)
|
||||
add_action("clear_all", "Удалить все", self.clear_all_assignments, "eraser", QKeySequence("Ctrl+Shift+Del"))
|
||||
add_action("clear_all", "Удалить все", self.clear_all_assignments, "eraser", QKeySequence("Shift+Del"))
|
||||
add_action("clear_selection", "Очистить выбор", lambda: self.canvas.select_points([]), "archive-restore", QKeySequence("Esc"))
|
||||
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 мм")
|
||||
@ -1701,32 +1722,39 @@ class MainWindow(QMainWindow):
|
||||
self.tree.setCurrentItem(item)
|
||||
item.setSelected(True)
|
||||
menu = QMenu(self)
|
||||
merge_action = menu.addAction("Объединить блоки")
|
||||
merge_action = menu.addAction("Объединить блоки\tCtrl+M")
|
||||
merge_action.setEnabled(self._can_merge_selected_tree_blocks())
|
||||
rename_block_action = None
|
||||
rename_action = None
|
||||
data = None
|
||||
if item is not None:
|
||||
data = item.data(0, Qt.UserRole)
|
||||
if data and data[0] == "block":
|
||||
rename_block_action = menu.addAction("Переименовать блок")
|
||||
label = "Удалить выбранную фиксацию"
|
||||
if data:
|
||||
if data[0] == "block":
|
||||
rename_action = menu.addAction("Переименовать блок\tF2")
|
||||
elif data[0] == "line":
|
||||
rename_action = menu.addAction("Переименовать линию\tF2")
|
||||
label = "Удалить выбранную фиксацию\tCtrl+Del"
|
||||
if item is not None and data is not None:
|
||||
if data[0] == "line":
|
||||
label = "Удалить линию"
|
||||
label = "Удалить линию\tCtrl+Del"
|
||||
elif data[0] == "block":
|
||||
label = "Удалить блок"
|
||||
label = "Удалить блок\tCtrl+Del"
|
||||
delete_action = menu.addAction(label)
|
||||
delete_action.setEnabled(item is not None)
|
||||
clear_all_action = menu.addAction("Удалить все фиксации")
|
||||
clear_all_action = menu.addAction("Удалить все фиксации\tShift+Del")
|
||||
clear_all_action.setEnabled(bool(self.document.lines))
|
||||
fit_action = menu.addAction("Показать все точки")
|
||||
fit_action.setEnabled(bool(self.document.points))
|
||||
chosen = menu.exec(self.tree.viewport().mapToGlobal(pos))
|
||||
if chosen == merge_action:
|
||||
self.merge_selected_blocks()
|
||||
elif rename_block_action and chosen == rename_block_action:
|
||||
line_id, block_id = data[1], data[2]
|
||||
self.rename_block(line_id, block_id)
|
||||
elif rename_action and chosen == rename_action:
|
||||
if data[0] == "block":
|
||||
line_id, block_id = data[1], data[2]
|
||||
self.rename_block(line_id, block_id)
|
||||
elif data[0] == "line":
|
||||
line_id = data[1]
|
||||
self.rename_line(line_id)
|
||||
elif chosen == delete_action:
|
||||
self.remove_selected_tree_item()
|
||||
elif chosen == clear_all_action:
|
||||
@ -1736,9 +1764,25 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _on_tree_item_double_clicked(self, item: QTreeWidgetItem, column: int) -> None:
|
||||
data = item.data(0, Qt.UserRole)
|
||||
if data and data[0] == "block":
|
||||
line_id, block_id = data[1], data[2]
|
||||
self.rename_block(line_id, block_id)
|
||||
if data:
|
||||
if data[0] == "block":
|
||||
line_id, block_id = data[1], data[2]
|
||||
self.rename_block(line_id, block_id)
|
||||
elif data[0] == "line":
|
||||
line_id = data[1]
|
||||
self.rename_line(line_id)
|
||||
|
||||
def rename_selected_tree_item(self) -> None:
|
||||
item = self.tree.currentItem()
|
||||
if item is not None:
|
||||
data = item.data(0, Qt.UserRole)
|
||||
if data:
|
||||
if data[0] == "block":
|
||||
line_id, block_id = data[1], data[2]
|
||||
self.rename_block(line_id, block_id)
|
||||
elif data[0] == "line":
|
||||
line_id = data[1]
|
||||
self.rename_line(line_id)
|
||||
|
||||
def rename_block(self, line_id: int, block_id: int) -> None:
|
||||
block = self._find_block_or_none(line_id, block_id)
|
||||
@ -1763,6 +1807,29 @@ class MainWindow(QMainWindow):
|
||||
self.refresh_tree()
|
||||
self._refresh_ui_state()
|
||||
|
||||
def rename_line(self, line_id: int) -> None:
|
||||
line = self._get_line_or_none(line_id)
|
||||
if line is None:
|
||||
return
|
||||
self._disable_shortcuts()
|
||||
try:
|
||||
new_name, ok = QInputDialog.getText(
|
||||
self,
|
||||
"Переименовать линию",
|
||||
"Введите новое имя линии:",
|
||||
QLineEdit.EchoMode.Normal,
|
||||
line.name
|
||||
)
|
||||
finally:
|
||||
self._enable_shortcuts()
|
||||
if ok and new_name.strip() and new_name.strip() != line.name:
|
||||
self._push_undo()
|
||||
line.name = new_name.strip()
|
||||
self._dirty = True
|
||||
self.status.setText(f"Линия переименована в '{line.name}'.")
|
||||
self.refresh_tree()
|
||||
self._refresh_ui_state()
|
||||
|
||||
def merge_selected_blocks(self) -> None:
|
||||
selected_blocks = self._selected_tree_blocks()
|
||||
if len(selected_blocks) < 2:
|
||||
|
||||
25
qa_smoke.py
25
qa_smoke.py
@ -120,6 +120,7 @@ def main() -> None:
|
||||
test_panel_mapping_generation_from_document,
|
||||
test_panel_mapping_generation_with_strategies,
|
||||
test_block_renaming_and_naming_strategy,
|
||||
test_line_renaming_and_tree_shortcuts,
|
||||
test_save_preserves_unknown_source_fields,
|
||||
test_ordered_json_preserves_document_and_remaps_uids,
|
||||
test_background_load_save_roundtrip,
|
||||
@ -1093,6 +1094,28 @@ def test_block_renaming_and_naming_strategy() -> None:
|
||||
assert "getBlockIndexByName" in legacy_ino
|
||||
|
||||
|
||||
def test_line_renaming_and_tree_shortcuts() -> None:
|
||||
if MainWindow is None or QApplication is None:
|
||||
return
|
||||
app = QApplication.instance() or QApplication([])
|
||||
window = MainWindow()
|
||||
|
||||
line = window.document.add_line()
|
||||
assert line.name == f"Линия {line.id}"
|
||||
|
||||
line.name = "Верхняя Линия"
|
||||
assert line.name == "Верхняя Линия"
|
||||
|
||||
assert "tree_rename" in window.actions
|
||||
assert "tree_delete" in window.actions
|
||||
assert "tree_merge" in window.actions
|
||||
assert window.actions["tree_rename"].shortcut().toString() == "F2"
|
||||
assert window.actions["tree_delete"].shortcut().toString() == "Ctrl+Del"
|
||||
assert window.actions["tree_merge"].shortcut().toString() == "Ctrl+M"
|
||||
|
||||
window.close()
|
||||
|
||||
|
||||
def test_save_preserves_unknown_source_fields() -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
source = Path(temp_dir) / "custom.json"
|
||||
@ -1954,7 +1977,7 @@ def test_main_window_action_shortcuts_are_assigned() -> None:
|
||||
"generate_script": "Ctrl+G",
|
||||
"settings": "Ctrl+,",
|
||||
"delete_points": "Ctrl+Del",
|
||||
"clear_all": "Ctrl+Shift+Del",
|
||||
"clear_all": "Shift+Del",
|
||||
"clear_selection": "Esc",
|
||||
"fit_view": "Ctrl+0",
|
||||
"grid": "G",
|
||||
|
||||
@ -18,7 +18,7 @@ SHORTCUT_DEFINITIONS: list[tuple[str, str, str]] = [
|
||||
("redo", "Повторить", "Ctrl+Y"),
|
||||
("delete", "Удалить фиксацию", "Del"),
|
||||
("delete_points", "Удалить точки", "Ctrl+Del"),
|
||||
("clear_all", "Удалить все", "Ctrl+Shift+Del"),
|
||||
("clear_all", "Удалить все", "Shift+Del"),
|
||||
("clear_selection", "Очистить выбор", "Esc"),
|
||||
("fit_view", "Показать всё", "Ctrl+0"),
|
||||
("grid", "Сетка 34 мм", "G"),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user