55 @param parent parent widget |
67 @param parent parent widget |
56 @type QWidget |
68 @type QWidget |
57 """ |
69 """ |
58 super().__init__(parent) |
70 super().__init__(parent) |
59 self.setupUi(self) |
71 self.setupUi(self) |
60 |
72 |
61 self.buttonBox.button( |
73 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) |
62 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
74 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
63 self.buttonBox.button( |
75 self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled(False) |
64 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
65 self.buttonBox.button( |
|
66 QDialogButtonBox.StandardButton.Save).setEnabled(False) |
|
67 if saveFilters is None: |
76 if saveFilters is None: |
68 self.buttonBox.button( |
77 self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setHidden(True) |
69 QDialogButtonBox.StandardButton.Save).setHidden(True) |
78 |
70 |
|
71 self.ioEncoding = Preferences.getSystem("IOEncoding") |
79 self.ioEncoding = Preferences.getSystem("IOEncoding") |
72 |
80 |
73 self.proc = None |
81 self.proc = None |
74 self.argsLists = [] |
82 self.argsLists = [] |
75 self.workingDir = None |
83 self.workingDir = None |
76 self.mergedOutput = False |
84 self.mergedOutput = False |
77 self.msgSuccess = msgSuccess |
85 self.msgSuccess = msgSuccess |
78 self.msgError = msgError |
86 self.msgError = msgError |
79 self.fileFilters = saveFilters |
87 self.fileFilters = saveFilters |
80 self.showInput = showInput |
88 self.showInput = showInput |
81 self.intercept = False |
89 self.intercept = False |
82 |
90 |
83 self.outputGroup.setTitle(text) |
91 self.outputGroup.setTitle(text) |
84 |
92 |
85 if fixed: |
93 if fixed: |
86 if isWindowsPlatform(): |
94 if isWindowsPlatform(): |
87 self.resultbox.setFontFamily("Lucida Console") |
95 self.resultbox.setFontFamily("Lucida Console") |
88 else: |
96 else: |
89 self.resultbox.setFontFamily("Monospace") |
97 self.resultbox.setFontFamily("Monospace") |
90 |
98 |
91 if not linewrap: |
99 if not linewrap: |
92 self.resultbox.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) |
100 self.resultbox.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) |
93 |
101 |
94 @pyqtSlot(QAbstractButton) |
102 @pyqtSlot(QAbstractButton) |
95 def on_buttonBox_clicked(self, button): |
103 def on_buttonBox_clicked(self, button): |
96 """ |
104 """ |
97 Private slot called by a button of the button box clicked. |
105 Private slot called by a button of the button box clicked. |
98 |
106 |
99 @param button button that was clicked |
107 @param button button that was clicked |
100 @type QAbstractButton |
108 @type QAbstractButton |
101 """ |
109 """ |
102 if button == self.buttonBox.button( |
110 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): |
103 QDialogButtonBox.StandardButton.Close |
|
104 ): |
|
105 self.close() |
111 self.close() |
106 elif button == self.buttonBox.button( |
112 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): |
107 QDialogButtonBox.StandardButton.Cancel |
|
108 ): |
|
109 self.__finish() |
113 self.__finish() |
110 elif button == self.buttonBox.button( |
114 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Save): |
111 QDialogButtonBox.StandardButton.Save |
|
112 ): |
|
113 self.__saveData() |
115 self.__saveData() |
114 |
116 |
115 def __finish(self): |
117 def __finish(self): |
116 """ |
118 """ |
117 Private slot called when the process finished or the user pressed the |
119 Private slot called when the process finished or the user pressed the |
118 button. |
120 button. |
119 """ |
121 """ |
120 if ( |
122 if ( |
121 self.proc is not None and |
123 self.proc is not None |
122 self.proc.state() != QProcess.ProcessState.NotRunning |
124 and self.proc.state() != QProcess.ProcessState.NotRunning |
123 ): |
125 ): |
124 self.proc.terminate() |
126 self.proc.terminate() |
125 QTimer.singleShot(2000, self.proc.kill) |
127 QTimer.singleShot(2000, self.proc.kill) |
126 self.proc.waitForFinished(3000) |
128 self.proc.waitForFinished(3000) |
127 |
129 |
128 self.buttonBox.button( |
130 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) |
129 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
131 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
130 self.buttonBox.button( |
132 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) |
131 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
133 self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled(True) |
132 self.buttonBox.button( |
134 |
133 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
134 self.buttonBox.button( |
|
135 QDialogButtonBox.StandardButton.Save).setEnabled(True) |
|
136 |
|
137 self.inputGroup.setEnabled(False) |
135 self.inputGroup.setEnabled(False) |
138 self.inputGroup.hide() |
136 self.inputGroup.hide() |
139 |
137 |
140 self.proc = None |
138 self.proc = None |
141 |
139 |
142 if self.argsLists: |
140 if self.argsLists: |
143 args = self.argsLists[0][:] |
141 args = self.argsLists[0][:] |
144 del self.argsLists[0] |
142 del self.argsLists[0] |
145 self.startProcess(args, self.workingDir, |
143 self.startProcess(args, self.workingDir, mergedOutput=self.mergedOutput) |
146 mergedOutput=self.mergedOutput) |
144 |
147 |
|
148 def __procFinished(self, exitCode, exitStatus): |
145 def __procFinished(self, exitCode, exitStatus): |
149 """ |
146 """ |
150 Private slot connected to the finished signal. |
147 Private slot connected to the finished signal. |
151 |
148 |
152 @param exitCode exit code of the process |
149 @param exitCode exit code of the process |
153 @type int |
150 @type int |
154 @param exitStatus exit status of the process |
151 @param exitStatus exit status of the process |
155 @type QProcess.ExitStatus |
152 @type QProcess.ExitStatus |
156 """ |
153 """ |
157 self.normal = ( |
154 self.normal = exitStatus == QProcess.ExitStatus.NormalExit and exitCode == 0 |
158 exitStatus == QProcess.ExitStatus.NormalExit and |
|
159 exitCode == 0 |
|
160 ) |
|
161 self.__finish() |
155 self.__finish() |
162 |
156 |
163 if self.normal and self.msgSuccess: |
157 if self.normal and self.msgSuccess: |
164 self.resultbox.insertPlainText(self.msgSuccess) |
158 self.resultbox.insertPlainText(self.msgSuccess) |
165 elif not self.normal and self.msgError: |
159 elif not self.normal and self.msgError: |
166 self.resultbox.insertPlainText(self.msgError) |
160 self.resultbox.insertPlainText(self.msgError) |
167 self.errorGroup.show() |
161 self.errorGroup.show() |
168 self.resultbox.ensureCursorVisible() |
162 self.resultbox.ensureCursorVisible() |
169 |
163 |
170 def startProcess(self, args, workingDir=None, showCommand=True, |
164 def startProcess(self, args, workingDir=None, showCommand=True, mergedOutput=False): |
171 mergedOutput=False): |
|
172 """ |
165 """ |
173 Public slot used to start the process. |
166 Public slot used to start the process. |
174 |
167 |
175 @param args list of arguments for the process |
168 @param args list of arguments for the process |
176 @type list of str |
169 @type list of str |
177 @param workingDir working directory for the process |
170 @param workingDir working directory for the process |
178 @type str |
171 @type str |
179 @param showCommand flag indicating to show the command executed |
172 @param showCommand flag indicating to show the command executed |
182 @type bool |
175 @type bool |
183 @return flag indicating a successful start of the process |
176 @return flag indicating a successful start of the process |
184 @rtype bool |
177 @rtype bool |
185 """ |
178 """ |
186 self.errorGroup.hide() |
179 self.errorGroup.hide() |
187 |
180 |
188 self.normal = False |
181 self.normal = False |
189 |
182 |
190 self.proc = QProcess() |
183 self.proc = QProcess() |
191 if mergedOutput: |
184 if mergedOutput: |
192 self.proc.setProcessChannelMode( |
185 self.proc.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels) |
193 QProcess.ProcessChannelMode.MergedChannels) |
186 |
194 |
|
195 if showCommand: |
187 if showCommand: |
196 self.resultbox.append(' '.join(args)) |
188 self.resultbox.append(" ".join(args)) |
197 self.resultbox.append('') |
189 self.resultbox.append("") |
198 |
190 |
199 self.proc.finished.connect(self.__procFinished) |
191 self.proc.finished.connect(self.__procFinished) |
200 self.proc.readyReadStandardOutput.connect(self.__readStdout) |
192 self.proc.readyReadStandardOutput.connect(self.__readStdout) |
201 self.proc.readyReadStandardError.connect(self.__readStderr) |
193 self.proc.readyReadStandardError.connect(self.__readStderr) |
202 |
194 |
203 if workingDir: |
195 if workingDir: |
204 self.proc.setWorkingDirectory(workingDir) |
196 self.proc.setWorkingDirectory(workingDir) |
205 self.workingDir = workingDir |
197 self.workingDir = workingDir |
206 else: |
198 else: |
207 self.workingDir = "" |
199 self.workingDir = "" |
208 |
200 |
209 prog = args[0] |
201 prog = args[0] |
210 del args[0] |
202 del args[0] |
211 self.proc.start(prog, args) |
203 self.proc.start(prog, args) |
212 procStarted = self.proc.waitForStarted() |
204 procStarted = self.proc.waitForStarted() |
213 if not procStarted: |
205 if not procStarted: |
214 self.buttonBox.setFocus() |
206 self.buttonBox.setFocus() |
215 self.inputGroup.setEnabled(False) |
207 self.inputGroup.setEnabled(False) |
216 EricMessageBox.critical( |
208 EricMessageBox.critical( |
217 self, |
209 self, |
218 self.tr('Process Generation Error'), |
210 self.tr("Process Generation Error"), |
219 self.tr( |
211 self.tr( |
220 'The process {0} could not be started. ' |
212 "The process {0} could not be started. " |
221 'Ensure, that it is in the search path.' |
213 "Ensure, that it is in the search path." |
222 ).format(prog)) |
214 ).format(prog), |
|
215 ) |
223 else: |
216 else: |
224 if self.showInput: |
217 if self.showInput: |
225 self.inputGroup.setEnabled(True) |
218 self.inputGroup.setEnabled(True) |
226 self.inputGroup.show() |
219 self.inputGroup.show() |
227 else: |
220 else: |
228 self.inputGroup.setEnabled(False) |
221 self.inputGroup.setEnabled(False) |
229 self.inputGroup.hide() |
222 self.inputGroup.hide() |
230 |
223 |
231 return procStarted |
224 return procStarted |
232 |
225 |
233 def startBatchProcesses(self, argsLists, workingDir=None, |
226 def startBatchProcesses(self, argsLists, workingDir=None, mergedOutput=False): |
234 mergedOutput=False): |
|
235 """ |
227 """ |
236 Public slot used to start a batch of processes. |
228 Public slot used to start a batch of processes. |
237 |
229 |
238 @param argsLists list of lists of arguments for the processes |
230 @param argsLists list of lists of arguments for the processes |
239 @type list of list of str |
231 @type list of list of str |
240 @param workingDir working directory for the process |
232 @param workingDir working directory for the process |
241 @type str |
233 @type str |
242 @param mergedOutput flag indicating to merge the output of the process |
234 @param mergedOutput flag indicating to merge the output of the process |
245 @rtype bool |
237 @rtype bool |
246 """ |
238 """ |
247 self.argsLists = argsLists[:] |
239 self.argsLists = argsLists[:] |
248 self.workingDir = workingDir |
240 self.workingDir = workingDir |
249 self.mergedOutput = mergedOutput |
241 self.mergedOutput = mergedOutput |
250 |
242 |
251 # start the first process |
243 # start the first process |
252 args = self.argsLists[0][:] |
244 args = self.argsLists[0][:] |
253 del self.argsLists[0] |
245 del self.argsLists[0] |
254 res = self.startProcess(args, self.workingDir, |
246 res = self.startProcess(args, self.workingDir, mergedOutput=self.mergedOutput) |
255 mergedOutput=self.mergedOutput) |
|
256 if not res: |
247 if not res: |
257 self.argsLists = [] |
248 self.argsLists = [] |
258 |
249 |
259 return res |
250 return res |
260 |
251 |
261 def normalExit(self): |
252 def normalExit(self): |
262 """ |
253 """ |
263 Public method to check for a normal process termination. |
254 Public method to check for a normal process termination. |
264 |
255 |
265 @return flag indicating normal process termination |
256 @return flag indicating normal process termination |
266 @rtype bool |
257 @rtype bool |
267 """ |
258 """ |
268 return self.normal |
259 return self.normal |
269 |
260 |
270 def normalExitWithoutErrors(self): |
261 def normalExitWithoutErrors(self): |
271 """ |
262 """ |
272 Public method to check for a normal process termination without |
263 Public method to check for a normal process termination without |
273 error messages. |
264 error messages. |
274 |
265 |
275 @return flag indicating normal process termination |
266 @return flag indicating normal process termination |
276 @rtype bool |
267 @rtype bool |
277 """ |
268 """ |
278 return self.normal and self.errors.toPlainText() == "" |
269 return self.normal and self.errors.toPlainText() == "" |
279 |
270 |
280 def __readStdout(self): |
271 def __readStdout(self): |
281 """ |
272 """ |
282 Private slot to handle the readyReadStdout signal. |
273 Private slot to handle the readyReadStdout signal. |
283 |
274 |
284 It reads the output of the process, formats it and inserts it into |
275 It reads the output of the process, formats it and inserts it into |
285 the contents pane. |
276 the contents pane. |
286 """ |
277 """ |
287 if self.proc is not None: |
278 if self.proc is not None: |
288 s = str(self.proc.readAllStandardOutput(), self.ioEncoding, |
279 s = str(self.proc.readAllStandardOutput(), self.ioEncoding, "replace") |
289 'replace') |
|
290 self.resultbox.insertPlainText(s) |
280 self.resultbox.insertPlainText(s) |
291 self.resultbox.ensureCursorVisible() |
281 self.resultbox.ensureCursorVisible() |
292 |
282 |
293 def __readStderr(self): |
283 def __readStderr(self): |
294 """ |
284 """ |
295 Private slot to handle the readyReadStderr signal. |
285 Private slot to handle the readyReadStderr signal. |
296 |
286 |
297 It reads the error output of the process and inserts it into the |
287 It reads the error output of the process and inserts it into the |
298 error pane. |
288 error pane. |
299 """ |
289 """ |
300 if self.proc is not None: |
290 if self.proc is not None: |
301 self.errorGroup.show() |
291 self.errorGroup.show() |
302 s = str(self.proc.readAllStandardError(), self.ioEncoding, |
292 s = str(self.proc.readAllStandardError(), self.ioEncoding, "replace") |
303 'replace') |
|
304 self.errors.insertPlainText(s) |
293 self.errors.insertPlainText(s) |
305 self.errors.ensureCursorVisible() |
294 self.errors.ensureCursorVisible() |
306 |
295 |
307 def __saveData(self): |
296 def __saveData(self): |
308 """ |
297 """ |
309 Private slot to save the output to a file. |
298 Private slot to save the output to a file. |
310 """ |
299 """ |
311 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
300 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
312 self, |
301 self, self.tr("Select data file"), self.workingDir, self.fileFilters, None |
313 self.tr("Select data file"), |
302 ) |
314 self.workingDir, |
303 |
315 self.fileFilters, |
|
316 None) |
|
317 |
|
318 if fname: |
304 if fname: |
319 ext = QFileInfo(fname).suffix() |
305 ext = QFileInfo(fname).suffix() |
320 if not ext: |
306 if not ext: |
321 ex = selectedFilter.split("(*")[1].split(")")[0] |
307 ex = selectedFilter.split("(*")[1].split(")")[0] |
322 if ex: |
308 if ex: |
323 fname += ex |
309 fname += ex |
324 |
310 |
325 txt = self.resultbox.toPlainText() |
311 txt = self.resultbox.toPlainText() |
326 |
312 |
327 try: |
313 try: |
328 with open(fname, "w", encoding="utf-8") as f: |
314 with open(fname, "w", encoding="utf-8") as f: |
329 f.write(txt) |
315 f.write(txt) |
330 except OSError as err: |
316 except OSError as err: |
331 EricMessageBox.critical( |
317 EricMessageBox.critical( |
332 self, |
318 self, |
333 self.tr("Error saving data"), |
319 self.tr("Error saving data"), |
334 self.tr("""<p>The data could not be written""" |
320 self.tr( |
335 """ to <b>{0}</b></p><p>Reason: {1}</p>""") |
321 """<p>The data could not be written""" |
336 .format(fname, str(err))) |
322 """ to <b>{0}</b></p><p>Reason: {1}</p>""" |
337 |
323 ).format(fname, str(err)), |
|
324 ) |
|
325 |
338 def on_passwordCheckBox_toggled(self, isOn): |
326 def on_passwordCheckBox_toggled(self, isOn): |
339 """ |
327 """ |
340 Private slot to handle the password checkbox toggled. |
328 Private slot to handle the password checkbox toggled. |
341 |
329 |
342 @param isOn flag indicating the status of the check box |
330 @param isOn flag indicating the status of the check box |
343 @type bool |
331 @type bool |
344 """ |
332 """ |
345 if isOn: |
333 if isOn: |
346 self.input.setEchoMode(QLineEdit.EchoMode.Password) |
334 self.input.setEchoMode(QLineEdit.EchoMode.Password) |
347 else: |
335 else: |
348 self.input.setEchoMode(QLineEdit.EchoMode.Normal) |
336 self.input.setEchoMode(QLineEdit.EchoMode.Normal) |
349 |
337 |
350 @pyqtSlot() |
338 @pyqtSlot() |
351 def on_sendButton_clicked(self): |
339 def on_sendButton_clicked(self): |
352 """ |
340 """ |
353 Private slot to send the input to the manage.py process. |
341 Private slot to send the input to the manage.py process. |
354 """ |
342 """ |
355 inputTxt = self.input.text() |
343 inputTxt = self.input.text() |
356 inputTxt += os.linesep |
344 inputTxt += os.linesep |
357 |
345 |
358 if self.passwordCheckBox.isChecked(): |
346 if self.passwordCheckBox.isChecked(): |
359 self.errors.insertPlainText(os.linesep) |
347 self.errors.insertPlainText(os.linesep) |
360 self.errors.ensureCursorVisible() |
348 self.errors.ensureCursorVisible() |
361 else: |
349 else: |
362 self.errors.insertPlainText(inputTxt) |
350 self.errors.insertPlainText(inputTxt) |
363 self.errors.ensureCursorVisible() |
351 self.errors.ensureCursorVisible() |
364 |
352 |
365 self.proc.write(strToQByteArray(inputTxt)) |
353 self.proc.write(strToQByteArray(inputTxt)) |
366 |
354 |
367 self.input.clear() |
355 self.input.clear() |
368 self.passwordCheckBox.setChecked(False) |
356 self.passwordCheckBox.setChecked(False) |
369 |
357 |
370 def on_input_returnPressed(self): |
358 def on_input_returnPressed(self): |
371 """ |
359 """ |
372 Private slot to handle the press of the return key in the input field. |
360 Private slot to handle the press of the return key in the input field. |
373 """ |
361 """ |
374 self.intercept = True |
362 self.intercept = True |
375 self.on_sendButton_clicked() |
363 self.on_sendButton_clicked() |
376 |
364 |
377 def keyPressEvent(self, evt): |
365 def keyPressEvent(self, evt): |
378 """ |
366 """ |
379 Protected slot to handle a key press event. |
367 Protected slot to handle a key press event. |
380 |
368 |
381 @param evt the key press event |
369 @param evt the key press event |
382 @type QKeyEvent |
370 @type QKeyEvent |
383 """ |
371 """ |
384 if self.intercept: |
372 if self.intercept: |
385 self.intercept = False |
373 self.intercept = False |
386 evt.accept() |
374 evt.accept() |
387 return |
375 return |
388 |
376 |
389 super().keyPressEvent(evt) |
377 super().keyPressEvent(evt) |