Coverage for src / ezqt_app / widgets / extended / setting_widgets.py: 84.54%

278 statements  

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

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

2# WIDGETS.EXTENDED.SETTING_WIDGETS - Settings widget components 

3# Project: ezqt_app 

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

5 

6"""Setting widget components: toggle, select, slider, text, checkbox.""" 

7 

8from __future__ import annotations 

9 

10# /////////////////////////////////////////////////////////////// 

11# IMPORTS 

12# /////////////////////////////////////////////////////////////// 

13# Third-party imports 

14from ezqt_widgets import ToggleSwitch 

15from PySide6.QtCore import QCoreApplication, Qt, Signal 

16from PySide6.QtWidgets import ( 

17 QComboBox, 

18 QHBoxLayout, 

19 QLabel, 

20 QLineEdit, 

21 QSlider, 

22 QVBoxLayout, 

23 QWidget, 

24) 

25 

26# Local imports 

27from ...services.settings import get_settings_service 

28 

29 

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

31# CLASSES 

32# /////////////////////////////////////////////////////////////// 

33class BaseSettingWidget(QWidget): 

34 """Base class for all setting widgets.""" 

35 

36 def __init__(self, label: str, description: str = ""): 

37 super().__init__() 

38 self._label_text = label 

39 self._description_text = description 

40 self._key = None 

41 self.setObjectName("base_setting_widget") 

42 

43 def set_key(self, key: str): 

44 """Set the setting key.""" 

45 self._key = key 

46 

47 def _tr(self, text: str) -> str: 

48 """Shortcut for translation with global context.""" 

49 return QCoreApplication.translate("EzQt_App", text) 

50 

51 def retranslate_ui(self): 

52 """To be implemented by subclasses.""" 

53 

54 

55class SettingToggle(BaseSettingWidget): 

56 """Widget for toggle settings (on/off).""" 

57 

58 valueChanged = Signal(str, bool) 

59 

60 def __init__(self, label: str, description: str = "", default: bool = False): 

61 super().__init__(label, description) 

62 self._value = default 

63 self.setObjectName("setting_toggle_container") 

64 self.setProperty("type", "setting_toggle") 

65 self._setup_ui() 

66 

67 def _setup_ui(self): 

68 """Configure the user interface.""" 

69 self._layout = QVBoxLayout(self) 

70 self._layout.setSpacing(4) 

71 self._layout.setContentsMargins(0, 0, 0, 0) 

72 

73 self._control_layout = QHBoxLayout() 

74 self._control_layout.setSpacing(8) 

75 self._control_layout.setContentsMargins(0, 0, 0, 0) 

76 

77 self._label_widget = QLabel(self._tr(self._label_text)) 

78 self._label_widget.setObjectName("setting_label") 

79 self._label_widget.setWordWrap(True) 

80 self._control_layout.addWidget(self._label_widget, 1) 

81 

82 animation_enabled = get_settings_service().gui.TIME_ANIMATION > 0 

83 self._control_widget = ToggleSwitch(animation=animation_enabled) 

84 self._control_widget.setObjectName("setting_toggle") 

85 self._control_widget.checked = self._value 

86 self._control_widget.toggled.connect(self._on_toggled) 

87 self._control_layout.addWidget(self._control_widget, 0) 

88 

89 self._layout.addLayout(self._control_layout) 

90 

91 self._description_label = None 

92 if self._description_text: 

93 self._description_label = QLabel(self._tr(self._description_text)) 

94 self._description_label.setObjectName("setting_description") 

95 self._description_label.setWordWrap(True) 

96 self._layout.addWidget(self._description_label) 

97 

98 def retranslate_ui(self): 

99 """Update strings after language change.""" 

100 self._label_widget.setText(self._tr(self._label_text)) 

101 if self._description_label: 

102 self._description_label.setText(self._tr(self._description_text)) 

103 

104 def _on_toggled(self, checked: bool): 

