eric7/Debugger/BreakPointViewer.py

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

eric ide

mercurial