src/eric7/Debugger/BreakPointViewer.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9152
8a68afaf1ba2
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2005 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Breakpoint viewer widget.
8 """
9
10 import pathlib
11
12 from PyQt6.QtCore import (
13 pyqtSignal, Qt, QItemSelectionModel, QSortFilterProxyModel
14 )
15 from PyQt6.QtWidgets import (
16 QTreeView, QAbstractItemView, QHeaderView, QMenu, QDialog
17 )
18
19 from EricWidgets.EricApplication import ericApp
20
21 from Globals import recentNameBreakpointFiles, recentNameBreakpointConditions
22
23 import Preferences
24
25
26 class BreakPointViewer(QTreeView):
27 """
28 Class implementing the Breakpoint viewer widget.
29
30 Breakpoints will be shown with all their details. They can be modified
31 through the context menu of this widget.
32
33 @signal sourceFile(str, int) emitted to show the source of a breakpoint
34 """
35 sourceFile = pyqtSignal(str, int)
36
37 def __init__(self, parent=None):
38 """
39 Constructor
40
41 @param parent the parent (QWidget)
42 """
43 super().__init__(parent)
44 self.setObjectName("BreakPointViewer")
45
46 self.__model = None
47
48 self.setItemsExpandable(False)
49 self.setRootIsDecorated(False)
50 self.setAlternatingRowColors(True)
51 self.setSelectionMode(
52 QAbstractItemView.SelectionMode.ExtendedSelection)
53 self.setSelectionBehavior(
54 QAbstractItemView.SelectionBehavior.SelectRows)
55
56 self.setWindowTitle(self.tr("Breakpoints"))
57
58 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
59 self.customContextMenuRequested.connect(self.__showContextMenu)
60 self.doubleClicked.connect(self.__doubleClicked)
61
62 self.__createPopupMenus()
63
64 def setModel(self, model):
65 """
66 Public slot to set the breakpoint model.
67
68 @param model reference to the breakpoint model
69 @type BreakPointModel
70 """
71 self.__model = model
72
73 self.sortingModel = QSortFilterProxyModel()
74 self.sortingModel.setDynamicSortFilter(True)
75 self.sortingModel.setSourceModel(self.__model)
76 super().setModel(self.sortingModel)
77
78 header = self.header()
79 header.setSortIndicator(0, Qt.SortOrder.AscendingOrder)
80 header.setSortIndicatorShown(True)
81 header.setSectionsClickable(True)
82
83 self.setSortingEnabled(True)
84
85 self.__layoutDisplay()
86
87 def __layoutDisplay(self):
88 """
89 Private slot to perform a layout operation.
90 """
91 self.__resizeColumns()
92 self.__resort()
93
94 def __resizeColumns(self):
95 """
96 Private slot to resize the view when items get added, edited or
97 deleted.
98 """
99 self.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
100 self.header().setStretchLastSection(True)
101
102 def __resort(self):
103 """
104 Private slot to resort the tree.
105 """
106 self.model().sort(self.header().sortIndicatorSection(),
107 self.header().sortIndicatorOrder())
108
109 def __toSourceIndex(self, index):
110 """
111 Private slot to convert an index to a source index.
112
113 @param index index to be converted
114 @type QModelIndex
115 @return mapped index
116 @rtype QModelIndex
117 """
118 return self.sortingModel.mapToSource(index)
119
120 def __fromSourceIndex(self, sindex):
121 """
122 Private slot to convert a source index to an index.
123
124 @param sindex source index to be converted
125 @type QModelIndex
126 @return mapped index
127 @rtype QModelIndex
128 """
129 return self.sortingModel.mapFromSource(sindex)
130
131 def __setRowSelected(self, index, selected=True):
132 """
133 Private slot to select a complete row.
134
135 @param index index determining the row to be selected
136 @type QModelIndex
137 @param selected flag indicating the action
138 @type bool
139 """
140 if not index.isValid():
141 return
142
143 flags = (
144 (QItemSelectionModel.SelectionFlag.ClearAndSelect |
145 QItemSelectionModel.SelectionFlag.Rows)
146 if selected else
147 (QItemSelectionModel.SelectionFlag.Deselect |
148 QItemSelectionModel.SelectionFlag.Rows)
149 )
150 self.selectionModel().select(index, flags)
151
152 def __createPopupMenus(self):
153 """
154 Private method to generate the popup menus.
155 """
156 self.menu = QMenu()
157 self.menu.addAction(self.tr("Add"), self.__addBreak)
158 self.menu.addAction(self.tr("Edit..."), self.__editBreak)
159 self.menu.addSeparator()
160 self.menu.addAction(self.tr("Enable"), self.__enableBreak)
161 self.menu.addAction(self.tr("Enable all"), self.__enableAllBreaks)
162 self.menu.addSeparator()
163 self.menu.addAction(self.tr("Disable"), self.__disableBreak)
164 self.menu.addAction(self.tr("Disable all"),
165 self.__disableAllBreaks)
166 self.menu.addSeparator()
167 self.menu.addAction(self.tr("Delete"), self.__deleteBreak)
168 self.menu.addAction(self.tr("Delete all"), self.__deleteAllBreaks)
169 self.menu.addSeparator()
170 self.menu.addAction(self.tr("Goto"), self.__showSource)
171 self.menu.addSeparator()
172 self.menu.addAction(self.tr("Clear Histories"),
173 self.clearHistories)
174 self.menu.addSeparator()
175 self.menu.addAction(self.tr("Configure..."), self.__configure)
176
177 self.backMenuActions = {}
178 self.backMenu = QMenu()
179 self.backMenu.addAction(self.tr("Add"), self.__addBreak)
180 self.backMenuActions["EnableAll"] = self.backMenu.addAction(
181 self.tr("Enable all"),
182 self.__enableAllBreaks)
183 self.backMenuActions["DisableAll"] = self.backMenu.addAction(
184 self.tr("Disable all"),
185 self.__disableAllBreaks)
186 self.backMenuActions["DeleteAll"] = self.backMenu.addAction(
187 self.tr("Delete all"),
188 self.__deleteAllBreaks)
189 self.backMenu.addSeparator()
190 self.backMenu.addAction(self.tr("Clear Histories"),
191 self.clearHistories)
192 self.backMenu.addSeparator()
193 self.backMenu.addAction(self.tr("Configure..."), self.__configure)
194 self.backMenu.aboutToShow.connect(self.__showBackMenu)
195
196 self.multiMenu = QMenu()
197 self.multiMenu.addAction(self.tr("Add"), self.__addBreak)
198 self.multiMenu.addSeparator()
199 self.multiMenu.addAction(self.tr("Enable selected"),
200 self.__enableSelectedBreaks)
201 self.multiMenu.addAction(self.tr("Enable all"),
202 self.__enableAllBreaks)
203 self.multiMenu.addSeparator()
204 self.multiMenu.addAction(self.tr("Disable selected"),
205 self.__disableSelectedBreaks)
206 self.multiMenu.addAction(self.tr("Disable all"),
207 self.__disableAllBreaks)
208 self.multiMenu.addSeparator()
209 self.multiMenu.addAction(self.tr("Delete selected"),
210 self.__deleteSelectedBreaks)
211 self.multiMenu.addAction(self.tr("Delete all"),
212 self.__deleteAllBreaks)
213 self.multiMenu.addSeparator()
214 self.multiMenu.addAction(self.tr("Clear Histories"),
215 self.clearHistories)
216 self.multiMenu.addSeparator()
217 self.multiMenu.addAction(self.tr("Configure..."), self.__configure)
218
219 def __showContextMenu(self, coord):
220 """
221 Private slot to show the context menu.
222
223 @param coord the position of the mouse pointer
224 @type QPoint
225 """
226 cnt = self.__getSelectedItemsCount()
227 if cnt <= 1:
228 index = self.indexAt(coord)
229 if index.isValid():
230 cnt = 1
231 self.__setRowSelected(index)
232 coord = self.mapToGlobal(coord)
233 if cnt > 1:
234 self.multiMenu.popup(coord)
235 elif cnt == 1:
236 self.menu.popup(coord)
237 else:
238 self.backMenu.popup(coord)
239
240 def __clearSelection(self):
241 """
242 Private slot to clear the selection.
243 """
244 for index in self.selectedIndexes():
245 self.__setRowSelected(index, False)
246
247 def __addBreak(self):
248 """
249 Private slot to handle the add breakpoint context menu entry.
250 """
251 from .EditBreakpointDialog import EditBreakpointDialog
252
253 fnHistory, condHistory = self.__loadRecent()
254
255 dlg = EditBreakpointDialog((fnHistory[0], None), None,
256 condHistory, self, modal=1,
257 addMode=1, filenameHistory=fnHistory)
258 if dlg.exec() == QDialog.DialogCode.Accepted:
259 fn, line, cond, temp, enabled, count = dlg.getAddData()
260 if fn is not None:
261 if fn in fnHistory:
262 fnHistory.remove(fn)
263 fnHistory.insert(0, fn)
264
265 if cond:
266 if cond in condHistory:
267 condHistory.remove(cond)
268 condHistory.insert(0, cond)
269
270 self.__saveRecent(fnHistory, condHistory)
271
272 self.__model.addBreakPoint(fn, line, (cond, temp, enabled, count))
273 self.__resizeColumns()
274 self.__resort()
275
276 def __doubleClicked(self, index):
277 """
278 Private slot to handle the double clicked signal.
279
280 @param index index of the entry that was double clicked
281 @type QModelIndex
282 """
283 if index.isValid():
284 sindex = self.__toSourceIndex(index)
285 bp = self.__model.getBreakPointByIndex(sindex)
286 if not bp:
287 return
288
289 fn, line = bp[:2]
290 self.sourceFile.emit(fn, line)
291
292 def __editBreak(self):
293 """
294 Private slot to handle the edit breakpoint context menu entry.
295 """
296 index = self.currentIndex()
297 if index.isValid():
298 self.__editBreakpoint(index)
299
300 def __editBreakpoint(self, index):
301 """
302 Private slot to edit a breakpoint.
303
304 @param index index of breakpoint to be edited
305 @type QModelIndex
306 """
307 sindex = self.__toSourceIndex(index)
308 if sindex.isValid():
309 bp = self.__model.getBreakPointByIndex(sindex)
310 if not bp:
311 return
312
313 fn, line, cond, temp, enabled, count = bp[:6]
314 fnHistory, condHistory = self.__loadRecent()
315
316 from .EditBreakpointDialog import EditBreakpointDialog
317 dlg = EditBreakpointDialog(
318 (fn, line), (cond, temp, enabled, count),
319 condHistory, self, modal=True)
320 if dlg.exec() == QDialog.DialogCode.Accepted:
321 cond, temp, enabled, count = dlg.getData()
322 if cond:
323 if cond in condHistory:
324 condHistory.remove(cond)
325 condHistory.insert(0, cond)
326
327 self.__saveRecent(fnHistory, condHistory)
328
329 self.__model.setBreakPointByIndex(
330 sindex, fn, line, (cond, temp, enabled, count))
331 self.__resizeColumns()
332 self.__resort()
333
334 def __setBpEnabled(self, index, enabled):
335 """
336 Private method to set the enabled status of a breakpoint.
337
338 @param index index of breakpoint to be enabled/disabled
339 @type QModelIndex
340 @param enabled flag indicating the enabled status to be set
341 @type bool
342 """
343 sindex = self.__toSourceIndex(index)
344 if sindex.isValid():
345 self.__model.setBreakPointEnabledByIndex(sindex, enabled)
346
347 def __enableBreak(self):
348 """
349 Private slot to handle the enable breakpoint context menu entry.
350 """
351 index = self.currentIndex()
352 self.__setBpEnabled(index, True)
353 self.__resizeColumns()
354 self.__resort()
355
356 def __enableAllBreaks(self):
357 """
358 Private slot to handle the enable all breakpoints context menu entry.
359 """
360 index = self.model().index(0, 0)
361 while index.isValid():
362 self.__setBpEnabled(index, True)
363 index = self.indexBelow(index)
364 self.__resizeColumns()
365 self.__resort()
366
367 def __enableSelectedBreaks(self):
368 """
369 Private slot to handle the enable selected breakpoints context menu
370 entry.
371 """
372 for index in self.selectedIndexes():
373 if index.column() == 0:
374 self.__setBpEnabled(index, True)
375 self.__resizeColumns()
376 self.__resort()
377
378 def __disableBreak(self):
379 """
380 Private slot to handle the disable breakpoint context menu entry.
381 """
382 index = self.currentIndex()
383 self.__setBpEnabled(index, False)
384 self.__resizeColumns()
385 self.__resort()
386
387 def __disableAllBreaks(self):
388 """
389 Private slot to handle the disable all breakpoints context menu entry.
390 """
391 index = self.model().index(0, 0)
392 while index.isValid():
393 self.__setBpEnabled(index, False)
394 index = self.indexBelow(index)
395 self.__resizeColumns()
396 self.__resort()
397
398 def __disableSelectedBreaks(self):
399 """
400 Private slot to handle the disable selected breakpoints context menu
401 entry.
402 """
403 for index in self.selectedIndexes():
404 if index.column() == 0:
405 self.__setBpEnabled(index, False)
406 self.__resizeColumns()
407 self.__resort()
408
409 def __deleteBreak(self):
410 """
411 Private slot to handle the delete breakpoint context menu entry.
412 """
413 index = self.currentIndex()
414 sindex = self.__toSourceIndex(index)
415 if sindex.isValid():
416 self.__model.deleteBreakPointByIndex(sindex)
417
418 def __deleteAllBreaks(self):
419 """
420 Private slot to handle the delete all breakpoints context menu entry.
421 """
422 self.__model.deleteAll()
423
424 def __deleteSelectedBreaks(self):
425 """
426 Private slot to handle the delete selected breakpoints context menu
427 entry.
428 """
429 idxList = []
430 for index in self.selectedIndexes():
431 sindex = self.__toSourceIndex(index)
432 if sindex.isValid() and index.column() == 0:
433 idxList.append(sindex)
434 self.__model.deleteBreakPoints(idxList)
435
436 def __showSource(self):
437 """
438 Private slot to handle the goto context menu entry.
439 """
440 index = self.currentIndex()
441 sindex = self.__toSourceIndex(index)
442 bp = self.__model.getBreakPointByIndex(sindex)
443 if not bp:
444 return
445
446 fn, line = bp[:2]
447 self.sourceFile.emit(fn, line)
448
449 def highlightBreakpoint(self, fn, lineno):
450 """
451 Public slot to handle the clientLine signal.
452
453 @param fn filename of the breakpoint
454 @type str
455 @param lineno line number of the breakpoint
456 @type int
457 """
458 sindex = self.__model.getBreakPointIndex(fn, lineno)
459 if sindex.isValid():
460 return
461
462 index = self.__fromSourceIndex(sindex)
463 if index.isValid():
464 self.__clearSelection()
465 self.__setRowSelected(index, True)
466
467 def handleResetUI(self):
468 """
469 Public slot to reset the breakpoint viewer.
470 """
471 self.__clearSelection()
472
473 def __showBackMenu(self):
474 """
475 Private slot to handle the aboutToShow signal of the background menu.
476 """
477 if self.model().rowCount() == 0:
478 self.backMenuActions["EnableAll"].setEnabled(False)
479 self.backMenuActions["DisableAll"].setEnabled(False)
480 self.backMenuActions["DeleteAll"].setEnabled(False)
481 else:
482 self.backMenuActions["EnableAll"].setEnabled(True)
483 self.backMenuActions["DisableAll"].setEnabled(True)
484 self.backMenuActions["DeleteAll"].setEnabled(True)
485
486 def __getSelectedItemsCount(self):
487 """
488 Private method to get the count of items selected.
489
490 @return count of items selected
491 @rtype int
492 """
493 count = len(self.selectedIndexes()) // (self.__model.columnCount() - 1)
494 # column count is 1 greater than selectable
495 return count
496
497 def __configure(self):
498 """
499 Private method to open the configuration dialog.
500 """
501 ericApp().getObject("UserInterface").showPreferences(
502 "debuggerGeneralPage")
503
504 def __loadRecent(self):
505 """
506 Private method to load the recently used file names and breakpoint
507 conditions.
508
509 @return tuple containing the recently used file names and breakpoint
510 conditions
511 @rtype tuple of (list of str, list of str)
512 """
513 Preferences.Prefs.rsettings.sync()
514
515 # load recently used file names
516 fnHistory = []
517 fnHistory.append('')
518 rs = Preferences.Prefs.rsettings.value(recentNameBreakpointFiles)
519 if rs is not None:
520 recent = [f
521 for f in Preferences.toList(rs)
522 if pathlib.Path(f).exists()]
523 fnHistory.extend(
524 recent[:Preferences.getDebugger("RecentNumber")])
525
526 # load recently entered condition expressions
527 condHistory = []
528 rs = Preferences.Prefs.rsettings.value(recentNameBreakpointConditions)
529 if rs is not None:
530 condHistory = Preferences.toList(rs)[
531 :Preferences.getDebugger("RecentNumber")]
532
533 return fnHistory, condHistory
534
535 def __saveRecent(self, fnHistory, condHistory):
536 """
537 Private method to save the list of recently used file names and
538 breakpoint conditions.
539
540 @param fnHistory list of recently used file names
541 @type list of str
542 @param condHistory list of recently used breakpoint conditions
543 @type list of str
544 """
545 recent = [f for f in fnHistory if f]
546 Preferences.Prefs.rsettings.setValue(recentNameBreakpointFiles, recent)
547 Preferences.Prefs.rsettings.setValue(recentNameBreakpointConditions,
548 condHistory)
549 Preferences.Prefs.rsettings.sync()
550
551 def clearHistories(self):
552 """
553 Public method to clear the recently used file names and breakpoint
554 conditions.
555 """
556 self.__saveRecent([], [])

eric ide

mercurial