ENB: rethinking Model/View/Controller split in Leo

140 views
Skip to first unread message

vitalije

unread,
May 3, 2020, 5:11:48 PM5/3/20
to leo-editor
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.

SegundoBob

unread,
May 3, 2020, 6:12:04 PM5/3/20
to leo-editor
I've been thinking about implementing a directed graph editor for a long time.  Of course, most of my thinking is based on my use of Leo-Editor and my limited understanding of its internals.  I've done only a little implementation of my ideas.  I have not produced anything of use to me or of interest to anyone else.

But several months ago, I concluded that Leo-Editor positions are a very bad idea and that using  GNX's instead would be much simpler and much more robust.  I also concluded that in a directed graph, I don't need clones because multiple parents is no big deal in a directed graph.  Consequently, I stopped worrying about clones.  So I don't know if clones make GNX's inadequate as pointers, requiring that v-nodes be used as pointers instead of just GNX's.

For the little it is worth, in so far as I understand Vitalije, and I don't completely understand him, I think he is right.

Respectfully,
SegundoBob

Thomas Passin

unread,
May 4, 2020, 12:23:08 AM5/4/20
to leo-editor
I'm always in favor of good separation of interests.  It can be hard to achieve in practice, and it's always helpful to be diligent about keeping one's eye on the ball.  What I'm reading here sounds pretty interesting.

About clones, we always talk about cloning nodes, but it seems to me that what actually gets cloned is an entire subtree - that is, the clone includes (clones of) a node's descendants.  Perhaps a change in the conceptual model of clones from nodes to subtrees would make it easier to work out how to handle them.

I'm not sure how a subtree is currently modeled in Leo.  Since a tree can be built out of nodes that have child and parent nodes,  one can be constructed without an explicit model for a (sub)tree.  But since many operations we want to do are really on (sub)trees and not nodes, maybe an explicit tree concept in addition to a node concept would be useful(if there isn't one already).

vitalije

unread,
May 4, 2020, 2:51:22 AM5/4/20
to leo-editor
SegundoBob, thanks for your comments. Multiple parents in Leo nodes is a way to keep track of all places clone can be found in the outline. Without this `parents` field, every time we need to find other clones it would be necessary to search entire tree. Having parents, it is possible to find positions of node without searching entire tree.


On Monday, May 4, 2020 at 12:12:04 AM UTC+2, SegundoBob wrote:

For the little it is worth, in so far as I understand Vitalije, and I don't completely understand him, I think he is right.


Which parts you didn't completely understand. Can you please point at some part that I haven't explained clearly enough, so that I can try to write better explanation.

Vitalije
 

vitalije

unread,
May 4, 2020, 3:13:47 AM5/4/20
to leo-editor
Thomas Passin, thanks for your comment.


On Monday, May 4, 2020 at 6:23:08 AM UTC+2, Thomas Passin wrote:

About clones, we always talk about cloning nodes, but it seems to me that what actually gets cloned is an entire subtree - that is, the clone includes (clones of) a node's descendants.  Perhaps a change in the conceptual model of clones from nodes to subtrees would make it easier to work out how to handle them.

I'm not sure how a subtree is currently modeled in Leo.  Since a tree can be built out of nodes that have child and parent nodes,  one can be constructed without an explicit model for a (sub)tree.  But since many operations we want to do are really on (sub)trees and not nodes, maybe an explicit tree concept in addition to a node concept would be useful(if there isn't one already).


VNode in Leo is always a subtree, although this subtree may be empty (it may contain no other nodes). In its current implementation it is not possible to separate node from the subtree. If a node contains at least one child, it is no longer possible to make a clone of just that node without its children. If you try to remove children from the clone, they will be removed from the outline too. You can copy just values from v node if you wish to have it isolated from its subtree for example when storing Leo document in '.db' format, all v-nodes are decomposed to their values (gnx, h, b, u, statusBits) and two lists one for parents and one for the children. Those two lists contain only gnxes not real v-nodes. Decomposed nodes are then stored in the db table. Later when we want to restore the outline from the db table we first create every node as an isolated single node not connected to any other node. And after all nodes are created, then we go through every list of gnxes and using them we link all children to their parents and vice versa. Of course this is not the only way to decompose v-nodes and recreate them from the decomposed data.

