Continued implementing the PyInstaller interface.

Wed, 17 Jan 2018 16:25:59 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 17 Jan 2018 16:25:59 +0100
changeset 3
eb2d30b4d34e
parent 2
5109c484bc49
child 4
52f0572b5908

Continued implementing the PyInstaller interface.

PluginPyInstaller.e4p file | annotate | diff | comparison | revisions
PluginPyInstaller.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerCleanupDialog.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerCleanupDialog.ui file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerConfigDialog.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerConfigDialog.ui file | annotate | diff | comparison | revisions
--- a/PluginPyInstaller.e4p	Tue Jan 16 15:36:57 2018 +0100
+++ b/PluginPyInstaller.e4p	Wed Jan 17 16:25:59 2018 +0100
@@ -15,9 +15,15 @@
   <Eol index="1"/>
   <Sources>
     <Source>PluginPyInstaller.py</Source>
+    <Source>PyInstaller/PyInstallerCleanupDialog.py</Source>
+    <Source>PyInstaller/PyInstallerConfigDialog.py</Source>
     <Source>PyInstaller/__init__.py</Source>
     <Source>__init__.py</Source>
   </Sources>
+  <Forms>
+    <Form>PyInstaller/PyInstallerCleanupDialog.ui</Form>
+    <Form>PyInstaller/PyInstallerConfigDialog.ui</Form>
+  </Forms>
   <Others>
     <Other>.hgignore</Other>
     <Other>ChangeLog</Other>
@@ -145,5 +151,6 @@
     <FiletypeAssociation pattern="*.ui" type="FORMS"/>
     <FiletypeAssociation pattern="README" type="OTHERS"/>
     <FiletypeAssociation pattern="README.*" type="OTHERS"/>
+    <FiletypeAssociation pattern="Ui_*.py" type="__IGNORE__"/>
   </FiletypeAssociations>
 </Project>
--- a/PluginPyInstaller.py	Tue Jan 16 15:36:57 2018 +0100
+++ b/PluginPyInstaller.py	Wed Jan 17 16:25:59 2018 +0100
@@ -11,8 +11,14 @@
 
 import os
 import platform
+import shutil
 
-from PyQt5.QtCore import QObject, QCoreApplication
+from PyQt5.QtCore import pyqtSlot, QObject, QCoreApplication, QTranslator
+from PyQt5.QtWidgets import QDialog
+
+from E5Gui import E5MessageBox
+from E5Gui.E5Action import E5Action
+from E5Gui.E5Application import e5App
 
 import Utilities
 
@@ -154,10 +160,7 @@
         #
         # Linux, Unix ...
         pyinstallerScripts = ['pyinstaller', 'pyi-makespec']
-##        scriptSuffixes = [""]
-##        for minorVersion in minorVersions:
-##            scriptSuffixes.append(
-##                "-python{0}.{1}".format(majorVersion, minorVersion))
+        
         # There could be multiple pyinstaller executables in the path
         # e.g. for different python variants
         path = Utilities.getEnvironmentEntry('PATH')
@@ -232,30 +235,246 @@
 
 class PyInstallerPlugin(QObject):
     """
-    Class documentation goes here.
+    Class implementing the PyInstaller interface plug-in.
     """
     def __init__(self, ui):
         """
         Constructor
         
-        @param ui reference to the user interface object (UI.UserInterface)
+        @param ui reference to the user interface object
+        @type UI.UserInterface
         """
         super(PyInstallerPlugin, self).__init__(ui)
         self.__ui = ui
+        
+        self.__initialize()
+        _checkProgram()
+        
+        self.__translator = None
+        self.__loadTranslator()
+        
+    def __initialize(self):
+        """
+        Private slot to (re)initialize the plug-in.
+        """
+        self.__projectActs = []
     
     def activate(self):
         """
-        Public method to activate this plugin.
+        Public method to activate this plug-in.
         
         @return tuple of None and activation status (boolean)
         """
         global error
