1 """A GTK dialog which displays a list of Zero Install applications in the menu."""
2
3
4
5 from zeroinstall import _
6 import os
7 import gtk, gobject, 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
15 return s.replace('&', '&').replace('<', '<')
16
18 """A list of applications which can be displayed in an L{AppListBox}.
19 For example, a program might implement this to display a list of plugins.
20 This default implementation lists applications in the freedesktop.org menus.
21 """
26
28 """Remove this application from the list."""
29 path = self.apps[uri]
30 os.unlink(path)
31
32 _tooltips = {
33 0: _("Run the application"),
34 1: _("Show documentation files"),
35 2: _("Upgrade or change versions"),
36 3: _("Remove launcher from the menu"),
37 }
38
40 """A dialog box which lists applications already added to the menus."""
41 ICON, URI, NAME, MARKUP = range(4)
42
43 - def __init__(self, iface_cache, app_list):
44 """Constructor.
45 @param iface_cache: used to find extra information about programs
46 @type iface_cache: L{zeroinstall.injector.iface_cache.IfaceCache}
47 @param app_list: used to list or remove applications
48 @type app_list: L{AppList}
49 """
50 builderfile = os.path.join(os.path.dirname(__file__), 'desktop.ui')
51 self.iface_cache = iface_cache
52 self.app_list = app_list
53
54 builder = gtk.Builder()
55 builder.set_translation_domain('zero-install')
56 builder.add_from_file(builderfile)
57 self.window = builder.get_object('applist')
58 tv = builder.get_object('treeview')
59
60 self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str)
61
62 self.populate_model()
63 tv.set_model(self.model)
64 tv.get_selection().set_mode(gtk.SELECTION_NONE)
65
66 cell_icon = gtk.CellRendererPixbuf()
67 cell_icon.set_property('xpad', 4)
68 cell_icon.set_property('ypad', 4)
69 column = gtk.TreeViewColumn('Icon', cell_icon, pixbuf = AppListBox.ICON)
70 tv.append_column(column)
71
72 cell_text = gtk.CellRendererText()
73 cell_text.set_property('ellipsize', pango.ELLIPSIZE_END)
74 column = gtk.TreeViewColumn('Name', cell_text, markup = AppListBox.MARKUP)
75 column.set_expand(True)
76 tv.append_column(column)
77
78 cell_actions = ActionsRenderer(self, tv)
79 actions_column = gtk.TreeViewColumn('Actions', cell_actions, uri = AppListBox.URI)
80 tv.append_column(actions_column)
81
82 def redraw_actions(path):
83 if path is not None:
84 area = tv.get_cell_area(path, actions_column)
85 tv.queue_draw_area(*area)
86
87 tv.set_property('has-tooltip', True)
88 def query_tooltip(widget, x, y, keyboard_mode, tooltip):
89 x, y = tv.convert_widget_to_bin_window_coords(x, y)
90 pos = tv.get_path_at_pos(x, y)
91 if pos:
92 new_hover = (None, None, None)
93 path, col, x, y = pos
94 if col == actions_column:
95 area = tv.get_cell_area(path, col)
96 iface = self.model[path][AppListBox.URI]
97 action = cell_actions.get_action(area, x, y)
98 if action is not None:
99 new_hover = (path, iface, action)
100 if new_hover != cell_actions.hover:
101 redraw_actions(cell_actions.hover[0])
102 cell_actions.hover = new_hover
103 redraw_actions(cell_actions.hover[0])
104 tv.set_tooltip_cell(tooltip, pos[0], pos[1], None)
105
106 if new_hover[2] is not None:
107 tooltip.set_text(_tooltips[cell_actions.hover[2]])
108 return True
109 return False
110 tv.connect('query-tooltip', query_tooltip)
111
112 def leave(widget, lev):
113 redraw_actions(cell_actions.hover[0])
114 cell_actions.hover = (None, None, None)
115
116 tv.connect('leave-notify-event', leave)
117
118 self.model.set_sort_column_id(AppListBox.NAME, gtk.SORT_ASCENDING)
119
120 show_cache = builder.get_object('show_cache')
121 self.window.action_area.set_child_secondary(show_cache, True)
122
123 def response(box, resp):
124 if resp == 0:
125 subprocess.Popen(['0store', 'manage'])
126 else:
127 box.destroy()
128 self.window.connect('response', response)
129
156
158 iface = self.iface_cache.get_interface(uri)
159 reader.update_from_cache(iface)
160 if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')):
161 for terminal in ['x-terminal-emulator', 'xterm', 'gnome-terminal', 'rxvt', 'konsole']:
162 exe = support.find_in_path(terminal)
163 if exe:
164 if terminal == 'gnome-terminal':
165 flag = '-x'
166 else:
167 flag = '-e'
168 subprocess.Popen([terminal, flag, '0launch', '--', uri])
169 break
170 else:
171 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator"))
172 box.run()
173 box.destroy()
174 else:
175 subprocess.Popen(['0launch', '--', uri])
176
212
214 subprocess.Popen(['0launch', '--gui', '--', uri])
215
217 try:
218 name = self.iface_cache.get_interface(uri).get_name()
219 except model.InvalidInterface:
220 name = uri
221
222 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, "")
223 box.set_markup(_("Remove <b>%s</b> from the menu?") % _pango_escape(name))
224 box.add_button(gtk.STOCK_DELETE, gtk.RESPONSE_OK)
225 box.set_default_response(gtk.RESPONSE_OK)
226 resp = box.run()
227 box.destroy()
228 if resp == gtk.RESPONSE_OK:
229 try:
230 self.app_list.remove_app(uri)
231 except Exception as ex:
232 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})
233 box.run()
234 box.destroy()
235 self.populate_model()
236
238 __gproperties__ = {
239 "uri": (gobject.TYPE_STRING, "Text", "Text", "-", gobject.PARAM_READWRITE),
240 }
241
243 "@param widget: widget used for style information"
244 gtk.GenericCellRenderer.__init__(self)
245 self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
246 self.padding = 4
247
248 self.applist = applist
249
250 self.size = 10
251 def stock_lookup(name):
252 pixbuf = widget.render_icon(name, gtk.ICON_SIZE_BUTTON)
253 self.size = max(self.size, pixbuf.get_width(), pixbuf.get_height())
254 return pixbuf
255
256 if hasattr(gtk, 'STOCK_MEDIA_PLAY'):
257 self.run = stock_lookup(gtk.STOCK_MEDIA_PLAY)
258 else:
259 self.run = stock_lookup(gtk.STOCK_YES)
260 self.help = stock_lookup(gtk.STOCK_HELP)
261 self.properties = stock_lookup(gtk.STOCK_PROPERTIES)
262 self.remove = stock_lookup(gtk.STOCK_DELETE)
263 self.hover = (None, None, None)
264
266 setattr(self, prop.name, value)
267
268 - def on_get_size(self, widget, cell_area, layout = None):
269 total_size = self.size * 2 + self.padding * 4
270 return (0, 0, total_size, total_size)
271
272 - def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
273 hovering = self.uri == self.hover[1]
274
275 s = self.size
276
277 cx = cell_area.x + self.padding
278 cy = cell_area.y + (cell_area.height / 2) - s - self.padding
279
280 ss = s + self.padding * 2
281
282 b = 0
283 for (x, y), icon in [((0, 0), self.run),
284 ((ss, 0), self.help),
285 ((0, ss), self.properties),
286 ((ss, ss), self.remove)]:
287 if hovering and b == self.hover[2]:
288 widget.style.paint_box(window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
289 expose_area, widget, None,
290 cx + x - 2, cy + y - 2, s + 4, s + 4)
291
292 window.draw_pixbuf(widget.style.white_gc, icon,
293 0, 0,
294 cx + x, cy + y)
295 b += 1
296
297 - def on_activate(self, event, widget, path, background_area, cell_area, flags):
298 if event.type != gtk.gdk.BUTTON_PRESS:
299 return False
300 action = self.get_action(cell_area, event.x - cell_area.x, event.y - cell_area.y)
301 if action == 0:
302 self.applist.action_run(self.uri)
303 elif action == 1:
304 self.applist.action_help(self.uri)
305 elif action == 2:
306 self.applist.action_properties(self.uri)
307 elif action == 3:
308 self.applist.action_remove(self.uri)
309
311 lower = int(y > (area.height / 2)) * 2
312
313 s = self.size + self.padding * 2
314 if x > s * 2:
315 return None
316 return int(x > s) + lower
317