eric6/Plugins/VcsPlugins/vcsMercurial/HgAnnotateDialog.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 to show the output of the hg annotate command.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode
13 except NameError:
14 pass
15
16 import os
17 import re
18
19 from PyQt5.QtCore import pyqtSlot, QProcess, QTimer, Qt, QCoreApplication
20 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHeaderView, \
21 QLineEdit, QTreeWidgetItem
22
23 from E5Gui import E5MessageBox
24
25 from .Ui_HgAnnotateDialog import Ui_HgAnnotateDialog
26
27 import Preferences
28 from Globals import strToQByteArray
29
30
31 class HgAnnotateDialog(QDialog, Ui_HgAnnotateDialog):
32 """
33 Class implementing a dialog to show the output of the hg annotate command.
34 """
35 def __init__(self, vcs, parent=None):
36 """
37 Constructor
38
39 @param vcs reference to the vcs object
40 @param parent parent widget (QWidget)
41 """
42 super(HgAnnotateDialog, self).__init__(parent)
43 self.setupUi(self)
44 self.setWindowFlags(Qt.Window)
45
46 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
47 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
48
49 self.vcs = vcs
50 self.__hgClient = vcs.getClient()
51
52 self.__annotateRe = re.compile(
53 r"""(.+)\s+(\d+)\s+([0-9a-fA-F]+)\s+([0-9-]+)\s+(.+)""")
54
55 self.annotateList.headerItem().setText(
56 self.annotateList.columnCount(), "")
57 font = Preferences.getEditorOtherFonts("MonospacedFont")
58 self.annotateList.setFont(font)
59
60 if self.__hgClient:
61 self.process = None
62 else:
63 self.process = QProcess()
64 self.process.finished.connect(self.__procFinished)
65 self.process.readyReadStandardOutput.connect(self.__readStdout)
66 self.process.readyReadStandardError.connect(self.__readStderr)
67
68 self.show()
69 QCoreApplication.processEvents()
70
71 def closeEvent(self, e):
72 """
73 Protected slot implementing a close event handler.
74
75 @param e close event (QCloseEvent)
76 """
77 if self.__hgClient:
78 if self.__hgClient.isExecuting():
79 self.__hgClient.cancel()
80 else:
81 if self.process is not None and \
82 self.process.state() != QProcess.NotRunning:
83 self.process.terminate()
84 QTimer.singleShot(2000, self.process.kill)
85 self.process.waitForFinished(3000)
86
87 e.accept()
88
89 def start(self, fn):
90 """
91 Public slot to start the annotate command.
92
93 @param fn filename to show the annotation for (string)
94 """
95 self.annotateList.clear()
96 self.errorGroup.hide()
97 self.intercept = False
98 self.activateWindow()
99 self.lineno = 1
100
101 dname, fname = self.vcs.splitPath(fn)
102
103 # find the root of the repo
104 repodir = dname
105 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
106 repodir = os.path.dirname(repodir)
107 if os.path.splitdrive(repodir)[1] == os.sep:
108 return
109
110 args = self.vcs.initCommand("annotate")
111 args.append('--follow')
112 args.append('--user')
113 args.append('--date')
114 args.append('--number')
115 args.append('--changeset')
116 args.append('--quiet')
117 args.append(fn)
118
119 if self.__hgClient:
120 self.inputGroup.setEnabled(False)
121 self.inputGroup.hide()
122
123 out, err = self.__hgClient.runcommand(args)
124 if err:
125 self.__showError(err)
126 if out:
127 for line in out.splitlines():
128 self.__processOutputLine(line)
129 if self.__hgClient.wasCanceled():
130 break
131 self.__finish()
132 else:
133 self.process.kill()
134 self.process.setWorkingDirectory(repodir)
135
136 self.process.start('hg', args)
137 procStarted = self.process.waitForStarted(5000)
138 if not procStarted:
139 self.inputGroup.setEnabled(False)
140 self.inputGroup.hide()
141 E5MessageBox.critical(
142 self,
143 self.tr('Process Generation Error'),
144 self.tr(
145 'The process {0} could not be started. '
146 'Ensure, that it is in the search path.'
147 ).format('hg'))
148 else:
149 self.inputGroup.setEnabled(True)
150 self.inputGroup.show()
151
152 def __finish(self):
153 """
154 Private slot called when the process finished or the user pressed
155 the button.
156 """
157 if self.process is not None and \
158 self.process.state() != QProcess.NotRunning:
159 self.process.terminate()
160 QTimer.singleShot(2000, self.process.kill)
161 self.process.waitForFinished(3000)
162
163 self.inputGroup.setEnabled(False)
164 self.inputGroup.hide()
165
166 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
167 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
168 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
169 self.buttonBox.button(QDialogButtonBox.Close).setFocus(
170 Qt.OtherFocusReason)
171
172 self.__resizeColumns()
173
174 def on_buttonBox_clicked(self, button):
175 """
176 Private slot called by a button of the button box clicked.
177
178 @param button button that was clicked (QAbstractButton)
179 """
180 if button == self.buttonBox.button(QDialogButtonBox.Close):
181 self.close()
182 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
183 if self.__hgClient:
184 self.__hgClient.cancel()
185 else:
186 self.__finish()
187
188 def __procFinished(self, exitCode, exitStatus):
189 """
190 Private slot connected to the finished signal.
191
192 @param exitCode exit code of the process (integer)
193 @param exitStatus exit status of the process (QProcess.ExitStatus)
194 """
195 self.__finish()
196
197 def __resizeColumns(self):
198 """
199 Private method to resize the list columns.
200 """
201 self.annotateList.header().resizeSections(QHeaderView.ResizeToContents)
202
203 def __generateItem(self, revision, changeset, author, date, text):
204 """
205 Private method to generate an annotate item in the annotation list.
206
207 @param revision revision string (string)
208 @param changeset changeset string (string)
209 @param author author of the change (string)
210 @param date date of the change (string)
211 @param text text of the change (string)
212 """
213 itm = QTreeWidgetItem(
214 self.annotateList,
215 [revision, changeset, author, date, "{0:d}".format(self.lineno),
216 text])
217 self.lineno += 1
218 itm.setTextAlignment(0, Qt.AlignRight)
219 itm.setTextAlignment(4, Qt.AlignRight)
220
221 def __readStdout(self):
222 """
223 Private slot to handle the readyReadStdout signal.
224
225 It reads the output of the process, formats it and inserts it into
226 the annotation list.
227 """
228 self.process.setReadChannel(QProcess.StandardOutput)
229
230 while self.process.canReadLine():
231 s = str(self.process.readLine(), self.vcs.getEncoding(),
232 'replace').strip()
233 self.__processOutputLine(s)
234
235 def __processOutputLine(self, line):
236 """
237 Private method to process the lines of output.
238
239 @param line output line to be processed (string)
240 """
241 try:
242 info, text = line.split(": ", 1)
243 except ValueError:
244 info = line[:-2]
245 text = ""
246 match = self.__annotateRe.match(info)
247 author, rev, changeset, date, file = match.groups()
248 self.__generateItem(rev.strip(), changeset.strip(), author.strip(),
249 date.strip(), text)
250
251 def __readStderr(self):
252 """
253 Private slot to handle the readyReadStderr signal.
254
255 It reads the error output of the process and inserts it into the
256 error pane.
257 """
258 if self.process is not None:
259 s = str(self.process.readAllStandardError(),
260 self.vcs.getEncoding(), 'replace')
261 self.__showError(s)
262
263 def __showError(self, out):
264 """
265 Private slot to show some error.
266
267 @param out error to be shown (string)
268 """
269 self.errorGroup.show()
270 self.errors.insertPlainText(out)
271 self.errors.ensureCursorVisible()
272
273 def on_passwordCheckBox_toggled(self, isOn):
274 """
275 Private slot to handle the password checkbox toggled.
276
277 @param isOn flag indicating the status of the check box (boolean)
278 """
279 if isOn:
280 self.input.setEchoMode(QLineEdit.Password)
281 else:
282 self.input.setEchoMode(QLineEdit.Normal)
283
284 @pyqtSlot()
285 def on_sendButton_clicked(self):
286 """
287 Private slot to send the input to the hg process.
288 """
289 inputTxt = self.input.text()
290 inputTxt += os.linesep
291
292 if self.passwordCheckBox.isChecked():
293 self.errors.insertPlainText(os.linesep)
294 self.errors.ensureCursorVisible()
295 else:
296 self.errors.insertPlainText(inputTxt)
297 self.errors.ensureCursorVisible()
298
299 self.process.write(strToQByteArray(inputTxt))
300
301 self.passwordCheckBox.setChecked(False)
302 self.input.clear()
303
304 def on_input_returnPressed(self):
305 """
306 Private slot to handle the press of the return key in the input field.
307 """
308 self.intercept = True
309 self.on_sendButton_clicked()
310
311 def keyPressEvent(self, evt):
312 """
313 Protected slot to handle a key press event.
314
315 @param evt the key press event (QKeyEvent)
316 """
317 if self.intercept:
318 self.intercept = False
319 evt.accept()
320 return
321 super(HgAnnotateDialog, self).keyPressEvent(evt)

eric ide

mercurial