Status report re PR #3911

86 views
Skip to first unread message

Edward K. Ream

unread,
May 16, 2024, 10:39:55 AMMay 16
to leo-editor

PR #3911 contains preliminary work on deprecating Terry's trailblazing free_layout and nested_splitter plugins. This post is a progress report.

Goals

Scripts (@button nodes) and plugins should be able to do the following easily and intuitively:

- Detach Leo's screen components, thereby making them floating windows.

- Move Leo's screen components to other locations.

Tools

Terry's plugins contain non-trivial low-level helpers that have served as models for utilities in the PR. A higher-order gui API will support the goals above.

The gui API will use a descriptor class analogous to the caching code in Terry's plugins. This class will output JSON descriptions of actions that can persist between different runs of Leo.

Summary

This post discusses the PR's design. The PR:

- turns Terry's innovative code inside out, exposing previously private helpers.

- creates a descriptive gui API that hides implementation details.

All details are still in flux. All your questions and comments are welcome.

Edward

Thomas Passin

unread,
May 16, 2024, 10:49:43 AMMay 16
to leo-editor
I'm very enthusiastic about this work.  It will eventually be a real boon to users and developers, I'm sure.  I don't know if it will be possible, but I hope that existing GUI plugins that use the nested-splitter/free-layout will be able to continue working without needing to be reworked

Edward K. Ream

unread,
May 16, 2024, 2:44:43 PMMay 16
to leo-editor
On Thursday, May 16, 2024 at 9:49:43 AM UTC-5 Thomas wrote:
I'm very enthusiastic about this work.  It will eventually be a real boon to users and developers, I'm sure.

Glad you like the concept.
 
I hope that existing GUI plugins that use the nested-splitter/free-layout will be able to continue working without needing to be reworked

When True, the g.allow_nested_splitter switch enables both plugins to work as before. As noted in the PR, this switch might be true "forever". Otoh, my hope is that eventually nobody will notice (or care) if the two plugins go away. We shall see.

Edward

Edward K. Ream

unread,
May 16, 2024, 7:44:19 PMMay 16
to leo-editor
On Thursday, May 16, 2024 at 1:44:43 PM UTC-5 Edward K. Ream wrote:

>> I hope that existing GUI plugins that use the nested-splitter/free-layout will be able to continue working without needing to be reworked

> When True, the g.allow_nested_splitter switch enables both plugins to work as before. As noted in the PR, this switch might be on "forever."

Belay that. Leo's codebase should not contain toxic code switches. Such switches are intolerable in the long run.

I feel strongly enough about this that I am willing to convert legacy code myself. This offer extends to you, Thomas, and anyone else.

Assuming the PR succeeds, here is my present plan:

- Terry's plugins (and the switch) will be part of Leo 6.7.9.

- As part of 6.7.9, I'll convert all affected code in LeoPyRef.py.

- The 6.7.9 release notes will warn of a breaking change in Leo 6.7.10 and will offer to help with conversion.

- I'll remove the switch and the two plugins as soon as 6.7.9 goes out the door.

Summary

If PR #3911 succeeds, Leo 6.7.9 will be the last release that supports the free_layout and nested_splitter plugins.

The 6.7.9 release notes will warn of the breaking change and will offer to help convert any existing code.

Leo's long history includes removing many overly complex features. Removing all vestiges of these plugins will make Leo simpler and more maintainable.

Again, I welcome all comments.

Edward

Thomas Passin

unread,
May 16, 2024, 11:14:51 PMMay 16
to leo-editor
This will be complicated enough and have a potentially big impact on existing code and users that I'd like to see a concept of operations and some requirements.  For one thing, without something like them no one will be able to help you.  For another, no one will know how things should work nor how to write code for them.    In addition, it's much easier to correct mistakes early, and if we can have some discussion about the goals and the design, the community could help.

Saying that you will "invert" Terry's code so the helpers are higher up or more visible is not very definite or specific.  I'd rather start with what Terry's code does that we like, and what about it we don't like.  For example, I found I can open VR3 on top of the body editor without using a  nested splitter except to use it to find the stacked widget that contains the body editor.  I need free layout instance to get that splitter but not for anything else.  If I could directly get that stacked widget I would not have to talk to the free layout or a nested splitter at all.  So to me, it would be very desirable to have a method on c to return a container of a known object, and probably to enumerate those objects and containers, without having to know how to work my way through all the current layers of splitters and container widgets.  Notionally, something like this:

body_widget = c.getObject('bodyWrapper1')
container = body_widget.parent()
my_widget_index  = container.add(myWidget(c))
container.setIndex(my_widget_index)

