Skip to content

Commit 7f77fff

Browse files
committed
UI ToggleSwitch for Enable/disable states
1 parent 8cd12e2 commit 7f77fff

File tree

1 file changed

+222
-1
lines changed
  • ui/opensnitch/plugins/list_subscriptions

1 file changed

+222
-1
lines changed

ui/opensnitch/plugins/list_subscriptions/_gui.py

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,223 @@ def initStyleOption(self, option, index):
119119
)
120120

121121

122+
class ToggleSwitch(QtWidgets.QCheckBox):
123+
def __init__(
124+
self,
125+
text: str = "",
126+
parent: QtWidgets.QWidget | None = None,
127+
):
128+
super().__init__(text, parent)
129+
self._base_text = text
130+
self._track_width = 38
131+
self._track_height = 22
132+
self._thumb_diameter = 16
133+
self._label_gap = 8
134+
self._outer_padding = 4
135+
self._paint_margin = 1.5
136+
self._focus_margin = 2.0
137+
self.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
138+
self.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
139+
self.setSizePolicy(
140+
QtWidgets.QSizePolicy.Policy.Preferred,
141+
QtWidgets.QSizePolicy.Policy.Fixed,
142+
)
143+
self.setContentsMargins(
144+
self._outer_padding,
145+
self._outer_padding,
146+
self._outer_padding,
147+
self._outer_padding,
148+
)
149+
self.toggled.connect(self._refresh_geometry)
150+
font = self.font()
151+
font.setBold(True)
152+
self.setFont(font)
153+
self._refresh_geometry(self.isChecked())
154+
155+
def sizeHint(self) -> QtCore.QSize:
156+
metrics = self.fontMetrics()
157+
label_text = self._display_text()
158+
text_width = metrics.horizontalAdvance(label_text) if label_text else 0
159+
width = self._track_width + int(self._focus_margin * 2) + text_width
160+
if text_width:
161+
width += self._label_gap
162+
margins = self.contentsMargins()
163+
width += margins.left() + margins.right()
164+
height = max(
165+
metrics.height(),
166+
self._track_height + int(self._focus_margin * 2),
167+
)
168+
height += margins.top() + margins.bottom()
169+
return QtCore.QSize(width, height)
170+
171+
def minimumSizeHint(self) -> QtCore.QSize:
172+
return self.sizeHint()
173+
174+
def hitButton(self, pos: QtCore.QPoint) -> bool:
175+
return self.rect().contains(pos)
176+
177+
def _refresh_geometry(self, _checked: bool):
178+
self.setMinimumHeight(self.sizeHint().height())
179+
self.updateGeometry()
180+
self.update()
181+
182+
def _display_text(self) -> str:
183+
base = (self._base_text or "").strip()
184+
if base.lower() == "enable list subscriptions plugin":
185+
state = QC.translate(
186+
"stats",
187+
"enabled" if self.isChecked() else "disabled",
188+
)
189+
return QC.translate(
190+
"stats",
191+
"List subscriptions plugin {0}",
192+
).format(state)
193+
if base.lower() in {"enabled", "disabled"}:
194+
return QC.translate(
195+
"stats",
196+
"Enabled" if self.isChecked() else "Disabled",
197+
)
198+
return base
199+
200+
def paintEvent(self, event: QtGui.QPaintEvent): # type: ignore[override]
201+
del event
202+
painter = QtGui.QPainter(self)
203+
painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
204+
205+
margins = self.contentsMargins()
206+
content_rect = self.rect().adjusted(
207+
margins.left(),
208+
margins.top(),
209+
-margins.right(),
210+
-margins.bottom(),
211+
)
212+
track_x = content_rect.left() + self._paint_margin + self._focus_margin
213+
track_y = (
214+
content_rect.top()
215+
+ (content_rect.height() - self._track_height) / 2.0
216+
+ self._paint_margin
217+
)
218+
track_rect = QtCore.QRectF(
219+
track_x,
220+
track_y,
221+
self._track_width - (self._paint_margin * 2.0),
222+
self._track_height - (self._paint_margin * 2.0),
223+
)
224+
thumb_margin = (track_rect.height() - self._thumb_diameter) / 2.0
225+
thumb_left_off = track_rect.left() + thumb_margin
226+
thumb_left_on = track_rect.right() - thumb_margin - self._thumb_diameter
227+
thumb_rect = QtCore.QRectF(
228+
thumb_left_on if self.isChecked() else thumb_left_off,
229+
track_rect.top() + thumb_margin,
230+
self._thumb_diameter,
231+
self._thumb_diameter,
232+
)
233+
234+
palette = self.palette()
235+
is_dark = palette.color(QtGui.QPalette.ColorRole.Window).lightness() < 128
236+
if self.isEnabled():
237+
off_role = (
238+
QtGui.QPalette.ColorRole.Midlight
239+
if is_dark
240+
else QtGui.QPalette.ColorRole.Mid
241+
)
242+
border_role = (
243+
QtGui.QPalette.ColorRole.Light
244+
if is_dark
245+
else QtGui.QPalette.ColorRole.Dark
246+
)
247+
track_color = (
248+
palette.color(QtGui.QPalette.ColorRole.Highlight)
249+
if self.isChecked()
250+
else palette.color(off_role)
251+
)
252+
thumb_color = palette.color(QtGui.QPalette.ColorRole.Base)
253+
text_color = palette.color(QtGui.QPalette.ColorRole.WindowText)
254+
border_color = palette.color(border_role)
255+
else:
256+
track_color = palette.color(QtGui.QPalette.ColorRole.Dark)
257+
thumb_color = palette.color(QtGui.QPalette.ColorRole.Mid)
258+
text_color = palette.color(QtGui.QPalette.ColorRole.Mid)
259+
border_color = palette.color(QtGui.QPalette.ColorRole.Mid)
260+
261+
border_pen = QtGui.QPen(border_color)
262+
border_pen.setWidth(1)
263+
painter.setPen(border_pen)
264+
painter.setBrush(track_color)
265+
radius = track_rect.height() / 2.0
266+
painter.drawRoundedRect(track_rect, radius, radius)
267+
thumb_border_pen = QtGui.QPen(border_color)
268+
thumb_border_pen.setWidth(1)
269+
painter.setPen(thumb_border_pen)
270+
painter.setBrush(thumb_color)
271+
painter.drawEllipse(thumb_rect)
272+
273+
label_text = self._display_text()
274+
if label_text:
275+
text_rect = QtCore.QRectF(
276+
track_rect.right() + self._label_gap,
277+
content_rect.top(),
278+
max(0.0, content_rect.right() - track_rect.right() - self._label_gap),
279+
content_rect.height(),
280+
)
281+
painter.setPen(text_color)
282+
painter.drawText(
283+
text_rect,
284+
int(
285+
QtCore.Qt.AlignmentFlag.AlignLeft
286+
| QtCore.Qt.AlignmentFlag.AlignVCenter
287+
),
288+
label_text,
289+
)
290+
291+
if self.hasFocus():
292+
focus_pen = QtGui.QPen(palette.color(QtGui.QPalette.ColorRole.Highlight))
293+
focus_pen.setWidth(1)
294+
painter.setPen(focus_pen)
295+
painter.setBrush(QtCore.Qt.BrushStyle.NoBrush)
296+
painter.drawRoundedRect(
297+
track_rect.adjusted(
298+
-self._focus_margin,
299+
-self._focus_margin,
300+
self._focus_margin,
301+
self._focus_margin,
302+
),
303+
radius + self._focus_margin,
304+
radius + self._focus_margin,
305+
)
306+
307+
308+
def _replace_checkbox_with_toggle(
309+
checkbox: QtWidgets.QCheckBox,
310+
) -> ToggleSwitch:
311+
toggle = ToggleSwitch(checkbox.text(), checkbox.parentWidget())
312+
toggle.setObjectName(checkbox.objectName())
313+
toggle.setChecked(checkbox.isChecked())
314+
toggle.setEnabled(checkbox.isEnabled())
315+
toggle.setToolTip(checkbox.toolTip())
316+
toggle.setStatusTip(checkbox.statusTip())
317+
toggle.setWhatsThis(checkbox.whatsThis())
318+
toggle.setAccessibleName(checkbox.accessibleName())
319+
toggle.setAccessibleDescription(checkbox.accessibleDescription())
320+
toggle.setSizePolicy(checkbox.sizePolicy())
321+
toggle.setMinimumSize(checkbox.minimumSize())
322+
toggle.setMaximumSize(checkbox.maximumSize())
323+
margins = checkbox.contentsMargins()
324+
toggle.setContentsMargins(
325+
max(toggle.contentsMargins().left(), margins.left()),
326+
max(toggle.contentsMargins().top(), margins.top()),
327+
max(toggle.contentsMargins().right(), margins.right()),
328+
max(toggle.contentsMargins().bottom(), margins.bottom()),
329+
)
330+
parent = checkbox.parentWidget()
331+
layout = parent.layout() if parent is not None else None
332+
if layout is not None:
333+
layout.replaceWidget(checkbox, toggle)
334+
checkbox.hide()
335+
checkbox.deleteLater()
336+
return toggle
337+
338+
122339
class SubscriptionDialog(QtWidgets.QDialog, SubscriptionDialogUI):
123340
_url_test_finished = QtCore.pyqtSignal(bool, str)
124341

