572
class CellRendererButton:
574
def __init__(self, layout, markup, alt_markup=None, xpad=14, ypad=4):
576
w, h, mx, amx = self._calc_markup_params(layout, markup, xpad, ypad)
578
w, h, mx, amx = self._calc_markup_params_alt(layout, markup, alt_markup, xpad, ypad)
583
'alt_markup': alt_markup,
587
'region_rect': gtk.gdk.region_rectangle(gtk.gdk.Rectangle(0,0,0,0)),
590
'state': gtk.STATE_NORMAL,
591
'shadow': gtk.SHADOW_OUT,
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
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)
613
mx = xpad + (amw - mw)/2
618
amx = xpad + (mw - amw)/2
620
# assume text height is the same for markups.
621
h = self._get_layout_pixel_height(layout) + 2*ypad
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]
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]
634
def set_state(self, state_type):
635
self.params['state'] = state_type
638
def set_shadow(self, shadow_type):
639
self.params['shadow'] = shadow_type
588
class CellRendererButton2:
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
595
self.current_variant = 0
597
self.markup_variants = (markup,)
599
# expects a list or tuple!
600
self.markup_variants = markup_variants
604
self.allocation = gdk.Rectangle(0,0,1,1)
605
self.state = gtk.STATE_NORMAL
606
self.has_focus = False
609
self._geometry = None
612
def _layout_reset(self, layout):
614
layout.set_ellipsize(pango.ELLIPSIZE_NONE)
617
def configure_geometry(self, widget):
618
if self._geometry: return
619
pc = widget.get_pango_context()
620
layout = pango.Layout(pc)
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:])
627
self.set_size(w+2*self.xpad, h+2*self.ypad)
628
self._geometry = True
631
def scrub_geometry(self):
632
self._geometry = None
635
def point_in(self, x, y):
636
return gdk.region_rectangle(self.allocation).point_in(x,y)
640
return a.width, a.height
642
def set_position(self, x, y):
644
self.size_allocate(gdk.Rectangle(x, y, a.width, a.height))
647
def set_size(self, w, h):
649
self.size_allocate(gdk.Rectangle(a.x, a.y, w, h))
652
def size_allocate(self, rect):
653
self.allocation = rect
656
def set_state(self, state):
657
if state == self.state: return
660
self._widget.queue_draw_area(*self.get_allocation_tuple())
641
663
def set_sensitive(self, 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)
650
def set_use_alt_markup(self, use_alt):
651
if self.use_alt == use_alt:
665
self.state = gtk.STATE_NORMAL
667
self.state = gtk.STATE_INSENSITIVE
669
self._widget.queue_draw_area(*self.get_allocation_tuple())
672
def set_markup(self, markup):
673
self.markup_variant = (markup,)
676
def set_markup_variants(self, markup_variants):
677
# expects a tuple or list
678
self.markup_variants = markup_variants
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
653
self.use_alt = use_alt
656
p['label'] = p['alt_markup']
657
p['layout_x'] = p['alt_markup_x']
659
p['label'] = p['markup']
660
p['layout_x'] = p['markup_x']
663
def get_use_alt_markup(self):
666
def set_param(self, key, value):
667
self.params[key] = value
670
def get_param(self, key):
671
return self.params[key]
673
def get_params(self, *keys):
676
r.append(self.params[k])
679
def draw(self, window, widget, layout, dst_x, cell_yO):
681
w, h, yO = self.get_params('width', 'height', 'y_offset_const')
685
# backgound "button" rect
686
self.current_variant = n
689
def get_allocation_tuple(self):
691
return (a.x, a.y, a.width, a.height)
693
def render(self, window, widget, layout=None):
695
self._widget = widget
696
if self.state != gtk.STATE_ACTIVE:
697
shadow = gtk.SHADOW_OUT
699
shadow = gtk.SHADOW_IN
702
pc = widget.get_pango_context()
703
layout = pango.Layout(pc)
705
self._layout_reset(layout)
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()
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)
718
cr.paint_with_alpha(0)
720
cr.set_operator(cairo.OPERATOR_OVER)
686
722
widget.style.paint_box(window,
689
(dst_x, dst_y, w, h),
697
# cache region_rectangle for event checks
698
p['region_rect'] = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(dst_x, dst_y, w, h))
700
# # if btn_has_focus:
702
# widget.style.paint_focus(window,
704
# (dst_x, dst_y, w, h),
713
dst_x += p['layout_x']
715
layout.set_markup(p['label'])
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]
736
# else, we can just use xpad as the x-offset...
740
if self.has_focus and self.state != gtk.STATE_INSENSITIVE and \
741
self._widget.has_focus():
742
widget.style.paint_focus(window,
744
(x+2, y+2, w-4, h-4),
716
750
widget.style.paint_layout(window,
719
(dst_x, dst_y, w, h),
728
761
# custom cell renderer to support dynamic grow
729
class CellRendererAppView(gtk.GenericCellRenderer):
762
class CellRendererAppView2(gtk.CellRendererText):
764
# offset of the install overlay icon
768
# size of the install overlay icon
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),
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),
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),
742
# 'reviews': (gobject.TYPE_INT, 'Reviews', 'Number of reviews', 0, 100, 0,
743
# gobject.PARAM_READWRITE),
745
782
'isactive': (bool, 'IsActive', 'Is active?', False,
746
783
gobject.PARAM_READWRITE),
757
794
'exists': (bool, 'exists', 'Is the app found in current channel', False,
758
795
gobject.PARAM_READWRITE),
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)
761
def __init__(self, show_ratings):
762
self.__gobject_init__()
766
self.button_height = 0
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
771
816
self.isactive = False
772
817
self.installed = False
773
818
self.show_ratings = show_ratings
821
self.button_spacing = 0
822
self._buttons = {gtk.PACK_START: [],
824
self._all_buttons = {}
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)
780
# specify the func that calc's distance from margin, based on text dir
781
self._calc_x = self._calc_x_ltr
832
self._installed = icons.load_icon(overlay_icon_name,
833
self.OVERLAY_SIZE, 0)
835
# icon not present in theme, probably because running uninstalled
836
self._installed = icons.load_icon('emblem-system',
837
self.OVERLAY_SIZE, 0)
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]
843
def _layout_get_pixel_height(self, layout):
844
return layout.get_pixel_extents()[1][3]
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
851
if direction != gtk.TEXT_DIR_RTL:
789
self._calc_x = self._calc_x_rtl
792
def set_base_height(self, base_height):
793
self.base_height = base_height
796
def set_button_height(self, button_height):
797
self.button_height = button_height
800
def do_set_property(self, pspec, value):
801
setattr(self, pspec.name, value)
803
def do_get_property(self, pspec):
804
return getattr(self, pspec.name)
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]
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]
816
def _calc_x_ltr(self, cell_area, aspect_width, margin_xO):
817
return cell_area.x + margin_xO
819
def _calc_x_rtl(self, cell_area, aspect_width, margin_xO):
820
return cell_area.x + cell_area.width - aspect_width - margin_xO
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)
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
856
# draw appicon pixbuf
857
window.draw_pixbuf(None,
860
x, cell_area.y+ypad+yo, # dest in window
864
# draw overlay if application is installed
866
if direction != gtk.TEXT_DIR_RTL:
869
x = cell_area.width - self.OFFSET_X - self.OVERLAY_SIZE
871
y = cell_area.y + self.OFFSET_Y
872
window.draw_pixbuf(None,
873
self._installed, # icon
875
x, y, # dest in window
880
def _render_appsummary(self, window, widget, cell_area, state, layout, xpad, ypad, direction):
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
887
if self.isactive and self.props.action_in_progress > 0:
888
action_btn = self.get_button_by_name('action0')
890
print 'No action button? This doesn\'t make sense!'
892
max_layout_width -= (xpad + action_btn.get_size()[0])
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
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
901
x = cell_area.x+cell_area.width-lw-self.pixbuf_width-2*xpad
904
w, h = lw, self.normal_height
905
widget.style.paint_layout(window, state,
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
916
# per the spec, the progressbar should be the width of the action button
917
action_btn = self.get_button_by_name('action0')
919
print 'No action button? This doesn\'t make sense!'
922
x, y, w, h = action_btn.get_allocation_tuple()
923
# shift the bar to the top edge
924
y = cell_area.y + ypad
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.
930
widget.style.paint_box(window,
938
if direction != gtk.TEXT_DIR_RTL:
939
clip = gdk.Rectangle(x, y, int((w)*percent), h)
941
clip = gdk.Rectangle(x+(w-int(w*percent)), y, int(w*percent), h)
943
widget.style.paint_box(window,
950
clip.width, clip.height)
953
def set_normal_height(self, h):
954
self.normal_height = int(h)
957
def set_pixbuf_width(self, w):
958
self.pixbuf_width = w
961
def set_selected_height(self, h):
962
self.selected_height = h
965
def set_button_spacing(self, spacing):
966
self.button_spacing = spacing
969
def get_button_by_name(self, name):
970
if name in self._all_buttons:
971
return self._all_buttons[name]
974
def get_buttons(self):
976
for k, v in self._buttons.iteritems():
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
985
def button_pack_start(self, btn):
986
self.button_pack(btn, gtk.PACK_START)
989
def button_pack_end(self, btn):
990
self.button_pack(btn, gtk.PACK_END)
993
def do_set_property(self, pspec, value):
994
setattr(self, pspec.name, value)
996
def do_get_property(self, pspec):
997
return getattr(self, pspec.name)
999
def do_render(self, window, widget, background_area, cell_area,
1000
expose_area, flags):
1002
xpad = self.get_property('xpad')
1003
ypad = self.get_property('ypad')
1004
direction = widget.get_direction()
841
1006
# important! ensures correct text rendering, esp. when using hicolor theme
842
1007
if (flags & gtk.CELL_RENDERER_SELECTED) != 0:
845
1010
state = gtk.STATE_NORMAL
847
widget.style.paint_layout(window,
856
# remove layout size constraints
858
return w, h, max_star_width
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:
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
874
self.draw_rating(window, cell_area, dst_y, max_star_width, xpad, self.rating)
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, }
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)
886
widget.style.paint_layout(window,
893
cell_area.y+ypad+h+1,
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)
905
dst_x = cell_area.x + xpad + max_star_width - w - i*(w+1)
908
window.draw_pixbuf(None,
909
self.star_pixbuf, # icon
916
window.draw_pixbuf(None,
917
self.star_not_pixbuf, # icon
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
931
# progress trough border
932
widget.style.paint_flat_box(window, gtk.STATE_ACTIVE, gtk.SHADOW_IN,
933
(dst_x, dst_y, w, h),
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),
952
prog_w = int(percent*w)
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),
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)
1017
self._render_icon(window, widget,
1022
self._layout.set_markup(self.markup)
1023
self._render_appsummary(window, widget,
1029
if not self.isactive:
1032
if self.props.action_in_progress > 0:
1033
self._render_progress(window,
1039
# layout buttons and paint
1040
y = cell_area.y+cell_area.height-ypad
1041
spacing = self.button_spacing
1043
if direction != gtk.TEXT_DIR_RTL:
1044
start = gtk.PACK_START
1046
xs = cell_area.x + 2*xpad + self.pixbuf_width
1047
xb = cell_area.x + cell_area.width - xpad
964
widget.style.paint_box(window, flags, gtk.SHADOW_OUT,
965
(dst_x + w+1-prog_w, dst_y, prog_w, 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
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
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)
974
def on_render(self, window, widget, background_area, cell_area,
976
xpad = self.get_property('xpad')
977
ypad = self.get_property('ypad')
979
# create pango layout with markup
980
pc = widget.get_pango_context()
981
layout = pango.Layout(pc)
982
layout.set_markup(self.markup)
984
w, h, max_star_width = self.draw_appname_summary(window, widget, cell_area, layout, xpad, ypad, flags)
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)
993
# Install/Remove button
994
# only draw a install/remove button if the app is actually 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)
1006
self.draw_progress(window, widget, cell_area, layout, dst_x, ypad, flags)
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)
1014
def on_get_size(self, widget, cell_area):
1015
h = self.base_height
1017
h += self.button_height
1018
return -1, -1, -1, h
1020
gobject.type_register(CellRendererAppView)
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
1027
It also supports "markup" and "text" so that orca and friends can
1028
read the content out
1032
# offset of the install overlay icon
1036
# size of the install overlay icon
1040
'overlay' : (bool, 'overlay', 'show an overlay icon', False,
1041
gobject.PARAM_READWRITE),
1042
'pixbuf' : (gtk.gdk.Pixbuf, 'pixbuf', 'pixbuf',
1043
gobject.PARAM_READWRITE)
1046
def __init__(self, overlay_icon_name):
1047
gtk.CellRendererText.__init__(self)
1048
icons = gtk.icon_theme_get_default()
1049
self.overlay = False
1051
self._installed = icons.load_icon(overlay_icon_name,
1052
self.OVERLAY_SIZE, 0)
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):
1064
# always render icon app icon centered with respect to an unexpanded CellRendererAppView
1065
ypad = self.get_property('ypad')
1067
area = (cell_area.x,
1072
dest_x = cell_area.x
1073
dest_y = cell_area.y
1074
window.draw_pixbuf(None,
1077
dest_x, dest_y, # dest in window
1082
dest_x += self.OFFSET_X
1083
dest_y += self.OFFSET_Y
1084
window.draw_pixbuf(None,
1085
self._installed, # icon
1087
dest_x, dest_y, # dest in window
1092
gobject.type_register(CellRendererPixbufWithOverlay)
1068
return -1, -1, -1, self.normal_height
1069
return -1, -1, -1, self.selected_height
1071
gobject.type_register(CellRendererAppView2)
1095
1075
class AppView(gtk.TreeView):
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)
1140
column = gtk.TreeViewColumn("Icon", tp,
1141
markup=AppStore.COL_MARKUP,
1119
tr = CellRendererAppView2(show_ratings, "software-center-installed")
1120
tr.set_pixbuf_width(32)
1121
tr.set_button_spacing(3)
1123
# translatable labels for cell buttons
1124
# string for info button, currently does not need any variants
1125
self._info_str = _('More Info')
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')}
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)
1138
tr.button_pack_start(info)
1139
tr.button_pack_end(action)
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)
1148
tr = CellRendererAppView(show_ratings)
1149
tr.set_property('xpad', 3)
1150
tr.set_property('ypad', 2)
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,
1212
1208
(path, column) = self.get_cursor()
1213
1209
model = self.get_model()
1214
action_in_progress = False
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)
1219
1212
def get_button(self, key):
1220
1213
return self.buttons[key]
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)
1226
return int(font_size)
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")
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
1232
1225
def _on_style_set(self, widget, old_style, tr):
1233
self._configure_cell_and_button_geometry(tr)
1228
tr.set_property('xpad', int(em*0.5+0.5))
1229
tr.set_property('ypad', int(em*0.5+0.5))
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))
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)
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)
1242
1248
path = tree.get_path_at_pos(x, y)
1243
1249
if not path: return
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):
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)
1252
1257
btn.set_state(gtk.STATE_ACTIVE)
1253
1258
elif not self.pressed:
1254
self.window.set_cursor(self._cursor_hand)
1255
1260
btn.set_state(gtk.STATE_PRELIGHT)
1257
if btn.get_param('state') != gtk.STATE_NORMAL:
1262
if btn.state != gtk.STATE_NORMAL:
1258
1263
btn.set_state(gtk.STATE_NORMAL)
1260
store = tree.get_model()
1261
store.row_changed(path[0], store.get_iter(path[0]))
1266
self.window.set_cursor(self._cursor_hand)
1268
self.window.set_cursor(None)
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)
1271
def _app_selected_timeout_cb(self, view):
1272
selection = view.get_selection()
1278
def _update_selected_row(self, view, tr):
1279
sel = view.get_selection()
1275
model, it = selection.get_selected()
1276
model, rows = selection.get_selected_rows()
1282
model, rows = sel.get_selected_rows()
1279
1286
row = rows[0][0]
1280
1287
# update active app, use row-ref as argument
1281
1288
model._set_active_app(row)
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
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)
1298
action_btn.set_state(gtk.STATE_NORMAL)
1301
action_btn.set_markup_variant_n(1)
1302
#action_btn.configure_geometry(self)
1304
action_btn.set_markup_variant_n(0)
1305
#action_btn.configure_geometry(self)
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)
1293
action_button.set_sensitive(True)
1294
1310
self.emit("application-selected", Application(name, pkgname, popcon))
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
1298
1319
model = view.get_model()
1299
1320
exists = model[path][AppStore.COL_EXISTS]
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:
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]
1361
s = gtk.settings_get_default()
1362
gobject.timeout_add(s.get_property("gtk-timeout-initial"),
1363
self._app_activated_cb,
1372
self._init_activated(btn, view.get_model(), path)
1373
1374
self.focal_btn = None
1376
def _on_key_press_event(self, widget, event, tr):
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]
1400
#self._init_activated(btn, self.get_model(), path)
1407
def _on_key_release_event(self, widget, event, tr):
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]
1419
self._init_activated(btn, self.get_model(), path)
1420
btn.has_focus = False
1427
def _init_activated(self, btn, model, path):
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]
1434
s = gtk.settings_get_default()
1435
gobject.timeout_add(s.get_property("gtk-timeout-initial"),
1436
self._app_activated_cb,
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
1457
self._action_block_list.append(pkgname)
1382
1459
perform_action = APP_ACTION_REMOVE
1384
1461
perform_action = APP_ACTION_INSTALL
1385
1462
self.emit("application-request-action", Application(appname, pkgname, popcon), perform_action)
1388
def _on_transaction_started(self, backend):
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)
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)
1393
def _on_transaction_finished(self, backend, success):
1473
action_btn = tr.get_button_by_name('action0')
1475
action_btn.set_sensitive(False)
1476
self._set_cursor(action_btn, None)
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)
1398
def _on_transaction_stopped(self, backend):
1480
# remove pkg from the block list
1481
self._check_remove_pkg_from_blocklist(pkgname)
1483
action_btn = tr.get_button_by_name('action0')
1485
action_btn.set_sensitive(True)
1486
self._set_cursor(action_btn, self._cursor_hand)
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)
1493
action_btn = tr.get_button_by_name('action0')
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)
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]
1403
1506
def _xy_is_over_focal_row(self, x, y):
1404
1507
res = self.get_path_at_pos(x, y)