# -*- coding: utf-8 -*-

# Copyright (c) 2011 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to show the results of the code style check.
"""


import os
import fnmatch

from PyQt5.QtCore import pyqtSlot, Qt, QTimer, QCoreApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
    QDialog, QTreeWidgetItem, QAbstractButton, QDialogButtonBox, QApplication,
    QHeaderView, QListWidgetItem
)

from E5Gui.E5Application import e5App

from .Ui_CodeStyleCheckerDialog import Ui_CodeStyleCheckerDialog

import UI.PixmapCache
import Preferences
import Utilities

from . import pycodestyle

try:
    basestring          # __IGNORE_WARNING__
except Exception:
    basestring = str    # define for Python3


class CodeStyleCheckerDialog(QDialog, Ui_CodeStyleCheckerDialog):
    """
    Class implementing a dialog to show the results of the code style check.
    """
    filenameRole = Qt.UserRole + 1
    lineRole = Qt.UserRole + 2
    positionRole = Qt.UserRole + 3
    messageRole = Qt.UserRole + 4
    fixableRole = Qt.UserRole + 5
    codeRole = Qt.UserRole + 6
    ignoredRole = Qt.UserRole + 7
    argsRole = Qt.UserRole + 8
    
    availableFutures = [
        'division', 'absolute_import', 'with_statement',
        'print_function', 'unicode_literals', 'generator_stop',
        'annotations']
    
    cryptoBitSelectionsDsaRsa = [
        "512", "1024", "2048", "4096", "8192", "16384", "32786",
    ]
    cryptoBitSelectionsEc = [
        "160", "224", "256", "384", "512",
    ]
    
    checkCategories = {
        "A": QCoreApplication.translate(
            "CheckerCategories",
            "Annotations"),
        "C": QCoreApplication.translate(
            "CheckerCategories",
            "Code Complexity"),
        "D": QCoreApplication.translate(
            "CheckerCategories",
            "Documentation"),
        "E": QCoreApplication.translate(
            "CheckerCategories",
            "Errors"),
        "M": QCoreApplication.translate(
            "CheckerCategories",
            "Miscellaneous"),
        "N": QCoreApplication.translate(
            "CheckerCategories",
            "Naming"),
        "S": QCoreApplication.translate(
            "CheckerCategories",
            "Security"),
        "W": QCoreApplication.translate(
            "CheckerCategories",
            "Warnings"),
    }
    
    noResults = 0
    noFiles = 1
    hasResults = 2
    
    def __init__(self, styleCheckService, project=None, parent=None):
        """
        Constructor
        
        @param styleCheckService reference to the service
        @type CodeStyleCheckService
        @param project reference to the project if called on project or project
            browser level
        @type Project
        @param parent reference to the parent widget
        @type QWidget
        """
        super(CodeStyleCheckerDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.Window)
        
        self.__project = project
        
        self.optionsTabWidget.setCurrentIndex(0)
        
        self.excludeMessagesSelectButton.setIcon(
            UI.PixmapCache.getIcon("select"))
        self.includeMessagesSelectButton.setIcon(
            UI.PixmapCache.getIcon("select"))
        self.fixIssuesSelectButton.setIcon(
            UI.PixmapCache.getIcon("select"))
        self.noFixIssuesSelectButton.setIcon(
            UI.PixmapCache.getIcon("select"))
        
        self.docTypeComboBox.addItem(self.tr("PEP-257"), "pep257")
        self.docTypeComboBox.addItem(self.tr("Eric"), "eric")
        
        for category, text in CodeStyleCheckerDialog.checkCategories.items():
            itm = QListWidgetItem(text, self.categoriesList)
            itm.setData(Qt.UserRole, category)
            itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable)
            itm.setCheckState(Qt.Unchecked)
        
        for future in CodeStyleCheckerDialog.availableFutures:
            itm = QListWidgetItem(future, self.futuresList)
            itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable)
            itm.setCheckState(Qt.Unchecked)
        
        self.dsaHighRiskCombo.addItems(
            CodeStyleCheckerDialog.cryptoBitSelectionsDsaRsa)
        self.dsaMediumRiskCombo.addItems(
            CodeStyleCheckerDialog.cryptoBitSelectionsDsaRsa)
        self.rsaHighRiskCombo.addItems(
            CodeStyleCheckerDialog.cryptoBitSelectionsDsaRsa)
        self.rsaMediumRiskCombo.addItems(
            CodeStyleCheckerDialog.cryptoBitSelectionsDsaRsa)
        self.ecHighRiskCombo.addItems(
            CodeStyleCheckerDialog.cryptoBitSelectionsEc)
        self.ecMediumRiskCombo.addItems(
            CodeStyleCheckerDialog.cryptoBitSelectionsEc)
        
        self.statisticsButton.setEnabled(False)
        self.showButton.setEnabled(False)
        self.cancelButton.setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        
        self.resultList.headerItem().setText(self.resultList.columnCount(), "")
        self.resultList.header().setSortIndicator(0, Qt.AscendingOrder)
        
        self.addBuiltinButton.setIcon(UI.PixmapCache.getIcon("plus"))
        self.deleteBuiltinButton.setIcon(UI.PixmapCache.getIcon("minus"))
        
        self.restartButton.setEnabled(False)
        self.fixButton.setEnabled(False)
        
        self.checkProgress.setVisible(False)
        self.checkProgressLabel.setVisible(False)
        self.checkProgressLabel.setMaximumWidth(600)
        
        self.styleCheckService = styleCheckService
        self.styleCheckService.styleChecked.connect(self.__processResult)
        self.styleCheckService.batchFinished.connect(self.__batchFinished)
        self.styleCheckService.error.connect(self.__processError)
        self.filename = None
        
        self.results = CodeStyleCheckerDialog.noResults
        self.cancelled = False
        self.__lastFileItem = None
        self.__batch = False
        self.__finished = True
        self.__errorItem = None
        
        self.__fileOrFileList = ""
        self.__project = None
        self.__forProject = False
        self.__data = {}
        self.__statistics = {}
        self.__onlyFixes = {}
        self.__noFixCodesList = []
        
        self.on_loadDefaultButton_clicked()
        
        self.mainWidget.setCurrentWidget(self.configureTab)
        self.optionsTabWidget.setCurrentWidget(self.globalOptionsTab)
    
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.resultList.sortItems(self.resultList.sortColumn(),
                                  self.resultList.header().sortIndicatorOrder()
                                  )
    
    def __createErrorItem(self, filename, message):
        """
        Private slot to create a new error item in the result list.
        
        @param filename name of the file
        @type str
        @param message error message
        @type str
        """
        if self.__errorItem is None:
            self.__errorItem = QTreeWidgetItem(self.resultList, [
                self.tr("Errors")])
            self.__errorItem.setExpanded(True)
            self.__errorItem.setForeground(0, Qt.red)
        
        msg = "{0} ({1})".format(self.__project.getRelativePath(filename),
                                 message)
        if not self.resultList.findItems(msg, Qt.MatchExactly):
            itm = QTreeWidgetItem(self.__errorItem, [msg])
            itm.setForeground(0, Qt.red)
            itm.setFirstColumnSpanned(True)
    
    def __createFileErrorItem(self, filename, message):
        """
        Private method to create an error entry for a given file.
        
        @param filename file name of the file
        @type str
        @param message error message text
        @type str
        """
        result = {
            "file": filename,
            "line": 1,
            "offset": 1,
            "code": "",
            "args": [],
            "display": self.tr("Error: {0}").format(message).rstrip(),
            "fixed": False,
            "autofixing": False,
            "ignored": False,
        }
        self.__createResultItem(filename, result)
    
    def __createResultItem(self, filename, result):
        """
        Private method to create an entry in the result list.
        
        @param filename file name of the file
        @type str
        @param result dictionary containing check result data
        @type dict
        @return reference to the created item
        @rtype QTreeWidgetItem
        """
        from .CodeStyleFixer import FixableCodeStyleIssues
        
        if self.__lastFileItem is None:
            # It's a new file
            self.__lastFileItem = QTreeWidgetItem(self.resultList, [
                self.__project.getRelativePath(filename)])
            self.__lastFileItem.setFirstColumnSpanned(True)
            self.__lastFileItem.setExpanded(True)
            self.__lastFileItem.setData(0, self.filenameRole, filename)
        
        msgCode = result["code"].split(".", 1)[0]
        
        fixable = False
        itm = QTreeWidgetItem(
            self.__lastFileItem, [
                "{0:6}".format(result["line"]),
                msgCode,
                result["display"]
            ]
        )
        if msgCode.startswith(("W", "-", "C", "M")):
            itm.setIcon(1, UI.PixmapCache.getIcon("warning"))
        elif msgCode.startswith(("A", "N")):
            itm.setIcon(1, UI.PixmapCache.getIcon("namingError"))
        elif msgCode.startswith("D"):
            itm.setIcon(1, UI.PixmapCache.getIcon("docstringError"))
        elif msgCode.startswith("S"):
            if "severity" in result:
                if result["severity"] == "H":
                    itm.setIcon(1, UI.PixmapCache.getIcon("securityLow"))
                elif result["severity"] == "M":
                    itm.setIcon(1, UI.PixmapCache.getIcon("securityMedium"))
                elif result["severity"] == "L":
                    itm.setIcon(1, UI.PixmapCache.getIcon("securityHigh"))
                else:
                    itm.setIcon(1, UI.PixmapCache.getIcon("securityLow"))
            else:
                itm.setIcon(1, UI.PixmapCache.getIcon("securityLow"))
        else:
            itm.setIcon(1, UI.PixmapCache.getIcon("syntaxError"))
        if result["fixed"]:
            itm.setIcon(0, UI.PixmapCache.getIcon("issueFixed"))
        elif (
            msgCode in FixableCodeStyleIssues and
            not result["autofixing"] and
            msgCode not in self.__noFixCodesList
        ):
            itm.setIcon(0, UI.PixmapCache.getIcon("issueFixable"))
            fixable = True
        
        itm.setTextAlignment(0, Qt.AlignRight)
        itm.setTextAlignment(1, Qt.AlignHCenter)
        
        itm.setTextAlignment(0, Qt.AlignVCenter)
        itm.setTextAlignment(1, Qt.AlignVCenter)
        itm.setTextAlignment(2, Qt.AlignVCenter)
        
        itm.setData(0, self.filenameRole, filename)
        itm.setData(0, self.lineRole, int(result["line"]))
        itm.setData(0, self.positionRole, int(result["offset"]))
        itm.setData(0, self.messageRole, result["display"])
        itm.setData(0, self.fixableRole, fixable)
        itm.setData(0, self.codeRole, msgCode)
        itm.setData(0, self.ignoredRole, result["ignored"])
        itm.setData(0, self.argsRole, result["args"])
        
        if result["ignored"]:
            font = itm.font(0)
            font.setItalic(True)
            for col in range(itm.columnCount()):
                itm.setFont(col, font)
        
        return itm
    
    def __modifyFixedResultItem(self, itm, result):
        """
        Private method to modify a result list entry to show its
        positive fixed state.
        
        @param itm reference to the item to modify
        @type QTreeWidgetItem
        @param result dictionary containing check result data
        @type dict
        """
        if result["fixed"]:
            itm.setText(2, result["display"])
            itm.setIcon(0, UI.PixmapCache.getIcon("issueFixed"))
            
            itm.setData(0, self.messageRole, result["display"])
        else:
            itm.setIcon(0, QIcon())
        itm.setData(0, self.fixableRole, False)
    
    def __updateStatistics(self, statistics, fixer, ignoredErrors, securityOk):
        """
        Private method to update the collected statistics.
        
        @param statistics dictionary of statistical data with
            message code as key and message count as value
        @type dict
        @param fixer reference to the code style fixer
        @type CodeStyleFixer
        @param ignoredErrors number of ignored errors
        @type int
        @param securityOk number of acknowledged security reports
        @type int
        """
        self.__statistics["_FilesCount"] += 1
        stats = [k for k in statistics.keys() if k[0].isupper()]
        if stats:
            self.__statistics["_FilesIssues"] += 1
            for key in statistics:
                if key in self.__statistics:
                    self.__statistics[key] += statistics[key]
                else:
                    self.__statistics[key] = statistics[key]
        self.__statistics["_IssuesFixed"] += fixer
        self.__statistics["_IgnoredErrors"] += ignoredErrors
        self.__statistics["_SecurityOK"] += securityOk
    
    def __updateFixerStatistics(self, fixer):
        """
        Private method to update the collected fixer related statistics.
        
        @param fixer reference to the code style fixer
        @type CodeStyleFixer
        """
        self.__statistics["_IssuesFixed"] += fixer
    
    def __resetStatistics(self):
        """
        Private slot to reset the statistics data.
        """
        self.__statistics = {}
        self.__statistics["_FilesCount"] = 0
        self.__statistics["_FilesIssues"] = 0
        self.__statistics["_IssuesFixed"] = 0
        self.__statistics["_IgnoredErrors"] = 0
        self.__statistics["_SecurityOK"] = 0
    
    def prepare(self, fileList, project):
        """
        Public method to prepare the dialog with a list of filenames.
        
        @param fileList list of filenames
        @type list of str
        @param project reference to the project object
        @type Project
        """
        self.__fileOrFileList = fileList[:]
        self.__project = project
        self.__forProject = True
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.cancelButton.setEnabled(False)
        
        self.__data = self.__project.getData("CHECKERSPARMS", "Pep8Checker")
        if (
            self.__data is None or
            len(self.__data) < 6
        ):
            # initialize the data structure
            self.__data = {
                "ExcludeFiles": "",
                "ExcludeMessages": pycodestyle.DEFAULT_IGNORE,
                "IncludeMessages": "",
                "RepeatMessages": False,
                "FixCodes": "",
                "FixIssues": False,
            }
        if "EnabledCheckerCategories" not in self.__data:
            self.__data["EnabledCheckerCategories"] = ",".join(
                CodeStyleCheckerDialog.checkCategories.keys())
        if "MaxLineLength" not in self.__data:
            self.__data["MaxLineLength"] = pycodestyle.MAX_LINE_LENGTH
        if "MaxDocLineLength" not in self.__data:
            # Use MAX_LINE_LENGTH to avoid messages on existing code
            self.__data["MaxDocLineLength"] = pycodestyle.MAX_LINE_LENGTH
        if "BlankLines" not in self.__data:
            self.__data["BlankLines"] = (2, 1)
            # top level, method
        if "HangClosing" not in self.__data:
            self.__data["HangClosing"] = False
        if "NoFixCodes" not in self.__data:
            self.__data["NoFixCodes"] = "E501"
        if "DocstringType" not in self.__data:
            self.__data["DocstringType"] = "pep257"
        if "ShowIgnored" not in self.__data:
            self.__data["ShowIgnored"] = False
        if "MaxCodeComplexity" not in self.__data:
            self.__data["MaxCodeComplexity"] = 10
        if "LineComplexity" not in self.__data:
            self.__data["LineComplexity"] = 15
        if "LineComplexityScore" not in self.__data:
            self.__data["LineComplexityScore"] = 10
        if "ValidEncodings" not in self.__data:
            self.__data["ValidEncodings"] = "latin-1, utf-8"
        if (
            "CopyrightMinFileSize" not in self.__data or
            "CopyrightAuthor" not in self.__data
        ):
            self.__data["CopyrightMinFileSize"] = 0
            self.__data["CopyrightAuthor"] = ""
        if "FutureChecker" not in self.__data:
            self.__data["FutureChecker"] = ""
        if "BuiltinsChecker" not in self.__data:
            self.__data["BuiltinsChecker"] = {
                "str": ["unicode", ],
                "chr": ["unichr", ],
            }
        if "CommentedCodeChecker" not in self.__data:
            self.__data["CommentedCodeChecker"] = {
                "Aggressive": False,
            }
        if "AnnotationsChecker" not in self.__data:
            self.__data["AnnotationsChecker"] = {
                "MinimumCoverage": 75,
                "MaximumComplexity": 3,
            }
        
        if "SecurityChecker" not in self.__data:
            from .Security.SecurityDefaults import SecurityDefaults
            self.__data["SecurityChecker"] = {
                "HardcodedTmpDirectories":
                    SecurityDefaults["hardcoded_tmp_directories"],
                "InsecureHashes":
                    SecurityDefaults["insecure_hashes"],
                "InsecureSslProtocolVersions":
                    SecurityDefaults["insecure_ssl_protocol_versions"],
                "WeakKeySizeDsaHigh":
                    str(SecurityDefaults["weak_key_size_dsa_high"]),
                "WeakKeySizeDsaMedium":
                    str(SecurityDefaults["weak_key_size_dsa_medium"]),
                "WeakKeySizeRsaHigh":
                    str(SecurityDefaults["weak_key_size_rsa_high"]),
                "WeakKeySizeRsaMedium":
                    str(SecurityDefaults["weak_key_size_rsa_medium"]),
                "WeakKeySizeEcHigh":
                    str(SecurityDefaults["weak_key_size_ec_high"]),
                "WeakKeySizeEcMedium":
                    str(SecurityDefaults["weak_key_size_ec_medium"]),
                "CheckTypedException":
                    SecurityDefaults["check_typed_exception"],
            }
        
        self.__initCategoriesList(self.__data["EnabledCheckerCategories"])
        self.excludeFilesEdit.setText(self.__data["ExcludeFiles"])
        self.excludeMessagesEdit.setText(self.__data["ExcludeMessages"])
        self.includeMessagesEdit.setText(self.__data["IncludeMessages"])
        self.repeatCheckBox.setChecked(self.__data["RepeatMessages"])
        self.fixIssuesEdit.setText(self.__data["FixCodes"])
        self.noFixIssuesEdit.setText(self.__data["NoFixCodes"])
        self.fixIssuesCheckBox.setChecked(self.__data["FixIssues"])
        self.ignoredCheckBox.setChecked(self.__data["ShowIgnored"])
        self.lineLengthSpinBox.setValue(self.__data["MaxLineLength"])
        self.docLineLengthSpinBox.setValue(self.__data["MaxDocLineLength"])
        self.blankBeforeTopLevelSpinBox.setValue(self.__data["BlankLines"][0])
        self.blankBeforeMethodSpinBox.setValue(self.__data["BlankLines"][1])
        self.hangClosingCheckBox.setChecked(self.__data["HangClosing"])
        self.docTypeComboBox.setCurrentIndex(
            self.docTypeComboBox.findData(self.__data["DocstringType"]))
        self.complexitySpinBox.setValue(self.__data["MaxCodeComplexity"])
        self.lineComplexitySpinBox.setValue(self.__data["LineComplexity"])
        self.lineComplexityScoreSpinBox.setValue(
            self.__data["LineComplexityScore"])
        self.encodingsEdit.setText(self.__data["ValidEncodings"])
        self.copyrightFileSizeSpinBox.setValue(
            self.__data["CopyrightMinFileSize"])
        self.copyrightAuthorEdit.setText(self.__data["CopyrightAuthor"])
        self.__initFuturesList(self.__data["FutureChecker"])
        self.__initBuiltinsIgnoreList(self.__data["BuiltinsChecker"])
        self.aggressiveCheckBox.setChecked(
            self.__data["CommentedCodeChecker"]["Aggressive"])
        self.minAnnotationsCoverageSpinBox.setValue(
            self.__data["AnnotationsChecker"]["MinimumCoverage"])
        self.maxAnnotationsComplexitySpinBox.setValue(
            self.__data["AnnotationsChecker"]["MaximumComplexity"])
        
        # security
        self.tmpDirectoriesEdit.setPlainText("\n".join(
            self.__data["SecurityChecker"]["HardcodedTmpDirectories"]))
        self.hashesEdit.setText(", ".join(
            self.__data["SecurityChecker"]["InsecureHashes"]))
        self.insecureSslProtocolsEdit.setPlainText("\n".join(
            self.__data["SecurityChecker"]["InsecureSslProtocolVersions"]))
        self.dsaHighRiskCombo.setCurrentText(
            self.__data["SecurityChecker"]["WeakKeySizeDsaHigh"])
        self.dsaMediumRiskCombo.setCurrentText(
            self.__data["SecurityChecker"]["WeakKeySizeDsaMedium"])
        self.rsaHighRiskCombo.setCurrentText(
            self.__data["SecurityChecker"]["WeakKeySizeRsaHigh"])
        self.rsaMediumRiskCombo.setCurrentText(
            self.__data["SecurityChecker"]["WeakKeySizeRsaMedium"])
        self.ecHighRiskCombo.setCurrentText(
            self.__data["SecurityChecker"]["WeakKeySizeEcHigh"])
        self.ecMediumRiskCombo.setCurrentText(
            self.__data["SecurityChecker"]["WeakKeySizeEcMedium"])
        self.typedExceptionsCheckBox.setChecked(
            self.__data["SecurityChecker"]["CheckTypedException"])
        
        self.__cleanupData()
    
    def __prepareProgress(self):
        """
        Private method to prepare the progress tab for the next run.
        """
        self.progressList.clear()
        if len(self.files) > 0:
            self.checkProgress.setMaximum(len(self.files))
            self.checkProgressLabel.setVisible(len(self.files) > 1)
            self.checkProgress.setVisible(len(self.files) > 1)
            if len(self.files) > 1:
                if self.__project:
                    self.progressList.addItems([
                        os.path.join("...", self.__project.getRelativePath(f))
                        for f in self.files
                    ])
                else:
                    self.progressList.addItems(self.files)
        
        QApplication.processEvents()
    
    def start(self, fn, save=False, repeat=None):
        """
        Public slot to start the code style check.
        
        @param fn file or list of files or directory to be checked
        @type str or list of str
        @param save flag indicating to save the given file/file list/directory
        @type bool
        @param repeat state of the repeat check box if it is not None
        @type None or bool
        """
        if self.__project is None:
            self.__project = e5App().getObject("Project")
        
        self.mainWidget.setCurrentWidget(self.progressTab)
        
        self.cancelled = False
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.cancelButton.setEnabled(True)
        self.cancelButton.setDefault(True)
        self.statisticsButton.setEnabled(False)
        self.showButton.setEnabled(False)
        self.fixButton.setEnabled(False)
        self.startButton.setEnabled(False)
        self.restartButton.setEnabled(False)
        if repeat is not None:
            self.repeatCheckBox.setChecked(repeat)
        self.checkProgress.setVisible(True)
        QApplication.processEvents()
        
        if save:
            self.__fileOrFileList = fn
        
        if isinstance(fn, list):
            self.files = fn[:]
        elif os.path.isdir(fn):
            self.files = []
            extensions = set(Preferences.getPython("Python3Extensions"))
            for ext in extensions:
                self.files.extend(Utilities.direntries(
                    fn, True, '*{0}'.format(ext), 0))
        else:
            self.files = [fn]
        
        # filter the list depending on the filter string
        if self.files:
            filterString = self.excludeFilesEdit.text()
            filterList = [f.strip() for f in filterString.split(",")
                          if f.strip()]
            for fileFilter in filterList:
                self.files = [
                    f for f in self.files
                    if not fnmatch.fnmatch(f, fileFilter.strip())
                ]
        
        self.__errorItem = None
        self.__resetStatistics()
        self.__clearErrors(self.files)
        self.__cleanupData()
        self.__prepareProgress()
        
        if len(self.files) > 0:
            self.securityNoteLabel.setVisible(
                "S" in self.__getCategories(True, asList=True))
            
            # extract the configuration values
            excludeMessages = self.__assembleExcludeMessages()
            includeMessages = self.includeMessagesEdit.text()
            repeatMessages = self.repeatCheckBox.isChecked()
            fixCodes = self.fixIssuesEdit.text()
            noFixCodes = self.noFixIssuesEdit.text()
            self.__noFixCodesList = [
                c.strip() for c in noFixCodes.split(",") if c.strip()
            ]
            fixIssues = self.fixIssuesCheckBox.isChecked() and repeatMessages
            self.showIgnored = (
                self.ignoredCheckBox.isChecked() and repeatMessages
            )
            maxLineLength = self.lineLengthSpinBox.value()
            maxDocLineLength = self.docLineLengthSpinBox.value()
            blankLines = (
                self.blankBeforeTopLevelSpinBox.value(),
                self.blankBeforeMethodSpinBox.value()
            )
            hangClosing = self.hangClosingCheckBox.isChecked()
            docType = self.docTypeComboBox.itemData(
                self.docTypeComboBox.currentIndex())
            codeComplexityArgs = {
                "McCabeComplexity": self.complexitySpinBox.value(),
                "LineComplexity": self.lineComplexitySpinBox.value(),
                "LineComplexityScore": self.lineComplexityScoreSpinBox.value(),
            }
            miscellaneousArgs = {
                "CodingChecker": self.encodingsEdit.text(),
                "CopyrightChecker": {
                    "MinFilesize": self.copyrightFileSizeSpinBox.value(),
                    "Author": self.copyrightAuthorEdit.text(),
                },
                "FutureChecker": self.__getSelectedFutureImports(),
                "BuiltinsChecker": self.__getBuiltinsIgnoreList(),
                "CommentedCodeChecker": {
                    "Aggressive": self.aggressiveCheckBox.isChecked(),
                }
            }
            annotationArgs = {
                "MinimumCoverage":
                    self.minAnnotationsCoverageSpinBox.value(),
                "MaximumComplexity":
                    self.maxAnnotationsComplexitySpinBox.value(),
            }
            
            securityArgs = {
                "hardcoded_tmp_directories": [
                    t.strip()
                    for t in self.tmpDirectoriesEdit.toPlainText().splitlines()
                ],
                "insecure_hashes": [
                    h.strip()
                    for h in self.hashesEdit.text().split(",")
                ],
                "insecure_ssl_protocol_versions": [
                    p.strip()
                    for p in self.insecureSslProtocolsEdit.toPlainText()
                    .splitlines()
                ],
                "weak_key_size_dsa_high":
                    int(self.dsaHighRiskCombo.currentText()),
                "weak_key_size_dsa_medium":
                    int(self.dsaMediumRiskCombo.currentText()),
                "weak_key_size_rsa_high":
                    int(self.rsaHighRiskCombo.currentText()),
                "weak_key_size_rsa_medium":
                    int(self.rsaMediumRiskCombo.currentText()),
                "weak_key_size_ec_high":
                    int(self.ecHighRiskCombo.currentText()),
                "weak_key_size_ec_medium":
                    int(self.ecMediumRiskCombo.currentText()),
                "check_typed_exception":
                    self.typedExceptionsCheckBox.isChecked(),
            }
            
            self.__options = [excludeMessages, includeMessages, repeatMessages,
                              fixCodes, noFixCodes, fixIssues, maxLineLength,
                              maxDocLineLength, blankLines, hangClosing,
                              docType, codeComplexityArgs, miscellaneousArgs,
                              annotationArgs, securityArgs]
            
            # now go through all the files
            self.progress = 0
            self.files.sort()
            if len(self.files) == 1:
                self.__batch = False
                self.mainWidget.setCurrentWidget(self.resultsTab)
                self.check()
            else:
                self.__batch = True
                self.checkBatch()
        else:
            self.results = CodeStyleCheckerDialog.noFiles
            self.__finished = False
            self.__finish()
    
    def __modifyOptions(self, source):
        """
        Private method to modify the options based on eflag: entries.
        
        This method looks for comment lines like '# eflag: noqa = M601'
        at the end of the source in order to extend the list of excluded
        messages for one file only.
        
        @param source source text
        @type list of str or str
        @return list of checker options
        @rtype list
        """
        options = self.__options[:]
        flags = Utilities.extractFlags(source)
        if "noqa" in flags and isinstance(flags["noqa"], basestring):
            excludeMessages = options[0].strip().rstrip(",")
            if excludeMessages:
                excludeMessages += ","
            excludeMessages += flags["noqa"]
            options[0] = excludeMessages
        return options
    
    def check(self, codestring=''):
        """
        Public method to start a style check for one file.
        
        The results are reported to the __processResult slot.
        
        @param codestring optional sourcestring
        @type str
        """
        if not self.files:
            self.checkProgressLabel.setPath("")
            self.checkProgress.setMaximum(1)
            self.checkProgress.setValue(1)
            self.__finish()
            return
        
        self.filename = self.files.pop(0)
        self.checkProgress.setValue(self.progress)
        self.checkProgressLabel.setPath(self.filename)
        QApplication.processEvents()

        if self.cancelled:
            self.__resort()
            return
        
        self.__lastFileItem = None
        
        if codestring:
            source = codestring.splitlines(True)
            encoding = Utilities.get_coding(source)
        else:
            try:
                source, encoding = Utilities.readEncodedFile(
                    self.filename)
                source = source.splitlines(True)
            except (UnicodeError, IOError) as msg:
                self.results = CodeStyleCheckerDialog.hasResults
                self.__createFileErrorItem(self.filename, str(msg))
                self.progress += 1
                # Continue with next file
                self.check()
                return
        if encoding.endswith(
                ('-selected', '-default', '-guessed', '-ignore')):
            encoding = encoding.rsplit('-', 1)[0]
        
        options = self.__modifyOptions(source)
        
        errors = []
        self.__itms = []
        for error, itm in self.__onlyFixes.pop(self.filename, []):
            errors.append(error)
            self.__itms.append(itm)
        
        eol = self.__getEol(self.filename)
        args = options + [
            errors, eol, encoding, Preferences.getEditor("CreateBackupFile")
        ]
        self.__finished = False
        self.styleCheckService.styleCheck(
            None, self.filename, source, args)
    
    def checkBatch(self):
        """
        Public method to start a style check batch job.
        
        The results are reported to the __processResult slot.
        """
        self.__lastFileItem = None
        
        self.checkProgressLabel.setPath(self.tr("Preparing files..."))
        progress = 0
        
        argumentsList = []
        for filename in self.files:
            progress += 1
            self.checkProgress.setValue(progress)
            QApplication.processEvents()
            
            try:
                source, encoding = Utilities.readEncodedFile(
                    filename)
                source = source.splitlines(True)
            except (UnicodeError, IOError) as msg:
                self.results = CodeStyleCheckerDialog.hasResults
                self.__createFileErrorItem(filename, str(msg))
                continue
            
            if encoding.endswith(
                    ('-selected', '-default', '-guessed', '-ignore')):
                encoding = encoding.rsplit('-', 1)[0]
            
            options = self.__modifyOptions(source)
            
            errors = []
            self.__itms = []
            for error, itm in self.__onlyFixes.pop(filename, []):
                errors.append(error)
                self.__itms.append(itm)
            
            eol = self.__getEol(filename)
            args = options + [
                errors, eol, encoding,
                Preferences.getEditor("CreateBackupFile")
            ]
            argumentsList.append((filename, source, args))
        
        # reset the progress bar to the checked files
        self.checkProgress.setValue(self.progress)
        self.checkProgressLabel.setPath(self.tr("Transferring data..."))
        QApplication.processEvents()
        
        self.__finished = False
        self.styleCheckService.styleBatchCheck(argumentsList)
    
    def __batchFinished(self):
        """
        Private slot handling the completion of a batch job.
        """
        self.checkProgressLabel.setPath("")
        self.checkProgress.setMaximum(1)
        self.checkProgress.setValue(1)
        self.__finish()
    
    def __processError(self, fn, msg):
        """
        Private slot to process an error indication from the service.
        
        @param fn filename of the file
        @type str
        @param msg error message
        @type str
        """
        self.__createErrorItem(fn, msg)
        
        if not self.__batch:
            self.check()
    
    def __processResult(self, fn, codeStyleCheckerStats, fixes, results):
        """
        Private slot called after perfoming a style check on one file.
        
        @param fn filename of the just checked file
        @type str
        @param codeStyleCheckerStats stats of style and name check
        @type dict
        @param fixes number of applied fixes
        @type int
        @param results dictionary containing check result data
        @type dict
        """
        if self.__finished:
            return
        
        # Check if it's the requested file, otherwise ignore signal if not
        # in batch mode
        if not self.__batch and fn != self.filename:
            return
        
        # disable updates of the list for speed
        self.resultList.setUpdatesEnabled(False)
        self.resultList.setSortingEnabled(False)
        
        fixed = None
        ignoredErrors = 0
        securityOk = 0
        if self.__itms:
            for itm, result in zip(self.__itms, results):
                self.__modifyFixedResultItem(itm, result)
            self.__updateFixerStatistics(fixes)
        else:
            self.__lastFileItem = None
            
            for result in results:
                if result["ignored"]:
                    ignoredErrors += 1
                    if self.showIgnored:
                        result["display"] = self.tr(
                            "{0} (ignored)"
                        ).format(result["display"])
                    else:
                        continue
                
                elif result["securityOk"]:
                    securityOk += 1
                    continue
                
                self.results = CodeStyleCheckerDialog.hasResults
                self.__createResultItem(fn, result)

            self.__updateStatistics(
                codeStyleCheckerStats, fixes, ignoredErrors, securityOk)
        
        if fixed:
            vm = e5App().getObject("ViewManager")
            editor = vm.getOpenEditor(fn)
            if editor:
                editor.refresh()
        
        self.progress += 1
        
        self.__resort()
        # reenable updates of the list
        self.resultList.setSortingEnabled(True)
        self.resultList.setUpdatesEnabled(True)
        
        self.__updateProgress(fn)
        
        if not self.__batch:
            self.check()
    
    def __updateProgress(self, fn):
        """
        Private method to update the progress tab.
        
        @param fn filename of the just checked file
        @type str
        """
        if self.__project:
            fn = os.path.join("...", self.__project.getRelativePath(fn))
        
        self.checkProgress.setValue(self.progress)
        self.checkProgressLabel.setPath(fn)
        
        # remove file from the list of jobs to do
        fileItems = self.progressList.findItems(fn, Qt.MatchExactly)
        if fileItems:
            row = self.progressList.row(fileItems[0])
            self.progressList.takeItem(row)
        
        QApplication.processEvents()
    
    def __finish(self):
        """
        Private slot called when the code style check finished or the user
        pressed the cancel button.
        """
        if not self.__finished:
            self.__finished = True
            
            self.cancelled = True
            self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
            self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
            self.cancelButton.setEnabled(False)
            self.statisticsButton.setEnabled(True)
            self.showButton.setEnabled(True)
            self.startButton.setEnabled(True)
            self.restartButton.setEnabled(True)
            
            if self.results != CodeStyleCheckerDialog.hasResults:
                if self.results == CodeStyleCheckerDialog.noResults:
                    QTreeWidgetItem(
                        self.resultList, [self.tr('No issues found.')])
                else:
                    QTreeWidgetItem(
                        self.resultList,
                        [self.tr('No files found (check your ignore list).')])
                QApplication.processEvents()
                self.showButton.setEnabled(False)
            else:
                self.showButton.setEnabled(True)
            self.resultList.header().resizeSections(
                QHeaderView.ResizeToContents)
            self.resultList.header().setStretchLastSection(True)
            
            self.checkProgress.setVisible(False)
            self.checkProgressLabel.setVisible(False)
            
            self.mainWidget.setCurrentWidget(self.resultsTab)
    
    def __getEol(self, fn):
        """
        Private method to get the applicable eol string.
        
        @param fn filename where to determine the line ending
        @type str
        @return eol string
        @rtype str
        """
        if self.__project.isOpen() and self.__project.isProjectFile(fn):
            eol = self.__project.getEolString()
        else:
            eol = Utilities.linesep()
        return eol
    
    @pyqtSlot()
    def on_startButton_clicked(self):
        """
        Private slot to start a code style check run.
        """
        self.__cleanupData()
        
        if self.__forProject:
            data = {
                "EnabledCheckerCategories": self.__getCategories(True),
                "ExcludeFiles": self.excludeFilesEdit.text(),
                "ExcludeMessages": self.excludeMessagesEdit.text(),
                "IncludeMessages": self.includeMessagesEdit.text(),
                "RepeatMessages": self.repeatCheckBox.isChecked(),
                "FixCodes": self.fixIssuesEdit.text(),
                "NoFixCodes": self.noFixIssuesEdit.text(),
                "FixIssues": self.fixIssuesCheckBox.isChecked(),
                "ShowIgnored": self.ignoredCheckBox.isChecked(),
                "MaxLineLength": self.lineLengthSpinBox.value(),
                "MaxDocLineLength": self.docLineLengthSpinBox.value(),
                "BlankLines": (
                    self.blankBeforeTopLevelSpinBox.value(),
                    self.blankBeforeMethodSpinBox.value()
                ),
                "HangClosing": self.hangClosingCheckBox.isChecked(),
                "DocstringType": self.docTypeComboBox.itemData(
                    self.docTypeComboBox.currentIndex()),
                "MaxCodeComplexity": self.complexitySpinBox.value(),
                "LineComplexity": self.lineComplexitySpinBox.value(),
                "LineComplexityScore": self.lineComplexityScoreSpinBox.value(),
                "ValidEncodings": self.encodingsEdit.text(),
                "CopyrightMinFileSize": self.copyrightFileSizeSpinBox.value(),
                "CopyrightAuthor": self.copyrightAuthorEdit.text(),
                "FutureChecker": self.__getSelectedFutureImports(),
                "BuiltinsChecker": self.__getBuiltinsIgnoreList(),
                "CommentedCodeChecker": {
                    "Aggressive": self.aggressiveCheckBox.isChecked(),
                },
                "AnnotationsChecker": {
                    "MinimumCoverage":
                        self.minAnnotationsCoverageSpinBox.value(),
                    "MaximumComplexity":
                        self.maxAnnotationsComplexitySpinBox.value(),
                },
                "SecurityChecker": {
                    "HardcodedTmpDirectories": [
                        t.strip()
                        for t in self.tmpDirectoriesEdit.toPlainText()
                        .splitlines()
                    ],
                    "InsecureHashes": [
                        h.strip()
                        for h in self.hashesEdit.text().split(",")
                    ],
                    "InsecureSslProtocolVersions": [
                        p.strip()
                        for p in self.insecureSslProtocolsEdit.toPlainText()
                        .splitlines()
                    ],
                    "WeakKeySizeDsaHigh":
                        self.dsaHighRiskCombo.currentText(),
                    "WeakKeySizeDsaMedium":
                        self.dsaMediumRiskCombo.currentText(),
                    "WeakKeySizeRsaHigh":
                        self.rsaHighRiskCombo.currentText(),
                    "WeakKeySizeRsaMedium":
                        self.rsaMediumRiskCombo.currentText(),
                    "WeakKeySizeEcHigh":
                        self.ecHighRiskCombo.currentText(),
                    "WeakKeySizeEcMedium":
                        self.ecMediumRiskCombo.currentText(),
                    "CheckTypedException":
                        self.typedExceptionsCheckBox.isChecked(),
                },
            }
            if data != self.__data:
                self.__data = data
                self.__project.setData("CHECKERSPARMS", "Pep8Checker",
                                       self.__data)
        
        self.resultList.clear()
        self.results = CodeStyleCheckerDialog.noResults
        self.cancelled = False
        
        self.start(self.__fileOrFileList)
    
    @pyqtSlot()
    def on_restartButton_clicked(self):
        """
        Private slot to restart a code style check run.
        """
        self.on_startButton_clicked()
    
    def __selectCodes(self, edit, categories, showFixCodes):
        """
        Private method to select message codes via a selection dialog.
        
        @param edit reference of the line edit to be populated
        @type QLineEdit
        @param categories list of message categories to omit
        @type list of str
        @param showFixCodes flag indicating to show a list of fixable
            issues
        @type bool
        """
        from .CodeStyleCodeSelectionDialog import CodeStyleCodeSelectionDialog
        dlg = CodeStyleCodeSelectionDialog(edit.text(), categories,
                                           showFixCodes, self)
        if dlg.exec_() == QDialog.Accepted:
            edit.setText(dlg.getSelectedCodes())
    
    @pyqtSlot()
    def on_excludeMessagesSelectButton_clicked(self):
        """
        Private slot to select the message codes to be excluded via a
        selection dialog.
        """
        self.__selectCodes(self.excludeMessagesEdit,
                           self.__getCategories(False, asList=True),
                           False)
    
    @pyqtSlot()
    def on_includeMessagesSelectButton_clicked(self):
        """
        Private slot to select the message codes to be included via a
        selection dialog.
        """
        self.__selectCodes(self.includeMessagesEdit,
                           self.__getCategories(True, asList=True),
                           False)
    
    @pyqtSlot()
    def on_fixIssuesSelectButton_clicked(self):
        """
        Private slot to select the issue codes to be fixed via a
        selection dialog.
        """
        self.__selectCodes(self.fixIssuesEdit, [], True)
    
    @pyqtSlot()
    def on_noFixIssuesSelectButton_clicked(self):
        """
        Private slot to select the issue codes not to be fixed via a
        selection dialog.
        """
        self.__selectCodes(self.noFixIssuesEdit, [], True)
    
    @pyqtSlot(QTreeWidgetItem, int)
    def on_resultList_itemActivated(self, item, column):
        """
        Private slot to handle the activation of an item.
        
        @param item reference to the activated item
        @type QTreeWidgetItem
        @param column column the item was activated in
        @type int
        """
        if self.results != CodeStyleCheckerDialog.hasResults:
            return
        
        if item.parent():
            fn = Utilities.normabspath(item.data(0, self.filenameRole))
            lineno = item.data(0, self.lineRole)
            position = item.data(0, self.positionRole)
            message = item.data(0, self.messageRole)
            code = item.data(0, self.codeRole)
            
            vm = e5App().getObject("ViewManager")
            vm.openSourceFile(fn, lineno=lineno, pos=position + 1)
            editor = vm.getOpenEditor(fn)
            
            if code in ["E901", "E902"]:
                editor.toggleSyntaxError(lineno, 0, True, message, True)
            else:
                editor.toggleWarning(
                    lineno, 0, True, message, warningType=editor.WarningStyle)
            
            editor.updateVerticalScrollBar()
    
    @pyqtSlot()
    def on_resultList_itemSelectionChanged(self):
        """
        Private slot to change the dialog state depending on the selection.
        """
        self.fixButton.setEnabled(len(self.__getSelectedFixableItems()) > 0)
    
    @pyqtSlot()
    def on_showButton_clicked(self):
        """
        Private slot to handle the "Show" button press.
        """
        vm = e5App().getObject("ViewManager")
        
        selectedIndexes = []
        for index in range(self.resultList.topLevelItemCount()):
            if self.resultList.topLevelItem(index).isSelected():
                selectedIndexes.append(index)
        if len(selectedIndexes) == 0:
            selectedIndexes = list(range(self.resultList.topLevelItemCount()))
        for index in selectedIndexes:
            itm = self.resultList.topLevelItem(index)
            fn = Utilities.normabspath(itm.data(0, self.filenameRole))
            vm.openSourceFile(fn, 1)
            editor = vm.getOpenEditor(fn)
            editor.clearStyleWarnings()
            for cindex in range(itm.childCount()):
                citm = itm.child(cindex)
                lineno = citm.data(0, self.lineRole)
                message = citm.data(0, self.messageRole)
                editor.toggleWarning(
                    lineno, 0, True, message, warningType=editor.WarningStyle)
        
        # go through the list again to clear warning markers for files,
        # that are ok
        openFiles = vm.getOpenFilenames()
        errorFiles = []
        for index in range(self.resultList.topLevelItemCount()):
            itm = self.resultList.topLevelItem(index)
            errorFiles.append(
                Utilities.normabspath(itm.data(0, self.filenameRole)))
        for file in openFiles:
            if file not in errorFiles:
                editor = vm.getOpenEditor(file)
                editor.clearStyleWarnings()
        
        editor = vm.activeWindow()
        editor.updateVerticalScrollBar()
    
    @pyqtSlot()
    def on_statisticsButton_clicked(self):
        """
        Private slot to show the statistics dialog.
        """
        from .CodeStyleStatisticsDialog import CodeStyleStatisticsDialog
        dlg = CodeStyleStatisticsDialog(self.__statistics, self)
        dlg.exec_()
    
    @pyqtSlot()
    def on_loadDefaultButton_clicked(self):
        """
        Private slot to load the default configuration values.
        """
        self.__initCategoriesList(Preferences.Prefs.settings.value(
            "PEP8/EnabledCheckerCategories",
            ",".join(CodeStyleCheckerDialog.checkCategories.keys())))
        self.excludeFilesEdit.setText(Preferences.Prefs.settings.value(
            "PEP8/ExcludeFilePatterns", ""))
        self.excludeMessagesEdit.setText(Preferences.Prefs.settings.value(
            "PEP8/ExcludeMessages", pycodestyle.DEFAULT_IGNORE))
        self.includeMessagesEdit.setText(Preferences.Prefs.settings.value(
            "PEP8/IncludeMessages", ""))
        self.repeatCheckBox.setChecked(Preferences.toBool(
            Preferences.Prefs.settings.value("PEP8/RepeatMessages", False)))
        self.fixIssuesEdit.setText(Preferences.Prefs.settings.value(
            "PEP8/FixCodes", ""))
        self.noFixIssuesEdit.setText(Preferences.Prefs.settings.value(
            "PEP8/NoFixCodes", "E501"))
        self.fixIssuesCheckBox.setChecked(Preferences.toBool(
            Preferences.Prefs.settings.value("PEP8/FixIssues", False)))
        self.ignoredCheckBox.setChecked(Preferences.toBool(
            Preferences.Prefs.settings.value("PEP8/ShowIgnored", False)))
        self.lineLengthSpinBox.setValue(int(Preferences.Prefs.settings.value(
            "PEP8/MaxLineLength", pycodestyle.MAX_LINE_LENGTH)))
        # Use MAX_LINE_LENGTH to avoid messages on existing code
        self.docLineLengthSpinBox.setValue(int(
            Preferences.Prefs.settings.value(
                "PEP8/MaxDocLineLength", pycodestyle.MAX_LINE_LENGTH)))
        self.blankBeforeTopLevelSpinBox.setValue(
            int(Preferences.Prefs.settings.value(
                "PEP8/BlankLinesBeforeTopLevel", 2)))
        self.blankBeforeMethodSpinBox.setValue(
            int(Preferences.Prefs.settings.value(
                "PEP8/BlankLinesBeforeMethod", 1)))
        self.hangClosingCheckBox.setChecked(Preferences.toBool(
            Preferences.Prefs.settings.value("PEP8/HangClosing", False)))
        self.docTypeComboBox.setCurrentIndex(self.docTypeComboBox.findData(
            Preferences.Prefs.settings.value("PEP8/DocstringType", "pep257")))
        self.complexitySpinBox.setValue(int(Preferences.Prefs.settings.value(
            "PEP8/MaxCodeComplexity", 10)))
        self.lineComplexitySpinBox.setValue(
            int(Preferences.Prefs.settings.value(
                "PEP8/LineComplexity", 15)))
        self.lineComplexityScoreSpinBox.setValue(
            int(Preferences.Prefs.settings.value(
                "PEP8/LineComplexityScore", 10)))
        self.encodingsEdit.setText(Preferences.Prefs.settings.value(
            "PEP8/ValidEncodings", "latin-1, utf-8"))
        self.copyrightFileSizeSpinBox.setValue(int(
            Preferences.Prefs.settings.value("PEP8/CopyrightMinFileSize", 0)))
        self.copyrightAuthorEdit.setText(
            Preferences.Prefs.settings.value("PEP8/CopyrightAuthor", ""))
        self.__initFuturesList(
            Preferences.Prefs.settings.value("PEP8/FutureChecker", ""))
        self.__initBuiltinsIgnoreList(Preferences.toDict(
            Preferences.Prefs.settings.value("PEP8/BuiltinsChecker", {
                "str": ["unicode", ],
                "chr": ["unichr", ],
            })))
        self.aggressiveCheckBox.setChecked(Preferences.toBool(
            Preferences.Prefs.settings.value("PEP8/AggressiveSearch", False)))
        self.minAnnotationsCoverageSpinBox.setValue(int(
            Preferences.Prefs.settings.value(
                "PEP8/MinimumAnnotationsCoverage", 75)))
        self.maxAnnotationsComplexitySpinBox.setValue(int(
            Preferences.Prefs.settings.value(
                "PEP8/MaximumAnnotationComplexity", 3)))
        
        # security
        from .Security.SecurityDefaults import SecurityDefaults
        self.tmpDirectoriesEdit.setPlainText("\n".join(
            Preferences.toList(Preferences.Prefs.settings.value(
                "PEP8/HardcodedTmpDirectories",
                SecurityDefaults["hardcoded_tmp_directories"]))))
        self.hashesEdit.setText(", ".join(
            Preferences.toList(Preferences.Prefs.settings.value(
                "PEP8/InsecureHashes",
                SecurityDefaults["insecure_hashes"])))),
        self.insecureSslProtocolsEdit.setPlainText("\n".join(
            Preferences.toList(Preferences.Prefs.settings.value(
                "PEP8/InsecureSslProtocolVersions",
                SecurityDefaults["insecure_ssl_protocol_versions"])))),
        self.dsaHighRiskCombo.setCurrentText(
            Preferences.Prefs.settings.value(
                "PEP8/WeakKeySizeDsaHigh",
                str(SecurityDefaults["weak_key_size_dsa_high"])))
        self.dsaMediumRiskCombo.setCurrentText(
            Preferences.Prefs.settings.value(
                "PEP8/WeakKeySizeDsaMedium",
                str(SecurityDefaults["weak_key_size_dsa_medium"]))),
        self.rsaHighRiskCombo.setCurrentText(
            Preferences.Prefs.settings.value(
                "PEP8/WeakKeySizeRsaHigh",
                str(SecurityDefaults["weak_key_size_rsa_high"]))),
        self.rsaMediumRiskCombo.setCurrentText(
            Preferences.Prefs.settings.value(
                "PEP8/WeakKeySizeRsaMedium",
                str(SecurityDefaults["weak_key_size_rsa_medium"]))),
        self.ecHighRiskCombo.setCurrentText(
            Preferences.Prefs.settings.value(
                "PEP8/WeakKeySizeEcHigh",
                str(SecurityDefaults["weak_key_size_ec_high"]))),
        self.ecMediumRiskCombo.setCurrentText(
            Preferences.Prefs.settings.value(
                "PEP8/WeakKeySizeEcMedium",
                str(SecurityDefaults["weak_key_size_ec_medium"]))),
        self.typedExceptionsCheckBox.setChecked(Preferences.toBool(
            Preferences.Prefs.settings.value(
                "PEP8/CheckTypedException",
                SecurityDefaults["check_typed_exception"]))),
        
        self.__cleanupData()
    
    @pyqtSlot()
    def on_storeDefaultButton_clicked(self):
        """
        Private slot to store the current configuration values as
        default values.
        """
        Preferences.Prefs.settings.setValue(
            "PEP8/EnabledCheckerCategories", self.__getCategories(True))
        Preferences.Prefs.settings.setValue(
            "PEP8/ExcludeFilePatterns", self.excludeFilesEdit.text())
        Preferences.Prefs.settings.setValue(
            "PEP8/ExcludeMessages", self.excludeMessagesEdit.text())
        Preferences.Prefs.settings.setValue(
            "PEP8/IncludeMessages", self.includeMessagesEdit.text())
        Preferences.Prefs.settings.setValue(
            "PEP8/RepeatMessages", self.repeatCheckBox.isChecked())
        Preferences.Prefs.settings.setValue(
            "PEP8/FixCodes", self.fixIssuesEdit.text())
        Preferences.Prefs.settings.setValue(
            "PEP8/NoFixCodes", self.noFixIssuesEdit.text())
        Preferences.Prefs.settings.setValue(
            "PEP8/FixIssues", self.fixIssuesCheckBox.isChecked())
        Preferences.Prefs.settings.setValue(
            "PEP8/ShowIgnored", self.ignoredCheckBox.isChecked())
        Preferences.Prefs.settings.setValue(
            "PEP8/MaxLineLength", self.lineLengthSpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/MaxDocLineLength", self.docLineLengthSpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/BlankLinesBeforeTopLevel",
            self.blankBeforeTopLevelSpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/BlankLinesBeforeMethod",
            self.blankBeforeMethodSpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/HangClosing", self.hangClosingCheckBox.isChecked())
        Preferences.Prefs.settings.setValue(
            "PEP8/DocstringType", self.docTypeComboBox.itemData(
                self.docTypeComboBox.currentIndex()))
        Preferences.Prefs.settings.setValue(
            "PEP8/MaxCodeComplexity", self.complexitySpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/LineComplexity", self.lineComplexitySpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/LineComplexityScore",
            self.lineComplexityScoreSpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/ValidEncodings", self.encodingsEdit.text())
        Preferences.Prefs.settings.setValue(
            "PEP8/CopyrightMinFileSize", self.copyrightFileSizeSpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/CopyrightAuthor", self.copyrightAuthorEdit.text())
        Preferences.Prefs.settings.setValue(
            "PEP8/FutureChecker", self.__getSelectedFutureImports())
        Preferences.Prefs.settings.setValue(
            "PEP8/BuiltinsChecker", self.__getBuiltinsIgnoreList())
        Preferences.Prefs.settings.setValue(
            "PEP8/AggressiveSearch", self.aggressiveCheckBox.isChecked())
        Preferences.Prefs.settings.setValue(
            "PEP8/MinimumAnnotationsCoverage",
            self.minAnnotationsCoverageSpinBox.value())
        Preferences.Prefs.settings.setValue(
            "PEP8/MaximumAnnotationComplexity",
            self.maxAnnotationsComplexitySpinBox.value())
        
        # security
        Preferences.Prefs.settings.setValue(
            "PEP8/HardcodedTmpDirectories",
            [t.strip()
             for t in self.tmpDirectoriesEdit.toPlainText().splitlines()
             ]),
        Preferences.Prefs.settings.setValue(
            "PEP8/InsecureHashes",
            [h.strip()
             for h in self.hashesEdit.text().split(",")
             ]),
        Preferences.Prefs.settings.setValue(
            "PEP8/InsecureSslProtocolVersions",
            [p.strip()
             for p in self.insecureSslProtocolsEdit.toPlainText().splitlines()
             ]),
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeDsaHigh",
            self.dsaHighRiskCombo.currentText()),
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeDsaMedium",
            self.dsaMediumRiskCombo.currentText()),
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeRsaHigh",
            self.rsaHighRiskCombo.currentText()),
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeRsaMedium",
            self.rsaMediumRiskCombo.currentText()),
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeEcHigh",
            self.ecHighRiskCombo.currentText()),
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeEcMedium",
            self.ecMediumRiskCombo.currentText()),
        Preferences.Prefs.settings.setValue(
            "PEP8/CheckTypedException",
            self.typedExceptionsCheckBox.isChecked()),
    
    @pyqtSlot()
    def on_resetDefaultButton_clicked(self):
        """
        Private slot to reset the configuration values to their default values.
        """
        Preferences.Prefs.settings.setValue(
            "PEP8/EnabledCheckerCategories",
            ",".join(CodeStyleCheckerDialog.checkCategories.keys()))
        Preferences.Prefs.settings.setValue("PEP8/ExcludeFilePatterns", "")
        Preferences.Prefs.settings.setValue(
            "PEP8/ExcludeMessages", pycodestyle.DEFAULT_IGNORE)
        Preferences.Prefs.settings.setValue("PEP8/IncludeMessages", "")
        Preferences.Prefs.settings.setValue("PEP8/RepeatMessages", False)
        Preferences.Prefs.settings.setValue("PEP8/FixCodes", "")
        Preferences.Prefs.settings.setValue("PEP8/NoFixCodes", "E501")
        Preferences.Prefs.settings.setValue("PEP8/FixIssues", False)
        Preferences.Prefs.settings.setValue("PEP8/ShowIgnored", False)
        Preferences.Prefs.settings.setValue(
            "PEP8/MaxLineLength", pycodestyle.MAX_LINE_LENGTH)
        # Hard reset to pycodestyle preferences
        Preferences.Prefs.settings.setValue(
            "PEP8/MaxDocLineLength", pycodestyle.MAX_DOC_LENGTH)
        Preferences.Prefs.settings.setValue(
            "PEP8/BlankLinesBeforeTopLevel", 2)
        Preferences.Prefs.settings.setValue(
            "PEP8/BlankLinesBeforeMethod", 1)
        Preferences.Prefs.settings.setValue("PEP8/HangClosing", False)
        Preferences.Prefs.settings.setValue("PEP8/DocstringType", "pep257")
        Preferences.Prefs.settings.setValue("PEP8/MaxCodeComplexity", 10)
        Preferences.Prefs.settings.setValue("PEP8/LineComplexity", 15)
        Preferences.Prefs.settings.setValue("PEP8/LineComplexityScore", 10)
        Preferences.Prefs.settings.setValue(
            "PEP8/ValidEncodings", "latin-1, utf-8")
        Preferences.Prefs.settings.setValue("PEP8/CopyrightMinFileSize", 0)
        Preferences.Prefs.settings.setValue("PEP8/CopyrightAuthor", "")
        Preferences.Prefs.settings.setValue("PEP8/FutureChecker", "")
        Preferences.Prefs.settings.setValue("PEP8/BuiltinsChecker", {
            "str": ["unicode", ],
            "chr": ["unichr", ],
        })
        Preferences.Prefs.settings.setValue("PEP8/AggressiveSearch", False)
        Preferences.Prefs.settings.setValue(
            "PEP8/MinimumAnnotationsCoverage", 75)
        Preferences.Prefs.settings.setValue(
            "PEP8/MaximumAnnotationComplexity", 3)
        
        # security
        from .Security.SecurityDefaults import SecurityDefaults
        Preferences.Prefs.settings.setValue(
            "PEP8/HardcodedTmpDirectories",
            SecurityDefaults["hardcoded_tmp_directories"])
        Preferences.Prefs.settings.setValue(
            "PEP8/InsecureHashes",
            SecurityDefaults["insecure_hashes"])
        Preferences.Prefs.settings.setValue(
            "PEP8/InsecureSslProtocolVersions",
            SecurityDefaults["insecure_ssl_protocol_versions"])
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeDsaHigh",
            str(SecurityDefaults["weak_key_size_dsa_high"]))
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeDsaMedium",
            str(SecurityDefaults["weak_key_size_dsa_medium"]))
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeRsaHigh",
            str(SecurityDefaults["weak_key_size_rsa_high"]))
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeRsaMedium",
            str(SecurityDefaults["weak_key_size_rsa_medium"]))
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeEcHigh",
            str(SecurityDefaults["weak_key_size_ec_high"]))
        Preferences.Prefs.settings.setValue(
            "PEP8/WeakKeySizeEcMedium",
            str(SecurityDefaults["weak_key_size_ec_medium"]))
        Preferences.Prefs.settings.setValue(
            "PEP8/CheckTypedException",
            SecurityDefaults["check_typed_exception"])
        
        # Update UI with default values
        self.on_loadDefaultButton_clicked()
    
    @pyqtSlot()
    def on_cancelButton_clicked(self):
        """
        Private slot to handle the "Cancel" button press.
        """
        if self.__batch:
            self.styleCheckService.cancelStyleBatchCheck()
            QTimer.singleShot(1000, self.__finish)
        else:
            self.__finish()
    
    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked
        @type QAbstractButton
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
    
    def __clearErrors(self, files):
        """
        Private method to clear all warning markers of open editors to be
        checked.
        
        @param files list of files to be checked
        @type list of str
        """
        vm = e5App().getObject("ViewManager")
        openFiles = vm.getOpenFilenames()
        for file in [f for f in openFiles if f in files]:
            editor = vm.getOpenEditor(file)
            editor.clearStyleWarnings()
    
    @pyqtSlot()
    def on_fixButton_clicked(self):
        """
        Private slot to fix selected issues.
        
        Build a dictionary of issues to fix. Update the initialized __options.
        Then call check with the dict as keyparam to fix selected issues.
        """
        fixableItems = self.__getSelectedFixableItems()
        # dictionary of lists of tuples containing the issue and the item
        fixesDict = {}
        for itm in fixableItems:
            filename = itm.data(0, self.filenameRole)
            if filename not in fixesDict:
                fixesDict[filename] = []
            fixesDict[filename].append((
                {
                    "file": filename,
                    "line": itm.data(0, self.lineRole),
                    "offset": itm.data(0, self.positionRole),
                    "code": itm.data(0, self.codeRole),
                    "display": itm.data(0, self.messageRole),
                    "args": itm.data(0, self.argsRole),
                },
                itm
            ))
    
        # update the configuration values (3: fixCodes, 4: noFixCodes,
        # 5: fixIssues, 6: maxLineLength)
        self.__options[3] = self.fixIssuesEdit.text()
        self.__options[4] = self.noFixIssuesEdit.text()
        self.__options[5] = True
        self.__options[6] = self.lineLengthSpinBox.value()
        
        self.files = list(fixesDict.keys())
        # now go through all the files
        self.progress = 0
        self.files.sort()
        self.cancelled = False
        self.__onlyFixes = fixesDict
        self.check()
    
    def __getSelectedFixableItems(self):
        """
        Private method to extract all selected items for fixable issues.
        
        @return selected items for fixable issues
        @rtype list of QTreeWidgetItem
        """
        fixableItems = []
        for itm in self.resultList.selectedItems():
            if itm.childCount() > 0:
                for index in range(itm.childCount()):
                    citm = itm.child(index)
                    if self.__itemFixable(citm) and citm not in fixableItems:
                        fixableItems.append(citm)
            elif self.__itemFixable(itm) and itm not in fixableItems:
                fixableItems.append(itm)
        
        return fixableItems
    
    def __itemFixable(self, itm):
        """
        Private method to check, if an item has a fixable issue.
        
        @param itm item to be checked
        @type QTreeWidgetItem
        @return flag indicating a fixable issue
        @rtype bool
        """
        return (itm.data(0, self.fixableRole) and
                not itm.data(0, self.ignoredRole))
    
    def __initFuturesList(self, selectedFutures):
        """
        Private method to set the selected status of the future imports.
        
        @param selectedFutures comma separated list of expected future imports
        @type str
        """
        if selectedFutures:
            expectedImports = [
                i.strip() for i in selectedFutures.split(",")
                if bool(i.strip())]
        else:
            expectedImports = []
        for row in range(self.futuresList.count()):
            itm = self.futuresList.item(row)
            if itm.text() in expectedImports:
                itm.setCheckState(Qt.Checked)
            else:
                itm.setCheckState(Qt.Unchecked)
    
    def __getSelectedFutureImports(self):
        """
        Private method to get the expected future imports.
        
        @return expected future imports as a comma separated string
        @rtype str
        """
        selectedFutures = []
        for row in range(self.futuresList.count()):
            itm = self.futuresList.item(row)
            if itm.checkState() == Qt.Checked:
                selectedFutures.append(itm.text())
        return ", ".join(selectedFutures)
    
    def __initBuiltinsIgnoreList(self, builtinsIgnoreDict):
        """
        Private method to populate the list of shadowed builtins to be ignored.
        
        @param builtinsIgnoreDict dictionary containing the builtins
            assignments to be ignored
        @type dict of list of str
        """
        self.builtinsAssignmentList.clear()
        for left, rightList in builtinsIgnoreDict.items():
            for right in rightList:
                QTreeWidgetItem(self.builtinsAssignmentList, [left, right])
        
        self.on_builtinsAssignmentList_itemSelectionChanged()
    
    def __getBuiltinsIgnoreList(self):
        """
        Private method to get a dictionary containing the builtins assignments
        to be ignored.
        
        @return dictionary containing the builtins assignments to be ignored
        @rtype dict of list of str
        """
        builtinsIgnoreDict = {}
        for row in range(self.builtinsAssignmentList.topLevelItemCount()):
            itm = self.builtinsAssignmentList.topLevelItem(row)
            left, right = itm.text(0), itm.text(1)
            if left not in builtinsIgnoreDict:
                builtinsIgnoreDict[left] = []
            builtinsIgnoreDict[left].append(right)
        
        return builtinsIgnoreDict
    
    @pyqtSlot()
    def on_builtinsAssignmentList_itemSelectionChanged(self):
        """
        Private slot to react upon changes of the selected builtin assignments.
        """
        self.deleteBuiltinButton.setEnabled(
            len(self.builtinsAssignmentList.selectedItems()) > 0)
    
    @pyqtSlot()
    def on_addBuiltinButton_clicked(self):
        """
        Private slot to add a built-in assignment to be ignored.
        """
        from .CodeStyleAddBuiltinIgnoreDialog import (
            CodeStyleAddBuiltinIgnoreDialog
        )
        dlg = CodeStyleAddBuiltinIgnoreDialog(self)
        if dlg.exec_() == QDialog.Accepted:
            left, right = dlg.getData()
            QTreeWidgetItem(self.builtinsAssignmentList, [left, right])
    
    @pyqtSlot()
    def on_deleteBuiltinButton_clicked(self):
        """
        Private slot to delete the selected items from the list.
        """
        for itm in self.builtinsAssignmentList.selectedItems():
            index = self.builtinsAssignmentList.indexOfTopLevelItem(itm)
            self.builtinsAssignmentList.takeTopLevelItem(index)
            del itm
    
    def __initCategoriesList(self, enabledCategories):
        """
        Private method to set the enabled status of the checker categories.
        
        @param enabledCategories comma separated list of enabled checker
            categories
        @type str
        """
        if enabledCategories:
            enabledCategoriesList = [
                c.strip() for c in enabledCategories.split(",")
                if bool(c.strip())]
        else:
            enabledCategoriesList = list(
                CodeStyleCheckerDialog.checkCategories.keys())
        for row in range(self.categoriesList.count()):
            itm = self.categoriesList.item(row)
            if itm.data(Qt.UserRole) in enabledCategoriesList:
                itm.setCheckState(Qt.Checked)
            else:
                itm.setCheckState(Qt.Unchecked)
    
    def __getCategories(self, enabled, asList=False):
        """
        Private method to get the enabled or disabled checker categories.
        
        @param enabled flag indicating to return enabled categories
        @type bool
        @param asList flag indicating to return the checker categories as a
            Python list
        @type bool
        @return checker categories as a list or comma separated string
        @rtype str or list of str
        """
        state = Qt.Checked if enabled else Qt.Unchecked
        
        checkerList = []
        for row in range(self.categoriesList.count()):
            itm = self.categoriesList.item(row)
            if itm.checkState() == state:
                checkerList.append(itm.data(Qt.UserRole))
        if asList:
            return checkerList
        else:
            return ", ".join(checkerList)
    
    def __assembleExcludeMessages(self):
        """
        Private method to assemble the list of excluded checks.
        
        @return list of excluded checks as a comma separated string.
        @rtype str
        """
        excludeMessages = self.excludeMessagesEdit.text()
        disabledCategories = self.__getCategories(False)
        
        if excludeMessages and disabledCategories:
            return disabledCategories + "," + excludeMessages
        elif disabledCategories:
            return disabledCategories
        elif excludeMessages:
            return excludeMessages
        else:
            return ""
    
    def __cleanupData(self):
        """
        Private method to clean the loaded/entered data of redundant entries.
        """
        # Migrate single letter exclude messages to disabled checker categories
        # and delete them from exlude messages
        excludedMessages = [
            m.strip()
            for m in self.excludeMessagesEdit.text().split(",")
            if bool(m)
        ]
        excludedMessageCategories = [
            c for c in excludedMessages if len(c) == 1
        ]
        enabledCheckers = self.__getCategories(True, asList=True)
        for category in excludedMessageCategories:
            if category in enabledCheckers:
                enabledCheckers.remove(category)
            excludedMessages.remove(category)
        
        # Remove excluded messages of an already excluded category
        disabledCheckers = self.__getCategories(False, asList=True)
        for message in excludedMessages[:]:
            if message[0] in disabledCheckers:
                excludedMessages.remove(message)
        
        self.excludeMessagesEdit.setText(",".join(excludedMessages))
        self.__initCategoriesList(",".join(enabledCheckers))
