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.
