eric6/Project/ProjectTranslationsBrowser.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7192
a22eee00b052
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a class used to display the translations part of the
8 project.
9 """
10
11 from __future__ import unicode_literals
12 try:
13 str = unicode
14 except NameError:
15 pass
16
17 import os
18 import shutil
19 import fnmatch
20
21 from PyQt5.QtCore import PYQT_VERSION, pyqtSignal, QProcess
22 from PyQt5.QtWidgets import QDialog, QMenu
23
24 from E5Gui import E5MessageBox
25 from E5Gui.E5Application import e5App
26
27 from .ProjectBrowserModel import ProjectBrowserFileItem, \
28 ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem, \
29 ProjectBrowserTranslationType
30 from .ProjectBaseBrowser import ProjectBaseBrowser
31
32 import UI.PixmapCache
33
34 import Preferences
35 import Utilities
36
37
38 class ProjectTranslationsBrowser(ProjectBaseBrowser):
39 """
40 A class used to display the translations part of the project.
41
42 @signal appendStdout(str) emitted after something was received from
43 a QProcess on stdout
44 @signal appendStderr(str) emitted after something was received from
45 a QProcess on stderr
46 @signal showMenu(str, QMenu) emitted when a menu is about to be shown.
47 The name of the menu and a reference to the menu are given.
48 """
49 appendStdout = pyqtSignal(str)
50 appendStderr = pyqtSignal(str)
51 showMenu = pyqtSignal(str, QMenu)
52
53 def __init__(self, project, parent=None):
54 """
55 Constructor
56
57 @param project reference to the project object
58 @param parent parent widget of this browser (QWidget)
59 """
60 ProjectBaseBrowser.__init__(self, project,
61 ProjectBrowserTranslationType, parent)
62 self.isTranslationsBrowser = True
63
64 self.selectedItemsFilter = \
65 [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem]
66
67 self.setWindowTitle(self.tr('Translations'))
68
69 self.setWhatsThis(self.tr(
70 """<b>Project Translations Browser</b>"""
71 """<p>This allows to easily see all translations contained in"""
72 """ the current project. Several actions can be executed via"""
73 """ the context menu.</p>"""
74 ))
75
76 self.__lreleaseProcesses = []
77 self.__pylupdateProcesses = []
78 self.lreleaseProcRunning = False
79 self.pylupdateProcRunning = False
80 self.__tmpProjects = []
81
82 def _createPopupMenus(self):
83 """
84 Protected overloaded method to generate the popup menu.
85 """
86 self.menuActions = []
87 self.multiMenuActions = []
88 self.dirMenuActions = []
89 self.dirMultiMenuActions = []
90
91 self.tsMenuActions = []
92 self.qmMenuActions = []
93 self.tsprocMenuActions = []
94 self.qmprocMenuActions = []
95
96 self.tsMultiMenuActions = []
97 self.qmMultiMenuActions = []
98 self.tsprocMultiMenuActions = []
99 self.qmprocMultiMenuActions = []
100
101 self.tsprocDirMenuActions = []
102 self.qmprocDirMenuActions = []
103
104 self.tsprocBackMenuActions = []
105 self.qmprocBackMenuActions = []
106
107 self.menu = QMenu(self)
108 if self.project.getProjectType() in \
109 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
110 "PySide", "PySideC", "PySide2", "PySideC2"]:
111 act = self.menu.addAction(
112 self.tr('Generate translation'), self.__generateSelected)
113 self.tsMenuActions.append(act)
114 self.tsprocMenuActions.append(act)
115 act = self.menu.addAction(
116 self.tr('Generate translation (with obsolete)'),
117 self.__generateObsoleteSelected)
118 self.tsMenuActions.append(act)
119 self.tsprocMenuActions.append(act)
120 act = self.menu.addAction(
121 self.tr('Generate all translations'), self.__generateAll)
122 self.tsprocMenuActions.append(act)
123 act = self.menu.addAction(
124 self.tr('Generate all translations (with obsolete)'),
125 self.__generateObsoleteAll)
126 self.tsprocMenuActions.append(act)
127 self.menu.addSeparator()
128 act = self.menu.addAction(
129 self.tr('Open in Qt-Linguist'), self._openItem)
130 self.tsMenuActions.append(act)
131 act = self.menu.addAction(
132 self.tr('Open in Editor'), self.__openFileInEditor)
133 self.tsMenuActions.append(act)
134 self.menu.addSeparator()
135 act = self.menu.addAction(
136 self.tr('Release translation'), self.__releaseSelected)
137 self.tsMenuActions.append(act)
138 self.qmprocMenuActions.append(act)
139 act = self.menu.addAction(
140 self.tr('Release all translations'), self.__releaseAll)
141 self.qmprocMenuActions.append(act)
142 self.menu.addSeparator()
143 act = self.menu.addAction(
144 self.tr('Preview translation'), self.__TRPreview)
145 self.qmMenuActions.append(act)
146 act = self.menu.addAction(
147 self.tr('Preview all translations'), self.__TRPreviewAll)
148 self.menu.addSeparator()
149 else:
150 if self.hooks["extractMessages"] is not None:
151 act = self.menu.addAction(
152 self.hooksMenuEntries.get(
153 "extractMessages",
154 self.tr('Extract messages')),
155 self.__extractMessages)
156 self.menuActions.append(act)
157 self.menu.addSeparator()
158 if self.hooks["generateSelected"] is not None:
159 act = self.menu.addAction(
160 self.hooksMenuEntries.get(
161 "generateSelected",
162 self.tr('Generate translation')),
163 self.__generateSelected)
164 self.tsMenuActions.append(act)
165 self.tsprocMenuActions.append(act)
166 if self.hooks["generateSelectedWithObsolete"] is not None:
167 act = self.menu.addAction(
168 self.hooksMenuEntries.get(
169 "generateSelectedWithObsolete",
170 self.tr('Generate translation (with obsolete)')),
171 self.__generateObsoleteSelected)
172 self.tsMenuActions.append(act)
173 self.tsprocMenuActions.append(act)
174 if self.hooks["generateAll"] is not None:
175 act = self.menu.addAction(
176 self.hooksMenuEntries.get(
177 "generateAll",
178 self.tr('Generate all translations')),
179 self.__generateAll)
180 self.tsprocMenuActions.append(act)
181 if self.hooks["generateAllWithObsolete"] is not None:
182 act = self.menu.addAction(
183 self.hooksMenuEntries.get(
184 "generateAllWithObsolete",
185 self.tr(
186 'Generate all translations (with obsolete)')),
187 self.__generateObsoleteAll)
188 self.tsprocMenuActions.append(act)
189 self.menu.addSeparator()
190 if self.hooks["open"] is not None:
191 act = self.menu.addAction(
192 self.hooksMenuEntries.get(
193 "open", self.tr('Open')),
194 self._openItem)
195 self.tsMenuActions.append(act)
196 act = self.menu.addAction(
197 self.tr('Open in Editor'), self.__openFileInEditor)
198 self.tsMenuActions.append(act)
199 self.menu.addSeparator()
200 if self.hooks["releaseSelected"] is not None:
201 act = self.menu.addAction(
202 self.hooksMenuEntries.get(
203 "releaseSelected",
204 self.tr('Release translation')),
205 self.__releaseSelected)
206 self.tsMenuActions.append(act)
207 self.qmprocMenuActions.append(act)
208 if self.hooks["releaseAll"] is not None:
209 act = self.menu.addAction(
210 self.hooksMenuEntries.get(
211 "releaseAll",
212 self.tr('Release all translations')),
213 self.__releaseAll)
214 self.qmprocMenuActions.append(act)
215 self.menu.addSeparator()
216 act = self.menu.addAction(
217 self.tr('Remove from project'), self.__removeLanguageFile)
218 self.menuActions.append(act)
219 act = self.menu.addAction(
220 self.tr('Delete'), self.__deleteLanguageFile)
221 self.menuActions.append(act)
222 self.menu.addSeparator()
223 self.__addTranslationAct = self.menu.addAction(
224 self.tr('Add translation...'), self.project.addLanguage)
225 self.menu.addAction(
226 self.tr('Add translation files...'),
227 self.__addTranslationFiles)
228 self.menu.addSeparator()
229 self.menu.addAction(
230 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
231 self.menu.addSeparator()
232 self.menu.addAction(self.tr('Configure...'), self._configure)
233
234 self.backMenu = QMenu(self)
235 if self.project.getProjectType() in \
236 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
237 "PySide", "PySideC", "PySide2", "PySideC2"]:
238 act = self.backMenu.addAction(
239 self.tr('Generate all translations'),
240 self.__generateAll)
241 self.tsprocBackMenuActions.append(act)
242 act = self.backMenu.addAction(
243 self.tr('Generate all translations (with obsolete)'),
244 self.__generateObsoleteAll)
245 self.tsprocBackMenuActions.append(act)
246 act = self.backMenu.addAction(
247 self.tr('Release all translations'),
248 self.__releaseAll)
249 self.qmprocBackMenuActions.append(act)
250 self.backMenu.addSeparator()
251 act = self.backMenu.addAction(
252 self.tr('Preview all translations'),
253 self.__TRPreview)
254 else:
255 if self.hooks["extractMessages"] is not None:
256 act = self.backMenu.addAction(
257 self.hooksMenuEntries.get(
258 "extractMessages",
259 self.tr('Extract messages')),
260 self.__extractMessages)
261 self.backMenu.addSeparator()
262 if self.hooks["generateAll"] is not None:
263 act = self.backMenu.addAction(
264 self.hooksMenuEntries.get(
265 "generateAll",
266 self.tr('Generate all translations')),
267 self.__generateAll)
268 self.tsprocBackMenuActions.append(act)
269 if self.hooks["generateAllWithObsolete"] is not None:
270 act = self.backMenu.addAction(
271 self.hooksMenuEntries.get(
272 "generateAllWithObsolete",
273 self.tr(
274 'Generate all translations (with obsolete)')),
275 self.__generateObsoleteAll)
276 self.tsprocBackMenuActions.append(act)
277 if self.hooks["releaseAll"] is not None:
278 act = self.backMenu.addAction(
279 self.hooksMenuEntries.get(
280 "releaseAll",
281 self.tr('Release all translations')),
282 self.__releaseAll)
283 self.qmprocBackMenuActions.append(act)
284 self.backMenu.addSeparator()
285 self.__addTranslationBackAct = self.backMenu.addAction(
286 self.tr('Add translation...'), self.project.addLanguage)
287 self.backMenu.addAction(
288 self.tr('Add translation files...'),
289 self.__addTranslationFiles)
290 self.backMenu.addSeparator()
291 self.backMenu.addAction(self.tr('Configure...'), self._configure)
292 self.backMenu.setEnabled(False)
293
294 # create the menu for multiple selected files
295 self.multiMenu = QMenu(self)
296 if self.project.getProjectType() in \
297 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
298 "PySide", "PySideC", "PySide2", "PySideC2"]:
299 act = self.multiMenu.addAction(
300 self.tr('Generate translations'),
301 self.__generateSelected)
302 self.tsMultiMenuActions.append(act)
303 self.tsprocMultiMenuActions.append(act)
304 act = self.multiMenu.addAction(
305 self.tr('Generate translations (with obsolete)'),
306 self.__generateObsoleteSelected)
307 self.tsMultiMenuActions.append(act)
308 self.tsprocMultiMenuActions.append(act)
309 self.multiMenu.addSeparator()
310 act = self.multiMenu.addAction(
311 self.tr('Open in Qt-Linguist'), self._openItem)
312 self.tsMultiMenuActions.append(act)
313 act = self.multiMenu.addAction(
314 self.tr('Open in Editor'), self.__openFileInEditor)
315 self.tsMultiMenuActions.append(act)
316 self.multiMenu.addSeparator()
317 act = self.multiMenu.addAction(
318 self.tr('Release translations'), self.__releaseSelected)
319 self.tsMultiMenuActions.append(act)
320 self.qmprocMultiMenuActions.append(act)
321 self.multiMenu.addSeparator()
322 act = self.multiMenu.addAction(
323 self.tr('Preview translations'), self.__TRPreview)
324 self.qmMultiMenuActions.append(act)
325 else:
326 if self.hooks["extractMessages"] is not None:
327 act = self.multiMenu.addAction(
328 self.hooksMenuEntries.get(
329 "extractMessages",
330 self.tr('Extract messages')),
331 self.__extractMessages)
332 self.multiMenuActions.append(act)
333 self.multiMenu.addSeparator()
334 if self.hooks["generateSelected"] is not None:
335 act = self.multiMenu.addAction(
336 self.hooksMenuEntries.get(
337 "generateSelected",
338 self.tr('Generate translations')),
339 self.__generateSelected)
340 self.tsMultiMenuActions.append(act)
341 self.tsprocMultiMenuActions.append(act)
342 if self.hooks["generateSelectedWithObsolete"] is not None:
343 act = self.multiMenu.addAction(
344 self.hooksMenuEntries.get(
345 "generateSelectedWithObsolete",
346 self.tr('Generate translations (with obsolete)')),
347 self.__generateObsoleteSelected)
348 self.tsMultiMenuActions.append(act)
349 self.tsprocMultiMenuActions.append(act)
350 self.multiMenu.addSeparator()
351 if self.hooks["open"] is not None:
352 act = self.multiMenu.addAction(
353 self.hooksMenuEntries.get(
354 "open", self.tr('Open')),
355 self._openItem)
356 self.tsMultiMenuActions.append(act)
357 act = self.multiMenu.addAction(
358 self.tr('Open in Editor'), self.__openFileInEditor)
359 self.tsMultiMenuActions.append(act)
360 self.multiMenu.addSeparator()
361 if self.hooks["releaseSelected"] is not None:
362 act = self.multiMenu.addAction(
363 self.hooksMenuEntries.get(
364 "releaseSelected",
365 self.tr('Release translations')),
366 self.__releaseSelected)
367 self.tsMultiMenuActions.append(act)
368 self.qmprocMultiMenuActions.append(act)
369 self.multiMenu.addSeparator()
370 act = self.multiMenu.addAction(
371 self.tr('Remove from project'), self.__removeLanguageFile)
372 self.multiMenuActions.append(act)
373 act = self.multiMenu.addAction(
374 self.tr('Delete'), self.__deleteLanguageFile)
375 self.multiMenuActions.append(act)
376 self.multiMenu.addSeparator()
377 self.multiMenu.addAction(self.tr('Configure...'), self._configure)
378
379 self.dirMenu = QMenu(self)
380 if self.project.getProjectType() in \
381 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
382 "PySide", "PySideC", "PySide2", "PySideC2"]:
383 act = self.dirMenu.addAction(
384 self.tr('Generate all translations'),
385 self.__generateAll)
386 self.tsprocDirMenuActions.append(act)
387 act = self.dirMenu.addAction(
388 self.tr('Generate all translations (with obsolete)'),
389 self.__generateObsoleteAll)
390 self.tsprocDirMenuActions.append(act)
391 act = self.dirMenu.addAction(
392 self.tr('Release all translations'),
393 self.__releaseAll)
394 self.qmprocDirMenuActions.append(act)
395 self.dirMenu.addSeparator()
396 act = self.dirMenu.addAction(
397 self.tr('Preview all translations'),
398 self.__TRPreview)
399 else:
400 if self.hooks["extractMessages"] is not None:
401 act = self.dirMenu.addAction(
402 self.hooksMenuEntries.get(
403 "extractMessages",
404 self.tr('Extract messages')),
405 self.__extractMessages)
406 self.dirMenuActions.append(act)
407 self.dirMenu.addSeparator()
408 if self.hooks["generateAll"] is not None:
409 act = self.dirMenu.addAction(
410 self.hooksMenuEntries.get(
411 "generateAll",
412 self.tr('Generate all translations')),
413 self.__generateAll)
414 self.tsprocDirMenuActions.append(act)
415 if self.hooks["generateAllWithObsolete"] is not None:
416 act = self.dirMenu.addAction(
417 self.hooksMenuEntries.get(
418 "generateAllWithObsolete",
419 self.tr(
420 'Generate all translations (with obsolete)')),
421 self.__generateObsoleteAll)
422 self.tsprocDirMenuActions.append(act)
423 if self.hooks["releaseAll"] is not None:
424 act = self.dirMenu.addAction(
425 self.hooksMenuEntries.get(
426 "releaseAll",
427 self.tr('Release all translations')),
428 self.__releaseAll)
429 self.qmprocDirMenuActions.append(act)
430 self.dirMenu.addSeparator()
431 act = self.dirMenu.addAction(
432 self.tr('Delete'), self._deleteDirectory)
433 self.dirMenuActions.append(act)
434 self.dirMenu.addSeparator()
435 self.__addTranslationDirAct = self.dirMenu.addAction(
436 self.tr('Add translation...'), self.project.addLanguage)
437 self.dirMenu.addAction(
438 self.tr('Add translation files...'),
439 self.__addTranslationFiles)
440 self.dirMenu.addSeparator()
441 self.dirMenu.addAction(
442 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
443 self.dirMenu.addSeparator()
444 self.dirMenu.addAction(self.tr('Configure...'), self._configure)
445
446 self.dirMultiMenu = None
447
448 self.menu.aboutToShow.connect(self.__showContextMenu)
449 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
450 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
451 self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
452 self.mainMenu = self.menu
453
454 def _contextMenuRequested(self, coord):
455 """
456 Protected slot to show the context menu.
457
458 @param coord the position of the mouse pointer (QPoint)
459 """
460 if not self.project.isOpen():
461 return
462
463 try:
464 categories = self.getSelectedItemsCountCategorized(
465 [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem])
466 cnt = categories["sum"]
467 if cnt <= 1:
468 index = self.indexAt(coord)
469 if index.isValid():
470 self._selectSingleItem(index)
471 categories = self.getSelectedItemsCountCategorized(
472 [ProjectBrowserFileItem,
473 ProjectBrowserSimpleDirectoryItem])
474 cnt = categories["sum"]
475
476 bfcnt = categories[str(ProjectBrowserFileItem)]
477 sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)]
478 if cnt > 1 and cnt == bfcnt:
479 self.multiMenu.popup(self.mapToGlobal(coord))
480 else:
481 index = self.indexAt(coord)
482 if cnt == 1 and index.isValid():
483 if bfcnt == 1:
484 self.menu.popup(self.mapToGlobal(coord))
485 elif sdcnt == 1:
486 self.dirMenu.popup(self.mapToGlobal(coord))
487 else:
488 self.backMenu.popup(self.mapToGlobal(coord))
489 else:
490 self.backMenu.popup(self.mapToGlobal(coord))
491 except Exception:
492 pass
493
494 def __showContextMenu(self):
495 """
496 Private slot called by the menu aboutToShow signal.
497 """
498 if self.project.getProjectType() in \
499 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
500 "PySide", "PySideC", "PySide2", "PySideC2"]:
501 tsFiles = 0
502 qmFiles = 0
503 itmList = self.getSelectedItems()
504 for itm in itmList[:]:
505 if itm.fileName().endswith('.ts'):
506 tsFiles += 1
507 elif itm.fileName().endswith('.qm'):
508 qmFiles += 1
509 if (tsFiles > 0 and qmFiles > 0) or \
510 (tsFiles == 0 and qmFiles == 0):
511 for act in self.tsMenuActions + self.qmMenuActions:
512 act.setEnabled(False)
513 elif tsFiles > 0:
514 for act in self.tsMenuActions:
515 act.setEnabled(True)
516 for act in self.qmMenuActions:
517 act.setEnabled(False)
518 elif qmFiles > 0:
519 for act in self.tsMenuActions:
520 act.setEnabled(False)
521 for act in self.qmMenuActions:
522 act.setEnabled(True)
523 if self.pylupdateProcRunning:
524 for act in self.tsprocMenuActions:
525 act.setEnabled(False)
526 if self.lreleaseProcRunning:
527 for act in self.qmprocMenuActions:
528 act.setEnabled(True)
529 self.__addTranslationAct.setEnabled(
530 self.project.getTranslationPattern() != "")
531
532 ProjectBaseBrowser._showContextMenu(self, self.menu)
533
534 self.showMenu.emit("Main", self.menu)
535
536 def __showContextMenuMulti(self):
537 """
538 Private slot called by the multiMenu aboutToShow signal.
539 """
540 if self.project.getProjectType() in \
541 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
542 "PySide", "PySideC", "PySide2", "PySideC2"]:
543 tsFiles = 0
544 qmFiles = 0
545 itmList = self.getSelectedItems()
546 for itm in itmList[:]:
547 if itm.fileName().endswith('.ts'):
548 tsFiles += 1
549 elif itm.fileName().endswith('.qm'):
550 qmFiles += 1
551 if (tsFiles > 0 and qmFiles > 0) or \
552 (tsFiles == 0 and qmFiles == 0):
553 for act in self.tsMultiMenuActions + self.qmMultiMenuActions:
554 act.setEnabled(False)
555 elif tsFiles > 0:
556 for act in self.tsMultiMenuActions:
557 act.setEnabled(True)
558 for act in self.qmMultiMenuActions:
559 act.setEnabled(False)
560 elif qmFiles > 0:
561 for act in self.tsMultiMenuActions:
562 act.setEnabled(False)
563 for act in self.qmMultiMenuActions:
564 act.setEnabled(True)
565 if self.pylupdateProcRunning:
566 for act in self.tsprocMultiMenuActions:
567 act.setEnabled(False)
568 if self.lreleaseProcRunning:
569 for act in self.qmprocMultiMenuActions:
570 act.setEnabled(True)
571
572 ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)
573
574 self.showMenu.emit("MainMulti", self.multiMenu)
575
576 def __showContextMenuDir(self):
577 """
578 Private slot called by the dirMenu aboutToShow signal.
579 """
580 if self.project.getProjectType() in \
581 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
582 "PySide", "PySideC", "PySide2", "PySideC2"]:
583 if self.pylupdateProcRunning:
584 for act in self.tsprocDirMenuActions:
585 act.setEnabled(False)
586 if self.lreleaseProcRunning:
587 for act in self.qmprocDirMenuActions:
588 act.setEnabled(True)
589 self.__addTranslationDirAct.setEnabled(
590 self.project.getTranslationPattern() != "")
591
592 ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)
593
594 self.showMenu.emit("MainDir", self.dirMenu)
595
596 def __showContextMenuBack(self):
597 """
598 Private slot called by the backMenu aboutToShow signal.
599 """
600 if self.project.getProjectType() in \
601 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
602 "PySide", "PySideC", "PySide2", "PySideC2"]:
603 if self.pylupdateProcRunning:
604 for act in self.tsprocBackMenuActions:
605 act.setEnabled(False)
606 if self.lreleaseProcRunning:
607 for act in self.qmprocBackMenuActions:
608 act.setEnabled(True)
609 self.__addTranslationBackAct.setEnabled(
610 self.project.getTranslationPattern() != "")
611
612 self.showMenu.emit("MainBack", self.backMenu)
613
614 def __addTranslationFiles(self):
615 """
616 Private method to add translation files to the project.
617 """
618 itm = self.model().item(self.currentIndex())
619 if isinstance(itm, ProjectBrowserFileItem):
620 dn = os.path.dirname(itm.fileName())
621 elif isinstance(itm, ProjectBrowserSimpleDirectoryItem) or \
622 isinstance(itm, ProjectBrowserDirectoryItem):
623 dn = itm.dirName()
624 else:
625 dn = None
626 self.project.addFiles('translation', dn)
627
628 def _openItem(self):
629 """
630 Protected slot to handle the open popup menu entry.
631 """
632 itmList = self.getSelectedItems()
633 for itm in itmList:
634 if isinstance(itm, ProjectBrowserFileItem):
635 # hook support
636 if self.hooks["open"] is not None:
637 self.hooks["open"](itm.fileName())
638 elif itm.isLinguistFile():
639 if itm.fileExt() == '.ts':
640 self.linguistFile.emit(itm.fileName())
641 else:
642 self.trpreview.emit([itm.fileName()])
643 else:
644 self.sourceFile.emit(itm.fileName())
645
646 def __openFileInEditor(self):
647 """
648 Private slot to handle the Open in Editor menu action.
649 """
650 itmList = self.getSelectedItems()
651 for itm in itmList[:]:
652 self.sourceFile.emit(itm.fileName())
653
654 def __removeLanguageFile(self):
655 """
656 Private method to remove a translation from the project.
657 """
658 itmList = self.getSelectedItems()
659
660 for itm in itmList[:]:
661 fn = itm.fileName()
662 self.closeSourceWindow.emit(fn)
663 self.project.removeLanguageFile(fn)
664
665 def __deleteLanguageFile(self):
666 """
667 Private method to delete a translation file from the project.
668 """
669 itmList = self.getSelectedItems()
670
671 translationFiles = [itm.fileName() for itm in itmList]
672
673 from UI.DeleteFilesConfirmationDialog import \
674 DeleteFilesConfirmationDialog
675 dlg = DeleteFilesConfirmationDialog(
676 self.parent(),
677 self.tr("Delete translation files"),
678 self.tr("Do you really want to delete these translation files"
679 " from the project?"),
680 translationFiles)
681
682 if dlg.exec_() == QDialog.Accepted:
683 for fn in translationFiles:
684 self.closeSourceWindow.emit(fn)
685 self.project.deleteLanguageFile(fn)
686
687 def __TRPreview(self, previewAll=False):
688 """
689 Private slot to handle the Preview translations action.
690
691 @param previewAll flag indicating, that all translations
692 should be previewed (boolean)
693 """
694 fileNames = []
695 itmList = self.getSelectedItems()
696 if itmList and not previewAll:
697 for itm in itmList:
698 if isinstance(itm, ProjectBrowserSimpleDirectoryItem):
699 dname = self.project.getRelativePath(itm.dirName())
700 trfiles = sorted(self.project.pdata["TRANSLATIONS"][:])
701 for trfile in trfiles:
702 if trfile.startswith(dname):
703 if trfile not in fileNames:
704 fileNames.append(
705 os.path.join(self.project.ppath, trfile))
706 else:
707 fn = itm.fileName()
708 if fn not in fileNames:
709 fileNames.append(os.path.join(self.project.ppath, fn))
710 else:
711 trfiles = sorted(self.project.pdata["TRANSLATIONS"][:])
712 fileNames.extend([os.path.join(self.project.ppath, trfile)
713 for trfile in trfiles
714 if trfile.endswith('.qm')])
715 self.trpreview[list, bool].emit(fileNames, True)
716
717 def __TRPreviewAll(self):
718 """
719 Private slot to handle the Preview all translations action.
720 """
721 self.__TRPreview(True)
722
723 ###########################################################################
724 ## Methods to support the generation and release commands
725 ###########################################################################
726
727 def __writeTempProjectFile(self, langs, filterList):
728 """
729 Private method to write a temporary project file suitable for
730 pylupdate and lrelease.
731
732 @param langs list of languages to include in the process. An empty
733 list (default) means that all translations should be included.
734 (list of ProjectBrowserFileItem)
735 @param filterList list of source file extension that should be
736 considered (list of strings)
737 @return flag indicating success
738 """
739 path, ext = os.path.splitext(self.project.pfile)
740 pfile = '{0}_e4x.pro'.format(path)
741
742 # only consider files satisfying the filter criteria
743 _sources = [s for s in self.project.pdata["SOURCES"]
744 if os.path.splitext(s)[1] in filterList]
745 sources = []
746 for s in _sources:
747 addIt = True
748 for transExcept in self.project.pdata["TRANSLATIONEXCEPTIONS"]:
749 if s.startswith(transExcept):
750 addIt = False
751 break
752 if addIt:
753 sources.append(s)
754
755 _forms = [f for f in self.project.pdata["FORMS"] if f.endswith('.ui')]
756 forms = []
757 for f in _forms:
758 addIt = True
759 for transExcept in self.project.pdata["TRANSLATIONEXCEPTIONS"]:
760 if f.startswith(transExcept):
761 addIt = False
762 break
763 if addIt:
764 forms.append(f)
765
766 if langs:
767 langs = [self.project.getRelativePath(lang.fileName())
768 for lang in langs if lang.fileName().endswith('.ts')]
769 else:
770 try:
771 pattern = self.project.pdata["TRANSLATIONPATTERN"]\
772 .replace("%language%", "*")
773 langs = [lang for lang in self.project.pdata["TRANSLATIONS"]
774 if fnmatch.fnmatch(lang, pattern)]
775 except IndexError:
776 langs = []
777 if not langs:
778 E5MessageBox.warning(
779 self,
780 self.tr("Write temporary project file"),
781 self.tr("""No translation files (*.ts) selected."""))
782 return False
783
784 # create a prefix relative from the *.ts down to the project path
785 langLevel = {}
786 for lang in langs:
787 level = lang.count(os.sep)
788 lst = langLevel.get(level, [])
789 lst.append(lang)
790 langLevel[level] = lst
791
792 for level, langs in langLevel.items():
793 prefix = '../' * level
794 sections = [
795 ("SOURCES",
796 [prefix + src for src in sources])]
797 sections.append(
798 ("FORMS",
799 [prefix + form for form in forms]))
800 sections.append(
801 ("TRANSLATIONS",
802 [prefix + lang for lang in langs]))
803
804 directory, name = os.path.split(pfile)
805 outFile = os.path.join(directory, os.path.dirname(langs[0]), name)
806 outDir = os.path.dirname(outFile)
807 if not os.path.exists(outDir):
808 os.makedirs(outDir)
809 try:
810 pf = open(outFile, "w", encoding="utf-8")
811 for key, fileList in sections:
812 if len(fileList) > 0:
813 pf.write('{0} = '.format(key))
814 pf.write(' \\\n\t'.join(
815 [f.replace(os.sep, '/') for f in fileList]))
816 pf.write('\n\n')
817
818 pf.close()
819 self.__tmpProjects.append(outFile)
820 except IOError:
821 E5MessageBox.critical(
822 self,
823 self.tr("Write temporary project file"),
824 self.tr(
825 "<p>The temporary project file <b>{0}</b> could not"
826 " be written.</p>").format(outFile))
827
828 if len(self.__tmpProjects) == 0:
829 return False
830
831 return True
832
833 def __readStdoutLupdate(self, proc):
834 """
835 Private slot to handle the readyReadStandardOutput signal of the
836 pylupdate process.
837
838 @param proc reference to the process
839 @type QProcess
840 """
841 self.__readStdout(proc, '{0}: '.format(self.pylupdate))
842
843 def __readStdoutLrelease(self, proc):
844 """
845 Private slot to handle the readyReadStandardOutput signal of the
846 lrelease process.
847
848 @param proc reference to the process
849 @type QProcess
850 """
851 self.__readStdout(proc, 'lrelease: ')
852
853 def __readStdout(self, proc, ps):
854 """
855 Private method to read from a process' stdout channel.
856
857 @param proc process to read from (QProcess)
858 @param ps prompt string (string)
859 """
860 ioEncoding = Preferences.getSystem("IOEncoding")
861
862 proc.setReadChannel(QProcess.StandardOutput)
863 while proc and proc.canReadLine():
864 s = ps
865 output = str(proc.readLine(), ioEncoding, 'replace')
866 s += output
867 self.appendStdout.emit(s)
868
869 def __readStderrLupdate(self, proc):
870 """
871 Private slot to handle the readyReadStandardError signal of the
872 pylupdate4/pylupdate5/pyside-lupdate/pyside2-lupdate process.
873
874 @param proc reference to the process
875 @type QProcess
876 """
877 self.__readStderr(proc, '{0}: '.format(self.pylupdate))
878
879 def __readStderrLrelease(self, proc):
880 """
881 Private slot to handle the readyReadStandardError signal of the
882 lrelease process.
883
884 @param proc reference to the process
885 @type QProcess
886 """
887 self.__readStderr(proc, 'lrelease: ')
888
889 def __readStderr(self, proc, ps):
890 """
891 Private method to read from a process' stderr channel.
892
893 @param proc process to read from (QProcess)
894 @param ps propmt string (string)
895 """
896 ioEncoding = Preferences.getSystem("IOEncoding")
897
898 proc.setReadChannel(QProcess.StandardError)
899 while proc and proc.canReadLine():
900 s = ps
901 error = str(proc.readLine(), ioEncoding, 'replace')
902 s += error
903 self.appendStderr.emit(s)
904
905 ###########################################################################
906 ## Methods for the generation commands
907 ###########################################################################
908
909 def __extractMessages(self):
910 """
911 Private slot to extract the messages to form a messages template file.
912 """
913 if self.hooks["extractMessages"] is not None:
914 self.hooks["extractMessages"]()
915
916 def __generateTSFileDone(self, exitCode, exitStatus, proc):
917 """
918 Private slot to handle the finished signal of the pylupdate process.
919
920 @param exitCode exit code of the process
921 @type int
922 @param exitStatus exit status of the process
923 @type QProcess.ExitStatus
924 @param proc reference to the process
925 @type QProcess
926 """
927 if exitStatus == QProcess.NormalExit and exitCode == 0:
928 ui = e5App().getObject("UserInterface")
929 if ui.notificationsEnabled():
930 ui.showNotification(
931 UI.PixmapCache.getPixmap("linguist48.png"),
932 self.tr("Translation file generation"),
933 self.tr(
934 "The generation of the translation files (*.ts)"
935 " was successful."))
936 else:
937 E5MessageBox.information(
938 self,
939 self.tr("Translation file generation"),
940 self.tr(
941 "The generation of the translation files (*.ts)"
942 " was successful."))
943 else:
944 if exitStatus == QProcess.CrashExit:
945 info = self.tr(" The process has crashed.")
946 else:
947 info = ""
948 E5MessageBox.critical(
949 self,
950 self.tr("Translation file generation"),
951 self.tr(
952 "The generation of the translation files (*.ts) has"
953 " failed.{0}").format(info))
954
955 for index in range(len(self.__pylupdateProcesses)):
956 if proc == self.__pylupdateProcesses[index][0]:
957 try:
958 self.__tmpProjects.remove(
959 self.__pylupdateProcesses[index][1])
960 os.remove(self.__pylupdateProcesses[index][1])
961 except EnvironmentError:
962 pass
963 del self.__pylupdateProcesses[index]
964 break
965 if not self.__pylupdateProcesses:
966 # all done
967 self.pylupdateProcRunning = False
968
969 def __generateTSFile(self, noobsolete=False, generateAll=True):
970 """
971 Private method used to run pylupdate4/pylupdate5/pyside-lupdate to
972 generate the .ts files.
973
974 @param noobsolete flag indicating whether obsolete entries should be
975 kept (boolean)
976 @param generateAll flag indicating whether all translations should be
977 generated (boolean)
978 """
979 if generateAll:
980 langs = []
981 else:
982 langs = self.getSelectedItems()
983
984 # Hook support
985 if generateAll:
986 if noobsolete:
987 if self.hooks["generateAll"] is not None:
988 self.hooks["generateAll"](
989 self.project.pdata["TRANSLATIONS"])
990 return
991 else:
992 if self.hooks["generateAllWithObsolete"] is not None:
993 self.hooks["generateAllWithObsolete"](
994 self.project.pdata["TRANSLATIONS"])
995 return
996 else:
997 if noobsolete:
998 if self.hooks["generateSelected"] is not None:
999 li = [self.project.getRelativePath(lang.fileName())
1000 for lang in langs]
1001 self.hooks["generateSelected"](li)
1002 return
1003 else:
1004 if self.hooks["generateSelectedWithObsolete"] is not None:
1005 li = [self.project.getRelativePath(lang.fileName())
1006 for lang in langs]
1007 self.hooks["generateSelectedWithObsolete"](li)
1008 return
1009
1010 # generate a minimal temporary projectfile suitable for pylupdate
1011 self.__tmpProjects = []
1012 if self.project.getProjectLanguage() in \
1013 ["Python", "Python2", "Python3"]:
1014 ok = self.__writeTempProjectFile(langs, [".py"])
1015 else:
1016 ok = False
1017 if not ok:
1018 return
1019
1020 if self.project.getProjectType() in ["Qt4", "Qt4C"]:
1021 self.pylupdate = Utilities.generatePyQtToolPath('pylupdate4')
1022 elif self.project.getProjectType() in ["PyQt5", "PyQt5C"]:
1023 self.pylupdate = Utilities.generatePyQtToolPath('pylupdate5')
1024 elif self.project.getProjectType() in ["E6Plugin"]:
1025 if PYQT_VERSION < 0x050000:
1026 self.pylupdate = Utilities.generatePyQtToolPath('pylupdate4')
1027 else:
1028 self.pylupdate = Utilities.generatePyQtToolPath('pylupdate5')
1029 elif self.project.getProjectType() in ["PySide", "PySideC"]:
1030 self.pylupdate = Utilities.generatePySideToolPath(
1031 'pyside-lupdate', "1")
1032 elif self.project.getProjectType() in ["PySide2", "PySide2C"]:
1033 self.pylupdate = Utilities.generatePySideToolPath(
1034 'pyside2-lupdate', "2")
1035 else:
1036 return
1037
1038 self.__pylupdateProcesses = []
1039 for tempProjectFile in self.__tmpProjects[:]:
1040 proc = QProcess()
1041 args = []
1042
1043 if noobsolete:
1044 args.append('-noobsolete')
1045
1046 args.append('-verbose')
1047 path, filename = os.path.split(tempProjectFile)
1048 args.append(filename)
1049 proc.setWorkingDirectory(os.path.join(self.project.ppath, path))
1050 proc.finished.connect(
1051 lambda c, s: self.__generateTSFileDone(c, s, proc))
1052 proc.readyReadStandardOutput.connect(
1053 lambda: self.__readStdoutLupdate(proc))
1054 proc.readyReadStandardError.connect(
1055 lambda: self.__readStderrLupdate(proc))
1056
1057 proc.start(self.pylupdate, args)
1058 procStarted = proc.waitForStarted()
1059 if procStarted:
1060 self.pylupdateProcRunning = True
1061 self.__pylupdateProcesses.append((proc, tempProjectFile))
1062 else:
1063 E5MessageBox.critical(
1064 self,
1065 self.tr('Process Generation Error'),
1066 self.tr(
1067 'Could not start {0}.<br>'
1068 'Ensure that it is in the search path.'
1069 ).format(self.pylupdate))
1070 # cleanup
1071 try:
1072 self.__tmpProjects.remove(tempProjectFile)
1073 os.remove(tempProjectFile)
1074 except EnvironmentError:
1075 pass
1076
1077 def __generateAll(self):
1078 """
1079 Private method to generate all translation files (.ts) for Qt Linguist.
1080
1081 All obsolete strings are removed from the .ts file.
1082 """
1083 self.__generateTSFile(noobsolete=True, generateAll=True)
1084
1085 def __generateObsoleteAll(self):
1086 """
1087 Private method to generate all translation files (.ts) for Qt Linguist.
1088
1089 Obsolete strings are kept.
1090 """
1091 self.__generateTSFile(noobsolete=False, generateAll=True)
1092
1093 def __generateSelected(self):
1094 """
1095 Private method to generate selected translation files (.ts) for
1096 Qt Linguist.
1097
1098 All obsolete strings are removed from the .ts file.
1099 """
1100 self.__generateTSFile(noobsolete=True, generateAll=False)
1101
1102 def __generateObsoleteSelected(self):
1103 """
1104 Private method to generate selected translation files (.ts) for
1105 Qt Linguist.
1106
1107 Obsolete strings are kept.
1108 """
1109 self.__generateTSFile(noobsolete=False, generateAll=False)
1110
1111 ###########################################################################
1112 ## Methods for the release commands
1113 ###########################################################################
1114
1115 def __releaseTSFileDone(self, exitCode, exitStatus, proc):
1116 """
1117 Private slot to handle the finished signal of the lrelease process.
1118
1119 @param exitCode exit code of the process
1120 @type int
1121 @param exitStatus exit status of the process
1122 @type QProcess.ExitStatus
1123 @param proc reference to the process
1124 @type QProcess
1125 """
1126 if exitStatus == QProcess.NormalExit and exitCode == 0:
1127 ui = e5App().getObject("UserInterface")
1128 if ui.notificationsEnabled():
1129 ui.showNotification(
1130 UI.PixmapCache.getPixmap("linguist48.png"),
1131 self.tr("Translation file release"),
1132 self.tr("The release of the translation files (*.qm)"
1133 " was successful."))
1134 else:
1135 E5MessageBox.information(
1136 self,
1137 self.tr("Translation file release"),
1138 self.tr("The release of the translation files (*.qm)"
1139 " was successful."))
1140 if self.project.pdata["TRANSLATIONSBINPATH"]:
1141 target = os.path.join(
1142 self.project.ppath,
1143 self.project.pdata["TRANSLATIONSBINPATH"])
1144 for langFile in self.project.pdata["TRANSLATIONS"][:]:
1145 if langFile.endswith('.ts'):
1146 qmFile = os.path.join(self.project.ppath,
1147 langFile.replace('.ts', '.qm'))
1148 if os.path.exists(qmFile):
1149 shutil.move(qmFile, target)
1150 else:
1151 E5MessageBox.critical(
1152 self,
1153 self.tr("Translation file release"),
1154 self.tr(
1155 "The release of the translation files (*.qm) has failed."))
1156
1157 for index in range(len(self.__lreleaseProcesses)):
1158 if proc == self.__lreleaseProcesses[index]:
1159 del self.__lreleaseProcesses[index]
1160 break
1161 if not self.__lreleaseProcesses:
1162 # all done
1163 self.lreleaseProcRunning = False
1164 self.project.checkLanguageFiles()
1165
1166 def __releaseTSFile(self, generateAll=False):
1167 """
1168 Private method to run lrelease to release the translation files (.qm).
1169
1170 @param generateAll flag indicating whether all translations should be
1171 released (boolean)
1172 """
1173 if generateAll:
1174 langs = []
1175 else:
1176 langs = self.getSelectedItems()
1177
1178 # Hooks support
1179 if generateAll:
1180 if self.hooks["releaseAll"] is not None:
1181 self.hooks["releaseAll"](self.project.pdata["TRANSLATIONS"])
1182 return
1183 else:
1184 if self.hooks["releaseSelected"] is not None:
1185 li = [self.project.getRelativePath(lang.fileName())
1186 for lang in langs]
1187 self.hooks["releaseSelected"](li)
1188 return
1189
1190 if self.project.getProjectType() in \
1191 ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E6Plugin",
1192 "PySide", "PySideC", "PySide2", "PySideC2"]:
1193 lrelease = os.path.join(
1194 Utilities.getQtBinariesPath(),
1195 Utilities.generateQtToolName("lrelease"))
1196 else:
1197 return
1198 if Utilities.isWindowsPlatform():
1199 lrelease = lrelease + '.exe'
1200
1201 if langs:
1202 langs = [self.project.getRelativePath(lang.fileName())
1203 for lang in langs if lang.fileName().endswith('.ts')]
1204 else:
1205 try:
1206 pattern = self.project.pdata["TRANSLATIONPATTERN"]\
1207 .replace("%language%", "*")
1208 langs = [lang for lang in self.project.pdata["TRANSLATIONS"]
1209 if fnmatch.fnmatch(lang, pattern)]
1210 except IndexError:
1211 langs = []
1212 if not langs:
1213 E5MessageBox.warning(
1214 self,
1215 self.tr("Write temporary project file"),
1216 self.tr("""No translation files (*.ts) selected."""))
1217 return
1218
1219 self.__lreleaseProcesses = []
1220 args = []
1221 args.append('-verbose')
1222 for langFile in langs:
1223 path, filename = os.path.split(langFile)
1224 args.append(filename)
1225
1226 proc = QProcess()
1227 proc.setWorkingDirectory(os.path.join(self.project.ppath, path))
1228 proc.finished.connect(
1229 lambda c, s: self.__releaseTSFileDone(c, s, proc))
1230 proc.readyReadStandardOutput.connect(
1231 lambda: self.__readStdoutLrelease(proc))
1232 proc.readyReadStandardError.connect(
1233 lambda: self.__readStderrLrelease(proc))
1234
1235 proc.start(lrelease, args)
1236 procStarted = proc.waitForStarted()
1237 if procStarted:
1238 self.lreleaseProcRunning = True
1239 self.__lreleaseProcesses.append(proc)
1240 else:
1241 E5MessageBox.critical(
1242 self,
1243 self.tr('Process Generation Error'),
1244 self.tr(
1245 '<p>Could not start lrelease.<br>'
1246 'Ensure that it is available as <b>{0}</b>.</p>'
1247 ).format(lrelease))
1248
1249 def __releaseSelected(self):
1250 """
1251 Private method to release the translation files (.qm).
1252 """
1253 self.__releaseTSFile(generateAll=False)
1254
1255 def __releaseAll(self):
1256 """
1257 Private method to release the translation files (.qm).
1258 """
1259 self.__releaseTSFile(generateAll=True)
1260
1261 ###########################################################################
1262 ## Support for hooks below
1263 ###########################################################################
1264
1265 def _initHookMethods(self):
1266 """
1267 Protected method to initialize the hooks dictionary.
1268
1269 Supported hook methods are:
1270 <ul>
1271 <li>extractMessages: takes no parameters</li>
1272 <li>generateAll: takes list of filenames as parameter</li>
1273 <li>generateAllWithObsolete: takes list of filenames as parameter</li>
1274 <li>generateSelected: takes list of filenames as parameter</li>
1275 <li>generateSelectedWithObsolete: takes list of filenames as
1276 parameter</li>
1277 <li>releaseAll: takes list of filenames as parameter</li>
1278 <li>releaseSelected: takes list of filenames as parameter</li>
1279 <li>open: takes a filename as parameter</li>
1280 </ul>
1281
1282 <b>Note</b>: Filenames are relative to the project directory.
1283 """
1284 self.hooks = {
1285 "extractMessages": None,
1286 "generateAll": None,
1287 "generateAllWithObsolete": None,
1288 "generateSelected": None,
1289 "generateSelectedWithObsolete": None,
1290 "releaseAll": None,
1291 "releaseSelected": None,
1292 "open": None,
1293 }

eric ide

mercurial