The relevant code is in the mvc-prototype branch in 
leo/extensions/myleoqt.py.

Leo architecture follows MVC design pattern. However during its long 
history some code parts ended up misplaced. In this post I'll try to 
explain why I think that the present split between Model, View and 
Controller is not optimal.

There were several factors that caused this misplacement. Moving from Tk to 
Qt (and for a long time keeping both GUIs around) is one of the causes. Tk 
didn't have tree widget and Leo had to provide tree drawing code. By 
switching to Qt which has a decent tree widget and keeping the old method 
for drawing nodes Leo didn't use Qt tree widget to its full potential. Also 
adding some features like user defined icons per node, chapters and 
hoist/dehoist commands many of the native tree widget features were not 
used but instead they were re-implemented. All these features are natively 
supported by qt tree widget. They naturally belong to the *View* but they 
ended up in *model* and in *controller*.

In every GUI framework which has a tree widget like Qt, wxPython, 
Jython/Swing, ... Tree widget has its own data model. The more powerful 
widget is, the more data goes in its model and more work for Leo to 
synchronize its own model with the model of tree widget.

By moving these features from Leo's *Controller* and *Model* to the *View* 
the synchronization between Leo's *Model* and tree widgets model becomes 
greatly simplified.

To prove this claim I wrote a little prototype. It is a Qt application 
which has three widgets: tree, body and toolbar. It can read Leo documents 
in '.leo.db' format. To keep this prototype as simple as possible I didn't 
want to add reading and writing of external files. The '.leo.db' file 
contains the whole outline (including external files).

In the toolbar there are several buttons for outline modification commands. 
Moving selected node up, down, left or right; demoting and promoting 
selected node, as well as undo/redo buttons. Using these buttons one can 
freely modify outline and then undo or redo operations. Selecting nodes in 
the tree causes body to show content of the selected node. One can edit 
body and changes will be recorded in Leo's model. Node can be selected 
either by mouse click or using keyboard when tree has focus. By double 
clicking node user can edit the headline and the change will be recorded in 
Leo's model too.

[Note that undo/redo work only for outline modifications not for typing in 
body or changing the headlines.]

There are also hoist and dehoist commands that work just like in Leo. If 
you hoist on the node whose headline starts with '@chapter' its children 
will become top level nodes. Otherwise hoisted node becomes the only top 
level node. Dehoist button returns the tree in its previous state. Hoists 
can be stacked.

All this functionality is achieved in less than 500 lines including 
comments.

*How it works?*

Instead of making changes in Leo's model (by linking and unlinking v-nodes) 
and then asking the tree to re-arrange its items according to the v-nodes 
which is at least O(n) complexity operation and can be a performance 
bottleneck for large outlines, or even worse asking the tree to recreate 
the whole data model from scratch, in this prototype all those outline 
operations are made at the same time in both tree widget data-model and in 
v-nodes. This eliminates need for synchronization between these two models. 
No need for c.redraw_after..., c.redraw, c.redraw_later,... No need for 
several locks, for checking if the selected position is visible or not, is 
it outside hoist/chapter,...

Then there is a nice feature that each clone can be expanded or collapsed 
independently of the other clones. In Leo this feature suffers from 
instability of position instances when outline changes. In this prototype 
this feature is fully supported. I haven't implemented yet preserving this 
expanded/collapsed state between two application runs, but it would be 
trivial to store this information in the c.db and restore it back on next 
run.

Please note that in this prototype I haven't use the leoNodes.Position 
class at all. In Leo's current code the Positions are used everywhere and 
their instability makes many of Leo's methods much more complicated than it 
is really necessary. The way I see it, positions are used in two ways.

   1. as a handle for v. (whenever p.v, p.h, p.b, p.u, p.is... are used). 
   And Position was meant to be just a pointer to some v node on its 
   particular place in the outline. Now, every tree widget must have its own 
   data-model and its own way of pointing to the particular node painted on 
   the screen. In Qt it is a trivial to attach user data to those pointers. 
   Pointers are tree widget items, and they have an API for storing and 
   retrieving user data. I used it in this prototype to attach v node to each 
   item. If the tree widget in some other framework doesn't offer such API, it 
   would be still trivial to store this data in two dict objects indexToV and 
   vToIndexes.
   2. The other way Leo uses positions is as a bunch of traversal methods 
   (like back, next, backVis, nextVis, threadNext, afterTree,... are used to 
   move position)

