File size: 10,211 Bytes
ed29c11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# behavior.py
"""

Overview comment here

"""

from qtpy.QtCore import QAbstractItemModel, QAbstractTableModel, QModelIndex, QObject, Qt, Signal, Slot
from qtpy.QtGui import QColor
import os

class Behavior(QObject):
    """

    An annotation behavior, which is quite simple.  It comprises:

    name        The name of the behavior, which is displayed on various UI widgets

    hot_key     The case-sensitive key stroke used to start and stop instances of the behavior

    color       The color with which to display this behavior

    """

    def __init__(self, name: str, hot_key: str = '', color: QColor = QColor('gray'), active = False, visible = True):
        super().__init__()
        self._name = name
        self._hot_key = '' if hot_key == '_' else hot_key
        self._color = color
        self._visible = visible
        self._active = active
        self._get_functions = {
            'hot_key': self.get_hot_key,
            'name': self.get_name,
            'color': self.get_color,
            'active': self.is_active,
            'visible': self.is_visible
            }
        self._set_functions = {
            'hot_key': self.set_hot_key,
            'name': self.set_name,
            'color': self.set_color,
            'active': self.set_active,
            'visible': self.set_visible
            }

    def __repr__(self):
        return f"Behavior: name={self._name}, hot_key={self._hot_key}, color={self._color}, active={self._active}, visible={self._visible}"

    def get(self, key):
        # may raise KeyError
        return self._get_functions[key]()

    def set(self, key, value):
        # may raise KeyError
        self._set_functions[key](value)

    def get_hot_key(self):
        return self._hot_key

    def set_hot_key(self, hot_key: str):
        self._hot_key = hot_key

    def get_color(self):
        return self._color

    def set_color(self, color: QColor):
        self._color = color

    def is_active(self):
        return self._active

    @Slot(bool)
    def set_active(self, active):
        self._active = active

    def is_visible(self):
        return self._visible

    @Slot(bool)
    def set_visible(self, visible):
        self._visible = visible

    def get_name(self):
        return self._name

    def set_name(self, name):
        self._name = name

    def toDict(self):
        return {
            'hot_key': '_' if self._hot_key == '' else self._hot_key,
            'color': self._color,
            'name': self._name,
            'active': self._active,
            'visible': self._visible
            }

