Coverage for src / ezqt_widgets / widgets / input / tab_replace_textedit.py: 77.03%

66 statements  

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

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

2# TAB_REPLACE_TEXTEDIT - Tab Replace Text Edit Widget 

3# Project: ezqt_widgets 

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

5 

6""" 

7Tab replace text edit widget module. 

8 

9Provides a QPlainTextEdit subclass that sanitizes pasted text by replacing 

10tab characters according to the chosen mode and removing empty lines for 

11PySide6 applications. 

12""" 

13 

14from __future__ import annotations 

15 

16# /////////////////////////////////////////////////////////////// 

17# IMPORTS 

18# /////////////////////////////////////////////////////////////// 

19# Standard library imports 

20from typing import Any 

21 

22# Third-party imports 

23from PySide6.QtCore import Qt 

24from PySide6.QtGui import QKeyEvent, QKeySequence 

25from PySide6.QtWidgets import QApplication, QPlainTextEdit 

26 

27# Local imports 

28from ...types import WidgetParent 

29 

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

31# CLASSES 

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

33 

34 

35class TabReplaceTextEdit(QPlainTextEdit): 

36 """QPlainTextEdit subclass with tab replacement and text sanitization. 

37 

38 Sanitizes pasted text by replacing tab characters according to the 

39 chosen mode and removing empty lines. Useful for pasting tabular data 

40 or ensuring clean input. 

41 

42 Args: 

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

44 tab_replacement: The string to replace tab characters with 

45 (default: "\\n"). 

46 sanitize_on_paste: Whether to sanitize pasted text (default: True). 

47 remove_empty_lines: Whether to remove empty lines during sanitization 

48 (default: True). 

49 preserve_whitespace: Whether to preserve leading/trailing whitespace 

50 (default: False). 

51 *args: Additional arguments passed to QPlainTextEdit. 

52 **kwargs: Additional keyword arguments passed to QPlainTextEdit. 

53 

54 Properties: 

55 tab_replacement: Get or set the string used to replace tab characters. 

56 sanitize_on_paste: Enable or disable sanitizing pasted text. 

57 remove_empty_lines: Get or set whether to remove empty lines. 

58 preserve_whitespace: Get or set whether to preserve whitespace. 

59 

60 Example: 

61 >>> from ezqt_widgets import TabReplaceTextEdit 

62 >>> editor = TabReplaceTextEdit(tab_replacement=";", remove_empty_lines=True) 

63 >>> editor.setPlainText("alpha\\tbeta\\n\\ngamma\\tdelta") 

64 >>> # Paste triggers sanitization; tabs become ";" and empty lines removed 

65 >>> editor.show() 

66 """ 

67 

68 # /////////////////////////////////////////////////////////////// 

69 # INIT 

70 # /////////////////////////////////////////////////////////////// 

71 

72 def __init__( 

73 self, 

74 parent: WidgetParent = None, 

75 tab_replacement: str = "\n", 

76 sanitize_on_paste: bool = True, 

77 remove_empty_lines: bool = True, 

78 preserve_whitespace: bool = False, 

79 *args: Any, 

80 **kwargs: Any, 

81 ) -> None: 

82 """Initialize the tab replace text edit.""" 

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

84 

85 # Set widget type property 

86 self.setProperty("type", "TabReplaceTextEdit") 

87 

88 # Initialize properties 

89 self._tab_replacement: str = tab_replacement 

90 self._sanitize_on_paste: bool = sanitize_on_paste 

91 self._remove_empty_lines: bool = remove_empty_lines 

92 self._preserve_whitespace: bool = preserve_whitespace 

93 

94 # /////////////////////////////////////////////////////////////// 

95 # PROPERTIES 

96 # /////////////////////////////////////////////////////////////// 

97 

98 @property 

99 def tab_replacement(self) -> str: 

100 """Get the string used to replace tab characters. 

101 

102 Returns: 

103 The current tab replacement string. 

104 """ 

105 return self._tab_replacement 

106 

107 @tab_replacement.setter 

108 def tab_replacement(self, value: str) -> None: 

109 """Set the string used to replace tab characters. 

110 

111 Args: 

112 value: The new tab replacement string. 

113 """ 

114 self._tab_replacement = str(value) 

115 

116 @property 

117 def sanitize_on_paste(self) -> bool: 

