Implemented the cyclomatic complexity stuff.

Sat, 19 Sep 2015 18:24:07 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 19 Sep 2015 18:24:07 +0200
changeset 13
22bc345844e7
parent 12
32a3c9d62e90
child 14
5f206edea27e

Implemented the cyclomatic complexity stuff.

PluginMetricsRadon.e4p file | annotate | diff | comparison | revisions
PluginMetricsRadon.py file | annotate | diff | comparison | revisions
RadonMetrics/CodeMetricsCalculator.py file | annotate | diff | comparison | revisions
RadonMetrics/CyclomaticComplexityCalculator.py file | annotate | diff | comparison | revisions
RadonMetrics/CyclomaticComplexityDialog.py file | annotate | diff | comparison | revisions
RadonMetrics/CyclomaticComplexityDialog.ui file | annotate | diff | comparison | revisions
RadonMetrics/MaintainabilityIndexCalculator.py file | annotate | diff | comparison | revisions
RadonMetrics/MaintainabilityIndexDialog.py file | annotate | diff | comparison | revisions
RadonMetrics/RawMetricsDialog.py file | annotate | diff | comparison | revisions
--- a/PluginMetricsRadon.e4p	Sat Sep 19 11:54:33 2015 +0200
+++ b/PluginMetricsRadon.e4p	Sat Sep 19 18:24:07 2015 +0200
@@ -16,6 +16,8 @@
   <Sources>
     <Source>PluginMetricsRadon.py</Source>
     <Source>RadonMetrics/CodeMetricsCalculator.py</Source>
+    <Source>RadonMetrics/CyclomaticComplexityCalculator.py</Source>
+    <Source>RadonMetrics/CyclomaticComplexityDialog.py</Source>
     <Source>RadonMetrics/MaintainabilityIndexCalculator.py</Source>
     <Source>RadonMetrics/MaintainabilityIndexDialog.py</Source>
     <Source>RadonMetrics/RawMetricsDialog.py</Source>
@@ -28,6 +30,7 @@
     <Source>__init__.py</Source>
   </Sources>
   <Forms>
+    <Form>RadonMetrics/CyclomaticComplexityDialog.ui</Form>
     <Form>RadonMetrics/MaintainabilityIndexDialog.ui</Form>
     <Form>RadonMetrics/RawMetricsDialog.ui</Form>
   </Forms>
--- a/PluginMetricsRadon.py	Sat Sep 19 11:54:33 2015 +0200
+++ b/PluginMetricsRadon.py	Sat Sep 19 18:24:07 2015 +0200
@@ -34,7 +34,10 @@
 shortDescription = "Code metrics plugin using radon package"
 longDescription = (
     """This plug-in implements dialogs to show various code metrics. These"""
-    """ are determined using the radon code metrics package."""
+    """ are determined using the radon code metrics package. 'Raw code"""
+    """ metrics', 'Maintainability Index' and 'McCabe Complexity' can be"""
+    """ requested through different dialogs for one file or the whole"""
+    """ project."""
 )
 needsRestart = False
 pyqtApi = 2
@@ -110,6 +113,22 @@
                 onBatchDone=lambda fx, lang: self.batchJobDone(
                     "mi", fx, lang))
             
+            # cyclomatic complexity
+            self.backgroundService.serviceConnect(
+                'radon_cc', 'Python2', path, 'CyclomaticComplexityCalculator',
+                lambda fn, res: self.metricsCalculationDone("cc", fn, res),
+                onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy2(
+                    "cc", fx, lang, fn, msg),
+                onBatchDone=lambda fx, lang: self.batchJobDone(
+                    "c", fx, lang))
+            self.backgroundService.serviceConnect(
+                'radon_cc', 'Python3', path, 'CyclomaticComplexityCalculator',
+                lambda fn, res: self.metricsCalculationDone("cc", fn, res),
+                onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3(
+                    "cc", fx, lang, fn, msg),
+                onBatchDone=lambda fx, lang: self.batchJobDone(
+                    "cc", fx, lang))
+            
             self.hasBatch = True
         except TypeError:
             # backward compatibility for eric 6.0
@@ -137,6 +156,18 @@
                 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3(
                     "mi", fx, lang, fn, msg))
             
