~mvo/software-center/new-apps-icons

« back to all changes in this revision

Viewing changes to softwarecenter/view/appview.py

  • Committer: Michael Vogt
  • Date: 2010-08-03 12:34:15 UTC
  • mfrom: (891.1.31 appview-tweaks)
  • Revision ID: michael.vogt@ubuntu.com-20100803123415-7o9paakjonskads2
merged  lp:~mmcg069/software-center/appview-tweaks,
many thanks

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
import sys
35
35
import time
36
36
import xapian
 
37
import cairo
37
38
 
38
39
if os.path.exists("./softwarecenter/enums.py"):
39
40
    sys.path.insert(0, ".")
42
43
from softwarecenter.db.database import StoreDatabase, Application
43
44
from softwarecenter.backend import get_install_backend
44
45
from softwarecenter.distro import get_distro
 
46
from widgets.mkit import get_em_value
 
47
from gtk import gdk
45
48
 
46
49
from gettext import gettext as _
47
50
 
48
51
# cache icons to speed up rendering
49
52
_app_icon_cache = {}
50
53
 
 
54
 
51
55
class AppStore(gtk.GenericTreeModel):
52
56
    """
53
57
    A subclass GenericTreeModel that reads its data from a xapian
66
70
     COL_POPCON,
67
71
     COL_IS_ACTIVE,
68
72
     COL_ACTION_IN_PROGRESS,
69
 
     COL_EXISTS) = range(11)
 
73
     COL_EXISTS,
 
74
     COL_ACCESSIBLE) = range(12)
70
75
 
71
76
    column_type = (str,
72
77
                   str,
78
83
                   int,
79
84
                   bool,
80
85
                   int,
81
 
                   bool)
 
86
                   bool,
 
87
                   str)
82
88
 
83
89
    ICON_SIZE = 24
84
90
    MAX_STARS = 5
479
485
                return False
480
486
            elif column == self.COL_ACTION_IN_PROGRESS:
481
487
                return -1
 
488
            elif column == self.COL_ACCESSIBLE:
 
489
                return '%s\n%s' % (app.pkgname, _('Package state unknown'))
482
490
 
483
491
        # Otherwise the app should return app data normally.
484
492
        if column == self.COL_APP_NAME:
540
548
                return -1
541
549
        elif column == self.COL_EXISTS:
542
550
            return True
 
551
        elif column == self.COL_ACCESSIBLE:
 
552
            pkgname = app.pkgname
 
553
            appname = app.appname
 
554
            summary = self.db.get_summary(doc)
 
555
            if pkgname in self.cache and self.cache[pkgname].is_installed:
 
556
                return "%s\n%s\n%s" % (appname, _('Installed'), summary)
 
557
            return "%s\n%s\n%s" % (appname, _('Not Installed'), summary) 
 
558
 
543
559
    def on_iter_next(self, rowref):
544
560
        #self._logger.debug("on_iter_next: %s" % rowref)
545
561
        new_rowref = int(rowref) + 1
569
585
        return None
570
586
 
571
587
 
572
 
class CellRendererButton:
573
 
 
574
 
    def __init__(self, layout, markup, alt_markup=None, xpad=14, ypad=4):
575
 
        if not alt_markup:
576
 
            w, h, mx, amx = self._calc_markup_params(layout, markup, xpad, ypad)
577
 
        else:
578
 
            w, h, mx, amx = self._calc_markup_params_alt(layout, markup, alt_markup, xpad, ypad)
579
 
 
580
 
        self.params = {
581
 
            'label': markup,
582
 
            'markup': markup,
583
 
            'alt_markup': alt_markup,
584
 
            'width': w,
585
 
            'height': h,
586
 
            'y_offset_const': 0,
587
 
            'region_rect': gtk.gdk.region_rectangle(gtk.gdk.Rectangle(0,0,0,0)),
588
 
            'xpad': xpad,
589
 
            'ypad': ypad,
590
 
            'state': gtk.STATE_NORMAL,
591
 
            'shadow': gtk.SHADOW_OUT,
592
 
            'layout_x': mx,
593
 
            'markup_x': mx,
594
 
            'alt_markup_x': amx
595
 
            }
596
 
        self.use_alt = False
597
 
        return
598
 
 
599
 
    def _calc_markup_params(self, layout, markup, xpad, ypad):
600
 
        layout.set_markup(markup)
601
 
        w = self._get_layout_pixel_width(layout) + 2*xpad
602
 
        h = self._get_layout_pixel_height(layout) + 2*ypad
603
 
        return w, h, xpad, 0
604
 
 
605
 
    def _calc_markup_params_alt(self, layout, markup, alt_markup, xpad, ypad):
606
 
        layout.set_markup(markup)
607
 
        mw = self._get_layout_pixel_width(layout)
608
 
        layout.set_markup(alt_markup)
609
 
        amw = self._get_layout_pixel_width(layout)
610
 
 
611
 
        if amw > mw:
612
 
            w = amw + 2*xpad
613
 
            mx = xpad + (amw - mw)/2
614
 
            amx = xpad
615
 
        else:
616
 
            w = mw + 2*xpad
617
 
            mx = xpad
618
 
            amx = xpad + (mw - amw)/2
619
 
 
620
 
        # assume text height is the same for markups.
621
 
        h = self._get_layout_pixel_height(layout) + 2*ypad
622
 
        return w, h, mx, amx
623
 
 
624
 
    def _get_layout_pixel_width(self, layout):
625
 
        (logical_extends, ink_extends) = layout.get_pixel_extents()
626
 
        # extens is (x, y, width, height)
627
 
        return ink_extends[2]
628
 
 
629
 
    def _get_layout_pixel_height(self, layout):
630
 
        (logical_extends, ink_extends) = layout.get_pixel_extents()
631
 
        # extens is (x, y, width, height)
632
 
        return ink_extends[3]
633
 
 
634
 
    def set_state(self, state_type):
635
 
        self.params['state'] = state_type
636
 
        return
637
 
 
638
 
    def set_shadow(self, shadow_type):
639
 
        self.params['shadow'] = shadow_type
 
588
class CellRendererButton2:
 
589
 
 
590
    def __init__(self, name, markup=None, markup_variants=None, xpad=12, ypad=4, use_max_variant_width=True):
 
591
        # use_max_variant_width is currently ignored. assumed to be True
 
592
 
 
593
        self.name = name
 
594
 
 
595
        self.current_variant = 0
 
596
        if markup:
 
597
            self.markup_variants = (markup,)
 
598
        else:
 
599
            # expects a list or tuple!
 
600
            self.markup_variants = markup_variants
 
601
 
 
602
        self.xpad = xpad
 
603
        self.ypad = ypad
 
604
        self.allocation = gdk.Rectangle(0,0,1,1)
 
605
        self.state = gtk.STATE_NORMAL
 
606
        self.has_focus = False
 
607
 
 
608
        self._widget = None
 
609
        self._geometry = None
 
610
        return
 
611
 
 
612
    def _layout_reset(self, layout):
 
613
        layout.set_width(-1)
 
614
        layout.set_ellipsize(pango.ELLIPSIZE_NONE)
 
615
        return
 
616
 
 
617
    def configure_geometry(self, widget):
 
618
        if self._geometry: return
 
619
        pc = widget.get_pango_context()
 
620
        layout = pango.Layout(pc)
 
621
        max_w = 0
 
622
        for variant in self.markup_variants:
 
623
            layout.set_markup(gobject.markup_escape_text(variant))
 
624
            max_size = max(self.get_size(), layout.get_pixel_extents()[1][2:])
 
625
 
 
626
        w, h = max_size
 
627
        self.set_size(w+2*self.xpad, h+2*self.ypad)
 
628
        self._geometry = True
 
629
        return
 
630
 
 
631
    def scrub_geometry(self):
 
632
        self._geometry = None
 
633
        return
 
634
 
 
635
    def point_in(self, x, y):
 
636
        return gdk.region_rectangle(self.allocation).point_in(x,y)
 
637
 
 
638
    def get_size(self):
 
639
        a = self.allocation
 
640
        return a.width, a.height 
 
641
 
 
642
    def set_position(self, x, y):
 
643
        a = self.allocation
 
644
        self.size_allocate(gdk.Rectangle(x, y, a.width, a.height))
 
645
        return
 
646
 
 
647
    def set_size(self, w, h):
 
648
        a = self.allocation
 
649
        self.size_allocate(gdk.Rectangle(a.x, a.y, w, h))
 
650
        return
 
651
 
 
652
    def size_allocate(self, rect):
 
653
        self.allocation = rect
 
654
        return
 
655
 
 
656
    def set_state(self, state):
 
657
        if state == self.state: return
 
658
        self.state = state
 
659
        if self._widget:
 
660
            self._widget.queue_draw_area(*self.get_allocation_tuple())
 
661
        return
640
662
 
641
663
    def set_sensitive(self, is_sensitive):
642
 
        if not is_sensitive:
643
 
            self.set_state(gtk.STATE_INSENSITIVE)
644
 
            self.set_shadow(gtk.SHADOW_OUT)
645
 
        elif self.params['state'] == gtk.STATE_INSENSITIVE:
646
 
            self.set_state(gtk.STATE_NORMAL)
647
 
            self.set_shadow(gtk.SHADOW_OUT)
648
 
        return
649
 
 
650
 
    def set_use_alt_markup(self, use_alt):
651
 
        if self.use_alt == use_alt: 
 
664
        if is_sensitive:
 
665
            self.state = gtk.STATE_NORMAL
 
666
        else:
 
667
            self.state = gtk.STATE_INSENSITIVE
 
668
        if self._widget:
 
669
            self._widget.queue_draw_area(*self.get_allocation_tuple())
 
670
        return
 
671
 
 
672
    def set_markup(self, markup):
 
673
        self.markup_variant = (markup,)
 
674
        return
 
675
 
 
676
    def set_markup_variants(self, markup_variants):
 
677
        # expects a tuple or list
 
678
        self.markup_variants = markup_variants
 
679
        return
 
680
 
 
681
    def set_markup_variant_n(self, n):
 
682
        # yes i know this is totally hideous...
 
683
        if n >= len(self.markup_variants):
 
684
            print n, 'Not in range', self.markup_variants
652
685
            return
653
 
        self.use_alt = use_alt
654
 
        p = self.params
655
 
        if use_alt:
656
 
            p['label'] = p['alt_markup']
657
 
            p['layout_x'] = p['alt_markup_x']
658
 
        else:
659
 
            p['label'] = p['markup']
660
 
            p['layout_x'] = p['markup_x']
661
 
        return
662
 
 
663
 
    def get_use_alt_markup(self):
664
 
        return self.use_alt
665
 
 
666
 
    def set_param(self, key, value):
667
 
        self.params[key] = value
668
 
        return
669
 
 
670
 
    def get_param(self, key):
671
 
        return self.params[key]
672
 
 
673
 
    def get_params(self, *keys):
674
 
        r = []
675
 
        for k in keys:
676
 
            r.append(self.params[k])
677
 
        return r
678
 
 
679
 
    def draw(self, window, widget, layout, dst_x, cell_yO):
680
 
        p = self.params
681
 
        w, h, yO = self.get_params('width', 'height', 'y_offset_const')
682
 
        dst_y = yO+cell_yO
683
 
        state = p['state']
684
 
 
685
 
        # backgound "button" rect
 
686
        self.current_variant = n
 
687
        return
 
688
 
 
689
    def get_allocation_tuple(self):
 
690
        a = self.allocation
 
691
        return (a.x, a.y, a.width, a.height)
 
692
 
 
693
    def render(self, window, widget, layout=None):
 
694
        if not self._widget:
 
695
            self._widget = widget
 
696
        if self.state != gtk.STATE_ACTIVE:
 
697
            shadow = gtk.SHADOW_OUT
 
698
        else:
 
699
            shadow = gtk.SHADOW_IN
 
700
 
 
701
        if not layout:
 
702
            pc = widget.get_pango_context()
 
703
            layout = pango.Layout(pc)
 
704
        else:
 
705
            self._layout_reset(layout)
 
706
 
 
707
        layout.set_markup(self.markup_variants[self.current_variant])
 
708
        xpad, ypad = self.xpad, self.ypad
 
709
        x, y, w, h = self.get_allocation_tuple()
 
710
 
 
711
        # clear teh background first
 
712
        # this prevents the button overdrawing on its self,
 
713
        # which results in transparent pixels accumulating alpha value
 
714
        cr = window.cairo_create()
 
715
        cr.set_operator(cairo.OPERATOR_CLEAR)
 
716
        cr.rectangle(x, y, w, h)
 
717
        cr.clip()
 
718
        cr.paint_with_alpha(0)
 
719
 
 
720
        cr.set_operator(cairo.OPERATOR_OVER)
 
721
        del cr
686
722
        widget.style.paint_box(window,
687
 
                               state,
688
 
                               p['shadow'],
689
 
                               (dst_x, dst_y, w, h),
 
723
                               self.state,
 
724
                               shadow,
 
725
                               (x, y, w, h),
690
726
                               widget,
691
727
                               "button",
692
 
                               dst_x,
693
 
                               dst_y,
694
 
                               w,
695
 
                               h)
696
 
 
697
 
        # cache region_rectangle for event checks
698
 
        p['region_rect'] = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(dst_x, dst_y, w, h))
699
 
 
700
 
#        # if btn_has_focus:
701
 
#        # draw focal rect
702
 
#        widget.style.paint_focus(window,
703
 
#                                 state,
704
 
#                                 (dst_x, dst_y, w, h),
705
 
#                                 widget,
706
 
#                                 "button",
707
 
#                                 dst_x-2,       # x
708
 
#                                 dst_y-2,       # y
709
 
#                                 w-4,          # width
710
 
#                                 h-4)          # height
711
 
 
712
 
        # draw button label
713
 
        dst_x += p['layout_x']
714
 
        dst_y += p['ypad']
715
 
        layout.set_markup(p['label'])
 
728
                               x, y, w, h)
 
729
 
 
730
        # if we have more than one markup variant
 
731
        # we need to calc layout x-offset for current variant markup
 
732
        if len(self.markup_variants) > 1:
 
733
            lw = layout.get_pixel_extents()[1][2]
 
734
            xo = x + (w - lw)/2
 
735
 
 
736
        # else, we can just use xpad as the x-offset... 
 
737
        else:
 
738
            xo = x + xpad
 
739
 
 
740
        if self.has_focus and self.state != gtk.STATE_INSENSITIVE and \
 
741
            self._widget.has_focus():
 
742
            widget.style.paint_focus(window,
 
743
                                     self.state,
 
744
                                     (x+2, y+2, w-4, h-4),
 
745
                                     widget,
 
746
                                     "expander",
 
747
                                     x+2, y+2,
 
748
                                     w-4, h-4)
 
749
 
716
750
        widget.style.paint_layout(window,
717
 
                            state,
718
 
                            True,
719
 
                            (dst_x, dst_y, w, h),
720
 
                            widget,
721
 
                            None,
722
 
                            dst_x,
723
 
                            dst_y,
724
 
                            layout)
 
751
                                  self.state,
 
752
                                  True,
 
753
                                  (xo, y+ypad, w, h),
 
754
                                  widget,
 
755
                                  "button",
 
756
                                  xo, y+ypad,
 
757
                                  layout)
725
758
        return
726
759
 
727
760
 
728
761
# custom cell renderer to support dynamic grow
729
 
class CellRendererAppView(gtk.GenericCellRenderer):
 
762
class CellRendererAppView2(gtk.CellRendererText):
 
763
 
 
764
    # offset of the install overlay icon
 
765
    OFFSET_X = 20
 
766
    OFFSET_Y = 20
 
767
 
 
768
    # size of the install overlay icon
 
769
    OVERLAY_SIZE = 16
730
770
 
731
771
    __gproperties__ = {
732
 
        'markup': (gobject.TYPE_STRING, 'Markup', 'Pango markup', '',
733
 
                    gobject.PARAM_READWRITE),
 
772
        'overlay' : (bool, 'overlay', 'show an overlay icon', False,
 
773
                     gobject.PARAM_READWRITE),
734
774
 
735
 
#        'addons': (bool, 'AddOns', 'Has add-ons?', False,
736
 
#                   gobject.PARAM_READWRITE),
 
775
        'pixbuf' :  (gtk.gdk.Pixbuf, "Pixbuf", 
 
776
                    "Application icon pixbuf image", gobject.PARAM_READWRITE),
737
777
 
738
778
        # numbers mean: min: 0, max: 5, default: 0
739
779
        'rating': (gobject.TYPE_INT, 'Rating', 'Popcon rating', 0, 5, 0,
740
780
            gobject.PARAM_READWRITE),
741
781
 
742
 
#        'reviews': (gobject.TYPE_INT, 'Reviews', 'Number of reviews', 0, 100, 0,
743
 
#            gobject.PARAM_READWRITE),
744
 
 
745
782
        'isactive': (bool, 'IsActive', 'Is active?', False,
746
783
                    gobject.PARAM_READWRITE),
747
784
 
756
793
 
757
794
        'exists': (bool, 'exists', 'Is the app found in current channel', False,
758
795
                   gobject.PARAM_READWRITE),
 
796
 
 
797
        # overload the native markup property becasue i didnt know how to read it
 
798
        # note; the 'text' attribute is used as the cell atk description
 
799
        'markup': (str, 'markup', 'The markup to paint', '',
 
800
                   gobject.PARAM_READWRITE)
759
801
        }
760
802
 
761
 
    def __init__(self, show_ratings):
762
 
        self.__gobject_init__()
763
 
 
764
 
        # height defaults
765
 
        self.base_height = 0
766
 
        self.button_height = 0
767
 
 
768
 
        self.markup = None
 
803
    def __init__(self, show_ratings, overlay_icon_name):
 
804
        gtk.CellRendererText.__init__(self)
 
805
        # geometry-state values
 
806
        self.overlay_icon_name = overlay_icon_name
 
807
        self.pixbuf_width = 0
 
808
        self.normal_height = 0
 
809
        self.selected_height = 0
 
810
 
 
811
        # attributes
 
812
        self.overlay = False
 
813
        self.pixbuf = None
 
814
        self.markup = ''
769
815
        self.rating = 0
770
 
        self.reviews = 0
771
816
        self.isactive = False
772
817
        self.installed = False
773
818
        self.show_ratings = show_ratings
774
819
 
775
 
        # get rating icons
 
820
        # button packing
 
821
        self.button_spacing = 0
 
822
        self._buttons = {gtk.PACK_START: [],
 
823
                         gtk.PACK_END:   []}
 
824
        self._all_buttons = {}
 
825
 
 
826
        # cache a layout
 
827
        self._layout = None
 
828
 
 
829
        # icon/overlay jazz
776
830
        icons = gtk.icon_theme_get_default()
777
 
        self.star_pixbuf = icons.load_icon("sc-emblem-favorite", 12, 0)
778
 
        self.star_not_pixbuf = icons.load_icon("sc-emblem-favorite-not", 12, 0)
779
 
 
780
 
        # specify the func that calc's distance from margin, based on text dir
781
 
        self._calc_x = self._calc_x_ltr
 
831
        try:
 
832
            self._installed = icons.load_icon(overlay_icon_name,
 
833
                                              self.OVERLAY_SIZE, 0)
 
834
        except glib.GError:
 
835
            # icon not present in theme, probably because running uninstalled
 
836
            self._installed = icons.load_icon('emblem-system',
 
837
                                              self.OVERLAY_SIZE, 0)
782
838
        return
783
839
 
784
 
    def set_direction(self, text_direction):
785
 
        self.text_direction = text_direction
786
 
        if text_direction != gtk.TEXT_DIR_RTL:
787
 
            self._calc_x = self._calc_x_ltr
 
840
    def _layout_get_pixel_width(self, layout):
 
841
        return layout.get_pixel_extents()[1][2]
 
842
 
 
843
    def _layout_get_pixel_height(self, layout):
 
844
        return layout.get_pixel_extents()[1][3]
 
845
 
 
846
    def _render_icon(self, window, widget, cell_area, state, xpad, ypad, direction):
 
847
        # calc offsets so icon is nicely centered
 
848
        xo = (32 - self.pixbuf.get_width())/2
 
849
        yo = (32 - self.pixbuf.get_height())/2
 
850
 
 
851
        if direction != gtk.TEXT_DIR_RTL:
 
852
            x = xpad+xo
788
853
        else:
789
 
            self._calc_x = self._calc_x_rtl
790
 
        return
791
 
 
792
 
    def set_base_height(self, base_height):
793
 
        self.base_height = base_height
794
 
        return
795
 
 
796
 
    def set_button_height(self, button_height):
797
 
        self.button_height = button_height
798
 
        return
799
 
 
800
 
    def do_set_property(self, pspec, value):
801
 
        setattr(self, pspec.name, value)
802
 
 
803
 
    def do_get_property(self, pspec):
804
 
        return getattr(self, pspec.name)
805
 
 
806
 
    def _get_layout_pixel_width(self, layout):
807
 
        (logical_extends, ink_extends) = layout.get_pixel_extents()
808
 
        # extens is (x, y, width, height)
809
 
        return ink_extends[2]
810
 
 
811
 
    def _get_layout_pixel_height(self, layout):
812
 
        (logical_extends, ink_extends) = layout.get_pixel_extents()
813
 
        # extens is (x, y, width, height)
814
 
        return ink_extends[3]
815
 
 
816
 
    def _calc_x_ltr(self, cell_area, aspect_width, margin_xO):
817
 
        return cell_area.x + margin_xO
818
 
 
819
 
    def _calc_x_rtl(self, cell_area, aspect_width, margin_xO):
820
 
        return cell_area.x + cell_area.width - aspect_width - margin_xO
821
 
 
822
 
    def draw_appname_summary(self, window, widget, cell_area, layout, xpad, ypad, flags):
823
 
        w = self.star_pixbuf.get_width()
824
 
        h = self.star_pixbuf.get_height()
825
 
        # total 5star width + 1 px spacing per star
826
 
        max_star_width = AppStore.MAX_STARS*(w+1)
827
 
 
828
 
        # work out layouts max width
829
 
        lw = self._get_layout_pixel_width(layout)
830
 
        max_layout_width = cell_area.width - 4*xpad - max_star_width
 
854
            x = cell_area.width-xpad+xo-32
 
855
 
 
856
        # draw appicon pixbuf
 
857
        window.draw_pixbuf(None,
 
858
                           self.pixbuf,             # icon
 
859
                           0, 0,                    # src pixbuf
 
860
                           x, cell_area.y+ypad+yo,  # dest in window
 
861
                           -1, -1,                  # size
 
862
                           0, 0, 0)                 # dither
 
863
 
 
864
        # draw overlay if application is installed
 
865
        if self.overlay:
 
866
            if direction != gtk.TEXT_DIR_RTL:
 
867
                x = self.OFFSET_X
 
868
            else:
 
869
                x = cell_area.width - self.OFFSET_X - self.OVERLAY_SIZE
 
870
 
 
871
            y = cell_area.y + self.OFFSET_Y
 
872
            window.draw_pixbuf(None,
 
873
                               self._installed,     # icon
 
874
                               0, 0,                # src pixbuf
 
875
                               x, y,                # dest in window
 
876
                               -1, -1,              # size
 
877
                               0, 0, 0)             # dither
 
878
        return
 
879
 
 
880
    def _render_appsummary(self, window, widget, cell_area, state, layout, xpad, ypad, direction):
 
881
        # adjust cell_area
 
882
 
 
883
        # work out max allowable layout width
 
884
        lw = self._layout_get_pixel_width(layout)
 
885
        max_layout_width = cell_area.width - self.pixbuf_width - 3*xpad
 
886
 
 
887
        if self.isactive and self.props.action_in_progress > 0:
 
888
            action_btn = self.get_button_by_name('action0')
 
889
            if not action_btn:
 
890
                print 'No action button? This doesn\'t make sense!'
 
891
                return
 
892
            max_layout_width -= (xpad + action_btn.get_size()[0]) 
831
893
 
832
894
        if lw >= max_layout_width:
833
 
            layout.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
834
895
            layout.set_width((max_layout_width)*pango.SCALE)
835
896
            lw = max_layout_width
836
897
 
837
 
        # work out where to draw layout
838
 
        dst_x = self._calc_x(cell_area, lw, xpad)
839
 
        dst_y = cell_area.y + ypad
 
898
        if direction != gtk.TEXT_DIR_RTL:
 
899
            x, y = 2*xpad+self.pixbuf_width, cell_area.y+ypad
 
900
        else:
 
901
            x = cell_area.x+cell_area.width-lw-self.pixbuf_width-2*xpad
 
902
            y = cell_area.y+ypad
 
903
 
 
904
        w, h = lw, self.normal_height
 
905
        widget.style.paint_layout(window, state,
 
906
                                  False,
 
907
                                  (x, y, w, h),
 
908
                                  widget, None,
 
909
                                  x, y, layout)
 
910
        return
 
911
 
 
912
    def _render_progress(self, window, widget, cell_area, ypad, direction):
 
913
        # as seen in gtk's cellprogress.c
 
914
        percent = self.props.action_in_progress * 0.01
 
915
 
 
916
        # per the spec, the progressbar should be the width of the action button
 
917
        action_btn = self.get_button_by_name('action0')
 
918
        if not action_btn:
 
919
            print 'No action button? This doesn\'t make sense!'
 
920
            return
 
921
 
 
922
        x, y, w, h = action_btn.get_allocation_tuple()
 
923
        # shift the bar to the top edge
 
924
        y = cell_area.y + ypad
 
925
 
 
926
#        FIXME: GtkProgressBar draws the box with "trough" detail,
 
927
#        but some engines don't paint anything with that detail for
 
928
#        non-GtkProgressBar widgets.
 
929
 
 
930
        widget.style.paint_box(window,
 
931
                               gtk.STATE_NORMAL,
 
932
                               gtk.SHADOW_IN,
 
933
                               (x, y, w, h),
 
934
                               widget,
 
935
                               None,
 
936
                               x, y, w, h)
 
937
 
 
938
        if direction != gtk.TEXT_DIR_RTL:
 
939
            clip = gdk.Rectangle(x, y, int((w)*percent), h)
 
940
        else:
 
941
            clip = gdk.Rectangle(x+(w-int(w*percent)), y, int(w*percent), h)
 
942
 
 
943
        widget.style.paint_box(window,
 
944
                               gtk.STATE_SELECTED,
 
945
                               gtk.SHADOW_OUT,
 
946
                               clip,
 
947
                               widget,
 
948
                               "bar",
 
949
                               clip.x, clip.y,
 
950
                               clip.width, clip.height)
 
951
        return
 
952
 
 
953
    def set_normal_height(self, h):
 
954
        self.normal_height = int(h)
 
955
        return
 
956
 
 
957
    def set_pixbuf_width(self, w):
 
958
        self.pixbuf_width = w
 
959
        return
 
960
 
 
961
    def set_selected_height(self, h):
 
962
        self.selected_height = h
 
963
        return
 
964
 
 
965
    def set_button_spacing(self, spacing):
 
966
        self.button_spacing = spacing
 
967
        return
 
968
 
 
969
    def get_button_by_name(self, name):
 
970
        if name in self._all_buttons:
 
971
            return self._all_buttons[name]
 
972
        return None
 
973
 
 
974
    def get_buttons(self):
 
975
        btns = ()
 
976
        for k, v in self._buttons.iteritems():
 
977
            btns += tuple(v)
 
978
        return btns
 
979
 
 
980
    def button_pack(self, btn, pack_type=gtk.PACK_START):
 
981
        self._buttons[pack_type].append(btn)
 
982
        self._all_buttons[btn.name] = btn
 
983
        return
 
984
 
 
985
    def button_pack_start(self, btn):
 
986
        self.button_pack(btn, gtk.PACK_START)
 
987
        return
 
988
 
 
989
    def button_pack_end(self, btn):
 
990
        self.button_pack(btn, gtk.PACK_END)
 
991
        return
 
992
 
 
993
    def do_set_property(self, pspec, value):
 
994
        setattr(self, pspec.name, value)
 
995
 
 
996
    def do_get_property(self, pspec):
 
997
        return getattr(self, pspec.name)
 
998
 
 
999
    def do_render(self, window, widget, background_area, cell_area,
 
1000
                  expose_area, flags):
 
1001
 
 
1002
        xpad = self.get_property('xpad')
 
1003
        ypad = self.get_property('ypad')
 
1004
        direction = widget.get_direction()
840
1005
 
841
1006
        # important! ensures correct text rendering, esp. when using hicolor theme
842
1007
        if (flags & gtk.CELL_RENDERER_SELECTED) != 0:
844
1009
        else:
845
1010
            state = gtk.STATE_NORMAL
846
1011
 
847
 
        widget.style.paint_layout(window,
848
 
                                  state,
849
 
                                  True,
850
 
                                  cell_area,
851
 
                                  widget,
852
 
                                  None,
853
 
                                  dst_x,
854
 
                                  dst_y,
855
 
                                  layout)
856
 
        # remove layout size constraints
857
 
        layout.set_width(-1)
858
 
        return w, h, max_star_width
859
 
 
860
 
    def draw_appname_activity_state(self, window, widget, cell_area, layout, xpad, ypad, flags, activity):
861
 
        # stub.  in the spec mpt has it so that when an app is being installed is has:
862
 
        # Audacity
863
 
        # Installing ...
864
 
 
865
 
        #or, for removal:
866
 
        # Audacity
867
 
        # Removing ...
868
 
        return
869
 
 
870
 
    def draw_rating_and_reviews(self, window, widget, cell_area, layout, xpad, ypad, w, h, max_star_width, flags):
871
 
        dst_y = cell_area.y+ypad + ( 0 if self.reviews > 0 else 10) # unnamed constant because I can't see a place to put these 
872
 
        
873
 
        # draw star rating
874
 
        self.draw_rating(window, cell_area, dst_y, max_star_width, xpad, self.rating)
875
 
 
876
 
        if self.reviews == 0: return
877
 
        # draw number of reviews
878
 
        nr_reviews_str = gettext.ngettext("%(amount)s review",
879
 
                                          "%(amount)s reviews",
880
 
                                          self.reviews) % { 'amount' : self.reviews, }
881
 
        
882
 
        layout.set_markup("<small>%s</small>" % nr_reviews_str)
883
 
        lw = self._get_layout_pixel_width(layout)
884
 
        dst_x = self._calc_x(cell_area, lw, cell_area.width-xpad-max_star_width+(max_star_width-lw)/2)
885
 
 
886
 
        widget.style.paint_layout(window,
887
 
                                  flags,
888
 
                                  True,
889
 
                                  cell_area,
890
 
                                  widget,
891
 
                                  None,
892
 
                                  dst_x,
893
 
                                  cell_area.y+ypad+h+1,
894
 
                                  layout)
895
 
        return
896
 
 
897
 
    def draw_rating(self, window, cell_area, dst_y, max_star_width, xpad, r):
898
 
        w = self.star_pixbuf.get_width()
899
 
        for i in range(AppStore.MAX_STARS):
900
 
            # special case.  not only do we want to shift the x offset, but we want to reverse the order in which
901
 
            # the gold stars are presented.
902
 
            if self.text_direction != gtk.TEXT_DIR_RTL:
903
 
                dst_x = cell_area.x + cell_area.width - xpad - max_star_width + i*(w+1)
904
 
            else:
905
 
                dst_x = cell_area.x + xpad + max_star_width - w - i*(w+1)
906
 
 
907
 
            if i < r:
908
 
                window.draw_pixbuf(None,
909
 
                                   self.star_pixbuf,                        # icon
910
 
                                   0, 0,                                    # src pixbuf
911
 
                                   dst_x,                                   # x
912
 
                                   dst_y,                                   # y
913
 
                                   -1, -1,                                  # size
914
 
                                   0, 0, 0)                                 # dither
915
 
            else:
916
 
                window.draw_pixbuf(None,
917
 
                                   self.star_not_pixbuf,                    # icon
918
 
                                   0, 0,                                    # src pixbuf
919
 
                                   dst_x,                                   # x
920
 
                                   dst_y,                                   # y
921
 
                                   -1, -1,                                  # size
922
 
                                   0, 0, 0)                                 # dither
923
 
        return
924
 
 
925
 
    def draw_progress(self, window, widget, cell_area, layout, dst_x, ypad, flags):
926
 
        percent = self.props.action_in_progress * 0.01
927
 
        w = widget.buttons['action'].get_param('width')
928
 
        h = 22  # pixel height. should be the same height of CellRendererProgress progressbar
929
 
        dst_y = cell_area.y + (self.base_height-h)/2
930
 
 
931
 
        # progress trough border
932
 
        widget.style.paint_flat_box(window, gtk.STATE_ACTIVE, gtk.SHADOW_IN,
933
 
                               (dst_x, dst_y, w, h),
934
 
                               widget, 
935
 
                               None,
936
 
                               dst_x,
937
 
                               dst_y,
938
 
                               w,
939
 
                               h)
940
 
 
941
 
        # progress trough inner
942
 
        widget.style.paint_flat_box(window, gtk.STATE_NORMAL, gtk.SHADOW_IN,
943
 
                               (dst_x+1, dst_y+1, w-2, h-2),
944
 
                               widget, 
945
 
                               None,
946
 
                               dst_x+1,
947
 
                               dst_y+1,
948
 
                               w-2,
949
 
                               h-2)
950
 
 
951
 
 
952
 
        prog_w = int(percent*w)
953
 
        # progress bar
954
 
        if self.text_direction != gtk.TEXT_DIR_RTL:
955
 
            widget.style.paint_box(window, flags, gtk.SHADOW_OUT,
956
 
                                   (dst_x, dst_y, prog_w, h),
957
 
                                   widget, 
958
 
                                   "bar",
959
 
                                   dst_x,
960
 
                                   dst_y,
961
 
                                   prog_w,
962
 
                                   h)
 
1012
        if not self._layout:
 
1013
            pc = widget.get_pango_context()
 
1014
            self._layout = pango.Layout(pc)
 
1015
            self._layout.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
 
1016
 
 
1017
        self._render_icon(window, widget,
 
1018
                          cell_area, state,
 
1019
                          xpad, ypad,
 
1020
                          direction)
 
1021
 
 
1022
        self._layout.set_markup(self.markup)
 
1023
        self._render_appsummary(window, widget,
 
1024
                                cell_area, state,
 
1025
                                self._layout,
 
1026
                                xpad, ypad,
 
1027
                                direction)
 
1028
 
 
1029
        if not self.isactive:
 
1030
            return
 
1031
 
 
1032
        if self.props.action_in_progress > 0:
 
1033
            self._render_progress(window,
 
1034
                                  widget,
 
1035
                                  cell_area,
 
1036
                                  ypad,
 
1037
                                  direction)
 
1038
 
 
1039
        # layout buttons and paint
 
1040
        y = cell_area.y+cell_area.height-ypad
 
1041
        spacing = self.button_spacing
 
1042
 
 
1043
        if direction != gtk.TEXT_DIR_RTL:
 
1044
            start = gtk.PACK_START
 
1045
            end = gtk.PACK_END
 
1046
            xs = cell_area.x + 2*xpad + self.pixbuf_width
 
1047
            xb = cell_area.x + cell_area.width - xpad
963
1048
        else:
964
 
            widget.style.paint_box(window, flags, gtk.SHADOW_OUT,
965
 
                                   (dst_x + w+1-prog_w, dst_y, prog_w, h),
966
 
                                   widget, 
967
 
                                   "bar",
968
 
                                   dst_x + w+1-prog_w,
969
 
                                   dst_y,
970
 
                                   prog_w,
971
 
                                   h)
 
1049
            start = gtk.PACK_END
 
1050
            end = gtk.PACK_START
 
1051
            xs = cell_area.x + xpad
 
1052
            xb = cell_area.x + cell_area.width - 2*xpad - self.pixbuf_width
 
1053
 
 
1054
        for btn in self._buttons[start]:
 
1055
            btn.set_position(xs, y-btn.allocation.height)
 
1056
            btn.render(window, widget, self._layout)
 
1057
            xs += btn.allocation.width + spacing
 
1058
 
 
1059
        for btn in self._buttons[end]:
 
1060
            xb -= btn.allocation.width
 
1061
            btn.set_position(xb, y-btn.allocation.height)
 
1062
            btn.render(window, widget, self._layout)
 
1063
            xb -= spacing
972
1064
        return
973
1065
 
974
 
    def on_render(self, window, widget, background_area, cell_area,
975
 
                  expose_area, flags):
976
 
        xpad = self.get_property('xpad')
977
 
        ypad = self.get_property('ypad')
978
 
 
979
 
        # create pango layout with markup
980
 
        pc = widget.get_pango_context()
981
 
        layout = pango.Layout(pc)
982
 
        layout.set_markup(self.markup)
983
 
 
984
 
        w, h, max_star_width = self.draw_appname_summary(window, widget, cell_area, layout, xpad, ypad, flags)
985
 
 
 
1066
    def do_get_size(self, widget, cell_area):
986
1067
        if not self.isactive:
987
 
            if self.show_ratings:
988
 
                # draw star rating only
989
 
                dst_y = cell_area.y + (cell_area.height-h)/2
990
 
                self.draw_rating(window, cell_area, dst_y, max_star_width, xpad, self.rating)
991
 
            return
992
 
 
993
 
        # Install/Remove button
994
 
        # only draw a install/remove button if the app is actually available
995
 
        if self.available:
996
 
            btn = widget.get_button('action')
997
 
            btn.set_use_alt_markup(self.installed)
998
 
            dst_x = self._calc_x(cell_area, btn.get_param('width'), cell_area.width-xpad-btn.get_param('width'))
999
 
            btn.draw(window, widget, layout, dst_x, cell_area.y)
1000
 
            # check if the current app has an action that is in progress
1001
 
            if self.props.action_in_progress < 0:
1002
 
                # draw rating with the number of reviews
1003
 
                if self.show_ratings:
1004
 
                    self.draw_rating_and_reviews(window, widget, cell_area, layout, xpad, ypad, w, h, max_star_width, flags)
1005
 
            else:
1006
 
                self.draw_progress(window, widget, cell_area, layout, dst_x, ypad, flags)
1007
 
 
1008
 
        # More Info button
1009
 
        btn = widget.buttons['info']
1010
 
        dst_x = self._calc_x(cell_area, btn.get_param('width'), xpad)
1011
 
        btn.draw(window, widget, layout, dst_x, cell_area.y)
1012
 
        return
1013
 
 
1014
 
    def on_get_size(self, widget, cell_area):
1015
 
        h = self.base_height
1016
 
        if self.isactive:
1017
 
            h += self.button_height
1018
 
        return -1, -1, -1, h
1019
 
 
1020
 
gobject.type_register(CellRendererAppView)
1021
 
 
1022
 
 
1023
 
# custom renderer for the arrow thing that mpt wants
1024
 
class CellRendererPixbufWithOverlay(gtk.CellRendererText):
1025
 
    """ A CellRenderer with support for a pixbuf and a overlay icon
