1 """A GTK dialog which displays a list of Zero Install applications in the menu."""
2
3
4
5 from zeroinstall import _, gobject
6 import os, sys
7 import gtk, pango
8 import subprocess
9
10 from zeroinstall import support
11 from zeroinstall.gtkui import icon, xdgutils
12 from zeroinstall.injector import reader, model, namespaces
13
14 gtk2 = sys.version_info[0] < 3
15
17 return s.replace('&', '&').replace('<', '<')
18
20 """A list of applications which can be displayed in an L{AppListBox}.
21 For example, a program might implement this to display a list of plugins.
22 This default implementation lists applications in the freedesktop.org menus.
23 """
28
30 """Remove this application from the list."""
31 path = self.apps[uri]
32 os.unlink(path)
33
34 _tooltips = {
35 0: _("Run the application"),
36 1: _("Show documentation files"),
37 2: _("Upgrade or change versions"),
38 3: _("Remove launcher from the menu"),
39 }
40
42 """A dialog box which lists applications already added to the menus."""
43 ICON, URI, NAME, MARKUP = range(4)
44
45 - def __init__(self, iface_cache, app_list):
46 """Constructor.
47 @param iface_cache: used to find extra information about programs
48 @type iface_cache: L{zeroinstall.injector.iface_cache.IfaceCache}
49 @param app_list: used to list or remove applications
50 @type app_list: L{AppList}
51 """
52 builderfile = os.path.join(os.path.dirname(__file__), 'desktop.ui')
53 self.iface_cache = iface_cache
54 self.app_list = app_list
55
56 builder = gtk.Builder()
57 builder.set_translation_domain('zero-install')
58 builder.add_from_file(builderfile)
59 self.window = builder.get_object('applist')
60 tv = builder.get_object('treeview')
61
62 self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str)
63
64 self.populate_model()
65 tv.set_model(self.model)
66 tv.get_selection().set_mode(gtk.SELECTION_NONE)
67
68 cell_icon = gtk.CellRendererPixbuf()
69 cell_icon.set_property('xpad', 4)
70 cell_icon.set_property('ypad', 4)
71 column = gtk.TreeViewColumn('Icon', cell_icon, pixbuf = AppListBox.ICON)
72 tv.append_column(column)
73
74 cell_text = gtk.CellRendererText()
75 cell_text.set_property('ellipsize', pango.ELLIPSIZE_END)
76 column = gtk.TreeViewColumn('Name', cell_text, markup = AppListBox.MARKUP)
77 column.set_expand(True)
78 tv.append_column(column)
79
80 cell_actions = ActionsRenderer(self, tv)
81 actions_column = gtk.TreeViewColumn('Actions', cell_actions, uri = AppListBox.URI)
82 tv.append_column(actions_column)
83
84 def redraw_actions(path):
85 if path is not None:
86 area = tv.get_cell_area(path, actions_column)
87 if gtk2:
88 tv.queue_draw_area(*area)
89 else:
90 tv.queue_draw_area(area.x, area.y, area.width, area.height)
91
92 tv.set_property('has-tooltip', True)
93 def query_tooltip(widget, x, y, keyboard_mode, tooltip):
94 x, y = tv.convert_widget_to_bin_window_coords(x, y)
95 pos = tv.get_path_at_pos(x, y)
96 if pos:
97 new_hover = (None, None, None)
98 path, col, x, y = pos
99 if col == actions_column:
100 area = tv.get_cell_area(path, col)
101 iface = self.model[path][AppListBox.URI]
102 action = cell_actions.get_action(area, x, y)
103 if action is not None:
104 new_hover = (path, iface, action)
105 if new_hover != cell_actions.hover:
106 redraw_actions(cell_actions.hover[0])
107 cell_actions.hover = new_hover
108 redraw_actions(cell_actions.hover[0])
109 tv.set_tooltip_cell(tooltip, pos[0], pos[1], None)
110
111 if new_hover[2] is not None:
112 tooltip.set_text(_tooltips[cell_actions.hover[2]])
113 return True
114 return False
115 tv.connect('query-tooltip', query_tooltip)
116
117 def leave(widget, lev):
118 redraw_actions(cell_actions.hover[0])
119 cell_actions.hover = (None, None, None)
120
121 tv.connect('leave-notify-event', leave)
122
123 self.model.set_sort_column_id(AppListBox.NAME, gtk.SORT_ASCENDING)
124
125 show_cache = builder.get_object('show_cache')
126 self.window.action_area.set_child_secondary(show_cache, True)
127
128 def response(box, resp):
129 if resp == 0:
130 subprocess.Popen(['0store', 'manage'])
131 elif resp == 1:
132 from zeroinstall.gtkui.addbox import AddBox
133 box = AddBox()
134 box.window.connect('destroy', lambda dialog: self.populate_model())
135 box.window.show()
136 else:
137 box.destroy()
138 self.window.connect('response', response)
139
172
174 iface = self.iface_cache.get_interface(uri)
175 reader.update_from_cache(iface)
176 if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')):
177 if gtk.pygtk_version >= (2,16,0) and gtk.gdk.WINDOWING == 'quartz':
178 script = ['0launch', '--', uri]
179 osascript = support.find_in_path('osascript')
180 subprocess.Popen([osascript, '-e', 'tell app "Terminal"', '-e', 'activate',
181 '-e', 'do script "%s"' % ' '.join(script), '-e', 'end tell'])
182 return
183 for terminal in ['x-terminal-emulator', 'xterm', 'gnome-terminal', 'rxvt', 'konsole']:
184 exe = support.find_in_path(terminal)
185 if exe:
186 if terminal == 'gnome-terminal':
187 flag = '-x'
188 else:
189 flag = '-e'
190 subprocess.Popen([terminal, flag, '0launch', '--', uri])
191 break
192 else:
193 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator"))
194 box.run()
195 box.destroy()
196 else:
197 subprocess.Popen(['0launch', '--', uri])
198
234
236 subprocess.Popen(['0launch', '--gui', '--', uri])
237
239 try:
240 name = self.iface_cache.get_interface(uri).get_name()
241 except model.InvalidInterface:
242 name = uri
243
244 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, "")
245 box.set_markup(_("Remove <b>%s</b> from the menu?") % _pango_escape(name))
246 box.add_button(gtk.STOCK_DELETE, gtk.RESPONSE_OK)
247 box.set_default_response(gtk.RESPONSE_OK)
248 resp = box.run()
249 box.destroy()
250 if resp == gtk.RESPONSE_OK:
251 try:
252 self.app_list.remove_app(uri)
253 except Exception as ex:
254 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Failed to remove %(interface_name)s: %(exception)s") % {'interface_name': name, 'exception': ex})
255 box.run()
256 box.destroy()
257 self.populate_model()
258
260 __gproperties__ = {
261 "uri": (gobject.TYPE_STRING, "Text", "Text", "-", gobject.PARAM_READWRITE),
262 }
263
265 "@param widget: widget used for style information"
266 gtk.GenericCellRenderer.__init__(self)
267 self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
268 self.padding = 4
269
270 self.applist = applist
271
272 self.size = 10
273 def stock_lookup(name):
274 pixbuf = widget.render_icon(name, gtk.ICON_SIZE_BUTTON)
275 self.size = max(self.size, pixbuf.get_width(), pixbuf.get_height())
276 return pixbuf
277
278 if hasattr(gtk, 'STOCK_MEDIA_PLAY'):
279 self.run = stock_lookup(gtk.STOCK_MEDIA_PLAY)
280 else:
281 self.run = stock_lookup(gtk.STOCK_YES)
282 self.help = stock_lookup(gtk.STOCK_HELP)
283 self.properties = stock_lookup(gtk.STOCK_PROPERTIES)
284 self.remove = stock_lookup(gtk.STOCK_DELETE)
285 self.hover = (None, None, None)
286
288 setattr(self, prop.name, value)
289
290 - def do_get_size(self, widget, cell_area, layout = None):
291 total_size = self.size * 2 + self.padding * 4
292 return (0, 0, total_size, total_size)
293 on_get_size = do_get_size
294
295 - def render(self, cr, widget, background_area, cell_area, flags, expose_area = None):
296 hovering = self.uri == self.hover[1]
297
298 s = self.size
299
300 cx = cell_area.x + self.padding
301 cy = cell_area.y + (cell_area.height / 2) - s - self.padding
302
303 ss = s + self.padding * 2
304
305 b = 0
306 for (x, y), icon in [((0, 0), self.run),
307 ((ss, 0), self.help),
308 ((0, ss), self.properties),
309 ((ss, ss), self.remove)]:
310 if gtk2:
311 if hovering and b == self.hover[2]:
312 widget.style.paint_box(cr, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
313 expose_area, widget, None,
314 cx + x - 2, cy + y - 2, s + 4, s + 4)
315
316 cr.draw_pixbuf(widget.style.white_gc, icon,
317 0, 0,
318 cx + x, cy + y)
319 else:
320 if hovering and b == self.hover[2]:
321 gtk.render_focus(widget.get_style_context(), cr,
322 cx + x - 2, cy + y - 2, s + 4, s + 4)
323 gtk.gdk.cairo_set_source_pixbuf(cr, icon, cx + x, cy + y)
324 cr.paint()
325 b += 1
326
327 if gtk2:
328 - def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
329 self.render(window, widget, background_area, cell_area, flags, expose_area = expose_area)
330
331 else:
332 do_render = render
333
334 - def on_activate(self, event, widget, path, background_area, cell_area, flags):
335 if event.type != gtk.gdk.BUTTON_PRESS:
336 return False
337 action = self.get_action(cell_area, event.x - cell_area.x, event.y - cell_area.y)
338 if action == 0:
339 self.applist.action_run(self.uri)
340 elif action == 1:
341 self.applist.action_help(self.uri)
342 elif action == 2:
343 self.applist.action_properties(self.uri)
344 elif action == 3:
345 self.applist.action_remove(self.uri)
346 do_activate = on_activate
347
349 lower = int(y > (area.height / 2)) * 2
350
351 s = self.size + self.padding * 2
352 if x > s * 2:
353 return None
354 return int(x > s) + lower
355