Coverage for src / ezqt_widgets / widgets / label / indicator_label.py: 97.44%
72 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-31 10:03 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-31 10:03 +0000
1# ///////////////////////////////////////////////////////////////
2# INDICATOR_LABEL - Indicator Label Widget
3# Project: ezqt_widgets
4# ///////////////////////////////////////////////////////////////
6"""
7Indicator label widget module.
9Provides a dynamic status indicator widget based on QFrame for displaying
10a status label and a colored LED in PySide6 applications.
11"""
13from __future__ import annotations
15# ///////////////////////////////////////////////////////////////
16# IMPORTS
17# ///////////////////////////////////////////////////////////////
18# Standard library imports
19from typing import Any
21# Third-party imports
22from PySide6.QtCore import QSize, Qt, Signal
23from PySide6.QtGui import QFont
24from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QSizePolicy
26# Local imports
27from ...types import WidgetParent
29# ///////////////////////////////////////////////////////////////
30# CLASSES
31# ///////////////////////////////////////////////////////////////
34class IndicatorLabel(QFrame):
35 """Dynamic status indicator widget with label and colored LED.
37 This widget encapsulates a QLabel for the status text and a QLabel for
38 the LED, both arranged horizontally. The possible states are defined in
39 a configurable dictionary (status_map), allowing for flexible text, color,
40 and state property assignment.
42 Features:
43 - Dynamic states defined via a status_map dictionary (text, state, color)
44 - Property-based access to the current status
45 - Emits a statusChanged(str) signal when the status changes
46 - Allows custom status sets and colors for various use cases
47 - Suitable for online/offline indicators, service status, etc.
49 Args:
50 parent: The parent widget (default: None).
51 status_map: Dictionary defining possible states. Each key is a state
52 name, and each value is a dict with keys:
53 - text (str): The label to display
54 - state (str): The value set as a Qt property for styling
55 - color (str): The LED color (any valid CSS color)
56 Example:
57 {
58 "neutral": {"text": "Waiting", "state": "none", "color": "#A0A0A0"},
59 "online": {"text": "Online", "state": "ok", "color": "#4CAF50"},
60 ...
61 }
62 initial_status: The initial status key to use (default: "neutral").
63 *args: Additional arguments passed to QFrame.
64 **kwargs: Additional keyword arguments passed to QFrame.
66 Signals:
67 statusChanged(str): Emitted when the status changes.
69 Example:
70 >>> from ezqt_widgets import IndicatorLabel
71 >>> status_map = {
72 ... "neutral": {"text": "Waiting", "state": "none", "color": "#A0A0A0"},
73 ... "online": {"text": "Online", "state": "ok", "color": "#4CAF50"},
74 ... "offline": {"text": "Offline", "state": "error", "color": "#F44336"},
75 ... }
76 >>> indicator = IndicatorLabel(status_map=status_map, initial_status="neutral")
77 >>> indicator.statusChanged.connect(lambda s: print(f"Status: {s}"))
78 >>> indicator.status = "online"
79 >>> indicator.show()
80 """
82 statusChanged = Signal(str)
84 # ///////////////////////////////////////////////////////////////
85 # INIT
86 # ///////////////////////////////////////////////////////////////
88 def __init__(
89 self,
90 parent: WidgetParent = None,
91 status_map: dict[str, dict[str, str]] | None = None,
92 initial_status: str = "neutral",
93 *args: Any,
94 **kwargs: Any,
95 ) -> None:
96 """Initialize the indicator label."""
97 super().__init__(parent, *args, **kwargs)
99 self.setProperty("type", "IndicatorLabel")
101 # Default status map
102 self._status_map: dict[str, dict[str, str]] = status_map or {
103 "neutral": {"text": "Waiting", "state": "none", "color": "#A0A0A0"},
104 "online": {"text": "Online", "state": "ok", "color": "#4CAF50"},
105 "partial": {
106 "text": "Services disrupted",
107 "state": "partial",
108 "color": "#FFC107",
109 },
110 "offline": {"text": "Offline", "state": "ko", "color": "#F44336"},
111 }
113 # State variables
114 self._current_status: str = ""
115 self._status_label: QLabel | None = None
116 self._led_label: QLabel | None = None
118 # Setup widget
119 self._setup_widget()
121 # Set initial status
122 self.status = initial_status
124 # ------------------------------------------------
125 # PRIVATE METHODS
126 # ------------------------------------------------
128 def _setup_widget(self) -> None:
129 """Setup the widget properties and layout."""
130 self.setFrameShape(QFrame.Shape.NoFrame)
131 self.setFrameShadow(QFrame.Shadow.Raised)
132 self.setContentsMargins(4, 2, 4, 2)
133 self.setFixedHeight(24)
134 self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
136 self._layout = QHBoxLayout(self)
137 self._layout.setObjectName("status_HLayout")
138 self._layout.setAlignment(Qt.AlignmentFlag.AlignTop)
139 self._layout.setContentsMargins(0, 0, 0, 0)
140 self._layout.setSpacing(8)
142 self._status_label = QLabel()
143 self._status_label.setObjectName("status_label")
144 self._status_label.setFont(QFont("Segoe UI", 10))
145 self._status_label.setLineWidth(0)
146 self._status_label.setSizePolicy(
147 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
148 )
149 self._status_label.setAlignment(
150 Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
151 )
153 self._led_label = QLabel()
154 self._led_label.setObjectName("status_led")
155 self._led_label.setFixedSize(QSize(13, 16))
156 self._led_label.setFont(QFont("Segoe UI", 10))
157 self._led_label.setLineWidth(0)
158 self._led_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
160 self._layout.addWidget(self._status_label, 0, Qt.AlignmentFlag.AlignTop)
161 self._layout.addWidget(self._led_label, 0, Qt.AlignmentFlag.AlignTop)
163 def _update_display(self) -> None:
164 """Update the display based on current status."""
165 if not self._status_label or not self._led_label: 165 ↛ 166line 165 didn't jump to line 166 because the condition on line 165 was never true
166 return
168 status_info = self._status_map.get(self._current_status, {})
169 text = status_info.get("text", "Unknown")
170 state = status_info.get("state", "none")
171 color = status_info.get("color", "#A0A0A0")
173 self._status_label.setText(text)
175 self._led_label.setStyleSheet(f"""
176 background-color: {color};
177 border: 2px solid rgb(66, 66, 66);
178 border-radius: 6px;
179 margin-top: 3px;
180 """)
182 self.setProperty("state", state)
183 self.style().unpolish(self)
184 self.style().polish(self)
186 # ///////////////////////////////////////////////////////////////
187 # PROPERTIES
188 # ///////////////////////////////////////////////////////////////
190 @property
191 def status(self) -> str:
192 """Get the current status key.
194 Returns:
195 The current status key.
196 """
197 return self._current_status
199 @status.setter
200 def status(self, value: str) -> None:
201 """Set the current status key.
203 Args:
204 value: The new status key.
205 """
206 self.setStatus(value)
208 # ///////////////////////////////////////////////////////////////
209 # PUBLIC METHODS
210 # ///////////////////////////////////////////////////////////////
212 def setStatus(self, status: str) -> None:
213 """Set the current status and update the display.
215 Args:
216 status: The status key to set.
218 Raises:
219 ValueError: If status is not in the status_map.
220 """
221 if status not in self._status_map:
222 raise ValueError(f"Unknown status: {status}")
224 if status != self._current_status:
225 self._current_status = status
226 self._update_display()
227 self.statusChanged.emit(self._current_status)
229 # ///////////////////////////////////////////////////////////////
230 # STYLE METHODS
231 # ///////////////////////////////////////////////////////////////
233 def refreshStyle(self) -> None:
234 """Refresh the widget style.
236 Useful after dynamic stylesheet changes.
237 """
238 self.style().unpolish(self)
239 self.style().polish(self)
240 self.update()
243# ///////////////////////////////////////////////////////////////
244# PUBLIC API
245# ///////////////////////////////////////////////////////////////
247__all__ = ["IndicatorLabel"]