3 from zeroinstall
.support
import basedir
, tasks
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(iface
)
344 for label
, cb
in [(_('Show Feeds'), lambda: properties
.edit(iface
)),
345 (_('Show Versions'), lambda: properties
.edit(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()