# Source code for pyqtgraph.parametertree.parameterTypes.checklist

    from ... import functions as fn
    from ...Qt import QtWidgets
    from ...SignalProxy import SignalProxy
    from ..ParameterItem import ParameterItem
    from . import BoolParameterItem, SimpleParameter
    from .basetypes import Emitter, GroupParameter, GroupParameterItem, WidgetParameterItem
    from .list import ListParameter

    class ChecklistParameterItem(GroupParameterItem):
        """
        Wraps a :class:`GroupParameterItem` to manage ``bool`` parameter children. Also provides convenience buttons to
        select or clear all values at once. Note these conveniences are disabled when ``exclusive`` is *True*.
        """
        def __init__(self, param, depth):
            self.btnGrp = QtWidgets.QButtonGroup()
            self.btnGrp.setExclusive(False)
            self._constructMetaBtns()

            super().__init__(param, depth)

        def _constructMetaBtns(self):
            self.metaBtnWidget = QtWidgets.QWidget()
            self.metaBtnLayout = lay = QtWidgets.QHBoxLayout(self.metaBtnWidget)
            lay.setContentsMargins(0, 0, 0, 0)
            lay.setSpacing(2)
            self.metaBtns = {}
            lay.addStretch(0)
            for title in 'Clear', 'Select':
                self.metaBtns[title] = btn = QtWidgets.QPushButton(f'{title} All')
                self.metaBtnLayout.addWidget(btn)
                btn.clicked.connect(getattr(self, f'{title.lower()}AllClicked'))

            self.metaBtns['default'] = self.makeDefaultButton()
            self.metaBtnLayout.addWidget(self.metaBtns['default'])

        def treeWidgetChanged(self):
            ParameterItem.treeWidgetChanged(self)
            tw = self.treeWidget()
            if tw is None:
                return
            tw.setItemWidget(self, 1, self.metaBtnWidget)

        def selectAllClicked(self):
            # timer stop: see explanation on param.setToDefault()
            self.param.valChangingProxy.timer.stop()
            self.param.setValue(self.param.reverse[0])

        def clearAllClicked(self):
            # timer stop: see explanation on param.setToDefault()
            self.param.valChangingProxy.timer.stop()
            self.param.setValue([])

        def insertChild(self, pos, item):
            ret = super().insertChild(pos, item)
            self.btnGrp.addButton(item.widget)
            return ret

        def addChild(self, item):
            ret = super().addChild(item)
            self.btnGrp.addButton(item.widget)
            return ret

        def takeChild(self, i):
            child = super().takeChild(i)
            self.btnGrp.removeButton(child.widget)

        def optsChanged(self, param, opts):
            super().optsChanged(param, opts)
            if 'expanded' in opts:
                for btn in self.metaBtns.values():
                    btn.setVisible(opts['expanded'])
            exclusive = opts.get('exclusive', param.opts['exclusive'])
            enabled = opts.get('enabled', param.opts['enabled'])
            for name, btn in self.metaBtns.items():
                if name != 'default':
                    btn.setDisabled(exclusive or (not enabled))
            self.btnGrp.setExclusive(exclusive)
            # "Limits" will force update anyway, no need to duplicate if it's present
            if 'limits' not in opts and ('enabled' in opts or 'readonly' in opts):
                self.updateDefaultBtn()

        def expandedChangedEvent(self, expanded):
            for btn in self.metaBtns.values():
                btn.setVisible(expanded)

        def valueChanged(self, param, val):
            self.updateDefaultBtn()

        def updateDefaultBtn(self):
            self.metaBtns["default"].setEnabled(
                not self.param.valueIsDefault()
                and self.param.opts["enabled"]
                and self.param.writable()
            )
            return

        makeDefaultButton = WidgetParameterItem.makeDefaultButton
        defaultClicked = WidgetParameterItem.defaultClicked

    class RadioParameterItem(BoolParameterItem):
        """
        Allows radio buttons to function as booleans when `exclusive` is *True*
        """

        def __init__(self, param, depth):
            self.emitter = Emitter()
            super().__init__(param, depth)

        def makeWidget(self):
            w = QtWidgets.QRadioButton()
            w.value = w.isChecked
            # Since these are only used during exclusive operations, only fire a signal when "True"
            # to avoid a double-fire
            w.setValue = w.setChecked
            w.sigChanged = self.emitter.sigChanged
            w.toggled.connect(self.maybeSigChanged)
            self.hideWidget = False
            return w

        def maybeSigChanged(self, val):
            """
            Make sure to only activate on a "true" value, since an exclusive button group fires once to deactivate
            the old option and once to activate the new selection
            """
            if not val:
                return
            self.emitter.sigChanged.emit(self, val)

    # Proxy around radio/bool type so the correct item class gets instantiated
    class BoolOrRadioParameter(SimpleParameter):

        @property
        def itemClass(self):
            if self.opts.get('type') == 'bool':
                return BoolParameterItem
            else:
                return RadioParameterItem

    class ChecklistParameter(GroupParameter):
        """
        Can be set just like a :class:`ListParameter`, but allows for multiple values to be selected simultaneously.

        ============== ========================================================
        **Options**
        exclusive      When *False*, any number of options can be selected. The resulting ``value()`` is a list of
                       all checked values. When *True*, it behaves like a ``list`` type -- only one value can be selected.
                       If no values are selected and ``exclusive`` is set to *True*, the first available limit is selected.
                       The return value of an ``exclusive`` checklist is a single value rather than a list with one element.
        delay          Controls the wait time between editing the checkboxes/radio button children and firing a "value changed"
                       signal. This allows users to edit multiple boxes at once for a single value update.
        ============== ========================================================
        """
        itemClass = ChecklistParameterItem

        def __init__(self, **opts):
            # Child options are populated through values, not explicit "children"
            if 'children' in opts:
                raise ValueError(
                    "Cannot pass 'children' to ChecklistParameter. Pass a 'value' key only."
                )
            self.targetValue = None
            limits = opts.setdefault('limits', [])
            self.forward, self.reverse = ListParameter.mapping(limits)
            value = opts.setdefault('value', limits)
            opts.setdefault('exclusive', False)
            super().__init__(**opts)
            # Force 'exclusive' to trigger by making sure value is not the same
            self.sigLimitsChanged.connect(self.updateLimits)
            self.sigOptionsChanged.connect(self.optsChanged)
            if len(limits):
                # Since update signal wasn't hooked up until after parameter construction, need to fire manually
                self.updateLimits(self, limits)
                # Also, value calculation will be incorrect until children are added, so make sure to recompute
                self.setValue(value)

            self.valChangingProxy = SignalProxy(
                self.sigValueChanging,
                delay=opts.get('delay', 1.0),
                slot=self._finishChildChanges,
                threadSafe=False,
            )

        def childrenValue(self):
            vals = [self.forward[p.name()] for p in self.children() if p.value()]
            exclusive = self.opts['exclusive']
            if not vals and exclusive:
                return None
            elif exclusive:
                return vals[0]
            else:
                return vals

        def _onChildChanging(self, child, value):
            # When exclusive, ensure only this value is True
            if self.opts['exclusive'] and value:
                value = self.forward[child.name()]
            else:
                value = self.childrenValue()
            self.sigValueChanging.emit(self, value)

        def updateLimits(self, _param, limits):
            oldOpts = self.names
            val = self.opts.get('value', None)
            # Make sure adding and removing children don't cause tree state changes
            self.blockTreeChangeSignal()
            self.clearChildren()
            self.forward, self.reverse = ListParameter.mapping(limits)
            if self.opts.get('exclusive'):
                typ = 'radio'
            else:
                typ = 'bool'
            for chName in self.forward:
                # Recycle old values if they match the new limits
                newVal = bool(oldOpts.get(chName, False))
                child = BoolOrRadioParameter(type=typ, name=chName, value=newVal, default=None)
                self.addChild(child)
                # Prevent child from broadcasting tree state changes, since this is handled by self
                child.blockTreeChangeSignal()
                child.sigValueChanged.connect(self._onChildChanging)
            # Purge child changes before unblocking
            self.treeStateChanges.clear()
            self.unblockTreeChangeSignal()
            self.setValue(val)

        def _finishChildChanges(self, paramAndValue):
            param, value = paramAndValue
            # Interpret value, fire sigValueChanged
            return self.setValue(value)

        def optsChanged(self, param, opts):
            if 'exclusive' in opts:
                self.updateLimits(None, self.opts.get('limits', []))
            if 'delay' in opts:
                self.valChangingProxy.setDelay(opts['delay'])

        def setValue(self, value, blockSignal=None):
            self.targetValue = value
            if not isinstance(value, list):
                value = [value]
            names, values = self._intersectionWithLimits(value)
            valueToSet = values

            if self.opts['exclusive']:
                if len(self.forward):
                    # Exclusive means at least one entry must exist, grab from limits
                    # if they exist
                    names.append(self.reverse[1][0])
                if len(names) > 1:
                    names = names[:1]
                if not len(names):
                    valueToSet = None
                else:
                    valueToSet = self.forward[names[0]]

            for chParam in self:
                checked = chParam.name() in names
                # Will emit at the end, so no problem discarding existing changes
                chParam.setValue(checked, self._onChildChanging)
            super().setValue(valueToSet, blockSignal)

        def _intersectionWithLimits(self, values: list):
            """
            Returns the (names, values) from limits that intersect with ``values``.
            """
            allowedNames = []
            allowedValues = []
            # Could be replaced by "value in self.reverse[0]" and "reverse[0].index",
            # but this allows for using pg.eq to cover more diverse value options
            for val in values:
                for limitName, limitValue in zip(*self.reverse):
                    if fn.eq(limitValue, val):
                        allowedNames.append(limitName)
                        allowedValues.append(val)
                        break
            return allowedNames, allowedValues

        def setToDefault(self):
            # Since changing values are covered by a proxy, this method must be overridden
            # to flush changes. Otherwise, setting to default while waiting for changes
            # to finalize will override the request to take default values
            self.valChangingProxy.timer.stop()
            super().setToDefault()

        def saveState(self, filter=None):
            # Unlike the normal GroupParameter, child states shouldn't be separately
            # preserved
            state = super().saveState(filter)
            state.pop("children", None)
            return state

        def restoreState(
            self,
            state,
            recursive=True,
            addChildren=True,
            removeChildren=True,
            blockSignals=True
        ):
            # Child management shouldn't happen through state
            return super().restoreState(
                state,
                recursive,
                addChildren=False,
                removeChildren=False,
                blockSignals=blockSignals
            )