Moved implementation box code to its own file.
[0publish-gui.git] / main.py
blob454a24cd6bcbc25a2d0ce051f8b99fcf3172f2fb
1 from xml.dom import Node, minidom
3 import rox, os, pango, sys, textwrap, traceback, subprocess, time, urlparse
4 from rox import g, tasks, loading
5 import gtk.glade
7 import signing
8 import archive
9 from implementation import ImplementationProperties
10 from xmltools import *
12 from zeroinstall.zerostore import unpack
14 RESPONSE_SAVE = 0
15 RESPONSE_SAVE_AND_TEST = 1
17 gladefile = os.path.join(rox.app_dir, '0publish-gui.glade')
19 def choose_feed():
20 tree = gtk.glade.XML(gladefile, 'no_file_specified')
21 box = tree.get_widget('no_file_specified')
22 tree.get_widget('new_button').grab_focus()
23 resp = box.run()
24 box.destroy()
25 if resp == 0:
26 chooser = g.FileChooserDialog('Choose a location for the new feed',
27 None, g.FILE_CHOOSER_ACTION_SAVE)
28 chooser.set_current_name('MyProg.xml')
29 chooser.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
30 chooser.add_button(g.STOCK_NEW, g.RESPONSE_OK)
31 elif resp == 1:
32 chooser = g.FileChooserDialog('Choose the feed to edit',
33 None, g.FILE_CHOOSER_ACTION_OPEN)
34 chooser.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
35 chooser.add_button(g.STOCK_OPEN, g.RESPONSE_OK)
36 else:
37 sys.exit(1)
38 chooser.set_default_response(g.RESPONSE_OK)
39 if chooser.run() != g.RESPONSE_OK:
40 sys.exit(1)
41 path = chooser.get_filename()
42 chooser.destroy()
43 return FeedEditor(path)
46 emptyFeed = """<?xml version='1.0'?>
47 <interface xmlns="%s">
48 <name>Name</name>
49 </interface>
50 """ % (XMLNS_INTERFACE)
52 class FeedEditor(loading.XDSLoader):
53 def __init__(self, pathname):
54 loading.XDSLoader.__init__(self, None)
56 self.pathname = pathname
58 self.wTree = gtk.glade.XML(gladefile, 'main')
59 self.window = self.wTree.get_widget('main')
60 self.window.connect('destroy', rox.toplevel_unref)
61 self.xds_proxy_for(self.window)
63 help = gtk.glade.XML(gladefile, 'main_help')
64 help_box = help.get_widget('main_help')
65 help_box.set_default_size(g.gdk.screen_width() / 4,
66 g.gdk.screen_height() / 4)
67 help_box.connect('delete-event', lambda box, ev: True)
68 help_box.connect('response', lambda box, resp: box.hide())
70 def resp(box, resp):
71 if resp == g.RESPONSE_HELP:
72 help_box.present()
73 elif resp == RESPONSE_SAVE_AND_TEST:
74 self.save(self.test)
75 elif resp == RESPONSE_SAVE:
76 self.save()
77 else:
78 box.destroy()
79 self.window.connect('response', resp)
80 rox.toplevel_ref()
82 keys = signing.get_secret_keys()
83 key_menu = self.wTree.get_widget('feed_key')
84 key_model = g.ListStore(str, str)
85 key_menu.set_model(key_model)
86 cell = g.CellRendererText()
87 cell.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE)
88 key_menu.pack_start(cell)
89 key_menu.add_attribute(cell, 'text', 1)
91 key_model.append((None, '(unsigned)'))
92 for k in keys:
93 key_model.append(k)
95 self.impl_model = g.TreeStore(str, object)
96 impl_tree = self.wTree.get_widget('impl_tree')
97 impl_tree.set_model(self.impl_model)
98 text = g.CellRendererText()
99 column = g.TreeViewColumn('', text)
100 column.add_attribute(text, 'text', 0)
101 impl_tree.append_column(column)
103 impl_tree.get_selection().set_mode(g.SELECTION_BROWSE)
105 if os.path.exists(self.pathname):
106 data, _, self.key = signing.check_signature(self.pathname)
107 self.doc = minidom.parseString(data)
108 self.update_fields()
109 else:
110 self.doc = minidom.parseString(emptyFeed)
111 self.key = None
112 key_menu.set_active(0)
114 #self.attr_model = g.ListStore(str, str)
115 #attributes = self.wTree.get_widget('attributes')
116 #attributes.set_model(self.attr_model)
117 #text = g.CellRendererText()
118 #for title in ['Attribute', 'Value']:
119 # column = g.TreeViewColumn(title, text)
120 # attributes.append_column(column)
122 self.wTree.get_widget('add_implementation').connect('clicked', lambda b: self.add_version())
123 self.wTree.get_widget('add_archive').connect('clicked', lambda b: self.add_archive())
124 self.wTree.get_widget('edit_properties').connect('clicked', lambda b: self.edit_version())
125 self.wTree.get_widget('remove').connect('clicked', lambda b: self.remove_version())
126 impl_tree.connect('row-activated', lambda tv, path, col: self.edit_version(path))
128 self.wTree.get_widget('notebook').next_page()
130 def add_version(self):
131 ImplementationProperties(self)
133 def edit_version(self, path = None, element = None):
134 assert not (path and element)
136 if element:
137 pass
138 elif path is None:
139 element = self.get_selected()
140 else:
141 element = self.impl_model[path][1]
143 ImplementationProperties(self, element)
145 def update_fields(self):
146 root = self.doc.documentElement
148 def set(name):
149 value = singleton_text(root, name)
150 if value:
151 self.wTree.get_widget('feed_' + name).set_text(value)
152 set('name')
153 set('summary')
154 set('homepage')
156 for icon in children(root, 'icon'):
157 if icon.getAttribute('type') == 'image/png':
158 href = icon.getAttribute('href')
159 self.wTree.get_widget('feed_icon').set_text(href)
160 break
162 description = singleton_text(root, 'description') or ''
163 paragraphs = [format_para(p) for p in description.split('\n\n')]
164 buffer = self.wTree.get_widget('feed_description').get_buffer()
165 buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
166 buffer.insert_at_cursor('\n'.join(paragraphs))
168 key_menu = self.wTree.get_widget('feed_key')
169 model = key_menu.get_model()
170 if self.key:
171 i = 0
172 for line in model:
173 if line[0] == self.key:
174 break
175 i += 1
176 else:
177 model.append((self.key, 'Missing key (%s)' % self.key))
178 key_menu.set_active(i)
179 else:
180 key_menu.set_active(0)
182 self.update_version_model()
184 def add_archives(self, impl_element, iter):
185 for child in child_elements(impl_element):
186 if child.namespaceURI != XMLNS_INTERFACE: continue
187 if child.localName == 'archive':
188 self.impl_model.append(iter, ['Archive ' + child.getAttribute('href'), child])
189 else:
190 self.impl_model.append(iter, ['<%s>' % child.localName, child])
192 def update_version_model(self):
193 self.impl_model.clear()
194 impl_tree = self.wTree.get_widget('impl_tree')
195 to_expand = []
197 def add_impls(elem, iter, attrs):
198 """Add all groups, implementations and requirements in elem"""
200 for x in child_elements(elem):
201 if x.namespaceURI != XMLNS_INTERFACE: continue
203 if x.localName == 'requires':
204 req_iface = x.getAttribute('interface')
205 new = self.impl_model.append(iter, ['Requires %s' % req_iface, x])
207 if x.localName not in ('implementation', 'group'): continue
209 new_attrs = attrs.copy()
210 attributes = x.attributes
211 for i in range(attributes.length):
212 a = attributes.item(i)
213 new_attrs[str(a.name)] = a.value
215 if x.localName == 'implementation':
216 version = new_attrs.get('version', '(missing version number)')
217 new = self.impl_model.append(iter, ['Version %s' % version, x])
218 self.add_archives(x, new)
219 elif x.localName == 'group':
220 new = self.impl_model.append(iter, ['Group', x])
221 to_expand.append(self.impl_model.get_path(new))
222 add_impls(x, new, new_attrs)
224 add_impls(self.doc.documentElement, None, attrs = {})
226 for path in to_expand:
227 impl_tree.expand_row(path, False)
229 def test(self):
230 child = os.fork()
231 if child == 0:
232 try:
233 try:
234 # We are the child
235 # Spawn a grandchild and exit
236 subprocess.Popen(['0launch', '--gui', self.pathname])
237 os._exit(0)
238 except:
239 traceback.print_exc()
240 finally:
241 os._exit(1)
242 pid, status = os.waitpid(child, 0)
243 assert pid == child
244 if status:
245 raise Exception('Failed to run 0launch - status code %d' % status)
247 def update_doc(self):
248 root = self.doc.documentElement
249 def update(name, required = False, attrs = {}, value_attr = None):
250 widget = self.wTree.get_widget('feed_' + name)
251 if isinstance(widget, g.TextView):
252 buffer = widget.get_buffer()
253 text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
254 paras = ['\n'.join(textwrap.wrap(para, 80)) for para in text.split('\n') if para.strip()]
255 value = '\n' + '\n\n'.join(paras)
256 else:
257 value = widget.get_text()
258 elems = list(children(root, name, attrs = attrs))
259 if value:
260 if elems:
261 elem = elems[0]
262 else:
263 elem = create_element(root, name,
264 before = ['group', 'implementation', 'requires'])
265 for x in attrs:
266 elem.setAttribute(x, attrs[x])
267 if value_attr:
268 elem.setAttribute(value_attr, value)
269 set_data(elem, None)
270 else:
271 set_data(elem, value)
272 else:
273 if required:
274 raise Exception('Missing required field "%s"' % name)
275 for e in elems:
276 remove_element(e)
278 update('name', True)
279 update('summary', True)
280 update('description', True)
281 update('homepage')
282 update('icon', attrs = {'type': 'image/png'}, value_attr = 'href')
284 uri = self.wTree.get_widget('feed_url').get_text()
285 if uri:
286 root.setAttribute('uri', uri)
287 elif root.hasAttribute('uri'):
288 root.removeAttribute('uri')
290 key_menu = self.wTree.get_widget('feed_key')
291 key_model = key_menu.get_model()
292 self.key = key_model[key_menu.get_active()][0]
294 def save(self, callback = None):
295 self.update_doc()
296 if self.key:
297 sign = signing.sign_xml
298 else:
299 sign = signing.sign_unsigned
300 data = self.doc.toxml() + '\n'
302 gen = sign(self.pathname, data, self.key, callback)
303 # May require interaction to get the pass-phrase, so run in the background...
304 if gen:
305 tasks.Task(gen)
307 def add_archive(self):
308 archive.AddArchiveBox(self)
310 def xds_load_from_file(self, path):
311 archive.AddArchiveBox(self, local_archive = path)
313 def remove_version(self, path = None):
314 elem = self.get_selected()
315 remove_element(elem)
316 self.update_version_model()
318 def get_selected(self):
319 tree = self.wTree.get_widget('impl_tree')
320 sel = tree.get_selection()
321 model, iter = sel.get_selected()
322 if not iter:
323 raise Exception('Select something first!')
324 return model[iter][1]
326 def find_implementation(self, id):
327 def find_impl(parent):
328 for x in child_elements(parent):
329 if x.namespaceURI != XMLNS_INTERFACE: continue
330 if x.localName == 'group':
331 sub = find_impl(x)
332 if sub: return sub
333 elif x.localName == 'implementation':
334 if x.getAttribute('id') == id:
335 return x
336 return find_impl(self.doc.documentElement)