Coverage for paintmaskanimator/main_window_tool_hold.py: 90%
66 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
1"""ツールの一時切り替え; owned by ``MainWindow`` as ``window.tool_hold``.
3CLIP STUDIO PAINT の「一時切り替え」と同じく、ツールのキーを押すとすぐその
4ツールに切り替え、環境設定の長さ以上押し続けてから離したら元のツールへ戻す。
5長さを 0 ms にすると、押している間だけ切り替わる(完全な一時切り替え)。
6環境設定で OFF のとき(既定)は、これまでどおり押したツールに切り替えたままにする。
8キーを押したことは、ショートカットの直前に届く ``ShortcutOverride`` で知る
9(ショートカットになったキーには ``KeyPress`` が届かないため)。メニューから
10選んだときは長押しにならないよう、キーの押下と結び付いたときだけ扱う。
12A collaborator rather than a mixin -- see ``main_window_export.py`` for why.
13"""
14from __future__ import annotations
16import time
17from dataclasses import dataclass
18from typing import TYPE_CHECKING, FrozenSet, Optional
20from PySide6.QtCore import QTimer
22from . import preferences
24if TYPE_CHECKING:
25 from .main_window import MainWindow
27_MODIFIERS = frozenset({"Ctrl", "Shift", "Alt", "Meta"})
28#: キーの押下(ShortcutOverride)からショートカットの実行までに許す時間(秒)。
29_PRESS_MATCH_SECONDS = 0.5
30#: 描いている途中で戻すのを待つとき、描き終わりを確かめる間隔(ミリ秒)。
31_WAIT_FOR_STROKE_MS = 30
34@dataclass
35class _Hold:
36 tool: str
37 previous: str
38 keys: FrozenSet[str]
39 started: float
42class ToolHoldController:
43 """Owned by ``MainWindow`` as ``window.tool_hold``."""
45 def __init__(self, window: "MainWindow"):
46 self.window = window
47 self._hold: Optional[_Hold] = None
48 self._last_key: Optional[tuple] = None
49 self._revert_to: Optional[tuple] = None
51 def key_pressed(self, token):
52 """キーが押された(ショートカットになる前の ShortcutOverride で呼ぶ)。"""
53 if token: 53 ↛ exitline 53 didn't return from function 'key_pressed' because the condition on line 53 was always true
54 self._last_key = (token, time.monotonic())
56 def select(self, tool_id, action):
57 """ツールのショートカット・メニューが選ばれた。"""
58 tools = self.window.tools
59 keys = frozenset(
60 token for token in self.window._shortcut_tokens(action, "")
61 if token not in _MODIFIERS
62 )
63 hold = self._hold
64 if hold is not None and hold.tool == tool_id: 64 ↛ 65line 64 didn't jump to line 65 because the condition on line 64 was never true
65 return # キーの自動リピート
66 from_key = (
67 self._last_key is not None
68 and self._last_key[0] in keys
69 and time.monotonic() - self._last_key[1] < _PRESS_MATCH_SECONDS
70 )
71 self._last_key = None
72 if not preferences.tool_hold_enabled() or not from_key:
73 self._hold = None
74 tools.select_tool(tool_id)
75 return
76 # 一時切り替え中に別のツールのキーを押したら、戻り先は最初のツールのまま。
77 previous = hold.previous if hold is not None else tools.active_tool
78 self._hold = _Hold(tool_id, previous, keys, time.monotonic())
79 tools.select_tool(tool_id)
81 def key_released(self, token):
82 """キーが離された。長押しだったら元のツールへ戻す。"""
83 hold = self._hold
84 if hold is None or token not in hold.keys:
85 return
86 self._hold = None
87 held_ms = (time.monotonic() - hold.started) * 1000.0
88 if held_ms >= preferences.tool_hold_ms():
89 self._revert(hold.tool, hold.previous)
91 def release_all(self):
92 """ウィンドウからフォーカスが外れた。押していたキーは離したものとみなす。"""
93 hold = self._hold
94 if hold is not None: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 self.key_released(next(iter(hold.keys), None))
97 def _revert(self, tool, previous):
98 self._revert_to = (tool, previous)
99 self._try_revert()
101 def _try_revert(self):
102 pending = self._revert_to
103 if pending is None: 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true
104 return
105 if self.window.canvas.drawing:
106 # 描き終わってから戻す(描いている途中でツールを変えない)。
107 QTimer.singleShot(_WAIT_FOR_STROKE_MS, self._try_revert)
108 return
109 self._revert_to = None
110 tool, previous = pending
111 # その間に別のツールを選んでいたら、そちらを優先する。
112 if self.window.tools.active_tool == tool and previous != tool: 112 ↛ exitline 112 didn't return from function '_try_revert' because the condition on line 112 was always true
113 self.window.tools.select_tool(previous)
116__all__ = ["ToolHoldController"]