On Mon, 14 Feb 2005 21:21:55 -0500, Thomas Mills Hinkle
<[EMAIL PROTECTED]> wrote:
> Second, I understand that according to the following bug, inserting
> pango markup (<u>,<b> etc.) into text-buffers is not easily supported
> in gtk (though perhaps coming soon)...
>
> http://bugzilla.gnome.org/show_bug.cgi?id=59390
>
> However, I'd like to do this. Actually, I'd like to allow my users to
> do basic formatting with "italic"/"bold"/"underline" buttons. I'd then
> like to preserve that formatting as pango to pass to e.g. gnomeprint
> to be layed out with pangolayout.
>
> Has anyone already done something like this in pygtk? I'm guessing
> that if I sit down and implement this, I'll be needless duplicating
> code that's already written somewhere -- can anyone confirm my
> suspicion by pointing me to an example? Thanks in advance...
Well, since I didn't get a good example to use (except for mozembed,
which for my purposes, was overkill), I cooked one up.
Here it is for anyone else following this thread. This implements
basic italic/bold/underline, etc., and provides get_text and set_text
methods that allow you to get pango markup out of a TextBuffer.
Tom
import pango,gtk
class PangoBuffer:
desc_to_attr_table = {
'family':[pango.AttrFamily,""],
'style':[pango.AttrStyle,pango.STYLE_NORMAL],
'variant':[pango.AttrVariant,pango.VARIANT_NORMAL],
'weight':[pango.AttrWeight,pango.WEIGHT_NORMAL],
'stretch':[pango.AttrStretch,pango.STRETCH_NORMAL],
}
pango_translation_properties={
# pango ATTR TYPE : (pango attr property / tag property)
pango.ATTR_SIZE : 'size',
pango.ATTR_WEIGHT: 'weight',
pango.ATTR_UNDERLINE: 'underline',
pango.ATTR_STRETCH: 'stretch',
pango.ATTR_VARIANT: 'variant',
pango.ATTR_STYLE: 'style',
pango.ATTR_SCALE: 'scale',
pango.ATTR_STRIKETHROUGH: 'strikethrough',
pango.ATTR_RISE: 'rise',
}
attval_to_markup={
'underline':{pango.UNDERLINE_SINGLE:'single',
pango.UNDERLINE_DOUBLE:'double',
pango.UNDERLINE_LOW:'low',
pango.UNDERLINE_NONE:'none'},
'stretch':{pango.STRETCH_ULTRA_EXPANDED:'ultraexpanded',
pango.STRETCH_EXPANDED:'expanded',
pango.STRETCH_EXTRA_EXPANDED:'extraexpanded',
pango.STRETCH_EXTRA_CONDENSED:'extracondensed',
pango.STRETCH_ULTRA_CONDENSED:'ultracondensed',
pango.STRETCH_CONDENSED:'condensed',
pango.STRETCH_NORMAL:'normal',
},
'variant':{pango.VARIANT_NORMAL:'normal',
pango.VARIANT_SMALL_CAPS:'smallcaps',
},
'style':{pango.STYLE_NORMAL:'normal',
pango.STYLE_OBLIQUE:'oblique',
pango.STYLE_ITALIC:'italic',
},
'stikethrough':{1:'true',
True:'true',
0:'false',
False:'false'},
}
def __init__ (self, txt, buf):
self.tagdict = {}
self.tags = {}
self.buf = buf
self.set_text(txt)
def set_text (self, txt):
self.parsed,self.txt,self.separator = pango.parse_markup(txt,u'0')
self.attrIter = self.parsed.get_iterator()
self.add_iter_to_buffer()
while self.attrIter.next():
self.add_iter_to_buffer()
def add_iter_to_buffer (self):
range=self.attrIter.range()
font,lang,attrs = self.attrIter.get_font()
tags = self.get_tags_from_attrs(font,lang,attrs)
text = self.txt[range[0]:range[1]]
if tags: self.buf.insert_with_tags(self.buf.get_end_iter(),text,*tags)
else: self.buf.insert_with_tags(self.buf.get_end_iter(),text)
def get_tags_from_attrs (self, font,lang,attrs):
tags = []
if font:
font,fontattrs = self.fontdesc_to_attrs(font)
fontdesc = font.to_string()
if fontattrs:
attrs.extend(fontattrs)
if fontdesc and fontdesc!='Normal':
if not self.tags.has_key(font.to_string()):
tag=self.buf.create_tag()
tag.set_property('font-desc',font)
if not self.tagdict.has_key(tag): self.tagdict[tag]={}
self.tagdict[tag]['font_desc']=font.to_string()
self.tags[font.to_string()]=tag
tags.append(self.tags[font.to_string()])
if lang:
if not self.tags.has_key(lang):
tag = self.buf.create_tag()
tag.set_property('language',lang)
self.tags[lang]=tag
tags.append(self.tags[lang])
if attrs:
for a in attrs:
if a.type == pango.ATTR_FOREGROUND:
gdkcolor = self.pango_color_to_gdk(a.color)
key = 'foreground%s'%self.color_to_hex(gdkcolor)
if not self.tags.has_key(key):
self.tags[key]=self.buf.create_tag()
self.tags[key].set_property('foreground-gdk',gdkcolor)
self.tagdict[self.tags[key]]={}
self.tagdict[self.tags[key]]['foreground']="#%s"%self.color_to_hex(gdkcolor)
tags.append(self.tags[key])
if a.type == pango.ATTR_BACKGROUND:
gdkcolor = self.pango_color_to_gdk(a.color)
tag.set_property('background-gdk',gdkcolor)
key = 'background%s'%self.color_to_hex(gdkcolor)
if not self.tags.has_key(key):
self.tags[key]=self.buf.create_tag()
self.tags[key].set_property('background-gdk',gdkcolor)
self.tagdict[self.tags[key]]={}
self.tagdict[self.tags[key]]['background']="#%s"%self.color_to_hex(gdkcolor)
tags.append(self.tags[key])
if self.pango_translation_properties.has_key(a.type):
prop=self.pango_translation_properties[a.type]
#print 'setting property %s of %s (type: %s)'%(prop,a,a.type)
val=getattr(a,'value')
#tag.set_property(prop,val)
mval = val
if self.attval_to_markup.has_key(prop):
#print 'converting ',prop,' in ',val
if self.attval_to_markup[prop].has_key(val):
mval = self.attval_to_markup[prop][val]
key="%s%s"%(prop,val)
if not self.tags.has_key(key):
self.tags[key]=self.buf.create_tag()
self.tags[key].set_property(prop,val)
self.tagdict[self.tags[key]]={}
self.tagdict[self.tags[key]][prop]=mval
tags.append(self.tags[key])
else:
print "Don't know what to do with attr %s"%a
return tags
def get_tags (self):
tagdict = {}
for pos in range(self.buf.get_char_count()):
iter=self.buf.get_iter_at_offset(pos)
for t in iter.get_tags():
if tagdict.has_key(tag):
if tagdict[tag][-1][1] == pos - 1:
tagdict[tag][-1] = (tagdict[tag][-1][0],pos)
else:
tagdict[tag].append((pos,pos))
else:
tagdict[tag]=[(pos,pos)]
return tagdict
def get_text (self):
tagdict=self.get_tags()
txt = self.buf.get_text(self.buf.get_start_iter(),self.buf.get_end_iter())
cuts = {}
for k,v in tagdict.items():
stag,etag = self.tag_to_markup(k)
for st,end in v:
if cuts.has_key(st): cuts[st].append(stag) #add start tags second
else: cuts[st]=[stag]
if cuts.has_key(end+1): cuts[end+1]=[etag]+cuts[end+1] #add end tags first
else: cuts[end+1]=[etag]
last_pos = 0
outbuff = ""
cut_indices = cuts.keys()
cut_indices.sort()
for c in cut_indices:
#print 'cut',c
if not last_pos==c:
outbuff += txt[last_pos:c]
last_pos = c
for tag in cuts[c]:
outbuff += tag
#print 'outbuff: ',outbuff
outbuff += txt[last_pos:]
return outbuff
def tag_to_markup (self, tag):
stag = "<span"
for k,v in self.tagdict[tag].items():
stag += ' %s="%s"'%(k,v)
stag += ">"
return stag,"</span>"
def fontdesc_to_attrs (self,font):
nicks = font.get_set_fields().value_nicks
attrs = []
for n in nicks:
if self.desc_to_attr_table.has_key(n):
#print 'attributeifying %s'%n
Attr,norm = self.desc_to_attr_table[n]
# create an attribute with our current value
attrs.append(Attr(getattr(font,'get_%s'%n)()))
# unset our font's value
getattr(font,'set_%s'%n)(norm)
return font,attrs
def pango_color_to_gdk (self, pc):
return gtk.gdk.Color(pc.red,pc.green,pc.blue)
def color_to_hex (self, color):
hexstring = ""
for col in 'red','green','blue':
hexfrag = hex(getattr(color,col)/(16*16)).split("x")[1]
if len(hexfrag)<2: hexfrag = "0" + hexfrag
hexstring += hexfrag
#print 'returning hexstring: ',hexstring
return hexstring
def apply_font_and_attrs (self, font, attrs):
tags = self.get_tags_from_attrs(font,None,attrs)
for t in tags: self.apply_tag(t)
def remove_font_and_attrs (self, font, attrs):
tags = self.get_tags_from_attrs(font,None,attrs)
for t in tags: self.remove_tag(t)
def setup_default_tags (self):
self.italics = self.get_tags_from_attrs(None,None,[pango.AttrStyle('italic')])[0]
self.bold = self.get_tags_from_attrs(None,None,[pango.AttrWeight('bold')])[0]
self.underline = self.get_tags_from_attrs(None,None,[pango.AttrUnderline('single')])[0]
def get_selection (self):
bounds = self.buf.get_selection_bounds()
if not bounds:
iter=self.buf.get_iter_at_mark(self.insert)
if iter.inside_word():
start_pos = iter.get_offset()
iter.forward_word_end()
word_end = iter.get_offset()
iter.backward_word_start()
word_start = iter.get_offset()
iter.set_offset(start_pos)
bounds = (self.buf.get_iter_at_offset(word_start),
self.buf.get_iter_at_offset(word_end))
return bounds
def apply_tag (self, tag):
selection = self.get_selection()
If selection:
self.buf.apply_tag(tag,*selection)
def remove_tag (self, tag):
selection = self.get_selection()
if selection:
self.buf.remove_tag(tag,*selection)
def remove_all_tags (self):
selection = self.get_selection()
if selection:
for t in self.tags.values():
self.buf.remove_tag(t,*selection)
class InteractivePangoBuffer (PangoBuffer):
def __init__ (self, txt, buf,
normal_button=None,
toggle_widget_alist=[]):
"""An interactive interface to allow marking up a gtk.TextBuffer.
txt is initial text, with markup.
buf is the gtk.TextBuffer
normal_button is a widget whose clicked signal will make us normal
toggle_widget_alist is a list that looks like this:
[(widget, (font,attr)),
(widget2, (font,attr))]
"""
PangoBuffer.__init__(self, txt, buf)
if normal_button: normal_button.connect('clicked',lambda *args: self.remove_all_tags())
self.tag_widgets = {}
self.internal_toggle = False
self.insert = self.buf.get_insert()
self.buf.connect('mark-set',self._mark_set_cb)
for w,tup in toggle_widget_alist:
self.setup_widget(w,*tup)
def setup_widget_from_pango (self, widg, markupstring):
"""setup widget from a pango markup string"""
#font = pango.FontDescription(fontstring)
a,t,s = pango.parse_markup(markupstring,u'0')
ai=a.get_iterator()
font,lang,attrs=ai.get_font()
self.setup_widget(widg,font,attrs)
def setup_widget (self, widg, font, attr):
tags=self.get_tags_from_attrs(font,None,attr)
self.tag_widgets[tuple(tags)]=widg
widg.connect('toggled',self._toggle,tags)
def _toggle (self, widget, tags):
#print '_toggle called'
if self.internal_toggle: return
#print 'not internal'
if widget.get_active():
#print 'applying'
for t in tags: self.apply_tag(t)
else:
#print 'removing'
for t in tags: self.remove_tag(t)
def _mark_set_cb (self, buffer, iter, mark, *params):
#print '_mark_set_cb'
if mark==self.insert:
#print 'insert moved!', self.tag_widgets
for tags,widg in self.tag_widgets.items():
active = True
for t in tags:
#print 'testing tag',t
if not iter.has_tag(t):
#print "iter doesn't have tag %s!"%t
active=False
self.internal_toggle=True
widg.set_active(active)
self.internal_toggle=False
class SimpleEditor:
def __init__ (self):
self.w = gtk.Window()
self.vb = gtk.VBox()
self.editBox = gtk.HButtonBox()
self.nb = gtk.Button('Normal')
self.editBox.add(self.nb)
self.sw = gtk.ScrolledWindow()
self.tv = gtk.TextView()
self.sw.add(self.tv)
self.ipb = InteractivePangoBuffer(
"""<b>This is bold</b>. <i>This is italic</i>
<b><i>This is bold, italic, and <u>underlined!</u></i></b>
This is <span color="blue">blue</span>, <span color="red">red</span> and <span color="green">green</span>""",
self.tv.get_buffer(),
normal_button=self.nb)
for lab,stock,font in [('gtk-italic',True,'<i>italic</i>'),
('gtk-bold',True,'<b>bold</b>'),
('gtk-underline',True,'<u>underline</u>'),
('Blue',True,'<span foreground="blue">blue</span>'),
('Red',False,'<span foreground="red">smallcaps</span>'),
]:
button = gtk.ToggleButton(lab)
self.editBox.add(button)
if stock: button.set_use_stock(True)
self.ipb.setup_widget_from_pango(button,font)
self.vb.add(self.editBox)
self.vb.add(self.sw)
self.actionBox = gtk.HButtonBox()
self.qb = gtk.Button(stock='quit')
self.pmbut = gtk.Button('Print markup')
self.pmbut.connect('clicked',self.print_markup)
self.qb.connect('clicked',lambda *args: self.w.destroy() or gtk.main_quit())
self.actionBox.add(self.pmbut)
self.actionBox.add(self.qb)
self.vb.add(self.actionBox)
self.w.add(self.vb)
self.w.show_all()
def print_markup (self,*args):
print self.ipb.get_text()
if __name__ == '__main__':
se = SimpleEditor()
se.w.connect('delete-event',lambda *args: gtk.main_quit())
gtk.main()
_______________________________________________
pygtk mailing list [email protected]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/