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