1026
 
    
1027
 
    It also supports "markup" and "text" so that orca and friends can
1028
 
    read the content out
1029
 
    """
1030
 
    
1031
 
 
1032
 
    # offset of the install overlay icon
1033
 
    OFFSET_X = 14
1034
 
    OFFSET_Y = 16
1035
 
 
1036
 
    # size of the install overlay icon
1037
 
    OVERLAY_SIZE = 16
1038
 
 
1039
 
    __gproperties__ = {
1040
 
        'overlay' : (bool, 'overlay', 'show an overlay icon', False,
1041
 
                     gobject.PARAM_READWRITE),
1042
 
        'pixbuf'  : (gtk.gdk.Pixbuf, 'pixbuf', 'pixbuf',
1043
 
                     gobject.PARAM_READWRITE)
1044
 
   }
1045
 
 
1046
 
    def __init__(self, overlay_icon_name):
1047
 
        gtk.CellRendererText.__init__(self)
1048
 
        icons = gtk.icon_theme_get_default()
1049
 
        self.overlay = False
1050
 
        try:
1051
 
            self._installed = icons.load_icon(overlay_icon_name,
1052
 
                                          self.OVERLAY_SIZE, 0)
1053
 
        except glib.GError:
1054
 
            # icon not present in theme, probably because running uninstalled
1055
 
            self._installed = icons.load_icon('emblem-system',
1056
 
                                          self.OVERLAY_SIZE, 0)
1057
 
    def do_set_property(self, pspec, value):
1058
 
        setattr(self, pspec.name, value)
1059
 
    def do_get_property(self, pspec):
1060
 
        return getattr(self, pspec.name)
1061
 
    def do_render(self, window, widget, background_area, cell_area,
1062
 
                  expose_area, flags):
1063
 
 
1064
 
        # always render icon app icon centered with respect to an unexpanded CellRendererAppView
1065
 
        ypad = self.get_property('ypad')
1066
 
 
1067
 
        area = (cell_area.x,
1068
 
                cell_area.y+ypad,
1069
 
                AppStore.ICON_SIZE,
1070
 
                AppStore.ICON_SIZE)
1071
 
 
1072
 
        dest_x = cell_area.x
1073
 
        dest_y = cell_area.y
1074
 
        window.draw_pixbuf(None,
1075
 
                           self.pixbuf, # icon
1076
 
                           0, 0,            # src pixbuf
1077
 
                           dest_x, dest_y,  # dest in window
1078
 
                           -1, -1,          # size
1079
 
                           0, 0, 0)         # dither
1080
 
 
1081
 
        if self.overlay:
1082
 
            dest_x += self.OFFSET_X
1083
 
            dest_y += self.OFFSET_Y
1084
 
            window.draw_pixbuf(None,
1085
 
                               self._installed, # icon
1086
 
                               0, 0,            # src pixbuf
1087
 
                               dest_x, dest_y,  # dest in window
1088
 
                               -1, -1,          # size
1089
 
                               0, 0, 0)         # dither
1090
 
        return
1091
 
 
1092
 
gobject.type_register(CellRendererPixbufWithOverlay)
 
1068
            return -1, -1, -1, self.normal_height
 
1069
        return -1, -1, -1, self.selected_height
 
1070
 
 
1071
gobject.type_register(CellRendererAppView2)
 
1072
 
1093
1073
 
1094
1074
 
1095
1075
class AppView(gtk.TreeView):
1114
1094
    def __init__(self, show_ratings, store=None):
1115
1095
        gtk.TreeView.__init__(self)
1116
1096
        self._logger = logging.getLogger("softwarecenter.view.appview")
1117
 
        self.buttons = {}
 
1097
        #self.buttons = {}
1118
1098
        self.pressed = False
1119
1099
        self.focal_btn = None
 
1100
        self._action_block_list = []
1120
1101
 
1121
1102
        # if this hacked mode is available everything will be fast
1122
1103
        # and we can set fixed_height mode and still have growing rows
1134
1115
        # we use it so that orca and other a11y tools get proper text to read
1135
1116
        # it needs to be the first one, because that is what the tools look
1136
1117
        # at by default
1137
 
        tp = CellRendererPixbufWithOverlay("software-center-installed")
1138
 
        tp.set_property('ypad', 2)
1139
 
 
1140
 
        column = gtk.TreeViewColumn("Icon", tp,
1141
 
                                    markup=AppStore.COL_MARKUP,
 
1118
 
 
1119
        tr = CellRendererAppView2(show_ratings, "software-center-installed")
 
1120
        tr.set_pixbuf_width(32)
 
1121
        tr.set_button_spacing(3)
 
1122
 
 
1123
        # translatable labels for cell buttons
 
1124
        # string for info button, currently does not need any variants
 
1125
        self._info_str = _('More Info')
 
1126
 
 
1127
        # string for action button
 
1128
        # needs variants for the current label states: install, remove & pending
 
1129
        self._action_strs = {'install' : _('Install'),
 
1130
                             'remove'  : _('Remove')}
 
1131
 
 
1132
        # create buttons and set initial strings
 
1133
        info = CellRendererButton2(name='info', markup=self._info_str)
 
1134
        variants = (self._action_strs['install'],
 
1135
                    self._action_strs['remove'])
 
1136
        action = CellRendererButton2(name='action0', markup_variants=variants)
 
1137
 
 
1138
        tr.button_pack_start(info)
 
1139
        tr.button_pack_end(action)
 
1140
 
 
1141
        column = gtk.TreeViewColumn("Available Apps", tr,
1142
1142
                                    pixbuf=AppStore.COL_ICON,
1143
 
                                    overlay=AppStore.COL_INSTALLED)
1144
 
        column.set_fixed_width(32)
1145
 
        column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1146
 
        self.append_column(column)
1147
 
 
1148
 
        tr = CellRendererAppView(show_ratings)
1149
 
        tr.set_property('xpad', 3)
1150
 
        tr.set_property('ypad', 2)
1151
 
 
1152
 
        column = gtk.TreeViewColumn("Apps", tr, 
 
1143
                                    overlay=AppStore.COL_INSTALLED,
 
1144
                                    text=AppStore.COL_ACCESSIBLE,
1153
1145
                                    markup=AppStore.COL_MARKUP,
1154
1146
                                    rating=AppStore.COL_POPCON,
1155
1147
                                    isactive=AppStore.COL_IS_ACTIVE,
1169
1161
        # custom cursor
1170
1162
        self._cursor_hand = gtk.gdk.Cursor(gtk.gdk.HAND2)
1171
1163
        # our own "activate" handler
1172
 
        self.connect("row-activated", self._on_row_activated)
 
1164
        self.connect("row-activated", self._on_row_activated, tr)
1173
1165
 
1174
1166
        # button and motion are "special"
1175
1167
        self.connect("style-set", self._on_style_set, tr)
1176
 
        self.connect("button-press-event", self._on_button_press_event, column)
1177
 
        self.connect("button-release-event", self._on_button_release_event, column)
1178
 
        self.connect("cursor-changed", self._on_cursor_changed)
1179
 
        self.connect("motion-notify-event", self._on_motion, tr, column)
 
1168
 
 
1169
        self.connect("button-press-event", self._on_button_press_event, tr)
 
1170
        self.connect("button-release-event", self._on_button_release_event, tr)
 
1171
 
 
1172
        self.connect("key-press-event", self._on_key_press_event, tr)
 
1173
        self.connect("key-release-event", self._on_key_release_event, tr)
 
1174
 
 
1175
        self.connect("cursor-changed", self._on_cursor_changed, tr)
 
1176
        self.connect("motion-notify-event", self._on_motion, tr)
1180
1177
 
1181
1178
        self.backend = get_install_backend()
1182
 
        self.backend.connect("transaction-started", self._on_transaction_started)
1183
 
        self.backend.connect("transaction-finished", self._on_transaction_finished)
1184
 
        self.backend.connect("transaction-stopped", self._on_transaction_stopped)
 
1179
        self._transactions_connected = False
 
1180
        self.connect('realize', self._on_realize, tr)
1185
1181
 
1186
1182
    def set_model(self, new_model):
1187
1183
        # unset
1211
1207
        """
