# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from ScrolledText import ScrolledText
from iterator import Style,StyletextError,Pool
from string import atoi,replace,split
class StyleText(ScrolledText):
def __init__(self, master, **kw):
apply(ScrolledText.__init__, (self, master), kw)
self.pool = Pool(self)
self.sorts = {}
self.sorts_order = []
self.clip_text = ''
self.clip_styling = []
# XXX force-mechanism should be moved to TextEditor
self.style_force = []
self._patch_tk()
self.bind('<Control-c>', self.copyevent)
self.bind('<Control-v>', self.pasteevent)
self.bind('<Alt-p>', lambda e, s=self:s.pool.dump())
self.bind('<Alt-t>', lambda e, s=self:s.tag_dump())
self.bind('<Alt-m>', lambda e, s=self:s.mark_dump())
self.bind('<Alt-g>', lambda e, s=self:s.styling_dump())
self.bind('<Alt-s>', lambda e, s=self:s.print_current_style())
self.bind('<Alt-f>', lambda e, s=self:s.fontify('1.0','end'))
def _patch_tk(self):
insert_tcl = self.register(self.insert)
delete_tcl = self.register(self.delete)
tclstr = """
rename _w _old_w
proc _w { command args } {
if { $command == "insert" } {
set ret [ eval "insert_tcl $args" ]
} elseif {$command == "delete"} {
set ret [ eval "delete_tcl $args" ]
} else {
set ret [ eval "_old_w $command $args" ]
}
return $ret
}
"""
self._old_w = '.old'+self._w
tclstr = replace(tclstr, '_old_w', self._old_w)
tclstr = replace(tclstr, '_w', self._w)
tclstr = replace(tclstr, 'insert_tcl', insert_tcl)
tclstr = replace(tclstr, 'delete_tcl', delete_tcl)
self.tk.eval(tclstr)
def copyevent(self, event):
range = self.tag_ranges('sel')
if len(range) !=2:
return
self.clip_text = apply(self.get, range)
self.clip_styling = apply(self.styling_get, range)
def pasteevent(self, event):
self.delselection()
index = self.index('insert')
self.insert(index, self.clip_text)
self.styling_apply(index, self.clip_styling)
def delselection(self):
sel = self.tag_ranges('sel')
if len(sel) == 2:
apply(self.delete, sel)
def register_sort(self, sortid, function, default):
defaultstyle = apply(Style,(),{sortid : default})
self.sorts[sortid] = (function, defaultstyle)
self.sorts_order.insert(0, sortid)
def print_current_style(self,event=None):
print "_____ current style %s ______" % self.index('insert')
for style in self.style_get('insert'):
print self.index('insert'),"\t", style
def mark_dump(self):
list = []
for name in self.mark_names():
list.append((self.index(name), name))
list.sort()
print "============== mark dump ================="
for pos, name in list:
# if name[0] == '_':
print pos,"\t", name
def tag_dump(self):
print "============== tag dump ==================="
names = list(self.tag_names())
out = []
for tag in names:
ranges = self.tag_ranges(tag)
if len(ranges)>0:
out.append((ranges, tag))
out.sort()
for ranges, tag in out:
print tag,"\t\t", ranges
def delete(self, index1, index2=None):
index1 = self.index(index1)
it = self.pool.iterator()
self.tk.call(self._old_w, 'delete', index1, index2)
# remove all tag changes which became obsolete.
# Obsolete means, (1) the tag is at or after 'end' or (2) there
# is another tag of the same sort at the same index position.
it.before(index1)
it.next()
dict = {}
while (not it.outofboundary) and self.compare(it, '==', index1):
sort = it.current.sort
if dict.has_key(sort):
it.delete()
else:
dict[sort] = 1
it.next()
it.i = len(self.pool)-1
it._update()
while (not it.outofboundary) and self.compare(it, '>=', 'end'):
it.delete()
def insert(self, index, chars, *tagsnstyles):
tags = []
styles = self.style_force
for arg in tagsnstyles:
if type(arg).__name__ == 'string':
tags = tags + arg
elif hasattr(arg, 'is_Style'):
styles.append(arg)
else:
raise StyletextError(
"arguments should be tags and styles: %s" % arg)
begin = self.index(index)
self.tk.call((self._old_w, 'insert', index, chars) + tuple(tags))
end = self.index('%s + %dc' % (begin, len(chars)))
for style in styles:
self.style_add(style, begin, end, supressfontify=1)
self.fontify("%s linestart - 1 lines" % begin, "%s lineend" % end)
self.style_force = []
def style_get(self, index, sort = None):
'''Returns the style of type sort at position index.
If sort is not given, returns a complete list of all styles.
'''
it = self.pool.iterator()
it.before(index)
if sort is not None:
# look for a style of type 'sort'
if it.outofboundary:
return self.sorts[sort][1]
while not it.is_first() and not it.current.sort == sort:
it.prev()
if it.current.sort == sort:
return it.current
else:
return self.sorts[sort][1]
else:
dict = {}
if not it.outofboundary:
dict[it.current.sort] = it.current
while (not it.is_first()) and len(dict)<len(self.sorts):
it.prev()
if not dict.has_key(it.current.sort):
dict[it.current.sort] = it.current
# if incomplete: fill up with defaults
for sort in self.sorts.keys():
if not dict.has_key(sort):
dict[sort] = self.sorts[sort][1]
ret = []
# keep the order as given in self.sorts.keys()
for sort in self.sorts_order:
ret.append(dict[sort])
return ret
def style_get_range(self, begin, end, sort=None):
'''returns all styles between begin and end.
If sort is given, only those of type sort are returned.
'''
styles = self.style_get(begin+'+1 chars')
it = self.pool.iterator()
it.before(begin+'+1 chars')
while not it.is_last():
it.next()
if self.compare(it.id,'>=', end):
break
try:
i = styles.index(it.current)
except:
styles.append(it.current)
return styles
def style_removeall(self):
for mark in self.mark_names():
if mark[0] == '_':
self.mark_unset(mark)
self.pool = Pool(self)
self.fontify('1.0','end')
def styling_get(self, begin, end):
it = self.pool.iterator()
styles = self.style_get(begin+"+ 1 chars")
styling = []
for style in styles:
styling.append((style, begin))
it.before(begin)
# need to replace befores when there a style at
while 1:
if it.i >= len(self.pool):
break
elif it.i >= 0:
if self.compare(it, '>', end):
break
elif self.compare(it, '>', begin):
style = it.current
styling.append((style, self.index(it)))
it.next()
#
end = self.index(end)
styling.append((None, end))
# relocate indizes
ret = []
bline, bchar = map(atoi, split(begin, '.'))
for style, indexstr in styling:
line, char = map(atoi, split(indexstr, '.'))
if line == bline:
char = char-bchar
line = line-bline
ret.append((style, line, char))
return ret
def styling_apply(self, index, styling):
if styling is None or len(styling) == 0:
return
index = self.index(index)
iline, ichar = map(atoi, split(index, '.'))
it = self.pool.iterator()
# determine end
tmp, eline, echar = styling[-1]
if eline == 0:
echar = echar+ichar
eline = eline+iline
end = "%i.%i" % (eline, echar)
i = 0
for style, line, char in styling[:-1]:
i = i+1
if line == 0:
char = char+ichar
line = line+iline
indexstr = "%i.%i" % (line, char)
if i<4:
self.style_add(style, index, end, supressfontify=1)
else:
# insert it
it.insert_right(style, indexstr)
self.fontify(index, end)
def styling_dump(self):
range = self.tag_ranges('sel')
if len(range) != 2:
return
print apply(self.styling_get, range)
def fontify(self, begin, end):
for name in self.tag_names():
if name[0]=='_':
self.tag_remove(name, begin, end)
styles = self.style_get(begin)
dict = {}
for style in styles:
name = name+style._signature_
dict[style.sort] = style.options
it = self.pool.iterator()
it.before(begin) # last style change before' begin'
a = begin
while self.compare(a,'<',end):
if it.is_last():
b = end
else:
it.next()
b = it.id
options = {}
name = '_'+str(dict)
self.tag_add(name,a, b)
_dict = dict.copy()
for sort in self.sorts_order:
func = self.sorts[sort][0]
apply(func, (options, _dict))
self.tag_configure(name, options)
if not it.outofboundary:
dict[it.current.sort] = it.current.options
a = b
def style_add(self, style, begin, end, supressfontify=0):
if self.compare(begin,'>=',end):
return
it = self.pool.iterator()
# this is the simplest, definitly not the fastest solution
ostyle_begin = self.style_get(begin, style.sort)
ostyle_end = self.style_get(end+' +1 chars', style.sort)
# delete all style changes of sort "style.sort" between "begin" and
# "end"
it.before(begin)
it.next()
while (not it.outofboundary) and self.compare(it,'<=',end):
if it.current.sort == style.sort:
it.delete()
else:
it.next()
if ostyle_begin != style:
it.insert_left(style, begin)
if ostyle_end != style:
it.insert_right(ostyle_end, end)
if not supressfontify:
self.fontify(begin, end)
# for older Tkinter version (those coming with Python 1.5.2) add
# some missing Text methods
def mark_next(self, index):
"""Return the name of the next mark after INDEX."""
return self.tk.call(self._w, 'mark', 'next', index) or None
def mark_previous(self, index):
"""Return the name of the previous mark before INDEX."""
return self.tk.call(self._w, 'mark', 'previous', index) or None
if __name__=='__main__':
from Tkinter import *
tk = Tk()
text = StyleText(tk, background='white')
text.pack(fill=BOTH, expand=1)
text.insert(END,'01234567890abcdefghijkl\n')
text.insert(END,'01234567890abcdefghijkl\n')
text.insert(END,'01234567890abcdefghijkl\n')
import sys
sys.path.append('/usr/lib/sketch-0.6.12/')
from Sketch.Graphics import font
class font_families:
def __init__(self):
self.family_to_fonts = font.make_family_to_fonts()
self.families = self.family_to_fonts.keys()
self.families.sort()
def xlfd(self, **kw):
fonts = self.family_to_fonts[kw['family']]
for name in fonts:
family, attrs, xlfd_start, encoding = font.fontmap[name]
if attrs == kw['attr']:
return font.xlfd_template % \
(xlfd_start, kw['size'], encoding)
kw['attr'] = 'Roman'
return apply(self.xlfd, (), kw)
def ps(self, **kw):
props = {}
props.update(DEFAULT)
props.update(kw)
fonts = self.family_to_fonts[props['family']]
for name in fonts:
family, attrs, xlfd_start, encoding = font.fontmap[name]
if attrs == props['attr']:
return name
return ''
font.read_font_dirs()
FONTS = font_families()
def FamilySort(tagoptions, dict):
return tagoptions
def AttrSort(tagoptions, dict):
return tagoptions
def SizeSort(tagoptions, dict):
xfont = apply(FONTS.xlfd, (), dict)
tagoptions['font'] = xfont
return tagoptions
text.register_sort('family', FamilySort, 'Times' )
text.register_sort('attr', AttrSort, 'Roman')
text.register_sort('size', SizeSort, 12)
text.style_add(Style(size=24),'1.5','2.13')
text.pool.dump()
text.style_add(Style(size=72),'1.4','2.14')
text.pool.dump()
raw_input()
|