ProjectDjango/DjangoDialog.py

branch
eric7
changeset 180
64339135bd61
parent 175
30cb5e553e7e
child 181
2f5c3487139c
equal deleted inserted replaced
179:8413c2429808 180:64339135bd61
9 9
10 import os 10 import os
11 11
12 from PyQt6.QtCore import pyqtSlot, QProcess, QTimer, QFileInfo 12 from PyQt6.QtCore import pyqtSlot, QProcess, QTimer, QFileInfo
13 from PyQt6.QtWidgets import ( 13 from PyQt6.QtWidgets import (
14 QDialog, QDialogButtonBox, QAbstractButton, QTextEdit, QLineEdit 14 QDialog,
15 QDialogButtonBox,
16 QAbstractButton,
17 QTextEdit,
18 QLineEdit,
15 ) 19 )
16 20
17 from EricWidgets import EricMessageBox, EricFileDialog 21 from EricWidgets import EricMessageBox, EricFileDialog
18 22
19 from .Ui_DjangoDialog import Ui_DjangoDialog 23 from .Ui_DjangoDialog import Ui_DjangoDialog
24 28
25 29
26 class DjangoDialog(QDialog, Ui_DjangoDialog): 30 class DjangoDialog(QDialog, Ui_DjangoDialog):
27 """ 31 """
28 Class implementing a dialog starting a process and showing its output. 32 Class implementing a dialog starting a process and showing its output.
29 33
30 It starts a QProcess and displays a dialog that 34 It starts a QProcess and displays a dialog that
31 shows the output of the process. The dialog is modal, 35 shows the output of the process. The dialog is modal,
32 which causes a synchronized execution of the process. 36 which causes a synchronized execution of the process.
33 """ 37 """
34 def __init__(self, text, fixed=False, linewrap=True, 38
35 msgSuccess=None, msgError=None, 39 def __init__(
36 saveFilters=None, showInput=False, 40 self,
37 parent=None): 41 text,
42 fixed=False,
43 linewrap=True,
44 msgSuccess=None,
45 msgError=None,
46 saveFilters=None,
47 showInput=False,
48 parent=None,
49 ):
38 """ 50 """
39 Constructor 51 Constructor
40 52
41 @param text text to be shown by the label 53 @param text text to be shown by the label
42 @type str 54 @type str
43 @param fixed flag indicating a fixed font should be used 55 @param fixed flag indicating a fixed font should be used
44 @type bool 56 @type bool
45 @param linewrap flag indicating to wrap long lines 57 @param linewrap flag indicating to wrap long lines
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)

eric ide

mercurial