Skip to content

mte.ui — form-spec builders

Every Phase 3 UI surface — ctx.docks.add_panel and ctx.settings_page.register — takes a form spec: a plain dict describing a widget tree. Build these specs with the mte.ui.* helpers so you don't have to write the JSON by hand.

from mte.ui import (
    Column, Row, Group,
    Label, Button, Checkbox, LineEdit, NumberSpin, ComboBox, List,
)

Nothing in mte.ui talks to Qt or PySide — each helper returns a dict following the wire schema the host's C++ form parser expects. Feel free to compose them, splice lists, or generate them programmatically:

from mte.ui import Column, Row, Label, Button

def make_toolbar(commands):
    return Row([Button(text=c["label"], on_click=c["id"])
                for c in commands])

form = Column([Label(text="Actions:"),
               make_toolbar(my_commands)])

The spec is validated on the host side; unknown control types or missing required fields produce an InvalidParams error you'll see in the log.


Layouts

Layout builders take a list of child dicts and arrange them vertically or horizontally.

Column(children)

Stacks children top-to-bottom with a trailing stretch — controls stay at their natural height instead of ballooning to fill a tall dock.

Row(children)

Same idea, left-to-right, with a trailing horizontal stretch.

Group(children, *, title="")

A titled frame (rendered as QGroupBox) containing a vertical stack. Use it to visually group related controls:

Group([
    Checkbox(id="cs", label="Case sensitive", binds="settings:cs"),
    Checkbox(id="ww", label="Whole words",    binds="settings:ww"),
], title="Search options")

Layouts nest freely — a Row inside a Column inside a Group is fine.


Leaf controls

Every leaf accepts the same four shared bindings via keyword args:

Argument Meaning
id= Stable id used by ctx.ui.get / ctx.ui.set and (implicitly) binds.
binds= "settings:<key>" — auto-persist through ctx.settings.
on_click= Command id to invoke — Buttons only.
on_change= Handler id (ctx.docks.on_change(...)) — every other control.

Only leaves that expose a "value" support binds and on_change.

Label(*, text="", id=None)

Static text.

Button(*, text, on_click=None, id=None)

A push button. Firing on_click invokes the registered command; if you want to keep the button in sync with something else you can ctx.ui.set its text.

Checkbox(*, label="", default=False, id=None, binds=None, on_change=None)

Boolean control. default is the initial state; binds overrides it with the persisted value if one exists.

LineEdit(*, placeholder="", default="", id=None, binds=None, on_change=None)

Single-line text input.

NumberSpin(*, default=0, min=None, max=None, step=1, id=None, binds=None, on_change=None)

Integer spin box. min and max clamp the range (both optional; the underlying QSpinBox uses its default range when unset). step is the increment on the arrows.

int range

NumberSpin is backed by QSpinBox, which stores an int (32-bit). Values outside -2_147_483_648 … 2_147_483_647 will saturate at the platform limit.

ComboBox(*, items, default="", id=None, binds=None, on_change=None)

Drop-down. items is a list — each entry may be either a plain string, or an object like {"value": "cpp", "label": "C++"} (only value is used today; label is reserved for a future refinement).

List(*, items, id=None, on_change=None)

List widget with single-selection. on_change fires with the newly selected item's text. No binds — persist the selection yourself if you need it.


Runtime access

Once the panel is registered, drive it from any command:

panel_id = ctx.docks.add_panel(id="my.panel", ..., form=Column([
    LineEdit(id="needle"),
    Checkbox(id="cs"),
]))

# Read current values
needle = ctx.ui.get(panel_id, "needle")
cs     = ctx.ui.get(panel_id, "cs") == "true"

# Write values (does NOT fire on_change)
ctx.ui.set(panel_id, "needle", "hello")
ctx.ui.set(panel_id, "cs", True)

See ctx.ui for the full reference.


Persisting values

binds="settings:<key>" is the shortest way to persist a control's value:

  • On panel build, the widget reads its initial value from ctx.settings.get(key, <default>).
  • On every user edit, the widget writes back via ctx.settings.set(key, new_value).

Because both panels and Preferences pages share ctx.settings, two controls with the same binds string mirror each other in real time. That's how HelloPython's dock and Preferences page keep the "Loud" checkbox in sync.

If you need to persist something the vocabulary doesn't cover (e.g. a List selection), read/write through ctx.settings directly in an on_change callback.