Vitalije

vitalije

unread,
May 4, 2020, 3:19:29 AM5/4/20
to leo-editor
The new version of the prototype now contains 750 lines and a few more commands:
  • insert new node
  • delete node
  • clone node
Also in the first version prototype didn't handle clones correctly. In the latest version 69953a0f77 handling clones should be correct. I'll try to write some tests to be sure.

Vitalije

jkn

unread,
May 4, 2020, 5:37:03 AM5/4/20
to leo-editor
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

vitalije

unread,
May 4, 2020, 6:34:32 AM5/4/20
to leo-editor

    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


AFAIK Tk support has been discontinued. There are some Tk remains  in Leo's code base, but I don't think it is possible to run Leo with TkGui anymore.

Vitalije

Edward K. Ream

unread,
May 4, 2020, 7:00:10 AM5/4/20
to leo-editor
On Sun, May 3, 2020 at 4:11 PM vitalije <vita...@gmail.com> 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.

Many thanks for this post. I was busy with other matters yesterday, and I am just now reading this.

I'll say more in the next day or so. For now, you have convinced me that this post and the prototype code merit serious study.

Edward

vitalije

unread,
May 4, 2020, 7:37:21 AM5/4/20
to leo-e...@googlegroups.com
The latest version of prototype is 84b54fc3af.

Currently it is almost 1000 lines including comments and includes the following features:

  • browsing outline
  • editing headlines
  • editing bodies
  • shows basic Leo icons on nodes
  • inserting new nodes
  • deleting nodes
  • moving nodes up, down, left or right
  • promote / demote
  • hoist / de-hoist
  • cloning nodes
  • undo/redo for outline editing commands

I don't plan to work more on this prototype. Its purpose is to prove my claims from the beginning of this post. I believe this covers about 5000 lines of code in Leo:
  • LeoQTreeWidget ~ 500,
  • LeoTree ~ 350,
  • leoChapters.py ~ 480,
  • qt_tree.py ~ 1430,
  • leoUndo.py ~ 1730
  • ... total 4490 LOC + about 500 lines for implementing all these commands.

This means that the bugs have about 4000 lines more space to hide than is necessary. It is also 4000 lines more for CPU to execute and a lot of those lines are inside loops. By applying all these refactorings Leo's code will be much smaller and hopefully it will run much faster.

The prototype is not fully documented. There are a few not very obvious methods that I had to rewrite several times to eliminate bugs that I haven't spotted at first. Those methods require more detailed explanation.

First is make_undoable_move. Here it is:

def make_undoable_move(self, oldparent, srcindex, newparent, dstindex):
    t
= self.tree
    root
= t.invisibleRootItem()
    trash
= []

    newitems
= [(x, oldparent.child(srcindex).clone())
                   
for x in all_other_items(root, newparent)]
   
def domove():
       
for item in all_other_items(root, oldparent):
            trash
.append(item.takeChild(srcindex))

       
for item, child in newitems:
            item
.insertChild(dstindex, child)

        curr
= move_treeitem(oldparent, srcindex, newparent, dstindex)

       
self.tree.setCurrentItem(curr)

   
def undomove():
       
for item, child in newitems:
            item
.takeChild(dstindex)

       
for item in all_other_items(root, oldparent):
            item
.insertChild(srcindex, trash.pop())

        curr
= move_treeitem(newparent, dstindex, oldparent, srcindex)

       
self.tree.setCurrentItem(curr)

    domove
()

   
self.c.addUndo(undomove, domove)


oldparent and newparent are tree widget items, and srcindex/dstindex are ints. At first I was creating and deleting child items inside functions domove and undomove. However, this caused exceptions when performing undo and redo later, because redo would create new items and maybe next undo block relies on the items that used to be created before. To solve this issue, all necessary items must be created outside domove/undomove and all items that will be deleted by this command must be kept in local variable trash. This pattern I had to repeat in other commands as well.