+            # cyclomatic complexity
+            self.backgroundService.serviceConnect(
+                'radon_cc', 'Python2', path, 'CyclomaticComplexityCalculator',
+                lambda fn, res: self.metricsCalculationDone("cc", fn, res),
+                onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy2(
+                    "cc", fx, lang, fn, msg))
+            self.backgroundService.serviceConnect(
+                'radon_cc', 'Python3', path, 'CyclomaticComplexityCalculator',
+                lambda fn, res: self.metricsCalculationDone("cc", fn, res),
+                onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3(
+                    "cc", fx, lang, fn, msg))
+            
             self.hasBatch = False
         
         self.queuedBatches = {
@@ -263,11 +294,13 @@
         """
         self.__projectRawMetricsDialog = None
         self.__projectMIDialog = None
+        self.__projectCCDialog = None
         self.__projectMetricsActs = []
         self.__projectSeparatorActs = []
         
         self.__projectBrowserRawMetricsDialog = None
         self.__projectBrowserMIDialog = None
+        self.__projectBrowserCCDialog = None
         self.__projectBrowserMenu = None
         self.__projectBrowserMetricsActs = []
         self.__projectBrowserSeparatorActs = []
@@ -275,6 +308,7 @@
         self.__editors = []
         self.__editorRawMetricsDialog = None
         self.__editorMIDialog = None
+        self.__editorCCDialog = None
         self.__editorMetricsActs = []
         self.__editorSeparatorActs = []
 
@@ -390,6 +424,62 @@
         for lang in ['Python2', 'Python3']:
             self.backgroundService.requestCancel('batch_radon_mi', lang)
     
+    def cyclomaticComplexity(self, lang, filename, source):
+        """
+        Public method to prepare cyclomatic complexity calculation on one
+        Python source file.
+
+        @param lang language of the file or None to determine by internal
+            algorithm
+        @type str or None
+        @param filename source filename
+        @type str
+        @param source string containing the code
+        @type str
+        """
+        if lang is None:
+            lang = 'Python{0}'.format(determinePythonVersion(filename, source))
+        if lang not in ['Python2', 'Python3']:
+            return
+        
+        self.backgroundService.enqueueRequest(
+            'radon_cc', lang, filename, [source])
+
+    def cyclomaticComplexityBatch(self, argumentsList):
+        """
+        Public method to prepare cyclomatic complexity calculation on multiple
+        Python source files.
+        
+        @param argumentsList list of arguments tuples with each tuple
+            containing filename and source
+        @type (str, str)
+        """
+        data = {
+            "Python2": [],
+            "Python3": [],
+        }
+        for filename, source in argumentsList:
+            lang = 'Python{0}'.format(determinePythonVersion(filename, source))
+            if lang not in ['Python2', 'Python3']:
+                continue
+            else:
+                data[lang].append((filename, source))
+        
+        self.queuedBatches["raw"] = []
+        for lang in ['Python2', 'Python3']:
+            if data[lang]:
+                self.queuedBatches["cc"].append(lang)
+                self.backgroundService.enqueueRequest('batch_radon_cc', lang,
+                                                      "", data[lang])
+                self.batchesFinished["cc"] = False
+    
+    def cancelComplexityBatch(self):
+        """
+        Public method to cancel all batch jobs.
+        """
+        for lang in ['Python2', 'Python3']:
+            self.backgroundService.requestCancel('batch_radon_cc', lang)
+
     def activate(self):
         """
         Public method to activate this plug-in.
@@ -448,6 +538,21 @@
             menu.addAction(act)
             self.__projectMetricsActs.append(act)
             
+            act = E5Action(
+                self.tr('Cyclomatic Complexity'),
+                self.tr('Cyclomatic &Complexity...'), 0, 0,
+                self, 'project_show_radon_cc')
+            act.setStatusTip(
+                self.tr('Show the cyclomatic complexity for Python files.'))
+            act.setWhatsThis(self.tr(
+                """<b>Cyclomatic Complexity...</b>"""
+                """<p>This calculates the cyclomatic complexity of Python"""
+                """ files and shows it together with a ranking.</p>"""
+            ))
+            act.triggered.connect(self.__projectCyclomaticComplexity)
+            menu.addAction(act)
+            self.__projectMetricsActs.append(act)
+            
             act = menu.addSeparator()
             self.__projectSeparatorActs.append(act)
             
@@ -468,7 +573,6 @@
         font.setBold(True)
         act.setFont(font)
         act.triggered.connect(self.__showRadonVersion)
-        menu.addAction(act)
         self.__editorMetricsActs.append(act)
         
         act = E5Action(
@@ -501,6 +605,20 @@
         act.triggered.connect(self.__editorMaintainabilityIndex)
         self.__editorMetricsActs.append(act)
         
+        act = E5Action(
+            self.tr('Cyclomatic Complexity'),
+            self.tr('Cyclomatic &Complexity...'), 0, 0,
+            self, '')
+        act.setStatusTip(
+            self.tr('Show the cyclomatic complexity for Python files.'))
+        act.setWhatsThis(self.tr(
+            """<b>Cyclomatic Complexity...</b>"""
+            """<p>This calculates the cyclomatic complexity of Python"""
+            """ files and shows it together with a ranking.</p>"""
+        ))
+        act.triggered.connect(self.__editorCyclomaticComplexity)
+        self.__editorMetricsActs.append(act)
+        
         e5App().getObject("Project").showMenu.connect(self.__projectShowMenu)
         e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\
             .showMenu.connect(self.__projectBrowserShowMenu)
@@ -618,9 +736,9 @@
                 act = E5Action(
                     self.tr('Code Metrics'),
                     self.tr('Code &Metrics...'), 0, 0,
-                        self, '')
-                act.setStatusTip(
-                    self.tr('Show raw code metrics.'))
+                    self, '')
+                act.setStatusTip(self.tr(
+                    'Show raw code metrics.'))
                 act.setWhatsThis(self.tr(
                     """<b>Code Metrics...</b>"""
                     """<p>This calculates raw code metrics of Python files"""
@@ -635,10 +753,9 @@
                 act = E5Action(
                     self.tr('Maintainability Index'),
                     self.tr('Maintainability &Index...'), 0, 0,
-                    self, 'project_show_radon_mi')
-                act.setStatusTip(
-                    self.tr('Show the maintainability index for Python'
-                            ' files.'))
+                    self, '')
+                act.setStatusTip(self.tr(
+                    'Show the maintainability index for Python files.'))
                 act.setWhatsThis(self.tr(
                     """<b>Maintainability Index...</b>"""
                     """<p>This calculates the maintainability index of"""
@@ -650,6 +767,23 @@
                 menu.addAction(act)
                 self.__projectBrowserMetricsActs.append(act)
                 
+                act = E5Action(
+                    self.tr('Cyclomatic Complexity'),
+                    self.tr('Cyclomatic &Complexity...'), 0, 0,
+                    self, '')
+                act.setStatusTip(self.tr(
+                    'Show the cyclomatic complexity for Python files.'))
+                act.setWhatsThis(self.tr(
+                    """<b>Cyclomatic Complexity...</b>"""
+                    """<p>This calculates the cyclomatic complexity of"""
+                    """ Python files and shows it together with a ranking."""
+                    """</p>"""
+                ))
+                act.triggered.connect(
+                    self.__projectBrowserCyclomaticComplexity)
+                menu.addAction(act)
+                self.__projectBrowserMetricsActs.append(act)
+                
                 act = menu.addSeparator()
                 self.__projectBrowserSeparatorActs.append(act)
     
@@ -813,6 +947,75 @@
                 self.__editorMIDialog.show()
                 self.__editorMIDialog.start(editor.getFileName())
     
+    ##################################################################
+    ## Cyclomatic complexity calculations
+    ##################################################################
+    
+    def __projectCyclomaticComplexity(self):
+        """
+        Private slot used to calculate the cyclomatic complexity for the
+        project.
+        """
+        project = e5App().getObject("Project")
+        project.saveAllScripts()
+        ppath = project.getProjectPath()
+        files = [os.path.join(ppath, file)
+                 for file in project.pdata["SOURCES"]
+                 if file.endswith(
+                     tuple(Preferences.getPython("Python3Extensions")) +
+                     tuple(Preferences.getPython("PythonExtensions")))]
+        
+        if self.__projectCCDialog is None:
+            from RadonMetrics.CyclomaticComplexityDialog import \
+                CyclomaticComplexityDialog
+            self.__projectCCDialog = CyclomaticComplexityDialog(self)
+        self.__projectCCDialog.show()
+        self.__projectCCDialog.prepare(files, project)
+    
+    def __projectBrowserCyclomaticComplexity(self):
+        """
+        Private method to handle the cyclomatic complexity context menu action
+        of the project sources browser.
+        """
+        browser = e5App().getObject("ProjectBrowser").getProjectBrowser(
+            "sources")
+        if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1:
+            fn = []
+            for itm in browser.getSelectedItems([ProjectBrowserFileItem]):
+                fn.append(itm.fileName())
+        else:
+            itm = browser.model().item(browser.currentIndex())
+            try:
+                fn = itm.fileName()
+            except AttributeError:
+                fn = itm.dirName()
+        
+        if self.__projectBrowserCCDialog is None:
+            from RadonMetrics.CyclomaticComplexityDialog import \
+                CyclomaticComplexityDialog
+            self.__projectBrowserCCDialog = CyclomaticComplexityDialog(self)
+        self.__projectBrowserCCDialog.show()
+        self.__projectBrowserCCDialog.start(fn)
+    
+    def __editorCyclomaticComplexity(self):
+        """
+        Private slot to handle the cyclomatic complexity action of the editor
+        show menu.
+        """
+        editor = e5App().getObject("ViewManager").activeWindow()
+        if editor is not None:
+            if editor.checkDirty() and editor.getFileName() is not None:
+                if self.__editorCCDialog is None:
+                    from RadonMetrics.CyclomaticComplexityDialog import \
+                        CyclomaticComplexityDialog
+                    self.__editorCCDialog = CyclomaticComplexityDialog(self)
+                self.__editorCCDialog.show()
+                self.__editorCCDialog.start(editor.getFileName())
+    
+    ##################################################################
+    ## Radon info display
+    ##################################################################
+    
     def __showRadonVersion(self):
         """
         Private slot to show the version number of the used radon library.
@@ -834,4 +1037,3 @@
                 """ complexity</li>"""
                 """</ul></p>"""
             ).format(__version__))
-        
--- a/RadonMetrics/CodeMetricsCalculator.py	Sat Sep 19 11:54:33 2015 +0200
+++ b/RadonMetrics/CodeMetricsCalculator.py	Sat Sep 19 18:24:07 2015 +0200
@@ -3,10 +3,14 @@
 # Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
+"""
+Module implementing the raw code metrics service.
+"""
+
 from __future__ import unicode_literals
 
 try:
-    str = unicode       # __IGNORE_EXCEPTION __IGNORE_WARNING__
+    str = unicode       # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
 except NameError:
     pass
 
@@ -38,7 +42,7 @@
     @param file source filename
     @type str
     @param text source text
-    @param str
+    @type str
     @return tuple containing the result dictionary
     @rtype (tuple of dict)
     """
@@ -49,7 +53,7 @@
     """
     Module function to calculate the raw code metrics for a batch of files.
     
-    @param argumentsList list of arguments tuples as given for check
+    @param argumentsList list of arguments tuples as given for rawCodeMetrics
     @type list
     @param send reference to send function
     @type function
@@ -97,7 +101,8 @@
 
 def worker(input, output):
     """
-    Module function acting as the parallel worker for the style check.
+    Module function acting as the parallel worker for the raw code metrics
+    calculation.
     
     @param input input queue
     @type multiprocessing.Queue
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RadonMetrics/CyclomaticComplexityCalculator.py	Sat Sep 19 18:24:07 2015 +0200
@@ -0,0 +1,211 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the cyclomatic complexity service.
+"""
+
+from __future__ import unicode_literals
+
+try:
+    str = unicode       # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
+except NameError:
+    pass
+
+import multiprocessing
+import sys
+
+
+def initService():
+    """
+    Initialize the service and return the entry point.
+    
+    @return the entry point for the background client (function)
+    """
+    return cyclomaticComplexity
+
+
+def initBatchService():
+    """
+    Initialize the batch service and return the entry point.
+    
+    @return the entry point for the background client (function)
+    """
+    return batchCyclomaticComplexity
+
+
+def cyclomaticComplexity(file, text=""):
+    """
+    Private function to calculate the cyclomatic complexity of one file.
+    
+    @param file source filename
+    @type str
+    @param text source text
+    @type str
+    @return tuple containing the result dictionary
+    @rtype (tuple of dict)
+    """
+    return __cyclomaticComplexity(file, text)
+
+
+def batchCyclomaticComplexity(argumentsList, send, fx, cancelled):
+    """
+    Module function to calculate the cyclomatic complexity for a batch of
+    files.
+    
+    @param argumentsList list of arguments tuples as given for
+        cyclomaticComplexity
+    @type list
+    @param send reference to send function
+    @type function
+    @param fx registered service name
+    @type str
+    @param cancelled reference to function checking for a cancellation
+    @type function
+    """
+    try:
+        NumberOfProcesses = multiprocessing.cpu_count()
+        if NumberOfProcesses >= 1:
+            NumberOfProcesses -= 1
+    except NotImplementedError:
+        NumberOfProcesses = 1
+
+    # Create queues
+    taskQueue = multiprocessing.Queue()
+    doneQueue = multiprocessing.Queue()
+
+    # Submit tasks (initially two time number of processes
+    initialTasks = 2 * NumberOfProcesses
+    for task in argumentsList[:initialTasks]:
+        taskQueue.put(task)
+
+    # Start worker processes
+    for i in range(NumberOfProcesses):
+        multiprocessing.Process(target=worker, args=(taskQueue, doneQueue))\
+            .start()
+
+    # Get and send results
+    endIndex = len(argumentsList) - initialTasks
+    for i in range(len(argumentsList)):
+        filename, result = doneQueue.get()
+        send(fx, filename, result)
+        if cancelled():
+            # just exit the loop ignoring the results of queued tasks
+            break
+        if i < endIndex:
+            taskQueue.put(argumentsList[i + initialTasks])
+
+    # Tell child processes to stop
+    for i in range(NumberOfProcesses):
+        taskQueue.put('STOP')
+
+
+def worker(input, output):
+    """
+    Module function acting as the parallel worker for the cyclomatic
+    complexity calculation.
+    
+    @param input input queue
+    @type multiprocessing.Queue
+    @param output output queue
+    @type multiprocessing.Queue
+    """
+    for filename, source in iter(input.get, 'STOP'):
+        result = __cyclomaticComplexity(filename, source)
+        output.put((filename, result))
+
+
+def __cyclomaticComplexity(file, text=""):
+    """
+    Private function to calculate the cyclomatic complexity for one Python
+    file.
+    
+    @param file source filename
+    @type str
+    @param text source text
+    @type str
+    @return tuple containing the result dictionary
+    @rtype (tuple of dict)
+    """
+    from radon.complexity import cc_visit, cc_rank
+    
+    # Check type for py2: if not str it's unicode
+    if sys.version_info[0] == 2:
+        try:
+            text = text.encode('utf-8')
+        except UnicodeError:
+            pass
+    
+    try:
+        cc = cc_visit(text)
+        res = {"result": [v for v in map(__cc2Dict, cc)
+                          if v["type"] != "method"]}
+        totalCC = 0
+        rankSummary = {
+            "A": 0,
+            "B": 0,
+            "C": 0,
+            "D": 0,
+            "E": 0,
+            "F": 0,
+        }
+        for block in cc:
+            totalCC += block.complexity
+            rankSummary[cc_rank(block.complexity)] += 1
+        res["total_cc"] = totalCC
+        res["count"] = len(cc)
+        res["summary"] = rankSummary
+    except Exception as err:
+        res = {"error": str(err)}
+    return (res, )
+
+
+def __cc2Dict(obj):
+    """
+    Private function to convert an object holding cyclomatic complexity results
+    into a dictionary.
+    
+    @param obj object as returned from analyze()
+    @type radon.raw.Module
+    @return conversion result
+    @rtype dict
+    """
+    from radon.complexity import cc_rank
+    from radon.visitors import Function
+    
+    result = {
+        'type': __getType(obj),
+        'rank': cc_rank(obj.complexity),
+    }
+    attrs = set(Function._fields) - set(('is_method', 'closures'))
+    attrs.add("fullname")
+    for attr in attrs:
+        v = getattr(obj, attr, None)
+        if v is not None:
+            result[attr] = v
+    for key in ('methods', 'closures'):
+        if hasattr(obj, key):
+            result[key] = list(map(__cc2Dict, getattr(obj, key)))
+    return result
+
+
+def __getType(obj):
+    """
+    Private function to get the type of an object as a string.
+    
+    @param obj object to be analyzed
+    @type radon.visitors.Function or radon.visitors.Class
+    @return type string for the object
+    @rtype str, one of ["method", "function", "class"]
+    """
+    from radon.visitors import Function
+    
+    if isinstance(obj, Function):
+        if obj.is_method:
+            return 'method'
+        else:
+            return 'function'
+    else:
+        return 'class'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RadonMetrics/CyclomaticComplexityDialog.py	Sat Sep 19 18:24:07 2015 +0200
@@ -0,0 +1,513 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show the cyclomatic complexity (McCabe
+complexity).
+"""
+
+from __future__ import unicode_literals
+
+try:
+    str = unicode       # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
+except NameError:
+    pass
+
+import os
+import fnmatch
+import sys
+
+sys.path.insert(0, os.path.dirname(__file__))
+
+from PyQt5.QtCore import pyqtSlot, qVersion, Qt, QTimer, QLocale
+from PyQt5.QtWidgets import (
+    QDialog, QDialogButtonBox, QAbstractButton, QHeaderView, QTreeWidgetItem,
+    QApplication
+)
+
+from .Ui_CyclomaticComplexityDialog import Ui_CyclomaticComplexityDialog
+from E5Gui.E5Application import e5App
+
+import Preferences
+import Utilities
+
+
+class CyclomaticComplexityDialog(QDialog, Ui_CyclomaticComplexityDialog):
+    """
+    Class implementing a dialog to show the cyclomatic complexity (McCabe
+    complexity).
+    """
+    def __init__(self, radonService, parent=None):
+        """
+        Constructor
+        
+        @param radonService reference to the service
+        @type RadonMetricsPlugin
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(CyclomaticComplexityDialog, 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.resultList.headerItem().setText(self.resultList.columnCount(), "")
+        
+        self.radonService = radonService
+        self.radonService.complexityDone.connect(self.__processResult)
+        self.radonService.error.connect(self.__processError)
+        self.radonService.batchFinished.connect(self.__batchFinished)
+        
+        self.cancelled = False
+        
+        self.__project = e5App().getObject("Project")
+        self.__locale = QLocale()
+        self.__finished = True
+        self.__errorItem = None
+        
+        self.__fileList = []
+        self.filterFrame.setVisible(False)
+        
+        self.explanationLabel.setText(self.tr(
+            "<table>"
+            "<tr><td colspan=3><b>Ranking:</b></td></tr>"
+            "<tr><td><b>A</b></td><td>1 - 5</td>"
+            "<td>(low risk - simple block)</td></tr>"
+            "<tr><td><b>B</b></td><td>6 - 10</td>"
+            "<td>(low risk - well structured and stable block)</td></tr>"
+            "<tr><td><b>C</b></td><td>11 - 20</td>"
+            "<td>(moderate risk - slightly complex block)</td></tr>"
+            "<tr><td><b>D</b></td><td>21 - 30</td>"
+            "<td>(more than moderate risk - more complex block)</td></tr>"
+            "<tr><td><b>E</b></td><td>31 - 40</td>"
+            "<td>(high risk - complex block, alarming)</td></tr>"
+            "<tr><td><b>F</b></td><td>&gt; 40</td>"
+            "<td>(very high risk - error-prone, unstable block)</td></tr>"
+            "</table>"
+        ))
+        self.typeLabel.setText(self.tr(
+            "<table>"
+            "<tr><td colspan=2><b>Type:</b></td></tr>"
+            "<tr><td><b>C</b></td><td>Class</td></tr>"
+            "<tr><td><b>F</b></td><td>Function</td></tr>"
+            "<tr><td><b>M</b></td><td>Method</td></tr>"
+            "</table>"
+        ))
+        
+        self.__rankColors = {
+            "A": Qt.green,
+            "B": Qt.green,
+            "C": Qt.yellow,
+            "D": Qt.yellow,
+            "E": Qt.red,
+            "F": Qt.red,
+        }
+    
+    def __resizeResultColumns(self):
+        """
+        Private method to resize the list columns.
+        """
+        self.resultList.header().resizeSections(QHeaderView.ResizeToContents)
+        self.resultList.header().setStretchLastSection(True)
+    
+    def __createFileItem(self, filename):
+        """
+        Private method to create a new file item in the result list.
+        
+        @param filename name of the file
+        @type str
+        @return reference to the created item
+        @rtype QTreeWidgetItem
+        """
+        itm = QTreeWidgetItem(
+            self.resultList,
+            [self.__project.getRelativePath(filename)])
+        itm.setExpanded(True)
+        itm.setFirstColumnSpanned(True)
+        return itm
+    
+    def __createResultItem(self, parentItem, values):
+        """
+        Private slot to create a new item in the result list.
+        
+        @param parentItem reference to the parent item
+        @type QTreeWidgetItem
+        @param values values to be displayed
+        @type dict
+        """
+        itm = QTreeWidgetItem(parentItem, [
+            self.__mappedType[values["type"]],
+            values["fullname"],
+            "{0:3}".format(values["complexity"]),
+            values["rank"],
+            "{0:6}".format(values["lineno"]),
+        ])
+        itm.setTextAlignment(2, Qt.Alignment(Qt.AlignRight))
+        itm.setTextAlignment(3, Qt.Alignment(Qt.AlignHCenter))
+        itm.setTextAlignment(4, Qt.Alignment(Qt.AlignRight))
+        if values["rank"] in ["A", "B", "C", "D", "E", "F"]:
+            itm.setBackground(3, self.__rankColors[values["rank"]])
+        
+        if "methods" in values:
+            itm.setExpanded(True)
+            for method in values["methods"]:
+                self.__createResultItem(itm, method)
+        
+        if "closures" in values and values["closures"]:
+            itm.setExpanded(True)
+            for closure in values["closures"]:
+                self.__createResultItem(itm, closure)
+    
+    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 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.__fileList = fileList[:]
+        self.__project = project
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
+        
+        self.filterFrame.setVisible(True)
+        
+        self.__data = self.__project.getData(
+            "OTHERTOOLSPARMS", "RadonCodeMetrics")
+        if self.__data is None or "ExcludeFiles" not in self.__data:
+            self.__data = {"ExcludeFiles": ""}
+        self.excludeFilesEdit.setText(self.__data["ExcludeFiles"])
+    
+    def start(self, fn):
+        """
+        Public slot to start the cyclomatic complexity determination.
+        
+        @param fn file or list of files or directory to show
+            the cyclomatic complexity for
+        @type str or list of str
+        """
+        self.__errorItem = None
+        self.resultList.clear()
+        self.cancelled = False
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
+        QApplication.processEvents()
+        
+        if isinstance(fn, list):
+            self.files = fn
+        elif os.path.isdir(fn):
+            self.files = []
+            extensions = set(Preferences.getPython("PythonExtensions") +
+                             Preferences.getPython("Python3Extensions"))
+            for ext in extensions:
+                self.files.extend(
+                    Utilities.direntries(fn, True, '*{0}'.format(ext), 0))
+        else:
+            self.files = [fn]
+        self.files.sort()
+        # check for missing files
+        for f in self.files[:]:
+            if not os.path.exists(f):
+                self.files.remove(f)
+        
+        self.__summary = {
+            "A": 0,
+            "B": 0,
+            "C": 0,
+            "D": 0,
+            "E": 0,
+            "F": 0,
+        }
+        self.__ccSum = 0
+        self.__ccCount = 0
+        
+        self.__mappedType = {
+            "class": "C",
+            "function": "F",
+            "method": "M",
+        }
+        
+        if len(self.files) > 0:
+            # disable updates of the list for speed
+            self.resultList.setUpdatesEnabled(False)
+            self.resultList.setSortingEnabled(False)
+            
+            self.checkProgress.setMaximum(len(self.files))
+            self.checkProgress.setVisible(len(self.files) > 1)
+            self.checkProgressLabel.setVisible(len(self.files) > 1)
+            QApplication.processEvents()
+            
+            # now go through all the files
+            self.progress = 0
+            if len(self.files) == 1 or not self.radonService.hasBatch:
+                self.__batch = False
+                self.cyclomaticComplexity()
+            else:
+                self.__batch = True
+                self.cyclomaticComplexityBatch()
+    
+    def cyclomaticComplexity(self, codestring=''):
+        """
+        Public method to start a cyclomatic complexity calculation for one
+        Python file.
+        
+        The results are reported to the __processResult slot.
+        
+        @keyparam 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:
+            return
+        
+        try:
+            self.source = Utilities.readEncodedFile(self.filename)[0]
+            self.source = Utilities.normalizeCode(self.source)
+        except (UnicodeError, IOError) as msg:
+            self.__createErrorItem(self.filename, str(msg).rstrip())
+            self.progress += 1
+            # Continue with next file
+            self.cyclomaticComplexity()
+            return
+
+        self.__finished = False
+        self.radonService.cyclomaticComplexity(
+            None, self.filename, self.source)
+
+    def cyclomaticComplexityBatch(self):
+        """
+        Public method to start a cyclomatic complexity calculation 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 = Utilities.readEncodedFile(filename)[0]
+                source = Utilities.normalizeCode(source)
+            except (UnicodeError, IOError) as msg:
+                self.__createErrorItem(filename, str(msg).rstrip())
+                continue
+            
+            argumentsList.append((filename, source))
+        
+        # reset the progress bar to the checked files
+        self.checkProgress.setValue(self.progress)
+        QApplication.processEvents()
+        
+        self.__finished = False
+        self.radonService.cyclomaticComplexityBatch(argumentsList)
+    
+    def __batchFinished(self, type_):
+        """
+        Private slot handling the completion of a batch job.
+        
+        @param type_ type of the calculated metrics
+        @type str, one of ["raw", "mi", "cc"]
+        """
+        if type_ == "cc":
+            self.checkProgressLabel.setPath("")
+            self.checkProgress.setMaximum(1)
+            self.checkProgress.setValue(1)
+            self.__finish()
+    
+    def __processError(self, type_, fn, msg):
+        """
+        Private slot to process an error indication from the service.
+        
+        @param type_ type of the calculated metrics
+        @type str, one of ["raw", "mi", "cc"]
+        @param fn filename of the file
+        @type str
+        @param msg error message
+        @type str
+        """
+        if type_ == "cc":
+            self.__createErrorItem(fn, msg)
+    
+    def __processResult(self, fn, result):
+        """
+        Private slot called after perfoming a cyclomatic complexity calculation
+        on one file.
+        
+        @param fn filename of the file
+        @type str
+        @param result result dict
+        @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
+        
+        if "error" in result:
+            self.__createErrorItem(fn, result["error"])
+        else:
+            if result["result"]:
+                fitm = self.__createFileItem(fn)
+                for resultDict in result["result"]:
+                    self.__createResultItem(fitm, resultDict)
+        
+            self.__ccCount += result["count"]
+            self.__ccSum += result["total_cc"]
+            for rank in result["summary"]:
+                self.__summary[rank] += result["summary"][rank]
+        
+        self.progress += 1
+        
+        self.checkProgress.setValue(self.progress)
+        self.checkProgressLabel.setPath(self.__project.getRelativePath(fn))
+        QApplication.processEvents()
+        
+        if not self.__batch:
+            self.cyclomaticComplexity()
+    
+    def __finish(self):
+        """
+        Private slot called when the action or the user pressed the button.
+        """
+        from radon.complexity import cc_rank
+        
+        if not self.__finished:
+            self.__finished = True
+            
+            # reenable updates of the list
+            self.resultList.setSortingEnabled(True)
+            self.resultList.sortItems(1, Qt.AscendingOrder)
+            self.resultList.setUpdatesEnabled(True)
+            
+            self.cancelled = True
+            self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
+            self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
+            self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
+            
+            self.resultList.header().resizeSections(
+                QHeaderView.ResizeToContents)
+            self.resultList.header().setStretchLastSection(True)
+            if qVersion() >= "5.0.0":
+                self.resultList.header().setSectionResizeMode(
+                    QHeaderView.Interactive)
+            else:
+                self.resultList.header().setResizeMode(QHeaderView.Interactive)
+            
+            averageCC = float(self.__ccSum) / self.__ccCount
+            
+            self.summaryLabel.setText(self.tr(
+                "<b>Summary:</b><br/>"
+                "{0} blocks (classes, functions, methods) analyzed.<br/>"
+                "Average complexity: {7} ({8})"
+                "<table>"
+                "<tr><td width=30><b>A</b></td><td>{1} blocks</td></tr>"
+                "<tr><td width=30><b>B</b></td><td>{2} blocks</td></tr>"
+                "<tr><td width=30><b>C</b></td><td>{3} blocks</td></tr>"
+                "<tr><td width=30><b>D</b></td><td>{4} blocks</td></tr>"
+                "<tr><td width=30><b>E</b></td><td>{5} blocks</td></tr>"
+                "<tr><td width=30><b>F</b></td><td>{6} blocks</td></tr>"
+                "</table>"
+            ).format(
+                self.__ccCount,
+                self.__summary["A"],
+                self.__summary["B"],
+                self.__summary["C"],
+                self.__summary["D"],
+                self.__summary["E"],
+                self.__summary["F"],
+                cc_rank(averageCC),
+                self.__locale.toString(averageCC, "f", 1)
+            ))
+            
+            self.checkProgress.setVisible(False)
+            self.checkProgressLabel.setVisible(False)
+    
+    @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()
+        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
+            if self.__batch:
+                self.radonService.cancelComplexityBatch()
+                QTimer.singleShot(1000, self.__finish)
+            else:
+                self.__finish()
+    
+    @pyqtSlot()
+    def on_startButton_clicked(self):
+        """
+        Private slot to start a cyclomatic complexity run.
+        """
+        fileList = self.__fileList[:]
+        
+        filterString = self.excludeFilesEdit.text()
+        if "ExcludeFiles" not in self.__data or \
+           filterString != self.__data["ExcludeFiles"]:
+            self.__data["ExcludeFiles"] = filterString
+            self.__project.setData(
+                "OTHERTOOLSPARMS", "RadonCodeMetrics", self.__data)
+        filterList = [f.strip() for f in filterString.split(",")
+                      if f.strip()]
+        if filterList:
+            for filter in filterList:
+                fileList = \
+                    [f for f in fileList if not fnmatch.fnmatch(f, filter)]
+        
+        self.start(fileList)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RadonMetrics/CyclomaticComplexityDialog.ui	Sat Sep 19 18:24:07 2015 +0200
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CyclomaticComplexityDialog</class>
+ <widget class="QDialog" name="CyclomaticComplexityDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>900</width>
+    <height>700</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Cyclomatic Complexity</string>
+  </property>
+  <property name="whatsThis">
+   <string>&lt;b&gt;Cyclomatic Complexity&lt;/b&gt;
+&lt;p&gt;This dialog shows the cyclomatic complexity and rank.&lt;/p&gt;</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QFrame" name="filterFrame">
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Exclude Files:</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="E5ClearableLineEdit" name="excludeFilesEdit">
+        <property name="toolTip">
+         <string>Enter filename patterns of files to be excluded separated by a comma</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="Line" name="line">
+        <property name="lineWidth">
+         <number>2</number>
+        </property>
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="startButton">
+        <property name="toolTip">
+         <string>Press to start the code metrics run</string>
+        </property>
+        <property name="text">
+         <string>Start</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTreeWidget" name="resultList">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <column>
+      <property name="text">
+       <string>Type</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Name</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Complexity</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Rank</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Start</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="summaryLabel">
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="typeLabel">
+       <property name="maximumSize">
+        <size>
+         <width>200</width>
+         <height>16777215</height>
+        </size>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="explanationLabel">
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="E5SqueezeLabelPath" name="checkProgressLabel">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QProgressBar" name="checkProgress">
+     <property name="toolTip">
+      <string>Shows the progress of the code metrics action</string>
+     </property>
+     <property name="value">
+      <number>0</number>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="format">
+      <string>%v/%m Files</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="6"/>
+ <pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
+ <customwidgets>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+  <customwidget>
+   <class>E5SqueezeLabelPath</class>
+   <extends>QLabel</extends>
+   <header>E5Gui/E5SqueezeLabels.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>startButton</tabstop>
+  <tabstop>excludeFilesEdit</tabstop>
+  <tabstop>resultList</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- a/RadonMetrics/MaintainabilityIndexCalculator.py	Sat Sep 19 11:54:33 2015 +0200
+++ b/RadonMetrics/MaintainabilityIndexCalculator.py	Sat Sep 19 18:24:07 2015 +0200
@@ -3,10 +3,14 @@
 # Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
+"""
+Module implementing the maintainability index service.
+"""
+
 from __future__ import unicode_literals
 
 try:
-    str = unicode       # __IGNORE_EXCEPTION __IGNORE_WARNING__
+    str = unicode       # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
 except NameError:
     pass
 
@@ -39,7 +43,7 @@
     @param file source filename
     @type str
     @param text source text
-    @param str
+    @type str
     @return tuple containing the result dictionary
     @rtype (tuple of dict)
     """
@@ -51,7 +55,8 @@
     Module function to calculate the maintainability index for a batch of
     files.
     
-    @param argumentsList list of arguments tuples as given for check
+    @param argumentsList list of arguments tuples as given for
+        maintainabilityIndex
     @type list
     @param send reference to send function
     @type function
@@ -99,7 +104,8 @@
 
 def worker(input, output):
     """
-    Module function acting as the parallel worker for the style check.
+    Module function acting as the parallel worker for the maintainability
+    index calculation.
     
     @param input input queue
     @type multiprocessing.Queue
--- a/RadonMetrics/MaintainabilityIndexDialog.py	Sat Sep 19 11:54:33 2015 +0200
+++ b/RadonMetrics/MaintainabilityIndexDialog.py	Sat Sep 19 18:24:07 2015 +0200
@@ -10,7 +10,7 @@
 from __future__ import unicode_literals
 
 try:
-    str = unicode       # __IGNORE_EXCEPTION __IGNORE_WARNING__
+    str = unicode       # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
 except NameError:
     pass
 
@@ -78,7 +78,7 @@
         ))
         self.__rankColors = {
             "A": Qt.green,
-            "B": Qt.yellow, #QColor("orange"),
+            "B": Qt.yellow,
             "C": Qt.red,
         }
     
@@ -168,6 +168,8 @@
             the maintainability index for
         @type str or list of str
         """
+        self.__errorItem = None
+        self.resultList.clear()
         self.cancelled = False
         self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
         self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
@@ -248,7 +250,7 @@
             self.__createErrorItem(self.filename, str(msg).rstrip())
             self.progress += 1
             # Continue with next file
-            self.rawMetrics()
+            self.maintainabilityIndex()
             return
 
         self.__finished = False
@@ -424,7 +426,4 @@
                 fileList = \
                     [f for f in fileList if not fnmatch.fnmatch(f, filter)]
         
-        self.__errorItem = None
-        self.resultList.clear()
-        self.cancelled = False
         self.start(fileList)
--- a/RadonMetrics/RawMetricsDialog.py	Sat Sep 19 11:54:33 2015 +0200
+++ b/RadonMetrics/RawMetricsDialog.py	Sat Sep 19 18:24:07 2015 +0200
@@ -10,7 +10,7 @@
 from __future__ import unicode_literals
 
 try:
-    str = unicode       # __IGNORE_EXCEPTION __IGNORE_WARNING__
+    str = unicode       # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
 except NameError:
     pass
 
@@ -119,7 +119,7 @@
         data.append("{0:3.0%}".format(
             values["comments"] / (float(values["sloc"]) or 1)))
         data.append("{0:3.0%}".format(
-            (values["comments"] + values["multi"]) / 
+            (values["comments"] + values["multi"]) /
             (float(values["loc"]) or 1)))
         itm = QTreeWidgetItem(self.resultList, data)
         for col in range(1, 10):
@@ -179,6 +179,8 @@
             the code metrics for
         @type str or list of str
         """
+        self.__errorItem = None
+        self.resultList.clear()
         self.cancelled = False
         self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
         self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
@@ -475,7 +477,4 @@
                 fileList = \
                     [f for f in fileList if not fnmatch.fnmatch(f, filter)]
         
-        self.__errorItem = None
-        self.resultList.clear()
-        self.cancelled = False
         self.start(fileList)

eric ide

mercurial