diff -r 9ecfea29a47c -r fc9ef9dcd51a PyInstallerInterface/PyInstallerConfigDialog.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyInstallerInterface/PyInstallerConfigDialog.py Thu May 27 20:28:55 2021 +0200 @@ -0,0 +1,378 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2018 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing PyInstallerConfigDialog. +""" + +import copy + +from PyQt6.QtCore import pyqtSlot +from PyQt6.QtWidgets import QDialog, QDialogButtonBox + +from EricWidgets.EricPathPicker import EricPathPickerModes + +from .Ui_PyInstallerConfigDialog import Ui_PyInstallerConfigDialog + +import Globals + + +class PyInstallerConfigDialog(QDialog, Ui_PyInstallerConfigDialog): + """ + Class implementing a dialog to enter the parameters for pyinstaller + and pyi-makespec. + """ + 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 + """ + super().__init__(parent) + self.setupUi(self) + + self.__project = project + self.__mode = mode + + self.inputFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) + self.inputFilePicker.setDefaultDirectory( + self.__project.getProjectPath()) + if self.__mode == "installer": + self.inputFilePicker.setFilters(self.tr( + "Python Files (*.py *.py3);;" + "Python GUI Files (*.pyw *.pyw3);;" + "Spec Files (*.spec);;" + "All Files (*)" + )) + elif self.__mode == "spec": + self.inputFilePicker.setFilters(self.tr( + "Python Files (*.py *.py3);;" + "Python GUI Files (*.pyw *.pyw3);;" + "All Files (*)" + )) + + self.executableCombo.addItems(executables) + + if not bool(project.getMainScript()): + # no main script defined + self.selectedScriptButton.setChecked(True) + self.mainScriptButton.setEnabled(False) + + self.iconFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) + self.iconFilePicker.setDefaultDirectory( + self.__project.getProjectPath()) + 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 (*)" + )) + + # disable platform specific tabs + self.tabWidget.setTabEnabled( + self.tabWidget.indexOf(self.windowsMacTab), + Globals.isMacPlatform() or Globals.isWindowsPlatform()) + self.tabWidget.setTabEnabled( + self.tabWidget.indexOf(self.macTab), + Globals.isMacPlatform()) + + self.__initializeDefaults() + + # get a copy of the defaults to store the user settings + self.__parameters = copy.deepcopy(self.__defaults) + + # combine it with the values of params + if params is not None: + self.__parameters.update(params) + + # initialize general tab + if mode == "installer" and bool(self.__parameters["pyinstaller"]): + self.executableCombo.setCurrentIndex( + self.executableCombo.findText( + self.__parameters["pyinstaller"])) + elif mode == "spec" and bool(self.__parameters["pyi-makespec"]): + self.executableCombo.setCurrentIndex( + self.executableCombo.findText( + self.__parameters["pyi-makespec"])) + if self.__parameters["mainscript"]: + self.mainScriptButton.setChecked(True) + else: + self.selectedScriptButton.setChecked(True) + self.inputFilePicker.setText(self.__parameters["inputFile"]) + if self.__parameters["oneDirectory"]: + self.oneDirButton.setChecked(True) + else: + self.oneFileButton.setChecked(True) + self.nameEdit.setText(self.__parameters["name"]) + self.keyEdit.setText(self.__parameters["encryptionKey"]) + self.cleanCheckBox.setChecked(self.__parameters["cleanBeforeBuilding"]) + + # initialize Windows and macOS tab + if self.__parameters["consoleApplication"]: + self.consoleButton.setChecked(True) + else: + self.windowedButton.setChecked(True) + self.iconFilePicker.setText(self.__parameters["iconFile"]) + self.iconIdEdit.setText(self.__parameters["iconId"]) + + # initialize maxOS specific tab + self.bundleIdentifierEdit.setText( + self.__parameters["bundleIdentifier"]) + + self.__updateOkButton() + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def __initializeDefaults(self): + """ + Private method to set the default values. + + These are needed later on to generate the command line parameters. + """ + self.__defaults = { + # general options + "pyinstaller": "", + "pyi-makespec": "", + "mainscript": bool(self.__project.getMainScript()), + "inputFile": "", + "oneDirectory": True, + "name": "", + "encryptionKey": "", + "cleanBeforeBuilding": False, + + # Windows and macOS options + "consoleApplication": True, + "iconFile": "", + "iconId": "", + + # macOS specific options + "bundleIdentifier": "", + } + + def generateParameters(self): + """ + Public method that generates the command line parameters. + + It generates a list of strings to be used to set the QProcess arguments + for the pyinstaller/pyi-makespec call and a list containing the non + default parameters. The second list can be passed back upon object + generation to overwrite the default settings. + + @return a tuple of the command line parameters, non default parameters + and the script path + @rtype tuple of (list of str, dict, str) + """ + parms = {} + args = [] + + # 1. the program name + if self.__mode == "installer": + args.append(self.__parameters["pyinstaller"]) + parms["pyinstaller"] = self.__parameters["pyinstaller"] + elif self.__mode == "spec": + args.append(self.__parameters["pyi-makespec"]) + parms["pyi-makespec"] = self.__parameters["pyi-makespec"] + + # 2. the commandline options + # 2.1 general options, input + if not self.__parameters["mainscript"]: + parms["mainscript"] = False + parms["inputFile"] = self.__parameters["inputFile"] + + runWithSpec = self.__parameters["inputFile"].endswith(".spec") + if not runWithSpec: + # 2.2 general options, part 1 + if not self.__parameters["oneDirectory"]: + parms["oneDirectory"] = self.__parameters["oneDirectory"] + args.append("--onefile") + if self.__parameters["name"] != self.__defaults["name"]: + parms["name"] = self.__parameters["name"] + args.append("--name") + args.append(self.__parameters["name"]) + if ( + self.__parameters["encryptionKey"] != + self.__defaults["encryptionKey"] + ): + parms["encryptionKey"] = self.__parameters["encryptionKey"] + args.append("--key") + args.append(self.__parameters["encryptionKey"]) + + # 2.3 Windows and macOS options + if ( + self.__parameters["consoleApplication"] != + self.__defaults["consoleApplication"] + ): + parms["consoleApplication"] = ( + self.__parameters["consoleApplication"] + ) + args.append("--windowed") + if self.__parameters["iconFile"] != self.__defaults["iconFile"]: + parms["iconFile"] = self.__parameters["iconFile"] + parms["iconId"] = self.__parameters["iconId"] + args.append("--icon") + if self.__parameters["iconFile"].endswith(".exe"): + if bool(self.__parameters["iconId"]): + iconId = self.__parameters["iconId"] + else: + iconId = "0" + args.append("{0},{1}".format( + self.__parameters["iconFile"], iconId)) + else: + args.append(self.__parameters["iconFile"]) + + # 2.4 macOS specific options + if ( + self.__parameters["bundleIdentifier"] != + self.__defaults["bundleIdentifier"] + ): + parms["bundleIdentifier"] = ( + self.__parameters["bundleIdentifier"] + ) + args.append("--osx-bundle-identifier") + args.append(self.__parameters["bundleIdentifier"]) + + # 2.5 general options, part 2 + if ( + self.__parameters["cleanBeforeBuilding"] != + self.__defaults["cleanBeforeBuilding"] + ): + parms["cleanBeforeBuilding"] = ( + self.__parameters["cleanBeforeBuilding"] + ) + args.append("--clean") + + # 3. always add these arguments + if self.__mode == "installer": + args.append("--noconfirm") # don't ask the user + + # determine the script to be processed + script = ( + self.__project.getMainScript() + if self.__parameters["mainscript"] else + self.__parameters["inputFile"] + ) + + return args, parms, script + + def accept(self): + """ + Public method called by the Ok button. + + It saves the values in the parameters dictionary. + """ + # get data of general tab + if self.__mode == "installer": + self.__parameters["pyinstaller"] = ( + self.executableCombo.currentText() + ) + elif self.__mode == "spec": + self.__parameters["pyi-makespec"] = ( + self.executableCombo.currentText() + ) + self.__parameters["mainscript"] = self.mainScriptButton.isChecked() + self.__parameters["inputFile"] = self.inputFilePicker.text() + self.__parameters["oneDirectory"] = self.oneDirButton.isChecked() + self.__parameters["name"] = self.nameEdit.text() + self.__parameters["encryptionKey"] = self.keyEdit.text() + self.__parameters["cleanBeforeBuilding"] = ( + self.cleanCheckBox.isChecked() + ) + + # get data of Windows and macOS tab + self.__parameters["consoleApplication"] = ( + self.consoleButton.isChecked() + ) + self.__parameters["iconFile"] = self.iconFilePicker.text() + self.__parameters["iconId"] = self.iconIdEdit.text() + + # get data of macOS specific tab + self.__parameters["bundleIdentifier"] = ( + self.bundleIdentifierEdit.text() + ) + + # call the accept slot of the base class + super().accept() + + def __updateOkButton(self): + """ + Private method to update the enabled state of the OK button. + """ + enable = True + + # If not to be run with the project main script, a script or + # spec file must be selected. + if ( + self.selectedScriptButton.isChecked() and + not bool(self.inputFilePicker.text()) + ): + enable = False + + # If the icon shall be picked from a .exe file, an icon ID + # must be entered (Windows only). + if ( + self.iconFilePicker.text().endswith(".exe") and + not bool(self.iconIdEdit.text()) + ): + enable = False + + self.buttonBox.button( + QDialogButtonBox.StandardButton.Ok).setEnabled(enable) + + @pyqtSlot(bool) + def on_selectedScriptButton_toggled(self, checked): + """ + Private slot to handle changes of the radio button state. + + @param checked state of the radio button + @type bool + """ + self.__updateOkButton() + + @pyqtSlot(str) + def on_inputFilePicker_textChanged(self, txt): + """ + Private slot to handle changes of the input file. + + @param txt text of the file edit + @type str + """ + self.__updateOkButton() + + @pyqtSlot(str) + def on_iconFilePicker_textChanged(self, txt): + """ + Private slot to handle changes of the icon file. + + @param txt text of the file edit + @type str + """ + self.iconIdEdit.setEnabled(txt.endswith(".exe")) + self.__updateOkButton() + + @pyqtSlot(str) + def on_iconIdEdit_textChanged(self, txt): + """ + Private slot to handle changes of the icon ID. + + @param txt iconID + @type str + """ + self.__updateOkButton()