src/eric7/Debugger/WatchPointViewer.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
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 watch expression viewer widget.
8 """
9
10 from PyQt6.QtCore import (
11 Qt, QModelIndex, QItemSelectionModel, QSortFilterProxyModel
12 )
13 from PyQt6.QtWidgets import (
14 QTreeView, QAbstractItemView, QMenu, QHeaderView, QDialog
15 )
16
17 from EricWidgets.EricApplication import ericApp
18 from EricWidgets import EricMessageBox
19
20 import Utilities
21
22
23 class WatchPointViewer(QTreeView):
24 """
25 Class implementing the watch expression viewer widget.
26
27 Watch expressions will be shown with all their details. They can be
28 modified through the context menu of this widget.
29 """
30 def __init__(self, parent=None):
31 """
32 Constructor
33
34 @param parent the parent (QWidget)
35 """
36 super().__init__(parent)
37 self.setObjectName("WatchExpressionViewer")
38
39 self.__model = None
40
41 self.setItemsExpandable(False)
42 self.setRootIsDecorated(False)
43 self.setAlternatingRowColors(True)
44 self.setSelectionMode(
45 QAbstractItemView.SelectionMode.ExtendedSelection)
46 self.setSelectionBehavior(
47 QAbstractItemView.SelectionBehavior.SelectRows)
48
49 self.setWindowTitle(self.tr("Watchpoints"))
50
51 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
52 self.customContextMenuRequested.connect(self.__showContextMenu)
53 self.doubleClicked.connect(self.__doubleClicked)
54
55 self.__createPopupMenus()
56
57 def setModel(self, model):
58 """
59 Public slot to set the watch expression model.
60
61 @param model reference to the watch expression model (WatchPointModel)
62 """
63 self.__model = model
64
65 self.sortingModel = QSortFilterProxyModel()
66 self.sortingModel.setDynamicSortFilter(True)
67 self.sortingModel.setSourceModel(self.__model)
68 super().setModel(self.sortingModel)
69
70 header = self.header()
71 header.setSortIndicator(0, Qt.SortOrder.AscendingOrder)
72 header.setSortIndicatorShown(True)
73 header.setSectionsClickable(True)
74
75 self.setSortingEnabled(True)
76
77 self.__layoutDisplay()
78
79 def __layoutDisplay(self):
80 """
81 Private slot to perform a layout operation.
82 """
83 self.__resizeColumns()
84 self.__resort()
85
86 def __resizeColumns(self):
87 """
88 Private slot to resize the view when items get added, edited or
89 deleted.
90 """
91 self.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
92 self.header().setStretchLastSection(True)
93
94 def __resort(self):
95 """
96 Private slot to resort the tree.
97 """
98 self.model().sort(self.header().sortIndicatorSection(),
99 self.header().sortIndicatorOrder())
100
101 def __toSourceIndex(self, index):
102 """
103 Private slot to convert an index to a source index.
104
105 @param index index to be converted (QModelIndex)
106 @return mapped index (QModelIndex)
107 """
108 return self.sortingModel.mapToSource(index)
109
110 def __fromSourceIndex(self, sindex):
111 """
112 Private slot to convert a source index to an index.
113
114 @param sindex source index to be converted (QModelIndex)
115 @return mapped index (QModelIndex)
116 """
117 return self.sortingModel.mapFromSource(sindex)
118
119 def __setRowSelected(self, index, selected=True):
120 """
121 Private slot to select a complete row.
122
123 @param index index determining the row to be selected (QModelIndex)
124 @param selected flag indicating the action (bool)
125 """
126 if not index.isValid():
127 return
128
129 flags = (
130 (QItemSelectionModel.SelectionFlag.ClearAndSelect |
131 QItemSelectionModel.SelectionFlag.Rows)
132 if selected else
133 (QItemSelectionModel.SelectionFlag.Deselect |
134 QItemSelectionModel.SelectionFlag.Rows)
135 )
136 self.selectionModel().select(index, flags)
137
138 def __createPopupMenus(self):
139 """
140 Private method to generate the popup menus.
141 """
142 self.menu = QMenu()
143 self.menu.addAction(self.tr("Add"), self.__addWatchPoint)
144 self.menu.addAction(self.tr("Edit..."), self.__editWatchPoint)
145 self.menu.addSeparator()
146 self.menu.addAction(self.tr("Enable"), self.__enableWatchPoint)
147 self.menu.addAction(self.tr("Enable all"),
148 self.__enableAllWatchPoints)
149 self.menu.addSeparator()
150 self.menu.addAction(self.tr("Disable"), self.__disableWatchPoint)
151 self.menu.addAction(self.tr("Disable all"),
152 self.__disableAllWatchPoints)
153 self.menu.addSeparator()
154 self.menu.addAction(self.tr("Delete"), self.__deleteWatchPoint)
155 self.menu.addAction(self.tr("Delete all"),
156 self.__deleteAllWatchPoints)
157 self.menu.addSeparator()
158 self.menu.addAction(self.tr("Configure..."), self.__configure)
159
160 self.backMenuActions = {}
161 self.backMenu = QMenu()
162 self.backMenu.addAction(self.tr("Add"), self.__addWatchPoint)
163 self.backMenuActions["EnableAll"] = self.backMenu.addAction(
164 self.tr("Enable all"),
165 self.__enableAllWatchPoints)
166 self.backMenuActions["DisableAll"] = self.backMenu.addAction(
167 self.tr("Disable all"),
168 self.__disableAllWatchPoints)
169 self.backMenuActions["DeleteAll"] = self.backMenu.addAction(
170 self.tr("Delete all"),
171 self.__deleteAllWatchPoints)
172 self.backMenu.addSeparator()
173 self.backMenu.addAction(self.tr("Configure..."), self.__configure)
174 self.backMenu.aboutToShow.connect(self.__showBackMenu)
175
176 self.multiMenu = QMenu()
177 self.multiMenu.addAction(self.tr("Add"), self.__addWatchPoint)
178 self.multiMenu.addSeparator()
179 self.multiMenu.addAction(self.tr("Enable selected"),
180 self.__enableSelectedWatchPoints)
181 self.multiMenu.addAction(self.tr("Enable all"),
182 self.__enableAllWatchPoints)
183 self.multiMenu.addSeparator()
184 self.multiMenu.addAction(self.tr("Disable selected"),
185 self.__disableSelectedWatchPoints)
186 self.multiMenu.addAction(self.tr("Disable all"),
187 self.__disableAllWatchPoints)
188 self.multiMenu.addSeparator()
189 self.multiMenu.addAction(self.tr("Delete selected"),
190 self.__deleteSelectedWatchPoints)
191 self.multiMenu.addAction(self.tr("Delete all"),
192 self.__deleteAllWatchPoints)
193 self.multiMenu.addSeparator()
194 self.multiMenu.addAction(self.tr("Configure..."), self.__configure)
195
196 def __showContextMenu(self, coord):
197 """
198 Private slot to show the context menu.
199
200 @param coord the position of the mouse pointer (QPoint)
201 """
202 cnt = self.__getSelectedItemsCount()
203 if cnt <= 1:
204 index = self.indexAt(coord)
205 if index.isValid():
206 cnt = 1
207 self.__setRowSelected(index)
208 coord = self.mapToGlobal(coord)
209 if cnt > 1:
210 self.multiMenu.popup(coord)
211 elif cnt == 1:
212 self.menu.popup(coord)
213 else:
214 self.backMenu.popup(coord)
215
216 def __clearSelection(self):
217 """
218 Private slot to clear the selection.
219 """
220 for index in self.selectedIndexes():
221 self.__setRowSelected(index, False)
222
223 def __findDuplicates(self, cond, special, showMessage=False,
224 index=None):
225 """
226 Private method to check, if an entry already exists.
227
228 @param cond condition to check (string)
229 @param special special condition to check (string)
230 @param showMessage flag indicating a message should be shown,
231 if a duplicate entry is found (boolean)
232 @param index index that should not be considered duplicate
233 (QModelIndex)
234 @return flag indicating a duplicate entry (boolean)
235 """
236 if index is None:
237 index = QModelIndex()
238 idx = self.__model.getWatchPointIndex(cond, special)
239 duplicate = (idx.isValid() and
240 idx.internalPointer() != index.internalPointer())
241 if showMessage and duplicate:
242 if not special:
243 msg = self.tr(
244 """<p>A watch expression '<b>{0}</b>'"""
245 """ already exists.</p>"""
246 ).format(Utilities.html_encode(cond))
247 else:
248 msg = self.tr(
249 """<p>A watch expression '<b>{0}</b>'"""
250 """ for the variable <b>{1}</b> already exists.</p>"""
251 ).format(special, Utilities.html_encode(cond))
252 EricMessageBox.warning(
253 self,
254 self.tr("Watch expression already exists"),
255 msg)
256
257 return duplicate
258
259 def __addWatchPoint(self):
260 """
261 Private slot to handle the add watch expression context menu entry.
262 """
263 from .EditWatchpointDialog import EditWatchpointDialog
264 dlg = EditWatchpointDialog(("", False, True, 0, ""), self)
265 if dlg.exec() == QDialog.DialogCode.Accepted:
266 cond, temp, enabled, ignorecount, special = dlg.getData()
267 if not self.__findDuplicates(cond, special, True):
268 self.__model.addWatchPoint(cond, special,
269 (temp, enabled, ignorecount))
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 (QModelIndex)
278 """
279 if index.isValid():
280 self.__doEditWatchPoint(index)
281
282 def __editWatchPoint(self):
283 """
284 Private slot to handle the edit watch expression context menu entry.
285 """
286 index = self.currentIndex()
287 if index.isValid():
288 self.__doEditWatchPoint(index)
289
290 def __doEditWatchPoint(self, index):
291 """
292 Private slot to edit a watch expression.
293
294 @param index index of watch expression to be edited (QModelIndex)
295 """
296 sindex = self.__toSourceIndex(index)
297 if sindex.isValid():
298 wp = self.__model.getWatchPointByIndex(sindex)
299 if not wp:
300 return
301
302 cond, special, temp, enabled, count = wp[:5]
303
304 from .EditWatchpointDialog import EditWatchpointDialog
305 dlg = EditWatchpointDialog(
306 (cond, temp, enabled, count, special), self)
307 if dlg.exec() == QDialog.DialogCode.Accepted:
308 cond, temp, enabled, count, special = dlg.getData()
309 if not self.__findDuplicates(cond, special, True, sindex):
310 self.__model.setWatchPointByIndex(
311 sindex, cond, special, (temp, enabled, count))
312 self.__resizeColumns()
313 self.__resort()
314
315 def __setWpEnabled(self, index, enabled):
316 """
317 Private method to set the enabled status of a watch expression.
318
319 @param index index of watch expression to be enabled/disabled
320 (QModelIndex)
321 @param enabled flag indicating the enabled status to be set (boolean)
322 """
323 sindex = self.__toSourceIndex(index)
324 if sindex.isValid():
325 self.__model.setWatchPointEnabledByIndex(sindex, enabled)
326
327 def __enableWatchPoint(self):
328 """
329 Private slot to handle the enable watch expression context menu entry.
330 """
331 index = self.currentIndex()
332 self.__setWpEnabled(index, True)
333 self.__resizeColumns()
334 self.__resort()
335
336 def __enableAllWatchPoints(self):
337 """
338 Private slot to handle the enable all watch expressions context menu
339 entry.
340 """
341 index = self.model().index(0, 0)
342 while index.isValid():
343 self.__setWpEnabled(index, True)
344 index = self.indexBelow(index)
345 self.__resizeColumns()
346 self.__resort()
347
348 def __enableSelectedWatchPoints(self):
349 """
350 Private slot to handle the enable selected watch expressions context
351 menu entry.
352 """
353 for index in self.selectedIndexes():
354 if index.column() == 0:
355 self.__setWpEnabled(index, True)
356 self.__resizeColumns()
357 self.__resort()
358
359 def __disableWatchPoint(self):
360 """
361 Private slot to handle the disable watch expression context menu entry.
362 """
363 index = self.currentIndex()
364 self.__setWpEnabled(index, False)
365 self.__resizeColumns()
366 self.__resort()
367
368 def __disableAllWatchPoints(self):
369 """
370 Private slot to handle the disable all watch expressions context menu
371 entry.
372 """
373 index = self.model().index(0, 0)
374 while index.isValid():
375 self.__setWpEnabled(index, False)
376 index = self.indexBelow(index)
377 self.__resizeColumns()
378 self.__resort()
379
380 def __disableSelectedWatchPoints(self):
381 """
382 Private slot to handle the disable selected watch expressions context
383 menu entry.
384 """
385 for index in self.selectedIndexes():
386 if index.column() == 0:
387 self.__setWpEnabled(index, False)
388 self.__resizeColumns()
389 self.__resort()
390
391 def __deleteWatchPoint(self):
392 """
393 Private slot to handle the delete watch expression context menu entry.
394 """
395 index = self.currentIndex()
396 sindex = self.__toSourceIndex(index)
397 if sindex.isValid():
398 self.__model.deleteWatchPointByIndex(sindex)
399
400 def __deleteAllWatchPoints(self):
401 """
402 Private slot to handle the delete all watch expressions context menu
403 entry.
404 """
405 self.__model.deleteAll()
406
407 def __deleteSelectedWatchPoints(self):
408 """
409 Private slot to handle the delete selected watch expressions context
410 menu entry.
411 """
412 idxList = []
413 for index in self.selectedIndexes():
414 sindex = self.__toSourceIndex(index)
415 if sindex.isValid() and index.column() == 0:
416 idxList.append(sindex)
417 self.__model.deleteWatchPoints(idxList)
418
419 def __showBackMenu(self):
420 """
421 Private slot to handle the aboutToShow signal of the background menu.
422 """
423 if self.model().rowCount() == 0:
424 self.backMenuActions["EnableAll"].setEnabled(False)
425 self.backMenuActions["DisableAll"].setEnabled(False)
426 self.backMenuActions["DeleteAll"].setEnabled(False)
427 else:
428 self.backMenuActions["EnableAll"].setEnabled(True)
429 self.backMenuActions["DisableAll"].setEnabled(True)
430 self.backMenuActions["DeleteAll"].setEnabled(True)
431
432 def __getSelectedItemsCount(self):
433 """
434 Private method to get the count of items selected.
435
436 @return count of items selected (integer)
437 """
438 count = len(self.selectedIndexes()) // (self.__model.columnCount() - 1)
439 # column count is 1 greater than selectable
440 return count
441
442 def __configure(self):
443 """
444 Private method to open the configuration dialog.
445 """
446 ericApp().getObject("UserInterface").showPreferences(
447 "debuggerGeneralPage")

eric ide

mercurial