# 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('', self.copyevent) self.bind('', self.pasteevent) self.bind('', lambda e, s=self:s.pool.dump()) self.bind('', lambda e, s=self:s.tag_dump()) self.bind('', lambda e, s=self:s.mark_dump()) self.bind('', lambda e, s=self:s.styling_dump()) self.bind('', lambda e, s=self:s.print_current_style()) self.bind('', 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)=', 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()