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.