1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2011 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing a dialog showing signed changesets. 8""" 9 10import re 11 12from PyQt5.QtCore import pyqtSlot, Qt, QCoreApplication 13from PyQt5.QtWidgets import ( 14 QDialog, QDialogButtonBox, QHeaderView, QTreeWidgetItem 15) 16 17from .Ui_HgGpgSignaturesDialog import Ui_HgGpgSignaturesDialog 18 19 20class 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) 251