@@ -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+
122339class 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