src/eric7/Plugins/UiExtensionPlugins/Translator/TranslatorWidget.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9148
b31f0d894b55
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the translator widget.
8 """
9
10 from PyQt6.QtCore import pyqtSlot, QTemporaryFile
11 from PyQt6.QtWidgets import QWidget
12 try:
13 from PyQt6.QtMultimedia import QMediaFormat, QMediaPlayer, QAudioOutput
14 MULTIMEDIA_AVAILABLE = True
15 except ImportError:
16 MULTIMEDIA_AVAILABLE = False
17
18 from EricWidgets import EricMessageBox
19 from EricWidgets.EricApplication import ericApp
20
21 from .Ui_TranslatorWidget import Ui_TranslatorWidget
22
23 from .TranslatorLanguagesDb import TranslatorLanguagesDb
24 from . import TranslatorEngines
25
26 import UI.PixmapCache
27
28
29 class TranslatorWidget(QWidget, Ui_TranslatorWidget):
30 """
31 Class implementing the translator widget.
32 """
33 def __init__(self, plugin, translator, parent=None):
34 """
35 Constructor
36
37 @param plugin reference to the plugin object
38 @type TranslatorPlugin
39 @param translator reference to the translator object
40 @type Translator
41 @param parent reference to the parent widget
42 @type QWidget
43 """
44 super().__init__(parent)
45 self.setupUi(self)
46
47 self.__plugin = plugin
48 self.__translator = translator
49
50 self.__languages = TranslatorLanguagesDb(self)
51
52 self.__translatorRequest = None
53 self.__translationEngine = None
54
55 self.__audioOutput = None
56 self.__mediaPlayer = None
57 self.__mediaFile = None
58
59 audioAvailable = False
60 if MULTIMEDIA_AVAILABLE:
61 mediaFormat = QMediaFormat()
62 audioAvailable = (
63 QMediaFormat.AudioCodec.MP3 in
64 mediaFormat.supportedAudioCodecs(
65 QMediaFormat.ConversionMode.Decode)
66 )
67 self.pronounceOrigButton.setVisible(audioAvailable)
68 self.pronounceTransButton.setVisible(audioAvailable)
69
70 self.pronounceOrigButton.setIcon(
71 self.__translator.getAppIcon("pronounce"))
72 self.pronounceTransButton.setIcon(
73 self.__translator.getAppIcon("pronounce"))
74 self.swapButton.setIcon(
75 self.__translator.getAppIcon("swap"))
76 self.translateButton.setIcon(
77 self.__translator.getAppIcon("translate"))
78 self.clearButton.setIcon(UI.PixmapCache.getIcon("editDelete"))
79 self.preferencesButton.setIcon(UI.PixmapCache.getIcon("configure"))
80
81 self.translateButton.setEnabled(False)
82 self.clearButton.setEnabled(False)
83 self.pronounceOrigButton.setEnabled(False)
84 self.pronounceTransButton.setEnabled(False)
85
86 selectedEngine = self.__plugin.getPreferences("SelectedEngine")
87
88 self.__updateEngines()
89 engineIndex = self.engineComboBox.findData(selectedEngine)
90 self.engineComboBox.setCurrentIndex(engineIndex)
91 self.__engineComboBoxCurrentIndexChanged(engineIndex)
92
93 self.engineComboBox.currentIndexChanged.connect(
94 self.__engineComboBoxCurrentIndexChanged)
95 self.__plugin.updateLanguages.connect(self.__updateLanguages)
96
97 def __updateLanguages(self):
98 """
99 Private slot to update the language combo boxes.
100 """
101 self.__ensureTranslationEngineReady()
102 if self.__translationEngine is not None:
103 supportedCodes = self.__translationEngine.supportedLanguages()
104 enabledCodes = self.__plugin.getPreferences("EnabledLanguages")
105
106 # 1. save current selections
107 origLanguage = self.origLanguageComboBox.itemData(
108 self.origLanguageComboBox.currentIndex())
109
110 # 2. reload the original language combo box
111 self.origLanguageComboBox.blockSignals(True)
112 self.origLanguageComboBox.clear()
113 for code in enabledCodes:
114 if code in supportedCodes:
115 language = self.__languages.getLanguage(code)
116 if language:
117 icon = self.__languages.getLanguageIcon(code)
118 self.origLanguageComboBox.addItem(
119 icon, language, code)
120 self.origLanguageComboBox.model().sort(0)
121 origIndex = self.origLanguageComboBox.findData(origLanguage)
122 if origIndex == -1:
123 origIndex = 0
124 self.origLanguageComboBox.blockSignals(False)
125 self.origLanguageComboBox.setCurrentIndex(origIndex)
126
127 def __updateEngines(self):
128 """
129 Private slot to update the engines combo box.
130 """
131 currentEngine = self.engineComboBox.itemData(
132 self.engineComboBox.currentIndex())
133 self.engineComboBox.clear()
134 for engineName in TranslatorEngines.supportedEngineNames():
135 icon = TranslatorEngines.getEngineIcon(engineName)
136 self.engineComboBox.addItem(
137 icon,
138 TranslatorEngines.engineDisplayName(engineName),
139 engineName)
140 self.engineComboBox.model().sort(0)
141 self.engineComboBox.setCurrentIndex(
142 self.engineComboBox.findData(currentEngine))
143
144 def __originalLanguage(self):
145 """
146 Private method to return the code of the selected original language.
147
148 @return code of the original language
149 @rtype str
150 """
151 return self.origLanguageComboBox.itemData(
152 self.origLanguageComboBox.currentIndex())
153
154 def __translationLanguage(self):
155 """
156 Private method to return the code of the selected translation language.
157
158 @return code of the translation language
159 @rtype str
160 """
161 return self.transLanguageComboBox.itemData(
162 self.transLanguageComboBox.currentIndex())
163
164 @pyqtSlot()
165 def on_translateButton_clicked(self):
166 """
167 Private slot to translate the entered text.
168 """
169 self.transEdit.clear()
170 result, ok = self.__translate(
171 self.origEdit.toPlainText(),
172 self.__originalLanguage(),
173 self.__translationLanguage())
174 if ok:
175 self.transEdit.setHtml(result)
176 else:
177 EricMessageBox.critical(
178 self,
179 self.tr("Translation Error"),
180 result)
181
182 @pyqtSlot()
183 def on_pronounceOrigButton_clicked(self):
184 """
185 Private slot to pronounce the original text.
186 """
187 self.__pronounce(
188 self.origEdit.toPlainText(), self.__originalLanguage())
189
190 @pyqtSlot()
191 def on_pronounceTransButton_clicked(self):
192 """
193 Private slot to pronounce the translated text.
194 """
195 self.__pronounce(
196 self.transEdit.toPlainText(), self.__translationLanguage())
197
198 @pyqtSlot()
199 def on_swapButton_clicked(self):
200 """
201 Private slot to swap the languages.
202 """
203 # save selected language codes
204 oLanguage = self.origLanguageComboBox.itemData(
205 self.origLanguageComboBox.currentIndex())
206
207 tLanguage = self.transLanguageComboBox.itemData(
208 self.transLanguageComboBox.currentIndex())
209
210 oIdx = self.origLanguageComboBox.findData(tLanguage)
211 if oIdx < 0:
212 oIdx = 0
213 self.origLanguageComboBox.setCurrentIndex(oIdx)
214
215 tIdx = self.transLanguageComboBox.findData(oLanguage)
216 if tIdx < 0:
217 tIdx = 0
218 self.transLanguageComboBox.setCurrentIndex(tIdx)
219
220 origText = self.origEdit.toPlainText()
221 self.origEdit.setPlainText(self.transEdit.toPlainText())
222 self.transEdit.setPlainText(origText)
223
224 @pyqtSlot()
225 def on_clearButton_clicked(self):
226 """
227 Private slot to clear the text fields.
228 """
229 self.origEdit.clear()
230 self.transEdit.clear()
231
232 @pyqtSlot()
233 def on_origEdit_textChanged(self):
234 """
235 Private slot to handle changes of the original text.
236 """
237 self.__updatePronounceButtons()
238 self.__updateClearButton()
239 self.__updateTranslateButton()
240
241 @pyqtSlot()
242 def on_transEdit_textChanged(self):
243 """
244 Private slot to handle changes of the translation text.
245 """
246 self.__updatePronounceButtons()
247 self.__updateClearButton()
248
249 @pyqtSlot(int)
250 def on_origLanguageComboBox_currentIndexChanged(self, index):
251 """
252 Private slot to handle the selection of the original language.
253
254 @param index current index
255 @type int
256 """
257 self.__plugin.setPreferences(
258 "OriginalLanguage", self.origLanguageComboBox.itemData(index))
259
260 supportedTargetCodes = (
261 self.__translationEngine.supportedTargetLanguages(
262 self.origLanguageComboBox.itemData(index)
263 )
264 )
265 if supportedTargetCodes is not None:
266 enabledCodes = self.__plugin.getPreferences("EnabledLanguages")
267 transLanguage = self.transLanguageComboBox.itemData(
268 self.transLanguageComboBox.currentIndex())
269 self.transLanguageComboBox.clear()
270 if len(supportedTargetCodes) > 0:
271 for code in enabledCodes:
272 if code in supportedTargetCodes:
273 language = self.__languages.getLanguage(code)
274 if language:
275 icon = self.__languages.getLanguageIcon(code)
276 self.transLanguageComboBox.addItem(
277 icon, language, code)
278 self.transLanguageComboBox.model().sort(0)
279 index = self.transLanguageComboBox.findData(transLanguage)
280 if index == -1:
281 index = 0
282 self.transLanguageComboBox.setCurrentIndex(index)
283
284 self.__updateTranslateButton()
285
286 @pyqtSlot(int)
287 def on_transLanguageComboBox_currentIndexChanged(self, index):
288 """
289 Private slot to handle the selection of the translation language.
290
291 @param index current index
292 @type int
293 """
294 self.__plugin.setPreferences(
295 "TranslationLanguage", self.transLanguageComboBox.itemData(index))
296
297 @pyqtSlot()
298 def __availableTranslationsLoaded(self):
299 """
300 Private slot to handle the availability of translations.
301 """
302 origLanguage = self.__plugin.getPreferences("OriginalLanguage")
303 transLanguage = self.__plugin.getPreferences("TranslationLanguage")
304
305 self.__updateLanguages()
306
307 origIndex = self.origLanguageComboBox.findData(origLanguage)
308 self.origLanguageComboBox.setCurrentIndex(origIndex)
309 self.on_origLanguageComboBox_currentIndexChanged(origIndex)
310 self.transLanguageComboBox.setCurrentIndex(
311 self.transLanguageComboBox.findData(transLanguage))
312
313 def __ensureTranslationEngineReady(self):
314 """
315 Private slot to ensure, that the currently selected translation engine
316 is ready.
317 """
318 engineName = self.engineComboBox.itemData(
319 self.engineComboBox.currentIndex())
320 if (
321 self.__translationEngine is not None and
322 self.__translationEngine.engineName() != engineName
323 ):
324 self.__translationEngine.availableTranslationsLoaded.disconnect(
325 self.__availableTranslationsLoaded)
326 self.__translationEngine.deleteLater()
327 self.__translationEngine = None
328
329 if self.__translationEngine is None:
330 self.__translationEngine = TranslatorEngines.getTranslationEngine(
331 engineName, self.__plugin, self)
332 if self.__translationEngine is not None:
333 self.__translationEngine.availableTranslationsLoaded.connect(
334 self.__availableTranslationsLoaded)
335
336 @pyqtSlot(int)
337 def __engineComboBoxCurrentIndexChanged(self, index):
338 """
339 Private slot to handle the selection of a translation service.
340
341 @param index current index
342 @type int
343 """
344 self.__ensureTranslationEngineReady()
345 if self.__translationEngine is not None:
346 self.__updateTranslateButton()
347 self.__updatePronounceButtons()
348
349 self.__plugin.setPreferences(
350 "SelectedEngine", self.engineComboBox.itemData(index))
351
352 def __updatePronounceButtons(self):
353 """
354 Private slot to set the state of the pronounce buttons.
355 """
356 hasTTS = self.__translationEngine and self.__translationEngine.hasTTS()
357 self.pronounceOrigButton.setEnabled(
358 hasTTS and bool(self.origEdit.toPlainText()))
359 self.pronounceTransButton.setEnabled(
360 hasTTS and bool(self.transEdit.toPlainText()))
361
362 def __updateClearButton(self):
363 """
364 Private slot to set the state of the clear button.
365 """
366 enable = (
367 bool(self.origEdit.toPlainText()) or
368 bool(self.transEdit.toPlainText())
369 )
370 self.clearButton.setEnabled(enable)
371
372 def __updateTranslateButton(self):
373 """
374 Private slot to set the state of the translate button.
375 """
376 enable = bool(self.origEdit.toPlainText())
377 enable &= bool(self.__translationLanguage())
378 enable &= bool(self.__originalLanguage())
379 self.translateButton.setEnabled(enable)
380
381 def __translate(self, text, originalLanguage, translationLanguage):
382 """
383 Private method to translate the given text.
384
385 @param text text to be translated
386 @type str
387 @param originalLanguage language code of the original
388 @type str
389 @param translationLanguage language code of the translation
390 @type str
391 @return tuple of translated text and flag indicating success
392 @rtype tuple of (str, bool)
393 """
394 if self.__translatorRequest is None:
395 from .TranslatorRequest import TranslatorRequest
396 self.__translatorRequest = TranslatorRequest(self)
397
398 self.__ensureTranslationEngineReady()
399 if self.__translationEngine is None:
400 return "", False
401 else:
402 result, ok = self.__translationEngine.getTranslation(
403 self.__translatorRequest, text, originalLanguage,
404 translationLanguage)
405
406 return result, ok
407
408 def __pronounce(self, text, language):
409 """
410 Private method to pronounce the given text.
411
412 @param text text to be pronounced
413 @type str
414 @param language language code of the text
415 @type str
416 """
417 if not text or not language:
418 return
419
420 if self.__translatorRequest is None:
421 from .TranslatorRequest import TranslatorRequest
422 self.__translatorRequest = TranslatorRequest(self)
423
424 if self.__mediaPlayer is None:
425 self.__mediaPlayer = QMediaPlayer(self)
426 self.__audioOutput = QAudioOutput(self)
427 self.__mediaPlayer.setAudioOutput(self.__audioOutput)
428
429 self.__mediaPlayer.playbackStateChanged.connect(
430 self.__mediaPlayerPlaybackStateChanged)
431 self.__mediaPlayer.errorOccurred.connect(
432 self.__mediaPlayerError)
433
434 if (
435 self.__mediaPlayer.playbackState() ==
436 QMediaPlayer.PlaybackState.PlayingState
437 ):
438 return
439
440 self.__ensureTranslationEngineReady()
441 if self.__translationEngine is not None:
442 if not self.__translationEngine.hasTTS():
443 EricMessageBox.critical(
444 self,
445 self.tr("Translation Error"),
446 self.tr("The selected translation service does not"
447 " support the Text-to-Speech function."))
448 return
449
450 data, ok = self.__translationEngine.getTextToSpeechData(
451 self.__translatorRequest, text, language)
452 if ok:
453 self.__mediaFile = QTemporaryFile(self)
454 self.__mediaFile.open()
455 self.__mediaFile.setAutoRemove(False)
456 self.__mediaFile.write(data)
457 self.__mediaFile.close()
458
459 from PyQt6.QtCore import QUrl
460 self.__mediaPlayer.setSource(
461 QUrl.fromLocalFile(self.__mediaFile.fileName()))
462 self.__mediaPlayer.play()
463 else:
464 EricMessageBox.critical(
465 self,
466 self.tr("Translation Error"),
467 data)
468
469 def __mediaPlayerPlaybackStateChanged(self, state):
470 """
471 Private slot handling changes of the media player state.
472
473 @param state media player state
474 @type QMediaPlayer.PlaybackState
475 """
476 if state == QMediaPlayer.PlaybackState.StoppedState:
477 self.__mediaFile.close()
478 self.__mediaFile.remove()
479 self.__mediaFile = None
480
481 def __mediaPlayerError(self, error, errorString):
482 """
483 Private slot to handle errors during playback of the data.
484
485 @param error media player error condition
486 @type QMediaPlayer.Error
487 @param errorString string representation for the error
488 @type str
489 """
490 if error != QMediaPlayer.Error.NoError:
491 EricMessageBox.warning(
492 self,
493 self.tr("Error playing pronunciation"),
494 self.tr("<p>The received pronunciation could not be played."
495 "</p><p>Reason: {0}</p>").format(errorString)
496 )
497
498 @pyqtSlot()
499 def on_preferencesButton_clicked(self):
500 """
501 Private slot to open the Translator configuration page.
502 """
503 ericApp().getObject("UserInterface").showPreferences("translatorPage")

eric ide

mercurial