diff -r eb2d30b4d34e -r 52f0572b5908 PyInstaller/PyInstallerConfigDialog.py --- a/PyInstaller/PyInstallerConfigDialog.py Wed Jan 17 16:25:59 2018 +0100 +++ b/PyInstaller/PyInstallerConfigDialog.py Thu Jan 18 14:30:06 2018 +0100 @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de> +# + """ Module implementing PyInstallerConfigDialog. """ +from __future__ import unicode_literals + +import copy + from PyQt5.QtCore import pyqtSlot -from PyQt5.QtWidgets import QDialog +from PyQt5.QtWidgets import QDialog, QDialogButtonBox from E5Gui.E5PathPicker import E5PathPickerModes @@ -16,7 +23,8 @@ class PyInstallerConfigDialog(QDialog, Ui_PyInstallerConfigDialog): """ - Class documentation goes here. + Class implementing a dialog to enter the parameters for pyinstaller + and pyi-makespec. """ def __init__(self, project, executables, params=None, mode="installer", parent=None): @@ -39,23 +47,36 @@ super(PyInstallerConfigDialog, self).__init__(parent) self.setupUi(self) + self.__project = project + self.__mode = mode + 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.inputFilePicker.setDefaultDirectory( + self.__project.getProjectPath()) + if self.__mode == "installer": + self.inputFilePicker.setFilters(self.tr( + "Python Files (*.py *.py2 *.py3);;" + "Python GUI Files (*.pyw *.pyw2 *.pyw3);;" + "Spec Files (*.spec);;" + "All Files (*)" + )) + elif self.__mode == "spec": + self.inputFilePicker.setFilters(self.tr( + "Python Files (*.py *.py2 *.py3);;" + "Python GUI Files (*.pyw *.pyw2 *.pyw3);;" + "All Files (*)" + )) self.executableCombo.addItems(executables) - self.__project = project - if project.getMainScript() == "": + if not bool(project.getMainScript()): # no main script defined - self.selectedScriptButton.setChecke(True) + self.selectedScriptButton.setChecked(True) self.mainScriptButton.setEnabled(False) self.iconFilePicker.setMode(E5PathPickerModes.OpenFileMode) + self.iconFilePicker.setDefaultDirectory( + self.__project.getProjectPath()) if Globals.isMacPlatform(): self.iconFilePicker.setFilters(self.tr( "Icon Files (*.icns);;" @@ -68,9 +89,273 @@ "All Files (*)" )) + # disable platform specific tabs self.tabWidget.setTabEnabled( - 1, + self.tabWidget.indexOf(self.windowsMacTab), Globals.isMacPlatform() or Globals.isWindowsPlatform()) self.tabWidget.setTabEnabled( - 2, + 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: + for key, value in params.items(): + if key in self.__parameters: + self.__parameters[key] = params[key] + + # 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 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 and non default + parameters + @rtype tuple of (list of str, dict) + """ + 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 + args.append("--noconfirm") # don't ask the user + + # finalize the arguments array + if self.__parameters["mainscript"]: + args.append(self.__project.getMainScript()) + else: + args.append(self.__parameters["inputFile"]) + + return args, parms + + 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(PyInstallerConfigDialog, self).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.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()