summaryrefslogtreecommitdiff
path: root/src/main/python/utils/streamTable.py
blob: 8b3b220726098bbbeed0976d57272447507e8a31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
from PyQt5.QtCore import Qt, QSize, QRect, QPoint, QAbstractTableModel, pyqtSignal, QModelIndex
from PyQt5.QtGui import QBrush, QPen, QColor, QCursor
from PyQt5.QtWidgets import QTableView, QMenu, QGraphicsRectItem, QInputDialog, QStyledItemDelegate, QHeaderView

from collections import defaultdict

class streamTableModel(QAbstractTableModel):
    """
    Defines the table model for the table view
    """
    updateEvent = pyqtSignal()
    
    def __init__(self, parent, list, header, *args):
        super(streamTableModel, self).__init__(parent, *args)
        self.list = list
        self.header = header
    
    # column count, row count, data and setdata are important methods to be overloaded
    def columnCount(self, parent=None):
        return len(self.list)
    
    def rowCount(self, parent=None):
        return len(self.header)
    
    def data(self, index, role):
        # retunrs data at index
        if role == Qt.TextAlignmentRole:
            return Qt.AlignHCenter
        
        if role == Qt.BackgroundColorRole:
            return Qt.white
        
        if not index.isValid():
            return None
        
        elif role != Qt.DisplayRole:
            return None
        
        if index.row() == 0:
            return self.list[index.column()].toPlainText() # return label name
        else:
            return self.list[index.column()].values[self.header[index.row()]] # retunr label values
    
    def setData(self, index, value, role):
        # defines how to manipulate data at a given index with value returns sucess value
        if not index.isValid():
            return False
        
        elif role != Qt.EditRole:
            return False
        
        if index.row() == 0:
            self.list[index.column()].setPlainText(value) # change label text
        else:
            self.list[index.column()].values[self.header[index.row()]] = value #change label values
            
        return True 
    
    def insertColumn(self, int=None, item=None):
        # inserting a label item
        int = int if int else self.rowCount()+1
        self.beginInsertColumns(QModelIndex(), int, int)
        self.list.insert(int, item)
        item.nameChanged.connect(self.parent().repaint)
        self.endInsertColumns()
        self.updateEvent.emit()
    
    def insertRow(self, int=None, name="newVal"):
        # inserting a header property
        self.beginInsertRows(QModelIndex(), int, int)
        self.header.insert(int, name)
        self.endInsertRows()
        self.updateEvent.emit()
    
    def deleteRow(self, row):
        # removing a property
        self.beginRemoveRows(QModelIndex(), row, row)
        valName = self.header.pop(row) # remove from header
        self.endRemoveRows()
        
        for i in self.list:
            i.values.pop(valName) #clear dictionary
            
        self.updateEvent.emit() # update request
        
    def headerData(self, col, orientation, role):
        # definds how to fetch header data
        if orientation == Qt.Vertical and role == Qt.DisplayRole:
            return self.header[col]
        return None
    
    def flags(self, index):
        # defines item editable flag
        return (super(streamTableModel, self).flags(index) | Qt.ItemIsEditable)
        
