This thread will be pre-writing for one or more improved "Theory of Operation" sections in Leo's documentation.
It is also an extended heads up about important changes soon to be made in the trunk. Yesterday I started work on what seems like the last largish project left, namely simplifying Leo's key-handling logic. This morning I realized that the key handling is already almost optimal, except for one or two global renaming issues. Instead of key-handling, it is *event* handling that can, and must (imo) be cleaned up. Before discussing the proposed change we must have some important background. Key handling domains ================= Leo's key-handling code is very complex, but this complexity has almost nothing to do with supporting multiple guis. Indeed, I shall leave the vast majority of the code strictly untouched. The only real advantage of killing the Tk gui is that I don't have to run the Tk unit tests :-) This is not a trivial advantage. It means that I can make subtle changes to the unit tests without worrying about breaking Tk. As a thought experiment, however, I am always considering how to re-implement the Tk gui. The changes I shall describe later would not make the Tk gui significantly harder to re- implement. But I digress. The reason the key-handling code is complex is that it must deal with the following four fundamentally different problem domains: Domain 1. Parsing user bindings in @keys, @mode and @shortcut nodes. In this domain, complexity arises from allowing the user a variety of *equivalent* ways of specifying bindings. Furthermore, this domain allows the user to specify modes and per-pane bindings. Thus, all these complexities are unavoidable. The present work will not change this domain in *any* way. Domain 2. Maintaining and using binding tables. The result of parsing user bindings are a set of binding tables. These tables are complex, but we need not go into details here because only one method, k.masterKeyHandler (and its helper, getPaneBinding) uses the tables. The only thing we have to remember about the binding tables is that bindings are expressed in terms of so-called **strokes**. Strokes are the "official" form of every user binding. The essential property of a stroke is that it contains *all* the information required to handle the stroke: Leo can unambiguously determine exactly what a stroke means and what bindings are in effect for a stroke. If necessary, Leo can correctly insert the proper character *corresponding* to the stroke into any text widget. This correspondence (association) between the stroke and the actual character to be inserted into text widgets is crucial. This correspondence is created in the next domain. Anticipating a bit, for any incoming key event, event.stroke is the stroke, and event.char is the character (if any) that *might* (depending on bindings) be inserted into text widgets. Anticipating a bit more, most of Leo's core will use event.char. By the time k.masterKeyHandler has dispatched the key event to Leo's commands, event.stroke will have been fully handled, and only event.char will be relevant. Domain 3. Translating incoming key events into standard events (including strokes). The eventFilter method in qtGui.py creates leoKeyEvent objects. Turning "raw" Qt key events into leoKeyEvents is unavoidably complicated because eventFilter (and its allies) **must** carefully compute the stroke corresponding to the raw key event. There is no way around this requirement if Leo's binding machinery in domain 2 is to work. Happily, all this work has been stable for a long time. I will change nothing there. Domain 4. Showing key bindings in the print-commands and print- bindings commands. It's important not to forget this domain: there are some situations in which we want to represent '\b','\r','\n' and '\t' as 'BackSpace','Linefeed','Return' and 'Tab' (!) Simplifying leoKeyEvents =================== As we have just seen, the fundamental behind-the-scenes work of creating leoKeyEvents must **not** change in any way. However, there are several ways in which how Leo uses (or simulates) leoKeyEvents can be cleaned up. 1. At present, Leo's core is just a bit confused about when to use event.stroke and when to use event.char. The basic rule is that only k.masterKeyHandler should use event.stroke: everything else should use event.char (or the entire event). It's not clear that this confusion will actually require any changes to the code. 2. At present, leoKeyEvents use the strings 'Backspace','Linefeed','Return' and 'Tab' (The Gang of Four) in event.char, when using '\b','\r','\n',and '\t' would be more intuitive. In practice, it *might* be easy to make the change by removing the entries for 'Backspace','Linefeed','Return' and 'Tab' in k.tkNamesList. However, I plan to leave the code as it is, because any change here would complicate domain 4, and because mistakes would have far-reaching and unpleasant consequences. 3. The first "real" simplification will be to remove a bad redundancy in Leo's core. Sometimes the core uses event.char, but in other cases the core uses event.keysym. Happily, the solution is simply to rename event.keysym to event.char everywhere in Leo's core. This will result in a few other minor simplifications. Imo, it is *essential* to kill event.keysym completely. It is, in fact, an artifact of Tk events and has absolutely no place in Leo's core. The point is that leoKeyEvent objects are an encapsulation of the raw gui key events, and it is both wrong and extremely confusing for the underlying details to bleed into Leo's core. As discussed below, it is already possible to assert that event.char == event.keysym everywhere. 4. We come at last to the most "interesting" set of simplifications. At present, a few places in the code use g.bunches as dummy leoKeyEvents. These hacks must go--they have surprisingly pernicious effects. The worst is an almost-ridiculous hack in k.masterKeyHandler. As what might be called a desperation measure, k.masterKeyHandler translates *all* non-None events into **another** kind of leoKeyEvent, one defined in leoKeys.py, **not** the one defined in qtGui.py. This bogus class ensures that all ivars that one might want in a key event actually exist, at least to the extent that accessing the ivars doesn't throw an AttributeError! This is just wrong: the key event created in domain 3 (in qtGui.py) surely should already have defined all these ivars, so why in the world did I ever think this *other* leoKeyEvent was needed? The reason is simple, and surprising. Until this morning, it did not occur to me that **all** event objects passed around Leo were, in fact, *key* event objects. This is actually a large change in point of view. Taking a look at the eventFilter method, we see clearly see that *only* key events ever get passed to Leo's core. All other events are handled by Qt-specific event handlers. As can be seen, these non-key events *can* be passed to Leo, but only as the event arg in g.doHook (!) Thus, the only remaining question is whether any plugin ever calls k.masterKeyHandler. At present, no plugins do. The only call to k.masterKeyHandler in qtGui.py is the expected call in eventFilter. There are other calls to k.masterKeyHandler in Leo's core, but we can prove (by induction, if you will), that all events passed to k.masterKeyHandler are (or soon will be) proper leoKeyEvent objects. In short, the additional wrapping of key events in k.masterKeyHandler is wrong and should be removed. Strategy and tactics =============== It would would have been difficult (impossible really) to do the kind of analysis I have just done without Leo's unique features, especially clones and clone-find-all. Happily, the results indicate that only a very few changes are necessary. I plan to do them in the trunk today. It's important to have as many (perhaps unwilling ;-) testers as possible, as soon as possible. Don't panic. I have a plan that should minimize or eliminate all potential problems. I am going to eat my own dog food before upping any changes to insure that almost everything works. 1. The essential invariant is that the events passed to Leo's core methods really must be the leoKeyEvent objects created by qtGui.py. Imo, problems could arise only if this invariant ever becomes False. 2. Rather than *asserting* this invariant, the code will contain (already contains) calls to c.check_event(event) in all the obvious places. c.check_event is a "relaxed" place to do as much error checking is needed. In particular, running the unit tests calls c.check_event many times. 3. Before upping any changes to the trunk, I shall eliminate all the dummy events represented by g.bunches. This will allow c.check_event to assert that either the event is None (which is a perfectly reasonable value for many commands) or is a proper leoKeyEvent **defined in qtGui.py**. To make this check bullet-proof I'll rename leoKeyEvent to be leoQtKeyEvent, and kill the bogus leoKeyEvent completely. That's about it. After hours and hours of analysis, I am left with just a few simple tasks: 1. Eliminate the bogus leoKeyEvent class, and rename rename leoKeyEvent to be leoQtKeyEvent. 2. Replace all g.bunches used as dummy events by proper leoQtKeyEvents. To make this work, it may be easiest to define g.app.gui.createKeyEventFromStroke. 3. Add tests in c.check_event that verify that event is either None or a leoQtKeyEvent. 4. Rename event.keysym to be event.char throughout Leo's core, and remove the keysym ivar from leoQtKeyEvents. Conclusions ========= Imo, this work must be done now, for three reasons. First, the present code is needlessly complex. Now is the time to simplify it, while the details are fresh in my mind. Second, simplifying (and explaining) the most difficult part of Leo will make it possible for somebody to take over after I am gone. It's important that that person not treat Leo's key handling as unknowable cesspool. Finally, simplifying key handling means that macros can be recorded as a sequence of key events. At present, iirc, they are recorded as a sequence of strokes. Using events will be better because key events contain widget info and event.char fields in addition to the event.stroke field. In spite of the complexities, I expect the transition to go smoothly. If it does not, I'll apologize and revert the trunk :-) Several hours at least remain before I change the trunk. All comments welcome, but you probably won't convince me to use a separate branch. This work is essential for b3, and we might as well get to it... Edward -- You received this message because you are subscribed to the Google Groups "leo-editor" group. To post to this group, send email to leo-editor@googlegroups.com. To unsubscribe from this group, send email to leo-editor+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/leo-editor?hl=en.