CxFreeze/CxfreezeConfigDialog.py

Tue, 25 Oct 2022 09:03:26 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 25 Oct 2022 09:03:26 +0200
branch
eric7
changeset 140
9e20ee9c7ca2
parent 139
4df5e67b084b
child 142
9ceff8a10455
permissions
-rw-r--r--

Adapted the import statements to the new structure.

# -*- coding: utf-8 -*-

# Copyright (c) 2010 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to enter the parameters for cxfreeze.
"""

import sys
import os
import copy
import contextlib

from PyQt6.QtCore import pyqtSlot, QDir, QProcess
from PyQt6.QtWidgets import QDialog, QListWidgetItem

from eric7 import Utilities

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

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 Utilities.isWindowsPlatform():
            iconFilter = "{0} (*.ico);;{1} (*.*)".format(iconsI18N, allFilesI18N)
        elif Utilities.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 Utilities.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 Utilities.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(Utilities.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)

eric ide

mercurial