home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / site-packages / AppInstall / AppInstall.py < prev    next >
Encoding:
Python Source  |  2006-08-28  |  55.2 KB  |  1,310 lines

  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. #
  4. # Copyright (C) 2004-2005 Ross Burton <ross@burtonini.com>
  5. #               2005-2006 Canonical
  6. #               2006 Sebastian Heinlein
  7. #
  8. # This program is free software; you can redistribute it and/or modify it under
  9. # the terms of the GNU General Public License as published by the Free Software
  10. # Foundation; either version 2 of the License, or (at your option) any later
  11. # version.
  12. #
  13. # This program is distributed in the hope that it will be useful, but WITHOUT
  14. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  16. # details.
  17. #
  18. # You should have received a copy of the GNU General Public License along with
  19. # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  20. # Place, Suite 330, Boston, MA 02111-1307 USA
  21.  
  22. # Don't forget to disbale this :)
  23. #import pdb
  24.  
  25. import pygtk; pygtk.require("2.0")
  26. import gtk
  27. import gtk.glade
  28. import gtk.gdk
  29. import gobject
  30. import gconf
  31. import pango
  32.  
  33. import gettext
  34. from gettext import gettext as _
  35.  
  36. #setup gettext
  37. app="gnome-app-install"
  38. gettext.textdomain(app)
  39. gettext.bindtextdomain(app)
  40. gtk.glade.textdomain(app)
  41. gtk.glade.bindtextdomain(app)
  42.  
  43.  
  44. import stat
  45. import glob
  46. import re
  47. import subprocess
  48. import tempfile
  49. import warnings
  50. import os
  51. import sys
  52. from datetime import datetime
  53.  
  54. import dbus
  55. import dbus.service
  56. import dbus.glib
  57.  
  58. from warnings import warn
  59. warnings.filterwarnings("ignore", "ICON:.*", UserWarning)
  60. warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
  61. import apt
  62. import apt_pkg
  63.  
  64. # from update-manager, needs to be factored out
  65. from UpdateManager.Common.aptsources import SourcesList, is_mirror
  66.  
  67. # internal imports
  68. from DialogNewlyInstalled import DialogNewlyInstalled
  69. from DialogPendingChanges import DialogPendingChanges
  70. from DialogMultipleApps import DialogMultipleApps
  71. from DialogUnavailable import DialogUnavailable
  72. from DialogProprietary import DialogProprietary
  73. from PackageWorker import PackageWorker
  74. from Menu import ApplicationMenu
  75.  
  76. # FIXME: Share this with update-manager
  77. #        the version in g-a-i is a little bit ahead :)
  78. from ReleaseNotesViewer import ReleaseNotesViewer
  79. from SimpleGladeApp import SimpleGladeApp
  80. from Progress import GtkOpProgressWindow
  81. from Util import *
  82. import common
  83.  
  84.  
  85. # this is used to guess the desktop environment that the application
  86. # was written for
  87. desktop_environment_mapping = {
  88.     ("kdelibs4c2a","python-kde3") :
  89.     (_("%s integrates well into the Kubuntu desktop"), "application-kubuntu"),
  90.     ("libgnome2-0","python-gnome2") :
  91.     (_("%s integrates well into the Ubuntu desktop"), "application-ubuntu"),
  92.     ("libgnustep-base1.11") :
  93.     (_("%s integrates well into the Gnustep desktop"), None),
  94.     ("libxfce4util4",) :
  95.     (_("%s integrates well into the Xubuntu desktop"), None),
  96. }
  97.  
  98. from Menu import SHOW_ALL, SHOW_ALL_SUPPORTED, SHOW_ALL_FREE, SHOW_ONLY_MAIN, SHOW_ONLY_PROPRIETARY, SHOW_ONLY_THIRD_PARTY
  99.  
  100.  
  101.  
  102. class AppInstallDbusControler(dbus.service.Object):
  103.     """ this is a helper to provide the AppInstallIFace """
  104.     def __init__(self, parent, bus_name,
  105.                  object_path='/org/freedesktop/AppInstallObject'):
  106.         dbus.service.Object.__init__(self, bus_name, object_path)
  107.         self.parent = parent
  108.  
  109.     @dbus.service.method('org.freedesktop.AppInstallIFace')
  110.     def bringToFront(self):
  111.         self.parent.window_main.present()
  112.         return True
  113.     
  114. class AppInstall(SimpleGladeApp):
  115.  
  116.     def __init__(self, datadir, desktopdir, arguments=None, mime_search=None):
  117.         self.setupDbus()
  118.  
  119.         self.search_timeout_id = 0
  120.  
  121.         # setup a default icon
  122.         self.icons = common.ToughIconTheme()
  123.         gtk.window_set_default_icon(self.icons.load_icon("gnome-app-install", 32, 0))
  124.  
  125.         SimpleGladeApp.__init__(self, domain="gnome-app-install",
  126.                                 path=datadir+"/gnome-app-install.glade")
  127.  
  128.         self.channelsdir = desktopdir+"/channels"       
  129.         self.datadir = datadir
  130.         self.desktopdir = desktopdir
  131.  
  132.         # sensitive stuff
  133.         self.button_apply.set_sensitive(False)
  134.  
  135.         # create the treeview
  136.         self.setupTreeview()
  137.  
  138.         # are we sorting by popcon
  139.         self.sort_by_ranking = False
  140.  
  141.         # setup the gconf backend
  142.         self.config = gconf.client_get_default()
  143.         self.config.add_dir ("/apps/gnome-app-install", gconf.CLIENT_PRELOAD_NONE)
  144.  
  145.         # Tooltips
  146.         self.tooltips = gtk.Tooltips()
  147.         self.tipmap = {}
  148.  
  149.         # combobx with filters for the application list
  150.         filter_to_restore = self.config.get_int("/apps/gnome-app-install/filter_applications")
  151.         if filter_to_restore not in range(5):
  152.             filter_to_restore = 0
  153.         list_filters = gtk.ListStore(gobject.TYPE_STRING,
  154.                                      gobject.TYPE_BOOLEAN,
  155.                                      gobject.TYPE_STRING,
  156.                                      gobject.TYPE_INT)
  157.         self.combobox_filter.set_model(list_filters)
  158.         filter_renderer = gtk.CellRendererText()
  159.         self.combobox_filter.pack_start(filter_renderer)
  160.         for (desc, sep, tooltip, filter) in [
  161.             (_("All Open Source applications"), False,
  162.              _("Show only applications which can be freely used, "
  163.                "modified and distributed. This includes the main as "
  164.                "well as the community maintained applications"),
  165.              SHOW_ALL_FREE),
  166.             (_("All supported applications"), False,
  167.              _("Show all applications (including commercial) that "
  168.                "are supported by Canonical Ltd or third party vendors. "),
  169.              SHOW_ALL_SUPPORTED),
  170.             (_("All available applications"), False,
  171.              _("Show all available applications including unsupported, "
  172.                "restricted and third party applications"),
  173.              SHOW_ALL),
  174.             ("separator", True, "separator", -1),
  175.             (_("Only main applications"), False,
  176.              _("Show only the main applications which are officially "
  177.                "supported by Canonical Ltd."), 
  178.              SHOW_ONLY_MAIN),
  179.              (_("Only restricted applications"), False,
  180.               _("Show only applications which are restricted in use "
  181.                 "or distribution by copyright or by legal issues in "
  182.                 "some countries"),
  183.              SHOW_ONLY_PROPRIETARY),
  184.             (_("Only third party applications"), False,
  185.              _("Show only applications which are provided and supported "
  186.                "by third party vendors"),
  187.              SHOW_ONLY_THIRD_PARTY)
  188.             ]:
  189.             list_filters.append((desc, sep, tooltip, filter))
  190.             self.combobox_filter.set_row_separator_func(self.separator_filter)
  191.             self.combobox_filter.set_cell_data_func(filter_renderer, 
  192.                                                     self.tooltip_on_filter)
  193.             if filter == filter_to_restore:
  194.                 self.combobox_filter.set_active(len(list_filters) - 1)
  195.                 self.tooltips.set_tip(self.eventbox_filter, tooltip)
  196.  
  197.         # connect the changed signal of the combobox
  198.         self.combobox_filter.connect("changed", self.on_combobox_filter_changed)
  199.  
  200.         self.textview_description = ReleaseNotesViewer()
  201.         self.textview_description.set_wrap_mode(gtk.WRAP_WORD)
  202.         #self.textview_description.set_pixels_above_lines(6)
  203.         self.textview_description.set_pixels_below_lines(3)
  204.         self.textview_description.set_right_margin(6)
  205.         self.textview_description.set_left_margin(6)
  206.         self.scrolled_description.add(self.textview_description)
  207.         self.scrolled_description.set_policy(gtk.POLICY_AUTOMATIC, 
  208.                                              gtk.POLICY_AUTOMATIC)
  209.         atk_desc = self.textview_description.get_accessible()
  210.         atk_desc.set_name(_("Description"))
  211.  
  212.         # now show the main window ...
  213.         if mime_search:
  214.             self.label_progress.set_markup("<big><b>%s</b></big>\n\n%s" %
  215.                                          (_("Searching for appropriate "
  216.                                             "applications"),
  217.                                           _("Please wait. This might take a "
  218.                                             "minute or two.")))
  219.         else:
  220.             self.window_main.show()
  221.  
  222.         # ... and open the cache
  223.         self.updateCache(filter_to_restore)
  224.  
  225.         self.search_entry.grab_focus()
  226.  
  227.         self.show_intro()
  228.         self.textview_description.show()
  229.  
  230.         # this is a set() of packagenames that contain multiple applications
  231.         # if a pkgname is in the set, a dialog was already displayed to the
  232.         # user about this (ugly ...)
  233.         self.multiple_pkgs_seen = set()
  234.  
  235.         # handle arguments
  236.     if mime_search:
  237.             self.window_main.show()
  238.             self.menu.mimeSearch = mime_search
  239.         self.scrolledwindow_left.hide()
  240.             self.search_entry.set_text(mime_search.string)
  241.             self.on_search_timeout()
  242.                     
  243.         # create a worker that does the actual installing etc
  244.         self.packageWorker = PackageWorker()
  245.  
  246.         # used for restore_state
  247.         self.last_toggle = None
  248.  
  249.         # now check if the cache is up-to-date
  250.         time_cache = 0
  251.         for f in glob.glob("/var/lib/apt/lists/*Packages"):
  252.             mt = os.stat(f)[stat.ST_MTIME]
  253.             ct = os.stat(f)[stat.ST_CTIME] 
  254.             if mt > time_cache:
  255.                time_cache = mt
  256.             if ct > time_cache:
  257.                time_cache = ct
  258.         time_source = os.stat("/etc/apt/sources.list")[stat.ST_MTIME]
  259.         for f in glob.glob("/etc/apt/sources.list.d/*.list"):
  260.             mt = os.stat(f)[stat.ST_MTIME]
  261.             ct = os.stat(f)[stat.ST_CTIME]
  262.             if mt > time_source:
  263.                 time_source = mt
  264.             if ct > time_source:
  265.                 time_source = ct
  266.         # FIXME: problem: what can happen is that the sources.list is modified
  267.         #        but we only get I-M-S hits and the mtime of the Packages
  268.         #        files do not change
  269.         #print "cache:  ", time_cache
  270.         #print "source: ", time_source
  271.         if time_cache < time_source:
  272.             self.dialog_cache_outdated.set_transient_for(self.window_main)
  273.             self.dialog_cache_outdated.realize()
  274.             self.dialog_cache_outdated.window.set_functions(gtk.gdk.FUNC_MOVE)
  275.             res = self.dialog_cache_outdated.run()
  276.             self.dialog_cache_outdated.hide()
  277.             if res == gtk.RESPONSE_YES:
  278.                 self.reloadSources()
  279.  
  280.     def separator_filter(self, model, iter, user_data=None):
  281.         """Used to draw a spearator in the combobox for the filters"""
  282.         return model.get_value(iter, 1)
  283.  
  284.     def setupDbus(self):
  285.         """ this sets up a dbus listener if none is installed alread """
  286.         # check if there is another g-a-i already and if not setup one
  287.         # listening on dbus
  288.         try:
  289.             bus = dbus.SessionBus()
  290.         except:
  291.             print "warning: could not initiate dbus"
  292.             return
  293.         proxy_obj = bus.get_object('org.freedesktop.AppInstall', '/org/freedesktop/AppInstallObject')
  294.         iface = dbus.Interface(proxy_obj, 'org.freedesktop.AppInstallIFace')
  295.         try:
  296.             iface.bringToFront()
  297.             #print "send bringToFront"
  298.             sys.exit(0)
  299.         except dbus.DBusException, e:
  300.             print "no listening object (%s) "% e
  301.             bus_name = dbus.service.BusName('org.freedesktop.AppInstall',bus)
  302.             self.dbusControler = AppInstallDbusControler(self, bus_name)
  303.  
  304.     def setBusy(self, flag):
  305.         """ Show a watch cursor if the app is busy for more than 0.3 sec.
  306.             Furthermore provide a loop to handle user interface events """
  307.         if self.window_main.window is None:
  308.             return
  309.         if flag == True:
  310.             self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
  311.         else:
  312.             self.window_main.window.set_cursor(None)
  313.         while gtk.events_pending():
  314.             gtk.main_iteration()
  315.  
  316.  
  317.     def on_combobox_filter_changed(self, combobox):
  318.         """The filter for the application list was changed"""
  319.         self.setBusy(True)
  320.         active = combobox.get_active()
  321.         model = combobox.get_model()
  322.         iter = model.get_iter(active)
  323.         filter = model.get_value(iter, 3)
  324.         if filter in range(6):
  325.             self.config.set_int("/apps/gnome-app-install/filter_applications", filter)
  326.             self.menu.filter = filter
  327.             self.menu._refilter()
  328.         if len(self.menu.treeview_packages.get_model()) == 0:
  329.              self.show_no_results_msg()
  330.         else:
  331.              self.menu.treeview_packages.set_cursor(0)
  332.         tooltip = model.get_value(iter, 2)
  333.         self.tooltips.set_tip(self.eventbox_filter, tooltip)
  334.         self.setBusy(False)
  335.  
  336.     def on_window_main_key_press_event(self, widget, event):
  337.         #print "on_window_main_key_press_event()"
  338.         # from /usr/include/gtk-2.0/gdk/gdkkeysyms.h
  339.         GDK_q = 0x071
  340.         if (event.state & gtk.gdk.CONTROL_MASK) and event.keyval == GDK_q:
  341.             self.on_window_main_delete_event(self.window_main, None)
  342.  
  343.     def error_not_available(self, item):
  344.          """Show an error message that the application cannot be installed"""
  345.          header = _("%s cannot be installed on your "
  346.                     "computer type (%s)") % (item.name,
  347.                                              self.cache.getArch())
  348.          msg = _("Either the application requires special hardware features "
  349.                  "or the vendor decided to not support your computer type.")
  350.          d = gtk.MessageDialog(parent=self.window_main,
  351.                                flags=gtk.DIALOG_MODAL,
  352.                                type=gtk.MESSAGE_ERROR,
  353.                                buttons=gtk.BUTTONS_CLOSE)
  354.          d.set_title("")
  355.          d.set_markup("<big><b>%s</b></big>\n\n%s" % (header, msg))
  356.          d.realize()
  357.          d.window.set_functions(gtk.gdk.FUNC_MOVE)
  358.          d.run()
  359.          d.destroy()
  360.  
  361.     def tooltip_on_filter(self, cell_view, cell_renderer, model, iter):
  362.         """
  363.         Show a disclaimer in the tooltips of the filters
  364.         """
  365.         id = model.get_path(iter)[0]
  366.         item_text = model.get_value(iter, 0)
  367.         item_disclaimer = model.get_value(iter, 2)
  368.         cell_renderer.set_property('text', item_text)
  369.         cell_parent = cell_view.get_parent()
  370.  
  371.         if isinstance(cell_parent, gtk.MenuItem) \
  372.            and (cell_parent not in self.tipmap \
  373.                 or self.tipmap[cell_parent] != item_disclaimer):
  374.             self.tipmap[cell_parent] = item_disclaimer
  375.             self.tooltips.set_tip(cell_parent, item_disclaimer)
  376.  
  377.     # install toggle on the treeview
  378.     def on_install_toggle(self, renderer, path):
  379.         #print "on_install_toggle: %s %s" % (renderer, path)
  380.         model = self.treeview_packages.get_model()
  381.         (name, item, popcon) = model[path]
  382.         #print "on_install_toggle(): %s %s %s" % (name, item)
  383.         # first check if the operation is save
  384.         pkg = item.pkgname
  385.         if not (self.cache.has_key(pkg) and \
  386.            self.cache[pkg].candidateDownloadable):
  387.             for it in self.cache._cache.FileList:
  388.                 if it.Component != "" and it.Component == item.component:
  389.                     # FIXME: use aptsources to check if no internet sources
  390.                     #        and only cdrom sources are used. if yes continue
  391.                     #        and if not report an error and provide the 
  392.                     #        possibility to apt-get update
  393.                     self.error_not_available(item)
  394.                     return False
  395.             self.saveState()
  396.             if self.addChannel(item):
  397.                 self.last_toggle = name
  398.                 self.restoreState()
  399.             return
  400.         if self.cache[pkg].isInstalled:
  401.             # check if it can be removed savly
  402.             # FIXME: Provide a list of the corresponding packages or
  403.             #        apps
  404.             self.cache[pkg].markDelete(autoFix=False)
  405.             if self.cache._depcache.BrokenCount > 0:
  406.                 d = gtk.MessageDialog(parent=self.window_main,
  407.                                       flags=gtk.DIALOG_MODAL,
  408.                                       type=gtk.MESSAGE_ERROR,
  409.                                       buttons=gtk.BUTTONS_CLOSE)
  410.                 d.set_title("")
  411.                 d.set_markup("<big><b>%s</b></big>\n\n%s" % \
  412.                              ((_("Cannot remove '%s'") % pkg),
  413.                               (_("One or more applications depend on '%s'. "
  414.                                 "To remove '%s' and the dependent applications,"
  415.                                 " please switch to the advanced software "
  416.                                 "manager.") % (pkg, pkg))))
  417.                 d.realize()
  418.                 d.window.set_functions(gtk.gdk.FUNC_MOVE)
  419.                 d.run()
  420.                 d.destroy()
  421.                 self.cache.clean()
  422.                 return
  423.             self.cache[pkg].markKeep()
  424.             # FIXME: those assert may be a bit too strong,
  425.             # we may just rebuild the cache if something is
  426.             # wrong
  427.             assert self.cache._depcache.BrokenCount == 0
  428.             assert self.cache._depcache.DelCount == 0
  429.         else:
  430.             # check if it can be installed savely
  431.             apt_error = False
  432.             try:
  433.                 self.cache[pkg].markInstall(autoFix=True)
  434.             except SystemError:
  435.                 apt_error = True
  436.             if self.cache._depcache.BrokenCount > 0 or \
  437.                self.cache._depcache.DelCount > 0 or apt_error:
  438.                 # FIXME: Resolve conflicts
  439.                 d = gtk.MessageDialog(parent=self.window_main,
  440.                                       flags=gtk.DIALOG_MODAL,
  441.                                       type=gtk.MESSAGE_ERROR,
  442.                                       buttons=gtk.BUTTONS_CLOSE)
  443.                 d.set_title("")
  444.                 d.set_markup("<big><b>%s</b></big>\n\n%s" % (
  445.                              (_("Cannot install '%s'") % pkg),
  446.                              (_("This application conflicts with other "
  447.                                 "installed software. To install '%s' "
  448.                                 "the conflicting software "
  449.                                 "must be removed before.\n\n"
  450.                                 "Switch to the advanced mode to resolve this "
  451.                                 "conflict.") % pkg)))
  452.                 d.realize()
  453.                 d.window.set_functions(gtk.gdk.FUNC_MOVE)
  454.                 d.run()
  455.                 d.destroy()
  456.                 # reset the cache
  457.                 # FIXME: a "pkgSimulateInstall,remove"  thing would
  458.                 # be nice
  459.                 self.cache.clean()
  460.                 # FIXME: those assert may be a bit too strong,
  461.                 # we may just rebuild the cache if something is
  462.                 # wrong
  463.                 assert self.cache._depcache.BrokenCount == 0
  464.                 assert self.cache._depcache.DelCount == 0
  465.                 return
  466.         # invert the current selection
  467.         item.toInstall = not item.toInstall
  468.         # check if the package provides multiple desktop applications
  469.         if len(self.menu.pkg_to_app[item.pkgname]) > 1:
  470.             apps = self.menu.pkg_to_app[item.pkgname]
  471.             # update the install-status of the other apps
  472.             for app in apps:
  473.                 app.toInstall = item.toInstall
  474.             # hack: redraw the treeview (to update the toggle icons after the
  475.             #       tree-model was changed)
  476.             self.treeview_packages.queue_draw()
  477.             # show something to the user (if he hasn't already seen it)
  478.             if not item.pkgname in self.multiple_pkgs_seen:
  479.                 dia = DialogMultipleApps(self.datadir, self.window_main, \
  480.                                          apps, item.name)
  481.                 dia.run()
  482.                 dia.hide()
  483.                 self.multiple_pkgs_seen.add(item.pkgname)
  484.  
  485.         self.button_apply.set_sensitive(self.menu.isChanged())
  486.  
  487.     def addChannel(self, item):
  488.         """Ask for confirmation to add the missing channel or
  489.            component of the current selected application"""
  490.         if item.thirdparty and item.channel:
  491.             dia = DialogProprietary(self.datadir, self.window_main, item)
  492.         else:
  493.             dia = DialogUnavailable(self.datadir, self.window_main, item)
  494.         res = dia.run()
  495.         dia.hide()
  496.         # the user canceld
  497.         if res != gtk.RESPONSE_OK:
  498.             return False
  499.         # let go
  500.         if item.component:
  501.             self.enableComponent(item.component)
  502.             # FIXME: make sure to fix this after release in a more elegant
  503.             #        and generic way
  504.             # we check if it is multiverse and if it is and we don't
  505.             # have universe already, we add universe too (because
  506.             # multiverse depends on universe)
  507.             if item.component == "multiverse":
  508.                 for it in self.cache._cache.FileList:
  509.                     if it.Component != "" and it.Component == "universe":
  510.                         break
  511.                 else:
  512.                     self.enableComponent("universe")
  513.         elif item.channel:
  514.             self.enableChannel(item.channel)
  515.         else:
  516.             # should never happen
  517.             print "ERROR: addChannel() called without channel or component"
  518.             return False
  519.         # now do the reload
  520.         self.reloadSources()
  521.         return True
  522.  
  523.  
  524.     def setupTreeview(self):
  525.         def popcon_view_func(cell_layout, renderer, model, iter, self):
  526.             """
  527.             Create a pixmap showing a row of stars representing the popularity
  528.             of the corresponding application
  529.             """
  530.             (name, item, popcon) = model[iter]
  531.             rank = int((5 * item.popcon / self.menu.popcon_max))
  532.             pix_rating = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True,
  533.                                         8, 96, 16) # depth, width, height
  534.             pix_rating.fill(0x0)
  535.             for i in range(5):
  536.                 if not i > rank:
  537.                     self.pixbuf_star.copy_area(0,0,        # from
  538.                                                16,16,      # size
  539.                                                pix_rating, # to-pixbuf
  540.                                                20 * i, 0)  # dest
  541.                 else:
  542.                     break
  543.             renderer.set_property("pixbuf", pix_rating)
  544.  
  545.         def package_view_func(cell_layout, renderer, model, iter):
  546.             app = model.get_value(iter, COL_ITEM)
  547.             name = app.name
  548.             desc = app.description
  549.             current = app.isInstalled
  550.             future = app.toInstall
  551.             available = app.available
  552.             if current != future:
  553.                 markup = "<b>%s</b>\n<small><b>%s</b></small>" % (name, desc)
  554.             else:
  555.                 markup = "%s\n<small>%s</small>" % (name, desc)
  556.             renderer.set_property("markup", markup)
  557.  
  558.         def toggle_cell_func(column, cell, model, iter):
  559.             menuitem = model.get_value(iter, COL_ITEM)
  560.             cell.set_property("active", menuitem.toInstall)
  561.             cell.set_property("visible", True)
  562.             if menuitem.architectures and \
  563.                self.cache.getArch() not in menuitem.architectures:
  564.                 cell.set_property("activatable", False)
  565.             else:
  566.                 cell.set_property("activatable", True)
  567.  
  568.         def icon_cell_func(column, cell, model, iter):
  569.             (menuitem,) = model.get(iter, COL_ITEM)
  570.             if menuitem == None or menuitem.iconname == None:
  571.                 cell.set_property("pixbuf", None)
  572.                 cell.set_property("visible", False)
  573.                 return
  574.             icon = menuitem.icontheme._getIcon(menuitem.iconname, 24)
  575.             cell.set_property("pixbuf", icon)
  576.             cell.set_property("visible", True)
  577.  
  578.         # columns
  579.         column = gtk.TreeViewColumn(_("Application"))
  580.         column.set_expand(True)
  581.         column.set_sort_column_id(COL_NAME)
  582.         column.set_resizable(True)
  583.         column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
  584.         check_column = gtk.TreeViewColumn("")
  585.  
  586.         # popcon renderer
  587.         renderer = gtk.CellRendererPixbuf()
  588.         renderer.set_property("xpad", 4)
  589.         popcon_column = gtk.TreeViewColumn(_("Popularity"), renderer)
  590.         popcon_column.set_sort_column_id(COL_POPCON)
  591.         popcon_column.set_cell_data_func(renderer, popcon_view_func, self)
  592.         self.pixbuf_star = self.icons.load_icon("gnome-app-install-star", 16, 0)
  593.  
  594.         # check boxes
  595.         self.toggle_render = gtk.CellRendererToggle()
  596.         self.store_toggle_id = self.toggle_render.connect('toggled', self.on_install_toggle)
  597.         self.toggle_render.set_property("xalign", 0.3)
  598.         check_column.pack_start(self.toggle_render, False)
  599.         check_column.set_cell_data_func (self.toggle_render, toggle_cell_func)
  600.         
  601.         # program icons
  602.         render = gtk.CellRendererPixbuf()
  603.         column.pack_start(render, False)
  604.         #column.add_attribute(render, "pixbuf", COL_ICON)
  605.         column.set_cell_data_func (render, icon_cell_func)
  606.  
  607.         # package names
  608.         render = gtk.CellRendererText()
  609.         render.set_property("ellipsize", pango.ELLIPSIZE_END)
  610.         column.pack_start(render, True)
  611.         column.add_attribute(render, "markup", COL_NAME)
  612.         column.set_cell_data_func (render, package_view_func)
  613.         
  614.         self.treeview_packages.append_column(check_column)
  615.         self.treeview_packages.append_column(column)
  616.         self.treeview_packages.append_column(popcon_column)
  617.  
  618.         # categories
  619.         column = gtk.TreeViewColumn("")
  620.         # program icons
  621.         render = gtk.CellRendererPixbuf()
  622.         column.pack_start(render, False)
  623.         column.set_cell_data_func (render, icon_cell_func)
  624.  
  625.         # menu group names
  626.         self.treeview_categories.set_search_column(COL_NAME)
  627.         render = gtk.CellRendererText()
  628.         render.set_property("scale", 1.0)
  629.         column.pack_start(render, True)
  630.         column.add_attribute(render, "markup", COL_NAME)
  631.         self.treeview_categories.append_column(column)
  632.         
  633.         
  634.     def saveState(self):
  635.         """ save the current state of the app """
  636.         # store the pkgs that are marked for removal or installation
  637.         (self.to_add, self.to_rm) = self.menu.getChanges()
  638.         (self.cursor_categories_path,x) = self.treeview_categories.get_cursor()
  639.         model = self.treeview_packages.get_model()
  640.         (packages_path, x) = self.treeview_packages.get_cursor()
  641.         if packages_path:
  642.             it = model.get_iter(packages_path)
  643.             self.cursor_pkgname = model.get_value(it, COL_NAME)
  644.         else:
  645.             self.cursor_pkgname = None
  646.  
  647.     def restoreState(self):
  648.         """ restore the current state of the app """
  649.         # set category
  650.         self.treeview_categories.set_cursor(self.cursor_categories_path)
  651.         model = self.treeview_packages.get_model()
  652.         # reapply search
  653.         query = self.search_entry.get_text()
  654.         if query:
  655.             self.on_search_timeout()
  656.         # remark all packages that were marked for installation
  657.         for item in self.to_add:
  658.             if self.cache.has_key(item.pkgname):
  659.                 try:
  660.                     self.cache[item.pkgname].markInstall(autoFix=True)
  661.                 except SystemError:
  662.                     continue
  663.                 # set the state of the corresponing apps
  664.                 apps = self.menu.pkg_to_app[item.pkgname]
  665.                 for app in apps:
  666.                     app.toInstall = item.toInstall
  667.         # remark all packages that were marked for removal
  668.         for item in self.to_rm:
  669.             if self.cache.has_key(item.pkgname):
  670.                 try:
  671.                     self.cache[item.pkgname].markDelete(autoFix=True)
  672.                 except SystemError:
  673.                     continue
  674.                 # set the state of the corresponing apps
  675.                 apps = self.menu.pkg_to_app[item.pkgname]
  676.                 for app in apps:
  677.                     app.toInstall = item.toInstall
  678.         # redraw the treeview so that all check buttons are updated
  679.         self.treeview_packages.queue_draw()
  680.  
  681.         # find package
  682.         for it in iterate_list_store(model,model.get_iter_first()):
  683.             name = model.get_value(it, COL_NAME)
  684.             # if the app corresponds to the one toggled before toggle it again
  685.             if name == self.last_toggle:
  686.                 self.last_toggle = None
  687.                 path = model.get_path(it)
  688.                 self.treeview_packages.set_cursor(path)
  689.                 self.on_install_toggle(None, path)
  690.                 break
  691.             # if the app correpsonds to the one selected before select it again
  692.             if name == self.cursor_pkgname and self.last_toggle == None:
  693.                 path = model.get_path(it)
  694.                 self.treeview_packages.set_cursor(path)
  695.                 break
  696.  
  697.     def updateCache(self, filter=SHOW_ONLY_MAIN):
  698.         self.window_main.set_sensitive(False)
  699.         self.setBusy(True)
  700.             
  701.         progress = GtkOpProgressWindow(self.glade,self.window_main)
  702.         try:
  703.             self.cache = MyCache(progress)
  704.         except Exception, e:
  705.             # show an error dialog if something went wrong with the cache
  706.             header = _("Failed to check for installed and available applications")
  707.             msg = _("This is a major failure of your software " \
  708.                     "management system. Check the file permissions and "\
  709.                     "correctness of the file '/etc/apt/sources.list' and "\
  710.                     "reload the software information: 'sudo apt-get update'.")
  711.             print e
  712.             d = gtk.MessageDialog(parent=self.window_main,
  713.                                   flags=gtk.DIALOG_MODAL,
  714.                                   type=gtk.MESSAGE_ERROR,
  715.                                   buttons=gtk.BUTTONS_CLOSE)
  716.             d.set_title("")
  717.             d.set_markup("<big><b>%s</b></big>\n\n%s" % (header, msg))
  718.             d.realize()
  719.             d.window.set_functions(gtk.gdk.FUNC_MOVE)
  720.             d.run()
  721.             d.destroy()
  722.             sys.exit(1)
  723.  
  724.         self.menu = ApplicationMenu(self.desktopdir,
  725.                                     self.datadir,
  726.                                     self.cache,
  727.                                     self.treeview_categories,
  728.                                     self.treeview_packages,
  729.                                     progress, filter)
  730.  
  731.         # move to "All" category per default
  732.         self.treeview_categories.set_cursor((0,))
  733.  
  734.         adj = self.scrolled_window.get_vadjustment()
  735.         adj.set_value(0)
  736.  
  737.         self.setBusy(False)
  738.         self.window_main.set_sensitive(True)
  739.     
  740.     def ignoreChanges(self):
  741.         """
  742.         If any changes have been made, ask the user to apply them and return
  743.         a value based on the status.
  744.         Returns True if the changes should be thrown away and False otherwise
  745.         """
  746.         if not self.menu.isChanged():
  747.             return True
  748.         (to_add, to_rm) = self.menu.getChanges()
  749.         # FIXME: move this set_markup into the dialog itself
  750.         dia = DialogPendingChanges(self.datadir, self.window_main,
  751.                                    to_add, to_rm)
  752.         header =_("Apply changes to installed applications before closing?")
  753.         msg = _("If you do not apply your changes they will be lost "\
  754.                 "permanently.")
  755.         dia.label_pending.set_markup("<big><b>%s</b></big>\n\n%s" % \
  756.                                      (header, msg))
  757.         dia.button_ignore_changes.set_label(_("_Close Without Applying"))
  758.         dia.button_ignore_changes.show()
  759.         dia.dialog_pending_changes.realize()
  760.         dia.dialog_pending_changes.window.set_functions(gtk.gdk.FUNC_MOVE)
  761.         res = dia.run()
  762.         dia.hide()
  763.         return res
  764.  
  765.     
  766.     # ----------------------------
  767.     # Main window button callbacks
  768.     # ----------------------------
  769.     
  770.     def on_button_help_clicked(self, widget):
  771.         subprocess.Popen(["/usr/bin/yelp", "ghelp:gnome-app-install"])
  772.  
  773.     def applyChanges(self, final=False):
  774.         #print "do_apply()"
  775.         (to_add, to_rm) = self.menu.getChanges()
  776.         # Set a busy cursor
  777.         self.setBusy(True)
  778.         # Get the selections delta for the changes and apply them
  779.         ret = self.packageWorker.perform_action(self.window_main, to_add, to_rm)
  780.  
  781.         # error from gksu
  782.         if ret != 0:
  783.             self.setBusy(False)
  784.             return False
  785.         
  786.         # Reload the APT cache and treeview
  787.         if final != True:
  788.             self.updateCache(filter=self.menu.filter)
  789.  
  790.         self.button_apply.set_sensitive(self.menu.isChanged())
  791.         
  792.         # Show window with newly installed programs
  793.         #self.checkNewStore() # only show things that successfully installed
  794.         if len(to_add) > 0:
  795.             dia = DialogNewlyInstalled(self.datadir, self.window_main,
  796.                                        to_add, self.cache)
  797.             dia.run()
  798.             dia.hide()
  799.         
  800.         # And reset the cursor
  801.         self.setBusy(False)
  802.         return True
  803.  
  804.     def on_button_ok_clicked(self, button):
  805.         # nothing changed, exit
  806.         if not self.menu.isChanged():
  807.             self.quit()
  808.         # something changed, only exit if the changes have
  809.         # succesfully been applied (otherwise cancel)
  810.         if self.confirmChanges():
  811.             if self.applyChanges(final=True):
  812.             if self.menu.mimeSearch: self.menu.mimeSearch.retry_open()
  813.                 self.quit()
  814.  
  815.     def confirmChanges(self):
  816.         (to_add, to_rm) = self.menu.getChanges()
  817.         dia = DialogPendingChanges(self.datadir, self.window_main,
  818.                                    to_add, to_rm)
  819.         # FIXME: move this inside the dialog class, we show a different
  820.         # text for a quit dialog and a approve dialog
  821.         header = _("Apply the following changes?")
  822.         msg = _("Please take a final look through the list of "\
  823.                 "applications that will be installed or removed.")
  824.         dia.label_pending.set_markup("<big><b>%s</b></big>\n\n%s" % \
  825.                                      (header, msg))
  826.         res = dia.run()
  827.         dia.hide()
  828.         if res != gtk.RESPONSE_APPLY:
  829.             # anything but ok makes us leave here
  830.             return False
  831.         else:
  832.             return True
  833.  
  834.     def on_button_apply_clicked(self, button):
  835.         ret = self.confirmChanges()
  836.         if ret == True :
  837.             self.applyChanges()
  838.  
  839.     def on_search_timeout(self):
  840.         query = self.search_entry.get_text()
  841.         store = self.menu.treeview_packages.get_model()
  842.         if query.lstrip() != "":
  843.             self.menu.searchTerms = query.lower().split(" ")
  844.             self.sort_by_ranking = True
  845.         else:
  846.             self.menu.searchTerms = []
  847.             self.sort_by_ranking = False
  848.         self.on_treeview_categories_cursor_changed(self.treeview_categories)
  849.         # FIXME: should be different for "show all applications"
  850.         if len(store) == 0:
  851.             self.show_no_results_msg()
  852.         else:
  853.             self.menu.treeview_packages.set_cursor(0)
  854.  
  855.     def on_search_entry_changed(self, widget):
  856.         #print "on_search_entry_changed()"
  857.         if self.search_timeout_id > 0:
  858.             gobject.source_remove(self.search_timeout_id)
  859.         self.search_timeout_id = gobject.timeout_add(500,self.on_search_timeout)
  860.             
  861.     def on_button_clear_clicked(self, button):
  862.         self.search_entry.set_text("")
  863.         # reset the search
  864.         self.menu.search(None)
  865.         # Point the treeview back to the original store
  866.         #self.treeview_packages.set_model(self.menu.store)
  867.         #self.treeview_packages.set_rules_hint(False)
  868.         self.button_clear.set_sensitive(False)
  869.  
  870.     def on_item_about_activate(self, button):
  871.         from Version import VERSION
  872.         self.dialog_about.set_version(VERSION)
  873.         self.dialog_about.run()
  874.         self.dialog_about.hide()
  875.  
  876.     def on_reload_activate(self, item):
  877.         self.reloadSources()
  878.         
  879.     def on_button_cancel_clicked(self, item):
  880.         self.quit()
  881.  
  882.                 
  883.     def reloadSources(self):
  884.         self.window_main.set_sensitive(False)
  885.         ret = self.packageWorker.perform_action(self.window_main,
  886.                                                 action=PackageWorker.UPDATE)
  887.         self.updateCache(filter=self.menu.filter)
  888.         self.window_main.set_sensitive(True)
  889.         return ret
  890.  
  891.     def enableChannel(self, channel):
  892.         """ enables a channel with 3rd party software """
  893.         # enabling a channel right now is very easy, just copy it in place
  894.         channelpath = "%s/%s.list" % (self.channelsdir,channel)
  895.         channelkey = "%s/%s.key" % (self.channelsdir,channel)
  896.         if not os.path.exists(channelpath):
  897.             print "WARNING: channel '%s' not found" % channelpath
  898.             return
  899.         #shutil.copy(channelpath,
  900.         #            apt_pkg.Config.FindDir("Dir::Etc::sourceparts"))
  901.         cmd = ["gksu",
  902.                "--desktop", "/usr/share/applications/gnome-app-install.desktop",
  903.                "--",
  904.                "cp", channelpath,
  905.                apt_pkg.Config.FindDir("Dir::Etc::sourceparts")]
  906.         subprocess.call(cmd)
  907.         # install the key as well
  908.         if os.path.exists(channelkey):
  909.             cmd = ["gksu",
  910.                    "--desktop",
  911.                    "/usr/share/applications/gnome-app-install.desktop",
  912.                    "--",
  913.                    "apt-key", "add",channelkey]
  914.             subprocess.call(cmd)
  915.                 
  916.     def enableComponent(self, component):
  917.         """ Enables a component of the current distribution
  918.             (in a seperate file in /etc/apt/sources.list.d/$dist-$comp)
  919.         """
  920.         # get the codename of the currently used distro
  921.         pipe = os.popen("lsb_release -c -s")
  922.         distro = pipe.read().strip()
  923.         del pipe
  924.         # sanity check
  925.         if component == "":
  926.             print "no repo found in enableRepository"
  927.             return
  928.  
  929.         # first find the master mirror, FIXME: something we should
  930.         # shove into aptsources.py?
  931.         mirror = "http://archive.ubuntu.com/ubuntu"
  932.         sources = SourcesList()
  933.         newentry_sec = ""
  934.         newentry_updates = ""
  935.         for source in sources:
  936.             if source.invalid or source.disabled:
  937.                 continue
  938.             #print "checking: %s" % source.dist
  939.             # check if the security updates are enabled
  940.             # if yes add the components to the security updates
  941.             if source.dist == ("%s-updates" % distro):
  942.                 if component in source.comps:
  943.                     newentry_updates = ""
  944.                 else:
  945.                     newentry_updates = "deb %s %s-updates %s\n" % (source.uri,
  946.                                                                    distro,
  947.                                                                    component)
  948.             if source.dist == ("%s-security" % distro):
  949.                 #print "need to enable security as well"
  950.                 if component in source.comps:
  951.                     newentry_sec = ""
  952.                 else:
  953.                     newentry_sec = "deb http://security.ubuntu.com/ubuntu "\
  954.                                    "%s-security %s\n" % (distro, 
  955.                                                          component)
  956.             if source.uri != mirror and is_mirror(mirror,source.uri):
  957.                 mirror = source.uri
  958.  
  959.         newentry = "# automatically added by gnome-app-install on %s\n" % \
  960.                    datetime.today()
  961.         newentry += "deb %s %s %s\n" % (mirror, distro, component)
  962.         if newentry_sec != "":
  963.             newentry += newentry_sec
  964.         if newentry_updates != "":
  965.             newentry += newentry_updates
  966.         channel_dir = apt_pkg.Config.FindDir("Dir::Etc::sourceparts")
  967.         channel_file = "%s-%s.list" % (distro, component)
  968.  
  969.         channel = tempfile.NamedTemporaryFile()
  970.         channel.write(newentry)
  971.         channel.flush()
  972.         #print "copy: %s %s" % (channel.name, channel_dir+channel_file)
  973.         cmd = ["gksu", "--desktop",
  974.                "/usr/share/applications/gnome-app-install.desktop",
  975.                "--",
  976.                "install","-m","644","-o","0",
  977.                channel.name, channel_dir+channel_file]
  978.         #print cmd
  979.         subprocess.call(cmd)
  980.             
  981.         
  982.     
  983.     # ---------------------------
  984.     # Window management functions
  985.     # ---------------------------
  986.     
  987.  
  988.     def on_window_main_delete_event(self, window, event):
  989.         if window.get_property("sensitive") == False:
  990.             return True
  991.         if self.menu.isChanged():
  992.             ret = self.ignoreChanges()
  993.             if ret == gtk.RESPONSE_APPLY:
  994.                 if not self.applyChanges(final=True):
  995.                     return True
  996.             elif ret == gtk.RESPONSE_CANCEL:
  997.                 return True
  998.             elif ret == gtk.RESPONSE_CLOSE:
  999.                 self.quit()
  1000.         self.quit()
  1001.  
  1002.     def on_window_main_destroy_event(self, data=None):
  1003.         #if self.window_installed.get_property("visible") == False:
  1004.         #    self.quit()
  1005.         self.quit()
  1006.             
  1007.     def quit(self):
  1008.         gtk.main_quit()
  1009.         sys.exit(0)
  1010.     
  1011.     def show_description(self, item):
  1012.         """Collect and show some information about the package that 
  1013.            contains the selected application"""
  1014.         details = []
  1015.         clean_desc = ""
  1016.         short_desc = ""
  1017.         version = ""
  1018.         desktop_environment = ""
  1019.         icons = []
  1020.  
  1021.         if self.cache.has_key(item.pkgname):
  1022.             version = self.cache[item.pkgname].candidateVersion
  1023.             # try to guess the used desktop environment
  1024.             for dependencies in desktop_environment_mapping:
  1025.                 for dep in dependencies:
  1026.                     if self.cache.pkgDependsOn(item.pkgname, dep):
  1027.                         details.append(desktop_environment_mapping[dependencies][0] % item.name)
  1028.                         if desktop_environment_mapping[dependencies][1] != None:
  1029.                             icons.append([desktop_environment_mapping[dependencies][1], desktop_environment_mapping[dependencies][0] % item.name])
  1030.                         break
  1031.  
  1032.         if item.available:
  1033.             pkg = self.cache[item.pkgname]
  1034.             rough_desc = pkg.description.rstrip(" \n\t")
  1035.             # the first line is the short description
  1036.             first_break = rough_desc.find("\n")
  1037.             short_desc = rough_desc[:first_break].rstrip("\n\t ")
  1038.             rough_desc = rough_desc[first_break + 1:].lstrip("\n\t ")
  1039.                     
  1040.             # so some regular expression magic on the description
  1041.             #print "\n\nAdd a newline before each bullet:\n"
  1042.             p = re.compile(r'^(\s|\t)*(\*|0|-)',re.MULTILINE)
  1043.             rough_desc = p.sub('\n*', rough_desc)
  1044.             #print rough_desc
  1045.  
  1046.             #print "\n\nreplace all newlines by spaces\n"
  1047.             p = re.compile(r'\n', re.MULTILINE)
  1048.             rough_desc = p.sub(" ", rough_desc)
  1049.             #print rough_desc
  1050.  
  1051.             #print "\n\nreplace all multiple spaces by newlines:\n"
  1052.             p = re.compile(r'\s\s+', re.MULTILINE)
  1053.             rough_desc = p.sub("\n", rough_desc)
  1054.  
  1055.             lines = rough_desc.split('\n')
  1056.             #print "\n\nrough: \n"
  1057.             #print rough_desc
  1058.  
  1059.             for i in range(len(lines)):
  1060.                 if lines[i].split() == []:
  1061.                     continue
  1062.                 first_chunk = lines[i].split()[0]
  1063.                 if first_chunk == "*":
  1064.                     p = re.compile(r'\*\s*', re.MULTILINE)
  1065.                     lines[i] = p.sub("", lines[i])
  1066.                     clean_desc += "ΓÇó %s\n" % lines[i]
  1067.                 else:
  1068.                     clean_desc += "%s\n" % lines[i]
  1069.             #print clean_desc
  1070.         else:
  1071.             msg = _("%s cannot be installed" % item.name)
  1072.             # check if we have seen the component
  1073.             for it in self.cache._cache.FileList:
  1074.                 # FIXME: we need to exclude cdroms here. the problem is
  1075.                 # how to detect if a PkgFileIterator is pointing to a cdrom
  1076.                 if (it.Component != "" and it.Component == item.component) or\
  1077.                    (item.architectures and \
  1078.                     not self.cache.getArch() in item.architectures):
  1079.                     # warn that this app is not available on this plattform
  1080.                     details.append(_("%s cannot be installed on your "
  1081.                                      "computer type (%s). Either the "
  1082.                                      "application requires special "
  1083.                                      "hardware features or the vendor "
  1084.                                      "decided to not support your "
  1085.                                      "computer type.") % (item.name, 
  1086.                                      self.cache.getArch()))
  1087.                     break
  1088.         # A short statement about the freedom and legal status of the 
  1089.         # application
  1090.         if item.component == "universe":
  1091.             care_about_freedom =_("This application is brought to you by the "
  1092.                                   "Ubuntu community.")
  1093.             icons.append(["application-community", care_about_freedom])
  1094.         elif item.component == "multiverse" or item.thirdparty:
  1095.             care_about_freedom = _("The use, modification and distribution "
  1096.                                    "of %s is restricted by copyright or by "
  1097.                                    "legal terms in some countries.") \
  1098.                                    % item.name
  1099.             icons.append(["application-proprietary", care_about_freedom])
  1100.         elif item.thirdparty or item.channel:
  1101.             care_about_freedom = ("%s is provided by a third party vendor "
  1102.                                   "and is therefore not an official part "
  1103.                                   "of Ubuntu. The third party vendor is "
  1104.                                   "responsible for support and security "
  1105.                                   "updates.") % item.name
  1106.             icons.append(["application-proprietary", care_about_freedom])
  1107.         elif item.component == "main" or item.supported:
  1108.             icons.append(["application-supported", 
  1109.                           _("Canonical Ltd. supports %s with "
  1110.                             "security updates") % item.name])
  1111.             care_about_freedom = ""
  1112.         else:
  1113.             care_about_freedom = ""
  1114.  
  1115.         # mutliple apps per pkg check
  1116.         s = ""
  1117.         if self.menu.pkg_to_app.has_key(item.name) and \
  1118.            len(self.menu.pkg_to_app[item.name]) > 1:
  1119.             s = _("This application is bundled with "
  1120.                   "the following applications: ")
  1121.             apps = self.menu.pkg_to_app[item.name]
  1122.             s += ", ".join([pkg.name for pkg in apps])
  1123.             details.append(s)
  1124.  
  1125.         # init the textview that shows the description of the app
  1126.         buffer = self.textview_description.get_buffer()
  1127.         buffer.set_text("")
  1128.         # remove all old tags
  1129.         tag_table = buffer.get_tag_table()
  1130.         tag_table.foreach((lambda tag, table: table.remove(tag)), tag_table)
  1131.         iter = buffer.get_start_iter()
  1132.         # we need the default font size for the vertically justification
  1133.         pango_context = self.textview_description.get_pango_context()
  1134.         font_desc = pango_context.get_font_description()
  1135.         font_size = font_desc.get_size() / pango.SCALE
  1136.         if item.iconname != "":
  1137.             # justify the icon and the app name
  1138.             if font_size * pango.SCALE_LARGE > 32:
  1139.                 icon_size = 32
  1140.                 adjust_vertically = 0
  1141.             elif font_size * pango.SCALE_LARGE < 10:
  1142.                 icon_size = 24
  1143.                 adjust_vertically = (icon_size - font_size * pango.SCALE_LARGE)\
  1144.                                     /2
  1145.             else:
  1146.                 icon_size = 32
  1147.                 adjust_vertically = (icon_size - font_size * pango.SCALE_LARGE)\
  1148.                                     / 2
  1149.             tag_name = buffer.create_tag("app-icon",
  1150.                                          pixels_above_lines=6)
  1151.             tag_name = buffer.create_tag("app-name",
  1152.                                          rise=adjust_vertically * pango.SCALE,
  1153.                                          pixels_above_lines=6,
  1154.                                          weight=pango.WEIGHT_BOLD,
  1155.                                          scale=pango.SCALE_LARGE)
  1156.             icon_pixbuf = item.icontheme._getIcon(item.iconname, icon_size)
  1157.             buffer.insert_pixbuf(iter, icon_pixbuf)
  1158.             (start_iter, end_iter,) = buffer.get_bounds()
  1159.             buffer.apply_tag_by_name("app-icon", start_iter, end_iter)
  1160.         else:
  1161.             tag_name = buffer.create_tag("app-name",
  1162.                                          pixels_above_lines=6,
  1163.                                          weight=pango.WEIGHT_BOLD,
  1164.                                          scale=pango.SCALE_LARGE)
  1165.         buffer.insert_with_tags_by_name(iter, " %s" % item.name, "app-name")
  1166.         if short_desc != "": 
  1167.             tag_name = buffer.create_tag("short-desc",
  1168.                                          weight=pango.WEIGHT_BOLD)
  1169.             buffer.insert_with_tags_by_name(iter,
  1170.                                             "\n%s" % short_desc,
  1171.                                             "short-desc")
  1172.             for emblem in icons:
  1173.                 image_emblem = gtk.Image()
  1174.                 image_emblem.set_from_icon_name(emblem[0],
  1175.                                                 gtk.ICON_SIZE_MENU)
  1176.                 image_emblem.set_pixel_size(16)
  1177.                 event = gtk.EventBox()
  1178.                 # use the base color of the textview for the image
  1179.                 style = self.textview_description.get_style()
  1180.                 event.modify_bg(gtk.STATE_NORMAL,
  1181.                                 style.base[gtk.STATE_NORMAL])
  1182.                 event.add(image_emblem)
  1183.                 self.tooltips.set_tip(event, emblem[1])
  1184.                 buffer.insert(iter, " ")
  1185.                 anchor = buffer.create_child_anchor(iter)
  1186.                 self.textview_description.add_child_at_anchor(event,
  1187.                                                               anchor)
  1188.                 event.show()
  1189.                 image_emblem.show()
  1190.         elif care_about_freedom != "": 
  1191.             buffer.insert(iter, "\n%s" % care_about_freedom)
  1192.         if clean_desc != "": 
  1193.             buffer.insert(iter, "\n%s" % clean_desc)
  1194.         if version != "":
  1195.             buffer.insert(iter, _("Version: %s (%s)") % (version, item.pkgname))
  1196.         if len(details) > 0:
  1197.             for x in details:
  1198.                 buffer.insert(iter, "\n%s" % x)
  1199.  
  1200.     def clear_description(self):
  1201.         buffer = self.textview_description.get_buffer()
  1202.         buffer.set_text("")
  1203.  
  1204.     def on_treeview_packages_row_activated(self, treeview, path, view_column):
  1205.         iter = treeview.get_model().get_iter(path)
  1206.         item = treeview.get_model().get_value(iter, COL_ITEM)
  1207.         # We have to do this check manually, since we don't know if the
  1208.         # corresponding CellRendererToggle is not activatable
  1209.         if item.architectures and \
  1210.             self.cache.getArch() not in item.architectures:
  1211.             return False
  1212.         self.on_install_toggle(None, path)
  1213.  
  1214.     def on_treeview_categories_cursor_changed(self, treeview):
  1215.         #print "cursor changed"
  1216.         self.setBusy(True)
  1217.         path = treeview.get_cursor()[0]
  1218.         iter = treeview.get_model().get_iter(path)
  1219.         (name, item) = treeview.get_model()[iter]
  1220.         self.treeview_packages.set_model(item.applications)
  1221.         self.menu._refilter()
  1222.         # FIXME: the ranking data needs to be updated here or a single model
  1223.         #        instead of all the models in the categories models should be
  1224.         #        used
  1225.         if self.sort_by_ranking:
  1226.             if not item.applications.has_default_sort_func():
  1227.                 # If a search term was entered and the app is in browsing mode,
  1228.                 # set the app into search mode: unset the current sorting 
  1229.                 # and a default sorting by result ranking
  1230.                 item.applications.set_default_sort_func(None)
  1231.                 item.applications.set_default_sort_func(self.menu._ranking_sort_func)
  1232.                 item.applications.set_sort_column_id(-1, gtk.SORT_ASCENDING)
  1233.             else:
  1234.                 #FIXME: evil hack to enforce a working default sort func
  1235.                 item.applications.set_default_sort_func(None)
  1236.                 item.applications.set_default_sort_func(self.menu._ranking_sort_func)
  1237.                 item.applications.set_sort_column_id(-1, gtk.SORT_ASCENDING)
  1238.         else:
  1239.             if item.applications.has_default_sort_func():
  1240.                 # If we return from search mode (the search term was cleaned),
  1241.                 # sort the apps by name
  1242.                 item.applications.set_sort_column_id(COL_NAME, 
  1243.                                                      gtk.SORT_ASCENDING)
  1244.             # remove the default sorting by result ranking in browsing mode
  1245.             item.applications.set_default_sort_func(None)
  1246.  
  1247.         if len(self.menu.treeview_packages.get_model()) == 0:
  1248.             self.show_no_results_msg()
  1249.         else:
  1250.             self.menu.treeview_packages.set_cursor(0)
  1251.         self.setBusy(False)
  1252.  
  1253.     def on_treeview_packages_cursor_changed(self, treeview):
  1254.         path = treeview.get_cursor()[0]
  1255.         iter = treeview.get_model().get_iter(path)
  1256.         (name, item, popcon) = treeview.get_model()[iter]
  1257.         self.show_description(item)
  1258.  
  1259.     def show_no_results_msg(self):
  1260.         """ Give the user some hints if the search returned 
  1261.             no results"""
  1262.         buffer = self.textview_description.get_buffer()
  1263.         buffer.set_text("")
  1264.         # remove all old tags
  1265.         tag_table = buffer.get_tag_table()
  1266.         tag_table.foreach((lambda tag, table: table.remove(tag)), tag_table)
  1267.         # create a tag for the first line
  1268.         tag_header = buffer.create_tag("first-line",
  1269.                                        weight = pango.WEIGHT_BOLD,
  1270.                                        pixels_above_lines=6)
  1271.         msg = _("There is no matching application available.")
  1272.         iter = buffer.get_start_iter()
  1273.         buffer.insert_with_tags_by_name(iter, msg, "first-line")
  1274.         if self.menu.filter != SHOW_ALL_FREE and \
  1275.            self.menu.filter != SHOW_ALL:
  1276.             msg = "\n%s" % _("To broaden your search, choose "
  1277.                              "'Show all Open Source applications' or "
  1278.                              "'Show all available' applications.")
  1279.             buffer.insert_with_tags(iter, msg)
  1280.         if self.treeview_categories.get_cursor()[0] != (0,):
  1281.             msg = "\n%s" % _("To broaden your search, choose "
  1282.                              "'All' categories.")
  1283.             buffer.insert_with_tags(iter, msg)
  1284.         
  1285.     def show_intro(self):
  1286.         """ Show a quick introduction to gnome-app-install 
  1287.             in the description view"""
  1288.         buffer = self.textview_description.get_buffer()
  1289.         buffer.set_text("")
  1290.         iter = buffer.get_start_iter()
  1291.         tag_header = buffer.create_tag("header",
  1292.                                        scale = pango.SCALE_LARGE,
  1293.                                        weight = pango.WEIGHT_BOLD,
  1294.                                        pixels_above_lines=6)
  1295.         msg = _("To install an application check the box next to the "
  1296.                 "application. Uncheck the box to remove "
  1297.                 "the application.") + "\n"
  1298.         msg += _("To perform advanced tasks use the "
  1299.                  "Synaptic package manager.")
  1300.         buffer.insert_with_tags(iter, "%s\n" % _("Quick Introduction"), 
  1301.                                 tag_header)
  1302.         buffer.insert(iter, msg)
  1303.  
  1304. # Entry point for testing in source tree
  1305. if __name__ == '__main__':
  1306.     app = AppInstall(os.path.abspath("menu-data"),
  1307.                      os.path.abspath("data"),
  1308.                      sys.argv)
  1309.     gtk.main()
  1310.