1212
1208
        (path, column) = self.get_cursor()
1213
1209
        model = self.get_model()
1214
 
        action_in_progress = False
1215
 
        if path:
1216
 
            action_in_progress = (model[path][AppStore.COL_ACTION_IN_PROGRESS] != -1)
1217
 
        return action_in_progress
 
1210
        return (model[path][AppStore.COL_ACTION_IN_PROGRESS] != -1)
1218
1211
 
1219
1212
    def get_button(self, key):
1220
1213
        return self.buttons[key]
1221
1214
 
1222
 
    def _get_default_font_size(self):
1223
 
        raw_font_name = gtk.settings_get_default().get_property("gtk-font-name")
1224
 
        (font_name, font_size) = string.rsplit(raw_font_name, maxsplit=1)
1225
 
        try:
1226
 
            return int(font_size)
1227
 
        except:
1228
 
            self._logger.warn("could not parse font size for font description: %s" % font_name)
1229
 
        #default size of default gtk font_name ("Sans 10")
1230
 
        return 10
 
1215
    def _on_realize(self, widget, tr):
 
1216
        # connect to backend events once self is realized so handlers 
 
1217
        # have access to the TreeView's initialised gtk.gdk.Window
 
1218
        if self._transactions_connected: return
 
1219
        self.backend.connect("transaction-started", self._on_transaction_started, tr)
 
