# -*- coding: utf-8 -*-
# This file is part of emesene.
#
# Emesene is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# emesene is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with emesene; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
import gtk
import pango
import cairo
import gtk.gdk
import gobject
from gobject import PARAM_READWRITE,TYPE_PYOBJECT
from Parser import Smiley
from emesenelib.ContactData import Contact,Group
class SmileyRenderer(gtk.GenericCellRenderer):
'''Cellrenderer with smiley support. '''
__gproperties__ = {
'markup': (TYPE_PYOBJECT, 'Smiley markup',
'A list with strs, Smileys, and \ns', PARAM_READWRITE),
'obj': (TYPE_PYOBJECT, 'Smiley markup',
'A list with strs, Smileys, and \ns', PARAM_READWRITE),
'ellipsize': (bool, 'Ellipsize True/False',
'',False, gobject.PARAM_READWRITE),
'wrap': (bool, 'Wrap True/False', '',False, gobject.PARAM_READWRITE),
}
def __init__(self, cache = {}):
self.__gobject_init__()
self._markup = ['']
self._ellipsize = True
self._wrap = True
self._cached_markup = {}
self._cached_layout = {}
self._style_handler_id = None
self.xpad = 2
self.ypad = 1
self._selected_flgs = (int(gtk.CELL_RENDERER_SELECTED), \
int(gtk.CELL_RENDERER_SELECTED) + int(gtk.CELL_RENDERER_PRELIT))
def do_get_property(self, prop):
return getattr(self, prop.name)
def do_set_property(self, prop, value):
setattr(self, prop.name, value)
def on_render(self, win, widget, bgnd_area, cell_area, expose_area, flags):
'''Called by gtk to render the cell.'''
x, y, width, height = cell_area
x += self.xpad
y += self.ypad
width -= self.xpad
ctx = win.cairo_create()
layout = self._get_layout(widget)
layout.set_width(width * pango.SCALE)
layout.set_in_color_override_mode(flags in self._selected_flgs)
layout.draw(ctx, (x, y, width, height))
def on_get_size(self, widget, cell_area):
'''Returns the size of the cellrenderer'''
if not self._style_handler_id:
self._style_handler_id = widget.connect('style-set', self._style_set)
layout = self._get_layout(widget)
width, height = layout.get_pixel_size()
return (0,0, -1, height + (self.ypad * 2))
def _get_layout(self, widget):
if type(self.obj) == Contact:
obj_id = self.obj.email
elif type(self.obj) == Group:
obj_id = self.obj.id
else:
obj_id = ''
if obj_id in self._cached_markup and \
self._cached_markup[obj_id] == self.markup:
layout = self._cached_layout[obj_id]
else:
layout = SmileyLayout(widget.create_pango_context(),
self.markup,
widget.style.text[gtk.STATE_NORMAL],
widget.style.text[gtk.STATE_SELECTED])
layout.set_ellipsize(pango.ELLIPSIZE_END)
self._cached_markup[obj_id] = self.markup
self._cached_layout[obj_id] = layout
return layout
def _style_set(self, widget, previous_style):
self._cached_markup = {}
self._cached_layout = {}
widget.queue_resize()
class SmileyLabel(gtk.Widget):
'''Label with smiley support. '''
__gsignals__ = { 'size_request' : 'override',
'size-allocate' : 'override',
'expose-event' : 'override'}
def __init__(self, smiley_theme, style):
gtk.Widget.__init__(self)
self._text = ['']
self._ellipsize = True
self._wrap = True
self._smiley_layout = None
self.set_flags(self.flags() | gtk.NO_WINDOW)
self._smiley_layout = SmileyLayout(self.create_pango_context())
def set_ellipsize(self, ellipsize):
''' Sets the ellipsize behavior '''
self._ellipsize = ellipsize
self.queue_resize()
def set_wrap(self, wrap):
''' Sets the wrap behavior '''
self._wrap = wrap
self.queue_resize()
def set_markup(self, text=['']):
self.set_text(text)
def set_text(self, text=['']):
''' Sets widget text '''
self._text = text
self.setup_smiley_layout()
self.queue_resize()
def set_smiley_scaling(self, smiley_scaling):
self._smiley_layout.set_smiley_scaling(smiley_scaling)
self.queue_resize()
def setup_smiley_layout(self):
self._smiley_layout.set_element_list(self._text)
def do_realize(self):
gtk.Widget.do_realize(self)
self.set_flags(self.flags() | gtk.REALIZED)
self.window = self.get_parent().window
def do_style_set(self, prev_style):
self._smiley_layout.set_colors(self.style.text[gtk.STATE_NORMAL])
self.queue_draw()
def do_size_request(self, requisition):
self._smiley_layout.set_width(-1)
width, height = self._smiley_layout.get_pixel_size()
requisition.height = height
if self._ellipsize or self._wrap:
requisition.width = 0
else:
requisition.width = width
def do_size_allocate(self, allocation):
if not (self._ellipsize or self._wrap):
self._smiley_layout.set_width(-1)
width, height = self._smiley_layout.get_pixel_size()
self.set_size_request(width, height)
else:
if self._ellipsize:
self._smiley_layout.set_ellipsize(pango.ELLIPSIZE_END)
else:
self._smiley_layout.set_ellipsize(pango.ELLIPSIZE_NONE)
self._smiley_layout.set_width(allocation.width * pango.SCALE)
self.set_size_request(-1, self._smiley_layout.get_pixel_size()[1])
gtk.Widget.do_size_allocate(self, allocation)
def do_expose_event(self, event):
area = self.get_allocation()
ctx = event.window.cairo_create()
self._smiley_layout.draw(ctx, area)
gobject.type_register(SmileyLabel)
class SmileyLayout(pango.Layout):
def __init__(self, context,
parsed_elements_list = [''],
color = gtk.gdk.Color(),
override_color = gtk.gdk.Color(),
scaling=1.0):
pango.Layout.__init__(self, context)
self._width = -1
self._ellipsize = True
self._elayout = pango.Layout(context)
self._elayout.set_text('___') #
self._elayout.set_ellipsize(pango.ELLIPSIZE_END)
self._elayout.set_width(0)
self._in_override = False # color override mode, used for selected text
self._base_to_center = 0
self._text_height = 0
self._smilies = {} # key: (index_pos), value(pixbuf)
self._base_attrlist = None # no color
self._attrlist = None # with color
self._override_attrlist = None # with override color
self._color = color
self._override_color = override_color
self._smilies_scaled = {} # key: (index_pos), value(pixbuf)
self._scaling = scaling # relative to ascent + desent, -1 for natural
self._is_rtl = False
self.set_element_list(parsed_elements_list)
self._update_layout()
def set_element_list(self, parsed_elements_list=['']):
''' Sets Layout Text based on parsed elements '''
self._update_base(parsed_elements_list)
def set_text(self, text):
''' Sets Layout Text '''
self.set_element_list(text)
def set_markup(self, markup):
''' Same as set_text() '''
self.set_element_list(text)
def set_width(self, width):
''' Set width of layout in pixels, -1 for natural width '''
self._width = width
self._update_layout()
def get_width(self):
return self._width
def set_ellipsize(self, value):
''' Turns Ellipsize ON/OFF '''
if value == pango.ELLIPSIZE_END:
self._ellipsize = True
else:
self._ellipsize = False
self._update_layout()
def set_smiley_scaling(self, smiley_scaling):
'''
Set smiley scalling relative to ascent + desent,
-1 for natural size
'''
self._scaling = smiley_scaling
self._update_smilies()
def set_in_color_override_mode(self, in_override):
if not in_override == self._in_override:
self._in_override = in_override
self._update_attributes()
def set_colors(self, color = gtk.gdk.Color(),
override_color = gtk.gdk.Color()):
self._color = color
self._override_color = override_color
self._update_attrlists()
def _update_base(self, elements_list=['']):
self._smilies = {}
self._base_attrlist = pango.AttrList()
text = ''
if type(elements_list) in (str, unicode):
elements_list = [elements_list]
for element in elements_list:
if type(element) in (str, unicode):
try:
attrl, ptxt, ac = pango.parse_markup(str(element), u'\x00')
except:
attrl, ptxt = pango.AttrList(), str(element)
#append attribute list
shift = len(text)
itter = attrl.get_iterator()
while True:
attrs = itter.get_attrs()
for attr in attrs:
attr.end_index += shift
attr.start_index += shift
self._base_attrlist.insert(attr)
if not itter.next(): break
text += ptxt
elif type(element) == Smiley:
self._smilies[len(text)] = element.getPixbuf(animated=False)
text += '_'
pango.Layout.set_text(self, text)
if hasattr(pango, 'find_base_dir'):
for line in text.splitlines():
if (pango.find_base_dir(line,-1) == pango.DIRECTION_RTL):
self._is_rtl = True
break
else:
self._is_rtl = False
logical = self.get_line(0).get_pixel_extents()[1]
ascent = pango.ASCENT(logical)
decent = pango.DESCENT(logical)
self._text_height = ascent + decent
self._base_to_center = (self._text_height / 2) - decent
self._update_smilies()
def _update_smilies(self):
self._base_attrlist.filter(lambda attr: attr.type == pango.ATTR_SHAPE)
self._smilies_scaled = {}
#set max height of a pixbuf
if self._scaling >= 0:
max_height = self._text_height * self._scaling
else:
max_height = sys.maxint
for index, pixbuf in self._smilies.iteritems():
if pixbuf:
height, width = pixbuf.get_height(), pixbuf.get_width()
npix = pixbuf.copy()
if height > max_height:
cairo_scale = float(max_height) / float(height)
height = int(height * cairo_scale)
width = int(width * cairo_scale)
npix = npix.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
self._smilies_scaled[index] = npix
rect = (0, -1 * (self._base_to_center + (height /2)) * pango.SCALE,\
width * pango.SCALE, height * pango.SCALE)
self._base_attrlist.insert(pango.AttrShape((0,0,0,0), rect, index, index + 1))
self._update_attrlists()
def _update_attrlists(self):
clr = self._color
oclr = self._override_color
norm_forground = pango.AttrForeground( clr.red,
clr.green, clr.blue, 0, len(self.get_text()))
override_forground = pango.AttrForeground( oclr.red,
oclr.green, oclr.blue, 0, len(self.get_text()))
self._attrlist = pango.AttrList()
self._attrlist.insert(norm_forground)
self._override_attrlist = pango.AttrList()
self._override_attrlist.insert(override_forground)
itter = self._base_attrlist.get_iterator()
while True:
attrs = itter.get_attrs()
for attr in attrs:
self._attrlist.insert(attr.copy())
if not (attr.type in (pango.ATTR_FOREGROUND, pango.ATTR_BACKGROUND)):
self._override_attrlist.insert(attr.copy())
if not itter.next(): break
self._update_attributes()
def _update_attributes(self):
if self._in_override:
self.set_attributes(self._override_attrlist)
else:
self.set_attributes(self._attrlist)
def _update_layout(self):
if self._width >= 0 and self._ellipsize == False: # if true, then wrap
pango.Layout.set_width(self, self._width)
else:
pango.Layout.set_width(self, -1)
def get_size(self):
natural_width, natural_height = pango.Layout.get_size(self)
if self._width >= 0 and self._ellipsize : # if ellipsize
return self._width, natural_height
else:
return natural_width, natural_height
def get_pixel_size(self):
natural_width, natural_height = pango.Layout.get_pixel_size(self)
if self._width >= 0 and self._ellipsize : # if ellipsize
return pango.PIXELS(self._width), natural_height
else:
return natural_width, natural_height
def draw(self, ctx, area):
x, y, width, height = area
pxls = pango.PIXELS
ctx.rectangle(x, y , width, height)
ctx.clip()
if self._is_rtl:
layout_width = pango.Layout.get_pixel_size(self)[0]
ctx.translate(x + width - layout_width, y)
else:
ctx.translate(x,y)
#Clipping and ellipsation
if self._width >= 0:
INLINE, BYTE, GRAPH = 0, 1, 2
X, Y, W, H = 0, 1, 2, 3
layout_width = self._width
lst = self.get_attributes()
e_ascent = pango.ASCENT(self._elayout.get_line(0).get_pixel_extents()[1])
coords = [] # of path in px
for i in range(self.get_line_count()):
line = self.get_line(i)
edge = line.x_to_index(layout_width)
if edge[INLINE]:
#create ellipsize layout with the style of the char
attrlist = pango.AttrList()
itter = lst.get_iterator()
while True:
attrs = itter.get_attrs()
for attr in attrs:
if not attr.type == pango.ATTR_SHAPE:
start, end = itter.range()
if start <= edge[BYTE] < end:
n_attr = attr.copy()
n_attr.start_index = 0
n_attr.end_index = 3
attrlist.insert(n_attr)
if not itter.next(): break
self._elayout.set_attributes(attrlist)
ellipsize_width = self._elayout.get_size()[0]
edge = line.x_to_index(layout_width - ellipsize_width)
char = self.index_to_pos(edge[BYTE])
char_x, char_y, char_h = pxls(char[X]), pxls(char[Y]), pxls(char[H])
y1, y2 = char_y, char_y + char_h
if edge[INLINE]:
x1 = char_x
else:
x1 = 0
coords.append((x1, y1))
coords.append((x1, y2))
line_ascent = pango.ASCENT(line.get_pixel_extents()[1])
ctx.move_to(x1, y1 + line_ascent - e_ascent)
ctx.show_layout(self._elayout)
else:
char = self.index_to_pos(edge[BYTE])
coords.append((pxls(char[X] + char[W]), pxls(char[Y])))
coords.append((pxls(char[X] + char[W]), pxls(char[Y] + char[H])))
if coords:
ctx.move_to(0, 0)
for x, y in coords:
ctx.line_to(x, y)
ctx.line_to(0, coords[-1][1])
ctx.close_path()
ctx.clip()
#layout
ctx.move_to(0,0)
ctx.show_layout(self)
#smilies
for index in self._smilies.keys():
try:
x, y, width, height = self.index_to_pos(index)
pixbuf = self._smilies_scaled[index]
tx = pxls(x)
ty = pxls(y) + (pxls(height)/2) - (pixbuf.get_height()/2)
ctx.set_source_pixbuf(pixbuf, tx, ty)
ctx.paint()
except:
pass
|