3 from zeroinstall
.injector
import basedir
4 from zeroinstall
.injector
.model
import Interface
, escape
6 from treetips
import TreeTips
7 from gui
import policy
, pretty_size
8 from logging
import warn
12 if impl
.user_stability
is None:
13 return impl
.upstream_stability
14 return _("%s (was %s)") % (impl
.user_stability
, impl
.upstream_stability
)
17 CELL_TEXT_INDENT
= int(ICON_SIZE
) + 4
19 class InterfaceTips(TreeTips
):
20 def get_tooltip_text(self
, item
):
21 interface
, model_column
= item
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
:
28 first_para
= interface
.description
.split('\n\n', 1)[0]
29 return first_para
.replace('\n', ' ')
31 impl
= policy
.implementation
.get(interface
, None)
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
))
45 assert model_column
== InterfaceBrowser
.DOWNLOAD_SIZE
47 if policy
.get_cached(impl
):
48 return _("This version is already stored on your computer.")
50 src
= policy
.get_best_source(impl
)
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
):
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):
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
)
77 rect
[0] + rect
[2] + CELL_TEXT_INDENT
,
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
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,
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
,
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
):
112 edit_properties
= None
122 columns
= [(_('Interface'), INTERFACE_NAME
),
123 (_('Version'), VERSION
),
124 (_('Fetch'), DOWNLOAD_SIZE
),
125 (_('Description'), SUMMARY
)]
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
)
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(),
154 image
= InterfaceBrowser
.ICON
)
156 column
= gtk
.TreeViewColumn(name
, text
, text
= model_column
)
157 tree_view
.append_column(column
)
158 column_objects
.append(column
)
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():
170 pos
= tree_view
.get_path_at_pos(int(ev
.x
), int(ev
.y
))
174 col_index
= column_objects
.index(pos
[1])
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
)
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
:
197 pos
= tree_view
.get_path_at_pos(int(bev
.x
), int(bev
.y
))
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()
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
)
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."""
223 return self
.cached_icon
[iface
.uri
]
225 if not hasattr(policy
, 'get_icon_path'):
226 return None # injector < 0.19
227 path
= policy
.get_icon_path(iface
)
230 loader
= gtk
.gdk
.PixbufLoader('png')
232 loader
.write(file(path
).read())
235 icon
= loader
.get_pixbuf()
236 except Exception, ex
:
237 warn("Failed to load cached PNG icon: %s", ex
)
240 h
= icon
.get_height()
241 scale
= max(w
, h
, 1) / ICON_SIZE
242 icon
= icon
.scale_simple(int(w
/ scale
),
244 gtk
.gdk
.INTERP_BILINEAR
)
245 self
.cached_icon
[iface
.uri
] = icon
250 def build_tree(self
):
251 if policy
.original_implementation
is None:
252 policy
.set_original_implementations()
254 done
= {} # Detect cycles
258 def add_node(parent
, iface
):
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)
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('/'):
283 src
= policy
.get_best_source(impl
)
285 fetch
= pretty_size(src
.size
)
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
))
292 self
.model
[iter][InterfaceBrowser
.VERSION
] = '(choose)'
293 add_node(None, self
.root
)
294 self
.tree_view
.expand_all()