"""
Helpers to create HBox and VBoxes.
Layout and group helpers are provided by picoui.helpers; this module
adds JD-Xi-specific helpers (filter buttons, icons, ADSR styling, etc.)
and re-exports picoui helpers under the names used by the rest of the app.
"""
from typing import Any
import qtawesome as qta
from PySide6.QtCore import QSize, Qt
from PySide6.QtGui import QIcon, QPixmap
from PySide6.QtWidgets import (
QCheckBox,
QFormLayout,
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QLayout,
QPushButton,
QScrollArea,
QSizePolicy,
QVBoxLayout,
QWidget,
)
from jdxi_editor.midi.data.digital.filter import DigitalFilterMode
from jdxi_editor.ui.common import JDXi, QVBoxLayout, QWidget
from jdxi_editor.ui.image.utils import base64_to_pixmap
from jdxi_editor.ui.image.waveform import generate_waveform_icon
from jdxi_editor.ui.style.dimensions import Dimensions
from picoui.helpers import (
build_group,
create_form_layout,
create_layout,
create_layout_with_inner_layouts,
create_layout_with_items,
create_left_aligned_row,
create_vertical_layout,
group_from_definition,
group_with_layout,
)
from picoui.specs.widgets import CheckBoxSpec
# Backward-compatible names (used across jdxi_editor)
[docs]
create_group = build_group
[docs]
create_group_with_layout = group_with_layout
[docs]
create_group_from_definition = group_from_definition
[docs]
def create_icon_from_qta(icon_name: str) -> QIcon:
"""create qta icon"""
return qta.icon(icon_name, color=JDXi.UI.Style.WHITE, icon_size=0.7)
[docs]
def create_icon_and_label(
label: str = "", icon: str = ""
) -> tuple[QHBoxLayout, QLabel]:
"""create icon and label"""
layout = QHBoxLayout()
label = QLabel(label)
icon_pixmap = JDXi.UI.Icon.get_icon_pixmap(
icon, color=JDXi.UI.Style.FOREGROUND, size=20
)
if icon_pixmap and not icon_pixmap.isNull():
icon_label = QLabel()
icon_label.setPixmap(icon_pixmap)
layout.addWidget(icon_label)
layout.addWidget(label)
return layout, label
[docs]
def create_checkbox_from_spec(spec: CheckBoxSpec) -> QCheckBox:
"""create checkbox from spec"""
checkbox = QCheckBox(spec.label)
checkbox.setChecked(spec.checked_state)
checkbox.stateChanged.connect(spec.slot)
checkbox.setToolTip(spec.tooltip)
return checkbox
[docs]
def create_icon_label_with_pixmap(pixmap: QPixmap) -> QLabel:
"""create icon label"""
label = QLabel()
label.setPixmap(pixmap)
label.setAlignment(Qt.AlignmentFlag.AlignHCenter)
return label
[docs]
def create_icons_layout() -> QHBoxLayout:
"""Create icons layout"""
icons_hlayout = QHBoxLayout()
for icon in [
"mdi.volume-variant-off",
"mdi6.volume-minus",
"mdi.amplifier",
"mdi6.volume-plus",
"mdi.waveform",
]:
icon_pixmap = qta.icon(icon, color=JDXi.UI.Style.GREY).pixmap(
JDXi.UI.Dimensions.Icon.WIDTH, JDXi.UI.Dimensions.Icon.HEIGHT
)
icon_label = create_icon_label_with_pixmap(icon_pixmap)
icons_hlayout.addWidget(icon_label)
return icons_hlayout
[docs]
def create_adsr_icon_pixmap() -> QPixmap:
"""Generate the ADSR waveform pixmap"""
icon_base64 = generate_waveform_icon(
waveform="adsr", foreground_color=JDXi.UI.Style.WHITE, icon_scale=2.0
)
pixmap = base64_to_pixmap(icon_base64)
return pixmap
[docs]
def create_adsr_icon_label() -> QLabel:
"""Generate the ADSR waveform icon (centered)"""
pixmap = create_adsr_icon_pixmap()
return create_icon_label_with_pixmap(pixmap)
[docs]
def create_adsr_icon() -> QIcon:
"""create adsr icon"""
return QIcon(create_adsr_icon_pixmap())
[docs]
def create_centered_adsr_icon_layout() -> QHBoxLayout:
"""Create a centered ADSR icon layout (for consistent centering in envelope groups)"""
icon_label = create_adsr_icon_label()
return create_layout_with_items([icon_label])
[docs]
def create_group_adsr_with_hlayout(
name: str, hlayout: QHBoxLayout, analog: bool = False
) -> QGroupBox:
"""create ADSR Group with an hlayout (harmonized for Digital and Analog)"""
group = QGroupBox(name)
group.setLayout(hlayout)
JDXi.UI.Theme.apply_adsr_style(group, analog=analog)
return group
[docs]
def create_envelope_group(
name: str = "Envelope",
icon_layout: QHBoxLayout = None,
adsr_widget: QWidget = None,
analog: bool = False,
) -> QGroupBox:
"""
Create a standardized envelope group with icon and ADSR widget.
The icon is centered horizontally using stretches in an HBoxLayout,
and the ADSR widget is placed below it in a VBoxLayout.
:param name: Group box title (default: "Envelope")
:param icon_layout: Optional icon layout (if None, creates centered ADSR icon)
:param adsr_widget: ADSR widget to add below the icon
:param analog: Whether to apply analog styling
:return: QGroupBox with envelope layout
"""
group = QGroupBox(name)
group.setProperty("adsr", True)
if icon_layout is None:
icon_layout = create_centered_adsr_icon_layout()
# --- Create vertical layout: icon layout on top (centered), ADSR widget below
env_layout = QVBoxLayout()
env_layout.addLayout(icon_layout) # Centered icon at top
if adsr_widget:
env_layout.addWidget(adsr_widget) # ADSR widget below icon
env_layout.addStretch() # --- Stretch at bottom for spacing
group.setLayout(env_layout)
if analog:
JDXi.UI.Theme.apply_adsr_style(group, analog=True)
else:
group.setStyleSheet(JDXi.UI.Style.ADSR)
return group
[docs]
def create_group_and_grid_layout(group_name: str) -> tuple[QGroupBox, QGridLayout]:
"""A named group box with grid layout"""
group = QGroupBox(group_name)
layout = QGridLayout()
group.setLayout(layout)
return group, layout
[docs]
def create_centered_layout_with_child(layout: QFormLayout | QHBoxLayout) -> QHBoxLayout:
"""create centered layout with a child layout"""
centered_layout = QHBoxLayout()
centered_layout.addStretch()
centered_layout.addLayout(layout)
centered_layout.addStretch()
return centered_layout
[docs]
def create_layout_with_child(widget_layout: QHBoxLayout) -> QHBoxLayout:
"""create layout with child layout"""
layout = QHBoxLayout()
layout.addStretch()
layout.addLayout(widget_layout)
layout.addStretch()
return layout
[docs]
def transfer_layout_items(
source_layout: QLayout, target_layout: QHBoxLayout | QVBoxLayout
) -> None:
"""
Transfer all items from a source layout to a target layout.
This is useful for avoiding "already has a parent" errors when reusing layouts.
All widgets, spacer items, and nested layouts are transferred to the target.
:param source_layout: The layout to take items from
:param target_layout: The layout to add items to
"""
while source_layout.count() > 0:
item = source_layout.takeAt(0)
if item.widget():
target_layout.addWidget(item.widget())
elif item.spacerItem():
target_layout.addItem(item.spacerItem())
elif item.layout():
target_layout.addLayout(item.layout())
[docs]
def create_icon_from_name(icon_name: str) -> Any:
icon = qta.icon(
icon_name,
color=JDXi.UI.Style.WHITE,
icon_size=JDXi.UI.Dimensions.Icon.SCALE_SMALL,
)
return icon
[docs]
def add_sublayout_to_layout(
layout: QVBoxLayout | QHBoxLayout, sub_layouts: list[QHBoxLayout | QVBoxLayout]
):
for sub_layout in sub_layouts:
layout.addLayout(sub_layout)
[docs]
def create_centered_layout(spacing: int = None) -> QHBoxLayout:
"""Hlayout to squish the slides of the widget together"""
layout = QHBoxLayout()
layout.addStretch()
if spacing is not None:
layout.setSpacing(spacing)
return layout