118 """Get whether sanitizing pasted text is enabled. 

119 

120 Returns: 

121 True if sanitization is enabled, False otherwise. 

122 """ 

123 return self._sanitize_on_paste 

124 

125 @sanitize_on_paste.setter 

126 def sanitize_on_paste(self, value: bool) -> None: 

127 """Set whether sanitizing pasted text is enabled. 

128 

129 Args: 

130 value: Whether to enable sanitization. 

131 """ 

132 self._sanitize_on_paste = bool(value) 

133 

134 @property 

135 def remove_empty_lines(self) -> bool: 

136 """Get whether empty lines are removed. 

137 

138 Returns: 

139 True if empty lines are removed, False otherwise. 

140 """ 

141 return self._remove_empty_lines 

142 

143 @remove_empty_lines.setter 

144 def remove_empty_lines(self, value: bool) -> None: 

145 """Set whether empty lines are removed. 

146 

147 Args: 

148 value: Whether to remove empty lines. 

149 """ 

150 self._remove_empty_lines = bool(value) 

151 

152 @property 

153 def preserve_whitespace(self) -> bool: 

154 """Get whether whitespace is preserved. 

155 

156 Returns: 

157 True if whitespace is preserved, False otherwise. 

158 """ 

159 return self._preserve_whitespace 

160 

161 @preserve_whitespace.setter 

162 def preserve_whitespace(self, value: bool) -> None: 

163 """Set whether whitespace is preserved. 

164 

165 Args: 

166 value: Whether to preserve whitespace. 

167 """ 

168 self._preserve_whitespace = bool(value) 

169 

170 # /////////////////////////////////////////////////////////////// 

171 # PUBLIC METHODS 

172 # /////////////////////////////////////////////////////////////// 

173 

174 def sanitizeText(self, text: str) -> str: 

175 """Sanitize text by replacing tabs and optionally removing empty lines. 

176 

177 Args: 

178 text: The text to sanitize. 

179 

180 Returns: 

181 The sanitized text. 

182 """ 

183 # Replace tabs 

184 sanitized = text.replace("\t", self._tab_replacement) 

185 

186 if self._remove_empty_lines: 

187 # Split into lines 

188 lines = sanitized.split("\n") 

189 

190 # Filter empty lines 

191 if self._preserve_whitespace: 

192 # Keep lines with whitespace 

193 lines = [line for line in lines if line.strip() or line] 

194 else: 

195 # Remove all empty lines but preserve whitespace 

196 lines = [line for line in lines if line.strip()] 

197 

198 # Rejoin lines 

199 sanitized = "\n".join(lines) 

200 

201 return sanitized 

202 

203 # /////////////////////////////////////////////////////////////// 

204 # EVENT HANDLERS 

205 # /////////////////////////////////////////////////////////////// 

206 

207 def keyPressEvent(self, event: QKeyEvent) -> None: 

208 """Handle key press events. 

209 

210 Overridden method from QPlainTextEdit. Modifies the behavior of 

211 the paste operation and tab key handling. 

212 

213 Args: 

214 event: The key event. 

215 """ 

216 # Handle tab key 

217 if event.key() == Qt.Key.Key_Tab: 

218 # Insert tab replacement 

219 cursor = self.textCursor() 

220 cursor.insertText(self._tab_replacement) 

221 event.accept() 

222 return 

223 

224 # Handle paste 

225 if self._sanitize_on_paste and event.matches(QKeySequence.StandardKey.Paste): 

226 # Get clipboard text 

227 clipboard = QApplication.clipboard() 

228 text = clipboard.text() 

229 

230 # Sanitize text 

231 text = self.sanitizeText(text) 

232 

233 # Insert sanitized text 

234 self.insertPlainText(text) 

235 event.accept() 

236 return 

237 

238 # Default behavior 

239 super().keyPressEvent(event) 

240 

241 # /////////////////////////////////////////////////////////////// 

242 # STYLE METHODS 

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

244 

245 def refreshStyle(self) -> None: 

246 """Refresh the widget's style. 

247 

248 Useful after dynamic stylesheet changes. 

249 """ 

250 self.style().unpolish(self) 

251 self.style().polish(self) 

252 self.update() 

253 

254 

255# /////////////////////////////////////////////////////////////// 

256# PUBLIC API 

257# /////////////////////////////////////////////////////////////// 

258 

259__all__ = ["TabReplaceTextEdit"]