src/eric7/EricWidgets/EricProcessDialog.py

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

eric ide

mercurial