eric7/Plugins/VcsPlugins/vcsGit/GitDialog.py

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

eric ide

mercurial