1
### Copyright (C) 2005 Thomas M. Hinkle
2
### Copyright (C) 2007 Imperial College London and others.
3
### Please see the AUTHORS file for a full list of contributors.
5
### This library is free software; you can redistribute it and/or
6
### modify it under the terms of the GNU General Public License as
7
### published by the Free Software Foundation; either version 2 of the
8
### License, or (at your option) any later version.
10
### This library is distributed in the hope that it will be useful,
11
### but WITHOUT ANY WARRANTY; without even the implied warranty of
12
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
### General Public License for more details.
15
### You should have received a copy of the GNU General Public License
16
### along with this library; if not, write to the Free Software
17
### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20
import pango,gtk, xml.sax.saxutils
24
class PangoBuffer (gtk.TextBuffer):
25
desc_to_attr_table = {
26
'family':[pango.AttrFamily,""],
27
'style':[pango.AttrStyle,pango.STYLE_NORMAL],
28
'variant':[pango.AttrVariant,pango.VARIANT_NORMAL],
29
'weight':[pango.AttrWeight,pango.WEIGHT_NORMAL],
30
'stretch':[pango.AttrStretch,pango.STRETCH_NORMAL],
32
pango_translation_properties={
33
# pango ATTR TYPE : (pango attr property / tag property)
34
pango.ATTR_SIZE : 'size',
35
pango.ATTR_WEIGHT: 'weight',
36
pango.ATTR_UNDERLINE: 'underline',
37
pango.ATTR_STRETCH: 'stretch',
38
pango.ATTR_VARIANT: 'variant',
39
pango.ATTR_STYLE: 'style',
40
pango.ATTR_SCALE: 'scale',
41
pango.ATTR_STRIKETHROUGH: 'strikethrough',
42
pango.ATTR_RISE: 'rise',
45
'underline':{pango.UNDERLINE_SINGLE:'single',
46
pango.UNDERLINE_DOUBLE:'double',
47
pango.UNDERLINE_LOW:'low',
48
pango.UNDERLINE_NONE:'none'},
49
'stretch':{pango.STRETCH_ULTRA_EXPANDED:'ultraexpanded',
50
pango.STRETCH_EXPANDED:'expanded',
51
pango.STRETCH_EXTRA_EXPANDED:'extraexpanded',
52
pango.STRETCH_EXTRA_CONDENSED:'extracondensed',
53
pango.STRETCH_ULTRA_CONDENSED:'ultracondensed',
54
pango.STRETCH_CONDENSED:'condensed',
55
pango.STRETCH_NORMAL:'normal',
57
'variant':{pango.VARIANT_NORMAL:'normal',
58
pango.VARIANT_SMALL_CAPS:'smallcaps',
60
'style':{pango.STYLE_NORMAL:'normal',
61
pango.STYLE_OBLIQUE:'oblique',
62
pango.STYLE_ITALIC:'italic',
64
'stikethrough':{1:'true',
74
gtk.TextBuffer.__init__(self)
76
def set_text (self, txt):
77
gtk.TextBuffer.set_text(self,"")
79
self.parsed,self.txt,self.separator = pango.parse_markup(txt,u'\x00')
81
debug.deprint('Escaping text, we seem to have a problem here!', 2)
82
txt=xml.sax.saxutils.escape(txt)
83
self.parsed,self.txt,self.separator = pango.parse_markup(txt,u'\x00')
84
self.attrIter = self.parsed.get_iterator()
85
self.add_iter_to_buffer()
86
while self.attrIter.next():
87
self.add_iter_to_buffer()
89
def add_iter_to_buffer (self):
90
range=self.attrIter.range()
91
font,lang,attrs = self.attrIter.get_font()
92
tags = self.get_tags_from_attrs(font,lang,attrs)
93
text = self.txt[range[0]:range[1]]
94
if tags: self.insert_with_tags(self.get_end_iter(),text,*tags)
95
else: self.insert_with_tags(self.get_end_iter(),text)
97
def get_tags_from_attrs (self, font,lang,attrs):
100
font,fontattrs = self.fontdesc_to_attrs(font)
101
fontdesc = font.to_string()
103
attrs.extend(fontattrs)
104
if fontdesc and fontdesc!='Normal':
105
if not self.tags.has_key(font.to_string()):
106
tag=self.create_tag()
107
tag.set_property('font-desc',font)
108
if not self.tagdict.has_key(tag): self.tagdict[tag]={}
109
self.tagdict[tag]['font_desc']=font.to_string()
110
self.tags[font.to_string()]=tag
111
tags.append(self.tags[font.to_string()])
113
if not self.tags.has_key(lang):
114
tag = self.create_tag()
115
tag.set_property('language',lang)
117
tags.append(self.tags[lang])
120
if a.type == pango.ATTR_FOREGROUND:
121
gdkcolor = self.pango_color_to_gdk(a.color)
122
key = 'foreground%s'%self.color_to_hex(gdkcolor)
123
if not self.tags.has_key(key):
124
self.tags[key]=self.create_tag()
125
self.tags[key].set_property('foreground-gdk',gdkcolor)
126
self.tagdict[self.tags[key]]={}
127
self.tagdict[self.tags[key]]['foreground']="#%s"%self.color_to_hex(gdkcolor)
128
tags.append(self.tags[key])
129
if a.type == pango.ATTR_BACKGROUND:
130
gdkcolor = self.pango_color_to_gdk(a.color)
131
key = 'background%s'%self.color_to_hex(gdkcolor)
132
if not self.tags.has_key(key):
133
self.tags[key]=self.create_tag()
134
self.tags[key].set_property('background-gdk',gdkcolor)
135
self.tagdict[self.tags[key]]={}
136
self.tagdict[self.tags[key]]['background']="#%s"%self.color_to_hex(gdkcolor)
137
tags.append(self.tags[key])
138
if self.pango_translation_properties.has_key(a.type):
139
prop=self.pango_translation_properties[a.type]
140
#print 'setting property %s of %s (type: %s)'%(prop,a,a.type)
141
val=getattr(a,'value')
142
#tag.set_property(prop,val)
144
if self.attval_to_markup.has_key(prop):
145
#print 'converting ',prop,' in ',val
146
if self.attval_to_markup[prop].has_key(val):
147
mval = self.attval_to_markup[prop][val]
149
debug.deprint("hmmm, didn't know what to do with value %s"%val, 2)
150
key="%s%s"%(prop,val)
151
if not self.tags.has_key(key):
152
self.tags[key]=self.create_tag()
153
self.tags[key].set_property(prop,val)
154
self.tagdict[self.tags[key]]={}
155
self.tagdict[self.tags[key]][prop]=mval
156
tags.append(self.tags[key])
161
for pos in range(self.get_char_count()):
162
iter=self.get_iter_at_offset(pos)
163
for tag in iter.get_tags():
164
if tagdict.has_key(tag):
165
if tagdict[tag][-1][1] == pos - 1:
166
tagdict[tag][-1] = (tagdict[tag][-1][0],pos)
168
tagdict[tag].append((pos,pos))
170
tagdict[tag]=[(pos,pos)]
173
def get_text (self, start=None, end=None, include_hidden_chars=True):
174
tagdict=self.get_tags()
175
if not start: start=self.get_start_iter()
176
if not end: end=self.get_end_iter()
177
txt = unicode(gtk.TextBuffer.get_text(self,start,end))
179
for k,v in tagdict.items():
180
stag,etag = self.tag_to_markup(k)
182
if cuts.has_key(st): cuts[st].append(stag) #add start tags second
183
else: cuts[st]=[stag]
184
if cuts.has_key(e+1): cuts[e+1]=[etag]+cuts[e+1] #add end tags first
185
else: cuts[e+1]=[etag]
188
cut_indices = cuts.keys()
190
soffset = start.get_offset()
191
eoffset = end.get_offset()
192
cut_indices = filter(lambda i: eoffset >= i >= soffset, cut_indices)
193
for c in cut_indices:
195
outbuff += xml.sax.saxutils.escape(txt[last_pos:c])
199
outbuff += xml.sax.saxutils.escape(txt[last_pos:])
202
def tag_to_markup (self, tag):
204
for k,v in self.tagdict[tag].items():
205
stag += ' %s="%s"'%(k,v)
207
return stag,"</span>"
209
def fontdesc_to_attrs (self,font):
210
nicks = font.get_set_fields().value_nicks
213
if self.desc_to_attr_table.has_key(n):
214
Attr,norm = self.desc_to_attr_table[n]
215
# create an attribute with our current value
216
attrs.append(Attr(getattr(font,'get_%s'%n)()))
217
# unset our font's value
218
getattr(font,'set_%s'%n)(norm)
221
def pango_color_to_gdk (self, pc):
222
return gtk.gdk.Color(pc.red,pc.green,pc.blue)
224
def color_to_hex (self, color):
226
for col in 'red','green','blue':
227
hexfrag = hex(getattr(color,col)/(16*16)).split("x")[1]
228
if len(hexfrag)<2: hexfrag = "0" + hexfrag
232
def apply_font_and_attrs (self, font, attrs):
233
tags = self.get_tags_from_attrs(font,None,attrs)
234
for t in tags: self.apply_tag_to_selection(t)
236
def remove_font_and_attrs (self, font, attrs):
237
tags = self.get_tags_from_attrs(font,None,attrs)
238
for t in tags: self.remove_tag_from_selection(t)
240
def setup_default_tags (self):
241
self.italics = self.get_tags_from_attrs(None,None,[pango.AttrStyle('italic')])[0]
242
self.bold = self.get_tags_from_attrs(None,None,[pango.AttrWeight('bold')])[0]
243
self.underline = self.get_tags_from_attrs(None,None,[pango.AttrUnderline('single')])[0]
245
def get_selection (self):
246
bounds = self.get_selection_bounds()
248
iter=self.get_iter_at_mark(self.get_insert())
249
if iter.inside_word():
250
start_pos = iter.get_offset()
251
iter.forward_word_end()
252
word_end = iter.get_offset()
253
iter.backward_word_start()
254
word_start = iter.get_offset()
255
iter.set_offset(start_pos)
256
bounds = (self.get_iter_at_offset(word_start),
257
self.get_iter_at_offset(word_end+1))
259
bounds = (iter,self.get_iter_at_offset(iter.get_offset()+1))
262
def apply_tag_to_selection (self, tag):
263
selection = self.get_selection()
265
self.apply_tag(tag,*selection)
267
def remove_tag_from_selection (self, tag):
268
selection = self.get_selection()
270
self.remove_tag(tag,*selection)
272
def remove_all_tags (self):
273
selection = self.get_selection()
275
for t in self.tags.values():
276
self.remove_tag(t,*selection)
278
class InteractivePangoBuffer (PangoBuffer):
281
toggle_widget_alist=[]):
282
"""An interactive interface to allow marking up a gtk.TextBuffer.
283
txt is initial text, with markup.
284
buf is the gtk.TextBuffer
285
normal_button is a widget whose clicked signal will make us normal
286
toggle_widget_alist is a list that looks like this:
287
[(widget, (font,attr)),
288
(widget2, (font,attr))]
290
PangoBuffer.__init__(self)
291
if normal_button: normal_button.connect('clicked',lambda *args: self.remove_all_tags())
292
self.tag_widgets = {}
293
self.internal_toggle = False
294
self.insert = self.get_insert()
295
self.connect('mark-set',self._mark_set_cb)
296
self.connect('changed',self._changed_cb)
297
for w,tup in toggle_widget_alist:
298
self.setup_widget(w,*tup)
300
def setup_widget_from_pango (self, widg, markupstring):
301
"""setup widget from a pango markup string"""
302
#font = pango.FontDescription(fontstring)
303
a,t,s = pango.parse_markup(markupstring,u'\x00')
305
font,lang,attrs=ai.get_font()
306
return self.setup_widget(widg,font,attrs)
308
def setup_widget (self, widg, font, attr):
309
tags=self.get_tags_from_attrs(font,None,attr)
310
self.tag_widgets[tuple(tags)]=widg
311
return widg.connect('toggled',self._toggle,tags)
313
def _toggle (self, widget, tags):
314
if self.internal_toggle: return
315
if widget.get_active():
316
for t in tags: self.apply_tag_to_selection(t)
318
for t in tags: self.remove_tag_from_selection(t)
320
def _mark_set_cb (self, buffer, iter, mark, *params):
321
# Every time the cursor moves, update our widgets that reflect
322
# the state of the text.
323
if hasattr(self,'_in_mark_set') and self._in_mark_set: return
324
self._in_mark_set = True
325
if mark.get_name()=='insert':
326
for tags,widg in self.tag_widgets.items():
329
if not iter.has_tag(t):
331
self.internal_toggle=True
332
widg.set_active(active)
333
self.internal_toggle=False
334
if hasattr(self,'last_mark'):
335
self.move_mark(self.last_mark,iter)
337
self.last_mark = self.create_mark('last',iter,left_gravity=True)
338
self._in_mark_set = False
340
def _changed_cb (self, tb):
341
if not hasattr(self,'last_mark'): return
342
# If our insertion point has a mark, we want to apply the tag
343
# each time the user types...
344
old_itr = self.get_iter_at_mark(self.last_mark)
345
insert_itr = self.get_iter_at_mark(self.insert)
346
if old_itr!=insert_itr:
347
# Use the state of our widgets to determine what
348
# properties to apply...
349
for tags,w in self.tag_widgets.items():
351
#print 'apply tags...',tags
352
for t in tags: self.apply_tag(t,old_itr,insert_itr)
360
self.w = gtk.Window()
362
self.editBox = gtk.HButtonBox()
363
self.nb = gtk.Button('Normal')
364
self.editBox.add(self.nb)
365
self.sw = gtk.ScrolledWindow()
366
self.tv = gtk.TextView()
368
self.ipb = InteractivePangoBuffer(
369
normal_button=self.nb)
370
self.ipb.set_text("""<b>This is bold</b>. <i>This is italic</i>
371
<b><i>This is bold, italic, and <u>underlined!</u></i></b>
372
<span background="blue">This is a test of bg color</span>
373
<span foreground="blue">This is a test of fg color</span>
374
<span foreground="white" background="blue">This is a test of fg and bg color</span>
376
# Here are some more: 1-2, 2-3, 3-4, 10-20, 30-40, 50-60
377
# This is <span color="blue">blue</span>, <span color="red">red</span> and <span color="green">green</span>""")
378
#self.ipb.set_text("""This is a numerical range (three hundred and fifty to four hundred) 350-400 which may get messed up.
379
#Here are some more: 1-2, 2-3, 3-4, 10-20, 30-40, 50-60""")
381
self.tv.set_buffer(self.ipb)
382
for lab,stock,font in [('gtk-italic',True,'<i>italic</i>'),
383
('gtk-bold',True,'<b>bold</b>'),
384
('gtk-underline',True,'<u>underline</u>'),
385
('Blue',True,'<span foreground="blue">blue</span>'),
386
('Red',False,'<span foreground="red">smallcaps</span>'),
388
button = gtk.ToggleButton(lab)
389
self.editBox.add(button)
390
if stock: button.set_use_stock(True)
391
self.ipb.setup_widget_from_pango(button,font)
392
self.vb.add(self.editBox)
394
self.actionBox = gtk.HButtonBox()
395
self.qb = gtk.Button(stock='quit')
396
self.pmbut = gtk.Button('Print markup')
397
self.pmbut.connect('clicked',self.print_markup)
398
self.qb.connect('clicked',lambda *args: self.w.destroy() or gtk.main_quit())
399
self.actionBox.add(self.pmbut)
400
self.actionBox.add(self.qb)
401
self.vb.add(self.actionBox)
405
def print_markup (self,*args):
406
debug.dprint(self.ipb.get_text(), 0)