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