@@ -245,6 +462,7 @@ def __init__(
245462

246463
def _build_ui(self):
247464
self.setupUi(self)
465+
self.enabled_check = _replace_checkbox_with_toggle(self.enabled_check)
248466
self._set_dialog_message("", error=False)
249467
self.rootLayout.setContentsMargins(0, 0, 0, 0)
250468
self.rootLayout.setSpacing(0)
@@ -847,7 +1065,7 @@ def _build_ui(self):
8471065
expanding = QtWidgets.QSizePolicy.Policy.Expanding
8481066
fixed = QtWidgets.QSizePolicy.Policy.Fixed
8491067

850-
self.enabled_value = QtWidgets.QCheckBox(QC.translate("stats", "Enabled"))
1068+
self.enabled_value = ToggleSwitch(QC.translate("stats", "Enabled"))
8511069
self.enabled_value.setSizePolicy(expanding, fixed)
8521070

8531071
self.group_value = QtWidgets.QComboBox()
@@ -1261,6 +1479,9 @@ def closeEvent(self, event: QtGui.QCloseEvent): # type: ignore
12611479

12621480
def _build_ui(self):
12631481
self.setupUi(self)
1482+
self.enable_plugin_check = _replace_checkbox_with_toggle(
1483+
self.enable_plugin_check
1484+
)
12641485
self.setWindowTitle(QC.translate("stats", "List subscriptions"))
12651486
self.resize(1180, 680)
12661487
self.rootLayout.setContentsMargins(0, 0, 0, 0)

0 commit comments

Comments
 (0)