AppListView.py 7.79 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
import pango
import gtk
import gobject
(COL_CAT_NAME,
 COL_CAT_ITEM) = range(0,2)


from math import log
from xml.sax.saxutils import escape

def xmlescape(s):
    if s==None:
        return ""
    else:
        return escape(s)

# Columns of the packages store
(COL_NAME,
 COL_ITEM,
 COL_POPCON) = range(3)

(STYLE_ALL, STYLE_DESC, STYLE_INSTALL) = range(3)

class AppListView(gtk.TreeView, gobject.GObject):
    __gsignals__ = {"toggled":(gobject.SIGNAL_RUN_FIRST,
                               gobject.TYPE_NONE,
                               (gobject.TYPE_PYOBJECT,))}
    def __init__(self, cache=None, menu=None, icons=None, style=STYLE_ALL):
        self.cache = cache
        self.menu = menu

        self.desc_cache = {}

        self.__gobject_init__()
        gtk.TreeView.__init__(self)
        self.set_rules_hint(True)
        if icons == None:
            self.icons = gtk.icon_theme_get_default()
        else:
            self.icons = icons
        # Add a fake liststore to the packages list, so that the headers
        # are already seen during start up
        fake_applications = gtk.ListStore(gobject.TYPE_INT,
                                          gobject.TYPE_STRING,
                                          gobject.TYPE_PYOBJECT,
                                          gobject.TYPE_INT)
        fake_applications.set_sort_column_id(COL_NAME, gtk.SORT_ASCENDING)
        self.set_model(fake_applications)

        self.stars = self._get_stars()

        # popcon renderer
        renderer_popcon = gtk.CellRendererPixbuf()
        renderer_popcon.set_property("xpad", 4)
        column_app_popcon = gtk.TreeViewColumn(_("Popularity"), 
                                               renderer_popcon)
        column_app_popcon.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        column_app_popcon.set_sort_column_id(COL_POPCON)
        column_app_popcon.set_cell_data_func(renderer_popcon, 
                                             self._popcon_view_func)
        column_app_popcon.set_fixed_width(108)
        self.column_app_popcon = column_app_popcon

        # check boxes
        renderer_status = gtk.CellRendererToggle()
        renderer_status.connect('toggled', self._on_toggled)
        renderer_status.set_property("xalign", 0.5)
        renderer_status.set_property("yalign", 0.5)
        column_app_status = gtk.TreeViewColumn("")
        column_app_status.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        column_app_status.pack_start(renderer_status, False)
        column_app_status.set_cell_data_func (renderer_status, 
                                              self._toggle_cell_func)
        # FIXME: we need to react on theme changes
        width = renderer_status.get_size(self)[2] + 8
        column_app_status.set_fixed_width(width)

        # Application column (icon, name, description)
        column_app = gtk.TreeViewColumn(_("Application"))
        column_app.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        column_app.set_expand(True)
        column_app.set_sort_column_id(COL_NAME)
        # The icon
        renderer_app_icon = gtk.CellRendererPixbuf()
        column_app.pack_start(renderer_app_icon, False)
        column_app.set_cell_data_func(renderer_app_icon, 
                                      self._icon_cell_func)
        # app name and description
        renderer_app = gtk.CellRendererText()
        renderer_app.set_property("ellipsize", pango.ELLIPSIZE_END)
        column_app.pack_start(renderer_app, True)
        column_app.set_cell_data_func(renderer_app, 
                                      self._package_view_func)

        if style == STYLE_ALL:
            self.append_column(column_app_status)
            self.append_column(column_app)
            self.append_column(column_app_popcon)
        elif style == STYLE_DESC:
            self.append_column(column_app)
        elif style == STYLE_INSTALL:
            self.append_column(column_app_status)
            self.append_column(column_app)

        self.set_fixed_height_mode(True)

    def hook(self, cache, menu):
        self.cache = cache
        self.menu = menu

    def _get_stars(self):
        """
        Return a prerendered list of rating stars pixbufs
        """
        stars = []
        try:
            pixbuf_star = self.icons.load_icon("gnome-app-install-star", 16, 0)
        except gobject.GError:
            pixbuf_star = self.icons.load_icon(gtk.STOCK_MISSING_IMAGE, 16, 0)
        for i in range(5):
            starlets = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True,
                                     8, 96, 16) # depth, width, height
            starlets.fill(0x0)
            for l in range(i+1):
                pixbuf_star.copy_area(0,0,        # from
                                      16,16,      # size
                                      starlets,   # to-pixbuf
                                      20 * l, 0)  # dest
            stars.append(starlets)
        return stars

    def _popcon_view_func(self, cell_layout, renderer, model, iter):
        """
        Create a pixmap showing a row of stars representing the popularity
        of the corresponding application
        """
        (name, item, popcon) = model[iter]
        rank = 0
        if item.popcon > 0:
            rank = int(5 * log(item.popcon) / log(self.menu.popcon_max + 1))
        renderer.set_property("pixbuf", self.stars[rank])

    def _package_view_func(self, cell_layout, renderer, model, iter):
        app = model.get_value(iter, COL_ITEM)
        app_name = model.get_value(iter, COL_NAME)
        if self.desc_cache.has_key(app_name):
            (name, desc) = self.desc_cache[app_name]
        else:
            name = xmlescape(app.desktop_entry.getName())
            desc = xmlescape(app.desktop_entry.getComment())
            # KDE stores the comment in the GerneicName
            if desc == "":
                desc = xmlescape(app.desktop_entry.get('GenericName'))
            self.desc_cache[app_name] = (name, desc)
        if self.menu:
            future = app.toInstall
            current = self.menu.itemIsInstalled(app)
        else:
            future = None
            current = None

        if current != future:
            markup = "<b>%s</b>\n<small><b>%s</b></small>" % (name, desc)
        else:
            markup = "%s\n<small>%s</small>" % (name, desc)
        renderer.set_property("markup", markup)

    def _toggle_cell_func(self, column, cell, model, iter):
        menuitem = model.get_value(iter, COL_ITEM)
        cell.set_property("active", menuitem.toInstall)
        if menuitem.architectures and \
           self.cache.getArch() not in menuitem.architectures:
            cell.set_property("activatable", False)
        else:
            cell.set_property("activatable", True)

    def _icon_cell_func(self, column, cell, model, iter):
        menuitem = model.get_value(iter, COL_ITEM)
        if menuitem == None or menuitem.iconname == None:
            cell.set_property("pixbuf", None)
            cell.set_property("visible", False)
            return
        try:
            icon = self.icons.load_icon(menuitem.iconname, 24, 0)
            # work around bug #209072 even if we ask for a 24px
            # icon, we sometimes get outrages big ones - 256x256
            if icon and (icon.get_height() > 24 or icon.get_width() > 24):
                #print "WARNING: scaling down ", menuitem.iconname
                icon = icon.scale_simple(24,24,gtk.gdk.INTERP_BILINEAR)
        except gobject.GError:
            try:
                icon = self.icons.load_icon("applications-other", 24, 0)
            except gobject.GError:
                icon = self.icons.load_icon(gtk.STOCK_MISSING_IMAGE, 24, 0)
        cell.set_property("pixbuf", icon)
        cell.set_property("visible", True)

    def _on_toggled(self, widget, path):
        model = self.get_model()
        (name, item, popcon) = model[path]
        self.emit("toggled", item)