Warn about GnuPG < 1.4.6 (CVE-2006-6235).
[zeroinstall.git] / zeroinstall / 0launch-gui / iface_browser.py
blobd38881ecae69c9f43f93e67ac26e10678790a72f
1 import gtk, gobject
3 from zeroinstall.injector import basedir
4 from zeroinstall.injector.model import Interface, escape
5 import properties
6 from treetips import TreeTips
7 from gui import policy, pretty_size
8 from logging import warn
10 def _stability(impl):
11 assert impl
12 if impl.user_stability is None:
13 return impl.upstream_stability
14 return _("%s (was %s)") % (impl.user_stability, impl.upstream_stability)
16 ICON_SIZE = 20.0
17 CELL_TEXT_INDENT = int(ICON_SIZE) + 4
19 class InterfaceTips(TreeTips):
20 def get_tooltip_text(self, item):
21 interface, model_column = item
22 assert interface
23 if model_column == InterfaceBrowser.INTERFACE_NAME:
24 return _("Full name: %s") % interface.uri
25 elif model_column == InterfaceBrowser.SUMMARY:
26 if not interface.description:
27 return None
28 first_para = interface.description.split('\n\n', 1)[0]
29 return first_para.replace('\n', ' ')
31 impl = policy.implementation.get(interface, None)
32 if not impl:
33 return _("No suitable implementation was found. Check the "
34 "interface properties to find out why.")
36 if model_column == InterfaceBrowser.VERSION:
37 text = _("Currently preferred version: %s (%s)") % \
38 (impl.get_version(), _stability(impl))
39 old_impl = policy.original_implementation.get(interface, None)
40 if old_impl is not None and old_impl is not impl:
41 text += _('\nPreviously preferred version: %s (%s)') % \
42 (old_impl.get_version(), _stability(old_impl))
43 return text
45 assert model_column == InterfaceBrowser.DOWNLOAD_SIZE
47 if policy.get_cached(impl):
48 return _("This version is already stored on your computer.")
49 else:
50 src = policy.get_best_source(impl)
51 if not src:
52 return _("No downloads available!")
53 return _("Need to download %s (%s bytes)") % \
54 (pretty_size(src.size), src.size)
56 tips = InterfaceTips()
58 class IconAndTextRenderer(gtk.GenericCellRenderer):
59 __gproperties__ = {
60 "image": (gobject.TYPE_OBJECT, "Image", "Image", gobject.PARAM_READWRITE),
61 "text": (gobject.TYPE_STRING, "Text", "Text", "-", gobject.PARAM_READWRITE),
64 def do_set_property(self, prop, value):
65 setattr(self, prop.name, value)
67 def on_get_size(self, widget, cell_area, layout = None):
68 if not layout:
69 layout = widget.create_pango_layout(self.text)
70 a, rect = layout.get_pixel_extents()
72 pixmap_height = self.image.get_height()
74 both_height = max(rect[1] + rect[3], pixmap_height)
76 return (0, 0,
77 rect[0] + rect[2] + CELL_TEXT_INDENT,
78 both_height)
80 def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
81 layout = widget.create_pango_layout(self.text)
82 a, rect = layout.get_pixel_extents()
84 if flags & gtk.CELL_RENDERER_SELECTED:
85 state = gtk.STATE_SELECTED
86 elif flags & gtk.CELL_RENDERER_PRELIT:
87 state = gtk.STATE_PRELIGHT
88 else:
89 state = gtk.STATE_NORMAL
91 image_y = int(0.5 * (cell_area.height - self.image.get_height()))
92 window.draw_pixbuf(widget.style.white_gc, self.image, 0, 0,
93 cell_area.x,
94 cell_area.y + image_y)
96 text_y = int(0.5 * (cell_area.height - (rect[1] + rect[3])))
98 widget.style.paint_layout(window, state, True,
99 expose_area, widget, "cellrenderertext",
100 cell_area.x + CELL_TEXT_INDENT,
101 cell_area.y + text_y,
102 layout)
104 if gtk.pygtk_version < (2, 8, 6):
105 # Note sure exactly which versions need this,
106 # but my 2.8.6 doesn't.
107 gobject.type_register(IconAndTextRenderer)
109 class InterfaceBrowser(gtk.ScrolledWindow):
110 model = None
111 root = None
112 edit_properties = None
113 cached_icon = None
115 INTERFACE = 0
116 INTERFACE_NAME = 1
117 VERSION = 2
118 SUMMARY = 3
119 DOWNLOAD_SIZE = 4
120 ICON = 5
122 columns = [(_('Interface'), INTERFACE_NAME),
123 (_('Version'), VERSION),
124 (_('Fetch'), DOWNLOAD_SIZE),
125 (_('Description'), SUMMARY)]
127 def __init__(self):
128 gtk.ScrolledWindow.__init__(self)
130 self.cached_icon = {} # URI -> GdkPixbuf
131 self.default_icon = self.style.lookup_icon_set(gtk.STOCK_EXECUTE).render_icon(self.style,
132 gtk.TEXT_DIR_NONE, gtk.STATE_NORMAL, gtk.ICON_SIZE_SMALL_TOOLBAR, self, None)
134 self.edit_properties = gtk.Action('edit_properties',
135 'Interface Properties...',
136 'Set which implementation of this interface to use.',
137 gtk.STOCK_PROPERTIES)
138 self.edit_properties.set_property('sensitive', False)
140 self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
141 self.set_shadow_type(gtk.SHADOW_IN)
143 self.model = gtk.TreeStore(object, str, str, str, str, gtk.gdk.Pixbuf)
144 self.tree_view = tree_view = gtk.TreeView(self.model)
146 column_objects = []
148 text = gtk.CellRendererText()
150 for name, model_column in self.columns:
151 if model_column == InterfaceBrowser.INTERFACE_NAME:
152 column = gtk.TreeViewColumn(name, IconAndTextRenderer(),
153 text = model_column,
154 image = InterfaceBrowser.ICON)
155 else:
156 column = gtk.TreeViewColumn(name, text, text = model_column)
157 tree_view.append_column(column)
158 column_objects.append(column)
160 self.add(tree_view)
161 tree_view.show()
163 tree_view.set_enable_search(True)
165 selection = tree_view.get_selection()
167 def motion(tree_view, ev):
168 if ev.window is not tree_view.get_bin_window():
169 return False
170 pos = tree_view.get_path_at_pos(int(ev.x), int(ev.y))
171 if pos:
172 path = pos[0]
173 try:
174 col_index = column_objects.index(pos[1])
175 except ValueError:
176 tips.hide()
177 else:
178 col = self.columns[col_index][1]
179 row = self.model[path]
180 item = (row[InterfaceBrowser.INTERFACE], col)
181 if item != tips.item:
182 tips.prime(tree_view, item)
183 else:
184 tips.hide()
186 tree_view.connect('motion-notify-event', motion)
187 tree_view.connect('leave-notify-event', lambda tv, ev: tips.hide())
189 def sel_changed(sel):
190 store, iter = sel.get_selected()
191 self.edit_properties.set_property('sensitive', iter != None)
192 selection.connect('changed', sel_changed)
194 def button_press(tree_view, bev):
195 if bev.button != 1 or bev.type != gtk.gdk._2BUTTON_PRESS:
196 return False
197 pos = tree_view.get_path_at_pos(int(bev.x), int(bev.y))
198 if not pos:
199 return False
200 path, col, x, y = pos
201 properties.edit(self.model[path][InterfaceBrowser.INTERFACE])
202 tree_view.connect('button-press-event', button_press)
204 def edit_selected(action):
205 store, iter = selection.get_selected()
206 assert iter
207 properties.edit(self.model[iter][InterfaceBrowser.INTERFACE])
208 self.edit_properties.connect('activate', edit_selected)
210 self.connect('destroy', lambda s: policy.watchers.remove(self.build_tree))
211 policy.watchers.append(self.build_tree)
213 def set_root(self, root):
214 assert isinstance(root, Interface)
215 self.root = root
216 policy.recalculate() # Calls build_tree
218 def get_icon(self, iface):
219 """Get an icon for this interface. If the icon is in the cache, use that.
220 If not, start a download. If we already started a download (successful or
221 not) do nothing. Returns None if no icon is currently available."""
222 try:
223 return self.cached_icon[iface.uri]
224 except KeyError:
225 if not hasattr(policy, 'get_icon_path'):
226 return None # injector < 0.19
227 path = policy.get_icon_path(iface)
228 if path:
229 try:
230 loader = gtk.gdk.PixbufLoader('png')
231 try:
232 loader.write(file(path).read())
233 finally:
234 loader.close()
235 icon = loader.get_pixbuf()
236 except Exception, ex:
237 warn("Failed to load cached PNG icon: %s", ex)
238 return None
239 w = icon.get_width()
240 h = icon.get_height()
241 scale = max(w, h, 1) / ICON_SIZE
242 icon = icon.scale_simple(int(w / scale),
243 int(h / scale),
244 gtk.gdk.INTERP_BILINEAR)
245 self.cached_icon[iface.uri] = icon
246 return icon
248 return None
250 def build_tree(self):
251 if policy.original_implementation is None:
252 policy.set_original_implementations()
254 done = {} # Detect cycles
256 self.model.clear()
257 parent = None
258 def add_node(parent, iface):
259 if iface in done:
260 return
261 done[iface] = True
263 iter = self.model.append(parent)
264 self.model[iter][InterfaceBrowser.INTERFACE] = iface
265 self.model[iter][InterfaceBrowser.INTERFACE_NAME] = iface.get_name()
266 self.model[iter][InterfaceBrowser.SUMMARY] = iface.summary
267 self.model[iter][InterfaceBrowser.ICON] = self.get_icon(iface) or self.default_icon
269 impl = policy.implementation.get(iface, None)
270 if impl:
271 old_impl = policy.original_implementation.get(iface, None)
272 version_str = impl.get_version()
273 if old_impl is not None and old_impl is not impl:
274 version_str += " (was " + old_impl.get_version() + ")"
275 self.model[iter][InterfaceBrowser.VERSION] = version_str
277 if policy.get_cached(impl):
278 if impl.id.startswith('/'):
279 fetch = '(local)'
280 else:
281 fetch = '(cached)'
282 else:
283 src = policy.get_best_source(impl)
284 if src:
285 fetch = pretty_size(src.size)
286 else:
287 fetch = '(unavailable)'
288 self.model[iter][InterfaceBrowser.DOWNLOAD_SIZE] = fetch
289 for child in impl.dependencies.values():
290 add_node(iter, policy.get_interface(child.interface))
291 else:
292 self.model[iter][InterfaceBrowser.VERSION] = '(choose)'
293 add_node(None, self.root)
294 self.tree_view.expand_all()