-        error = ""     # clear previous error
+        
+        # There is already an error, don't activate
+        if error:
+            return None, False
+        
+        # pyinstaller interface is only activated if it is available
+        if not _checkProgram():
+            return None, False
+        
+        # clear previous error
+        error = ""
+        
+        project = e5App().getObject("Project")
+        menu = project.getMenu("Packagers")
+        if menu:
+            # Execute PyInstaller
+            act = E5Action(
+                self.tr('Execute PyInstaller'),
+                self.tr('Execute Py&Installer'), 0, 0,
+                self, 'packagers_pyinstaller_run')
+            act.setStatusTip(
+                self.tr('Generate a distribution package using PyInstaller'))
+            act.setWhatsThis(self.tr(
+                """<b>Execute PyInstaller</b>"""
+                """<p>Generate a distribution package using PyInstaller."""
+                """ The command is executed in the project path. All"""
+                """ files and directories must be given absolute or"""
+                """ relative to the project directory.</p>"""
+            ))
+            act.triggered.connect(self.__pyinstaller)
+            menu.addAction(act)
+            self.__projectActs.append(act)
+            
+            # Execute pyi-makespec
+            act = E5Action(
+                self.tr('Make PyInstaller Spec File'),
+                self.tr('Make PyInstaller &Spec File'), 0, 0,
+                self, 'packagers_pyinstaller_spec')
+            act.setStatusTip(
+                self.tr('Generate a spec file to be used by PyInstaller'))
+            act.setWhatsThis(self.tr(
+                """<b>Make PyInstaller Spec File</b>"""
+                """<p>Generate a spec file to be used by PyInstaller"""
+                """ PyInstaller. The command is executed in the project"""
+                """ path. All files and directories must be given absolute"""
+                """ or relative to the project directory.</p>"""
+            ))
+            act.triggered.connect(self.__pyiMakeSpec)
+            menu.addAction(act)
+            self.__projectActs.append(act)
+            
+            # clean the pyinstaller created directories
+            act = E5Action(
+                self.tr('Clean PyInstaller'),
+                self.tr('&Clean PyInstaller'), 0, 0,
+                self, 'packagers_pyinstaller_clean')
+            act.setStatusTip(
+                self.tr('Remove the PyInstaller created directories'))
+            act.setWhatsThis(self.tr(
+                """<b>Clean PyInstaller</b>"""
+                """<p>Remove the PyInstaller created directories (dist and"""
+                """ build). These are subdirectories within the project"""
+                """ path."""
+            ))
+            act.triggered.connect(self.__pyinstallerCleanup)
+            menu.addAction(act)
+            self.__projectActs.append(act)
+            
+            project.addE5Actions(self.__projectActs)
+            project.showMenu.connect(self.__projectShowMenu)
         
         return None, True
     
     def deactivate(self):
         """
-        Public method to deactivate this plugin.
+        Public method to deactivate this plug-in.
+        """
+        menu = e5App().getObject("Project").getMenu("Packagers")
+        if menu:
+            for act in self.__projectActs:
+                menu.removeAction(act)
+            
+            e5App().getObject("Project").removeE5Actions(
+                self.__projectActs)
+        
+        self.__initialize()
+    
+    def __projectShowMenu(self, menuName, menu):
+        """
+        Private slot called, when the the project menu or a submenu is
+        about to be shown.
+        
+        @param menuName name of the menu to be shown
+        @type str
+        @param menu reference to the menu
+        @type QMenu
+        """
+        if menuName == "Packagers":
+            enable = e5App().getObject("Project").getProjectLanguage() in \
+                ["Python", "Python2", "Python3"]
+            for act in self.__projectActs:
+                act.setEnabled(enable)
+    
+    def __loadTranslator(self):
+        """
+        Private method to load the translation file.
+        """
+        if self.__ui is not None:
+            loc = self.__ui.getLocale()
+            if loc and loc != "C":
+                locale_dir = \
+                    os.path.join(os.path.dirname(__file__),
+                                 "PyInstaller", "i18n")
+                translation = "pyinstaller_{0}".format(loc)
+                translator = QTranslator(None)
+                loaded = translator.load(translation, locale_dir)
+                if loaded:
+                    self.__translator = translator
+                    e5App().installTranslator(self.__translator)
+                else:
+                    print("Warning: translation file '{0}' could not be"
+                          " loaded.".format(translation))
+                    print("Using default.")
+    
+    @pyqtSlot()
+    def __pyinstaller(self):
+        """
+        Private slot to execute the pyinstaller command for the current
+        project.
         """
