src/eric7/Plugins/VcsPlugins/vcsGit/GitDialog.py

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

eric ide

mercurial