1220
        self.backend.connect("transaction-finished", self._on_transaction_finished, tr)
 
1221
        self.backend.connect("transaction-stopped", self._on_transaction_stopped, tr)
 
1222
        self._transactions_connected = True
 
1223
        return
1231
1224
 
1232
1225
    def _on_style_set(self, widget, old_style, tr):
1233
 
        self._configure_cell_and_button_geometry(tr)
 
1226
        em = get_em_value()
 
1227
 
 
1228
        tr.set_property('xpad', int(em*0.5+0.5))
 
1229
        tr.set_property('ypad', int(em*0.5+0.5))
 
1230
 
 
1231
        normal_height = int(2.75*em+0.5 + 2*tr.get_property('ypad'))
 
1232
        tr.set_normal_height(normal_height)
 
1233
        tr.set_selected_height(int(normal_height + 3*em))
 
1234
 
 
1235
        for btn in tr.get_buttons():
 
1236
            # reset cached button geometry (x, y, w, h)
 
1237
            btn.scrub_geometry()
 
1238
            # recalc button geometry and cache
 
1239
            btn.configure_geometry(self)
1234
1240
        return
1235
1241
 
1236
 
    def _on_motion(self, tree, event, tr, col):
 
1242
    def _on_motion(self, tree, event, tr):