-        pass
+        project = e5App().getObject("Project")
+        majorVersionStr = project.getProjectLanguage()
+        if majorVersionStr == "Python3":
+            executables = [f for f in exePy3 if 
+                           f.endswith(("pyinstaller", "pyinstaller.exe"))]
+        else:
+            executables = [f for f in exePy2 if 
+                           f.endswith(("pyinstaller", "pyinstaller.exe"))]
+        if not executables:
+            E5MessageBox.critical(
+                self.__ui,
+                self.tr("pyinstaller"),
+                self.tr("""The pyinstaller executable could not be found."""))
+            return
+        
+        # check if all files saved and errorfree before continue
+        if not project.checkAllScriptsDirty(reportSyntaxErrors=True):
+            return
+        
+        from PyInstaller.PyInstallerConfigDialog import PyInstallerConfigDialog
+        params = project.getData('PACKAGERSPARMS', "PYINSTALLER")
+        dlg = PyInstallerConfigDialog(project, executables, params,
+                                      mode="installer")
+        if dlg.exec_() == QDialog.Accepted:
+            args, params = dlg.generateParameters()
+            project.setData('PACKAGERSPARMS', "PYINSTALLER", params)
+            
+            # now do the call
+            from PyInstaller.PyInstallerExecDialog import PyInstallerExecDialog
+            dia = PyInstallerExecDialog("pyinstaller")
+            dia.show()
+            res = dia.start(args, params, project.getProjectPath(),
+                            project.getMainScript())
+            if res:
+                dia.exec_()
+        # TODO: implement pyinstaller
+    
+    @pyqtSlot()
+    def __pyiMakeSpec(self):
+        """
+        Private slot to execute the pyi-makespec command for the current
+        project to generate a spec file to be used by pyinstaller.
+        """
+        project = e5App().getObject("Project")
+        majorVersionStr = project.getProjectLanguage()
+        if majorVersionStr == "Python3":
+            executables = [f for f in exePy3 if 
+                           f.endswith(("pyi-makespec", "pyi-makespec.exe"))]
+        else:
+            executables = [f for f in exePy2 if 
+                           f.endswith(("pyi-makespec", "pyi-makespec.exe"))]
+        if not executables:
+            E5MessageBox.critical(
+                self.__ui,
+                self.tr("pyi-makespec"),
+                self.tr("""The pyi-makespec executable could not be found."""))
+            return
+        
+        # check if all files saved and errorfree before continue
+        if not project.checkAllScriptsDirty(reportSyntaxErrors=True):
+            return
+        
+        # TODO: implement pyi-makespec
+    
+    @pyqtSlot()
+    def __pyinstallerCleanup(self):
+        """
+        Private slot to remove the directories created by pyinstaller.
+        """
+        project = e5App().getObject("Project")
+        
+        from PyInstaller.PyInstallerCleanupDialog import \
+            PyInstallerCleanupDialog
+        dlg = PyInstallerCleanupDialog()
+        if dlg.exec_() == QDialog.Accepted:
+            removeDirs = dlg.getDirectories()
+            for directory in removeDirs:
+                rd = os.path.join(project.getProjectPath(), directory)
+                shutil.rmtree(rd, ignore_errors=True)
+
+#
+# eflag: noqa = M801
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PyInstaller/PyInstallerCleanupDialog.py	Wed Jan 17 16:25:59 2018 +0100
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to select the cleanup action.
+"""
+
+from PyQt5.QtWidgets import QDialog
+
+from .Ui_PyInstallerCleanupDialog import Ui_PyInstallerCleanupDialog
+
+
+class PyInstallerCleanupDialog(QDialog, Ui_PyInstallerCleanupDialog):
+    """
+    Class implementing a dialog to select the cleanup action.
+    """
+    BuildPath = "build"
+    DistPath = "dist"
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(PyInstallerCleanupDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    def getDirectories(self):
+        """
+        Public method to get the project relative directories to be cleaned.
+        """
+        dirs = []
+        
+        if self.buildButton.isChecked() or self.bothButton.isChecked():
+            dirs.append(PyInstallerCleanupDialog.BuildPath)
+        if self.distButton.isChecked() or self.bothButton.isChecked():
+            dirs.append(PyInstallerCleanupDialog.DistPath)
+        
+        return dirs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PyInstaller/PyInstallerCleanupDialog.ui	Wed Jan 17 16:25:59 2018 +0100
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PyInstallerCleanupDialog</class>
+ <widget class="QDialog" name="PyInstallerCleanupDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>158</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Clean PyInstaller</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Select the PyInstaller directories to be removed:</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QRadioButton" name="buildButton">
+     <property name="text">
+      <string>Build Directory</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QRadioButton" name="distButton">
+     <property name="text">
+      <string>Distribution Directory</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QRadioButton" name="bothButton">
+     <property name="text">
+      <string>Both Directories</string>
+     </property>
+     <property name="checked">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PyInstallerCleanupDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PyInstallerCleanupDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PyInstaller/PyInstallerConfigDialog.py	Wed Jan 17 16:25:59 2018 +0100
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+"""
+Module implementing PyInstallerConfigDialog.
+"""
+
+from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtWidgets import QDialog
+
+from E5Gui.E5PathPicker import E5PathPickerModes
+
+from .Ui_PyInstallerConfigDialog import Ui_PyInstallerConfigDialog
+
+import Globals
+
+
+class PyInstallerConfigDialog(QDialog, Ui_PyInstallerConfigDialog):
+    """
+    Class documentation goes here.
+    """
+    def __init__(self, project, executables, params=None, mode="installer",
+                 parent=None):
+        """
+        Constructor
+        
+        @param project reference to the project object
+        @type Project.Project
+        @param executables names of the pyinstaller executables
+        @type list of str
+        @param params parameters to set in the dialog
+        @type dict
+        @param mode mode of the dialog
+        @type str (one of 'installer' or 'spec')
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        assert mode in ("installer", "spec")
+        
+        super(PyInstallerConfigDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.inputFilePicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.inputFilePicker.setFilters(self.tr(
+            "Python Files (*.py *.py2 *.py3);;"
+            "Python GUI Files (*.pyw *.pyw2 *.pyw3);;"
+            "Spec Files (*.spec);;"
+            "All Files (*)"
+        ))
+        
+        self.executableCombo.addItems(executables)
+        
+        self.__project = project
+        if project.getMainScript() == "":
+            # no main script defined
+            self.selectedScriptButton.setChecke(True)
+            self.mainScriptButton.setEnabled(False)
+        
+        self.iconFilePicker.setMode(E5PathPickerModes.OpenFileMode)
+        if Globals.isMacPlatform():
+            self.iconFilePicker.setFilters(self.tr(
+                "Icon Files (*.icns);;"
+                "All Files (*)"
+            ))
+        elif Globals.isWindowsPlatform():
+            self.iconFilePicker.setFilters(self.tr(
+                "Icon Files (*.ico);;"
+                "Executable Files (*.exe);;"
+                "All Files (*)"
+            ))
+        
+        self.tabWidget.setTabEnabled(
+            1,
+            Globals.isMacPlatform() or Globals.isWindowsPlatform())
+        self.tabWidget.setTabEnabled(
+            2,
+            Globals.isMacPlatform())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PyInstaller/PyInstallerConfigDialog.ui	Wed Jan 17 16:25:59 2018 +0100
@@ -0,0 +1,386 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PyInstallerConfigDialog</class>
+ <widget class="QDialog" name="PyInstallerConfigDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>435</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>PyInstaller Configuration</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="generalTab">
+      <attribute name="title">
+       <string>&amp;General</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <widget class="QGroupBox" name="groupBox_2">
+         <property name="title">
+          <string>Executable</string>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout">
+          <item>
+           <widget class="QComboBox" name="executableCombo">
+            <property name="toolTip">
+             <string>Select the executable to be run</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="groupBox">
+         <property name="title">
+          <string>Input File</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout">
+          <item row="0" column="0">
+           <widget class="QRadioButton" name="mainScriptButton">
+            <property name="text">
+             <string>Project Main Script</string>
+            </property>
+            <property name="checked">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QRadioButton" name="selectedScriptButton">
+            <property name="text">
+             <string>Script or Spec File</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0" colspan="2">
+           <widget class="E5PathPicker" name="inputFilePicker" native="true">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="focusPolicy">
+             <enum>Qt::StrongFocus</enum>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="groupBox_3">
+         <property name="title">
+          <string>Generate Option</string>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout_2">
+          <item>
+           <widget class="QRadioButton" name="oneDirButton">
+            <property name="text">
+             <string>One Directory</string>
+            </property>
+            <property name="checked">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QRadioButton" name="oneFileButton">
+            <property name="text">
+             <string>One File</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <layout class="QGridLayout" name="gridLayout_2">
+         <item row="0" column="0">
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>Name (optional):</string>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="E5ClearableLineEdit" name="nameEdit">
+           <property name="toolTip">
+            <string>Enter an optional name for the application</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>Encryption Key (optional):</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="E5ClearableLineEdit" name="keyEdit">
+           <property name="toolTip">
+            <string>Enter an optional key used to encrypt Python bytecode</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="cleanCheckBox">
+         <property name="text">
+          <string>Clean Before Building</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="windowsMacTab">
+      <attribute name="title">
+       <string>&amp;Windows and macOS</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <item>
+        <widget class="QGroupBox" name="groupBox_4">
+         <property name="title">
+          <string>Application Mode</string>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout_3">
+          <item>
+           <widget class="QRadioButton" name="consoleButton">
+            <property name="toolTip">
+             <string>Select for a console application</string>
+            </property>
+            <property name="text">
+             <string>Console Application</string>
+            </property>
+            <property name="checked">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QRadioButton" name="windowedButton">
+            <property name="toolTip">
+             <string>Select for a windowed application (i.e. do not open a console window)</string>
+            </property>
+            <property name="text">
+             <string>Windowed Application</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="groupBox_5">
+         <property name="title">
+          <string>Icon</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_3">
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_3">
+            <property name="text">
+             <string>Icon File:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="E5PathPicker" name="iconFilePicker" native="true">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="focusPolicy">
+             <enum>Qt::StrongFocus</enum>
+            </property>
+            <property name="toolTip">
+             <string/>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_4">
+            <property name="text">
+             <string>Icon ID:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="E5ClearableLineEdit" name="iconIdEdit">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="toolTip">
+             <string>Enter the icon ID to be extracted from the exe file</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>156</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="macTab">
+      <attribute name="title">
+       <string>&amp;macOS Specific</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_4">
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <item>
+          <widget class="QLabel" name="label_5">
+           <property name="text">
+            <string>Bundle Identifier:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="E5ClearableLineEdit" name="BundleIdentifierEdit">
+           <property name="toolTip">
+            <string>Enter the macOS app bundle identifier</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>298</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>tabWidget</tabstop>
+  <tabstop>executableCombo</tabstop>
+  <tabstop>mainScriptButton</tabstop>
+  <tabstop>selectedScriptButton</tabstop>
+  <tabstop>inputFilePicker</tabstop>
+  <tabstop>oneDirButton</tabstop>
+  <tabstop>oneFileButton</tabstop>
+  <tabstop>nameEdit</tabstop>
+  <tabstop>keyEdit</tabstop>
+  <tabstop>cleanCheckBox</tabstop>
+  <tabstop>consoleButton</tabstop>
+  <tabstop>windowedButton</tabstop>
+  <tabstop>iconFilePicker</tabstop>
+  <tabstop>iconIdEdit</tabstop>
+  <tabstop>BundleIdentifierEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PyInstallerConfigDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>227</x>
+     <y>379</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PyInstallerConfigDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>295</x>
+     <y>385</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>selectedScriptButton</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>inputFilePicker</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>119</x>
+     <y>116</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>124</x>
+     <y>150</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

eric ide

mercurial