neuralcomputation/batik
0
1# behavior.py
2"""
3Overview comment here
4"""
5
6from qtpy.QtCore import QAbstractItemModel, QAbstractTableModel, QModelIndex, QObject, Qt, Signal, Slot
7from qtpy.QtGui import QColor
8import os
9
10class Behavior(QObject):
11 """
12 An annotation behavior, which is quite simple. It comprises:
13 name The name of the behavior, which is displayed on various UI widgets
14 hot_key The case-sensitive key stroke used to start and stop instances of the behavior
15 color The color with which to display this behavior
16 """
17
18 def __init__(self, name: str, hot_key: str = '', color: QColor = QColor('gray'), active = False, visible = True):
19 super().__init__()
20 self._name = name
21 self._hot_key = '' if hot_key == '_' else hot_key
22 self._color = color
23 self._visible = visible
24 self._active = active
25 self._get_functions = {
26 'hot_key': self.get_hot_key,
27 'name': self.get_name,
28 'color': self.get_color,
29 'active': self.is_active,
30 'visible': self.is_visible
31 }
32 self._set_functions = {
33 'hot_key': self.set_hot_key,
34 'name': self.set_name,
35 'color': self.set_color,
36 'active': self.set_active,
37 'visible': self.set_visible
38 }
39
40 def __repr__(self):
41 return f"Behavior: name={self._name}, hot_key={self._hot_key}, color={self._color}, active={self._active}, visible={self._visible}"
42
43 def get(self, key):
44 # may raise KeyError
45 return self._get_functions[key]()
46
47 def set(self, key, value):
48 # may raise KeyError
49 self._set_functions[key](value)
50
51 def get_hot_key(self):
52 return self._hot_key
53
54 def set_hot_key(self, hot_key: str):
55 self._hot_key = hot_key
56
57 def get_color(self):
58 return self._color
59
60 def set_color(self, color: QColor):
61 self._color = color
62
63 def is_active(self):
64 return self._active
65
66 @Slot(bool)
67 def set_active(self, active):
68 self._active = active
69
70 def is_visible(self):
71 return self._visible
72
73 @Slot(bool)
74 def set_visible(self, visible):
75 self._visible = visible
76
77 def get_name(self):
78 return self._name
79
80 def set_name(self, name):
81 self._name = name
82
83 def toDict(self):
84 return {
85 'hot_key': '_' if self._hot_key == '' else self._hot_key,
86 'color': self._color,
87 'name': self._name,
88 'active': self._active,
89 'visible': self._visible
90 }
91
92class Behaviors(QAbstractTableModel):
93 """
94 A set of behaviors, which represent "all" possible behaviors.
95 The class supports reading from and writing to profile files that specify
96 the default hot_key and color for each behavior, along with the name.
97
98 Derives from QAbstractTableModel so that it can be viewed and edited
99 directly in a QTableView widget.
100
101 Use getattr(name) to get the Behavior instance for a given name.
102 Use from_hot_key(key) to get the behavior(s) given the hot key.
103 Returns None if the hot_key isn't defined.
104 """
105
106 behaviors_changed = Signal()
107 layout_changed = Signal()
108
109 def __init__(self):
110 super().__init__()
111 self._items = []
112 self._by_name = {}
113 self._by_hot_key = {}
114 self._header = ['hot_key', 'color', 'name', 'active', 'visible']
115 self._searchList = [self._by_hot_key, None, self._by_name, None, None]
116 self._delete_behavior = Behavior('_delete', color = QColor('black'))
117 self._immutableColumns = set()
118 self._booleanColumns = set([self._header.index('active'), self._header.index('visible')])
119 self._role_to_str = {
120 Qt.DisplayRole: "DisplayRole",
121 Qt.DecorationRole: "DecorationRole",
122 Qt.EditRole: "EditRole",
123 Qt.ToolTipRole: "ToolTipRole",
124 Qt.StatusTipRole: "StatusTipRole",
125 Qt.WhatsThisRole: "WhatsThisRole",
126 Qt.SizeHintRole: "SizeHintRole",
127 Qt.FontRole: "FontRole",
128 Qt.TextAlignmentRole: "TextAlignmentRole",
129 Qt.BackgroundRole: "BackgroundRole",
130 Qt.ForegroundRole: "ForegroundRole",
131 Qt.CheckStateRole: "CheckStateRole",
132 Qt.InitialSortOrderRole: "InitialSortOrderRole",
133 Qt.AccessibleTextRole: "AccessibleTextRole",
134 Qt.UserRole: "UserRole"
135 }
136
137 def add(self, beh: Behavior, row=-1):
138 if row < 0:
139 row = self.rowCount()
140 self.beginInsertRows(QModelIndex(), row, row)
141 self._items.insert(row, beh)
142 self._by_name[beh.get_name()] = beh
143 hot_key = beh.get_hot_key()
144 if hot_key:
145 if hot_key not in self._by_hot_key.keys():
146 self._by_hot_key[hot_key] = []
147 assert(isinstance(self._by_hot_key[hot_key], list))
148 self._by_hot_key[hot_key].append(beh)
149 self.endInsertRows()
150 self.dataChanged.emit(
151 self.index(row, 0, QModelIndex()),
152 self.index(row, self.columnCount()-1, QModelIndex()),
153 [Qt.DisplayRole, Qt.EditRole])
154 self.behaviors_changed.emit()
155
156 def load(self, f):
157 line = f.readline()
158 while line:
159 hot_key, name, r, g, b = line.strip().split(' ')
160 if hot_key == '_':
161 hot_key = ''
162 self.add(Behavior(name, hot_key, QColor.fromRgbF(float(r), float(g), float(b))))
163 line = f.readline()
164
165 def save(self, f):
166 for beh in self._items:
167 h = beh.get_hot_key()
168 if h == '':
169 h = '_'
170 color = beh.get_color()
171 f.write(f"{h} {beh.get_name()} {color.redF()} {color.greenF()} {color.blueF()}" + os.linesep)
172
173 def get(self, name):
174 if name not in self._by_name.keys():
175 return None
176 return self._by_name[name]
177
178 def from_hot_key(self, key):
179 """
180 Return the list of behaviors associated with this hot key, if any
181 """
182 try:
183 return self._by_hot_key[key]
184 except KeyError:
185 return None
186
187 def len(self):
188 return len(self._items)
189
190 def header(self):
191 return self._header
192
193 def colorColumns(self):
194 return [self._header.index('color')]
195
196 def __iter__(self):
197 return iter(self._items)
198
199 def getDeleteBehavior(self):
200 return self._delete_behavior
201
202 def addIfMissing(self, nameToAdd):
203 if nameToAdd not in self._by_name:
204 self.add(Behavior(nameToAdd, '', QColor('gray')))
205 return True
206 return False
207
208 def isImmutable(self, index):
209 return index.column() in self._immutableColumns
210
211 def setImmutable(self, column):
212 self._immutableColumns.add(column)
213
214 # QAbstractTableModel API methods
215
216 def headerData(self, col, orientation, role):
217 if orientation == Qt.Horizontal and role == Qt.DisplayRole:
218 return self._header[col]
219 return None
220
221 def rowCount(self, parent=None):
222 return len(self._items)
223
224 def columnCount(self, parent=None):
225 return len(self._header)
226
227 def data(self, index, role=Qt.DisplayRole):
228 datum = self._items[index.row()].get(self._header[index.column()])
229 if isinstance(datum, bool):
230 if role in [Qt.CheckStateRole, Qt.EditRole]:
231 return Qt.Checked if datum else Qt.Unchecked
232 return None
233 if role in [Qt.DisplayRole, Qt.EditRole]:
234 return self._items[index.row()].get(self._header[index.column()])
235 return None
236
237 def setData(self, index, value, role=Qt.EditRole):
238 if not role in [Qt.CheckStateRole, Qt.EditRole]:
239 return False
240 if role == Qt.CheckStateRole:
241 value = bool(value)
242 beh = self._items[index.row()]
243 key = self._header[index.column()]
244 name = beh.get_name()
245 hot_key = beh.get_hot_key()
246 beh.set(key, value)
247 if key == 'hot_key' and value != hot_key:
248 # disassociate this behavior from the hot_key
249 # and associate with the new hot_key if not ''
250 if hot_key != '':
251 del(self._by_hot_key[hot_key])
252 if value != '':
253 if value not in self._by_hot_key.keys():
254 self._by_hot_key[value] = []
255 assert(isinstance(self._by_hot_key[value], list))
256 self._by_hot_key[value].append(beh)
257 elif key == 'name' and value != name:
258 if name in self._by_name.keys():
259 del(self._by_name[name])
260 self._by_name[value] = beh
261 self.behaviors_changed.emit()
262 self.dataChanged.emit(index, index, [role])
263 return True
264
265 def insertRows(self, row, count, parent):
266 if count < 1 or row < 0 or row > self.rowCount():
267 return False
268 self.beginInsertRows(QModelIndex(), row, row)
269 for r in range(count):
270 self._items.insert(row, Behavior('', active=True))
271 self.endInsertRows()
272 return True
273
274 def removeRows(self, row, count, parent=QModelIndex()):
275 if count <= 0 or row < 0 or row + count > self.rowCount(parent):
276 return False
277 self.beginRemoveRows(parent, row, row + count - 1)
278 for item in self._items[row:row+count-1]:
279 self._by_name.pop(item.name)
280 self._by_hot_key.pop(item.hot_key)
281 for i in range(count):
282 self._items.pop(row)
283 self.endRemoveRows()
284
285 def flags(self, index):
286 f = super().flags(index)
287 if index.column() not in self._immutableColumns:
288 f |= Qt.ItemIsEditable
289 if index.column() in self._booleanColumns:
290 f = (f & ~(Qt.ItemIsSelectable | Qt.ItemIsEditable)) | Qt.ItemIsUserCheckable
291 return f
292 