Support basedir spec.
[mime-editor.git] / fields.py
blobf1efafb9cde5206d3a50542525d0d7142f236fff
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 type.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'\n"
174 "(or '.HTML', etc)"),
175 False, True, 0)
176 self.entry = g.Entry()
177 self.entry.set_text(self.item)
178 vbox.pack_start(self.entry, False, True, 0)
179 self.entry.set_activates_default(True)
181 def delete_from_node(self, node):
182 for x in node.childNodes:
183 if x.nodeType != Node.ELEMENT_NODE: continue
184 if x.localName == 'glob' and x.namespaceURI == FREE_NS:
185 if x.getAttributeNS(None, 'pattern') == self.item:
186 x.parentNode.removeChild(x)
187 break
188 else:
189 raise Exception("Can't find this pattern in Override.xml!")
191 def add_to_node(self, node):
192 new = self.entry.get_text()
193 if not new:
194 raise Invalid("Pattern can't be empty")
195 glob = node.ownerDocument.createElementNS(FREE_NS, 'glob')
196 glob.setAttributeNS(None, 'pattern', new)
197 node.appendChild(glob)
199 class Magic(Field):
200 def __init__(self, type, item = None):
201 if item:
202 prio, match, user = item
203 if prio is None or prio is '':
204 prio = 50
205 else:
206 prio = int(prio)
207 item = (int(prio), match, user)
208 Field.__init__(self, type, item)
209 self.match = Match(self.type, (self, self.item[1], self.item[1].user))
211 def get_blank_item(self):
212 return (50, type.Match(None, True))
214 def __str__(self):
215 prio, match = self.item
216 return "Match with priority %d" % prio
218 def add_sub_field(self):
219 self.match.add_sub_field()
221 def get_sub_fields(self):
222 return self.match.get_sub_fields()
224 def add_edit_widgets(self, vbox):
225 vbox.pack_start(
226 g.Label("The priority is from 0 (low) to 100 (high).\n"
227 "High priority matches take precedence over low ones.\n"
228 "To add matches to this group, select it and click on Add."),
229 False, True, 0)
230 prio = self.item[0]
231 self.adj = g.Adjustment(prio, lower = 0, upper = 100, step_incr = 1)
232 spinner = g.SpinButton(self.adj, 1, 0)
233 vbox.pack_start(spinner, False, True, 0)
234 spinner.set_activates_default(True)
236 def add_to_node(self, node, child_edit = False):
237 prio, match = self.item
239 if child_edit:
240 new = prio
241 else:
242 new = int(self.adj.value)
244 magic_node = node.ownerDocument.createElementNS(FREE_NS, 'magic')
245 if new != 50:
246 magic_node.setAttributeNS(None, 'priority', str(new))
247 node.appendChild(magic_node)
248 match.write_xml(magic_node)
250 def equals_node(self, node):
251 prio, match = self.item
252 node_prio = node.getAttributeNS(None, 'priority')
253 if node_prio is None or node_prio is '':
254 node_prio = 50
255 else:
256 node_prio = int(node_prio)
257 if node_prio != prio:
258 return False
259 return match.equals_node(node)
261 def delete_from_node(self, node):
262 for x in node.childNodes:
263 if x.nodeType != Node.ELEMENT_NODE: continue
264 if x.localName == 'magic' and x.namespaceURI == FREE_NS:
265 if self.equals_node(x):
266 x.parentNode.removeChild(x)
267 break
268 else:
269 raise Exception("Can't find this magic in Override.xml!")
271 class Match(Field):
272 # Note, this is a different class to type.Match!
273 def get_blank_item(self):
274 match = type.Match(None, True)
275 match.type = 'string'
276 match.offset = '0'
277 match.value = ''
278 match.mask = None
279 return [None, match]
281 def add_sub_field(self):
282 new = Match(self.type)
283 new.item[0] = self.item[0]
284 new.item[1].parent = self.item[1]
285 new.edit()
287 def get_sub_fields(self):
288 magic, match = self.item
289 return [Match(self.type, (magic, m, m.user)) for m in match.matches]
291 def add_edit_widgets(self, vbox):
292 magic, match = self.item
294 vbox.pack_start(
295 g.Label("Offset is the position in the file to check at. It can be a\n"
296 "single number, or a range in the form 'first:last'\n"
297 "To add sub-matches to this one, select it and click on Add."),
298 False, True, 0)
300 menu = g.Menu()
301 for t in match_types:
302 item = g.MenuItem(t)
303 menu.append(item)
304 self.match_type = g.OptionMenu()
305 self.match_type.set_menu(menu)
306 self.match_type.set_history(match_types.index(match.type))
307 pair(vbox, 'Type', self.match_type)
309 self.offset = g.Entry()
310 pair(vbox, 'Offset(s)', self.offset)
311 self.offset.set_activates_default(True)
312 self.offset.set_text(match.offset)
313 self.offset.grab_focus()
315 self.value = g.Entry()
316 pair(vbox, 'Value', self.value)
317 self.value.set_text(match.value)
318 self.value.set_activates_default(True)
320 self.mask = g.Entry()
321 pair(vbox, 'Mask', self.mask)
322 self.mask.set_text(match.mask or '')
323 self.mask.set_activates_default(True)
325 def delete_from_node(self, node):
326 magic, match = self.item
327 assert match.parent
329 magic.delete_from_node(node)
331 match.parent.matches.remove(match)
333 magic.add_to_node(node, child_edit = True)
335 def commit_edit(self, box):
336 magic, match = self.item
338 offset = self.offset.get_text()
339 value = self.value.get_text()
341 if not offset or not value:
342 raise Invalid('Missing offset or value')
344 doc, node = override.get_override_type(self.type.get_name())
345 magic.delete_from_node(node)
347 match.type = match_types[self.match_type.get_history()]
348 match.offset = offset
349 match.value = value
350 match.mask = self.mask.get_text() or None
352 if match not in match.parent.matches:
353 match.parent.matches.append(match)
355 magic.add_to_node(node, child_edit = True)
356 override.write_override(doc)
358 def __str__(self):
359 magic, m = self.item
360 text = '%s at %s = %s' % (m.type, m.offset, m.value)
361 if m.mask:
362 text += ' masked with ' + m.mask
363 return text
365 class XML(Field):
366 def get_blank_item(self):
367 return ('http://example.com', 'documentElement')
369 def __str__(self):
370 return "<%s> with namespace '%s'" % (self.item[1], self.item[0])
372 def add_edit_widgets(self, vbox):
373 vbox.pack_start(
374 g.Label("Enter the namespace URI and element name of the root element."),
375 False, True, 0)
377 self.ns = g.Entry()
378 self.ns.set_text(self.item[0])
379 self.ns.set_activates_default(True)
380 pair(vbox, 'Namespace', self.ns)
382 self.entry = g.Entry()
383 self.entry.set_text(self.item[1])
384 self.entry.set_activates_default(True)
385 pair(vbox, 'Local name', self.entry)
387 def delete_from_node(self, node):
388 ns = self.item[0]
389 localName = self.item[1]
390 for x in node.childNodes:
391 if x.nodeType != Node.ELEMENT_NODE: continue
392 if x.localName == 'root-XML' and x.namespaceURI == FREE_NS:
393 if (x.getAttributeNS(None, 'namespaceURI') == ns and \
394 x.getAttributeNS(None, 'localName') == localName):
395 x.parentNode.removeChild(x)
396 break
397 else:
398 raise Exception("Can't find this root-XML in Override.xml!")
400 def add_to_node(self, node):
401 new_ns = self.ns.get_text()
402 new = self.entry.get_text()
403 if (not new) and (not new_ns):
404 raise Invalid("Name and namespace can't both be empty")
405 if ' ' in new or ' ' in new_ns:
406 raise Invalid("Name and namespace can't contain spaces")
407 xmlroot = node.ownerDocument.createElementNS(FREE_NS, 'root-XML')
408 xmlroot.setAttributeNS(None, 'namespaceURI', new_ns)
409 xmlroot.setAttributeNS(None, 'localName', new)
410 node.appendChild(xmlroot)
412 class Other(Field):
413 def can_edit(self): return False