1237
1243
        x, y = int(event.x), int(event.y)
1238
 
        if not self._xy_is_over_focal_row(x, y) or not self.buttons:
 
1244
        if not self._xy_is_over_focal_row(x, y):
1239
1245
            self.window.set_cursor(None)
1240
1246
            return
1241
1247
 
1242
1248
        path = tree.get_path_at_pos(x, y)
1243
1249
        if not path: return
1244
1250
 
1245
 
        self.window.set_cursor(None)
1246
 
        for id, btn in self.buttons.iteritems():
1247
 
            rr = btn.get_param('region_rect')
1248
 
            if btn.get_param('state') != gtk.STATE_INSENSITIVE:
1249
 
                if rr.point_in(x, y):
 
1251
        use_hand = False
 
1252
        for btn in tr.get_buttons():
 
1253
            if btn.state != gtk.STATE_INSENSITIVE:
 
1254
                if btn.point_in(x, y):
1250
1255
                    if self.focal_btn is btn:
1251
 
                        self.window.set_cursor(self._cursor_hand)
 
1256
                        use_hand = True
1252
1257
                        btn.set_state(gtk.STATE_ACTIVE)
1253
1258
                    elif not self.pressed:
1254
 
                        self.window.set_cursor(self._cursor_hand)
 
1259
                        use_hand = True
