Skip to content

Instantly share code, notes, and snippets.

@minimalefforttech
Last active April 8, 2025 09:16
Show Gist options
  • Save minimalefforttech/bd7aa3d6eb177421bc357800d9bf5630 to your computer and use it in GitHub Desktop.
Save minimalefforttech/bd7aa3d6eb177421bc357800d9bf5630 to your computer and use it in GitHub Desktop.
An example QTreeView that is styled to look like a form.
from PySide6 import QtCore
from types import SimpleNamespace
from dataclasses import dataclass
from typing import Dict, List, Optional, Union
WidgetType = SimpleNamespace(
TEXT_INPUT="text_input",
DROPDOWN="dropdown",
SPINBOX="spinbox",
CHECKBOX="checkbox",
DATE_PICKER="date_picker",
TEXT_AREA="text_area",
FILE_PATH="file_path",
NUMERIC_INPUT="numeric_input",
COLOR_PICKER="color_picker",
SLIDER="slider",
FRAME_RANGE="frame_range",
)
LABEL_ROLE = QtCore.Qt.UserRole + 1
CONFIG_ROLE = QtCore.Qt.UserRole + 2
IS_GROUP_ROLE = QtCore.Qt.UserRole + 3
@dataclass
class EditorConfig:
"""Configuration for editor widgets."""
widget_type: str
options: Optional[Union[List, Dict]] = None
label: str = ""
from PySide6 import QtWidgets, QtCore, QtGui
from .constants import EditorConfig, IS_GROUP_ROLE, LABEL_ROLE, CONFIG_ROLE
from .editors import EditorFactory
class AdaptiveItemDelegate(QtWidgets.QStyledItemDelegate):
"""Delegate for adaptive item rendering in a tree view."""
def __init__(self, parent=None):
super().__init__(parent)
self._editors = {}
def paint(self, painter, option, index):
painter.save()
is_group = index.data(IS_GROUP_ROLE)
widget_label = index.data(LABEL_ROLE)
rect = option.rect
opt = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(opt, index)
if option.state & QtWidgets.QStyle.State_Selected:
painter.fillRect(rect, option.palette.highlight())
painter.setPen(option.palette.color(QtGui.QPalette.HighlightedText))
else:
painter.setPen(option.palette.color(QtGui.QPalette.Text))
if is_group:
font = painter.font()
font.setBold(True)
painter.setFont(font)
text_rect = QtCore.QRect(
rect.left(), rect.top(), rect.width() - 10, rect.height() - 4
)
painter.drawText(
text_rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, widget_label
)
line_y = rect.bottom() - 2
painter.drawLine(rect.left(), line_y, rect.right(), line_y)
else:
text_rect = QtCore.QRect(rect.left(), rect.top() + 2, rect.width() - 10, 16)
font = painter.font()
font.setBold(True)
painter.setFont(font)
painter.drawText(text_rect, QtCore.Qt.AlignLeft, widget_label)
painter.restore()
def createEditor(self, parent, option, index):
if index.data(IS_GROUP_ROLE):
return None
config = self._get_editor_config(index)
editor = EditorFactory.create_editor(config, parent)
self._editors[QtCore.QPersistentModelIndex(index)] = editor
return editor
def setEditorData(self, editor, index):
value = index.data(QtCore.Qt.DisplayRole)
editor.blockSignals(True)
try:
editor.set_value(value)
finally:
editor.blockSignals(False)
def setModelData(self, editor, model, index):
value = editor.get_value()
model.setData(index, value, QtCore.Qt.EditRole)
def _get_editor_config(self, index):
"""Create a config object from index data"""
config_data = index.data(CONFIG_ROLE) or {}
return EditorConfig(
widget_type=config_data.get("widget_type", ""),
options=config_data.get("options"),
label=config_data.get("label", ""),
)
def sizeHint(self, option, index):
is_group = index.data(IS_GROUP_ROLE)
if is_group:
return QtCore.QSize(option.rect.width(), 30)
else:
config = self._get_editor_config(index)
font_metrics = QtGui.QFontMetrics(option.font)
text_height = font_metrics.height() + 4
return QtCore.QSize(
option.rect.width(),
text_height + EditorFactory.required_size(config.widget_type).height(),
)
def updateEditorGeometry(self, editor, option, index):
font_metrics = QtGui.QFontMetrics(option.font)
text_height = font_metrics.height() + 4
editor_rect = QtCore.QRect(
option.rect.left(),
option.rect.top() + text_height,
option.rect.width(),
option.rect.height() - text_height,
)
editor.setGeometry(editor_rect)
def _update_model_data(self, index, value):
if not index.isValid():
return
model = index.model()
if model:
model.setData(index, value, QtCore.Qt.EditRole)
from PySide6 import QtWidgets, QtCore
import traceback
from .constants import WidgetType, EditorConfig
class IEditor:
"""Base interface for all editor widgets."""
valueChanged = QtCore.Signal("QVariant")
def __init__(self, config):
self.config = config
def set_value(self, value):
raise NotImplementedError()
def get_value(self):
raise NotImplementedError()
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class TextEditor(IEditor, QtWidgets.QLineEdit):
"""Simple text input editor."""
def __init__(self, parent=None, config=None):
QtWidgets.QLineEdit.__init__(self, parent)
try:
IEditor.__init__(self, config)
except Exception as e:
traceback.print_exc()
self.textChanged.connect(self._on_value_changed)
def set_value(self, value):
if value is not None:
self.setText(str(value))
def get_value(self):
return self.text()
def _on_value_changed(self):
try:
self.valueChanged.emit(self.get_value())
except Exception as e:
traceback.print_exc()
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class TextAreaEditor(IEditor, QtWidgets.QTextEdit):
"""Multi-line text editor for longer content."""
def __init__(self, parent=None, config=None):
QtWidgets.QTextEdit.__init__(self, parent)
IEditor.__init__(self, config)
self.textChanged.connect(self._on_value_changed)
def set_value(self, value):
if value is not None:
self.setPlainText(str(value))
def get_value(self):
return self.toPlainText()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 60)
class DropdownEditor(IEditor, QtWidgets.QComboBox):
"""Dropdown selection editor for choosing from predefined options."""
def __init__(self, parent=None, config=None):
QtWidgets.QComboBox.__init__(self, parent)
IEditor.__init__(self, config)
options = config.options if config else None
if options:
self.addItems([str(option) for option in options])
self.currentIndexChanged.connect(self._on_value_changed)
def set_value(self, value):
if isinstance(value, int):
self.setCurrentIndex(value)
elif isinstance(value, str):
items = [self.itemText(i) for i in range(self.count())]
if value in items:
self.setCurrentText(value)
def get_value(self):
return self.currentText()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class SpinBoxEditor(IEditor, QtWidgets.QSpinBox):
"""Integer value editor with increment/decrement buttons."""
def __init__(self, parent=None, config=None):
QtWidgets.QSpinBox.__init__(self, parent)
IEditor.__init__(self, config)
options = config.options if config else None
if options and isinstance(options, dict):
min_val = options.get("min", 0)
max_val = options.get("max", 100)
step = options.get("step", 1)
self.setRange(min_val, max_val)
self.setSingleStep(step)
self.valueChanged.connect(self._on_value_changed)
def set_value(self, value):
if value is not None:
self.setValue(int(value))
def get_value(self):
return self.value()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class CheckBoxEditor(IEditor, QtWidgets.QCheckBox):
"""Boolean value editor represented as a checkbox."""
def __init__(self, parent=None, config=None):
QtWidgets.QCheckBox.__init__(self, parent)
IEditor.__init__(self, config)
self.stateChanged.connect(self._on_value_changed)
def set_value(self, value):
self.setChecked(bool(value))
def get_value(self):
return self.isChecked()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class DatePickerEditor(IEditor, QtWidgets.QDateEdit):
"""Date selection editor with calendar popup."""
def __init__(self, parent=None, config=None):
QtWidgets.QDateEdit.__init__(self, parent)
IEditor.__init__(self, config)
self.setCalendarPopup(True)
self.setDate(QtCore.QDate.currentDate())
self.dateChanged.connect(self._on_value_changed)
def set_value(self, value):
if isinstance(value, QtCore.QDate):
self.setDate(value)
def get_value(self):
return self.date()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class FilePathEditor(IEditor, QtWidgets.QWidget):
""" File path input editor with browse button."""
def __init__(self, parent=None, config=None):
QtWidgets.QWidget.__init__(self, parent)
IEditor.__init__(self, config)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self._line_edit = QtWidgets.QLineEdit()
browse_button = QtWidgets.QPushButton("...")
layout.addWidget(self._line_edit)
layout.addWidget(browse_button)
self.setFocusProxy(self._line_edit)
browse_button.clicked.connect(self._browse_file)
self._line_edit.textChanged.connect(self._on_value_changed)
def _browse_file(self):
path, _ = QtWidgets.QFileDialog.getOpenFileName()
if path:
self._line_edit.setText(path)
def set_value(self, value):
if value is not None:
self._line_edit.setText(str(value))
def get_value(self):
return self._line_edit.text()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class NumericInputEditor(IEditor, QtWidgets.QDoubleSpinBox):
"""Floating-point value editor with increment/decrement buttons."""
def __init__(self, parent=None, config=None):
QtWidgets.QDoubleSpinBox.__init__(self, parent)
IEditor.__init__(self, config)
options = config.options if config else None
if options and isinstance(options, dict):
min_val = options.get("min", 0.0)
max_val = options.get("max", 100.0)
step = options.get("step", 0.1)
decimals = options.get("decimals", 2)
self.setRange(min_val, max_val)
self.setSingleStep(step)
self.setDecimals(decimals)
self.valueChanged.connect(self._on_value_changed)
def set_value(self, value):
if value is not None:
self.setValue(float(value))
def get_value(self):
return self.value()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class SliderEditor(IEditor, QtWidgets.QSlider):
"""Slider editor for selecting a numeric value within a range."""
def __init__(self, parent=None, config=None):
QtWidgets.QSlider.__init__(self, QtCore.Qt.Horizontal, parent)
IEditor.__init__(self, config)
options = config.options if config else None
if options and isinstance(options, dict):
min_val = options.get("min", 0)
max_val = options.get("max", 100)
step = options.get("step", 1)
self.setRange(min_val, max_val)
self.setSingleStep(step)
self.setPageStep(step * 10)
self.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.setTickInterval(
(max_val - min_val) // 10 if max_val != min_val else 10
)
self.valueChanged.connect(self._on_value_changed)
def set_value(self, value):
if value is not None:
self.setValue(int(value))
def get_value(self):
return self.value()
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 30)
class FrameRangeEditor(IEditor, QtWidgets.QWidget):
"""Frame range editor for specifying a start and end frame with increment."""
def __init__(self, parent=None, config=None):
QtWidgets.QWidget.__init__(self, parent)
IEditor.__init__(self, config)
# Focus is required in order for setModelData to be called
self.setFocusPolicy(QtCore.Qt.StrongFocus)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
start_label = QtWidgets.QLabel("Start:")
end_label = QtWidgets.QLabel("End:")
incr_label = QtWidgets.QLabel("Increment:")
self._start_spin = QtWidgets.QSpinBox()
self._end_spin = QtWidgets.QSpinBox()
self._incr_spin = QtWidgets.QSpinBox()
for spin in [self._start_spin, self._end_spin]:
spin.setRange(1, 10000)
self._incr_spin.setRange(1, 100)
self._start_spin.setValue(1)
self._end_spin.setValue(100)
self._incr_spin.setValue(1)
layout.addWidget(start_label)
layout.addWidget(self._start_spin)
layout.addWidget(end_label)
layout.addWidget(self._end_spin)
layout.addWidget(incr_label)
layout.addWidget(self._incr_spin)
self._start_spin.valueChanged.connect(self._on_value_changed)
self._end_spin.valueChanged.connect(self._on_value_changed)
self._incr_spin.valueChanged.connect(self._on_value_changed)
def set_value(self, value):
if isinstance(value, dict):
self._start_spin.setValue(value.get("start", 1))
self._end_spin.setValue(value.get("end", 100))
self._incr_spin.setValue(value.get("incr", 1))
def get_value(self):
return {
"start": self._start_spin.value(),
"end": self._end_spin.value(),
"incr": self._incr_spin.value(),
}
def _on_value_changed(self):
self.valueChanged.emit(self.get_value())
@staticmethod
def required_size(config=None):
return QtCore.QSize(-1, 20)
class EditorFactory:
"""Factory class to create editor widgets based on the widget type."""
_EDITOR_CLASS_MAP = {
WidgetType.TEXT_INPUT: TextEditor,
WidgetType.TEXT_AREA: TextAreaEditor,
WidgetType.DROPDOWN: DropdownEditor,
WidgetType.SPINBOX: SpinBoxEditor,
WidgetType.CHECKBOX: CheckBoxEditor,
WidgetType.DATE_PICKER: DatePickerEditor,
WidgetType.FILE_PATH: FilePathEditor,
WidgetType.NUMERIC_INPUT: NumericInputEditor,
WidgetType.COLOR_PICKER: None,
WidgetType.SLIDER: SliderEditor,
WidgetType.FRAME_RANGE: FrameRangeEditor,
}
@staticmethod
def create_editor(config:EditorConfig, parent=None):
editor_class = EditorFactory._get_editor_class(config.widget_type)
if not editor_class:
return TextEditor(parent, config)
return editor_class(parent, config)
@staticmethod
def _get_editor_class(widget_type):
"""Get the editor class for the widget type"""
return EditorFactory._EDITOR_CLASS_MAP.get(widget_type)
@staticmethod
def required_size(widget_type, value=None):
"""Get the required size for the widget type"""
if isinstance(widget_type, EditorConfig):
config = widget_type
widget_type = config.widget_type
editor_class = EditorFactory._get_editor_class(widget_type)
if editor_class and hasattr(editor_class, "required_size"):
return editor_class.required_size(value)
if widget_type == WidgetType.TEXT_AREA:
return QtCore.QSize(-1, 60)
else:
return QtCore.QSize(-1, 20)
from PySide6 import QtWidgets, QtGui, QtCore
from .delegate import AdaptiveItemDelegate
from .constants import IS_GROUP_ROLE, LABEL_ROLE, CONFIG_ROLE, WidgetType
class VFXIngestionForm(QtWidgets.QWidget):
"""Main form for VFX asset ingestion."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("VFX Asset Ingestion")
self.resize(800, 700)
self._apply_dark_pastel_palette()
layout = QtWidgets.QVBoxLayout(self)
self._tree_view = QtWidgets.QTreeView()
model = QtGui.QStandardItemModel()
self._tree_view.setModel(model)
self._tree_view.setHeaderHidden(True)
self._tree_view.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self._tree_view.setItemDelegate(AdaptiveItemDelegate(self))
self._tree_view.setIndentation(20)
self._tree_view.setItemsExpandable(True)
self._tree_view.setAnimated(True)
self._tree_view.expanded.connect(self._handle_item_expanded)
self._tree_view.collapsed.connect(self._handle_item_collapsed)
self._tree_view.setStyleSheet(
"""
QTreeView {
background-color: transparent;
border: none;
}
QTreeView::item:hover {
background-color: palette(window);
}
"""
)
layout.addWidget(self._tree_view)
button_layout = QtWidgets.QHBoxLayout()
self._submit_button = QtWidgets.QPushButton("Submit")
self._cancel_button = QtWidgets.QPushButton("Cancel")
self._submit_button.setMinimumHeight(36)
self._cancel_button.setMinimumHeight(36)
self._submit_button.setProperty("class", "primary-button")
button_layout.addStretch()
button_layout.addWidget(self._cancel_button)
button_layout.addWidget(self._submit_button)
layout.addLayout(button_layout)
self._setup_form()
self._open_all_persistent_editors()
self._submit_button.clicked.connect(self.submit_form)
self._cancel_button.clicked.connect(self.close)
def _apply_dark_pastel_palette(self):
"""Apply a dark pastel theme palette to the application"""
palette = QtGui.QPalette()
dark_color = QtGui.QColor(42, 42, 42)
darker_color = QtGui.QColor(34, 34, 34)
highlight_color = QtGui.QColor(74, 108, 212)
text_color = QtGui.QColor(224, 224, 224)
palette.setColor(QtGui.QPalette.Window, dark_color)
palette.setColor(QtGui.QPalette.WindowText, text_color)
palette.setColor(QtGui.QPalette.Base, darker_color)
palette.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(48, 48, 48))
palette.setColor(QtGui.QPalette.ToolTipBase, dark_color)
palette.setColor(QtGui.QPalette.ToolTipText, text_color)
palette.setColor(QtGui.QPalette.Text, text_color)
palette.setColor(QtGui.QPalette.Button, dark_color)
palette.setColor(QtGui.QPalette.ButtonText, text_color)
palette.setColor(QtGui.QPalette.BrightText, QtCore.Qt.white)
palette.setColor(QtGui.QPalette.Link, QtGui.QColor(92, 179, 255))
palette.setColor(QtGui.QPalette.LinkVisited, QtGui.QColor(180, 120, 220))
palette.setColor(QtGui.QPalette.Highlight, highlight_color)
palette.setColor(QtGui.QPalette.HighlightedText, QtCore.Qt.white)
palette.setColor(
QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(128, 128, 128)
)
palette.setColor(
QtGui.QPalette.Disabled,
QtGui.QPalette.ButtonText,
QtGui.QColor(128, 128, 128),
)
palette.setColor(
QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, QtGui.QColor(80, 80, 80)
)
palette.setColor(
QtGui.QPalette.Disabled,
QtGui.QPalette.HighlightedText,
QtGui.QColor(160, 160, 160),
)
self.setPalette(palette)
QtWidgets.QApplication.setPalette(palette)
def _handle_item_expanded(self, index):
QtCore.QTimer.singleShot(200, lambda: self._open_editors_for_index(index))
def _handle_item_collapsed(self, index):
self._close_editors_for_index(index)
def _open_editors_for_index(self, parent_index):
row_count = self._tree_view.model().rowCount(parent_index)
for row in range(row_count):
index = self._tree_view.model().index(row, 0, parent_index)
is_group = index.data(IS_GROUP_ROLE)
if not is_group:
self._tree_view.openPersistentEditor(index)
def _close_editors_for_index(self, parent_index):
row_count = self._tree_view.model().rowCount(parent_index)
for row in range(row_count):
index = self._tree_view.model().index(row, 0, parent_index)
is_group = index.data(IS_GROUP_ROLE)
if not is_group:
self._tree_view.closePersistentEditor(index)
if self._tree_view.model().hasChildren(index):
self._close_editors_for_index(index)
def _open_all_persistent_editors(self):
def _recursive_open(parent_index):
for row in range(self._tree_view.model().rowCount(parent_index)):
index = self._tree_view.model().index(row, 0, parent_index)
is_group = index.data(IS_GROUP_ROLE)
if not is_group:
self._tree_view.openPersistentEditor(index)
_recursive_open(index)
_recursive_open(QtCore.QModelIndex())
def _setup_form(self):
self._add_field("Asset Name", WidgetType.TEXT_INPUT, value="New Asset")
self._add_field(
"Asset Type",
WidgetType.DROPDOWN,
options=[
"Model",
"Texture",
"Animation",
"Rig",
"FX",
"Lighting",
"Compositing",
],
value=0,
)
self._add_field(
"Project",
WidgetType.DROPDOWN,
options=["Project A", "Project B", "Project C"],
value=0,
)
self._add_field(
"Department",
WidgetType.DROPDOWN,
options=[
"Modeling",
"Animation",
"Rigging",
"FX",
"Lighting",
"Compositing",
],
value=0,
)
self._add_field(
"Version",
WidgetType.SPINBOX,
options={"min": 1, "max": 100, "step": 1},
value=1,
)
metadata_group = self._add_group("Metadata")
self._add_field(
"Description",
WidgetType.TEXT_AREA,
value="Enter a detailed description of the asset here...",
parent=metadata_group,
)
self._add_field(
"Tags", WidgetType.TEXT_INPUT, value="asset, vfx", parent=metadata_group
)
self._add_field(
"Author", WidgetType.TEXT_INPUT, value="VFX Artist", parent=metadata_group
)
self._add_field(
"Creation Date",
WidgetType.DATE_PICKER,
value=QtCore.QDate.currentDate(),
parent=metadata_group,
)
self._add_field(
"Priority",
WidgetType.SLIDER,
options={"min": 1, "max": 10, "step": 1},
value=5,
parent=metadata_group,
)
tech_group = self._add_group("Technical Specifications")
self._add_field(
"File Format",
WidgetType.DROPDOWN,
options=["FBX", "USD", "Alembic", "OBJ", "EXR", "JPEG", "PNG"],
value=0,
parent=tech_group,
)
self._add_field(
"Resolution", WidgetType.TEXT_INPUT, value="1920x1080", parent=tech_group
)
self._add_field(
"Frame Range",
WidgetType.FRAME_RANGE,
value={"start": 1001, "end": 1100, "incr": 1},
parent=tech_group,
)
self._add_field(
"Color Space",
WidgetType.DROPDOWN,
options=["sRGB", "ACEScg", "Linear", "Rec.709"],
value=0,
parent=tech_group,
)
self._add_field(
"Detail Level",
WidgetType.SLIDER,
options={"min": 1, "max": 10, "step": 1},
value=7,
parent=tech_group,
)
pipeline_group = self._add_group("Pipeline Integration")
self._add_field(
"Target Location",
WidgetType.FILE_PATH,
value="C:/VFX/Assets/",
parent=pipeline_group,
)
self._add_field(
"Dependency Path",
WidgetType.TEXT_INPUT,
value="//server/assets/dependencies/",
parent=pipeline_group,
)
self._add_field(
"Review Status",
WidgetType.DROPDOWN,
options=["Not Reviewed", "In Progress", "Rejected", "Approved"],
value=0,
parent=pipeline_group,
)
self._add_field(
"Notes",
WidgetType.TEXT_AREA,
value="Add any pipeline-specific notes here...",
parent=pipeline_group,
)
self._add_field(
"Quality Score",
WidgetType.SLIDER,
options={"min": 0, "max": 100, "step": 5},
value=75,
parent=pipeline_group,
)
self._add_field(
"Auto-publish", WidgetType.CHECKBOX, value=True, parent=pipeline_group
)
def _add_group(self, label):
item = QtGui.QStandardItem(label)
item.setData(True, IS_GROUP_ROLE)
config = {}
item.setData(label, LABEL_ROLE)
item.setData(config, CONFIG_ROLE)
item.setFlags(QtCore.Qt.ItemIsEnabled)
self._tree_view.model().appendRow(item)
return item
def _add_field(self, label, widget_type, options=None, value=None, parent=None):
item = QtGui.QStandardItem()
config = {"widget_type": widget_type, "options": options}
item.setText(str(value) if value is not None else "")
item.setData(value, QtCore.Qt.DisplayRole)
item.setData(value, QtCore.Qt.EditRole)
item.setData(False, IS_GROUP_ROLE)
item.setData(config, CONFIG_ROLE)
item.setData(label, LABEL_ROLE)
if parent is None:
self._tree_view.model().appendRow(item)
else:
parent.appendRow(item)
return item
def submit_form(self):
form_data = {}
for row in range(self._tree_view.model().rowCount()):
index = self._tree_view.model().index(row, 0)
is_group = index.data(IS_GROUP_ROLE)
label = index.data(LABEL_ROLE) or ""
if is_group:
group_data = {}
for child_row in range(self._tree_view.model().rowCount(index)):
child_index = self._tree_view.model().index(child_row, 0, index)
child_label = child_index.data(LABEL_ROLE) or ""
child_value = child_index.data(QtCore.Qt.DisplayRole)
group_data[child_label] = child_value
form_data[label] = group_data
else:
value = index.data(QtCore.Qt.DisplayRole)
form_data[label] = value
html_content = "<h3>Form Submission Details:</h3>"
for key, value in form_data.items():
if isinstance(value, dict):
html_content += f"<h4>{key}</h4>"
for sub_key, sub_value in value.items():
html_content += (
f"{sub_key} = {sub_value}<br>"
)
else:
html_content += f"{key} = {value}<br>"
dialog = QtWidgets.QDialog(self)
dialog.setWindowTitle("Form Submitted")
dialog.resize(600, 400)
layout = QtWidgets.QVBoxLayout(dialog)
message_label = QtWidgets.QLabel(
"Asset ingestion form has been submitted successfully!"
)
font = message_label.font()
font.setBold(True)
message_label.setFont(font)
result_display = QtWidgets.QTextBrowser()
result_display.setHtml(html_content)
button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok)
button_box.accepted.connect(dialog.accept)
layout.addWidget(message_label)
layout.addWidget(result_display)
layout.addWidget(button_box)
dialog.exec()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
form = VFXIngestionForm()
form.show()
sys.exit(app.exec())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment