|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog showing signed changesets. |
|
8 """ |
|
9 |
|
10 import re |
|
11 |
|
12 from PyQt5.QtCore import pyqtSlot, Qt, QCoreApplication |
|
13 from PyQt5.QtWidgets import ( |
|
14 QDialog, QDialogButtonBox, QHeaderView, QTreeWidgetItem |
|
15 ) |
|
16 |
|
17 from .Ui_HgGpgSignaturesDialog import Ui_HgGpgSignaturesDialog |
|
18 |
|
19 |
|
20 class HgGpgSignaturesDialog(QDialog, Ui_HgGpgSignaturesDialog): |
|
21 """ |
|
22 Class implementing a dialog showing signed changesets. |
|
23 """ |
|
24 def __init__(self, vcs, parent=None): |
|
25 """ |
|
26 Constructor |
|
27 |
|
28 @param vcs reference to the vcs object |
|
29 @param parent reference to the parent widget (QWidget) |
|
30 """ |
|
31 super().__init__(parent) |
|
32 self.setupUi(self) |
|
33 self.setWindowFlags(Qt.WindowType.Window) |
|
34 |
|
35 self.buttonBox.button( |
|
36 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
|
37 self.buttonBox.button( |
|
38 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
39 |
|
40 self.vcs = vcs |
|
41 self.__hgClient = vcs.getClient() |
|
42 |
|
43 self.show() |
|
44 QCoreApplication.processEvents() |
|
45 |
|
46 def closeEvent(self, e): |
|
47 """ |
|
48 Protected slot implementing a close event handler. |
|
49 |
|
50 @param e close event (QCloseEvent) |
|
51 """ |
|
52 if self.__hgClient.isExecuting(): |
|
53 self.__hgClient.cancel() |
|
54 |
|
55 e.accept() |
|
56 |
|
57 def start(self): |
|
58 """ |
|
59 Public slot to start the list command. |
|
60 """ |
|
61 self.errorGroup.hide() |
|
62 |
|
63 self.intercept = False |
|
64 self.activateWindow() |
|
65 |
|
66 args = self.vcs.initCommand("sigs") |
|
67 |
|
68 out, err = self.__hgClient.runcommand(args) |
|
69 if err: |
|
70 self.__showError(err) |
|
71 if out: |
|
72 for line in out.splitlines(): |
|
73 self.__processOutputLine(line) |
|
74 if self.__hgClient.wasCanceled(): |
|
75 break |
|
76 self.__finish() |
|
77 |
|
78 def __finish(self): |
|
79 """ |
|
80 Private slot called when the process finished or the user pressed |
|
81 the button. |
|
82 """ |
|
83 self.buttonBox.button( |
|
84 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
|
85 self.buttonBox.button( |
|
86 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
|
87 self.buttonBox.button( |
|
88 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
89 self.buttonBox.button( |
|
90 QDialogButtonBox.StandardButton.Close).setFocus( |
|
91 Qt.FocusReason.OtherFocusReason) |
|
92 |
|
93 if self.signaturesList.topLevelItemCount() == 0: |
|
94 # no patches present |
|
95 self.__generateItem("", "", self.tr("no signatures found")) |
|
96 self.__resizeColumns() |
|
97 self.__resort() |
|
98 |
|
99 def on_buttonBox_clicked(self, button): |
|
100 """ |
|
101 Private slot called by a button of the button box clicked. |
|
102 |
|
103 @param button button that was clicked (QAbstractButton) |
|
104 """ |
|
105 if button == self.buttonBox.button( |
|
106 QDialogButtonBox.StandardButton.Close |
|
107 ): |
|
108 self.close() |
|
109 elif button == self.buttonBox.button( |
|
110 QDialogButtonBox.StandardButton.Cancel |
|
111 ): |
|
112 self.__hgClient.cancel() |
|
113 |
|
114 def __resort(self): |
|
115 """ |
|
116 Private method to resort the tree. |
|
117 """ |
|
118 self.signaturesList.sortItems( |
|
119 self.signaturesList.sortColumn(), |
|
120 self.signaturesList.header().sortIndicatorOrder()) |
|
121 |
|
122 def __resizeColumns(self): |
|
123 """ |
|
124 Private method to resize the list columns. |
|
125 """ |
|
126 self.signaturesList.header().resizeSections( |
|
127 QHeaderView.ResizeMode.ResizeToContents) |
|
128 self.signaturesList.header().setStretchLastSection(True) |
|
129 |
|
130 def __generateItem(self, revision, changeset, signature): |
|
131 """ |
|
132 Private method to generate a patch item in the list of patches. |
|
133 |
|
134 @param revision revision number (string) |
|
135 @param changeset changeset of the bookmark (string) |
|
136 @param signature signature of the changeset (string) |
|
137 """ |
|
138 if revision == "" and changeset == "": |
|
139 QTreeWidgetItem(self.signaturesList, [signature]) |
|
140 else: |
|
141 revString = "{0:>7}:{1}".format(revision, changeset) |
|
142 topItems = self.signaturesList.findItems( |
|
143 revString, Qt.MatchFlag.MatchExactly) |
|
144 if len(topItems) == 0: |
|
145 # first signature for this changeset |
|
146 topItm = QTreeWidgetItem(self.signaturesList, [ |
|
147 "{0:>7}:{1}".format(revision, changeset)]) |
|
148 topItm.setExpanded(True) |
|
149 font = topItm.font(0) |
|
150 font.setBold(True) |
|
151 topItm.setFont(0, font) |
|
152 else: |
|
153 topItm = topItems[0] |
|
154 QTreeWidgetItem(topItm, [signature]) |
|
155 |
|
156 def __processOutputLine(self, line): |
|
157 """ |
|
158 Private method to process the lines of output. |
|
159 |
|
160 @param line output line to be processed (string) |
|
161 """ |
|
162 li = line.split() |
|
163 if li[-1][0] in "1234567890": |
|
164 # last element is a rev:changeset |
|
165 rev, changeset = li[-1].split(":", 1) |
|
166 del li[-1] |
|
167 signature = " ".join(li) |
|
168 self.__generateItem(rev, changeset, signature) |
|
169 |
|
170 def __showError(self, out): |
|
171 """ |
|
172 Private slot to show some error. |
|
173 |
|
174 @param out error to be shown (string) |
|
175 """ |
|
176 self.errorGroup.show() |
|
177 self.errors.insertPlainText(out) |
|
178 self.errors.ensureCursorVisible() |
|
179 |
|
180 @pyqtSlot() |
|
181 def on_signaturesList_itemSelectionChanged(self): |
|
182 """ |
|
183 Private slot handling changes of the selection. |
|
184 """ |
|
185 selectedItems = self.signaturesList.selectedItems() |
|
186 if ( |
|
187 len(selectedItems) == 1 and |
|
188 self.signaturesList.indexOfTopLevelItem(selectedItems[0]) != -1 |
|
189 ): |
|
190 self.verifyButton.setEnabled(True) |
|
191 else: |
|
192 self.verifyButton.setEnabled(False) |
|
193 |
|
194 @pyqtSlot() |
|
195 def on_verifyButton_clicked(self): |
|
196 """ |
|
197 Private slot to verify the signatures of the selected revision. |
|
198 """ |
|
199 rev = ( |
|
200 self.signaturesList.selectedItems()[0].text(0) |
|
201 .split(":")[0].strip() |
|
202 ) |
|
203 self.vcs.getExtensionObject("gpg").hgGpgVerifySignatures(rev) |
|
204 |
|
205 @pyqtSlot(int) |
|
206 def on_categoryCombo_activated(self, index): |
|
207 """ |
|
208 Private slot called, when a new filter category is selected. |
|
209 |
|
210 @param index index of the selected entry |
|
211 @type int |
|
212 """ |
|
213 self.__filterSignatures() |
|
214 |
|
215 @pyqtSlot(str) |
|
216 def on_rxEdit_textChanged(self, txt): |
|
217 """ |
|
218 Private slot called, when a filter expression is entered. |
|
219 |
|
220 @param txt filter expression (string) |
|
221 """ |
|
222 self.__filterSignatures() |
|
223 |
|
224 def __filterSignatures(self): |
|
225 """ |
|
226 Private method to filter the log entries. |
|
227 """ |
|
228 searchRxText = self.rxEdit.text() |
|
229 filterTop = self.categoryCombo.currentText() == self.tr("Revision") |
|
230 searchRx = ( |
|
231 re.compile( |
|
232 r"^\s*{0}".format(searchRxText[1:]), re.IGNORECASE) |
|
233 if filterTop and searchRxText.startswith("^") else |
|
234 re.compile(searchRxText, re.IGNORECASE) |
|
235 ) |
|
236 for topIndex in range(self.signaturesList.topLevelItemCount()): |
|
237 topLevelItem = self.signaturesList.topLevelItem(topIndex) |
|
238 if filterTop: |
|
239 topLevelItem.setHidden( |
|
240 searchRx.search(topLevelItem.text(0)) is None) |
|
241 else: |
|
242 visibleChildren = topLevelItem.childCount() |
|
243 for childIndex in range(topLevelItem.childCount()): |
|
244 childItem = topLevelItem.child(childIndex) |
|
245 if searchRx.search(childItem.text(0)) is None: |
|
246 childItem.setHidden(True) |
|
247 visibleChildren -= 1 |
|
248 else: |
|
249 childItem.setHidden(False) |
|
250 topLevelItem.setHidden(visibleChildren == 0) |