|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2018 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing PyInstallerConfigDialog. |
|
8 """ |
|
9 |
|
10 import copy |
|
11 |
|
12 from PyQt6.QtCore import pyqtSlot |
|
13 from PyQt6.QtWidgets import QDialog, QDialogButtonBox |
|
14 |
|
15 from EricWidgets.EricPathPicker import EricPathPickerModes |
|
16 |
|
17 from .Ui_PyInstallerConfigDialog import Ui_PyInstallerConfigDialog |
|
18 |
|
19 import Globals |
|
20 |
|
21 |
|
22 class PyInstallerConfigDialog(QDialog, Ui_PyInstallerConfigDialog): |
|
23 """ |
|
24 Class implementing a dialog to enter the parameters for pyinstaller |
|
25 and pyi-makespec. |
|
26 """ |
|
27 def __init__(self, project, executables, params=None, mode="installer", |
|
28 parent=None): |
|
29 """ |
|
30 Constructor |
|
31 |
|
32 @param project reference to the project object |
|
33 @type Project.Project |
|
34 @param executables names of the pyinstaller executables |
|
35 @type list of str |
|
36 @param params parameters to set in the dialog |
|
37 @type dict |
|
38 @param mode mode of the dialog |
|
39 @type str (one of 'installer' or 'spec') |
|
40 @param parent reference to the parent widget |
|
41 @type QWidget |
|
42 """ |
|
43 super().__init__(parent) |
|
44 self.setupUi(self) |
|
45 |
|
46 self.__project = project |
|
47 self.__mode = mode |
|
48 |
|
49 self.inputFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) |
|
50 self.inputFilePicker.setDefaultDirectory( |
|
51 self.__project.getProjectPath()) |
|
52 if self.__mode == "installer": |
|
53 self.inputFilePicker.setFilters(self.tr( |
|
54 "Python Files (*.py *.py3);;" |
|
55 "Python GUI Files (*.pyw *.pyw3);;" |
|
56 "Spec Files (*.spec);;" |
|
57 "All Files (*)" |
|
58 )) |
|
59 elif self.__mode == "spec": |
|
60 self.inputFilePicker.setFilters(self.tr( |
|
61 "Python Files (*.py *.py3);;" |
|
62 "Python GUI Files (*.pyw *.pyw3);;" |
|
63 "All Files (*)" |
|
64 )) |
|
65 |
|
66 self.executableCombo.addItems(executables) |
|
67 |
|
68 if not bool(project.getMainScript()): |
|
69 # no main script defined |
|
70 self.selectedScriptButton.setChecked(True) |
|
71 self.mainScriptButton.setEnabled(False) |
|
72 |
|
73 self.iconFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) |
|
74 self.iconFilePicker.setDefaultDirectory( |
|
75 self.__project.getProjectPath()) |
|
76 if Globals.isMacPlatform(): |
|
77 self.iconFilePicker.setFilters(self.tr( |
|
78 "Icon Files (*.icns);;" |
|
79 "All Files (*)" |
|
80 )) |
|
81 elif Globals.isWindowsPlatform(): |
|
82 self.iconFilePicker.setFilters(self.tr( |
|
83 "Icon Files (*.ico);;" |
|
84 "Executable Files (*.exe);;" |
|
85 "All Files (*)" |
|
86 )) |
|
87 |
|
88 # disable platform specific tabs |
|
89 self.tabWidget.setTabEnabled( |
|
90 self.tabWidget.indexOf(self.windowsMacTab), |
|
91 Globals.isMacPlatform() or Globals.isWindowsPlatform()) |
|
92 self.tabWidget.setTabEnabled( |
|
93 self.tabWidget.indexOf(self.macTab), |
|
94 Globals.isMacPlatform()) |
|
95 |
|
96 self.__initializeDefaults() |
|
97 |
|
98 # get a copy of the defaults to store the user settings |
|
99 self.__parameters = copy.deepcopy(self.__defaults) |
|
100 |
|
101 # combine it with the values of params |
|
102 if params is not None: |
|
103 self.__parameters.update(params) |
|
104 |
|
105 # initialize general tab |
|
106 if mode == "installer" and bool(self.__parameters["pyinstaller"]): |
|
107 self.executableCombo.setCurrentIndex( |
|
108 self.executableCombo.findText( |
|
109 self.__parameters["pyinstaller"])) |
|
110 elif mode == "spec" and bool(self.__parameters["pyi-makespec"]): |
|
111 self.executableCombo.setCurrentIndex( |
|
112 self.executableCombo.findText( |
|
113 self.__parameters["pyi-makespec"])) |
|
114 if self.__parameters["mainscript"]: |
|
115 self.mainScriptButton.setChecked(True) |
|
116 else: |
|
117 self.selectedScriptButton.setChecked(True) |
|
118 self.inputFilePicker.setText(self.__parameters["inputFile"]) |
|
119 if self.__parameters["oneDirectory"]: |
|
120 self.oneDirButton.setChecked(True) |
|
121 else: |
|
122 self.oneFileButton.setChecked(True) |
|
123 self.nameEdit.setText(self.__parameters["name"]) |
|
124 self.keyEdit.setText(self.__parameters["encryptionKey"]) |
|
125 self.cleanCheckBox.setChecked(self.__parameters["cleanBeforeBuilding"]) |
|
126 |
|
127 # initialize Windows and macOS tab |
|
128 if self.__parameters["consoleApplication"]: |
|
129 self.consoleButton.setChecked(True) |
|
130 else: |
|
131 self.windowedButton.setChecked(True) |
|
132 self.iconFilePicker.setText(self.__parameters["iconFile"]) |
|
133 self.iconIdEdit.setText(self.__parameters["iconId"]) |
|
134 |
|
135 # initialize maxOS specific tab |
|
136 self.bundleIdentifierEdit.setText( |
|
137 self.__parameters["bundleIdentifier"]) |
|
138 |
|
139 self.__updateOkButton() |
|
140 |
|
141 msh = self.minimumSizeHint() |
|
142 self.resize(max(self.width(), msh.width()), msh.height()) |
|
143 |
|
144 def __initializeDefaults(self): |
|
145 """ |
|
146 Private method to set the default values. |
|
147 |
|
148 These are needed later on to generate the command line parameters. |
|
149 """ |
|
150 self.__defaults = { |
|
151 # general options |
|
152 "pyinstaller": "", |
|
153 "pyi-makespec": "", |
|
154 "mainscript": bool(self.__project.getMainScript()), |
|
155 "inputFile": "", |
|
156 "oneDirectory": True, |
|
157 "name": "", |
|
158 "encryptionKey": "", |
|
159 "cleanBeforeBuilding": False, |
|
160 |
|
161 # Windows and macOS options |
|
162 "consoleApplication": True, |
|
163 "iconFile": "", |
|
164 "iconId": "", |
|
165 |
|
166 # macOS specific options |
|
167 "bundleIdentifier": "", |
|
168 } |
|
169 |
|
170 def generateParameters(self): |
|
171 """ |
|
172 Public method that generates the command line parameters. |
|
173 |
|
174 It generates a list of strings to be used to set the QProcess arguments |
|
175 for the pyinstaller/pyi-makespec call and a list containing the non |
|
176 default parameters. The second list can be passed back upon object |
|
177 generation to overwrite the default settings. |
|
178 |
|
179 @return a tuple of the command line parameters, non default parameters |
|
180 and the script path |
|
181 @rtype tuple of (list of str, dict, str) |
|
182 """ |
|
183 parms = {} |
|
184 args = [] |
|
185 |
|
186 # 1. the program name |
|
187 if self.__mode == "installer": |
|
188 args.append(self.__parameters["pyinstaller"]) |
|
189 parms["pyinstaller"] = self.__parameters["pyinstaller"] |
|
190 elif self.__mode == "spec": |
|
191 args.append(self.__parameters["pyi-makespec"]) |
|
192 parms["pyi-makespec"] = self.__parameters["pyi-makespec"] |
|
193 |
|
194 # 2. the commandline options |
|
195 # 2.1 general options, input |
|
196 if not self.__parameters["mainscript"]: |
|
197 parms["mainscript"] = False |
|
198 parms["inputFile"] = self.__parameters["inputFile"] |
|
199 |
|
200 runWithSpec = self.__parameters["inputFile"].endswith(".spec") |
|
201 if not runWithSpec: |
|
202 # 2.2 general options, part 1 |
|
203 if not self.__parameters["oneDirectory"]: |
|
204 parms["oneDirectory"] = self.__parameters["oneDirectory"] |
|
205 args.append("--onefile") |
|
206 if self.__parameters["name"] != self.__defaults["name"]: |
|
207 parms["name"] = self.__parameters["name"] |
|
208 args.append("--name") |
|
209 args.append(self.__parameters["name"]) |
|
210 if ( |
|
211 self.__parameters["encryptionKey"] != |
|
212 self.__defaults["encryptionKey"] |
|
213 ): |
|
214 parms["encryptionKey"] = self.__parameters["encryptionKey"] |
|
215 args.append("--key") |
|
216 args.append(self.__parameters["encryptionKey"]) |
|
217 |
|
218 # 2.3 Windows and macOS options |
|
219 if ( |
|
220 self.__parameters["consoleApplication"] != |
|
221 self.__defaults["consoleApplication"] |
|
222 ): |
|
223 parms["consoleApplication"] = ( |
|
224 self.__parameters["consoleApplication"] |
|
225 ) |
|
226 args.append("--windowed") |
|
227 if self.__parameters["iconFile"] != self.__defaults["iconFile"]: |
|
228 parms["iconFile"] = self.__parameters["iconFile"] |
|
229 parms["iconId"] = self.__parameters["iconId"] |
|
230 args.append("--icon") |
|
231 if self.__parameters["iconFile"].endswith(".exe"): |
|
232 if bool(self.__parameters["iconId"]): |
|
233 iconId = self.__parameters["iconId"] |
|
234 else: |
|
235 iconId = "0" |
|
236 args.append("{0},{1}".format( |
|
237 self.__parameters["iconFile"], iconId)) |
|
238 else: |
|
239 args.append(self.__parameters["iconFile"]) |
|
240 |
|
241 # 2.4 macOS specific options |
|
242 if ( |
|
243 self.__parameters["bundleIdentifier"] != |
|
244 self.__defaults["bundleIdentifier"] |
|
245 ): |
|
246 parms["bundleIdentifier"] = ( |
|
247 self.__parameters["bundleIdentifier"] |
|
248 ) |
|
249 args.append("--osx-bundle-identifier") |
|
250 args.append(self.__parameters["bundleIdentifier"]) |
|
251 |
|
252 # 2.5 general options, part 2 |
|
253 if ( |
|
254 self.__parameters["cleanBeforeBuilding"] != |
|
255 self.__defaults["cleanBeforeBuilding"] |
|
256 ): |
|
257 parms["cleanBeforeBuilding"] = ( |
|
258 self.__parameters["cleanBeforeBuilding"] |
|
259 ) |
|
260 args.append("--clean") |
|
261 |
|
262 # 3. always add these arguments |
|
263 if self.__mode == "installer": |
|
264 args.append("--noconfirm") # don't ask the user |
|
265 |
|
266 # determine the script to be processed |
|
267 script = ( |
|
268 self.__project.getMainScript() |
|
269 if self.__parameters["mainscript"] else |
|
270 self.__parameters["inputFile"] |
|
271 ) |
|
272 |
|
273 return args, parms, script |
|
274 |
|
275 def accept(self): |
|
276 """ |
|
277 Public method called by the Ok button. |
|
278 |
|
279 It saves the values in the parameters dictionary. |
|
280 """ |
|
281 # get data of general tab |
|
282 if self.__mode == "installer": |
|
283 self.__parameters["pyinstaller"] = ( |
|
284 self.executableCombo.currentText() |
|
285 ) |
|
286 elif self.__mode == "spec": |
|
287 self.__parameters["pyi-makespec"] = ( |
|
288 self.executableCombo.currentText() |
|
289 ) |
|
290 self.__parameters["mainscript"] = self.mainScriptButton.isChecked() |
|
291 self.__parameters["inputFile"] = self.inputFilePicker.text() |
|
292 self.__parameters["oneDirectory"] = self.oneDirButton.isChecked() |
|
293 self.__parameters["name"] = self.nameEdit.text() |
|
294 self.__parameters["encryptionKey"] = self.keyEdit.text() |
|
295 self.__parameters["cleanBeforeBuilding"] = ( |
|
296 self.cleanCheckBox.isChecked() |
|
297 ) |
|
298 |
|
299 # get data of Windows and macOS tab |
|
300 self.__parameters["consoleApplication"] = ( |
|
301 self.consoleButton.isChecked() |
|
302 ) |
|
303 self.__parameters["iconFile"] = self.iconFilePicker.text() |
|
304 self.__parameters["iconId"] = self.iconIdEdit.text() |
|
305 |
|
306 # get data of macOS specific tab |
|
307 self.__parameters["bundleIdentifier"] = ( |
|
308 self.bundleIdentifierEdit.text() |
|
309 ) |
|
310 |
|
311 # call the accept slot of the base class |
|
312 super().accept() |
|
313 |
|
314 def __updateOkButton(self): |
|
315 """ |
|
316 Private method to update the enabled state of the OK button. |
|
317 """ |
|
318 enable = True |
|
319 |
|
320 # If not to be run with the project main script, a script or |
|
321 # spec file must be selected. |
|
322 if ( |
|
323 self.selectedScriptButton.isChecked() and |
|
324 not bool(self.inputFilePicker.text()) |
|
325 ): |
|
326 enable = False |
|
327 |
|
328 # If the icon shall be picked from a .exe file, an icon ID |
|
329 # must be entered (Windows only). |
|
330 if ( |
|
331 self.iconFilePicker.text().endswith(".exe") and |
|
332 not bool(self.iconIdEdit.text()) |
|
333 ): |
|
334 enable = False |
|
335 |
|
336 self.buttonBox.button( |
|
337 QDialogButtonBox.StandardButton.Ok).setEnabled(enable) |
|
338 |
|
339 @pyqtSlot(bool) |
|
340 def on_selectedScriptButton_toggled(self, checked): |
|
341 """ |
|
342 Private slot to handle changes of the radio button state. |
|
343 |
|
344 @param checked state of the radio button |
|
345 @type bool |
|
346 """ |
|
347 self.__updateOkButton() |
|
348 |
|
349 @pyqtSlot(str) |
|
350 def on_inputFilePicker_textChanged(self, txt): |
|
351 """ |
|
352 Private slot to handle changes of the input file. |
|
353 |
|
354 @param txt text of the file edit |
|
355 @type str |
|
356 """ |
|
357 self.__updateOkButton() |
|
358 |
|
359 @pyqtSlot(str) |
|
360 def on_iconFilePicker_textChanged(self, txt): |
|
361 """ |
|
362 Private slot to handle changes of the icon file. |
|
363 |
|
364 @param txt text of the file edit |
|
365 @type str |
|
366 """ |
|
367 self.iconIdEdit.setEnabled(txt.endswith(".exe")) |
|
368 self.__updateOkButton() |
|
369 |
|
370 @pyqtSlot(str) |
|
371 def on_iconIdEdit_textChanged(self, txt): |
|
372 """ |
|
373 Private slot to handle changes of the icon ID. |
|
374 |
|
375 @param txt iconID |
|
376 @type str |
|
377 """ |
|
378 self.__updateOkButton() |