Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

uitk

Package Overview
Dependencies
Maintainers
1
Versions
147
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

uitk

A comprehensive UI toolkit extending Qt Designer workflows with dynamic loading, custom widgets, and automatic signal-slot management.

pipPyPI
Version
1.1.37
Maintainers
1

License: LGPL v3 PyPI Python Qt Tests

uitk

Name it, and it connects. UITK is a convention-driven Qt framework that eliminates boilerplate. Design in Qt Designer, name your widgets, write matching Python methods — UITK discovers the files, auto-wires signals, persists state, and applies themes. Every convention is overridable when you need control.

Built on qtpy (PySide2 / PySide6). Runs standalone or hosted inside DCCs (Maya, Blender, 3ds Max) through a pluggable handler ecosystem, and ships a marking-menu subsystem for radial-menu tool shells.

Install

pip install uitk

Live demo

The package ships an interactive example window that exercises the full feature set — option_box plugins, the pythontk logging console, header/footer, themes, and more:

python -m uitk.examples.example

Quickstart

from uitk import Switchboard

class EditorSlots:
    def __init__(self, **kwargs):
        self.sb = kwargs["switchboard"]
        self.ui = self.sb.loaded_ui.editor

    def btn_save_init(self, widget):   # runs once when btn_save registers
        widget.setText("Save")

    def btn_save(self):                # runs on clicked (QPushButton default signal)
        self.sb.message_box("Saved")

sb = Switchboard(ui_source="editor.ui", slot_source=EditorSlots)
sb.loaded_ui.editor.show(pos="screen", app_exec=True)

Widget btn_save in editor.ui is connected to EditorSlots.btn_save because the names match.

How it wires up

ConventionExampleResult
UI file → slot classeditor.uiEditorSlotsClass discovered, instantiated with switchboard= kwarg
Widget → slot methodbtn_save (objectName) → def btn_save(self)Widget's default signal connected
Widget → init hookbtn_savedef btn_save_init(self, widget)Called once on registration
UI hierarchymenu#file.ui is child of menu.uiResolvable via sb.get_ui_relatives(ui, upstream=True)
Tagspanel#floating.uiExposed as ui.tags == {"floating"}

Default signals by base Qt type:

WidgetSignalCallback arg
QPushButtonclicked
QCheckBoxtoggledchecked: bool
QRadioButtontoggledchecked: bool
QComboBoxcurrentIndexChangedindex: int
QLineEdittextChangedtext: str
QTextEdittextChanged
QSpinBox / QDoubleSpinBoxvalueChangedvalue
QSlider / QDial / QScrollBarvalueChangedvalue: int
QListWidget / QTreeWidgetitemClickeditem[, column]
QTableWidgetcellChangedrow, column
QTabWidget / QStackedWidget / QToolBoxcurrentChangedindex: int

Override with @Signals — declare one or more signals to connect instead of the default. @Signals.blockSignals is a companion decorator that suppresses widget signals while the slot runs (useful for programmatic state changes):

from uitk import Signals

@Signals("released")           # override default (e.g. "clicked") on a button
def btn_confirm(self):
    self.commit()

@Signals("textChanged", "editingFinished")  # connect to multiple signals
def txt_search(self, *args):
    self.filter_results()

@Signals.blockSignals          # run without firing widget signals
def refresh_spinbox(self):
    self.ui.spn_count.setValue(10)

Parameter injection — slots can request widget by name; UITK introspects the signature:

def btn_save(self): ...                       # no params
def btn_save(self, widget): ...               # widget injected
def cmb_font(self, index): ...                # signal arg only
def cmb_font(self, index, widget): ...        # signal arg + widget

Widget enhancements

Every registered widget gains these lazy-initialized properties.

.menu — dynamic popup menu

def btn_options_init(self, widget):
    widget.menu.add("QCheckBox", setText="Auto-save", setObjectName="chk_auto")
    widget.menu.add("QSpinBox", setPrefix="Interval: ", setObjectName="spn_int")
    widget.menu.add("QSeparator")
    widget.menu.add("QPushButton", setText="Apply", setObjectName="btn_apply")

def btn_options(self):
    auto = self.ui.btn_options.menu.chk_auto.isChecked()
    interval = self.ui.btn_options.menu.spn_int.value()

menu.add() accepts a widget class string, a list of strings (shorthand for multiple items), a dict (text → data), or another widget instance. Added widgets are accessible by objectName on the menu.

.option_box — action panel attached to input widgets

def txt_path_init(self, widget):
    widget.option_box.menu.add("QPushButton", setText="Browse...", setObjectName="btn_browse")
    widget.option_box.menu.btn_browse.clicked.connect(self.browse)

