3 from zeroinstall
.support
import basedir
, tasks
, pretty_size
4 from zeroinstall
.injector
.iface_cache
import iface_cache
5 from zeroinstall
.injector
import model
7 from treetips
import TreeTips
8 from zeroinstall
import support
9 from logging
import warn
13 if impl
.user_stability
is None:
14 return impl
.upstream_stability
15 return _("%s (was %s)") % (impl
.user_stability
, impl
.upstream_stability
)
18 CELL_TEXT_INDENT
= int(ICON_SIZE
) + 4
20 class InterfaceTips(TreeTips
):
23 def __init__(self
, mainwindow
):
24 self
.mainwindow
= mainwindow
26 def get_tooltip_text(self
, item
):
27 interface
, model_column
= item
29 if model_column
== InterfaceBrowser
.INTERFACE_NAME
:
30 return _("Full name: %s") % interface
.uri
31 elif model_column
== InterfaceBrowser
.SUMMARY
:
32 if not interface
.description
:
34 first_para
= interface
.description
.split('\n\n', 1)[0]
35 return first_para
.replace('\n', ' ')
37 impl
= self
.mainwindow
.policy
.implementation
.get(interface
, None)
39 return _("No suitable implementation was found. Check the "
40 "interface properties to find out why.")
42 if model_column
== InterfaceBrowser
.VERSION
:
43 text
= _("Currently preferred version: %s (%s)") % \
44 (impl
.get_version(), _stability(impl
))
45 old_impl
= self
.mainwindow
.original_implementation
.get(interface
, None)
46 if old_impl
is not None and old_impl
is not impl
:
47 text
+= _('\nPreviously preferred version: %s (%s)') % \
48 (old_impl
.get_version(), _stability(old_impl
))
51 assert model_column
== InterfaceBrowser
.DOWNLOAD_SIZE
53 if self
.mainwindow
.policy
.get_cached(impl
):
54 return _("This version is already stored on your computer.")
56 src
= self
.mainwindow
.policy
.fetcher
.get_best_source(impl
)
58 return _("No downloads available!")
59 return _("Need to download %s (%s bytes)") % \
60 (support
.pretty_size(src
.size
), src
.size
)
62 class IconAndTextRenderer(gtk
.GenericCellRenderer
):
64 "image": (gobject
.TYPE_OBJECT
, "Image", "Image", gobject
.PARAM_READWRITE
),
65 "text": (gobject
.TYPE_STRING
, "Text", "Text", "-", gobject
.PARAM_READWRITE
),
68 def do_set_property(self
, prop
, value
):
69 setattr(self
, prop
.name
, value
)
71 def on_get_size(self
, widget
, cell_area
, layout
= None):
73 layout
= widget
.create_pango_layout(self
.text
)
74 a
, rect
= layout
.get_pixel_extents()
76 pixmap_height
= self
.image
.get_height()
78 both_height
= max(rect
[1] + rect
[3], pixmap_height
)
81 rect
[0] + rect
[2] + CELL_TEXT_INDENT
,
84 def on_render(self
, window
, widget
, background_area
, cell_area
, expose_area
, flags
):
85 layout
= widget
.create_pango_layout(self
.text
)
86 a
, rect
= layout
.get_pixel_extents()
88 if flags
& gtk
.CELL_RENDERER_SELECTED
:
89 state
= gtk
.STATE_SELECTED
90 elif flags
& gtk
.CELL_RENDERER_PRELIT
:
91 state
= gtk
.STATE_PRELIGHT
93 state
= gtk
.STATE_NORMAL
95 image_y
= int(0.5 * (cell_area
.height
- self
.image
.get_height()))
96 window
.draw_pixbuf(widget
.style
.white_gc
, self
.image
, 0, 0,
98 cell_area
.y
+ image_y
)
100 text_y
= int(0.5 * (cell_area
.height
- (rect
[1] + rect
[3])))
102 widget
.style
.paint_layout(window
, state
, True,
103 expose_area
, widget
, "cellrenderertext",
104 cell_area
.x
+ CELL_TEXT_INDENT
,
105 cell_area
.y
+ text_y
,
108 if gtk
.pygtk_version
< (2, 8, 0):
109 # Note sure exactly which versions need this.
110 # 2.8.0 gives a warning if you include it, though.
111 gobject
.type_register(IconAndTextRenderer
)
113 class InterfaceBrowser
:
116 edit_properties
= None
119 original_implementation
= None
128 columns
= [(_('Interface'), INTERFACE_NAME
),
129 (_('Version'), VERSION
),
130 (_('Fetch'), DOWNLOAD_SIZE
),
131 (_('Description'), SUMMARY
)]
133 def __init__(self
, policy
, widgets
):
134 tips
= InterfaceTips(self
)
136 tree_view
= widgets
.get_widget('components')
139 self
.cached_icon
= {} # URI -> GdkPixbuf
140 self
.default_icon
= tree_view
.style
.lookup_icon_set(gtk
.STOCK_EXECUTE
).render_icon(tree_view
.style
,
141 gtk
.TEXT_DIR_NONE
, gtk
.STATE_NORMAL
, gtk
.ICON_SIZE_SMALL_TOOLBAR
, tree_view
, None)
143 self
.edit_properties
= widgets
.get_widget('properties')
144 self
.edit_properties
.set_property('sensitive', False)
146 self
.model
= gtk
.TreeStore(object, str, str, str, str, gtk
.gdk
.Pixbuf
)
147 self
.tree_view
= tree_view
148 tree_view
.set_model(self
.model
)
152 text
= gtk
.CellRendererText()
154 for name
, model_column
in self
.columns
:
155 if model_column
== InterfaceBrowser
.INTERFACE_NAME
:
156 column
= gtk
.TreeViewColumn(name
, IconAndTextRenderer(),
158 image
= InterfaceBrowser
.ICON
)
160 column
= gtk
.TreeViewColumn(name
, text
, text
= model_column
)
161 tree_view
.append_column(column
)
162 column_objects
.append(column
)
164 tree_view
.set_enable_search(True)
166 selection
= tree_view
.get_selection()
168 def motion(tree_view
, ev
):
169 if ev
.window
is not tree_view
.get_bin_window():
171 pos
= tree_view
.get_path_at_pos(int(ev
.x
), int(ev
.y
))
175 col_index
= column_objects
.index(pos
[1])
179 col
= self
.columns
[col_index
][1]
180 row
= self
.model
[path
]
181 item
= (row
[InterfaceBrowser
.INTERFACE
], col
)
182 if item
!= tips
.item
:
183 tips
.prime(tree_view
, item
)
187 tree_view
.connect('motion-notify-event', motion
)
188 tree_view
.connect('leave-notify-event', lambda tv
, ev
: tips
.hide())
190 def sel_changed(sel
):
191 store
, iter = sel
.get_selected()
192 self
.edit_properties
.set_property('sensitive', iter != None)
193 selection
.connect('changed', sel_changed
)
195 def button_press(tree_view
, bev
):
196 if bev
.button
== 3 and bev
.type == gtk
.gdk
.BUTTON_PRESS
:
197 pos
= tree_view
.get_path_at_pos(int(bev
.x
), int(bev
.y
))
200 path
, col
, x
, y
= pos
201 selection
.select_path(path
)
202 iface
= self
.model
[path
][InterfaceBrowser
.INTERFACE
]
203 self
.show_popup_menu(iface
, bev
)
205 if bev
.button
!= 1 or bev
.type != gtk
.gdk
._2BUTTON
_PRESS
:
207 pos
= tree_view
.get_path_at_pos(int(bev
.x
), int(bev
.y
))
210 path
, col
, x
, y
= pos
211 properties
.edit(policy
, self
.model
[path
][InterfaceBrowser
.INTERFACE
])
212 tree_view
.connect('button-press-event', button_press
)
214 def edit_selected(action
):
215 store
, iter = selection
.get_selected()
217 properties
.edit(policy
, self
.model
[iter][InterfaceBrowser
.INTERFACE
])
218 self
.edit_properties
.connect('clicked', edit_selected
)
220 tree_view
.connect('destroy', lambda s
: policy
.watchers
.remove(self
.build_tree
))
221 policy
.watchers
.append(self
.build_tree
)
223 def set_root(self
, root
):
224 assert isinstance(root
, model
.Interface
)
227 def get_icon(self
, iface
):
228 """Get an icon for this interface. If the icon is in the cache, use that.
229 If not, start a download. If we already started a download (successful or
230 not) do nothing. Returns None if no icon is currently available."""
232 return self
.cached_icon
[iface
.uri
]
234 path
= iface_cache
.get_icon_path(iface
)
237 loader
= gtk
.gdk
.PixbufLoader('png')
239 loader
.write(file(path
).read())
242 icon
= loader
.get_pixbuf()
243 assert icon
, "Failed to load cached PNG icon data"
244 except Exception, ex
:
245 warn("Failed to load cached PNG icon: %s", ex
)
248 h
= icon
.get_height()
249 scale
= max(w
, h
, 1) / ICON_SIZE
250 icon
= icon
.scale_simple(int(w
/ scale
),
252 gtk
.gdk
.INTERP_BILINEAR
)
253 self
.cached_icon
[iface
.uri
] = icon
256 # Try to download the icon
257 fetcher
= self
.policy
.download_icon(iface
)
260 def update_display():
265 except Exception, ex
:
267 traceback
.print_exc()
268 self
.policy
.handler
.report_error(ex
)
273 def build_tree(self
):
274 if self
.original_implementation
is None:
275 self
.set_original_implementations()
277 done
= {} # Detect cycles
281 def add_node(parent
, iface
):
286 iter = self
.model
.append(parent
)
287 self
.model
[iter][InterfaceBrowser
.INTERFACE
] = iface
288 self
.model
[iter][InterfaceBrowser
.INTERFACE_NAME
] = iface
.get_name()
289 self
.model
[iter][InterfaceBrowser
.SUMMARY
] = iface
.summary
290 self
.model
[iter][InterfaceBrowser
.ICON
] = self
.get_icon(iface
) or self
.default_icon
292 impl
= self
.policy
.implementation
.get(iface
, None)
294 old_impl
= self
.original_implementation
.get(iface
, None)
295 version_str
= impl
.get_version()
296 if old_impl
is not None and old_impl
is not impl
:
297 version_str
+= " (was " + old_impl
.get_version() + ")"
298 self
.model
[iter][InterfaceBrowser
.VERSION
] = version_str
300 if self
.policy
.get_cached(impl
):
301 if impl
.id.startswith('/'):
303 elif impl
.id.startswith('package:'):
308 src
= self
.policy
.fetcher
.get_best_source(impl
)
310 fetch
= support
.pretty_size(src
.size
)
312 fetch
= '(unavailable)'
313 self
.model
[iter][InterfaceBrowser
.DOWNLOAD_SIZE
] = fetch
314 if hasattr(impl
, 'requires'):
315 children
= impl
.requires
317 children
= impl
.dependencies
319 for child
in children
:
320 if isinstance(child
, model
.InterfaceDependency
):
321 add_node(iter, iface_cache
.get_interface(child
.interface
))
323 child_iter
= self
.model
.append(parent
)
324 self
.model
[child_iter
][InterfaceBrowser
.INTERFACE_NAME
] = '?'
325 self
.model
[child_iter
][InterfaceBrowser
.SUMMARY
] = \
326 'Unknown dependency type : %s' % child
327 self
.model
[child_iter
][InterfaceBrowser
.ICON
] = self
.default_icon
329 self
.model
[iter][InterfaceBrowser
.VERSION
] = '(choose)'
330 add_node(None, self
.root
)
331 self
.tree_view
.expand_all()
333 def show_popup_menu(self
, iface
, bev
):
336 if properties
.have_source_for(self
.policy
, iface
):
339 compile.compile(self
.policy
, iface
)
344 for label
, cb
in [(_('Show Feeds'), lambda: properties
.edit(self
.policy
, iface
)),
345 (_('Show Versions'), lambda: properties
.edit(self
.policy
, iface
, show_versions
= True)),
346 (_('Report a Bug...'), lambda: bugs
.report_bug(self
.policy
, iface
)),
347 (_('Compile...'), compile_cb
)]:
348 item
= gtk
.MenuItem(label
)
350 item
.connect('activate', lambda item
, cb
=cb
: cb())
352 item
.set_sensitive(False)
355 menu
.popup(None, None, None, bev
.button
, bev
.time
)
357 def set_original_implementations(self
):
358 assert self
.original_implementation
is None
359 self
.original_implementation
= self
.policy
.implementation
.copy()
361 def update_download_status(self
):
362 """Called at regular intervals while there are downloads in progress,
363 and once at the end. Update the TreeView with the interfaces."""
365 for dl
in self
.policy
.handler
.monitored_downloads
.values():
367 if dl
.hint
not in hints
:
369 hints
[dl
.hint
].append(dl
)
371 selections
= self
.policy
.solver
.selections
376 for x
in walk(self
.model
.iter_children(it
)): yield x
377 it
= self
.model
.iter_next(it
)
379 for row
in walk(self
.model
.get_iter_root()):
380 iface
= row
[InterfaceBrowser
.INTERFACE
]
382 # Is this interface the download's hint?
383 downloads
= hints
.get(iface
, []) # The interface itself
384 downloads
+= hints
.get(iface
.uri
, []) # The main feed
385 for feed
in iface
.feeds
:
386 downloads
+= hints
.get(feed
.uri
, []) # Other feeds
387 impl
= selections
.get(iface
, None)
389 downloads
+= hints
.get(impl
, []) # The chosen implementation
396 expected
= (expected
or 0) + dl
.expected_size
397 so_far
+= dl
.get_bytes_downloaded_so_far()
399 fraction
= "%s [%.2f%%]" % (pretty_size(expected
), 100 * so_far
/ float(expected
))
402 if len(downloads
) > 1:
403 fraction
+= " in %d downloads" % len(downloads
)
404 row
[InterfaceBrowser
.SUMMARY
] = "(downloading %s/%s)" % (pretty_size(so_far
), fraction
)
406 row
[InterfaceBrowser
.SUMMARY
] = iface
.summary