eric7/E5Gui/EricProcessDialog.py

branch
eric7
changeset 8356
68ec9c3d4de5
parent 8318
962bce857696
equal deleted inserted replaced
8355:8a7677a63c8d 8356:68ec9c3d4de5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog starting a process and showing its output.
8 """
9
10 import os
11 import re
12
13 from PyQt6.QtCore import (
14 QProcess, QTimer, pyqtSlot, Qt, QCoreApplication, QProcessEnvironment
15 )
16 from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QLineEdit
17
18 from E5Gui import EricMessageBox
19
20 from .Ui_EricProcessDialog import Ui_EricProcessDialog
21
22 from Globals import strToQByteArray
23 import Preferences
24
25
26 class EricProcessDialog(QDialog, Ui_EricProcessDialog):
27 """
28 Class implementing a dialog starting a process and showing its output.
29
30 It starts a QProcess and displays a dialog that shows the output of the
31 process. The dialog is modal, which causes a synchronized execution of
32 the process.
33 """
34 def __init__(self, outputTitle="", windowTitle="", showProgress=False,
35 parent=None):
36 """
37 Constructor
38
39 @param outputTitle title for the output group
40 @type str
41 @param windowTitle title of the dialog
42 @type str
43 @param showProgress flag indicating to show a progress bar
44 @type bool
45 @param parent reference to the parent widget
46 @type QWidget
47 """
48 super().__init__(parent)
49 self.setupUi(self)
50
51 self.buttonBox.button(
52 QDialogButtonBox.StandardButton.Close).setEnabled(False)
53 self.buttonBox.button(
54 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
55
56 font = Preferences.getEditorOtherFonts("MonospacedFont")
57 self.resultbox.setFontFamily(font.family())
58 self.resultbox.setFontPointSize(font.pointSize())
59 self.errors.setFontFamily(font.family())
60 self.errors.setFontPointSize(font.pointSize())
61
62 if windowTitle:
63 self.setWindowTitle(windowTitle)
64 if outputTitle:
65 self.outputGroup.setTitle(outputTitle)
66 self.__showProgress = showProgress
67 self.progressBar.setVisible(self.__showProgress)
68
69 self.__process = None
70 self.__progressRe = re.compile(r"""(\d{1,3})\s*%""")
71
72 self.show()
73 QCoreApplication.processEvents()
74
75 def __finish(self):
76 """
77 Private slot called when the process finished or the user pressed
78 the button.
79 """
80 if (
81 self.__process is not None and
82 self.__process.state() != QProcess.ProcessState.NotRunning
83 ):
84 self.__process.terminate()
85 QTimer.singleShot(2000, self.__process.kill)
86 self.__process.waitForFinished(3000)
87
88 self.inputGroup.setEnabled(False)
89 self.inputGroup.hide()
90
91 self.__process = None
92
93 self.buttonBox.button(
94 QDialogButtonBox.StandardButton.Close).setEnabled(True)
95 self.buttonBox.button(
96 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
97 self.buttonBox.button(
98 QDialogButtonBox.StandardButton.Close).setDefault(True)
99 self.buttonBox.button(
100 QDialogButtonBox.StandardButton.Close).setFocus(
101 Qt.FocusReason.OtherFocusReason)
102
103 def on_buttonBox_clicked(self, button):
104 """
105 Private slot called by a button of the button box clicked.
106
107 @param button button that was clicked
108 @type QAbstractButton
109 """
110 if button == self.buttonBox.button(
111 QDialogButtonBox.StandardButton.Close
112 ):
113 self.close()
114 elif button == self.buttonBox.button(
115 QDialogButtonBox.StandardButton.Cancel
116 ):
117 self.statusLabel.setText(self.tr("Process canceled."))
118 self.__finish()
119
120 def __procFinished(self, exitCode, exitStatus):
121 """
122 Private slot connected to the finished signal.
123
124 @param exitCode exit code of the process
125 @type int
126 @param exitStatus exit status of the process
127 @type QProcess.ExitStatus
128 """
129 self.__normal = (
130 (exitStatus == QProcess.ExitStatus.NormalExit) and
131 (exitCode == 0)
132 )
133 if self.__normal:
134 self.statusLabel.setText(self.tr("Process finished successfully."))
135 elif exitStatus == QProcess.ExitStatus.CrashExit:
136 self.statusLabel.setText(self.tr("Process crashed."))
137 else:
138 self.statusLabel.setText(
139 self.tr("Process finished with exit code {0}")
140 .format(exitCode))
141 self.__finish()
142
143 def startProcess(self, program, args, workingDir=None, showArgs=True,
144 environment=None):
145 """
146 Public slot used to start the process.
147
148 @param program path of the program to be executed
149 @type str
150 @param args list of arguments for the process
151 @type list of str
152 @param workingDir working directory for the process
153 @type str
154 @param showArgs flag indicating to show the arguments
155 @type bool
156 @param environment dictionary of environment settings to add
157 or change for the process
158 @type dict
159 @return flag indicating a successful start of the process
160 @rtype bool
161 """
162 self.errorGroup.hide()
163 self.__normal = False
164 self.__intercept = False
165
166 if environment is None:
167 environment = {}
168
169 if showArgs:
170 self.resultbox.append(program + ' ' + ' '.join(args))
171 self.resultbox.append('')
172
173 self.__process = QProcess()
174 if environment:
175 env = QProcessEnvironment.systemEnvironment()
176 for key, value in environment.items():
177 env.insert(key, value)
178 self.__process.setProcessEnvironment(env)
179
180 self.__process.finished.connect(self.__procFinished)
181 self.__process.readyReadStandardOutput.connect(self.__readStdout)
182 self.__process.readyReadStandardError.connect(self.__readStderr)
183
184 if workingDir:
185 self.__process.setWorkingDirectory(workingDir)
186
187 self.__process.start(program, args)
188 procStarted = self.__process.waitForStarted(10000)
189 if not procStarted:
190 self.buttonBox.setFocus()
191 self.inputGroup.setEnabled(False)
192 EricMessageBox.critical(
193 self,
194 self.tr('Process Generation Error'),
195 self.tr(
196 '<p>The process <b>{0}</b> could not be started.</p>'
197 ).format(program))
198 else:
199 self.inputGroup.setEnabled(True)
200 self.inputGroup.show()
201
202 return procStarted
203
204 def normalExit(self):
205 """
206 Public method to check for a normal process termination.
207
208 @return flag indicating normal process termination
209 @rtype bool
210 """
211 return self.__normal
212
213 def normalExitWithoutErrors(self):
214 """
215 Public method to check for a normal process termination without
216 error messages.
217
218 @return flag indicating normal process termination
219 @rtype bool
220 """
221 return self.__normal and self.errors.toPlainText() == ""
222
223 def __readStdout(self):
224 """
225 Private slot to handle the readyReadStandardOutput signal.
226
227 It reads the output of the process and inserts it into the
228 output pane.
229 """
230 if self.__process is not None:
231 s = str(self.__process.readAllStandardOutput(),
232 Preferences.getSystem("IOEncoding"),
233 'replace')
234 if self.__showProgress:
235 match = self.__progressRe.search(s)
236 if match:
237 progress = int(match.group(1))
238 self.progressBar.setValue(progress)
239 if not s.endswith("\n"):
240 s += "\n"
241 self.resultbox.insertPlainText(s)
242 self.resultbox.ensureCursorVisible()
243
244 QCoreApplication.processEvents()
245
246 def __readStderr(self):
247 """
248 Private slot to handle the readyReadStandardError signal.
249
250 It reads the error output of the process and inserts it into the
251 error pane.
252 """
253 if self.__process is not None:
254 s = str(self.__process.readAllStandardError(),
255 Preferences.getSystem("IOEncoding"),
256 'replace')
257
258 self.errorGroup.show()
259 self.errors.insertPlainText(s)
260 self.errors.ensureCursorVisible()
261
262 QCoreApplication.processEvents()
263
264 def on_passwordCheckBox_toggled(self, isOn):
265 """
266 Private slot to handle the password checkbox toggled.
267
268 @param isOn flag indicating the status of the check box
269 @type bool
270 """
271 if isOn:
272 self.input.setEchoMode(QLineEdit.EchoMode.Password)
273 else:
274 self.input.setEchoMode(QLineEdit.EchoMode.Normal)
275
276 @pyqtSlot()
277 def on_sendButton_clicked(self):
278 """
279 Private slot to send the input to the git process.
280 """
281 inputTxt = self.input.text()
282 inputTxt += os.linesep
283
284 if self.passwordCheckBox.isChecked():
285 self.errors.insertPlainText(os.linesep)
286 self.errors.ensureCursorVisible()
287 else:
288 self.errors.insertPlainText(inputTxt)
289 self.errors.ensureCursorVisible()
290
291 self.__process.write(strToQByteArray(inputTxt))
292
293 self.passwordCheckBox.setChecked(False)
294 self.input.clear()
295
296 def on_input_returnPressed(self):
297 """
298 Private slot to handle the press of the return key in the input field.
299 """
300 self.__intercept = True
301 self.on_sendButton_clicked()
302
303 def keyPressEvent(self, evt):
304 """
305 Protected slot to handle a key press event.
306
307 @param evt the key press event (QKeyEvent)
308 """
309 if self.__intercept:
310 self.__intercept = False
311 evt.accept()
312 return
313
314 super().keyPressEvent(evt)

eric ide

mercurial