1 """A GTK dialog which displays a list of Zero Install applications in the menu."""
2 # Copyright (C) 2008, Thomas Leonard
3 # See the README file for details, or visit http://0install.net.
10 from zeroinstall
.gtkui
import icon
, xdgutils
13 return s
.replace('&', '&').replace('<', '<')
16 """A dialog box which lists applications already added to the menus."""
17 ICON
, URI
, NAME
, MARKUP
= range(4)
19 def __init__(self
, iface_cache
):
20 gladefile
= os
.path
.join(os
.path
.dirname(__file__
), 'desktop.glade')
21 self
.iface_cache
= iface_cache
23 widgets
= gtk
.glade
.XML(gladefile
, 'applist')
24 self
.window
= widgets
.get_widget('applist')
25 tv
= widgets
.get_widget('treeview')
27 self
.model
= gtk
.ListStore(gtk
.gdk
.Pixbuf
, str, str, str)
30 tv
.set_model(self
.model
)
31 tv
.get_selection().set_mode(gtk
.SELECTION_NONE
)
33 cell_icon
= gtk
.CellRendererPixbuf()
34 cell_icon
.set_property('xpad', 4)
35 cell_icon
.set_property('ypad', 4)
36 column
= gtk
.TreeViewColumn('Icon', cell_icon
, pixbuf
= AppListBox
.ICON
)
37 tv
.append_column(column
)
39 cell_text
= gtk
.CellRendererText()
40 column
= gtk
.TreeViewColumn('Name', cell_text
, markup
= AppListBox
.MARKUP
)
41 tv
.append_column(column
)
43 cell_actions
= ActionsRenderer(self
, tv
)
44 actions_column
= gtk
.TreeViewColumn('Actions', cell_actions
, uri
= AppListBox
.URI
)
45 tv
.append_column(actions_column
)
47 def redraw_actions(path
):
49 area
= tv
.get_cell_area(path
, actions_column
)
50 tv
.queue_draw_area(*area
)
52 def motion(widget
, mev
):
53 if mev
.window
== tv
.get_bin_window():
54 new_hover
= (None, None, None)
55 pos
= tv
.get_path_at_pos(int(mev
.x
), int(mev
.y
))
58 if col
== actions_column
:
59 area
= tv
.get_cell_area(path
, col
)
60 iface
= self
.model
[path
][AppListBox
.URI
]
61 action
= cell_actions
.get_action(area
, x
, y
)
62 if action
is not None:
63 new_hover
= (path
, iface
, action
)
64 if new_hover
!= cell_actions
.hover
:
65 redraw_actions(cell_actions
.hover
[0])
66 cell_actions
.hover
= new_hover
67 redraw_actions(cell_actions
.hover
[0])
68 tv
.connect('motion-notify-event', motion
)
70 def leave(widget
, lev
):
71 redraw_actions(cell_actions
.hover
[0])
72 cell_actions
.hover
= (None, None, None)
74 tv
.connect('leave-notify-event', leave
)
76 self
.model
.set_sort_column_id(AppListBox
.NAME
, gtk
.SORT_ASCENDING
)
78 def response(box
, resp
):
80 self
.window
.connect('response', response
)
82 def populate_model(self
):
83 self
.apps
= xdgutils
.discover_existing_apps()
89 model
[itr
][AppListBox
.URI
] = uri
91 iface
= self
.iface_cache
.get_interface(uri
)
92 name
= iface
.get_name()
93 summary
= iface
.summary
or 'No information available'
94 summary
= summary
[:1].capitalize() + summary
[1:]
96 model
[itr
][AppListBox
.NAME
] = name
97 pixbuf
= icon
.load_icon(self
.iface_cache
.get_icon_path(iface
))
99 pixbuf
= self
.window
.render_icon(gtk
.STOCK_EXECUTE
, gtk
.ICON_SIZE_DIALOG
)
100 model
[itr
][AppListBox
.ICON
] = pixbuf
102 model
[itr
][AppListBox
.MARKUP
] = '<b>%s</b>\n<i>%s</i>' % (_pango_escape(name
), _pango_escape(summary
))
104 def action_run(self
, uri
):
105 subprocess
.Popen(['0launch', '--', uri
])
107 def action_help(self
, uri
):
108 from zeroinstall
.injector
import policy
, reader
, model
109 p
= policy
.Policy(uri
)
110 policy
.network_use
= model
.network_offline
111 if p
.need_download():
112 child
= subprocess
.Popen(['0launch', '-d', '--', uri
])
116 iface
= self
.iface_cache
.get_interface(uri
)
117 reader
.update_from_cache(iface
)
118 p
.solve_with_downloads()
119 impl
= p
.solver
.selections
[iface
]
120 assert impl
, "Failed to choose an implementation of " + uri
121 help_dir
= impl
.metadata
.get('doc-dir')
122 path
= p
.get_implementation_path(impl
)
123 assert path
, "Chosen implementation is not cached!"
125 path
= os
.path
.join(path
, help_dir
)
126 subprocess
.Popen(['xdg-open', path
])
128 def action_properties(self
, uri
):
129 subprocess
.Popen(['0launch', '--gui', '--', uri
])
131 def action_remove(self
, uri
):
132 name
= self
.iface_cache
.get_interface(uri
).get_name()
134 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_CANCEL
, "")
135 box
.set_markup("Remove <b>%s</b> from the menu?" % _pango_escape(name
))
136 box
.add_button(gtk
.STOCK_DELETE
, gtk
.RESPONSE_OK
)
137 box
.set_default_response(gtk
.RESPONSE_OK
)
140 if resp
== gtk
.RESPONSE_OK
:
141 path
= self
.apps
[uri
]
144 except Exception, ex
:
145 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
, "Failed to remove %s: %s" % (path
, ex
))
148 self
.populate_model()
150 class ActionsRenderer(gtk
.GenericCellRenderer
):
152 "uri": (gobject
.TYPE_STRING
, "Text", "Text", "-", gobject
.PARAM_READWRITE
),
155 def __init__(self
, applist
, widget
):
156 "@param widget: widget used for style information"
157 gtk
.GenericCellRenderer
.__init
__(self
)
158 self
.set_property('mode', gtk
.CELL_RENDERER_MODE_ACTIVATABLE
)
161 self
.applist
= applist
164 def stock_lookup(name
):
165 pixbuf
= widget
.render_icon(name
, gtk
.ICON_SIZE_BUTTON
)
166 self
.size
= max(self
.size
, pixbuf
.get_width(), pixbuf
.get_height())
169 if hasattr(gtk
, 'STOCK_MEDIA_PLAY'):
170 self
.run
= stock_lookup(gtk
.STOCK_MEDIA_PLAY
)
172 self
.run
= stock_lookup(gtk
.STOCK_YES
)
173 self
.help = stock_lookup(gtk
.STOCK_HELP
)
174 self
.properties
= stock_lookup(gtk
.STOCK_PROPERTIES
)
175 self
.remove
= stock_lookup(gtk
.STOCK_DELETE
)
176 self
.hover
= (None, None, None) # Path, URI, action
178 def do_set_property(self
, prop
, value
):
179 setattr(self
, prop
.name
, value
)
181 def on_get_size(self
, widget
, cell_area
, layout
= None):
182 total_size
= self
.size
* 2 + self
.padding
* 4
183 return (0, 0, total_size
, total_size
)
185 def on_render(self
, window
, widget
, background_area
, cell_area
, expose_area
, flags
):
186 hovering
= self
.uri
== self
.hover
[1]
190 cx
= cell_area
.x
+ self
.padding
191 cy
= cell_area
.y
+ (cell_area
.height
/ 2) - s
- self
.padding
193 ss
= s
+ self
.padding
* 2
196 for (x
, y
), icon
in [((0, 0), self
.run
),
197 ((ss
, 0), self
.help),
198 ((0, ss
), self
.properties
),
199 ((ss
, ss
), self
.remove
)]:
200 if hovering
and b
== self
.hover
[2]:
201 widget
.style
.paint_box(window
, gtk
.STATE_NORMAL
, gtk
.SHADOW_OUT
,
202 expose_area
, widget
, None,
203 cx
+ x
- 2, cy
+ y
- 2, s
+ 4, s
+ 4)
205 window
.draw_pixbuf(widget
.style
.white_gc
, icon
,
210 def on_activate(self
, event
, widget
, path
, background_area
, cell_area
, flags
):
211 if event
.type != gtk
.gdk
.BUTTON_PRESS
:
213 action
= self
.get_action(cell_area
, event
.x
- cell_area
.x
, event
.y
- cell_area
.y
)
215 self
.applist
.action_run(self
.uri
)
217 self
.applist
.action_help(self
.uri
)
219 self
.applist
.action_properties(self
.uri
)
221 self
.applist
.action_remove(self
.uri
)
223 def get_action(self
, area
, x
, y
):
224 lower
= int(y
> (area
.height
/ 2)) * 2
226 s
= self
.size
+ self
.padding
* 2
229 return int(x
> s
) + lower
231 if gtk
.pygtk_version
< (2, 8, 0):
232 # Note sure exactly which versions need this.
233 # 2.8.0 gives a warning if you include it, though.
234 gobject
.type_register(ActionsRenderer
)