1
# Copyright (C) 2011 Canonical
7
# This program is free software; you can redistribute it and/or modify it under
8
# the terms of the GNU General Public License as published by the Free Software
9
# Foundation; version 3.
11
# This program is distributed in the hope that it will be useful, but WITHOUT
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16
# You should have received a copy of the GNU General Public License along with
17
# this program; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
from gi.repository import Gtk, Gdk, GObject, Pango
22
from softwarecenter.utils import utf8
23
from softwarecenter.ui.gtk3.em import EM
24
from softwarecenter.ui.gtk3.models.appstore2 import CategoryRowReference
26
from stars import StarRenderer, StarSize
34
# custom cell renderer to support dynamic grow
35
class CellRendererAppView(Gtk.CellRendererText):
37
# x, y offsets for the overlay icon
38
OVERLAY_XO = OVERLAY_YO = 2
40
# size of the install overlay icon
48
'application' : (GObject.TYPE_PYOBJECT, 'document',
49
'a xapian document containing pkg information',
50
GObject.PARAM_READWRITE),
52
'isactive' : (bool, 'isactive', 'is cell active/selected', False,
53
GObject.PARAM_READWRITE),
57
def __init__(self, icons, layout, show_ratings, overlay_icon_name):
58
GObject.GObject.__init__(self)
60
# geometry-state values
62
self.apptitle_width = 0
63
self.apptitle_height = 0
64
self.normal_height = 0
65
self.selected_height = 0
66
self.show_ratings = show_ratings
69
self.button_spacing = 0
70
self._buttons = {Gtk.PackType.START: [],
72
self._all_buttons = {}
76
# star painter, paints stars
77
self._stars = StarRenderer()
78
self._stars.size = StarSize.SMALL
82
self._installed = icons.load_icon(overlay_icon_name,
84
except GObject.GError:
85
# icon not present in theme, probably because running uninstalled
86
self._installed = icons.load_icon('emblem-system',
90
def _layout_get_pixel_width(self, layout):
91
return layout.get_size()[0] / Pango.SCALE
93
def _layout_get_pixel_height(self, layout):
94
return layout.get_size()[1] / Pango.SCALE
96
def _render_category(self,
97
context, cr, app, cell_area, layout, xpad, ypad, is_rtl):
99
layout.set_markup('<b>%s</b>' % app.display_name, -1)
101
# work out max allowable layout width
102
lw = self._layout_get_pixel_width(layout)
103
lh = self._layout_get_pixel_height(layout)
108
x = cell_area.x + cell_area.width - lw
109
y = cell_area.y + (cell_area.height - lh)/2
111
Gtk.render_layout(context, cr, x, y, layout)
114
def _render_price(self, context, cr, app, layout, cell_area, xpad, ypad, is_rtl):
115
layout.set_markup("US$ %s" % self.model.get_price(app), -1)
118
x = cell_area.x + xpad
120
x = (cell_area.x + cell_area.width - xpad -
121
self._layout_get_pixel_width(layout))
123
Gtk.render_layout(context, cr,
124
x, ypad + cell_area.y, layout)
127
def _render_icon(self, cr, app, cell_area, xpad, ypad, is_rtl):
128
# calc offsets so icon is nicely centered
129
icon = self.model.get_icon(app)
130
xo = (self.pixbuf_width - icon.get_width())/2
133
x = cell_area.x + xo + xpad
135
x = cell_area.x + cell_area.width + xo - self.pixbuf_width - xpad
136
y = cell_area.y + ypad
138
# draw appicon pixbuf
139
Gdk.cairo_set_source_pixbuf(cr, icon, x, y)
142
# draw overlay if application is installed
143
if self.model.is_installed(app):
145
x += (self.pixbuf_width - self.OVERLAY_SIZE + self.OVERLAY_XO)
148
y += (self.pixbuf_width - self.OVERLAY_SIZE + self.OVERLAY_YO)
149
Gdk.cairo_set_source_pixbuf(cr, self._installed, x, y)
153
def _render_summary(self, context, cr, app,
154
cell_area, layout, xpad, ypad,
157
layout.set_markup(self.model.get_markup(app), -1)
159
# work out max allowable layout width
161
lw = self._layout_get_pixel_width(layout)
162
max_layout_width = (cell_area.width - self.pixbuf_width -
165
max_layout_width = cell_area.width - self.pixbuf_width - 3*xpad
167
stats = self.model.get_review_stats(app)
168
if self.show_ratings and stats:
169
max_layout_width -= star_width+6*xpad
171
if self.props.isactive and self.model.get_transaction_progress(app) > 0:
172
action_btn = self.get_button_by_name(CellButtonIDs.ACTION)
173
max_layout_width -= (xpad + action_btn.width)
175
if lw >= max_layout_width:
176
layout.set_width((max_layout_width)*Pango.SCALE)
177
layout.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
178
lw = max_layout_width
180
apptitle_extents = layout.get_line_readonly(0).get_pixel_extents()[1]
181
self.apptitle_width = apptitle_extents.width
182
self.apptitle_height = apptitle_extents.height
185
x = cell_area.x+2*xpad+self.pixbuf_width
187
x = cell_area.x+cell_area.width-lw-self.pixbuf_width-2*xpad
191
Gtk.render_layout(context, cr, x, y, layout)
194
def _render_rating(self, context, cr, app,
195
cell_area, layout, xpad, ypad,
196
star_width, star_height, is_rtl):
198
stats = self.model.get_review_stats(app)
204
x = (cell_area.x + 3 * xpad + self.pixbuf_width +
207
x = (cell_area.x + cell_area.width
210
- self.apptitle_width
213
y = cell_area.y + ypad + (self.apptitle_height-self.STAR_SIZE)/2
215
sr.rating = stats.ratings_average
216
sr.render_star(context, cr, x, y)
218
# and nr-reviews in parenthesis to the right of the title
219
nreviews = stats.ratings_total
220
s = "(%i)" % nreviews
222
layout.set_markup("<small>%s</small>" % s, -1)
227
x -= xpad+self._layout_get_pixel_width(layout)
230
context.add_class("cellrenderer-avgrating-label")
231
Gtk.render_layout(context, cr, x, y, layout)
235
def _render_progress(self, context, cr, progress, cell_area, ypad, is_rtl):
236
percent = progress * 0.01
237
# per the spec, the progressbar should be the width of the action button
238
action_btn = self.get_button_by_name(CellButtonIDs.ACTION)
240
x, _, w, h = action_btn.allocation
241
# shift the bar to the top edge
242
y = cell_area.y + ypad
245
context.add_class("trough")
247
Gtk.render_background(context, cr, x, y, w, h)
248
Gtk.render_frame(context, cr, x, y, w, h)
252
bar_size = w * percent
255
context.add_class ("progressbar")
260
Gtk.render_activity(context, cr, x, y, bar_size, h)
266
self, context, cr, cell_area, layout, xpad, ypad, is_rtl):
268
# layout buttons and paint
269
y = cell_area.y+cell_area.height-ypad
270
spacing = self.button_spacing
273
start = Gtk.PackType.START
274
end = Gtk.PackType.END
275
xs = cell_area.x + 2 * xpad + self.pixbuf_width
276
xb = cell_area.x + cell_area.width - xpad
278
start = Gtk.PackType.END
279
end = Gtk.PackType.START
280
xs = cell_area.x + xpad
281
xb = cell_area.x + cell_area.width - 2 * xpad - self.pixbuf_width
283
for btn in self._buttons[start]:
284
btn.set_position(xs, y-btn.height)
285
btn.render(context, cr, layout)
286
xs += btn.width + spacing
288
for btn in self._buttons[end]:
290
btn.set_position(xb, y-btn.height)
291
btn.render(context, cr, layout)
296
def set_pixbuf_width(self, w):
297
self.pixbuf_width = w
300
def set_button_spacing(self, spacing):
301
self.button_spacing = spacing
304
def get_button_by_name(self, name):
305
if name in self._all_buttons:
306
return self._all_buttons[name]
309
def get_buttons(self):
311
for k, v in self._buttons.items():
315
def button_pack(self, btn, pack_type=Gtk.PackType.START):
316
self._buttons[pack_type].append(btn)
317
self._all_buttons[btn.name] = btn
320
def button_pack_start(self, btn):
321
self.button_pack(btn, Gtk.PackType.START)
324
def button_pack_end(self, btn):
325
self.button_pack(btn, Gtk.PackType.END)
328
def do_set_property(self, pspec, value):
329
setattr(self, pspec.name, value)
331
def do_get_property(self, pspec):
332
return getattr(self, pspec.name)
334
def do_get_preferred_height_for_width(self, treeview, width):
336
if not self.get_properties("isactive")[0]:
337
return self.normal_height, self.normal_height
339
return self.selected_height, self.selected_height
341
def do_render(self, cr, widget, bg_area, cell_area, flags):
342
app = self.props.application
345
self.model = widget.appmodel
347
context = widget.get_style_context()
348
xpad = self.get_property('xpad')
349
ypad = self.get_property('ypad')
350
star_width, star_height = self._stars.get_visible_size(context)
351
is_rtl = widget.get_direction() == Gtk.TextDirection.RTL
352
layout = self._layout
354
# important! ensures correct text rendering, esp. when using hicolor theme
355
#~ if (flags & Gtk.CellRendererState.SELECTED) != 0:
356
#~ # this follows the behaviour that gtk+ uses for states in treeviews
357
#~ if widget.has_focus():
358
#~ state = Gtk.StateFlags.SELECTED
360
#~ state = Gtk.StateFlags.ACTIVE
362
#~ state = Gtk.StateFlags.NORMAL
365
#~ context.set_state(state)
367
if isinstance(app, CategoryRowReference):
368
self._render_category(context, cr, app,
375
self._render_icon(cr, app,
380
self._render_summary(context, cr, app,
387
# only show ratings if we have one
388
if self.show_ratings:
389
self._render_rating(context, cr, app,
397
progress = self.model.get_transaction_progress(app)
399
self._render_progress(context, cr, progress,
404
elif self.model.is_purchasable(app):
405
self._render_price(context, cr, app, layout,
406
cell_area, xpad, ypad, is_rtl)
408
# below is the stuff that is only done for the active cell
409
if not self.props.isactive:
412
self._render_buttons(context, cr,
422
class CellButtonRenderer(object):
424
def __init__(self, widget, name, use_max_variant_width=True):
425
# use_max_variant_width is currently ignored. assumed to be True
428
self.markup_variants = {}
429
self.current_variant = None
433
self.allocation = [0, 0, 1, 1]
434
self.state = Gtk.StateFlags.NORMAL
435
self.has_focus = False
441
def _layout_reset(self, layout):
443
layout.set_ellipsize(Pango.EllipsizeMode.NONE)
447
def x(self): return self.allocation[0]
450
def y(self): return self.allocation[1]
453
def width(self): return self.allocation[2]
456
def height(self): return self.allocation[3]
458
def configure_geometry(self, layout):
459
self._layout_reset(layout)
462
for k, variant in self.markup_variants.items():
463
safe_markup = GObject.markup_escape_text(utf8(variant))
464
layout.set_markup(safe_markup, -1)
465
size = layout.get_size()
466
max_size = max(max_size, size)
472
self.set_size(w+2*self.xpad, h+2*self.ypad)
475
def point_in(self, px, py):
476
x, y, w, h = self.allocation
477
return (px >= x and px <= x + w and
478
py >= y and py <= y + h)
481
return self.allocation[2:]
483
def set_position(self, x, y):
484
self.allocation[:2] = int(x), int(y)
487
def set_size(self, w, h):
488
self.allocation[2:] = int(w), int(h)
491
def set_state(self, state):
492
if not isinstance(state, Gtk.StateFlags):
493
msg = "state should be of type Gtk.StateFlags, got %s" % type(state)
496
elif state == self.state: return
499
self.widget.queue_draw_area(*self.allocation)
502
def set_sensitive(self, is_sensitive):
504
state = Gtk.StateFlags.PRELIGHT
506
state = Gtk.StateFlags.INSENSITIVE
507
self.set_state(state)
516
def set_markup(self, markup):
517
self.markup_variant = (markup,)
520
def set_markup_variants(self, markup_variants):
521
if not isinstance(markup_variants, dict):
522
msg = type(markup_variants)
523
raise TypeError("Expects a dict object, got %s" % msg)
525
elif not markup_variants: return
527
self.markup_variants = markup_variants
528
self.current_variant = markup_variants.keys()[0]
531
def set_variant(self, current_var):
532
self.current_variant = current_var
535
def is_sensitive(self):
536
return self.state == Gtk.StateFlags.INSENSITIVE
538
def render(self, context, cr, layout):
542
x, y, width, height = self.allocation
545
context.add_class("cellrenderer-button")
548
context.set_state(self.state | Gtk.StateFlags.FOCUSED)
550
context.set_state(self.state)
552
# render background and focal frame if has-focus
554
context.add_class(Gtk.STYLE_CLASS_BUTTON)
555
Gtk.render_background(context, cr, x, y, width, height)
559
Gtk.render_focus(context, cr,
561
width - 6, height - 6)
563
# position and render layout markup
565
context.add_class(Gtk.STYLE_CLASS_BUTTON)
566
layout.set_markup(self.markup_variants[self.current_variant], -1)
567
layout_width = layout.get_pixel_extents()[1].width
568
x = x + (width - layout_width) / 2
570
Gtk.render_layout(context, cr, x, y, layout)