1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing the Start Program dialog.
8"""
9
10import os
11
12from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QComboBox, QInputDialog
13
14from E5Gui.E5PathPicker import E5PathPickerModes
15from E5Gui.E5Application import e5App
16
17import Preferences
18
19
20class StartDialog(QDialog):
21    """
22    Class implementing the Start Program dialog.
23
24    It implements a dialog that is used to start an
25    application for debugging. It asks the user to enter
26    the commandline parameters, the working directory and
27    whether exception reporting should be disabled.
28    """
29    def __init__(self, caption, lastUsedVenvName, argvList, wdList, envList,
30                 exceptions,
31                 parent=None, dialogType=0, modfuncList=None,
32                 tracePython=False, autoClearShell=True, autoContinue=True,
33                 enableMultiprocess=False, multiprocessNoDebugHistory=None,
34                 configOverride=None):
35        """
36        Constructor
37
38        @param caption the caption to be displayed
39        @type str
40        @param lastUsedVenvName name of the most recently used virtual
41            environment
42        @type str
43        @param argvList history list of command line arguments
44        @type list of str
45        @param wdList history list of working directories
46        @type list of str
47        @param envList history list of environment parameter settings
48        @type list of str
49        @param exceptions exception reporting flag
50        @type bool
51        @param parent parent widget of this dialog
52        @type QWidget
53        @param dialogType type of the start dialog
54                <ul>
55                <li>0 = start debug dialog</li>
56                <li>1 = start run dialog</li>
57                <li>2 = start coverage dialog</li>
58                <li>3 = start profile dialog</li>
59                </ul>
60        @type int (0 to 3)
61        @param modfuncList history list of module functions
62        @type list of str
63        @param tracePython flag indicating if the Python library should
64            be traced as well
65        @type bool
66        @param autoClearShell flag indicating, that the interpreter window
67            should be cleared automatically
68        @type bool
69        @param autoContinue flag indicating, that the debugger should not
70            stop at the first executable line
71        @type bool
72        @param enableMultiprocess flag indicating the support for multi process
73            debugging
74        @type bool
75        @param multiprocessNoDebugHistory list of lists with programs not to be
76            debugged
77        @type list of str
78        @param configOverride dictionary containing the global config override
79            data
80        @type dict
81        """
82        super().__init__(parent)
83        self.setModal(True)
84
85        self.dialogType = dialogType
86        if dialogType == 0:
87            from .Ui_StartDebugDialog import Ui_StartDebugDialog
88            self.ui = Ui_StartDebugDialog()
89        elif dialogType == 1:
90            from .Ui_StartRunDialog import Ui_StartRunDialog
91            self.ui = Ui_StartRunDialog()
92        elif dialogType == 2:
93            from .Ui_StartCoverageDialog import Ui_StartCoverageDialog
94            self.ui = Ui_StartCoverageDialog()
95        elif dialogType == 3:
96            from .Ui_StartProfileDialog import Ui_StartProfileDialog
97            self.ui = Ui_StartProfileDialog()
98        self.ui.setupUi(self)
99
100        self.ui.venvComboBox.addItem("")
101        self.ui.venvComboBox.addItems(
102            sorted(e5App().getObject("VirtualEnvManager")
103                   .getVirtualenvNames()))
104
105        self.ui.workdirPicker.setMode(E5PathPickerModes.DirectoryMode)
106        self.ui.workdirPicker.setDefaultDirectory(
107            Preferences.getMultiProject("Workspace"))
108        self.ui.workdirPicker.setInsertPolicy(
109            QComboBox.InsertPolicy.InsertAtTop)
110        self.ui.workdirPicker.setSizeAdjustPolicy(
111            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
112
113        self.clearButton = self.ui.buttonBox.addButton(
114            self.tr("Clear Histories"), QDialogButtonBox.ButtonRole.ActionRole)
115        self.editButton = self.ui.buttonBox.addButton(
116            self.tr("Edit History"), QDialogButtonBox.ButtonRole.ActionRole)
117
118        self.setWindowTitle(caption)
119        self.ui.cmdlineCombo.clear()
120        self.ui.cmdlineCombo.addItems(argvList)
121        if len(argvList) > 0:
122            self.ui.cmdlineCombo.setCurrentIndex(0)
123        self.ui.workdirPicker.clear()
124        self.ui.workdirPicker.addItems(wdList)
125        if len(wdList) > 0:
126            self.ui.workdirPicker.setCurrentIndex(0)
127        self.ui.environmentCombo.clear()
128        self.ui.environmentCombo.addItems(envList)
129        self.ui.exceptionCheckBox.setChecked(exceptions)
130        self.ui.clearShellCheckBox.setChecked(autoClearShell)
131        self.ui.consoleCheckBox.setEnabled(
132            Preferences.getDebugger("ConsoleDbgCommand") != "")
133        self.ui.consoleCheckBox.setChecked(False)
134        venvIndex = max(0, self.ui.venvComboBox.findText(lastUsedVenvName))
135        self.ui.venvComboBox.setCurrentIndex(venvIndex)
136        self.ui.globalOverrideGroup.setChecked(configOverride["enable"])
137        self.ui.redirectCheckBox.setChecked(configOverride["redirect"])
138
139        if dialogType == 0:        # start debug dialog
140            enableMultiprocessGlobal = Preferences.getDebugger(
141                "MultiProcessEnabled")
142            self.ui.tracePythonCheckBox.setChecked(tracePython)
143            self.ui.tracePythonCheckBox.show()
144            self.ui.autoContinueCheckBox.setChecked(autoContinue)
145            self.ui.multiprocessGroup.setEnabled(enableMultiprocessGlobal)
146            self.ui.multiprocessGroup.setChecked(
147                enableMultiprocess & enableMultiprocessGlobal)
148            self.ui.multiprocessNoDebugCombo.clear()
149            self.ui.multiprocessNoDebugCombo.setToolTip(self.tr(
150                "Enter the list of programs or program patterns not to be"
151                " debugged separated by '{0}'.").format(os.pathsep)
152            )
153            if multiprocessNoDebugHistory:
154                self.ui.multiprocessNoDebugCombo.addItems(
155                    multiprocessNoDebugHistory)
156                self.ui.multiprocessNoDebugCombo.setCurrentIndex(0)
157
158        if dialogType == 3:       # start coverage or profile dialog
159            self.ui.eraseCheckBox.setChecked(True)
160
161        self.__clearHistoryLists = False
162        self.__historiesModified = False
163
164        msh = self.minimumSizeHint()
165        self.resize(max(self.width(), msh.width()), msh.height())
166
167    def on_modFuncCombo_editTextChanged(self):
168        """
169        Private slot to enable/disable the OK button.
170        """
171        self.ui.buttonBox.button(
172            QDialogButtonBox.StandardButton.Ok).setDisabled(
173                self.ui.modFuncCombo.currentText() == "")
174
175    def getData(self):
176        """
177        Public method to retrieve the data entered into this dialog.
178
179        @return a tuple of interpreter, argv, workdir, environment,
180            exceptions flag, clear interpreter flag and run in console flag
181        @rtype tuple of (str, str, str, str, bool, bool, bool)
182        """
183        cmdLine = self.ui.cmdlineCombo.currentText()
184        workdir = self.ui.workdirPicker.currentText(toNative=False)
185        environment = self.ui.environmentCombo.currentText()
186        venvName = self.ui.venvComboBox.currentText()
187
188        return (venvName,
189                cmdLine,
190                workdir,
191                environment,
192                self.ui.exceptionCheckBox.isChecked(),
193                self.ui.clearShellCheckBox.isChecked(),
194                self.ui.consoleCheckBox.isChecked(),
195                )
196
197    def getGlobalOverrideData(self):
198        """
199        Public method to retrieve the global configuration override data
200        entered into this dialog.
201
202        @return dictionary containing a flag indicating to activate the global
203            override and a flag indicating a redirect of stdin/stdout/stderr
204        @rtype dict
205        """
206        return {
207            "enable": self.ui.globalOverrideGroup.isChecked(),
208            "redirect": self.ui.redirectCheckBox.isChecked(),
209        }
210
211    def getDebugData(self):
212        """
213        Public method to retrieve the debug related data entered into this
214        dialog.
215
216        @return a tuple of a flag indicating, if the Python library should be
217            traced as well, a flag indicating, that the debugger should not
218            stop at the first executable line, a flag indicating to support
219            multi process debugging and a space separated list of programs not
220            to be debugged
221        @rtype tuple of (bool, bool, bool, str)
222        """
223        if self.dialogType == 0:
224            return (self.ui.tracePythonCheckBox.isChecked(),
225                    self.ui.autoContinueCheckBox.isChecked(),
226                    self.ui.multiprocessGroup.isChecked(),
227                    self.ui.multiprocessNoDebugCombo.currentText())
228        else:
229            return (False, False, False, "")
230
231    def getCoverageData(self):
232        """
233        Public method to retrieve the coverage related data entered into this
234        dialog.
235
236        @return flag indicating erasure of coverage info
237        @rtype bool
238        """
239        if self.dialogType == 2:
240            return self.ui.eraseCheckBox.isChecked()
241        else:
242            return False
243
244    def getProfilingData(self):
245        """
246        Public method to retrieve the profiling related data entered into this
247        dialog.
248
249        @return flag indicating erasure of profiling info
250        @rtype bool
251        """
252        if self.dialogType == 3:
253            return self.ui.eraseCheckBox.isChecked()
254        else:
255            return False
256
257    def __clearHistories(self):
258        """
259        Private slot to clear the combo boxes lists and record a flag to
260        clear the lists.
261        """
262        self.__clearHistoryLists = True
263        self.__historiesModified = False    # clear catches it all
264
265        cmdLine = self.ui.cmdlineCombo.currentText()
266        workdir = self.ui.workdirPicker.currentText()
267        environment = self.ui.environmentCombo.currentText()
268
269        self.ui.cmdlineCombo.clear()
270        self.ui.workdirPicker.clear()
271        self.ui.environmentCombo.clear()
272
273        self.ui.cmdlineCombo.addItem(cmdLine)
274        self.ui.workdirPicker.addItem(workdir)
275        self.ui.environmentCombo.addItem(environment)
276
277        if self.dialogType == 0:
278            noDebugList = self.ui.multiprocessNoDebugCombo.currentText()
279            self.ui.multiprocessNoDebugCombo.clear()
280            self.ui.multiprocessNoDebugCombo.addItem(noDebugList)
281
282    def __editHistory(self):
283        """
284        Private slot to edit a history list.
285        """
286        histories = [
287            "",
288            self.tr("Command Line"),
289            self.tr("Working Directory"),
290            self.tr("Environment"),
291        ]
292        combos = [
293            None,
294            self.ui.cmdlineCombo,
295            self.ui.workdirPicker,
296            self.ui.environmentCombo,
297        ]
298        if self.dialogType == 0:
299            histories.append(self.tr("No Debug Programs"))
300            combos.append(self.ui.multiprocessNoDebugCombo)
301        historyKind, ok = QInputDialog.getItem(
302            self,
303            self.tr("Edit History"),
304            self.tr("Select the history list to be edited:"),
305            histories,
306            0, False)
307        if ok and historyKind:
308            history = []
309            historiesIndex = histories.index(historyKind)
310            if historiesIndex == 2:
311                history = self.ui.workdirPicker.getPathItems()
312            else:
313                combo = combos[historiesIndex]
314                if combo:
315                    for index in range(combo.count()):
316                        history.append(combo.itemText(index))
317
318            if history:
319                from .StartHistoryEditDialog import StartHistoryEditDialog
320                dlg = StartHistoryEditDialog(history, self)
321            if dlg.exec() == QDialog.DialogCode.Accepted:
322                history = dlg.getHistory()
323                combo = combos[historiesIndex]
324                if combo:
325                    combo.clear()
326                    combo.addItems(history)
327
328                    self.__historiesModified = True
329
330    def historiesModified(self):
331        """
332        Public method to test for modified histories.
333
334        @return flag indicating modified histories
335        @rtype bool
336        """
337        return self.__historiesModified
338
339    def clearHistories(self):
340        """
341        Public method to test, if histories shall be cleared.
342
343        @return flag indicating histories shall be cleared
344        @rtype bool
345        """
346        return self.__clearHistoryLists
347
348    def getHistories(self):
349        """
350        Public method to get the lists of histories.
351
352        @return tuple containing the histories of command line arguments,
353            working directories, environment settings and no debug programs
354            lists
355        @rtype tuple of four list of str
356        """
357        noDebugHistory = (
358            [
359                self.ui.multiprocessNoDebugCombo.itemText(index)
360                for index in range(self.ui.multiprocessNoDebugCombo.count())
361            ]
362            if self.dialogType == 0 else
363            None
364        )
365        return (
366            [self.ui.cmdlineCombo.itemText(index) for index in range(
367                self.ui.cmdlineCombo.count())],
368            self.ui.workdirPicker.getPathItems(),
369            [self.ui.environmentCombo.itemText(index) for index in range(
370                self.ui.environmentCombo.count())],
371            noDebugHistory,
372        )
373
374    def on_buttonBox_clicked(self, button):
375        """
376        Private slot called by a button of the button box clicked.
377
378        @param button button that was clicked
379        @type QAbstractButton
380        """
381        if button == self.clearButton:
382            self.__clearHistories()
383        elif button == self.editButton:
384            self.__editHistory()
385