A Script To Undo Many Layout Changes

38 views
Skip to first unread message

Thomas Passin

unread,
Jul 27, 2024, 3:12:40 PM7/27/24
to leo-editor
This script will safely undo all the layout changes we have been discussing recently.  It will work for most layouts that include zero, one, or both of VR and VR3. It does not solve the general problem.  One of many reasons for that is that any Qt GUI widgets that may have been added by a user script or plugin won't have a standard way to tear them down and unhook them from Leo's event system.  For any special case a savvy user may be able to figure out how to do it but that won't work in general. These widgets also won't have a general way to discover the object names that are needed.

The script returns to the original Leo default layout with the Tree and Log frame on the left and the Body Editor on the right.  It works with Leo 6.8.1 and the devel branch but I don't know if it will work for earlier Leo versions.

Note that the way I import the VR module is a bit indirect.  I could just have used a regular import but this way is a template for a more general future script where the object types may not be known ahead of time.

import importlib
from leo.core.leoQt import Orientation

gui = g.app.gui

MAIN_SPLITTER_ORIENTATION = Orientation.Horizontal
SECONDARY_SPLITTER_ORIENTATION = Orientation.Vertical

ms = gui.find_widget_by_name(c, 'main_splitter')
ss = gui.find_widget_by_name(c, 'secondary_splitter')
edf = gui.find_widget_by_name(c, 'bodyFrame')
lf = gui.find_widget_by_name(c, 'logFrame')
vr3 = gui.find_widget_by_name(c, 'viewrendered3_pane')
vr = gui.find_widget_by_name(c, 'viewrendered_pane')

edf_parent = edf.parent()
lf_parent = lf.parent()

delete_laters = set({})
if vr3:
    c.doCommandByName('vr3-hide')

if vr:
    v = importlib.import_module(vr.__module__)
    vr.hide()
    vr.closeEvent({})
    vr.deleteLater()
    h = c.hash()
    del v.controllers[h]

ms.setOrientation(MAIN_SPLITTER_ORIENTATION)
ss.setOrientation(SECONDARY_SPLITTER_ORIENTATION)

if edf_parent != ms:
    ms.insertWidget(2, edf)
    edf.show()
    if edf_parent != ss:
        delete_laters.add(edf_parent)

if lf_parent != ss:
    ss.insertWidget(2, lf)
    lf.show()
    if lf_parent != ms:
        delete_laters.add(lf.parent)

for w in delete_laters:
    w.deleteLater()

ms.setSizes([100_000] * len(ms.sizes()))
ss.setSizes([100_000] * len(ss.sizes()))


Edward K. Ream

unread,
Jul 28, 2024, 4:04:23 AM7/28/24
to leo-e...@googlegroups.com
On Sat, Jul 27, 2024 at 2:12 PM Thomas Passin wrote:

This script will safely undo all the layout changes we have been discussing recently.  It will work for most layouts that include zero, one, or both of VR and VR3. It does not solve the general problem.

Thanks for this work. My experiments with PR #4017 suggest there is no way (in general) to switch layouts without restarting Leo.

In the PR, dw.reloadSettings calls the new dw.recreateMainWindow method. This method restarts Leo if the layout has changed.

This approach solves one problem but creates another. How can we allow users to define their own layouts? We don't want to mandate executing `@script` nodes at startup, so a descriptive approach seems necessary. The PR suggests using an `@data` node but provides no details. Let me sketch the idea.

When the main splitter has the (usual) vertical orientation, the (working!!) code to create the layout is (roughly):

self.createOutlinePane(secondary_splitter)
self.createLogPane(secondary_splitter)
self.vr_parent_frame = vr_splitter = QtWidgets.QSplitter()
vr_splitter.setObjectName('vr-splitter')
self.createBodyPane(vr_splitter)
main_splitter.addWidget(vr_splitter)

A description of this code in an @data qt-layouts node might look something like this:

legacy=
  secondary_splitter: tree, log
  main_splitter: vr_splitter

with the understanding that:

- Layouts always create main and secondary splitters.
- Layouts will create the vr_splitter only if needed.
- The vr_splitter always contains the body and VR pane.

This scheme will surely be modified as we go along.

Summary

The PR will soon contain hard-coded methods that define all Jake's suggested layouts.

When that work is complete, I'll start work on @data qt-layouts.

Edward

Edward K. Ream

unread,
Jul 28, 2024, 4:12:57 AM7/28/24
to leo-e...@googlegroups.com
On Sun, Jul 28, 2024 at 3:04 AM Edward K. Ream wrote:

In the PR, dw.reloadSettings calls the new dw.recreateMainWindow method. This method restarts Leo if the layout has changed.

This approach solves one problem but creates another. How can we allow users to define their own layouts?

I forgot to mention that the PR will soon define an empty layout.  This layout should help define @button nodes that define prototype layouts.

Edward

Edward K. Ream

unread,
Jul 28, 2024, 4:21:09 AM7/28/24
to leo-e...@googlegroups.com
On Sun, Jul 28, 2024 at 3:12 AM Edward K. Ream wrote:

