eric6/Plugins/VcsPlugins/vcsMercurial/GpgExtension/HgGpgSignaturesDialog.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) 2011 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog showing signed changesets.
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 pyqtSlot, QProcess, QTimer, Qt, QRegExp, \
19 QCoreApplication
20 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHeaderView, \
21 QTreeWidgetItem, QLineEdit
22
23 from E5Gui import E5MessageBox
24
25 from .Ui_HgGpgSignaturesDialog import Ui_HgGpgSignaturesDialog
26
27 from Globals import strToQByteArray
28
29
30 class HgGpgSignaturesDialog(QDialog, Ui_HgGpgSignaturesDialog):
31 """
32 Class implementing a dialog showing signed changesets.
33 """
34 def __init__(self, vcs, parent=None):
35 """
36 Constructor
37
38 @param vcs reference to the vcs object
39 @param parent reference to the parent widget (QWidget)
40 """
41 super(HgGpgSignaturesDialog, self).__init__(parent)
42 self.setupUi(self)
43 self.setWindowFlags(Qt.Window)
44
45 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
46 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
47
48 self.process = QProcess()
49 self.vcs = vcs
50 self.__hgClient = vcs.getClient()
51
52 self.process.finished.connect(self.__procFinished)
53 self.process.readyReadStandardOutput.connect(self.__readStdout)
54 self.process.readyReadStandardError.connect(self.__readStderr)
55
56 self.show()
57 QCoreApplication.processEvents()
58
59 def closeEvent(self, e):
60 """
61 Protected slot implementing a close event handler.
62
63 @param e close event (QCloseEvent)
64 """
65 if self.__hgClient:
66 if self.__hgClient.isExecuting():
67 self.__hgClient.cancel()
68 else:
69 if self.process is not None and \
70 self.process.state() != QProcess.NotRunning:
71 self.process.terminate()
72 QTimer.singleShot(2000, self.process.kill)
73 self.process.waitForFinished(3000)
74
75 e.accept()
76
77 def start(self, path):
78 """
79 Public slot to start the list command.
80
81 @param path name of directory (string)
82 """
83 self.errorGroup.hide()
84
85 self.intercept = False
86 self.activateWindow()
87
88 self.__path = path
89 dname, fname = self.vcs.splitPath(path)
90
91 # find the root of the repo
92 repodir = dname
93 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
94 repodir = os.path.dirname(repodir)
95 if os.path.splitdrive(repodir)[1] == os.sep:
96 return
97
98 args = self.vcs.initCommand("sigs")
99
100 if self.__hgClient:
101 self.inputGroup.setEnabled(False)
102 self.inputGroup.hide()
103
104 out, err = self.__hgClient.runcommand(args)
105 if err:
106 self.__showError(err)
107 if out:
108 for line in out.splitlines():
109 self.__processOutputLine(line)
110 if self.__hgClient.wasCanceled():
111 break
112 self.__finish()
113 else:
114 self.process.kill()
115 self.process.setWorkingDirectory(repodir)
116
117 self.process.start('hg', args)
118 procStarted = self.process.waitForStarted(5000)
119 if not procStarted:
120 self.inputGroup.setEnabled(False)
121 self.inputGroup.hide()
122 E5MessageBox.critical(
123 self,
124 self.tr('Process Generation Error'),
125 self.tr(
126 'The process {0} could not be started. '
127 'Ensure, that it is in the search path.'
128 ).format('hg'))
129 else:
130 self.inputGroup.setEnabled(True)
131 self.inputGroup.show()
132
133 def __finish(self):
134 """
135 Private slot called when the process finished or the user pressed
136 the button.
137 """
138 if self.process is not None and \
139 self.process.state() != QProcess.NotRunning:
140 self.process.terminate()
141 QTimer.singleShot(2000, self.process.kill)
142 self.process.waitForFinished(3000)
143
144 self.inputGroup.setEnabled(False)
145 self.inputGroup.hide()
146
147 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
148 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
149 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
150 self.buttonBox.button(QDialogButtonBox.Close).setFocus(
151 Qt.OtherFocusReason)
152
153 if self.signaturesList.topLevelItemCount() == 0:
154 # no patches present
155 self.__generateItem("", "", self.tr("no signatures found"))
156 self.__resizeColumns()
157 self.__resort()
158
159 def on_buttonBox_clicked(self, button):
160 """
161 Private slot called by a button of the button box clicked.
162
163 @param button button that was clicked (QAbstractButton)
164 """
165 if button == self.buttonBox.button(QDialogButtonBox.Close):
166 self.close()
167 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
168 if self.__hgClient:
169 self.__hgClient.cancel()
170 else:
171 self.__finish()
172
173 def __procFinished(self, exitCode, exitStatus):
174 """
175 Private slot connected to the finished signal.
176
177 @param exitCode exit code of the process (integer)
178 @param exitStatus exit status of the process (QProcess.ExitStatus)
179 """
180 self.__finish()
181
182 def __resort(self):
183 """
184 Private method to resort the tree.
185 """
186 self.signaturesList.sortItems(
187 self.signaturesList.sortColumn(),
188 self.signaturesList.header().sortIndicatorOrder())
189
190 def __resizeColumns(self):
191 """
192 Private method to resize the list columns.
193 """
194 self.signaturesList.header().resizeSections(
195 QHeaderView.ResizeToContents)
196 self.signaturesList.header().setStretchLastSection(True)
197
198 def __generateItem(self, revision, changeset, signature):
199 """
200 Private method to generate a patch item in the list of patches.
201
202 @param revision revision number (string)
203 @param changeset changeset of the bookmark (string)
204 @param signature signature of the changeset (string)
205 """
206 if revision == "" and changeset == "":
207 QTreeWidgetItem(self.signaturesList, [signature])
208 else:
209 revString = "{0:>7}:{1}".format(revision, changeset)
210 topItems = self.signaturesList.findItems(
211 revString, Qt.MatchExactly)
212 if len(topItems) == 0:
213 # first signature for this changeset
214 topItm = QTreeWidgetItem(self.signaturesList, [
215 "{0:>7}:{1}".format(revision, changeset)])
216 topItm.setExpanded(True)
217 font = topItm.font(0)
218 font.setBold(True)
219 topItm.setFont(0, font)
220 else:
221 topItm = topItems[0]
222 QTreeWidgetItem(topItm, [signature])
223
224 def __readStdout(self):
225 """
226 Private slot to handle the readyReadStdout signal.
227
228 It reads the output of the process, formats it and inserts it into
229 the contents pane.
230 """
231 self.process.setReadChannel(QProcess.StandardOutput)
232
233 while self.process.canReadLine():
234 s = str(self.process.readLine(), self.vcs.getEncoding(),
235 'replace').strip()
236 self.__processOutputLine(s)
237
238 def __processOutputLine(self, line):
239 """
240 Private method to process the lines of output.
241
242 @param line output line to be processed (string)
243 """
244 li = line.split()
245 if li[-1][0] in "1234567890":
246 # last element is a rev:changeset
247 rev, changeset = li[-1].split(":", 1)
248 del li[-1]
249 signature = " ".join(li)
250 self.__generateItem(rev, changeset, signature)
251
252 def __readStderr(self):
253 """
254 Private slot to handle the readyReadStderr signal.
255
256 It reads the error output of the process and inserts it into the
257 error pane.
258 """
259 if self.process is not None:
260 s = str(self.process.readAllStandardError(),
261 self.vcs.getEncoding(), 'replace')
262 self.__showError(s)
263
264 def __showError(self, out):
265 """
266 Private slot to show some error.
267
268 @param out error to be shown (string)
269 """
270 self.errorGroup.show()
271 self.errors.insertPlainText(out)
272 self.errors.ensureCursorVisible()
273
274 @pyqtSlot()
275 def on_signaturesList_itemSelectionChanged(self):
276 """
277 Private slot handling changes of the selection.
278 """
279 selectedItems = self.signaturesList.selectedItems()
280 if len(selectedItems) == 1 and \
281 self.signaturesList.indexOfTopLevelItem(selectedItems[0]) != -1:
282 self.verifyButton.setEnabled(True)
283 else:
284 self.verifyButton.setEnabled(False)
285
286 @pyqtSlot()
287 def on_verifyButton_clicked(self):
288 """
289 Private slot to verify the signatures of the selected revision.
290 """
291 rev = self.signaturesList.selectedItems()[0].text(0)\
292 .split(":")[0].strip()
293 self.vcs.getExtensionObject("gpg")\
294 .hgGpgVerifySignatures(self.__path, rev)
295
296 @pyqtSlot(str)
297 def on_categoryCombo_activated(self, txt):
298 """
299 Private slot called, when a new filter category is selected.
300
301 @param txt text of the selected category (string)
302 """
303 self.__filterSignatures()
304
305 @pyqtSlot(str)
306 def on_rxEdit_textChanged(self, txt):
307 """
308 Private slot called, when a filter expression is entered.
309
310 @param txt filter expression (string)
311 """
312 self.__filterSignatures()
313
314 def __filterSignatures(self):
315 """
316 Private method to filter the log entries.
317 """
318 searchRxText = self.rxEdit.text()
319 filterTop = self.categoryCombo.currentText() == self.tr("Revision")
320 if filterTop and searchRxText.startswith("^"):
321 searchRx = QRegExp(
322 r"^\s*{0}".format(searchRxText[1:]), Qt.CaseInsensitive)
323 else:
324 searchRx = QRegExp(searchRxText, Qt.CaseInsensitive)
325 for topIndex in range(self.signaturesList.topLevelItemCount()):
326 topLevelItem = self.signaturesList.topLevelItem(topIndex)
327 if filterTop:
328 topLevelItem.setHidden(
329 searchRx.indexIn(topLevelItem.text(0)) == -1)
330 else:
331 visibleChildren = topLevelItem.childCount()
332 for childIndex in range(topLevelItem.childCount()):
333 childItem = topLevelItem.child(childIndex)
334 if searchRx.indexIn(childItem.text(0)) == -1:
335 childItem.setHidden(True)
336 visibleChildren -= 1
337 else:
338 childItem.setHidden(False)
339 topLevelItem.setHidden(visibleChildren == 0)
340
341 def on_passwordCheckBox_toggled(self, isOn):
342 """
343 Private slot to handle the password checkbox toggled.
344
345 @param isOn flag indicating the status of the check box (boolean)
346 """
347 if isOn:
348 self.input.setEchoMode(QLineEdit.Password)
349 else:
350 self.input.setEchoMode(QLineEdit.Normal)
351
352 @pyqtSlot()
353 def on_sendButton_clicked(self):
354 """
355 Private slot to send the input to the subversion process.
356 """
357 inputTxt = self.input.text()
358 inputTxt += os.linesep
359
360 if self.passwordCheckBox.isChecked():
361 self.errors.insertPlainText(os.linesep)
362 self.errors.ensureCursorVisible()
363 else:
364 self.errors.insertPlainText(inputTxt)
365 self.errors.ensureCursorVisible()
366
367 self.process.write(strToQByteArray(inputTxt))
368
369 self.passwordCheckBox.setChecked(False)
370 self.input.clear()
371
372 def on_input_returnPressed(self):
373 """
374 Private slot to handle the press of the return key in the input field.
375 """
376 self.intercept = True
377 self.on_sendButton_clicked()
378
379 def keyPressEvent(self, evt):
380 """
381 Protected slot to handle a key press event.
382
383 @param evt the key press event (QKeyEvent)
384 """
385 if self.intercept:
386 self.intercept = False
387 evt.accept()
388 return
389 super(HgGpgSignaturesDialog, self).keyPressEvent(evt)

eric ide

mercurial