On Sunday, May 3, 2020 at 10:11:48 PM UTC+1, vitalije wrote:
>
> 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.
>

Hi Vitalije
    interesting stuff - but I want to check.question an assumption here. As 
far as I know tk has not been retired, and Leo 'must' continue to support 
it. Or have I missed some news?

    Jon N

-- 
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 leo-editor+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/leo-editor/24612237-ba6c-47dc-82af-143a1efc59a2%40googlegroups.com.

Reply via email to