Generator all_other_items yields tree widget items that point to the same v node, skipping the given one. This generator uses links between v-nodes so the order of the execution is important. In this method this order of the execution doesn't matter but in some other methods it does.

When moving item from one parent to another one, if the oldparent is cloned or belongs to the cloned subtree it means we have to remove child not only from the oldparent, but from all other items that point to the same v node as oldparent. It means we will have to delete some items. The deleted items will go to trash from where we will be able to collect them again during the undo command.

OTOH if the newparent is part of cloned subtree, that means we will need extra items to insert in every other clone. The command moves single item from oldparent to the newparent and all other clones both on the source side and on the destination side are processed using all_other_items generator. Sometimes like in this method the results of this generator are kept in local variable and iterated again using that variable (like newitems variable above).

Vitalije

Edward K. Ream

unread,
May 4, 2020, 8:23:17 AM5/4/20
to leo-editor
On Monday, May 4, 2020 at 4:37:03 AM UTC-5, jkn wrote:

> As far as I know tk has not been retired, and Leo 'must' continue to support it. Or have I missed some news?

Leo does not support a tk gui.  It hasn't for at least a decade. See leoPlugins.leo#Plugins-->Gui.

It's possible to run tk code from Leo, but that is another matter entirely.

Edward

jkn

unread,
May 4, 2020, 10:25:11 AM5/4/20
to leo-editor
My apologies for the noise in that case. I had not realised that tk was no longer supported. "at least a decade"? - wow, how time flies...

Edward K. Ream

unread,
May 4, 2020, 10:32:41 AM5/4/20
to leo-editor
On Mon, May 4, 2020 at 9:25 AM jkn <jkn...@nicorp.f9.co.uk> wrote:

My apologies for the noise in that case. I had not realised that tk was no longer supported. "at least a decade"? - wow, how time flies...

No need for an apology!

Edward

Félix

unread,
May 4, 2020, 3:31:25 PM5/4/20
to leo-editor
just thought i'd mention i'm glad to see other people concerned about separation of leo's core features/model and it's GUI... Been fiddling around for alomst a year with leo's API and vsCode's API to use the 'pure' leo 'model' into another view and controller: vscode.

So i'm very happy to see this discussion going on! 

So a little update : now working on the last features that are a bit problematic to me : undo, redo and execute script. I predict having a new master branch release for everyone to try/test by the end of May. (they are problematic because of vscode technical details, not leo hehe!)

Y'all have a great Day ! :)
--
Félix

Edward K. Ream

unread,
May 6, 2020, 6:51:50 AM5/6/20
to leo-editor
On Sunday, May 3, 2020 at 4:11:48 PM UTC-5, vitalije wrote:

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,...

I'm not sure what I think about this plan. How do you propose to handle outline operations initiated from scripts?

In the "real" Leo, scripts can update the outline (both model and view) in one of two ways:

1. Call methods such as c.clone. These Commands methods call c.redraw "automatically".

2. Call methods, such as p.clone. These Position methods don't call c.redraw. The script must then c.redraw explicitly.

I don't mind if c.redraw becomes a do-nothing. I just want to know how it's going to work.

Edward

Edward K. Ream

unread,
May 6, 2020, 7:01:31 AM5/6/20
to leo-editor
On Sun, May 3, 2020 at 5:12 PM SegundoBob <segun...@gmail.com> wrote:

several months ago, I concluded that Leo-Editor positions are a very bad idea and that using  GNX's instead would be much simpler and much more robust.

I think that's an overstatement. Scripts are free to use either vnodes or positions as they please.
I also concluded that in a directed graph, I don't need clones because multiple parents is no big deal in a directed graph.

Clones are well defined only in a DAG. Similarly, "outline order" is well defined only in a DAG.

It is possible to define various kinds of traversals on general directed graphs. These are more complex and less intuitive than for DAGs, but they may be useful in special situations.
 
