Plugins/VcsPlugins/vcsGit/GitDialog.py

changeset 6020
baf6da1ae288
child 6048
82ad8ec9548c
equal deleted inserted replaced
6019:58ecdaf0b789 6020:baf6da1ae288
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2017 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 __future__ import unicode_literals
11 try:
12 str = unicode
13 except NameError:
14 pass
15
16 import os
17
18 from PyQt5.QtCore import QProcess, QTimer, pyqtSlot, Qt, QCoreApplication, \
19 QProcessEnvironment
20 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QLineEdit
21
22 from E5Gui import E5MessageBox
23
24 from .Ui_GitDialog import Ui_GitDialog
25
26 from .GitUtilities import strToQByteArray
27
28 import Preferences
29
30
31 class GitDialog(QDialog, Ui_GitDialog):
32 """
33 Class implementing a dialog starting a process and showing its output.
34
35 It starts a QProcess and displays a dialog that
36 shows the output of the process. The dialog is modal,
37 which causes a synchronized execution of the process.
38 """
39 def __init__(self, text, git=None, parent=None):
40 """
41 Constructor
42
43 @param text text to be shown by the label (string)
44 @param git reference to the Git interface object (Git)
45 @param parent parent widget (QWidget)
46 """
47 super(GitDialog, self).__init__(parent)
48 self.setupUi(self)
49
50 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
51 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
52
53 self.process = None
54 self.username = ''
55 self.password = ''
56 self.vcs = git
57
58 self.outputGroup.setTitle(text)
59
60 self.show()
61 QCoreApplication.processEvents()
62
63 def __finish(self):
64 """
65 Private slot called when the process finished or the user pressed
66 the button.
67 """
68 if self.process is not None and \
69 self.process.state() != QProcess.NotRunning:
70 self.process.terminate()
71 QTimer.singleShot(2000, self.process.kill)
72 self.process.waitForFinished(3000)
73
74 self.inputGroup.setEnabled(False)
75 self.inputGroup.hide()
76
77 self.process = None
78
79 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
80 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
81 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
82 self.buttonBox.button(QDialogButtonBox.Close).setFocus(
83 Qt.OtherFocusReason)
84
85 if self.normal and self.errors.toPlainText():
86 self.errorGroup.setTitle(self.tr("Additional Output"))
87
88 if Preferences.getVCS("AutoClose") and \
89 self.normal and \
90 self.errors.toPlainText() == "":
91 self.accept()
92
93 def on_buttonBox_clicked(self, button):
94 """
95 Private slot called by a button of the button box clicked.
96
97 @param button button that was clicked (QAbstractButton)
98 """
99 if button == self.buttonBox.button(QDialogButtonBox.Close):
100 self.close()
101 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
102 self.statusLabel.setText(self.tr("Process canceled."))
103 self.__finish()
104
105 def __procFinished(self, exitCode, exitStatus):
106 """
107 Private slot connected to the finished signal.
108
109 @param exitCode exit code of the process (integer)
110 @param exitStatus exit status of the process (QProcess.ExitStatus)
111 """
112 self.normal = (exitStatus == QProcess.NormalExit) and (exitCode == 0)
113 if self.normal:
114 self.statusLabel.setText(self.tr("Process finished successfully."))
115 elif exitStatus == QProcess.CrashExit:
116 self.statusLabel.setText(self.tr("Process crashed."))
117 else:
118 self.statusLabel.setText(
119 self.tr("Proces finished with exit code {0}").format(exitCode))
120 self.__finish()
121
122 def startProcess(self, args, workingDir=None, showArgs=True,
123 environment=None):
124 """
125 Public slot used to start the process.
126
127 @param args list of arguments for the process (list of strings)
128 @keyparam workingDir working directory for the process (string)
129 @keyparam showArgs flag indicating to show the arguments (boolean)
130 @keyparam environment dictionary of environment settings to add
131 or change for the git process (dict of string and string)
132 @return flag indicating a successful start of the process (boolean)
133 """
134 self.errorGroup.hide()
135 self.normal = False
136 self.intercept = False
137
138 if environment is None:
139 environment = {}
140
141 self.__hasAddOrDelete = False
142 if args[0] in ["checkout", "fetch", "pull", "rebase", "reset",
143 "merge", "cherry-pick", "stash"]:
144 self.__updateCommand = True
145 else:
146 self.__updateCommand = False
147
148 if showArgs:
149 self.resultbox.append(' '.join(args))
150 self.resultbox.append('')
151
152 self.process = QProcess()
153 if environment:
154 env = QProcessEnvironment.systemEnvironment()
155 for key, value in environment.items():
156 env.insert(key, value)
157 self.process.setProcessEnvironment(env)
158
159 self.process.finished.connect(self.__procFinished)
160 self.process.readyReadStandardOutput.connect(self.__readStdout)
161 self.process.readyReadStandardError.connect(self.__readStderr)
162
163 if workingDir:
164 self.process.setWorkingDirectory(workingDir)
165 self.process.start('git', args)
166 procStarted = self.process.waitForStarted(5000)
167 if not procStarted:
168 self.buttonBox.setFocus()
169 self.inputGroup.setEnabled(False)
170 E5MessageBox.critical(
171 self,
172 self.tr('Process Generation Error'),
173 self.tr(
174 'The process {0} could not be started. '
175 'Ensure, that it is in the search path.'
176 ).format('git'))
177 else:
178 self.inputGroup.setEnabled(True)
179 self.inputGroup.show()
180 return procStarted
181
182 def normalExit(self):
183 """
184 Public method to check for a normal process termination.
185
186 @return flag indicating normal process termination (boolean)
187 """
188 return self.normal
189
190 def normalExitWithoutErrors(self):
191 """
192 Public method to check for a normal process termination without
193 error messages.
194
195 @return flag indicating normal process termination (boolean)
196 """
197 return self.normal and self.errors.toPlainText() == ""
198
199 def __readStdout(self):
200 """
201 Private slot to handle the readyReadStandardOutput signal.
202
203 It reads the output of the process, formats it and inserts it into
204 the contents pane.
205 """
206 if self.process is not None:
207 s = str(self.process.readAllStandardOutput(),
208 Preferences.getSystem("IOEncoding"),
209 'replace')
210 self.__showOutput(s)
211
212 def __showOutput(self, out):
213 """
214 Private slot to show some output.
215
216 @param out output to be shown (string)
217 """
218 self.resultbox.insertPlainText(out)
219 self.resultbox.ensureCursorVisible()
220
221 # check for a changed project file
222 if self.__updateCommand:
223 for line in out.splitlines():
224 if '.e4p' in line or '.e6p' in line:
225 self.__hasAddOrDelete = True
226 break
227
228 QCoreApplication.processEvents()
229
230 def __readStderr(self):
231 """
232 Private slot to handle the readyReadStandardError signal.
233
234 It reads the error output of the process and inserts it into the
235 error pane.
236 """
237 if self.process is not None:
238 s = str(self.process.readAllStandardError(),
239 Preferences.getSystem("IOEncoding"),
240 'replace')
241 self.__showError(s)
242
243 def __showError(self, out):
244 """
245 Private slot to show some error.
246
247 @param out error to be shown (string)
248 """
249 self.errorGroup.show()
250 self.errors.insertPlainText(out)
251 self.errors.ensureCursorVisible()
252
253 QCoreApplication.processEvents()
254
255 def on_passwordCheckBox_toggled(self, isOn):
256 """
257 Private slot to handle the password checkbox toggled.
258
259 @param isOn flag indicating the status of the check box (boolean)
260 """
261 if isOn:
262 self.input.setEchoMode(QLineEdit.Password)
263 else:
264 self.input.setEchoMode(QLineEdit.Normal)
265
266 @pyqtSlot()
267 def on_sendButton_clicked(self):
268 """
269 Private slot to send the input to the git process.
270 """
271 inputTxt = self.input.text()
272 inputTxt += os.linesep
273
274 if self.passwordCheckBox.isChecked():
275 self.errors.insertPlainText(os.linesep)
276 self.errors.ensureCursorVisible()
277 else:
278 self.errors.insertPlainText(inputTxt)
279 self.errors.ensureCursorVisible()
280
281 self.process.write(strToQByteArray(inputTxt))
282
283 self.passwordCheckBox.setChecked(False)
284 self.input.clear()
285
286 def on_input_returnPressed(self):
287 """
288 Private slot to handle the press of the return key in the input field.
289 """
290 self.intercept = True
291 self.on_sendButton_clicked()
292
293 def keyPressEvent(self, evt):
294 """
295 Protected slot to handle a key press event.
296
297 @param evt the key press event (QKeyEvent)
298 """
299 if self.intercept:
300 self.intercept = False
301 evt.accept()
302 return
303 super(GitDialog, self).keyPressEvent(evt)
304
305 def hasAddOrDelete(self):
306 """
307 Public method to check, if the last action contained an add or delete.
308
309 @return flag indicating the presence of an add or delete (boolean)
310 """
311 return self.__hasAddOrDelete

eric ide

mercurial