1255
1260
                        btn.set_state(gtk.STATE_PRELIGHT)
1256
1261
                else:
1257
 
                    if btn.get_param('state') != gtk.STATE_NORMAL:
 
1262
                    if btn.state != gtk.STATE_NORMAL:
1258
1263
                        btn.set_state(gtk.STATE_NORMAL)
1259
1264
 
1260
 
        store = tree.get_model()
1261
 
        store.row_changed(path[0], store.get_iter(path[0]))
 
1265
        if use_hand:
 
1266
            self.window.set_cursor(self._cursor_hand)
 
1267
        else:
 
1268
            self.window.set_cursor(None)
1262
1269
        return
1263
1270
 
1264
 
    def _on_cursor_changed(self, view):
1265
 
        # trigger callback, if we do it here get_selection() returns
1266
 
        # the previous selected row for some reason
1267
 
        #   without the timeout a row gets multiple times selected
1268
 
        #   and "wobbles" when switching between categories
1269
 
        gobject.timeout_add(1, self._app_selected_timeout_cb, view)
 
1271
    def _on_cursor_changed(self, view, tr):
 
1272
        model = view.get_model()
 
1273
        sel = view.get_selection()
 
1274
        path = view.get_cursor()[0] or (0,)
 
1275
        sel.select_path(path)
 
