summaryrefslogtreecommitdiff
path: root/lib/hachoir_editor/fieldset.py
diff options
context:
space:
mode:
authorjvoisin2011-06-07 18:40:44 +0200
committerjvoisin2011-06-07 18:40:44 +0200
commitf7082a21d6511c5069fbb9ff186ce22f3e22fed7 (patch)
tree93322a3220e50c1a32583ba197afec870298767c /lib/hachoir_editor/fieldset.py
First commit
Diffstat (limited to 'lib/hachoir_editor/fieldset.py')
-rw-r--r--lib/hachoir_editor/fieldset.py346
1 files changed, 346 insertions, 0 deletions
diff --git a/lib/hachoir_editor/fieldset.py b/lib/hachoir_editor/fieldset.py
new file mode 100644
index 0000000..1669b5a
--- /dev/null
+++ b/lib/hachoir_editor/fieldset.py
@@ -0,0 +1,346 @@
1from hachoir_core.dict import UniqKeyError
2from hachoir_core.field import MissingField, Float32, Float64, FakeArray
3from hachoir_core.compatibility import any
4from hachoir_core.i18n import _
5from hachoir_editor import createEditableField, EditorError
6from collections import deque # Python 2.4
7import weakref # Python 2.1
8import struct
9
10class EditableFieldSet(object):
11 MAX_SIZE = (1 << 40) # Arbitrary limit to catch errors
12 is_field_set = True
13
14 def __init__(self, parent, fieldset):
15 self._parent = parent
16 self.input = fieldset # original FieldSet
17 self._fields = {} # cache of editable fields
18 self._deleted = set() # Names of deleted fields
19 self._inserted = {} # Inserted field (name => list of field,
20 # where name is the name after)
21
22 def array(self, key):
23 # FIXME: Use cache?
24 return FakeArray(self, key)
25
26 def _getParent(self):
27 return self._parent
28 parent = property(_getParent)
29
30 def _isAltered(self):
31 if self._inserted:
32 return True
33 if self._deleted:
34 return True
35 return any(field.is_altered for field in self._fields.itervalues())
36 is_altered = property(_isAltered)
37
38 def reset(self):
39 """
40 Reset the field set and the input field set.
41 """
42 for key, field in self._fields.iteritems():
43 if not field.is_altered:
44 del self._fields[key]
45 self.input.reset()
46
47 def __len__(self):
48 return len(self.input) \
49 - len(self._deleted) \
50 + sum( len(new) for new in self._inserted.itervalues() )
51
52 def __iter__(self):
53 for field in self.input:
54 name = field.name
55 if name in self._inserted:
56 for newfield in self._inserted[name]:
57 yield weakref.proxy(newfield)
58 if name not in self._deleted:
59 yield self[name]
60 if None in self._inserted:
61 for newfield in self._inserted[None]:
62 yield weakref.proxy(newfield)
63
64 def insertBefore(self, name, *new_fields):
65 self._insert(name, new_fields, False)
66
67 def insertAfter(self, name, *new_fields):
68 self._insert(name, new_fields, True)
69
70 def insert(self, *new_fields):
71 self._insert(None, new_fields, True)
72
73 def _insert(self, key, new_fields, next):
74 """
75 key is the name of the field before which new_fields
76 will be inserted. If next is True, the fields will be inserted
77 _after_ this field.
78 """
79 # Set unique field name
80 for field in new_fields:
81 if field._name.endswith("[]"):
82 self.input.setUniqueFieldName(field)
83
84 # Check that there is no duplicate in inserted fields
85 new_names = list(field.name for field in new_fields)
86 names_set = set(new_names)
87 if len(names_set) != len(new_fields):
88 duplicates = (name for name in names_set if 1 < new_names.count(name))
89 raise UniqKeyError(_("Duplicates in inserted fields: %s") % ", ".join(duplicates))
90
91 # Check that field names are not in input
92 if self.input: # Write special version for NewFieldSet?
93 for name in new_names:
94 if name in self.input and name not in self._deleted:
95 raise UniqKeyError(_("Field name '%s' already exists") % name)
96
97 # Check that field names are not in inserted fields
98 for fields in self._inserted.itervalues():
99 for field in fields:
100 if field.name in new_names:
101 raise UniqKeyError(_("Field name '%s' already exists") % field.name)
102
103 # Input have already inserted field?
104 if key in self._inserted:
105 if next:
106 self._inserted[key].extend( reversed(new_fields) )
107 else:
108 self._inserted[key].extendleft( reversed(new_fields) )
109 return
110
111 # Whould like to insert in inserted fields?
112 if key:
113 for fields in self._inserted.itervalues():
114 names = [item.name for item in fields]
115 try:
116 pos = names.index(key)
117 except ValueError:
118 continue
119 if 0 <= pos:
120 if next:
121 pos += 1
122 fields.rotate(-pos)
123 fields.extendleft( reversed(new_fields) )
124 fields.rotate(pos)
125 return
126
127 # Get next field. Use None if we are at the end.
128 if next:
129 index = self.input[key].index + 1
130 try:
131 key = self.input[index].name
132 except IndexError:
133 key = None
134
135 # Check that field names are not in input
136 if key not in self.input:
137 raise MissingField(self, key)
138
139 # Insert in original input
140 self._inserted[key]= deque(new_fields)
141
142 def _getDescription(self):
143 return self.input.description
144 description = property(_getDescription)
145
146 def _getStream(self):
147 # FIXME: This property is maybe a bad idea since address may be differents
148 return self.input.stream
149 stream = property(_getStream)
150
151 def _getName(self):
152 return self.input.name
153 name = property(_getName)
154
155 def _getEndian(self):
156 return self.input.endian
157 endian = property(_getEndian)
158
159 def _getAddress(self):
160 if self._parent:
161 return self._parent._getFieldAddress(self.name)
162 else:
163 return 0
164 address = property(_getAddress)
165
166 def _getAbsoluteAddress(self):
167 address = self.address
168 current = self._parent
169 while current:
170 address += current.address
171 current = current._parent
172 return address
173 absolute_address = property(_getAbsoluteAddress)
174
175 def hasValue(self):
176 return False
177# return self._parent.input[self.name].hasValue()
178
179 def _getSize(self):
180 if self.is_altered:
181 return sum(field.size for field in self)
182 else:
183 return self.input.size
184 size = property(_getSize)
185
186 def _getPath(self):
187 return self.input.path
188 path = property(_getPath)
189
190 def _getOriginalField(self, name):
191 assert name in self.input
192 return self.input[name]
193
194 def _getFieldInputAddress(self, name):
195 """
196 Absolute address of a field from the input field set.
197 """
198 assert name in self.input
199 return self.input[name].absolute_address
200
201 def _getFieldAddress(self, name):
202 """
203 Compute relative address of a field. The operation takes care of
204 deleted and resized fields.
205 """
206 #assert name not in self._deleted
207 addr = 0
208 for field in self:
209 if field.name == name:
210 return addr
211 addr += field.size
212 raise MissingField(self, name)
213
214 def _getItemByPath(self, path):
215 if not path[0]:
216 path = path[1:]
217 field = self
218 for name in path:
219 field = field[name]
220 return field
221
222 def __contains__(self, name):
223 try:
224 field = self[name]
225 return (field is not None)
226 except MissingField:
227 return False
228
229 def __getitem__(self, key):
230 """
231 Create a weak reference to an editable field (EditableField) for the
232 field with specified name. If the field is removed later, using the
233 editable field will raise a weakref.ReferenceError exception.
234
235 May raise a MissingField error if the field doesn't exist in original
236 field set or it has been deleted.
237 """
238 if "/" in key:
239 return self._getItemByPath(key.split("/"))
240 if isinstance(key, (int, long)):
241 raise EditorError("Integer index are not supported")
242
243 if (key in self._deleted) or (key not in self.input):
244 raise MissingField(self, key)
245 if key not in self._fields:
246 field = self.input[key]
247 if field.is_field_set:
248 self._fields[key] = createEditableFieldSet(self, field)
249 else:
250 self._fields[key] = createEditableField(self, field)
251 return weakref.proxy(self._fields[key])
252
253 def __delitem__(self, name):
254 """
255 Remove a field from the field set. May raise an MissingField exception
256 if the field has already been deleted.
257 """
258 if name in self._deleted:
259 raise MissingField(self, name)
260 self._deleted.add(name)
261 if name in self._fields:
262 del self._fields[name]
263
264 def writeInto(self, output):
265 """
266 Write the content if this field set into the output stream
267 (OutputStream).
268 """
269 if not self.is_altered:
270 # Not altered: just copy bits/bytes
271 input = self.input
272 if input.size % 8:
273 output.copyBitsFrom(input.stream,
274 input.absolute_address, input.size, input.endian)
275 else:
276 output.copyBytesFrom(input.stream,
277 input.absolute_address, input.size//8)
278 else:
279 # Altered: call writeInto() method of each field
280 realaddr = 0
281 for field in self:
282 field.writeInto(output)
283 realaddr += field.size
284
285 def _getValue(self):
286 raise EditorError('Field set "%s" has no value' % self.path)
287 def _setValue(self, value):
288 raise EditorError('Field set "%s" value is read only' % self.path)
289 value = property(_getValue, _setValue, "Value of field")
290
291class EditableFloat(EditableFieldSet):
292 _value = None
293
294 def _isAltered(self):
295 return (self._value is not None)
296 is_altered = property(_isAltered)
297
298 def writeInto(self, output):
299 if self._value is not None:
300 self._write(output)
301 else:
302 EditableFieldSet.writeInto(self, output)
303
304 def _write(self, output):
305 format = self.input.struct_format
306 raw = struct.pack(format, self._value)
307 output.writeBytes(raw)
308
309 def _setValue(self, value):
310 self.parent._is_altered = True
311 self._value = value
312 value = property(EditableFieldSet._getValue, _setValue)
313
314def createEditableFieldSet(parent, field):
315 cls = field.__class__
316 # FIXME: Support Float80
317 if cls in (Float32, Float64):
318 return EditableFloat(parent, field)
319 else:
320 return EditableFieldSet(parent, field)
321
322class NewFieldSet(EditableFieldSet):
323 def __init__(self, parent, name):
324 EditableFieldSet.__init__(self, parent, None)
325 self._name = name
326 self._endian = parent.endian
327
328 def __iter__(self):
329 if None in self._inserted:
330 return iter(self._inserted[None])
331 else:
332 raise StopIteration()
333
334 def _getName(self):
335 return self._name
336 name = property(_getName)
337
338 def _getEndian(self):
339 return self._endian
340 endian = property(_getEndian)
341
342 is_altered = property(lambda self: True)
343
344def createEditor(fieldset):
345 return EditableFieldSet(None, fieldset)
346