Editing of match fields works fully now.
[mime-editor.git] / fields.py
bloba709eace3bc4e1c8061637e516e800fd529026d8
1 import rox
2 from rox import g
3 from xml.dom import XML_NAMESPACE, Node
4 import type
5 import override
6 from override import FREE_NS
8 class Invalid(Exception):
9 pass
11 def pair(vbox, label, widget):
12 hbox = g.HBox(False, 0)
13 vbox.pack_start(hbox, False, True, 0)
14 hbox.pack_start(g.Label(label + ': '), False, True, 0)
15 hbox.pack_start(widget, True, True, 0)
17 match_types = ['string', 'byte', 'big16', 'big32', 'little16', 'little32', 'host16', 'host32']
19 class Field:
20 "MIME_Type.get_* functions return a list of these."
21 def __init__(self, type, item = None):
22 self.type = type
23 if item is None:
24 self.item = self.get_blank_item()
25 self.user = True
26 self.new = True
27 else:
28 if len(item) == 2:
29 self.item = item[0]
30 else:
31 self.item = item[:-1]
32 self.user = item[-1]
33 self.new = False
34 assert self.user in (True, False)
36 def can_edit(self):
37 return self.user
39 def get_blank_item(self):
40 raise Exception("Can't create fields of this type yet")
42 def __str__(self):
43 return "<%s>" % self.item
45 def get_sub_fields(self):
46 return []
48 def add_subtree(self, model, iter, grey):
49 return
51 def delete(self):
52 rox.alert('TODO')
54 def edit(self):
55 "Returns True on success."
56 if not hasattr(self, 'add_edit_widgets'):
57 rox.alert('Sorry, MIME-Editor does not support editing fields of this type')
58 return
59 box = rox.Dialog()
60 self.box = box
61 box.set_has_separator(False)
62 vbox = g.VBox(False, 4)
63 vbox.set_border_width(4)
64 box.vbox.pack_start(vbox, True, True, 0)
65 self.add_edit_widgets(vbox)
66 box.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
67 box.add_button(g.STOCK_OK, g.RESPONSE_OK)
68 box.set_default_response(g.RESPONSE_OK)
69 box.vbox.show_all()
70 while 1:
71 try:
72 resp = box.run()
73 if resp == g.RESPONSE_OK:
74 try:
75 self.commit_edit(box)
76 except Invalid, e:
77 rox.alert(str(e))
78 continue
79 box.destroy()
80 return True
81 except:
82 rox.report_exception()
83 box.destroy()
84 return False
86 def delete_from_node(self, type_node):
87 raise Invalid("TODO: Can't delete/edit fields of this type yet")
89 def add_to_node(self, node):
90 raise Invalid("TODO: Can't add/edit fields of this type yet")
92 def delete(self):
93 assert self.user
94 doc, node = override.get_override_type(self.type.get_name())
95 self.delete_from_node(node)
96 override.write_override(doc)
98 def commit_edit(self, box):
99 doc, node = override.get_override_type(self.type.get_name())
100 if not self.new:
101 self.delete_from_node(node)
102 self.add_to_node(node)
103 override.write_override(doc)
105 class Comment(Field):
106 def get_blank_item(self):
107 return (None, 'Unknown format')
109 def __str__(self):
110 lang, data = self.item
111 if lang:
112 lang = '[' + lang + '] '
113 else:
114 lang = '(default) '
115 return lang + data
117 def add_edit_widgets(self, vbox):
118 vbox.pack_start(
119 g.Label("Enter a brief description of the type, eg 'HTML Page'.\n"
120 "Leave the language blank unless this is a translation, in \n"
121 "which case enter the country code (eg 'fr')."),
122 False, True, 0)
124 self.lang = g.Entry()
125 self.lang.set_text(self.item[0] or '')
126 self.lang.set_activates_default(True)
127 pair(vbox, 'Language', self.lang)
129 self.entry = g.Entry()
130 self.entry.set_text(self.item[1])
131 self.entry.grab_focus()
132 self.entry.set_activates_default(True)
133 pair(vbox, 'Description', self.entry)
135 def delete_from_node(self, node):
136 lang = self.item[0] or ''
137 for x in node.childNodes:
138 if x.nodeType != Node.ELEMENT_NODE: continue
139 if x.localName == 'comment' and x.namespaceURI == FREE_NS:
140 if (x.getAttributeNS(XML_NAMESPACE, 'lang') or '') == lang and \
141 data(x) == self.item[1]:
142 x.parentNode.removeChild(x)
143 break
144 else:
145 raise Exception("Can't find this comment in Override.xml!")
147 def add_to_node(self, node):
148 new_lang = self.lang.get_text()
149 new = self.entry.get_text()
150 if not new:
151 raise Invalid("Comment can't be empty")
152 comment = node.ownerDocument.createElementNS(FREE_NS, 'comment')
153 if new_lang:
154 comment.setAttributeNS(XML_NAMESPACE, 'xml:lang', new_lang)
155 data = node.ownerDocument.createTextNode(new)
156 comment.appendChild(data)
157 node.appendChild(comment)
159 class Glob(Field):
160 def get_blank_item(self):
161 return ''
163 def __str__(self):
164 return "Match '%s'" % self.item
166 def add_edit_widgets(self, vbox):
167 vbox.pack_start(
168 g.Label("Enter a glob pattern which matches files of this type.\n"
169 "Special characters are:\n"
170 "? - any one character\n"
171 "* - zero or more characters\n"
172 "[abc] - any character between the brackets\n"
173 "Example: '*.html' matches all files ending in '.html'"),
174 False, True, 0)
175 self.entry = g.Entry()
176 self.entry.set_text(self.item)
177 vbox.pack_start(self.entry, False, True, 0)
178 self.entry.set_activates_default(True)
180 def delete_from_node(self, node):
181 for x in node.childNodes:
182 if x.nodeType != Node.ELEMENT_NODE: continue
183 if x.localName == 'glob' and x.namespaceURI == FREE_NS:
184 if x.getAttributeNS(None, 'pattern') == self.item:
185 x.parentNode.removeChild(x)
186 break
187 else:
188 raise Exception("Can't find this pattern in Override.xml!")
190 def add_to_node(self, node):
191 new = self.entry.get_text()
192 if not new:
193 raise Invalid("Pattern can't be empty")
194 glob = node.ownerDocument.createElementNS(FREE_NS, 'glob')
195 glob.setAttributeNS(None, 'pattern', new)
196 node.appendChild(glob)
198 class Magic(Field):
199 def __init__(self, type, item = None):
200 if item:
201 prio, match, user = item
202 if prio is None or prio is '':
203 prio = 50
204 else:
205 prio = int(prio)
206 item = (int(prio), match, user)
207 Field.__init__(self, type, item)
208 self.match = Match(self.type, (self, self.item[1], self.item[1].user))
210 def get_blank_item(self):
211 return (50, type.Match(None, True))
213 def __str__(self):
214 prio, match = self.item
215 return "Match with priority %d" % prio
217 def add_sub_field(self):
218 self.match.add_sub_field()
220 def get_sub_fields(self):
221 return self.match.get_sub_fields()
223 def add_edit_widgets(self, vbox):
224 vbox.pack_start(
225 g.Label("The priority is from 0 (low) to 100 (high).\n"
226 "High priority matches take precedence over low ones."),
227 False, True, 0)
228 prio = self.item[0]
229 self.adj = g.Adjustment(prio, lower = 0, upper = 100, step_incr = 1)
230 spinner = g.SpinButton(self.adj, 1, 0)
231 vbox.pack_start(spinner, False, True, 0)
232 spinner.set_activates_default(True)
234 def add_to_node(self, node, child_edit = False):
235 prio, match = self.item
237 if child_edit:
238 new = prio
239 else:
240 new = int(self.adj.value)
242 magic_node = node.ownerDocument.createElementNS(FREE_NS, 'magic')
243 if new != 50:
244 magic_node.setAttributeNS(None, 'priority', str(new))
245 node.appendChild(magic_node)
246 match.write_xml(magic_node)
248 def equals_node(self, node):
249 prio, match = self.item
250 node_prio = node.getAttributeNS(None, 'priority')
251 if node_prio is None or node_prio is '':
252 node_prio = 50
253 else:
254 node_prio = int(node_prio)
255 if node_prio != prio:
256 return False
257 return match.equals_node(node)
259 def delete_from_node(self, node):
260 for x in node.childNodes:
261 if x.nodeType != Node.ELEMENT_NODE: continue
262 if x.localName == 'magic' and x.namespaceURI == FREE_NS:
263 if self.equals_node(x):
264 x.parentNode.removeChild(x)
265 break
266 else:
267 raise Exception("Can't find this magic in Override.xml!")
269 class Match(Field):
270 # Note, this is a different class to type.Match!
271 def get_blank_item(self):
272 match = type.Match(None, True)
273 match.type = 'string'
274 match.offset = '0'
275 match.value = ''
276 match.mask = None
277 return [None, match]
279 def add_sub_field(self):
280 new = Match(self.type)
281 new.item[0] = self.item[0]
282 new.item[1].parent = self.item[1]
283 new.edit()
285 def get_sub_fields(self):
286 magic, match = self.item
287 return [Match(self.type, (magic, m, m.user)) for m in match.matches]
289 def add_edit_widgets(self, vbox):
290 magic, match = self.item
292 vbox.pack_start(
293 g.Label("Offset is the position in the file to check at. It can be a\n"
294 "single number, or a range in the form 'first:last'\n"),
295 False, True, 0)
297 menu = g.Menu()
298 for t in match_types:
299 item = g.MenuItem(t)
300 menu.append(item)
301 self.match_type = g.OptionMenu()
302 self.match_type.set_menu(menu)
303 self.match_type.set_history(match_types.index(match.type))
304 pair(vbox, 'Type', self.match_type)
306 self.offset = g.Entry()
307 pair(vbox, 'Offset(s)', self.offset)
308 self.offset.set_activates_default(True)
309 self.offset.set_text(match.offset)
310 self.offset.grab_focus()
312 self.value = g.Entry()
313 pair(vbox, 'Value', self.value)
314 self.value.set_text(match.value)
315 self.value.set_activates_default(True)
317 self.mask = g.Entry()
318 pair(vbox, 'Mask', self.mask)
319 self.mask.set_text(match.mask or '')
320 self.mask.set_activates_default(True)
322 def delete_from_node(self, node):
323 magic, match = self.item
324 assert match.parent
326 magic.delete_from_node(node)
328 match.parent.matches.remove(match)
330 magic.add_to_node(node, child_edit = True)
332 def commit_edit(self, box):
333 magic, match = self.item
335 offset = self.offset.get_text()
336 value = self.value.get_text()
338 if not offset or not value:
339 raise Invalid('Missing offset or value')
341 doc, node = override.get_override_type(self.type.get_name())
342 magic.delete_from_node(node)
344 match.type = match_types[self.match_type.get_history()]
345 match.offset = offset
346 match.value = value
347 match.mask = self.mask.get_text() or None
349 if match not in match.parent.matches:
350 match.parent.matches.append(match)
352 magic.add_to_node(node, child_edit = True)
353 override.write_override(doc)
355 def __str__(self):
356 magic, m = self.item
357 text = '%s at %s = %s' % (m.type, m.offset, m.value)
358 if m.mask:
359 text += ' masked with ' + m.mask
360 return text
362 class XML(Field):
363 def get_blank_item(self):
364 return ('http://example.com', 'documentElement')
366 def __str__(self):
367 return "<%s> with namespace '%s'" % (self.item[1], self.item[0])
369 def add_edit_widgets(self, vbox):
370 vbox.pack_start(
371 g.Label("Enter the namespace URI and element name of the root element."),
372 False, True, 0)
374 self.ns = g.Entry()
375 self.ns.set_text(self.item[0])
376 self.ns.set_activates_default(True)
377 pair(vbox, 'Namespace', self.ns)
379 self.entry = g.Entry()
380 self.entry.set_text(self.item[1])
381 self.entry.set_activates_default(True)
382 pair(vbox, 'Local name', self.entry)
384 def delete_from_node(self, node):
385 ns = self.item[0]
386 localName = self.item[1]
387 for x in node.childNodes:
388 if x.nodeType != Node.ELEMENT_NODE: continue
389 if x.localName == 'root-XML' and x.namespaceURI == FREE_NS:
390 if (x.getAttributeNS(None, 'namespaceURI') == ns and \
391 x.getAttributeNS(None, 'localName') == localName):
392 x.parentNode.removeChild(x)
393 break
394 else:
395 raise Exception("Can't find this root-XML in Override.xml!")
397 def add_to_node(self, node):
398 new_ns = self.ns.get_text()
399 new = self.entry.get_text()
400 if (not new) and (not new_ns):
401 raise Invalid("Name and namespace can't both be empty")
402 if ' ' in new or ' ' in new_ns:
403 raise Invalid("Name and namespace can't contain spaces")
404 xmlroot = node.ownerDocument.createElementNS(FREE_NS, 'root-XML')
405 xmlroot.setAttributeNS(None, 'namespaceURI', new_ns)
406 xmlroot.setAttributeNS(None, 'localName', new)
407 node.appendChild(xmlroot)
409 class Other(Field):
410 def can_edit(self): return False