Requires Python < 3
[mime-editor.git] / fields.py
bloba8f9944e4da20d2834167dbfb491f0cfae74b2c6
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>" % str(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 == int(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 TreeMagic(Field):
200 def __str__(self):
201 prio, matches = self.item
202 return _("Match with priority %d") % prio
204 class Magic(Field):
205 def __init__(self, type, item = None):
206 if item:
207 prio, match, user = item
208 if prio is None or prio is '':
209 prio = 50
210 else:
211 prio = int(prio)
212 item = (int(prio), match, user)
213 Field.__init__(self, type, item)
214 self.match = Match(self.type, (self, self.item[1], self.item[1].user))
216 def get_blank_item(self):
217 return (50, type.Match(None, True))
219 def __str__(self):
220 prio, match = self.item
221 return _("Match with priority %d") % prio
223 def add_sub_field(self):
224 self.match.add_sub_field()
226 def get_sub_fields(self):
227 return self.match.get_sub_fields()
229 def add_edit_widgets(self, vbox):
230 vbox.pack_start(
231 g.Label(_("The priority is from 0 (low) to 100 (high).\n"
232 "High priority matches take precedence over low ones.\n"
233 "To add matches to this group, select it and click on Add.")),
234 False, True, 0)
235 prio = self.item[0]
236 self.adj = g.Adjustment(prio, lower = 0, upper = 100, step_incr = 1)
237 spinner = g.SpinButton(self.adj, 1, 0)
238 vbox.pack_start(spinner, False, True, 0)
239 spinner.set_activates_default(True)
241 def add_to_node(self, node, child_edit = False):
242 prio, match = self.item
244 if child_edit:
245 new = prio
246 else:
247 new = int(self.adj.value)
249 magic_node = node.ownerDocument.createElementNS(FREE_NS, 'magic')
250 if new != 50:
251 magic_node.setAttributeNS(None, 'priority', str(new))
252 node.appendChild(magic_node)
253 match.write_xml(magic_node)
255 def equals_node(self, node):
256 prio, match = self.item
257 node_prio = node.getAttributeNS(None, 'priority')
258 if node_prio is None or node_prio is '':
259 node_prio = 50
260 else:
261 node_prio = int(node_prio)
262 if node_prio != prio:
263 return False
264 return match.equals_node(node)
266 def delete_from_node(self, node):
267 for x in node.childNodes:
268 if x.nodeType != Node.ELEMENT_NODE: continue
269 if x.localName == 'magic' and x.namespaceURI == FREE_NS:
270 if self.equals_node(x):
271 x.parentNode.removeChild(x)
272 break
273 else:
274 raise Exception(_("Can't find this magic in Override.xml!"))
276 class Match(Field):
277 # Note, this is a different class to type.Match!
278 def get_blank_item(self):
279 match = type.Match(None, True)
280 match.type = 'string'
281 match.offset = '0'
282 match.value = ''
283 match.mask = None
284 return [None, match]
286 def add_sub_field(self):
287 new = Match(self.type)
288 new.item[0] = self.item[0]
289 new.item[1].parent = self.item[1]
290 new.edit()
292 def get_sub_fields(self):
293 magic, match = self.item
294 return [Match(self.type, (magic, m, m.user)) for m in match.matches]
296 def add_edit_widgets(self, vbox):
297 magic, match = self.item
299 vbox.pack_start(
300 g.Label(_("Offset is the position in the file to check at. It can be a\n"
301 "single number, or a range in the form 'first:last'\n"
302 "To add sub-matches to this one, select it and click on Add.")),
303 False, True, 0)
305 menu = g.Menu()
306 for t in match_types:
307 item = g.MenuItem(t)
308 menu.append(item)
309 self.match_type = g.OptionMenu()
310 self.match_type.set_menu(menu)
311 self.match_type.set_history(match_types.index(match.type))
312 pair(vbox, 'Type', self.match_type)
314 self.offset = g.Entry()
315 pair(vbox, 'Offset(s)', self.offset)
316 self.offset.set_activates_default(True)
317 self.offset.set_text(match.offset)
318 self.offset.grab_focus()
320 self.value = g.Entry()
321 pair(vbox, 'Value', self.value)
322 self.value.set_text(match.value)
323 self.value.set_activates_default(True)
325 self.mask = g.Entry()
326 pair(vbox, 'Mask', self.mask)
327 self.mask.set_text(match.mask or '')
328 self.mask.set_activates_default(True)
330 def delete_from_node(self, node):
331 magic, match = self.item
332 assert match.parent
334 magic.delete_from_node(node)
336 match.parent.matches.remove(match)
338 magic.add_to_node(node, child_edit = True)
340 def commit_edit(self, box):
341 magic, match = self.item
343 offset = self.offset.get_text()
344 value = self.value.get_text()
346 if not offset or not value:
347 raise Invalid(_('Missing offset or value'))
349 doc, node = override.get_override_type(self.type.get_name())
350 magic.delete_from_node(node)
352 match.type = match_types[self.match_type.get_history()]
353 match.offset = offset
354 match.value = value
355 match.mask = self.mask.get_text() or None
357 if match not in match.parent.matches:
358 match.parent.matches.append(match)
360 magic.add_to_node(node, child_edit = True)
361 override.write_override(doc)
363 def __str__(self):
364 magic, m = self.item
365 text = '%s at %s = %s' % (m.type, m.offset, m.value)
366 if m.mask:
367 text += ' masked with ' + m.mask
368 return text
370 class XML(Field):
371 def get_blank_item(self):
372 return (_('http://example.com'), _('documentElement'))
374 def __str__(self):
375 return _("<%s> with namespace '%s'") % (self.item[1], self.item[0])
377 def add_edit_widgets(self, vbox):
378 vbox.pack_start(
379 g.Label(_("Enter the namespace URI and element name of the root element.")),
380 False, True, 0)
382 self.ns = g.Entry()
383 self.ns.set_text(self.item[0])
384 self.ns.set_activates_default(True)
385 pair(vbox, _('Namespace'), self.ns)
387 self.entry = g.Entry()
388 self.entry.set_text(self.item[1])
389 self.entry.set_activates_default(True)
390 pair(vbox, _('Local name'), self.entry)
392 def delete_from_node(self, node):
393 ns = self.item[0]
394 localName = self.item[1]
395 for x in node.childNodes:
396 if x.nodeType != Node.ELEMENT_NODE: continue
397 if x.localName == 'root-XML' and x.namespaceURI == FREE_NS:
398 if (x.getAttributeNS(None, 'namespaceURI') == ns and \
399 x.getAttributeNS(None, 'localName') == localName):
400 x.parentNode.removeChild(x)
401 break
402 else:
403 raise Exception(_("Can't find this root-XML in Override.xml!"))
405 def add_to_node(self, node):
406 new_ns = self.ns.get_text()
407 new = self.entry.get_text()
408 if (not new) and (not new_ns):
409 raise Invalid(_("Name and namespace can't both be empty"))
410 if ' ' in new or ' ' in new_ns:
411 raise Invalid(_("Name and namespace can't contain spaces"))
412 xmlroot = node.ownerDocument.createElementNS(FREE_NS, 'root-XML')
413 xmlroot.setAttributeNS(None, 'namespaceURI', new_ns)
414 xmlroot.setAttributeNS(None, 'localName', new)
415 node.appendChild(xmlroot)
417 class Other(Field):
418 def can_edit(self): return False