# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2015-2023 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.

from __future__ import annotations
from typing import Union
from typing import Dict
from typing import Callable
from typing import Optional

import logging

from silx.gui import qt
from silx.gui.widgets.LegendIconWidget import LegendIconWidget
from silx.gui import colors as silx_colors
from silx.gui import icons
from silx.gui import utils

from bliss.flint.model import flint_model
from bliss.flint.model import plot_model
from bliss.flint.model import plot_item_model
from bliss.flint.model import scan_model
from bliss.flint.model import style_model
from bliss.flint.widgets.eye_check_box import EyeCheckBox
from bliss.flint.helper import model_helper
from bliss.flint.widgets.style_dialog import StyleDialogEditor
from bliss.flint.widgets.style_dialog import FlintColormapDialog


_logger = logging.getLogger(__name__)


PlotItemRole = qt.Qt.UserRole + 100
VisibilityRole = qt.Qt.UserRole + 101
RadioRole = qt.Qt.UserRole + 102
CheckRole = qt.Qt.UserRole + 103
FlintModelRole = qt.Qt.UserRole + 104
ObjectRole = qt.Qt.UserRole + 105


_colormapPixmap: Dict[str, qt.QPixmap] = {}
_COLORMAP_PIXMAP_SIZE = 32


class VisibilityPropertyItemDelegate(qt.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(VisibilityPropertyItemDelegate, self).createEditor(
                parent, option, index
            )

        editor = EyeCheckBox(parent=parent)
        editor.toggled.connect(self.__commitData)
        state = index.data(VisibilityRole)
        editor.setChecked(state == qt.Qt.Checked)
        self.__updateEditorStyle(editor, state)
        return editor

    def __commitData(self):
        editor = self.sender()
        self.commitData.emit(editor)

    def __updateEditorStyle(self, editor: qt.QCheckBox, state: qt.Qt.CheckState):
        editor.setVisible(state is not None)

    def setEditorData(self, editor, index):
        state = index.data(VisibilityRole)
        self.__updateEditorStyle(editor, state)

    def setModelData(self, editor, model, index):
        state = qt.Qt.Checked if editor.isChecked() else qt.Qt.Unchecked
        model.setData(index, state, role=VisibilityRole)

    def updateEditorGeometry(self, editor, option, index):
        # Center the widget to the cell
        size = editor.sizeHint()
        half = size / 2
        halfPoint = qt.QPoint(half.width(), half.height() - 1)
        pos = option.rect.center() - halfPoint
        editor.move(pos)


class CheckBoxItemDelegate(qt.QStyledItemDelegate):
    """CheckBox delegate to edit CheckStateRole only.

    Without that Qt is not able to display properly a check box without
    the text on the side.

    This allows to center the check box and hide a bug which make the default
    check box hit box at the wrong location (cause of custom the cell margin).

    Use a custom CheckRole to avoid to display the default check box on
    background.
    """

    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(CheckBoxItemDelegate, self).createEditor(parent, option, index)

        # Create group to avoid interferences
        editor = qt.QWidget(parent=parent)
        editor.setContentsMargins(1, 1, 1, 1)
        layout = qt.QHBoxLayout(editor)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(1)

        check = qt.QCheckBox(parent=editor)
        check.setObjectName("check")
        check.toggled.connect(self.__commitData)
        check.setMinimumSize(check.minimumSizeHint())
        check.setMaximumSize(check.minimumSizeHint())
        layout.addWidget(check)

        self.setEditorData(editor, index)
        return editor

    def __commitData(self):
        editor = self.sender().parent()
        self.commitData.emit(editor)

    def setEditorData(self, editor, index):
        check = editor.findChildren(qt.QCheckBox, "check")[0]
        state = index.data(role=CheckRole)
        with utils.blockSignals(check):
            check.setVisible(state is not None)
            check.setChecked(state == qt.Qt.Checked)

    def setModelData(self, editor, model, index):
        check = editor.findChildren(qt.QCheckBox, "check")[0]
        state = qt.Qt.Checked if check.isChecked() else qt.Qt.Unchecked
        model.setData(index, state, role=CheckRole)

    def updateEditorGeometry(self, editor, option, index):
        # Center the widget to the cell
        size = editor.sizeHint()
        half = size / 2
        halfPoint = qt.QPoint(half.width(), half.height() - 1)
        pos = option.rect.center() - halfPoint
        editor.move(pos)


class StyleItemDelegate(qt.QStyledItemDelegate):
    """Style delegate to edit item style."""

    def __init__(self, parent=None, editable=True):
        qt.QStyledItemDelegate.__init__(self, parent=parent)
        self.__editable = editable

    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(StyleItemDelegate, self).createEditor(parent, option, index)

        editor = StylePropertyWidget(parent)
        editor.setEditable(self.__editable)
        editor.setMinimumSize(editor.sizeHint())
        self.__updateEditor(editor, index)
        return editor

    def __updateEditor(self, editor: qt.QWidget, index: qt.QModelIndex):
        plotItem = index.data(PlotItemRole)
        flintModel = index.data(FlintModelRole)
        editor.setPlotItem(plotItem)
        editor.setFlintModel(flintModel)

    def setEditorData(self, editor, index):
        self.__updateEditor(editor, index)

    def setModelData(self, editor, model, index):
        pass

    def updateEditorGeometry(self, editor, option, index):
        # Center the widget to the cell
        size = editor.sizeHint()
        half = size / 2
        halfPoint = qt.QPoint(half.width(), half.height() - 1)
        pos = option.rect.center() - halfPoint
        editor.move(pos)


class RemovePropertyItemDelegate(qt.QStyledItemDelegate):
    def __init__(self, parent):
        qt.QStyledItemDelegate.__init__(self, parent=parent)

    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(RemovePropertyItemDelegate, self).createEditor(
                parent, option, index
            )
        editor = RemovePlotItemButton(parent=parent)
        plotItem = self.getPlotItem(index)
        editor.setVisible(plotItem is not None)
        return editor

    def getPlotItem(self, index) -> Union[None, plot_model.Item]:
        plotItem = index.data(PlotItemRole)
        if not isinstance(plotItem, plot_model.Item):
            return None
        return plotItem

    def setEditorData(self, editor, index):
        plotItem = self.getPlotItem(index)
        editor.setVisible(plotItem is not None)
        editor.setPlotItem(plotItem)

    def setModelData(self, editor, model, index):
        pass


class ScanStyleDelegate(qt.QStyledItemDelegate):
    """Style delegate to display scan style."""

    EDITOR_ALWAYS_OPEN = True

    def __init__(self, parent=None, editable=False):
        qt.QStyledItemDelegate.__init__(self, parent=parent)
        self.__editable = editable

    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(ScanStyleDelegate, self).createEditor(parent, option, index)

        editor = StylePropertyWidget(parent)
        editor.setEditable(self.__editable)
        editor.setMinimumSize(editor.sizeHint())
        editor.setCheckItemValidity(True)
        self.__updateEditor(editor, index)
        return editor

    def __getFirstItem(self, plotModel: plot_model.Plot):
        if plotModel is None:
            return None
        for item in plotModel.items():
            if isinstance(item, plot_item_model.ScanItem):
                pass
            elif isinstance(item, plot_model.ComputableMixIn):
                pass
            else:
                return item
        return None

    def __updateEditor(self, editor: qt.QWidget, index: qt.QModelIndex):
        scanItem = index.data(ObjectRole)
        plotItem = scanItem.plotItem
        if plotItem is None:
            plotItem = self.__getFirstItem(scanItem.plotModel)
        editor.setScan(scanItem.scan)
        editor.setPlotItem(plotItem)

    def setEditorData(self, editor, index):
        self.__updateEditor(editor, index)

    def setModelData(self, editor, model, index):
        pass

    def updateEditorGeometry(self, editor, option, index):
        # Center the widget to the cell
        size = editor.sizeHint()
        half = size / 2
        halfPoint = qt.QPoint(half.width(), half.height() - 1)
        pos = option.rect.center() - halfPoint
        editor.move(pos)


class RemovePlotItemButton(qt.QToolButton):
    def __init__(self, parent: qt.QWidget = None):
        super(RemovePlotItemButton, self).__init__(parent=parent)
        self.__plotItem: Optional[plot_model.Item] = None
        self.clicked.connect(self.__requestRemoveItem)
        icon = icons.getQIcon("flint:icons/remove-item")
        self.setIcon(icon)
        self.setAutoRaise(True)

    def __requestRemoveItem(self):
        plotItem = self.__plotItem
        plotModel = plotItem.plot()
        if plotModel is not None:
            model_helper.removeItemAndKeepAxes(plotModel, plotItem)
            plotModel.tagUserEditTime()

    def setPlotItem(self, plotItem: plot_model.Item):
        self.__plotItem = plotItem


class RemoveScanButton(qt.QToolButton):
    def __init__(self, parent: qt.QWidget = None):
        super(RemoveScanButton, self).__init__(parent=parent)
        self.__scan: Optional[scan_model.Scan] = None
        self.__plotWidget: Optional[qt.QWidget] = None
        self.clicked.connect(self.__requestRemoveScan)
        icon = icons.getQIcon("flint:icons/remove-item")
        self.setIcon(icon)
        self.setAutoRaise(True)

    def __requestRemoveScan(self):
        widget = self.__plotWidget
        widget.removeScan(self.__scan)

    def setScan(self, scan: scan_model.Scan):
        self.__scan = scan

    def setPlotWidget(self, plotWidget: qt.QWidget):
        self.__plotWidget = plotWidget


class RemoveScanDelegate(qt.QStyledItemDelegate):
    EDITOR_ALWAYS_OPEN = True

    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(RemoveScanDelegate, self).createEditor(parent, option, index)
        editor = RemoveScanButton(parent=parent)
        self.setEditorData(editor, index)
        return editor

    def setEditorData(self, editor, index):
        scanItem = index.data(ObjectRole)
        editor.setScan(scanItem.scan)
        editor.setPlotWidget(scanItem.curveWidget)
        isRemovable = True
        if scanItem.scan is None or scanItem.curveWidget is None:
            isRemovable = False
        else:
            if scanItem.scan is scanItem.curveWidget.scan():
                isRemovable = False
                editor.setToolTip("The active scan can't be removed")
        editor.setEnabled(isRemovable)

    def setModelData(self, editor, model, index):
        pass


class ScanNumberDelegate(qt.QStyledItemDelegate):
    def initStyleOption(self, option: qt.QStyleOptionViewItem, index: qt.QModelIndex):
        scanItem = index.data(ObjectRole)
        scanInfo = scanItem.scan.scanInfo()
        scanNb = scanInfo.get("scan_nb", None)
        if scanNb is None:
            scanNb = ""
        else:
            scanNb = f"#{scanNb}"
        option.text = scanNb

    def sizeHint(self, option: qt.QStyleOptionViewItem, index: qt.QModelIndex):
        size = option.fontMetrics.size(qt.Qt.TextSingleLine, "####")
        return size


class ScanTitleDelegate(qt.QStyledItemDelegate):
    def initStyleOption(self, option: qt.QStyleOptionViewItem, index: qt.QModelIndex):
        scanItem = index.data(ObjectRole)
        scanInfo = scanItem.scan.scanInfo()
        scanTitle = scanInfo.get("title", None)
        if scanTitle is None:
            scanTitle = scanInfo.get("type", None)
        if scanTitle is None:
            scanTitle = ""
        option.text = scanTitle


class ScanStartTimeDelegate(qt.QStyledItemDelegate):
    def __toStartTimeText(self, scan: scan_model.Scan) -> str:
        value = scan.startTime()
        if value is None:
            return ""
        return value.strftime("%H:%M")

    def initStyleOption(self, option: qt.QStyleOptionViewItem, index: qt.QModelIndex):
        scanItem = index.data(ObjectRole)
        text = self.__toStartTimeText(scanItem.scan)
        option.text = text

    def sizeHint(self, option: qt.QStyleOptionViewItem, index: qt.QModelIndex):
        size = option.fontMetrics.size(qt.Qt.TextSingleLine, "##:##")
        return size


class ScanVisibilityDelegate(qt.QStyledItemDelegate):

    EDITOR_ALWAYS_OPEN = True

    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(ScanVisibilityDelegate, self).createEditor(
                parent, option, index
            )

        editor = EyeCheckBox(parent=parent)
        editor.toggled.connect(self.__commitData)
        scanItem = index.data(ObjectRole)
        state = qt.Qt.Checked if scanItem.scanItem.isVisible() else qt.Qt.Unchecked
        editor.setChecked(state == qt.Qt.Checked)
        self.__updateEditorStyle(editor, state)
        return editor

    def __commitData(self):
        editor = self.sender()
        self.commitData.emit(editor)

    def __updateEditorStyle(self, editor: qt.QCheckBox, state: qt.Qt.CheckState):
        editor.setVisible(True)

    def setEditorData(self, editor, index):
        scanItem = index.data(ObjectRole)
        editor.setChecked(scanItem.scanItem.isVisible())

    def setModelData(self, editor, model, index):
        scanItem = index.data(ObjectRole)
        scanItem.scanItem.setVisible(editor.isChecked())

    def updateEditorGeometry(self, editor, option, index):
        # Center the widget to the cell
        size = editor.sizeHint()
        half = size / 2
        halfPoint = qt.QPoint(half.width(), half.height() - 1)
        pos = option.rect.center() - halfPoint
        editor.move(pos)


class StylePropertyWidget(qt.QWidget):
    def __init__(self, parent):
        super(StylePropertyWidget, self).__init__(parent=parent)
        layout = qt.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setLayout(layout)

        self.__legend = LegendIconWidget(self)
        self.__legend.setFixedWidth(30)
        layout.addWidget(self.__legend)
        layout.addSpacing(2)

        self.__displayContrast = False

        self.__buttonStyle: Optional[qt.QToolButton] = None
        self.__buttonContrast: Optional[qt.QToolButton] = None

        self.__flintModel: Optional[flint_model.FlintState] = None
        self.__plotItem: Optional[plot_model.Item] = None
        self.__plotModel: Optional[plot_model.Plot] = None
        """Holder the item plot to avoid earlier release of the plot"""
        self.__scan: Union[None, scan_model.Scan] = None
        self.__checkItem = False

    def setCheckItemValidity(self, check: bool):
        self.__checkItem = check
        self.__update()

    def setEditable(self, isEditable):
        """Set the widget editable.

        A button is enabled to be able to edit the style, and to propagate it to
        the item.
        """
        style = self.style()
        w = style.pixelMetric(qt.QStyle.PM_ExclusiveIndicatorWidth)
        h = style.pixelMetric(qt.QStyle.PM_ExclusiveIndicatorHeight)
        indicatorSize = qt.QSize(w, h) + qt.QSize(4, 4)

        layout = self.layout()

        if self.__buttonStyle is not None:
            self.__buttonStyle.setVisible(isEditable)
        elif isEditable:
            icon = icons.getQIcon("flint:icons/style")
            self.__buttonStyle = qt.QToolButton(self)
            self.__buttonStyle.setToolTip("Edit the style of this item")
            self.__buttonStyle.setIcon(icon)
            self.__buttonStyle.setAutoRaise(True)
            self.__buttonStyle.clicked.connect(self.__editStyle)
            self.__buttonStyle.setFixedSize(indicatorSize)
            layout.addWidget(self.__buttonStyle)

        if self.__buttonContrast is not None:
            self.__buttonContrast.setVisible(isEditable)
        elif isEditable and self.__displayContrast:
            icon = icons.getQIcon("flint:icons/contrast")
            self.__buttonContrast = qt.QToolButton(self)
            self.__buttonContrast.setToolTip("Edit the contrast of this item")
            self.__buttonContrast.setIcon(icon)
            self.__buttonContrast.setAutoRaise(True)
            self.__buttonContrast.clicked.connect(self.__editConstrast)
            self.__buttonContrast.setFixedSize(indicatorSize)
            layout.addWidget(self.__buttonContrast)
        self.__updateEditButton()

    def __updateEditButton(self):
        if self.__buttonContrast is not None:
            visible = self.__plotItem is not None and isinstance(
                self.__plotItem,
                (plot_item_model.ImageItem, plot_item_model.ScatterItem),
            )
            self.__buttonContrast.setVisible(visible)

    def __editStyle(self):
        if self.__plotItem is None:
            return
        dialog = StyleDialogEditor(self)
        dialog.setPlotItem(self.__plotItem)
        dialog.setFlintModel(self.__flintModel)
        result = dialog.exec_()
        if result:
            style = dialog.selectedStyle()
            self.__plotItem.setCustomStyle(style)

    def __editConstrast(self):
        if self.__plotItem is None:
            return

        scan = self.__scan
        item = self.__plotItem
        item.customStyle()

        style = item.getStyle(scan)
        colormap = model_helper.getColormapFromItem(item, style)

        saveCustomStyle = item.customStyle()
        saveColormap = item.colormap().copy()

        def updateCustomStyle():
            style = item.getStyle(scan)
            style = style_model.Style(colormapLut=colormap.getName(), style=style)
            item.setCustomStyle(style)

        colormap.sigChanged.connect(updateCustomStyle)

        dialog = FlintColormapDialog(self)
        dialog.setModal(True)
        dialog.setPlotItem(item, scan)
        dialog.setColormap(colormap)
        result = dialog.exec_()
        if result:
            style = item.customStyle()
            style = style_model.Style(colormapLut=colormap.getName(), style=style)
            self.__plotItem.setCustomStyle(style)
        else:
            item.setCustomStyle(saveCustomStyle)
            item.colormap().setFromColormap(saveColormap)

    def setPlotItem(self, plotItem: plot_model.Item):
        if self.__plotItem is not None:
            self.__plotItem.valueChanged.disconnect(self.__plotItemChanged)
        self.__plotItem = plotItem
        if self.__plotItem is not None:
            self.__plotModel = self.__plotItem.plot()
            self.__plotItem.valueChanged.connect(self.__plotItemChanged)
            self.__plotItemStyleChanged()
        else:
            self.__plotModel = None
        self.__updateEditButton()

    def setFlintModel(self, flintModel: flint_model.FlintState = None):
        if self.__flintModel is not None:
            self.__flintModel.currentScanChanged.disconnect(self.__currentScanChanged)
            self.__setScan(None)
        self.__flintModel = flintModel
        if self.__flintModel is not None:
            self.__flintModel.currentScanChanged.connect(self.__currentScanChanged)
            self.__setScan(self.__flintModel.currentScan())

    def __currentScanChanged(self):
        self.__setScan(self.__flintModel.currentScan())

    def setScan(self, scan: Union[None, scan_model.Scan]):
        self.__scan = scan
        self.__update()

    def __setScan(self, scan: Union[None, scan_model.Scan]):
        self.__scan = scan
        self.__update()

    def __plotItemChanged(self, eventType):
        if eventType == plot_model.ChangeEventType.CUSTOM_STYLE:
            self.__plotItemStyleChanged()

    def __plotItemStyleChanged(self):
        self.__update()

    def getQColor(self, color):
        if color is None:
            return qt.QColor()
        return silx_colors.asQColor(color)

    def __updateScatter(self, style: plot_model.Style):
        pointBased = True
        if style.fillStyle is not style_model.FillStyle.NO_FILL:
            if not isinstance(style.fillStyle, str):
                pointBased = False
                self.__legend.setColormap(style.colormapLut)
            else:
                self.__legend.setColormap(None)
        else:
            self.__legend.setColormap(None)

        if style.lineStyle is not style_model.LineStyle.NO_LINE:
            self.__legend.setLineStyle("-")
            color = self.getQColor(style.lineColor)
            self.__legend.setLineColor(color)
            self.__legend.setLineWidth(1.5)
        else:
            self.__legend.setLineStyle(" ")

        if pointBased:
            if style.symbolStyle == style_model.SymbolStyle.NO_SYMBOL:
                symbolStyle = "o"
            else:
                symbolStyle = style_model.symbol_to_silx(style.symbolStyle)
            self.__legend.setSymbol(symbolStyle)
            self.__legend.setSymbolColormap(style.colormapLut)
            self.__legend.setSymbolColor(None)
        elif style.symbolStyle is not style_model.SymbolStyle.NO_SYMBOL:
            symbolStyle = style_model.symbol_to_silx(style.symbolStyle)
            self.__legend.setSymbol(symbolStyle)
            color = self.getQColor(style.symbolColor)
            self.__legend.setSymbolColor(color)
            self.__legend.setSymbolColormap(None)
        else:
            self.__legend.setSymbol(" ")

    def __isItemAvailable(self, item: plot_model.Item, scan: scan_model.Scan):
        if not self.__checkItem:
            return True
        if scan is None:
            return False
        return item.isAvailableInScan(scan)

    def __update(self):
        plotItem = self.__plotItem
        if plotItem is None:
            self.__legend.setLineColor("red")
            self.__legend.setLineStyle(":")
            self.__legend.setLineWidth(1.5)
        else:
            scan = self.__scan
            try:
                if not self.__isItemAvailable(plotItem, scan):
                    self.__legend.setSymbol("x")
                    self.__legend.setSymbolColor("red")
                    self.setToolTip("Not available in this scan")
                elif isinstance(plotItem, plot_item_model.ScatterItem):
                    self.setToolTip("")
                    style = plotItem.getStyle(scan)
                    self.__updateScatter(style)
                else:
                    self.setToolTip("")
                    style = plotItem.getStyle(scan)
                    color = self.getQColor(style.lineColor)
                    if style.symbolStyle is not style_model.SymbolStyle.NO_SYMBOL:
                        symbolStyle = style_model.symbol_to_silx(style.symbolStyle)
                        self.__legend.setSymbol(symbolStyle)
                        if style.symbolColor is None:
                            self.__legend.setSymbolColor(qt.QColor(0xE0, 0xE0, 0xE0))
                        else:
                            symbolColor = self.getQColor(style.symbolColor)
                            self.__legend.setSymbolColor(symbolColor)
                    self.__legend.setSymbolColormap(style.colormapLut)
                    if isinstance(style.lineStyle, str):
                        lineStyle = style.lineStyle
                    elif style.lineStyle == style_model.LineStyle.NO_LINE:
                        lineStyle = " "
                    elif style.lineStyle == style_model.LineStyle.SCATTER_SEQUENCE:
                        lineStyle = "-"
                    self.__legend.setLineColor(color)
                    self.__legend.setLineStyle(lineStyle)
                    self.__legend.setLineWidth(1.5)
            except Exception as e:
                self.setToolTip(f"Unknown error<br/>{str(e)}")
                self.__legend.setLineColor("grey")
                self.__legend.setLineStyle(":")
                self.__legend.setLineWidth(1.5)
        self.__legend.update()


class HookedStandardItem(qt.QStandardItem):
    def __init__(self, text: str):
        qt.QStandardItem.__init__(self, text)
        self.modelUpdated: Optional[Callable[[qt.QStandardItem], None]] = None

    def setData(self, value, role=qt.Qt.UserRole + 1):
        qt.QStandardItem.setData(self, value, role)
        if self.modelUpdated is None:
            return
        method = self.modelUpdated()
        if method is not None:
            method(self)


class RadioPropertyItemDelegate(qt.QStyledItemDelegate):
    def __init__(self, parent):
        qt.QStyledItemDelegate.__init__(self, parent=parent)

    def createEditor(self, parent, option, index):
        if not index.isValid():
            return super(RadioPropertyItemDelegate, self).createEditor(
                parent, option, index
            )

        # Create group to avoid interferences
        editor = qt.QWidget(parent=parent)
        editor.setContentsMargins(1, 1, 1, 1)
        layout = qt.QHBoxLayout(editor)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(1)

        radio = qt.QRadioButton(parent=editor)
        radio.setObjectName("radio")
        radio.setAutoExclusive(False)
        radio.clicked.connect(self.__editorsChanged)
        radio.setMinimumSize(radio.minimumSizeHint())
        radio.setMaximumSize(radio.minimumSizeHint())
        layout.addWidget(radio)

        editor.setSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Fixed)
        self.setEditorData(editor, index)
        return editor

    def __editorsChanged(self):
        editor = self.sender().parent()
        self.commitData.emit(editor)

    def setEditorData(self, editor: qt.QWidget, index):
        radio = editor.findChildren(qt.QRadioButton, "radio")[0]
        data = index.data(role=RadioRole)
        with utils.blockSignals(radio):
            if data is None:
                radio.setVisible(False)
            elif data == qt.Qt.Checked:
                radio.setVisible(True)
                radio.setChecked(True)
            elif data == qt.Qt.Unchecked:
                radio.setVisible(True)
                radio.setChecked(False)
            else:
                _logger.warning("Unsupported data %s", data)

    def setModelData(self, editor, model, index):
        radio = editor.findChildren(qt.QRadioButton, "radio")[0]
        data = qt.Qt.Checked if radio.isChecked() else qt.Qt.Unchecked
        model.setData(index, data, role=RadioRole)

    def updateEditorGeometry(self, editor, option, index):
        # Center the widget to the cell
        size = editor.sizeHint()
        half = size / 2
        halfPoint = qt.QPoint(half.width(), half.height() - 1)
        pos = option.rect.center() - halfPoint
        editor.move(pos)