1276
        self._update_selected_row(view, tr)
1270
1277
 
1271
 
    def _app_selected_timeout_cb(self, view):
1272
 
        selection = view.get_selection()
1273
 
        if not selection:
 
1278
    def _update_selected_row(self, view, tr):
 
1279
        sel = view.get_selection()
 
1280
        if not sel:
1274
1281
            return False
1275
 
        model, it = selection.get_selected()
1276
 
        model, rows = selection.get_selected_rows()
 
1282
        model, rows = sel.get_selected_rows()
1277
1283
        if not rows: 
1278
1284
            return False
 
1285
 
1279
1286
        row = rows[0][0]
1280
1287
        # update active app, use row-ref as argument
1281
1288
        model._set_active_app(row)
1282
 
        #self.queue_draw()
1283
 
        # emit selected signal
 
1289
        installed = model[row][AppStore.COL_INSTALLED]
 
1290
        action_btn = tr.get_button_by_name('action0')
 
1291
        #if not action_btn: return False
 
1292
 
 
1293
        if self.is_action_in_progress_for_selected_app():
 
1294
            action_btn.set_sensitive(False)
 
1295
        elif self.pressed and self.focal_btn == action_btn:
 
1296
            action_btn.set_state(gtk.STATE_ACTIVE)
 
1297
        else:
 
1298
            action_btn.set_state(gtk.STATE_NORMAL)
 
1299
 
 
1300
        if installed:
 
1301
            action_btn.set_markup_variant_n(1)
 
1302
            #action_btn.configure_geometry(self)
 
1303
        else:
 
1304
            action_btn.set_markup_variant_n(0)
 
1305
            #action_btn.configure_geometry(self)
 
1306
 
1284
1307
        name = model[row][AppStore.COL_APP_NAME]
1285
1308
        pkgname = model[row][AppStore.COL_PKGNAME]
1286
 
        #print name, pkgname
1287
1309
        popcon = model[row][AppStore.COL_POPCON]
1288
 
        if self.buttons.has_key('action'):
1289
 
            action_button = self.buttons['action']
1290
 
            if self.is_action_in_progress_for_selected_app():
1291
 
                action_button.set_sensitive(False)
1292
 
            else:
1293
 
                action_button.set_sensitive(True)
1294
1310
        self.emit("application-selected", Application(name, pkgname, popcon))
1295
1311
        return False
1296
1312
 
1297
 
    def _on_row_activated(self, view, path, column):
 
1313
    def _on_row_activated(self, view, path, column, tr):
 
1314
        pointer = gtk.gdk.device_get_core_pointer()
 
1315
        x, y = pointer.get_state(view.window)[0]
 
1316
        for btn in tr.get_buttons():
 
1317
            if btn.point_in(int(x), int(y)): return
 
1318
 
1298
1319
        model = view.get_model()
1299
1320
        exists = model[path][AppStore.COL_EXISTS]
1300
1321
        if exists:
1303
1324
            popcon = model[path][AppStore.COL_POPCON]
1304
1325
            self.emit("application-activated", Application(name, pkgname, popcon))
1305
1326
 
1306
 
    def _on_button_press_event(self, view, event, col):
 
1327
    def _on_button_press_event(self, view, event, tr):
1307
1328
        if event.button != 1:
1308
1329
            return
1309
1330
        self.pressed = True
1310
1331
        res = view.get_path_at_pos(int(event.x), int(event.y))
1311
1332
        if not res:
1312
1333
            return
1313
 
        (path, column, wx, wy) = res
 
1334
        path = res[0]
1314
1335
        if path is None:
1315
1336
            return
1316
1337
        # only act when the selection is already there
1319
1340
            return
1320
1341
 
1321
1342
        x, y = int(event.x), int(event.y)
1322
 
        for btn_id, btn in self.buttons.iteritems():
1323
 
            rr = btn.get_param('region_rect')
1324
 
            if rr.point_in(x, y) and (btn.get_param('state') != gtk.STATE_INSENSITIVE):
 
1343
        for btn in tr.get_buttons():
 
1344
            if btn.point_in(x, y) and (btn.state != gtk.STATE_INSENSITIVE):
1325
1345
                self.focal_btn = btn
1326
1346
                btn.set_state(gtk.STATE_ACTIVE)
1327
 
                btn.set_shadow(gtk.SHADOW_IN)
1328
1347
                return
1329
1348
        self.focal_btn = None
1330
1349
 
1331
 
    def _on_button_release_event(self, view, event, col):
 
1350
    def _on_button_release_event(self, view, event, tr):
1332
1351
        if event.button != 1:
1333
1352
            return
1334
1353
        self.pressed = False
1335
1354
        res = view.get_path_at_pos(int(event.x), int(event.y))
1336
1355
        if not res:
1337
1356
            return
1338
 
        (path, column, wx, wy) = res
 
1357
        path = res[0]
1339
1358
        if path is None:
1340
1359
            return
1341
1360
        # only act when the selection is already there
1344
1363
            return
1345
1364
 
1346
1365
        x, y = int(event.x), int(event.y)
1347
 
        for btn_id, btn in self.buttons.iteritems():
1348
 
            rr = btn.get_param('region_rect')
1349
 
            if rr.point_in(x, y) and (btn.get_param('state') != gtk.STATE_INSENSITIVE):
 
1366
        for btn in tr.get_buttons():
 
1367
            if btn.point_in(x, y) and (btn.state != gtk.STATE_INSENSITIVE):
1350
1368
                btn.set_state(gtk.STATE_NORMAL)
1351
 
                btn.set_shadow(gtk.SHADOW_OUT)
1352
1369
                self.window.set_cursor(self._cursor_hand)
1353
1370
                if self.focal_btn is not btn:
1354
1371
                    break
1355
 
                model = view.get_model()
1356
 
                appname = model[path][AppStore.COL_APP_NAME]
1357
 
                pkgname = model[path][AppStore.COL_PKGNAME]
1358
 
                installed = model[path][AppStore.COL_INSTALLED]
1359
 
                popcon = model[path][AppStore.COL_POPCON]
1360
 
 
1361
 
                s = gtk.settings_get_default()
1362
 
                gobject.timeout_add(s.get_property("gtk-timeout-initial"),
1363
 
                                    self._app_activated_cb,
1364
 
                                    btn,
1365
 
                                    btn_id,
1366
 
                                    appname,
1367
 
                                    pkgname,
1368
 
                                    popcon,
1369
 
                                    installed,
1370
 
                                    view.get_model(),
1371
 
                                    path)
 
1372
                self._init_activated(btn, view.get_model(), path)
1372
1373
                break
1373
1374
        self.focal_btn = None
1374
1375
 
 
1376
    def _on_key_press_event(self, widget, event, tr):
 
