eric6/Plugins/VcsPlugins/vcsMercurial/HgDialog.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) 2010 - 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_HgDialog import Ui_HgDialog
25
26 import Preferences
27 import Utilities
28 from Globals import strToQByteArray
29
30
31 class HgDialog(QDialog, Ui_HgDialog):
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, hg=None, useClient=True, parent=None):
40 """
41 Constructor
42
43 @param text text to be shown by the label (string)
44 @param hg reference to the Mercurial interface object (Hg)
45 @param useClient flag indicating to use the command server client
46 if possible (boolean)
47 @param parent parent widget (QWidget)
48 """
49 super(HgDialog, self).__init__(parent)
50 self.setupUi(self)
51 self.setWindowFlags(Qt.Window)
52
53 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
54 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
55
56 self.process = None
57 self.username = ''
58 self.password = ''
59 if useClient:
60 self.__hgClient = hg.getClient()
61 else:
62 self.__hgClient = None
63 self.vcs = hg
64
65 self.outputGroup.setTitle(text)
66
67 self.show()
68 QCoreApplication.processEvents()
69
70 def __finish(self):
71 """
72 Private slot called when the process finished or the user pressed
73 the button.
74 """
75 if self.process is not None and \
76 self.process.state() != QProcess.NotRunning:
77 self.process.terminate()
78 QTimer.singleShot(2000, self.process.kill)
79 self.process.waitForFinished(3000)
80
81 self.inputGroup.setEnabled(False)
82 self.inputGroup.hide()
83
84 self.process = None
85
86 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
87 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
88 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
89 self.buttonBox.button(QDialogButtonBox.Close).setFocus(
90 Qt.OtherFocusReason)
91
92 if Preferences.getVCS("AutoClose") and \
93 self.normal and \
94 self.errors.toPlainText() == "":
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(QDialogButtonBox.Close):
104 self.close()
105 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
106 if self.__hgClient:
107 self.__hgClient.cancel()
108 else:
109 self.__finish()
110
111 def __procFinished(self, exitCode, exitStatus):
112 """
113 Private slot connected to the finished signal.
114
115 @param exitCode exit code of the process (integer)
116 @param exitStatus exit status of the process (QProcess.ExitStatus)
117 """
118 self.normal = (exitStatus == QProcess.NormalExit) and (exitCode == 0)
119 self.__finish()
120
121 def startProcess(self, args, workingDir=None, showArgs=True,
122 environment=None):
123 """
124 Public slot used to start the process.
125
126 @param args list of arguments for the process (list of strings)
127 @keyparam workingDir working directory for the process (string)
128 @keyparam showArgs flag indicating to show the arguments (boolean)
129 @keyparam environment dictionary of environment settings to add
130 or change for the git process (dict of string and string)
131 @return flag indicating a successful start of the process
132 """
133 self.errorGroup.hide()
134 self.normal = False
135 self.intercept = False
136
137 self.__hasAddOrDelete = False
138 if args[0] in ["fetch", "qpush", "qpop", "qgoto", "rebase",
139 "update", "import", "revert", "graft", "shelve",
140 "unshelve", "strip", "histedit"] or \
141 (args[0] in ["pull", "unbundle"] and
142 ("--update" in args[1:] or "--rebase" in args[1:])):
143 self.__updateCommand = True
144 else:
145 self.__updateCommand = False
146
147 if showArgs:
148 self.resultbox.append(' '.join(args))
149 self.resultbox.append('')
150
151 if self.__hgClient is None:
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('hg', 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('hg'))
177 else:
178 self.inputGroup.setEnabled(True)
179 self.inputGroup.show()
180 return procStarted
181 else:
182 self.inputGroup.setEnabled(False)
183 self.inputGroup.hide()
184
185 out, err = self.__hgClient.runcommand(
186 args, output=self.__showOutput, error=self.__showError)
187
188 if err:
189 self.__showError(err)
190 if out:
191 self.__showOutput(out)
192
193 self.normal = True
194
195 self.__finish()
196
197 return True
198
199 def normalExit(self):
200 """
201 Public method to check for a normal process termination.
202
203 @return flag indicating normal process termination (boolean)
204 """
205 return self.normal
206
207 def normalExitWithoutErrors(self):
208 """
209 Public method to check for a normal process termination without
210 error messages.
211
212 @return flag indicating normal process termination (boolean)
213 """
214 return self.normal and self.errors.toPlainText() == ""
215
216 def __readStdout(self):
217 """
218 Private slot to handle the readyReadStandardOutput signal.
219
220 It reads the output of the process, formats it and inserts it into
221 the contents pane.
222 """
223 if self.process is not None:
224 s = str(self.process.readAllStandardOutput(),
225 self.vcs.getEncoding(),
226 'replace')
227 self.__showOutput(s)
228
229 def __showOutput(self, out):
230 """
231 Private slot to show some output.
232
233 @param out output to be shown (string)
234 """
235 self.resultbox.insertPlainText(Utilities.filterAnsiSequences(out))
236 self.resultbox.ensureCursorVisible()
237
238 # check for a changed project file
239 if self.__updateCommand:
240 for line in out.splitlines():
241 if '.e4p' in line:
242 self.__hasAddOrDelete = True
243 break
244
245 QCoreApplication.processEvents()
246
247 def __readStderr(self):
248 """
249 Private slot to handle the readyReadStandardError signal.
250
251 It reads the error output of the process and inserts it into the
252 error pane.
253 """
254 if self.process is not None:
255 s = str(self.process.readAllStandardError(),
256 self.vcs.getEncoding(),
257 'replace')
258 self.__showError(s)
259
260 def __showError(self, out):
261 """
262 Private slot to show some error.
263
264 @param out error to be shown (string)
265 """
266 self.errorGroup.show()
267 self.errors.insertPlainText(Utilities.filterAnsiSequences(out))
268 self.errors.ensureCursorVisible()
269
270 QCoreApplication.processEvents()
271
272 def on_passwordCheckBox_toggled(self, isOn):
273 """
274 Private slot to handle the password checkbox toggled.
275
276 @param isOn flag indicating the status of the check box (boolean)
277 """
278 if isOn:
279 self.input.setEchoMode(QLineEdit.Password)
280 else:
281 self.input.setEchoMode(QLineEdit.Normal)
282
283 @pyqtSlot()
284 def on_sendButton_clicked(self):
285 """
286 Private slot to send the input to the Mercurial process.
287 """
288 inputTxt = self.input.text()
289 inputTxt += os.linesep
290
291 if self.passwordCheckBox.isChecked():
292 self.errors.insertPlainText(os.linesep)
293 self.errors.ensureCursorVisible()
294 else:
295 self.errors.insertPlainText(inputTxt)
296 self.errors.ensureCursorVisible()
297
298 self.process.write(strToQByteArray(inputTxt))
299
300 self.passwordCheckBox.setChecked(False)
301 self.input.clear()
302
303 def on_input_returnPressed(self):
304 """
305 Private slot to handle the press of the return key in the input field.
306 """
307 self.intercept = True
308 self.on_sendButton_clicked()
309
310 def keyPressEvent(self, evt):
311 """
312 Protected slot to handle a key press event.
313
314 @param evt the key press event (QKeyEvent)
315 """
316 if self.intercept:
317 self.intercept = False
318 evt.accept()
319 return
320 super(HgDialog, self).keyPressEvent(evt)
321
322 def hasAddOrDelete(self):
323 """
324 Public method to check, if the last action contained an add or delete.
325
326 @return flag indicating the presence of an add or delete (boolean)
327 """
328 return self.__hasAddOrDelete

eric ide

mercurial