Consequently, I stopped worrying about clones.  So I don't know if clones make GNX's inadequate as pointers, requiring that v-nodes be used as pointers instead of just GNX's.

Each vnode has a unique, unchanging gnx, so on some level of understanding you can use gnx's or vnodes interchangeably.

For the little it is worth, in so far as I understand Vitalije, and I don't completely understand him, I think he is right.

Imo, Vitalije has create a useful and important distinction about positions. They can be used either to identify vnodes or as the basis of traversal methods.  I'd like to reserve judgement about the new scheme for now.

Edward

Edward K. Ream

unread,
May 6, 2020, 7:05:51 AM5/6/20
to leo-editor
On Sun, May 3, 2020 at 11:23 PM Thomas Passin <tbp1...@gmail.com> wrote:

About clones, we always talk about cloning nodes, but it seems to me that what actually gets cloned is an entire subtree

Correct.
I'm not sure how a subtree is currently modeled in Leo. 

This has a long history.  The result of this history is that the tree contains nothing but pointers (references) to vnodes. This is the so-called "unified node" world. In this world, clones are simply additional references to a vnodes. That is, vnodes may have multiple parent vnodes, provided that there are no "cycles" (loops) in the resulting graph.
Since a tree can be built out of nodes that have child and parent nodes,  one can be constructed without an explicit model for a (sub)tree.  But since many operations we want to do are really on (sub)trees and not nodes, maybe an explicit tree concept in addition to a node concept would be useful(if there isn't one already).

It's very unlikely that Leo's vnode structure will ever change.

Edward

Edward K. Ream

unread,
May 6, 2020, 7:15:28 AM5/6/20
to leo-editor
On Wednesday, May 6, 2020 at 6:01:31 AM UTC-5, Edward K. Ream wrote:

> Clones are well defined only in a DAG. Similarly, "outline order" is well defined only in a DAG.

Perhaps I should say that clones are not uniquely or intuitively defined in general graphs. We could go down a rabbit hole here, but I'd rather not. Suffice it to say that in general it's hard to generalize about general graphs, directed or otherwise :-)

Edward

Edward K. Ream

unread,
May 6, 2020, 8:05:50 AM5/6/20
to leo-editor
On Sunday, May 3, 2020 at 4:11:48 PM UTC-5, vitalije wrote:


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.

I checked out tree-refresh, then attempted to clone qt_tree.py:

Traceback (most recent call last):

  File "c:\leo.repo\leo-editor\leo\core\leoCommands.py", line 2282, in executeAnyCommand
    return command(event)

  File "c:\leo.repo\leo-editor\leo\core\leoGlobals.py", line 245, in commander_command_wrapper
    method(event=event)

  File "c:\leo.repo\leo-editor\leo\commands\commanderOutlineCommands.py", line 736, in clone
    c.redraw(clone)

  File "c:\leo.repo\leo-editor\leo\core\leoCommands.py", line 2950, in redraw
    p2 = c.frame.tree.redraw(p)

  File "c:\leo.repo\leo-editor\leo\plugins\qt_tree.py", line 309, in full_redraw
    self.drawTopTree(p)

  File "c:\leo.repo\leo-editor\leo\plugins\qt_tree.py", line 635, in drawTopTree
    apply_deletes(deletes)

  File "c:\leo.repo\leo-editor\leo\plugins\qt_tree.py", line 596, in apply_deletes
    v2i[vch].remove(chitem)

ValueError: list.remove(x): x not in list

Edward

vitalije

unread,
May 6, 2020, 8:17:31 AM5/6/20
to leo-editor
For outline modifications initiated by scripts we will have to redraw the outline. To make it quick I plan to use diff algorithm similar to one I wrote earlier in the tree-refresh branch.

I would still recommend that p.clone and other methods for model modifications get redirected to tree.clone_node, tree.move_node_up,... and others. They will automatically update view. This updates should be quite fast and no need to distinguish them from pure model modifications. This screen updates can be prevented by using tree.blockSignlas if necessary.

The only way to modify the outline that we can't really control is when scripts use only v-nodes for making direct links like parent_v.children.append(child) or similar. For those modifications we need to redraw the tree after each script execution.

