50 # Start with a default dictionary based on the current locale |
57 # Start with a default dictionary based on the current locale |
51 # or the configured default language. |
58 # or the configured default language. |
52 spellDict = enchant.DictWithPWL( |
59 spellDict = enchant.DictWithPWL( |
53 SpellCheckMixin.DefaultLanguage, |
60 SpellCheckMixin.DefaultLanguage, |
54 SpellCheckMixin.DefaultUserWordList, |
61 SpellCheckMixin.DefaultUserWordList, |
55 SpellCheckMixin.DefaultUserExceptionList |
62 SpellCheckMixin.DefaultUserExceptionList, |
56 ) |
63 ) |
57 except DictNotFoundError: |
64 except DictNotFoundError: |
58 try: |
65 try: |
59 # Use English dictionary if no locale dictionary is |
66 # Use English dictionary if no locale dictionary is |
60 # available or the default one could not be found. |
67 # available or the default one could not be found. |
61 spellDict = enchant.DictWithPWL( |
68 spellDict = enchant.DictWithPWL( |
62 "en", |
69 "en", |
63 SpellCheckMixin.DefaultUserWordList, |
70 SpellCheckMixin.DefaultUserWordList, |
64 SpellCheckMixin.DefaultUserExceptionList |
71 SpellCheckMixin.DefaultUserExceptionList, |
65 ) |
72 ) |
66 except DictNotFoundError: |
73 except DictNotFoundError: |
67 # Still no dictionary could be found. Forget about spell |
74 # Still no dictionary could be found. Forget about spell |
68 # checking. |
75 # checking. |
69 spellDict = None |
76 spellDict = None |
70 |
77 |
71 self.__highlighter.setDict(spellDict) |
78 self.__highlighter.setDict(spellDict) |
72 |
79 |
73 def contextMenuEvent(self, evt): |
80 def contextMenuEvent(self, evt): |
74 """ |
81 """ |
75 Protected method to handle context menu events to add a spelling |
82 Protected method to handle context menu events to add a spelling |
76 suggestions submenu. |
83 suggestions submenu. |
77 |
84 |
78 @param evt reference to the context menu event |
85 @param evt reference to the context menu event |
79 @type QContextMenuEvent |
86 @type QContextMenuEvent |
80 """ |
87 """ |
81 menu = self.__createSpellcheckContextMenu(evt.pos()) |
88 menu = self.__createSpellcheckContextMenu(evt.pos()) |
82 menu.exec(evt.globalPos()) |
89 menu.exec(evt.globalPos()) |
83 |
90 |
84 def __createSpellcheckContextMenu(self, pos): |
91 def __createSpellcheckContextMenu(self, pos): |
85 """ |
92 """ |
86 Private method to create the spell-check context menu. |
93 Private method to create the spell-check context menu. |
87 |
94 |
88 @param pos position of the mouse pointer |
95 @param pos position of the mouse pointer |
89 @type QPoint |
96 @type QPoint |
90 @return context menu with additional spell-check entries |
97 @return context menu with additional spell-check entries |
91 @rtype QMenu |
98 @rtype QMenu |
92 """ |
99 """ |
93 menu = self.createStandardContextMenu(pos) |
100 menu = self.createStandardContextMenu(pos) |
94 |
101 |
95 # Add a submenu for setting the spell-check language and |
102 # Add a submenu for setting the spell-check language and |
96 # document format. |
103 # document format. |
97 menu.addSeparator() |
104 menu.addSeparator() |
98 self.__addRemoveEntry(self.__cursorForPosition(pos), menu) |
105 self.__addRemoveEntry(self.__cursorForPosition(pos), menu) |
99 menu.addMenu(self.__createLanguagesMenu(menu)) |
106 menu.addMenu(self.__createLanguagesMenu(menu)) |
100 menu.addMenu(self.__createFormatsMenu(menu)) |
107 menu.addMenu(self.__createFormatsMenu(menu)) |
101 |
108 |
102 # Try to retrieve a menu of corrections for the right-clicked word |
109 # Try to retrieve a menu of corrections for the right-clicked word |
103 spellMenu = self.__createCorrectionsMenu( |
110 spellMenu = self.__createCorrectionsMenu( |
104 self.__cursorForMisspelling(pos), menu) |
111 self.__cursorForMisspelling(pos), menu |
105 |
112 ) |
|
113 |
106 if spellMenu: |
114 if spellMenu: |
107 menu.insertSeparator(menu.actions()[0]) |
115 menu.insertSeparator(menu.actions()[0]) |
108 menu.insertMenu(menu.actions()[0], spellMenu) |
116 menu.insertMenu(menu.actions()[0], spellMenu) |
109 |
117 |
110 return menu |
118 return menu |
111 |
119 |
112 def __createCorrectionsMenu(self, cursor, parent=None): |
120 def __createCorrectionsMenu(self, cursor, parent=None): |
113 """ |
121 """ |
114 Private method to create a menu for corrections of the selected |
122 Private method to create a menu for corrections of the selected |
115 word. |
123 word. |
116 |
124 |
117 @param cursor reference to the text cursor |
125 @param cursor reference to the text cursor |
118 @type QTextCursor |
126 @type QTextCursor |
119 @param parent reference to the parent widget (defaults to None) |
127 @param parent reference to the parent widget (defaults to None) |
120 @type QWidget (optional) |
128 @type QWidget (optional) |
121 @return menu with corrections |
129 @return menu with corrections |
122 @rtype QMenu |
130 @rtype QMenu |
123 """ |
131 """ |
124 if cursor is None: |
132 if cursor is None: |
125 return None |
133 return None |
126 |
134 |
127 text = cursor.selectedText() |
135 text = cursor.selectedText() |
128 suggestions = trim_suggestions( |
136 suggestions = trim_suggestions( |
129 text, self.__highlighter.dict().suggest(text), |
137 text, |
130 SpellCheckMixin.MaxSuggestions) |
138 self.__highlighter.dict().suggest(text), |
131 |
139 SpellCheckMixin.MaxSuggestions, |
|
140 ) |
|
141 |
132 spellMenu = QMenu( |
142 spellMenu = QMenu( |
133 QCoreApplication.translate("SpellCheckMixin", |
143 QCoreApplication.translate("SpellCheckMixin", "Spelling Suggestions"), |
134 "Spelling Suggestions"), |
144 parent, |
135 parent) |
145 ) |
136 for word in suggestions: |
146 for word in suggestions: |
137 act = spellMenu.addAction(word) |
147 act = spellMenu.addAction(word) |
138 act.setData((cursor, word)) |
148 act.setData((cursor, word)) |
139 |
149 |
140 if suggestions: |
150 if suggestions: |
141 spellMenu.addSeparator() |
151 spellMenu.addSeparator() |
142 |
152 |
143 # add management entry |
153 # add management entry |
144 act = spellMenu.addAction(QCoreApplication.translate( |
154 act = spellMenu.addAction( |
145 "SpellCheckMixin", "Add to Dictionary")) |
155 QCoreApplication.translate("SpellCheckMixin", "Add to Dictionary") |
|
156 ) |
146 act.setData((cursor, text, "add")) |
157 act.setData((cursor, text, "add")) |
147 |
158 |
148 spellMenu.triggered.connect(self.__spellMenuTriggered) |
159 spellMenu.triggered.connect(self.__spellMenuTriggered) |
149 return spellMenu |
160 return spellMenu |
150 |
161 |
151 def __addRemoveEntry(self, cursor, menu): |
162 def __addRemoveEntry(self, cursor, menu): |
152 """ |
163 """ |
153 Private method to create a menu entry to remove the word at the |
164 Private method to create a menu entry to remove the word at the |
154 menu position. |
165 menu position. |
155 |
166 |
156 @param cursor reference to the text cursor for the misspelled word |
167 @param cursor reference to the text cursor for the misspelled word |
157 @type QTextCursor |
168 @type QTextCursor |
158 @param menu reference to the context menu |
169 @param menu reference to the context menu |
159 @type QMenu |
170 @type QMenu |
160 """ |
171 """ |
161 if cursor is None: |
172 if cursor is None: |
162 return |
173 return |
163 |
174 |
164 text = cursor.selectedText() |
175 text = cursor.selectedText() |
165 menu.addAction(QCoreApplication.translate( |
176 menu.addAction( |
166 "SpellCheckMixin", |
177 QCoreApplication.translate( |
167 "Remove '{0}' from Dictionary").format(text), |
178 "SpellCheckMixin", "Remove '{0}' from Dictionary" |
168 lambda: self.__addToUserDict(text, "remove")) |
179 ).format(text), |
169 |
180 lambda: self.__addToUserDict(text, "remove"), |
|
181 ) |
|
182 |
170 def __createLanguagesMenu(self, parent=None): |
183 def __createLanguagesMenu(self, parent=None): |
171 """ |
184 """ |
172 Private method to create a menu for selecting the spell-check |
185 Private method to create a menu for selecting the spell-check |
173 language. |
186 language. |
174 |
187 |
175 @param parent reference to the parent widget (defaults to None) |
188 @param parent reference to the parent widget (defaults to None) |
176 @type QWidget (optional) |
189 @type QWidget (optional) |
177 @return menu with spell-check languages |
190 @return menu with spell-check languages |
178 @rtype QMenu |
191 @rtype QMenu |
179 """ |
192 """ |
180 curLanguage = self.__highlighter.dict().tag.lower() |
193 curLanguage = self.__highlighter.dict().tag.lower() |
181 languageMenu = QMenu( |
194 languageMenu = QMenu( |
182 QCoreApplication.translate("SpellCheckMixin", "Language"), |
195 QCoreApplication.translate("SpellCheckMixin", "Language"), parent |
183 parent) |
196 ) |
184 languageActions = QActionGroup(languageMenu) |
197 languageActions = QActionGroup(languageMenu) |
185 |
198 |
186 for language in sorted(enchant.list_languages()): |
199 for language in sorted(enchant.list_languages()): |
187 act = QAction(language, languageActions) |
200 act = QAction(language, languageActions) |
188 act.setCheckable(True) |
201 act.setCheckable(True) |
189 act.setChecked(language.lower() == curLanguage) |
202 act.setChecked(language.lower() == curLanguage) |
190 act.setData(language) |
203 act.setData(language) |
191 languageMenu.addAction(act) |
204 languageMenu.addAction(act) |
192 |
205 |
193 languageMenu.triggered.connect(self.__setLanguage) |
206 languageMenu.triggered.connect(self.__setLanguage) |
194 return languageMenu |
207 return languageMenu |
195 |
208 |
196 def __createFormatsMenu(self, parent=None): |
209 def __createFormatsMenu(self, parent=None): |
197 """ |
210 """ |
198 Private method to create a menu for selecting the document format. |
211 Private method to create a menu for selecting the document format. |
199 |
212 |
200 @param parent reference to the parent widget (defaults to None) |
213 @param parent reference to the parent widget (defaults to None) |
201 @type QWidget (optional) |
214 @type QWidget (optional) |
202 @return menu with document formats |
215 @return menu with document formats |
203 @rtype QMenu |
216 @rtype QMenu |
204 """ |
217 """ |
205 formatMenu = QMenu( |
218 formatMenu = QMenu( |
206 QCoreApplication.translate("SpellCheckMixin", "Format"), |
219 QCoreApplication.translate("SpellCheckMixin", "Format"), parent |
207 parent) |
220 ) |
208 formatActions = QActionGroup(formatMenu) |
221 formatActions = QActionGroup(formatMenu) |
209 |
222 |
210 curFormat = self.__highlighter.chunkers() |
223 curFormat = self.__highlighter.chunkers() |
211 for name, chunkers in ( |
224 for name, chunkers in ( |
212 (QCoreApplication.translate("SpellCheckMixin", "Text"), |
225 (QCoreApplication.translate("SpellCheckMixin", "Text"), []), |
213 []), |
226 ( |
214 (QCoreApplication.translate("SpellCheckMixin", "HTML"), |
227 QCoreApplication.translate("SpellCheckMixin", "HTML"), |
215 [enchant.tokenize.HTMLChunker]) |
228 [enchant.tokenize.HTMLChunker], |
|
229 ), |
216 ): |
230 ): |
217 act = QAction(name, formatActions) |
231 act = QAction(name, formatActions) |
218 act.setCheckable(True) |
232 act.setCheckable(True) |
219 act.setChecked(chunkers == curFormat) |
233 act.setChecked(chunkers == curFormat) |
220 act.setData(chunkers) |
234 act.setData(chunkers) |
221 formatMenu.addAction(act) |
235 formatMenu.addAction(act) |
222 |
236 |
223 formatMenu.triggered.connect(self.__setFormat) |
237 formatMenu.triggered.connect(self.__setFormat) |
224 return formatMenu |
238 return formatMenu |
225 |
239 |
226 def __cursorForPosition(self, pos): |
240 def __cursorForPosition(self, pos): |
227 """ |
241 """ |
228 Private method to create a text cursor selecting the word at the |
242 Private method to create a text cursor selecting the word at the |
229 given position. |
243 given position. |
230 |
244 |
231 @param pos position of the misspelled word |
245 @param pos position of the misspelled word |
232 @type QPoint |
246 @type QPoint |
233 @return text cursor for the word |
247 @return text cursor for the word |
234 @rtype QTextCursor |
248 @rtype QTextCursor |
235 """ |
249 """ |
236 cursor = self.cursorForPosition(pos) |
250 cursor = self.cursorForPosition(pos) |
237 cursor.select(QTextCursor.SelectionType.WordUnderCursor) |
251 cursor.select(QTextCursor.SelectionType.WordUnderCursor) |
238 |
252 |
239 if cursor.hasSelection(): |
253 if cursor.hasSelection(): |
240 return cursor |
254 return cursor |
241 else: |
255 else: |
242 return None |
256 return None |
243 |
257 |
244 def __cursorForMisspelling(self, pos): |
258 def __cursorForMisspelling(self, pos): |
245 """ |
259 """ |
246 Private method to create a text cursor selecting the misspelled |
260 Private method to create a text cursor selecting the misspelled |
247 word. |
261 word. |
248 |
262 |
249 @param pos position of the misspelled word |
263 @param pos position of the misspelled word |
250 @type QPoint |
264 @type QPoint |
251 @return text cursor for the misspelled word |
265 @return text cursor for the misspelled word |
252 @rtype QTextCursor |
266 @rtype QTextCursor |
253 """ |
267 """ |
254 cursor = self.cursorForPosition(pos) |
268 cursor = self.cursorForPosition(pos) |
255 misspelledWords = getattr(cursor.block().userData(), |
269 misspelledWords = getattr(cursor.block().userData(), "misspelled", []) |
256 "misspelled", []) |
270 |
257 |
|
258 # If the cursor is within a misspelling, select the word |
271 # If the cursor is within a misspelling, select the word |
259 for (start, end) in misspelledWords: |
272 for (start, end) in misspelledWords: |
260 if start <= cursor.positionInBlock() <= end: |
273 if start <= cursor.positionInBlock() <= end: |
261 blockPosition = cursor.block().position() |
274 blockPosition = cursor.block().position() |
262 |
275 |
263 cursor.setPosition(blockPosition + start, |
276 cursor.setPosition( |
264 QTextCursor.MoveMode.MoveAnchor) |
277 blockPosition + start, QTextCursor.MoveMode.MoveAnchor |
265 cursor.setPosition(blockPosition + end, |
278 ) |
266 QTextCursor.MoveMode.KeepAnchor) |
279 cursor.setPosition( |
|
280 blockPosition + end, QTextCursor.MoveMode.KeepAnchor |
|
281 ) |
267 break |
282 break |
268 |
283 |
269 if cursor.hasSelection(): |
284 if cursor.hasSelection(): |
270 return cursor |
285 return cursor |
271 else: |
286 else: |
272 return None |
287 return None |
273 |
288 |
274 def __correctWord(self, cursor, word): |
289 def __correctWord(self, cursor, word): |
275 """ |
290 """ |
276 Private method to replace some misspelled text. |
291 Private method to replace some misspelled text. |
277 |
292 |
278 @param cursor reference to the text cursor for the misspelled word |
293 @param cursor reference to the text cursor for the misspelled word |
279 @type QTextCursor |
294 @type QTextCursor |
280 @param word replacement text |
295 @param word replacement text |
281 @type str |
296 @type str |
282 """ |
297 """ |
283 cursor.beginEditBlock() |
298 cursor.beginEditBlock() |
284 cursor.removeSelectedText() |
299 cursor.removeSelectedText() |
285 cursor.insertText(word) |
300 cursor.insertText(word) |
286 cursor.endEditBlock() |
301 cursor.endEditBlock() |
287 |
302 |
288 def __addToUserDict(self, word, command): |
303 def __addToUserDict(self, word, command): |
289 """ |
304 """ |
290 Private method to add a word to the user word or exclude list. |
305 Private method to add a word to the user word or exclude list. |
291 |
306 |
292 @param word text to be added |
307 @param word text to be added |
293 @type str |
308 @type str |
294 @param command command indicating the user dictionary type |
309 @param command command indicating the user dictionary type |
295 @type str |
310 @type str |
296 """ |
311 """ |
298 dictionary = self.__highlighter.dict() |
313 dictionary = self.__highlighter.dict() |
299 if command == "add": |
314 if command == "add": |
300 dictionary.add(word) |
315 dictionary.add(word) |
301 elif command == "remove": |
316 elif command == "remove": |
302 dictionary.remove(word) |
317 dictionary.remove(word) |
303 |
318 |
304 self.__highlighter.rehighlight() |
319 self.__highlighter.rehighlight() |
305 |
320 |
306 @pyqtSlot(QAction) |
321 @pyqtSlot(QAction) |
307 def __spellMenuTriggered(self, act): |
322 def __spellMenuTriggered(self, act): |
308 """ |
323 """ |
309 Private slot to handle a selection of the spell menu. |
324 Private slot to handle a selection of the spell menu. |
310 |
325 |
311 @param act reference to the selected action |
326 @param act reference to the selected action |
312 @type QAction |
327 @type QAction |
313 """ |
328 """ |
314 data = act.data() |
329 data = act.data() |
315 if len(data) == 2: |
330 if len(data) == 2: |
316 # replace the misspelled word |
331 # replace the misspelled word |
317 self.__correctWord(*data) |
332 self.__correctWord(*data) |
318 |
333 |
319 elif len(data) == 3: |
334 elif len(data) == 3: |
320 # dictionary management action |
335 # dictionary management action |
321 _, word, command = data |
336 _, word, command = data |
322 self.__addToUserDict(word, command) |
337 self.__addToUserDict(word, command) |
323 |
338 |
324 @pyqtSlot(QAction) |
339 @pyqtSlot(QAction) |
325 def __setLanguage(self, act): |
340 def __setLanguage(self, act): |
326 """ |
341 """ |
327 Private slot to set the selected language. |
342 Private slot to set the selected language. |
328 |
343 |
329 @param act reference to the selected action |
344 @param act reference to the selected action |
330 @type QAction |
345 @type QAction |
331 """ |
346 """ |
332 language = act.data() |
347 language = act.data() |
333 self.setLanguage(language) |
348 self.setLanguage(language) |
334 |
349 |
335 @pyqtSlot(QAction) |
350 @pyqtSlot(QAction) |
336 def __setFormat(self, act): |
351 def __setFormat(self, act): |
337 """ |
352 """ |
338 Private slot to set the selected document format. |
353 Private slot to set the selected document format. |
339 |
354 |
340 @param act reference to the selected action |
355 @param act reference to the selected action |
341 @type QAction |
356 @type QAction |
342 """ |
357 """ |
343 chunkers = act.data() |
358 chunkers = act.data() |
344 self.__highlighter.setChunkers(chunkers) |
359 self.__highlighter.setChunkers(chunkers) |
345 |
360 |
346 def setFormat(self, formatName): |
361 def setFormat(self, formatName): |
347 """ |
362 """ |
348 Public method to set the document format. |
363 Public method to set the document format. |
349 |
364 |
350 @param formatName name of the document format |
365 @param formatName name of the document format |
351 @type str |
366 @type str |
352 """ |
367 """ |
353 self.__highlighter.setChunkers( |
368 self.__highlighter.setChunkers( |
354 [enchant.tokenize.HTMLChunker] |
369 [enchant.tokenize.HTMLChunker] if format == "html" else [] |
355 if format == "html" else |
|
356 [] |
|
357 ) |
370 ) |
358 |
371 |
359 def dict(self): |
372 def dict(self): |
360 """ |
373 """ |
361 Public method to get a reference to the dictionary in use. |
374 Public method to get a reference to the dictionary in use. |
362 |
375 |
363 @return reference to the current dictionary |
376 @return reference to the current dictionary |
364 @rtype enchant.Dict |
377 @rtype enchant.Dict |
365 """ |
378 """ |
366 return self.__highlighter.dict() |
379 return self.__highlighter.dict() |
367 |
380 |
368 def setDict(self, spellDict): |
381 def setDict(self, spellDict): |
369 """ |
382 """ |
370 Public method to set the dictionary to be used. |
383 Public method to set the dictionary to be used. |
371 |
384 |
372 @param spellDict reference to the spell-check dictionary |
385 @param spellDict reference to the spell-check dictionary |
373 @type emchant.Dict |
386 @type emchant.Dict |
374 """ |
387 """ |
375 self.__highlighter.setDict(spellDict) |
388 self.__highlighter.setDict(spellDict) |
376 |
389 |
377 @pyqtSlot(str) |
390 @pyqtSlot(str) |
378 def setLanguage(self, language): |
391 def setLanguage(self, language): |
379 """ |
392 """ |
380 Public slot to set the spellchecker language. |
393 Public slot to set the spellchecker language. |
381 |
394 |
382 @param language language to be set |
395 @param language language to be set |
383 @type str |
396 @type str |
384 """ |
397 """ |
385 epwl = self.dict().pwl |
398 epwl = self.dict().pwl |
386 pwl = ( |
399 pwl = epwl.provider.file if isinstance(epwl, enchant.Dict) else None |
387 epwl.provider.file |
400 |
388 if isinstance(epwl, enchant.Dict) else |
|
389 None |
|
390 ) |
|
391 |
|
392 epel = self.dict().pel |
401 epel = self.dict().pel |
393 pel = ( |
402 pel = epel.provider.file if isinstance(epel, enchant.Dict) else None |
394 epel.provider.file |
|
395 if isinstance(epel, enchant.Dict) else |
|
396 None |
|
397 ) |
|
398 self.setLanguageWithPWL(language, pwl, pel) |
403 self.setLanguageWithPWL(language, pwl, pel) |
399 |
404 |
400 @pyqtSlot(str, str, str) |
405 @pyqtSlot(str, str, str) |
401 def setLanguageWithPWL(self, language, pwl, pel): |
406 def setLanguageWithPWL(self, language, pwl, pel): |
402 """ |
407 """ |
403 Public slot to set the spellchecker language and associated user |
408 Public slot to set the spellchecker language and associated user |
404 word lists. |
409 word lists. |
405 |
410 |
406 @param language language to be set |
411 @param language language to be set |
407 @type str |
412 @type str |
408 @param pwl file name of the personal word list |
413 @param pwl file name of the personal word list |
409 @type str |
414 @type str |
410 @param pel file name of the personal exclude list |
415 @param pel file name of the personal exclude list |
420 except DictNotFoundError: |
425 except DictNotFoundError: |
421 # Still no dictionary could be found. Forget about spell |
426 # Still no dictionary could be found. Forget about spell |
422 # checking. |
427 # checking. |
423 spellDict = None |
428 spellDict = None |
424 self.__highlighter.setDict(spellDict) |
429 self.__highlighter.setDict(spellDict) |
425 |
430 |
426 @classmethod |
431 @classmethod |
427 def setDefaultLanguage(cls, language, pwl=None, pel=None): |
432 def setDefaultLanguage(cls, language, pwl=None, pel=None): |
428 """ |
433 """ |
429 Class method to set the default spell-check language. |
434 Class method to set the default spell-check language. |
430 |
435 |
431 @param language language to be set as default |
436 @param language language to be set as default |
432 @type str |
437 @type str |
433 @param pwl file name of the personal word list |
438 @param pwl file name of the personal word list |
434 @type str |
439 @type str |
435 @param pel file name of the personal exclude list |
440 @param pel file name of the personal exclude list |
436 @type str |
441 @type str |
437 """ |
442 """ |
438 with contextlib.suppress(DictNotFoundError): |
443 with contextlib.suppress(DictNotFoundError): |
439 cls.DefaultUserWordList = pwl |
444 cls.DefaultUserWordList = pwl |
440 cls.DefaultUserExceptionList = pel |
445 cls.DefaultUserExceptionList = pel |
441 |
446 |
442 # set default language only, if a dictionary is available |
447 # set default language only, if a dictionary is available |
443 enchant.Dict(language) |
448 enchant.Dict(language) |
444 cls.DefaultLanguage = language |
449 cls.DefaultLanguage = language |
445 |
450 |
446 class EnchantHighlighter(QSyntaxHighlighter): |
451 class EnchantHighlighter(QSyntaxHighlighter): |
447 """ |
452 """ |
448 Class implementing a QSyntaxHighlighter subclass that consults a |
453 Class implementing a QSyntaxHighlighter subclass that consults a |
449 pyEnchant dictionary to highlight misspelled words. |
454 pyEnchant dictionary to highlight misspelled words. |
450 """ |
455 """ |
451 TokenFilters = (enchant.tokenize.EmailFilter, |
456 |
452 enchant.tokenize.URLFilter) |
457 TokenFilters = (enchant.tokenize.EmailFilter, enchant.tokenize.URLFilter) |
453 |
458 |
454 # Define the spell-check style once and just assign it as necessary |
459 # Define the spell-check style once and just assign it as necessary |
455 ErrorFormat = QTextCharFormat() |
460 ErrorFormat = QTextCharFormat() |
456 ErrorFormat.setUnderlineColor(Qt.GlobalColor.red) |
461 ErrorFormat.setUnderlineColor(Qt.GlobalColor.red) |
457 ErrorFormat.setUnderlineStyle( |
462 ErrorFormat.setUnderlineStyle( |
458 QTextCharFormat.UnderlineStyle.SpellCheckUnderline) |
463 QTextCharFormat.UnderlineStyle.SpellCheckUnderline |
459 |
464 ) |
|
465 |
460 def __init__(self, *args): |
466 def __init__(self, *args): |
461 """ |
467 """ |
462 Constructor |
468 Constructor |
463 |
469 |
464 @param *args list of arguments for the QSyntaxHighlighter |
470 @param *args list of arguments for the QSyntaxHighlighter |
465 @type list |
471 @type list |
466 """ |
472 """ |
467 QSyntaxHighlighter.__init__(self, *args) |
473 QSyntaxHighlighter.__init__(self, *args) |
468 |
474 |
469 self.__spellDict = None |
475 self.__spellDict = None |
470 self.__tokenizer = None |
476 self.__tokenizer = None |
471 self.__chunkers = [] |
477 self.__chunkers = [] |
472 |
478 |
473 def chunkers(self): |
479 def chunkers(self): |
474 """ |
480 """ |
475 Public method to get the chunkers in use. |
481 Public method to get the chunkers in use. |
476 |
482 |
477 @return list of chunkers in use |
483 @return list of chunkers in use |
478 @rtype list |
484 @rtype list |
479 """ |
485 """ |
480 return self.__chunkers |
486 return self.__chunkers |
481 |
487 |
482 def setChunkers(self, chunkers): |
488 def setChunkers(self, chunkers): |
483 """ |
489 """ |
484 Public method to set the chunkers to be used. |
490 Public method to set the chunkers to be used. |
485 |
491 |
486 @param chunkers chunkers to be used |
492 @param chunkers chunkers to be used |
487 @type list |
493 @type list |
488 """ |
494 """ |
489 self.__chunkers = chunkers |
495 self.__chunkers = chunkers |
490 self.setDict(self.dict()) |
496 self.setDict(self.dict()) |
491 |
497 |
492 def dict(self): |
498 def dict(self): |
493 """ |
499 """ |
494 Public method to get the spelling dictionary in use. |
500 Public method to get the spelling dictionary in use. |
495 |
501 |
496 @return spelling dictionary |
502 @return spelling dictionary |
497 @rtype enchant.Dict |
503 @rtype enchant.Dict |
498 """ |
504 """ |
499 return self.__spellDict |
505 return self.__spellDict |
500 |
506 |
501 def setDict(self, spellDict): |
507 def setDict(self, spellDict): |
502 """ |
508 """ |
503 Public method to set the spelling dictionary to be used. |
509 Public method to set the spelling dictionary to be used. |
504 |
510 |
505 @param spellDict spelling dictionary |
511 @param spellDict spelling dictionary |
506 @type enchant.Dict |
512 @type enchant.Dict |
507 """ |
513 """ |
508 if spellDict: |
514 if spellDict: |
509 try: |
515 try: |
510 self.__tokenizer = enchant.tokenize.get_tokenizer( |
516 self.__tokenizer = enchant.tokenize.get_tokenizer( |
511 spellDict.tag, |
517 spellDict.tag, |
512 chunkers=self.__chunkers, |
518 chunkers=self.__chunkers, |
513 filters=EnchantHighlighter.TokenFilters) |
519 filters=EnchantHighlighter.TokenFilters, |
|
520 ) |
514 except TokenizerNotFoundError: |
521 except TokenizerNotFoundError: |
515 # Fall back to the "good for most euro languages" |
522 # Fall back to the "good for most euro languages" |
516 # English tokenizer |
523 # English tokenizer |
517 self.__tokenizer = enchant.tokenize.get_tokenizer( |
524 self.__tokenizer = enchant.tokenize.get_tokenizer( |
518 chunkers=self.__chunkers, |
525 chunkers=self.__chunkers, |
519 filters=EnchantHighlighter.TokenFilters) |
526 filters=EnchantHighlighter.TokenFilters, |
|
527 ) |
520 else: |
528 else: |
521 self.__tokenizer = None |
529 self.__tokenizer = None |
522 |
530 |
523 self.__spellDict = spellDict |
531 self.__spellDict = spellDict |
524 |
532 |
525 self.rehighlight() |
533 self.rehighlight() |
526 |
534 |
527 def highlightBlock(self, text): |
535 def highlightBlock(self, text): |
528 """ |
536 """ |
529 Public method to apply the text highlight. |
537 Public method to apply the text highlight. |
530 |
538 |
531 @param text text to be spell-checked |
539 @param text text to be spell-checked |
532 @type str |
540 @type str |
533 """ |
541 """ |
534 """Overridden QSyntaxHighlighter method to apply the highlight""" |
542 """Overridden QSyntaxHighlighter method to apply the highlight""" |
535 if self.__spellDict is None or self.__tokenizer is None: |
543 if self.__spellDict is None or self.__tokenizer is None: |
536 return |
544 return |
537 |
545 |
538 # Build a list of all misspelled words and highlight them |
546 # Build a list of all misspelled words and highlight them |
539 misspellings = [] |
547 misspellings = [] |
540 for (word, pos) in self.__tokenizer(text): |
548 for (word, pos) in self.__tokenizer(text): |
541 if not self.__spellDict.check(word): |
549 if not self.__spellDict.check(word): |
542 self.setFormat(pos, len(word), |
550 self.setFormat(pos, len(word), EnchantHighlighter.ErrorFormat) |
543 EnchantHighlighter.ErrorFormat) |
|
544 misspellings.append((pos, pos + len(word))) |
551 misspellings.append((pos, pos + len(word))) |
545 |
552 |
546 # Store the list so the context menu can reuse this tokenization |
553 # Store the list so the context menu can reuse this tokenization |
547 # pass (Block-relative values so editing other blocks won't |
554 # pass (Block-relative values so editing other blocks won't |
548 # invalidate them) |
555 # invalidate them) |
549 data = QTextBlockUserData() |
556 data = QTextBlockUserData() |
550 data.misspelled = misspellings |
557 data.misspelled = misspellings |
551 self.setCurrentBlockUserData(data) |
558 self.setCurrentBlockUserData(data) |
552 |
559 |
553 else: |
560 else: |
554 |
561 |
555 class SpellCheckMixin(): |
562 class SpellCheckMixin: |
556 """ |
563 """ |
557 Class implementing the spell-check mixin for the widget classes. |
564 Class implementing the spell-check mixin for the widget classes. |
558 """ |
565 """ |
|
566 |
559 # |
567 # |
560 # This is just a stub to provide the same API as the enchant enabled |
568 # This is just a stub to provide the same API as the enchant enabled |
561 # one. |
569 # one. |
562 # |
570 # |
563 def __init__(self): |
571 def __init__(self): |
564 """ |
572 """ |
565 Constructor |
573 Constructor |
566 """ |
574 """ |
567 pass |
575 pass |
568 |
576 |
569 def setFormat(self, formatName): |
577 def setFormat(self, formatName): |
570 """ |
578 """ |
571 Public method to set the document format. |
579 Public method to set the document format. |
572 |
580 |
573 @param formatName name of the document format |
581 @param formatName name of the document format |
574 @type str |
582 @type str |
575 """ |
583 """ |
576 pass |
584 pass |
577 |
585 |
578 def dict(self): |
586 def dict(self): |
579 """ |
587 """ |
580 Public method to get a reference to the dictionary in use. |
588 Public method to get a reference to the dictionary in use. |
581 |
589 |
582 @return reference to the current dictionary |
590 @return reference to the current dictionary |
583 @rtype enchant.Dict |
591 @rtype enchant.Dict |
584 """ |
592 """ |
585 pass |
593 pass |
586 |
594 |
587 def setDict(self, spellDict): |
595 def setDict(self, spellDict): |
588 """ |
596 """ |
589 Public method to set the dictionary to be used. |
597 Public method to set the dictionary to be used. |
590 |
598 |
591 @param spellDict reference to the spell-check dictionary |
599 @param spellDict reference to the spell-check dictionary |
592 @type emchant.Dict |
600 @type emchant.Dict |
593 """ |
601 """ |
594 pass |
602 pass |
595 |
603 |
596 @pyqtSlot(str) |
604 @pyqtSlot(str) |
597 def setLanguage(self, language): |
605 def setLanguage(self, language): |
598 """ |
606 """ |
599 Public slot to set the spellchecker language. |
607 Public slot to set the spellchecker language. |
600 |
608 |
601 @param language language to be set |
609 @param language language to be set |
602 @type str |
610 @type str |
603 """ |
611 """ |
604 pass |
612 pass |
605 |
613 |
606 @pyqtSlot(str, str, str) |
614 @pyqtSlot(str, str, str) |
607 def setLanguageWithPWL(self, language, pwl, pel): |
615 def setLanguageWithPWL(self, language, pwl, pel): |
608 """ |
616 """ |
609 Public slot to set the spellchecker language and associated user |
617 Public slot to set the spellchecker language and associated user |
610 word lists. |
618 word lists. |
611 |
619 |
612 @param language language to be set |
620 @param language language to be set |
613 @type str |
621 @type str |
614 @param pwl file name of the personal word list |
622 @param pwl file name of the personal word list |
615 @type str |
623 @type str |
616 @param pel file name of the personal exclude list |
624 @param pel file name of the personal exclude list |
617 @type str |
625 @type str |
618 """ |
626 """ |
619 pass |
627 pass |
620 |
628 |
621 @classmethod |
629 @classmethod |
622 def setDefaultLanguage(cls, language, pwl=None, pel=None): |
630 def setDefaultLanguage(cls, language, pwl=None, pel=None): |
623 """ |
631 """ |
624 Class method to set the default spell-check language. |
632 Class method to set the default spell-check language. |
625 |
633 |
626 @param language language to be set as default |
634 @param language language to be set as default |
627 @type str |
635 @type str |
628 @param pwl file name of the personal word list |
636 @param pwl file name of the personal word list |
629 @type str |
637 @type str |
630 @param pel file name of the personal exclude list |
638 @param pel file name of the personal exclude list |