Tue, 10 Dec 2024 15:48:55 +0100
Updated copyright for 2025.
# -*- coding: utf-8 -*- # Copyright (c) 2010 - 2025 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog to enter the parameters for cxfreeze. """ import contextlib import copy import os import sys from PyQt6.QtCore import QDir, QProcess, pyqtSlot from PyQt6.QtWidgets import QDialog, QListWidgetItem try: from eric7.EricGui import EricPixmapCache except ImportError: from UI import PixmapCache as EricPixmapCache from eric7.EricWidgets.EricDirFileDialog import EricDirFileDialog from eric7.EricWidgets.EricPathPicker import EricPathPickerModes try: from eric7.SystemUtilities.OSUtilities import isMacPlatform, isWindowsPlatform except ImportError: # imports for eric < 23.1 from eric7.Globals import isMacPlatform, isWindowsPlatform try: from eric7.SystemUtilities.FileSystemUtilities import toNativeSeparators except ImportError: # imports for eric < 23.1 from eric7.Utilities import toNativeSeparators from .Ui_CxfreezeConfigDialog import Ui_CxfreezeConfigDialog class CxfreezeConfigDialog(QDialog, Ui_CxfreezeConfigDialog): """ Class implementing a dialog to enter the parameters for cxfreeze. """ def __init__(self, project, exe, parms=None, parent=None): """ Constructor @param project reference to the project object @type Project @param exe name of the cxfreeze executable @type str @param parms parameters to set in the dialog @type dict @param parent parent widget of this dialog @type QWidget """ QDialog.__init__(self, parent) self.setupUi(self) self.selectFileOrFolderButton.setIcon(EricPixmapCache.getIcon("open.png")) self.targetDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE) self.targetDirPicker.setWindowTitle(self.tr("Select target directory")) iconsI18N = self.tr("Icons") allFilesI18N = self.tr("All files") if isWindowsPlatform(): iconFilter = "{0} (*.ico);;{1} (*.*)".format(iconsI18N, allFilesI18N) elif isMacPlatform(): iconFilter = "{0} (*.icns *.png);;{1} (*.*)".format(iconsI18N, allFilesI18N) else: iconFilter = "{0} (*.png);;{1} (*.*)".format(iconsI18N, allFilesI18N) self.applicationIconPicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) self.applicationIconPicker.setWindowTitle( self.tr("Select the application icon") ) self.applicationIconPicker.setFilters(iconFilter) self.extListFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) self.extListFilePicker.setWindowTitle(self.tr("Select external list file")) self.__project = project 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 parms if parms is not None: self.__parameters.update(parms) self.cxfreezeExecCombo.addItems(exe) # try to set the saved script path with contextlib.suppress(ValueError): idx = exe.index(self.__parameters["script"]) self.cxfreezeExecCombo.setCurrentIndex(idx) # initialize general tab self.targetDirPicker.setText(self.__parameters["targetDirectory"]) self.targetNameEdit.setText(self.__parameters["targetName"]) self.basenameCombo.setEditText(self.__parameters["baseName"]) self.initscriptCombo.setEditText(self.__parameters["initScript"]) self.applicationIconPicker.setText(self.__parameters["applicationIcon"]) self.keeppathCheckBox.setChecked(self.__parameters["keepPath"]) self.compressCheckBox.setChecked(self.__parameters["compress"]) if self.__parameters["optimize"] == 0: self.nooptimizeRadioButton.setChecked(True) elif self.__parameters["optimize"] == 1: self.optimizeRadioButton.setChecked(True) else: self.optimizeDocRadioButton.setChecked(True) # initialize advanced tab self.defaultPathEdit.setText(os.pathsep.join(self.__parameters["defaultPath"])) self.includePathEdit.setText(os.pathsep.join(self.__parameters["includePath"])) self.replacePathsEdit.setText( os.pathsep.join(self.__parameters["replacePaths"]) ) self.includeModulesEdit.setText(",".join(self.__parameters["includeModules"])) self.excludeModulesEdit.setText(",".join(self.__parameters["excludeModules"])) self.extListFilePicker.setText(self.__parameters["extListFile"]) # initialize additional files tab self.fileOrFolderList.addItems(self.__parameters["additionalFiles"]) 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 "targetDirectory": "", "targetName": "", "baseName": "Console", "initScript": "Console", "applicationIcon": "", "script": "", "keepPath": False, "compress": False, "optimize": 0, # 0, 1 or 2 # advanced options "defaultPath": [], "includePath": [], "replacePaths": [], "includeModules": [], "excludeModules": [], "extListFile": "", # additional files tab "additionalFiles": [], } # overwrite 'baseName' if OS is Windows if sys.platform == "win32": self.defaults["baseName"] = "Win32GUI" # overwrite 'initScript' if version 3 interpreter if self.__project.getProjectLanguage() == "Python3": self.defaults["initScript"] = "Console3" 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 cxfreeze 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 args.append(self.cxfreezeExecCombo.currentText()) # 2. the commandline options # 2.1 general options if self.__parameters["targetDirectory"] != self.defaults["targetDirectory"]: parms["targetDirectory"] = self.__parameters["targetDirectory"] args.append("--target-dir={0}".format(self.__parameters["targetDirectory"])) if self.__parameters["targetName"] != self.defaults["targetName"]: parms["targetName"] = self.__parameters["targetName"][:] args.append("--target-name={0}".format(self.__parameters["targetName"])) parms["baseName"] = self.__parameters["baseName"][:] if self.__parameters["baseName"] != "": args.append("--base-name={0}".format(self.__parameters["baseName"])) parms["initScript"] = self.__parameters["initScript"][:] if self.__parameters["initScript"] != "": args.append("--init-script={0}".format(self.__parameters["initScript"])) parms["applicationIcon"] = self.__parameters["applicationIcon"][:] if self.__parameters["applicationIcon"] != self.defaults["applicationIcon"]: args.append("--icon={0}".format(self.__parameters["applicationIcon"])) parms["script"] = self.__parameters["script"][:] if self.__parameters["keepPath"] != self.defaults["keepPath"]: parms["keepPath"] = self.__parameters["keepPath"] args.append("--no-copy-deps") if self.__parameters["compress"] != self.defaults["compress"]: parms["compress"] = self.__parameters["compress"] args.append("--compress") if self.__parameters["optimize"] != self.defaults["optimize"]: parms["optimize"] = self.__parameters["optimize"] if self.__parameters["optimize"] == 1: args.append("-O") elif self.__parameters["optimize"] == 2: args.append("-OO") # 2.2 advanced options if self.__parameters["defaultPath"] != self.defaults["defaultPath"]: parms["defaultPath"] = self.__parameters["defaultPath"][:] args.append( "--default-path={0}".format( os.pathsep.join(self.__parameters["defaultPath"]) ) ) if self.__parameters["includePath"] != self.defaults["includePath"]: parms["includePath"] = self.__parameters["includePath"][:] args.append( "--include-path={0}".format( os.pathsep.join(self.__parameters["includePath"]) ) ) if self.__parameters["replacePaths"] != self.defaults["replacePaths"]: parms["replacePaths"] = self.__parameters["replacePaths"][:] args.append( "--replace-paths={0}".format( os.pathsep.join(self.__parameters["replacePaths"]) ) ) if self.__parameters["includeModules"] != self.defaults["includeModules"]: parms["includeModules"] = self.__parameters["includeModules"][:] args.append( "--include-modules={0}".format( ",".join(self.__parameters["includeModules"]) ) ) if self.__parameters["excludeModules"] != self.defaults["excludeModules"]: parms["excludeModules"] = self.__parameters["excludeModules"][:] args.append( "--exclude-modules={0}".format( ",".join(self.__parameters["excludeModules"]) ) ) if self.__parameters["extListFile"] != self.defaults["extListFile"]: parms["extListFile"] = self.__parameters["extListFile"] args.append("--ext-list-file={0}".format(self.__parameters["extListFile"])) # 2.3 additional files tab if self.__parameters["additionalFiles"] != []: parms["additionalFiles"] = self.__parameters["additionalFiles"][:] return (args, parms) @pyqtSlot(str) def on_cxfreezeExecCombo_currentIndexChanged(self, text): """ Private slot to handle the selection of a cxfreeze executable. @param text selected cxfreeze executable @type str """ # version specific setup if isWindowsPlatform(): # remove "\Scripts\cx_Freeze.bat" from path dirname = os.path.dirname(text) dirname = os.path.dirname(dirname) # first try the fast way modpath = os.path.join(dirname, "Lib", "site-packages", "cx_Freeze") if not os.path.exists(modpath): # but if it failed search in the whole directory tree modpath = None for dirpath, dirnames, _ in os.walk(dirname): if "cx_Freeze" in dirnames: modpath = os.path.join(dirpath, "cx_Freeze") break else: with open(text, "r") as f: args = f.readline() if not args: return args = args.strip("!#\n").split(" ") program = args.pop(0) script = os.path.join( os.path.dirname(os.path.abspath(__file__)), "CxfreezeFindPath.py" ) if not os.path.exists(script): return args.append(script) process = QProcess() process.start(program, args) process.waitForFinished(5000) # get a QByteArray of the output cxPath = process.readAllStandardOutput() modpath = str(cxPath, encoding="utf-8").strip("\n\r") if not modpath.endswith("cx_Freeze"): return # populate combo boxes if modpath: d = QDir(os.path.join(modpath, "bases")) basesList = d.entryList(QDir.Filter.Files) if isWindowsPlatform(): # strip the final '.exe' from the bases tmpBasesList = basesList[:] basesList = [] for b in tmpBasesList: base, ext = os.path.splitext(b) if ext == ".exe": basesList.append(base) else: basesList.append(b) basesList.insert(0, "") currentText = self.basenameCombo.currentText() self.basenameCombo.clear() self.basenameCombo.addItems(basesList) self.basenameCombo.setEditText(currentText) d = QDir(os.path.join(modpath, "initscripts")) initList = d.entryList(["*.py"]) initList.insert(0, "") currentText = self.initscriptCombo.currentText() self.initscriptCombo.clear() self.initscriptCombo.addItems([os.path.splitext(i)[0] for i in initList]) self.initscriptCombo.setEditText(currentText) @pyqtSlot(int) def on_fileOrFolderList_currentRowChanged(self, row): """ Private slot to handle the currentRowChanged signal of the fileOrFolderList. @param row the current row @type int """ self.deleteSelectedButton.setEnabled(row != -1) if row != -1: self.fileOrFolderList.setCurrentRow(row) @pyqtSlot(QListWidgetItem) def on_fileOrFolderList_itemDoubleClicked(self, itm): """ Private slot to handle the itemDoubleClicked signal of the fileOrFolderList. @param itm the selected row @type QListWidgetItem """ self.fileOrFolderEdit.setText(itm.text()) row = self.fileOrFolderList.currentRow() itm = self.fileOrFolderList.takeItem(row) del itm @pyqtSlot() def on_addFileOrFolderButton_clicked(self): """ Private slot to add the entered file or directory to the list view. """ txt = self.fileOrFolderEdit.text() if txt: self.fileOrFolderList.addItem(txt) self.fileOrFolderEdit.clear() row = self.fileOrFolderList.currentRow() self.on_fileOrFolderList_currentRowChanged(row) @pyqtSlot(str) def on_fileOrFolderEdit_textChanged(self, txt): """ Private slot to handle the textChanged signal of the directory edit. @param txt the text of the directory edit @type str """ self.addFileOrFolderButton.setEnabled(txt != "") @pyqtSlot() def on_deleteSelectedButton_clicked(self): """ Private slot to delete the selected entry from the list view. """ row = self.fileOrFolderList.currentRow() itm = self.fileOrFolderList.takeItem(row) del itm row = self.fileOrFolderList.currentRow() self.on_fileOrFolderList_currentRowChanged(row) @pyqtSlot() def on_selectFileOrFolderButton_clicked(self): """ Private slot to select files or folders. It displays a file and directory selection dialog to select the files and directories which should be copied into the distribution folder. """ items = EricDirFileDialog.getOpenFileAndDirNames( self, self.tr("Select files and folders"), self.__project.getProjectPath() ) for itm in items: itm = self.__project.getRelativePath(itm) self.fileOrFolderList.addItem(toNativeSeparators(itm)) row = self.fileOrFolderList.currentRow() self.on_fileOrFolderList_currentRowChanged(row) def accept(self): """ Public method called by the Ok button. It saves the values in the parameters dictionary. """ # get data of general tab self.__parameters["targetDirectory"] = self.__project.getRelativePath( self.targetDirPicker.text() ) self.__parameters["targetName"] = self.targetNameEdit.text() self.__parameters["baseName"] = self.basenameCombo.currentText() self.__parameters["initScript"] = self.initscriptCombo.currentText() self.__parameters["applicationIcon"] = self.__project.getRelativePath( self.applicationIconPicker.text() ) self.__parameters["script"] = self.cxfreezeExecCombo.currentText() self.__parameters["keepPath"] = self.keeppathCheckBox.isChecked() self.__parameters["compress"] = self.compressCheckBox.isChecked() if self.nooptimizeRadioButton.isChecked(): self.__parameters["optimize"] = 0 elif self.optimizeRadioButton.isChecked(): self.__parameters["optimize"] = 1 else: self.__parameters["optimize"] = 2 # get data of advanced tab self.__parameters["defaultPath"] = self.__splitIt( self.defaultPathEdit.text(), os.pathsep ) self.__parameters["includePath"] = self.__splitIt( self.includePathEdit.text(), os.pathsep ) self.__parameters["replacePaths"] = self.__splitIt( self.replacePathsEdit.text(), os.pathsep ) self.__parameters["includeModules"] = self.__splitIt( self.includeModulesEdit.text(), "," ) self.__parameters["excludeModules"] = self.__splitIt( self.excludeModulesEdit.text(), "," ) self.__parameters["extListFile"] = self.__project.getRelativePath( self.extListFilePicker.text() ) # get data of the additional files tab additionalFiles = [ self.fileOrFolderList.item(x).text() for x in range(self.fileOrFolderList.count()) ] self.__parameters["additionalFiles"] = additionalFiles # call the accept slot of the base class QDialog.accept(self) def __splitIt(self, s, sep): """ Private method to split a string observing various conditions. @param s string to split @type str @param sep separator string @type str @return list of split values @rtype list of str """ if s == "" or s is None: return [] if s.endswith(sep): s = s[:-1] return s.split(sep)