Those two use cases should be treated separately. I believe that many of 
Leo's internal methods and commands can be quite easily changed to use 
directly v nodes instead of positions. For backward compatibility we could 
add new methods with the same name + '_v' to indicate that function/method 
expects p.v as argument and not p. Old methods (without '_v' in their name) 
should just call the new methods with the p.v as an argument. And Leo 
internal code should be changed to use those new methods as much as 
possible.

For the first use case when p is used just as a handle to underlying v, 
instead of Position, Leo internally can use something that comes from the 
*View*. After all the idea of Position is to be just a pointer to the 
object on the user's screen. Therefore it belongs to the *View*. In this 
prototype I have simply used g.bunch(v=v), i.e. the only field of p that is 
used in the prototype is p.v field.

If we refactor Leo controller to ask its GUI for a suitable implementation 
of Position class instead of using the default leoNodes.Position, we can 
allow each GUI to provide implementation that best fits. Other GUI's (like 
console GUI), for now can simply return leoNodes.Position. But Qt GUI can 
return something better, an object which combine native QTreeWidgetItem 
with the set of traversal methods. Most likely this QtPosition will be 
immune to outline changes. (QTreeWidgetItems are immune).

*What about user scripts that modify outline?*

User scripts should work without any modification. They can freely modify 
outline using the standard Position class or even directly manipulating 
v-nodes. This will certainly cause tree widget data model to get out of 
sync. The possible solution would be to make a snapshot of the current 
outline shape before executing user script. When the script finishes, Leo 
will compare outline shape with the last snapshot and apply differences to 
the tree data-model to re-synchronize it. The code for this 
re-synchronization can be found in another branch tree-refresh.

*How to read the prototype code?*

Nevermind the arguments 0, 1024 when you see them. Items have a method 
setData which takes three arguments: a column index, an int representing a 
role of data and finally data which is v instance. The first two arguments 
are always 0 and 1024 which means column 0 and UserData Role. Having 
everywhere those two arguments (0, 1024) is a bit ugly but just ignore 
those arguments for now.

The DummyLeoController has a minimal set of fields to be used as a context 
argument when creating VNodes. It has a minimal undo/redo implementation 
very similar to Leo's Undoer class but simplified. It also has a 'p' 
property which is not a real Position but just a bunch object with the 
single field v. Instead of setCurrentPosition, DummyLeoController has 
setCurrentNode which accepts v instance as an argument and not p. The 
DummyLeoController has a field *guiapi* which is a handle to the *View* 
part of the M-V-C trio. The idea is that c can access all view related 
functions through this field. Each GUI implementation can provide its own 
version of this field but they all should have the same (required) 
fields/methods. This can fully replace Leo's GUI wrappers. The effect is 
the same *View* and *Controller* are separated and they communicate using 
the restricted API defined by this field. But there is no need for 
additional layers of classes. Each GUI is free to chose the most suitable 
way to create this field. In this simple prototype this field is just full 
MyGUI instance.

The MyGUI class is a GUI provider for a controller and a QApplication at 
the same time. It sets the *guiapi* ivar on c. In real implementation this 
ivar should contain just an official API methods for GUI, but in this 
prototype it is the full MyGUI instance.

*How to see this prototype in action?*

   1. checkout the mvc-prototype branch
   2. open leo/core/LeoPyRef.leo in Leo and save this document as 
   leo/core/LeoPyRef.db This should provide a large outline for the 
   prototype to play with.
   3. run prototype using 
   python leo/extensions/myleoqt.py
   
   
*Summary*

I hope that this prototype gives enough evidence for my claim that the tree 
selecting code, tree drawing code, hoisting code, chapter code all can be 
much simpler than they are now in Leo's code. All these features in the 
prototype are implemented in less than 500 lines including the comments. 
Much of this simplicity is achieved through consistent avoidance of the 
Position class.

Your comments please.

-- 
You received this message because you are subscribed to the Google Groups 
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/leo-editor/9d332be7-3f59-4fa9-ac3d-e0726ea02809%40googlegroups.com.

Reply via email to