Project/ProjectTranslationsBrowser.py

Wed, 01 Jan 2014 14:39:32 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 01 Jan 2014 14:39:32 +0100
branch
Py2 comp.
changeset 3161
06f57a834adf
parent 3145
a9de05d4a22f
child 3178
f25fc1364c88
permissions
-rw-r--r--

Updated copyright for 2014.

# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a class used to display the translations part of the
project.
"""

from __future__ import unicode_literals
try:
    str = unicode    # __IGNORE_WARNING__
except (NameError):
    pass

import os
import shutil
import fnmatch

from PyQt4.QtCore import pyqtSignal, QProcess
from PyQt4.QtGui import QDialog, QMenu

from E5Gui import E5MessageBox
from E5Gui.E5Application import e5App

from .ProjectBrowserModel import ProjectBrowserFileItem, \
    ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem, \
    ProjectBrowserTranslationType
from .ProjectBaseBrowser import ProjectBaseBrowser

import UI.PixmapCache

import Preferences
import Utilities


class ProjectTranslationsBrowser(ProjectBaseBrowser):
    """
    A class used to display the translations part of the project.
    
    @signal linguistFile(str) emitted to open a translation file with
        Qt-Linguist
    @signal appendStdout(str) emitted after something was received from
        a QProcess on stdout
    @signal appendStderr(str) emitted after something was received from
        a QProcess on stderr
    @signal sourceFile(str) emitted to open a translation file in an editor
    @signal closeSourceWindow(str) emitted after a file has been
        removed/deleted from the project
    @signal trpreview(list of str, bool = False) emitted to preview
        translations in the translations previewer
    @signal showMenu(str, QMenu) emitted when a menu is about to be shown.
        The name of the menu and a reference to the menu are given.
    """
    appendStdout = pyqtSignal(str)
    appendStderr = pyqtSignal(str)
    showMenu = pyqtSignal(str, QMenu)
    
    def __init__(self, project, parent=None):
        """
        Constructor
        
        @param project reference to the project object
        @param parent parent widget of this browser (QWidget)
        """
        ProjectBaseBrowser.__init__(self, project,
                                    ProjectBrowserTranslationType, parent)
        self.isTranslationsBrowser = True
        
        self.selectedItemsFilter = \
            [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem]
        
        self.setWindowTitle(self.trUtf8('Translations'))

        self.setWhatsThis(self.trUtf8(
            """<b>Project Translations Browser</b>"""
            """<p>This allows to easily see all translations contained in"""
            """ the current project. Several actions can be executed via"""
            """ the context menu.</p>"""
        ))
        
        self.__lreleaseProcesses = []
        self.__pylupdateProcesses = []
        self.lreleaseProcRunning = False
        self.pylupdateProcRunning = False
        self.__tmpProjects = []
        
    def _createPopupMenus(self):
        """
        Protected overloaded method to generate the popup menu.
        """
        self.menuActions = []
        self.multiMenuActions = []
        self.dirMenuActions = []
        self.dirMultiMenuActions = []
        
        self.tsMenuActions = []
        self.qmMenuActions = []
        self.tsprocMenuActions = []
        self.qmprocMenuActions = []
        
        self.tsMultiMenuActions = []
        self.qmMultiMenuActions = []
        self.tsprocMultiMenuActions = []
        self.qmprocMultiMenuActions = []
        
        self.tsprocDirMenuActions = []
        self.qmprocDirMenuActions = []
        
        self.tsprocBackMenuActions = []
        self.qmprocBackMenuActions = []
        
        self.menu = QMenu(self)
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            act = self.menu.addAction(
                self.trUtf8('Generate translation'), self.__generateSelected)
            self.tsMenuActions.append(act)
            self.tsprocMenuActions.append(act)
            act = self.menu.addAction(
                self.trUtf8('Generate translation (with obsolete)'),
                self.__generateObsoleteSelected)
            self.tsMenuActions.append(act)
            self.tsprocMenuActions.append(act)
            act = self.menu.addAction(
                self.trUtf8('Generate all translations'), self.__generateAll)
            self.tsprocMenuActions.append(act)
            act = self.menu.addAction(
                self.trUtf8('Generate all translations (with obsolete)'),
                self.__generateObsoleteAll)
            self.tsprocMenuActions.append(act)
            self.menu.addSeparator()
            act = self.menu.addAction(
                self.trUtf8('Open in Qt-Linguist'), self._openItem)
            self.tsMenuActions.append(act)
            act = self.menu.addAction(
                self.trUtf8('Open in Editor'), self.__openFileInEditor)
            self.tsMenuActions.append(act)
            self.menu.addSeparator()
            act = self.menu.addAction(
                self.trUtf8('Release translation'), self.__releaseSelected)
            self.tsMenuActions.append(act)
            self.qmprocMenuActions.append(act)
            act = self.menu.addAction(
                self.trUtf8('Release all translations'), self.__releaseAll)
            self.qmprocMenuActions.append(act)
            self.menu.addSeparator()
            act = self.menu.addAction(
                self.trUtf8('Preview translation'), self.__TRPreview)
            self.qmMenuActions.append(act)
            act = self.menu.addAction(
                self.trUtf8('Preview all translations'), self.__TRPreviewAll)
            self.menu.addSeparator()
        else:
            if self.hooks["extractMessages"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "extractMessages",
                        self.trUtf8('Extract messages')),
                    self.__extractMessages)
                self.menuActions.append(act)
                self.menu.addSeparator()
            if self.hooks["generateSelected"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "generateSelected",
                        self.trUtf8('Generate translation')),
                    self.__generateSelected)
                self.tsMenuActions.append(act)
                self.tsprocMenuActions.append(act)
            if self.hooks["generateSelectedWithObsolete"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "generateSelectedWithObsolete",
                        self.trUtf8('Generate translation (with obsolete)')),
                    self.__generateObsoleteSelected)
                self.tsMenuActions.append(act)
                self.tsprocMenuActions.append(act)
            if self.hooks["generateAll"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "generateAll",
                        self.trUtf8('Generate all translations')),
                    self.__generateAll)
                self.tsprocMenuActions.append(act)
            if self.hooks["generateAllWithObsolete"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "generateAllWithObsolete",
                        self.trUtf8(
                            'Generate all translations (with obsolete)')),
                    self.__generateObsoleteAll)
                self.tsprocMenuActions.append(act)
            self.menu.addSeparator()
            if self.hooks["open"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "open", self.trUtf8('Open')),
                    self._openItem)
                self.tsMenuActions.append(act)
            act = self.menu.addAction(
                self.trUtf8('Open in Editor'), self.__openFileInEditor)
            self.tsMenuActions.append(act)
            self.menu.addSeparator()
            if self.hooks["releaseSelected"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "releaseSelected",
                        self.trUtf8('Release translation')),
                    self.__releaseSelected)
                self.tsMenuActions.append(act)
                self.qmprocMenuActions.append(act)
            if self.hooks["releaseAll"] is not None:
                act = self.menu.addAction(
                    self.hooksMenuEntries.get(
                        "releaseAll",
                        self.trUtf8('Release all translations')),
                    self.__releaseAll)
                self.qmprocMenuActions.append(act)
            self.menu.addSeparator()
        act = self.menu.addAction(
            self.trUtf8('Remove from project'), self.__removeLanguageFile)
        self.menuActions.append(act)
        act = self.menu.addAction(
            self.trUtf8('Delete'), self.__deleteLanguageFile)
        self.menuActions.append(act)
        self.menu.addSeparator()
        self.__addTranslationAct = self.menu.addAction(
            self.trUtf8('Add translation...'), self.project.addLanguage)
        self.menu.addAction(
            self.trUtf8('Add translation files...'),
            self.__addTranslationFiles)
        self.menu.addSeparator()
        self.menu.addAction(
            self.trUtf8('Copy Path to Clipboard'), self._copyToClipboard)
        self.menu.addSeparator()
        self.menu.addAction(self.trUtf8('Configure...'), self._configure)
        
        self.backMenu = QMenu(self)
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            act = self.backMenu.addAction(
                self.trUtf8('Generate all translations'),
                self.__generateAll)
            self.tsprocBackMenuActions.append(act)
            act = self.backMenu.addAction(
                self.trUtf8('Generate all translations (with obsolete)'),
                self.__generateObsoleteAll)
            self.tsprocBackMenuActions.append(act)
            act = self.backMenu.addAction(
                self.trUtf8('Release all translations'),
                self.__releaseAll)
            self.qmprocBackMenuActions.append(act)
            self.backMenu.addSeparator()
            act = self.backMenu.addAction(
                self.trUtf8('Preview all translations'),
                self.__TRPreview)
        else:
            if self.hooks["extractMessages"] is not None:
                act = self.backMenu.addAction(
                    self.hooksMenuEntries.get(
                        "extractMessages",
                        self.trUtf8('Extract messages')),
                    self.__extractMessages)
                self.backMenu.addSeparator()
            if self.hooks["generateAll"] is not None:
                act = self.backMenu.addAction(
                    self.hooksMenuEntries.get(
                        "generateAll",
                        self.trUtf8('Generate all translations')),
                    self.__generateAll)
                self.tsprocBackMenuActions.append(act)
            if self.hooks["generateAllWithObsolete"] is not None:
                act = self.backMenu.addAction(
                    self.hooksMenuEntries.get(
                        "generateAllWithObsolete",
                        self.trUtf8(
                            'Generate all translations (with obsolete)')),
                    self.__generateObsoleteAll)
                self.tsprocBackMenuActions.append(act)
            if self.hooks["releaseAll"] is not None:
                act = self.backMenu.addAction(
                    self.hooksMenuEntries.get(
                        "releaseAll",
                        self.trUtf8('Release all translations')),
                    self.__releaseAll)
                self.qmprocBackMenuActions.append(act)
        self.backMenu.addSeparator()
        self.__addTranslationBackAct = self.backMenu.addAction(
            self.trUtf8('Add translation...'), self.project.addLanguage)
        self.backMenu.addAction(
            self.trUtf8('Add translation files...'),
            self.__addTranslationFiles)
        self.backMenu.addSeparator()
        self.backMenu.addAction(self.trUtf8('Configure...'), self._configure)
        self.backMenu.setEnabled(False)

        # create the menu for multiple selected files
        self.multiMenu = QMenu(self)
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            act = self.multiMenu.addAction(
                self.trUtf8('Generate translations'),
                self.__generateSelected)
            self.tsMultiMenuActions.append(act)
            self.tsprocMultiMenuActions.append(act)
            act = self.multiMenu.addAction(
                self.trUtf8('Generate translations (with obsolete)'),
                self.__generateObsoleteSelected)
            self.tsMultiMenuActions.append(act)
            self.tsprocMultiMenuActions.append(act)
            self.multiMenu.addSeparator()
            act = self.multiMenu.addAction(
                self.trUtf8('Open in Qt-Linguist'), self._openItem)
            self.tsMultiMenuActions.append(act)
            act = self.multiMenu.addAction(
                self.trUtf8('Open in Editor'), self.__openFileInEditor)
            self.tsMultiMenuActions.append(act)
            self.multiMenu.addSeparator()
            act = self.multiMenu.addAction(
                self.trUtf8('Release translations'), self.__releaseSelected)
            self.tsMultiMenuActions.append(act)
            self.qmprocMultiMenuActions.append(act)
            self.multiMenu.addSeparator()
            act = self.multiMenu.addAction(
                self.trUtf8('Preview translations'), self.__TRPreview)
            self.qmMultiMenuActions.append(act)
        else:
            if self.hooks["extractMessages"] is not None:
                act = self.multiMenu.addAction(
                    self.hooksMenuEntries.get(
                        "extractMessages",
                        self.trUtf8('Extract messages')),
                    self.__extractMessages)
                self.multiMenuActions.append(act)
                self.multiMenu.addSeparator()
            if self.hooks["generateSelected"] is not None:
                act = self.multiMenu.addAction(
                    self.hooksMenuEntries.get(
                        "generateSelected",
                        self.trUtf8('Generate translations')),
                    self.__generateSelected)
                self.tsMultiMenuActions.append(act)
                self.tsprocMultiMenuActions.append(act)
            if self.hooks["generateSelectedWithObsolete"] is not None:
                act = self.multiMenu.addAction(
                    self.hooksMenuEntries.get(
                        "generateSelectedWithObsolete",
                        self.trUtf8('Generate translations (with obsolete)')),
                    self.__generateObsoleteSelected)
                self.tsMultiMenuActions.append(act)
                self.tsprocMultiMenuActions.append(act)
            self.multiMenu.addSeparator()
            if self.hooks["open"] is not None:
                act = self.multiMenu.addAction(
                    self.hooksMenuEntries.get(
                        "open", self.trUtf8('Open')),
                    self._openItem)
                self.tsMultiMenuActions.append(act)
            act = self.multiMenu.addAction(
                self.trUtf8('Open in Editor'), self.__openFileInEditor)
            self.tsMultiMenuActions.append(act)
            self.multiMenu.addSeparator()
            if self.hooks["releaseSelected"] is not None:
                act = self.multiMenu.addAction(
                    self.hooksMenuEntries.get(
                        "releaseSelected",
                        self.trUtf8('Release translations')),
                    self.__releaseSelected)
                self.tsMultiMenuActions.append(act)
                self.qmprocMultiMenuActions.append(act)
        self.multiMenu.addSeparator()
        act = self.multiMenu.addAction(
            self.trUtf8('Remove from project'), self.__removeLanguageFile)
        self.multiMenuActions.append(act)
        act = self.multiMenu.addAction(
            self.trUtf8('Delete'), self.__deleteLanguageFile)
        self.multiMenuActions.append(act)
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.trUtf8('Configure...'), self._configure)

        self.dirMenu = QMenu(self)
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            act = self.dirMenu.addAction(
                self.trUtf8('Generate all translations'),
                self.__generateAll)
            self.tsprocDirMenuActions.append(act)
            act = self.dirMenu.addAction(
                self.trUtf8('Generate all translations (with obsolete)'),
                self.__generateObsoleteAll)
            self.tsprocDirMenuActions.append(act)
            act = self.dirMenu.addAction(
                self.trUtf8('Release all translations'),
                self.__releaseAll)
            self.qmprocDirMenuActions.append(act)
            self.dirMenu.addSeparator()
            act = self.dirMenu.addAction(
                self.trUtf8('Preview all translations'),
                self.__TRPreview)
        else:
            if self.hooks["extractMessages"] is not None:
                act = self.dirMenu.addAction(
                    self.hooksMenuEntries.get(
                        "extractMessages",
                        self.trUtf8('Extract messages')),
                    self.__extractMessages)
                self.dirMenuActions.append(act)
                self.dirMenu.addSeparator()
            if self.hooks["generateAll"] is not None:
                act = self.dirMenu.addAction(
                    self.hooksMenuEntries.get(
                        "generateAll",
                        self.trUtf8('Generate all translations')),
                    self.__generateAll)
                self.tsprocDirMenuActions.append(act)
            if self.hooks["generateAllWithObsolete"] is not None:
                act = self.dirMenu.addAction(
                    self.hooksMenuEntries.get(
                        "generateAllWithObsolete",
                        self.trUtf8(
                            'Generate all translations (with obsolete)')),
                    self.__generateObsoleteAll)
                self.tsprocDirMenuActions.append(act)
            if self.hooks["releaseAll"] is not None:
                act = self.dirMenu.addAction(
                    self.hooksMenuEntries.get(
                        "releaseAll",
                        self.trUtf8('Release all translations')),
                    self.__releaseAll)
                self.qmprocDirMenuActions.append(act)
        self.dirMenu.addSeparator()
        act = self.dirMenu.addAction(
            self.trUtf8('Delete'), self._deleteDirectory)
        self.dirMenuActions.append(act)
        self.dirMenu.addSeparator()
        self.__addTranslationDirAct = self.dirMenu.addAction(
            self.trUtf8('Add translation...'), self.project.addLanguage)
        self.dirMenu.addAction(
            self.trUtf8('Add translation files...'),
            self.__addTranslationFiles)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(
            self.trUtf8('Copy Path to Clipboard'), self._copyToClipboard)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.trUtf8('Configure...'), self._configure)
        
        self.dirMultiMenu = None
        
        self.menu.aboutToShow.connect(self.__showContextMenu)
        self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
        self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
        self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
        self.mainMenu = self.menu
        
    def _contextMenuRequested(self, coord):
        """
        Protected slot to show the context menu.
        
        @param coord the position of the mouse pointer (QPoint)
        """
        if not self.project.isOpen():
            return
        
        try:
            categories = self.getSelectedItemsCountCategorized(
                [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem])
            cnt = categories["sum"]
            if cnt <= 1:
                index = self.indexAt(coord)
                if index.isValid():
                    self._selectSingleItem(index)
                    categories = self.getSelectedItemsCountCategorized(
                        [ProjectBrowserFileItem,
                         ProjectBrowserSimpleDirectoryItem])
                    cnt = categories["sum"]
                        
            bfcnt = categories[str(ProjectBrowserFileItem)]
            sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)]
            if cnt > 1 and cnt == bfcnt:
                self.multiMenu.popup(self.mapToGlobal(coord))
            else:
                index = self.indexAt(coord)
                if cnt == 1 and index.isValid():
                    if bfcnt == 1:
                        self.menu.popup(self.mapToGlobal(coord))
                    elif sdcnt == 1:
                        self.dirMenu.popup(self.mapToGlobal(coord))
                    else:
                        self.backMenu.popup(self.mapToGlobal(coord))
                else:
                    self.backMenu.popup(self.mapToGlobal(coord))
        except:
            pass
        
    def __showContextMenu(self):
        """
        Private slot called by the menu aboutToShow signal.
        """
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            tsFiles = 0
            qmFiles = 0
            itmList = self.getSelectedItems()
            for itm in itmList[:]:
                if itm.fileName().endswith('.ts'):
                    tsFiles += 1
                elif itm.fileName().endswith('.qm'):
                    qmFiles += 1
            if (tsFiles > 0 and qmFiles > 0) or \
               (tsFiles == 0 and qmFiles == 0):
                for act in self.tsMenuActions + self.qmMenuActions:
                    act.setEnabled(False)
            elif tsFiles > 0:
                for act in self.tsMenuActions:
                    act.setEnabled(True)
                for act in self.qmMenuActions:
                    act.setEnabled(False)
            elif qmFiles > 0:
                for act in self.tsMenuActions:
                    act.setEnabled(False)
                for act in self.qmMenuActions:
                    act.setEnabled(True)
            if self.pylupdateProcRunning:
                for act in self.tsprocMenuActions:
                    act.setEnabled(False)
            if self.lreleaseProcRunning:
                for act in self.qmprocMenuActions:
                    act.setEnabled(True)
        self.__addTranslationAct.setEnabled(
            self.project.getTranslationPattern() != "")
        
        ProjectBaseBrowser._showContextMenu(self, self.menu)
        
        self.showMenu.emit("Main", self.menu)
        
    def __showContextMenuMulti(self):
        """
        Private slot called by the multiMenu aboutToShow signal.
        """
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            tsFiles = 0
            qmFiles = 0
            itmList = self.getSelectedItems()
            for itm in itmList[:]:
                if itm.fileName().endswith('.ts'):
                    tsFiles += 1
                elif itm.fileName().endswith('.qm'):
                    qmFiles += 1
            if (tsFiles > 0 and qmFiles > 0) or \
               (tsFiles == 0 and qmFiles == 0):
                for act in self.tsMultiMenuActions + self.qmMultiMenuActions:
                    act.setEnabled(False)
            elif tsFiles > 0:
                for act in self.tsMultiMenuActions:
                    act.setEnabled(True)
                for act in self.qmMultiMenuActions:
                    act.setEnabled(False)
            elif qmFiles > 0:
                for act in self.tsMultiMenuActions:
                    act.setEnabled(False)
                for act in self.qmMultiMenuActions:
                    act.setEnabled(True)
            if self.pylupdateProcRunning:
                for act in self.tsprocMultiMenuActions:
                    act.setEnabled(False)
            if self.lreleaseProcRunning:
                for act in self.qmprocMultiMenuActions:
                    act.setEnabled(True)
        
        ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)
        
        self.showMenu.emit("MainMulti", self.multiMenu)
        
    def __showContextMenuDir(self):
        """
        Private slot called by the dirMenu aboutToShow signal.
        """
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            if self.pylupdateProcRunning:
                for act in self.tsprocDirMenuActions:
                    act.setEnabled(False)
            if self.lreleaseProcRunning:
                for act in self.qmprocDirMenuActions:
                    act.setEnabled(True)
        self.__addTranslationDirAct.setEnabled(
            self.project.getTranslationPattern() != "")
        
        ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)
        
        self.showMenu.emit("MainDir", self.dirMenu)
        
    def __showContextMenuBack(self):
        """
        Private slot called by the backMenu aboutToShow signal.
        """
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            if self.pylupdateProcRunning:
                for act in self.tsprocBackMenuActions:
                    act.setEnabled(False)
            if self.lreleaseProcRunning:
                for act in self.qmprocBackMenuActions:
                    act.setEnabled(True)
        self.__addTranslationBackAct.setEnabled(
            self.project.getTranslationPattern() != "")
        
        self.showMenu.emit("MainBack", self.backMenu)
        
    def __addTranslationFiles(self):
        """
        Private method to add translation files to the project.
        """
        itm = self.model().item(self.currentIndex())
        if isinstance(itm, ProjectBrowserFileItem):
            dn = os.path.dirname(itm.fileName())
        elif isinstance(itm, ProjectBrowserSimpleDirectoryItem) or \
                isinstance(itm, ProjectBrowserDirectoryItem):
            dn = itm.dirName()
        else:
            dn = None
        self.project.addFiles('translation', dn)
        
    def _openItem(self):
        """
        Protected slot to handle the open popup menu entry.
        """
        itmList = self.getSelectedItems()
        for itm in itmList:
            if isinstance(itm, ProjectBrowserFileItem):
                # hook support
                if self.hooks["open"] is not None:
                    self.hooks["open"](itm.fileName())
                elif itm.isLinguistFile():
                    if itm.fileExt() == '.ts':
                        self.linguistFile.emit(itm.fileName())
                    else:
                        self.trpreview.emit([itm.fileName()])
                else:
                    self.sourceFile.emit(itm.fileName())
        
    def __openFileInEditor(self):
        """
        Private slot to handle the Open in Editor menu action.
        """
        itmList = self.getSelectedItems()
        for itm in itmList[:]:
            self.sourceFile.emit(itm.fileName())
        
    def __removeLanguageFile(self):
        """
        Private method to remove a translation from the project.
        """
        itmList = self.getSelectedItems()
        
        for itm in itmList[:]:
            fn = itm.fileName()
            self.closeSourceWindow.emit(fn)
            self.project.removeLanguageFile(fn)
        
    def __deleteLanguageFile(self):
        """
        Private method to delete a translation file from the project.
        """
        itmList = self.getSelectedItems()
        
        translationFiles = [itm.fileName() for itm in itmList]
        
        from UI.DeleteFilesConfirmationDialog import \
            DeleteFilesConfirmationDialog
        dlg = DeleteFilesConfirmationDialog(
            self.parent(),
            self.trUtf8("Delete translation files"),
            self.trUtf8("Do you really want to delete these translation files"
                        " from the project?"),
            translationFiles)
        
        if dlg.exec_() == QDialog.Accepted:
            for fn in translationFiles:
                self.closeSourceWindow.emit(fn)
                self.project.deleteLanguageFile(fn)
        
    def __TRPreview(self, previewAll=False):
        """
        Private slot to handle the Preview translations action.
        
        @param previewAll flag indicating, that all translations
            should be previewed (boolean)
        """
        fileNames = []
        itmList = self.getSelectedItems()
        if itmList and not previewAll:
            for itm in itmList:
                if isinstance(itm, ProjectBrowserSimpleDirectoryItem):
                    dname = self.project.getRelativePath(itm.dirName())
                    trfiles = sorted(self.project.pdata["TRANSLATIONS"][:])
                    for trfile in trfiles:
                        if trfile.startswith(dname):
                            if trfile not in fileNames:
                                fileNames.append(
                                    os.path.join(self.project.ppath, trfile))
                else:
                    fn = itm.fileName()
                    if fn not in fileNames:
                        fileNames.append(os.path.join(self.project.ppath, fn))
        else:
            trfiles = sorted(self.project.pdata["TRANSLATIONS"][:])
            fileNames.extend([os.path.join(self.project.ppath, trfile)
                              for trfile in trfiles
                              if trfile.endswith('.qm')])
        self.trpreview[list, bool].emit(fileNames, True)
        
    def __TRPreviewAll(self):
        """
        Private slot to handle the Preview all translations action.
        """
        self.__TRPreview(True)
    
    ###########################################################################
    ##  Methods to support the generation and release commands
    ###########################################################################
    
    def __writeTempProjectFile(self, langs, filter):
        """
        Private method to write a temporary project file suitable for
        pylupdate and lrelease.
        
        @param langs list of languages to include in the process. An empty
            list (default) means that all translations should be included.
            (list of ProjectBrowserFileItem)
        @param filter list of source file extension that should be considered
            (list of strings)
        @return flag indicating success
        """
        path, ext = os.path.splitext(self.project.pfile)
        pfile = '{0}_e4x.pro'.format(path)
        
        # only consider files satisfying the filter criteria
        _sources = [s for s in self.project.pdata["SOURCES"]
                    if os.path.splitext(s)[1] in filter]
        sources = []
        for s in _sources:
            addIt = True
            for transExcept in self.project.pdata["TRANSLATIONEXCEPTIONS"]:
                if s.startswith(transExcept):
                    addIt = False
                    break
            if addIt:
                sources.append(s)
        
        _forms = [f for f in self.project.pdata["FORMS"] if f.endswith('.ui')]
        forms = []
        for f in _forms:
            addIt = True
            for transExcept in self.project.pdata["TRANSLATIONEXCEPTIONS"]:
                if f.startswith(transExcept):
                    addIt = False
                    break
            if addIt:
                forms.append(f)
        
        if langs:
            langs = [self.project.getRelativePath(lang.fileName())
                     for lang in langs if lang.fileName().endswith('.ts')]
        else:
            try:
                pattern = self.project.pdata["TRANSLATIONPATTERN"][0]\
                    .replace("%language%", "*")
                langs = [lang for lang in self.project.pdata["TRANSLATIONS"]
                         if fnmatch.fnmatch(lang, pattern)]
            except IndexError:
                langs = []
        if not langs:
            E5MessageBox.warning(
                self,
                self.trUtf8("Write temporary project file"),
                self.trUtf8("""No translation files (*.ts) selected."""))
            return False
        
        # create a prefix relative from the *.ts down to the project path
        langLevel = {}
        for lang in langs:
            level = lang.count(os.sep)
            lst = langLevel.get(level, [])
            lst.append(lang)
            langLevel[level] = lst

        for level, langs in langLevel.items():
            prefix = '../' * level
            sections = [
                ("SOURCES",
                 [prefix + src for src in sources])]
            sections.append(
                ("FORMS",
                 [prefix + form for form in forms]))
            sections.append(
                ("TRANSLATIONS",
                 [prefix + lang for lang in langs]))
            
            dir, name = os.path.split(pfile)
            outFile = os.path.join(dir, os.path.dirname(langs[0]), name)
            try:
                pf = open(outFile, "w", encoding="utf-8")
                for key, list in sections:
                    if len(list) > 0:
                        pf.write('{0} = '.format(key))
                        last = len(list) - 1
                        if last > 0:
                            pf.write('{0} \\{1}'.format(
                                list[0].replace(os.sep, '/'), "\n"))
                            for i in range(1, last):
                                pf.write('\t{0} \\{1}'.format(
                                    list[i].replace(os.sep, '/'), "\n"))
                            pf.write('\t{0} {1}{2}'.format(
                                list[last].replace(os.sep, '/'), "\n", "\n"))
                        else:
                            pf.write('{0} {1}{2}'.format(
                                list[0].replace(os.sep, '/'), "\n", "\n"))
                    
                pf.close()
                self.__tmpProjects.append(outFile)
            except IOError:
                E5MessageBox.critical(
                    self,
                    self.trUtf8("Write temporary project file"),
                    self.trUtf8(
                        "<p>The temporary project file <b>{0}</b> could not"
                        " be written.</p>").format(outFile))
        
        if len(self.__tmpProjects) == 0:
            return False
            
        return True
    
    def __readStdoutLupdate(self):
        """
        Private slot to handle the readyReadStandardOutput signal of the
        pylupdate process.
        """
        proc = self.sender()
        if proc is not None:
            self.__readStdout(proc, '{0}: '.format(self.pylupdate))
        else:
            return
        
    def __readStdoutLrelease(self):
        """
        Private slot to handle the readyReadStandardOutput signal of the
        lrelease process.
        """
        proc = self.sender()
        if proc is not None:
            self.__readStdout(proc, 'lrelease: ')
        else:
            return
        
    def __readStdout(self, proc, ps):
        """
        Private method to read from a process' stdout channel.
        
        @param proc process to read from (QProcess)
        @param ps prompt string (string)
        """
        ioEncoding = Preferences.getSystem("IOEncoding")
        
        proc.setReadChannel(QProcess.StandardOutput)
        while proc and proc.canReadLine():
            s = ps
            output = str(proc.readLine(), ioEncoding, 'replace')
            s += output
            self.appendStdout.emit(s)
        
    def __readStderrLupdate(self):
        """
        Private slot to handle the readyReadStandardError signal of the
        pylupdate process.
        """
        proc = self.sender()
        if proc is not None:
            self.__readStderr(proc, '{0}: '.format(self.pylupdate))
        else:
            return
        
    def __readStderrLrelease(self):
        """
        Private slot to handle the readyReadStandardError signal of the
        lrelease process.
        """
        proc = self.sender()
        if proc is not None:
            self.__readStderr(proc, 'lrelease: ')
        else:
            return
        
    def __readStderr(self, proc, ps):
        """
        Private method to read from a process' stderr channel.
        
        @param proc process to read from (QProcess)
        @param ps propmt string (string)
        """
        ioEncoding = Preferences.getSystem("IOEncoding")
        
        proc.setReadChannel(QProcess.StandardError)
        while proc and proc.canReadLine():
            s = ps
            error = str(proc.readLine(), ioEncoding, 'replace')
            s += error
            self.appendStderr.emit(s)
    
    ###########################################################################
    ##  Methods for the generation commands
    ###########################################################################
    
    def __extractMessages(self):
        """
        Private slot to extract the messages to form a messages template file.
        """
        if self.hooks["extractMessages"] is not None:
            self.hooks["extractMessages"]()
        
    def __generateTSFileDone(self, exitCode, exitStatus):
        """
        Private slot to handle the finished signal of the pylupdate process.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        if exitStatus == QProcess.NormalExit and exitCode == 0:
            ui = e5App().getObject("UserInterface")
            if ui.notificationsEnabled():
                ui.showNotification(
                    UI.PixmapCache.getPixmap("linguist48.png"),
                    self.trUtf8("Translation file generation"),
                    self.trUtf8(
                        "The generation of the translation files (*.ts)"
                        " was successful."))
            else:
                E5MessageBox.information(
                    self,
                    self.trUtf8("Translation file generation"),
                    self.trUtf8(
                        "The generation of the translation files (*.ts)"
                        " was successful."))
        else:
            E5MessageBox.critical(
                self,
                self.trUtf8("Translation file generation"),
                self.trUtf8(
                    "The generation of the translation files (*.ts) has"
                    " failed."))
        
        proc = self.sender()
        for index in range(len(self.__pylupdateProcesses)):
            if proc == self.__pylupdateProcesses[index][0]:
                try:
                    self.__tmpProjects.remove(
                        self.__pylupdateProcesses[index][1])
                    os.remove(self.__pylupdateProcesses[index][1])
                except EnvironmentError:
                    pass
                del self.__pylupdateProcesses[index]
                break
        if not self.__pylupdateProcesses:
            # all done
            self.pylupdateProcRunning = False
        
    def __generateTSFile(self, noobsolete=False, generateAll=True):
        """
        Private method used to run pylupdate/pylupdate4 to generate the .ts
        files.
        
        @param noobsolete flag indicating whether obsolete entries should be
            kept (boolean)
        @param generateAll flag indicating whether all translations should be
            generated (boolean)
        """
        if generateAll:
            langs = []
        else:
            langs = self.getSelectedItems()
        
        # Hook support
        if generateAll:
            if noobsolete:
                if self.hooks["generateAll"] is not None:
                    self.hooks["generateAll"](
                        self.project.pdata["TRANSLATIONS"])
                    return
            else:
                if self.hooks["generateAllWithObsolete"] is not None:
                    self.hooks["generateAllWithObsolete"](
                        self.project.pdata["TRANSLATIONS"])
                    return
        else:
            if noobsolete:
                if self.hooks["generateSelected"] is not None:
                    li = [self.project.getRelativePath(lang.fileName())
                          for lang in langs]
                    self.hooks["generateSelected"](li)
                    return
            else:
                if self.hooks["generateSelectedWithObsolete"] is not None:
                    li = [self.project.getRelativePath(lang.fileName())
                          for lang in langs]
                    self.hooks["generateSelectedWithObsolete"](li)
                    return
        
        # generate a minimal temporary projectfile suitable for pylupdate
        self.__tmpProjects = []
        if self.project.pdata["PROGLANGUAGE"][0] in \
                ["Python", "Python2", "Python3"]:
            ok = self.__writeTempProjectFile(langs, [".py"])
        else:
            ok = False
        if not ok:
            return
        
        if self.project.getProjectType() in ["Qt4", "Qt4C", "E4Plugin"]:
            self.pylupdate = 'pylupdate4'
            if Utilities.isWindowsPlatform():
                self.pylupdate = self.pylupdate + '.exe'
        elif self.project.getProjectType() in ["PyQt5", "PyQt5C"]:
            self.pylupdate = 'pylupdate5'
            if Utilities.isWindowsPlatform():
                self.pylupdate = self.pylupdate + '.exe'
        elif self.project.getProjectType() in ["PySide", "PySideC"]:
            self.pylupdate = Utilities.generatePySideToolPath('pyside-lupdate')
        else:
            return

        self.__pylupdateProcesses = []
        for tempProjectFile in self.__tmpProjects[:]:
            proc = QProcess()
            args = []

            if noobsolete:
                args.append('-noobsolete')
            
            args.append('-verbose')
            path, filename = os.path.split(tempProjectFile)
            args.append(filename)
            proc.setWorkingDirectory(os.path.join(self.project.ppath, path))
            proc.finished.connect(self.__generateTSFileDone)
            proc.readyReadStandardOutput.connect(self.__readStdoutLupdate)
            proc.readyReadStandardError.connect(self.__readStderrLupdate)
            
            proc.start(self.pylupdate, args)
            procStarted = proc.waitForStarted()
            if procStarted:
                self.pylupdateProcRunning = True
                self.__pylupdateProcesses.append((proc, tempProjectFile))
            else:
                E5MessageBox.critical(
                    self,
                    self.trUtf8('Process Generation Error'),
                    self.trUtf8(
                        'Could not start {0}.<br>'
                        'Ensure that it is in the search path.'
                    ).format(self.pylupdate))
                # cleanup
                try:
                    self.__tmpProjects.remove(tempProjectFile)
                    os.remove(tempProjectFile)
                except EnvironmentError:
                    pass
        
    def __generateAll(self):
        """
        Private method to generate all translation files (.ts) for Qt Linguist.
        
        All obsolete strings are removed from the .ts file.
        """
        self.__generateTSFile(noobsolete=True, generateAll=True)
        
    def __generateObsoleteAll(self):
        """
        Private method to generate all translation files (.ts) for Qt Linguist.
        
        Obsolete strings are kept.
        """
        self.__generateTSFile(noobsolete=False, generateAll=True)
        
    def __generateSelected(self):
        """
        Private method to generate selected translation files (.ts) for
        Qt Linguist.
        
        All obsolete strings are removed from the .ts file.
        """
        self.__generateTSFile(noobsolete=True, generateAll=False)
        
    def __generateObsoleteSelected(self):
        """
        Private method to generate selected translation files (.ts) for
        Qt Linguist.
        
        Obsolete strings are kept.
        """
        self.__generateTSFile(noobsolete=False, generateAll=False)
    
    ###########################################################################
    ##  Methods for the release commands
    ###########################################################################
    
    def __releaseTSFileDone(self, exitCode, exitStatus):
        """
        Private slot to handle the finished signal of the lrelease process.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        if exitStatus == QProcess.NormalExit and exitCode == 0:
            ui = e5App().getObject("UserInterface")
            if ui.notificationsEnabled():
                ui.showNotification(
                    UI.PixmapCache.getPixmap("linguist48.png"),
                    self.trUtf8("Translation file release"),
                    self.trUtf8("The release of the translation files (*.qm)"
                                " was successful."))
            else:
                E5MessageBox.information(
                    self,
                    self.trUtf8("Translation file release"),
                    self.trUtf8("The release of the translation files (*.qm)"
                                " was successful."))
            if self.project.pdata["TRANSLATIONSBINPATH"] and \
                    self.project.pdata["TRANSLATIONSBINPATH"][0]:
                target = os.path.join(
                    self.project.ppath,
                    self.project.pdata["TRANSLATIONSBINPATH"][0])
                for langFile in self.project.pdata["TRANSLATIONS"][:]:
                    if langFile.endswith('.ts'):
                        qmFile = os.path.join(self.project.ppath,
                                              langFile.replace('.ts', '.qm'))
                        if os.path.exists(qmFile):
                            shutil.move(qmFile, target)
        else:
            E5MessageBox.critical(
                self,
                self.trUtf8("Translation file release"),
                self.trUtf8(
                    "The release of the translation files (*.qm) has failed."))
        
        proc = self.sender()
        for index in range(len(self.__lreleaseProcesses)):
            if proc == self.__lreleaseProcesses[index][0]:
                try:
                    self.__tmpProjects.remove(
                        self.__lreleaseProcesses[index][1])
                    os.remove(self.__lreleaseProcesses[index][1])
                except EnvironmentError:
                    pass
                del self.__lreleaseProcesses[index]
                break
        if not self.__lreleaseProcesses:
            # all done
            self.lreleaseProcRunning = False
            self.project.checkLanguageFiles()
        
    def __releaseTSFile(self, generateAll=False):
        """
        Private method to run lrelease to release the translation files (.qm).
        
        @param generateAll flag indicating whether all translations should be
            released (boolean)
        """
        if generateAll:
            langs = []
        else:
            langs = self.getSelectedItems()
        
        # Hooks support
        if generateAll:
            if self.hooks["releaseAll"] is not None:
                self.hooks["releaseAll"](self.project.pdata["TRANSLATIONS"])
                return
        else:
            if self.hooks["releaseSelected"] is not None:
                li = [self.project.getRelativePath(lang.fileName())
                      for lang in langs]
                self.hooks["releaseSelected"](li)
                return
        
        # generate a minimal temporary projectfile suitable for lrelease
        self.__tmpProjects = []
        if self.project.pdata["PROGLANGUAGE"][0] in \
                ["Python", "Python2", "Python3"]:
            ok = self.__writeTempProjectFile(langs, [".py"])
        else:
            ok = False
        if not ok:
            return
        
        if self.project.getProjectType() in \
                ["Qt4", "Qt4C", "PyQt5", "PyQt5C", "E4Plugin",
                 "PySide", "PySideC"]:
            lrelease = os.path.join(
                Utilities.getQtBinariesPath(),
                Utilities.generateQtToolName("lrelease"))
        else:
            return
        if Utilities.isWindowsPlatform():
            lrelease = lrelease + '.exe'
        
        self.__lreleaseProcesses = []
        for tempProjectFile in self.__tmpProjects[:]:
            proc = QProcess()
            args = []

            args.append('-verbose')
            path, filename = os.path.split(tempProjectFile)
            args.append(filename)
            proc.setWorkingDirectory(os.path.join(self.project.ppath, path))
            proc.finished.connect(self.__releaseTSFileDone)
            proc.readyReadStandardOutput.connect(self.__readStdoutLrelease)
            proc.readyReadStandardError.connect(self.__readStderrLrelease)
            
            proc.start(lrelease, args)
            procStarted = proc.waitForStarted()
            if procStarted:
                self.lreleaseProcRunning = True
                self.__lreleaseProcesses.append((proc, tempProjectFile))
            else:
                E5MessageBox.critical(
                    self,
                    self.trUtf8('Process Generation Error'),
                    self.trUtf8(
                        '<p>Could not start lrelease.<br>'
                        'Ensure that it is available as <b>{0}</b>.</p>'
                    ).format(lrelease))
                
                # cleanup
                try:
                    self.__tmpProjects.remove(tempProjectFile)
                    os.remove(tempProjectFile)
                except EnvironmentError:
                    pass
        
    def __releaseSelected(self):
        """
        Private method to release the translation files (.qm).
        """
        self.__releaseTSFile(generateAll=False)
        
    def __releaseAll(self):
        """
        Private method to release the translation files (.qm).
        """
        self.__releaseTSFile(generateAll=True)
    
    ###########################################################################
    ## Support for hooks below
    ###########################################################################
    
    def _initHookMethods(self):
        """
        Protected method to initialize the hooks dictionary.
        
        Supported hook methods are:
        <ul>
        <li>extractMessages: takes no parameters</li>
        <li>generateAll: takes list of filenames as parameter</li>
        <li>generateAllWithObsolete: takes list of filenames as parameter</li>
        <li>generateSelected: takes list of filenames as parameter</li>
        <li>generateSelectedWithObsolete: takes list of filenames as
            parameter</li>
        <li>releaseAll: takes list of filenames as parameter</li>
        <li>releaseSelected: takes list of filenames as parameter</li>
        <li>open: takes a filename as parameter</li>
        </ul>
        
        <b>Note</b>: Filenames are relative to the project directory.
        """
        self.hooks = {
            "extractMessages": None,
            "generateAll": None,
            "generateAllWithObsolete": None,
            "generateSelected": None,
            "generateSelectedWithObsolete": None,
            "releaseAll": None,
            "releaseSelected": None,
            "open": None,
        }

eric ide

mercurial