--- a/src/eric7/Plugins/VcsPlugins/vcsSubversion/SvnRepoBrowserDialog.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Plugins/VcsPlugins/vcsSubversion/SvnRepoBrowserDialog.py Wed Jul 13 14:55:47 2022 +0200 @@ -12,7 +12,11 @@ from PyQt6.QtCore import pyqtSlot, Qt, QTimer, QProcess from PyQt6.QtWidgets import ( - QHeaderView, QLineEdit, QDialog, QDialogButtonBox, QTreeWidgetItem + QHeaderView, + QLineEdit, + QDialog, + QDialogButtonBox, + QTreeWidgetItem, ) from EricWidgets import EricMessageBox @@ -30,10 +34,11 @@ """ Class implementing the subversion repository browser dialog. """ + def __init__(self, vcs, mode="browse", parent=None): """ Constructor - + @param vcs reference to the vcs object @param mode mode of the dialog (string, "browse" or "select") @param parent parent widget (QWidget) @@ -41,78 +46,76 @@ super().__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.WindowType.Window) - + self.repoTree.headerItem().setText(self.repoTree.columnCount(), "") self.repoTree.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder) - + self.vcs = vcs self.mode = mode - + self.__process = EricOverrideCursorProcess() self.__process.finished.connect(self.__procFinished) self.__process.readyReadStandardOutput.connect(self.__readStdout) self.__process.readyReadStandardError.connect(self.__readStderr) - + if self.mode == "select": - self.buttonBox.button( - QDialogButtonBox.StandardButton.Ok).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False) self.buttonBox.button(QDialogButtonBox.StandardButton.Close).hide() else: self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).hide() - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).hide() - + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).hide() + self.__dirIcon = UI.PixmapCache.getIcon("dirClosed") self.__fileIcon = UI.PixmapCache.getIcon("fileMisc") - + self.__urlRole = Qt.ItemDataRole.UserRole self.__ignoreExpand = False self.intercept = False - + self.__rx_dir = re.compile( r"""\s*([0-9]+)\s+(\w+)\s+""" - r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""") + r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""" + ) self.__rx_file = re.compile( r"""\s*([0-9]+)\s+(\w+)\s+([0-9]+)\s""" - r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""") - + r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""" + ) + def closeEvent(self, e): """ Protected slot implementing a close event handler. - + @param e close event (QCloseEvent) """ if ( - self.__process is not None and - self.__process.state() != QProcess.ProcessState.NotRunning + self.__process is not None + and self.__process.state() != QProcess.ProcessState.NotRunning ): self.__process.terminate() QTimer.singleShot(2000, self.__process.kill) self.__process.waitForFinished(3000) - + e.accept() - + def __resort(self): """ Private method to resort the tree. """ self.repoTree.sortItems( - self.repoTree.sortColumn(), - self.repoTree.header().sortIndicatorOrder()) - + self.repoTree.sortColumn(), self.repoTree.header().sortIndicatorOrder() + ) + def __resizeColumns(self): """ Private method to resize the tree columns. """ - self.repoTree.header().resizeSections( - QHeaderView.ResizeMode.ResizeToContents) + self.repoTree.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) self.repoTree.header().setStretchLastSection(True) - - def __generateItem(self, repopath, revision, author, size, date, - nodekind, url): + + def __generateItem(self, repopath, revision, author, size, date, nodekind, url): """ Private method to generate a tree item in the repository tree. - + @param repopath path of the item (string) @param revision revision info (string) @param author author info (string) @@ -124,133 +127,131 @@ """ rev = "" if revision == "" else int(revision) sz = "" if size == "" else int(size) - + itm = QTreeWidgetItem(self.parentItem) itm.setData(0, Qt.ItemDataRole.DisplayRole, repopath) itm.setData(1, Qt.ItemDataRole.DisplayRole, rev) itm.setData(2, Qt.ItemDataRole.DisplayRole, author) itm.setData(3, Qt.ItemDataRole.DisplayRole, sz) itm.setData(4, Qt.ItemDataRole.DisplayRole, date) - + if nodekind == "dir": itm.setIcon(0, self.__dirIcon) itm.setChildIndicatorPolicy( - QTreeWidgetItem.ChildIndicatorPolicy.ShowIndicator) + QTreeWidgetItem.ChildIndicatorPolicy.ShowIndicator + ) elif nodekind == "file": itm.setIcon(0, self.__fileIcon) - + itm.setData(0, self.__urlRole, url) - + itm.setTextAlignment(0, Qt.AlignmentFlag.AlignLeft) itm.setTextAlignment(1, Qt.AlignmentFlag.AlignRight) itm.setTextAlignment(2, Qt.AlignmentFlag.AlignLeft) itm.setTextAlignment(3, Qt.AlignmentFlag.AlignRight) itm.setTextAlignment(4, Qt.AlignmentFlag.AlignLeft) - + return itm - + def __repoRoot(self, url): """ Private method to get the repository root using the svn info command. - + @param url the repository URL to browser (string) @return repository root (string) """ ioEncoding = Preferences.getSystem("IOEncoding") repoRoot = None - + process = QProcess() - + args = [] - args.append('info') - self.vcs.addArguments(args, self.vcs.options['global']) - args.append('--xml') + args.append("info") + self.vcs.addArguments(args, self.vcs.options["global"]) + args.append("--xml") args.append(url) - - process.start('svn', args) + + process.start("svn", args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(30000) if finished: if process.exitCode() == 0: - output = str(process.readAllStandardOutput(), ioEncoding, - 'replace') + output = str(process.readAllStandardOutput(), ioEncoding, "replace") for line in output.splitlines(): line = line.strip() - if line.startswith('<root>'): - repoRoot = ( - line.replace('<root>', '') - .replace('</root>', '') - ) + if line.startswith("<root>"): + repoRoot = line.replace("<root>", "").replace("</root>", "") break else: - error = str(process.readAllStandardError(), - Preferences.getSystem("IOEncoding"), - 'replace') + error = str( + process.readAllStandardError(), + Preferences.getSystem("IOEncoding"), + "replace", + ) self.errors.insertPlainText(error) self.errors.ensureCursorVisible() else: EricMessageBox.critical( self, - self.tr('Process Generation Error'), + self.tr("Process Generation Error"), self.tr( - 'The process {0} could not be started. ' - 'Ensure, that it is in the search path.' - ).format('svn')) + "The process {0} could not be started. " + "Ensure, that it is in the search path." + ).format("svn"), + ) return repoRoot - + def __listRepo(self, url, parent=None): """ Private method to perform the svn list command. - + @param url the repository URL to browse (string) @param parent reference to the item, the data should be appended to (QTreeWidget or QTreeWidgetItem) """ self.errorGroup.hide() - + self.repoUrl = url - + if parent is None: self.parentItem = self.repoTree else: self.parentItem = parent - + if self.parentItem == self.repoTree: repoRoot = self.__repoRoot(url) if repoRoot is None: self.__finish() return self.__ignoreExpand = True - itm = self.__generateItem( - repoRoot, "", "", "", "", "dir", repoRoot) + itm = self.__generateItem(repoRoot, "", "", "", "", "dir", repoRoot) itm.setExpanded(True) self.parentItem = itm urlPart = repoRoot for element in url.replace(repoRoot, "").split("/"): if element: urlPart = "{0}/{1}".format(urlPart, element) - itm = self.__generateItem( - element, "", "", "", "", "dir", urlPart) + itm = self.__generateItem(element, "", "", "", "", "dir", urlPart) itm.setExpanded(True) self.parentItem = itm itm.setExpanded(False) self.__ignoreExpand = False self.__finish() return - + self.intercept = False - + self.__process.kill() - + args = [] - args.append('list') - self.vcs.addArguments(args, self.vcs.options['global']) - if '--verbose' not in self.vcs.options['global']: - args.append('--verbose') + args.append("list") + self.vcs.addArguments(args, self.vcs.options["global"]) + if "--verbose" not in self.vcs.options["global"]: + args.append("--verbose") args.append(url) - - self.__process.start('svn', args) + + self.__process.start("svn", args) procStarted = self.__process.waitForStarted(5000) if not procStarted: self.__finish() @@ -258,45 +259,46 @@ self.inputGroup.hide() EricMessageBox.critical( self, - self.tr('Process Generation Error'), + self.tr("Process Generation Error"), self.tr( - 'The process {0} could not be started. ' - 'Ensure, that it is in the search path.' - ).format('svn')) + "The process {0} could not be started. " + "Ensure, that it is in the search path." + ).format("svn"), + ) else: self.inputGroup.setEnabled(True) self.inputGroup.show() - + def __normalizeUrl(self, url): """ Private method to normalite the url. - + @param url the url to normalize (string) @return normalized URL (string) """ if url.endswith("/"): return url[:-1] return url - + def start(self, url): """ Public slot to start the svn info command. - + @param url the repository URL to browser (string) """ self.repoTree.clear() - + self.url = "" - + url = self.__normalizeUrl(url) if self.urlCombo.findText(url) == -1: self.urlCombo.addItem(url) - + @pyqtSlot(int) def on_urlCombo_currentIndexChanged(self, index): """ Private slot called, when a new repository URL is entered or selected. - + @param index index of the current item @type int """ @@ -306,50 +308,49 @@ self.url = url self.repoTree.clear() self.__listRepo(url) - + @pyqtSlot(QTreeWidgetItem) def on_repoTree_itemExpanded(self, item): """ Private slot called when an item is expanded. - + @param item reference to the item to be expanded (QTreeWidgetItem) """ if not self.__ignoreExpand: url = item.data(0, self.__urlRole) self.__listRepo(url, item) - + @pyqtSlot(QTreeWidgetItem) def on_repoTree_itemCollapsed(self, item): """ Private slot called when an item is collapsed. - + @param item reference to the item to be collapsed (QTreeWidgetItem) """ for child in item.takeChildren(): del child - + @pyqtSlot() def on_repoTree_itemSelectionChanged(self): """ Private slot called when the selection changes. """ if self.mode == "select": - self.buttonBox.button( - QDialogButtonBox.StandardButton.Ok).setEnabled(True) - + self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(True) + def accept(self): """ Public slot called when the dialog is accepted. """ if self.focusWidget() == self.urlCombo: return - + super().accept() - + def getSelectedUrl(self): """ Public method to retrieve the selected repository URL. - + @return the selected repository URL (string) """ items = self.repoTree.selectedItems() @@ -357,54 +358,52 @@ return items[0].data(0, self.__urlRole) else: return "" - + def __finish(self): """ Private slot called when the process finished or the user pressed the button. """ if ( - self.__process is not None and - self.__process.state() != QProcess.ProcessState.NotRunning + self.__process is not None + and self.__process.state() != QProcess.ProcessState.NotRunning ): self.__process.terminate() QTimer.singleShot(2000, self.__process.kill) self.__process.waitForFinished(3000) - + self.inputGroup.setEnabled(False) self.inputGroup.hide() - + self.__resizeColumns() self.__resort() - + def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. - + @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__finish() - + def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. - + It reads the output of the process, formats it and inserts it into the contents pane. """ if self.__process is not None: - self.__process.setReadChannel( - QProcess.ProcessChannel.StandardOutput) - + self.__process.setReadChannel(QProcess.ProcessChannel.StandardOutput) + while self.__process.canReadLine(): - s = str(self.__process.readLine(), - Preferences.getSystem("IOEncoding"), - 'replace') - match = ( - self.__rx_dir.fullmatch(s) or - self.__rx_file.fullmatch(s) + s = str( + self.__process.readLine(), + Preferences.getSystem("IOEncoding"), + "replace", ) + match = self.__rx_dir.fullmatch(s) or self.__rx_file.fullmatch(s) if match is None: continue elif match.re is self.__rx_dir: @@ -425,37 +424,38 @@ date = match.group(4) name = match.group(5).strip() nodekind = "file" - + url = "{0}/{1}".format(self.repoUrl, name) - self.__generateItem( - name, revision, author, size, date, nodekind, url) - + self.__generateItem(name, revision, author, size, date, nodekind, url) + def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. - + It reads the error output of the process and inserts it into the error pane. """ if self.__process is not None: - s = str(self.__process.readAllStandardError(), - Preferences.getSystem("IOEncoding"), - 'replace') + s = str( + self.__process.readAllStandardError(), + Preferences.getSystem("IOEncoding"), + "replace", + ) self.errors.insertPlainText(s) self.errors.ensureCursorVisible() self.errorGroup.show() - + def on_passwordCheckBox_toggled(self, isOn): """ Private slot to handle the password checkbox toggled. - + @param isOn flag indicating the status of the check box (boolean) """ if isOn: self.input.setEchoMode(QLineEdit.EchoMode.Password) else: self.input.setEchoMode(QLineEdit.EchoMode.Normal) - + @pyqtSlot() def on_sendButton_clicked(self): """ @@ -463,30 +463,30 @@ """ inputTxt = self.input.text() inputTxt += os.linesep - + if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(inputTxt) self.errors.ensureCursorVisible() - + self.__process.write(strToQByteArray(inputTxt)) - + self.passwordCheckBox.setChecked(False) self.input.clear() - + def on_input_returnPressed(self): """ Private slot to handle the press of the return key in the input field. """ self.intercept = True self.on_sendButton_clicked() - + def keyPressEvent(self, evt): """ Protected slot to handle a key press event. - + @param evt the key press event (QKeyEvent) """ if self.intercept: