28 class CyclomaticComplexityDialog(QDialog, Ui_CyclomaticComplexityDialog): |
33 class CyclomaticComplexityDialog(QDialog, Ui_CyclomaticComplexityDialog): |
29 """ |
34 """ |
30 Class implementing a dialog to show the cyclomatic complexity (McCabe |
35 Class implementing a dialog to show the cyclomatic complexity (McCabe |
31 complexity). |
36 complexity). |
32 """ |
37 """ |
|
38 |
33 FilePathRole = Qt.ItemDataRole.UserRole + 1 |
39 FilePathRole = Qt.ItemDataRole.UserRole + 1 |
34 LineNumberRole = Qt.ItemDataRole.UserRole + 2 |
40 LineNumberRole = Qt.ItemDataRole.UserRole + 2 |
35 |
41 |
36 def __init__(self, radonService, isSingle=False, parent=None): |
42 def __init__(self, radonService, isSingle=False, parent=None): |
37 """ |
43 """ |
38 Constructor |
44 Constructor |
39 |
45 |
40 @param radonService reference to the service |
46 @param radonService reference to the service |
41 @type RadonMetricsPlugin |
47 @type RadonMetricsPlugin |
42 @param isSingle flag indicating a single file dialog |
48 @param isSingle flag indicating a single file dialog |
43 @type bool |
49 @type bool |
44 @param parent reference to the parent widget |
50 @param parent reference to the parent widget |
45 @type QWidget |
51 @type QWidget |
46 """ |
52 """ |
47 super().__init__(parent) |
53 super().__init__(parent) |
48 self.setupUi(self) |
54 self.setupUi(self) |
49 self.setWindowFlags(Qt.WindowType.Window) |
55 self.setWindowFlags(Qt.WindowType.Window) |
50 |
56 |
51 self.buttonBox.button( |
57 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) |
52 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
58 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
53 self.buttonBox.button( |
59 |
54 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
55 |
|
56 self.resultList.headerItem().setText(self.resultList.columnCount(), "") |
60 self.resultList.headerItem().setText(self.resultList.columnCount(), "") |
57 |
61 |
58 self.rankComboBox.addItems(["A", "B", "C", "D", "E", "F"]) |
62 self.rankComboBox.addItems(["A", "B", "C", "D", "E", "F"]) |
59 self.rankComboBox.setCurrentIndex(self.rankComboBox.findText("D")) |
63 self.rankComboBox.setCurrentIndex(self.rankComboBox.findText("D")) |
60 self.__minimumRank = "D" |
64 self.__minimumRank = "D" |
61 |
65 |
62 self.radonService = radonService |
66 self.radonService = radonService |
63 self.radonService.complexityDone.connect(self.__processResult) |
67 self.radonService.complexityDone.connect(self.__processResult) |
64 self.radonService.error.connect(self.__processError) |
68 self.radonService.error.connect(self.__processError) |
65 self.radonService.batchFinished.connect(self.__batchFinished) |
69 self.radonService.batchFinished.connect(self.__batchFinished) |
66 |
70 |
67 self.__isSingle = isSingle |
71 self.__isSingle = isSingle |
68 self.cancelled = False |
72 self.cancelled = False |
69 |
73 |
70 self.__project = ericApp().getObject("Project") |
74 self.__project = ericApp().getObject("Project") |
71 self.__locale = QLocale() |
75 self.__locale = QLocale() |
72 self.__finished = True |
76 self.__finished = True |
73 self.__errorItem = None |
77 self.__errorItem = None |
74 |
78 |
75 self.__fileList = [] |
79 self.__fileList = [] |
76 self.filterFrame.setVisible(False) |
80 self.filterFrame.setVisible(False) |
77 |
81 |
78 self.explanationLabel.setText(self.tr( |
82 self.explanationLabel.setText( |
79 "<table>" |
83 self.tr( |
80 "<tr><td colspan=3><b>Ranking:</b></td></tr>" |
84 "<table>" |
81 "<tr><td><b>A</b></td><td>1 - 5</td>" |
85 "<tr><td colspan=3><b>Ranking:</b></td></tr>" |
82 "<td>(low risk - simple block)</td></tr>" |
86 "<tr><td><b>A</b></td><td>1 - 5</td>" |
83 "<tr><td><b>B</b></td><td>6 - 10</td>" |
87 "<td>(low risk - simple block)</td></tr>" |
84 "<td>(low risk - well structured and stable block)</td></tr>" |
88 "<tr><td><b>B</b></td><td>6 - 10</td>" |
85 "<tr><td><b>C</b></td><td>11 - 20</td>" |
89 "<td>(low risk - well structured and stable block)</td></tr>" |
86 "<td>(moderate risk - slightly complex block)</td></tr>" |
90 "<tr><td><b>C</b></td><td>11 - 20</td>" |
87 "<tr><td><b>D</b></td><td>21 - 30</td>" |
91 "<td>(moderate risk - slightly complex block)</td></tr>" |
88 "<td>(more than moderate risk - more complex block)</td></tr>" |
92 "<tr><td><b>D</b></td><td>21 - 30</td>" |
89 "<tr><td><b>E</b></td><td>31 - 40</td>" |
93 "<td>(more than moderate risk - more complex block)</td></tr>" |
90 "<td>(high risk - complex block, alarming)</td></tr>" |
94 "<tr><td><b>E</b></td><td>31 - 40</td>" |
91 "<tr><td><b>F</b></td><td>> 40</td>" |
95 "<td>(high risk - complex block, alarming)</td></tr>" |
92 "<td>(very high risk - error-prone, unstable block)</td></tr>" |
96 "<tr><td><b>F</b></td><td>> 40</td>" |
93 "</table>" |
97 "<td>(very high risk - error-prone, unstable block)</td></tr>" |
94 )) |
98 "</table>" |
95 self.typeLabel.setText(self.tr( |
99 ) |
96 "<table>" |
100 ) |
97 "<tr><td colspan=2><b>Type:</b></td></tr>" |
101 self.typeLabel.setText( |
98 "<tr><td><b>C</b></td><td>Class</td></tr>" |
102 self.tr( |
99 "<tr><td><b>F</b></td><td>Function</td></tr>" |
103 "<table>" |
100 "<tr><td><b>M</b></td><td>Method</td></tr>" |
104 "<tr><td colspan=2><b>Type:</b></td></tr>" |
101 "</table>" |
105 "<tr><td><b>C</b></td><td>Class</td></tr>" |
102 )) |
106 "<tr><td><b>F</b></td><td>Function</td></tr>" |
103 |
107 "<tr><td><b>M</b></td><td>Method</td></tr>" |
|
108 "</table>" |
|
109 ) |
|
110 ) |
|
111 |
104 self.__mappedType = { |
112 self.__mappedType = { |
105 "class": "C", |
113 "class": "C", |
106 "function": "F", |
114 "function": "F", |
107 "method": "M", |
115 "method": "M", |
108 } |
116 } |
109 |
117 |
110 try: |
118 try: |
111 usesDarkPalette = ericApp().usesDarkPalette() |
119 usesDarkPalette = ericApp().usesDarkPalette() |
112 except AttributeError: |
120 except AttributeError: |
113 from PyQt6.QtGui import QPalette |
121 from PyQt6.QtGui import QPalette |
|
122 |
114 palette = ericApp().palette() |
123 palette = ericApp().palette() |
115 lightness = palette.color(QPalette.Window).lightness() |
124 lightness = palette.color(QPalette.Window).lightness() |
116 usesDarkPalette = lightness <= 128 |
125 usesDarkPalette = lightness <= 128 |
117 if usesDarkPalette: |
126 if usesDarkPalette: |
118 self.__typeColors = { |
127 self.__typeColors = { |
140 "C": QColor("#ffff00"), |
149 "C": QColor("#ffff00"), |
141 "D": QColor("#ffff00"), |
150 "D": QColor("#ffff00"), |
142 "E": QColor("#ff0000"), |
151 "E": QColor("#ff0000"), |
143 "F": QColor("#ff0000"), |
152 "F": QColor("#ff0000"), |
144 } |
153 } |
145 |
154 |
146 self.__menu = QMenu(self) |
155 self.__menu = QMenu(self) |
147 self.__menu.addAction(self.tr("Collapse all"), |
156 self.__menu.addAction(self.tr("Collapse all"), self.__resultCollapse) |
148 self.__resultCollapse) |
|
149 self.__menu.addAction(self.tr("Expand all"), self.__resultExpand) |
157 self.__menu.addAction(self.tr("Expand all"), self.__resultExpand) |
150 self.resultList.setContextMenuPolicy( |
158 self.resultList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
151 Qt.ContextMenuPolicy.CustomContextMenu) |
159 self.resultList.customContextMenuRequested.connect(self.__showContextMenu) |
152 self.resultList.customContextMenuRequested.connect( |
160 |
153 self.__showContextMenu) |
|
154 |
|
155 def __resizeResultColumns(self): |
161 def __resizeResultColumns(self): |
156 """ |
162 """ |
157 Private method to resize the list columns. |
163 Private method to resize the list columns. |
158 """ |
164 """ |
159 self.resultList.header().resizeSections( |
165 self.resultList.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) |
160 QHeaderView.ResizeMode.ResizeToContents) |
|
161 self.resultList.header().setStretchLastSection(True) |
166 self.resultList.header().setStretchLastSection(True) |
162 |
167 |
163 def __createFileItem(self, filename): |
168 def __createFileItem(self, filename): |
164 """ |
169 """ |
165 Private method to create a new file item in the result list. |
170 Private method to create a new file item in the result list. |
166 |
171 |
167 @param filename name of the file |
172 @param filename name of the file |
168 @type str |
173 @type str |
169 @return reference to the created item |
174 @return reference to the created item |
170 @rtype QTreeWidgetItem |
175 @rtype QTreeWidgetItem |
171 """ |
176 """ |
172 itm = QTreeWidgetItem( |
177 itm = QTreeWidgetItem([self.__project.getRelativePath(filename)]) |
173 [self.__project.getRelativePath(filename)]) |
|
174 itm.setData(0, self.FilePathRole, filename) |
178 itm.setData(0, self.FilePathRole, filename) |
175 itm.setData(0, self.LineNumberRole, 1) |
179 itm.setData(0, self.LineNumberRole, 1) |
176 return itm |
180 return itm |
177 |
181 |
178 def __createResultItem(self, parentItem, values): |
182 def __createResultItem(self, parentItem, values): |
179 """ |
183 """ |
180 Private slot to create a new item in the result list. |
184 Private slot to create a new item in the result list. |
181 |
185 |
182 @param parentItem reference to the parent item |
186 @param parentItem reference to the parent item |
183 @type QTreeWidgetItem |
187 @type QTreeWidgetItem |
184 @param values values to be displayed |
188 @param values values to be displayed |
185 @type dict |
189 @type dict |
186 """ |
190 """ |
187 if values["rank"] >= self.__minimumRank: |
191 if values["rank"] >= self.__minimumRank: |
188 itm = QTreeWidgetItem(parentItem, [ |
192 itm = QTreeWidgetItem( |
189 self.__mappedType[values["type"]], |
193 parentItem, |
190 values["fullname"], |
194 [ |
191 "{0:3}".format(values["complexity"]), |
195 self.__mappedType[values["type"]], |
192 values["rank"], |
196 values["fullname"], |
193 "{0:6}".format(values["lineno"]), |
197 "{0:3}".format(values["complexity"]), |
194 ]) |
198 values["rank"], |
|
199 "{0:6}".format(values["lineno"]), |
|
200 ], |
|
201 ) |
195 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) |
202 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) |
196 itm.setTextAlignment(3, Qt.AlignmentFlag.AlignHCenter) |
203 itm.setTextAlignment(3, Qt.AlignmentFlag.AlignHCenter) |
197 itm.setTextAlignment(4, Qt.AlignmentFlag.AlignRight) |
204 itm.setTextAlignment(4, Qt.AlignmentFlag.AlignRight) |
198 if values["rank"] in self.__rankColors: |
205 if values["rank"] in self.__rankColors: |
199 itm.setBackground(3, self.__rankColors[values["rank"]]) |
206 itm.setBackground(3, self.__rankColors[values["rank"]]) |
200 if values["type"] in self.__typeColors: |
207 if values["type"] in self.__typeColors: |
201 itm.setForeground(0, self.__typeColors[values["type"]]) |
208 itm.setForeground(0, self.__typeColors[values["type"]]) |
202 itm.setData(0, self.FilePathRole, |
209 itm.setData(0, self.FilePathRole, parentItem.data(0, self.FilePathRole)) |
203 parentItem.data(0, self.FilePathRole)) |
|
204 itm.setData(0, self.LineNumberRole, values["lineno"]) |
210 itm.setData(0, self.LineNumberRole, values["lineno"]) |
205 |
211 |
206 if "methods" in values: |
212 if "methods" in values: |
207 for method in values["methods"]: |
213 for method in values["methods"]: |
208 self.__createResultItem(parentItem, method) |
214 self.__createResultItem(parentItem, method) |
209 |
215 |
210 if "closures" in values and values["closures"]: |
216 if "closures" in values and values["closures"]: |
211 for closure in values["closures"]: |
217 for closure in values["closures"]: |
212 self.__createResultItem(parentItem, closure) |
218 self.__createResultItem(parentItem, closure) |
213 |
219 |
214 def __createErrorItem(self, filename, message): |
220 def __createErrorItem(self, filename, message): |
215 """ |
221 """ |
216 Private slot to create a new error item in the result list. |
222 Private slot to create a new error item in the result list. |
217 |
223 |
218 @param filename name of the file |
224 @param filename name of the file |
219 @type str |
225 @type str |
220 @param message error message |
226 @param message error message |
221 @type str |
227 @type str |
222 """ |
228 """ |
223 if self.__errorItem is None: |
229 if self.__errorItem is None: |
224 self.__errorItem = QTreeWidgetItem(self.resultList, [ |
230 self.__errorItem = QTreeWidgetItem(self.resultList, [self.tr("Errors")]) |
225 self.tr("Errors")]) |
|
226 self.__errorItem.setExpanded(True) |
231 self.__errorItem.setExpanded(True) |
227 self.__errorItem.setForeground(0, Qt.GlobalColor.red) |
232 self.__errorItem.setForeground(0, Qt.GlobalColor.red) |
228 |
233 |
229 msg = "{0} ({1})".format(self.__project.getRelativePath(filename), |
234 msg = "{0} ({1})".format(self.__project.getRelativePath(filename), message) |
230 message) |
|
231 if not self.resultList.findItems(msg, Qt.MatchFlag.MatchExactly): |
235 if not self.resultList.findItems(msg, Qt.MatchFlag.MatchExactly): |
232 itm = QTreeWidgetItem(self.__errorItem, [msg]) |
236 itm = QTreeWidgetItem(self.__errorItem, [msg]) |
233 itm.setForeground(0, Qt.GlobalColor.red) |
237 itm.setForeground(0, Qt.GlobalColor.red) |
234 itm.setFirstColumnSpanned(True) |
238 itm.setFirstColumnSpanned(True) |
235 |
239 |
236 def prepare(self, fileList, project): |
240 def prepare(self, fileList, project): |
237 """ |
241 """ |
238 Public method to prepare the dialog with a list of filenames. |
242 Public method to prepare the dialog with a list of filenames. |
239 |
243 |
240 @param fileList list of filenames |
244 @param fileList list of filenames |
241 @type list of str |
245 @type list of str |
242 @param project reference to the project object |
246 @param project reference to the project object |
243 @type Project |
247 @type Project |
244 """ |
248 """ |
245 self.__fileList = fileList[:] |
249 self.__fileList = fileList[:] |
246 self.__project = project |
250 self.__project = project |
247 |
251 |
248 self.buttonBox.button( |
252 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) |
249 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
253 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
250 self.buttonBox.button( |
254 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) |
251 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
255 |
252 self.buttonBox.button( |
|
253 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
254 |
|
255 self.filterFrame.setVisible(True) |
256 self.filterFrame.setVisible(True) |
256 |
257 |
257 self.__data = self.__project.getData( |
258 self.__data = self.__project.getData("OTHERTOOLSPARMS", "RadonCodeMetrics") |
258 "OTHERTOOLSPARMS", "RadonCodeMetrics") |
|
259 if self.__data is None or "ExcludeFiles" not in self.__data: |
259 if self.__data is None or "ExcludeFiles" not in self.__data: |
260 self.__data = {"ExcludeFiles": ""} |
260 self.__data = {"ExcludeFiles": ""} |
261 if "MinimumRank" not in self.__data: |
261 if "MinimumRank" not in self.__data: |
262 self.__data["MinimumRank"] = "D" |
262 self.__data["MinimumRank"] = "D" |
263 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) |
263 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) |
264 self.__minimumRank = self.__data["MinimumRank"] |
264 self.__minimumRank = self.__data["MinimumRank"] |
265 self.rankComboBox.setCurrentIndex(self.rankComboBox.findText( |
265 self.rankComboBox.setCurrentIndex( |
266 self.__minimumRank)) |
266 self.rankComboBox.findText(self.__minimumRank) |
267 |
267 ) |
|
268 |
268 def start(self, fn, minRank="D"): |
269 def start(self, fn, minRank="D"): |
269 """ |
270 """ |
270 Public slot to start the cyclomatic complexity determination. |
271 Public slot to start the cyclomatic complexity determination. |
271 |
272 |
272 @param fn file or list of files or directory to show |
273 @param fn file or list of files or directory to show |
273 the cyclomatic complexity for |
274 the cyclomatic complexity for |
274 @type str or list of str |
275 @type str or list of str |
275 @param minRank minimum rank of entries to be shown |
276 @param minRank minimum rank of entries to be shown |
276 @type str (one character out of A - F) |
277 @type str (one character out of A - F) |
278 self.cancelled = False |
279 self.cancelled = False |
279 self.__errorItem = None |
280 self.__errorItem = None |
280 self.resultList.clear() |
281 self.resultList.clear() |
281 self.summaryLabel.clear() |
282 self.summaryLabel.clear() |
282 QApplication.processEvents() |
283 QApplication.processEvents() |
283 |
284 |
284 self.buttonBox.button( |
285 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) |
285 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
286 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True) |
286 self.buttonBox.button( |
287 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
287 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) |
|
288 self.buttonBox.button( |
|
289 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
290 self.rankComboBox.setEnabled(False) |
288 self.rankComboBox.setEnabled(False) |
291 QApplication.processEvents() |
289 QApplication.processEvents() |
292 |
290 |
293 if isinstance(fn, list): |
291 if isinstance(fn, list): |
294 self.files = fn |
292 self.files = fn |
295 elif os.path.isdir(fn): |
293 elif os.path.isdir(fn): |
296 self.files = [] |
294 self.files = [] |
297 extensions = set(Preferences.getPython("Python3Extensions")) |
295 extensions = set(Preferences.getPython("Python3Extensions")) |
298 for ext in extensions: |
296 for ext in extensions: |
299 self.files.extend( |
297 self.files.extend(Utilities.direntries(fn, True, "*{0}".format(ext), 0)) |
300 Utilities.direntries(fn, True, '*{0}'.format(ext), 0)) |
|
301 else: |
298 else: |
302 self.files = [fn] |
299 self.files = [fn] |
303 self.files.sort() |
300 self.files.sort() |
304 # check for missing files |
301 # check for missing files |
305 for f in self.files[:]: |
302 for f in self.files[:]: |
306 if not os.path.exists(f): |
303 if not os.path.exists(f): |
307 self.files.remove(f) |
304 self.files.remove(f) |
308 if self.__isSingle: |
305 if self.__isSingle: |
309 self.__fileList = self.files[:] |
306 self.__fileList = self.files[:] |
310 |
307 |
311 self.__summary = { |
308 self.__summary = { |
312 "A": 0, |
309 "A": 0, |
313 "B": 0, |
310 "B": 0, |
314 "C": 0, |
311 "C": 0, |
315 "D": 0, |
312 "D": 0, |
316 "E": 0, |
313 "E": 0, |
317 "F": 0, |
314 "F": 0, |
318 } |
315 } |
319 self.__ccSum = 0 |
316 self.__ccSum = 0 |
320 self.__ccCount = 0 |
317 self.__ccCount = 0 |
321 |
318 |
322 self.__minimumRank = self.rankComboBox.currentText() |
319 self.__minimumRank = self.rankComboBox.currentText() |
323 |
320 |
324 if len(self.files) > 0: |
321 if len(self.files) > 0: |
325 # disable updates of the list for speed |
322 # disable updates of the list for speed |
326 self.resultList.setUpdatesEnabled(False) |
323 self.resultList.setUpdatesEnabled(False) |
327 self.resultList.setSortingEnabled(False) |
324 self.resultList.setSortingEnabled(False) |
328 |
325 |
329 self.checkProgress.setMaximum(len(self.files)) |
326 self.checkProgress.setMaximum(len(self.files)) |
330 self.checkProgress.setVisible(len(self.files) > 1) |
327 self.checkProgress.setVisible(len(self.files) > 1) |
331 QApplication.processEvents() |
328 QApplication.processEvents() |
332 |
329 |
333 # now go through all the files |
330 # now go through all the files |
334 self.progress = 0 |
331 self.progress = 0 |
335 if len(self.files) == 1: |
332 if len(self.files) == 1: |
336 self.__batch = False |
333 self.__batch = False |
337 self.cyclomaticComplexity() |
334 self.cyclomaticComplexity() |
338 else: |
335 else: |
339 self.__batch = True |
336 self.__batch = True |
340 self.cyclomaticComplexityBatch() |
337 self.cyclomaticComplexityBatch() |
341 |
338 |
342 def cyclomaticComplexity(self, codestring=''): |
339 def cyclomaticComplexity(self, codestring=""): |
343 """ |
340 """ |
344 Public method to start a cyclomatic complexity calculation for one |
341 Public method to start a cyclomatic complexity calculation for one |
345 Python file. |
342 Python file. |
346 |
343 |
347 The results are reported to the __processResult slot. |
344 The results are reported to the __processResult slot. |
348 |
345 |
349 @param codestring optional sourcestring |
346 @param codestring optional sourcestring |
350 @type str |
347 @type str |
351 """ |
348 """ |
352 if not self.files: |
349 if not self.files: |
353 self.checkProgress.setMaximum(1) |
350 self.checkProgress.setMaximum(1) |
354 self.checkProgress.setValue(1) |
351 self.checkProgress.setValue(1) |
355 self.__finish() |
352 self.__finish() |
356 return |
353 return |
357 |
354 |
358 self.filename = self.files.pop(0) |
355 self.filename = self.files.pop(0) |
359 self.checkProgress.setValue(self.progress) |
356 self.checkProgress.setValue(self.progress) |
360 QApplication.processEvents() |
357 QApplication.processEvents() |
361 |
358 |
362 if self.cancelled: |
359 if self.cancelled: |
363 return |
360 return |
364 |
361 |
365 try: |
362 try: |
366 self.source = Utilities.readEncodedFile(self.filename)[0] |
363 self.source = Utilities.readEncodedFile(self.filename)[0] |
367 self.source = Utilities.normalizeCode(self.source) |
364 self.source = Utilities.normalizeCode(self.source) |
368 except (UnicodeError, OSError) as msg: |
365 except (UnicodeError, OSError) as msg: |
369 self.__createErrorItem(self.filename, str(msg).rstrip()) |
366 self.__createErrorItem(self.filename, str(msg).rstrip()) |
371 # Continue with next file |
368 # Continue with next file |
372 self.cyclomaticComplexity() |
369 self.cyclomaticComplexity() |
373 return |
370 return |
374 |
371 |
375 self.__finished = False |
372 self.__finished = False |
376 self.radonService.cyclomaticComplexity( |
373 self.radonService.cyclomaticComplexity(None, self.filename, self.source) |
377 None, self.filename, self.source) |
|
378 |
374 |
379 def cyclomaticComplexityBatch(self): |
375 def cyclomaticComplexityBatch(self): |
380 """ |
376 """ |
381 Public method to start a cyclomatic complexity calculation batch job. |
377 Public method to start a cyclomatic complexity calculation batch job. |
382 |
378 |
383 The results are reported to the __processResult slot. |
379 The results are reported to the __processResult slot. |
384 """ |
380 """ |
385 self.__lastFileItem = None |
381 self.__lastFileItem = None |
386 |
382 |
387 argumentsList = [] |
383 argumentsList = [] |
388 for progress, filename in enumerate(self.files, start=1): |
384 for progress, filename in enumerate(self.files, start=1): |
389 self.checkProgress.setValue(progress) |
385 self.checkProgress.setValue(progress) |
390 QApplication.processEvents() |
386 QApplication.processEvents() |
391 |
387 |
392 try: |
388 try: |
393 source = Utilities.readEncodedFile(filename)[0] |
389 source = Utilities.readEncodedFile(filename)[0] |
394 source = Utilities.normalizeCode(source) |
390 source = Utilities.normalizeCode(source) |
395 except (UnicodeError, OSError) as msg: |
391 except (UnicodeError, OSError) as msg: |
396 self.__createErrorItem(filename, str(msg).rstrip()) |
392 self.__createErrorItem(filename, str(msg).rstrip()) |
397 continue |
393 continue |
398 |
394 |
399 argumentsList.append((filename, source)) |
395 argumentsList.append((filename, source)) |
400 |
396 |
401 # reset the progress bar to the checked files |
397 # reset the progress bar to the checked files |
402 self.checkProgress.setValue(self.progress) |
398 self.checkProgress.setValue(self.progress) |
403 QApplication.processEvents() |
399 QApplication.processEvents() |
404 |
400 |
405 self.__finished = False |
401 self.__finished = False |
406 self.radonService.cyclomaticComplexityBatch(argumentsList) |
402 self.radonService.cyclomaticComplexityBatch(argumentsList) |
407 |
403 |
408 def __batchFinished(self, type_): |
404 def __batchFinished(self, type_): |
409 """ |
405 """ |
410 Private slot handling the completion of a batch job. |
406 Private slot handling the completion of a batch job. |
411 |
407 |
412 @param type_ type of the calculated metrics |
408 @param type_ type of the calculated metrics |
413 @type str, one of ["raw", "mi", "cc"] |
409 @type str, one of ["raw", "mi", "cc"] |
414 """ |
410 """ |
415 if type_ == "cc": |
411 if type_ == "cc": |
416 self.checkProgress.setMaximum(1) |
412 self.checkProgress.setMaximum(1) |
417 self.checkProgress.setValue(1) |
413 self.checkProgress.setValue(1) |
418 self.__finish() |
414 self.__finish() |
419 |
415 |
420 def __processError(self, type_, fn, msg): |
416 def __processError(self, type_, fn, msg): |
421 """ |
417 """ |
422 Private slot to process an error indication from the service. |
418 Private slot to process an error indication from the service. |
423 |
419 |
424 @param type_ type of the calculated metrics |
420 @param type_ type of the calculated metrics |
425 @type str, one of ["raw", "mi", "cc"] |
421 @type str, one of ["raw", "mi", "cc"] |
426 @param fn filename of the file |
422 @param fn filename of the file |
427 @type str |
423 @type str |
428 @param msg error message |
424 @param msg error message |
429 @type str |
425 @type str |
430 """ |
426 """ |
431 if type_ == "cc": |
427 if type_ == "cc": |
432 self.__createErrorItem(fn, msg) |
428 self.__createErrorItem(fn, msg) |
433 |
429 |
434 def __processResult(self, fn, result): |
430 def __processResult(self, fn, result): |
435 """ |
431 """ |
436 Private slot called after perfoming a cyclomatic complexity calculation |
432 Private slot called after perfoming a cyclomatic complexity calculation |
437 on one file. |
433 on one file. |
438 |
434 |
439 @param fn filename of the file |
435 @param fn filename of the file |
440 @type str |
436 @type str |
441 @param result result dict |
437 @param result result dict |
442 @type dict |
438 @type dict |
443 """ |
439 """ |
444 if self.__finished: |
440 if self.__finished: |
445 return |
441 return |
446 |
442 |
447 # Check if it's the requested file, otherwise ignore signal if not |
443 # Check if it's the requested file, otherwise ignore signal if not |
448 # in batch mode |
444 # in batch mode |
449 if not self.__batch and fn != self.filename: |
445 if not self.__batch and fn != self.filename: |
450 return |
446 return |
451 |
447 |
452 QApplication.processEvents() |
448 QApplication.processEvents() |
453 |
449 |
454 if "error" in result: |
450 if "error" in result: |
455 self.__createErrorItem(fn, result["error"]) |
451 self.__createErrorItem(fn, result["error"]) |
456 else: |
452 else: |
457 if result["result"]: |
453 if result["result"]: |
458 fitm = self.__createFileItem(fn) |
454 fitm = self.__createFileItem(fn) |
460 self.__createResultItem(fitm, resultDict) |
456 self.__createResultItem(fitm, resultDict) |
461 if fitm.childCount() > 0: |
457 if fitm.childCount() > 0: |
462 self.resultList.addTopLevelItem(fitm) |
458 self.resultList.addTopLevelItem(fitm) |
463 fitm.setExpanded(True) |
459 fitm.setExpanded(True) |
464 fitm.setFirstColumnSpanned(True) |
460 fitm.setFirstColumnSpanned(True) |
465 |
461 |
466 self.__ccCount += result["count"] |
462 self.__ccCount += result["count"] |
467 self.__ccSum += result["total_cc"] |
463 self.__ccSum += result["total_cc"] |
468 for rank in result["summary"]: |
464 for rank in result["summary"]: |
469 self.__summary[rank] += result["summary"][rank] |
465 self.__summary[rank] += result["summary"][rank] |
470 |
466 |
471 self.progress += 1 |
467 self.progress += 1 |
472 |
468 |
473 self.checkProgress.setValue(self.progress) |
469 self.checkProgress.setValue(self.progress) |
474 QApplication.processEvents() |
470 QApplication.processEvents() |
475 |
471 |
476 if not self.__batch: |
472 if not self.__batch: |
477 self.cyclomaticComplexity() |
473 self.cyclomaticComplexity() |
478 |
474 |
479 def __finish(self): |
475 def __finish(self): |
480 """ |
476 """ |
481 Private slot called when the action or the user pressed the button. |
477 Private slot called when the action or the user pressed the button. |
482 """ |
478 """ |
483 from radon.complexity import cc_rank |
479 from radon.complexity import cc_rank |
484 |
480 |
485 if not self.__finished: |
481 if not self.__finished: |
486 self.__finished = True |
482 self.__finished = True |
487 |
483 |
488 # re-enable updates of the list |
484 # re-enable updates of the list |
489 self.resultList.setSortingEnabled(True) |
485 self.resultList.setSortingEnabled(True) |
490 self.resultList.sortItems(0, Qt.SortOrder.AscendingOrder) |
486 self.resultList.sortItems(0, Qt.SortOrder.AscendingOrder) |
491 self.resultList.sortItems(1, Qt.SortOrder.AscendingOrder) |
487 self.resultList.sortItems(1, Qt.SortOrder.AscendingOrder) |
492 self.resultList.setUpdatesEnabled(True) |
488 self.resultList.setUpdatesEnabled(True) |
493 |
489 |
494 self.cancelled = True |
490 self.cancelled = True |
495 self.buttonBox.button( |
491 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled( |
496 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
492 True |
497 self.buttonBox.button( |
493 ) |
498 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
494 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled( |
499 self.buttonBox.button( |
495 False |
500 QDialogButtonBox.StandardButton.Close).setDefault(True) |
496 ) |
|
497 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault( |
|
498 True |
|
499 ) |
501 self.rankComboBox.setEnabled(True) |
500 self.rankComboBox.setEnabled(True) |
502 |
501 |
503 self.resultList.header().resizeSections( |
502 self.resultList.header().resizeSections( |
504 QHeaderView.ResizeMode.ResizeToContents) |
503 QHeaderView.ResizeMode.ResizeToContents |
|
504 ) |
505 self.resultList.header().setStretchLastSection(True) |
505 self.resultList.header().setStretchLastSection(True) |
506 self.resultList.header().setSectionResizeMode( |
506 self.resultList.header().setSectionResizeMode( |
507 QHeaderView.ResizeMode.Interactive) |
507 QHeaderView.ResizeMode.Interactive |
508 |
508 ) |
|
509 |
509 averageCC = float(self.__ccSum) / (self.__ccCount or 1) |
510 averageCC = float(self.__ccSum) / (self.__ccCount or 1) |
510 |
511 |
511 self.summaryLabel.setText(self.tr( |
512 self.summaryLabel.setText( |
512 "<b>Summary:</b><br/>" |
513 self.tr( |
513 "{0} blocks (classes, functions, methods) analyzed.<br/>" |
514 "<b>Summary:</b><br/>" |
514 "Average complexity: {7} ({8})" |
515 "{0} blocks (classes, functions, methods) analyzed.<br/>" |
515 "<table>" |
516 "Average complexity: {7} ({8})" |
516 "<tr><td width=30><b>A</b></td>" |
517 "<table>" |
517 "<td align='right'>{1} blocks</td></tr>" |
518 "<tr><td width=30><b>A</b></td>" |
518 "<tr><td width=30><b>B</b></td>" |
519 "<td align='right'>{1} blocks</td></tr>" |
519 "<td align='right'>{2} blocks</td></tr>" |
520 "<tr><td width=30><b>B</b></td>" |
520 "<tr><td width=30><b>C</b></td>" |
521 "<td align='right'>{2} blocks</td></tr>" |
521 "<td align='right'>{3} blocks</td></tr>" |
522 "<tr><td width=30><b>C</b></td>" |
522 "<tr><td width=30><b>D</b></td>" |
523 "<td align='right'>{3} blocks</td></tr>" |
523 "<td align='right'>{4} blocks</td></tr>" |
524 "<tr><td width=30><b>D</b></td>" |
524 "<tr><td width=30><b>E</b></td>" |
525 "<td align='right'>{4} blocks</td></tr>" |
525 "<td align='right'>{5} blocks</td></tr>" |
526 "<tr><td width=30><b>E</b></td>" |
526 "<tr><td width=30><b>F</b></td>" |
527 "<td align='right'>{5} blocks</td></tr>" |
527 "<td align='right'>{6} blocks</td></tr>" |
528 "<tr><td width=30><b>F</b></td>" |
528 "</table>" |
529 "<td align='right'>{6} blocks</td></tr>" |
529 ).format( |
530 "</table>" |
530 self.__locale.toString(self.__ccCount), |
531 ).format( |
531 self.__locale.toString(self.__summary["A"]), |
532 self.__locale.toString(self.__ccCount), |
532 self.__locale.toString(self.__summary["B"]), |
533 self.__locale.toString(self.__summary["A"]), |
533 self.__locale.toString(self.__summary["C"]), |
534 self.__locale.toString(self.__summary["B"]), |
534 self.__locale.toString(self.__summary["D"]), |
535 self.__locale.toString(self.__summary["C"]), |
535 self.__locale.toString(self.__summary["E"]), |
536 self.__locale.toString(self.__summary["D"]), |
536 self.__locale.toString(self.__summary["F"]), |
537 self.__locale.toString(self.__summary["E"]), |
537 cc_rank(averageCC), |
538 self.__locale.toString(self.__summary["F"]), |
538 self.__locale.toString(averageCC, "f", 1) |
539 cc_rank(averageCC), |
539 )) |
540 self.__locale.toString(averageCC, "f", 1), |
540 |
541 ) |
|
542 ) |
|
543 |
541 self.checkProgress.setVisible(False) |
544 self.checkProgress.setVisible(False) |
542 |
545 |
543 @pyqtSlot(QAbstractButton) |
546 @pyqtSlot(QAbstractButton) |
544 def on_buttonBox_clicked(self, button): |
547 def on_buttonBox_clicked(self, button): |
545 """ |
548 """ |
546 Private slot called by a button of the button box clicked. |
549 Private slot called by a button of the button box clicked. |
547 |
550 |
548 @param button button that was clicked |
551 @param button button that was clicked |
549 @type QAbstractButton |
552 @type QAbstractButton |
550 """ |
553 """ |
551 if button == self.buttonBox.button( |
554 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): |
552 QDialogButtonBox.StandardButton.Close |
|
553 ): |
|
554 self.close() |
555 self.close() |
555 elif button == self.buttonBox.button( |
556 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): |
556 QDialogButtonBox.StandardButton.Cancel |
|
557 ): |
|
558 if self.__batch: |
557 if self.__batch: |
559 self.radonService.cancelComplexityBatch() |
558 self.radonService.cancelComplexityBatch() |
560 QTimer.singleShot(1000, self.__finish) |
559 QTimer.singleShot(1000, self.__finish) |
561 else: |
560 else: |
562 self.__finish() |
561 self.__finish() |
563 |
562 |
564 @pyqtSlot() |
563 @pyqtSlot() |
565 def on_startButton_clicked(self): |
564 def on_startButton_clicked(self): |
566 """ |
565 """ |
567 Private slot to start a cyclomatic complexity run. |
566 Private slot to start a cyclomatic complexity run. |
568 """ |
567 """ |
569 fileList = self.__fileList[:] |
568 fileList = self.__fileList[:] |
570 dataChanged = False |
569 dataChanged = False |
571 |
570 |
572 filterString = self.excludeFilesEdit.text() |
571 filterString = self.excludeFilesEdit.text() |
573 if ( |
572 if ( |
574 "ExcludeFiles" not in self.__data or |
573 "ExcludeFiles" not in self.__data |
575 filterString != self.__data["ExcludeFiles"] |
574 or filterString != self.__data["ExcludeFiles"] |
576 ): |
575 ): |
577 self.__data["ExcludeFiles"] = filterString |
576 self.__data["ExcludeFiles"] = filterString |
578 dataChanged = True |
577 dataChanged = True |
579 filterList = [f.strip() for f in filterString.split(",") |
578 filterList = [f.strip() for f in filterString.split(",") if f.strip()] |
580 if f.strip()] |
|
581 if filterList: |
579 if filterList: |
582 for fileFilter in filterList: |
580 for fileFilter in filterList: |
583 fileList = [f for f in fileList |
581 fileList = [f for f in fileList if not fnmatch.fnmatch(f, fileFilter)] |
584 if not fnmatch.fnmatch(f, fileFilter)] |
582 |
585 |
|
586 minimumRank = self.rankComboBox.currentText() |
583 minimumRank = self.rankComboBox.currentText() |
587 if ( |
584 if ( |
588 "MinimumRank" not in self.__data or |
585 "MinimumRank" not in self.__data |
589 minimumRank != self.__data["MinimumRank"] |
586 or minimumRank != self.__data["MinimumRank"] |
590 ): |
587 ): |
591 self.__data["MinimumRank"] = minimumRank |
588 self.__data["MinimumRank"] = minimumRank |
592 dataChanged = True |
589 dataChanged = True |
593 |
590 |
594 if dataChanged: |
591 if dataChanged: |
595 self.__project.setData( |
592 self.__project.setData("OTHERTOOLSPARMS", "RadonCodeMetrics", self.__data) |
596 "OTHERTOOLSPARMS", "RadonCodeMetrics", self.__data) |
593 |
597 |
|
598 self.start(fileList) |
594 self.start(fileList) |
599 |
595 |
600 def __showContextMenu(self, coord): |
596 def __showContextMenu(self, coord): |
601 """ |
597 """ |
602 Private slot to show the context menu of the resultlist. |
598 Private slot to show the context menu of the resultlist. |
603 |
599 |
604 @param coord the position of the mouse pointer (QPoint) |
600 @param coord the position of the mouse pointer (QPoint) |
605 """ |
601 """ |
606 if self.resultList.topLevelItemCount() > 0: |
602 if self.resultList.topLevelItemCount() > 0: |
607 self.__menu.popup(self.mapToGlobal(coord)) |
603 self.__menu.popup(self.mapToGlobal(coord)) |
608 |
604 |
609 def __resultCollapse(self): |
605 def __resultCollapse(self): |
610 """ |
606 """ |
611 Private slot to collapse all entries of the resultlist. |
607 Private slot to collapse all entries of the resultlist. |
612 """ |
608 """ |
613 for index in range(self.resultList.topLevelItemCount()): |
609 for index in range(self.resultList.topLevelItemCount()): |
614 self.resultList.topLevelItem(index).setExpanded(False) |
610 self.resultList.topLevelItem(index).setExpanded(False) |
615 |
611 |
616 def __resultExpand(self): |
612 def __resultExpand(self): |
617 """ |
613 """ |
618 Private slot to expand all entries of the resultlist. |
614 Private slot to expand all entries of the resultlist. |
619 """ |
615 """ |
620 for index in range(self.resultList.topLevelItemCount()): |
616 for index in range(self.resultList.topLevelItemCount()): |
621 self.resultList.topLevelItem(index).setExpanded(True) |
617 self.resultList.topLevelItem(index).setExpanded(True) |
622 |
618 |
623 def clear(self): |
619 def clear(self): |
624 """ |
620 """ |
625 Public method to clear all results. |
621 Public method to clear all results. |
626 """ |
622 """ |
627 self.resultList.clear() |
623 self.resultList.clear() |
628 self.summaryLabel.clear() |
624 self.summaryLabel.clear() |
629 |
625 |
630 @pyqtSlot(QTreeWidgetItem, int) |
626 @pyqtSlot(QTreeWidgetItem, int) |
631 def on_resultList_itemActivated(self, item, column): |
627 def on_resultList_itemActivated(self, item, column): |
632 """ |
628 """ |
633 Private slot to handle the activation of a result item. |
629 Private slot to handle the activation of a result item. |
634 |
630 |
635 @param item reference to the activated item |
631 @param item reference to the activated item |
636 @type QTreeWidgetItem |
632 @type QTreeWidgetItem |
637 @param column activated column |
633 @param column activated column |
638 @type int |
634 @type int |
639 """ |
635 """ |
640 filename = item.data(0, self.FilePathRole) |
636 filename = item.data(0, self.FilePathRole) |
641 lineno = item.data(0, self.LineNumberRole) |
637 lineno = item.data(0, self.LineNumberRole) |
642 if filename: |
638 if filename: |
643 vm = ericApp().getObject("ViewManager") |
639 vm = ericApp().getObject("ViewManager") |
644 vm.openSourceFile(filename, lineno) |
640 vm.openSourceFile(filename, lineno) |
645 |
641 |
646 @pyqtSlot(str) |
642 @pyqtSlot(str) |
647 def on_rankComboBox_activated(self, rank): |
643 def on_rankComboBox_activated(self, rank): |
648 """ |
644 """ |
649 Private slot to handle the selection of a minimum rank. |
645 Private slot to handle the selection of a minimum rank. |
650 |
646 |
651 @param rank selected minimum rank |
647 @param rank selected minimum rank |
652 @type str |
648 @type str |
653 """ |
649 """ |
654 if self.__isSingle: |
650 if self.__isSingle: |
655 self.start(self.__fileList[:]) |
651 self.start(self.__fileList[:]) |