Skip to main content

Render C struct / JSON schema as editable PySide6 UI, export to C/JSON/bin

Project description

struct2ui

Render C structs / JSON schemas as an editable PySide6 UI, and convert freely between C / JSON / bin. Built for algorithm parameter tuning: describe a C interface in JSON, auto-generate a Qt form, edit it, then export back to C source, JSON, or binary.

Features

  • JSON → UI: describe C structs / enums / arrays with minimal JSON and auto-render Qt widgets (int→QSpinBox, float→QDoubleSpinBox, enum→QComboBox, etc.).
  • Embeddable: StructEditor is a plain QWidget that drops into any PySide6 / PyQt UI.
  • Multi-format export: edited results export to C source, JSON, or binary; C source can also be parsed back into a schema.
  • Validation: keyword spell-checking (with “Did you mean X?” hints), semantic audits (min<=max, step>0), and pipeline cross-validation.
  • Qt-binding agnostic: built on Qt.py — works with PySide6 / PyQt6 / PySide2 / PyQt5.

Installation

pip install struct2ui

# Pick a Qt binding (choose one)
pip install "struct2ui[pyside6]"
pip install "struct2ui[pyqt6]"

# Enable ELF layout verification (optional)
pip install "struct2ui[elf]"

The library itself only depends on Qt.py; you must install a Qt binding (PySide6 / PyQt6 / etc.) yourself. ELF verification depends on pyelftools, installed via the [elf] extra.

Quick Start

from Qt import QtWidgets
from struct2ui import StructEditor

app = QtWidgets.QApplication([])

editor = StructEditor(
    flow_file="abc.json",   # pipeline definition file (optional)
    cfg_dir="cfg_t",        # modules dir: *.json holding struct/enum/typedef (optional)
)
editor.resize(480, 600)
editor.show()

app.exec_()

JSON Schema Reference

This is the core of the library: you describe your C types in JSON and the UI is generated from it. Files live in the modules directory (cfg_dir), one *.json per logical group. Each file is a flat object whose keys are either type names (your structs / enums) or the reserved key typedefs.

File layout

{
  // optional free-form metadata (ignored by the loader, allowed anywhere)
  "version": "1.0",
  "description": "audio EQ parameters",

  // type aliases: map a custom C type to a primitive
  "typedefs": { "gain_t": "int32_t", "freq_t": "float" },

  // a struct definition
  "eq_cfg_t": {
    "type": "struct",
    "items": [
      { "name": "enabled", "type": "uint8_t", "value": 1 },
      { "name": "gain",    "type": "gain_t",  "value": 0,
        "min": -12, "max": 12, "step": 1, "unit": "dB",
        "tip": "output gain", "when": { "enabled": 1 } }
    ]
  },

  // an enum definition
  "mode_t": {
    "type": "enum",
    "items": { "OFF": 0, "LOW": 1, "HIGH": 2 }
  }
}

Top-level blocks

Block type Keys Notes
struct type, items (list of field specs) items is required
enum type, items (object {NAME: value}) reads back the enumerator name
typedefs object {alias: real_type} resolved before field building

Free-form metadata keys are allowed and ignored at any level: version, description, author, comment, note.

