src/eric7/DataViews/CodeMetricsDialog.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9210
15743bae8a50
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
12 import os 12 import os
13 import time 13 import time
14 14
15 from PyQt6.QtCore import pyqtSlot, Qt, QLocale 15 from PyQt6.QtCore import pyqtSlot, Qt, QLocale
16 from PyQt6.QtWidgets import ( 16 from PyQt6.QtWidgets import (
17 QDialog, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem, 17 QDialog,
18 QApplication 18 QDialogButtonBox,
19 QMenu,
20 QHeaderView,
21 QTreeWidgetItem,
22 QApplication,
19 ) 23 )
20 24
21 from EricWidgets.EricApplication import ericApp 25 from EricWidgets.EricApplication import ericApp
22 26
23 from .Ui_CodeMetricsDialog import Ui_CodeMetricsDialog 27 from .Ui_CodeMetricsDialog import Ui_CodeMetricsDialog
28 32
29 class CodeMetricsDialog(QDialog, Ui_CodeMetricsDialog): 33 class CodeMetricsDialog(QDialog, Ui_CodeMetricsDialog):
30 """ 34 """
31 Class implementing a dialog to display the code metrics. 35 Class implementing a dialog to display the code metrics.
32 """ 36 """
37
33 def __init__(self, parent=None): 38 def __init__(self, parent=None):
34 """ 39 """
35 Constructor 40 Constructor
36 41
37 @param parent parent widget (QWidget) 42 @param parent parent widget (QWidget)
38 """ 43 """
39 super().__init__(parent) 44 super().__init__(parent)
40 self.setupUi(self) 45 self.setupUi(self)
41 self.setWindowFlags(Qt.WindowType.Window) 46 self.setWindowFlags(Qt.WindowType.Window)
42 47
43 self.buttonBox.button( 48 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
44 QDialogButtonBox.StandardButton.Close).setEnabled(False) 49 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
45 self.buttonBox.button( 50
46 QDialogButtonBox.StandardButton.Cancel).setDefault(True) 51 self.summaryList.headerItem().setText(self.summaryList.columnCount(), "")
47
48 self.summaryList.headerItem().setText(
49 self.summaryList.columnCount(), "")
50 self.summaryList.header().resizeSection(0, 200) 52 self.summaryList.header().resizeSection(0, 200)
51 self.summaryList.header().resizeSection(1, 100) 53 self.summaryList.header().resizeSection(1, 100)
52 54
53 self.resultList.headerItem().setText(self.resultList.columnCount(), "") 55 self.resultList.headerItem().setText(self.resultList.columnCount(), "")
54 56
55 self.cancelled = False 57 self.cancelled = False
56 58
57 self.__menu = QMenu(self) 59 self.__menu = QMenu(self)
58 self.__menu.addAction(self.tr("Collapse All"), 60 self.__menu.addAction(self.tr("Collapse All"), self.__resultCollapse)
59 self.__resultCollapse)
60 self.__menu.addAction(self.tr("Expand All"), self.__resultExpand) 61 self.__menu.addAction(self.tr("Expand All"), self.__resultExpand)
61 self.resultList.setContextMenuPolicy( 62 self.resultList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
62 Qt.ContextMenuPolicy.CustomContextMenu) 63 self.resultList.customContextMenuRequested.connect(self.__showContextMenu)
63 self.resultList.customContextMenuRequested.connect( 64
64 self.__showContextMenu)
65
66 self.__fileList = [] 65 self.__fileList = []
67 self.__project = ericApp().getObject("Project") 66 self.__project = ericApp().getObject("Project")
68 self.filterFrame.setVisible(False) 67 self.filterFrame.setVisible(False)
69 68
70 def __resizeResultColumns(self): 69 def __resizeResultColumns(self):
71 """ 70 """
72 Private method to resize the list columns. 71 Private method to resize the list columns.
73 """ 72 """
74 self.resultList.header().resizeSections( 73 self.resultList.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
75 QHeaderView.ResizeMode.ResizeToContents)
76 self.resultList.header().setStretchLastSection(True) 74 self.resultList.header().setStretchLastSection(True)
77 75
78 def __createResultItem(self, parent, values): 76 def __createResultItem(self, parent, values):
79 """ 77 """
80 Private slot to create a new item in the result list. 78 Private slot to create a new item in the result list.
81 79
82 @param parent parent of the new item (QTreeWidget or QTreeWidgetItem) 80 @param parent parent of the new item (QTreeWidget or QTreeWidgetItem)
83 @param values values to be displayed (list) 81 @param values values to be displayed (list)
84 @return the generated item 82 @return the generated item
85 """ 83 """
86 data = [values[0]] 84 data = [values[0]]
89 data.append("{0:5}".format(int(value))) 87 data.append("{0:5}".format(int(value)))
90 except ValueError: 88 except ValueError:
91 data.append(value) 89 data.append(value)
92 itm = QTreeWidgetItem(parent, data) 90 itm = QTreeWidgetItem(parent, data)
93 for col in range(1, 7): 91 for col in range(1, 7):
94 itm.setTextAlignment( 92 itm.setTextAlignment(col, Qt.AlignmentFlag.AlignRight)
95 col, Qt.AlignmentFlag.AlignRight)
96 return itm 93 return itm
97 94
98 def __resizeSummaryColumns(self): 95 def __resizeSummaryColumns(self):
99 """ 96 """
100 Private method to resize the list columns. 97 Private method to resize the list columns.
101 """ 98 """
102 self.summaryList.header().resizeSections( 99 self.summaryList.header().resizeSections(
103 QHeaderView.ResizeMode.ResizeToContents) 100 QHeaderView.ResizeMode.ResizeToContents
101 )
104 self.summaryList.header().setStretchLastSection(True) 102 self.summaryList.header().setStretchLastSection(True)
105 103
106 def __createSummaryItem(self, col0, col1): 104 def __createSummaryItem(self, col0, col1):
107 """ 105 """
108 Private slot to create a new item in the summary list. 106 Private slot to create a new item in the summary list.
109 107
110 @param col0 string for column 0 (string) 108 @param col0 string for column 0 (string)
111 @param col1 string for column 1 (string) 109 @param col1 string for column 1 (string)
112 """ 110 """
113 itm = QTreeWidgetItem(self.summaryList, [col0, col1]) 111 itm = QTreeWidgetItem(self.summaryList, [col0, col1])
114 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignRight) 112 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignRight)
115 113
116 def prepare(self, fileList): 114 def prepare(self, fileList):
117 """ 115 """
118 Public method to prepare the dialog with a list of filenames. 116 Public method to prepare the dialog with a list of filenames.
119 117
120 @param fileList list of filenames 118 @param fileList list of filenames
121 @type list of str 119 @type list of str
122 """ 120 """
123 self.__fileList = fileList[:] 121 self.__fileList = fileList[:]
124 122
125 self.buttonBox.button( 123 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
126 QDialogButtonBox.StandardButton.Close).setEnabled(True) 124 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
127 self.buttonBox.button( 125 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
128 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) 126
129 self.buttonBox.button(
130 QDialogButtonBox.StandardButton.Close).setDefault(True)
131
132 self.filterFrame.setVisible(True) 127 self.filterFrame.setVisible(True)
133 128
134 self.__data = self.__project.getData("OTHERTOOLSPARMS", "CodeMetrics") 129 self.__data = self.__project.getData("OTHERTOOLSPARMS", "CodeMetrics")
135 if self.__data is None or "ExcludeFiles" not in self.__data: 130 if self.__data is None or "ExcludeFiles" not in self.__data:
136 self.__data = {"ExcludeFiles": ""} 131 self.__data = {"ExcludeFiles": ""}
137 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) 132 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"])
138 133
139 def start(self, fn): 134 def start(self, fn):
140 """ 135 """
141 Public slot to start the code metrics determination. 136 Public slot to start the code metrics determination.
142 137
143 @param fn file or list of files or directory to show 138 @param fn file or list of files or directory to show
144 the code metrics for (string or list of strings) 139 the code metrics for (string or list of strings)
145 """ 140 """
146 self.cancelled = False 141 self.cancelled = False
147 self.buttonBox.button( 142 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
148 QDialogButtonBox.StandardButton.Close).setEnabled(False) 143 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
149 self.buttonBox.button( 144 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
150 QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
151 self.buttonBox.button(
152 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
153 QApplication.processEvents() 145 QApplication.processEvents()
154 146
155 loc = QLocale() 147 loc = QLocale()
156 if isinstance(fn, list): 148 if isinstance(fn, list):
157 files = fn 149 files = fn
158 elif os.path.isdir(fn): 150 elif os.path.isdir(fn):
159 files = Utilities.direntries(fn, True, '*.py', False) 151 files = Utilities.direntries(fn, True, "*.py", False)
160 else: 152 else:
161 files = [fn] 153 files = [fn]
162 files.sort() 154 files.sort()
163 # check for missing files 155 # check for missing files
164 for f in files[:]: 156 for f in files[:]:
165 if not os.path.exists(f): 157 if not os.path.exists(f):
166 files.remove(f) 158 files.remove(f)
167 159
168 self.checkProgress.setMaximum(len(files)) 160 self.checkProgress.setMaximum(len(files))
169 QApplication.processEvents() 161 QApplication.processEvents()
170 162
171 total = collections.defaultdict(int) 163 total = collections.defaultdict(int)
172 CodeMetrics.summarize(total, 'files', len(files)) 164 CodeMetrics.summarize(total, "files", len(files))
173 165
174 try: 166 try:
175 # disable updates of the list for speed 167 # disable updates of the list for speed
176 self.resultList.setUpdatesEnabled(False) 168 self.resultList.setUpdatesEnabled(False)
177 self.resultList.setSortingEnabled(False) 169 self.resultList.setSortingEnabled(False)
178 170
179 # now go through all the files 171 # now go through all the files
180 now = time.monotonic() 172 now = time.monotonic()
181 for progress, file in enumerate(files, start=1): 173 for progress, file in enumerate(files, start=1):
182 if self.cancelled: 174 if self.cancelled:
183 return 175 return
184 176
185 stats = CodeMetrics.analyze(file, total) 177 stats = CodeMetrics.analyze(file, total)
186 178
187 v = self.__getValues(loc, stats, 'TOTAL ') 179 v = self.__getValues(loc, stats, "TOTAL ")
188 # make the file name project relative 180 # make the file name project relative
189 fitm = self.__createResultItem( 181 fitm = self.__createResultItem(
190 self.resultList, 182 self.resultList, [self.__project.getRelativePath(file)] + v
191 [self.__project.getRelativePath(file)] + v
192 ) 183 )
193 184
194 identifiers = stats.identifiers 185 identifiers = stats.identifiers
195 for identifier in identifiers: 186 for identifier in identifiers:
196 v = self.__getValues(loc, stats, identifier) 187 v = self.__getValues(loc, stats, identifier)
197 188
198 self.__createResultItem(fitm, [identifier] + v) 189 self.__createResultItem(fitm, [identifier] + v)
199 self.resultList.expandItem(fitm) 190 self.resultList.expandItem(fitm)
200 191
201 self.checkProgress.setValue(progress) 192 self.checkProgress.setValue(progress)
202 if time.monotonic() - now > 0.01: 193 if time.monotonic() - now > 0.01:
203 QApplication.processEvents() 194 QApplication.processEvents()
204 now = time.monotonic() 195 now = time.monotonic()
205 finally: 196 finally:
206 # reenable updates of the list 197 # reenable updates of the list
207 self.resultList.setSortingEnabled(True) 198 self.resultList.setSortingEnabled(True)
208 self.resultList.setUpdatesEnabled(True) 199 self.resultList.setUpdatesEnabled(True)
209 self.__resizeResultColumns() 200 self.__resizeResultColumns()
210 201
211 # now do the summary stuff 202 # now do the summary stuff
212 self.__createSummaryItem(self.tr("files"), 203 self.__createSummaryItem(self.tr("files"), loc.toString(total["files"]))
213 loc.toString(total['files'])) 204 self.__createSummaryItem(self.tr("lines"), loc.toString(total["lines"]))
214 self.__createSummaryItem(self.tr("lines"), 205 self.__createSummaryItem(self.tr("bytes"), loc.toString(total["bytes"]))
215 loc.toString(total['lines'])) 206 self.__createSummaryItem(self.tr("comments"), loc.toString(total["comments"]))
216 self.__createSummaryItem(self.tr("bytes"), 207 self.__createSummaryItem(
217 loc.toString(total['bytes'])) 208 self.tr("comment lines"), loc.toString(total["commentlines"])
218 self.__createSummaryItem(self.tr("comments"), 209 )
219 loc.toString(total['comments'])) 210 self.__createSummaryItem(
220 self.__createSummaryItem(self.tr("comment lines"), 211 self.tr("empty lines"), loc.toString(total["empty lines"])
221 loc.toString(total['commentlines'])) 212 )
222 self.__createSummaryItem(self.tr("empty lines"), 213 self.__createSummaryItem(
223 loc.toString(total['empty lines'])) 214 self.tr("non-commentary lines"), loc.toString(total["non-commentary lines"])
224 self.__createSummaryItem(self.tr("non-commentary lines"), 215 )
225 loc.toString(total['non-commentary lines']))
226 self.__resizeSummaryColumns() 216 self.__resizeSummaryColumns()
227 self.__finish() 217 self.__finish()
228 218
229 def __getValues(self, loc, stats, identifier): 219 def __getValues(self, loc, stats, identifier):
230 """ 220 """
231 Private method to extract the code metric values. 221 Private method to extract the code metric values.
232 222
233 @param loc reference to the locale object (QLocale) 223 @param loc reference to the locale object (QLocale)
234 @param stats reference to the code metric statistics object 224 @param stats reference to the code metric statistics object
235 @param identifier identifier to get values for 225 @param identifier identifier to get values for
236 @return list of values suitable for display (list of strings) 226 @return list of values suitable for display (list of strings)
237 """ 227 """
238 counters = stats.counters.get(identifier, {}) 228 counters = stats.counters.get(identifier, {})
239 v = [] 229 v = []
240 for key in ('start', 'end', 'lines', 'nloc', 'commentlines', 'empty'): 230 for key in ("start", "end", "lines", "nloc", "commentlines", "empty"):
241 if counters.get(key, 0): 231 if counters.get(key, 0):
242 v.append(loc.toString(counters[key])) 232 v.append(loc.toString(counters[key]))
243 else: 233 else:
244 v.append('') 234 v.append("")
245 return v 235 return v
246 236
247 def __finish(self): 237 def __finish(self):
248 """ 238 """
249 Private slot called when the action finished or the user pressed the 239 Private slot called when the action finished or the user pressed the
250 button. 240 button.
251 """ 241 """
252 self.cancelled = True 242 self.cancelled = True
253 self.buttonBox.button( 243 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
254 QDialogButtonBox.StandardButton.Close).setEnabled(True) 244 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
255 self.buttonBox.button( 245 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
256 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) 246
257 self.buttonBox.button(
258 QDialogButtonBox.StandardButton.Close).setDefault(True)
259
260 self.resultList.header().setSectionResizeMode( 247 self.resultList.header().setSectionResizeMode(
261 QHeaderView.ResizeMode.Interactive) 248 QHeaderView.ResizeMode.Interactive
249 )
262 self.summaryList.header().setSectionResizeMode( 250 self.summaryList.header().setSectionResizeMode(
263 QHeaderView.ResizeMode.Interactive) 251 QHeaderView.ResizeMode.Interactive
264 252 )
253
265 def on_buttonBox_clicked(self, button): 254 def on_buttonBox_clicked(self, button):
266 """ 255 """
267 Private slot called by a button of the button box clicked. 256 Private slot called by a button of the button box clicked.
268 257
269 @param button button that was clicked (QAbstractButton) 258 @param button button that was clicked (QAbstractButton)
270 """ 259 """
271 if button == self.buttonBox.button( 260 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close):
272 QDialogButtonBox.StandardButton.Close
273 ):
274 self.close() 261 self.close()
275 elif button == self.buttonBox.button( 262 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel):
276 QDialogButtonBox.StandardButton.Cancel
277 ):
278 self.__finish() 263 self.__finish()
279 264
280 @pyqtSlot() 265 @pyqtSlot()
281 def on_startButton_clicked(self): 266 def on_startButton_clicked(self):
282 """ 267 """
283 Private slot to start a code metrics run. 268 Private slot to start a code metrics run.
284 """ 269 """
285 fileList = self.__fileList[:] 270 fileList = self.__fileList[:]
286 271
287 filterString = self.excludeFilesEdit.text() 272 filterString = self.excludeFilesEdit.text()
288 if ("ExcludeFiles" not in self.__data or 273 if (
289 filterString != self.__data["ExcludeFiles"]): 274 "ExcludeFiles" not in self.__data
275 or filterString != self.__data["ExcludeFiles"]
276 ):
290 self.__data["ExcludeFiles"] = filterString 277 self.__data["ExcludeFiles"] = filterString
291 self.__project.setData("OTHERTOOLSPARMS", "CodeMetrics", 278 self.__project.setData("OTHERTOOLSPARMS", "CodeMetrics", self.__data)
292 self.__data)
293 filterList = filterString.split(",") 279 filterList = filterString.split(",")
294 if filterList: 280 if filterList:
295 for filterString in filterList: 281 for filterString in filterList:
296 fileList = [f for f in fileList 282 fileList = [
297 if not fnmatch.fnmatch(f, filterString.strip())] 283 f for f in fileList if not fnmatch.fnmatch(f, filterString.strip())
298 284 ]
285
299 self.resultList.clear() 286 self.resultList.clear()
300 self.summaryList.clear() 287 self.summaryList.clear()
301 self.start(fileList) 288 self.start(fileList)
302 289
303 def __showContextMenu(self, coord): 290 def __showContextMenu(self, coord):
304 """ 291 """
305 Private slot to show the context menu of the listview. 292 Private slot to show the context menu of the listview.
306 293
307 @param coord the position of the mouse pointer (QPoint) 294 @param coord the position of the mouse pointer (QPoint)
308 """ 295 """
309 if self.resultList.topLevelItemCount() > 0: 296 if self.resultList.topLevelItemCount() > 0:
310 self.__menu.popup(self.mapToGlobal(coord)) 297 self.__menu.popup(self.mapToGlobal(coord))
311 298
312 def __resultCollapse(self): 299 def __resultCollapse(self):
313 """ 300 """
314 Private slot to collapse all entries of the resultlist. 301 Private slot to collapse all entries of the resultlist.
315 """ 302 """
316 for index in range(self.resultList.topLevelItemCount()): 303 for index in range(self.resultList.topLevelItemCount()):
317 self.resultList.topLevelItem(index).setExpanded(False) 304 self.resultList.topLevelItem(index).setExpanded(False)
318 305
319 def __resultExpand(self): 306 def __resultExpand(self):
320 """ 307 """
321 Private slot to expand all entries of the resultlist. 308 Private slot to expand all entries of the resultlist.
322 """ 309 """
323 for index in range(self.resultList.topLevelItemCount()): 310 for index in range(self.resultList.topLevelItemCount()):

eric ide

mercurial