1377
        kv = event.keyval
 
1378
        #print kv
 
1379
        r = False
 
1380
        if kv == gtk.keysyms.Right: # right-key
 
1381
            btn = tr.get_button_by_name('action0')
 
1382
            if btn.state != gtk.STATE_INSENSITIVE:
 
1383
                btn.has_focus = True
 
1384
                btn = tr.get_button_by_name('info')
 
1385
                btn.has_focus = False
 
1386
        elif kv == gtk.keysyms.Left: # left-key
 
1387
            btn = tr.get_button_by_name('action0')
 
1388
            btn.has_focus = False
 
1389
            btn = tr.get_button_by_name('info')
 
1390
            btn.has_focus = True
 
1391
        elif kv == gtk.keysyms.space:  # spacebar
 
1392
            for btn in tr.get_buttons():
 
1393
                if btn.has_focus and btn.state != gtk.STATE_INSENSITIVE:
 
1394
                    btn.set_state(gtk.STATE_ACTIVE)
 
1395
                    sel = self.get_selection()
 
1396
                    model, it = sel.get_selected()
 
1397
                    path = model.get_path(it)
 
1398
                    #print model[path][AppStore.COL_APP_NAME]
 
1399
                    if path:
 
1400
                        #self._init_activated(btn, self.get_model(), path)
 
1401
                        r = True
 
1402
                    break
 
1403
 
 
1404
        self.queue_draw()
 
1405
        return r
 
1406
 
 
1407
    def _on_key_release_event(self, widget, event, tr):
 
1408
        kv = event.keyval
 
1409
        r = False
 
1410
        if kv == 32:    # spacebar
 
1411
            for btn in tr.get_buttons():
 
1412
                if btn.has_focus and btn.state != gtk.STATE_INSENSITIVE:
 
1413
                    btn.set_state(gtk.STATE_NORMAL)
 
1414
                    sel = self.get_selection()
 
1415
                    model, it = sel.get_selected()
 
1416
                    path = model.get_path(it)
 
1417
                    #print model[path][AppStore.COL_APP_NAME]
 
1418
                    if path:
 
1419
                        self._init_activated(btn, self.get_model(), path)
 
1420
                        btn.has_focus = False
 
1421
                        r = True
 
1422
                    break
 
1423
 
 
1424
        self.queue_draw()
 
1425
        return r
 
1426
 
 
1427
    def _init_activated(self, btn, model, path):
 
1428
 
 
1429
        appname = model[path][AppStore.COL_APP_NAME]
 
1430
        pkgname = model[path][AppStore.COL_PKGNAME]
 
1431
        installed = model[path][AppStore.COL_INSTALLED]
 
1432
        popcon = model[path][AppStore.COL_POPCON]
 
1433
 
 
1434
        s = gtk.settings_get_default()
 
1435
        gobject.timeout_add(s.get_property("gtk-timeout-initial"),
 
1436
                            self._app_activated_cb,
 
1437
                            btn,
 
1438
                            btn.name,
 
1439
                            appname,
 
1440
                            pkgname,
 
1441
                            popcon,
 
1442
                            installed,
 
1443
                            model,
 
1444
                            path)
 
1445
        return
 
1446
 
1375
1447
    def _app_activated_cb(self, btn, btn_id, appname, pkgname, popcon, installed, store, path):
1376
1448
        if btn_id == 'info':
1377
1449
            self.emit("application-activated", Application(appname, pkgname, popcon))
1378
 
        elif btn_id == 'action':
 
1450
        elif btn_id == 'action0':
1379
1451
            btn.set_sensitive(False)
1380
1452
            store.row_changed(path[0], store.get_iter(path[0]))
 
1453
            # be sure we dont request an action for a pkg with pre-existing actions
 
1454
            if pkgname in self._action_block_list:
 
1455
                print 'Action already in progress for package: %s' % pkgname
 
1456
                return
 
1457
            self._action_block_list.append(pkgname)
1381
1458
            if installed:
1382
1459
                perform_action = APP_ACTION_REMOVE
1383
1460
            else:
1384
1461
                perform_action = APP_ACTION_INSTALL
1385
1462
            self.emit("application-request-action", Application(appname, pkgname, popcon), perform_action)
1386
1463
        return False
1387
 
        
1388
 
    def _on_transaction_started(self, backend):
 
1464
 
 
1465
    def _set_cursor(self, btn, cursor):
 
1466
        pointer = gtk.gdk.device_get_core_pointer()
 
1467
        x, y = pointer.get_state(self.window)[0]
 
1468
        if btn.point_in(int(x), int(y)):
 
1469
            self.window.set_cursor(cursor)
 
1470
 
 
1471
    def _on_transaction_started(self, backend, tr):
1389
1472
        """ callback when an application install/remove transaction has started """
1390
 
        if self.buttons.has_key('action'):
1391
 
            self.buttons['action'].set_sensitive(False)
1392
 
        
1393
 
    def _on_transaction_finished(self, backend, success):
 
1473
        action_btn = tr.get_button_by_name('action0')
 
1474
        if action_btn:
 
1475
            action_btn.set_sensitive(False)
 
1476
            self._set_cursor(action_btn, None)
 
1477
 
 
1478
    def _on_transaction_finished(self, backend, pkgname, success, tr):
1394
1479
        """ callback when an application install/remove transaction has finished """
1395
 
        if self.buttons.has_key('action'):
1396
 
            self.buttons['action'].set_sensitive(True)
1397
 
 
1398
 
    def _on_transaction_stopped(self, backend):
 
1480
        # remove pkg from the block list
 
1481
        self._check_remove_pkg_from_blocklist(pkgname)
 
1482
 
 
1483
        action_btn = tr.get_button_by_name('action0')
 
1484
        if action_btn:
 
1485
            action_btn.set_sensitive(True)
 
1486
            self._set_cursor(action_btn, self._cursor_hand)
 
1487
 
 
1488
    def _on_transaction_stopped(self, backend, pkgname, tr):
1399
1489
        """ callback when an application install/remove transaction has stopped """
1400
 
        if self.buttons.has_key('action'):
1401
 
            self.buttons['action'].set_sensitive(True)
 
1490
        # remove pkg from the block list
 
1491
        self._check_remove_pkg_from_blocklist(pkgname)
 
1492
 
 
1493
        action_btn = tr.get_button_by_name('action0')
 
1494
        if action_btn:
 
1495
            # this should be a function that decides action button state label...
 
1496
            if action_btn.current_variant == 2:
 
1497
                action_btn.set_markup_variant_n(1)
 
1498
            action_btn.set_sensitive(True)
 
1499
            self._set_cursor(action_btn, self._cursor_hand)
 
1500
 
 
1501
    def _check_remove_pkg_from_blocklist(self, pkgname):
 
1502
        if pkgname in self._action_block_list:
 
1503
            i = self._action_block_list.index(pkgname)
 
1504
            del self._action_block_list[i]
1402
1505
 
1403
1506
    def _xy_is_over_focal_row(self, x, y):
1404
1507
        res = self.get_path_at_pos(x, y)
1407
1510
            return False
1408
1511
        return self.get_path_at_pos(x, y)[0] == self.get_cursor()[0]
1409
1512
 
1410
 
    def _configure_cell_and_button_geometry(self, tr):
1411
 
        # tell the cellrenderer the text direction for renderering purposes
1412
 
        tr.set_direction(self.get_direction())
1413
 
 
1414
 
        pc = self.get_pango_context()
1415
 
        layout = pango.Layout(pc)
1416
 
 
1417
 
        font_size = self._get_default_font_size()
1418
 
        tr.set_base_height(max(int(3.5*font_size), 32))    # 32, the pixbufoverlay height
1419
 
 
1420
 
        action_btn = CellRendererButton(layout, markup=_("Install"), alt_markup=_("Remove"))
1421
 
        info_btn = CellRendererButton(layout, _("More Info"))
1422
 
 
1423
 
        max_h = max(action_btn.get_param('height'), info_btn.get_param('height'))
1424
 
        tr.set_button_height(max_h+tr.get_property('ypad')*2)
1425
 
 
1426
 
        yO = tr.base_height+tr.get_property('ypad')
1427
 
        action_btn.set_param('y_offset_const', yO)
1428
 
        info_btn.set_param('y_offset_const', yO)
1429
 
 
1430
 
        self.buttons['action'] = action_btn
1431
 
        self.buttons['info'] = info_btn
1432
 
        return
1433
 
 
1434
1513
 
1435
1514
# XXX should we use a xapian.MatchDecider instead?
1436
1515
class AppViewFilter(object):