Coverage for graphica/gui/tools/manager.py: 93.3%
29 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""マウスの7つのモード(データカーソル / 注釈 / 自由配置 / 範囲選択 / ピーク配置 / スライス抽出 / 領域強調)を、
2どれか1つだけが有効になるよう MOUSE_MODES の表でまとめて管理する。
4モードを足すときは MOUSE_MODES に1行足す。各モードの _toggle_*_mode は、有効になるときの先頭で
5self._deactivate_other_mouse_modes('<名前>') を呼ぶだけでよい。QAction は toggled ではなく triggered に
6つないであるので、setChecked(False) だけではスロットが呼ばれない。解除ではチェックを外すのと
7_toggle_*_mode(False) の呼び出しの両方をする。
8"""
9import logging
10from collections import namedtuple
12logger = logging.getLogger(__name__)
15# name: モードの識別子(_deactivate_other_mouse_modes に渡す名前)
16# flag_attr: PlotterApp 上の「有効かどうか」を持つ属性名
17# action_attr: ツールバーの QAction を持つ属性名
18# toggle_method: ON/OFF を切り替えるメソッド名
19# uses_left_click / uses_right_click: プロット内の(ドラッグでない)クリックに意味があるか。
20# そのモードの最中は、表示範囲の操作(ダブルクリックでのリセット / 右クリックで戻す)を譲る(gui/tools/view_navigation.py)
21MouseMode = namedtuple("MouseMode", "name flag_attr action_attr toggle_method uses_left_click uses_right_click")
23MOUSE_MODES = (
24 MouseMode("cursor", "cursor_mode_enabled",
25 "cursor_action", "_toggle_cursor_mode", True, False),
26 MouseMode("annotation", "annotation_mode_enabled",
27 "annotation_action", "_toggle_annotation_mode", True, True),
28 MouseMode("layout_edit", "layout_edit_mode_enabled",
29 "layout_edit_action", "_toggle_layout_edit_mode", True, False),
30 MouseMode("range_select", "range_select_mode_enabled",
31 "range_select_action", "_toggle_range_select_mode", False, False),
32 MouseMode("peak_placement", "peak_placement_mode_enabled",
33 "peak_placement_action", "_toggle_peak_placement_mode", True, True),
34 MouseMode("slice_extraction", "slice_extraction_mode_enabled",
35 "slice_extraction_action", "_toggle_slice_extraction_mode", False, False),
36 MouseMode("region_highlight", "region_highlight_mode_enabled",
37 "region_highlight_action", "_toggle_region_highlight_mode", False, True),
38)
40MOUSE_MODES_BY_NAME = {mode.name: mode for mode in MOUSE_MODES}
43class ToolManager:
44 """モードの排他。タブの MOUSE_MODES の表を見て、フラグ・QAction・切り替えはタブの名前(ツールへの窓口)で触る。"""
46 def __init__(self, app):
47 self._app = app
49 def deactivate_others(self, active_name):
50 """active_name 以外のモードを解除する。有効でないモードには何もしないので、解除から呼び戻されて循環しない。"""
51 if active_name not in MOUSE_MODES_BY_NAME:
52 # 表への登録漏れ。動作は続けるが、開発中に気づけるようログに残す
53 logger.warning(
54 "未登録のマウスモード名です: %s (gui/tools/manager.py の "
55 "MOUSE_MODES に追加してください)", active_name
56 )
58 for mode in self._app.MOUSE_MODES:
59 if mode.name == active_name:
60 continue
61 if not getattr(self._app, mode.flag_attr, False):
62 continue
64 action = getattr(self._app, mode.action_attr, None)
65 if action is not None: 65 ↛ 68line 65 didn't jump to line 68 because the condition on line 65 was always true
66 action.setChecked(False)
68 toggle = getattr(self._app, mode.toggle_method, None)
69 if callable(toggle): 69 ↛ 73line 69 didn't jump to line 73 because the condition on line 69 was always true
70 toggle(False)
71 else:
72 # メソッドが無くても、フラグは落として「2つ有効」を残さない
73 setattr(self._app, mode.flag_attr, False)
75 def active_name(self):
76 """有効なモードの名前。無ければ None。"""
77 for mode in self._app.MOUSE_MODES:
78 if getattr(self._app, mode.flag_attr, False):
79 return mode.name
80 return None