Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
    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
Esempio n. 6
0
    def valueFromText(text):
        regExp = QRegExp("(\\d+)(\\s*[xx]\\s*\\d+)?")

        if regExp.exactMatch(text):
            return int(regExp.cap(1))
        else:
            return 0
Esempio n. 7
0
    def valueFromText(text):
        regExp = QRegExp("(\\d+)(\\s*[xx]\\s*\\d+)?")

        if regExp.exactMatch(text):
            return int(regExp.cap(1))
        else:
            return 0
Esempio n. 8
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)
Esempio n. 9
0
    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]
Esempio n. 11
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()
Esempio n. 12
0
    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
Esempio n. 13
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()
Esempio n. 14
0
 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()
Esempio n. 15
0
 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()
Esempio n. 17
0
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))
Esempio n. 18
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
Esempio n. 19
0
    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')
Esempio n. 20
0
    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')
Esempio n. 21
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)
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)
Esempio n. 23
0
 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."""))
Esempio n. 24
0
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)
Esempio n. 25
0
 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
Esempio n. 26
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)
Esempio n. 27
0
 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
Esempio n. 28
0
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
Esempio n. 29
0
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
Esempio n. 30
0
    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()
Esempio n. 31
0
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
Esempio n. 32
0
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()
Esempio n. 34
0
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)
Esempio n. 35
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)
Esempio n. 36
0
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)
Esempio n. 37
0
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))
Esempio n. 38
0
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))
Esempio n. 39
0
    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)
Esempio n. 42
0
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)
Esempio n. 43
0
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)
Esempio n. 44
0
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.")
Esempio n. 45
0
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)
Esempio n. 46
0
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)
Esempio n. 47
0
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)
Esempio n. 48
0
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())
Esempio n. 50
0
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.")
Esempio n. 51
0
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()
Esempio n. 52
0
 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())
Esempio n. 53
0
    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()
Esempio n. 54
0
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)
Esempio n. 55
0
 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()
Esempio n. 56
0
    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)