Coverage for src / ezqt_widgets / widgets / label / framed_label.py: 98.63%

69 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-31 10:03 +0000

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

2# FRAMED_LABEL - Framed Label Widget 

3# Project: ezqt_widgets 

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

5 

6""" 

7Framed label widget module. 

8 

9Provides a flexible label widget based on QFrame for advanced styling and 

10layout 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.QtWidgets import QFrame, QLabel, QSizePolicy, QVBoxLayout 

24 

25# Local imports 

26from ...types import WidgetParent 

27 

28# /////////////////////////////////////////////////////////////// 

29# CLASSES 

30# /////////////////////////////////////////////////////////////// 

31 

32 

33class FramedLabel(QFrame): 

34 """Flexible label widget based on QFrame for advanced styling. 

35 

36 This widget encapsulates a QLabel inside a QFrame, allowing you to benefit 

37 from QFrame's styling and layout capabilities while providing a simple 

38 interface for text display, alignment, and dynamic style updates. 

39 

40 Features: 

41 - Property-based access to the label text and alignment 

42 - Emits a textChanged(str) signal when the text changes 

43 - Allows custom stylesheet injection for advanced appearance 

44 - Suitable for use as a header, section label, or any styled context 

45 

46 Args: 

47 text: The initial text to display in the label (default: ""). 

48 parent: The parent widget (default: None). 

49 alignment: The alignment of the label text 

50 (default: Qt.AlignmentFlag.AlignCenter). 

51 style_sheet: Custom stylesheet to apply to the QFrame 

52 (default: None, uses transparent background). 

53 min_width: Minimum width constraint for the widget (default: None). 

54 min_height: Minimum height constraint for the widget (default: None). 

55 *args: Additional arguments passed to QFrame. 

56 **kwargs: Additional keyword arguments passed to QFrame. 

57 

58 Signals: 

59 textChanged(str): Emitted when the label text changes. 

60 

61 Example: 

62 >>> from ezqt_widgets import FramedLabel 

63 >>> label = FramedLabel(text="Section Title", min_height=30) 

64 >>> label.textChanged.connect(lambda t: print(f"New text: {t}")) 

65 >>> label.text = "Updated Title" 

66 >>> label.show() 

67 """ 

68 

69 textChanged = Signal(str) 

70 

71 # /////////////////////////////////////////////////////////////// 

72 # INIT 

73 # /////////////////////////////////////////////////////////////// 

74 

75 def __init__( 

76 self, 

77 text: str = "", 

78 parent: WidgetParent = None, 

79 alignment: Qt.AlignmentFlag = Qt.AlignmentFlag.AlignCenter, 

80 style_sheet: str | None = None, 

81 min_width: int | None = None, 

82 min_height: int | None = None, 

83 *args: Any, 

84 **kwargs: Any, 

85 ) -> None: 

86 """Initialize the framed label.""" 

87 super().__init__(parent, *args, **kwargs) 

88 self.setProperty("type", "FramedLabel") 

89 

90 # Initialize properties 

91 self._min_width: int | None = min_width 

92 self._min_height: int | None = min_height 

93 self._alignment: Qt.AlignmentFlag = alignment 

94 

95 # Setup styling 

96 self.setStyleSheet(style_sheet or "background-color: transparent;") 

97 self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) 

98 

99 # Setup layout 

100 layout = QVBoxLayout(self) 

101 layout.setSpacing(0) 

102 layout.setContentsMargins(0, 0, 0, 0) 

103 layout.setAlignment(alignment) 

104 

105 # Setup label 

106 self._label = QLabel(text, self) 

107 self._label.setAlignment(alignment) 

108 layout.addWidget(self._label) 

109 

110 # /////////////////////////////////////////////////////////////// 

111 # PROPERTIES 

112 # /////////////////////////////////////////////////////////////// 

113 

114 @property 

115 def text(self) -> str: 

116 """Get or set the label text. 

117 

118 Returns: 

119 The current label text. 

120 """ 

121 return self._label.text() 

122 

123 @text.setter 

124 def text(self, value: str) -> None: 

125 """Set the label text. 

126 

127 Args: 

128 value: The new label text. 

129 """ 

130 str_value = str(value) 

131 if str_value != self._label.text(): 

132 self._label.setText(str_value) 

133 self.textChanged.emit(str_value) 

134 

135 @property 

136 def alignment(self) -> Qt.AlignmentFlag: 

137 """Get or set the alignment of the label. 

138 

139 Returns: 

140 The current alignment. 

141 """ 

142 return self._alignment 

143 

144 @alignment.setter 

145 def alignment(self, value: Qt.AlignmentFlag) -> None: 

146 """Set the alignment of the label. 

147 

148 Args: 

149 value: The new alignment. 

150 """ 

151 self._alignment = value 

152 self._label.setAlignment(value) 

153 layout = self.layout() 

154 if layout is not None: 154 ↛ exitline 154 didn't return from function 'alignment' because the condition on line 154 was always true

155 layout.setAlignment(value) 

156 

157 @property 

158 def min_width(self) -> int | None: 

159 """Get or set the minimum width. 

160 

161 Returns: 

162 The minimum width, or None if not set. 

163 """ 

164 return self._min_width 

165 

166 @min_width.setter 

167 def min_width(self, value: int | None) -> None: 

168 """Set the minimum width. 

169 

170 Args: 

171 value: The minimum width, or None to auto-calculate. 

172 """ 

173 self._min_width = value 

174 self.updateGeometry() 

175 

176 @property 

177 def min_height(self) -> int | None: 

178 """Get or set the minimum height. 

179 

180 Returns: 

181 The minimum height, or None if not set. 

182 """ 

183 return self._min_height 

184 

185 @min_height.setter 

186 def min_height(self, value: int | None) -> None: 

187 """Set the minimum height. 

188 

189 Args: 

190 value: The minimum height, or None to auto-calculate. 

191 """ 

192 self._min_height = value 

193 self.updateGeometry() 

194 

195 # /////////////////////////////////////////////////////////////// 

196 # OVERRIDE METHODS 

197 # /////////////////////////////////////////////////////////////// 

198 

199 def minimumSizeHint(self) -> QSize: 

200 """Get the minimum size hint for the widget. 

201 

202 Returns: 

203 The minimum size hint. 

204 """ 

205 font_metrics = self.fontMetrics() 

206 text_width = font_metrics.horizontalAdvance(self.text) 

207 text_height = font_metrics.height() 

208 

209 content_width = text_width + 16 # 8px padding on each side 

210 content_height = text_height + 8 # 4px padding top/bottom 

211 

212 min_width = self._min_width if self._min_width is not None else content_width 

213 min_height = ( 

214 self._min_height if self._min_height is not None else content_height 

215 ) 

216 

217 return QSize(max(min_width, content_width), max(min_height, content_height)) 

218 

219 # /////////////////////////////////////////////////////////////// 

220 # STYLE METHODS 

221 # /////////////////////////////////////////////////////////////// 

222 

223 def refreshStyle(self) -> None: 

224 """Refresh the widget's style. 

225 

226 Useful after dynamic stylesheet changes. 

227 """ 

228 self.style().unpolish(self) 

229 self.style().polish(self) 

230 self.update() 

231 

232 

233# /////////////////////////////////////////////////////////////// 

234# PUBLIC API 

235# /////////////////////////////////////////////////////////////// 

236 

237__all__ = ["FramedLabel"]