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