Coverage for C:\Repos\leo-editor\leo\plugins\qt_events.py: 19%
266 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20140907103315.18766: * @file ../plugins/qt_events.py
4#@@first
5"""Leo's Qt event handling code."""
6#@+<< about internal bindings >>
7#@+node:ekr.20110605121601.18538: ** << about internal bindings >>
8#@@language rest
9#@+at
10# Here are the rules for translating key bindings (in leoSettings.leo) into keys
11# for k.bindingsDict:
12#
13# 1. The case of plain letters is significant: a is not A.
14#
15# 2. The Shift- prefix can be applied *only* to letters. Leo will ignore (with a
16# warning) the shift prefix applied to any other binding, e.g., Ctrl-Shift-(
17#
18# 3. The case of letters prefixed by Ctrl-, Alt-, Key- or Shift- is *not*
19# significant. Thus, the Shift- prefix is required if you want an upper-case
20# letter (with the exception of 'bare' uppercase letters.)
21#
22# The following table illustrates these rules. In each row, the first entry is the
23# key (for k.bindingsDict) and the other entries are equivalents that the user may
24# specify in leoSettings.leo:
25#
26# a, Key-a, Key-A
27# A, Shift-A
28# Alt-a, Alt-A
29# Alt-A, Alt-Shift-a, Alt-Shift-A
30# Ctrl-a, Ctrl-A
31# Ctrl-A, Ctrl-Shift-a, Ctrl-Shift-A
32# , Key-!,Key-exclam,exclam
33#
34# This table is consistent with how Leo already works (because it is consistent
35# with Tk's key-event specifiers). It is also, I think, the least confusing set of
36# rules.
37#@-<< about internal bindings >>
38import sys
39from typing import Any, List
40from leo.core import leoGlobals as g
41from leo.core import leoGui
42from leo.core.leoQt import QtCore, QtGui, QtWidgets
43from leo.core.leoQt import Key, KeyboardModifier, Type
44#@+others
45#@+node:ekr.20210512101604.1: ** class LossageData
46class LossageData:
48 def __init__(self, actual_ch, binding, ch, keynum, mods, mods2, mods3, text, toString):
50 self.actual_ch = actual_ch
51 self.binding = binding
52 self.ch = ch
53 self.keynum = keynum
54 self.mods = mods
55 self.mods2 = mods2
56 self.mods3 = mods3
57 self.stroke = None # Set later.
58 self.text = text
59 self.toString = toString
61 def __repr__(self):
62 return (
63 f"keynum: {self.keynum:>7x} "
64 f"binding: {self.binding}"
65 # f"ch: {self.ch:>7s} "
66 # f"= {self.actual_ch!r}"
67 # f"mods: {self.mods}, {self.mods2}, {self.mods3}\n"
68 # f"stroke: {self.stroke!r}\n"
69 # f"text: {self.text!r}\n"
70 # f"toString: {self.toString!r}\n"
71 )
73 __str__ = __repr__
74#@+node:ekr.20141028061518.17: ** class LeoQtEventFilter
75class LeoQtEventFilter(QtCore.QObject): # type:ignore
76 #@+others
77 #@+node:ekr.20110605121601.18539: *3* filter.ctor
78 def __init__(self, c, w, tag=''):
79 """Ctor for LeoQtEventFilter class."""
80 super().__init__()
81 self.c = c
82 self.w = w # A leoQtX object, *not* a Qt object.
83 self.tag = tag
84 # Debugging.
85 self.keyIsActive = False
86 # Pretend there is a binding for these characters.
87 close_flashers = c.config.getString('close-flash-brackets') or ''
88 open_flashers = c.config.getString('open-flash-brackets') or ''
89 self.flashers = open_flashers + close_flashers
90 # #1563: Support alternate keyboards.
91 self.keyboard_kind = c.config.getString('keyboard-kind') or 'default-keyboard'
92 # Support for ctagscompleter.py plugin.
93 self.ctagscompleter_active = False
94 self.ctagscompleter_onKey = None
95 #@+node:ekr.20110605121601.18540: *3* filter.eventFilter & helpers
96 def eventFilter(self, obj, event):
97 """Return False if Qt should handle the event."""
98 c, k = self.c, self.c.k
99 #
100 # Handle non-key events first.
101 if not g.app:
102 return False # For unit tests, but g.unitTesting may be False!
103 if not self.c.p:
104 return False # Startup.
105 #
106 # Trace events.
107 if 'events' in g.app.debug:
108 if isinstance(event, QtGui.QKeyEvent):
109 self.traceKeys(obj, event)
110 else:
111 self.traceEvent(obj, event)
112 self.traceWidget(event)
113 #
114 # Let Qt handle the non-key events.
115 if self.doNonKeyEvent(event, obj):
116 return False
117 #
118 # Ignore incomplete key events.
119 if self.shouldIgnoreKeyEvent(event, obj):
120 return False
121 #
122 # Generate a g.KeyStroke for k.masterKeyHandler.
123 try:
124 binding, ch, lossage = self.toBinding(event)
125 if not binding:
126 return False # Let Qt handle the key.
127 #
128 # Pass the KeyStroke to masterKeyHandler.
129 key_event = self.createKeyEvent(event, c, self.w, ch, binding)
130 #
131 # #1933: Update the g.app.lossage
132 if len(g.app.lossage) > 99:
133 g.app.lossage.pop()
134 lossage.stroke = key_event.stroke
135 g.app.lossage.insert(0, lossage)
136 #
137 # Call masterKeyHandler!
138 k.masterKeyHandler(key_event)
139 c.outerUpdate()
140 except Exception:
141 g.es_exception()
142 return True # Whatever happens, suppress all other Qt key handling.
143 #@+node:ekr.20110605195119.16937: *4* filter.createKeyEvent
144 def createKeyEvent(self, event, c, w, ch, binding):
146 return leoGui.LeoKeyEvent(
147 c=self.c,
148 # char = None doesn't work at present.
149 # But really, the binding should suffice.
150 char=ch,
151 event=event,
152 binding=binding,
153 w=w,
154 x=getattr(event, 'x', None) or 0,
155 y=getattr(event, 'y', None) or 0,
156 x_root=getattr(event, 'x_root', None) or 0,
157 y_root=getattr(event, 'y_root', None) or 0,
158 )
159 #@+node:ekr.20180413180751.2: *4* filter.doNonKeyEvent
160 def doNonKeyEvent(self, event, obj):
161 """Handle all non-key event. """
162 c = self.c
163 eventType = event.type()
164 if eventType == Type.WindowActivate:
165 g.app.gui.onActivateEvent(event, c, obj, self.tag)
166 elif eventType == Type.WindowDeactivate:
167 g.app.gui.onDeactivateEvent(event, c, obj, self.tag)
168 elif eventType == Type.FocusIn:
169 if self.tag == 'body':
170 c.frame.body.onFocusIn(obj)
171 if c.frame and c.frame.top and obj is c.frame.top.lineEdit:
172 if c.k.getStateKind() == 'getArg':
173 c.frame.top.lineEdit.restore_selection()
174 elif eventType == Type.FocusOut and self.tag == 'body':
175 c.frame.body.onFocusOut(obj)
176 # Return True unless we have a key event.
177 return eventType not in (Type.ShortcutOverride, Type.KeyPress, Type.KeyRelease)
178 #@+node:ekr.20180413180751.3: *4* filter.shouldIgnoreKeyEvent
179 def shouldIgnoreKeyEvent(self, event, obj):
180 """
181 Return True if we should ignore the key event.
183 Alas, QLineEdit *only* generates ev.KeyRelease on Windows, Ubuntu,
184 so the following hack is required.
185 """
186 c = self.c
187 t = event.type()
188 isEditWidget = (obj == c.frame.tree.edit_widget(c.p))
189 if isEditWidget:
190 # QLineEdit: ignore all key events except keyRelease events.
191 return t != Type.KeyRelease
192 if t == Type.KeyPress:
193 # Hack Alert!
194 # On some Linux systems (Kubuntu, Debian, the Win or SHIFT-Win keys
195 # insert garbage symbols into editing areas. Filter out these
196 # key events. NOTE - this is a *magic number* - who knows if
197 # it could change in the future?
198 if event.key() == 0x1000053 and sys.platform == 'linux':
199 return True
200 return False # Never ignore KeyPress events.
201 # This doesn't work. Two shortcut-override events are generated!
202 # if t == ev.ShortcutOverride and event.text():
203 # return False # Don't ignore shortcut overrides with a real value.
204 return True # Ignore everything else.
205 #@+node:ekr.20110605121601.18543: *4* filter.toBinding & helpers
206 def toBinding(self, event):
207 """
208 Return (binding, actual_ch):
210 binding: A user binding, to create g.KeyStroke.
211 Spelling no longer fragile.
212 actual_ch: The insertable key, or ''.
213 """
214 mods = self.qtMods(event)
215 keynum, text, toString, ch = self.qtKey(event)
216 actual_ch = text or toString
217 #
218 # Never allow empty chars, or chars in g.app.gui.ignoreChars
219 if toString in g.app.gui.ignoreChars:
220 return None, None, None
221 ch = ch or toString or ''
222 if not ch:
223 return None, None, None
224 #
225 # Check for AltGr and Alt+Ctrl keys *before* creating a binding.
226 actual_ch, ch, mods2 = self.doMacTweaks(actual_ch, ch, mods)
227 mods3 = self.doAltTweaks(actual_ch, keynum, mods2, toString)
228 #
229 # Use *ch* in the binding.
230 # Clearer w/o f-strings.
231 binding = '%s%s' % (''.join([f"{z}+" for z in mods3]), ch)
232 #
233 # Return the tweaked *actual* char.
234 binding, actual_ch = self.doLateTweaks(binding, actual_ch)
235 #
236 # #1933: Create lossage data.
237 lossage = LossageData(
238 actual_ch, binding, ch, keynum, mods, mods2, mods3, text, toString)
239 return binding, actual_ch, lossage
240 #@+node:ekr.20180419154543.1: *5* filter.doAltTweaks
241 def doAltTweaks(self, actual_ch, keynum, mods, toString):
242 """Turn AltGr and some Alt-Ctrl keys into plain keys."""
244 def removeAltCtrl(mods):
245 for mod in ('Alt', 'Control'):
246 if mod in mods:
247 mods.remove(mod)
248 return mods
250 #
251 # Remove Alt, Ctrl for AltGr keys.
252 # See https://en.wikipedia.org/wiki/AltGr_key
254 if keynum == Key.Key_AltGr:
255 return removeAltCtrl(mods)
256 #
257 # Never alter complex characters.
258 if len(actual_ch) != 1:
259 return mods
260 #
261 # #1563: A hack for German and Spanish keyboards:
262 # Remove *plain* Shift modifier for colon and semicolon.
263 # https://en.m.wikipedia.org/wiki/German_keyboard_layout
264 kind = self.keyboard_kind.lower()
265 if (kind in ('german', 'spanish')
266 and actual_ch in ":;"
267 and 'Shift' in mods
268 and 'Alt' not in mods and 'Control' not in mods
269 ):
270 mods.remove('Shift')
271 elif kind == 'us-international':
272 pass # To do.
273 #
274 # Handle Alt-Ctrl modifiers for chars whose that are not ascii.
275 # Testing: Alt-Ctrl-E is '€'.
276 if ord(actual_ch) > 127 and 'Alt' in mods and 'Control' in mods:
277 return removeAltCtrl(mods)
278 return mods
279 #@+node:ekr.20180417161548.1: *5* filter.doLateTweaks
280 def doLateTweaks(self, binding, ch):
281 """Make final tweaks. g.KeyStroke does other tweaks later."""
282 #
283 # These are needed because ch is separate from binding.
284 if ch == '\r':
285 ch = '\n'
286 if binding == 'Escape':
287 ch = 'Escape'
288 #
289 # Adjust the case of the binding string (for the minibuffer).
290 if len(ch) == 1 and len(binding) == 1 and ch.isalpha() and binding.isalpha():
291 if ch != binding:
292 binding = ch
293 return binding, ch
294 #@+node:ekr.20180419160958.1: *5* filter.doMacTweaks
295 def doMacTweaks(self, actual_ch, ch, mods):
296 """Replace MacOS Alt characters."""
297 if not g.isMac:
298 return actual_ch, ch, mods
299 if ch == 'Backspace':
300 # On the Mac, the reported char can be DEL (7F)
301 return '\b', ch, mods
302 if len(mods) == 1 and mods[0] == 'Alt':
303 # Patch provided by resi147.
304 # See the thread: special characters in MacOSX, like '@'.
305 mac_d = {
306 '/': '\\',
307 '5': '[',
308 '6': ']',
309 '7': '|',
310 '8': '{',
311 '9': '}',
312 'e': '€',
313 'l': '@',
314 }
315 if ch.lower() in mac_d:
316 # Ignore the case.
317 actual_ch = ch = g.checkUnicode(mac_d.get(ch.lower()))
318 mods = []
319 return actual_ch, ch, mods
320 #@+node:ekr.20110605121601.18544: *5* filter.qtKey
321 def qtKey(self, event):
322 """
323 Return the components of a Qt key event.
325 Modifiers are handled separately.
327 Return (keynum, text, toString, ch).
329 keynum: event.key()
330 ch: chr(keynum) or '' if there is an exception.
331 toString:
332 For special keys: made-up spelling that become part of the setting.
333 For all others: QtGui.QKeySequence(keynum).toString()
334 text: event.text()
335 """
336 text, toString, ch = '', '', '' # Defaults.
337 #
338 # Leo 6.4: Test keynum's directly.
339 # The values are the same in Qt4, Qt5, Qt6.
340 keynum = event.key()
341 if keynum in (
342 0x01000020, # Key_Shift
343 0x01000021, # Key_Control
344 0x01000022, # Key_Meta
345 0x01000023, # Key_Alt
346 0x01001103, # Key_AltGr
347 0x01000024, # Key_CapsLock
348 ):
349 # Disallow bare modifiers.
350 return keynum, text, toString, ch
351 #
352 # Compute toString and ch.
353 text = event.text() # This is the unicode character!
354 toString = QtGui.QKeySequence(keynum).toString()
355 #
356 # #1244461: Numpad 'Enter' key does not work in minibuffer
357 if toString == 'Enter':
358 toString = 'Return'
359 if toString == 'Esc':
360 toString = 'Escape'
361 try:
362 ch = chr(keynum)
363 except ValueError:
364 pass
365 return keynum, text, toString, ch
366 #@+node:ekr.20120204061120.10084: *5* filter.qtMods
367 def qtMods(self, event):
368 """Return the text version of the modifiers of the key event."""
369 modifiers = event.modifiers()
370 mod_table = (
371 (KeyboardModifier.AltModifier, 'Alt'),
372 (KeyboardModifier.ControlModifier, 'Control'),
373 (KeyboardModifier.MetaModifier, 'Meta'),
374 (KeyboardModifier.ShiftModifier, 'Shift'),
375 # #1448: Replacing this by 'Key' would make separate keypad bindings impossible.
376 (KeyboardModifier.KeypadModifier, 'KeyPad'),
377 )
378 # pylint: disable=superfluous-parens.
379 mods = [b for a, b in mod_table if (modifiers & a)]
380 return mods
381 #@+node:ekr.20140907103315.18767: *3* filter.Tracing
382 #@+node:ekr.20190922075339.1: *4* filter.traceKeys
383 def traceKeys(self, obj, event):
384 if g.unitTesting:
385 return
386 e = QtCore.QEvent
387 key_events = {
388 e.KeyPress: 'key-press', # 6
389 e.KeyRelease: 'key-release', # 7
390 e.Shortcut: 'shortcut', # 117
391 e.ShortcutOverride: 'shortcut-override', # 51
392 }
393 kind = key_events.get(event.type())
394 if kind:
395 mods = ','.join(self.qtMods(event))
396 g.trace(f"{kind:>20}: {mods:>7} {event.text()!r}")
397 #@+node:ekr.20110605121601.18548: *4* filter.traceEvent
398 def traceEvent(self, obj, event):
399 if g.unitTesting:
400 return
401 # http://qt-project.org/doc/qt-4.8/qevent.html#properties
402 exclude_names = ('tree', 'log', 'body', 'minibuffer')
403 traceActivate = True
404 traceFocus = False
405 traceHide = False
406 traceHover = False
407 traceKey = False
408 traceLayout = False
409 traceMouse = False
410 tracePaint = False
411 traceUpdate = False
412 c, e = self.c, QtCore.QEvent
413 eventType = event.type()
414 # http://doc.qt.io/qt-5/qevent.html
415 show: List[Any] = []
416 ignore = [
417 e.MetaCall, # 43
418 e.Timer, # 1
419 e.ToolTip, # 110
420 ]
421 activate_events = (
422 (e.Close, 'close'), # 19
423 (e.WindowActivate, 'window-activate'), # 24
424 (e.WindowBlocked, 'window-blocked'), # 103
425 (e.WindowUnblocked, 'window-unblocked'), # 104
426 (e.WindowDeactivate, 'window-deactivate'), # 25
427 )
428 focus_events = [
429 (e.Enter, 'enter'), # 10
430 (e.Leave, 'leave'), # 11
431 (e.FocusIn, 'focus-in'), # 8
432 (e.FocusOut, 'focus-out'), # 9
433 (e.ShowToParent, 'show-to-parent'), # 26
434 ]
435 if hasattr(e, 'FocusAboutToChange'):
436 # pylint: disable=no-member
437 focus_events.extend([
438 (e.FocusAboutToChange, 'focus-about-to-change'), # 23
439 ])
440 hide_events = (
441 (e.Hide, 'hide'), # 18
442 (e.HideToParent, 'hide-to-parent'), # 27
443 # (e.LeaveEditFocus,'leave-edit-focus'), # 151
444 (e.Show, 'show'), # 17
445 )
446 hover_events = (
447 (e.HoverEnter, 'hover-enter'), # 127
448 (e.HoverLeave, 'hover-leave'), # 128
449 (e.HoverMove, 'hover-move'), # 129
450 )
451 key_events = [
452 (e.KeyPress, 'key-press'), # 6
453 (e.KeyRelease, 'key-release'), # 7
454 (e.Shortcut, 'shortcut'), # 117
455 (e.ShortcutOverride, 'shortcut-override'), # 51
456 ]
457 if hasattr(e, 'InputMethodQuery'):
458 # pylint: disable=no-member
459 key_events.extend([
460 (e.InputMethodQuery, 'input-method-query'), # 207
461 ])
462 layout_events = [
463 (e.ChildAdded, 'child-added'), # 68
464 (e.ChildRemoved, 'child-removed'), # 71
465 (e.DynamicPropertyChange, 'dynamic-property-change'), # 170
466 (e.FontChange, 'font-change'), # 97
467 (e.LayoutRequest, 'layout-request'), # 76
468 (e.Move, 'move'), # 13 widget's position changed.
469 (e.Resize, 'resize'), # 14
470 (e.StyleChange, 'style-change'), # 100
471 (e.ZOrderChange, 'z-order-change'), # 126
472 ]
473 if hasattr(e, 'CloseSoftwareInputPanel'):
474 layout_events.extend([
475 (e.CloseSoftwareInputPanel, 'close-sip'), # 200
476 ])
477 mouse_events = (
478 (e.MouseMove, 'mouse-move'), # 155
479 (e.MouseButtonPress, 'mouse-press'), # 2
480 (e.MouseButtonRelease, 'mouse-release'), # 3
481 (e.Wheel, 'mouse-wheel'), # 31
482 )
483 paint_events = [
484 (e.ChildPolished, 'child-polished'), # 69
485 (e.PaletteChange, 'palette-change'), # 39
486 (e.ParentChange, 'parent-change'), # 21
487 (e.Paint, 'paint'), # 12
488 (e.Polish, 'polish'), # 75
489 (e.PolishRequest, 'polish-request'), # 74
490 ]
491 if hasattr(e, 'RequestSoftwareInputPanel'):
492 paint_events.extend([
493 (e.RequestSoftwareInputPanel, 'sip'), # 199
494 ])
495 update_events = (
496 (e.UpdateLater, 'update-later'), # 78
497 (e.UpdateRequest, 'update'), # 77
498 )
499 option_table = (
500 (traceActivate, activate_events),
501 (traceFocus, focus_events),
502 (traceHide, hide_events),
503 (traceHover, hover_events),
504 (traceKey, key_events),
505 (traceLayout, layout_events),
506 (traceMouse, mouse_events),
507 (tracePaint, paint_events),
508 (traceUpdate, update_events),
509 )
510 for option, table in option_table:
511 if option:
512 show.extend(table)
513 else:
514 for n, tag in table:
515 ignore.append(n)
516 for val, kind in show:
517 if self.tag in exclude_names:
518 return
519 if eventType == val:
520 tag = (
521 obj.objectName() if hasattr(obj, 'objectName')
522 else f"id: {id(obj)}, {obj.__class__.__name__}"
523 )
524 if traceKey:
525 g.trace(
526 f"{kind:-25} {self.tag:-25} "
527 f"in-state: {repr(c.k and c.k.inState()):5} obj: {tag}")
528 return
529 if eventType not in ignore:
530 tag = (
531 obj.objectName() if hasattr(obj, 'objectName')
532 else f"id: {id(obj)}, {obj.__class__.__name__}"
533 )
534 g.trace(f"{eventType:-25} {self.tag:-25} {tag}")
535 #@+node:ekr.20131121050226.16331: *4* filter.traceWidget
536 def traceWidget(self, event):
537 """Show unexpected events in unusual widgets."""
538 verbose = False # Not good for --trace-events
539 e = QtCore.QEvent
540 assert isinstance(event, QtCore.QEvent)
541 et = event.type()
542 # http://qt-project.org/doc/qt-4.8/qevent.html#properties
543 ignore_d = {
544 e.ChildAdded: 'child-added', # 68
545 e.ChildPolished: 'child-polished', # 69
546 e.ChildRemoved: 'child-removed', # 71
547 e.Close: 'close', # 19
548 e.CloseSoftwareInputPanel: 'close-software-input-panel', # 200
549 178: 'contents-rect-change', # 178
550 # e.DeferredDelete:'deferred-delete', # 52 (let's trace this)
551 e.DynamicPropertyChange: 'dynamic-property-change', # 170
552 e.FocusOut: 'focus-out', # 9 (We don't care if we are leaving an unknown widget)
553 e.FontChange: 'font-change', # 97
554 e.Hide: 'hide', # 18
555 e.HideToParent: 'hide-to-parent', # 27
556 e.HoverEnter: 'hover-enter', # 127
557 e.HoverLeave: 'hover-leave', # 128
558 e.HoverMove: 'hover-move', # 129
559 e.KeyPress: 'key-press', # 6
560 e.KeyRelease: 'key-release', # 7
561 e.LayoutRequest: 'layout-request', # 76
562 e.Leave: 'leave', # 11 (We don't care if we are leaving an unknown widget)
563 # e.LeaveEditFocus:'leave-edit-focus', # 151
564 e.MetaCall: 'meta-call', # 43
565 e.Move: 'move', # 13 widget's position changed.
566 e.MouseButtonPress: 'mouse-button-press', # 2
567 e.MouseButtonRelease: 'mouse-button-release', # 3
568 e.MouseButtonDblClick: 'mouse-button-double-click', # 4
569 e.MouseMove: 'mouse-move', # 5
570 e.MouseTrackingChange: 'mouse-tracking-change', # 105
571 e.Paint: 'paint', # 12
572 e.PaletteChange: 'palette-change', # 39
573 e.ParentChange: 'parent-change', # 21
574 e.Polish: 'polish', # 75
575 e.PolishRequest: 'polish-request', # 74
576 e.RequestSoftwareInputPanel: 'request-software-input-panel', # 199
577 e.Resize: 'resize', # 14
578 e.ShortcutOverride: 'shortcut-override', # 51
579 e.Show: 'show', # 17
580 e.ShowToParent: 'show-to-parent', # 26
581 e.StyleChange: 'style-change', # 100
582 e.StatusTip: 'status-tip', # 112
583 e.Timer: 'timer', # 1
584 e.ToolTip: 'tool-tip', # 110
585 e.WindowBlocked: 'window-blocked', # 103
586 e.WindowUnblocked: 'window-unblocked', # 104
587 e.ZOrderChange: 'z-order-change', # 126
588 }
589 focus_d = {
590 e.DeferredDelete: 'deferred-delete', # 52
591 e.Enter: 'enter', # 10
592 e.FocusIn: 'focus-in', # 8
593 e.WindowActivate: 'window-activate', # 24
594 e.WindowDeactivate: 'window-deactivate', # 25
595 }
596 line_edit_ignore_d = {
597 e.Enter: 'enter', # 10 (mouse over)
598 e.Leave: 'leave', # 11 (mouse over)
599 e.FocusOut: 'focus-out', # 9
600 e.WindowActivate: 'window-activate', # 24
601 e.WindowDeactivate: 'window-deactivate', # 25
602 }
603 none_ignore_d = {
604 e.Enter: 'enter', # 10 (mouse over)
605 e.Leave: 'leave', # 11 (mouse over)
606 e.FocusOut: 'focus-out', # 9
607 e.WindowActivate: 'window-activate', # 24
608 }
609 if et in ignore_d:
610 return
611 w = QtWidgets.QApplication.focusWidget()
612 if verbose: # Too verbose for --trace-events.
613 for d in (ignore_d, focus_d, line_edit_ignore_d, none_ignore_d):
614 t = d.get(et)
615 if t:
616 break
617 else:
618 t = et
619 g.trace(f"{t:20} {w.__class__}")
620 return
621 if w is None:
622 if et not in none_ignore_d:
623 t = focus_d.get(et) or et
624 g.trace(f"None {t}")
625 if isinstance(w, QtWidgets.QPushButton):
626 return
627 if isinstance(w, QtWidgets.QLineEdit):
628 if et not in line_edit_ignore_d:
629 t = focus_d.get(et) or et
630 if hasattr(w, 'objectName'):
631 tag = w.objectName()
632 else:
633 tag = f"id: {id(w)}, {w.__class__.__name__}"
634 g.trace(f"{t:20} {tag}")
635 return
636 t = focus_d.get(et) or et
637 if hasattr(w, 'objectName'):
638 tag = w.objectName()
639 else:
640 tag = f"id: {id(w)}, {w.__class__.__name__}"
641 g.trace(f"{t:20} {tag}")
642 #@-others
643#@-others
644#@@language python
645#@@tabwidth -4
646#@@pagewidth 70
647#@-leo