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.

Reply via email to