-
Notifications
You must be signed in to change notification settings - Fork 15
/
xy_list_store.py
227 lines (190 loc) · 7.45 KB
/
xy_list_store.py
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
# coding=UTF-8
# ex:ts=4:sw=4:et=on
# -------------------------------------------------------------------------
# Copyright (C) 2014 by Mathijs Dumon <mathijs dot dumon at gmail dot com>
#
# mvc is a framework derived from the original pygtkmvc framework
# hosted at: <http://sourceforge.net/projects/pygtkmvc/>
#
# mvc is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# mvc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110, USA.
# -------------------------------------------------------------------------
from collections import namedtuple
from ....observers import Observer
from ....models.xydata import XYData
import gi
gi.require_version('Gtk', '3.0') # @UndefinedVariable
from gi.repository import Gtk, GLib, GObject # @UnresolvedImport
from .base_models import BaseObjectListStore
class PointMeta():
@classmethod
def get_column_properties(cls):
return [
('x', float),
('y', float)
]
Point = namedtuple('Point', ['x', 'y'])
Point.Meta = PointMeta
class XYListStore(BaseObjectListStore, Observer):
"""
GenericTreeModel implementation that wraps an XYData model.
"""
_model = None
_prop_name = None
_last_lenght = 0
__gsignals__ = {
'columns-changed' : (GObject.SignalFlags.RUN_LAST, None, ()) # @UndefinedVariable
}
@property
def _data(self):
return getattr(self._model, self._prop_name, None)
def is_wrapping(self, model, prop_name):
return self._model == model and self._prop_name == prop_name
# ------------------------------------------------------------
# Initialisation and other internals
# ------------------------------------------------------------
def __init__(self, model, prop):
# Continue initialisation:
BaseObjectListStore.__init__(self, Point)
# Check this really is an XYData property:
self._flush()
self._model = model
self._prop_name = prop.label
_data = getattr(self._model, self._prop_name, None)
assert isinstance(_data, XYData), \
"Can only wrap XYData (or subclasses) instances to a XYListStore," + \
"but got '%s' instead from property '%s' on model '%s'." % (
_data, self._prop_name, self._model)
Observer.__init__(self, model=self._data)
self.set_property("leak-references", False)
self._last_length = len(self)
self._last_num_col = self._data.num_columns
# Force update:
self._emit_update()
@Observer.observe("data_changed", signal=True)
def on_data_changed(self, model, name, info):
if model == self._data:
self._emit_update()
def _emit_update(self):
# Invalidate iters, we're (probably) changing stuff:
self._schedule_flush()
# 1. check if number of columns has changed since last update
# if it has changed, emit the corresponding event
if self._last_num_col != self._data.num_columns:
self.emit("columns-changed")
self._last_num_col = self._data.num_columns
# 2. check if length has changed, if shorter emit removed signals
# for the lost elements, if longer emit insert signals
row_diff = len(self._data) - self._last_length
if row_diff > 0:
for i in range(self._last_length, self._last_length + row_diff, 1):
path = self.on_get_path(i)
itr = self.get_iter(path)
self.row_inserted(path, itr)
elif row_diff < 0:
for i in range(self._last_length, self._last_length + row_diff - 1, -1):
path = self.on_get_path(i)
self.row_deleted(path)
self._last_length = len(self._data)
# 3. Emit row-changed signals for all other rows:
for i in range(0, len(self._data)):
path = self.on_get_path(i)
itr = self.get_iter(path)
self.row_changed(path, itr)
# ------------------------------------------------------------
# Methods & Functions
# ------------------------------------------------------------
def on_get_flags(self):
return Gtk.TreeModelFlags.LIST_ONLY
def on_get_iter(self, path): # returns a rowref, they're actually just paths
if hasattr(path, "get_indices"):
path = path.get_indices()
sp = ":".join(map(lambda i: "%d" % i, path))
if not sp in self._cache:
try:
i = path[0]
if i >= 0 and i < len(self):
self._cache[sp] = [i, ]
except IndexError:
pass
return self._cache.get(sp, None)
self._schedule_flush()
return
def _schedule_flush(self):
if not self._flush_scheduled:
def idle_add():
GLib.idle_add(self._flush)
return False # delete timeout
GLib.timeout_add(500, idle_add)
self._flush_scheduled = True
def _flush(self):
self.invalidate_iters()
self._cache = {} # del _cache - keep no ref to this dict
self._flush_scheduled = False
return False # In case we are called from idle signal
def on_get_value(self, rowref, column):
if column == self.c_x:
return self._data.data_x[rowref[0]]
elif column >= self.c_y:
return self._data.data_y[rowref[0], column - 1]
else:
raise AttributeError
def on_get_path(self, rowref): # rowrefs are paths, unless they're None
if rowref is None:
return None
if isinstance(rowref, tuple):
return rowref
elif isinstance(rowref, list):
return tuple(rowref)
else:
return rowref,
def on_iter_next(self, rowref):
if rowref is not None:
itr = self.on_get_iter((rowref[0] + 1,))
return itr
else:
return None
def on_iter_children(self, rowref):
if rowref is not None:
return None
elif len(self) > 0:
return self.on_get_iter((0,))
return None
def on_iter_has_child(self, rowref):
if rowref is not None:
return False
elif len(self) > 0:
return True
return False
def on_iter_n_children(self, rowref):
if rowref is not None:
return 0
return len(self)
def on_iter_nth_child(self, rowref, n):
if rowref is not None:
return None
if n < 0 or n >= len(self):
return None
return self.on_get_iter((n,))
def on_iter_parent(self, rowref):
return None
def on_get_n_columns(self):
return self._data.num_columns
def on_get_column_type(self, index):
return float
def __len__(self):
return self._data.size
pass # end of class
GObject.type_register(XYListStore) # @UndefinedVariable