1 # Copyright (C) 2009 Canonical
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
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
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
32 from urltextview import UrlTextView
34 from aptdaemon import policykit1
35 from aptdaemon import client
36 from aptdaemon import enums
38 from gettext import gettext as _
41 from appcenter.enums import *
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])
48 class AppDetailsView(UrlTextView):
50 # the size of the icon on the left side
54 def __init__(self, xapiandb, icons, cache):
55 super(AppDetailsView, self).__init__()
56 self.xapiandb = xapiandb
60 self.set_editable(False)
61 self.set_cursor_visible(False)
62 self.set_wrap_mode(gtk.WRAP_WORD)
64 atk_desc = self.get_accessible()
65 atk_desc.set_name(_("Description"))
67 self._create_tag_table()
69 self.aptd_client = client.AptClient()
70 self.window_main_xid = None
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",
87 scale=pango.SCALE_SMALL,
90 def show_app(self, appname):
91 logging.debug("AppDetailsView.show_app %s" % appname)
92 self.appname = appname
95 for m in self.xapiandb.postlist("AA"+appname):
96 doc = self.xapiandb.get_document(m.docid)
99 raise IndexError, "No app '%s' in database" % appname
101 iconname = doc.get_value(XAPIAN_VALUE_ICON)
103 pkgname = doc.get_value(XAPIAN_VALUE_PKGNAME)
105 if self.cache.has_key(pkgname):
106 pkg = self.cache[pkgname]
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)
121 # helper to fill the buffer with the pkg information
123 """clean the buffer"""
124 buffer = self.get_buffer()
125 buffer.delete(buffer.get_start_iter(),
126 buffer.get_end_iter())
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")
134 def add_main_icon(self, iconname):
135 buffer = self.get_buffer()
136 iter = buffer.get_end_iter()
139 pixbuf = self.icons.load_icon(iconname, self.APP_ICON_SIZE, 0)
140 except gobject.GError, e:
141 pixbuf = self._empty_pixbuf()
143 pixbuf = self._empty_pixbuf()
145 buffer.insert_pixbuf(iter, pixbuf)
147 def add_price(self, appname, pkg):
148 buffer = self.get_buffer()
149 iter = buffer.get_end_iter()
150 s = _("Price: %s") % _("Free")
152 buffer.insert_with_tags_by_name(iter, s, "align-to-icon", "small")
154 def add_pkg_information(self, pkg):
155 buffer = self.get_buffer()
156 iter = buffer.get_end_iter()
157 version = pkg.candidate.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")
163 def add_main_description(self, appname, pkg):
164 buffer = self.get_buffer()
165 iter = buffer.get_end_iter()
167 details = pkg.candidate.description
169 details = _("Not available in the current data")
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")
177 def add_enable_channel_button(self, doc):
178 """add enable-channel button (if needed)"""
182 def add_maintainance_end_dates(self, pkg):
183 """add the end of the maintainance time"""
187 def add_pkg_action_button(self, appname, pkg, iconname):
188 """add pkg action button (install/remove/upgrade)"""
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)
198 button.set_label(_("Remove"))
199 button.connect("clicked", self.on_button_remove_clicked, appname, pkg.name, iconname)
201 button.set_label(_("Install"))
202 button.connect("clicked", self.on_button_install_clicked, appname, pkg.name, iconname)
204 def add_homepage_button(self, pkg):
205 """add homepage button to the current buffer"""
208 buffer = self.get_buffer()
209 iter = buffer.get_end_iter()
210 url = pkg.candidate.homepage
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)
221 def _insert_button(self, iter, tag_names=None):
223 insert a gtk.Button into at iter with a (optinal) list of tag names
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)
230 start.backward_char()
232 for tag in tag_names:
233 buffer.apply_tag_by_name(tag, start, iter)
234 button = gtk.Button("")
236 self.add_child_at_anchor(button, anchor)
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])
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)
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)
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)
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" % (
280 enums.get_error_string_from_enum(excep.code),
281 enums.get_error_description_from_enum(excep.code),
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)
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:
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)
302 if __name__ == "__main__":
303 logging.basicConfig(level=logging.DEBUG)
305 xapian_base_path = "/var/cache/app-install"
306 pathname = os.path.join(xapian_base_path, "xapian")
307 db = xapian.Database(pathname)
309 icons = gtk.icon_theme_get_default()
310 icons.append_search_path("/usr/share/app-install/icons/")
315 scroll = gtk.ScrolledWindow()
316 view = AppDetailsView(db, icons, cache)
317 view.show_app("AMOR")
318 #view.show_app("3D Chess")
323 win.set_size_request(600,400)