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-04-01 22:46 +0000

1# /////////////////////////////////////////////////////////////// 

2# INDICATOR_LABEL - Indicator Label Widget 

3# Project: ezqt_widgets 

4# /////////////////////////////////////////////////////////////// 

5 

6""" 

7Indicator label widget module. 

8 

9Provides a dynamic status indicator widget based on QFrame for displaying 

10a status label and a colored LED in PySide6 applications. 

11""" 

12 

13from __future__ import annotations 

14 

15# /////////////////////////////////////////////////////////////// 

16# IMPORTS 

17# /////////////////////////////////////////////////////////////// 

18# Standard library imports 

19from typing import Any 

20 

21# Third-party imports 

22from PySide6.QtCore import QSize, Qt, Signal 

23from PySide6.QtGui import QFont 

24from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QSizePolicy 

25 

26# Local imports 

27from ...types import WidgetParent 

28 

29# /////////////////////////////////////////////////////////////// 

30# CLASSES 

31# /////////////////////////////////////////////////////////////// 

32 

33 

34class IndicatorLabel(QFrame): 

35 """Dynamic status indicator widget with label and colored LED. 

36 

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. 

41 

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. 

48 

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. 

65 

66 Signals: 

67 statusChanged(str): Emitted when the status changes. 

68 

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 """ 

81 

82 statusChanged = Signal(str) 

83 

84 # /////////////////////////////////////////////////////////////// 

85 # INIT 

86 # /////////////////////////////////////////////////////////////// 

87 

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) 

98 

99 self.setProperty("type", "IndicatorLabel") 

100 

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 } 

112 

113 # State variables 

114 self._current_status: str = "" 

115 self._status_label: QLabel | None = None 

116 self._led_label: QLabel | None = None 

117 

118 # Setup widget 

119 self._setup_widget() 

120 

121 # Set initial status 

122 self.status = initial_status 

123 

124 # ------------------------------------------------ 

125 # PRIVATE METHODS 

126 # ------------------------------------------------ 

127 

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) 

135 

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) 

141 

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 ) 

152 

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) 

159 

160 self._layout.addWidget(self._status_label, 0, Qt.AlignmentFlag.AlignTop) 

161 self._layout.addWidget(self._led_label, 0, Qt.AlignmentFlag.AlignTop) 

162 

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 

167 

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") 

172 

173 self._status_label.setText(text) 

174 

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 """) 

181 

182 self.setProperty("state", state) 

183 self.style().unpolish(self) 

184 self.style().polish(self) 

185 

186 # /////////////////////////////////////////////////////////////// 

187 # PROPERTIES 

188 # /////////////////////////////////////////////////////////////// 

189 

190 @property 

191 def status(self) -> str: 

192 """Get the current status key. 

193 

194 Returns: 

195 The current status key. 

196 """ 

197 return self._current_status 

198 

199 @status.setter 

200 def status(self, value: str) -> None: 

201 """Set the current status key. 

202 

203 Args: 

204 value: The new status key. 

205 """ 

206 self.setStatus(value) 

207 

208 # /////////////////////////////////////////////////////////////// 

209 # PUBLIC METHODS 

210 # /////////////////////////////////////////////////////////////// 

211 

212 def setStatus(self, status: str) -> None: 

213 """Set the current status and update the display. 

214 

215 Args: 

216 status: The status key to set. 

217 

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}") 

223 

224 if status != self._current_status: 

225 self._current_status = status 

226 self._update_display() 

227 self.statusChanged.emit(self._current_status) 

228 

229 # /////////////////////////////////////////////////////////////// 

230 # STYLE METHODS 

231 # /////////////////////////////////////////////////////////////// 

232 

233 def refreshStyle(self) -> None: 

234 """Refresh the widget style. 

235 

236 Useful after dynamic stylesheet changes. 

237 """ 

238 self.style().unpolish(self) 

239 self.style().polish(self) 

240 self.update() 

241 

242 

243# /////////////////////////////////////////////////////////////// 

244# PUBLIC API 

245# /////////////////////////////////////////////////////////////// 

246 

247__all__ = ["IndicatorLabel"]