eric7/Plugins/UiExtensionPlugins/Translator/TranslatorWidget.py

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

eric ide

mercurial