105 self._value = checked 

106 self.valueChanged.emit(self._key, checked) 

107 

108 @property 

109 def value(self) -> bool: 

110 return self._value 

111 

112 @value.setter 

113 def value(self, val: bool): 

114 self._value = val 

115 self._control_widget.checked = val 

116 

117 def get_value(self) -> bool: 

118 return self._value 

119 

120 def set_value(self, val: bool): 

121 self._value = val 

122 self._control_widget.checked = val 

123 

124 

125class SettingSelect(BaseSettingWidget): 

126 """Widget for selection settings.""" 

127 

128 valueChanged = Signal(str, str) 

129 

130 def __init__( 

131 self, 

132 label: str, 

133 description: str = "", 

134 options: list | None = None, 

135 default: str | None = None, 

136 ): 

137 super().__init__(label, description) 

138 self._options = options or [] 

139 self._value = default or (options[0] if options else "") 

140 self.setObjectName("setting_select_container") 

141 self.setProperty("type", "setting_select") 

142 self._setup_ui() 

143 

144 def _setup_ui(self): 

145 self._layout = QVBoxLayout(self) 

146 self._layout.setSpacing(4) 

147 self._layout.setContentsMargins(0, 0, 0, 0) 

148 

149 self._label_widget = QLabel(self._tr(self._label_text)) 

150 self._label_widget.setObjectName("setting_label") 

151 self._label_widget.setWordWrap(True) 

152 self._layout.addWidget(self._label_widget) 

153 

154 self._control_widget = QComboBox() 

155 self._control_widget.setObjectName("setting_combo_box") 

156 self._control_widget.addItems(self._options) 

157 if self._value in self._options: 

158 self._control_widget.setCurrentText(self._value) 

159 self._control_widget.currentTextChanged.connect(self._on_text_changed) 

160 self._layout.addWidget(self._control_widget) 

161 

162 self._description_label = None 

163 if self._description_text: 

164 self._description_label = QLabel(self._tr(self._description_text)) 

165 self._description_label.setObjectName("setting_description") 

166 self._description_label.setWordWrap(True) 

167 self._layout.addWidget(self._description_label) 

168 

169 def retranslate_ui(self): 

170 self._label_widget.setText(self._tr(self._label_text)) 

171 if self._description_label: 

172 self._description_label.setText(self._tr(self._description_text)) 

173 

174 def _on_text_changed(self, text: str): 

175 self._value = text 

176 self.valueChanged.emit(self._key, text) 

177 

178 @property 

179 def value(self) -> str: 

180 return self._value 

181 

182 @value.setter 

183 def value(self, val: str): 

184 self._value = val 

185 if val in self._options: 185 ↛ exitline 185 didn't return from function 'value' because the condition on line 185 was always true

186 self._control_widget.setCurrentText(val) 

187 

188 def get_value(self) -> str: 

189 return self._value 

190 

191 def set_value(self, val: str): 

192 self._value = val 

193 if val in self._options: 193 ↛ exitline 193 didn't return from function 'set_value' because the condition on line 193 was always true

194 self._control_widget.setCurrentText(val) 

195 

196 

197class SettingSlider(BaseSettingWidget): 

198 """Widget for numeric settings with slider.""" 

199 

200 valueChanged = Signal(str, int) 

201 

202 def __init__( 

203 self, 

204 label: str, 

205 description: str = "", 

206 min_val: int = 0, 

207 max_val: int = 100, 

208 default: int = 50, 

209 unit: str = "", 

210 ): 

211 super().__init__(label, description) 

212 self._min_val = min_val 

213 self._max_val = max_val 

214 self._value = default 

215 self._unit = unit 

216 self.setObjectName("setting_slider_container") 

217 self.setProperty("type", "setting_slider") 

218 self._setup_ui() 

219 

220 def _setup_ui(self): 

221 self._layout = QVBoxLayout(self) 

