eric6/Project/ProjectFormsBrowser.py

changeset 6942
2602857055c5
parent 6659
f6c059deb5ea
child 7050
b66cac9a6560
diff -r f99d60d6b59b -r 2602857055c5 eric6/Project/ProjectFormsBrowser.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Project/ProjectFormsBrowser.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,1120 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a class used to display the forms part of the project.
+"""
+
+from __future__ import unicode_literals
+try:
+    str = unicode
+except NameError:
+    pass
+
+import os
+import sys
+import shutil
+
+from PyQt5.QtCore import PYQT_VERSION, QThread, QFileInfo, pyqtSignal, QProcess
+from PyQt5.QtWidgets import QDialog, QInputDialog, QApplication, QMenu
+
+from E5Gui.E5Application import e5App
+from E5Gui import E5MessageBox, E5FileDialog
+from E5Gui.E5ProgressDialog import E5ProgressDialog
+
+from .ProjectBrowserModel import ProjectBrowserFileItem, \
+    ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem, \
+    ProjectBrowserFormType
+from .ProjectBaseBrowser import ProjectBaseBrowser
+
+import UI.PixmapCache
+
+import Preferences
+import Utilities
+
+from eric6config import getConfig
+
+
+class ProjectFormsBrowser(ProjectBaseBrowser):
+    """
+    A class used to display the forms part of the project.
+    
+    @signal appendStderr(str) emitted after something was received from
+        a QProcess on stderr
+    @signal uipreview(str) emitted to preview a forms file
+    @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.
+    @signal menusAboutToBeCreated() emitted when the context menus are about to
+        be created. This is the right moment to add or remove hook methods.
+    """
+    appendStderr = pyqtSignal(str)
+    uipreview = pyqtSignal(str)
+    showMenu = pyqtSignal(str, QMenu)
+    menusAboutToBeCreated = pyqtSignal()
+    
+    PyuicIndentDefault = 4
+    
+    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, ProjectBrowserFormType,
+                                    parent)
+        
+        self.selectedItemsFilter = \
+            [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem]
+        
+        self.setWindowTitle(self.tr('Forms'))
+
+        self.setWhatsThis(self.tr(
+            """<b>Project Forms Browser</b>"""
+            """<p>This allows to easily see all forms contained in the"""
+            """ current project. Several actions can be executed via the"""
+            """ context menu.</p>"""
+        ))
+        
+        # templates for Qt4
+        # these two lists have to stay in sync
+        self.templates4 = [
+            'dialog4.tmpl', 'widget4.tmpl', 'mainwindow4.tmpl',
+            'dialogbuttonboxbottom4.tmpl', 'dialogbuttonboxright4.tmpl',
+            'dialogbuttonsbottom4.tmpl', 'dialogbuttonsbottomcenter4.tmpl',
+            'dialogbuttonsright4.tmpl', '', 'wizard4.tmpl', 'wizardpage4.tmpl',
+            'qdockwidget4.tmpl', 'qframe4.tmpl', 'qgroupbox4.tmpl',
+            'qscrollarea4.tmpl', 'qmdiarea4.tmpl', 'qtabwidget4.tmpl',
+            'qtoolbox4.tmpl', 'qstackedwidget4.tmpl'
+        ]
+        self.templateTypes4 = [
+            self.tr("Dialog"),
+            self.tr("Widget"),
+            self.tr("Main Window"),
+            self.tr("Dialog with Buttonbox (Bottom)"),
+            self.tr("Dialog with Buttonbox (Right)"),
+            self.tr("Dialog with Buttons (Bottom)"),
+            self.tr("Dialog with Buttons (Bottom-Center)"),
+            self.tr("Dialog with Buttons (Right)"),
+            '',
+            self.tr("QWizard"),
+            self.tr("QWizardPage"),
+            self.tr("QDockWidget"),
+            self.tr("QFrame"),
+            self.tr("QGroupBox"),
+            self.tr("QScrollArea"),
+            self.tr("QMdiArea"),
+            self.tr("QTabWidget"),
+            self.tr("QToolBox"),
+            self.tr("QStackedWidget"),
+        ]
+        
+        self.compileProc = None
+        self.__uicompiler = ""
+        
+        self.project.projectClosed.connect(self.__resetUiCompiler)
+        self.project.projectPropertiesChanged.connect(self.__resetUiCompiler)
+        
+    def _createPopupMenus(self):
+        """
+        Protected overloaded method to generate the popup menu.
+        """
+        self.menuActions = []
+        self.multiMenuActions = []
+        self.dirMenuActions = []
+        self.dirMultiMenuActions = []
+        
+        self.menusAboutToBeCreated.emit()
+        
+        self.menu = QMenu(self)
+        if self.project.getProjectType() in \
+                ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+            self.menu.addAction(
+                self.tr('Compile form'), self.__compileForm)
+            self.menu.addAction(
+                self.tr('Compile all forms'),
+                self.__compileAllForms)
+            self.menu.addAction(
+                self.tr('Generate Dialog Code...'),
+                self.__generateDialogCode)
+            self.menu.addSeparator()
+            self.menu.addAction(
+                self.tr('Configure uic Compiler'),
+                self.__configureUicCompiler)
+            self.menu.addSeparator()
+            self.menu.addAction(
+                self.tr('Open in Qt-Designer'), self.__openFile)
+            self.menu.addAction(
+                self.tr('Open in Editor'), self.__openFileInEditor)
+            self.menu.addSeparator()
+            self.menu.addAction(self.tr('Preview form'), self.__UIPreview)
+            self.menu.addAction(
+                self.tr('Preview translations'), self.__TRPreview)
+        else:
+            if self.hooks["compileForm"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileForm",
+                        self.tr('Compile form')), self.__compileForm)
+            if self.hooks["compileAllForms"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileAllForms",
+                        self.tr('Compile all forms')),
+                    self.__compileAllForms)
+            if self.hooks["generateDialogCode"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get(
+                        "generateDialogCode",
+                        self.tr('Generate Dialog Code...')),
+                    self.__generateDialogCode)
+            if self.hooks["compileForm"] is not None or \
+               self.hooks["compileAllForms"] is not None or \
+               self.hooks["generateDialogCode"] is not None:
+                self.menu.addSeparator()
+            if self.hooks["open"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get("open", self.tr('Open')),
+                    self.__openFile)
+            self.menu.addAction(self.tr('Open'), self.__openFileInEditor)
+        self.menu.addSeparator()
+        act = self.menu.addAction(self.tr('Rename file'), self._renameFile)
+        self.menuActions.append(act)
+        act = self.menu.addAction(
+            self.tr('Remove from project'), self._removeFile)
+        self.menuActions.append(act)
+        act = self.menu.addAction(self.tr('Delete'), self.__deleteFile)
+        self.menuActions.append(act)
+        self.menu.addSeparator()
+        if self.project.getProjectType() in \
+                ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+            self.menu.addAction(self.tr('New form...'), self.__newForm)
+        else:
+            if self.hooks["newForm"] is not None:
+                self.menu.addAction(
+                    self.hooksMenuEntries.get(
+                        "newForm", self.tr('New form...')), self.__newForm)
+        self.menu.addAction(self.tr('Add forms...'), self.__addFormFiles)
+        self.menu.addAction(
+            self.tr('Add forms directory...'), self.__addFormsDirectory)
+        self.menu.addSeparator()
+        self.menu.addAction(
+            self.tr('Copy Path to Clipboard'), self._copyToClipboard)
+        self.menu.addSeparator()
+        self.menu.addAction(
+            self.tr('Expand all directories'), self._expandAllDirs)
+        self.menu.addAction(
+            self.tr('Collapse all directories'), self._collapseAllDirs)
+        self.menu.addSeparator()
+        self.menu.addAction(self.tr('Configure...'), self._configure)
+
+        self.backMenu = QMenu(self)
+        if self.project.getProjectType() in \
+                ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"] or \
+                self.hooks["compileAllForms"] is not None:
+            self.backMenu.addAction(
+                self.tr('Compile all forms'), self.__compileAllForms)
+            self.backMenu.addSeparator()
+            self.backMenu.addAction(
+                self.tr('Configure uic Compiler'),
+                self.__configureUicCompiler)
+            self.backMenu.addSeparator()
+            self.backMenu.addAction(self.tr('New form...'), self.__newForm)
+        else:
+            if self.hooks["newForm"] is not None:
+                self.backMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "newForm", self.tr('New form...')), self.__newForm)
+        self.backMenu.addAction(
+            self.tr('Add forms...'), self.project.addUiFiles)
+        self.backMenu.addAction(
+            self.tr('Add forms directory...'), self.project.addUiDir)
+        self.backMenu.addSeparator()
+        self.backMenu.addAction(
+            self.tr('Expand all directories'), self._expandAllDirs)
+        self.backMenu.addAction(
+            self.tr('Collapse all directories'), self._collapseAllDirs)
+        self.backMenu.addSeparator()
+        self.backMenu.addAction(self.tr('Configure...'), self._configure)
+        self.backMenu.setEnabled(False)
+
+        # create the menu for multiple selected files
+        self.multiMenu = QMenu(self)
+        if self.project.getProjectType() in \
+                ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+            self.multiMenu.addAction(
+                self.tr('Compile forms'), self.__compileSelectedForms)
+            self.multiMenu.addSeparator()
+            self.multiMenu.addAction(
+                self.tr('Configure uic Compiler'),
+                self.__configureUicCompiler)
+            self.multiMenu.addSeparator()
+            self.multiMenu.addAction(
+                self.tr('Open in Qt-Designer'), self.__openFile)
+            self.multiMenu.addAction(
+                self.tr('Open in Editor'), self.__openFileInEditor)
+            self.multiMenu.addSeparator()
+            self.multiMenu.addAction(
+                self.tr('Preview translations'), self.__TRPreview)
+        else:
+            if self.hooks["compileSelectedForms"] is not None:
+                act = self.multiMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileSelectedForms",
+                        self.tr('Compile forms')),
+                    self.__compileSelectedForms)
+                self.multiMenu.addSeparator()
+            if self.hooks["open"] is not None:
+                self.multiMenu.addAction(
+                    self.hooksMenuEntries.get("open", self.tr('Open')),
+                    self.__openFile)
+            self.multiMenu.addAction(
+                self.tr('Open'), self.__openFileInEditor)
+        self.multiMenu.addSeparator()
+        act = self.multiMenu.addAction(
+            self.tr('Remove from project'), self._removeFile)
+        self.multiMenuActions.append(act)
+        act = self.multiMenu.addAction(
+            self.tr('Delete'), self.__deleteFile)
+        self.multiMenuActions.append(act)
+        self.multiMenu.addSeparator()
+        self.multiMenu.addAction(
+            self.tr('Expand all directories'), self._expandAllDirs)
+        self.multiMenu.addAction(
+            self.tr('Collapse all directories'), self._collapseAllDirs)
+        self.multiMenu.addSeparator()
+        self.multiMenu.addAction(self.tr('Configure...'), self._configure)
+
+        self.dirMenu = QMenu(self)
+        if self.project.getProjectType() in \
+                ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+            self.dirMenu.addAction(
+                self.tr('Compile all forms'), self.__compileAllForms)
+            self.dirMenu.addSeparator()
+            self.dirMenu.addAction(
+                self.tr('Configure uic Compiler'),
+                self.__configureUicCompiler)
+            self.dirMenu.addSeparator()
+        else:
+            if self.hooks["compileAllForms"] is not None:
+                self.dirMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileAllForms",
+                        self.tr('Compile all forms')),
+                    self.__compileAllForms)
+                self.dirMenu.addSeparator()
+        act = self.dirMenu.addAction(
+            self.tr('Remove from project'), self._removeDir)
+        self.dirMenuActions.append(act)
+        act = self.dirMenu.addAction(
+            self.tr('Delete'), self._deleteDirectory)
+        self.dirMenuActions.append(act)
+        self.dirMenu.addSeparator()
+        if self.project.getProjectType() in \
+                ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+            self.dirMenu.addAction(self.tr('New form...'), self.__newForm)
+        else:
+            if self.hooks["newForm"] is not None:
+                self.dirMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "newForm",
+                        self.tr('New form...')), self.__newForm)
+        self.dirMenu.addAction(
+            self.tr('Add forms...'), self.__addFormFiles)
+        self.dirMenu.addAction(
+            self.tr('Add forms directory...'), self.__addFormsDirectory)
+        self.dirMenu.addSeparator()
+        self.dirMenu.addAction(
+            self.tr('Copy Path to Clipboard'), self._copyToClipboard)
+        self.dirMenu.addSeparator()
+        self.dirMenu.addAction(
+            self.tr('Expand all directories'), self._expandAllDirs)
+        self.dirMenu.addAction(
+            self.tr('Collapse all directories'), self._collapseAllDirs)
+        self.dirMenu.addSeparator()
+        self.dirMenu.addAction(self.tr('Configure...'), self._configure)
+        
+        self.dirMultiMenu = QMenu(self)
+        if self.project.getProjectType() in \
+                ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+            self.dirMultiMenu.addAction(
+                self.tr('Compile all forms'), self.__compileAllForms)
+            self.dirMultiMenu.addSeparator()
+            self.dirMultiMenu.addAction(
+                self.tr('Configure uic Compiler'),
+                self.__configureUicCompiler)
+            self.dirMultiMenu.addSeparator()
+        else:
+            if self.hooks["compileAllForms"] is not None:
+                self.dirMultiMenu.addAction(
+                    self.hooksMenuEntries.get(
+                        "compileAllForms",
+                        self.tr('Compile all forms')),
+                    self.__compileAllForms)
+                self.dirMultiMenu.addSeparator()
+        self.dirMultiMenu.addAction(
+            self.tr('Add forms...'), self.project.addUiFiles)
+        self.dirMultiMenu.addAction(
+            self.tr('Add forms directory...'), self.project.addUiDir)
+        self.dirMultiMenu.addSeparator()
+        self.dirMultiMenu.addAction(
+            self.tr('Expand all directories'), self._expandAllDirs)
+        self.dirMultiMenu.addAction(
+            self.tr('Collapse all directories'), self._collapseAllDirs)
+        self.dirMultiMenu.addSeparator()
+        self.dirMultiMenu.addAction(
+            self.tr('Configure...'), self._configure)
+        
+        self.menu.aboutToShow.connect(self.__showContextMenu)
+        self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
+        self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
+        self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
+        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))
+            elif cnt > 1 and cnt == sdcnt:
+                self.dirMultiMenu.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 Exception:
+            pass
+        
+    def __showContextMenu(self):
+        """
+        Private slot called by the menu aboutToShow signal.
+        """
+        ProjectBaseBrowser._showContextMenu(self, self.menu)
+        
+        self.showMenu.emit("Main", self.menu)
+        
+    def __showContextMenuMulti(self):
+        """
+        Private slot called by the multiMenu aboutToShow signal.
+        """
+        ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)
+        
+        self.showMenu.emit("MainMulti", self.multiMenu)
+        
+    def __showContextMenuDir(self):
+        """
+        Private slot called by the dirMenu aboutToShow signal.
+        """
+        ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)
+        
+        self.showMenu.emit("MainDir", self.dirMenu)
+        
+    def __showContextMenuDirMulti(self):
+        """
+        Private slot called by the dirMultiMenu aboutToShow signal.
+        """
+        ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu)
+        
+        self.showMenu.emit("MainDirMulti", self.dirMultiMenu)
+        
+    def __showContextMenuBack(self):
+        """
+        Private slot called by the backMenu aboutToShow signal.
+        """
+        ProjectBaseBrowser._showContextMenuBack(self, self.backMenu)
+        
+        self.showMenu.emit("MainBack", self.backMenu)
+        
+    def __addFormFiles(self):
+        """
+        Private method to add form 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('form', dn)
+        
+    def __addFormsDirectory(self):
+        """
+        Private method to add form files of a directory 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.addDirectory('form', dn)
+        
+    def __openFile(self):
+        """
+        Private slot to handle the Open menu action.
+        """
+        itmList = self.getSelectedItems()
+        for itm in itmList[:]:
+            try:
+                if isinstance(itm, ProjectBrowserFileItem):
+                    # hook support
+                    if self.hooks["open"] is not None:
+                        self.hooks["open"](itm.fileName())
+                    else:
+                        self.designerFile.emit(itm.fileName())
+            except Exception:
+                pass
+        
+    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 _openItem(self):
+        """
+        Protected slot to handle the open popup menu entry.
+        """
+        itmList = self.getSelectedItems()
+        for itm in itmList:
+            if isinstance(itm, ProjectBrowserFileItem):
+                if itm.isDesignerFile():
+                    self.designerFile.emit(itm.fileName())
+                else:
+                    self.sourceFile.emit(itm.fileName())
+        
+    def __UIPreview(self):
+        """
+        Private slot to handle the Preview menu action.
+        """
+        itmList = self.getSelectedItems()
+        self.uipreview.emit(itmList[0].fileName())
+        
+    def __TRPreview(self):
+        """
+        Private slot to handle the Preview translations action.
+        """
+        fileNames = []
+        for itm in self.getSelectedItems():
+            fileNames.append(itm.fileName())
+        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].emit(fileNames)
+        
+    def __newForm(self):
+        """
+        Private slot to handle the New Form menu action.
+        """
+        itm = self.model().item(self.currentIndex())
+        if itm is None:
+            path = self.project.ppath
+        else:
+            try:
+                path = os.path.dirname(itm.fileName())
+            except AttributeError:
+                try:
+                    path = itm.dirName()
+                except AttributeError:
+                    path = os.path.join(self.project.ppath, itm.data(0))
+        
+        if self.hooks["newForm"] is not None:
+            self.hooks["newForm"](path)
+        else:
+            if self.project.getProjectType() in \
+                    ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+                self.__newUiForm(path)
+        
+    def __newUiForm(self, path):
+        """
+        Private slot to handle the New Form menu action for Qt-related
+        projects.
+        
+        @param path full directory path for the new form file (string)
+        """
+        selectedForm, ok = QInputDialog.getItem(
+            None,
+            self.tr("New Form"),
+            self.tr("Select a form type:"),
+            self.templateTypes4,
+            0, False)
+        if not ok or not selectedForm:
+            # user pressed cancel
+            return
+        
+        templateIndex = self.templateTypes4.index(selectedForm)
+        templateFile = os.path.join(
+            getConfig('ericTemplatesDir'), self.templates4[templateIndex])
+        
+        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
+            self,
+            self.tr("New Form"),
+            path,
+            self.tr("Qt User-Interface Files (*.ui);;All Files (*)"),
+            "",
+            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
+        
+        if not fname:
+            # user aborted or didn't enter a filename
+            return
+        
+        ext = QFileInfo(fname).suffix()
+        if not ext:
+            ex = selectedFilter.split("(*")[1].split(")")[0]
+            if ex:
+                fname += ex
+        
+        if os.path.exists(fname):
+            res = E5MessageBox.yesNo(
+                self,
+                self.tr("New Form"),
+                self.tr("The file already exists! Overwrite it?"),
+                icon=E5MessageBox.Warning)
+            if not res:
+                # user selected to not overwrite
+                return
+        
+        try:
+            shutil.copy(templateFile, fname)
+        except IOError as e:
+            E5MessageBox.critical(
+                self,
+                self.tr("New Form"),
+                self.tr(
+                    "<p>The new form file <b>{0}</b> could not be created.<br>"
+                    "Problem: {1}</p>").format(fname, str(e)))
+            return
+        
+        self.project.appendFile(fname)
+        self.designerFile.emit(fname)
+        
+    def __deleteFile(self):
+        """
+        Private method to delete a form file from the project.
+        """
+        itmList = self.getSelectedItems()
+        
+        files = []
+        fullNames = []
+        for itm in itmList:
+            fn2 = itm.fileName()
+            fullNames.append(fn2)
+            fn = self.project.getRelativePath(fn2)
+            files.append(fn)
+        
+        from UI.DeleteFilesConfirmationDialog import \
+            DeleteFilesConfirmationDialog
+        dlg = DeleteFilesConfirmationDialog(
+            self.parent(),
+            self.tr("Delete forms"),
+            self.tr(
+                "Do you really want to delete these forms from the project?"),
+            files)
+        
+        if dlg.exec_() == QDialog.Accepted:
+            for fn2, fn in zip(fullNames, files):
+                self.closeSourceWindow.emit(fn2)
+                self.project.deleteFile(fn)
+    
+    ###########################################################################
+    ##  Methods to handle the various compile commands
+    ###########################################################################
+    
+    def __resetUiCompiler(self):
+        """
+        Private slot to reset the determined UI compiler executable.
+        """
+        self.__uicompiler = ""
+    
+    def __determineUiCompiler(self):
+        """
+        Private method to determine the UI compiler for the project.
+        """
+        self.__resetUiCompiler()
+        
+        if self.project.getProjectLanguage() in \
+                ["Python", "Python2", "Python3"]:
+            if self.project.getProjectType() in ["Qt4", ]:
+                self.__uicompiler = Utilities.generatePyQtToolPath(
+                    'pyuic4', ["py3uic4", "py2uic4"])
+            elif self.project.getProjectType() in ["PyQt5"]:
+                self.__uicompiler = Utilities.generatePyQtToolPath(
+                    'pyuic5', ["py3uic5", "py2uic5"])
+            elif self.project.getProjectType() in ["E6Plugin"]:
+                if PYQT_VERSION < 0x050000:
+                    self.__uicompiler = Utilities.generatePyQtToolPath(
+                        'pyuic4', ["py3uic4", "py2uic4"])
+                else:
+                    self.__uicompiler = Utilities.generatePyQtToolPath(
+                        'pyuic5', ["py3uic5", "py2uic5"])
+            elif self.project.getProjectType() == "PySide":
+                self.__uicompiler = \
+                    Utilities.generatePySideToolPath('pyside-uic', "1")
+            elif self.project.getProjectType() == "PySide2":
+                self.__uicompiler = \
+                    Utilities.generatePySideToolPath('pyside2-uic', "2")
+        elif self.project.getProjectLanguage() == "Ruby":
+            if self.project.getProjectType() == "Qt4":
+                self.__uicompiler = 'rbuic4'
+                if Utilities.isWindowsPlatform():
+                    self.__uicompiler = \
+                        Utilities.getWindowsExecutablePath(self.__uicompiler)
+    
+    def getUiCompiler(self):
+        """
+        Public method to get the UI compiler executable of the project.
+        
+        @return UI compiler executable
+        @rtype str
+        """
+        if not self.__uicompiler:
+            self.__determineUiCompiler()
+        
+        return self.__uicompiler
+    
+    def __readStdout(self):
+        """
+        Private slot to handle the readyReadStandardOutput signal of the
+        pyuic4/pyuic5/pyside-uic/pyside2-uic/rbuic4 process.
+        """
+        if self.compileProc is None:
+            return
+        self.compileProc.setReadChannel(QProcess.StandardOutput)
+        
+        while self.compileProc and self.compileProc.canReadLine():
+            self.buf += str(self.compileProc.readLine(),
+                            "utf-8", 'replace')
+        
+    def __readStderr(self):
+        """
+        Private slot to handle the readyReadStandardError signal of the
+        pyuic4/pyuic5/pyside-uic/pyside2-uic/rbuic4 process.
+        """
+        if self.compileProc is None:
+            return
+        
+        ioEncoding = Preferences.getSystem("IOEncoding")
+        
+        self.compileProc.setReadChannel(QProcess.StandardError)
+        while self.compileProc and self.compileProc.canReadLine():
+            s = self.__uicompiler + ': '
+            error = str(self.compileProc.readLine(),
+                        ioEncoding, 'replace')
+            s += error
+            self.appendStderr.emit(s)
+        
+    def __compileUIDone(self, exitCode, exitStatus):
+        """
+        Private slot to handle the finished signal of the pyuic/rbuic process.
+        
+        @param exitCode exit code of the process (integer)
+        @param exitStatus exit status of the process (QProcess.ExitStatus)
+        """
+        self.compileRunning = False
+        e5App().getObject("ViewManager").enableEditorsCheckFocusIn(True)
+        ui = e5App().getObject("UserInterface")
+        if exitStatus == QProcess.NormalExit and exitCode == 0 and self.buf:
+            ofn = os.path.join(self.project.ppath, self.compiledFile)
+            try:
+                if self.project.useSystemEol():
+                    newline = None
+                else:
+                    newline = self.project.getEolString()
+                f = open(ofn, "w", encoding="utf-8", newline=newline)
+                for line in self.buf.splitlines():
+                    f.write(line + "\n")
+                f.close()
+                if self.compiledFile not in self.project.pdata["SOURCES"]:
+                    self.project.appendFile(ofn)
+                if not self.noDialog and not ui.notificationsEnabled():
+                    E5MessageBox.information(
+                        self,
+                        self.tr("Form Compilation"),
+                        self.tr("The compilation of the form file"
+                                " was successful."))
+                else:
+                    ui.showNotification(
+                        UI.PixmapCache.getPixmap("designer48.png"),
+                        self.tr("Form Compilation"),
+                        self.tr("The compilation of the form file"
+                                " was successful."))
+                self.project.projectFormCompiled.emit(self.compiledFile)
+            except IOError as msg:
+                if not self.noDialog:
+                    E5MessageBox.information(
+                        self,
+                        self.tr("Form Compilation"),
+                        self.tr(
+                            "<p>The compilation of the form file failed.</p>"
+                            "<p>Reason: {0}</p>").format(str(msg)))
+                else:
+                    ui.showNotification(
+                        UI.PixmapCache.getPixmap("designer48.png"),
+                        self.tr("Form Compilation"),
+                        self.tr(
+                            "<p>The compilation of the form file failed.</p>"
+                            "<p>Reason: {0}</p>").format(str(msg)))
+        else:
+            if not self.noDialog:
+                E5MessageBox.information(
+                    self,
+                    self.tr("Form Compilation"),
+                    self.tr("The compilation of the form file failed."))
+            else:
+                ui.showNotification(
+                    UI.PixmapCache.getPixmap("designer48.png"),
+                    self.tr("Form Compilation"),
+                    self.tr("The compilation of the form file failed."))
+        self.compileProc = None
+        
+    def __compileUI(self, fn, noDialog=False, progress=None):
+        """
+        Private method to compile a .ui file to a .py/.rb file.
+        
+        @param fn filename of the .ui file to be compiled
+        @param noDialog flag indicating silent operations
+        @param progress reference to the progress dialog
+        @return reference to the compile process (QProcess)
+        """
+        self.compileProc = QProcess()
+        args = []
+        self.buf = ""
+        
+        uicompiler = self.getUiCompiler()
+        if not uicompiler:
+            return None
+        
+        ofn, ext = os.path.splitext(fn)
+        fn = os.path.join(self.project.ppath, fn)
+        
+        if self.project.getProjectLanguage() in \
+                ["Python", "Python2", "Python3"]:
+            dirname, filename = os.path.split(ofn)
+            self.compiledFile = os.path.join(dirname, "Ui_" + filename + ".py")
+            args.append("-x")
+            indentWidth = Preferences.getQt("PyuicIndent")
+            if indentWidth != self.PyuicIndentDefault:
+                args.append("--indent={0}".format(indentWidth))
+            
+            if self.project.getProjectType() in ["PySide", "PySide2"]:
+                # PySide and PySide2
+                if Preferences.getQt("PyuicFromImports"):
+                    args.append("--from-imports")
+            else:
+                # PyQt4 and PyQt5
+                if 'uic5' in uicompiler and \
+                   PYQT_VERSION >= 0x050600 and \
+                   self.project.pdata["UICPARAMS"]["Package"]:
+                    # only supported for PyQt5 >= 5.6 (April 2016)
+                    args.append("--import-from={0}".format(
+                        self.project.pdata["UICPARAMS"]["Package"]))
+                elif Preferences.getQt("PyuicFromImports"):
+                    args.append("--from-imports")
+                if self.project.pdata["UICPARAMS"]["RcSuffix"]:
+                    args.append("--resource-suffix={0}".format(
+                        self.project.pdata["UICPARAMS"]["RcSuffix"]))
+        elif self.project.getProjectLanguage() == "Ruby":
+            self.compiledFile = ofn + '.rb'
+            args.append('-x')
+        
+        args.append(fn)
+        self.compileProc.finished.connect(self.__compileUIDone)
+        self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
+        self.compileProc.readyReadStandardError.connect(self.__readStderr)
+        
+        self.noDialog = noDialog
+        self.compileProc.setWorkingDirectory(self.project.getProjectPath())
+        self.compileProc.start(uicompiler, args)
+        procStarted = self.compileProc.waitForStarted(5000)
+        if procStarted:
+            self.compileRunning = True
+            e5App().getObject("ViewManager").enableEditorsCheckFocusIn(False)
+            return self.compileProc
+        else:
+            self.compileRunning = False
+            if progress is not None:
+                progress.cancel()
+            E5MessageBox.critical(
+                self,
+                self.tr('Process Generation Error'),
+                self.tr(
+                    'Could not start {0}.<br>'
+                    'Ensure that it is in the search path.'
+                ).format(uicompiler))
+            return None
+        
+    def __generateDialogCode(self):
+        """
+        Private method to generate dialog code for the form (Qt4 only).
+        """
+        itm = self.model().item(self.currentIndex())
+        fn = itm.fileName()
+        
+        if self.hooks["generateDialogCode"] is not None:
+            self.hooks["generateDialogCode"](fn)
+        else:
+            from .CreateDialogCodeDialog import CreateDialogCodeDialog
+            
+            # change environment
+            sys.path.insert(0, self.project.getProjectPath())
+            cwd = os.getcwd()
+            os.chdir(os.path.dirname(os.path.abspath(fn)))
+            
+            dlg = CreateDialogCodeDialog(fn, self.project, self)
+            if not dlg.initError():
+                dlg.exec_()
+            
+            # reset the environment
+            os.chdir(cwd)
+            del sys.path[0]
+        
+    def __compileForm(self):
+        """
+        Private method to compile a form to a source file.
+        """
+        itm = self.model().item(self.currentIndex())
+        fn2 = itm.fileName()
+        fn = self.project.getRelativePath(fn2)
+        if self.hooks["compileForm"] is not None:
+            self.hooks["compileForm"](fn)
+        else:
+            self.__compileUI(fn)
+        
+    def __compileAllForms(self):
+        """
+        Private method to compile all forms to source files.
+        """
+        if self.hooks["compileAllForms"] is not None:
+            self.hooks["compileAllForms"](self.project.pdata["FORMS"])
+        else:
+            numForms = len(self.project.pdata["FORMS"])
+            progress = E5ProgressDialog(
+                self.tr("Compiling forms..."),
+                self.tr("Abort"), 0, numForms,
+                self.tr("%v/%m Forms"), self)
+            progress.setModal(True)
+            progress.setMinimumDuration(0)
+            progress.setWindowTitle(self.tr("Forms"))
+            i = 0
+            
+            for fn in self.project.pdata["FORMS"]:
+                progress.setValue(i)
+                if progress.wasCanceled():
+                    break
+                
+                proc = self.__compileUI(fn, True, progress)
+                if proc is not None:
+                    while proc.state() == QProcess.Running:
+                        QApplication.processEvents()
+                        QThread.msleep(300)
+                        QApplication.processEvents()
+                else:
+                    break
+                i += 1
+                
+            progress.setValue(numForms)
+        
+    def __compileSelectedForms(self):
+        """
+        Private method to compile selected forms to source files.
+        """
+        items = self.getSelectedItems()
+        files = [self.project.getRelativePath(itm.fileName())
+                 for itm in items]
+        
+        if self.hooks["compileSelectedForms"] is not None:
+            self.hooks["compileSelectedForms"](files)
+        else:
+            numForms = len(files)
+            progress = E5ProgressDialog(
+                self.tr("Compiling forms..."),
+                self.tr("Abort"), 0, numForms,
+                self.tr("%v/%m Forms"), self)
+            progress.setModal(True)
+            progress.setMinimumDuration(0)
+            progress.setWindowTitle(self.tr("Forms"))
+            i = 0
+            
+            for fn in files:
+                progress.setValue(i)
+                if progress.wasCanceled():
+                    break
+                
+                proc = self.__compileUI(fn, True, progress)
+                if proc is not None:
+                    while proc.state() == QProcess.Running:
+                        QApplication.processEvents()
+                        QThread.msleep(300)
+                        QApplication.processEvents()
+                else:
+                    break
+                i += 1
+                
+            progress.setValue(numForms)
+        
+    def compileChangedForms(self):
+        """
+        Public method to compile all changed forms to source files.
+        """
+        if self.hooks["compileChangedForms"] is not None:
+            self.hooks["compileChangedForms"](self.project.pdata["FORMS"])
+        else:
+            if self.project.getProjectType() not in \
+               ["Qt4", "PyQt5", "E6Plugin", "PySide", "PySide2"]:
+                # ignore the request for non Qt GUI projects
+                return
+            
+            progress = E5ProgressDialog(
+                self.tr("Determining changed forms..."),
+                self.tr("Abort"), 0, 100, self.tr("%v/%m Forms"))
+            progress.setMinimumDuration(0)
+            progress.setWindowTitle(self.tr("Forms"))
+            i = 0
+            
+            # get list of changed forms
+            changedForms = []
+            progress.setMaximum(len(self.project.pdata["FORMS"]))
+            for fn in self.project.pdata["FORMS"]:
+                progress.setValue(i)
+                QApplication.processEvents()
+                
+                ifn = os.path.join(self.project.ppath, fn)
+                if self.project.getProjectLanguage() in \
+                   ["Python", "Python2", "Python3"]:
+                    dirname, filename = os.path.split(os.path.splitext(ifn)[0])
+                    ofn = os.path.join(dirname, "Ui_" + filename + ".py")
+                elif self.project.getProjectLanguage() == "Ruby":
+                    ofn = os.path.splitext(ifn)[0] + '.rb'
+                if not os.path.exists(ofn) or \
+                   os.stat(ifn).st_mtime > os.stat(ofn).st_mtime:
+                    changedForms.append(fn)
+                i += 1
+            progress.setValue(i)
+            QApplication.processEvents()
+            
+            if changedForms:
+                progress.setLabelText(
+                    self.tr("Compiling changed forms..."))
+                progress.setMaximum(len(changedForms))
+                i = 0
+                progress.setValue(i)
+                QApplication.processEvents()
+                for fn in changedForms:
+                    progress.setValue(i)
+                    if progress.wasCanceled():
+                        break
+                    
+                    proc = self.__compileUI(fn, True, progress)
+                    if proc is not None:
+                        while proc.state() == QProcess.Running:
+                            QApplication.processEvents()
+                            QThread.msleep(300)
+                            QApplication.processEvents()
+                    else:
+                        break
+                    i += 1
+                progress.setValue(len(changedForms))
+                QApplication.processEvents()
+        
+    def handlePreferencesChanged(self):
+        """
+        Public slot used to handle the preferencesChanged signal.
+        """
+        ProjectBaseBrowser.handlePreferencesChanged(self)
+        
+        self.__resetUiCompiler()
+    
+    def __configureUicCompiler(self):
+        """
+        Private slot to configure some non-common uic compiler options.
+        """
+        from .UicCompilerOptionsDialog import UicCompilerOptionsDialog
+        
+        params = self.project.pdata["UICPARAMS"]
+        
+        if self.project.getProjectType() in ["Qt4", "PyQt5", "E6Plugin"]:
+            dlg = UicCompilerOptionsDialog(params, self.getUiCompiler())
+            if dlg.exec_() == QDialog.Accepted:
+                package, suffix = dlg.getData()
+                if package != params["Package"]:
+                    params["Package"] = package
+                    self.project.setDirty(True)
+                if suffix != params["RcSuffix"]:
+                    params["RcSuffix"] = suffix
+                    self.project.setDirty(True)
+        elif self.project.getProjectType() in ["PySide", "PySide2"]:
+            E5MessageBox.information(
+                self,
+                self.tr("Configure uic Compiler"),
+                self.tr("""No project specific uic compiler flags are"""
+                        """ supported for PySide or PySide2."""))
+    
+    ###########################################################################
+    ## Support for hooks below
+    ###########################################################################
+    
+    def _initHookMethods(self):
+        """
+        Protected method to initialize the hooks dictionary.
+        
+        Supported hook methods are:
+        <ul>
+        <li>compileForm: takes filename as parameter</li>
+        <li>compileAllForms: takes list of filenames as parameter</li>
+        <li>compileSelectedForms: takes list of filenames as parameter</li>
+        <li>compileChangedForms: takes list of filenames as parameter</li>
+        <li>generateDialogCode: takes filename as parameter</li>
+        <li>newForm: takes full directory path of new file as parameter</li>
+        <li>open: takes a filename as parameter</li>
+        </ul>
+        
+        <b>Note</b>: Filenames are relative to the project directory, if not
+        specified differently.
+        """
+        self.hooks = {
+            "compileForm": None,
+            "compileAllForms": None,
+            "compileChangedForms": None,
+            "compileSelectedForms": None,
+            "generateDialogCode": None,
+            "newForm": None,
+            "open": None,
+        }

eric ide

mercurial