Pluggable option system: ClearOption, BrowseOption, PinValuesOption, ActionOption, MenuOption, ContextMenuOption, RecentValuesOption. See WIDGETS.md.

State persistence

Widget values save on change, restore on show:

# User sets spinbox to 5, closes app. Next launch: spinbox is 5 again.

widget.restore_state = False        # per-widget opt-out
ui.restore_widget_states = False    # per-UI opt-out
ui.restore_window_size = False      # skip window geometry

widget.block_signals_on_restore = True  # restore without firing slot

Window geometry also persists automatically, debounced to 500ms on resize/move.

Slot-level controls

widget.debounce = 300          # coalesce rapid signals into one slot call after 300ms
widget.slot_timeout = 60       # warn if slot takes > 60s; allow Esc to cancel
widget.refresh_on_show = True  # call *_init again on every subsequent show
ui.default_slot_timeout = 360  # fallback timeout for all slots in this UI

Theming

ui.style.set(theme="dark", style_class="translucentBgWithBorder")

Themes (light, dark) are palette dicts — WIDGET_BACKGROUND, BUTTON_HOVER, BORDER_COLOR, etc. QSS variables are substituted at apply time. Monochrome SVG icons in uitk/icons/ are auto-colored to match ICON_COLOR.

Hierarchy & tags

UI filenames with # encode hierarchy and metadata:

menu.ui              # base
menu#file.ui         # child of menu (tag "file")
menu#file#recent.ui  # grandchild (tags "file", "recent")
panel#floating.ui    # base "panel" with tag "floating"
  • ui.tags — set of tags
  • ui.has_tags("floating") — check
  • ui.edit_tags(add="active", remove="inactive")
  • sb.get_ui_relatives(ui, upstream=True) — ancestors
  • sb.get_ui_relatives(ui, downstream=True) — children
  • sb.get_ui_relatives(ui, exact=True) — siblings

Cross-UI widget value sync: when a widget's value changes, MainWindow.on_child_changed syncs that widget's value to same-named widgets in related UIs via get_ui_relatives.

MainWindow

Every UI is wrapped in a MainWindow instance.

Lifecycle signalson_show, on_first_show, on_hide, on_close, on_focus_in, on_focus_out, on_child_registered(widget), on_child_changed(widget, value), on_pinned_changed(bool).

Key propertiesui.sb, ui.widgets (set), ui.slots (slot instance), ui.settings (SettingsManager branch), ui.state (StateManager), ui.style (StyleSheet), ui.tags (set), ui.path (str), ui.is_initialized, ui.is_current_ui, ui.is_pinned, ui.header, ui.footer, ui.presets.

Show positioningui.show(pos="screen" | "cursor" | QPoint, app_exec=False). app_exec=True starts the Qt event loop and exits the process on close.

Handler ecosystem

Extend UITK without editing it. Handlers are classes with a DEFAULTS dict that register under sb.handlers.<name>:

sb = Switchboard(
    ui_source="...",
    handlers={"ui": MyCustomUiHandler},   # replaces the default UiHandler
)

sb.handlers.ui.apply_styles(ui)
sb.handlers.ui.show(ui, pos="cursor")
sb.configurable.ui.default_position.set("cursor")  # handler DEFAULTS merged here

The built-in UiHandler applies default styling and positions windows. MarkingMenu registers itself as sb.handlers.marking_menu. Consumers like tentacle ship subclassed handlers (MayaUiHandler) for DCC-specific behavior.

Consumer patterns

Standalone app

sb = Switchboard(ui_source="./ui", slot_source="./slots")
sb.loaded_ui.main.show(app_exec=True)

Hosted-or-standalone tool (mayatk pattern)

def launch(sb=None):
    if sb is None:
        sb = Switchboard(ui_source="my_tool.ui", slot_source=MyToolSlots)
        ui = sb.loaded_ui.my_tool
        ui.show(pos="screen")
    else:
        ui = sb.handlers.marking_menu.show("my_tool")
    return ui

Marking-menu DCC shell (tentacle pattern)

from uitk import MarkingMenu

class TclMaya(MarkingMenu):
    def __init__(self, parent=None, **kwargs):
        bindings = {
            "Key_F12": "main#startmenu",
            "Key_F12|LeftButton": "cameras#startmenu",
            "Key_F12|RightButton": "scene#startmenu",
        }
        super().__init__(parent, ui_source="ui", slot_source="slots",
                         bindings=bindings, **kwargs)

Deeper documentation

Rendered from the GitHub repository:

License

LGPL-3.0-or-later — see COPYING.LESSER.

FAQs

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts