Coverage for paintmaskanimator/main_window_input.py: 68%
390 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"""Application-level keyboard/mouse input handling for MainWindow.
3Split out of ``main_window.py`` as a mixin. This is the ``eventFilter`` installed
4on the application and the machinery behind it: shortcut-token normalisation and
5matching, the auxiliary hold-to-drag gestures (hand scroll, rotate, zoom) with
6their cursor bookkeeping, and the canvas hold-operation sync. Keeping it separate
7means the window shell no longer carries the event-loop hot path.
8"""
9import time
10from typing import Any
12from PySide6.QtCore import QEvent, QObject, QPoint, QPointF, QTimer, Qt
13from PySide6.QtGui import QCursor, QKeySequence
14from PySide6.QtWidgets import QAbstractButton, QApplication, QSplitter, QTabBar, QWidget
15from ._main_window_members import MainWindowMembers
16from .constants import HOLD_ZOOM_SHORTCUT
17from .utils import drag_zoom_factor, magnifier_cursor
18from .widgets import ShortcutDialog
19from .errors import OPERATION_ERRORS
20from .logging_setup import get_logger
22log = get_logger(__name__)
24_OPERATION_ERRORS = OPERATION_ERRORS
27class InputMixin(MainWindowMembers):
28 def _is_color_chart_widget(self, watched):
29 """Return safely during Qt teardown when the chart is already gone."""
30 if not isinstance(watched, QWidget):
31 return False
32 chart = getattr(self, "color_chart", None)
33 if chart is None: 33 ↛ 34line 33 didn't jump to line 34 because the condition on line 33 was never true
34 return False
35 try:
36 return watched is chart or chart.isAncestorOf(watched)
37 except RuntimeError:
38 # Qt can deliver one final application event after ADS has deleted
39 # the dock contents but before this Python event filter is removed.
40 return False
42 @staticmethod
43 def _normalize_shortcut_token(token):
44 aliases = {
45 "Control": "Ctrl",
46 "CTRL": "Ctrl",
47 "SHIFT": "Shift",
48 "ALT": "Alt",
49 "META": "Meta",
50 " ": "Space",
51 }
52 token = str(token).strip()
53 return aliases.get(token, token)
55 def _shortcut_tokens(self, action, fallback):
56 stored = action.property("holdShortcutText")
57 if stored is not None:
58 text = str(stored)
59 else:
60 text = action.shortcut().toString(
61 QKeySequence.SequenceFormat.PortableText
62 )
63 if not text: 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true
64 text = fallback
65 # Hold operations use the first chord only.
66 text = text.split(",", 1)[0]
67 return {
68 self._normalize_shortcut_token(token)
69 for token in text.split("+")
70 if token.strip()
71 }
73 def _event_key_token(self, event):
74 key_map = {
75 Qt.Key.Key_Control: "Ctrl",
76 Qt.Key.Key_Shift: "Shift",
77 Qt.Key.Key_Alt: "Alt",
78 Qt.Key.Key_Meta: "Meta",
79 Qt.Key.Key_Space: "Space",
80 }
81 if event.key() in key_map:
82 return key_map[event.key()]
83 text = QKeySequence(int(event.key())).toString(
84 QKeySequence.SequenceFormat.PortableText
85 )
86 return self._normalize_shortcut_token(text) if text else None
88 def _sync_modifier_tokens(self, modifiers):
89 mapping = (
90 (Qt.KeyboardModifier.ControlModifier, "Ctrl"),
91 (Qt.KeyboardModifier.ShiftModifier, "Shift"),
92 (Qt.KeyboardModifier.AltModifier, "Alt"),
93 (Qt.KeyboardModifier.MetaModifier, "Meta"),
94 )
95 for flag, token in mapping:
96 if modifiers & flag:
97 self._held_canvas_shortcut_tokens.add(token)
98 else:
99 self._held_canvas_shortcut_tokens.discard(token)
101 def _widget_in_timeline(self, widget):
102 return bool(
103 isinstance(widget, QWidget)
104 and (
105 widget is self.timeline
106 or self.timeline.isAncestorOf(widget)
107 )
108 )
110 def _hand_scroll_area_for_widget(self, widget):
111 if not isinstance(widget, QWidget): 111 ↛ 112line 111 didn't jump to line 112 because the condition on line 111 was never true
112 return None
113 if self._widget_in_timeline(widget):
114 return self.timeline.table
115 candidates = (
116 self.palette.scroll,
117 self.tools_scroll,
118 self.color_wheel_scroll,
119 self.color_slider_scroll,
120 self.palette_scroll,
121 )
122 for area in candidates:
123 if widget is area or area.isAncestorOf(widget): 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true
124 return area
125 return None
127 @staticmethod
128 def _mouse_global_position(event):
129 if hasattr(event, "globalPosition"): 129 ↛ 131line 129 didn't jump to line 131 because the condition on line 129 was always true
130 return QPointF(event.globalPosition())
131 return QPointF(QCursor.pos())
133 def _auxiliary_cursor_targets(self):
134 return (
135 self.timeline,
136 self.timeline.table.viewport(),
137 self.tools_dock,
138 self.tools_scroll.viewport(),
139 self.color_wheel_dock,
140 self.color_wheel_scroll.viewport(),
141 self.color_slider_dock,
142 self.color_slider_scroll.viewport(),
143 self.palette_dock,
144 self.palette_scroll.viewport(),
145 self.palette.scroll.viewport(),
146 )
148 def _update_auxiliary_hold_cursors(self):
149 tool = self.canvas.temp_tool
150 for widget in self._auxiliary_cursor_targets():
151 try:
152 widget.unsetCursor()
153 except RuntimeError:
154 log.debug("cursor target was deleted during cleanup", exc_info=True)
155 if tool == "hand":
156 for widget in self._auxiliary_cursor_targets():
157 try:
158 widget.setCursor(Qt.CursorShape.OpenHandCursor)
159 except RuntimeError:
160 log.debug("cursor target was deleted during update", exc_info=True)
161 elif tool == "zoom": 161 ↛ 162line 161 didn't jump to line 162 because the condition on line 161 was never true
162 try:
163 self.timeline.setCursor(magnifier_cursor())
164 self.timeline.table.viewport().setCursor(magnifier_cursor())
165 except RuntimeError:
166 log.debug("timeline was deleted during cursor update", exc_info=True)
168 def _finish_auxiliary_hold_drag(self):
169 grab_widget = self._ui_hold_grab_widget
170 self._ui_hold_drag_mode = None
171 self._ui_hold_scroll_area = None
172 self._ui_hold_grab_widget = None
173 if grab_widget is not None:
174 try:
175 grab_widget.releaseMouse()
176 except RuntimeError:
177 log.debug("mouse grab owner was deleted during release", exc_info=True)
178 self._update_auxiliary_hold_cursors()
180 def _handle_auxiliary_hold_event(self, watched, event):
181 event_type = event.type()
182 tool = self.canvas.temp_tool
184 if self._ui_hold_drag_mode is not None: 184 ↛ 185line 184 didn't jump to line 185 because the condition on line 184 was never true
185 if event_type == QEvent.Type.MouseMove:
186 current = self._mouse_global_position(event)
187 if self._ui_hold_drag_mode == "hand":
188 area = self._ui_hold_scroll_area
189 if area is not None:
190 delta = current - self._ui_hold_start_global
191 horizontal, vertical = self._ui_hold_start_scroll
192 area.horizontalScrollBar().setValue(
193 int(round(horizontal - delta.x()))
194 )
195 area.verticalScrollBar().setValue(
196 int(round(vertical - delta.y()))
197 )
198 area.viewport().setCursor(
199 Qt.CursorShape.ClosedHandCursor
200 )
201 elif self._ui_hold_drag_mode == "timeline_zoom":
202 delta_x = current.x() - self._ui_hold_last_global.x()
203 factor = drag_zoom_factor(delta_x)
204 viewport = self.timeline.table.viewport()
205 anchor = viewport.mapFromGlobal(
206 current.toPoint()
207 )
208 self.timeline.adjust_timeline_zoom(
209 factor,
210 anchor.x(),
211 anchor.y(),
212 )
213 self._ui_hold_last_global = current
214 event.accept()
215 return True
216 if event_type == QEvent.Type.MouseButtonRelease:
217 self._finish_auxiliary_hold_drag()
218 event.accept()
219 return True
221 # ズームツール保持中、またはAltを押しながらのホイールでタイムラインを拡大縮小する。
222 if event_type == QEvent.Type.Wheel and (
223 tool == "zoom"
224 or event.modifiers() & Qt.KeyboardModifier.AltModifier
225 ):
226 if self._widget_in_timeline(watched): 226 ↛ 242line 226 didn't jump to line 242 because the condition on line 226 was always true
227 angle = event.angleDelta()
228 # Alt+ホイールを横スクロールに読み替える環境では、縦の量が横に入る。
229 delta = angle.y() or angle.x()
230 if delta: 230 ↛ 242line 230 didn't jump to line 242 because the condition on line 230 was always true
231 viewport = self.timeline.table.viewport()
232 global_point = self._mouse_global_position(event)
233 anchor = viewport.mapFromGlobal(global_point.toPoint())
234 self.timeline.adjust_timeline_zoom(
235 1.15 ** (delta / 120),
236 anchor.x(),
237 anchor.y(),
238 )
239 event.accept()
240 return True
242 if event_type == QEvent.Type.MouseButtonPress:
243 if event.button() != Qt.MouseButton.LeftButton: 243 ↛ 244line 243 didn't jump to line 244 because the condition on line 243 was never true
244 return False
245 area = self._hand_scroll_area_for_widget(watched)
246 if tool == "hand" and area is not None: 246 ↛ 247line 246 didn't jump to line 247 because the condition on line 246 was never true
247 self._ui_hold_drag_mode = "hand"
248 self._ui_hold_scroll_area = area
249 self._ui_hold_start_global = self._mouse_global_position(event)
250 self._ui_hold_last_global = QPointF(
251 self._ui_hold_start_global
252 )
253 self._ui_hold_start_scroll = (
254 area.horizontalScrollBar().value(),
255 area.verticalScrollBar().value(),
256 )
257 self._ui_hold_grab_widget = (
258 watched if isinstance(watched, QWidget) else area.viewport()
259 )
260 try:
261 self._ui_hold_grab_widget.grabMouse()
262 except RuntimeError:
263 self._ui_hold_grab_widget = None
264 area.viewport().setCursor(Qt.CursorShape.ClosedHandCursor)
265 event.accept()
266 return True
267 if tool == "zoom" and self._widget_in_timeline(watched): 267 ↛ 268line 267 didn't jump to line 268 because the condition on line 267 was never true
268 current = self._mouse_global_position(event)
269 self._ui_hold_drag_mode = "timeline_zoom"
270 self._ui_hold_scroll_area = self.timeline.table
271 self._ui_hold_start_global = current
272 self._ui_hold_last_global = QPointF(current)
273 self._ui_hold_grab_widget = (
274 watched
275 if isinstance(watched, QWidget)
276 else self.timeline.table.viewport()
277 )
278 try:
279 self._ui_hold_grab_widget.grabMouse()
280 except RuntimeError:
281 self._ui_hold_grab_widget = None
282 event.accept()
283 return True
285 if event_type == QEvent.Type.MouseMove:
286 if tool == "hand" and self._hand_scroll_area_for_widget(watched): 286 ↛ 287line 286 didn't jump to line 287 because the condition on line 286 was never true
287 event.accept()
288 return True
289 if tool == "zoom" and self._widget_in_timeline(watched): 289 ↛ 290line 289 didn't jump to line 290 because the condition on line 289 was never true
290 event.accept()
291 return True
292 return False
294 def _update_canvas_hold_operation(self):
295 held = set(self._held_canvas_shortcut_tokens)
296 bindings = [
297 ("zoom", self.a_hold_zoom, HOLD_ZOOM_SHORTCUT),
298 ("rotate", self.a_hold_rotate, "Shift+Space"),
299 ("hand", self.a_hold_hand, "Space"),
300 ("eyedropper", self.a_hold_eyedropper, "Alt"),
301 ]
302 matches = []
303 for priority, (tool, action, fallback) in enumerate(bindings):
304 required = self._shortcut_tokens(action, fallback)
305 if required and required.issubset(held):
306 matches.append((len(required), -priority, tool))
307 new_tool = max(matches)[2] if matches else None
308 if {"Ctrl", "Alt"}.issubset(held) and self.canvas.tool in ("brush", "line"):
309 new_tool = None
310 if self.canvas.temp_tool != new_tool:
311 self.canvas.temp_tool = new_tool
312 self.canvas.drawing = False
313 self.canvas.middle_hand = False
314 self.canvas.update_tool_cursor()
315 self.canvas.update()
316 if ( 316 ↛ 323line 316 didn't jump to line 323 because the condition on line 316 was never true
317 self._ui_hold_drag_mode == "hand"
318 and new_tool != "hand"
319 ) or (
320 self._ui_hold_drag_mode == "timeline_zoom"
321 and new_tool != "zoom"
322 ):
323 self._finish_auxiliary_hold_drag()
324 else:
325 self._update_auxiliary_hold_cursors()
327 #: Space をこの秒数より短く押して離したら、ハンドではなく再生/停止にする。
328 SPACE_TAP_SECONDS = 0.3
330 def _space_tap_interrupted(self):
331 """Space を押している間に別の操作をしたら、短押し(再生)とみなさない。"""
332 self._space_tap_started = None
334 def _handle_space_tap(self, token, pressed):
335 """Space の短押しを再生/停止に、長押しをハンド(画面の移動)にする。
337 ハンドは押した瞬間から効くので、押してすぐドラッグすれば画面が動く。
338 ドラッグもせず他のキーも押さずに短く離したときだけ再生/停止する。
339 """
340 if pressed:
341 if token == "Space":
342 others = self._held_canvas_shortcut_tokens - {"Space"}
343 # Shift+Space(回転)などの組み合わせは短押しにしない。
344 self._space_tap_started = None if others else time.monotonic()
345 else:
346 self._space_tap_interrupted()
347 return
348 if token != "Space":
349 return
350 started = self._space_tap_started
351 self._space_tap_started = None
352 if started is None:
353 return
354 if time.monotonic() - started < self.SPACE_TAP_SECONDS:
355 self.a_tl_play.trigger()
357 def eventFilter(self, watched, event):
358 if not isinstance(watched, QObject): 358 ↛ 361line 358 didn't jump to line 361 because the condition on line 358 was never true
359 # QObject ではない watched を super().eventFilter に渡すと TypeError に
360 # なる。理由は SubView.eventFilter のコメントを参照。
361 return False
362 if (
363 self._space_tap_started is not None
364 and event.type() in (QEvent.Type.MouseButtonPress, QEvent.Type.Wheel)
365 ):
366 self._space_tap_interrupted()
367 if isinstance(watched, QWidget) and event.type() in (
368 QEvent.Type.ContextMenu,
369 QEvent.Type.MouseButtonPress,
370 QEvent.Type.MouseButtonRelease,
371 ):
372 candidate = watched
373 on_dock_chrome = False
374 while candidate is not None:
375 if (
376 isinstance(candidate, QTabBar)
377 or type(candidate).__name__ == "CDockWidgetTab"
378 or type(candidate).__name__ == "CDockAreaTitleBar"
379 ):
380 on_dock_chrome = True
381 break
382 candidate = candidate.parentWidget()
383 if on_dock_chrome and ( 383 ↛ 387line 383 didn't jump to line 387 because the condition on line 383 was never true
384 event.type() == QEvent.Type.ContextMenu
385 or event.button() == Qt.MouseButton.RightButton
386 ):
387 event.accept()
388 return True
390 pending_tab_drag = getattr(self, "_pending_dock_tab_drag", None)
391 if pending_tab_drag is not None:
392 if event.type() == QEvent.Type.MouseMove and ( 392 ↛ 417line 392 didn't jump to line 417 because the condition on line 392 was always true
393 event.buttons() & Qt.MouseButton.LeftButton
394 ):
395 dock, press_global, press_in_tab = pending_tab_drag
396 current_global = event.globalPosition().toPoint()
397 if (current_global - press_global).manhattanLength() >= 4: 397 ↛ 422line 397 didn't jump to line 422 because the condition on line 397 was always true
398 self._pending_dock_tab_drag = None
399 dock.setFloating()
400 floating = dock.window()
401 self._floating_move_window = floating
402 self._floating_tab_drag_dock = dock
403 floating_tab = dock.tabWidget()
404 grab_point = QPoint(
405 max(0, min(press_in_tab.x(), floating_tab.width() - 1)),
406 max(0, min(press_in_tab.y(), floating_tab.height() - 1)),
407 )
408 self._floating_move_offset = (
409 floating_tab.mapToGlobal(grab_point)
410 - floating.frameGeometry().topLeft()
411 )
412 self._start_manual_tab_drag_polling()
413 floating.move(current_global - self._floating_move_offset)
414 self._update_manual_tab_drop(current_global)
415 event.accept()
416 return True
417 elif event.type() == QEvent.Type.MouseButtonRelease:
418 self._pending_dock_tab_drag = None
419 event.accept()
420 return True
422 moving_window = getattr(self, "_floating_move_window", None)
423 if moving_window is not None:
424 if event.type() == QEvent.Type.MouseMove and (
425 event.buttons() & Qt.MouseButton.LeftButton
426 ):
427 offset = getattr(self, "_floating_move_offset", QPoint())
428 moving_window.move(event.globalPosition().toPoint() - offset)
429 if getattr(self, "_floating_tab_drag_dock", None) is not None: 429 ↛ 433line 429 didn't jump to line 433 because the condition on line 429 was always true
430 self._update_manual_tab_drop(
431 event.globalPosition().toPoint()
432 )
433 event.accept()
434 return True
435 if event.type() == QEvent.Type.MouseButtonRelease:
436 self._manual_tab_drag_timer.stop()
437 if getattr(self, "_floating_tab_drag_dock", None) is not None: 437 ↛ 441line 437 didn't jump to line 441 because the condition on line 437 was always true
438 self._finish_manual_tab_drop(
439 event.globalPosition().toPoint()
440 )
441 self._floating_drag_grabber = None
442 self._floating_move_window = None
443 self._floating_tab_drag_dock = None
444 event.accept()
445 return True
447 if (
448 event.type() == QEvent.Type.MouseButtonPress
449 and event.button() == Qt.MouseButton.LeftButton
450 and isinstance(watched, QWidget)
451 ):
452 candidate = watched
453 # ADS の CFloatingDockContainer / CDockWidgetTab は型名で見分けるので Any。
454 floating: Any = None
455 blocked_by_button = False
456 tab_drag = False
457 dock_tab: Any = None
458 on_dock_title_bar = False
459 while candidate is not None:
460 if isinstance(candidate, QAbstractButton): 460 ↛ 461line 460 didn't jump to line 461 because the condition on line 460 was never true
461 blocked_by_button = True
462 break
463 if isinstance(candidate, QTabBar): 463 ↛ 464line 463 didn't jump to line 464 because the condition on line 463 was never true
464 tab_drag = True
465 if type(candidate).__name__ == "CDockWidgetTab":
466 tab_drag = True
467 dock_tab = candidate
468 if type(candidate).__name__ == "CDockAreaTitleBar":
469 on_dock_title_bar = True
470 floating = getattr(
471 candidate, "_paintmask_move_container", None
472 )
473 if floating is not None: 473 ↛ 474line 473 didn't jump to line 474 because the condition on line 473 was never true
474 break
475 candidate = candidate.parentWidget()
476 if floating is None and on_dock_title_bar:
477 current_window = watched.window()
478 if type(current_window).__name__ == "CFloatingDockContainer": 478 ↛ 479line 478 didn't jump to line 479 because the condition on line 478 was never true
479 floating = current_window
480 if (
481 floating is None
482 and dock_tab is not None
483 and not blocked_by_button
484 ):
485 dock = dock_tab.dockWidget()
486 area = dock.dockAreaWidget()
487 if area is not None: 487 ↛ 489line 487 didn't jump to line 489 because the condition on line 487 was always true
488 area.setCurrentDockWidget(dock)
489 self._pending_dock_tab_drag = (
490 dock,
491 event.globalPosition().toPoint(),
492 dock_tab.mapFromGlobal(event.globalPosition().toPoint()),
493 )
494 event.accept()
495 return True
496 if floating is not None and not blocked_by_button: 496 ↛ 497line 496 didn't jump to line 497 because the condition on line 496 was never true
497 if tab_drag:
498 self._floating_move_window = floating
499 self._floating_tab_drag_dock = (
500 floating.topLevelDockWidget()
501 )
502 self._start_manual_tab_drag_polling()
503 self._floating_move_offset = (
504 event.globalPosition().toPoint()
505 - floating.frameGeometry().topLeft()
506 )
507 event.accept()
508 return True
509 # CFloatingDockContainer can expose a QWidgetWindow handle
510 # which is not an OS top-level window. startSystemMove() then
511 # emits "must be a top level window" on a plain click. Manual
512 # movement below works for both native and alien containers.
513 self._floating_move_window = floating
514 self._floating_move_offset = (
515 event.globalPosition().toPoint()
516 - floating.frameGeometry().topLeft()
517 )
518 event.accept()
519 return True
521 # カラーチャート上では、フォーカスがボタンやスクロールバーに
522 # あってもSpace系操作をチャートキャンバスへ渡す。
523 if ( 523 ↛ 531line 523 didn't jump to line 531 because the condition on line 523 was never true
524 self._is_color_chart_widget(watched)
525 and event.type() in (
526 QEvent.Type.ShortcutOverride,
527 QEvent.Type.KeyPress,
528 QEvent.Type.KeyRelease,
529 )
530 ):
531 if (
532 event.type() == QEvent.Type.ShortcutOverride
533 and event.key() in (Qt.Key.Key_Space, Qt.Key.Key_Control)
534 ):
535 event.accept()
536 return True
537 if (
538 event.type() in (QEvent.Type.KeyPress, QEvent.Type.KeyRelease)
539 and event.key() in (Qt.Key.Key_Space, Qt.Key.Key_Control)
540 and not event.isAutoRepeat()
541 ):
542 self.color_chart.tile_canvas.handle_hold_key_event(event)
543 event.accept()
544 return True
546 # サブビューパネルのSpace系操作を、アプリ全体のキャンバス用
547 # ショートカットへ横取りさせない。
548 if ( 548 ↛ 564line 548 didn't jump to line 564 because the condition on line 548 was never true
549 isinstance(watched, QWidget)
550 and (
551 watched is self.subview
552 or self.subview.isAncestorOf(watched)
553 )
554 and event.type() in (
555 QEvent.Type.ShortcutOverride,
556 QEvent.Type.KeyPress,
557 QEvent.Type.KeyRelease,
558 QEvent.Type.MouseButtonPress,
559 QEvent.Type.MouseButtonRelease,
560 QEvent.Type.MouseMove,
561 QEvent.Type.Wheel,
562 )
563 ):
564 if (
565 event.type() == QEvent.Type.ShortcutOverride
566 and event.key() in (Qt.Key.Key_Space, Qt.Key.Key_Control)
567 ):
568 event.accept()
569 return True
570 if (
571 event.type() in (QEvent.Type.KeyPress, QEvent.Type.KeyRelease)
572 and event.key() in (Qt.Key.Key_Space, Qt.Key.Key_Control)
573 and not event.isAutoRepeat()
574 ):
575 self.subview.handle_hold_key_event(event)
576 event.accept()
577 return True
578 return super().eventFilter(watched, event)
579 if event.type() == QEvent.Type.MouseButtonRelease:
580 QTimer.singleShot(0, self._sync_all_area_hamburgers)
581 if isinstance(watched, QWidget) and type(watched).__name__ == "QSplitterHandle":
582 splitter = watched.parentWidget()
583 area = self.tool_selector_dock.dockAreaWidget()
584 if (
585 isinstance(splitter, QSplitter)
586 and splitter.orientation() == Qt.Orientation.Horizontal
587 and area is not None
588 and splitter.indexOf(area) >= 0
589 ):
590 if event.type() == QEvent.Type.MouseButtonPress:
591 self._tool_selector_resize_drag_active = True
592 elif event.type() == QEvent.Type.MouseButtonRelease:
593 self._tool_selector_resize_drag_active = False
594 if (
595 event.type() == QEvent.Type.MouseButtonRelease
596 and self._pending_tool_selector_snap is not None
597 ):
598 self._tool_selector_snap_timer.stop()
599 QTimer.singleShot(0, self._apply_tool_selector_snap)
600 if event.type() in (
601 QEvent.Type.MouseButtonPress,
602 QEvent.Type.MouseButtonRelease,
603 QEvent.Type.MouseMove,
604 QEvent.Type.Wheel,
605 ) and self._handle_auxiliary_hold_event(watched, event):
606 return True
607 if ( 607 ↛ 612line 607 didn't jump to line 612 because the condition on line 607 was never true
608 event.type() == QEvent.Type.KeyPress
609 and self.canvas.transform_active
610 and event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter)
611 ):
612 self.canvas.commit_selection_transform()
613 return True
614 if ( 614 ↛ 619line 614 didn't jump to line 619 because the condition on line 614 was never true
615 event.type() == QEvent.Type.KeyPress
616 and self.canvas.transform_active
617 and event.key() == Qt.Key.Key_Escape
618 ):
619 self.canvas.cancel_selection_transform()
620 return True
622 if event.type() in (
623 QEvent.Type.ApplicationDeactivate,
624 QEvent.Type.WindowDeactivate,
625 ):
626 self.canvas._brush_resize_anchor = None
627 self._held_canvas_shortcut_tokens.clear()
628 self.tool_hold.release_all()
629 try:
630 self.color_chart.tile_canvas.clear_hold_keys()
631 except RuntimeError:
632 # The chart dock can already be destroyed during app teardown.
633 pass
634 self._update_canvas_hold_operation()
635 return super().eventFilter(watched, event)
637 if (
638 event.type() == QEvent.Type.ShortcutOverride
639 and not getattr(event, "isAutoRepeat", lambda: False)()
640 ):
641 # ツールの一時切り替え:ショートカットになるキーには KeyPress が
642 # 届かないので、押したことはここで覚える。
643 self.tool_hold.key_pressed(self._event_key_token(event))
644 if event.type() in (QEvent.Type.KeyPress, QEvent.Type.KeyRelease):
645 # ショートカット編集ダイアログ内では、入力したキーをキャンバス操作に使わない。
646 modal = QApplication.activeModalWidget()
647 if isinstance(modal, ShortcutDialog): 647 ↛ 648line 647 didn't jump to line 648 because the condition on line 647 was never true
648 return super().eventFilter(watched, event)
649 if getattr(event, "isAutoRepeat", lambda: False)(): 649 ↛ 650line 649 didn't jump to line 650 because the condition on line 649 was never true
650 return super().eventFilter(watched, event)
651 token = self._event_key_token(event)
652 if event.type() == QEvent.Type.KeyRelease:
653 self.tool_hold.key_released(token)
654 self._handle_space_tap(token, event.type() == QEvent.Type.KeyPress)
655 if event.type() == QEvent.Type.KeyPress:
656 if token: 656 ↛ 658line 656 didn't jump to line 658 because the condition on line 656 was always true
657 self._held_canvas_shortcut_tokens.add(token)
658 self._sync_modifier_tokens(event.modifiers())
659 else:
660 if token: 660 ↛ 662line 660 didn't jump to line 662 because the condition on line 660 was always true
661 self._held_canvas_shortcut_tokens.discard(token)
662 self._sync_modifier_tokens(event.modifiers())
663 # Qtの環境によっては解放イベントのmodifiersに解放前のキーが残る。
664 if token in ("Ctrl", "Shift", "Alt", "Meta"):
665 self._held_canvas_shortcut_tokens.discard(token)
666 self._update_canvas_hold_operation()
667 if token in ("Space", "Ctrl", "Shift", "Alt", "Meta"):
668 return True
669 return super().eventFilter(watched, event)