class streamTable(QTableView):
    """
    subclasses stream table to display data properly
    """
    def __init__(self, itemLabels=[], canvas=None, parent=None):
        super(streamTable, self).__init__(parent=parent)
        self.canvas = canvas
        
        for i in itemLabels:
            i.nameChanged.connect(self.repaint) # connect repaint requests on name change
            
        header = ["name", "val1", "val2", "val3", "val4", "val5"] # prepare header names
        self.model = streamTableModel(self, itemLabels, header)
        self.setShowGrid(False) # disable table grid
        
        self.horizontalHeader().hide() # remove horizontal header
        
        header = verticalHeader(Qt.Vertical, self) #create custom vertical header
        self.setVerticalHeader(header)
        header.labelChangeRequested.connect(self.labelChange)
        
        self.setModel(self.model) #declare model
        self.borderThickness = defaultdict(lambda: False) #thickness bool dict
        self.model.updateEvent.connect(self.resizeHandler)
        
        self.setItemDelegateForRow(0, drawBorderDelegate(self))
        self.borderThickness[0] = True # set border true for name row
        
    def mousePressEvent(self, event):
        # handle context menu request
        if event.button() == Qt.RightButton:
            point = event.pos()
            index = self.indexAt(point)
            menu = QMenu("Context Menu", self)
            menu.addAction("Toggle bottom border thickness", lambda x=index.row(): self.changeRowBorder(x))
            menu.addAction("Insert Row to bottom", lambda x=index.row(): self.insertRowBottom(x))
            menu.addAction("Delete row", lambda x=index.row(): self.model.deleteRow(x))
            menu.exec_(self.mapToGlobal(point)+ QPoint(20, 25))
            event.accept()
        return super(streamTable, self).mousePressEvent(event)
    
    def changeRowBorder(self, row):
        # toggle column border thicnkess
        if self.borderThickness[row]:
            self.borderThickness.pop(row)
            self.setItemDelegateForRow(row, QStyledItemDelegate(self))
        else:
            self.borderThickness[row] = True
            self.setItemDelegateForRow(row, drawBorderDelegate(self))
        self.verticalHeader().repaint()
    
    def labelChange(self, index):
        # label name change
        newName, bool = QInputDialog.getText(self, "Change Property Name", "Enter new name", 
                                             text = self.model.header[index])
        if bool:
            for i in self.model.list:
                i.values[newName] = i.values.pop(self.model.header[index])
            self.model.header[index] = newName
            self.repaint()

    def insertRowBottom(self, row):
        # dialog box for new property
        name, bool = QInputDialog.getText(self, "New Property", "Enter name", 
                                             text = "newVal")
        if bool:
            self.model.insertRow(row + 1, name)
            self.repaint()
        
    def resizeHandler(self):
        self.resize(self.sizeHint())
        
    def sizeHint(self):
        return self.rect().size()
    
    def rect(self):
        w = self.verticalHeader().width() + 4
        for i in range(self.model.columnCount()):
            w += self.columnWidth(i)
        h = 0
        for i in range(self.model.rowCount()):
            h += self.rowHeight(i)
        return QRect(0, 0, w, h)
    
    def __getstate__(self):
        return {
            "borderThickness": self.borderThickness,
            "header": self.model.header
        }
    
    def __setstate__(self, dict):
        for key, value in dict['borderThickness'].items():
            self.borderThickness[key] = value
        self.model.header = dict['header']
        self.repaint()

class drawBorderDelegate(QStyledItemDelegate):
    """
    class for drawing border line
    """    
    def paint(self, painter, option, index):
        rect = option.rect
        painter.drawLine(rect.bottomLeft(), rect.bottomRight())
        painter.setPen(QPen(Qt.black, 1, Qt.SolidLine))
        super(drawBorderDelegate, self).paint(painter, option, index)

class moveRect(QGraphicsRectItem):
    """
    use to move the table on the scene
    """
    def __init__(self, sideLength = 15, *args):
        super(moveRect, self).__init__(-sideLength, -sideLength, sideLength, sideLength)
        self.setBrush(Qt.transparent)
        self.setPen(QPen(Qt.transparent))
        self.setCursor(QCursor(Qt.SizeAllCursor))
        self.setAcceptHoverEvents(True)
        
    def hoverEnterEvent(self, event):
        self.setBrush(QBrush(QColor(0, 0, 0, 120)))
        return super(moveRect, self).hoverEnterEvent(event)
        
    def hoverLeaveEvent(self, event):
        self.setBrush(QBrush(QColor(0, 0, 0, 0)))
        return super(moveRect, self).hoverLeaveEvent(event)
    
class verticalHeader(QHeaderView):
    """
    Custom Vertical header for the table, with line border against corresponding rows
    """
    labelChangeRequested = pyqtSignal(int)
    
    def mouseDoubleClickEvent(self, event):
        index = self.logicalIndexAt(event.pos())
        self.labelChangeRequested.emit(index)
        return super().mouseDoubleClickEvent(event)
    
    def paintSection(self, painter, option, index):
        painter.save()
        super(verticalHeader, self).paintSection(painter, option, index)
        painter.restore()
        if self.parentWidget().borderThickness[index]:
            rect = option
            painter.drawLine(rect.bottomLeft(), rect.bottomRight())  
            painter.setPen(QPen(Qt.black, 1, Qt.SolidLine))