Field properties (inside a struct's items)

Each entry in items is a field spec. Only the following keys are recognized — any other key triggers an "unknown keyword" error (with a "Did you mean …?" hint):

Property Applies to Description
name all Required. C field name.
type all Required. C type or a typedefs alias (e.g. int32_t, float, char, an enum/struct name).
value all Default value. For arrays, a list (per element) or a scalar (applied to every element).
count arrays Makes the field an array. Integer, a #define name, an expression (N - 1), or a list for multi-dim ([3, 4], stored flat as 12).
min / max int, float Numeric bounds. Validated min <= max. Drive spin-box / slider / dial ranges.
step int, float Increment, must be > 0.
decimals float Number of fractional digits shown/stored. Non-negative integer; defaults to digits implied by step.
unit all Unit suffix shown in the label, e.g. gain (dB).
tip all Tooltip text.
choices int, float, char[N] Discrete value set rendered as a combo (see below).
when all Conditional enable/disable (see below).
widget all Override the auto-picked editor (see table below).

widget values

When omitted, the widget is inferred from type (and from min/max, choices). Override it explicitly with widget:

widget Valid for Renders as
checkbox int Check box (also auto-picked when min:0, max:1).
toggle bool/int Toggle push-button.
combo int, float, char[N] Combo box; pair with choices.
slider int Slider with min/max labels (requires min & max).
dial int Rotary dial popup (requires min & max).
file array Path label + Browse button; loads array values from a text file.
multiline array Single-line shell that pops up a multi-line editor.
table struct array Force grid layout (one column per struct member).

Numeric fields without an explicit widget become a QSpinBox / QDoubleSpinBox; scalar arrays become a comma-separated line edit.

choices (discrete values)

A non-enum integer / float / char[N] field can be constrained to a fixed set. Two forms are accepted:

"choices": [0, 1, 2, 3]                              // label = str(value)
"choices": [{ "Off": 0 }, { "Low": 1 }, { "High": 2 }]  // explicit labels

The combo reads back the underlying value (not the label), so C / JSON / bin export still emits the number (or string for char[N]). Audits enforce: each value within [min, max] when declared, and value (the default) must be one of the choices.

when (conditional enable/disable)

A field can be greyed out unless other fields hold specific values. when is an object mapping a sibling field name to its required value; all entries must match (logical AND):

{ "name": "cutoff", "type": "float", "when": { "enabled": 1, "mode": "HIGH" } }

The dependency name is matched against the dotted leaf name in scope. When a dependency is not found, the field stays enabled.

count expressions & multi-dimensional arrays

count may be an integer, a #define constant name, or an integer expression of those using + - * / % () (e.g. MAX_BAND_NUM - 1). A list makes a multi-dimensional array that is stored flat to match the C ABI:

{ "name": "matrix", "type": "float", "count": [3, 4] }  // 12 contiguous floats, shown as [3][4]

char[N] is treated as a C string by default (use widget to override).

Validation

Loading runs three layers of checks, surfaced in the load report panel:

  • Keyword spelling — unknown keys report "Did you mean tip?" style hints.
  • Semantic auditsmin <= max, step > 0, count > 0, choices within bounds, default is one of the choices.
  • Pipeline cross-validation — values in the pipeline file are checked against the schema (range, choices, enum membership).

Embedding into an Existing UI

StructEditor is a regular QWidget; just put it into a layout:

from Qt import QtWidgets
from struct2ui import StructEditor

class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        editor = StructEditor(
            "abc.json", "cfg_t",
            settings_org="MyCompany",   # custom QSettings scope to
            settings_app="MyApp",       # avoid clashing with the host app
        )
        self.setCentralWidget(editor)

Constructor Parameters

Parameter Description
flow_file Path to the pipeline JSON file (pipeline definition), defaults to None; pick it in the UI when omitted
cfg_dir Modules directory holding *.json (struct / enum / typedef definitions), defaults to None; pick it in the UI when omitted
elf_file Path to an ELF for layout verification, defaults to None; pick it under Settings when omitted. The enable checkbox there toggles verification on/off while keeping the path.
parent Qt parent object, defaults to None
appearance Button appearance overrides, {key: {'mode': ..., 'icon': ..., 'text': ...}}
settings_org QSettings organization name, defaults to 'struct2ui'
settings_app QSettings application name, defaults to 'StructEditor'
toolbar_orientation Toolbar layout, 'horizontal' (default) or 'vertical'
custom_button_position Where add_custom_button() drops buttons relative to the built-ins: 'end' (after, default) or 'start' (before / far left)

Custom Toolbar Buttons

Host applications can add their own buttons to the top path bar via add_custom_button(). All custom buttons form a single group, separated from the built-in buttons by a divider that is added automatically — you only choose which side they sit on (once, via the custom_button_position constructor arg).

editor = StructEditor("abc.json", "cfg_t", custom_button_position="end")

def on_send():
    print(editor.current_values())

btn = editor.add_custom_button(
    "Send",                 # tooltip / accessible label (shown when no icon)
    on_send,                # callable invoked on click (no arguments)
    icon="icons/send.png",  # optional; falls back to text when missing
    checkable=False,        # optional; make the button toggleable
)
# `btn` is the created QPushButton, returned so you can tweak it further.
Parameter Description
text Tooltip / accessible label, also shown when no icon is given. Required.
on_click Callable invoked on click, takes no arguments. Required.
icon Path to an icon file; falls back to text when the path is missing/empty.
checkable Make the button toggleable, defaults to False.

Returns the created QPushButton.

Programmatic API

Beyond the UI, StructEditor exposes methods to read state and drive exports from code (e.g. wired to a custom button):

Method Description
current_values() -> dict Live editor values of the active section, keyed by field name (empty dict when no parameter section is shown).
current_paths() -> dict Current {'modules', 'pipeline', 'elf'} file paths; an empty string means that slot is unset.
export_bin(path, show_dialogs=False) -> bool Write the current pipeline to a .bin at path — the programmatic equivalent of clicking Export → Binary. Returns True when there are no ELF layout errors (or ELF verification is off), False otherwise.

export_bin verifies the ELF layout first when an ELF is selected and enabled (the file is still written, matching the UI flow). With show_dialogs=True it pops the same success / ELF-mismatch / failure dialogs the UI shows; with the default show_dialogs=False no dialogs appear and render/write failures are raised as exceptions for the caller to handle.

paths = editor.current_paths()           # {'modules': ..., 'pipeline': ..., 'elf': ...}
name = paths['pipeline']                  # derive an output name from the pipeline
ok = editor.export_bin("out/speech.bin")  # True when ELF layout is clean

Export API

The low-level export functions can be used standalone, without any UI:

from struct2ui.schema import SchemaRegistry
from struct2ui.exporters import (
    emit_c,          # sections + registry -> C source string
    dumps_json,      # -> JSON string
    emit_bin,        # -> binary bytes
    merge_abi,       # merge ABI info
    verify_sections, # verify .bin layout against an ELF
    parse_c_source,  # C source -> parse result
    build_schema_dict,
)

Architecture

Layer Module Responsibility
Schema (pure data, no Qt) struct2ui.schema Parse *.json into a Field tree; spell-checking, semantic audits, pipeline cross-validation
UI rendering struct2ui.ui WidgetFactory, FormRenderer/TreeRenderer, array tables, when conditional binding
Export struct2ui.exporters C / JSON / bin export, C source reverse parsing, ELF verification
Top-level widget struct2ui.StructEditor Action toolbar / content area; Settings panel for source paths; load report panel; QSettings path memory

Development

pip install -e ".[dev]" --no-build-isolation
python -m pytest

Tests live under tests/ and drive real Qt widgets headlessly on the offscreen platform.

License

MIT © Jay

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

struct2ui-0.4.0.tar.gz (92.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

struct2ui-0.4.0-py3-none-any.whl (81.0 kB view details)

Uploaded Python 3

File details

Details for the file struct2ui-0.4.0.tar.gz.

File metadata

  • Download URL: struct2ui-0.4.0.tar.gz
  • Upload date:
  • Size: 92.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for struct2ui-0.4.0.tar.gz
Algorithm Hash digest
SHA256 abf954724aa6243972570ed4492d9b49c72e9b12e82952eef9bf46ef0ac0579d
MD5 8088b209cfc16320b9015cd9488179f6
BLAKE2b-256 94b9a10a6fccc4426bf1276f25ab2cea1f0501b9a9ffb042e5fe44dc4d791c1d

See more details on using hashes here.

File details

Details for the file struct2ui-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: struct2ui-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 81.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for struct2ui-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 43306ff0740e97cdee923c28583615eb727c50dc96bbdd371e22791dc14d4568
MD5 5e61ba8b66832a05b33d7ae59cd708b0
BLAKE2b-256 b94aede07903c6a33fb07f410b097e6aea7bf659db7a94f3b6132ad3b70b1f94

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page