RadonMetrics/RawMetricsDialog.py

changeset 3
7150ed890fd5
child 4
9ac53bf21182
equal deleted inserted replaced
2:1ad320a50a01 3:7150ed890fd5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show raw code metrics.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13 import fnmatch
14
15 from PyQt5.QtCore import pyqtSlot, qVersion, Qt, QTimer
16 from PyQt5.QtWidgets import (
17 QDialog, QDialogButtonBox, QAbstractButton, QMenu, QHeaderView,
18 QTreeWidgetItem, QApplication
19 )
20
21 from .Ui_RawMetricsDialog import Ui_RawMetricsDialog
22
23 import Preferences
24 import Utilities
25
26
27 class RawMetricsDialog(QDialog, Ui_RawMetricsDialog):
28 """
29 Class implementing a dialog to show raw code metrics.
30 """
31 def __init__(self, radonService, parent=None):
32 """
33 Constructor
34
35 @param radonService reference to the service
36 @type RadonMetricsPlugin
37 @param parent reference to the parent widget
38 @type QWidget
39 """
40 super(RawMetricsDialog, self).__init__(parent)
41 self.setupUi(self)
42 self.setWindowFlags(Qt.Window)
43
44 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
45 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
46
47 self.resultList.headerItem().setText(self.resultList.columnCount(), "")
48
49 self.radonService = radonService
50 self.radonService.metricsDone.connect(self.__processResult)
51 self.radonService.metricsError.connect(self.__processError)
52 self.radonService.batchFinished.connect(self.__batchFinished)
53
54 self.cancelled = False
55
56 self.__menu = QMenu(self)
57 self.__menu.addAction(self.tr("Collapse all"),
58 self.__resultCollapse)
59 self.__menu.addAction(self.tr("Expand all"), self.__resultExpand)
60 self.resultList.setContextMenuPolicy(Qt.CustomContextMenu)
61 self.resultList.customContextMenuRequested.connect(
62 self.__showContextMenu)
63
64 self.__fileList = []
65 self.__project = None
66 self.filterFrame.setVisible(False)
67
68 def __resizeResultColumns(self):
69 """
70 Private method to resize the list columns.
71 """
72 self.resultList.header().resizeSections(QHeaderView.ResizeToContents)
73 self.resultList.header().setStretchLastSection(True)
74
75 def __createResultItem(self, filename, values):
76 """
77 Private slot to create a new item in the result list.
78
79 @param parent parent of the new item
80 @type QTreeWidget or QTreeWidgetItem
81 @param values values to be displayed
82 @type list
83 @return the generated item
84 @rtype QTreeWidgetItem
85 """
86 data = [filename]
87 for value in values:
88 try:
89 data.append("{0:5}".format(int(value)))
90 except ValueError:
91 data.append(value)
92 itm = QTreeWidgetItem(self.resultList, data)
93 for col in range(1, 6):
94 itm.setTextAlignment(col, Qt.Alignment(Qt.AlignRight))
95 return itm
96
97 def prepare(self, fileList, project):
98 """
99 Public method to prepare the dialog with a list of filenames.
100
101 @param fileList list of filenames
102 @type list of str
103 @param project reference to the project object
104 @type Project
105 """
106 self.__fileList = fileList[:]
107 self.__project = project
108
109 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
110 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
111 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
112
113 self.filterFrame.setVisible(True)
114
115 self.__data = self.__project.getData(
116 "OTHERTOOLSPARMS", "RadonCodeMetrics")
117 if self.__data is None or "ExcludeFiles" not in self.__data:
118 self.__data = {"ExcludeFiles": ""}
119 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"])
120
121 def start(self, fn):
122 """
123 Public slot to start the code metrics determination.
124
125 @param fn file or list of files or directory to show
126 the code metrics for (string or list of strings)
127 """
128 self.cancelled = False
129 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
130 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
131 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
132 QApplication.processEvents()
133
134 if isinstance(fn, list):
135 self.files = fn
136 elif os.path.isdir(fn):
137 self.files = []
138 extensions = set(Preferences.getPython("PythonExtensions") +
139 Preferences.getPython("Python3Extensions"))
140 for ext in extensions:
141 self.files.extend(
142 Utilities.direntries(fn, True, '*{0}'.format(ext), 0))
143 else:
144 self.files = [fn]
145 self.files.sort()
146 # check for missing files
147 for f in self.files[:]:
148 if not os.path.exists(f):
149 self.files.remove(f)
150
151 if len(self.files) > 0:
152 self.checkProgress.setMaximum(len(self.files))
153 self.checkProgress.setVisible(len(self.files) > 1)
154 self.checkProgressLabel.setVisible(len(self.files) > 1)
155 QApplication.processEvents()
156
157 # now go through all the files
158 self.progress = 0
159 self.files.sort()
160 if len(self.files) == 1:
161 self.__batch = False
162 self.rawMetrics()
163 else:
164 self.__batch = True
165 self.rawMetricsBatch()
166
167 def rawMetrics(self, codestring=''):
168 """
169 Public method to start a code metrics calculation for one Python file.
170
171 The results are reported to the __processResult slot.
172
173 @keyparam codestring optional sourcestring
174 @type str
175 """
176 if not self.files:
177 self.checkProgressLabel.setPath("")
178 self.checkProgress.setMaximum(1)
179 self.checkProgress.setValue(1)
180 self.__finish()
181 return
182
183 self.filename = self.files.pop(0)
184 self.checkProgress.setValue(self.progress)
185 self.checkProgressLabel.setPath(self.filename)
186 QApplication.processEvents()
187
188 if self.cancelled:
189 return
190
191 try:
192 self.source = Utilities.readEncodedFile(self.filename)[0]
193 self.source = Utilities.normalizeCode(self.source)
194 except (UnicodeError, IOError) as msg:
195 # TODO: adjust this
196 self.__createResultItem(
197 self.filename, 1,
198 "Error: {0}".format(str(msg)).rstrip())
199 self.progress += 1
200 # Continue with next file
201 self.rawMetrics()
202 return
203
204 self.__finished = False
205 self.radonService.rawMetrics(
206 None, self.filename, self.source)
207
208 def rawMetricsBatch(self):
209 """
210 Public method to start a code metrics calculation batch job.
211
212 The results are reported to the __processResult slot.
213 """
214 self.__lastFileItem = None
215
216 self.checkProgressLabel.setPath(self.tr("Preparing files..."))
217 progress = 0
218
219 argumentsList = []
220 for filename in self.files:
221 progress += 1
222 self.checkProgress.setValue(progress)
223 QApplication.processEvents()
224
225 try:
226 source = Utilities.readEncodedFile(filename)[0]
227 source = Utilities.normalizeCode(source)
228 except (UnicodeError, IOError) as msg:
229 # TODO: adjust this
230 self.__createResultItem(
231 filename, 1,
232 "Error: {0}".format(str(msg)).rstrip())
233 continue
234
235 argumentsList.append((filename, source))
236
237 # reset the progress bar to the checked files
238 self.checkProgress.setValue(self.progress)
239 QApplication.processEvents()
240
241 self.__finished = False
242 self.radonService.rawMetricsBatch(argumentsList)
243
244 def __batchFinished(self):
245 """
246 Private slot handling the completion of a batch job.
247 """
248 self.checkProgressLabel.setPath("")
249 self.checkProgress.setMaximum(1)
250 self.checkProgress.setValue(1)
251 self.__finish()
252
253 def __processError(self, fn, msg):
254 # TODO: implement this
255 print("Error", fn, msg)
256
257 def __processResult(self, fn, result):
258 """
259 Private slot called after perfoming a code metrics calculation on one
260 file.
261
262 @param fn filename of the file
263 @type str
264 @param result result list
265 @type list
266 """
267 if self.__finished:
268 return
269
270 # Check if it's the requested file, otherwise ignore signal if not
271 # in batch mode
272 if not self.__batch and fn != self.filename:
273 return
274
275 self.__createResultItem(fn, result)
276
277 self.progress += 1
278
279 self.checkProgress.setValue(self.progress)
280 self.checkProgressLabel.setPath(fn)
281 QApplication.processEvents()
282
283 if not self.__batch:
284 self.rawMetrics()
285
286 def __finish(self):
287 """
288 Private slot called when the action or the user pressed the button.
289 """
290 if not self.__finished:
291 self.__finished = True
292
293 self.cancelled = True
294 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
295 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
296 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
297
298 self.resultList.header().resizeSections(
299 QHeaderView.ResizeToContents)
300 self.resultList.header().setStretchLastSection(True)
301 if qVersion() >= "5.0.0":
302 self.resultList.header().setSectionResizeMode(
303 QHeaderView.Interactive)
304 else:
305 self.resultList.header().setResizeMode(QHeaderView.Interactive)
306
307 self.checkProgress.setVisible(False)
308 self.checkProgressLabel.setVisible(False)
309
310 @pyqtSlot(QAbstractButton)
311 def on_buttonBox_clicked(self, button):
312 """
313 Private slot called by a button of the button box clicked.
314
315 @param button button that was clicked
316 @type QAbstractButton
317 """
318 if button == self.buttonBox.button(QDialogButtonBox.Close):
319 self.close()
320 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
321 if self.__batch:
322 self.radonService.cancelRawMetricsBatch()
323 QTimer.singleShot(1000, self.__finish)
324 else:
325 self.__finish()
326
327 @pyqtSlot()
328 def on_startButton_clicked(self):
329 """
330 Private slot to start a code metrics run.
331 """
332 fileList = self.__fileList[:]
333
334 filterString = self.excludeFilesEdit.text()
335 if "ExcludeFiles" not in self.__data or \
336 filterString != self.__data["ExcludeFiles"]:
337 self.__data["ExcludeFiles"] = filterString
338 self.__project.setData(
339 "OTHERTOOLSPARMS", "RadonCodeMetrics", self.__data)
340 filterList = [f.strip() for f in filterString.split(",")
341 if f.strip()]
342 if filterList:
343 for filter in filterList:
344 fileList = \
345 [f for f in fileList if not fnmatch.fnmatch(f, filter)]
346
347 self.resultList.clear()
348 self.cancelled = False
349 self.start(fileList)
350
351 def __showContextMenu(self, coord):
352 """
353 Private slot to show the context menu of the result list.
354
355 @param coord position of the mouse pointer
356 @type QPoint
357 """
358 if self.resultList.topLevelItemCount() > 0:
359 self.__menu.popup(self.mapToGlobal(coord))
360
361 def __resultCollapse(self):
362 """
363 Private slot to collapse all entries of the result list.
364 """
365 for index in range(self.resultList.topLevelItemCount()):
366 self.resultList.topLevelItem(index).setExpanded(False)
367
368 def __resultExpand(self):
369 """
370 Private slot to expand all entries of the result list.
371 """
372 for index in range(self.resultList.topLevelItemCount()):
373 self.resultList.topLevelItem(index).setExpanded(True)

eric ide

mercurial