> ...the PR will soon define an empty layout.  This layout should help define @button nodes that define prototype layouts.

Well, that idea quickly blew up.  There are a lot of assumptions that an empty layout would violate.

Edward

Thomas Passin

unread,
Jul 28, 2024, 7:48:54 AM7/28/24
to leo-editor
It's not necessary that VR/VR3 be in their own separate splitter.  None of my layout scripts create one.  In one case, VRx goes into the body frame's existing tabbed layout widget.  In another, it goes into the body frame's existing splitter.  In another it goes into the Log frame.  So don't go making assumptions about added splitters. We have to remember too that users may write layout scripts for other widgets too.  Another wrinkle is that after running a layout restore script we want to be able to apply another layout script and have it work.  I have learned that isn't always the case.

Also, don't assume that a script will create a new layout only by inserting an enabled plugin widget.  My VRx layout scripts work fine whether or not their plugin has been enabled.

Here are some of the things that need to be done to restore the default layout (I will assume that a only a single Gui widget has been added), possibly with a new splitter that may have had some standard Leo elements moved into it.  I will call the basic Leo structural widgets (main splitter, secondary splitter, body frame, log frame, tree) the "standard widget set" (SWS):

1. Discover the newly added widget.  In general we don't know its name.
2. Discover what GUI element it's in and whether or not that element is part of the SWS.
3. Delete the new widget.  Note that none of our usual additional widgets (VR/VRx and whatever else someone may want to work with in the future) have a teardown method, let alone a standard API call for it.  The teardown has to:
    a. remove any registered hooks into the Leo event system;
    b. remove any references the widget has set up outside of itself, such as controllers[h] in the case of VRx;
    c. set the widget's parent to None (after making itself invisible, otherwise it would appear in a free-floating window);
    d. delete the widget itself;
4. Reparent any standard SWS widgets back to the standard parents;
5. Reset the orientations of the standard containers to their default values;
6. Remove any added splitters while NOT removing any of the SWS;

The script I presented above does all these things except it only looks for VRx, and it knows how to tear them down using insider knowledge.

Restarting Leo to change a layout is just not a viable approach.  It's too slow and disruptive to a user (or at least to me). Yet I don't see a perfectly general way to restore a standard layout either.

There is a way forward if we are willing to insist on some conventions:

1. Each custom GUI widget, like VR/VR3, must have a teardown API method with a standard name, such as teardown().
2. The object names of the custom widgets must be recorded somewhere in a record of the new layout.
3. The object names of any other added GUI components, such as splitters, must also be recorded.

Developing a teardown() method may require some development or even refactoring for some plugins.  But they should have had one all along anyway.

The conventions would apply to the custom GUI widgets, and writers of layout scripts would have to make their scripts align with them.  There could, for example, be a new Leo method c.registerLayout(), and a layout script would have to call it to make sure that their layout could be reverted back to the default ( we can assume that the reversion command will always look for VR/VR3, so layout scripts relocating them could be an exception).  

In the future, no new plugins would be accepted into Leo's plugins directory unless it complied with these conventions. Anyone who cared could revise existing plugins.  Script writers of layout scripts could always roll their own and create restore scripts of their own.  They just wouldn't be general.

Edward K. Ream

unread,
Jul 28, 2024, 10:03:05 AM7/28/24
to leo-e...@googlegroups.com
On Sun, Jul 28, 2024 at 6:48 AM Thomas Passin <tbp1...@gmail.com> wrote:

It's not necessary that VR/VR3 be in their own separate splitter.

I agree. Layouts are free to put the VR panes (plural) anywhere.

Also, don't assume that a script will create a new layout only by inserting an enabled plugin widget.  My VRx layout scripts work fine whether or not their plugin has been enabled.

Scripts must not assume that any VR plugin has been enabled. The 'show-plugin-info' menu item may enable a VR plugin. I forget the details.

Here are some of the things that need to be done to restore the default layout .

Not necessary, as I'll explain next.

Edward

Edward K. Ream

unread,
Jul 28, 2024, 10:11:19 AM7/28/24
to leo-e...@googlegroups.com
On Sun, Jul 28, 2024 at 3:04 AM Edward K. Ream <edre...@gmail.com> wrote:

The PR will soon contain hard-coded methods that define all Jake's suggested layouts.

When that work is complete, I'll start work on @data qt-layouts.

@data qt-layouts is a wretched idea. The advantage of writing up bad ideas is that they can be killed before doing any work on them ;-)

Why create a half-baked description language when plugins can execute Qt code safely? A new Leo event,

    g.doHook("after-create-layout-dict", c=c, layout_dict=layout_dict)

should allow plugins to define one or more bespoke layouts. I'll test this theory today.

Edward

Thomas Passin

unread,
Jul 28, 2024, 10:21:57 AM7/28/24
to leo-editor
It's somewhat akin to undo/redo. I would say. No matter what the details, the undo or restore code still needs to know how to delete added GUI widgets.  That's where a teardown() API method comes in.
Reply all
Reply to author
Forward
0 new messages