23 from PyQt6.QtGui import ( |
23 from PyQt6.QtGui import ( |
24 QAction, QActionGroup, QSyntaxHighlighter, QTextBlockUserData, |
24 QAction, QActionGroup, QSyntaxHighlighter, QTextBlockUserData, |
25 QTextCharFormat, QTextCursor |
25 QTextCharFormat, QTextCursor |
26 ) |
26 ) |
27 from PyQt6.QtWidgets import QMenu, QTextEdit, QPlainTextEdit |
27 from PyQt6.QtWidgets import QMenu, QTextEdit, QPlainTextEdit |
28 |
|
29 # TODO: add user dictionaries with respective menu entries |
|
30 |
28 |
31 if ENCHANT_AVAILABLE: |
29 if ENCHANT_AVAILABLE: |
32 class SpellCheckMixin(): |
30 class SpellCheckMixin(): |
33 """ |
31 """ |
34 Class implementing the spell-check mixin for the widget classes. |
32 Class implementing the spell-check mixin for the widget classes. |
89 menu = self.createStandardContextMenu(pos) |
87 menu = self.createStandardContextMenu(pos) |
90 |
88 |
91 # Add a submenu for setting the spell-check language and |
89 # Add a submenu for setting the spell-check language and |
92 # document format. |
90 # document format. |
93 menu.addSeparator() |
91 menu.addSeparator() |
|
92 self.__addRemoveEntry(self.__cursorForPosition(pos), menu) |
94 menu.addMenu(self.__createLanguagesMenu(menu)) |
93 menu.addMenu(self.__createLanguagesMenu(menu)) |
95 menu.addMenu(self.__createFormatsMenu(menu)) |
94 menu.addMenu(self.__createFormatsMenu(menu)) |
96 |
95 |
97 # Try to retrieve a menu of corrections for the right-clicked word |
96 # Try to retrieve a menu of corrections for the right-clicked word |
98 spellMenu = self.__createCorrectionsMenu( |
97 spellMenu = self.__createCorrectionsMenu( |
130 parent) |
129 parent) |
131 for word in suggestions: |
130 for word in suggestions: |
132 act = spellMenu.addAction(word) |
131 act = spellMenu.addAction(word) |
133 act.setData((cursor, word)) |
132 act.setData((cursor, word)) |
134 |
133 |
135 # Only return the menu if it's non-empty |
134 if suggestions: |
136 if spellMenu.actions(): |
135 spellMenu.addSeparator() |
137 spellMenu.triggered.connect(self.__correctWord) |
136 |
138 return spellMenu |
137 # add management entry |
139 |
138 act = spellMenu.addAction(QCoreApplication.translate( |
140 return None |
139 "SpellCheckMixin", "Add to Dictionary")) |
|
140 act.setData((cursor, text, "add")) |
|
141 |
|
142 spellMenu.triggered.connect(self.__spellMenuTriggered) |
|
143 return spellMenu |
|
144 |
|
145 def __addRemoveEntry(self, cursor, menu): |
|
146 """ |
|
147 Private method to create a menu entry to remove the word at the |
|
148 menu position. |
|
149 |
|
150 @param cursor reference to the text cursor for the misspelled word |
|
151 @type QTextCursor |
|
152 @param menu reference to the context menu |
|
153 @type QMenu |
|
154 """ |
|
155 if cursor is None: |
|
156 return |
|
157 |
|
158 text = cursor.selectedText() |
|
159 menu.addAction(QCoreApplication.translate( |
|
160 "SpellCheckMixin", |
|
161 "Remove '{0}' from Dictionary").format(text), |
|
162 lambda: self.__addToUserDict(text, "remove")) |
141 |
163 |
142 def __createLanguagesMenu(self, parent=None): |
164 def __createLanguagesMenu(self, parent=None): |
143 """ |
165 """ |
144 Private method to create a menu for selecting the spell-check |
166 Private method to create a menu for selecting the spell-check |
145 language. |
167 language. |
160 act.setCheckable(True) |
182 act.setCheckable(True) |
161 act.setChecked(language.lower() == curLanguage) |
183 act.setChecked(language.lower() == curLanguage) |
162 act.setData(language) |
184 act.setData(language) |
163 languageMenu.addAction(act) |
185 languageMenu.addAction(act) |
164 |
186 |
165 # TODO: add action to add a word to the lists |
|
166 languageMenu.triggered.connect(self.__setLanguage) |
187 languageMenu.triggered.connect(self.__setLanguage) |
167 return languageMenu |
188 return languageMenu |
168 |
189 |
169 def __createFormatsMenu(self, parent=None): |
190 def __createFormatsMenu(self, parent=None): |
170 """ |
191 """ |
194 formatMenu.addAction(act) |
215 formatMenu.addAction(act) |
195 |
216 |
196 formatMenu.triggered.connect(self.__setFormat) |
217 formatMenu.triggered.connect(self.__setFormat) |
197 return formatMenu |
218 return formatMenu |
198 |
219 |
|
220 def __cursorForPosition(self, pos): |
|
221 """ |
|
222 Private method to create a text cursor selecting the word at the |
|
223 given position. |
|
224 |
|
225 @param pos position of the misspelled word |
|
226 @type QPoint |
|
227 @return text cursor for the word |
|
228 @rtype QTextCursor |
|
229 """ |
|
230 cursor = self.cursorForPosition(pos) |
|
231 cursor.select(QTextCursor.SelectionType.WordUnderCursor) |
|
232 |
|
233 if cursor.hasSelection(): |
|
234 return cursor |
|
235 else: |
|
236 return None |
|
237 |
199 def __cursorForMisspelling(self, pos): |
238 def __cursorForMisspelling(self, pos): |
200 """ |
239 """ |
201 Private method to create a text cursor selecting the misspelled |
240 Private method to create a text cursor selecting the misspelled |
202 word. |
241 word. |
203 |
242 |
224 if cursor.hasSelection(): |
263 if cursor.hasSelection(): |
225 return cursor |
264 return cursor |
226 else: |
265 else: |
227 return None |
266 return None |
228 |
267 |
229 @pyqtSlot(QAction) |
268 def __correctWord(self, cursor, word): |
230 def __correctWord(self, act): |
269 """ |
231 """ |
270 Private method to replace some misspelled text. |
232 Private slot to correct the misspelled word with the selected |
271 |
233 correction. |
272 @param cursor reference to the text cursor for the misspelled word |
234 |
273 @type QTextCursor |
235 @param act reference to the selected action |
274 @param word replacement text |
236 @type QAction |
275 @type str |
237 """ |
276 """ |
238 cursor, word = act.data() |
|
239 |
|
240 cursor.beginEditBlock() |
277 cursor.beginEditBlock() |
241 cursor.removeSelectedText() |
278 cursor.removeSelectedText() |
242 cursor.insertText(word) |
279 cursor.insertText(word) |
243 cursor.endEditBlock() |
280 cursor.endEditBlock() |
|
281 |
|
282 def __addToUserDict(self, word, command): |
|
283 """ |
|
284 Private method to add a word to the user word or exclude list. |
|
285 |
|
286 @param word text to be added |
|
287 @type str |
|
288 @param command command indicating the user dictionary type |
|
289 @type str |
|
290 """ |
|
291 if word: |
|
292 dictionary = self.__highlighter.dict() |
|
293 if command == "add": |
|
294 dictionary.add(word) |
|
295 elif command == "remove": |
|
296 dictionary.remove(word) |
|
297 |
|
298 self.__highlighter.rehighlight() |
|
299 |
|
300 @pyqtSlot(QAction) |
|
301 def __spellMenuTriggered(self, act): |
|
302 """ |
|
303 Private slot to handle a selection of the spell menu. |
|
304 |
|
305 @param act reference to the selected action |
|
306 @type QAction |
|
307 """ |
|
308 data = act.data() |
|
309 if len(data) == 2: |
|
310 # replace the misspelled word |
|
311 self.__correctWord(*data) |
|
312 |
|
313 elif len(data) == 3: |
|
314 # dictionary management action |
|
315 _, word, command = data |
|
316 self.__addToUserDict(word, command) |
244 |
317 |
245 @pyqtSlot(QAction) |
318 @pyqtSlot(QAction) |
246 def __setLanguage(self, act): |
319 def __setLanguage(self, act): |
247 """ |
320 """ |
248 Private slot to set the selected language. |
321 Private slot to set the selected language. |
298 @type emchant.Dict |
371 @type emchant.Dict |
299 """ |
372 """ |
300 self.__highlighter.setDict(spellDict) |
373 self.__highlighter.setDict(spellDict) |
301 |
374 |
302 @classmethod |
375 @classmethod |
303 def setDefaultLanguage(cls, language, pwl="", pel=""): |
376 def setDefaultLanguage(cls, language, pwl=None, pel=None): |
304 """ |
377 """ |
305 Class method to set the default spell-check language. |
378 Class method to set the default spell-check language. |
306 |
379 |
307 @param language language to be set as default |
380 @param language language to be set as default |
308 @type str |
381 @type str |
309 @param pwl file name of the personal word list |
382 @param pwl file name of the personal word list |
310 @type str |
383 @type str |
311 @param pel name of the personal exclude list |
384 @param pel file name of the personal exclude list |
312 @type str |
385 @type str |
313 """ |
386 """ |
314 with contextlib.suppress(DictNotFoundError): |
387 with contextlib.suppress(DictNotFoundError): |
315 cls.DefaultUserWordList = pwl |
388 cls.DefaultUserWordList = pwl |
316 cls.DefaultUserExceptionList = pel |
389 cls.DefaultUserExceptionList = pel |
465 EricSpellCheckedPlainTextEdit = QPlainTextEdit |
538 EricSpellCheckedPlainTextEdit = QPlainTextEdit |
466 EricSpellCheckedTextEdit = QTextEdit |
539 EricSpellCheckedTextEdit = QTextEdit |
467 |
540 |
468 if __name__ == '__main__': |
541 if __name__ == '__main__': |
469 import sys |
542 import sys |
|
543 import os |
470 from PyQt6.QtWidgets import QApplication |
544 from PyQt6.QtWidgets import QApplication |
471 |
545 |
472 if ENCHANT_AVAILABLE: |
546 if ENCHANT_AVAILABLE: |
473 SpellCheckMixin.setDefaultLanguage("en_US") |
547 dictPath = os.path.expanduser(os.path.join("~", ".eric7", "spelling")) |
|
548 SpellCheckMixin.setDefaultLanguage( |
|
549 "en_US", |
|
550 os.path.join(dictPath, "pwl.dic"), |
|
551 os.path.join(dictPath, "pel.dic") |
|
552 ) |
474 |
553 |
475 app = QApplication(sys.argv) |
554 app = QApplication(sys.argv) |
476 spellEdit = EricSpellCheckedPlainTextEdit() |
555 spellEdit = EricSpellCheckedPlainTextEdit() |
477 spellEdit.show() |
556 spellEdit.show() |
478 |
557 |