Removed code to get database from Zero Install. It should already be available
[mime-editor.git] / editor.py
blobed66d46c8febf4858f74f01fb885fcf094ce8337
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 self.edit(type_name)
147 return
148 iter = model.iter_next(iter)
150 class EditBox(rox.Dialog):
151 def __init__(self, type_name):
152 rox.Dialog.__init__(self)
153 self.set_has_separator(False)
154 self.set_default_size(-1, 400)
155 self.mime_type = type_name
157 self.set_title('MIME-Editor: %s' % type_name)
159 swin = g.ScrolledWindow()
160 swin.set_border_width(4)
161 swin.set_shadow_type(g.SHADOW_IN)
162 self.vbox.pack_start(swin, True, True, 0)
164 self.model = g.TreeStore(str, object, g.gdk.Color)
165 view = g.TreeView(self.model)
166 self.view = view
167 swin.add(view)
169 cell = g.CellRendererText()
170 column = g.TreeViewColumn(type_name, cell, text = 0, foreground_gdk = 2)
171 view.append_column(column)
173 swin.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
174 self.update()
176 self.add_button(g.STOCK_ADD, ADD)
177 self.add_button(g.STOCK_DELETE, DELETE)
178 self.add_button(g.STOCK_PROPERTIES, EDIT)
179 self.add_button(g.STOCK_CLOSE, g.RESPONSE_CANCEL)
180 def response(w, resp):
181 if resp == ADD:
182 self.add_field(view)
183 elif resp == DELETE:
184 self.delete_field(view)
185 elif resp == EDIT:
186 self.edit_field(view)
187 else:
188 self.destroy()
189 self.connect('response', response)
191 def changed(selection):
192 model, iter = selection.get_selected()
193 on_field = False
194 if iter != None:
195 path = model.get_path(iter)
196 on_field = len(path) > 1
197 self.set_response_sensitive(ADD, iter != None)
198 self.set_response_sensitive(EDIT, on_field)
199 self.set_response_sensitive(DELETE, on_field)
200 selection = view.get_selection()
201 selection.connect('changed', changed)
202 def may_change(path):
203 "Prevent selecting greyed-out lines"
204 iter = self.model.get_iter(path)
205 if self.model.get_value(iter, 2):
206 return False
207 return True
208 selection.set_select_function(may_change)
209 changed(selection)
211 def activate(view, path, column):
212 iter = self.model.get_iter(path)
213 if self.model.get_value(iter, 2):
214 return # Shaded
215 field = self.model.get_value(iter, 1)
216 if len(path) == 1:
217 self.add_new_field(field)
218 else:
219 field.edit()
220 view.connect('row-activated', activate)
222 self.set_default_response(g.RESPONSE_CANCEL)
224 label = g.Label(_("Shaded entries are provided by system packages and cannot "
225 "be edited or deleted. However, any information you add will "
226 "take precedence over them."))
227 label.set_line_wrap(True)
228 self.vbox.pack_start(label, False, True, 0)
230 self.vbox.show_all()
232 def update(self):
233 grey = self.get_style().fg[g.STATE_INSENSITIVE]
234 def build(parent, fields):
235 fields.sort(lambda a, b: cmp(str(a), str(b)))
236 for field in fields:
237 iter = self.model.append(parent)
238 if field.can_edit():
239 self.model.set(iter, 0, str(field), 1, field)
240 else:
241 self.model.set(iter, 0, str(field), 1, field, 2, grey)
242 build(iter, field.get_sub_fields())
243 t = type.get_type(self.mime_type)
244 self.model.clear()
245 for aspect, getter, klass in [(_('Name matching'), t.get_globs, fields.Glob),
246 (_('Contents matching'), t.get_magic, fields.Magic),
247 (_('XML namespace matching'), t.get_xml, fields.XML),
248 (_('Others'), t.get_others, None),
249 (_('Descriptions'), t.get_comments, fields.Comment)]:
250 iter = self.model.append(None)
251 if klass:
252 self.model.set(iter, 0, aspect, 1, klass)
253 else:
254 self.model.set(iter, 0, aspect, 1, klass, 2, grey)
255 build(iter, getter())
256 self.view.expand_all()
258 def add_new_field(self, klass):
259 if not klass: return
260 new = klass(type.get_type(self.mime_type))
261 new.edit()
263 def add_field(self, view):
264 model, iter = view.get_selection().get_selected()
265 if not iter:
266 rox.alert(_('You need to select a group, so I know what kind of thing to add'))
267 return
268 path = model.get_path(iter)
269 field = model.get_value(model.get_iter(path), 1)
270 if isinstance(field, fields.Field) and hasattr(field, 'add_sub_field'):
271 field.add_sub_field() # For magic
272 else:
273 field = model.get_value(model.get_iter(path[:1]), 1)
274 self.add_new_field(field)
276 def delete_field(self, view):
277 model, iter = view.get_selection().get_selected()
278 if not iter:
279 rox.alert(_("Nothing selected!"))
280 field = model.get_value(iter, 1)
281 assert field
282 field.delete()
284 def edit_field(self, view):
285 model, iter = view.get_selection().get_selected()
286 if not iter:
287 rox.alert(_("Nothing selected!"))
288 field = model.get_value(iter, 1)
289 assert field
290 field.edit()
292 class NewType(rox.Dialog):
293 def __init__(self):
294 rox.Dialog.__init__(self)
295 self.set_position(g.WIN_POS_MOUSE)
296 self.set_title(_('Add new MIME type'))
297 self.set_has_separator(False)
299 self.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
300 self.add_button(g.STOCK_ADD, g.RESPONSE_OK)
301 self.set_default_response(g.RESPONSE_OK)
303 tips = g.Tooltips()
304 self.tips = tips # (pygtk refcount bug again)
306 vbox = g.VBox(False, 4)
307 self.vbox.pack_start(vbox, True, True, 0)
308 vbox.set_border_width(4)
310 def pair(label, widget):
311 hbox = g.HBox(False, 0)
312 vbox.pack_start(hbox, False, True, 0)
313 hbox.pack_start(g.Label(label + ': '), False, True, 0)
314 hbox.pack_start(widget, True, True, 0)
316 combo = g.Combo()
317 combo.set_popdown_strings(["text", "application", "image", "audio",
318 "video", "message", "model"])
319 pair(_('Media type'), combo)
320 tips.set_tip(combo.entry, _("Choose what sort of files this type describes.\n"
321 "Note that the 'text' group should only be used "
322 "for types that can be viewed in a normal text "
323 "editor. For example, HTML is text/html, "
324 "but Word documents are in the 'application' group."))
326 entry = g.Entry()
327 entry.set_text('x-my-type')
328 pair(_('Subtype'), entry)
329 entry.set_activates_default(True)
330 tips.set_tip(entry, _("Note: unoffical types should start with 'x-'"))
332 self.comment = g.Entry()
333 pair(_('Description'), self.comment)
334 tips.set_tip(self.comment, _("A brief description of this type. For example:\n"
335 "OpenOffice Spreadsheet"))
336 self.comment.set_activates_default(True)
338 self.glob = g.Entry()
339 self.glob.set_text('*.myapp')
340 pair(_('For files named'), self.glob)
341 self.glob.set_activates_default(True)
342 tips.set_tip(self.glob,
343 _("A rule to spot which files should have this type. Use '*' to "
344 "mean 'anything'. For example, to match anything ending in '.app' "
345 "use:\n"
346 "*.app\n"
347 "You can leave this blank, or add extra patterns later."))
349 self.vbox.pack_start(g.Label(_('Hold the pointer over any field\n'
350 'for more information.')), False, True, 2)
352 self.vbox.show_all()
354 def response(w, resp):
355 if resp == g.RESPONSE_OK:
356 type_name = combo.entry.get_text() + '/' + entry.get_text()
357 if type_name.count('/') != 1 or type_name.count(' '):
358 rox.alert(_("Invalid MIME type name '%s'") % type_name)
359 return
360 if type_name in type.types:
361 rox.alert(_("Type '%s' already exists!") % type_name)
362 import __main__
363 __main__.box.show_type(type_name)
364 return
365 if not self.comment.get_text():
366 rox.alert(_("Please provide a description for this type"))
367 return
368 type.add_type(type_name, self.comment.get_text(),
369 self.glob.get_text())
370 self.destroy()
371 self.connect('response', response)