eric6/Plugins/VcsPlugins/vcsGit/GitDialog.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7192
a22eee00b052
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2019 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("Process finished with exit code {0}")
120 .format(exitCode))
121 self.__finish()
122
123 def startProcess(self, args, workingDir=None, showArgs=True,
124 environment=None):
125 """
126 Public slot used to start the process.
127
128 @param args list of arguments for the process (list of strings)
129 @keyparam workingDir working directory for the process (string)
130 @keyparam showArgs flag indicating to show the arguments (boolean)
131 @keyparam environment dictionary of environment settings to add
132 or change for the git process (dict of string and string)
133 @return flag indicating a successful start of the process (boolean)
134 """
135 self.errorGroup.hide()
136 self.normal = False
137 self.intercept = False
138
139 if environment is None:
140 environment = {}
141
142 self.__hasAddOrDelete = False
143 if args[0] in ["checkout", "fetch", "pull", "rebase", "reset",
144 "merge", "cherry-pick", "stash"]:
145 self.__updateCommand = True
146 else:
147 self.__updateCommand = False
148
149 if showArgs:
150 self.resultbox.append(' '.join(args))
151 self.resultbox.append('')
152
153 self.process = QProcess()
154 if environment:
155 env = QProcessEnvironment.systemEnvironment()
156 for key, value in environment.items():
157 env.insert(key, value)
158 self.process.setProcessEnvironment(env)
159
160 self.process.finished.connect(self.__procFinished)
161 self.process.readyReadStandardOutput.connect(self.__readStdout)
162 self.process.readyReadStandardError.connect(self.__readStderr)
163
164 if workingDir:
165 self.process.setWorkingDirectory(workingDir)
166 self.process.start('git', args)
167 procStarted = self.process.waitForStarted(5000)
168 if not procStarted:
169 self.buttonBox.setFocus()
170 self.inputGroup.setEnabled(False)
171 E5MessageBox.critical(
172 self,
173 self.tr('Process Generation Error'),
174 self.tr(
175 'The process {0} could not be started. '
176 'Ensure, that it is in the search path.'
177 ).format('git'))
178 else:
179 self.inputGroup.setEnabled(True)
180 self.inputGroup.show()
181 return procStarted
182
183 def normalExit(self):
184 """
185 Public method to check for a normal process termination.
186
187 @return flag indicating normal process termination (boolean)
188 """
189 return self.normal
190
191 def normalExitWithoutErrors(self):
192 """
193 Public method to check for a normal process termination without
194 error messages.
195
196 @return flag indicating normal process termination (boolean)
197 """
198 return self.normal and self.errors.toPlainText() == ""
199
200 def __readStdout(self):
201 """
202 Private slot to handle the readyReadStandardOutput signal.
203
204 It reads the output of the process, formats it and inserts it into
205 the contents pane.
206 """
207 if self.process is not None:
208 s = str(self.process.readAllStandardOutput(),
209 Preferences.getSystem("IOEncoding"),
210 'replace')
211 self.__showOutput(s)
212
213 def __showOutput(self, out):
214 """
215 Private slot to show some output.
216
217 @param out output to be shown (string)
218 """
219 self.resultbox.insertPlainText(out)
220 self.resultbox.ensureCursorVisible()
221
222 # check for a changed project file
223 if self.__updateCommand:
224 for line in out.splitlines():
225 if '.e4p' in line or '.e6p' in line:
226 self.__hasAddOrDelete = True
227 break
228
229 QCoreApplication.processEvents()
230
231 def __readStderr(self):
232 """
233 Private slot to handle the readyReadStandardError signal.
234
235 It reads the error output of the process and inserts it into the
236 error pane.
237 """
238 if self.process is not None:
239 s = str(self.process.readAllStandardError(),
240 Preferences.getSystem("IOEncoding"),
241 'replace')
242 self.__showError(s)
243
244 def __showError(self, out):
245 """
246 Private slot to show some error.
247
248 @param out error to be shown (string)
249 """
250 self.errorGroup.show()
251 self.errors.insertPlainText(out)
252 self.errors.ensureCursorVisible()
253
254 QCoreApplication.processEvents()
255
256 def on_passwordCheckBox_toggled(self, isOn):
257 """
258 Private slot to handle the password checkbox toggled.
259
260 @param isOn flag indicating the status of the check box (boolean)
261 """
262 if isOn:
263 self.input.setEchoMode(QLineEdit.Password)
264 else:
265 self.input.setEchoMode(QLineEdit.Normal)
266
267 @pyqtSlot()
268 def on_sendButton_clicked(self):
269 """
270 Private slot to send the input to the git process.
271 """
272 inputTxt = self.input.text()
273 inputTxt += os.linesep
274
275 if self.passwordCheckBox.isChecked():
276 self.errors.insertPlainText(os.linesep)
277 self.errors.ensureCursorVisible()
278 else:
279 self.errors.insertPlainText(inputTxt)
280 self.errors.ensureCursorVisible()
281
282 self.process.write(strToQByteArray(inputTxt))
283
284 self.passwordCheckBox.setChecked(False)
285 self.input.clear()
286
287 def on_input_returnPressed(self):
288 """
289 Private slot to handle the press of the return key in the input field.
290 """
291 self.intercept = True
292 self.on_sendButton_clicked()
293
294 def keyPressEvent(self, evt):
295 """
296 Protected slot to handle a key press event.
297
298 @param evt the key press event (QKeyEvent)
299 """
300 if self.intercept:
301 self.intercept = False
302 evt.accept()
303 return
304 super(GitDialog, self).keyPressEvent(evt)
305
306 def hasAddOrDelete(self):
307 """
308 Public method to check, if the last action contained an add or delete.
309
310 @return flag indicating the presence of an add or delete (boolean)
311 """
312 return self.__hasAddOrDelete

eric ide

mercurial