All modifications from the Leo core, should be just redirected to their doubles implemented in tree (c.clone -> tree.clone_node, c.moveOutlineUp -> tree.move_node_up, ...)

Vitalije

vitalije

unread,
May 6, 2020, 8:32:11 AM5/6/20
to leo-editor

I checked out tree-refresh, then attempted to clone qt_tree.py:

Traceback (most recent call last):

  File "c:\leo.repo\leo-editor\leo\core\leoCommands.py", line 2282, in executeAnyCommand
    return command(event)

  File "c:\leo.repo\leo-editor\leo\core\leoGlobals.py", line 245, in commander_command_wrapper
    method(event=event)

  File "c:\leo.repo\leo-editor\leo\commands\commanderOutlineCommands.py", line 736, in clone
    c.redraw(clone)

  File "c:\leo.repo\leo-editor\leo\core\leoCommands.py", line 2950, in redraw
    p2 = c.frame.tree.redraw(p)

  File "c:\leo.repo\leo-editor\leo\plugins\qt_tree.py", line 309, in full_redraw
    self.drawTopTree(p)

  File "c:\leo.repo\leo-editor\leo\plugins\qt_tree.py", line 635, in drawTopTree
    apply_deletes(deletes)

  File "c:\leo.repo\leo-editor\leo\plugins\qt_tree.py", line 596, in apply_deletes
    v2i[vch].remove(chitem)

ValueError: list.remove(x): x not in list

Edward

The tree-refresh branch is not finished and some operations like this one doesn't work. But the essence of the applied algorithm is correct. It basically creates a set of all links that exist in the outline. Links are represented by tuples (parent_v, child_v, index). The set can be calculated very fast and using python set operations difference on the set cached during the previous redraw and the current set, gives a set of links that need to be deleted and set of links that need to be established. This algorithm must be adjusted a bit to work with the prototype but it is doable. The undo/redo code in the prototype relies on preserving the tree items while the algorithm in its current version drops the items and create new ones. Other than that it should work without problems. If undo/redo code in the prototype is not required this limitation on preserving the tree items can be lifted.

Vitalije

Edward K. Ream

unread,
May 6, 2020, 9:21:59 AM5/6/20
to leo-editor
On Wed, May 6, 2020 at 7:17 AM vitalije <vita...@gmail.com> wrote:

>> I don't mind if c.redraw becomes a do-nothing. I just want to know how it's going to work.

For outline modifications initiated by scripts we will have to redraw the outline. To make it quick I plan to use diff algorithm similar to one I wrote earlier in the tree-refresh branch.

Hmm. I suppose that would work. If it's fast enough it could be called automatically at the end of every command, including execute-script.

I would still recommend that p.clone and other methods for model modifications get redirected to tree.clone_node, tree.move_node_up,... and others. They will automatically update view. This updates should be quite fast and no need to distinguish them from pure model modifications. This screen updates can be prevented by using tree.blockSignals if necessary.

I have a queasy feeling about this. It would seem to imply major changes to the console gui code.

Btw, I forgot to mention in my earlier comments that w.blockSignals looks like the way to avoid lockouts.
The only way to modify the outline that we can't really control is when scripts use only v-nodes for making direct links like parent_v.children.append(child) or similar. For those modifications we need to redraw the tree after each script execution.

Or maybe redraw after idle time.

All modifications from the Leo core, should be just redirected to their doubles implemented in tree (c.clone -> tree.clone_node, c.moveOutlineUp -> tree.move_node_up, ...)

I'd like to withhold judgment about all of this for now.

Imo, the prototype, including the tree-refresh branch, is worth further exploration. The ideal would be to leave all the assumptions behind Leo's core unchanged.

Edward

Edward K. Ream

unread,
May 6, 2020, 9:23:16 AM5/6/20
to leo-editor
On Wed, May 6, 2020 at 7:32 AM vitalije <vita...@gmail.com> wrote:

> The tree-refresh branch is not finished and some operations like this one doesn't work. But the essence of the applied algorithm is correct.

