Started on editing of magic match fields.
[mime-editor.git] / editor.py
blob41666d329f8392323d172b4a464ab0dd9281e635
1 import rox
2 from rox import g
3 import os
4 import type
5 import fields
7 edit_boxes = {}
9 ADD = 1
10 DELETE = 2
11 EDIT = 3
13 class Editor(rox.Dialog):
14 def __init__(self):
15 rox.Dialog.__init__(self)
16 self.set_default_size(600, 400)
17 self.set_has_separator(False)
19 self.add_type = None
21 self.add_button(g.STOCK_ADD, ADD)
22 self.add_button(g.STOCK_DELETE, DELETE)
23 self.add_button(g.STOCK_PROPERTIES, EDIT)
24 self.add_button(g.STOCK_CLOSE, g.RESPONSE_CANCEL)
26 self.set_default_response(g.RESPONSE_CANCEL)
28 swin = g.ScrolledWindow()
29 swin.set_border_width(4)
30 swin.set_policy(g.POLICY_NEVER, g.POLICY_ALWAYS)
31 swin.set_shadow_type(g.SHADOW_IN)
32 self.vbox.pack_start(swin, True, True, 0)
34 self.mime_model = g.TreeStore(str, str)
35 view = g.TreeView(self.mime_model)
36 self.view = view
37 swin.add(view)
38 view.set_search_column(1)
40 cell = g.CellRendererText()
41 column = g.TreeViewColumn('MIME-Type', cell, text = 0)
42 view.append_column(column)
43 column.set_sort_column_id(0)
45 cell = g.CellRendererText()
46 column = g.TreeViewColumn('Description', cell, text = 1)
47 view.append_column(column)
48 column.set_sort_column_id(1)
50 view.connect('row-activated', self.activate)
52 self.vbox.show_all()
54 def response(self, resp):
55 if resp == EDIT:
56 self.edit_selected(view.get_selection())
57 elif resp == DELETE:
58 self.delete_type(view)
59 elif resp == ADD:
60 if self.add_type:
61 self.add_type.present()
62 else:
63 self.add_type = NewType()
64 def destroyed(widget): self.add_type = None
65 self.add_type.connect('destroy', destroyed)
66 self.add_type.show()
67 else:
68 self.destroy()
69 self.connect('response', response)
71 def changed(selection):
72 model, iter = selection.get_selected()
73 self.set_response_sensitive(EDIT, iter != None)
74 self.set_response_sensitive(DELETE, iter != None)
75 selection = view.get_selection()
76 selection.connect('changed', changed)
77 changed(selection)
79 def delete_type(self, view):
80 model, iter = view.get_selection().get_selected()
81 if not iter:
82 rox.alert('Nothing selected')
83 return
84 type_name = model.get_value(iter, 0)
85 type.delete_type(type_name)
87 def update(self):
88 self.set_title('Scanning... please wait')
89 g.gdk.flush()
91 type.init()
93 self.mime_model.clear()
95 medias = {}
97 def items_cmp(a, b):
98 return cmp(a[1], b[1])
99 def caps(a):
100 return a[:1].upper() + a[1:]
101 items = [(t, caps(t.get_comment())) for t in type.types.values()]
102 items.sort(items_cmp)
103 for t, c in items:
104 iter = self.mime_model.append(None)
105 self.mime_model.set(iter, 1, c, 0, t.get_name())
107 self.set_title('MIME-Editor')
109 for b in edit_boxes.values():
110 if b.mime_type in type.types:
111 b.update()
112 else:
113 b.destroy()
115 def activate(self, view, path, column):
116 iter = self.mime_model.get_iter(path)
117 type_name = self.mime_model.get_value(iter, 0)
118 self.edit(type_name)
120 def edit(self, type_name):
121 if type_name in edit_boxes:
122 edit_boxes[type_name].present()
123 else:
124 box = EditBox(type_name)
125 edit_boxes[type_name] = box
126 def destroy(dialog): del edit_boxes[type_name]
127 box.connect('destroy', destroy)
128 box.show()
130 def edit_selected(self, selection):
131 model, iter = selection.get_selected()
132 if not iter:
133 rox.alert('You need to select a type to edit first')
134 else:
135 type_name = model.get_value(iter, 0)
136 self.edit(type_name)
138 def show_type(self, type_name):
139 model = self.mime_model
140 iter = model.get_iter_first()
141 while iter:
142 name = model.get_value(iter, 0)
143 if name == type_name:
144 path = model.get_path(iter)
145 self.view.set_cursor(path, None, False)
146 return
147 iter = model.iter_next(iter)
149 class EditBox(rox.Dialog):
150 def __init__(self, type_name):
151 rox.Dialog.__init__(self)
152 self.set_has_separator(False)
153 self.set_default_size(-1, 400)
154 self.mime_type = type_name
156 self.set_title('MIME-Editor: %s' % type_name)
158 swin = g.ScrolledWindow()
159 swin.set_border_width(4)
160 swin.set_shadow_type(g.SHADOW_IN)
161 self.vbox.pack_start(swin, True, True, 0)
163 self.model = g.TreeStore(str, object, g.gdk.Color)
164 view = g.TreeView(self.model)
165 self.view = view
166 swin.add(view)
168 cell = g.CellRendererText()
169 column = g.TreeViewColumn(type_name, cell, text = 0, foreground_gdk = 2)
170 view.append_column(column)
172 swin.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
173 self.update()
175 self.add_button(g.STOCK_ADD, ADD)
176 self.add_button(g.STOCK_DELETE, DELETE)
177 self.add_button(g.STOCK_PROPERTIES, EDIT)
178 self.add_button(g.STOCK_CLOSE, g.RESPONSE_CANCEL)
179 def response(w, resp):
180 if resp == ADD:
181 self.add_field(view)
182 elif resp == DELETE:
183 self.delete_field(view)
184 elif resp == EDIT:
185 self.edit_field(view)
186 else:
187 self.destroy()
188 self.connect('response', response)
190 def changed(selection):
191 model, iter = selection.get_selected()
192 on_field = False
193 if iter != None:
194 path = model.get_path(iter)
195 on_field = len(path) > 1
196 self.set_response_sensitive(ADD, iter != None)
197 self.set_response_sensitive(EDIT, on_field)
198 self.set_response_sensitive(DELETE, on_field)
199 selection = view.get_selection()
200 selection.connect('changed', changed)
201 def may_change(path):
202 "Prevent selecting greyed-out lines"
203 iter = self.model.get_iter(path)
204 if self.model.get_value(iter, 2):
205 return False
206 return True
207 selection.set_select_function(may_change)
208 changed(selection)
210 def activate(view, path, column):
211 iter = self.model.get_iter(path)
212 if self.model.get_value(iter, 2):
213 return # Shaded
214 field = self.model.get_value(iter, 1)
215 if len(path) == 1:
216 self.add_new_field(field)
217 else:
218 field.edit()
219 view.connect('row-activated', activate)
221 self.set_default_response(g.RESPONSE_CANCEL)
223 label = g.Label("Shaded entries are provided by system packages and cannot "
224 "be edited or deleted. However, any information you add will "
225 "take precedence over them.")
226 label.set_line_wrap(True)
227 self.vbox.pack_start(label, False, True, 0)
229 self.vbox.show_all()
231 def update(self):
232 grey = self.get_style().fg[g.STATE_INSENSITIVE]
233 def build(parent, fields):
234 fields.sort(lambda a, b: cmp(str(a), str(b)))
235 for field in fields:
236 iter = self.model.append(parent)
237 if field.can_edit():
238 self.model.set(iter, 0, str(field), 1, field)
239 else:
240 self.model.set(iter, 0, str(field), 1, field, 2, grey)
241 build(iter, field.get_sub_fields())
242 t = type.get_type(self.mime_type)
243 self.model.clear()
244 for aspect, getter, klass in [('Name matching', t.get_globs, fields.Glob),
245 ('Contents matching', t.get_magic, fields.Magic),
246 ('XML namespace matching', t.get_xml, fields.XML),
247 ('Others', t.get_others, None),
248 ('Descriptions', t.get_comments, fields.Comment)]:
249 iter = self.model.append(None)
250 if klass:
251 self.model.set(iter, 0, aspect, 1, klass)
252 else:
253 self.model.set(iter, 0, aspect, 1, klass, 2, grey)
254 build(iter, getter())
255 self.view.expand_all()
257 def add_new_field(self, klass):
258 if not klass: return
259 new = klass(type.get_type(self.mime_type))
260 new.edit()
262 def add_field(self, view):
263 model, iter = view.get_selection().get_selected()
264 if not iter:
265 rox.alert('You need to select a group, so I know what kind of thing to add')
266 return
267 path = model.get_path(iter)
268 field = model.get_value(model.get_iter(path), 1)
269 if isinstance(field, fields.Field) and hasattr(field, 'add_sub_field'):
270 field.add_sub_field() # For magic
271 else:
272 field = model.get_value(model.get_iter(path[:1]), 1)
273 self.add_new_field(field)
275 def delete_field(self, view):
276 model, iter = view.get_selection().get_selected()
277 if not iter:
278 rox.alert("Nothing selected!")
279 field = model.get_value(iter, 1)
280 assert field
281 field.delete()
283 def edit_field(self, view):
284 model, iter = view.get_selection().get_selected()
285 if not iter:
286 rox.alert("Nothing selected!")
287 field = model.get_value(iter, 1)
288 assert field
289 field.edit()
291 class NewType(rox.Dialog):
292 def __init__(self):
293 rox.Dialog.__init__(self)
294 self.set_title('Add new MIME type')
296 self.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
297 self.add_button(g.STOCK_ADD, g.RESPONSE_OK)
298 self.set_default_response(g.RESPONSE_OK)
300 vbox = g.VBox(False, 4)
301 self.vbox.pack_start(vbox, True, True, 0)
302 vbox.set_border_width(4)
304 hbox = g.HBox(False, 4)
305 vbox.pack_start(hbox, False, True, 0)
306 label = g.Label("Media type:")
307 hbox.pack_start(label, False, True, 0)
308 combo = g.Combo()
309 combo.set_popdown_strings(["text", "application", "image", "audio",
310 "video", "message", "model"])
311 hbox.pack_start(combo, True, True, 0)
313 hbox = g.HBox(False, 4)
314 vbox.pack_start(hbox, False, True, 0)
315 label = g.Label("Subtype:")
316 hbox.pack_start(label, False, True, 0)
317 entry = g.Entry()
318 entry.set_text('x-my-type')
319 hbox.pack_start(entry, True, True, 0)
320 entry.set_activates_default(True)
322 label = g.Label("Note: the 'text' group should only be used for types that can be "
323 "viewed in a normal text editor. For example, HTML is text/html, "
324 "but Word documents are in the 'application' group.\n"
325 "Unoffical types should start with 'x-'.")
326 label.set_line_wrap(True)
327 vbox.pack_start(label, False, True, 0)
329 self.vbox.show_all()
331 def response(w, resp):
332 if resp == g.RESPONSE_OK:
333 type_name = combo.entry.get_text() + '/' + entry.get_text()
334 if type_name.count('/') != 1 or type_name.count(' '):
335 rox.alert("Invalid MIME type name '%s'" % type_name)
336 return
337 if type_name in type.types:
338 rox.alert("Type '%s' already exists!" % type_name)
339 import __main__
340 __main__.box.show_type(type_name)
341 return
342 type.add_type(type_name)
343 self.destroy()
344 self.connect('response', response)