1
'''renderers for the ContactList'''
2
# -*- coding: utf-8 -*-
4
# This file is part of emesene.
6
# emesene is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 3 of the License, or
9
# (at your option) any later version.
11
# emesene is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with emesene; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
from gui.base import Plus
31
log = logging.getLogger('gtkui.Renderers')
33
def replace_markup(markup, arg=None):
34
'''replace the tags defined in gui.base.ContactList'''
35
markup = markup.replace("[$nl]", "\n")
37
markup = markup.replace("[$small]", "<small>")
38
markup = markup.replace("[$/small]", "</small>")
40
if markup.count("[$COLOR=") > 0:
41
hexcolor = color = markup.split("[$COLOR=")[1].split("]")[0]
42
if color.count("#") == 0:
43
hexcolor = "#" + color
45
markup = markup.replace("[$COLOR=" + color + "]", \
46
"<span foreground='" + hexcolor + "'>")
47
markup = markup.replace("[$/COLOR]", "</span>")
49
markup = markup.replace("[$b]", "<b>")
50
markup = markup.replace("[$/b]", "</b>")
52
markup = markup.replace("[$i]", "<i>")
53
markup = markup.replace("[$/i]", "</i>")
57
class CellRendererFunction(gtk.GenericCellRenderer):
59
CellRenderer that behaves like a label, but apply a function to "markup"
60
to show a modified nick. Its a base class, and it is intended to be
61
inherited by extensions.
64
'markup': (gobject.TYPE_STRING,
66
"text we'll display (even with plus markup!)",
68
gobject.PARAM_READWRITE),
69
'ellipsize': (gobject.TYPE_BOOLEAN,
73
gobject.PARAM_READWRITE)
76
property_names = __gproperties__.keys()
78
def __init__(self, function):
79
self.__gobject_init__()
80
gtk.GenericCellRenderer.__init__(self)
81
self.__dict__['markup'] = ''
82
self.function = function
83
self._style_handler_id = None
84
self._selected_flgs = (int(gtk.CELL_RENDERER_SELECTED),
85
int(gtk.CELL_RENDERER_SELECTED) + int(gtk.CELL_RENDERER_PRELIT))
87
self._cached_markup = None
88
self._cached_layout = None
90
def __getattr__(self, name):
92
return self.get_property(name)
94
raise AttributeError, name
96
def __setattr__(self, name, value):
98
self.set_property(name, value)
100
self.__dict__[name] = value
102
def on_get_size(self, widget, cell_area):
103
'''Returns the size of the cellrenderer'''
104
if not self._style_handler_id:
105
self._style_handler_id = widget.connect('style-set',
108
layout = self.get_layout(widget)
110
width, height = layout.get_pixel_size()
112
width, height = [0,0]
113
return (0, 0, -1, height + (self.ypad * 2))
115
def do_get_property(self, prop):
116
'''return the value of prop if exists, raise TypeError if not found'''
117
if prop.name not in self.property_names:
118
raise TypeError('No property named %s' % (prop.name,))
120
return self.__dict__[prop.name]
122
def do_set_property(self, prop, value):
123
'''set the value of prop if exists, raise TypeError if not found'''
124
if prop.name not in self.property_names:
125
raise TypeError('No property named %s' % (prop.name,))
127
if prop == 'markup': #plus formatting
128
value = Plus.msnplus_to_dict
130
self.__dict__[prop.name] = value
132
def on_render(self, win, widget, bgnd_area, cell_area, expose_area, flags):
133
'''Called by gtk to render the cell.'''
134
x_coord, y_coord, width, height = cell_area
138
ctx = win.cairo_create()
139
layout = self.get_layout(widget)
141
layout.set_width(width * pango.SCALE)
142
layout.set_in_color_override_mode(flags in self._selected_flgs)
143
layout.draw(ctx, (x_coord, y_coord, width, height))
145
def get_layout(self, widget):
146
'''Gets the Pango layout used in the cell in a TreeView widget.'''
147
layout = SmileyLayout(widget.create_pango_context())
151
decorated_markup = self.function(self.markup)
152
except Exception, error:
153
log.error("this nick: '%s' made the parser go crazy, striping. Error: %s" % (
156
decorated_markup = Plus.msnplus_strip(self.markup)
158
layout.set_text(decorated_markup)
161
def _style_set(self, widget, previous_style):
162
'''callback to the style-set signal of widget'''
163
self._cached_markup = {}
164
self._cached_layout = {}
165
widget.queue_resize()
167
################################################################################
168
# emesene1 parsers rock the streets, here they're instanciated and used
169
# if you could fix the emesene2 one, you would be very welcome.
170
################################################################################
172
bigparser = Parser.UnifiedParser()
173
mohrtutchy_plus_parser = Plus.MsnPlusMarkupMohrtutchy()
174
plus_or_noplus = 1 # 1 means plus, 0 means noplus
176
def plus_parse(obj, parser, filterdata):
177
global plus_or_noplus
178
# get a plain string with objects
179
format, objects = filterdata.serialize(filterdata.list)
181
if parser and parser.tags != Parser.TAGS_NONE:
183
mohrtutchy_plus_parser.isHtml = False
184
if parser.tags == Parser.TAGS_PANGO and plus_or_noplus:
185
# replace msn plus markup with pango
186
format = mohrtutchy_plus_parser.replaceMarkup(format)
188
# remove all msn plus markup
189
format = mohrtutchy_plus_parser.removeMarkup(format)
191
# put back the objects
192
filterdata.list = filterdata.deserialize(format, objects)
194
format = mohrtutchy_plus_parser.removeMarkup(format)
195
filterdata.list = filterdata.deserialize(format, objects)
197
bigparser.connect('filter', plus_parse)
199
def msnplus_to_list(txt, do_parse_emotes=True):
200
'''parte text to a DictObj and return a list of strings and
203
########################################
204
# Mohrtutchy hax, it works (not sure how)
205
parser = bigparser.getParser(Parser.unescape(txt), Parser.PangoDataType)
206
parsed_stuff = parser.get(smileys=True)
209
for item in parsed_stuff:
210
if type(item) is Parser.Smiley:
211
list_stuff.append(item.pixbuf)
213
list_stuff.append(replace_markup(item))
216
########################################
217
# boyska's implementation, quite incomplete.
218
dct = Plus.msnplus(txt, do_parse_emotes)
220
if not do_parse_emotes:
223
items = flatten_tree(dct, [], [])
228
if type(item) in (str, unicode):
231
text = replace_markup("".join(temp))
236
accum.append(replace_markup("".join(temp)))
240
def msnplus_to_plain_text(txt):
241
''' from a nasty string, returns a nice plain text string without
242
bells and whistles, just text '''
243
return bigparser.getParser(txt).get(escaped=False)
245
def flatten_tree(dct, accum, parents):
246
'''convert the tree of markup into a list of string that contain pango
247
markup and pixbufs, if an img tag is found all the parent tags should be
248
closed before the pixbuf and reopened after.
252
["<b>hi! </b>", pixbuf, "<b> lol</b>"]
255
attrs = " ".join("%s=\"%s\"" % (attr, value) for attr, value in
256
tag.iteritems() if attr not in ['tag', 'childs'] and value)
259
return '<%s %s>' % (tag.tag, attrs)
261
return '<%s>' % (tag.tag, )
267
while i>=0 and type(accum[i]) != gtk.gdk.Pixbuf:
268
previous.append(accum[i])
277
for prev in previous:
278
pos = prev.find("[$")
294
prev = prev[index+1:]
295
pos = prev.find("[$")
299
closed = closed+"[$/"+tag+"]"
300
opened = "[$"+tag+"]"+opened
302
closed = closed+"".join("</%s>" % (parent.tag, ) for parent in \
303
parents[::-1] if parent)
305
opened = "".join(open_tag(parent) for parent in \
306
parents if parent)+opened
308
accum += [closed, gtk.gdk.pixbuf_new_from_file(dct.src), opened]
312
accum += [open_tag(dct)]
314
for child in dct.childs:
315
if type(child) in (str, unicode):
318
flatten_tree(child, accum, parents + [dct])
320
flatten_tree(child, accum, parents)
323
accum += ['</%s>' % dct.tag]
327
class CellRendererPlus(CellRendererFunction):
328
'''Nick renderer that parse the MSN+ markup, showing colors, gradients and
332
global plus_or_noplus
334
CellRendererFunction.__init__(self, msnplus_to_list)
336
extension.implements(CellRendererPlus, 'nick renderer')
338
gobject.type_register(CellRendererPlus)
340
class CellRendererNoPlus(CellRendererFunction):
341
'''Nick renderer that "strip" MSN+ markup, not showing any effect/color,
342
but improving the readability'''
345
global plus_or_noplus
347
CellRendererFunction.__init__(self, msnplus_to_list)
349
extension.implements(CellRendererNoPlus, 'nick renderer')
351
gobject.type_register(CellRendererNoPlus)
353
class SmileyLayout(pango.Layout):
354
'''a pango layout to draw smilies'''
356
def __init__(self, context,
357
parsed_elements_list = None,
359
override_color = None,
361
pango.Layout.__init__(self, context)
364
self._ellipsize = True
365
self._elayout = pango.Layout(context)
366
self._elayout.set_text('___') #…
367
self._elayout.set_ellipsize(pango.ELLIPSIZE_END)
368
self._elayout.set_width(0)
369
self._in_override = False # color override mode, used for selected text
370
self._base_to_center = 0
371
self._text_height = 0
372
self._smilies = {} # key: (index_pos), value(pixbuf)
373
self._base_attrlist = None # no color
374
self._attrlist = None # with color
375
self._override_attrlist = None # with override color
378
self._color = gtk.gdk.Color()
382
if override_color is None:
383
self._override_color = gtk.gdk.Color()
385
self._override_color = override_color
387
self._smilies_scaled = {} # key: (index_pos), value(pixbuf)
388
self._scaling = scaling # relative to ascent + desent, -1 for natural
391
self.set_element_list(parsed_elements_list)
392
self._update_layout()
394
def set_element_list(self, parsed_elements_list=None):
395
''' Sets Layout Text based on parsed elements '''
396
if parsed_elements_list is None:
397
parsed_elements_list = ['']
399
self._update_base(parsed_elements_list)
401
def set_text(self, text):
402
''' Sets Layout Text '''
403
self.set_element_list(text)
405
def set_markup(self, markup):
406
''' Same as set_text() '''
407
self.set_element_list(markup)
409
def set_width(self, width):
410
''' Set width of layout in pixels, -1 for natural width '''
412
self._update_layout()
415
'''return thw width of layout in pixels, -1 for natural width'''
418
def set_ellipsize(self, value):
419
''' Turns Ellipsize ON/OFF '''
420
if value == pango.ELLIPSIZE_END:
421
self._ellipsize = True
423
self._ellipsize = False
425
self._update_layout()
427
def set_smiley_scaling(self, smiley_scaling):
429
Set smiley scalling relative to ascent + desent,
432
self._scaling = smiley_scaling
433
self._update_smilies()
435
def set_in_color_override_mode(self, in_override):
436
if not in_override == self._in_override:
437
self._in_override = in_override
438
self._update_attributes()
440
def set_colors(self, color=None, override_color=None):
442
color = gtk.gdk.Color()
444
if override_color is None:
445
override_color = gtk.gdk.Color()
448
self._override_color = override_color
449
self._update_attrlists()
451
def _update_base(self, elements_list=None):
453
if elements_list is None:
457
self._base_attrlist = pango.AttrList()
460
if type(elements_list) in (str, unicode):
461
elements_list = [elements_list]
463
for element in elements_list:
464
if type(element) in (str, unicode):
466
attrl, ptxt, unused = pango.parse_markup(element, u'\x00')
468
attrl, ptxt = pango.AttrList(), element
470
#append attribute list
472
itter = attrl.get_iterator()
475
attrs = itter.get_attrs()
478
attr.end_index += shift
479
attr.start_index += shift
480
self._base_attrlist.insert(attr)
486
elif type(element) == gtk.gdk.Pixbuf:
487
self._smilies[len(text)] = element
490
pango.Layout.set_text(self, text)
492
if hasattr(pango, 'find_base_dir'):
493
for line in text.splitlines():
494
if (pango.find_base_dir(line, -1) == pango.DIRECTION_RTL):
500
logical = self.get_line(0).get_pixel_extents()[1]
501
ascent = pango.ASCENT(logical)
502
decent = pango.DESCENT(logical)
503
self._text_height = ascent + decent
504
self._base_to_center = (self._text_height / 2) - decent
505
self._update_smilies()
507
def _update_smilies(self):
508
self._base_attrlist.filter(lambda attr: attr.type == pango.ATTR_SHAPE)
509
self._smilies_scaled = {}
511
#set max height of a pixbuf
512
if self._scaling >= 0:
513
max_height = self._text_height * self._scaling
515
max_height = sys.maxint
517
for index, pixbuf in self._smilies.iteritems():
520
height, width = pixbuf.get_height(), pixbuf.get_width()
523
if height > max_height:
524
cairo_scale = float(max_height) / float(height)
525
height = int(height * cairo_scale)
526
width = int(width * cairo_scale)
527
npix = npix.scale_simple(width, height,
528
gtk.gdk.INTERP_BILINEAR)
530
self._smilies_scaled[index] = npix
532
-1 * (self._base_to_center + (height /2)) * pango.SCALE,
533
width * pango.SCALE, height * pango.SCALE)
535
self._base_attrlist.insert(pango.AttrShape((0, 0, 0, 0),
536
rect, index, index + 1))
538
self._update_attrlists()
540
def _update_attrlists(self):
542
oclr = self._override_color
543
norm_forground = pango.AttrForeground( clr.red,
544
clr.green, clr.blue, 0, len(self.get_text()))
545
override_forground = pango.AttrForeground( oclr.red,
546
oclr.green, oclr.blue, 0, len(self.get_text()))
547
self._attrlist = pango.AttrList()
548
self._attrlist.insert(norm_forground)
549
self._override_attrlist = pango.AttrList()
550
self._override_attrlist.insert(override_forground)
551
itter = self._base_attrlist.get_iterator()
554
attrs = itter.get_attrs()
557
self._attrlist.insert(attr.copy())
559
if not (attr.type in (pango.ATTR_FOREGROUND,
560
pango.ATTR_BACKGROUND)):
561
self._override_attrlist.insert(attr.copy())
566
self._update_attributes()
568
def _update_attributes(self):
569
if self._in_override:
570
self.set_attributes(self._override_attrlist)
572
self.set_attributes(self._attrlist)
574
def _update_layout(self):
575
if self._width >= 0 and self._ellipsize == False: # if true, then wrap
576
pango.Layout.set_width(self, self._width)
578
pango.Layout.set_width(self, -1)
581
natural_width, natural_height = pango.Layout.get_size(self)
583
if self._width >= 0 and self._ellipsize : # if ellipsize
584
return self._width, natural_height
586
return natural_width, natural_height
588
def get_pixel_size(self):
589
natural_width, natural_height = pango.Layout.get_pixel_size(self)
591
if self._width >= 0 and self._ellipsize : # if ellipsize
592
return pango.PIXELS(self._width), natural_height
594
return natural_width, natural_height
596
def draw(self, ctx, area):
597
x, y, width, height = area
599
ctx.rectangle(x, y , width, height)
603
layout_width = pango.Layout.get_pixel_size(self)[0]
604
ctx.translate(x + width - layout_width, y)
608
#Clipping and ellipsation
611
X, Y, W, H = 0, 1, 2, 3
612
layout_width = self._width
613
lst = self.get_attributes()
614
e_ascent = pango.ASCENT(
615
self._elayout.get_line(0).get_pixel_extents()[1])
616
coords = [] # of path in px
618
for i in range(self.get_line_count()):
619
line = self.get_line(i)
620
edge = line.x_to_index(layout_width)
623
#create ellipsize layout with the style of the char
624
attrlist = pango.AttrList()
625
itter = lst.get_iterator()
628
attrs = itter.get_attrs()
631
if not attr.type == pango.ATTR_SHAPE:
632
start, end = itter.range()
634
if start <= edge[byte] < end:
636
n_attr.start_index = 0
638
attrlist.insert(n_attr)
643
self._elayout.set_attributes(attrlist)
644
ellipsize_width = self._elayout.get_size()[0]
645
edge = line.x_to_index(layout_width - ellipsize_width)
646
char = self.index_to_pos(edge[byte])
647
char_x, char_y, char_h = (pxls(char[X]), pxls(char[Y]),
649
y1, y2 = char_y, char_y + char_h
656
coords.append((x1, y1))
657
coords.append((x1, y2))
658
line_ascent = pango.ASCENT(line.get_pixel_extents()[1])
659
ctx.move_to(x1, y1 + line_ascent - e_ascent)
660
ctx.show_layout(self._elayout)
662
char = self.index_to_pos(edge[byte])
663
coords.append((pxls(char[X] + char[W]), pxls(char[Y])))
664
coords.append((pxls(char[X] + char[W]),
665
pxls(char[Y] + char[H])))
672
ctx.line_to(0, coords[-1][1])
678
ctx.show_layout(self)
681
for index in self._smilies.keys():
683
x, y, width, height = self.index_to_pos(index)
684
pixbuf = self._smilies_scaled[index]
686
ty = pxls(y) + (pxls(height)/2) - (pixbuf.get_height()/2)
687
ctx.set_source_pixbuf(pixbuf, tx, ty)
689
except Exception, error:
690
log.error("Error when painting smilies: %s" % error)
692
class SmileyLabel(gtk.Label):
693
'''Label with smiley support. '''
695
__gsignals__ = { 'size_request' : 'override',
696
'size-allocate' : 'override',
697
'expose-event' : 'override'}
700
gtk.Widget.__init__(self)
702
self._ellipsize = True
704
self._smiley_layout = None
705
self.set_flags(self.flags() | gtk.NO_WINDOW)
706
self._smiley_layout = SmileyLayout(self.create_pango_context())
708
def set_ellipsize(self, ellipsize):
709
''' Sets the ellipsize behavior '''
710
self._ellipsize = ellipsize
713
def set_wrap(self, wrap):
714
''' Sets the wrap behavior '''
718
def set_markup(self, text=['']):
721
def set_text(self, text=['']):
722
''' Sets widget text '''
724
self.setup_smiley_layout()
727
def set_smiley_scaling(self, smiley_scaling):
728
self._smiley_layout.set_smiley_scaling(smiley_scaling)
731
def setup_smiley_layout(self):
732
self._smiley_layout.set_element_list(self._text)
734
def do_realize(self):
735
gtk.Widget.do_realize(self)
736
self.set_flags(self.flags() | gtk.REALIZED)
737
self.window = self.get_parent().window
739
def do_style_set(self, prev_style):
740
self._smiley_layout.set_colors(self.style.text[gtk.STATE_NORMAL])
743
def do_size_request(self, requisition):
744
self._smiley_layout.set_width(-1)
745
width, height = self._smiley_layout.get_pixel_size()
746
requisition.height = height
748
if self._ellipsize or self._wrap:
749
requisition.width = 0
751
requisition.width = width
753
def do_size_allocate(self, allocation):
754
if not (self._ellipsize or self._wrap):
755
self._smiley_layout.set_width(-1)
756
width, height = self._smiley_layout.get_pixel_size()
757
self.set_size_request(width, height)
760
self._smiley_layout.set_ellipsize(pango.ELLIPSIZE_END)
762
self._smiley_layout.set_ellipsize(pango.ELLIPSIZE_NONE)
764
self._smiley_layout.set_width(allocation.width * pango.SCALE)
765
self.set_size_request(-1, self._smiley_layout.get_pixel_size()[1])
767
gtk.Widget.do_size_allocate(self, allocation)
769
def do_expose_event(self, event):
770
area = self.get_allocation()
771
ctx = event.window.cairo_create()
772
self._smiley_layout.draw(ctx, area)
774
gobject.type_register(SmileyLabel)
776
#from emesene1 by mariano guerra adapted by cando
777
#animation support by cando
778
#TODO add transformation field in configuration
779
#TODO signals in configuration for transformation changes????
781
class AvatarRenderer(gtk.GenericCellRenderer):
782
"""Renderer for avatar """
785
'image': (gobject.TYPE_OBJECT, 'The contact image', '', gobject.PARAM_READWRITE),
786
'blocked': (bool, 'Contact Blocked', '', False, gobject.PARAM_READWRITE),
787
'dimention': (gobject.TYPE_INT, 'cell dimentions',
788
'height width of cell', 0, 96, 32, gobject.PARAM_READWRITE),
789
'offline': (bool, 'Contact is offline', '', False, gobject.PARAM_READWRITE),
790
'radius_factor': (gobject.TYPE_FLOAT,'radius of pixbuf',
791
'0.0 to 0.5 with 0.1 = 10% of dimention',
792
0.0, 0.5,0.11, gobject.PARAM_READWRITE),
795
def __init__(self, cellDimention = 32, cellRadius = 0.11):
796
self.__gobject_init__()
798
self._dimention = cellDimention
799
self._radius_factor = cellRadius
800
self._offline = False
802
#icon source used to render grayed out offline avatar
803
self._icon_source = gtk.IconSource()
804
self._icon_source.set_state(gtk.STATE_INSENSITIVE)
806
self.set_property('xpad', 1)
807
self.set_property('ypad', 1)
809
#set up information of statusTransformation
810
self._set_transformation('corner|gray')
811
#self.transId = self._config.connect('change::statusTransformation',
812
#self._transformation_callback)
815
self._config.disconnect(self.transId)
816
gtk.GenericCellRenderer.destroy(self)
818
def _get_padding(self):
819
return (self.get_property('xpad'), self.get_property('ypad'))
821
def _set_transformation(self, setting):
822
transformation = setting.split('|')
823
self._corner = ('corner' in transformation)
824
self._alpha_status = ('alpha' in transformation)
825
self._gray = ('gray' in transformation)
827
def _transformation_callback(self, config, newvalue, oldvalue):
828
self._set_transformation(newvalue)
830
def do_get_property(self, property):
831
if property.name == 'image':
833
elif property.name == 'dimention':
834
return self._dimention
835
elif property.name == 'radius-factor':
836
return self._radius_factor
837
elif property.name == 'offline':
840
raise AttributeError, 'unknown property %s' % property.name
842
def do_set_property(self, property, value):
843
if property.name == 'image':
845
elif property.name == 'dimention':
846
self._dimention = value
847
elif property.name == 'radius-factor':
848
self._radius_factor = value
849
elif property.name == 'offline':
850
self._offline = value
852
raise AttributeError, 'unknown property %s' % property.name
854
def on_get_size(self, widget, cell_area=None):
855
"""Requisition size"""
856
xpad, ypad = self._get_padding()
858
if self._dimention >= 32:
859
width = self._dimention
861
width = self._dimention * 2
863
width = self._dimention
865
height = self._dimention + (ypad * 2)
867
return (0, 0, width, height)
869
def func(self, model, path, iter, (image, tree)):
870
if model.get_value(iter, 0) == image:
872
cell_area = tree.get_cell_area(path, tree.get_column(1))
873
tree.queue_draw_area(cell_area.x, cell_area.y, cell_area.width,
876
def animation_timeout(self, tree, image):
877
if image.get_storage_type() == gtk.IMAGE_ANIMATION:
879
image.get_data('iter').advance()
880
model = tree.get_model()
881
model.foreach(self.func, (image, tree))
884
gobject.timeout_add(image.get_data('iter').get_delay_time(),
885
self.animation_timeout, tree, image)
887
image.set_data('iter', None)
890
def on_render(self, window, widget, bg_area, cell_area, expose_area, flags):
891
"""Prepare rendering setting for avatar"""
892
xpad, ypad = self._get_padding()
893
x, y, width, height = cell_area
894
ctx = window.cairo_create()
899
dim = self._dimention
901
if self._image.get_storage_type() == gtk.IMAGE_ANIMATION:
902
if not self._image.get_data('iter'):
903
animation = self._image.get_animation()
904
self._image.set_data('iter', animation.get_iter())
905
gobject.timeout_add(self._image.get_data('iter').get_delay_time(),
906
self.animation_timeout, widget, self._image)
908
avatar = self._image.get_data('iter').get_pixbuf()
909
elif self._image.get_storage_type() == gtk.IMAGE_PIXBUF:
910
avatar = self._image.get_pixbuf()
914
if self._gray and self._offline and avatar != None:
916
source = self._icon_source
917
source.set_pixbuf(avatar)
918
direction = widget.get_direction()
919
avatar = widget.style.render_icon(source, direction,
920
gtk.STATE_INSENSITIVE, -1, widget, "gtk-image")
923
self._draw_avatar(ctx, avatar, width - dim, ypad, dim,
924
gtk.ANCHOR_CENTER, self._radius_factor, alpha)
926
def _draw_avatar(self, ctx, pixbuf, x, y, dimention,
927
position = gtk.ANCHOR_CENTER,
928
radius = 0, alpha = 1):
931
ctx.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
934
pix_width = pixbuf.get_width()
935
pix_height = pixbuf.get_height()
937
if (pix_width > dimention) or (pix_height > dimention):
938
scale_factor = float(dimention) / max (pix_width,pix_height)
942
scale_width = pix_width* scale_factor
943
scale_height = pix_height* scale_factor
946
if position in (gtk.ANCHOR_NW, gtk.ANCHOR_W, gtk.ANCHOR_SW):
948
elif position in (gtk.ANCHOR_N, gtk.ANCHOR_CENTER, gtk.ANCHOR_S):
949
x = (dimention/2) - (scale_width/2)
951
x = dimention - scale_width
952
if position in (gtk.ANCHOR_NW, gtk.ANCHOR_N, gtk.ANCHOR_NE):
954
elif position in (gtk.ANCHOR_E, gtk.ANCHOR_CENTER, gtk.ANCHOR_W):
955
y = (dimention/2) - (scale_height/2)
957
y = dimention - scale_height
962
self._rounded_rectangle(ctx, 0, 0, scale_width, scale_height,
963
self._dimention * radius)
966
ctx.scale(scale_factor,scale_factor)
967
ctx.set_source_pixbuf(pixbuf, 0, 0)
968
ctx.paint_with_alpha(alpha)
971
def _rounded_rectangle(self, cr, x, y, w, h, radius=5):
972
"""Create rounded rectangle path"""
973
# http://cairographics.org/cookbook/roundedrectangles/
974
# modified from mono moonlight aka mono silverlight
975
# test limits (without using multiplications)
976
# http://graphics.stanford.edu/courses/cs248-98-fall/Final/q1.html
978
ARC_TO_BEZIER = 0.55228475
980
if radius > (min(w,h)/2):
981
radius = (min(w,h)/2)
983
#approximate (quite close) the arc using a bezier curve
984
c = ARC_TO_BEZIER * radius
987
cr.move_to ( x + radius, y)
988
cr.rel_line_to ( w - 2 * radius, 0.0)
989
cr.rel_curve_to ( c, 0.0, radius, c, radius, radius)
990
cr.rel_line_to ( 0, h - 2 * radius)
991
cr.rel_curve_to ( 0.0, c, c - radius, radius, -radius, radius)
992
cr.rel_line_to ( -w + 2 * radius, 0)
993
cr.rel_curve_to ( -c, 0, -radius, -c, -radius, -radius)
994
cr.rel_line_to (0, -h + 2 * radius)
995
cr.rel_curve_to (0.0, -c, radius - c, -radius, radius, -radius)
998
gobject.type_register(AvatarRenderer)