--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/RadonMetrics/RawMetricsDialog.py Mon Sep 14 20:18:39 2015 +0200 @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show raw code metrics. +""" + +from __future__ import unicode_literals + +import os +import fnmatch + +from PyQt5.QtCore import pyqtSlot, qVersion, Qt, QTimer +from PyQt5.QtWidgets import ( + QDialog, QDialogButtonBox, QAbstractButton, QMenu, QHeaderView, + QTreeWidgetItem, QApplication +) + +from .Ui_RawMetricsDialog import Ui_RawMetricsDialog + +import Preferences +import Utilities + + +class RawMetricsDialog(QDialog, Ui_RawMetricsDialog): + """ + Class implementing a dialog to show raw code metrics. + """ + 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(RawMetricsDialog, 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.metricsDone.connect(self.__processResult) + self.radonService.metricsError.connect(self.__processError) + self.radonService.batchFinished.connect(self.__batchFinished) + + self.cancelled = False + + self.__menu = QMenu(self) + self.__menu.addAction(self.tr("Collapse all"), + self.__resultCollapse) + self.__menu.addAction(self.tr("Expand all"), self.__resultExpand) + self.resultList.setContextMenuPolicy(Qt.CustomContextMenu) + self.resultList.customContextMenuRequested.connect( + self.__showContextMenu) + + self.__fileList = [] + self.__project = None + self.filterFrame.setVisible(False) + + def __resizeResultColumns(self): + """ + Private method to resize the list columns. + """ + self.resultList.header().resizeSections(QHeaderView.ResizeToContents) + self.resultList.header().setStretchLastSection(True) + + def __createResultItem(self, filename, values): + """ + Private slot to create a new item in the result list. + + @param parent parent of the new item + @type QTreeWidget or QTreeWidgetItem + @param values values to be displayed + @type list + @return the generated item + @rtype QTreeWidgetItem + """ + data = [filename] + for value in values: + try: + data.append("{0:5}".format(int(value))) + except ValueError: + data.append(value) + itm = QTreeWidgetItem(self.resultList, data) + for col in range(1, 6): + itm.setTextAlignment(col, Qt.Alignment(Qt.AlignRight)) + return itm + + 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 code metrics determination. + + @param fn file or list of files or directory to show + the code metrics for (string or list of strings) + """ + 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) + + if len(self.files) > 0: + 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 + self.files.sort() + if len(self.files) == 1: + self.__batch = False + self.rawMetrics() + else: + self.__batch = True + self.rawMetricsBatch() + + def rawMetrics(self, codestring=''): + """ + Public method to start a code metrics 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: + # TODO: adjust this + self.__createResultItem( + self.filename, 1, + "Error: {0}".format(str(msg)).rstrip()) + self.progress += 1 + # Continue with next file + self.rawMetrics() + return + + self.__finished = False + self.radonService.rawMetrics( + None, self.filename, self.source) + + def rawMetricsBatch(self): + """ + Public method to start a code metrics 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: + # TODO: adjust this + self.__createResultItem( + filename, 1, + "Error: {0}".format(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.rawMetricsBatch(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): + # TODO: implement this + print("Error", fn, msg) + + def __processResult(self, fn, result): + """ + Private slot called after perfoming a code metrics calculation on one + file. + + @param fn filename of the file + @type str + @param result result list + @type list + """ + 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 + + self.__createResultItem(fn, result) + + self.progress += 1 + + self.checkProgress.setValue(self.progress) + self.checkProgressLabel.setPath(fn) + QApplication.processEvents() + + if not self.__batch: + self.rawMetrics() + + def __finish(self): + """ + Private slot called when the action or the user pressed the button. + """ + if not self.__finished: + self.__finished = 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) + + 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.cancelRawMetricsBatch() + QTimer.singleShot(1000, self.__finish) + else: + self.__finish() + + @pyqtSlot() + def on_startButton_clicked(self): + """ + Private slot to start a code metrics 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.resultList.clear() + self.cancelled = False + self.start(fileList) + + def __showContextMenu(self, coord): + """ + Private slot to show the context menu of the result list. + + @param coord position of the mouse pointer + @type QPoint + """ + if self.resultList.topLevelItemCount() > 0: + self.__menu.popup(self.mapToGlobal(coord)) + + def __resultCollapse(self): + """ + Private slot to collapse all entries of the result list. + """ + for index in range(self.resultList.topLevelItemCount()): + self.resultList.topLevelItem(index).setExpanded(False) + + def __resultExpand(self): + """ + Private slot to expand all entries of the result list. + """ + for index in range(self.resultList.topLevelItemCount()): + self.resultList.topLevelItem(index).setExpanded(True)