1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2006 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing the Breakpoint model.
8"""
9
10import copy
11
12from PyQt5.QtCore import pyqtSignal, Qt, QAbstractItemModel, QModelIndex
13
14
15class BreakPointModel(QAbstractItemModel):
16    """
17    Class implementing a custom model for breakpoints.
18
19    @signal dataAboutToBeChanged(QModelIndex, QModelIndex) emitted to indicate
20        a change of the data
21    """
22    dataAboutToBeChanged = pyqtSignal(QModelIndex, QModelIndex)
23
24    def __init__(self, project, parent=None):
25        """
26        Constructor
27
28        @param project reference to the project object
29        @type Project
30        @param parent reference to the parent widget
31        @type QObject
32        """
33        super().__init__(parent)
34
35        self.__project = project
36
37        self.breakpoints = []
38        self.header = [
39            self.tr("Filename"),
40            self.tr("Line"),
41            self.tr('Condition'),
42            self.tr('Temporary'),
43            self.tr('Enabled'),
44            self.tr('Ignore Count'),
45        ]
46        self.alignments = [Qt.Alignment(Qt.AlignmentFlag.AlignLeft),
47                           Qt.Alignment(Qt.AlignmentFlag.AlignRight),
48                           Qt.Alignment(Qt.AlignmentFlag.AlignLeft),
49                           Qt.Alignment(Qt.AlignmentFlag.AlignHCenter),
50                           Qt.Alignment(Qt.AlignmentFlag.AlignHCenter),
51                           Qt.Alignment(Qt.AlignmentFlag.AlignRight),
52                           Qt.Alignment(Qt.AlignmentFlag.AlignHCenter),
53                           ]
54
55    def columnCount(self, parent=None):
56        """
57        Public method to get the current column count.
58
59        @param parent reference to parent index (Unused)
60        @type QModelIndex
61        @return column count
62        @rtype int
63        """
64        return len(self.header)
65
66    def rowCount(self, parent=None):
67        """
68        Public method to get the current row count.
69
70        @param parent reference to parent index
71        @type QModelIndex
72        @return row count
73        @rtype int
74        """
75        # we do not have a tree, parent should always be invalid
76        if parent is None or not parent.isValid():
77            return len(self.breakpoints)
78        else:
79            return 0
80
81    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
82        """
83        Public method to get the requested data.
84
85        @param index index of the requested data
86        @type QModelIndex
87        @param role role of the requested data
88        @type Qt.ItemDataRole
89        @return the requested data
90        @rtype any
91        """
92        if not index.isValid():
93            return None
94
95        if role == Qt.ItemDataRole.DisplayRole:
96            if index.column() == 0:
97                filename = self.breakpoints[index.row()][0]
98                if self.__project.isOpen():
99                    return self.__project.getRelativePath(filename)
100                else:
101                    return filename
102            elif index.column() in (1, 2, 5):
103                return self.breakpoints[index.row()][index.column()]
104
105        if (
106            role == Qt.ItemDataRole.CheckStateRole and
107            index.column() in (3, 4)
108        ):
109            return self.breakpoints[index.row()][index.column()]
110
111        if (
112            role == Qt.ItemDataRole.ToolTipRole and
113            index.column() in (0, 2)
114        ):
115            return self.breakpoints[index.row()][index.column()]
116
117        if (
118            role == Qt.ItemDataRole.TextAlignmentRole and
119            index.column() < len(self.alignments)
120        ):
121            return self.alignments[index.column()]
122
123        return None
124
125    def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
126        """
127        Public method to change data in the model.
128
129        @param index index of the changed data
130        @type QModelIndex
131        @param value value of the changed data
132        @type  any
133        @param role role of the changed data
134        @type Qt.ItemDataRole
135        @return flag indicating success
136        @rtype bool
137        """
138        if (not index.isValid() or
139            index.column() >= len(self.header) or
140                index.row() >= len(self.breakpoints)):
141            return False
142
143        self.dataAboutToBeChanged.emit(index, index)
144        self.breakpoints[index.row()][index.column()] = value
145        self.dataChanged.emit(index, index)
146        return True
147
148    def flags(self, index):
149        """
150        Public method to get item flags.
151
152        @param index index of the requested flags
153        @type QModelIndex
154        @return item flags for the given index
155        @rtype Qt.ItemFlags
156        """
157        if not index.isValid():
158            return Qt.ItemFlag.ItemIsEnabled
159
160        return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
161
162    def headerData(self, section, orientation,
163                   role=Qt.ItemDataRole.DisplayRole):
164        """
165        Public method to get header data.
166
167        @param section section number of the requested header data
168        @type int
169        @param orientation orientation of the header
170        @type Qt.Orientation
171        @param role role of the requested data
172        @type Qt.ItemDataRole
173        @return header data
174        @rtype str
175        """
176        if (
177            orientation == Qt.Orientation.Horizontal and
178            role == Qt.ItemDataRole.DisplayRole
179        ):
180            if section >= len(self.header):
181                return ""
182            else:
183                return self.header[section]
184
185        return None
186
187    def index(self, row, column, parent=None):
188        """
189        Public method to create an index.
190
191        @param row row number for the index
192        @type int
193        @param column column number for the index
194        @type int
195        @param parent index of the parent item
196        @type QModelIndex
197        @return requested index
198        @rtype QModelIndex
199        """
200        if ((parent and parent.isValid()) or
201            row < 0 or row >= len(self.breakpoints) or
202                column < 0 or column >= len(self.header)):
203            return QModelIndex()
204
205        return self.createIndex(row, column, self.breakpoints[row])
206
207    def parent(self, index):
208        """
209        Public method to get the parent index.
210
211        @param index index of item to get parent
212        @type QModelIndex
213        @return index of parent
214        @rtype QModelIndex
215        """
216        return QModelIndex()
217
218    def hasChildren(self, parent=None):
219        """
220        Public method to check for the presence of child items.
221
222        @param parent index of parent item
223        @type QModelIndex
224        @return flag indicating the presence of child items
225        @rtype bool
226        """
227        if parent is None or not parent.isValid():
228            return len(self.breakpoints) > 0
229        else:
230            return False
231
232    ###########################################################################
233
234    def addBreakPoint(self, fn, line, properties):
235        """
236        Public method to add a new breakpoint to the list.
237
238        @param fn filename of the breakpoint
239        @type str
240        @param line line number of the breakpoint
241        @type int
242        @param properties properties of the breakpoint
243            (tuple of condition, temporary flag, enabled flag, ignore count)
244        @type tuple of (str, bool, bool, int)
245        """
246        bp = [fn, line] + list(properties)
247        cnt = len(self.breakpoints)
248        self.beginInsertRows(QModelIndex(), cnt, cnt)
249        self.breakpoints.append(bp)
250        self.endInsertRows()
251
252    def addBreakPoints(self, breakpoints):
253        """
254        Public method to add multiple breakpoints to the list.
255
256        @param breakpoints list of breakpoints with file name, line number,
257            condition, temporary flag, enabled flag and ignore count each
258        @type list of (str, int, str, bool, bool, int)
259        """
260        cnt = len(self.breakpoints)
261        self.beginInsertRows(QModelIndex(), cnt, cnt + len(breakpoints) - 1)
262        self.breakpoints += breakpoints
263        self.endInsertRows()
264
265    def setBreakPointByIndex(self, index, fn, line, properties):
266        """
267        Public method to set the values of a breakpoint given by index.
268
269        @param index index of the breakpoint
270        @type QModelIndex
271        @param fn filename of the breakpoint
272        @type str
273        @param line line number of the breakpoint
274        @type int
275        @param properties properties of the breakpoint
276            (tuple of condition, temporary flag, enabled flag, ignore count)
277        @type tuple of (str, bool, bool, int)
278        """
279        if index.isValid():
280            row = index.row()
281            index1 = self.createIndex(row, 0, self.breakpoints[row])
282            index2 = self.createIndex(
283                row, len(self.breakpoints[row]), self.breakpoints[row])
284            self.dataAboutToBeChanged.emit(index1, index2)
285            self.breakpoints[row] = [fn, line] + list(properties)
286            self.dataChanged.emit(index1, index2)
287
288    def setBreakPointEnabledByIndex(self, index, enabled):
289        """
290        Public method to set the enabled state of a breakpoint given by index.
291
292        @param index index of the breakpoint
293        @type QModelIndex
294        @param enabled flag giving the enabled state
295        @type bool
296        """
297        if index.isValid():
298            row = index.row()
299            col = 4
300            index1 = self.createIndex(row, col, self.breakpoints[row])
301            self.dataAboutToBeChanged.emit(index1, index1)
302            self.breakpoints[row][col] = enabled
303            self.dataChanged.emit(index1, index1)
304
305    def deleteBreakPointByIndex(self, index):
306        """
307        Public method to set the values of a breakpoint given by index.
308
309        @param index index of the breakpoint
310        @type QModelIndex
311        """
312        if index.isValid():
313            row = index.row()
314            self.beginRemoveRows(QModelIndex(), row, row)
315            del self.breakpoints[row]
316            self.endRemoveRows()
317
318    def deleteBreakPoints(self, idxList):
319        """
320        Public method to delete a list of breakpoints given by their indexes.
321
322        @param idxList list of breakpoint indexes
323        @type list of QModelIndex
324        """
325        rows = []
326        for index in idxList:
327            if index.isValid():
328                rows.append(index.row())
329        rows.sort(reverse=True)
330        for row in rows:
331            if row < len(self.breakpoints):
332                self.beginRemoveRows(QModelIndex(), row, row)
333                del self.breakpoints[row]
334                self.endRemoveRows()
335
336    def deleteAll(self):
337        """
338        Public method to delete all breakpoints.
339        """
340        if self.breakpoints:
341            self.beginRemoveRows(QModelIndex(), 0, len(self.breakpoints) - 1)
342            self.breakpoints = []
343            self.endRemoveRows()
344
345    def getBreakPointByIndex(self, index):
346        """
347        Public method to get the values of a breakpoint given by index.
348
349        @param index index of the breakpoint
350        @type QModelIndex
351        @return breakpoint (list of six values (filename, line number,
352            condition, temporary flag, enabled flag, ignore count))
353        @rtype list of (str, int, str, bool, bool, int)
354        """
355        if index.isValid():
356            return self.breakpoints[index.row()][:]  # return a copy
357        else:
358            return []
359
360    def getAllBreakpoints(self):
361        """
362        Public method to get a copy of the breakpoints.
363
364        @return list of breakpoints
365        @rtype list of list of [str, int, str, bool, bool, int]
366        """
367        return copy.deepcopy(self.breakpoints)
368
369    def getBreakPointIndex(self, fn, lineno):
370        """
371        Public method to get the index of a breakpoint given by filename and
372        line number.
373
374        @param fn filename of the breakpoint
375        @type str
376        @param lineno line number of the breakpoint
377        @type int
378        @return index
379        @rtype QModelIndex
380        """
381        for row in range(len(self.breakpoints)):
382            bp = self.breakpoints[row]
383            if bp[0] == fn and bp[1] == lineno:
384                return self.createIndex(row, 0, self.breakpoints[row])
385
386        return QModelIndex()
387
388    def isBreakPointTemporaryByIndex(self, index):
389        """
390        Public method to test, if a breakpoint given by its index is temporary.
391
392        @param index index of the breakpoint to test
393        @type QModelIndex
394        @return flag indicating a temporary breakpoint
395        @rtype bool
396        """
397        if index.isValid():
398            return self.breakpoints[index.row()][3]
399        else:
400            return False
401