222 self._layout.setSpacing(4) 

223 self._layout.setContentsMargins(0, 0, 0, 0) 

224 

225 self._header_layout = QHBoxLayout() 

226 self._header_layout.setSpacing(8) 

227 self._header_layout.setContentsMargins(0, 0, 0, 0) 

228 

229 self._label_widget = QLabel(self._tr(self._label_text)) 

230 self._label_widget.setObjectName("setting_label") 

231 self._label_widget.setWordWrap(True) 

232 self._header_layout.addWidget(self._label_widget, 1) 

233 

234 self._value_label = QLabel(f"{self._value}{self._unit}") 

235 self._value_label.setObjectName("setting_value_label") 

236 self._header_layout.addWidget(self._value_label, 0) 

237 

238 self._layout.addLayout(self._header_layout) 

239 

240 self._control_widget = QSlider(Qt.Orientation.Horizontal) 

241 self._control_widget.setObjectName("setting_slider") 

242 self._control_widget.setMinimum(self._min_val) 

243 self._control_widget.setMaximum(self._max_val) 

244 self._control_widget.setValue(self._value) 

245 self._control_widget.valueChanged.connect(self._on_value_changed) 

246 self._layout.addWidget(self._control_widget) 

247 

248 self._description_label = None 

249 if self._description_text: 249 ↛ 250line 249 didn't jump to line 250 because the condition on line 249 was never true

250 self._description_label = QLabel(self._tr(self._description_text)) 

251 self._description_label.setObjectName("setting_description") 

252 self._description_label.setWordWrap(True) 

253 self._layout.addWidget(self._description_label) 

254 

255 def retranslate_ui(self): 

256 self._label_widget.setText(self._tr(self._label_text)) 

257 if self._description_label: 

258 self._description_label.setText(self._tr(self._description_text)) 

259 

260 def _on_value_changed(self, value: int): 

261 self._value = value 

262 self._value_label.setText(f"{value}{self._unit}") 

263 self.valueChanged.emit(self._key, value) 

264 

265 @property 

266 def value(self) -> int: 

267 return self._value 

268 

269 @value.setter 

270 def value(self, val: int): 

271 self._value = val 

272 self._control_widget.setValue(val) 

273 self._value_label.setText(f"{val}{self._unit}") 

274 

275 def get_value(self) -> int: 

276 return self._value 

277 

278 def set_value(self, val: int): 

279 self._value = val 

280 self._control_widget.setValue(val) 

281 self._value_label.setText(f"{val}{self._unit}") 

282 

283 

284class SettingText(BaseSettingWidget): 

285 """Widget for text settings.""" 

286 

287 valueChanged = Signal(str, str) 

288 

289 def __init__(self, label: str, description: str = "", default: str = ""): 

290 super().__init__(label, description) 

291 self._value = default 

292 self.setObjectName("setting_text_container") 

293 self.setProperty("type", "setting_text") 

294 self._setup_ui() 

295 

296 def _setup_ui(self): 

297 self._layout = QVBoxLayout(self) 

298 self._layout.setSpacing(4) 

299 self._layout.setContentsMargins(0, 0, 0, 0) 

300 

301 self._label_widget = QLabel(self._tr(self._label_text)) 

302 self._label_widget.setObjectName("setting_label") 

303 self._label_widget.setWordWrap(True) 

304 self._layout.addWidget(self._label_widget) 

305 

306 self._control_widget = QLineEdit() 

307 self._control_widget.setObjectName("setting_text_edit") 

308 self._control_widget.setText(self._value) 

309 self._control_widget.textChanged.connect(self._on_text_changed) 

310 self._layout.addWidget(self._control_widget) 

311 

312 self._description_label = None 

313 if self._description_text: 313 ↛ 314line 313 didn't jump to line 314 because the condition on line 313 was never true

314 self._description_label = QLabel(self._tr(self._description_text)) 