Alright. I'll take a look at the code without cloning it :-)

Edward

tfer

unread,
May 6, 2020, 10:37:01 AM5/6/20
to leo-editor
I've expressed before that I feel that the use of the word "clone", does not really capture what we are doing here, in fact, due to its use in biology, it leads to some incorrect thinking about what is going on.  In biology, a clone is a separate entity with its own life-cycle, they have the same DNA, but can have entirely different lives.  In Leo, a clone is the same entity, what you do to it in one place, (or to its descendants), gets passed on to the clones in all the other places, not because of some code running in the background updating all the copies, there are no copies, there is only one node, but it is reachable from multiple places.

In the talk on Leo I gave at PyOhio, (to which there is a link Leo  documentation), I tried to convey this by making an analogy to the computer game "Portal", if you have some room you want to get to, you can create multiple ways to get to it with your portal gun. There is only one room, but now you have made several different ways to get to it.

In a recent thread, Edward said that a clone is a node with multiple parents.  That was revelatory to me, I'm still thinking over the implications of that.  To me it shifts what "being a clone" means from the node to the path to the node.  Visually, it means that when walking down the corridor to our node, there are now side corridors that join into it from anywhere we want in own outline.  

Edward K. Ream

unread,
May 6, 2020, 1:20:46 PM5/6/20
to leo-editor
On Wednesday, May 6, 2020 at 8:21:59 AM UTC-5, Edward K. Ream wrote:

>> I would still recommend that p.clone and other methods for model modifications get redirected to tree.clone_node, tree.move_node_up...

> I have a queasy feeling about this. It would seem to imply major changes to the console gui code.

Just to be clear, the prototype is so good that I seen no risk at all in exploring it. Good things are sure to come from it.

Furthermore, since you (Vitalije) and I have somewhat different points of view, the final result will likely be better than either of us imagines at present.

Finally, when the dust settles, I am willing to consider changes to both the console and flexx guis if that seems warranted.

Edward

Edward K. Ream

unread,
May 6, 2020, 1:26:08 PM5/6/20
to leo-editor
On Wed, May 6, 2020 at 9:37 AM 'tfer' via leo-editor <leo-e...@googlegroups.com> wrote:

I've expressed before that I feel that the use of the word "clone", does not really capture what we are doing here, in fact, due to its use in biology

Hmm. In the Leo world the term "clone" is a term of art. I see no inherent reason why this should confuse people.

Edward

Thomas Passin

unread,
May 6, 2020, 1:33:57 PM5/6/20
to leo-editor
I've never had a problem with the term as used in Leo.  To me, it conveys 1) that a (clone) node looks different from another because it appears in a different place, but 2) if you look closer, it's exactly the same inside.

David Szent-Györgyi

unread,
May 6, 2020, 2:26:19 PM5/6/20
to leo-editor
On Monday, May 4, 2020 at 8:23:17 AM UTC-4, Edward K. Ream wrote:
Leo does not support a tk gui.  It hasn't for at least a decade. See leoPlugins.leo#Plugins-->Gui.

It's possible to run tk code from Leo, but that is another matter entirely.

The Leo distributions had a virtue that is not shared by the current code: simplicity of installation and use. I could fit the installers for Python, for pywin32, and the LEO files for my projects on a CD-RW or a thumb drive, and be ready in two minutes to work on the outlines in which I was developing code. The Tkinter interface of the time was not pretty, but it worked, and allowed me to get my work done. I miss that simplicity!

Tkinter and enhancements for current Python are far more capable than the plain Tkinter in which Edward wrote the GUI that was part of Leo 4.2 and Leo 4.3. I wonder whether a from-scratch implementation of a Leo GUI using modern Tkinter could be done without the pain caused to Edward by the gaps in the old toolkit:

Tkinter.ttk is themeable, to help Python-Tkinter GUIs match the appearance of native GUIs. 

The BWidget widgets include a tree widget. 

