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