rename die appcenter dir (pep08)
[appstream:software-center.git] / appcenter / view / appdetailsview.py
1 #!/usr/bin/python
2
3 import apt
4 import logging
5 import gtk
6 import gobject
7 import apt
8 import os
9 import pango
10 import subprocess
11 import sys
12 import time
13 import xapian
14
15 from urltextview import UrlTextView
16
17 from aptdaemon import policykit1
18 from aptdaemon import client
19 from aptdaemon import enums
20  
21 from gettext import gettext as _
22
23 try:
24     from appcenter.enums import *
25 except ImportError:
26     # support running from the dir too
27     d = os.path.dirname(os.path.abspath(os.path.join(os.getcwd(),__file__)))
28     sys.path.insert(0, os.path.split(d)[0])
29     from enums import *
30
31 class AppDetailsView(UrlTextView):
32
33     # the size of the icon on the left side
34     APP_ICON_SIZE = 32
35     APP_ICON_PADDING = 8
36
37     def __init__(self, xapiandb, icons, cache):
38         super(AppDetailsView, self).__init__()
39         self.xapiandb = xapiandb
40         self.icons = icons
41         self.cache = cache
42         # customization
43         self.set_editable(False)
44         self.set_cursor_visible(False)
45         self.set_wrap_mode(gtk.WRAP_WORD)
46         # atk
47         atk_desc = self.get_accessible()
48         atk_desc.set_name(_("Description"))
49         # tags
50         self._create_tag_table()
51         # aptdaemon
52         self.aptd_client = client.AptClient()
53         self.window_main_xid = None
54         # data
55         self.appname = None
56
57     def _create_tag_table(self):
58         buffer = self.get_buffer()
59         buffer.create_tag("align-to-icon", 
60                           left_margin=self.APP_ICON_SIZE)
61         buffer.create_tag("heading", 
62                           weight=pango.WEIGHT_HEAVY,
63                           scale=pango.SCALE_LARGE)
64         #buffer.create_tag("align-right", 
65         #                  justification=gtk.JUSTIFY_RIGHT))
66         buffer.create_tag("small", 
67                           scale=pango.SCALE_SMALL)
68         buffer.create_tag("maint-status", 
69                           scale_set=True,
70                           scale=pango.SCALE_SMALL,
71                           foreground="#888")
72
73     def show_app(self, appname):
74         logging.debug("AppDetailsView.show_app %s" % appname)
75         self.appname = appname
76         # get xapian document
77         doc = None
78         for m in self.xapiandb.postlist("AA"+appname):
79             doc = self.xapiandb.get_document(m.docid)
80             break
81         if not doc:
82             raise IndexError, "No app '%s' in database" % appname
83         # icon
84         iconname = doc.get_value(XAPIAN_VALUE_ICON)
85         # get apt cache data
86         pkgname = doc.get_value(XAPIAN_VALUE_PKGNAME)
87         pkg = None
88         if self.cache.has_key(pkgname):
89             pkg = self.cache[pkgname]
90
91         # fill the buffer
92         self.clean()
93         self.add_main_icon(iconname)
94         self.add_main_description(appname, pkg)
95         self.add_empty_lines(2)
96         self.add_price(appname, pkg)
97         self.add_enable_channel_button(doc)
98         self.add_pkg_action_button(appname, pkg, iconname)
99         self.add_homepage_button(pkg)
100         self.add_pkg_information(pkg)
101         self.add_maintainance_end_dates(pkg)
102         self.add_empty_lines(2)
103
104     # helper to fill the buffer with the pkg information
105     def clean(self):
106         """clean the buffer"""
107         buffer = self.get_buffer()
108         buffer.delete(buffer.get_start_iter(),
109                       buffer.get_end_iter())
110
111     def add_empty_lines(self, count=1):
112         buffer = self.get_buffer()
113         iter = buffer.get_end_iter()
114         for i in range(count):
115             buffer.insert(iter, "\n")
116
117     def add_main_icon(self, iconname):
118         buffer = self.get_buffer()
119         iter = buffer.get_end_iter()
120         if iconname:
121             try:
122                 pixbuf = self.icons.load_icon(iconname, self.APP_ICON_SIZE, 0)
123             except gobject.GError, e:
124                 pixbuf = self._empty_pixbuf()
125         else:
126             pixbuf = self._empty_pixbuf()
127         # insert description 
128         buffer.insert_pixbuf(iter, pixbuf)
129
130     def add_price(self, appname, pkg):
131         buffer = self.get_buffer()
132         iter = buffer.get_end_iter()
133         s = _("Price: %s") % _("Free")
134         s += "\n\n"
135         buffer.insert_with_tags_by_name(iter, s, "align-to-icon", "small")
136
137     def add_pkg_information(self, pkg):
138         buffer = self.get_buffer()
139         iter = buffer.get_end_iter()
140         version = pkg.candidate.version
141         if version:
142             buffer.insert(iter, "\n\n")
143             s = _("Version: %s (%s)") % (version, pkg.name)
144             buffer.insert_with_tags_by_name(iter, s, "align-to-icon", "small")
145
146     def add_main_description(self, appname, pkg):
147         buffer = self.get_buffer()
148         iter = buffer.get_end_iter()
149         if pkg:
150             details = pkg.candidate.description
151         else:
152             details = _("Not available in the current data")
153
154         heading = "%s" % appname
155         text = "\n\n%s" % details
156         buffer.insert_with_tags_by_name(iter, heading, "heading")
157         buffer.insert_with_tags_by_name(iter, text, "align-to-icon")
158         
159
160     def add_enable_channel_button(self, doc):
161         """add enable-channel button (if needed)"""
162         # FIXME: add code
163         return
164
165     def add_maintainance_end_dates(self, pkg):
166         """add the end of the maintainance time"""
167         # FIXME: add code
168         return
169     
170     def add_pkg_action_button(self, appname, pkg, iconname):
171         """add pkg action button (install/remove/upgrade)"""
172         if not pkg:
173             return 
174         buffer = self.get_buffer()
175         iter = buffer.get_end_iter()
176         button = self._insert_button(iter, ["align-to-icon"])
177         if pkg.installed and pkg.isUpgradable:
178             button.set_label(_("Upgrade"))
179             button.connect("clicked", self.on_button_upgrade_clicked, appname, pkg.name, iconname)
180         elif pkg.installed:
181             button.set_label(_("Remove"))
182             button.connect("clicked", self.on_button_remove_clicked, appname, pkg.name, iconname)
183         else:
184             button.set_label(_("Install"))
185             button.connect("clicked", self.on_button_install_clicked, appname, pkg.name, iconname)
186     
187     def add_homepage_button(self, pkg):
188         """add homepage button to the current buffer"""
189         if not pkg:
190             return
191         buffer = self.get_buffer()
192         iter = buffer.get_end_iter()
193         url = pkg.candidate.homepage
194         if not url:
195             return
196         # FIXME: right-align
197         buffer.insert(iter, 4*" ")
198         #button = self._insert_button(iter, ["align-right"])
199         button = self._insert_button(iter)
200         button.set_label(_("Homepage"))
201         button.set_tooltip_text(url)
202         button.connect("clicked", self.on_button_homepage_clicked, url)
203
204     def _insert_button(self, iter, tag_names=None):
205         """
206         insert a gtk.Button into at iter with a (optinal) list of tag names
207         """
208         buffer = self.get_buffer()
209         anchor = buffer.create_child_anchor(iter)
210         # align-to-icon needs (start,end) and we can not just copy
211         # the iter before create_child_anchor (it invalidates it)
212         start = iter.copy()
213         start.backward_char()
214         if tag_names:
215             for tag in tag_names:
216                 buffer.apply_tag_by_name(tag, start, iter)
217         button = gtk.Button("")
218         button.show()
219         self.add_child_at_anchor(button, anchor)
220         return button
221
222     # callbacks
223     def on_button_homepage_clicked(self, button, url):
224         logging.debug("on_button_homepage_clicked: '%s'" % url)
225         cmd = self._url_launch_app()
226         subprocess.call([cmd, url])
227
228     def on_button_upgrade_clicked(self, button, appname, pkgname, iconname):
229         #print "on_button_upgrade_clicked", pkgname
230         trans = self.aptd_client.commit_packages([], [], [], [], [pkgname], 
231                                           exit_handler=self._on_trans_finished)
232         trans.set_data("appname", appname)
233         trans.set_data("iconname", iconname)
234         trans.set_data("pkgname", pkgname)
235         trans.run()
236
237     def on_button_remove_clicked(self, button, appname, pkgname, iconname):
238         #print "on_button_remove_clicked", pkgname
239         trans = self.aptd_client.commit_packages([], [], [pkgname], [], [],
240                                          exit_handler=self._on_trans_finished)
241         trans.set_data("pkgname", pkgname)
242         trans.set_data("appname", appname)
243         trans.set_data("iconname", iconname)
244         trans.run()
245
246     def on_button_install_clicked(self, button, appname, pkgname, iconname):
247         #print "on_button_install_clicked", pkgname
248         trans = self.aptd_client.commit_packages([pkgname], [], [], [], [],
249                                           exit_handler=self._on_trans_finished)
250         trans.set_data("pkgname", pkgname)
251         trans.set_data("appname", appname)
252         trans.set_data("iconname", iconname)
253         trans.run()
254
255     def _on_trans_finished(self, trans, enum):
256         """callback when a aptdaemon transaction finished"""
257         #print "finish: ", trans, enum
258         # FIXME: do something useful here
259         if enum == enums.EXIT_FAILED:
260             excep = trans.get_error()
261             msg = "%s: %s\n%s\n\n%s" % (
262                    _("ERROR"),
263                    enums.get_error_string_from_enum(excep.code),
264                    enums.get_error_description_from_enum(excep.code),
265                    excep.details)
266             print msg
267         # re-open cache and refresh app display
268         # FIXME: threaded to keep the UI alive
269         self.cache.open(apt.progress.OpProgress())
270         self.show_app(self.appname)
271
272     # internal helpers
273     def _url_launch_app(self):
274         """return the most suitable program for opening a url"""
275         if "GNOME_DESKTOP_SESSION_ID" in os.environ:
276             return "gnome-open"
277         return "xdg-open"
278
279     def _empty_pixbuf(self):
280         pix = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
281                              self.APP_ICON_SIZE, self.APP_ICON_SIZE)
282         pix.fill(0)
283         return pix
284
285 if __name__ == "__main__":
286     logging.basicConfig(level=logging.DEBUG)
287
288     xapian_base_path = "/var/cache/app-install"
289     pathname = os.path.join(xapian_base_path, "xapian")
290     db = xapian.Database(pathname)
291
292     icons = gtk.icon_theme_get_default()
293     icons.append_search_path("/usr/share/app-install/icons/")
294     
295     cache = apt.Cache()
296
297     # gui
298     scroll = gtk.ScrolledWindow()
299     view = AppDetailsView(db, icons, cache)
300     view.show_app("AMOR")
301     #view.show_app("3D Chess")
302
303     win = gtk.Window()
304     scroll.add(view)
305     win.add(scroll)
306     win.set_size_request(600,400)
307     win.show_all()
308
309     gtk.main()