I'm not assuming that Edward or another of you reading this would do the work. I am curious, because I could really use Leo as a PIM in the environments in which I work: macOS 10.13 "High Sierra" or later, 64-bit Windows 10 with IronPython already installed, and 64-bit Windows 10 with Python 3.7 installed. My macOS environment is stable although it changes every couple of years to track Apple's upgrade treadmill, the others switch machines frequently. So, for me, ease of installation is a need. 

I'm not asking for an explanation here of the best practices for installing current Leo with the Qt-based interface. If the answer to my curiosity about making a Tkinter GUI anew is that it simply isn't practical, I'll discuss my view of using current Leo and Qt in a separate thread. 

Edward K. Ream

unread,
May 6, 2020, 3:50:29 PM5/6/20
to leo-editor
On Wed, May 6, 2020 at 1:26 PM David Szent-Györgyi <das...@gmail.com> wrote:

> Tkinter and enhancements for current Python are far more capable than the plain Tkinter in which Edward wrote the GUI that was part of Leo 4.2 and Leo 4.3.

Thanks for this update. Vitalije recently created a prototype in Tk, so there is actually some code available.

The great advantage Qt has (or had) over Tk was in the appearance of text. Has Tk improved in that regard?

Edward

tfer

unread,
May 6, 2020, 6:20:59 PM5/6/20
to leo-editor
>>> I've never had a problem with the term as used in Leo. To me, it conveys 1) that a (clone) node looks different from another because it appears in a different place, but 2) if you look closer, it's exactly the same inside.

I'm not saying that the term that the term should be abandoned, just that it should have the points that you see, mentioned more explicitly in the documentation. Personally, my understanding of cloning from biology lead me astray.

"Clone" is as term of art, it's just that it's meaning in Leo is more specialized than it is in general usage.

I've had trouble in the past trying to get my point across to Edward, I suspect that in part, this is due to different thinking styles, I tend to see thinks visually, coming up with a mental model, and elaborating on that so it has all the behavior of the system I'm modeling.