315 self._description_label.setObjectName("setting_description") 

316 self._description_label.setWordWrap(True) 

317 self._layout.addWidget(self._description_label) 

318 

319 def retranslate_ui(self): 

320 self._label_widget.setText(self._tr(self._label_text)) 

321 if self._description_label: 

322 self._description_label.setText(self._tr(self._description_text)) 

323 

324 def _on_text_changed(self, text: str): 

325 self._value = text 

326 self.valueChanged.emit(self._key, text) 

327 

328 @property 

329 def value(self) -> str: 

330 return self._value 

331 

332 @value.setter 

333 def value(self, val: str): 

334 self._value = val 

335 self._control_widget.setText(val) 

336 

337 def get_value(self) -> str: 

338 return self._value 

339 

340 def set_value(self, val: str): 

341 self._value = val 

342 self._control_widget.setText(val) 

343 

344 

345class SettingCheckbox(BaseSettingWidget): 

346 """Widget for checkbox settings (on/off).""" 

347 

348 valueChanged = Signal(str, bool) 

349 

350 def __init__(self, label: str, description: str = "", default: bool = False): 

351 super().__init__(label, description) 

352 self._value = default 

353 self.setObjectName("setting_checkbox_container") 

354 self.setProperty("type", "setting_checkbox") 

355 self._setup_ui() 

356 

357 def _setup_ui(self): 

358 self._layout = QVBoxLayout(self) 

359 self._layout.setSpacing(4) 

360 self._layout.setContentsMargins(0, 0, 0, 0) 

361 

362 self._control_layout = QHBoxLayout() 

363 self._control_layout.setSpacing(8) 

364 self._control_layout.setContentsMargins(0, 0, 0, 0) 

365 

366 self._label_widget = QLabel(self._tr(self._label_text)) 

367 self._label_widget.setObjectName("setting_label") 

368 self._label_widget.setWordWrap(True) 

369 self._control_layout.addWidget(self._label_widget, 1) 

370 

371 animation_enabled = get_settings_service().gui.TIME_ANIMATION > 0 

372 self._control_widget = ToggleSwitch(animation=animation_enabled) 

373 self._control_widget.setObjectName("setting_toggle") 

374 self._control_widget.checked = self._value 

375 self._control_widget.toggled.connect(self._on_toggled) 

376 self._control_layout.addWidget(self._control_widget, 0) 

377 

378 self._layout.addLayout(self._control_layout) 

379 

380 self._description_label = None 

381 if self._description_text: 381 ↛ 382line 381 didn't jump to line 382 because the condition on line 381 was never true

382 self._description_label = QLabel(self._tr(self._description_text)) 

383 self._description_label.setObjectName("setting_description") 

384 self._description_label.setWordWrap(True) 

385 self._layout.addWidget(self._description_label) 

386 

387 def retranslate_ui(self): 

388 self._label_widget.setText(self._tr(self._label_text)) 

389 if self._description_label: 

390 self._description_label.setText(self._tr(self._description_text)) 

391 

392 def _on_toggled(self, checked: bool): 

393 self._value = checked 

394 self.valueChanged.emit(self._key, checked) 

395 

396 @property 

397 def value(self) -> bool: 

398 return self._value 

399 

400 @value.setter 

401 def value(self, val: bool): 

402 self._value = val 

403 self._control_widget.checked = val 

404 

405 def get_value(self) -> bool: 

406 return self._value 

407 

408 def set_value(self, val: bool): 

409 self._value = val 

410 self._control_widget.checked = val 

411 

412 

413# /////////////////////////////////////////////////////////////// 

414# PUBLIC API 

415# /////////////////////////////////////////////////////////////// 

416__all__ = [ 

417 "BaseSettingWidget", 

418 "SettingCheckbox", 

419 "SettingSelect", 

420 "SettingSlider", 

421 "SettingText", 

422 "SettingToggle", 

423]