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) |
|