1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
# SPDX-License-Identifier: LGPL-2.1-only
import collections
from contextlib import suppress
import enum
import setools
import typing
from PyQt6 import QtCore, QtWidgets
from .criteria import CriteriaWidget
E = typing.TypeVar("E", bound=enum.Enum)
__all__ = ('RadioEnumWidget',)
class RadioEnumWidget(CriteriaWidget, typing.Generic[E]):
"""
Criteria selection widget presenting possible options as vertical list of
radio buttons.
"""
selectionChanged = QtCore.pyqtSignal(enum.Enum)
def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, enum_class: type[E],
colspan: int = 1, parent: QtWidgets.QWidget | None = None) -> None:
super().__init__(title, query, attrname, parent=parent)
self.enum_class: typing.Final[type[E]] = enum_class
self.top_layout = QtWidgets.QGridLayout(self)
count: int
enu: E
self.criteria = collections.OrderedDict[E, QtWidgets.QRadioButton]()
for count, enu in enumerate(enum_class):
w = QtWidgets.QRadioButton(enu.value, parent=self)
w.toggled.connect(self._update_query)
w.setChecked(False if count else True) # set first option checked.
self.top_layout.addWidget(w, count, 0, 1, colspan)
self.criteria[enu] = w
self._update_query()
@property
def has_errors(self) -> bool:
"""Get error state of this widget."""
# Radio buttons are exclusive, so this should always be false
# if the widget is configured correctly.
checks: int = sum(1 for w in self.criteria.values() if w.isChecked())
return (checks != 1)
def selection(self) -> E:
"""Return the current selection."""
for enu, w in self.criteria.items():
if w.isChecked():
return enu
raise RuntimeError(f"No options selected in {self.attrname}. This is an SETools bug.")
def set_selection(self, val: E) -> None:
"""Set the selection of the enum."""
for enu, w in self.criteria.items():
w.setChecked(enu == val)
def _update_query(self, val: bool = True) -> None:
"""Update the query based on the radio button state."""
if not val:
return # only apply updates once per radio button switch
for enu, w in self.criteria.items():
if w.isChecked():
setattr(self.query, self.attrname, enu)
self.selectionChanged.emit(enu)
self.log.debug(f"Selection changed to {enu}")
break
#
# Workspace methods
#
def save(self, settings: dict) -> None:
settings[self.attrname] = self.selection().name
def load(self, settings: dict) -> None:
with suppress(AttributeError, KeyError):
self.set_selection(self.enum_class[settings[self.attrname]])
if __name__ == '__main__':
import sys
import logging
import pprint
import warnings
class local_enum_class(enum.Enum):
"""Enum for local testing"""
Val1 = "Value 1"
Val2 = "Value 2"
Val3 = "Value 3"
Val4 = "Value 4"
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
warnings.simplefilter("default")
q = setools.TERuleQuery(setools.SELinuxPolicy())
app = QtWidgets.QApplication(sys.argv)
mw = QtWidgets.QMainWindow()
widget = RadioEnumWidget("Test radio enum", q, "radioattrname", local_enum_class, parent=mw)
widget.setToolTip("test tooltip")
widget.setWhatsThis("test what's this")
mw.setCentralWidget(widget)
mw.resize(widget.size())
whatsthis = QtWidgets.QWhatsThis.createAction(mw)
mw.menuBar().addAction(whatsthis) # type: ignore[union-attr]
mw.show()
rc = app.exec()
local_settings: typing.Dict[str, str] = {}
widget.save(local_settings)
pprint.pprint(local_settings)
sys.exit(rc)
|