The log frame works much like this.  Why can't all the container frames be more or less like the Log frame?

There are many Qt applications out there that have multiple panels and frames.  In some of them parts can be torn off and moved elsewhere.  New frames can be opened and populated. I don't suppose they all do it the same way, but there are probably some standard ways.  Pyzo is probably as good an exemplar as we could find.  Just how should the revised Leo interface work, from the point of view of users?  That would be a good place to start.

Edward K. Ream

unread,
May 17, 2024, 4:44:40 AMMay 17
to leo-e...@googlegroups.com
On Thu, May 16, 2024 at 10:14 PM Thomas Passin <tbp1...@gmail.com> wrote:

This will be complicated enough and have a potentially big impact on existing code and users that I'd like to see a concept of operations and some requirements.

Every time I wake up the project (PR #3911) gets simpler!

It looks like a few documented helpers and patterns will suffice!!

I'll say more after I run some experiments. Stay tuned.

Edward

HaveF HaveF

unread,
May 26, 2024, 5:17:22 AMMay 26
to leo-editor
I haven't kept up with the latest progress. In my previous code, I created a textedit panel to output something.

```python
     w = c.frame.body.widget
     while not isinstance(w, NestedSplitter):
         w = w.parent()
     something_log = w.find_child(QtWidgets.QTextEdit, "something_log")
     if something_log == None:
         self.something_log = QtWidgets.QTextEdit()
         self.something_log.setObjectName('something_log')
         w.insert(-1, self.something_log)
```
Regarding the recent changes, I try to fix it.

```python
    w = c.frame.body.widget
    while not isinstance(w, QSplitter):
        w = w.parent()
    something_log = w.findChild(QtWidgets.QTextEdit, "something_log")
    if something_log == None:
        self.something_log = QtWidgets.QTextEdit()
        self.something_log.setObjectName('something_log')
        w.addWidget(self.something_log)
```

It works. But I'm not sure, is there anything else I need to pay attention to? The original code maybe copy from Thomas, I don't remember :D I don’t have the guts to study layout yet


Edward K. Ream

unread,
May 26, 2024, 6:21:17 AMMay 26
to leo-e...@googlegroups.com
On Sun, May 26, 2024 at 4:17 AM HaveF HaveF <iamap...@gmail.com> wrote:
I haven't kept up with the latest progress. In my previous code, I created a textedit panel to output something.
...

It works. But I'm not sure, is there anything else I need to pay attention to? The original code maybe copy from Thomas, I don't remember :D I don’t have the guts to study layout yet

Looks good to me. QSplitters manage their children automatically. No new layouts should be needed, nor any changes to layouts.

Edward

Thomas Passin

unread,
May 26, 2024, 8:24:28 AMMay 26
to leo-editor
That seems fine.  It can be done a little easier with the new method  find_widget_by_name():

from leo.core.leoQt import QtWidgets
QSplitter, QTextEdit = QtWidgets.QSplitter, QtWidgets.QTextEdit

gui = g.app.gui
w = gui.find_widget_by_name(c, 'main_splitter')
something_log = w.findChild(QTextEdit, "something_log")
if something_log == None:
    something_log = QtWidgets.QTextEdit()
    something_log.setObjectName('something_log')
    w.addWidget(something_log)


If you ever want to delete your added widget, remember to call deleteLater() on it as the last thing.  This will cause Qt to delete the underlying C++ widget.  Otherwise only the Python widget wrapper will get deleted.

HaveF HaveF

unread,
May 26, 2024, 9:06:52 AMMay 26
to leo-e...@googlegroups.com
Thanks Edward and Thomas.

Btw, do we need to put Leo's Qt Widget hierarchy Code to the doc? I believe it is very useful.

I let AI add some nice `|` or `-`, it works, but it remove Edward's useful 'if 1, else' part 🤭

```python
# Clear the console
g.cls()

# Global total counter for the number of objects processed
total = 0

# Function to get the name of a widget
def w_name(w):
    name = w.objectName() or 'no name'
    return f"<{name}>:{w.__class__.__name__}"

# Function to print information about a widget
def dump(tag, w, level=0, is_last=True, prefix=""):
    global total
    total += 1
    connector = "└── " if is_last else "├── "
    indent = prefix + connector
    print(f"{id(w):<14} lvl {level:2}: {indent}{tag}:{w_name(w)}")
    prefix += "    " if is_last else "│   "
    return prefix

# Function to recursively dump children of a widget
def dump_children(w, level=0, prefix=""):
    wanted_classes = (
        'DynamicWindow', 'Frame', 'Layout',
        'Splitter', 'Stacked', 'Text', 'Widget'
    )
    children = [child for child in w.children()
                if any(cls in child.__class__.__name__ for cls in wanted_classes)]
    for i, child in enumerate(children):
        is_last = (i == len(children) - 1)
        new_prefix = dump(f"child {i}", child, level + 1, is_last, prefix)
        dump_children(child, level + 1, new_prefix)

# Function to perform a full dump starting from a widget
def full_dump(tag, w):
    print(f"\nFull dump of {w_name(w)} at {id(w)}...\n")
    dump(tag, w, level=0, is_last=True)
    dump_children(w, level=0)


full_dump('c.frame.top.parent()', c.frame.top.parent())
print(f"\ntotal objects: {total}")
```

Here is the output:

```
6176345456     lvl  0: └── c.frame.top.parent():<qt_tabwidget_stackedwidget>:QStackedWidget
6176345616     lvl  1: ├── child 0:<no name>:QStackedLayout
5375113840     lvl  1: └── child 1:<MainWindow>:DynamicWindow
6176345776     lvl  2:     ├── child 0:<_layout>:QLayout
5375113360     lvl  2:     └── child 1:<centralwidget>:QWidget
5375113040     lvl  3:         ├── child 0:<main_splitter>:QSplitter
5375106960     lvl  4:         │   ├── child 0:<bodyFrame>:QFrame
5375107120     lvl  5:         │   │   ├── child 0:<innerBodyFrame>:QFrame
5375107280     lvl  6:         │   │   │   ├── child 0:<bodyStackedWidget>:QStackedWidget
6176346256     lvl  7:         │   │   │   │   ├── child 0:<no name>:QStackedLayout
5375105840     lvl  7:         │   │   │   │   └── child 1:<bodyPage2>:QWidget
5375105680     lvl  8:         │   │   │   │       ├── child 0:<bodyVLayout>:QVBoxLayout
5375105200     lvl  8:         │   │   │   │       └── child 1:<no name>:LeoLineTextWidget
5375103760     lvl  9:         │   │   │   │           ├── child 0:<no name>:QHBoxLayout
5375106320     lvl  9:         │   │   │   │           └── child 1:<richTextEdit>:LeoQTextBrowser
5375103440     lvl 10:         │   │   │   │               ├── child 0:<qt_scrollarea_viewport>:QWidget
6176346416     lvl 10:         │   │   │   │               ├── child 1:<qt_scrollarea_hcontainer>:QWidget
6176346896     lvl 11:         │   │   │   │               │   └── child 0:<no name>:QBoxLayout
6176346736     lvl 10:         │   │   │   │               └── child 2:<qt_scrollarea_vcontainer>:QWidget
6176346896     lvl 11:         │   │   │   │                   └── child 0:<no name>:QBoxLayout
5375105520     lvl  6:         │   │   │   └── child 1:<bodyInnerGrid>:QGridLayout
5375105360     lvl  5:         │   │   └── child 1:<bodyGrid>:QGridLayout
5375112880     lvl  4:         │   ├── child 1:<secondary_splitter>:QSplitter
5375114800     lvl  5:         │   │   ├── child 0:<logFrame>:QFrame
5375111760     lvl  6:         │   │   │   ├── child 0:<logInnerFrame>:QFrame
5375111920     lvl  7:         │   │   │   │   ├── child 0:<logTabWidget>:QTabWidget
6176346736     lvl  8:         │   │   │   │   │   └── child 0:<qt_tabwidget_stackedwidget>:QStackedWidget
6175371216     lvl  9:         │   │   │   │   │       ├── child 0:<LeoQuickSearchWidget>:LeoQuickSearchWidget
6175371376     lvl 10:         │   │   │   │   │       │   ├── child 0:<verticalLayout_2>:QVBoxLayout
6175371536     lvl 11:         │   │   │   │   │       │   │   └── child 0:<verticalLayout>:QGridLayout
6175372016     lvl 10:         │   │   │   │   │       │   └── child 1:<listWidget>:QListWidget
6176346576     lvl 11:         │   │   │   │   │       │       ├── child 0:<qt_scrollarea_viewport>:QWidget
6176347216     lvl 11:         │   │   │   │   │       │       ├── child 1:<qt_scrollarea_hcontainer>:QWidget
6176347696     lvl 12:         │   │   │   │   │       │       │   └── child 0:<no name>:QBoxLayout
6176347056     lvl 11:         │   │   │   │   │       │       └── child 2:<qt_scrollarea_vcontainer>:QWidget
6176348016     lvl 12:         │   │   │   │   │       │           └── child 0:<no name>:QBoxLayout
6175361616     lvl  9:         │   │   │   │   │       ├── child 1:<LeoTagWidget>:LeoTagWidget
6175361136     lvl 10:         │   │   │   │   │       │   ├── child 0:<nodetags-verticalLayout_2>:QVBoxLayout
6175370256     lvl 11:         │   │   │   │   │       │   │   └── child 0:<nodetags-verticalLayout>:QVBoxLayout
6175369456     lvl 12:         │   │   │   │   │       │   │       ├── child 0:<nodetags-horizontalLayout>:QHBoxLayout
6175369936     lvl 12:         │   │   │   │   │       │   │       └── child 1:<nodetags-horizontalLayout2>:QHBoxLayout
6175370736     lvl 10:         │   │   │   │   │       │   └── child 1:<nodetags-listWidget>:QListWidget
6176347056     lvl 11:         │   │   │   │   │       │       ├── child 0:<qt_scrollarea_viewport>:QWidget
6176346576     lvl 11:         │   │   │   │   │       │       ├── child 1:<qt_scrollarea_hcontainer>:QWidget
6176348016     lvl 12:         │   │   │   │   │       │       │   └── child 0:<no name>:QBoxLayout
6176347216     lvl 11:         │   │   │   │   │       │       └── child 2:<qt_scrollarea_vcontainer>:QWidget
6176347536     lvl 12:         │   │   │   │   │       │           └── child 0:<no name>:QBoxLayout
6176346896     lvl  9:         │   │   │   │   │       ├── child 2:<no name>:QStackedLayout
5375110000     lvl  9:         │   │   │   │   │       ├── child 3:<spellTab>:QWidget
5375110160     lvl 10:         │   │   │   │   │       │   ├── child 0:<spellVLayout>:QVBoxLayout
5375110320     lvl 10:         │   │   │   │   │       │   └── child 1:<spellFrame>:QFrame
5375110480     lvl 11:         │   │   │   │   │       │       ├── child 0:<spellVLayout>:QVBoxLayout
5375110640     lvl 12:         │   │   │   │   │       │       │   └── child 0:<spellGrid>:QGridLayout
5375107600     lvl 11:         │   │   │   │   │       │       └── child 1:<leo_spell_listBox>:QListWidget
6176346576     lvl 12:         │   │   │   │   │       │           ├── child 0:<qt_scrollarea_viewport>:QWidget
6176347216     lvl 12:         │   │   │   │   │       │           ├── child 1:<qt_scrollarea_hcontainer>:QWidget
6176347696     lvl 13:         │   │   │   │   │       │           │   └── child 0:<no name>:QBoxLayout
6176347056     lvl 12:         │   │   │   │   │       │           └── child 2:<qt_scrollarea_vcontainer>:QWidget
6176347696     lvl 13:         │   │   │   │   │       │               └── child 0:<no name>:QBoxLayout
6176090352     lvl  9:         │   │   │   │   │       ├── child 4:<no name>:LeoQTextBrowser
6176348016     lvl 10:         │   │   │   │   │       │   ├── child 0:<qt_scrollarea_viewport>:QWidget
6176347856     lvl 10:         │   │   │   │   │       │   ├── child 1:<qt_scrollarea_hcontainer>:QWidget
6176347216     lvl 11:         │   │   │   │   │       │   │   └── child 0:<no name>:QBoxLayout
6176346576     lvl 10:         │   │   │   │   │       │   └── child 2:<qt_scrollarea_vcontainer>:QWidget
6176347216     lvl 11:         │   │   │   │   │       │       └── child 0:<no name>:QBoxLayout
5374261072     lvl  9:         │   │   │   │   │       └── child 5:<log-widget>:LeoQTextBrowser
6176348016     lvl 10:         │   │   │   │   │           ├── child 0:<qt_scrollarea_viewport>:QWidget
6176347856     lvl 10:         │   │   │   │   │           ├── child 1:<qt_scrollarea_hcontainer>:QWidget
6176346576     lvl 11:         │   │   │   │   │           │   └── child 0:<no name>:QBoxLayout
6176347696     lvl 10:         │   │   │   │   │           └── child 2:<qt_scrollarea_vcontainer>:QWidget
6176346576     lvl 11:         │   │   │   │   │               └── child 0:<no name>:QBoxLayout
5375111120     lvl  7:         │   │   │   │   └── child 1:<logInnerGrid>:QGridLayout
5375111280     lvl  6:         │   │   │   └── child 1:<logGrid>:QGridLayout
5375112240     lvl  5:         │   │   ├── child 1:<outlineFrame>:QFrame
5375112080     lvl  6:         │   │   │   ├── child 0:<outlineInnerFrame>:QFrame
5375111600     lvl  7:         │   │   │   │   ├── child 0:<treeWidget>:LeoQTreeWidget
6176346736     lvl  8:         │   │   │   │   │   ├── child 0:<qt_scrollarea_viewport>:QWidget
6176346896     lvl  8:         │   │   │   │   │   ├── child 1:<qt_scrollarea_hcontainer>:QWidget
6176348016     lvl  9:         │   │   │   │   │   │   └── child 0:<no name>:QBoxLayout
6176346576     lvl  8:         │   │   │   │   │   └── child 2:<qt_scrollarea_vcontainer>:QWidget
6176348016     lvl  9:         │   │   │   │   │       └── child 0:<no name>:QBoxLayout
4660528464     lvl  7:         │   │   │   │   └── child 1:<outlineInnerGrid>:QGridLayout
4660528624     lvl  6:         │   │   │   └── child 1:<outlineGrid>:QGridLayout
6176346256     lvl  5:         │   │   ├── child 2:<qt_splithandle_>:QSplitterHandle
6176346416     lvl  5:         │   │   └── child 3:<qt_splithandle_>:QSplitterHandle
6176346096     lvl  4:         │   ├── child 2:<qt_splithandle_>:QSplitterHandle
6176345936     lvl  4:         │   └── child 3:<qt_splithandle_>:QSplitterHandle
5375112560     lvl  3:         ├── child 1:<mainVLayout>:QVBoxLayout
5375103600     lvl  3:         └── child 2:<minibufferFrame>:QFrame
5375101680     lvl  4:             └── child 0:<minibufferHLayout>:QHBoxLayout

total objects: 90
```

Edward K. Ream

unread,
May 26, 2024, 1:48:17 PMMay 26
to leo-e...@googlegroups.com
On Sun, May 26, 2024 at 8:06 AM HaveF HaveF <iamap...@gmail.com> wrote:
Thanks Edward and Thomas.

Btw, do we need to put Leo's Qt Widget hierarchy Code to the doc? I believe it is very useful.

The 6.8.0 docs will mention the show-qt-widgets command. That should suffice.

Edward

Thomas Passin

unread,
May 26, 2024, 3:06:27 PMMay 26
to leo-editor
For commands that I think I might use often enough, I add them to my "Local" menu, which I define in myLeoSettings.leo.  It's a handy way to deal with those things that don't rate a button or keyboard shortcut but that I want to remember.

HaveF HaveF

unread,
May 26, 2024, 6:54:12 PMMay 26
to leo-e...@googlegroups.com
The 6.8.0 docs will mention the show-qt-widgets command. That should suffice.

> Nice. Thanks, Edward,

For commands that I think I might use often enough, I add them to my "Local" menu, which I define in myLeoSettings.leo.  It's a handy way to deal with those things that don't rate a button or keyboard shortcut but that I want to remember.

> Nice tip! My toolbar buttons are a little too crowded at the moment. Just need this. Thanks Thomas

Thomas Passin

unread,
May 26, 2024, 11:38:47 PMMay 26
to leo-editor
For those who haven't defined a custom menu in myLeoSettings.leo before, here is a screenshot of what I've got in my setting tree.  It defines a menu named "Local", which displays just before (to the left of) Leo's standard "Help" menu.  The string after the @item in each headline is the name of a minibuffer command.

I like to use a prefix to help remind me which commands are mine and which are Leo's. Most of mine are prefixed by tp- or x-. My commands used in the custom menus are defined under the @commands node in myLeoSettings.leo.
local-menu-settings.png

Thomas Passin

unread,
May 26, 2024, 11:41:15 PMMay 26
to leo-editor
I forgot to write that the body of an @item node contains the item's label  that you see when you open the menu.

HaveF HaveF

unread,
May 27, 2024, 3:18:38 AMMay 27
to leo-e...@googlegroups.com
For those who haven't defined a custom menu in myLeoSettings.leo before, here is a screenshot of what I've got in my setting tree.  It defines a menu named "Local", which displays just before (to the left of) Leo's standard "Help" menu.  The string after the @item in each headline is the name of a minibuffer command.

Thanks Thomas! Your `@menuat help before` saved me.

I can finally group some of my buttons into sub menus.
Reply all
Reply to author
Forward
0 new messages