Leo sucks me in every time I come back for a visit, (it's a pleasant side trip each time), currently, I've been pulled towards a review of the documentation. Going round and round with Sphinx trying to get a PDF rendering as I find that easiest to review.

In a week or so, I'd like to do some teleconferencing with Edward, (not Zoom, but something with the ability to share computer screens), so I can do a show and tell on some of the stuff in Leo I'm working on.
Tom

Matt Wilkie

unread,
May 6, 2020, 8:29:07 PM5/6/20
to leo-editor

>>> I've never had a problem with the term as used in Leo.  To me, it conveys 1) that a (clone) node looks different from another because it appears in a different place, but 2) if you look closer, it's exactly the same inside.

I'm not saying that the term that the term should be abandoned, just that it should have the points that you see, mentioned more explicitly in the documentation.  Personally, my understanding of cloning from biology lead me astray.


For me I found your contrast of clone as it's used in biology and as it's used in Leo useful. The term portal, not as generally communicative to the world as is clone in my opinion, is helpful in this conversation. I've not thought of a better word or short phrase than clone though. I definitely like the visuals of portals and corridors. After today I might foreverafter hear the Star Trek transporter sound effect when using "goto any clone" command. ;-)

-matt

Thomas Passin

unread,
May 6, 2020, 10:01:38 PM5/6/20
to leo-editor
Actually, Zoom does let you share your screen.  The host can let any participant take control, and they can share their screen.

Matt Wilkie

unread,
May 7, 2020, 12:56:14 AM5/7/20
to leo-editor

In a week or so, I'd like to do some teleconferencing with Edward, (not Zoom, but something with the ability to share computer screens), so I can do a show and tell on some of the stuff in Leo I'm working on.

Actually, Zoom does let you share your screen.  The host can let any participant take control, and they can share their screen.

If it's Zoom in particular you're looking to avoid Jitsi is worth investigating:

-matt
 

Félix

unread,
May 7, 2020, 1:22:36 AM5/7/20
to leo-editor
The "Clone" terminology used in Leo is also used in some 2d and 3d graphical design software among others, in the same manner: A 'live' reference to an object that will change in real-time... 

Just thought I'd make this fact known to people reading this thread :)


On Wednesday, May 6, 2020 at 6:20:59 PM UTC-4, tfer wrote:

tfer

unread,
May 7, 2020, 11:49:31 AM5/7/20
to leo-editor
Thanks Matt and Thomas, I'll look into that. It's not that I want to avoid Zoom so much, I want to demonstrate some work flow stuff, (Windows 10, some Powershell stuff, and It's virtual workspace), so that's why I need to show what's on my screen.
If anyone is interested, and it's okay with Edward more people could jump on the call .


>>> A 'live' reference to an object that will change in real-time...
-- I like that a lot, I'll probably add it in my rework of the documentation, thanks Felix

David Szent-Györgyi

unread,
May 7, 2020, 1:16:45 PM5/7/20
to leo-editor
Edward writes: 

Thanks for this update. Vitalije recently created a prototype in Tk, so there is actually some code available.

The great advantage Qt has (or had) over Tk was in the appearance of text. Has Tk improved in that regard?

Answer for Windows: work has been done. See the Python bug tracker issue 26698, which states that a fix for IDLE on Windows made it into Python 3.6.6rc1 and Python 3.7.0rc1. That work was superseded by work documented by Python bug tracker issue 33656

Issue 33656 mentions the addition of a call to a Windows API to say that tk scales for DPI - the change is in Iines 15-20 of the source code

That change is documented in as new in Python 3.6.6 and new in Python 3.7. In each case, the text reads, 

On Windows, a new API call tells Windows that tk scales for DPI. On Windows 8.1+ or 10, with DPI compatibility properties of the Python binary unchanged, and a monitor resolution greater than 96 DPI, this should make text and lines sharper. It should otherwise have no effect. (Contributed by Terry Jan Reedy in bpo-33656.) 

Thomas Passin

unread,
May 7, 2020, 1:33:35 PM5/7/20
to leo-editor

On Thursday, May 7, 2020 at 11:49:31 AM UTC-4, tfer wrote:
Thanks Matt and Thomas, I'll look into that.  It's not that I want to avoid Zoom so much, I want to demonstrate some work flow stuff, (Windows 10, some Powershell stuff, and It's virtual workspace), so that's why I need to show what's on my screen.
If anyone is interested, and it's okay with Edward more people could jump on the call .

 I have a periodic Zoom meeting for my particular software project.  One of the people acts as the recorder/secretary.  He gets the screen control and pulls up the (Word) document that records progress and issues, scrolls through it, and we see him update it while we discuss the issues.  The only thing is that it's a bit slow compared with being at your own screen.  At the end of the meeting, his work is done and he emails us the revised doc.

David Szent-Györgyi

unread,
May 7, 2020, 1:52:19 PM5/7/20
to leo-e...@googlegroups.com
Answer for macOS:

Edward writes: 

The great advantage Qt has (or had) over Tk was in the appearance of text. Has Tk improved in that regard?

My guess is that tkinter as shipped with Python 3.6.8 and 3.7.7 and 3.8.2 would need careful testing. The macOS interface via Tcl/Tk is undergoing work - at present, the ball is in the court of the developers of Tcl/Tk.

See IDLE and tkinter with Tcl/Tk on macOS, which documents that python.org Python installers for versions 3.8.0+, 3.7.2+, 3.6.8 all ship with built-in Tcl/Tk 8.6.8. 

See Python bug tracker issue 35402, which documents reverting the use of Tcl/Tk 8.6.9.1, and Python bug tracker issue 35485, which documents specific problems and gives a reference to the Tcl/Tk ticket that documents the problem.  The current release of Tcl/Tk is 8.6.10; that dates from November of 2019, and it hasn't been tried with Python as yet, but I see on the timeline for Tcl/Tk that work on the macOS interface is under way as I type this in May of 2020, so the bundled Tcl/Tk 8.6.8 won't be upgraded immediately. The developers are working around macOS bugs, and Apple is changing the operating system significantly, so their work is not easy. 
Reply all
Reply to author
Forward
0 new messages