ProjectDjango/DjangoDialog.py

changeset 1
13a0cced0c6e
child 2
1e97424fda0c
equal deleted inserted replaced
0:9ea520275f3f 1:13a0cced0c6e
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog starting a process and showing its output.
8 """
9
10 from PyQt4.QtCore import pyqtSlot, QProcess, QTimer, QFileInfo
11 from PyQt4.QtGui import QDialog, QDialogButtonBox, QAbstractButton, QTextEdit
12
13 from E5Gui import E5MessageBox, E5FileDialog
14
15 from .Ui_DjangoDialog import Ui_DjangoDialog
16
17 import Preferences
18
19 from Globals import isWindowsPlatform
20
21
22 class DjangoDialog(QDialog, Ui_DjangoDialog):
23 """
24 Class implementing a dialog starting a process and showing its output.
25
26 It starts a QProcess and displays a dialog that
27 shows the output of the process. The dialog is modal,
28 which causes a synchronized execution of the process.
29 """
30 def __init__(self, text, fixed = False, linewrap = True,
31 msgSuccess = None, msgError = None,
32 saveFilters = None,
33 parent = None):
34 """
35 Constructor
36
37 @param text text to be shown by the label (string)
38 @keyparam fixed flag indicating a fixed font should be used (boolean)
39 @keyparam linewrap flag indicating to wrap long lines (boolean)
40 @keyparam msgSuccess optional string to show upon successful execution
41 (string)
42 @keyparam msgError optional string to show upon unsuccessful execution
43 (string)
44 @keyparam saveFilters filename filter string (string)
45 @keyparam parent parent widget (QWidget)
46 """
47 super().__init__(parent)
48 self.setupUi(self)
49
50 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
51 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
52 self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
53 if saveFilters is None:
54 self.buttonBox.button(QDialogButtonBox.Save).setHidden(True)
55
56 self.ioEncoding = Preferences.getSystem("IOEncoding")
57
58 self.proc = None
59 self.argsLists = []
60 self.workingDir = None
61 self.mergedOutput = False
62 self.msgSuccess = msgSuccess
63 self.msgError = msgError
64 self.fileFilters = saveFilters
65
66 self.outputGroup.setTitle(text)
67
68 if fixed:
69 if isWindowsPlatform():
70 self.resultbox.setFontFamily("Lucida Console")
71 else:
72 self.resultbox.setFontFamily("Monospace")
73
74 if not linewrap:
75 self.resultbox.setLineWrapMode(QTextEdit.NoWrap)
76
77 @pyqtSlot(QAbstractButton)
78 def on_buttonBox_clicked(self, button):
79 """
80 Private slot called by a button of the button box clicked.
81
82 @param button button that was clicked (QAbstractButton)
83 """
84 if button == self.buttonBox.button(QDialogButtonBox.Close):
85 self.close()
86 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
87 self.__finish()
88 elif button == self.buttonBox.button(QDialogButtonBox.Save):
89 self.__saveData()
90
91 def __finish(self):
92 """
93 Private slot called when the process finished or the user pressed the button.
94 """
95 if self.proc is not None and \
96 self.proc.state() != QProcess.NotRunning:
97 self.proc.terminate()
98 QTimer.singleShot(2000, self.proc.kill)
99 self.proc.waitForFinished(3000)
100
101 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
102 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
103 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
104 self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True)
105
106 self.proc = None
107
108 if self.argsLists:
109 args = self.argsLists[0][:]
110 del self.argsLists[0]
111 self.startProcess(args, self.workingDir, mergedOutput = self.mergedOutput)
112
113 def __procFinished(self, exitCode, exitStatus):
114 """
115 Private slot connected to the finished signal.
116
117 @param exitCode exit code of the process (integer)
118 @param exitStatus exit status of the process (QProcess.ExitStatus)
119 """
120 self.normal = (exitStatus == QProcess.NormalExit) and (exitCode == 0)
121 self.__finish()
122
123 if self.normal and self.msgSuccess:
124 self.resultbox.insertPlainText(self.msgSuccess)
125 elif not self.normal and self.msgError:
126 self.resultbox.insertPlainText(self.msgError)
127 self.errorGroup.show()
128 self.resultbox.ensureCursorVisible()
129
130 def startProcess(self, args, workingDir = None, showCommand = True,
131 mergedOutput = False):
132 """
133 Public slot used to start the process.
134
135 @param args list of arguments for the process (list of strings)
136 @param workingDir working directory for the process (string)
137 @param showCommand flag indicating to show the command executed (boolean)
138 @param mergedOutput flag indicating to merge the output of the process (boolean)
139 @return flag indicating a successful start of the process (boolean)
140 """
141 self.errorGroup.hide()
142
143 self.normal = False
144
145 self.proc = QProcess()
146 if mergedOutput:
147 self.proc.setProcessChannelMode(QProcess.MergedChannels)
148
149 if showCommand:
150 self.resultbox.append(' '.join(args))
151 self.resultbox.append('')
152
153 self.proc.finished.connect(self.__procFinished)
154 self.proc.readyReadStandardOutput.connect(self.__readStdout)
155 self.proc.readyReadStandardError.connect(self.__readStderr)
156
157 if workingDir:
158 self.proc.setWorkingDirectory(workingDir)
159 self.workingDir = workingDir
160 else:
161 self.workingDir = ""
162
163 prog = args[0]
164 del args[0]
165 self.proc.start(prog, args)
166 procStarted = self.proc.waitForStarted()
167 if not procStarted:
168 self.buttonBox.setFocus()
169 E5MessageBox.critical(None,
170 self.trUtf8('Process Generation Error'),
171 self.trUtf8(
172 'The process {0} could not be started. '
173 'Ensure, that it is in the search path.'
174 ).format(prog))
175 return procStarted
176
177 def startBatchProcesses(self, argsLists, workingDir = None, mergedOutput = False):
178 """
179 Public slot used to start a batch of processes.
180
181 @param argsLists list of lists of arguments for the processes
182 (list of lists of strings)
183 @param workingDir working directory for the process (string)
184 @param mergedOutput flag indicating to merge the output of the process (boolean)
185 @return flag indicating a successful start of the first process (boolean)
186 """
187 self.argsLists = argsLists[:]
188 self.workingDir = workingDir
189 self.mergedOutput = mergedOutput
190
191 # start the first process
192 args = self.argsLists[0][:]
193 del self.argsLists[0]
194 res = self.startProcess(args, self.workingDir, mergedOutput = self.mergedOutput)
195 if not res:
196 self.argsLists = []
197
198 return res
199
200 def normalExit(self):
201 """
202 Public method to check for a normal process termination.
203
204 @return flag indicating normal process termination (boolean)
205 """
206 return self.normal
207
208 def normalExitWithoutErrors(self):
209 """
210 Public method to check for a normal process termination without
211 error messages.
212
213 @return flag indicating normal process termination (boolean)
214 """
215 return self.normal and self.errors.toPlainText() == ""
216
217 def __readStdout(self):
218 """
219 Private slot to handle the readyReadStdout signal.
220
221 It reads the output of the process, formats it and inserts it into
222 the contents pane.
223 """
224 if self.proc is not None:
225 s = str(self.proc.readAllStandardOutput(), self.ioEncoding, 'replace')
226 self.resultbox.insertPlainText(s)
227 self.resultbox.ensureCursorVisible()
228
229 def __readStderr(self):
230 """
231 Private slot to handle the readyReadStderr signal.
232
233 It reads the error output of the process and inserts it into the
234 error pane.
235 """
236 if self.proc is not None:
237 self.errorGroup.show()
238 s = str(self.proc.readAllStandardError(), self.ioEncoding, 'replace')
239 self.errors.insertPlainText(s)
240 self.errors.ensureCursorVisible()
241
242 def __saveData(self):
243 """
244 Private slot to save the output to a file.
245 """
246 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
247 self,
248 self.trUtf8("Select data file"),
249 self.workingDir,
250 self.fileFilters,
251 None)
252
253 if not fname.isEmpty():
254 ext = QFileInfo(fname).suffix()
255 if ext.isEmpty():
256 ex = selectedFilter.section('(*', 1, 1).section(')', 0, 0)
257 if not ex.isEmpty():
258 fname.append(ex)
259
260 txt = self.resultbox.toPlainText()
261
262 try:
263 f = open(fname, "w", encoding="utf-8")
264 f.write(txt)
265 f.close()
266 except IOError as err:
267 E5MessageBox.critical(self,
268 self.trUtf8("Error saving data"),
269 self.trUtf8("""<p>The data could not be written"""
270 """ to <b>{0}</b></p><p>Reason: {1}</p>""")\
271 .format(fname, str(err)))

eric ide

mercurial