RadonMetrics/RawMetricsDialog.py

changeset 5
db25d1d5cc3a
parent 4
9ac53bf21182
child 7
3eb5fb0ae72c
equal deleted inserted replaced
4:9ac53bf21182 5:db25d1d5cc3a
7 Module implementing a dialog to show raw code metrics. 7 Module implementing a dialog to show raw code metrics.
8 """ 8 """
9 9
10 from __future__ import unicode_literals 10 from __future__ import unicode_literals
11 11
12 try:
13 str = unicode # __IGNORE_EXCEPTION __IGNORE_WARNING__
14 except NameError:
15 pass
16
12 import os 17 import os
13 import fnmatch 18 import fnmatch
14 19
15 from PyQt5.QtCore import pyqtSlot, qVersion, Qt, QTimer 20 from PyQt5.QtCore import pyqtSlot, qVersion, Qt, QTimer, QLocale
16 from PyQt5.QtWidgets import ( 21 from PyQt5.QtWidgets import (
17 QDialog, QDialogButtonBox, QAbstractButton, QMenu, QHeaderView, 22 QDialog, QDialogButtonBox, QAbstractButton, QHeaderView, QTreeWidgetItem,
18 QTreeWidgetItem, QApplication 23 QApplication
19 ) 24 )
20 25
21 from .Ui_RawMetricsDialog import Ui_RawMetricsDialog 26 from .Ui_RawMetricsDialog import Ui_RawMetricsDialog
22 27
23 from E5Gui.E5Application import e5App 28 from E5Gui.E5Application import e5App
24 29
25 import Preferences 30 import Preferences
26 import Utilities 31 import Utilities
27 32
28 # TODO: add summary table
29 # TODO: add column explanations
30 class RawMetricsDialog(QDialog, Ui_RawMetricsDialog): 33 class RawMetricsDialog(QDialog, Ui_RawMetricsDialog):
31 """ 34 """
32 Class implementing a dialog to show raw code metrics. 35 Class implementing a dialog to show raw code metrics.
33 """ 36 """
34 def __init__(self, radonService, parent=None): 37 def __init__(self, radonService, parent=None):
45 self.setWindowFlags(Qt.Window) 48 self.setWindowFlags(Qt.Window)
46 49
47 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) 50 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
48 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) 51 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
49 52
53 self.summaryList.headerItem().setText(
54 self.summaryList.columnCount(), "")
55 self.summaryList.header().resizeSection(0, 200)
56 self.summaryList.header().resizeSection(1, 100)
57
50 self.resultList.headerItem().setText(self.resultList.columnCount(), "") 58 self.resultList.headerItem().setText(self.resultList.columnCount(), "")
51 59
52 self.radonService = radonService 60 self.radonService = radonService
53 self.radonService.metricsDone.connect(self.__processResult) 61 self.radonService.metricsDone.connect(self.__processResult)
54 self.radonService.metricsError.connect(self.__processError) 62 self.radonService.metricsError.connect(self.__processError)
55 self.radonService.batchFinished.connect(self.__batchFinished) 63 self.radonService.batchFinished.connect(self.__batchFinished)
56 64
57 self.cancelled = False 65 self.cancelled = False
58 66
59 self.__project = e5App().getObject("Project") 67 self.__project = e5App().getObject("Project")
60 68 self.__locale = QLocale()
61 self.__menu = QMenu(self)
62 self.__menu.addAction(self.tr("Collapse all"),
63 self.__resultCollapse)
64 self.__menu.addAction(self.tr("Expand all"), self.__resultExpand)
65 self.resultList.setContextMenuPolicy(Qt.CustomContextMenu)
66 self.resultList.customContextMenuRequested.connect(
67 self.__showContextMenu)
68 69
69 self.__fileList = [] 70 self.__fileList = []
70 self.filterFrame.setVisible(False) 71 self.filterFrame.setVisible(False)
72
73 self.explanationLabel.setText(self.tr(
74 "<table>"
75 "<tr><td><b>LOC</b></td>"
76 "<td>Lines of code (LOC = SLOC + Empty)</td></tr>"
77 "<tr><td><b>SLOC</b></td><td>Source lines of code</td></tr>"
78 "<tr><td><b>LLOC</b></td><td>Logical lines of code</td></tr>"
79 "<tr><td><b>Comments</b></td><td>Comment lines</td></tr>"
80 "<tr><td><b>Multi</b></td>"
81 "<td>Lines in multi line strings</td></tr>"
82 "<tr><td><b>Empty</b></td><td>Blank lines</td></tr>"
83 "<tr><td colspan=2><b>Comment Statistics:</b></td</tr>"
84 "<tr><td><b>C % L</b></td><td>Comments to lines ratio</td></tr>"
85 "<tr><td><b>C % S</b></td>"
86 "<td>Comments to source lines ratio</td></tr>"
87 "<tr><td><b>C + M % L</b></td>"
88 "<td>Comments plus multi line strings to lines ratio</td></tr>"
89 "</table>"
90 ))
71 91
72 def __resizeResultColumns(self): 92 def __resizeResultColumns(self):
73 """ 93 """
74 Private method to resize the list columns. 94 Private method to resize the list columns.
75 """ 95 """
84 @type str 104 @type str
85 @param values values to be displayed 105 @param values values to be displayed
86 @type dict 106 @type dict
87 """ 107 """
88 data = [self.__project.getRelativePath(filename)] 108 data = [self.__project.getRelativePath(filename)]
89 for key in ['loc', 'sloc', 'lloc', 'comments', 'multi', 'blank']: 109 for value in self.__getValues(values):
90 try: 110 try:
91 data.append("{0:5}".format(int(values[key]))) 111 data.append("{0:5}".format(int(value)))
92 except ValueError: 112 except ValueError:
93 data.append(values[key]) 113 data.append(value)
94 except KeyError:
95 data.append("")
96 data.append("{0:3.0%}".format( 114 data.append("{0:3.0%}".format(
97 values["comments"] / (float(values["loc"]) or 1))) 115 values["comments"] / (float(values["loc"]) or 1)))
98 data.append("{0:3.0%}".format( 116 data.append("{0:3.0%}".format(
99 values["comments"] / (float(values["sloc"]) or 1))) 117 values["comments"] / (float(values["sloc"]) or 1)))
100 data.append("{0:3.0%}".format( 118 data.append("{0:3.0%}".format(
111 @param filename name of the file 129 @param filename name of the file
112 @type str 130 @type str
113 @param message error message 131 @param message error message
114 @type str 132 @type str
115 """ 133 """
116 # TODO: implement this 134 itm = QTreeWidgetItem(self.resultList, [
135 "{0} ({1})".format(self.__project.getRelativePath(filename),
136 message)])
137 itm.setFirstColumnSpanned(True)
138 font = itm.font(0)
139 font.setItalic(True)
140 itm.setFont(0, font)
117 141
118 def prepare(self, fileList, project): 142 def prepare(self, fileList, project):
119 """ 143 """
120 Public method to prepare the dialog with a list of filenames. 144 Public method to prepare the dialog with a list of filenames.
121 145
167 # check for missing files 191 # check for missing files
168 for f in self.files[:]: 192 for f in self.files[:]:
169 if not os.path.exists(f): 193 if not os.path.exists(f):
170 self.files.remove(f) 194 self.files.remove(f)
171 195
196 self.__summary = {"files": 0}
197 for key in ['loc', 'sloc', 'lloc', 'comments', 'multi', 'blank']:
198 self.__summary[key] = 0
199
172 if len(self.files) > 0: 200 if len(self.files) > 0:
173 # disable updates of the list for speed 201 # disable updates of the list for speed
174 self.resultList.setUpdatesEnabled(False) 202 self.resultList.setUpdatesEnabled(False)
175 self.resultList.setSortingEnabled(False) 203 self.resultList.setSortingEnabled(False)
176 204
215 243
216 try: 244 try:
217 self.source = Utilities.readEncodedFile(self.filename)[0] 245 self.source = Utilities.readEncodedFile(self.filename)[0]
218 self.source = Utilities.normalizeCode(self.source) 246 self.source = Utilities.normalizeCode(self.source)
219 except (UnicodeError, IOError) as msg: 247 except (UnicodeError, IOError) as msg:
220 # TODO: adjust this 248 self.__createErrorItem(self.filename, str(msg).rstrip())
221 self.__createResultItem(
222 self.filename, 1,
223 "Error: {0}".format(str(msg)).rstrip())
224 self.progress += 1 249 self.progress += 1
225 # Continue with next file 250 # Continue with next file
226 self.rawMetrics() 251 self.rawMetrics()
227 return 252 return
228 253
249 274
250 try: 275 try:
251 source = Utilities.readEncodedFile(filename)[0] 276 source = Utilities.readEncodedFile(filename)[0]
252 source = Utilities.normalizeCode(source) 277 source = Utilities.normalizeCode(source)
253 except (UnicodeError, IOError) as msg: 278 except (UnicodeError, IOError) as msg:
254 # TODO: adjust this 279 self.__createErrorItem(filename, str(msg).rstrip())
255 self.__createResultItem(
256 filename, 1,
257 "Error: {0}".format(str(msg)).rstrip())
258 continue 280 continue
259 281
260 argumentsList.append((filename, source)) 282 argumentsList.append((filename, source))
261 283
262 # reset the progress bar to the checked files 284 # reset the progress bar to the checked files
274 self.checkProgress.setMaximum(1) 296 self.checkProgress.setMaximum(1)
275 self.checkProgress.setValue(1) 297 self.checkProgress.setValue(1)
276 self.__finish() 298 self.__finish()
277 299
278 def __processError(self, fn, msg): 300 def __processError(self, fn, msg):
279 # TODO: implement this 301 """
280 print("Error", fn, msg) 302 Private slot to process an error indication from the service.
303
304 @param fn filename of the file
305 @type str
306 @param msg error message
307 @type str
308 """
309 self.__createErrorItem(fn, msg)
281 310
282 def __processResult(self, fn, result): 311 def __processResult(self, fn, result):
283 """ 312 """
284 Private slot called after perfoming a code metrics calculation on one 313 Private slot called after perfoming a code metrics calculation on one
285 file. 314 file.
308 self.checkProgressLabel.setPath(fn) 337 self.checkProgressLabel.setPath(fn)
309 QApplication.processEvents() 338 QApplication.processEvents()
310 339
311 if not self.__batch: 340 if not self.__batch:
312 self.rawMetrics() 341 self.rawMetrics()
313 342
343 def __getValues(self, result):
344 """
345 Private method to extract the code metric values.
346
347 @param result result dict
348 @type dict
349 @return list of values suitable for display
350 @rtype list of str
351 """
352 v = []
353 for key in ['loc', 'sloc', 'lloc', 'comments', 'multi', 'blank']:
354 val = result.get(key, 0)
355 if val:
356 v.append(self.__locale.toString(val))
357 else:
358 v.append('')
359 self.__summary[key] += int(val)
360 self.__summary["files"] += 1
361 return v
362
314 def __finish(self): 363 def __finish(self):
315 """ 364 """
316 Private slot called when the action or the user pressed the button. 365 Private slot called when the action or the user pressed the button.
317 """ 366 """
318 if not self.__finished: 367 if not self.__finished:
319 self.__finished = True 368 self.__finished = True
320 369
321 # reenable updates of the list 370 # reenable updates of the list
322 self.resultList.setSortingEnabled(True) 371 self.resultList.setSortingEnabled(True)
323 self.resultList.setUpdatesEnabled(True) 372 self.resultList.setUpdatesEnabled(True)
373
374 self.__createSummary()
324 375
325 self.cancelled = True 376 self.cancelled = True
326 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) 377 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
327 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) 378 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
328 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) 379 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
336 else: 387 else:
337 self.resultList.header().setResizeMode(QHeaderView.Interactive) 388 self.resultList.header().setResizeMode(QHeaderView.Interactive)
338 389
339 self.checkProgress.setVisible(False) 390 self.checkProgress.setVisible(False)
340 self.checkProgressLabel.setVisible(False) 391 self.checkProgressLabel.setVisible(False)
392
393 def __createSummary(self):
394 """
395 Private method to create the code metrics summary.
396 """
397 self.__createSummaryItem(
398 self.tr("Files"), self.__locale.toString(self.__summary["files"]))
399 self.__createSummaryItem(
400 self.tr("LOC"), self.__locale.toString(self.__summary["loc"]))
401 self.__createSummaryItem(
402 self.tr("SLOC"), self.__locale.toString(self.__summary["sloc"]))
403 self.__createSummaryItem(
404 self.tr("LLOC"), self.__locale.toString(self.__summary["lloc"]))
405 self.__createSummaryItem(
406 self.tr("Comments"),
407 self.__locale.toString(self.__summary["comments"]))
408 self.__createSummaryItem(
409 self.tr("Multi"), self.__locale.toString(self.__summary["multi"]))
410 self.__createSummaryItem(
411 self.tr("Empty"), self.__locale.toString(self.__summary["blank"]))
412
413 self.summaryList.header().resizeSections(QHeaderView.ResizeToContents)
414 self.summaryList.header().setStretchLastSection(True)
415
416 def __createSummaryItem(self, col0, col1):
417 """
418 Private slot to create a new item in the summary list.
419
420 @param col0 string for column 0 (string)
421 @param col1 string for column 1 (string)
422 """
423 itm = QTreeWidgetItem(self.summaryList, [col0, col1])
424 itm.setTextAlignment(1, Qt.Alignment(Qt.AlignRight))
341 425
342 @pyqtSlot(QAbstractButton) 426 @pyqtSlot(QAbstractButton)
343 def on_buttonBox_clicked(self, button): 427 def on_buttonBox_clicked(self, button):
344 """ 428 """
345 Private slot called by a button of the button box clicked. 429 Private slot called by a button of the button box clicked.
377 [f for f in fileList if not fnmatch.fnmatch(f, filter)] 461 [f for f in fileList if not fnmatch.fnmatch(f, filter)]
378 462
379 self.resultList.clear() 463 self.resultList.clear()
380 self.cancelled = False 464 self.cancelled = False
381 self.start(fileList) 465 self.start(fileList)
382
383 def __showContextMenu(self, coord):
384 """
385 Private slot to show the context menu of the result list.
386
387 @param coord position of the mouse pointer
388 @type QPoint
389 """
390 if self.resultList.topLevelItemCount() > 0:
391 self.__menu.popup(self.mapToGlobal(coord))
392
393 def __resultCollapse(self):
394 """
395 Private slot to collapse all entries of the result list.
396 """
397 for index in range(self.resultList.topLevelItemCount()):
398 self.resultList.topLevelItem(index).setExpanded(False)
399
400 def __resultExpand(self):
401 """
402 Private slot to expand all entries of the result list.
403 """
404 for index in range(self.resultList.topLevelItemCount()):
405 self.resultList.topLevelItem(index).setExpanded(True)

eric ide

mercurial