class Behaviors(QAbstractTableModel):
    """

    A set of behaviors, which represent "all" possible behaviors.

    The class supports reading from and writing to profile files that specify

    the default hot_key and color for each behavior, along with the name.



    Derives from QAbstractTableModel so that it can be viewed and edited

    directly in a QTableView widget.



    Use getattr(name) to get the Behavior instance for a given name.

    Use from_hot_key(key) to get the behavior(s) given the hot key.

        Returns None if the hot_key isn't defined.

    """

    behaviors_changed = Signal()
    layout_changed = Signal()

    def __init__(self):
        super().__init__()
        self._items = []
        self._by_name = {}
        self._by_hot_key = {}
        self._header = ['hot_key', 'color', 'name', 'active', 'visible']
        self._searchList = [self._by_hot_key, None, self._by_name, None, None]
        self._delete_behavior = Behavior('_delete', color = QColor('black'))
        self._immutableColumns = set()
        self._booleanColumns = set([self._header.index('active'), self._header.index('visible')])
        self._role_to_str = {
            Qt.DisplayRole: "DisplayRole",
            Qt.DecorationRole: "DecorationRole",
            Qt.EditRole: "EditRole",
            Qt.ToolTipRole: "ToolTipRole",
            Qt.StatusTipRole: "StatusTipRole",
            Qt.WhatsThisRole: "WhatsThisRole",
            Qt.SizeHintRole: "SizeHintRole",
            Qt.FontRole: "FontRole",
            Qt.TextAlignmentRole: "TextAlignmentRole",
            Qt.BackgroundRole: "BackgroundRole",
            Qt.ForegroundRole: "ForegroundRole",
            Qt.CheckStateRole: "CheckStateRole",
            Qt.InitialSortOrderRole: "InitialSortOrderRole",
            Qt.AccessibleTextRole: "AccessibleTextRole",
            Qt.UserRole: "UserRole"
        }

    def add(self, beh: Behavior, row=-1):
        if row < 0:
            row = self.rowCount()
        self.beginInsertRows(QModelIndex(), row, row)
        self._items.insert(row, beh)
        self._by_name[beh.get_name()] = beh
        hot_key = beh.get_hot_key()
        if hot_key:
            if hot_key not in self._by_hot_key.keys():
                self._by_hot_key[hot_key] = []
            assert(isinstance(self._by_hot_key[hot_key], list))
            self._by_hot_key[hot_key].append(beh)
        self.endInsertRows()
        self.dataChanged.emit(
            self.index(row, 0, QModelIndex()),
            self.index(row, self.columnCount()-1, QModelIndex()),
            [Qt.DisplayRole, Qt.EditRole])
        self.behaviors_changed.emit()

    def load(self, f):
        line = f.readline()
        while line:
            hot_key, name, r, g, b = line.strip().split(' ')
            if hot_key == '_':
                hot_key = ''
            self.add(Behavior(name, hot_key, QColor.fromRgbF(float(r), float(g), float(b))))
            line = f.readline()

    def save(self, f):
        for beh in self._items:
            h = beh.get_hot_key()
            if h == '':
                h = '_'
            color = beh.get_color()
            f.write(f"{h} {beh.get_name()} {color.redF()} {color.greenF()} {color.blueF()}" + os.linesep)

    def get(self, name):
        if name not in self._by_name.keys():
            return None
        return self._by_name[name]

    def from_hot_key(self, key):
        """

        Return the list of behaviors associated with this hot key, if any

        """
        try:
            return self._by_hot_key[key]
        except KeyError:
            return None

    def len(self):
        return len(self._items)

    def header(self):
        return self._header

    def colorColumns(self):
        return [self._header.index('color')]

    def __iter__(self):
        return iter(self._items)

    def getDeleteBehavior(self):
        return self._delete_behavior

    def addIfMissing(self, nameToAdd):
        if nameToAdd not in self._by_name:
            self.add(Behavior(nameToAdd, '', QColor('gray')))
            return True
        return False

    def isImmutable(self, index):
        return index.column() in self._immutableColumns

    def setImmutable(self, column):
        self._immutableColumns.add(column)

    # QAbstractTableModel API methods

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._header[col]
        return None

    def rowCount(self, parent=None):
        return len(self._items)

    def columnCount(self, parent=None):
        return len(self._header)

    def data(self, index, role=Qt.DisplayRole):
        datum = self._items[index.row()].get(self._header[index.column()])
        if isinstance(datum, bool):
            if role in [Qt.CheckStateRole, Qt.EditRole]:
                return Qt.Checked if datum else Qt.Unchecked
            return None
        if role in [Qt.DisplayRole, Qt.EditRole]:
            return self._items[index.row()].get(self._header[index.column()])
        return None

    def setData(self, index, value, role=Qt.EditRole):
        if not role in [Qt.CheckStateRole, Qt.EditRole]:
            return False
        if role == Qt.CheckStateRole:
            value = bool(value)
        beh = self._items[index.row()]
        key = self._header[index.column()]
        name = beh.get_name()
        hot_key = beh.get_hot_key()
        beh.set(key, value)
        if key == 'hot_key' and value != hot_key:
            # disassociate this behavior from the hot_key
            # and associate with the new hot_key if not ''
            if hot_key != '':
                del(self._by_hot_key[hot_key])
            if value != '':
                if value not in self._by_hot_key.keys():
                    self._by_hot_key[value] = []
                assert(isinstance(self._by_hot_key[value], list))
                self._by_hot_key[value].append(beh)
        elif key == 'name' and value != name:
            if name in self._by_name.keys():
                del(self._by_name[name])
            self._by_name[value] = beh
        self.behaviors_changed.emit()
        self.dataChanged.emit(index, index, [role])
        return True

    def insertRows(self, row, count, parent):
        if count < 1 or row < 0 or row > self.rowCount():
            return False
        self.beginInsertRows(QModelIndex(), row, row)
        for r in range(count):
            self._items.insert(row, Behavior('', active=True))
        self.endInsertRows()
        return True

    def removeRows(self, row, count, parent=QModelIndex()):
        if count <= 0 or row < 0 or row + count > self.rowCount(parent):
            return False
        self.beginRemoveRows(parent, row, row + count - 1)
        for item in self._items[row:row+count-1]:
            self._by_name.pop(item.name)
            self._by_hot_key.pop(item.hot_key)
        for i in range(count):
            self._items.pop(row)
        self.endRemoveRows()

    def flags(self, index):
        f = super().flags(index)
        if index.column() not in self._immutableColumns:
            f |= Qt.ItemIsEditable
        if index.column() in self._booleanColumns:
            f = (f & ~(Qt.ItemIsSelectable | Qt.ItemIsEditable)) | Qt.ItemIsUserCheckable
        return f