def highlightBlock(self, text): for pattern, format in self.rules: exp = QRegExp(pattern) index = exp.indexIn(text) while index >= 0: length = exp.matchedLength() if exp.numCaptures() > 0: self.setFormat(exp.pos(1), len(str(exp.cap(1))), format) else: self.setFormat(exp.pos(0), len(str(exp.cap(0))), format) index = exp.indexIn(text, index + length) # Multi line strings start = self.multilineStart end = self.multilineEnd self.setCurrentBlockState(0) startIndex, skip = 0, 0 if self.previousBlockState() != 1: startIndex, skip = start.indexIn(text), 3 while startIndex >= 0: endIndex = end.indexIn(text, startIndex + skip) if endIndex == -1: self.setCurrentBlockState(1) commentLen = len(text) - startIndex else: commentLen = endIndex - startIndex + 3 self.setFormat(startIndex, commentLen, self.stringFormat) startIndex, skip = (start.indexIn(text, startIndex + commentLen + 3), 3)
def parseInDocRules(self): oldRules = self.inDocRules self.inDocRules = [] t = self.thisDocument.toPlainText() # Get all conf files confs = [] lines = t.split("\n") for l in lines: r = QRegExp(r'^%!includeconf:\s*([^\s]*)\s*') if r.indexIn(l) != -1: confs.append(r.cap(1)) # Try to load conf files for c in confs: try: import codecs f = self.editor.fileWidget.file d = QDir.cleanPath(QFileInfo(f).absoluteDir().absolutePath() + "/" + c) file = codecs.open(d, 'r', "utf-8") except: print(("Error: cannot open {}.".format(c))) continue # We add the content to the current lines of the current document lines += file.readlines() # lines.extend(file.readlines()) # b = self.thisDocument.firstBlock() lastColor = "" # while b.isValid(): for l in lines: text = l # b.text() r = QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*(\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\")') if r.indexIn(text) != -1: rule = r.cap(1)[1:-1] # Check if there was a color-comment above that post/preproc bloc if lastColor: self.inDocRules.append((str(rule), lastColor)) # Check if previous block is a comment like it should else: previousText = lines[lines.indexOf(l) - 1] # b.previous().text() r = QRegExp(r'^%.*\s\((.*)\)') if r.indexIn(previousText) != -1: lastColor = r.cap(1) self.inDocRules.append((str(rule), lastColor)) else: lastColor = "" # b = b.next() if oldRules != self.inDocRules: # Rules have changed, we need to rehighlight # print("Rules have changed.", len(self.inDocRules)) # self.rehighlight() # Doesn't work (seg fault), why? pass
def lessThan(self, left, right): leftData = self.sourceModel().data(left) rightData = self.sourceModel().data(right) if not isinstance(leftData, QDate): emailPattern = QRegExp("([\\w\\.]*@[\\w\\.]*)") if left.column() == 1 and emailPattern.indexIn(leftData) != -1: leftData = emailPattern.cap(1) if right.column() == 1 and emailPattern.indexIn(rightData) != -1: rightData = emailPattern.cap(1) return leftData < rightData
def valueFromText(text): regExp = QRegExp("(\\d+)(\\s*[xx]\\s*\\d+)?") if regExp.exactMatch(text): return int(regExp.cap(1)) else: return 0
def refresh(self): self.setUpdatesEnabled(False) pattern = self.patternComboBox.currentText() text = self.textComboBox.currentText() escaped = str(pattern) escaped.replace('\\', '\\\\') escaped.replace('"', '\\"') self.escapedPatternLineEdit.setText('"' + escaped + '"') rx = QRegExp(pattern) cs = Qt.CaseSensitive if self.caseSensitiveCheckBox.isChecked() else Qt.CaseInsensitive rx.setCaseSensitivity(cs) rx.setMinimal(self.minimalCheckBox.isChecked()) syntax = self.syntaxComboBox.itemData(self.syntaxComboBox.currentIndex()) rx.setPatternSyntax(syntax) palette = self.patternComboBox.palette() if rx.isValid(): palette.setColor(QPalette.Text, self.textComboBox.palette().color(QPalette.Text)) else: palette.setColor(QPalette.Text, Qt.red) self.patternComboBox.setPalette(palette) self.indexEdit.setText(str(rx.indexIn(text))) self.matchedLengthEdit.setText(str(rx.matchedLength())) for i in range(self.MaxCaptures): self.captureLabels[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setText(rx.cap(i)) self.setUpdatesEnabled(True)
def run(self): """Execute this rules in another thread to avoid blocking the ui.""" styles = {} self.msleep(300) block = self._highlighter.document().begin() while block.blockNumber() != -1: text = block.text() formats = [] for expression, nth, char_format in self._highlighter.rules: index = expression.indexIn(text, 0) while index >= 0: # We actually want the index of the nth match index = expression.pos(nth) length = len(expression.cap(nth)) formats.append((index, length, char_format)) index = expression.indexIn(text, index + length) #Spaces expression = QRegExp('\s+') index = expression.indexIn(text, 0) while index >= 0: index = expression.pos(0) length = len(expression.cap(0)) formats.append((index, length, STYLES['spaces'])) index = expression.indexIn(text, index + length) styles[block.blockNumber()] = formats block = block.next()# block = next(block) self.highlightingDetected.emit(styles)
def findCodecs(self): codecMap = [] iso8859RegExp = QRegExp('ISO[- ]8859-([0-9]+).*') for mib in QTextCodec.availableMibs(): codec = QTextCodec.codecForMib(mib) sortKey = codec_name(codec).upper() rank = 0 if sortKey.startswith('UTF-8'): rank = 1 elif sortKey.startswith('UTF-16'): rank = 2 elif iso8859RegExp.exactMatch(sortKey): if len(iso8859RegExp.cap(1)) == 1: rank = 3 else: rank = 4 else: rank = 5 codecMap.append((str(rank) + sortKey, codec)) codecMap.sort() self.codecs = [item[-1] for item in codecMap]
def getSize(self): text, ok = QInputDialog.getText(self, "Grabber", "Enter pixmap size:", QLineEdit.Normal, "%d x %d" % (self.glWidget.width(), self.glWidget.height())) if not ok: return QSize() regExp = QRegExp(r'([0-9]+) *x *([0-9]+)') if regExp.exactMatch(text): width = int(regExp.cap(1)) height = int(regExp.cap(2)) if width > 0 and width < 2048 and height > 0 and height < 2048: return QSize(width, height) return self.glWidget.size()
def valueFromText(self, strings): regExp = QRegExp("(\\d+)(\\s*[xx]\\s*\\d+)?") # 正则表达式的对象:\s 匹配任意的空白符,\d 匹配数字 ,\\表示转义\ if regExp.exactMatch(strings): return int(regExp.cap(1)) # cap()匹配的元素的顺序如下。第一个元素cap(0)是整个匹配的字符串。cap(1)是第一个捕获括号的文本。 return 0
def getSize(self): text, ok = QInputDialog.getText( self, "Grabber", "Enter pixmap size:", QLineEdit.Normal, "%d x %d" % (self.glWidget.width(), self.glWidget.height())) if not ok: return QSize() regExp = QRegExp(r'([0-9]+) *x *([0-9]+)') if regExp.exactMatch(text): width = int(regExp.cap(1)) height = int(regExp.cap(2)) if width > 0 and width < 2048 and height > 0 and height < 2048: return QSize(width, height) return self.glWidget.size()
def __scriptDownloaded(self): """ Private slot to handle the finished download of a script. """ if self.sender() != self.__reply: self.finished.emit() return response = bytes(self.__reply.readAll()).decode() if self.__reply.error() == QNetworkReply.NoError and \ "// ==UserScript==" in response: from Helpviewer import HelpUtilities filePath = os.path.join( self.__manager.scriptsDirectory(), HelpUtilities.getFileNameFromUrl(self.__reply.url())) self.__fileName = HelpUtilities.ensureUniqueFilename(filePath) try: f = open(self.__fileName, "w", encoding="utf-8") except (IOError, OSError) as err: E5MessageBox.critical( None, self.tr("GreaseMonkey Download"), self.tr( """<p>The file <b>{0}</b> could not be opened""" """ for writing.<br/>Reason: {1}</p>""").format( self.__fileName, str(err))) self.finished.emit() return f.write(response) f.close() settings = QSettings( os.path.join(self.__manager.requireScriptsDirectory(), "requires.ini"), QSettings.IniFormat) settings.beginGroup("Files") rx = QRegExp("@require(.*)\\n") rx.setMinimal(True) rx.indexIn(response) for i in range(1, rx.captureCount() + 1): url = rx.cap(i).strip() if url and not settings.contains(url): self.__requireUrls.append(QUrl(url)) self.__reply.deleteLater() self.__reply = None self.__downloadRequires()
def items_dwnl(self,item): item =item.text() print(item) take_name = QRegExp("^\S*") take_name.indexIn(item) input_val= take_name.cap(); self.s.sendall(json.dumps({"command":"descargar","path":".","nombres":[str(input_val)]}).encode()) print(json.dumps({"command":"descargar","path":".","nombres":[str(input_val)]}).encode())# Peticion de descarga data = self.s.recv(1024) data = json.loads(data.decode(errors='ignore')) # Leemos la respuesta name = data["nombres"][0] filesize = data["sizes"][0] self.downloads(self.s,name,filesize)
def __scriptDownloaded(self): """ Private slot to handle the finished download of a script. """ if self.sender() != self.__reply: self.finished.emit() return response = bytes(self.__reply.readAll()).decode() if self.__reply.error() == QNetworkReply.NoError and \ "// ==UserScript==" in response: from Helpviewer import HelpUtilities filePath = os.path.join( self.__manager.scriptsDirectory(), HelpUtilities.getFileNameFromUrl(self.__reply.url())) self.__fileName = HelpUtilities.ensureUniqueFilename(filePath) try: f = open(self.__fileName, "w", encoding="utf-8") except (IOError, OSError) as err: E5MessageBox.critical( None, self.tr("GreaseMonkey Download"), self.tr("""<p>The file <b>{0}</b> could not be opened""" """ for writing.<br/>Reason: {1}</p>""").format( self.__fileName, str(err))) self.finished.emit() return f.write(response) f.close() settings = QSettings( os.path.join(self.__manager.requireScriptsDirectory(), "requires.ini"), QSettings.IniFormat) settings.beginGroup("Files") rx = QRegExp("@require(.*)\\n") rx.setMinimal(True) rx.indexIn(response) for i in range(1, rx.captureCount() + 1): url = rx.cap(i).strip() if url and not settings.contains(url): self.__requireUrls.append(QUrl(url)) self.__reply.deleteLater() self.__reply = None self.__downloadRequires()
class IdentifierParser(): """Used to parse a tag for information. For example get image caption and image filepath from image tag. Call extract() to get the parsed information. The tag to be parsed is stored in regexp field after calling regexp.indexIn() on the text to be parsed. Field 'extractor' stores the function which should be used on the tag to parse it. Field 'category' stores the key of the 'document_info_template' dictionary, which should be updated with the parsed information""" def __init__(self, pattern, extractor, category): self.regexp = QRegExp(pattern) self.extractor = extractor self.category = category def extract(self) -> dict: """Returns the dictionary with information parsed by the extractor function""" return self.extractor(self.regexp.cap(0))
def __init__(self, mainWindow, parent = None): super().__init__(parent) self.mMainWindow = mainWindow self.setRootIsDecorated(False) self.setHeaderHidden(True) self.setItemsExpandable(False) self.setUniformRowHeights(True) self.setDragEnabled(True) self.setDefaultDropAction(Qt.MoveAction) prefs = preferences.Preferences.instance() prefs.mapsDirectoryChanged.connect(self.onMapsDirectoryChanged) mapsDir = QDir(prefs.mapsDirectory()) if (not mapsDir.exists()): mapsDir.setPath(QDir.currentPath()) self.mFSModel = FileSystemModel(self) self.mFSModel.setRootPath(mapsDir.absolutePath()) nameFilters = QStringList("*.tmx") # The file system model name filters are plain, whereas the plugins expose # a filter as part of the file description filterFinder = QRegExp("\\((\\*\\.[^\\)\\s]*)") for format in PluginManager.objects(MapFormat): if not (format.capabilities() & MapFormat.Read): continue filter = format.nameFilter() if (filterFinder.indexIn(filter) != -1): nameFilters.append(filterFinder.cap(1)) self.mFSModel.setFilter(QDir.AllDirs | QDir.Files | QDir.NoDot) self.mFSModel.setNameFilters(nameFilters) self.mFSModel.setNameFilterDisables(False) # hide filtered files self.setModel(self.mFSModel) headerView = self.header() headerView.hideSection(1) # Size column headerView.hideSection(2) headerView.hideSection(3) self.setRootIndex(self.mFSModel.index(mapsDir.absolutePath())) self.header().setStretchLastSection(False) self.header().setSectionResizeMode(0, QHeaderView.Stretch) self.activated.connect(self.onActivated) self.mMainWindow = None self.mFSModel = None
def __init__(self, parent=None): super(StyleSheetEditor, self).__init__(parent) self.ui = Ui_StyleSheetEditor() self.ui.setupUi(self) regExp = QRegExp(r'.(.*)\+?Style') defaultStyle = QApplication.style().metaObject().className() if regExp.exactMatch(defaultStyle): defaultStyle = regExp.cap(1) self.ui.styleCombo.addItems(QStyleFactory.keys()) self.ui.styleCombo.setCurrentIndex( self.ui.styleCombo.findText(defaultStyle, Qt.MatchContains)) self.ui.styleSheetCombo.setCurrentIndex( self.ui.styleSheetCombo.findText('Coffee')) self.loadStyleSheet('Coffee')
def refresh(self): self.setUpdatesEnabled(False) pattern = self.patternComboBox.currentText() text = self.textComboBox.currentText() escaped = str(pattern) escaped.replace('\\', '\\\\') escaped.replace('"', '\\"') self.escapedPatternLineEdit.setText('"' + escaped + '"') rx = QRegExp(pattern) cs = Qt.CaseSensitive if self.caseSensitiveCheckBox.isChecked( ) else Qt.CaseInsensitive rx.setCaseSensitivity(cs) rx.setMinimal(self.minimalCheckBox.isChecked()) syntax = self.syntaxComboBox.itemData( self.syntaxComboBox.currentIndex()) rx.setPatternSyntax(syntax) palette = self.patternComboBox.palette() if rx.isValid(): palette.setColor(QPalette.Text, self.textComboBox.palette().color(QPalette.Text)) else: palette.setColor(QPalette.Text, Qt.red) self.patternComboBox.setPalette(palette) self.indexEdit.setText(str(rx.indexIn(text))) self.matchedLengthEdit.setText(str(rx.matchedLength())) for i in range(self.MaxCaptures): self.captureLabels[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setText(rx.cap(i)) self.setUpdatesEnabled(True)
class MacroHighlighter(QSyntaxHighlighter): def __init__(self,textboxdoc,valid_list_of_commands): QSyntaxHighlighter.__init__(self,textboxdoc) self.valid_syntax="|".join([command.regexp_str for command in valid_list_of_commands]) self.my_expression = QRegExp(self.valid_syntax) #define a blue font format for valid commands self.valid = QTextCharFormat() self.valid.setForeground(Qt.black) #define a bold red font format for invalid commands self.invalid = QTextCharFormat() self.invalid.setFontWeight(QFont.Bold) self.invalid.setForeground(Qt.red) #define a blue font format for valid parameters self.valid_value=QTextCharFormat() self.valid_value.setFontWeight(QFont.Bold) #self.valid_value.setForeground(QColor.fromRgb(255,85,0)) self.valid_value.setForeground(Qt.blue) def highlightBlock(self, text): #this function is automatically called when some text is changed #in the texbox. 'text' is the line of text where the change occured #check if the line of text contains a valid command match = self.my_expression.exactMatch(text) if match: #valid command found: highlight the command in blue self.setFormat(0, len(text), self.valid) #highlight the parameters in orange #loop on all the parameters that can be captured for i in range(self.my_expression.captureCount()): #if a parameter was captured, it's position in the text will be >=0 and its capture contains some value 'xxx' #otherwise its position is -1 and its capture contains an empty string '' if self.my_expression.pos(i+1)!=-1: self.setFormat(self.my_expression.pos(i+1), len(self.my_expression.cap(i+1)), self.valid_value) else: #no valid command found: highlight in red self.setFormat(0, len(text), self.invalid)
def on_executeButton_clicked(self, startpos=0): """ Private slot to execute the entered regexp on the test text. This slot will execute the entered regexp on the entered test data and will display the result in the table part of the dialog. @param startpos starting position for the regexp matching """ regex = self.regexpLineEdit.text() text = self.textTextEdit.toPlainText() if regex and text: re = QRegExp(regex) if self.caseSensitiveCheckBox.isChecked(): re.setCaseSensitivity(Qt.CaseSensitive) else: re.setCaseSensitivity(Qt.CaseInsensitive) re.setMinimal(self.minimalCheckBox.isChecked()) syntax = self.syntaxCombo.itemData(self.syntaxCombo.currentIndex()) wildcard = syntax in [QRegExp.Wildcard, QRegExp.WildcardUnix] re.setPatternSyntax(syntax) if not re.isValid(): E5MessageBox.critical( self, self.tr("Error"), self.tr("""Invalid regular expression: {0}""") .format(re.errorString())) return offset = re.indexIn(text, startpos) captures = re.captureCount() row = 0 OFFSET = 5 self.resultTable.setColumnCount(0) self.resultTable.setColumnCount(3) self.resultTable.setRowCount(0) self.resultTable.setRowCount(OFFSET) self.resultTable.setItem( row, 0, QTableWidgetItem(self.tr("Regexp"))) self.resultTable.setItem(row, 1, QTableWidgetItem(regex)) if offset != -1: self.lastMatchEnd = offset + re.matchedLength() self.nextButton.setEnabled(True) row += 1 self.resultTable.setItem( row, 0, QTableWidgetItem(self.tr("Offset"))) self.resultTable.setItem( row, 1, QTableWidgetItem("{0:d}".format(offset))) if not wildcard: row += 1 self.resultTable.setItem( row, 0, QTableWidgetItem(self.tr("Captures"))) self.resultTable.setItem( row, 1, QTableWidgetItem("{0:d}".format(captures))) row += 1 self.resultTable.setItem( row, 1, QTableWidgetItem(self.tr("Text"))) self.resultTable.setItem( row, 2, QTableWidgetItem(self.tr("Characters"))) row += 1 self.resultTable.setItem( row, 0, QTableWidgetItem(self.tr("Match"))) self.resultTable.setItem( row, 1, QTableWidgetItem(re.cap(0))) self.resultTable.setItem( row, 2, QTableWidgetItem("{0:d}".format(re.matchedLength()))) if not wildcard: for i in range(1, captures + 1): if len(re.cap(i)) > 0: row += 1 self.resultTable.insertRow(row) self.resultTable.setItem( row, 0, QTableWidgetItem( self.tr("Capture #{0}").format(i))) self.resultTable.setItem( row, 1, QTableWidgetItem(re.cap(i))) self.resultTable.setItem( row, 2, QTableWidgetItem( "{0:d}".format(len(re.cap(i))))) else: self.resultTable.setRowCount(3) # highlight the matched text tc = self.textTextEdit.textCursor() tc.setPosition(offset) tc.setPosition(self.lastMatchEnd, QTextCursor.KeepAnchor) self.textTextEdit.setTextCursor(tc) else: self.nextButton.setEnabled(False) self.resultTable.setRowCount(2) row += 1 if startpos > 0: self.resultTable.setItem( row, 0, QTableWidgetItem(self.tr("No more matches"))) else: self.resultTable.setItem( row, 0, QTableWidgetItem(self.tr("No matches"))) # remove the highlight tc = self.textTextEdit.textCursor() tc.setPosition(0) self.textTextEdit.setTextCursor(tc) self.resultTable.resizeColumnsToContents() self.resultTable.resizeRowsToContents() self.resultTable.verticalHeader().hide() self.resultTable.horizontalHeader().hide() else: E5MessageBox.critical( self, self.tr("Error"), self.tr("""A regular expression and a text must""" """ be given."""))
class SvnChangeListsDialog(QDialog, Ui_SvnChangeListsDialog): """ Class implementing a dialog to browse the change lists. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnChangeListsDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.process = None self.vcs = vcs self.rx_status = QRegExp( '(.{8,9})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)\\s*') # flags (8 or 9 anything), revision, changed rev, author, path self.rx_status2 = \ QRegExp('(.{8,9})\\s+(.+)\\s*') # flags (8 or 9 anything), path self.rx_changelist = \ QRegExp('--- \\S+ .([\\w\\s]+).:\\s+') # three dashes, Changelist (translated), quote, # changelist name, quote, : @pyqtSlot(QListWidgetItem, QListWidgetItem) def on_changeLists_currentItemChanged(self, current, previous): """ Private slot to handle the selection of a new item. @param current current item (QListWidgetItem) @param previous previous current item (QListWidgetItem) """ self.filesList.clear() if current is not None: changelist = current.text() if changelist in self.changeListsDict: self.filesList.addItems( sorted(self.changeListsDict[changelist])) def start(self, path): """ Public slot to populate the data. @param path directory name to show change lists for (string) """ self.changeListsDict = {} self.filesLabel.setText( self.tr("Files (relative to {0}):").format(path)) self.errorGroup.hide() self.intercept = False self.path = path self.currentChangelist = "" self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) args = [] args.append('status') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['status']) if '--verbose' not in self.vcs.options['global'] and \ '--verbose' not in self.vcs.options['status']: args.append('--verbose') if isinstance(path, list): self.dname, fnames = self.vcs.splitPathList(path) self.vcs.addArguments(args, fnames) else: self.dname, fname = self.vcs.splitPath(path) args.append(fname) self.process.setWorkingDirectory(self.dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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 __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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() if len(self.changeListsDict) == 0: self.changeLists.addItem(self.tr("No changelists found")) self.buttonBox.button(QDialogButtonBox.Close).setFocus( Qt.OtherFocusReason) else: self.changeLists.addItems(sorted(self.changeListsDict.keys())) self.changeLists.setCurrentRow(0) self.changeLists.setFocus(Qt.OtherFocusReason) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() 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.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.currentChangelist != "" and \ self.rx_status.exactMatch(s): file = self.rx_status.cap(5).strip() filename = file.replace(self.path + os.sep, "") if filename not in \ self.changeListsDict[self.currentChangelist]: self.changeListsDict[self.currentChangelist].append( filename) elif self.currentChangelist != "" and \ self.rx_status2.exactMatch(s): file = self.rx_status2.cap(2).strip() filename = file.replace(self.path + os.sep, "") if filename not in \ self.changeListsDict[self.currentChangelist]: self.changeListsDict[self.currentChangelist].append( filename) elif self.rx_changelist.exactMatch(s): self.currentChangelist = self.rx_changelist.cap(1) if self.currentChangelist not in self.changeListsDict: self.changeListsDict[self.currentChangelist] = [] 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnChangeListsDialog, self).keyPressEvent(evt)
def __parseScript(self, path): """ Private method to parse the given script and populate the data structure. @param path path of the Javascript file (string) """ try: f = open(path, "r", encoding="utf-8") fileData = f.read() f.close() except (IOError, OSError): # silently ignore because it shouldn't happen return rx = QRegExp("// ==UserScript==(.*)// ==/UserScript==") rx.indexIn(fileData) metaDataBlock = rx.cap(1).strip() if metaDataBlock == "": # invalid script file return requireList = [] for line in metaDataBlock.splitlines(): if not line.startswith("// @"): continue line = line[3:].replace("\t", " ") index = line.find(" ") if index < 0: continue key = line[:index].strip() value = line[index + 1:].strip() # Ignored values: @resource, @unwrap if not key or not value: continue if key == "@name": self.__name = value elif key == "@namespace": self.__namespace = value elif key == "@description": self.__description = value elif key == "@version": self.__version = value elif key == "@updateURL": self.__downloadUrl = QUrl(value) elif key in ["@include", "@match"]: self.__include.append(GreaseMonkeyUrlMatcher(value)) elif key in ["@exclude", "@exclude_match"]: self.__exclude.append(GreaseMonkeyUrlMatcher(value)) elif key == "@require": requireList.append(value) elif key == "@run-at": if value == "document-end": self.__startAt = GreaseMonkeyScript.DocumentEnd elif value == "document-start": self.__startAt = GreaseMonkeyScript.DocumentStart elif key == "@downloadURL" and self.__downloadUrl.isEmpty(): self.__downloadUrl = QUrl(value) if not self.__include: self.__include.append(GreaseMonkeyUrlMatcher("*")) marker = "// ==/UserScript==" index = fileData.find(marker) + len(marker) script = fileData[index:].strip() script = "{0}{1}".format( self.__manager.requireScripts(requireList), script) self.__script = "(function(){{{0}}})();".format(script) self.__valid = len(script) > 0
class SvnLogDialog(QWidget, Ui_SvnLogDialog): """ Class implementing a dialog to show the output of the svn log command process. The dialog is nonmodal. Clicking a link in the upper text pane shows a diff of the versions. """ def __init__(self, vcs, isFile=False, parent=None): """ Constructor @param vcs reference to the vcs object @param isFile flag indicating log for a file is to be shown (boolean) @param parent parent widget (QWidget) """ super(SvnLogDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.process = QProcess() self.vcs = vcs self.contents.setHtml( self.tr('<b>Processing your request, please wait...</b>')) self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.contents.anchorClicked.connect(self.__sourceChanged) self.rx_sep = QRegExp('\\-+\\s*') self.rx_sep2 = QRegExp('=+\\s*') self.rx_rev = QRegExp( 'rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "rev" followed by one or more decimals followed by a colon followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_rev2 = QRegExp( 'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "r" followed by one or more decimals followed by " | " followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_flags = QRegExp(' ([ADM])( .*)\\s*') # three blanks followed by A or D or M self.rx_changed = QRegExp('Changed .*\\s*') self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified') } self.revisions = [] # stack of remembered revisions self.revString = self.tr('revision') self.buf = [] # buffer for stdout self.diff = None self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, fn, noEntries=0): """ Public slot to start the cvs log command. @param fn filename to show the log for (string) @param noEntries number of entries to show (integer) """ self.errorGroup.hide() QApplication.processEvents() self.intercept = False self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.process.kill() args = [] args.append('log') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['log']) if noEntries: args.append('--limit') args.append(str(noEntries)) self.activateWindow() self.raise_() args.append(self.fname) self.process.setWorkingDirectory(self.dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('svn')) 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.inputGroup.setEnabled(False) self.inputGroup.hide() self.contents.clear() lvers = 1 for s in self.buf: rev_match = False if self.rx_rev.exactMatch(s): ver = self.rx_rev.cap(1) author = self.rx_rev.cap(2) date = self.rx_rev.cap(3) # number of lines is ignored rev_match = True elif self.rx_rev2.exactMatch(s): ver = self.rx_rev2.cap(1) author = self.rx_rev2.cap(2) date = self.rx_rev2.cap(3) # number of lines is ignored rev_match = True if rev_match: dstr = '<b>{0} {1}</b>'.format(self.revString, ver) try: lv = self.revisions[lvers] lvers += 1 url = QUrl() url.setScheme("file") url.setPath(self.filename) if qVersion() >= "5.0.0": query = lv + '_' + ver url.setQuery(query) else: query = QByteArray() query.append(lv).append('_').append(ver) url.setEncodedQuery(query) dstr += ' [<a href="{0}" name="{1}">{2}</a>]'.format( url.toString(), query, self.tr('diff to {0}').format(lv), ) except IndexError: pass dstr += '<br />\n' self.contents.insertHtml(dstr) dstr = self.tr('<i>author: {0}</i><br />\n').format(author) self.contents.insertHtml(dstr) dstr = self.tr('<i>date: {0}</i><br />\n').format(date) self.contents.insertHtml(dstr) elif self.rx_sep.exactMatch(s) or self.rx_sep2.exactMatch(s): self.contents.insertHtml('<hr />\n') elif self.rx_flags.exactMatch(s): dstr = self.flags[self.rx_flags.cap(1)] dstr += self.rx_flags.cap(2) dstr += '<br />\n' self.contents.insertHtml(dstr) elif self.rx_changed.exactMatch(s): dstr = '<br />{0}<br />\n'.format(s) self.contents.insertHtml(dstr) else: if s == "": s = self.contents.insertHtml('<br />\n') else: self.contents.insertHtml(Utilities.html_encode(s)) self.contents.insertHtml('<br />\n') tc = self.contents.textCursor() tc.movePosition(QTextCursor.Start) self.contents.setTextCursor(tc) self.contents.ensureCursorVisible() def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.buf.append(line) if self.rx_rev.exactMatch(line): ver = self.rx_rev.cap(1) # save revision number for later use self.revisions.append(ver) elif self.rx_rev2.exactMatch(line): ver = self.rx_rev2.cap(1) # save revision number for later use self.revisions.append(ver) 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() def __sourceChanged(self, url): """ Private slot to handle the sourceChanged signal of the contents pane. @param url the url that was clicked (QUrl) """ self.contents.setSource(QUrl('')) filename = url.path() if Utilities.isWindowsPlatform(): if filename.startswith("/"): filename = filename[1:] if qVersion() >= "5.0.0": ver = url.query() else: ver = bytes(url.encodedQuery()).decode() v1 = ver.split('_')[0] v2 = ver.split('_')[1] if v1 == "" or v2 == "": return self.contents.scrollToAnchor(ver) if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(filename, revisions=(v1, v2)) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.start(filename, [v1, v2]) 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnLogDialog, self).keyPressEvent(evt)
def start(self, path, tags=True): """ Public slot to start the svn status command. @param path name of directory to be listed (string) @param tags flag indicating a list of tags is requested (False = branches, True = tags) @return flag indicating success (boolean) """ self.errorGroup.hide() self.tagList.clear() if not tags: self.setWindowTitle(self.tr("Subversion Branches List")) self.activateWindow() QApplication.processEvents() dname, fname = self.vcs.splitPath(path) reposURL = self.vcs.svnGetReposName(dname) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The list operation""" """ will be aborted""")) self.close() return False if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+)/(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository has an""" """ invalid format. The list operation will""" """ be aborted""")) return False reposRoot = rx_base.cap(1) if tags: path = "{0}/tags".format(reposRoot) else: path = "{0}/branches".format(reposRoot) else: reposPath, ok = QInputDialog.getText( self, self.tr("Subversion List"), self.tr("Enter the repository URL containing the" " tags or branches"), QLineEdit.Normal, self.vcs.svnNormalizeURL(reposURL)) if not ok: self.close() return False if not reposPath: E5MessageBox.critical( self, self.tr("Subversion List"), self.tr( """The repository URL is empty. Aborting...""")) self.close() return False path = reposPath locker = QMutexLocker(self.vcs.vcsExecutionMutex) self.tagsList = [] cwd = os.getcwd() os.chdir(dname) try: entries = self.client.list(path, recurse=False) # dirent, lock already unicode in Python 2 for dirent, lock in entries: if dirent["path"] != path: name = dirent["path"].replace(path + '/', "") self.__generateItem(dirent["created_rev"].number, dirent["last_author"], formatTime(dirent["time"]), name) if self.vcs.otherData["standardLayout"]: self.tagsList.append(name) else: self.tagsList.append(path + '/' + name) if self._clientCancelCallback(): break res = True except pysvn.ClientError as e: self.__showError(e.args[0]) res = False except AttributeError: self.__showError( self.tr("The installed version of PySvn should be" " 1.4.0 or better.")) res = False locker.unlock() self.__finish() os.chdir(cwd) return res
class VariantDelegate(QItemDelegate): def __init__(self, parent=None): super(VariantDelegate, self).__init__(parent) self.boolExp = QRegExp() self.boolExp.setPattern("true|false") self.boolExp.setCaseSensitivity(Qt.CaseInsensitive) self.byteArrayExp = QRegExp() self.byteArrayExp.setPattern("[\\x00-\\xff]*") self.charExp = QRegExp() self.charExp.setPattern(".") self.colorExp = QRegExp() self.colorExp.setPattern("\\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\\)") self.doubleExp = QRegExp() self.doubleExp.setPattern("") self.pointExp = QRegExp() self.pointExp.setPattern("\\((-?[0-9]*),(-?[0-9]*)\\)") self.rectExp = QRegExp() self.rectExp.setPattern("\\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\\)") self.signedIntegerExp = QRegExp() self.signedIntegerExp.setPattern("-?[0-9]*") self.sizeExp = QRegExp(self.pointExp) self.unsignedIntegerExp = QRegExp() self.unsignedIntegerExp.setPattern("[0-9]*") self.dateExp = QRegExp() self.dateExp.setPattern("([0-9]{,4})-([0-9]{,2})-([0-9]{,2})") self.timeExp = QRegExp() self.timeExp.setPattern("([0-9]{,2}):([0-9]{,2}):([0-9]{,2})") self.dateTimeExp = QRegExp() self.dateTimeExp.setPattern( self.dateExp.pattern() + "T" + self.timeExp.pattern() ) def paint(self, painter, option, index): if index.column() == 2: value = index.model().data(index, Qt.UserRole) if not self.isSupportedType(value): myOption = QStyleOptionViewItem(option) myOption.state &= ~QStyle.State_Enabled super(VariantDelegate, self).paint(painter, myOption, index) return super(VariantDelegate, self).paint(painter, option, index) def createEditor(self, parent, option, index): if index.column() != 2: return None originalValue = index.model().data(index, Qt.UserRole) if not self.isSupportedType(originalValue): return None lineEdit = QLineEdit(parent) lineEdit.setFrame(False) if isinstance(originalValue, bool): regExp = self.boolExp elif isinstance(originalValue, float): regExp = self.doubleExp elif isinstance(originalValue, int): regExp = self.signedIntegerExp elif isinstance(originalValue, QByteArray): regExp = self.byteArrayExp elif isinstance(originalValue, QColor): regExp = self.colorExp elif isinstance(originalValue, QDate): regExp = self.dateExp elif isinstance(originalValue, QDateTime): regExp = self.dateTimeExp elif isinstance(originalValue, QTime): regExp = self.timeExp elif isinstance(originalValue, QPoint): regExp = self.pointExp elif isinstance(originalValue, QRect): regExp = self.rectExp elif isinstance(originalValue, QSize): regExp = self.sizeExp else: regExp = QRegExp() if not regExp.isEmpty(): validator = QRegExpValidator(regExp, lineEdit) lineEdit.setValidator(validator) return lineEdit def setEditorData(self, editor, index): value = index.model().data(index, Qt.UserRole) if editor is not None: editor.setText(self.displayText(value)) def setModelData(self, editor, model, index): if not editor.isModified(): return text = editor.text() validator = editor.validator() if validator is not None: state, text, _ = validator.validate(text, 0) if state != QValidator.Acceptable: return originalValue = index.model().data(index, Qt.UserRole) if isinstance(originalValue, QColor): self.colorExp.exactMatch(text) value = QColor( min(int(self.colorExp.cap(1)), 255), min(int(self.colorExp.cap(2)), 255), min(int(self.colorExp.cap(3)), 255), min(int(self.colorExp.cap(4)), 255), ) elif isinstance(originalValue, QDate): value = QDate.fromString(text, Qt.ISODate) if not value.isValid(): return elif isinstance(originalValue, QDateTime): value = QDateTime.fromString(text, Qt.ISODate) if not value.isValid(): return elif isinstance(originalValue, QTime): value = QTime.fromString(text, Qt.ISODate) if not value.isValid(): return elif isinstance(originalValue, QPoint): self.pointExp.exactMatch(text) value = QPoint(int(self.pointExp.cap(1)), int(self.pointExp.cap(2))) elif isinstance(originalValue, QRect): self.rectExp.exactMatch(text) value = QRect( int(self.rectExp.cap(1)), int(self.rectExp.cap(2)), int(self.rectExp.cap(3)), int(self.rectExp.cap(4)), ) elif isinstance(originalValue, QSize): self.sizeExp.exactMatch(text) value = QSize(int(self.sizeExp.cap(1)), int(self.sizeExp.cap(2))) elif isinstance(originalValue, list): value = text.split(",") else: value = type(originalValue)(text) model.setData(index, self.displayText(value), Qt.DisplayRole) model.setData(index, value, Qt.UserRole) @staticmethod def isSupportedType(value): return isinstance( value, ( bool, float, int, QByteArray, str, QColor, QDate, QDateTime, QTime, QPoint, QRect, QSize, list, ), ) @staticmethod def displayText(value): if isinstance(value, (bool, int, QByteArray)): return str(value) if isinstance(value, str): return value elif isinstance(value, float): return "%g" % value elif isinstance(value, QColor): return "(%u,%u,%u,%u)" % ( value.red(), value.green(), value.blue(), value.alpha(), ) elif isinstance(value, (QDate, QDateTime, QTime)): return value.toString(Qt.ISODate) elif isinstance(value, QPoint): return "(%d,%d)" % (value.x(), value.y()) elif isinstance(value, QRect): return "(%d,%d,%d,%d)" % ( value.x(), value.y(), value.width(), value.height(), ) elif isinstance(value, QSize): return "(%d,%d)" % (value.width(), value.height()) elif isinstance(value, list): return ",".join(value) elif value is None: return "<Invalid>" return "<%s>" % value
class VariantDelegate(QItemDelegate): def __init__(self, parent=None): super(VariantDelegate, self).__init__(parent) self.boolExp = QRegExp() self.boolExp.setPattern('true|false') self.boolExp.setCaseSensitivity(Qt.CaseInsensitive) self.byteArrayExp = QRegExp() self.byteArrayExp.setPattern('[\\x00-\\xff]*') self.charExp = QRegExp() self.charExp.setPattern('.') self.colorExp = QRegExp() self.colorExp.setPattern('\\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\\)') self.doubleExp = QRegExp() self.doubleExp.setPattern('') self.pointExp = QRegExp() self.pointExp.setPattern('\\((-?[0-9]*),(-?[0-9]*)\\)') self.rectExp = QRegExp() self.rectExp.setPattern('\\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\\)') self.signedIntegerExp = QRegExp() self.signedIntegerExp.setPattern('-?[0-9]*') self.sizeExp = QRegExp(self.pointExp) self.unsignedIntegerExp = QRegExp() self.unsignedIntegerExp.setPattern('[0-9]*') self.dateExp = QRegExp() self.dateExp.setPattern('([0-9]{,4})-([0-9]{,2})-([0-9]{,2})') self.timeExp = QRegExp() self.timeExp.setPattern('([0-9]{,2}):([0-9]{,2}):([0-9]{,2})') self.dateTimeExp = QRegExp() self.dateTimeExp.setPattern(self.dateExp.pattern() + 'T' + self.timeExp.pattern()) def paint(self, painter, option, index): if index.column() == 2: value = index.model().data(index, Qt.UserRole) if not self.isSupportedType(value): myOption = QStyleOptionViewItem(option) myOption.state &= ~QStyle.State_Enabled super(VariantDelegate, self).paint(painter, myOption, index) return super(VariantDelegate, self).paint(painter, option, index) def createEditor(self, parent, option, index): if index.column() != 2: return None originalValue = index.model().data(index, Qt.UserRole) if not self.isSupportedType(originalValue): return None lineEdit = QLineEdit(parent) lineEdit.setFrame(False) if isinstance(originalValue, bool): regExp = self.boolExp elif isinstance(originalValue, float): regExp = self.doubleExp elif isinstance(originalValue, int): regExp = self.signedIntegerExp elif isinstance(originalValue, QByteArray): regExp = self.byteArrayExp elif isinstance(originalValue, QColor): regExp = self.colorExp elif isinstance(originalValue, QDate): regExp = self.dateExp elif isinstance(originalValue, QDateTime): regExp = self.dateTimeExp elif isinstance(originalValue, QTime): regExp = self.timeExp elif isinstance(originalValue, QPoint): regExp = self.pointExp elif isinstance(originalValue, QRect): regExp = self.rectExp elif isinstance(originalValue, QSize): regExp = self.sizeExp else: regExp = QRegExp() if not regExp.isEmpty(): validator = QRegExpValidator(regExp, lineEdit) lineEdit.setValidator(validator) return lineEdit def setEditorData(self, editor, index): value = index.model().data(index, Qt.UserRole) if editor is not None: editor.setText(self.displayText(value)) def setModelData(self, editor, model, index): if not editor.isModified(): return text = editor.text() validator = editor.validator() if validator is not None: state, text, _ = validator.validate(text, 0) if state != QValidator.Acceptable: return originalValue = index.model().data(index, Qt.UserRole) if isinstance(originalValue, QColor): self.colorExp.exactMatch(text) value = QColor(min(int(self.colorExp.cap(1)), 255), min(int(self.colorExp.cap(2)), 255), min(int(self.colorExp.cap(3)), 255), min(int(self.colorExp.cap(4)), 255)) elif isinstance(originalValue, QDate): value = QDate.fromString(text, Qt.ISODate) if not value.isValid(): return elif isinstance(originalValue, QDateTime): value = QDateTime.fromString(text, Qt.ISODate) if not value.isValid(): return elif isinstance(originalValue, QTime): value = QTime.fromString(text, Qt.ISODate) if not value.isValid(): return elif isinstance(originalValue, QPoint): self.pointExp.exactMatch(text) value = QPoint(int(self.pointExp.cap(1)), int(self.pointExp.cap(2))) elif isinstance(originalValue, QRect): self.rectExp.exactMatch(text) value = QRect(int(self.rectExp.cap(1)), int(self.rectExp.cap(2)), int(self.rectExp.cap(3)), int(self.rectExp.cap(4))) elif isinstance(originalValue, QSize): self.sizeExp.exactMatch(text) value = QSize(int(self.sizeExp.cap(1)), int(self.sizeExp.cap(2))) elif isinstance(originalValue, list): value = text.split(',') else: value = type(originalValue)(text) model.setData(index, self.displayText(value), Qt.DisplayRole) model.setData(index, value, Qt.UserRole) @staticmethod def isSupportedType(value): return isinstance(value, (bool, float, int, QByteArray, str, QColor, QDate, QDateTime, QTime, QPoint, QRect, QSize, list)) @staticmethod def displayText(value): if isinstance(value, (bool, int, QByteArray)): return str(value) if isinstance(value, str): return value elif isinstance(value, float): return '%g' % value elif isinstance(value, QColor): return '(%u,%u,%u,%u)' % (value.red(), value.green(), value.blue(), value.alpha()) elif isinstance(value, (QDate, QDateTime, QTime)): return value.toString(Qt.ISODate) elif isinstance(value, QPoint): return '(%d,%d)' % (value.x(), value.y()) elif isinstance(value, QRect): return '(%d,%d,%d,%d)' % (value.x(), value.y(), value.width(), value.height()) elif isinstance(value, QSize): return '(%d,%d)' % (value.width(), value.height()) elif isinstance(value, list): return ','.join(value) elif value is None: return '<Invalid>' return '<%s>' % value
def __parseScript(self): """ Private method to parse the given script and populate the data structure. """ self.__name = "" self.__namespace = "GreaseMonkeyNS" self.__description = "" self.__version = "" self.__include = [] self.__exclude = [] self.__require = [] self.__icon = QIcon() self.__iconUrl = QUrl() self.__downloadUrl = QUrl() self.__updateUrl = QUrl() self.__startAt = GreaseMonkeyScript.DocumentEnd self.__script = "" self.__enabled = True self.__valid = False self.__noFrames = False try: f = open(self.__fileName, "r", encoding="utf-8") fileData = f.read() f.close() except (IOError, OSError): # silently ignore because it shouldn't happen return if self.__fileName not in self.__fileWatcher.files(): self.__fileWatcher.addPath(self.__fileName) rx = QRegExp("// ==UserScript==(.*)// ==/UserScript==") rx.indexIn(fileData) metaDataBlock = rx.cap(1).strip() if metaDataBlock == "": # invalid script file return for line in metaDataBlock.splitlines(): if not line.strip(): continue if not line.startswith("// @"): continue line = line[3:].replace("\t", " ") index = line.find(" ") key = line[:index].strip() if index > 0: value = line[index + 1:].strip() else: value = "" if not key: continue if key == "@name": self.__name = value elif key == "@namespace": self.__namespace = value elif key == "@description": self.__description = value elif key == "@version": self.__version = value elif key in ["@include", "@match"]: self.__include.append(value) elif key in ["@exclude", "@exclude_match"]: self.__exclude.append(value) elif key == "@require": self.__require.append(value) elif key == "@run-at": if value == "document-end": self.__startAt = GreaseMonkeyScript.DocumentEnd elif value == "document-start": self.__startAt = GreaseMonkeyScript.DocumentStart elif value == "document-idle": self.__startAt = GreaseMonkeyScript.DocumentIdle elif key == "@downloadURL" and self.__downloadUrl.isEmpty(): self.__downloadUrl = QUrl(value) elif key == "@updateURL" and self.__updateUrl.isEmpty(): self.__updateUrl = QUrl(value) elif key == "@icon": self.__iconUrl = QUrl(value) elif key == "@noframes": self.__noFrames = True self.__iconUrl = self.__downloadUrl.resolved(self.__iconUrl) if not self.__include: self.__include.append("*") nspace = bytes( QCryptographicHash.hash( QByteArray(self.fullName().encode("utf-8")), QCryptographicHash.Md4).toHex()).decode("ascii") valuesScript = values_js.format(nspace) self.__script = "(function(){{{0}\n{1}\n{2}\n}})();".format( valuesScript, self.__manager.requireScripts(self.__require), fileData) self.__valid = True self.__downloadIcon() self.__downloadRequires()
class CompleterRuby(CompleterBase): """ Class implementing typing completer for Ruby. """ def __init__(self, editor, parent=None): """ Constructor @param editor reference to the editor object (QScintilla.Editor) @param parent reference to the parent object (QObject) """ CompleterBase.__init__(self, editor, parent) self.__beginRX = QRegExp(r"""^=begin """) self.__beginNlRX = QRegExp(r"""^=begin\r?\n""") self.__hereRX = QRegExp(r"""<<-?['"]?(\w*)['"]?\r?\n""") self.readSettings() def readSettings(self): """ Public slot called to reread the configuration parameters. """ self.setEnabled(Preferences.getEditorTyping("Ruby/EnabledTypingAids")) self.__insertClosingBrace = Preferences.getEditorTyping("Ruby/InsertClosingBrace") self.__indentBrace = Preferences.getEditorTyping("Ruby/IndentBrace") self.__skipBrace = Preferences.getEditorTyping("Ruby/SkipBrace") self.__insertQuote = Preferences.getEditorTyping("Ruby/InsertQuote") self.__insertBlank = Preferences.getEditorTyping("Ruby/InsertBlank") self.__insertHereDoc = Preferences.getEditorTyping("Ruby/InsertHereDoc") self.__insertInlineDoc = Preferences.getEditorTyping("Ruby/InsertInlineDoc") def charAdded(self, charNumber): """ Public slot called to handle the user entering a character. @param charNumber value of the character entered (integer) """ char = chr(charNumber) if char not in ["(", ")", "{", "}", "[", "]", ",", "'", '"', "\n", " "]: return # take the short route line, col = self.editor.getCursorPosition() if ( self.__inComment(line, col) or self.__inDoubleQuotedString() or self.__inSingleQuotedString() or self.__inHereDocument() or self.__inInlineDocument() ): return # open parenthesis # insert closing parenthesis and self if char == "(": txt = self.editor.text(line)[:col] if self.__insertClosingBrace: self.editor.insert(")") # closing parenthesis # skip matching closing parenthesis elif char in [")", "}", "]"]: txt = self.editor.text(line) if col < len(txt) and char == txt[col]: if self.__skipBrace: self.editor.setSelection(line, col, line, col + 1) self.editor.removeSelectedText() # space # complete inline documentation elif char == " ": txt = self.editor.text(line)[:col] if self.__insertInlineDoc and self.__beginRX.exactMatch(txt): self.editor.insert("=end") # comma # insert blank elif char == ",": if self.__insertBlank: self.editor.insert(" ") self.editor.setCursorPosition(line, col + 1) # open curly brace # insert closing brace elif char == "{": if self.__insertClosingBrace: self.editor.insert("}") # open bracket # insert closing bracket elif char == "[": if self.__insertClosingBrace: self.editor.insert("]") # double quote # insert double quote elif char == '"': if self.__insertQuote: self.editor.insert('"') # quote # insert quote elif char == "'": if self.__insertQuote: self.editor.insert("'") # new line # indent to opening brace, complete inline documentation elif char == "\n": txt = self.editor.text(line - 1) if self.__insertInlineDoc and self.__beginNlRX.exactMatch(txt): self.editor.insert("=end") elif self.__insertHereDoc and self.__hereRX.exactMatch(txt): self.editor.insert(self.__hereRX.cap(1)) elif self.__indentBrace and re.search(":\r?\n", txt) is None: openCount = len(re.findall("[({[]", txt)) closeCount = len(re.findall("[)}\]]", txt)) if openCount > closeCount: openCount = 0 closeCount = 0 openList = list(re.finditer("[({[]", txt)) index = len(openList) - 1 while index > -1 and openCount == closeCount: lastOpenIndex = openList[index].start() txt2 = txt[lastOpenIndex:] openCount = len(re.findall("[({[]", txt2)) closeCount = len(re.findall("[)}\]]", txt2)) index -= 1 if openCount > closeCount and lastOpenIndex > col: self.editor.insert(" " * (lastOpenIndex - col + 1)) self.editor.setCursorPosition(line, lastOpenIndex + 1) def __inComment(self, line, col): """ Private method to check, if the cursor is inside a comment. @param line current line (integer) @param col current position within line (integer) @return flag indicating, if the cursor is inside a comment (boolean) """ txt = self.editor.text(line) if col == len(txt): col -= 1 while col >= 0: if txt[col] == "#": return True col -= 1 return False def __inDoubleQuotedString(self): """ Private method to check, if the cursor is within a double quoted string. @return flag indicating, if the cursor is inside a double quoted string (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.DoubleQuotedString def __inSingleQuotedString(self): """ Private method to check, if the cursor is within a single quoted string. @return flag indicating, if the cursor is inside a single quoted string (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.SingleQuotedString def __inHereDocument(self): """ Private method to check, if the cursor is within a here document. @return flag indicating, if the cursor is inside a here document (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.HereDocument def __inInlineDocument(self): """ Private method to check, if the cursor is within an inline document. @return flag indicating, if the cursor is inside an inline document (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.POD
class CompleterRuby(CompleterBase): """ Class implementing typing completer for Ruby. """ def __init__(self, editor, parent=None): """ Constructor @param editor reference to the editor object (QScintilla.Editor) @param parent reference to the parent object (QObject) """ CompleterBase.__init__(self, editor, parent) self.__beginRX = QRegExp(r"""^=begin """) self.__beginNlRX = QRegExp(r"""^=begin\r?\n""") self.__hereRX = QRegExp(r"""<<-?['"]?(\w*)['"]?\r?\n""") self.readSettings() def readSettings(self): """ Public slot called to reread the configuration parameters. """ self.setEnabled(Preferences.getEditorTyping("Ruby/EnabledTypingAids")) self.__insertClosingBrace = \ Preferences.getEditorTyping("Ruby/InsertClosingBrace") self.__indentBrace = \ Preferences.getEditorTyping("Ruby/IndentBrace") self.__skipBrace = \ Preferences.getEditorTyping("Ruby/SkipBrace") self.__insertQuote = \ Preferences.getEditorTyping("Ruby/InsertQuote") self.__insertBlank = \ Preferences.getEditorTyping("Ruby/InsertBlank") self.__insertHereDoc = \ Preferences.getEditorTyping("Ruby/InsertHereDoc") self.__insertInlineDoc = \ Preferences.getEditorTyping("Ruby/InsertInlineDoc") def charAdded(self, charNumber): """ Public slot called to handle the user entering a character. @param charNumber value of the character entered (integer) """ char = chr(charNumber) if char not in [ '(', ')', '{', '}', '[', ']', ',', "'", '"', '\n', ' ' ]: return # take the short route line, col = self.editor.getCursorPosition() if self.__inComment(line, col) or \ self.__inDoubleQuotedString() or \ self.__inSingleQuotedString() or \ self.__inHereDocument() or \ self.__inInlineDocument(): return # open parenthesis # insert closing parenthesis and self if char == '(': txt = self.editor.text(line)[:col] if self.__insertClosingBrace: self.editor.insert(')') # closing parenthesis # skip matching closing parenthesis elif char in [')', '}', ']']: txt = self.editor.text(line) if col < len(txt) and char == txt[col]: if self.__skipBrace: self.editor.setSelection(line, col, line, col + 1) self.editor.removeSelectedText() # space # complete inline documentation elif char == ' ': txt = self.editor.text(line)[:col] if self.__insertInlineDoc and self.__beginRX.exactMatch(txt): self.editor.insert('=end') # comma # insert blank elif char == ',': if self.__insertBlank: self.editor.insert(' ') self.editor.setCursorPosition(line, col + 1) # open curly brace # insert closing brace elif char == '{': if self.__insertClosingBrace: self.editor.insert('}') # open bracket # insert closing bracket elif char == '[': if self.__insertClosingBrace: self.editor.insert(']') # double quote # insert double quote elif char == '"': if self.__insertQuote: self.editor.insert('"') # quote # insert quote elif char == '\'': if self.__insertQuote: self.editor.insert('\'') # new line # indent to opening brace, complete inline documentation elif char == '\n': txt = self.editor.text(line - 1) if self.__insertInlineDoc and self.__beginNlRX.exactMatch(txt): self.editor.insert('=end') elif self.__insertHereDoc and self.__hereRX.exactMatch(txt): self.editor.insert(self.__hereRX.cap(1)) elif self.__indentBrace and re.search(":\r?\n", txt) is None: openCount = len(re.findall("[({[]", txt)) closeCount = len(re.findall("[)}\]]", txt)) if openCount > closeCount: openCount = 0 closeCount = 0 openList = list(re.finditer("[({[]", txt)) index = len(openList) - 1 while index > -1 and openCount == closeCount: lastOpenIndex = openList[index].start() txt2 = txt[lastOpenIndex:] openCount = len(re.findall("[({[]", txt2)) closeCount = len(re.findall("[)}\]]", txt2)) index -= 1 if openCount > closeCount and lastOpenIndex > col: self.editor.insert(' ' * (lastOpenIndex - col + 1)) self.editor.setCursorPosition(line, lastOpenIndex + 1) def __inComment(self, line, col): """ Private method to check, if the cursor is inside a comment. @param line current line (integer) @param col current position within line (integer) @return flag indicating, if the cursor is inside a comment (boolean) """ txt = self.editor.text(line) if col == len(txt): col -= 1 while col >= 0: if txt[col] == "#": return True col -= 1 return False def __inDoubleQuotedString(self): """ Private method to check, if the cursor is within a double quoted string. @return flag indicating, if the cursor is inside a double quoted string (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.DoubleQuotedString def __inSingleQuotedString(self): """ Private method to check, if the cursor is within a single quoted string. @return flag indicating, if the cursor is inside a single quoted string (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.SingleQuotedString def __inHereDocument(self): """ Private method to check, if the cursor is within a here document. @return flag indicating, if the cursor is inside a here document (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.HereDocument def __inInlineDocument(self): """ Private method to check, if the cursor is within an inline document. @return flag indicating, if the cursor is inside an inline document (boolean) """ return self.editor.currentStyle() == QsciLexerRuby.POD
class SvnPropListDialog(QWidget, Ui_SvnPropListDialog): """ Class implementing a dialog to show the output of the svn proplist command process. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnPropListDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.process = QProcess() env = QProcessEnvironment.systemEnvironment() env.insert("LANG", "C") self.process.setProcessEnvironment(env) self.vcs = vcs self.propsList.headerItem().setText(self.propsList.columnCount(), "") self.propsList.header().setSortIndicator(0, Qt.AscendingOrder) self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_path = QRegExp(r"Properties on '([^']+)':\s*") self.rx_prop = QRegExp(r" (.*) *: *(.*)[\r\n]") self.lastPath = None self.lastProp = None self.propBuffer = "" def __resort(self): """ Private method to resort the tree. """ self.propsList.sortItems(self.propsList.sortColumn(), self.propsList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.propsList.header().resizeSections(QHeaderView.ResizeToContents) self.propsList.header().setStretchLastSection(True) def __generateItem(self, path, propName, propValue): """ Private method to generate a properties item in the properties list. @param path file/directory name the property applies to (string) @param propName name of the property (string) @param propValue value of the property (string) """ QTreeWidgetItem(self.propsList, [path, propName, propValue.strip()]) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, fn, recursive=False): """ Public slot to start the svn status command. @param fn filename(s) (string or list of string) @param recursive flag indicating a recursive list is requested """ self.errorGroup.hide() self.process.kill() args = [] args.append('proplist') self.vcs.addArguments(args, self.vcs.options['global']) args.append('--verbose') if recursive: args.append('--recursive') if isinstance(fn, list): dname, fnames = self.vcs.splitPathList(fn) self.vcs.addArguments(args, fnames) else: dname, fname = self.vcs.splitPath(fn) args.append(fname) self.process.setWorkingDirectory(dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('svn')) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.process = None if self.lastProp: self.__generateItem(self.lastPath, self.lastProp, self.propBuffer) self.__resort() self.__resizeColumns() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() 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) """ if self.lastPath is None: self.__generateItem('', 'None', '') 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. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.rx_path.exactMatch(s): if self.lastProp: self.__generateItem(self.lastPath, self.lastProp, self.propBuffer) self.lastPath = self.rx_path.cap(1) self.lastProp = None self.propBuffer = "" elif self.rx_prop.exactMatch(s): if self.lastProp: self.__generateItem(self.lastPath, self.lastProp, self.propBuffer) self.lastProp = self.rx_prop.cap(1) self.propBuffer = self.rx_prop.cap(2) else: self.propBuffer += ' ' self.propBuffer += s 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible()
class SvnTagBranchListDialog(QDialog, Ui_SvnTagBranchListDialog): """ Class implementing a dialog to show a list of tags or branches. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnTagBranchListDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.process = QProcess() self.vcs = vcs self.tagsList = None self.allTagsList = None self.tagList.headerItem().setText(self.tagList.columnCount(), "") self.tagList.header().setSortIndicator(3, Qt.AscendingOrder) self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_list = QRegExp( r"""\w*\s*(\d+)\s+(\w+)\s+\d*\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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, path, tags, tagsList, allTagsList): """ Public slot to start the svn status command. @param path name of directory to be listed (string) @param tags flag indicating a list of tags is requested (False = branches, True = tags) @param tagsList reference to string list receiving the tags (list of strings) @param allTagsList reference to string list all tags (list of strings) """ self.errorGroup.hide() self.intercept = False if not tags: self.setWindowTitle(self.tr("Subversion Branches List")) self.activateWindow() self.tagsList = tagsList self.allTagsList = allTagsList dname, fname = self.vcs.splitPath(path) self.process.kill() reposURL = self.vcs.svnGetReposName(dname) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The list operation""" """ will be aborted""")) self.close() return args = [] args.append('list') self.vcs.addArguments(args, self.vcs.options['global']) args.append('--verbose') if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+)/(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr("""The URL of the project repository has an""" """ invalid format. The list operation will""" """ be aborted""")) return reposRoot = rx_base.cap(1) if tags: args.append("{0}/tags".format(reposRoot)) else: args.append("{0}/branches".format(reposRoot)) self.path = None else: reposPath, ok = QInputDialog.getText( self, self.tr("Subversion List"), self.tr("Enter the repository URL containing the tags" " or branches"), QLineEdit.Normal, self.vcs.svnNormalizeURL(reposURL)) if not ok: self.close() return if not reposPath: E5MessageBox.critical( self, self.tr("Subversion List"), self.tr("""The repository URL is empty.""" """ Aborting...""")) self.close() return args.append(reposPath) self.path = reposPath self.process.setWorkingDirectory(dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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 __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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.buttonBox.button(QDialogButtonBox.Close).setFocus( Qt.OtherFocusReason) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.process = None self.__resizeColumns() self.__resort() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() 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 __resort(self): """ Private method to resort the tree. """ self.tagList.sortItems(self.tagList.sortColumn(), self.tagList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.tagList.header().resizeSections(QHeaderView.ResizeToContents) self.tagList.header().setStretchLastSection(True) def __generateItem(self, revision, author, date, name): """ Private method to generate a tag item in the taglist. @param revision revision string (string) @param author author of the tag (string) @param date date of the tag (string) @param name name (path) of the tag (string) """ itm = QTreeWidgetItem(self.tagList) itm.setData(0, Qt.DisplayRole, int(revision)) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, date) itm.setData(3, Qt.DisplayRole, name) itm.setTextAlignment(0, Qt.AlignRight) def __readStdout(self): """ Private slot to handle the readyReadStdout signal. It reads the output of the process, formats it and inserts it into the contents pane. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.rx_list.exactMatch(s): rev = "{0:6}".format(self.rx_list.cap(1)) author = self.rx_list.cap(2) date = self.rx_list.cap(3) path = self.rx_list.cap(4) if path == ".": continue self.__generateItem(rev, author, date, path) if not self.vcs.otherData["standardLayout"]: path = self.path + '/' + path if self.tagsList is not None: self.tagsList.append(path) if self.allTagsList is not None: self.allTagsList.append(path) def __readStderr(self): """ Private slot to handle the readyReadStderr signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnTagBranchListDialog, self).keyPressEvent(evt)
class SvnLogDialog(QWidget, Ui_SvnLogDialog): """ Class implementing a dialog to show the output of the svn log command process. The dialog is nonmodal. Clicking a link in the upper text pane shows a diff of the versions. """ def __init__(self, vcs, isFile=False, parent=None): """ Constructor @param vcs reference to the vcs object @param isFile flag indicating log for a file is to be shown (boolean) @param parent parent widget (QWidget) """ super(SvnLogDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.process = QProcess() self.vcs = vcs self.contents.setHtml( self.tr('<b>Processing your request, please wait...</b>')) self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.contents.anchorClicked.connect(self.__sourceChanged) self.rx_sep = QRegExp('\\-+\\s*') self.rx_sep2 = QRegExp('=+\\s*') self.rx_rev = QRegExp( 'rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "rev" followed by one or more decimals followed by a colon followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_rev2 = QRegExp( 'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "r" followed by one or more decimals followed by " | " followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_flags = QRegExp(' ([ADM])( .*)\\s*') # three blanks followed by A or D or M self.rx_changed = QRegExp('Changed .*\\s*') self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified') } self.revisions = [] # stack of remembered revisions self.revString = self.tr('revision') self.buf = [] # buffer for stdout self.diff = None self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, fn, noEntries=0): """ Public slot to start the cvs log command. @param fn filename to show the log for (string) @param noEntries number of entries to show (integer) """ self.errorGroup.hide() QApplication.processEvents() self.intercept = False self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.process.kill() args = [] args.append('log') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['log']) if noEntries: args.append('--limit') args.append(str(noEntries)) self.activateWindow() self.raise_() args.append(self.fname) self.process.setWorkingDirectory(self.dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('svn')) 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.inputGroup.setEnabled(False) self.inputGroup.hide() self.contents.clear() lvers = 1 for s in self.buf: rev_match = False if self.rx_rev.exactMatch(s): ver = self.rx_rev.cap(1) author = self.rx_rev.cap(2) date = self.rx_rev.cap(3) # number of lines is ignored rev_match = True elif self.rx_rev2.exactMatch(s): ver = self.rx_rev2.cap(1) author = self.rx_rev2.cap(2) date = self.rx_rev2.cap(3) # number of lines is ignored rev_match = True if rev_match: dstr = '<b>{0} {1}</b>'.format(self.revString, ver) try: lv = self.revisions[lvers] lvers += 1 url = QUrl() url.setScheme("file") url.setPath(self.filename) if qVersion() >= "5.0.0": query = lv + '_' + ver url.setQuery(query) else: query = QByteArray() query.append(lv).append('_').append(ver) url.setEncodedQuery(query) dstr += ' [<a href="{0}" name="{1}">{2}</a>]'.format( url.toString(), query, self.tr('diff to {0}').format(lv), ) except IndexError: pass dstr += '<br />\n' self.contents.insertHtml(dstr) dstr = self.tr('<i>author: {0}</i><br />\n').format(author) self.contents.insertHtml(dstr) dstr = self.tr('<i>date: {0}</i><br />\n').format(date) self.contents.insertHtml(dstr) elif self.rx_sep.exactMatch(s) or self.rx_sep2.exactMatch(s): self.contents.insertHtml('<hr />\n') elif self.rx_flags.exactMatch(s): dstr = self.flags[self.rx_flags.cap(1)] dstr += self.rx_flags.cap(2) dstr += '<br />\n' self.contents.insertHtml(dstr) elif self.rx_changed.exactMatch(s): dstr = '<br />{0}<br />\n'.format(s) self.contents.insertHtml(dstr) else: if s == "": s = self.contents.insertHtml('<br />\n') else: self.contents.insertHtml(Utilities.html_encode(s)) self.contents.insertHtml('<br />\n') tc = self.contents.textCursor() tc.movePosition(QTextCursor.Start) self.contents.setTextCursor(tc) self.contents.ensureCursorVisible() def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.buf.append(line) if self.rx_rev.exactMatch(line): ver = self.rx_rev.cap(1) # save revision number for later use self.revisions.append(ver) elif self.rx_rev2.exactMatch(line): ver = self.rx_rev2.cap(1) # save revision number for later use self.revisions.append(ver) 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() def __sourceChanged(self, url): """ Private slot to handle the sourceChanged signal of the contents pane. @param url the url that was clicked (QUrl) """ self.contents.setSource(QUrl('')) filename = url.path() if Utilities.isWindowsPlatform(): if filename.startswith("/"): filename = filename[1:] if qVersion() >= "5.0.0": ver = url.query() else: ver = bytes(url.encodedQuery()).decode() v1 = ver.split('_')[0] v2 = ver.split('_')[1] if v1 == "" or v2 == "": return self.contents.scrollToAnchor(ver) if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(filename, revisions=(v1, v2)) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.start(filename, [v1, v2]) 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnLogDialog, self).keyPressEvent(evt)
class SvnTagBranchListDialog(QDialog, Ui_SvnTagBranchListDialog): """ Class implementing a dialog to show a list of tags or branches. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnTagBranchListDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.vcs = vcs self.tagsList = None self.allTagsList = None self.tagList.headerItem().setText(self.tagList.columnCount(), "") self.tagList.header().setSortIndicator(3, Qt.AscendingOrder) self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_list = QRegExp( r"""\w*\s*(\d+)\s+(\w+)\s+\d*\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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, path, tags, tagsList, allTagsList): """ Public slot to start the svn status command. @param path name of directory to be listed (string) @param tags flag indicating a list of tags is requested (False = branches, True = tags) @param tagsList reference to string list receiving the tags (list of strings) @param allTagsList reference to string list all tags (list of strings) """ self.errorGroup.hide() self.tagList.clear() self.intercept = False if not tags: self.setWindowTitle(self.tr("Subversion Branches List")) self.activateWindow() self.tagsList = tagsList self.allTagsList = allTagsList dname, fname = self.vcs.splitPath(path) self.process.kill() reposURL = self.vcs.svnGetReposName(dname) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The list operation""" """ will be aborted""")) self.close() return args = [] args.append('list') self.vcs.addArguments(args, self.vcs.options['global']) args.append('--verbose') if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+)/(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository has an""" """ invalid format. The list operation will""" """ be aborted""")) return reposRoot = rx_base.cap(1) if tags: args.append("{0}/tags".format(reposRoot)) else: args.append("{0}/branches".format(reposRoot)) self.path = None else: reposPath, ok = QInputDialog.getText( self, self.tr("Subversion List"), self.tr("Enter the repository URL containing the tags" " or branches"), QLineEdit.Normal, self.vcs.svnNormalizeURL(reposURL)) if not ok: self.close() return if not reposPath: E5MessageBox.critical( self, self.tr("Subversion List"), self.tr("""The repository URL is empty.""" """ Aborting...""")) self.close() return args.append(reposPath) self.path = reposPath self.process.setWorkingDirectory(dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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 __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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.buttonBox.button(QDialogButtonBox.Close).setFocus( Qt.OtherFocusReason) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.__resizeColumns() self.__resort() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() 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 __resort(self): """ Private method to resort the tree. """ self.tagList.sortItems( self.tagList.sortColumn(), self.tagList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.tagList.header().resizeSections(QHeaderView.ResizeToContents) self.tagList.header().setStretchLastSection(True) def __generateItem(self, revision, author, date, name): """ Private method to generate a tag item in the taglist. @param revision revision string (string) @param author author of the tag (string) @param date date of the tag (string) @param name name (path) of the tag (string) """ itm = QTreeWidgetItem(self.tagList) itm.setData(0, Qt.DisplayRole, int(revision)) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, date) itm.setData(3, Qt.DisplayRole, name) itm.setTextAlignment(0, Qt.AlignRight) def __readStdout(self): """ Private slot to handle the readyReadStdout signal. It reads the output of the process, formats it and inserts it into the contents pane. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.rx_list.exactMatch(s): rev = "{0:6}".format(self.rx_list.cap(1)) author = self.rx_list.cap(2) date = self.rx_list.cap(3) path = self.rx_list.cap(4) if path == ".": continue self.__generateItem(rev, author, date, path) if not self.vcs.otherData["standardLayout"]: path = self.path + '/' + path if self.tagsList is not None: self.tagsList.append(path) if self.allTagsList is not None: self.allTagsList.append(path) def __readStderr(self): """ Private slot to handle the readyReadStderr signal. It reads the error output of the process and inserts it into the error pane. """ if self.process is not None: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnTagBranchListDialog, self).keyPressEvent(evt)
class Zoomable(QObject): scaleChanged = pyqtSignal(float) def __init__(self, parent = None): super().__init__(parent) self.mScale = 1 self.mZoomFactors = QVector() self.mGestureStartScale = 0 self.mComboBox = None self.mComboRegExp = QRegExp("^\\s*(\\d+)\\s*%?\\s*$") self.mComboValidator = None for i in range(zoomFactorCount): self.mZoomFactors.append(zoomFactors[i]) def setScale(self, scale): if (scale == self.mScale): return self.mScale = scale self.syncComboBox() self.scaleChanged.emit(self.mScale) def scale(self): return self.mScale def canZoomIn(self): return self.mScale < self.mZoomFactors.last() def canZoomOut(self): return self.mScale > self.mZoomFactors.first() ## # Changes the current scale based on the given mouse wheel \a delta. # # For convenience, the delta is assumed to be in the same units as # QWheelEvent.delta, which is the distance that the wheel is rotated, # in eighths of a degree. ## def handleWheelDelta(self, delta): if (delta <= -120): self.zoomOut() elif (delta >= 120): self.zoomIn() else: # We're dealing with a finer-resolution mouse. Allow it to have finer # control over the zoom level. factor = 1 + 0.3 * qAbs(delta / 8 / 15) if (delta < 0): factor = 1 / factor scale = qBound(self.mZoomFactors.first(), self.mScale * factor, self.mZoomFactors.back()) # Round to at most four digits after the decimal point self.setScale(math.floor(scale * 10000 + 0.5) / 10000) ## # Changes the current scale based on the given pinch gesture. ## def handlePinchGesture(self, pinch): if (not (pinch.changeFlags() & QPinchGesture.ScaleFactorChanged)): return x = pinch.state() if x==Qt.NoGesture: pass elif x==Qt.GestureStarted: self.mGestureStartScale = self.mScale # fall through elif x==Qt.GestureUpdated: factor = pinch.scaleFactor() scale = qBound(self.mZoomFactors.first(), self.mGestureStartScale * factor, self.mZoomFactors.back()) self.setScale(math.floor(scale * 10000 + 0.5) / 10000) elif x==Qt.GestureFinished: pass elif x==Qt.GestureCanceled: pass ## # Returns whether images should be smoothly transformed when drawn at the # current scale. This is the case when the scale is not 1 and smaller than # 2. ## def smoothTransform(self): return self.mScale != 1.0 and self.mScale < 2.0 def setZoomFactors(self, factors): self.mZoomFactors = factors def connectToComboBox(self, comboBox): if (self.mComboBox): self.mComboBox.disconnect() if (self.mComboBox.lineEdit()): self.mComboBox.lineEdit().disconnect() self.mComboBox.setValidator(None) self.mComboBox = comboBox if type(comboBox) is QComboBox: self.mComboBox.clear() for scale in self.mZoomFactors: self.mComboBox.addItem(scaleToString(scale), scale) self.syncComboBox() self.mComboBox.activated.connect(self.comboActivated) self.mComboBox.setEditable(True) self.mComboBox.setInsertPolicy(QComboBox.NoInsert) self.mComboBox.lineEdit().editingFinished.connect(self.comboEdited) if (not self.mComboValidator): self.mComboValidator = QRegExpValidator(self.mComboRegExp, self) self.mComboBox.setValidator(self.mComboValidator) def zoomIn(self): for scale in self.mZoomFactors: if (scale > self.mScale): self.setScale(scale) break def zoomOut(self): for i in range(self.mZoomFactors.count() - 1, -1, -1): if (self.mZoomFactors[i] < self.mScale): self.setScale(self.mZoomFactors[i]) break def resetZoom(self): self.setScale(1) def comboActivated(self, index): self.setScale(self.mComboBox.itemData(index)) def comboEdited(self): pos = self.mComboRegExp.indexIn(self.mComboBox.currentText()) pos != -1 scale = qBound(self.mZoomFactors.first(), Float(self.mComboRegExp.cap(1)) / 100.0, self.mZoomFactors.last()) self.setScale(scale) def syncComboBox(self): if (not self.mComboBox): return index = self.mComboBox.findData(self.mScale) # For a custom scale, the current index must be set to -1 self.mComboBox.setCurrentIndex(index) self.mComboBox.setEditText(scaleToString(self.mScale))
class Zoomable(QObject): scaleChanged = pyqtSignal(float) def __init__(self, parent=None): super().__init__(parent) self.mScale = 1 self.mZoomFactors = QVector() self.mGestureStartScale = 0 self.mComboBox = None self.mComboRegExp = QRegExp("^\\s*(\\d+)\\s*%?\\s*$") self.mComboValidator = None for i in range(zoomFactorCount): self.mZoomFactors.append(zoomFactors[i]) def setScale(self, scale): if (scale == self.mScale): return self.mScale = scale self.syncComboBox() self.scaleChanged.emit(self.mScale) def scale(self): return self.mScale def canZoomIn(self): return self.mScale < self.mZoomFactors.last() def canZoomOut(self): return self.mScale > self.mZoomFactors.first() ## # Changes the current scale based on the given mouse wheel \a delta. # # For convenience, the delta is assumed to be in the same units as # QWheelEvent.delta, which is the distance that the wheel is rotated, # in eighths of a degree. ## def handleWheelDelta(self, delta): if (delta <= -120): self.zoomOut() elif (delta >= 120): self.zoomIn() else: # We're dealing with a finer-resolution mouse. Allow it to have finer # control over the zoom level. factor = 1 + 0.3 * qAbs(delta / 8 / 15) if (delta < 0): factor = 1 / factor scale = qBound(self.mZoomFactors.first(), self.mScale * factor, self.mZoomFactors.back()) # Round to at most four digits after the decimal point self.setScale(math.floor(scale * 10000 + 0.5) / 10000) ## # Changes the current scale based on the given pinch gesture. ## def handlePinchGesture(self, pinch): if (not (pinch.changeFlags() & QPinchGesture.ScaleFactorChanged)): return x = pinch.state() if x == Qt.NoGesture: pass elif x == Qt.GestureStarted: self.mGestureStartScale = self.mScale # fall through elif x == Qt.GestureUpdated: factor = pinch.scaleFactor() scale = qBound(self.mZoomFactors.first(), self.mGestureStartScale * factor, self.mZoomFactors.back()) self.setScale(math.floor(scale * 10000 + 0.5) / 10000) elif x == Qt.GestureFinished: pass elif x == Qt.GestureCanceled: pass ## # Returns whether images should be smoothly transformed when drawn at the # current scale. This is the case when the scale is not 1 and smaller than # 2. ## def smoothTransform(self): return self.mScale != 1.0 and self.mScale < 2.0 def setZoomFactors(self, factors): self.mZoomFactors = factors def connectToComboBox(self, comboBox): if (self.mComboBox): self.mComboBox.disconnect() if (self.mComboBox.lineEdit()): self.mComboBox.lineEdit().disconnect() self.mComboBox.setValidator(None) self.mComboBox = comboBox if type(comboBox) is QComboBox: self.mComboBox.clear() for scale in self.mZoomFactors: self.mComboBox.addItem(scaleToString(scale), scale) self.syncComboBox() self.mComboBox.activated.connect(self.comboActivated) self.mComboBox.setEditable(True) self.mComboBox.setInsertPolicy(QComboBox.NoInsert) self.mComboBox.lineEdit().editingFinished.connect(self.comboEdited) if (not self.mComboValidator): self.mComboValidator = QRegExpValidator( self.mComboRegExp, self) self.mComboBox.setValidator(self.mComboValidator) def zoomIn(self): for scale in self.mZoomFactors: if (scale > self.mScale): self.setScale(scale) break def zoomOut(self): for i in range(self.mZoomFactors.count() - 1, -1, -1): if (self.mZoomFactors[i] < self.mScale): self.setScale(self.mZoomFactors[i]) break def resetZoom(self): self.setScale(1) def comboActivated(self, index): self.setScale(self.mComboBox.itemData(index)) def comboEdited(self): pos = self.mComboRegExp.indexIn(self.mComboBox.currentText()) pos != -1 scale = qBound(self.mZoomFactors.first(), Float(self.mComboRegExp.cap(1)) / 100.0, self.mZoomFactors.last()) self.setScale(scale) def syncComboBox(self): if (not self.mComboBox): return index = self.mComboBox.findData(self.mScale) # For a custom scale, the current index must be set to -1 self.mComboBox.setCurrentIndex(index) self.mComboBox.setEditText(scaleToString(self.mScale))
def highlightBlock(self, text): """Apply syntax highlighting to the given block of text. """ basicHighlighter.highlightBlockBefore(self, text) # Check if syntax highlighting is enabled if self.style is None: default = QTextBlockFormat() QTextCursor(self.currentBlock()).setBlockFormat(default) print("t2tHighlighter.py: is style supposed to be None?") return block = self.currentBlock() oldState = blockUserData.getUserState(block) self.identifyBlock(block) # formatBlock prevent undo/redo from working # TODO: find a todo/undo compatible way of formatting block # self.formatBlock(block) state = blockUserData.getUserState(block) data = blockUserData.getUserData(block) inList = self.isList(block) op = self.style.format(State.MARKUP) # self.setFormat(0, len(text), self.style.format(State.DEFAULT)) # InDocRules: is it a settings which might have a specific rule, # a comment which contains color infos, or a include conf? # r'^%!p[or][se]t?proc[^\s]*\s*:\s*\'(.*)\'\s*\'.*\'' rlist = [QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))'), # pre/postproc QRegExp(r'^%.*\s\((.*)\)'), # comment QRegExp(r'^%!includeconf:\s*([^\s]*)\s*')] # includeconf for r in rlist: if r.indexIn(text) != -1: self.parseInDocRules() # Format the whole line: for lineState in [ State.BLOCKQUOTE_LINE, State.HORIZONTAL_LINE, State.HEADER_LINE, ]: if not inList and state == lineState: self.setFormat(0, len(text), self.style.format(lineState)) for (lineState, marker) in [ (State.COMMENT_LINE, "%"), (State.CODE_LINE, "```"), (State.RAW_LINE, "\"\"\""), (State.TAGGED_LINE, "'''"), (State.SETTINGS_LINE, "%!") ]: if state == lineState and \ not (inList and state == State.SETTINGS_LINE): n = 0 # If it's a comment, we want to highlight all '%'. if state == State.COMMENT_LINE: while text[n:n + 1] == "%": n += 1 n -= 1 # Apply Format self.setFormat(0, len(marker) + n, op) self.setFormat(len(marker) + n, len(text) - len(marker) - n, self.style.format(lineState)) # If it's a setting, we might do something if state == State.SETTINGS_LINE: # Target r = QRegExp(r'^%!([^\s]+)\s*:\s*(\b\w*\b)$') if r.indexIn(text) != -1: setting = r.cap(1) val = r.cap(2) if setting == "target" and \ val in self.editor.main.targetsNames: self.editor.fileWidget.preview.setPreferredTarget(val) # Pre/postproc r = QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))') if r.indexIn(text) != -1: p = r.pos(1) length = len(r.cap(1)) self.setFormat(p, length, self.style.makeFormat(base=self.format(p), fixedPitch=True)) # Tables for lineState in [State.TABLE_LINE, State.TABLE_HEADER]: if state == lineState: for i, t in enumerate(text): if t == "|": self.setFormat(i, 1, op) else: self.setFormat(i, 1, self.style.format(lineState)) # Lists # if text == " p": print(data.isList()) if data.isList(): r = QRegExp(r'^\s*[\+\-\:]? ?') r.indexIn(text) self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET)) # if state == State.LIST_BEGINS: # r = QRegExp(r'^\s*[+-:] ') # r.indexIn(text) # self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET)) if state == State.LIST_ENDS: self.setFormat(0, len(text), self.style.format(State.LIST_BULLET_ENDS)) # Titles if not inList and state in State.TITLES: r = [i for (i, s) in State.Rules if s == state][0] pos = r.indexIn(text) if pos >= 0: f = self.style.format(state) # Uncomment for markup to be same size as title # op = self.formats(preset="markup", # base=self.formats(preset=state)) self.setFormat(r.pos(2), len(r.cap(2)), f) self.setFormat(r.pos(1), len(r.cap(1)), op) self.setFormat(r.pos(3), len(r.cap(3)), op) # Areas: comment, code, raw tagged for (begins, middle, ends) in [ (State.COMMENT_AREA_BEGINS, State.COMMENT_AREA, State.COMMENT_AREA_ENDS), (State.CODE_AREA_BEGINS, State.CODE_AREA, State.CODE_AREA_ENDS), (State.RAW_AREA_BEGINS, State.RAW_AREA, State.RAW_AREA_ENDS), (State.TAGGED_AREA_BEGINS, State.TAGGED_AREA, State.TAGGED_AREA_ENDS), ]: if state == middle: self.setFormat(0, len(text), self.style.format(middle)) elif state in [begins, ends]: self.setFormat(0, len(text), op) # Inline formatting if state not in [ # State.COMMENT_AREA, # State.COMMENT_LINE, State.RAW_AREA, State.RAW_LINE, State.CODE_AREA, State.CODE_LINE, State.TAGGED_AREA, State.TAGGED_LINE, State.SETTINGS_LINE, State.HORIZONTAL_LINE, ] and state not in State.TITLES: formatArray = textToFormatArray(text) # InDocRules for (r, c) in self.inDocRules: i = re.finditer(r.decode('utf8'), text, re.UNICODE) for m in i: f = self.format(m.start()) l = m.end() - m.start() if "," in c: c1, c2 = c.split(",") self.setFormat(m.start(), l, self.style.makeFormat(color=c1, bgcolor=c2, base=f)) else: self.setFormat(m.start(), l, self.style.makeFormat(color=c, base=f)) # Links if state not in [State.COMMENT_LINE, State.COMMENT_AREA]: r = QRegExp(r'\[(\[[^\]]*\])?[^\]]*\s*([^\s]+)\]') r.setMinimal(False) pos = r.indexIn(text) links = [] while pos >= 0: # TODO: The text should not be formatted if [**not bold**] # if max([k[pos] for k in formatArray]) == 0 or 1 == 1: self.setFormat(pos, 1, self.style.format(State.MARKUP)) self.setFormat(pos + 1, len(r.cap(0)) - 1, self.style.format(State.LINKS)) self.setFormat(pos + len(r.cap(0)) - 1, 1, self.style.format(State.MARKUP)) if r.pos(2) > 0: _f = QTextCharFormat(self.style.format(State.LINKS)) _f.setForeground(QBrush(_f.foreground() .color().lighter())) _f.setFontUnderline(True) self.setFormat(r.pos(2), len(r.cap(2)), _f) links.append([pos, len(r.cap(0))]) # To remember for the next highlighter (single links) pos = r.indexIn(text, pos + 1) # Links like www.theologeek.ch, http://www.fsf.org, ... # FIXME: - "http://adresse et http://adresse" is detected also as italic # - some error, like "http://adress.htm." also color the final "." # - also: [email protected], ftp://, www2, www3, etc. # - But for now, does the job r = QRegExp(r'http://[^\s]*|www\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+[^\s]*') # r.setMinimal(True) pos = r.indexIn(text) while pos >= 0: for k in links: # print pos, k[0], k[1] if k[0] < pos < k[0] + k[1]: # already highlighted break else: self.setFormat(pos, len(r.cap(0)), self.style.format(State.LINKS)) pos = r.indexIn(text, pos + 1) # Bold, Italic, Underline, Code, Tagged, Strikeout for i, t in enumerate(text): f = self.format(i) beautifiers = [k[i] for k in formatArray] self.setFormat(i, 1, self.style.beautifyFormat(f, beautifiers)) # Macro words for r in [r'(%%)\b\w+\b', r'(%%)\b\w+\b\(.+\)']: r = QRegExp(r) r.setMinimal(True) pos = r.indexIn(text) while pos >= 0: if max([k[pos] for k in formatArray]) == 0: self.setFormat(pos, len(r.cap(0)), self.style.format(State.MACRO)) pos = r.indexIn(text, pos + 1) # Highlighted word (for search) if self.editor.highlightWord: if self.editor.highligtCS and self.editor.highlightWord in text or \ not self.editor.highlightCs and self.editor.highlightWord.lower() in text.lower(): # if self.editor.highlightCS: # s = self.editor.highlightWord # else: # s = self.editor.highlightWord.toLower() # print(s) p = text.indexOf(self.editor.highlightWord, cs=self.editor.highlightCS) while p >= 0: self.setFormat(p, len(self.editor.highlightWord), self.style.makeFormat(preset="higlighted", base=self.format(p))) p = text.indexOf(self.editor.highlightWord, p + 1, cs=self.editor.highlightCS) ### Highlight Selection ### TODO: way to slow, find another way. ##sel = self.editor.textCursor().selectedText() ##if len(sel) > 5: self.keywordRules.append((QRegExp(sel), "selected")) ## Do keyword formatting # for expression, style in self.keywordRules: # expression.setMinimal( True ) # index = expression.indexIn(text, 0) ## There might be more than one on the same line # while index >= 0: # length = expression.cap(0).length() # f = self.formats(preset=style, base=self.formats(index)) # self.setFormat(index, length, f) # index = expression.indexIn(text, index + length) basicHighlighter.highlightBlockAfter(self, text)
def start(self, path, tags=True): """ Public slot to start the svn status command. @param path name of directory to be listed (string) @param tags flag indicating a list of tags is requested (False = branches, True = tags) @return flag indicating success (boolean) """ self.errorGroup.hide() self.tagList.clear() if not tags: self.setWindowTitle(self.tr("Subversion Branches List")) self.activateWindow() QApplication.processEvents() dname, fname = self.vcs.splitPath(path) reposURL = self.vcs.svnGetReposName(dname) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The list operation""" """ will be aborted""")) self.close() return False if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+)/(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr("""The URL of the project repository has an""" """ invalid format. The list operation will""" """ be aborted""")) return False reposRoot = rx_base.cap(1) if tags: path = "{0}/tags".format(reposRoot) else: path = "{0}/branches".format(reposRoot) else: reposPath, ok = QInputDialog.getText( self, self.tr("Subversion List"), self.tr("Enter the repository URL containing the" " tags or branches"), QLineEdit.Normal, self.vcs.svnNormalizeURL(reposURL)) if not ok: self.close() return False if not reposPath: E5MessageBox.critical( self, self.tr("Subversion List"), self.tr("""The repository URL is empty. Aborting...""")) self.close() return False path = reposPath locker = QMutexLocker(self.vcs.vcsExecutionMutex) self.tagsList = [] cwd = os.getcwd() os.chdir(dname) try: entries = self.client.list(path, recurse=False) # dirent, lock already unicode in Python 2 for dirent, _lock in entries: if dirent["path"] != path: name = dirent["path"].replace(path + '/', "") self.__generateItem(dirent["created_rev"].number, dirent["last_author"], formatTime(dirent["time"]), name) if self.vcs.otherData["standardLayout"]: self.tagsList.append(name) else: self.tagsList.append(path + '/' + name) if self._clientCancelCallback(): break res = True except pysvn.ClientError as e: self.__showError(e.args[0]) res = False except AttributeError: self.__showError( self.tr("The installed version of PySvn should be" " 1.4.0 or better.")) res = False locker.unlock() self.__finish() os.chdir(cwd) return res
class SvnRepoBrowserDialog(QDialog, Ui_SvnRepoBrowserDialog): """ 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) """ super(SvnRepoBrowserDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.repoTree.headerItem().setText(self.repoTree.columnCount(), "") self.repoTree.header().setSortIndicator(0, Qt.AscendingOrder) self.process = None self.vcs = vcs self.mode = mode if self.mode == "select": self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).hide() else: self.buttonBox.button(QDialogButtonBox.Ok).hide() self.buttonBox.button(QDialogButtonBox.Cancel).hide() self.__dirIcon = UI.PixmapCache.getIcon("dirClosed.png") self.__fileIcon = UI.PixmapCache.getIcon("fileMisc.png") self.__urlRole = Qt.UserRole self.__ignoreExpand = False self.intercept = False self.__rx_dir = QRegExp( r"""\s*([0-9]+)\s+(\w+)\s+""" r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""") self.__rx_file = QRegExp( r"""\s*([0-9]+)\s+(\w+)\s+([0-9]+)\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.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()) def __resizeColumns(self): """ Private method to resize the tree columns. """ self.repoTree.header().resizeSections(QHeaderView.ResizeToContents) self.repoTree.header().setStretchLastSection(True) 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) @param size size info (string) @param date date info (string) @param nodekind node kind info (string, "dir" or "file") @param url url of the entry (string) @return reference to the generated item (QTreeWidgetItem) """ path = repopath if revision == "": rev = "" else: rev = int(revision) if size == "": sz = "" else: sz = int(size) itm = QTreeWidgetItem(self.parentItem) itm.setData(0, Qt.DisplayRole, path) itm.setData(1, Qt.DisplayRole, rev) itm.setData(2, Qt.DisplayRole, author) itm.setData(3, Qt.DisplayRole, sz) itm.setData(4, Qt.DisplayRole, date) if nodekind == "dir": itm.setIcon(0, self.__dirIcon) itm.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator) elif nodekind == "file": itm.setIcon(0, self.__fileIcon) itm.setData(0, self.__urlRole, url) itm.setTextAlignment(0, Qt.AlignLeft) itm.setTextAlignment(1, Qt.AlignRight) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignRight) itm.setTextAlignment(4, Qt.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(url) 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') for line in output.splitlines(): line = line.strip() if line.startswith('<root>'): repoRoot = line.replace('<root>', '')\ .replace('</root>', '') break else: error = str(process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(error) self.errors.ensureCursorVisible() else: QApplication.restoreOverrideCursor() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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) """ QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() 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.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.setExpanded(True) self.parentItem = itm itm.setExpanded(False) self.__ignoreExpand = False self.__finish() return self.intercept = False if self.process: self.process.kill() else: self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) 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(url) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.__finish() self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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.url = "" self.urlCombo.addItem(self.__normalizeUrl(url)) @pyqtSlot(str) def on_urlCombo_currentIndexChanged(self, text): """ Private slot called, when a new repository URL is entered or selected. @param text the text of the current item (string) """ url = self.__normalizeUrl(text) if url != self.url: 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.Ok).setEnabled(True) def accept(self): """ Public slot called when the dialog is accepted. """ if self.focusWidget() == self.urlCombo: return super(SvnRepoBrowserDialog, self).accept() def getSelectedUrl(self): """ Public method to retrieve the selected repository URL. @return the selected repository URL (string) """ items = self.repoTree.selectedItems() if len(items) == 1: 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.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() QApplication.restoreOverrideCursor() 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.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.__rx_dir.exactMatch(s): revision = self.__rx_dir.cap(1) author = self.__rx_dir.cap(2) date = self.__rx_dir.cap(3) name = self.__rx_dir.cap(4).strip() if name.endswith("/"): name = name[:-1] size = "" nodekind = "dir" elif self.__rx_file.exactMatch(s): revision = self.__rx_file.cap(1) author = self.__rx_file.cap(2) size = self.__rx_file.cap(3) date = self.__rx_file.cap(4) name = self.__rx_file.cap(5).strip() nodekind = "file" else: continue url = "{0}/{1}".format(self.repoUrl, name) 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') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnRepoBrowserDialog, self).keyPressEvent(evt)
class SvnLogBrowserDialog(QWidget, Ui_SvnLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnLogBrowserDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder) self.vcs = vcs self.__initData() self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() self.__messageRole = Qt.UserRole self.__changesRole = Qt.UserRole + 1 self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_sep1 = QRegExp('\\-+\\s*') self.rx_sep2 = QRegExp('=+\\s*') self.rx_rev1 = QRegExp( 'rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "rev" followed by one or more decimals followed by a colon followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_rev2 = QRegExp( 'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "r" followed by one or more decimals followed by " | " followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_flags1 = QRegExp( r""" ([ADM])\s(.*)\s+\(\w+\s+(.*):([0-9]+)\)\s*""") # three blanks followed by A or D or M followed by path followed by # path copied from followed by copied from revision self.rx_flags2 = QRegExp(' ([ADM]) (.*)\\s*') # three blanks followed by A or D or M followed by path self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified'), 'R': self.tr('Replaced'), } def __initData(self): """ Private method to (re-)initialize some data. """ self.__maxDate = QDate() self.__minDate = QDate() self.__filterLogsEnabled = True self.buf = [] # buffer for stdout self.diff = None self.__started = False self.__lastRev = 0 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(SvnLogBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex( self.fieldCombo.findText(self.tr("Message"))) self.limitSpinBox.setValue( self.vcs.getPlugin().getPreferences("LogLimit")) self.stopCheckBox.setChecked( self.vcs.getPlugin().getPreferences("StopLogOnCopy")) self.logTree.clear() self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ self.logTree.header().resizeSections(QHeaderView.ResizeToContents) self.logTree.header().setStretchLastSection(True) def __resortLog(self): """ Private method to resort the log tree. """ self.logTree.sortItems(self.logTree.sortColumn(), self.logTree.header().sortIndicatorOrder()) def __resizeColumnsFiles(self): """ Private method to resize the changed files tree columns. """ self.filesTree.header().resizeSections(QHeaderView.ResizeToContents) self.filesTree.header().setStretchLastSection(True) def __resortFiles(self): """ Private method to resort the changed files tree. """ sortColumn = self.filesTree.sortColumn() self.filesTree.sortItems(1, self.filesTree.header().sortIndicatorOrder()) self.filesTree.sortItems(sortColumn, self.filesTree.header().sortIndicatorOrder()) def __generateLogItem(self, author, date, message, revision, changedPaths): """ Private method to generate a log tree entry. @param author author info (string) @param date date info (string) @param message text of the log message (list of strings) @param revision revision info (string) @param changedPaths list of dictionary objects containing info about the changed files/directories @return reference to the generated item (QTreeWidgetItem) """ msg = [] for line in message: msg.append(line.strip()) itm = QTreeWidgetItem(self.logTree) itm.setData(0, Qt.DisplayRole, int(revision)) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, date) itm.setData(3, Qt.DisplayRole, " ".join(msg)) itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changedPaths) itm.setTextAlignment(0, Qt.AlignRight) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignLeft) itm.setTextAlignment(4, Qt.AlignLeft) try: self.__lastRev = int(revision) except ValueError: self.__lastRev = 0 return itm def __generateFileItem(self, action, path, copyFrom, copyRev): """ Private method to generate a changed files tree entry. @param action indicator for the change action ("A", "D" or "M") @param path path of the file in the repository (string) @param copyFrom path the file was copied from (None, string) @param copyRev revision the file was copied from (None, string) @return reference to the generated item (QTreeWidgetItem) """ itm = QTreeWidgetItem(self.filesTree, [ self.flags[action], path, copyFrom, copyRev, ]) itm.setTextAlignment(3, Qt.AlignRight) return itm def __getLogEntries(self, startRev=None): """ Private method to retrieve log entries from the repository. @param startRev revision number to start from (integer, string) """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.intercept = False self.process.kill() self.buf = [] self.cancelled = False self.errors.clear() args = [] args.append('log') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['log']) args.append('--verbose') args.append('--limit') args.append('{0:d}'.format(self.limitSpinBox.value())) if startRev is not None: args.append('--revision') args.append('{0}:0'.format(startRev)) if self.stopCheckBox.isChecked(): args.append('--stop-on-copy') args.append(self.fname) self.process.setWorkingDirectory(self.dname) self.inputGroup.setEnabled(True) self.inputGroup.show() self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('svn')) def start(self, fn, isFile=False): """ Public slot to start the svn log command. @param fn filename to show the log for (string) @keyparam isFile flag indicating log for a file is to be shown (boolean) """ self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) self.errorGroup.hide() QApplication.processEvents() self.__initData() self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.activateWindow() self.raise_() self.logTree.clear() self.__started = True self.__getLogEntries() 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.__processBuffer() self.__finish() 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() def __processBuffer(self): """ Private method to process the buffered output of the svn log command. """ noEntries = 0 log = {"message": []} changedPaths = [] for s in self.buf: if self.rx_rev1.exactMatch(s): log["revision"] = self.rx_rev.cap(1) log["author"] = self.rx_rev.cap(2) log["date"] = self.rx_rev.cap(3) # number of lines is ignored elif self.rx_rev2.exactMatch(s): log["revision"] = self.rx_rev2.cap(1) log["author"] = self.rx_rev2.cap(2) log["date"] = self.rx_rev2.cap(3) # number of lines is ignored elif self.rx_flags1.exactMatch(s): changedPaths.append({ "action": self.rx_flags1.cap(1).strip(), "path": self.rx_flags1.cap(2).strip(), "copyfrom_path": self.rx_flags1.cap(3).strip(), "copyfrom_revision": self.rx_flags1.cap(4).strip(), }) elif self.rx_flags2.exactMatch(s): changedPaths.append({ "action": self.rx_flags2.cap(1).strip(), "path": self.rx_flags2.cap(2).strip(), "copyfrom_path": "", "copyfrom_revision": "", }) elif self.rx_sep1.exactMatch(s) or self.rx_sep2.exactMatch(s): if len(log) > 1: self.__generateLogItem(log["author"], log["date"], log["message"], log["revision"], changedPaths) dt = QDate.fromString(log["date"], Qt.ISODate) if not self.__maxDate.isValid() and \ not self.__minDate.isValid(): self.__maxDate = dt self.__minDate = dt else: if self.__maxDate < dt: self.__maxDate = dt if self.__minDate > dt: self.__minDate = dt noEntries += 1 log = {"message": []} changedPaths = [] else: if s.strip().endswith(":") or not s.strip(): continue else: log["message"].append(s) self.__resizeColumnsLog() self.__resortLog() if self.__started: self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) self.__started = False if noEntries < self.limitSpinBox.value() and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) self.fromDate.setMaximumDate(self.__maxDate) self.fromDate.setDate(self.__minDate) self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) self.__filterLogsEnabled = True self.__filterLogs() def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.buf.append(line) 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() def __diffRevisions(self, rev1, rev2): """ Private method to do a diff of two revisions. @param rev1 first revision number (integer) @param rev2 second revision number (integer) """ if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(self.filename, revisions=(str(rev1), str(rev2))) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.raise_() self.diff.start(self.filename, [rev1, rev2]) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True self.__finish() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_logTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the log tree changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ if current is not None: self.messageEdit.clear() for line in current.data(0, self.__messageRole): self.messageEdit.append(line.strip()) self.filesTree.clear() changes = current.data(0, self.__changesRole) if len(changes) > 0: for change in changes: self.__generateFileItem(change["action"], change["path"], change["copyfrom_path"], change["copyfrom_revision"]) self.__resizeColumnsFiles() self.__resortFiles() self.diffPreviousButton.setEnabled( current != self.logTree.topLevelItem( self.logTree.topLevelItemCount() - 1)) @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ Private slot called, when the selection has changed. """ self.diffRevisionsButton.setEnabled( len(self.logTree.selectedItems()) == 2) @pyqtSlot() def on_nextButton_clicked(self): """ Private slot to handle the Next button. """ if self.__lastRev > 1: self.__getLogEntries(self.__lastRev - 1) @pyqtSlot() def on_diffPreviousButton_clicked(self): """ Private slot to handle the Diff to Previous button. """ itm = self.logTree.currentItem() if itm is None: self.diffPreviousButton.setEnabled(False) return rev2 = int(itm.text(0)) itm = self.logTree.topLevelItem( self.logTree.indexOfTopLevelItem(itm) + 1) if itm is None: self.diffPreviousButton.setEnabled(False) return rev1 = int(itm.text(0)) self.__diffRevisions(rev1, rev2) @pyqtSlot() def on_diffRevisionsButton_clicked(self): """ Private slot to handle the Compare Revisions button. """ items = self.logTree.selectedItems() if len(items) != 2: self.diffRevisionsButton.setEnabled(False) return rev2 = int(items[0].text(0)) rev1 = int(items[1].text(0)) self.__diffRevisions(min(rev1, rev2), max(rev1, rev2)) @pyqtSlot(QDate) def on_fromDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(QDate) def on_toDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(str) def on_fieldCombo_activated(self, txt): """ Private slot called, when a new filter field is selected. @param txt text of the selected field (string) """ self.__filterLogs() @pyqtSlot(str) def on_rxEdit_textChanged(self, txt): """ Private slot called, when a filter expression is entered. @param txt filter expression (string) """ self.__filterLogs() def __filterLogs(self): """ Private method to filter the log entries. """ if self.__filterLogsEnabled: from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") txt = self.fieldCombo.currentText() if txt == self.tr("Author"): fieldIndex = 1 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) elif txt == self.tr("Revision"): fieldIndex = 0 txt = self.rxEdit.text() if txt.startswith("^"): searchRx = QRegExp("^\s*{0}".format(txt[1:]), Qt.CaseInsensitive) else: searchRx = QRegExp(txt, Qt.CaseInsensitive) else: fieldIndex = 3 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): topItem = self.logTree.topLevelItem(topIndex) if topItem.text(2) <= to_ and topItem.text(2) >= from_ and \ searchRx.indexIn(topItem.text(fieldIndex)) > -1: topItem.setHidden(False) if topItem is currentItem: self.on_logTree_currentItemChanged(topItem, None) else: topItem.setHidden(True) if topItem is currentItem: self.messageEdit.clear() self.filesTree.clear() @pyqtSlot(bool) def on_stopCheckBox_clicked(self, checked): """ Private slot called, when the stop on copy/move checkbox is clicked. @param checked flag indicating the checked state (boolean) """ self.vcs.getPlugin().setPreferences("StopLogOnCopy", self.stopCheckBox.isChecked()) self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.errorGroup.show() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnLogBrowserDialog, self).keyPressEvent(evt)
class Highlighter(QSyntaxHighlighter): """Syntax Highlighter for NINJA-IDE.""" # braces braces = ['\\(', '\\)', '\\{', '\\}', '\\[', '\\]'] def __init__(self, document, lang=None, scheme=None, errors=None, pep8=None, migration=None): super(Highlighter, self).__init__(document) self.highlight_function = self.realtime_highlight self.errors = errors self.pep8 = pep8 self.migration = migration self._old_search = None self.selected_word_lines = [] self.visible_limits = (0, 50) self._styles = {} if lang is not None: self.apply_highlight(lang, scheme) def sanitize(self, word): """Sanitize the string to avoid problems with the regex.""" return word.replace('\\', '\\\\') def apply_highlight(self, lang, scheme=None, syntax=None): """Set the rules that will decide what to highlight and how.""" if syntax is None: langSyntax = settings.SYNTAX.get(lang, {}) else: langSyntax = syntax if scheme is not None: restyle(scheme) keywords = langSyntax.get('keywords', []) operators = langSyntax.get('operators', []) extras = langSyntax.get('extras', []) rules = [] # Keyword, operator, brace and extras rules keyword_pattern = '(^|[^\w\.]{1})(%s)([^\w]{1}|$)' rules += [(keyword_pattern % w, 2, STYLES['keyword']) for w in keywords] rules += [(r'%s' % o, 0, STYLES['operator']) for o in operators] rules += [(r'%s' % b, 0, STYLES['brace']) for b in Highlighter.braces] rules += [(keyword_pattern % e, 2, STYLES['extras']) for e in extras] # All other rules proper = langSyntax.get('properObject', None) if proper is not None: proper = r'\b%s\b' % str(proper[0]) rules += [(proper, 0, STYLES['properObject'])] rules.append((r'__\w+__', 0, STYLES['properObject'])) # Classes and functions definition = langSyntax.get('definition', []) for de in definition: expr = r'\b%s\b\s*(\w+)' % de rules.append((expr, 1, STYLES['definition'])) # Numeric literals rules += [ (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']), (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']), ] # Regular expressions regex = langSyntax.get('regex', []) for reg in regex: expr = reg[0] color = resources.COLOR_SCHEME['extras'] style = '' if len(reg) > 1: if reg[1] in resources.CUSTOM_SCHEME: color = resources.CUSTOM_SCHEME[reg[1]] elif reg[1] in resources.COLOR_SCHEME: color = resources.COLOR_SCHEME[reg[1]] if len(reg) > 2: style = reg[2] rules.append((expr, 0, format(color, style))) # Strings stringChar = langSyntax.get('string', []) for sc in stringChar: expr = r'"[^"\\]*(\\.[^"\\]*)*"' if sc == '"' \ else r"'[^'\\]*(\\.[^'\\]*)*'" rules.append((expr, 0, STYLES['string'])) # Comments comments = langSyntax.get('comment', []) for co in comments: expr = co + '[^\\n]*' rules.append((expr, 0, STYLES['comment'])) # Multi-line strings (expression, flag, style) # FIXME: The triple-quotes in these two lines will mess up the # syntax highlighting from this point onward self.tri_single = (QRegExp("'''"), 1, STYLES["string2"]) self.tri_double = (QRegExp('"""'), 2, STYLES['string2']) multi = langSyntax.get('multiline_comment', []) if multi: self.multi_start = (QRegExp( re.escape(multi['open'])), STYLES['comment']) self.multi_end = (QRegExp( re.escape(multi['close'])), STYLES['comment']) else: self.multi_start = None # Build a QRegExp for each pattern self.rules = [(QRegExp(pat), index, fmt) for (pat, index, fmt) in rules] self.selected_word_pattern = None #Apply Highlight to the document... (when colors change) self.rehighlight() def set_selected_word(self, word, partial=True): """Set the word to highlight.""" # partial = True for new highlighter compatibility hl_worthy = len(word) > 2 if hl_worthy: self.selected_word_pattern = QRegExp( r'\b%s\b' % self.sanitize(word)) else: self.selected_word_pattern = None suffix = "(?![A-Za-z_\d])" prefix = "(?<![A-Za-z_\d])" word = re.escape(word) if not partial: word = "%s%s%s" % (prefix, word, suffix) lines = [] pat_find = re.compile(word) document = self.document() for lineno, text in enumerate(document.toPlainText().splitlines()): if hl_worthy and pat_find.search(text): lines.append(lineno) elif self._old_search and self._old_search.search(text): lines.append(lineno) # Ask perrito if i don't know what the next line does: self._old_search = hl_worthy and pat_find self.rehighlight_lines(lines) def __highlight_pep8(self, char_format, user_data): """Highlight the lines with pep8 errors.""" user_data.error = True char_format = char_format.toCharFormat() char_format.setUnderlineColor(QColor( resources.CUSTOM_SCHEME.get('pep8-underline', resources.COLOR_SCHEME['pep8-underline']))) char_format.setUnderlineStyle( QTextCharFormat.WaveUnderline) return char_format def __highlight_lint(self, char_format, user_data): """Highlight the lines with lint errors.""" user_data.error = True char_format = char_format.toCharFormat() char_format.setUnderlineColor(QColor( resources.CUSTOM_SCHEME.get('error-underline', resources.COLOR_SCHEME['error-underline']))) char_format.setUnderlineStyle( QTextCharFormat.WaveUnderline) return char_format def __highlight_migration(self, char_format, user_data): """Highlight the lines with lint errors.""" user_data.error = True char_format = char_format.toCharFormat() char_format.setUnderlineColor(QColor( resources.CUSTOM_SCHEME.get('migration-underline', resources.COLOR_SCHEME['migration-underline']))) char_format.setUnderlineStyle( QTextCharFormat.WaveUnderline) return char_format def highlightBlock(self, text): """Apply syntax highlighting to the given block of text.""" self.highlight_function(text) def set_open_visible_area(self, is_line, position): """Set the range of lines that should be highlighted on open.""" if is_line: self.visible_limits = (position - 50, position + 50) def open_highlight(self, text): """Only highlight the lines inside the accepted range.""" if self.visible_limits[0] <= self.currentBlock().blockNumber() <= \ self.visible_limits[1]: self.realtime_highlight(text) else: self.setCurrentBlockState(0) def async_highlight(self): """Execute a thread to collect the info of the things to highlight. The thread will collect the data from where to where to highlight, and which kind of highlight to use for those sections, and return that info to the main thread after it process all the file.""" self.thread_highlight = HighlightParserThread(self) self.thread_highlight.highlightingDetected.connect(self._execute_threaded_highlight) self.thread_highlight.start() def _execute_threaded_highlight(self, styles=None): """Function called with the info collected when the thread ends.""" self.highlight_function = self.threaded_highlight if styles: self._styles = styles lines = list(set(styles.keys()) - set(range(self.visible_limits[0], self.visible_limits[1]))) # Highlight the rest of the lines that weren't highlighted on open self.rehighlight_lines(lines, False) else: self._styles = {} self.highlight_function = self.realtime_highlight self.thread_highlight.wait() def threaded_highlight(self, text): """Highlight each line using the info collected by the thread. This function doesn't need to execute the regular expressions to see where the highlighting starts and end for each rule, it just take the start and end point, and the proper highlighting style from the info returned from the thread and applied that to the document.""" hls = [] block = self.currentBlock() user_data = syntax_highlighter.get_user_data(block) user_data.clear_data() block_number = block.blockNumber() highlight_errors = lambda cf, ud: cf if self.errors and (block_number in self.errors.errorsSummary): highlight_errors = self.__highlight_lint elif self.pep8 and (block_number in self.pep8.pep8checks): highlight_errors = self.__highlight_pep8 elif self.migration and ( block_number in self.migration.migration_data): highlight_errors = self.__highlight_migration char_format = block.charFormat() char_format = highlight_errors(char_format, user_data) self.setFormat(0, len(block.text()), char_format) block_styles = self._styles.get(block.blockNumber(), ()) for index, length, char_format in block_styles: char_format = highlight_errors(char_format, user_data) if (self.format(index) != STYLES['string']): self.setFormat(index, length, char_format) if char_format == STYLES['string']: hls.append((index, index + length)) user_data.add_str_group(index, index + length) elif char_format == STYLES['comment']: user_data.comment_start_at(index) self.setCurrentBlockState(0) if not self.multi_start: # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single, hls=hls, highlight_errors=highlight_errors, user_data=user_data) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double, hls=hls, highlight_errors=highlight_errors, user_data=user_data) else: # Do multi-line comment self.comment_multiline(text, self.multi_end[0], *self.multi_start) block.setUserData(user_data) def realtime_highlight(self, text): """Highlight each line while it is being edited. This function apply the proper highlight to the line being edited by the user, this is a really fast process for each line once you already have the document highlighted, but slow to do it the first time to highlight all the lines together.""" hls = [] block = self.currentBlock() user_data = syntax_highlighter.get_user_data(block) user_data.clear_data() block_number = block.blockNumber() highlight_errors = lambda cf, ud: cf if self.errors and (block_number in self.errors.errorsSummary): highlight_errors = self.__highlight_lint elif self.pep8 and (block_number in self.pep8.pep8checks): highlight_errors = self.__highlight_pep8 elif self.migration and ( block_number in self.migration.migration_data): highlight_errors = self.__highlight_migration char_format = block.charFormat() char_format = highlight_errors(char_format, user_data) self.setFormat(0, len(block.text()), char_format) for expression, nth, char_format in self.rules: index = expression.indexIn(text, 0) while index >= 0: # We actually want the index of the nth match index = expression.pos(nth) length = len(expression.cap(nth)) char_format = highlight_errors(char_format, user_data) if (self.format(index) != STYLES['string']): self.setFormat(index, length, char_format) if char_format == STYLES['string']: hls.append((index, index + length)) user_data.add_str_group(index, index + length) elif char_format == STYLES['comment']: user_data.comment_start_at(index) index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) if not self.multi_start: # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single, hls=hls, highlight_errors=highlight_errors, user_data=user_data) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double, hls=hls, highlight_errors=highlight_errors, user_data=user_data) else: # Do multi-line comment self.comment_multiline(text, self.multi_end[0], *self.multi_start) #Highlight selected word if self.selected_word_pattern is not None: index = self.selected_word_pattern.indexIn(text, 0) while index >= 0: index = self.selected_word_pattern.pos(0) length = len(self.selected_word_pattern.cap(0)) char_format = self.format(index) color = STYLES['selectedWord'].foreground().color() color.setAlpha(100) char_format.setBackground(color) self.setFormat(index, length, char_format) index = self.selected_word_pattern.indexIn( text, index + length) #Spaces expression = QRegExp('\s+') index = expression.indexIn(text, 0) while index >= 0: index = expression.pos(0) length = len(expression.cap(0)) char_format = STYLES['spaces'] char_format = highlight_errors(char_format, user_data) self.setFormat(index, length, char_format) index = expression.indexIn(text, index + length) block.setUserData(user_data) def _rehighlight_lines(self, lines): """If the document is valid, highlight the list of lines received.""" if self.document() is None: return for line in lines: block = self.document().findBlockByNumber(line) self.rehighlightBlock(block) def _get_errors_lines(self): """Return the number of lines that contains errors to highlight.""" errors_lines = [] block = self.document().begin() while block.isValid(): user_data = syntax_highlighter.get_user_data(block) if user_data.error: errors_lines.append(block.blockNumber()) block = block.next()# block = next(block) return errors_lines def rehighlight_lines(self, lines, errors=True): """Rehighlight the lines for errors or selected words.""" if errors: errors_lines = self._get_errors_lines() refresh_lines = set(lines + errors_lines) else: refresh_lines = set(lines + self.selected_word_lines) self.selected_word_lines = lines self._rehighlight_lines(refresh_lines) def match_multiline(self, text, delimiter, in_state, style, hls=[], highlight_errors=lambda x: x, user_data=None): """Do highlighting of multi-line strings. ``delimiter`` should be a ``QRegExp`` for triple-single-quotes or triple-double-quotes, and ``in_state`` should be a unique integer to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. """ # If inside triple-single quotes, start at 0 if self.previousBlockState() == in_state: start = 0 add = 0 # Otherwise, look for the delimiter on this line else: start = delimiter.indexIn(text) # Move past this match add = delimiter.matchedLength() # As long as there's a delimiter match on this line... while start >= 0: # Look for the ending delimiter end = delimiter.indexIn(text, start + add) # Ending delimiter on this line? if end >= add: length = end - start + add + delimiter.matchedLength() self.setCurrentBlockState(0) # No; multi-line string else: self.setCurrentBlockState(in_state) length = len(text) - start + add st_fmt = self.format(start) start_collides = [pos for pos in hls if pos[0] < start < pos[1]] # Apply formatting if ((st_fmt != STYLES['comment']) or ((st_fmt == STYLES['comment']) and (self.previousBlockState() != 0))) and \ (len(start_collides) == 0): style = highlight_errors(style, user_data) self.setFormat(start, length, style) else: self.setCurrentBlockState(0) # Look for the next match start = delimiter.indexIn(text, start + length) # Return True if still inside a multi-line string, False otherwise if self.currentBlockState() == in_state: return True else: return False def comment_multiline(self, text, delimiter_end, delimiter_start, style): """Process the beggining and end of a multiline comment.""" startIndex = 0 if self.previousBlockState() != 1: startIndex = delimiter_start.indexIn(text) while startIndex >= 0: endIndex = delimiter_end.indexIn(text, startIndex) commentLength = 0 if endIndex == -1: self.setCurrentBlockState(1) commentLength = len(text) - startIndex else: commentLength = endIndex - startIndex + \ delimiter_end.matchedLength() self.setFormat(startIndex, commentLength, style) startIndex = delimiter_start.indexIn(text, startIndex + commentLength)
class SvnStatusMonitorThread(VcsStatusMonitorThread): """ Class implementing the VCS status monitor thread class for Subversion. """ def __init__(self, interval, project, vcs, parent=None): """ Constructor @param interval new interval in seconds (integer) @param project reference to the project object (Project) @param vcs reference to the version control object @param parent reference to the parent object (QObject) """ VcsStatusMonitorThread.__init__(self, interval, project, vcs, parent) self.__ioEncoding = Preferences.getSystem("IOEncoding") self.rx_status1 = \ QRegExp('(.{8,9})\\s+([0-9-]+)\\s+(.+)\\s*') self.rx_status2 = QRegExp( '(.{8,9})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)\\s*') def _performMonitor(self): """ Protected method implementing the monitoring action. This method populates the statusList member variable with a list of strings giving the status in the first column and the path relative to the project directory starting with the third column. The allowed status flags are: <ul> <li>"A" path was added but not yet comitted</li> <li>"M" path has local changes</li> <li>"O" path was removed</li> <li>"R" path was deleted and then re-added</li> <li>"U" path needs an update</li> <li>"Z" path contains a conflict</li> <li>" " path is back at normal</li> </ul> @return tuple of flag indicating successful operation (boolean) and a status message in case of non successful operation (string) """ self.shouldUpdate = False process = QProcess() args = [] args.append('status') if not Preferences.getVCS("MonitorLocalStatus"): args.append('--show-updates') args.append('--non-interactive') args.append('.') process.setWorkingDirectory(self.projectDir) process.start('svn', args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(300000) if finished and process.exitCode() == 0: output = str(process.readAllStandardOutput(), self.__ioEncoding, 'replace') states = {} for line in output.splitlines(): if self.rx_status1.exactMatch(line): flags = self.rx_status1.cap(1) path = self.rx_status1.cap(3).strip() elif self.rx_status2.exactMatch(line): flags = self.rx_status2.cap(1) path = self.rx_status2.cap(5).strip() else: continue if flags[0] in "ACDMR" or \ (flags[0] == " " and flags[-1] == "*"): if flags[-1] == "*": status = "U" else: status = flags[0] if status == "C": status = "Z" # give it highest priority elif status == "D": status = "O" if status == "U": self.shouldUpdate = True name = path states[name] = status try: if self.reportedStates[name] != status: self.statusList.append("{0} {1}".format( status, name)) except KeyError: self.statusList.append("{0} {1}".format( status, name)) for name in list(self.reportedStates.keys()): if name not in states: self.statusList.append(" {0}".format(name)) self.reportedStates = states return True, self.tr( "Subversion status checked successfully (using svn)") else: process.kill() process.waitForFinished() return False, \ str(process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') else: process.kill() process.waitForFinished() return False, self.tr("Could not start the Subversion process.")
class SvnLogBrowserDialog(QWidget, Ui_SvnLogBrowserDialog): """ Class implementing a dialog to browse the log history. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnLogBrowserDialog, self).__init__(parent) self.setupUi(self) self.__position = QPoint() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.filesTree.headerItem().setText(self.filesTree.columnCount(), "") self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder) self.vcs = vcs self.__initData() self.fromDate.setDisplayFormat("yyyy-MM-dd") self.toDate.setDisplayFormat("yyyy-MM-dd") self.__resetUI() self.__messageRole = Qt.UserRole self.__changesRole = Qt.UserRole + 1 self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_sep1 = QRegExp('\\-+\\s*') self.rx_sep2 = QRegExp('=+\\s*') self.rx_rev1 = QRegExp( 'rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "rev" followed by one or more decimals followed by a colon followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_rev2 = QRegExp( 'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*') # "r" followed by one or more decimals followed by " | " followed # anything up to " | " (twice) followed by one or more decimals # followed by anything self.rx_flags1 = QRegExp( r""" ([ADM])\s(.*)\s+\(\w+\s+(.*):([0-9]+)\)\s*""") # three blanks followed by A or D or M followed by path followed by # path copied from followed by copied from revision self.rx_flags2 = QRegExp(' ([ADM]) (.*)\\s*') # three blanks followed by A or D or M followed by path self.flags = { 'A': self.tr('Added'), 'D': self.tr('Deleted'), 'M': self.tr('Modified'), 'R': self.tr('Replaced'), } self.intercept = False def __initData(self): """ Private method to (re-)initialize some data. """ self.__maxDate = QDate() self.__minDate = QDate() self.__filterLogsEnabled = True self.buf = [] # buffer for stdout self.diff = None self.__started = False self.__lastRev = 0 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.__position = self.pos() e.accept() def show(self): """ Public slot to show the dialog. """ if not self.__position.isNull(): self.move(self.__position) self.__resetUI() super(SvnLogBrowserDialog, self).show() def __resetUI(self): """ Private method to reset the user interface. """ self.fromDate.setDate(QDate.currentDate()) self.toDate.setDate(QDate.currentDate()) self.fieldCombo.setCurrentIndex(self.fieldCombo.findText( self.tr("Message"))) self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences( "LogLimit")) self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences( "StopLogOnCopy")) self.logTree.clear() self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) def __resizeColumnsLog(self): """ Private method to resize the log tree columns. """ self.logTree.header().resizeSections(QHeaderView.ResizeToContents) self.logTree.header().setStretchLastSection(True) def __resortLog(self): """ Private method to resort the log tree. """ self.logTree.sortItems( self.logTree.sortColumn(), self.logTree.header().sortIndicatorOrder()) def __resizeColumnsFiles(self): """ Private method to resize the changed files tree columns. """ self.filesTree.header().resizeSections(QHeaderView.ResizeToContents) self.filesTree.header().setStretchLastSection(True) def __resortFiles(self): """ Private method to resort the changed files tree. """ sortColumn = self.filesTree.sortColumn() self.filesTree.sortItems( 1, self.filesTree.header().sortIndicatorOrder()) self.filesTree.sortItems( sortColumn, self.filesTree.header().sortIndicatorOrder()) def __generateLogItem(self, author, date, message, revision, changedPaths): """ Private method to generate a log tree entry. @param author author info (string) @param date date info (string) @param message text of the log message (list of strings) @param revision revision info (string) @param changedPaths list of dictionary objects containing info about the changed files/directories @return reference to the generated item (QTreeWidgetItem) """ msg = [] for line in message: msg.append(line.strip()) itm = QTreeWidgetItem(self.logTree) itm.setData(0, Qt.DisplayRole, int(revision)) itm.setData(1, Qt.DisplayRole, author) itm.setData(2, Qt.DisplayRole, date) itm.setData(3, Qt.DisplayRole, " ".join(msg)) itm.setData(0, self.__messageRole, message) itm.setData(0, self.__changesRole, changedPaths) itm.setTextAlignment(0, Qt.AlignRight) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignLeft) itm.setTextAlignment(4, Qt.AlignLeft) try: self.__lastRev = int(revision) except ValueError: self.__lastRev = 0 return itm def __generateFileItem(self, action, path, copyFrom, copyRev): """ Private method to generate a changed files tree entry. @param action indicator for the change action ("A", "D" or "M") @param path path of the file in the repository (string) @param copyFrom path the file was copied from (None, string) @param copyRev revision the file was copied from (None, string) @return reference to the generated item (QTreeWidgetItem) """ itm = QTreeWidgetItem(self.filesTree, [ self.flags[action], path, copyFrom, copyRev, ]) itm.setTextAlignment(3, Qt.AlignRight) return itm def __getLogEntries(self, startRev=None): """ Private method to retrieve log entries from the repository. @param startRev revision number to start from (integer, string) """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.intercept = False self.process.kill() self.buf = [] self.cancelled = False self.errors.clear() args = [] args.append('log') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['log']) args.append('--verbose') args.append('--limit') args.append('{0:d}'.format(self.limitSpinBox.value())) if startRev is not None: args.append('--revision') args.append('{0}:0'.format(startRev)) if self.stopCheckBox.isChecked(): args.append('--stop-on-copy') args.append(self.fname) self.process.setWorkingDirectory(self.dname) self.inputGroup.setEnabled(True) self.inputGroup.show() self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('svn')) def start(self, fn, isFile=False): """ Public slot to start the svn log command. @param fn filename to show the log for (string) @keyparam isFile flag indicating log for a file is to be shown (boolean) """ self.sbsCheckBox.setEnabled(isFile) self.sbsCheckBox.setVisible(isFile) self.errorGroup.hide() QApplication.processEvents() self.__initData() self.filename = fn self.dname, self.fname = self.vcs.splitPath(fn) self.activateWindow() self.raise_() self.logTree.clear() self.__started = True self.__getLogEntries() 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.__processBuffer() self.__finish() 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) QApplication.restoreOverrideCursor() self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() def __processBuffer(self): """ Private method to process the buffered output of the svn log command. """ noEntries = 0 log = {"message": []} changedPaths = [] for s in self.buf: if self.rx_rev1.exactMatch(s): log["revision"] = self.rx_rev.cap(1) log["author"] = self.rx_rev.cap(2) log["date"] = self.rx_rev.cap(3) # number of lines is ignored elif self.rx_rev2.exactMatch(s): log["revision"] = self.rx_rev2.cap(1) log["author"] = self.rx_rev2.cap(2) log["date"] = self.rx_rev2.cap(3) # number of lines is ignored elif self.rx_flags1.exactMatch(s): changedPaths.append({ "action": self.rx_flags1.cap(1).strip(), "path": self.rx_flags1.cap(2).strip(), "copyfrom_path": self.rx_flags1.cap(3).strip(), "copyfrom_revision": self.rx_flags1.cap(4).strip(), }) elif self.rx_flags2.exactMatch(s): changedPaths.append({ "action": self.rx_flags2.cap(1).strip(), "path": self.rx_flags2.cap(2).strip(), "copyfrom_path": "", "copyfrom_revision": "", }) elif self.rx_sep1.exactMatch(s) or self.rx_sep2.exactMatch(s): if len(log) > 1: self.__generateLogItem( log["author"], log["date"], log["message"], log["revision"], changedPaths) dt = QDate.fromString(log["date"], Qt.ISODate) if not self.__maxDate.isValid() and \ not self.__minDate.isValid(): self.__maxDate = dt self.__minDate = dt else: if self.__maxDate < dt: self.__maxDate = dt if self.__minDate > dt: self.__minDate = dt noEntries += 1 log = {"message": []} changedPaths = [] else: if s.strip().endswith(":") or not s.strip(): continue else: log["message"].append(s) self.__resizeColumnsLog() self.__resortLog() if self.__started: self.logTree.setCurrentItem(self.logTree.topLevelItem(0)) self.__started = False if noEntries < self.limitSpinBox.value() and not self.cancelled: self.nextButton.setEnabled(False) self.limitSpinBox.setEnabled(False) self.__filterLogsEnabled = False self.fromDate.setMinimumDate(self.__minDate) self.fromDate.setMaximumDate(self.__maxDate) self.fromDate.setDate(self.__minDate) self.toDate.setMinimumDate(self.__minDate) self.toDate.setMaximumDate(self.__maxDate) self.toDate.setDate(self.__maxDate) self.__filterLogsEnabled = True self.__filterLogs() def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into a buffer. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): line = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') self.buf.append(line) 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() def __diffRevisions(self, rev1, rev2): """ Private method to do a diff of two revisions. @param rev1 first revision number (integer) @param rev2 second revision number (integer) """ if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked(): self.vcs.svnSbsDiff(self.filename, revisions=(str(rev1), str(rev2))) else: if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() self.diff.raise_() self.diff.start(self.filename, [rev1, rev2]) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.cancelled = True self.__finish() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_logTree_currentItemChanged(self, current, previous): """ Private slot called, when the current item of the log tree changes. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ if current is not None: self.messageEdit.clear() for line in current.data(0, self.__messageRole): self.messageEdit.append(line.strip()) self.filesTree.clear() changes = current.data(0, self.__changesRole) if len(changes) > 0: for change in changes: self.__generateFileItem( change["action"], change["path"], change["copyfrom_path"], change["copyfrom_revision"]) self.__resizeColumnsFiles() self.__resortFiles() self.diffPreviousButton.setEnabled( current != self.logTree.topLevelItem( self.logTree.topLevelItemCount() - 1)) @pyqtSlot() def on_logTree_itemSelectionChanged(self): """ Private slot called, when the selection has changed. """ self.diffRevisionsButton.setEnabled( len(self.logTree.selectedItems()) == 2) @pyqtSlot() def on_nextButton_clicked(self): """ Private slot to handle the Next button. """ if self.__lastRev > 1: self.__getLogEntries(self.__lastRev - 1) @pyqtSlot() def on_diffPreviousButton_clicked(self): """ Private slot to handle the Diff to Previous button. """ itm = self.logTree.currentItem() if itm is None: self.diffPreviousButton.setEnabled(False) return rev2 = int(itm.text(0)) itm = self.logTree.topLevelItem( self.logTree.indexOfTopLevelItem(itm) + 1) if itm is None: self.diffPreviousButton.setEnabled(False) return rev1 = int(itm.text(0)) self.__diffRevisions(rev1, rev2) @pyqtSlot() def on_diffRevisionsButton_clicked(self): """ Private slot to handle the Compare Revisions button. """ items = self.logTree.selectedItems() if len(items) != 2: self.diffRevisionsButton.setEnabled(False) return rev2 = int(items[0].text(0)) rev1 = int(items[1].text(0)) self.__diffRevisions(min(rev1, rev2), max(rev1, rev2)) @pyqtSlot(QDate) def on_fromDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(QDate) def on_toDate_dateChanged(self, date): """ Private slot called, when the from date changes. @param date new date (QDate) """ self.__filterLogs() @pyqtSlot(str) def on_fieldCombo_activated(self, txt): """ Private slot called, when a new filter field is selected. @param txt text of the selected field (string) """ self.__filterLogs() @pyqtSlot(str) def on_rxEdit_textChanged(self, txt): """ Private slot called, when a filter expression is entered. @param txt filter expression (string) """ self.__filterLogs() def __filterLogs(self): """ Private method to filter the log entries. """ if self.__filterLogsEnabled: from_ = self.fromDate.date().toString("yyyy-MM-dd") to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd") txt = self.fieldCombo.currentText() if txt == self.tr("Author"): fieldIndex = 1 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) elif txt == self.tr("Revision"): fieldIndex = 0 txt = self.rxEdit.text() if txt.startswith("^"): searchRx = QRegExp( "^\s*{0}".format(txt[1:]), Qt.CaseInsensitive) else: searchRx = QRegExp(txt, Qt.CaseInsensitive) else: fieldIndex = 3 searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive) currentItem = self.logTree.currentItem() for topIndex in range(self.logTree.topLevelItemCount()): topItem = self.logTree.topLevelItem(topIndex) if topItem.text(2) <= to_ and topItem.text(2) >= from_ and \ searchRx.indexIn(topItem.text(fieldIndex)) > -1: topItem.setHidden(False) if topItem is currentItem: self.on_logTree_currentItemChanged(topItem, None) else: topItem.setHidden(True) if topItem is currentItem: self.messageEdit.clear() self.filesTree.clear() @pyqtSlot(bool) def on_stopCheckBox_clicked(self, checked): """ Private slot called, when the stop on copy/move checkbox is clicked. @param checked flag indicating the checked state (boolean) """ self.vcs.getPlugin().setPreferences("StopLogOnCopy", self.stopCheckBox.isChecked()) self.nextButton.setEnabled(True) self.limitSpinBox.setEnabled(True) 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.errorGroup.show() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnLogBrowserDialog, self).keyPressEvent(evt)
class SvnChangeListsDialog(QDialog, Ui_SvnChangeListsDialog): """ Class implementing a dialog to browse the change lists. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnChangeListsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.process = None self.vcs = vcs self.rx_status = QRegExp( '(.{8,9})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)\\s*') # flags (8 or 9 anything), revision, changed rev, author, path self.rx_status2 = \ QRegExp('(.{8,9})\\s+(.+)\\s*') # flags (8 or 9 anything), path self.rx_changelist = \ QRegExp('--- \\S+ .([\\w\\s]+).:\\s+') # three dashes, Changelist (translated), quote, # changelist name, quote, : @pyqtSlot(QListWidgetItem, QListWidgetItem) def on_changeLists_currentItemChanged(self, current, previous): """ Private slot to handle the selection of a new item. @param current current item (QListWidgetItem) @param previous previous current item (QListWidgetItem) """ self.filesList.clear() if current is not None: changelist = current.text() if changelist in self.changeListsDict: self.filesList.addItems( sorted(self.changeListsDict[changelist])) def start(self, path): """ Public slot to populate the data. @param path directory name to show change lists for (string) """ self.changeListsDict = {} self.filesLabel.setText( self.tr("Files (relative to {0}):").format(path)) self.errorGroup.hide() self.intercept = False self.path = path self.currentChangelist = "" self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) args = [] args.append('status') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['status']) if '--verbose' not in self.vcs.options['global'] and \ '--verbose' not in self.vcs.options['status']: args.append('--verbose') if isinstance(path, list): self.dname, fnames = self.vcs.splitPathList(path) self.vcs.addArguments(args, fnames) else: self.dname, fname = self.vcs.splitPath(path) args.append(fname) self.process.setWorkingDirectory(self.dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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 __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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.inputGroup.setEnabled(False) self.inputGroup.hide() if len(self.changeListsDict) == 0: self.changeLists.addItem(self.tr("No changelists found")) self.buttonBox.button(QDialogButtonBox.Close).setFocus( Qt.OtherFocusReason) else: self.changeLists.addItems(sorted(self.changeListsDict.keys())) self.changeLists.setCurrentRow(0) self.changeLists.setFocus(Qt.OtherFocusReason) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() 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.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.currentChangelist != "" and \ self.rx_status.exactMatch(s): file = self.rx_status.cap(5).strip() filename = file.replace(self.path + os.sep, "") if filename not in \ self.changeListsDict[self.currentChangelist]: self.changeListsDict[self.currentChangelist].append( filename) elif self.currentChangelist != "" and \ self.rx_status2.exactMatch(s): file = self.rx_status2.cap(2).strip() filename = file.replace(self.path + os.sep, "") if filename not in \ self.changeListsDict[self.currentChangelist]: self.changeListsDict[self.currentChangelist].append( filename) elif self.rx_changelist.exactMatch(s): self.currentChangelist = self.rx_changelist.cap(1) if self.currentChangelist not in self.changeListsDict: self.changeListsDict[self.currentChangelist] = [] 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnChangeListsDialog, self).keyPressEvent(evt)
class SvnStatusDialog(QWidget, Ui_SvnStatusDialog): """ Class implementing a dialog to show the output of the svn status command process. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnStatusDialog, self).__init__(parent) self.setupUi(self) self.__toBeCommittedColumn = 0 self.__changelistColumn = 1 self.__statusColumn = 2 self.__propStatusColumn = 3 self.__lockedColumn = 4 self.__historyColumn = 5 self.__switchedColumn = 6 self.__lockinfoColumn = 7 self.__upToDateColumn = 8 self.__pathColumn = 12 self.__lastColumn = self.statusList.columnCount() self.refreshButton = \ self.buttonBox.addButton(self.tr("Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the status display")) self.refreshButton.setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.diff = None self.vcs = vcs self.vcs.committed.connect(self.__committed) self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.statusList.headerItem().setText(self.__lastColumn, "") self.statusList.header().setSortIndicator(self.__pathColumn, Qt.AscendingOrder) if self.vcs.version < (1, 5, 0): self.statusList.header().hideSection(self.__changelistColumn) self.menuactions = [] self.menu = QMenu() self.menuactions.append( self.menu.addAction(self.tr("Commit changes to repository..."), self.__commit)) self.menuactions.append( self.menu.addAction(self.tr("Select all for commit"), self.__commitSelectAll)) self.menuactions.append( self.menu.addAction(self.tr("Deselect all from commit"), self.__commitDeselectAll)) self.menu.addSeparator() self.menuactions.append( self.menu.addAction(self.tr("Add to repository"), self.__add)) self.menuactions.append( self.menu.addAction(self.tr("Show differences"), self.__diff)) self.menuactions.append( self.menu.addAction(self.tr("Show differences side-by-side"), self.__sbsDiff)) self.menuactions.append( self.menu.addAction(self.tr("Revert changes"), self.__revert)) self.menuactions.append( self.menu.addAction(self.tr("Restore missing"), self.__restoreMissing)) if self.vcs.version >= (1, 5, 0): self.menu.addSeparator() self.menuactions.append( self.menu.addAction(self.tr("Add to Changelist"), self.__addToChangelist)) self.menuactions.append( self.menu.addAction(self.tr("Remove from Changelist"), self.__removeFromChangelist)) if self.vcs.version >= (1, 2, 0): self.menu.addSeparator() self.menuactions.append( self.menu.addAction(self.tr("Lock"), self.__lock)) self.menuactions.append( self.menu.addAction(self.tr("Unlock"), self.__unlock)) self.menuactions.append( self.menu.addAction(self.tr("Break lock"), self.__breakLock)) self.menuactions.append( self.menu.addAction(self.tr("Steal lock"), self.__stealLock)) self.menu.addSeparator() self.menuactions.append( self.menu.addAction(self.tr("Adjust column sizes"), self.__resizeColumns)) for act in self.menuactions: act.setEnabled(False) self.statusList.setContextMenuPolicy(Qt.CustomContextMenu) self.statusList.customContextMenuRequested.connect( self.__showContextMenu) self.modifiedIndicators = [ self.tr('added'), self.tr('deleted'), self.tr('modified'), ] self.missingIndicators = [ self.tr('missing'), ] self.unversionedIndicators = [ self.tr('unversioned'), ] self.lockedIndicators = [ self.tr('locked'), ] self.stealBreakLockIndicators = [ self.tr('other lock'), self.tr('stolen lock'), self.tr('broken lock'), ] self.unlockedIndicators = [ self.tr('not locked'), ] self.status = { ' ': self.tr('normal'), 'A': self.tr('added'), 'D': self.tr('deleted'), 'M': self.tr('modified'), 'R': self.tr('replaced'), 'C': self.tr('conflict'), 'X': self.tr('external'), 'I': self.tr('ignored'), '?': self.tr('unversioned'), '!': self.tr('missing'), '~': self.tr('type error'), } self.propStatus = { ' ': self.tr('normal'), 'M': self.tr('modified'), 'C': self.tr('conflict'), } self.locked = { ' ': self.tr('no'), 'L': self.tr('yes'), } self.history = { ' ': self.tr('no'), '+': self.tr('yes'), } self.switched = { ' ': self.tr('no'), 'S': self.tr('yes'), } self.lockinfo = { ' ': self.tr('not locked'), 'K': self.tr('locked'), 'O': self.tr('other lock'), 'T': self.tr('stolen lock'), 'B': self.tr('broken lock'), } self.uptodate = { ' ': self.tr('yes'), '*': self.tr('no'), } self.rx_status = QRegExp( '(.{8,9})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)\\s*') # flags (8 or 9 anything), revision, changed rev, author, path self.rx_status2 = \ QRegExp('(.{8,9})\\s+(.+)\\s*') # flags (8 or 9 anything), path self.rx_changelist = \ QRegExp('--- \\S+ .([\\w\\s]+).:\\s+') # three dashes, Changelist (translated), quote, # changelist name, quote, : self.__nonverbose = True def __resort(self): """ Private method to resort the tree. """ self.statusList.sortItems( self.statusList.sortColumn(), self.statusList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.statusList.header().resizeSections(QHeaderView.ResizeToContents) self.statusList.header().setStretchLastSection(True) def __generateItem(self, status, propStatus, locked, history, switched, lockinfo, uptodate, revision, change, author, path): """ Private method to generate a status item in the status list. @param status status indicator (string) @param propStatus property status indicator (string) @param locked locked indicator (string) @param history history indicator (string) @param switched switched indicator (string) @param lockinfo lock indicator (string) @param uptodate up to date indicator (string) @param revision revision string (string) @param change revision of last change (string) @param author author of the last change (string) @param path path of the file or directory (string) """ if self.__nonverbose and \ status == " " and \ propStatus == " " and \ locked == " " and \ history == " " and \ switched == " " and \ lockinfo == " " and \ uptodate == " " and \ self.currentChangelist == "": return if revision == "": rev = "" else: try: rev = int(revision) except ValueError: rev = revision if change == "": chg = "" else: try: chg = int(change) except ValueError: chg = change statusText = self.status[status] itm = QTreeWidgetItem(self.statusList) itm.setData(0, Qt.DisplayRole, "") itm.setData(1, Qt.DisplayRole, self.currentChangelist) itm.setData(2, Qt.DisplayRole, statusText) itm.setData(3, Qt.DisplayRole, self.propStatus[propStatus]) itm.setData(4, Qt.DisplayRole, self.locked[locked]) itm.setData(5, Qt.DisplayRole, self.history[history]) itm.setData(6, Qt.DisplayRole, self.switched[switched]) itm.setData(7, Qt.DisplayRole, self.lockinfo[lockinfo]) itm.setData(8, Qt.DisplayRole, self.uptodate[uptodate]) itm.setData(9, Qt.DisplayRole, rev) itm.setData(10, Qt.DisplayRole, chg) itm.setData(11, Qt.DisplayRole, author) itm.setData(12, Qt.DisplayRole, path) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignHCenter) itm.setTextAlignment(3, Qt.AlignHCenter) itm.setTextAlignment(4, Qt.AlignHCenter) itm.setTextAlignment(5, Qt.AlignHCenter) itm.setTextAlignment(6, Qt.AlignHCenter) itm.setTextAlignment(7, Qt.AlignHCenter) itm.setTextAlignment(8, Qt.AlignHCenter) itm.setTextAlignment(9, Qt.AlignRight) itm.setTextAlignment(10, Qt.AlignRight) itm.setTextAlignment(11, Qt.AlignLeft) itm.setTextAlignment(12, Qt.AlignLeft) if status in "ADM" or propStatus in "M": itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable) itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked) else: itm.setFlags(itm.flags() & ~Qt.ItemIsUserCheckable) self.hidePropertyStatusColumn = self.hidePropertyStatusColumn and \ propStatus == " " self.hideLockColumns = self.hideLockColumns and \ locked == " " and lockinfo == " " self.hideUpToDateColumn = self.hideUpToDateColumn and uptodate == " " self.hideHistoryColumn = self.hideHistoryColumn and history == " " self.hideSwitchedColumn = self.hideSwitchedColumn and switched == " " if statusText not in self.__statusFilters: self.__statusFilters.append(statusText) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, fn): """ Public slot to start the svn status command. @param fn filename(s)/directoryname(s) to show the status of (string or list of strings) """ self.errorGroup.hide() self.intercept = False self.args = fn for act in self.menuactions: act.setEnabled(False) self.addButton.setEnabled(False) self.commitButton.setEnabled(False) self.diffButton.setEnabled(False) self.sbsDiffButton.setEnabled(False) self.revertButton.setEnabled(False) self.restoreButton.setEnabled(False) self.statusFilterCombo.clear() self.__statusFilters = [] self.statusList.clear() self.currentChangelist = "" self.changelistFound = False self.hidePropertyStatusColumn = True self.hideLockColumns = True self.hideUpToDateColumn = True self.hideHistoryColumn = True self.hideSwitchedColumn = True self.process.kill() args = [] args.append('status') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['status']) if '--verbose' not in self.vcs.options['global'] and \ '--verbose' not in self.vcs.options['status']: args.append('--verbose') self.__nonverbose = True else: self.__nonverbose = False if '--show-updates' in self.vcs.options['status'] or \ '-u' in self.vcs.options['status']: self.activateWindow() self.raise_() if isinstance(fn, list): self.dname, fnames = self.vcs.splitPathList(fn) self.vcs.addArguments(args, fnames) else: self.dname, fname = self.vcs.splitPath(fn) args.append(fname) self.process.setWorkingDirectory(self.dname) self.setWindowTitle(self.tr('Subversion Status')) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.').format('svn')) else: self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.inputGroup.setEnabled(True) self.inputGroup.show() self.refreshButton.setEnabled(False) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.buttonBox.button(QDialogButtonBox.Close).setFocus( Qt.OtherFocusReason) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) self.__statusFilters.sort() self.__statusFilters.insert(0, "<{0}>".format(self.tr("all"))) self.statusFilterCombo.addItems(self.__statusFilters) for act in self.menuactions: act.setEnabled(True) self.__resort() self.__resizeColumns() self.statusList.setColumnHidden(self.__changelistColumn, not self.changelistFound) self.statusList.setColumnHidden(self.__propStatusColumn, self.hidePropertyStatusColumn) self.statusList.setColumnHidden(self.__lockedColumn, self.hideLockColumns) self.statusList.setColumnHidden(self.__lockinfoColumn, self.hideLockColumns) self.statusList.setColumnHidden(self.__upToDateColumn, self.hideUpToDateColumn) self.statusList.setColumnHidden(self.__historyColumn, self.hideHistoryColumn) self.statusList.setColumnHidden(self.__switchedColumn, self.hideSwitchedColumn) self.__updateButtons() self.__updateCommitButton() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() 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.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.rx_status.exactMatch(s): flags = self.rx_status.cap(1) rev = self.rx_status.cap(2) change = self.rx_status.cap(3) author = self.rx_status.cap(4) path = self.rx_status.cap(5).strip() self.__generateItem(flags[0], flags[1], flags[2], flags[3], flags[4], flags[5], flags[-1], rev, change, author, path) elif self.rx_status2.exactMatch(s): flags = self.rx_status2.cap(1) path = self.rx_status2.cap(2).strip() self.__generateItem(flags[0], flags[1], flags[2], flags[3], flags[4], flags[5], flags[-1], "", "", "", path) elif self.rx_changelist.exactMatch(s): self.currentChangelist = self.rx_changelist.cap(1) self.changelistFound = True 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnStatusDialog, self).keyPressEvent(evt) @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the status display. """ self.start(self.args) def __updateButtons(self): """ Private method to update the VCS buttons status. """ modified = len(self.__getModifiedItems()) unversioned = len(self.__getUnversionedItems()) missing = len(self.__getMissingItems()) self.addButton.setEnabled(unversioned) self.diffButton.setEnabled(modified) self.sbsDiffButton.setEnabled(modified == 1) self.revertButton.setEnabled(modified) self.restoreButton.setEnabled(missing) def __updateCommitButton(self): """ Private method to update the Commit button status. """ commitable = len(self.__getCommitableItems()) self.commitButton.setEnabled(commitable) @pyqtSlot(str) def on_statusFilterCombo_activated(self, txt): """ Private slot to react to the selection of a status filter. @param txt selected status filter (string) """ if txt == "<{0}>".format(self.tr("all")): for topIndex in range(self.statusList.topLevelItemCount()): topItem = self.statusList.topLevelItem(topIndex) topItem.setHidden(False) else: for topIndex in range(self.statusList.topLevelItemCount()): topItem = self.statusList.topLevelItem(topIndex) topItem.setHidden(topItem.text(self.__statusColumn) != txt) @pyqtSlot(QTreeWidgetItem, int) def on_statusList_itemChanged(self, item, column): """ Private slot to act upon item changes. @param item reference to the changed item (QTreeWidgetItem) @param column index of column that changed (integer) """ if column == self.__toBeCommittedColumn: self.__updateCommitButton() @pyqtSlot() def on_statusList_itemSelectionChanged(self): """ Private slot to act upon changes of selected items. """ self.__updateButtons() @pyqtSlot() def on_commitButton_clicked(self): """ Private slot to handle the press of the Commit button. """ self.__commit() @pyqtSlot() def on_addButton_clicked(self): """ Private slot to handle the press of the Add button. """ self.__add() @pyqtSlot() def on_diffButton_clicked(self): """ Private slot to handle the press of the Differences button. """ self.__diff() @pyqtSlot() def on_sbsDiffButton_clicked(self): """ Private slot to handle the press of the Side-by-Side Diff button. """ self.__sbsDiff() @pyqtSlot() def on_revertButton_clicked(self): """ Private slot to handle the press of the Revert button. """ self.__revert() @pyqtSlot() def on_restoreButton_clicked(self): """ Private slot to handle the press of the Restore button. """ self.__restoreMissing() ########################################################################### ## Context menu handling methods ########################################################################### def __showContextMenu(self, coord): """ Private slot to show the context menu of the status list. @param coord the position of the mouse pointer (QPoint) """ self.menu.popup(self.statusList.mapToGlobal(coord)) def __commit(self): """ Private slot to handle the Commit context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getCommitableItems() ] if not names: E5MessageBox.information( self, self.tr("Commit"), self.tr("""There are no entries selected to be""" """ committed.""")) return if Preferences.getVCS("AutoSaveFiles"): vm = e5App().getObject("ViewManager") for name in names: vm.saveEditor(name) self.vcs.vcsCommit(names, '') def __committed(self): """ Private slot called after the commit has finished. """ if self.isVisible(): self.on_refreshButton_clicked() self.vcs.checkVCSStatus() def __commitSelectAll(self): """ Private slot to select all entries for commit. """ self.__commitSelect(True) def __commitDeselectAll(self): """ Private slot to deselect all entries from commit. """ self.__commitSelect(False) def __add(self): """ Private slot to handle the Add context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getUnversionedItems() ] if not names: E5MessageBox.information( self, self.tr("Add"), self.tr("""There are no unversioned entries""" """ available/selected.""")) return self.vcs.vcsAdd(names) self.on_refreshButton_clicked() project = e5App().getObject("Project") for name in names: project.getModel().updateVCSStatus(name) self.vcs.checkVCSStatus() def __revert(self): """ Private slot to handle the Revert context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems() ] if not names: E5MessageBox.information( self, self.tr("Revert"), self.tr("""There are no uncommitted changes""" """ available/selected.""")) return self.vcs.vcsRevert(names) self.raise_() self.activateWindow() self.on_refreshButton_clicked() project = e5App().getObject("Project") for name in names: project.getModel().updateVCSStatus(name) self.vcs.checkVCSStatus() def __restoreMissing(self): """ Private slot to handle the Restore Missing context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getMissingItems() ] if not names: E5MessageBox.information( self, self.tr("Revert"), self.tr("""There are no missing entries""" """ available/selected.""")) return self.vcs.vcsRevert(names) self.on_refreshButton_clicked() self.vcs.checkVCSStatus() def __diff(self): """ Private slot to handle the Diff context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems() ] if not names: E5MessageBox.information( self, self.tr("Differences"), self.tr("""There are no uncommitted changes""" """ available/selected.""")) return if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() QApplication.processEvents() self.diff.start(names, refreshable=True) def __sbsDiff(self): """ Private slot to handle the Side-by-Side Diff context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems() ] if not names: E5MessageBox.information( self, self.tr("Side-by-Side Diff"), self.tr("""There are no uncommitted changes""" """ available/selected.""")) return elif len(names) > 1: E5MessageBox.information( self, self.tr("Side-by-Side Diff"), self.tr("""Only one file with uncommitted changes""" """ must be selected.""")) return self.vcs.svnSbsDiff(names[0]) def __lock(self): """ Private slot to handle the Lock context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems(self.unlockedIndicators) ] if not names: E5MessageBox.information( self, self.tr("Lock"), self.tr("""There are no unlocked files""" """ available/selected.""")) return self.vcs.svnLock(names, parent=self) self.on_refreshButton_clicked() def __unlock(self): """ Private slot to handle the Unlock context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems(self.lockedIndicators) ] if not names: E5MessageBox.information( self, self.tr("Unlock"), self.tr("""There are no locked files""" """ available/selected.""")) return self.vcs.svnUnlock(names, parent=self) self.on_refreshButton_clicked() def __breakLock(self): """ Private slot to handle the Break Lock context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems(self.stealBreakLockIndicators) ] if not names: E5MessageBox.information( self, self.tr("Break Lock"), self.tr("""There are no locked files""" """ available/selected.""")) return self.vcs.svnUnlock(names, parent=self, breakIt=True) self.on_refreshButton_clicked() def __stealLock(self): """ Private slot to handle the Break Lock context menu entry. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems(self.stealBreakLockIndicators) ] if not names: E5MessageBox.information( self, self.tr("Steal Lock"), self.tr("""There are no locked files""" """ available/selected.""")) return self.vcs.svnLock(names, parent=self, stealIt=True) self.on_refreshButton_clicked() def __addToChangelist(self): """ Private slot to add entries to a changelist. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getNonChangelistItems() ] if not names: E5MessageBox.information( self, self.tr("Remove from Changelist"), self.tr("""There are no files available/selected not """ """belonging to a changelist.""")) return self.vcs.svnAddToChangelist(names) self.on_refreshButton_clicked() def __removeFromChangelist(self): """ Private slot to remove entries from their changelists. """ names = [ os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getChangelistItems() ] if not names: E5MessageBox.information( self, self.tr("Remove from Changelist"), self.tr("""There are no files available/selected belonging""" """ to a changelist.""")) return self.vcs.svnRemoveFromChangelist(names) self.on_refreshButton_clicked() def __getCommitableItems(self): """ Private method to retrieve all entries the user wants to commit. @return list of all items, the user has checked """ commitableItems = [] for index in range(self.statusList.topLevelItemCount()): itm = self.statusList.topLevelItem(index) if itm.checkState(self.__toBeCommittedColumn) == Qt.Checked: commitableItems.append(itm) return commitableItems def __getModifiedItems(self): """ Private method to retrieve all entries, that have a modified status. @return list of all items with a modified status """ modifiedItems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__statusColumn) in self.modifiedIndicators or \ itm.text(self.__propStatusColumn) in self.modifiedIndicators: modifiedItems.append(itm) return modifiedItems def __getUnversionedItems(self): """ Private method to retrieve all entries, that have an unversioned status. @return list of all items with an unversioned status """ unversionedItems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__statusColumn) in self.unversionedIndicators: unversionedItems.append(itm) return unversionedItems def __getMissingItems(self): """ Private method to retrieve all entries, that have a missing status. @return list of all items with a missing status """ missingItems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__statusColumn) in self.missingIndicators: missingItems.append(itm) return missingItems def __getLockActionItems(self, indicators): """ Private method to retrieve all emtries, that have a locked status. @param indicators list of indicators to check against (list of strings) @return list of all items with a locked status """ lockitems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__lockinfoColumn) in indicators: lockitems.append(itm) return lockitems def __getChangelistItems(self): """ Private method to retrieve all entries, that are members of a changelist. @return list of all items belonging to a changelist """ clitems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__changelistColumn) != "": clitems.append(itm) return clitems def __getNonChangelistItems(self): """ Private method to retrieve all entries, that are not members of a changelist. @return list of all items not belonging to a changelist """ clitems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__changelistColumn) == "": clitems.append(itm) return clitems def __commitSelect(self, selected): """ Private slot to select or deselect all entries. @param selected commit selection state to be set (boolean) """ for index in range(self.statusList.topLevelItemCount()): itm = self.statusList.topLevelItem(index) if itm.flags() & Qt.ItemIsUserCheckable: if selected: itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked) else: itm.setCheckState(self.__toBeCommittedColumn, Qt.Unchecked)
class SvnStatusDialog(QWidget, Ui_SvnStatusDialog): """ Class implementing a dialog to show the output of the svn status command process. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnStatusDialog, self).__init__(parent) self.setupUi(self) self.__toBeCommittedColumn = 0 self.__changelistColumn = 1 self.__statusColumn = 2 self.__propStatusColumn = 3 self.__lockedColumn = 4 self.__historyColumn = 5 self.__switchedColumn = 6 self.__lockinfoColumn = 7 self.__upToDateColumn = 8 self.__pathColumn = 12 self.__lastColumn = self.statusList.columnCount() self.refreshButton = \ self.buttonBox.addButton(self.tr("Refresh"), QDialogButtonBox.ActionRole) self.refreshButton.setToolTip( self.tr("Press to refresh the status display")) self.refreshButton.setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.diff = None self.process = None self.vcs = vcs self.vcs.committed.connect(self.__committed) self.statusList.headerItem().setText(self.__lastColumn, "") self.statusList.header().setSortIndicator(self.__pathColumn, Qt.AscendingOrder) if self.vcs.version < (1, 5, 0): self.statusList.header().hideSection(self.__changelistColumn) self.menuactions = [] self.menu = QMenu() self.menuactions.append(self.menu.addAction( self.tr("Commit changes to repository..."), self.__commit)) self.menuactions.append(self.menu.addAction( self.tr("Select all for commit"), self.__commitSelectAll)) self.menuactions.append(self.menu.addAction( self.tr("Deselect all from commit"), self.__commitDeselectAll)) self.menu.addSeparator() self.menuactions.append(self.menu.addAction( self.tr("Add to repository"), self.__add)) self.menuactions.append(self.menu.addAction( self.tr("Show differences"), self.__diff)) self.menuactions.append(self.menu.addAction( self.tr("Show differences side-by-side"), self.__sbsDiff)) self.menuactions.append(self.menu.addAction( self.tr("Revert changes"), self.__revert)) self.menuactions.append(self.menu.addAction( self.tr("Restore missing"), self.__restoreMissing)) if self.vcs.version >= (1, 5, 0): self.menu.addSeparator() self.menuactions.append(self.menu.addAction( self.tr("Add to Changelist"), self.__addToChangelist)) self.menuactions.append(self.menu.addAction( self.tr("Remove from Changelist"), self.__removeFromChangelist)) if self.vcs.version >= (1, 2, 0): self.menu.addSeparator() self.menuactions.append(self.menu.addAction( self.tr("Lock"), self.__lock)) self.menuactions.append(self.menu.addAction( self.tr("Unlock"), self.__unlock)) self.menuactions.append(self.menu.addAction( self.tr("Break lock"), self.__breakLock)) self.menuactions.append(self.menu.addAction( self.tr("Steal lock"), self.__stealLock)) self.menu.addSeparator() self.menuactions.append(self.menu.addAction( self.tr("Adjust column sizes"), self.__resizeColumns)) for act in self.menuactions: act.setEnabled(False) self.statusList.setContextMenuPolicy(Qt.CustomContextMenu) self.statusList.customContextMenuRequested.connect( self.__showContextMenu) self.modifiedIndicators = [ self.tr('added'), self.tr('deleted'), self.tr('modified'), ] self.missingIndicators = [ self.tr('missing'), ] self.unversionedIndicators = [ self.tr('unversioned'), ] self.lockedIndicators = [ self.tr('locked'), ] self.stealBreakLockIndicators = [ self.tr('other lock'), self.tr('stolen lock'), self.tr('broken lock'), ] self.unlockedIndicators = [ self.tr('not locked'), ] self.status = { ' ': self.tr('normal'), 'A': self.tr('added'), 'D': self.tr('deleted'), 'M': self.tr('modified'), 'R': self.tr('replaced'), 'C': self.tr('conflict'), 'X': self.tr('external'), 'I': self.tr('ignored'), '?': self.tr('unversioned'), '!': self.tr('missing'), '~': self.tr('type error'), } self.propStatus = { ' ': self.tr('normal'), 'M': self.tr('modified'), 'C': self.tr('conflict'), } self.locked = { ' ': self.tr('no'), 'L': self.tr('yes'), } self.history = { ' ': self.tr('no'), '+': self.tr('yes'), } self.switched = { ' ': self.tr('no'), 'S': self.tr('yes'), } self.lockinfo = { ' ': self.tr('not locked'), 'K': self.tr('locked'), 'O': self.tr('other lock'), 'T': self.tr('stolen lock'), 'B': self.tr('broken lock'), } self.uptodate = { ' ': self.tr('yes'), '*': self.tr('no'), } self.rx_status = QRegExp( '(.{8,9})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)\\s*') # flags (8 or 9 anything), revision, changed rev, author, path self.rx_status2 = \ QRegExp('(.{8,9})\\s+(.+)\\s*') # flags (8 or 9 anything), path self.rx_changelist = \ QRegExp('--- \\S+ .([\\w\\s]+).:\\s+') # three dashes, Changelist (translated), quote, # changelist name, quote, : self.__nonverbose = True def __resort(self): """ Private method to resort the tree. """ self.statusList.sortItems( self.statusList.sortColumn(), self.statusList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.statusList.header().resizeSections(QHeaderView.ResizeToContents) self.statusList.header().setStretchLastSection(True) def __generateItem(self, status, propStatus, locked, history, switched, lockinfo, uptodate, revision, change, author, path): """ Private method to generate a status item in the status list. @param status status indicator (string) @param propStatus property status indicator (string) @param locked locked indicator (string) @param history history indicator (string) @param switched switched indicator (string) @param lockinfo lock indicator (string) @param uptodate up to date indicator (string) @param revision revision string (string) @param change revision of last change (string) @param author author of the last change (string) @param path path of the file or directory (string) """ if self.__nonverbose and \ status == " " and \ propStatus == " " and \ locked == " " and \ history == " " and \ switched == " " and \ lockinfo == " " and \ uptodate == " " and \ self.currentChangelist == "": return if revision == "": rev = "" else: try: rev = int(revision) except ValueError: rev = revision if change == "": chg = "" else: try: chg = int(change) except ValueError: chg = change statusText = self.status[status] itm = QTreeWidgetItem(self.statusList) itm.setData(0, Qt.DisplayRole, "") itm.setData(1, Qt.DisplayRole, self.currentChangelist) itm.setData(2, Qt.DisplayRole, statusText) itm.setData(3, Qt.DisplayRole, self.propStatus[propStatus]) itm.setData(4, Qt.DisplayRole, self.locked[locked]) itm.setData(5, Qt.DisplayRole, self.history[history]) itm.setData(6, Qt.DisplayRole, self.switched[switched]) itm.setData(7, Qt.DisplayRole, self.lockinfo[lockinfo]) itm.setData(8, Qt.DisplayRole, self.uptodate[uptodate]) itm.setData(9, Qt.DisplayRole, rev) itm.setData(10, Qt.DisplayRole, chg) itm.setData(11, Qt.DisplayRole, author) itm.setData(12, Qt.DisplayRole, path) itm.setTextAlignment(1, Qt.AlignLeft) itm.setTextAlignment(2, Qt.AlignHCenter) itm.setTextAlignment(3, Qt.AlignHCenter) itm.setTextAlignment(4, Qt.AlignHCenter) itm.setTextAlignment(5, Qt.AlignHCenter) itm.setTextAlignment(6, Qt.AlignHCenter) itm.setTextAlignment(7, Qt.AlignHCenter) itm.setTextAlignment(8, Qt.AlignHCenter) itm.setTextAlignment(9, Qt.AlignRight) itm.setTextAlignment(10, Qt.AlignRight) itm.setTextAlignment(11, Qt.AlignLeft) itm.setTextAlignment(12, Qt.AlignLeft) if status in "ADM" or propStatus in "M": itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable) itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked) else: itm.setFlags(itm.flags() & ~Qt.ItemIsUserCheckable) self.hidePropertyStatusColumn = self.hidePropertyStatusColumn and \ propStatus == " " self.hideLockColumns = self.hideLockColumns and \ locked == " " and lockinfo == " " self.hideUpToDateColumn = self.hideUpToDateColumn and uptodate == " " self.hideHistoryColumn = self.hideHistoryColumn and history == " " self.hideSwitchedColumn = self.hideSwitchedColumn and switched == " " if statusText not in self.__statusFilters: self.__statusFilters.append(statusText) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, fn): """ Public slot to start the svn status command. @param fn filename(s)/directoryname(s) to show the status of (string or list of strings) """ self.errorGroup.hide() self.intercept = False self.args = fn for act in self.menuactions: act.setEnabled(False) self.addButton.setEnabled(False) self.commitButton.setEnabled(False) self.diffButton.setEnabled(False) self.sbsDiffButton.setEnabled(False) self.revertButton.setEnabled(False) self.restoreButton.setEnabled(False) self.statusFilterCombo.clear() self.__statusFilters = [] self.currentChangelist = "" self.changelistFound = False self.hidePropertyStatusColumn = True self.hideLockColumns = True self.hideUpToDateColumn = True self.hideHistoryColumn = True self.hideSwitchedColumn = True if self.process: self.process.kill() else: self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) args = [] args.append('status') self.vcs.addArguments(args, self.vcs.options['global']) self.vcs.addArguments(args, self.vcs.options['status']) if '--verbose' not in self.vcs.options['global'] and \ '--verbose' not in self.vcs.options['status']: args.append('--verbose') self.__nonverbose = True else: self.__nonverbose = False if '--show-updates' in self.vcs.options['status'] or \ '-u' in self.vcs.options['status']: self.activateWindow() self.raise_() if isinstance(fn, list): self.dname, fnames = self.vcs.splitPathList(fn) self.vcs.addArguments(args, fnames) else: self.dname, fname = self.vcs.splitPath(fn) args.append(fname) self.process.setWorkingDirectory(self.dname) self.setWindowTitle(self.tr('Subversion Status')) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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 __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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.buttonBox.button(QDialogButtonBox.Close).setFocus( Qt.OtherFocusReason) self.inputGroup.setEnabled(False) self.inputGroup.hide() self.refreshButton.setEnabled(True) self.__statusFilters.sort() self.__statusFilters.insert(0, "<{0}>".format(self.tr("all"))) self.statusFilterCombo.addItems(self.__statusFilters) for act in self.menuactions: act.setEnabled(True) self.process = None self.__resort() self.__resizeColumns() self.statusList.setColumnHidden(self.__changelistColumn, not self.changelistFound) self.statusList.setColumnHidden(self.__propStatusColumn, self.hidePropertyStatusColumn) self.statusList.setColumnHidden(self.__lockedColumn, self.hideLockColumns) self.statusList.setColumnHidden(self.__lockinfoColumn, self.hideLockColumns) self.statusList.setColumnHidden(self.__upToDateColumn, self.hideUpToDateColumn) self.statusList.setColumnHidden(self.__historyColumn, self.hideHistoryColumn) self.statusList.setColumnHidden(self.__switchedColumn, self.hideSwitchedColumn) self.__updateButtons() self.__updateCommitButton() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() elif button == self.refreshButton: self.on_refreshButton_clicked() 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.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.rx_status.exactMatch(s): flags = self.rx_status.cap(1) rev = self.rx_status.cap(2) change = self.rx_status.cap(3) author = self.rx_status.cap(4) path = self.rx_status.cap(5).strip() self.__generateItem(flags[0], flags[1], flags[2], flags[3], flags[4], flags[5], flags[-1], rev, change, author, path) elif self.rx_status2.exactMatch(s): flags = self.rx_status2.cap(1) path = self.rx_status2.cap(2).strip() self.__generateItem(flags[0], flags[1], flags[2], flags[3], flags[4], flags[5], flags[-1], "", "", "", path) elif self.rx_changelist.exactMatch(s): self.currentChangelist = self.rx_changelist.cap(1) self.changelistFound = True 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnStatusDialog, self).keyPressEvent(evt) @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the status display. """ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.inputGroup.setEnabled(True) self.inputGroup.show() self.refreshButton.setEnabled(False) self.statusList.clear() self.start(self.args) def __updateButtons(self): """ Private method to update the VCS buttons status. """ modified = len(self.__getModifiedItems()) unversioned = len(self.__getUnversionedItems()) missing = len(self.__getMissingItems()) self.addButton.setEnabled(unversioned) self.diffButton.setEnabled(modified) self.sbsDiffButton.setEnabled(modified == 1) self.revertButton.setEnabled(modified) self.restoreButton.setEnabled(missing) def __updateCommitButton(self): """ Private method to update the Commit button status. """ commitable = len(self.__getCommitableItems()) self.commitButton.setEnabled(commitable) @pyqtSlot(str) def on_statusFilterCombo_activated(self, txt): """ Private slot to react to the selection of a status filter. @param txt selected status filter (string) """ if txt == "<{0}>".format(self.tr("all")): for topIndex in range(self.statusList.topLevelItemCount()): topItem = self.statusList.topLevelItem(topIndex) topItem.setHidden(False) else: for topIndex in range(self.statusList.topLevelItemCount()): topItem = self.statusList.topLevelItem(topIndex) topItem.setHidden(topItem.text(self.__statusColumn) != txt) @pyqtSlot(QTreeWidgetItem, int) def on_statusList_itemChanged(self, item, column): """ Private slot to act upon item changes. @param item reference to the changed item (QTreeWidgetItem) @param column index of column that changed (integer) """ if column == self.__toBeCommittedColumn: self.__updateCommitButton() @pyqtSlot() def on_statusList_itemSelectionChanged(self): """ Private slot to act upon changes of selected items. """ self.__updateButtons() @pyqtSlot() def on_commitButton_clicked(self): """ Private slot to handle the press of the Commit button. """ self.__commit() @pyqtSlot() def on_addButton_clicked(self): """ Private slot to handle the press of the Add button. """ self.__add() @pyqtSlot() def on_diffButton_clicked(self): """ Private slot to handle the press of the Differences button. """ self.__diff() @pyqtSlot() def on_sbsDiffButton_clicked(self): """ Private slot to handle the press of the Side-by-Side Diff button. """ self.__sbsDiff() @pyqtSlot() def on_revertButton_clicked(self): """ Private slot to handle the press of the Revert button. """ self.__revert() @pyqtSlot() def on_restoreButton_clicked(self): """ Private slot to handle the press of the Restore button. """ self.__restoreMissing() ########################################################################### ## Context menu handling methods ########################################################################### def __showContextMenu(self, coord): """ Private slot to show the context menu of the status list. @param coord the position of the mouse pointer (QPoint) """ self.menu.popup(self.statusList.mapToGlobal(coord)) def __commit(self): """ Private slot to handle the Commit context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getCommitableItems()] if not names: E5MessageBox.information( self, self.tr("Commit"), self.tr("""There are no entries selected to be""" """ committed.""")) return if Preferences.getVCS("AutoSaveFiles"): vm = e5App().getObject("ViewManager") for name in names: vm.saveEditor(name) self.vcs.vcsCommit(names, '') def __committed(self): """ Private slot called after the commit has finished. """ if self.isVisible(): self.on_refreshButton_clicked() self.vcs.checkVCSStatus() def __commitSelectAll(self): """ Private slot to select all entries for commit. """ self.__commitSelect(True) def __commitDeselectAll(self): """ Private slot to deselect all entries from commit. """ self.__commitSelect(False) def __add(self): """ Private slot to handle the Add context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getUnversionedItems()] if not names: E5MessageBox.information( self, self.tr("Add"), self.tr("""There are no unversioned entries""" """ available/selected.""")) return self.vcs.vcsAdd(names) self.on_refreshButton_clicked() project = e5App().getObject("Project") for name in names: project.getModel().updateVCSStatus(name) self.vcs.checkVCSStatus() def __revert(self): """ Private slot to handle the Revert context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems()] if not names: E5MessageBox.information( self, self.tr("Revert"), self.tr("""There are no uncommitted changes""" """ available/selected.""")) return self.vcs.vcsRevert(names) self.raise_() self.activateWindow() self.on_refreshButton_clicked() project = e5App().getObject("Project") for name in names: project.getModel().updateVCSStatus(name) self.vcs.checkVCSStatus() def __restoreMissing(self): """ Private slot to handle the Restore Missing context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getMissingItems()] if not names: E5MessageBox.information( self, self.tr("Revert"), self.tr("""There are no missing entries""" """ available/selected.""")) return self.vcs.vcsRevert(names) self.on_refreshButton_clicked() self.vcs.checkVCSStatus() def __diff(self): """ Private slot to handle the Diff context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems()] if not names: E5MessageBox.information( self, self.tr("Differences"), self.tr("""There are no uncommitted changes""" """ available/selected.""")) return if self.diff is None: from .SvnDiffDialog import SvnDiffDialog self.diff = SvnDiffDialog(self.vcs) self.diff.show() QApplication.processEvents() self.diff.start(names) def __sbsDiff(self): """ Private slot to handle the Side-by-Side Diff context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems()] if not names: E5MessageBox.information( self, self.tr("Side-by-Side Diff"), self.tr("""There are no uncommitted changes""" """ available/selected.""")) return elif len(names) > 1: E5MessageBox.information( self, self.tr("Side-by-Side Diff"), self.tr("""Only one file with uncommitted changes""" """ must be selected.""")) return self.vcs.svnSbsDiff(names[0]) def __lock(self): """ Private slot to handle the Lock context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems(self.unlockedIndicators)] if not names: E5MessageBox.information( self, self.tr("Lock"), self.tr("""There are no unlocked files""" """ available/selected.""")) return self.vcs.svnLock(names, parent=self) self.on_refreshButton_clicked() def __unlock(self): """ Private slot to handle the Unlock context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems(self.lockedIndicators)] if not names: E5MessageBox.information( self, self.tr("Unlock"), self.tr("""There are no locked files""" """ available/selected.""")) return self.vcs.svnUnlock(names, parent=self) self.on_refreshButton_clicked() def __breakLock(self): """ Private slot to handle the Break Lock context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems( self.stealBreakLockIndicators)] if not names: E5MessageBox.information( self, self.tr("Break Lock"), self.tr("""There are no locked files""" """ available/selected.""")) return self.vcs.svnUnlock(names, parent=self, breakIt=True) self.on_refreshButton_clicked() def __stealLock(self): """ Private slot to handle the Break Lock context menu entry. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getLockActionItems( self.stealBreakLockIndicators)] if not names: E5MessageBox.information( self, self.tr("Steal Lock"), self.tr("""There are no locked files""" """ available/selected.""")) return self.vcs.svnLock(names, parent=self, stealIt=True) self.on_refreshButton_clicked() def __addToChangelist(self): """ Private slot to add entries to a changelist. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getNonChangelistItems()] if not names: E5MessageBox.information( self, self.tr("Remove from Changelist"), self.tr( """There are no files available/selected not """ """belonging to a changelist.""" ) ) return self.vcs.svnAddToChangelist(names) self.on_refreshButton_clicked() def __removeFromChangelist(self): """ Private slot to remove entries from their changelists. """ names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getChangelistItems()] if not names: E5MessageBox.information( self, self.tr("Remove from Changelist"), self.tr( """There are no files available/selected belonging""" """ to a changelist.""" ) ) return self.vcs.svnRemoveFromChangelist(names) self.on_refreshButton_clicked() def __getCommitableItems(self): """ Private method to retrieve all entries the user wants to commit. @return list of all items, the user has checked """ commitableItems = [] for index in range(self.statusList.topLevelItemCount()): itm = self.statusList.topLevelItem(index) if itm.checkState(self.__toBeCommittedColumn) == Qt.Checked: commitableItems.append(itm) return commitableItems def __getModifiedItems(self): """ Private method to retrieve all entries, that have a modified status. @return list of all items with a modified status """ modifiedItems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__statusColumn) in self.modifiedIndicators or \ itm.text(self.__propStatusColumn) in self.modifiedIndicators: modifiedItems.append(itm) return modifiedItems def __getUnversionedItems(self): """ Private method to retrieve all entries, that have an unversioned status. @return list of all items with an unversioned status """ unversionedItems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__statusColumn) in self.unversionedIndicators: unversionedItems.append(itm) return unversionedItems def __getMissingItems(self): """ Private method to retrieve all entries, that have a missing status. @return list of all items with a missing status """ missingItems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__statusColumn) in self.missingIndicators: missingItems.append(itm) return missingItems def __getLockActionItems(self, indicators): """ Private method to retrieve all emtries, that have a locked status. @param indicators list of indicators to check against (list of strings) @return list of all items with a locked status """ lockitems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__lockinfoColumn) in indicators: lockitems.append(itm) return lockitems def __getChangelistItems(self): """ Private method to retrieve all entries, that are members of a changelist. @return list of all items belonging to a changelist """ clitems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__changelistColumn) != "": clitems.append(itm) return clitems def __getNonChangelistItems(self): """ Private method to retrieve all entries, that are not members of a changelist. @return list of all items not belonging to a changelist """ clitems = [] for itm in self.statusList.selectedItems(): if itm.text(self.__changelistColumn) == "": clitems.append(itm) return clitems def __commitSelect(self, selected): """ Private slot to select or deselect all entries. @param selected commit selection state to be set (boolean) """ for index in range(self.statusList.topLevelItemCount()): itm = self.statusList.topLevelItem(index) if itm.flags() & Qt.ItemIsUserCheckable: if selected: itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked) else: itm.setCheckState(self.__toBeCommittedColumn, Qt.Unchecked)
def __init__(self, vcs, tagsList, branchesList, path, parent=None): """ Constructor @param vcs reference to the vcs object @param tagsList list of tags (list of strings) @param branchesList list of branches (list of strings) @param path pathname to determine the repository URL from (string) @param parent parent widget of the dialog (QWidget) """ super(SvnUrlSelectionDialog, self).__init__(parent) self.setupUi(self) if not hasattr(pysvn.Client(), 'diff_summarize'): self.summaryCheckBox.setEnabled(False) self.summaryCheckBox.setChecked(False) self.vcs = vcs self.tagsList = tagsList self.branchesList = branchesList self.typeCombo1.addItems(["trunk/", "tags/", "branches/"]) self.typeCombo2.addItems(["trunk/", "tags/", "branches/"]) reposURL = self.vcs.svnGetReposName(path) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The operation will""" """ be aborted""")) self.reject() return if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+/)(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr("""The URL of the project repository has an""" """ invalid format. The operation will""" """ be aborted""")) self.reject() return reposRoot = rx_base.cap(1) self.repoRootLabel1.setText(reposRoot) self.repoRootLabel2.setText(reposRoot) else: project = e5App().getObject('Project') if Utilities.normcasepath(path) != \ Utilities.normcasepath(project.getProjectPath()): path = project.getRelativePath(path) reposURL = reposURL.replace(path, '') self.repoRootLabel1.hide() self.typeCombo1.hide() self.labelCombo1.addItems([reposURL] + sorted(self.vcs.tagsList)) self.labelCombo1.setEnabled(True) self.repoRootLabel2.hide() self.typeCombo2.hide() self.labelCombo2.addItems([reposURL] + sorted(self.vcs.tagsList)) self.labelCombo2.setEnabled(True) msh = self.minimumSizeHint() self.resize(max(self.width(), msh.width()), msh.height())
class SvnStatusMonitorThread(VcsStatusMonitorThread): """ Class implementing the VCS status monitor thread class for Subversion. """ def __init__(self, interval, project, vcs, parent=None): """ Constructor @param interval new interval in seconds (integer) @param project reference to the project object (Project) @param vcs reference to the version control object @param parent reference to the parent object (QObject) """ VcsStatusMonitorThread.__init__(self, interval, project, vcs, parent) self.__ioEncoding = Preferences.getSystem("IOEncoding") self.rx_status1 = \ QRegExp('(.{8,9})\\s+([0-9-]+)\\s+(.+)\\s*') self.rx_status2 = QRegExp( '(.{8,9})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)\\s*') def _performMonitor(self): """ Protected method implementing the monitoring action. This method populates the statusList member variable with a list of strings giving the status in the first column and the path relative to the project directory starting with the third column. The allowed status flags are: <ul> <li>"A" path was added but not yet comitted</li> <li>"M" path has local changes</li> <li>"O" path was removed</li> <li>"R" path was deleted and then re-added</li> <li>"U" path needs an update</li> <li>"Z" path contains a conflict</li> <li>" " path is back at normal</li> </ul> @return tuple of flag indicating successful operation (boolean) and a status message in case of non successful operation (string) """ self.shouldUpdate = False process = QProcess() args = [] args.append('status') if not Preferences.getVCS("MonitorLocalStatus"): args.append('--show-updates') args.append('--non-interactive') args.append('.') process.setWorkingDirectory(self.projectDir) process.start('svn', args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(300000) if finished and process.exitCode() == 0: output = str(process.readAllStandardOutput(), self.__ioEncoding, 'replace') states = {} for line in output.splitlines(): if self.rx_status1.exactMatch(line): flags = self.rx_status1.cap(1) path = self.rx_status1.cap(3).strip() elif self.rx_status2.exactMatch(line): flags = self.rx_status2.cap(1) path = self.rx_status2.cap(5).strip() else: continue if flags[0] in "ACDMR" or \ (flags[0] == " " and flags[-1] == "*"): if flags[-1] == "*": status = "U" else: status = flags[0] if status == "C": status = "Z" # give it highest priority elif status == "D": status = "O" if status == "U": self.shouldUpdate = True name = path states[name] = status try: if self.reportedStates[name] != status: self.statusList.append( "{0} {1}".format(status, name)) except KeyError: self.statusList.append( "{0} {1}".format(status, name)) for name in list(self.reportedStates.keys()): if name not in states: self.statusList.append(" {0}".format(name)) self.reportedStates = states return True, self.tr( "Subversion status checked successfully (using svn)") else: process.kill() process.waitForFinished() return False, \ str(process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') else: process.kill() process.waitForFinished() return False, self.tr( "Could not start the Subversion process.")
class SvnPropListDialog(QWidget, Ui_SvnPropListDialog): """ Class implementing a dialog to show the output of the svn proplist command process. """ def __init__(self, vcs, parent=None): """ Constructor @param vcs reference to the vcs object @param parent parent widget (QWidget) """ super(SvnPropListDialog, self).__init__(parent) self.setupUi(self) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.process = QProcess() env = QProcessEnvironment.systemEnvironment() env.insert("LANG", "C") self.process.setProcessEnvironment(env) self.vcs = vcs self.propsList.headerItem().setText(self.propsList.columnCount(), "") self.propsList.header().setSortIndicator(0, Qt.AscendingOrder) self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.rx_path = QRegExp(r"Properties on '([^']+)':\s*") self.rx_prop = QRegExp(r" (.*) *: *(.*)[\r\n]") self.lastPath = None self.lastProp = None self.propBuffer = "" def __resort(self): """ Private method to resort the tree. """ self.propsList.sortItems( self.propsList.sortColumn(), self.propsList.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.propsList.header().resizeSections(QHeaderView.ResizeToContents) self.propsList.header().setStretchLastSection(True) def __generateItem(self, path, propName, propValue): """ Private method to generate a properties item in the properties list. @param path file/directory name the property applies to (string) @param propName name of the property (string) @param propValue value of the property (string) """ QTreeWidgetItem(self.propsList, [path, propName, propValue.strip()]) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) e.accept() def start(self, fn, recursive=False): """ Public slot to start the svn status command. @param fn filename(s) (string or list of string) @param recursive flag indicating a recursive list is requested """ self.errorGroup.hide() self.process.kill() args = [] args.append('proplist') self.vcs.addArguments(args, self.vcs.options['global']) args.append('--verbose') if recursive: args.append('--recursive') if isinstance(fn, list): dname, fnames = self.vcs.splitPathList(fn) self.vcs.addArguments(args, fnames) else: dname, fname = self.vcs.splitPath(fn) args.append(fname) self.process.setWorkingDirectory(dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('svn')) 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.NotRunning: self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.process = None if self.lastProp: self.__generateItem(self.lastPath, self.lastProp, self.propBuffer) self.__resort() self.__resizeColumns() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.buttonBox.button(QDialogButtonBox.Close): self.close() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.__finish() 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) """ if self.lastPath is None: self.__generateItem('', 'None', '') 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. """ self.process.setReadChannel(QProcess.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.rx_path.exactMatch(s): if self.lastProp: self.__generateItem( self.lastPath, self.lastProp, self.propBuffer) self.lastPath = self.rx_path.cap(1) self.lastProp = None self.propBuffer = "" elif self.rx_prop.exactMatch(s): if self.lastProp: self.__generateItem( self.lastPath, self.lastProp, self.propBuffer) self.lastProp = self.rx_prop.cap(1) self.propBuffer = self.rx_prop.cap(2) else: self.propBuffer += ' ' self.propBuffer += s 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: self.errorGroup.show() s = str(self.process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible()
def __init__(self, vcs, tagsList, branchesList, path, parent=None): """ Constructor @param vcs reference to the vcs object @param tagsList list of tags (list of strings) @param branchesList list of branches (list of strings) @param path pathname to determine the repository URL from (string) @param parent parent widget of the dialog (QWidget) """ super(SvnUrlSelectionDialog, self).__init__(parent) self.setupUi(self) if not hasattr(pysvn.Client(), 'diff_summarize'): self.summaryCheckBox.setEnabled(False) self.summaryCheckBox.setChecked(False) self.vcs = vcs self.tagsList = tagsList self.branchesList = branchesList self.typeCombo1.addItems(["trunk/", "tags/", "branches/"]) self.typeCombo2.addItems(["trunk/", "tags/", "branches/"]) reposURL = self.vcs.svnGetReposName(path) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The operation will""" """ be aborted""")) self.reject() return if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+/)(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository has an""" """ invalid format. The operation will""" """ be aborted""")) self.reject() return reposRoot = rx_base.cap(1) self.repoRootLabel1.setText(reposRoot) self.repoRootLabel2.setText(reposRoot) else: project = e5App().getObject('Project') if Utilities.normcasepath(path) != \ Utilities.normcasepath(project.getProjectPath()): path = project.getRelativePath(path) reposURL = reposURL.replace(path, '') self.repoRootLabel1.hide() self.typeCombo1.hide() self.labelCombo1.addItems([reposURL] + sorted(self.vcs.tagsList)) self.labelCombo1.setEnabled(True) self.repoRootLabel2.hide() self.typeCombo2.hide() self.labelCombo2.addItems([reposURL] + sorted(self.vcs.tagsList)) self.labelCombo2.setEnabled(True) msh = self.minimumSizeHint() self.resize(max(self.width(), msh.width()), msh.height())
def start(self, path, tags, tagsList, allTagsList): """ Public slot to start the svn status command. @param path name of directory to be listed (string) @param tags flag indicating a list of tags is requested (False = branches, True = tags) @param tagsList reference to string list receiving the tags (list of strings) @param allTagsList reference to string list all tags (list of strings) """ self.errorGroup.hide() self.intercept = False if not tags: self.setWindowTitle(self.tr("Subversion Branches List")) self.activateWindow() self.tagsList = tagsList self.allTagsList = allTagsList dname, fname = self.vcs.splitPath(path) self.process.kill() reposURL = self.vcs.svnGetReposName(dname) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The list operation""" """ will be aborted""")) self.close() return args = [] args.append('list') self.vcs.addArguments(args, self.vcs.options['global']) args.append('--verbose') if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+)/(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr("""The URL of the project repository has an""" """ invalid format. The list operation will""" """ be aborted""")) return reposRoot = rx_base.cap(1) if tags: args.append("{0}/tags".format(reposRoot)) else: args.append("{0}/branches".format(reposRoot)) self.path = None else: reposPath, ok = QInputDialog.getText( self, self.tr("Subversion List"), self.tr("Enter the repository URL containing the tags" " or branches"), QLineEdit.Normal, self.vcs.svnNormalizeURL(reposURL)) if not ok: self.close() return if not reposPath: E5MessageBox.critical( self, self.tr("Subversion List"), self.tr("""The repository URL is empty.""" """ Aborting...""")) self.close() return args.append(reposPath) self.path = reposPath self.process.setWorkingDirectory(dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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()
class SvnRepoBrowserDialog(QDialog, Ui_SvnRepoBrowserDialog): """ 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) """ super(SvnRepoBrowserDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.repoTree.headerItem().setText(self.repoTree.columnCount(), "") self.repoTree.header().setSortIndicator(0, Qt.AscendingOrder) self.vcs = vcs self.mode = mode self.process = QProcess() 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.Ok).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).hide() else: self.buttonBox.button(QDialogButtonBox.Ok).hide() self.buttonBox.button(QDialogButtonBox.Cancel).hide() self.__dirIcon = UI.PixmapCache.getIcon("dirClosed.png") self.__fileIcon = UI.PixmapCache.getIcon("fileMisc.png") self.__urlRole = Qt.UserRole self.__ignoreExpand = False self.intercept = False self.__rx_dir = QRegExp( r"""\s*([0-9]+)\s+(\w+)\s+""" r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""") self.__rx_file = QRegExp( r"""\s*([0-9]+)\s+(\w+)\s+([0-9]+)\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.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()) def __resizeColumns(self): """ Private method to resize the tree columns. """ self.repoTree.header().resizeSections(QHeaderView.ResizeToContents) self.repoTree.header().setStretchLastSection(True) 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) @param size size info (string) @param date date info (string) @param nodekind node kind info (string, "dir" or "file") @param url url of the entry (string) @return reference to the generated item (QTreeWidgetItem) """ path = repopath if revision == "": rev = "" else: rev = int(revision) if size == "": sz = "" else: sz = int(size) itm = QTreeWidgetItem(self.parentItem) itm.setData(0, Qt.DisplayRole, path) itm.setData(1, Qt.DisplayRole, rev) itm.setData(2, Qt.DisplayRole, author) itm.setData(3, Qt.DisplayRole, sz) itm.setData(4, Qt.DisplayRole, date) if nodekind == "dir": itm.setIcon(0, self.__dirIcon) itm.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator) elif nodekind == "file": itm.setIcon(0, self.__fileIcon) itm.setData(0, self.__urlRole, url) itm.setTextAlignment(0, Qt.AlignLeft) itm.setTextAlignment(1, Qt.AlignRight) itm.setTextAlignment(2, Qt.AlignLeft) itm.setTextAlignment(3, Qt.AlignRight) itm.setTextAlignment(4, Qt.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(url) 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') for line in output.splitlines(): line = line.strip() if line.startswith('<root>'): repoRoot = line.replace('<root>', '')\ .replace('</root>', '') break else: error = str(process.readAllStandardError(), Preferences.getSystem("IOEncoding"), 'replace') self.errors.insertPlainText(error) self.errors.ensureCursorVisible() else: QApplication.restoreOverrideCursor() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() 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.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.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(url) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.__finish() self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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(str) def on_urlCombo_currentIndexChanged(self, text): """ Private slot called, when a new repository URL is entered or selected. @param text the text of the current item (string) """ url = self.__normalizeUrl(text) if url != self.url: 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.Ok).setEnabled(True) def accept(self): """ Public slot called when the dialog is accepted. """ if self.focusWidget() == self.urlCombo: return super(SvnRepoBrowserDialog, self).accept() def getSelectedUrl(self): """ Public method to retrieve the selected repository URL. @return the selected repository URL (string) """ items = self.repoTree.selectedItems() if len(items) == 1: 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.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() QApplication.restoreOverrideCursor() 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.StandardOutput) while self.process.canReadLine(): s = str(self.process.readLine(), Preferences.getSystem("IOEncoding"), 'replace') if self.__rx_dir.exactMatch(s): revision = self.__rx_dir.cap(1) author = self.__rx_dir.cap(2) date = self.__rx_dir.cap(3) name = self.__rx_dir.cap(4).strip() if name.endswith("/"): name = name[:-1] size = "" nodekind = "dir" if name == ".": continue elif self.__rx_file.exactMatch(s): revision = self.__rx_file.cap(1) author = self.__rx_file.cap(2) size = self.__rx_file.cap(3) date = self.__rx_file.cap(4) name = self.__rx_file.cap(5).strip() nodekind = "file" else: continue url = "{0}/{1}".format(self.repoUrl, name) 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') 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.Password) else: self.input.setEchoMode(QLineEdit.Normal) @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the input to the subversion process. """ input = self.input.text() input += os.linesep if self.passwordCheckBox.isChecked(): self.errors.insertPlainText(os.linesep) self.errors.ensureCursorVisible() else: self.errors.insertPlainText(input) self.errors.ensureCursorVisible() self.process.write(input) 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: self.intercept = False evt.accept() return super(SvnRepoBrowserDialog, self).keyPressEvent(evt)
def start(self, path, tags, tagsList, allTagsList): """ Public slot to start the svn status command. @param path name of directory to be listed (string) @param tags flag indicating a list of tags is requested (False = branches, True = tags) @param tagsList reference to string list receiving the tags (list of strings) @param allTagsList reference to string list all tags (list of strings) """ self.errorGroup.hide() self.tagList.clear() self.intercept = False if not tags: self.setWindowTitle(self.tr("Subversion Branches List")) self.activateWindow() self.tagsList = tagsList self.allTagsList = allTagsList dname, fname = self.vcs.splitPath(path) self.process.kill() reposURL = self.vcs.svnGetReposName(dname) if reposURL is None: E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository could not be""" """ retrieved from the working copy. The list operation""" """ will be aborted""")) self.close() return args = [] args.append('list') self.vcs.addArguments(args, self.vcs.options['global']) args.append('--verbose') if self.vcs.otherData["standardLayout"]: # determine the base path of the project in the repository rx_base = QRegExp('(.+)/(trunk|tags|branches).*') if not rx_base.exactMatch(reposURL): E5MessageBox.critical( self, self.tr("Subversion Error"), self.tr( """The URL of the project repository has an""" """ invalid format. The list operation will""" """ be aborted""")) return reposRoot = rx_base.cap(1) if tags: args.append("{0}/tags".format(reposRoot)) else: args.append("{0}/branches".format(reposRoot)) self.path = None else: reposPath, ok = QInputDialog.getText( self, self.tr("Subversion List"), self.tr("Enter the repository URL containing the tags" " or branches"), QLineEdit.Normal, self.vcs.svnNormalizeURL(reposURL)) if not ok: self.close() return if not reposPath: E5MessageBox.critical( self, self.tr("Subversion List"), self.tr("""The repository URL is empty.""" """ Aborting...""")) self.close() return args.append(reposPath) self.path = reposPath self.process.setWorkingDirectory(dname) self.process.start('svn', args) procStarted = self.process.waitForStarted(5000) if not procStarted: self.inputGroup.setEnabled(False) self.inputGroup.hide() E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( '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 realtime_highlight(self, text): """Highlight each line while it is being edited. This function apply the proper highlight to the line being edited by the user, this is a really fast process for each line once you already have the document highlighted, but slow to do it the first time to highlight all the lines together.""" hls = [] block = self.currentBlock() user_data = syntax_highlighter.get_user_data(block) user_data.clear_data() block_number = block.blockNumber() highlight_errors = lambda cf, ud: cf if self.errors and (block_number in self.errors.errorsSummary): highlight_errors = self.__highlight_lint elif self.pep8 and (block_number in self.pep8.pep8checks): highlight_errors = self.__highlight_pep8 elif self.migration and ( block_number in self.migration.migration_data): highlight_errors = self.__highlight_migration char_format = block.charFormat() char_format = highlight_errors(char_format, user_data) self.setFormat(0, len(block.text()), char_format) for expression, nth, char_format in self.rules: index = expression.indexIn(text, 0) while index >= 0: # We actually want the index of the nth match index = expression.pos(nth) length = len(expression.cap(nth)) char_format = highlight_errors(char_format, user_data) if (self.format(index) != STYLES['string']): self.setFormat(index, length, char_format) if char_format == STYLES['string']: hls.append((index, index + length)) user_data.add_str_group(index, index + length) elif char_format == STYLES['comment']: user_data.comment_start_at(index) index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) if not self.multi_start: # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single, hls=hls, highlight_errors=highlight_errors, user_data=user_data) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double, hls=hls, highlight_errors=highlight_errors, user_data=user_data) else: # Do multi-line comment self.comment_multiline(text, self.multi_end[0], *self.multi_start) #Highlight selected word if self.selected_word_pattern is not None: index = self.selected_word_pattern.indexIn(text, 0) while index >= 0: index = self.selected_word_pattern.pos(0) length = len(self.selected_word_pattern.cap(0)) char_format = self.format(index) color = STYLES['selectedWord'].foreground().color() color.setAlpha(100) char_format.setBackground(color) self.setFormat(index, length, char_format) index = self.selected_word_pattern.indexIn( text, index + length) #Spaces expression = QRegExp('\s+') index = expression.indexIn(text, 0) while index >= 0: index = expression.pos(0) length = len(expression.cap(0)) char_format = STYLES['spaces'] char_format = highlight_errors(char_format, user_data) self.setFormat(index, length, char_format) index = expression.indexIn(text, index + length) block.setUserData(user_data)