import Tix as Tk import tkFont as Font DIALOG_FONT = 'Times 8' DEFAULT_TEXT_FONT = ('Lucida Sans Unicode', 12) class ImageFactory( object ): _images = { } @staticmethod def makeImage( filename ): if filename not in ImageFactory._images: ImageFactory._images[ filename ] = Tk.PhotoImage( file=filename ) return ImageFactory._images[ filename ] class StylerFont( object ): FIELD_NAMES = [ 'family', 'size', 'weight', 'slant', 'offset', 'bold', 'italic' ] FONT_TAG_FORMAT = '-font-%(family)s-%(size)d-%(weight)s-%(slant)s-%(offset)s' FONT_TAG_PREFIX = '-font-' FONT_LIBRARY = { } DEFAULT_FONT = None def __init__( self, fontOpts ): self._fontOpts = fontOpts def __setitem__( self, key, value ): if key == 'bold': self._fontOpts[ 'weight' ] = 'bold' if value else 'normal' elif key == 'italic': self._fontOpts[ 'slant' ] = 'italic' if value else 'roman' else: self._fontOpts[ key ] = value def __getitem__( self, key ): if key == 'bold': return self._fontOpts[ 'weight' ] == 'bold' elif key == 'italic': return self._fontOpts[ 'slant' ] == 'italic' else: return self._fontOpts[ key ] def fontName( self ): return StylerFont.FONT_TAG_FORMAT % self._fontOpts def fontOptions( self ): return self._fontOpts def tagOptions( self ): options = { } styleList = [ ] if self._fontOpts['weight'] == 'bold': styleList.append( 'bold' ) if self._fontOpts['slant'] == 'italic': styleList.append( 'italic' ) if self._fontOpts['offset'] != 'normal': # If the region includes an offset, we need to reestablish that baseSize = self._fontOpts['size'] size = int(baseSize * (3.0 / 5.0) + 0.5) if self._fontOpts['offset'] == 'superscript': options[ 'offset' ] = '%dp' % int(baseSize / 2.0 + 0.5) else: options[ 'offset' ] = '-%dp' % int(baseSize / 5.0 + 0.5) else: size = self._fontOpts['size'] options[ 'font' ] = ( self._fontOpts['family'], size, ' '.join(styleList) ) return options def deriveFont( self, **newOpts ): newFont = copy.deepcopy( self ) for key,val in newOpts.iteritems(): newFont[ key ] = val fontName = newFont.fontName( ) if fontName not in StylerFont.FONT_LIBRARY: StylerFont.FONT_LIBRARY[ fontName ] = newFont return StylerFont.FONT_LIBRARY[ fontName ] def tkFont( self ): return Font.Font( family=self._fontOpts['family'], size=self._fontOpts['size'], weight=self._fontOpts['weight'], slant=self._fontOpts['slant'] ) @staticmethod def setup( aTextWidget ): StylerFont.DEFAULT_FONT = StylerFont.getFont( aTextWidget['font'] ) StylerFont.FONT_LIBRARY[ 'default' ] = StylerFont.DEFAULT_FONT @staticmethod def getFont( aFontSpec=None, **options ): fontOpts = { } fontStyling = '' if aFontSpec: if isinstance( aFontSpec, (str,unicode) ): if aFontSpec == 'default': return StylerFont.FONT_LIBRARY[ 'default' ] elif aFontSpec.startswith( StylerFont.FONT_TAG_PREFIX ): # We have a font name try: return StylerFont.FONT_LIBRARY[ aFontSpec ] except: for key,val in zip( ['family','size','weight','slant','offset'], aFontSpec.split( '-' )[2:] ): fontOpts[ key ] = val fontOpts['size'] = int(fontOpts['size']) StylerFont.FONT_LIBRARY[ aFontSpec ] = StylerFont( fontOpts ) return StylerFont.FONT_LIBRARY[ aFontSpec ] else: # We need to try to decode a tkinter font string if aFontSpec[0] == '{': fontFamilyStringBegin = 1 fontFamilyStringEnd = aFontSpec.find( '}' ) fontSize, sep, fontStyling = aFontSpec[ fontFamilyStringEnd + 2 : ].lower().partition( ' ' ) else: fontFamilyStringBegin = 0 fontFamilyStringEnd = aFontSpec.find( ' ' ) fontSize, sep, fontStyling = aFontSpec[ fontFamilyStringEnd + 1 : ].lower().partition( ' ' ) fontOpts['family' ] = aFontSpec[ fontFamilyStringBegin : fontFamilyStringEnd ] fontOpts['size' ] = int(fontSize) elif isinstance( aFontSpec, (list,tuple) ): # We need to decode a tkinter font tuple fontStyling = aFontSpec[2] if len(aFontSpec)==3 else '' fontOpts['family' ] = aFontSpec[0] fontOpts['size' ] = aFontSpec[1] elif options: if 'family' in options: if 'bold' in options: options[ 'weight' ] = 'bold' if options['bold'] else 'normal' del options[ 'bold' ] if 'italic' in options: options[ 'slant' ] = 'italic' if options['italic'] else 'roman' del options[ 'italic' ] fontName = StylerFont.FONT_TAG_FORMAT % options try: return StylerFont.FONT_LIBRARY[ fontName ] except: StylerFont.FONT_LIBRARY[ fontName ] = StylerFont( options ) return StylerFont.FONT_LIBRARY[ fontName ] else: fontTuple = options[ 'font' ] fontStyling = aFontSpec[2] if len(aFontSpec)==3 else '' fontOpts['family' ] = fontTuple[0] fontOpts['size' ] = fontTuple[1] else: return StylerFont.getFont( 'default' ) fontOpts['weight' ] = 'bold' if 'bold' in fontStyling else 'normal' fontOpts['slant' ] = 'italic' if 'italic' in fontStyling else 'roman' fontOpts['underline' ] = False fontOpts['overstrike'] = False fontOpts['offset' ] = 'normal' fontName = StylerFont.FONT_TAG_FORMAT % fontOpts try: return StylerFont.FONT_LIBRARY[ fontName ] except: theFontObj = StylerFont( fontOpts ) StylerFont.FONT_LIBRARY[ fontName ] = theFontObj return theFontObj class Style( dict ): FIELD_NAMES = [ 'font', 'underline', 'overstrike', 'foreground', 'background' 'fgstipple', 'bgstipple', 'borderwidth', 'relief', 'justify', 'wrap', 'lmargin1', 'lmargin2', 'rmargin', 'spacing1', 'spacing2', 'spacing3', 'tabs' ] ATTRIBUTE_TAG_FORMAT = '-attribute-%(name)s-%(value)s' ATTRIBUTE_TAG_PREFIX = '-attribute-' DEFAULT_STYLE = None OFF_VALUES = { } '''Most fields have the exact same set of possible values as the related option for the text widget. 'font' is a legitimate Style field. While any subfield of 'font' is not a style field of Style, they are accepted by __init__, __setitem__ and __getitem__ but will simply be passed to the StylerFont subobject. (e.g. family, may be requested of a style instance and will be passed to the StylerFont object). The 'offset' and the three 'spacing' options can take any of the usual values available to the text widget. However, several new values are also possible for these fields. font: StylerFont family: string. A font family name size: int. A font size in points weight: string. One of: 'normal', 'bold' bold: boolean. An alternate for weight slant: string. One of: 'roman', 'italic' italic: boolean. An alternate for slant underline: boolean. overstrike: boolean. offset: Dimension or string. One of: 'normal', 'subscript', 'superscript' foreground: Color. background: Color. fgstipple: bitmap. bgstipple: bitmap. borderwidth: Dimension. relief: string. One of: 'flat', 'sunken', 'raised', 'groove', 'ridge', 'solid', '' justify: string. One of: 'left', 'right', 'center', '' wrap: string. One of: 'none', 'char', 'word', '' lmargin1: Dimension. lmargin2: Dimension. rmargin: Dimension. spacing1: Dimension or string. One of: 'None', 'Half Line', 'One Line', 'Two Lines' spacing2: Dimension or string. One of: 'None', 'Half Line', 'One Line', 'Two Lines' spacing3: Dimension or string. One of: 'None', 'Half Line', 'One Line', 'Two Lines' tabs: string of Dimension. ''' def __init__( self, **options ): dict.__init__( self ) self._font = None for key,value in options.iteritems( ): if key == 'font': if isinstance( value, StylerFont ): self._font = value else: self._font = StylerFont( value ) else: dict.__setitem__( self, key, value ) def __setitem__( self, key, value ): if key in StylerFont.FIELD_NAMES: if key == 'bold': key = 'weight' value = 'bold' if value else 'normal' elif key == 'italic': key = 'slant' value = 'italic' if value else 'roman' self._font[ key ] = value else: if (value in Style.OFF_VALUES[key]) or (value == Style.DEFAULT_STYLE[key]): if key in self: self.__delitem__( key ) else: if key == 'font': if isinstance( value, StylerFont ): self._font = value else: self._font = StylerFont.getFont( value ) else: dict.__setitem__( self, key, value ) def __getitem__( self, key ): if key in StylerFont.FIELD_NAMES: return self._font[ key ] elif key in self: return dict.__getitem__( self, key ) elif key == 'font': return self._font def tagOptions( self ): options = { } options.update( self ) try: options.update( self._font.tagOptions( ) ) lineHeight = self._font.tkFont().metrics('linespace') except: lineHeight = Style.DEFAULT_STYLE._font.tkFont().metrics('linespace') # Adjust fields with non-tk options for spacingKind in [ 'spacing1', 'spacing2', 'spacing3' ]: try: selectedSpacing = options[ spacingKind ] if selectedSpacing == 'None': options[ spacingKind ] = '0' elif selectedSpacing == 'Half Line': options[ spacingKind ] = '%dp' % int(float(lineHeight * 0.5 + 0.5)) elif selectedSpacing == 'One Line': options[ spacingKind ] = '%dp' % int(lineHeight) elif selectedSpacing == 'Two Lines': options[ spacingKind ] = '%dp' % int(lineHeight * 2) else: options[ spacingKind ] = selectedSpacing except: pass return options @staticmethod def setup( aTextWidget ): StylerFont.setup( aTextWidget ) Style.DEFAULT_STYLE = Style( font = StylerFont.DEFAULT_FONT, foreground = aTextWidget[ 'foreground' ], background = aTextWidget[ 'background' ], fgstipple = '', bgstipple = '', borderwidth = 0, relief = Tk.FLAT, justify = Tk.LEFT, wrap = Tk.CHAR, lmargin1 = 0, lmargin2 = 0, rmargin = 0, spacing1 = 0, spacing2 = 0, spacing3 = 0, tabs = '' ) Style.OFF_VALUES = { # Font Attributes 'family': [ StylerFont.DEFAULT_FONT['family'] ], 'size': [ StylerFont.DEFAULT_FONT['size' ] ], 'weight': [ Font.NORMAL, None ], 'slant': [ Font.ROMAN, None ], 'offset': [ 'normal', '0', '0p', '0i', '0c', '0m', '', None ], # Paragraph Attributes 'justify': [ Tk.LEFT, '', None ], 'wrap': [ Tk.CHAR, '', None ], 'lmargin1': [ '0', '0p', '0i', '0c', '0m', '', None ], 'lmargin2': [ '0', '0p', '0i', '0c', '0m', '', None ], 'rmargin': [ '0', '0p', '0i', '0c', '0m', '', None ], 'spacing1': [ '0', '0p', '0i', '0c', '0m', 'None', '', None ], 'spacing2': [ '0', '0p', '0i', '0c', '0m', 'None', '', None ], 'spacing3': [ '0', '0p', '0i', '0c', '0m', 'None', '', None ], 'tabs': [ '', None ], # Other Attributes 'underline': [ Tk.FALSE, '0', 'false', 'False', False, '', None ], 'overstrike': [ Tk.FALSE, '0', 'false', 'False', False, '', None ], 'foreground': [ aTextWidget[ 'foreground' ], '', None ], 'background': [ aTextWidget[ 'background' ], '', None ], 'fgstipple': [ '', 'none', None ], 'bgstipple': [ '', 'none', None ], 'borderwidth':[ '0', '0p', '0i', '0c', '0m', '', None ], 'relief': [ Tk.FLAT, '', None ] } @staticmethod def getStyle( aSpec=None, **options ): if aSpec: if isinstance( aSpec, str ): if aSpec.startswith(StylerFont.FONT_TAG_PREFIX): return StylerFont.getFont( aSpec ) elif aSpec.startswith(Style.ATTRIBUTE_TAG_PREFIX): key,value = aSpec.replace( '_',' ' ).split('-')[2:] theStyle = Style( ) theStyle[ key ] = value return theStyle if options: return Style( **options) class Styler( object ): ATTRIBUTE_TAG_FORMAT = '-attribute-%s-%s' # name, value ATTRIBUTE_TAG_PREFIX = '-attribute-' def __init__( self, aTextWidget ): self.text = aTextWidget Style.setup( self.text ) self.reinitizlize( ) def reinitizlize( self, styles=None ): self.text.delete( '1.0', Tk.END ) if styles: self._styles = styles else: self._styles = { } def applyStyleAttribute( self, index1, index2, attributeName, attributeValue ): '''Applies an attribute name:value pair to a given region indicated by index1, index2. Index1 is part of the region, index2 is not. Valid values for name and value are the same as those accepted by Style.__setitem__(). ''' try: index1 = self.text.index( index1 ) index2 = self.text.index( index2 ) except: return # Manipulate the styles and tags in the region if attributeName in ( 'family', 'size', 'weight', 'slant', 'offset', 'bold', 'italic' ): for beg, end, tagNameList in self.mapRegion( index1, index2 ): # Get the old font info (oldFontOpts) and delete the styling tag for oldTagName in tagNameList: if oldTagName.startswith( StylerFont.FONT_TAG_PREFIX ): currentFont = StylerFont.getFont( oldTagName ) self.text.tag_remove( oldTagName, beg, end ) break else: currentFont = StylerFont.getFont( ) # Calculating & Install new values newFont = currentFont.deriveFont( **{ attributeName:attributeValue } ) newTagName = newFont.fontName( ) self.text.tag_add( newTagName, beg, end ) self.text.tag_config( newTagName, **newFont.tagOptions() ) else: for beg, end, tagNameList in self.mapRegion( index1, index2 ): # If the attribute already exists, remove it. for oldTagName in tagNameList: if oldTagName.startswith( Styler.ATTRIBUTE_TAG_PREFIX + attributeName + '-' ): self.text.tag_remove( oldTagName, beg, end ) break # If we're just deactivating, then we're done if attributeValue in Style.OFF_VALUES[ attributeName ]: continue # Calculate the new values newTagName = Styler.ATTRIBUTE_TAG_FORMAT % ( attributeName, str(attributeValue) ) # Install the new values self.text.tag_add( newTagName, beg, end ) self.text.tag_config( newTagName, **{ attributeName : attributeValue } ) def tagNames( self, index1, index2=None ): tagNames = list(self.text.tag_names( index )) if 'sel' in styles: tagNames.remove( 'sel' ) if not index2: return tagNames for action, tagName, index in self.text.dump( '1.0', Tk.END, tag=True ): if action == 'tagon': tagNames.append( tagName ) # Rearrange the names into tag stack order result = [ ] for name in self.text.tag_names( ): if name in tagNames: result.append( name ) return result def attributesAtIndex( self, index ): index = self.text.index( index ) styles = self.tagNames( index ) tagOpts = { } fontOpts = { } for styleName in styles: if styleName.startswith( StylerFont.FONT_TAG_PREFIX ): font = StylerFont.getFont( styleName ) tagOpts.update( **font.tagOptions() ) fontOpts = font.fontOptions( ) else: attribStyleName = styleName.replace( '_', ' ' ) attribName, attribValue = attribStyleName.split( '-' )[ 2: ] tagOpts[ attribName ] = attribValue return tagOpts, fontOpts def mapRegion( self, index1, index2 ): '''This method returns a map of the styles for a given region. The result is a list of 'subregion' style descriptions of the form: ( beginIndex, endIndex, [ activeStyleNames ] ) The list is guaranteed complete. beginIndex of the first subregion description will always equal index1, and endIndex of the last subregion will always equal index2. Further, for any two adjacent subregion descriptions endIndex of the first will always equal beginIndex of the second. Thus no gaps occur in the map. ''' # Build a complete dump of the region index1 = self.text.index( index1 ) index2 = self.text.index( index2 ) theDump = self.text.dump( index1, index2, tag=True ) if len(theDump) == 0: return theDump # Consolidate the Dump finalReport = [ ] activeTags = list( self.text.tag_names( index1 ) ) if 'sel' in activeTags: activeTags.remove( 'sel' ) currentIndex = index1 for action, tag, index in theDump: if tag == 'sel': continue if index != currentIndex: finalReport.append( ( currentIndex, index, copy.copy(activeTags) ) ) currentIndex = index if action == 'tagon': if tag not in activeTags: activeTags.append( tag ) elif action == 'tagoff': activeTags.remove( tag ) if len(finalReport) == 0: finalReport = [ ( index1, index2, activeTags ) ] else: if finalReport[-1][1] != index2: finalReport.append( ( finalReport[-1][1], index2, copy.copy(activeTags) ) ) return finalReport class Edit( Tk.Frame ): NAME_COUNTER = 1 def __init__( self, parent, **options ): Tk.Frame.__init__( self, parent ) self.text = None self.styler = None self._images = { } self._windows = { } self._fgColorBtn = None self._bgColorBtn = None self._styleCombo = None self._currentFontName = Tk.StringVar( ) self._currentFontSize = Tk.StringVar( ) self._currentFontOffset = Tk.StringVar( ) self._currentFGStipple = Tk.StringVar( ) self._currentBGStipple = Tk.StringVar( ) self._currentBorder = Tk.StringVar( ) self._currentRelief = Tk.StringVar( ) self._currentJustify = Tk.StringVar( ) self._currentWrap = Tk.StringVar( ) self._currentLMargin1 = Tk.StringVar( ) self._currentLMargin2 = Tk.StringVar( ) self._currentRMargin = Tk.StringVar( ) self._currentSpacing1 = Tk.StringVar( ) self._currentSpacing2 = Tk.StringVar( ) self._currentSpacing3 = Tk.StringVar( ) self._currentTabs = Tk.StringVar( ) self._currentStyle = Tk.StringVar( ) self.buildGUI( **options ) def reinitialize( self, styles=None ): self.text.delete( '1.0', Tk.END ) self.styler.reinitizlize( styles ) # GUI Building def buildGUI( self, **options ): buildToolbars = True if 'toolbars' in options: buildToolbars = options['toolbars'] del options['toolbars'] # Build the main Text widget stxt = Tk.ScrolledText( self, scrollbar=Tk.AUTO ) self.text = stxt.subwidget( 'text' ) self.text.configure( **options ) self.styler = Styler( self.text ) # Build the toolbars if buildToolbars: tb1Frame = Tk.Frame( self, relief=Tk.RAISED, borderwidth=2 ) tb2Frame = Tk.Frame( self, relief=Tk.RAISED, borderwidth=2 ) paragraphFrame = self.paragraphAttributeToolbar( tb1Frame ) fontFrame = self.fontAttributeToolbar( tb2Frame ) objectFrame = self.objectToolbar( tb2Frame ) # Pack Everything paragraphFrame.pack( side=Tk.LEFT, fill=Tk.Y, padx=4, pady=2 ) fontFrame.pack( side=Tk.LEFT, fill=Tk.Y, padx=4, pady=2 ) objectFrame.pack( side=Tk.LEFT, fill=Tk.Y, padx=4, pady=2 ) tb1Frame.pack( side=Tk.TOP, fill=Tk.X, padx=2, pady=2 ) tb2Frame.pack( side=Tk.TOP, fill=Tk.X, padx=2, pady=2 ) stxt.pack( side=Tk.TOP, fill=Tk.BOTH ) def toolbarNames( self ): return [ 'fontAttributes', 'paragraphAttributes', 'objects' ] def fontAttributeToolbar( self, parent ): # Create the toolbar widgets fontFrame = Tk.Frame( parent, borderwidth=2, relief=Tk.GROOVE ) fontFamilyCombo = Tk.ComboBox( fontFrame, editable=False, dropdown=True, fancy=False, variable=self._currentFontName, history=False, selectmode=Tk.BROWSE, command=self.onFamilyChosen ) fontFamilyCombo.subwidget('listbox').config( font=DIALOG_FONT, width=30, height=20 ) fontFamilyCombo.subwidget('entry'). config( font=DIALOG_FONT ) fontFamilyCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) fontSizeCombo = Tk.ComboBox( fontFrame, editable=False, dropdown=True, fancy=False, variable=self._currentFontSize, history=False, selectmode=Tk.BROWSE, command=self.onSizeChosen ) fontSizeCombo.subwidget('listbox').config( font=DIALOG_FONT, width=3 ) fontSizeCombo.subwidget('entry'). config( font=DIALOG_FONT, width=3 ) fontSizeCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( fontFrame, relief=Tk.GROOVE, text='B', font=DIALOG_FONT + ' bold', command=self.onBold ).pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( fontFrame, relief=Tk.GROOVE, text='I', font=DIALOG_FONT + ' italic', command=self.onItalic ).pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( fontFrame, relief=Tk.GROOVE, text='U', font=DIALOG_FONT + ' underline',command=self.onUnderline ).pack(side=Tk.LEFT, padx=2, pady=2) Tk.Button( fontFrame, relief=Tk.GROOVE, text='O', font=DIALOG_FONT + ' overstrike',command=self.onOverstrike ).pack(side=Tk.LEFT, padx=2, pady=2) offsetCombo = Tk.ComboBox( fontFrame, editable=False, dropdown=True, fancy=False, variable=self._currentFontOffset, history=False, selectmode=Tk.BROWSE, command=self.onOffsetChosen ) offsetCombo.subwidget('listbox').config( font=DIALOG_FONT, width=9 ) offsetCombo.subwidget('entry'). config( font=DIALOG_FONT, width=9 ) offsetCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) self._fgColorBtn = Tk.Button( fontFrame, relief=Tk.GROOVE, text='A', font=DIALOG_FONT + ' bold', command=self.onChangeFG ) self._fgColorBtn.pack( side=Tk.LEFT, padx=2, pady=2 ) self._bgColorBtn = Tk.Button( fontFrame, relief=Tk.GROOVE, text='A', font=DIALOG_FONT + ' bold', command=self.onChangeBG ) self._bgColorBtn.pack( side=Tk.LEFT, padx=2, pady=2 ) fgStippleCombo = Tk.ComboBox( fontFrame, editable=False, label='fg', dropdown=True, fancy=False, variable=self._currentFGStipple, history=False, selectmode=Tk.BROWSE, command=self.onFGStippleChosen ) fgStippleCombo.subwidget('listbox').config( font=DIALOG_FONT, width=7 ) fgStippleCombo.subwidget('entry'). config( font=DIALOG_FONT, width=7 ) fgStippleCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) bgStippleCombo = Tk.ComboBox( fontFrame, editable=False, label='bg', dropdown=True, fancy=False, variable=self._currentBGStipple, history=False, selectmode=Tk.BROWSE, command=self.onBGStippleChosen ) bgStippleCombo.subwidget('listbox').config( font=DIALOG_FONT, width=7 ) bgStippleCombo.subwidget('entry'). config( font=DIALOG_FONT, width=7 ) bgStippleCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Label( fontFrame, text='border' ).pack( side=Tk.LEFT, padx=2, pady=2 ) le = Tk.Entry( fontFrame, width=4, textvariable=self._currentBorder ) le.bind( '', self.onBorderChange ) le.pack( side=Tk.LEFT, padx=2, pady=2 ) reliefCombo = Tk.ComboBox( fontFrame, editable=False, dropdown=True, fancy=False, variable=self._currentRelief, history=False, selectmode=Tk.BROWSE, command=self.onReliefChosen ) reliefCombo.subwidget('listbox').config( font=DIALOG_FONT, width=7 ) reliefCombo.subwidget('entry'). config( font=DIALOG_FONT, width=7 ) reliefCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) # Populate the widgets familyList = list(Font.families( )) familyList.sort() for family in familyList: if family[0] == '@': continue fontFamilyCombo.insert( Tk.END, family ) self._currentFontName.set( StylerFont.DEFAULT_FONT['family'] ) for size in range( 6,31 ): fontSizeCombo.insert( Tk.END, str(size) ) self._currentFontSize.set( str(StylerFont.DEFAULT_FONT['size']) ) for offset in [ 'normal', 'superscript', 'subscript' ]: offsetCombo.insert( Tk.END, offset ) self._currentFontOffset.set( 'normal' ) for stippling in [ 'gray12', 'gray25', 'gray50', 'gray75' ]: fgStippleCombo.insert( Tk.END, stippling ) bgStippleCombo.insert( Tk.END, stippling ) for relief in [ 'flat', 'sunken', 'raised', 'groove', 'ridge', 'solid' ]: reliefCombo.insert( Tk.END, relief ) self._fgColorBtn.config( foreground=self.text['foreground'] ) self._bgColorBtn.config( background=self.text['background'] ) return fontFrame def paragraphAttributeToolbar( self, parent ): # Create the toolbar widgets paragraphFrame = Tk.Frame( parent, borderwidth=2, relief=Tk.GROOVE ) justifyCombo = Tk.ComboBox( paragraphFrame, editable=False, dropdown=True, fancy=False, variable=self._currentJustify, history=False, selectmode=Tk.BROWSE, command=self.onJustifyChosen ) justifyCombo.subwidget('listbox').config( font=DIALOG_FONT, width=5 ) justifyCombo.subwidget('entry'). config( font=DIALOG_FONT, width=5 ) justifyCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) wrapCombo = Tk.ComboBox( paragraphFrame, editable=False, dropdown=True, fancy=False, variable=self._currentWrap, history=False, selectmode=Tk.BROWSE, command=self.onWrapChosen ) wrapCombo.subwidget('listbox').config( font=DIALOG_FONT, width=5 ) wrapCombo.subwidget('entry'). config( font=DIALOG_FONT, width=5 ) wrapCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) le = Tk.LabelEntry( paragraphFrame, label='L-Margin1' ) le.label.configure( font=DIALOG_FONT ) le.entry.configure( width=4, textvariable=self._currentLMargin1 ) le.entry.bind( '', self.onLMargin1Change ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) le = Tk.LabelEntry( paragraphFrame, label='L-Margin2' ) le.label.configure( font=DIALOG_FONT ) le.entry.configure( width=4, textvariable=self._currentLMargin2 ) le.entry.bind( '', self.onLMargin2Change ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) le = Tk.LabelEntry( paragraphFrame, label='R-Margin' ) le.label.configure( font=DIALOG_FONT ) le.entry.configure( width=4, textvariable=self._currentRMargin ) le.entry.bind( '', self.onRMarginChange ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) le = Tk.LabelEntry( paragraphFrame, label='Spacing1' ) le.label.configure( font=DIALOG_FONT ) le.entry.configure( width=4, textvariable=self._currentSpacing1 ) le.entry.bind( '', self.onSpacing1Change ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) le = Tk.LabelEntry( paragraphFrame, label='Spacing2' ) le.label.configure( font=DIALOG_FONT ) le.entry.configure( width=4, textvariable=self._currentSpacing2 ) le.entry.bind( '', self.onSpacing2Change ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) le = Tk.LabelEntry( paragraphFrame, label='Spacing3' ) le.label.configure( font=DIALOG_FONT ) le.entry.configure( width=4, textvariable=self._currentSpacing3 ) le.entry.bind( '', self.onSpacing3Change ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) le = Tk.LabelEntry( paragraphFrame, label='Tabs' ) le.label.configure( font=DIALOG_FONT ) le.entry.configure( textvariable=self._currentTabs ) le.entry.bind( '', self.onTabChange ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) Tk.Button( paragraphFrame, font=DIALOG_FONT, text='Apply', command=self.onApplyParagraph ).pack( side=Tk.LEFT, padx=5, pady=5 ) # Populate the widgets for justify in [ Tk.LEFT, Tk.CENTER, Tk.RIGHT ]: justifyCombo.insert( Tk.END, justify ) self._currentJustify.set( Tk.LEFT ) for wrap in [ Tk.NONE, Tk.CHAR, Tk.WORD ]: wrapCombo.insert( Tk.END, wrap ) self._currentWrap.set( self.text[ 'wrap' ] ) return paragraphFrame def objectToolbar( self, parent ): # Create the toolbar widgets objectFrame = Tk.Frame( parent, borderwidth=2, relief=Tk.GROOVE ) Tk.Button( objectFrame, font=DIALOG_FONT, text='image', command=self.onInsertImage ).pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( objectFrame, font=DIALOG_FONT, text='list', command=self.onInsertList ).pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( objectFrame, font=DIALOG_FONT, text='table', command=self.onInsertTable ).pack( side=Tk.LEFT, padx=2, pady=2 ) return objectFrame # Content def insert_image( self, index, filename=None, **options ): if filename: theImage = ImageFactory.makeImage( filename ) if filename not in self._images: self._images[ filename ] = Tk.PhotoImage( file=filename ) options[ 'image' ] = self._images[ filename ] if 'name' in options: theImageName = options[ 'name' ] else: theImageName = self._generateImageName( ) options[ 'name' ] = theImageName self._images[ theImageName ] = [ filename, options ] self.text.image_create( index, **options ) #self.text.tag_config( theImageName, offset='-3i' ) return theImageName def delete( self, index1, index2=None ): self.text.delete( index1, index2 ) def image_configure( self, imageName, **options ): self._image[ imageName ][1].update( options ) self.text.image_configure( imageName, **options ) def image_configuration( self, index ): return self.text.image_configure( ) def image_cget( self, index, option ): return self.text.image_cget( index, option ) def image_names( self ): return self.text.image_names( ) def getModel( self ): content = self.text.dump( '1.0', Tk.END ) styles = self.styler.styles( ) images = self._images windows = self._windows return content,styles,images,windows def setModel( self, content, styles=None, images=None, windows=None ): self.reinitialize( ) if images: self._images = images if isinstance( content, (str,unicode) ): self.insert( '1.0', content ) else: tags = { } for element in content: action = element[0] value = element[1] index = self.text.index( Tk.INSERT ) if action == 'tagon': if value == 'sel': continue tags[ value ] = index elif action == 'tagoff': if value == 'sel': continue elif value not in tags: raise Exception( 'tagoff not preceded by tagon.' ) regionBegin = tags[ value ] regionEnd = index tagName = value try: newTagOpts = self.styler.styleDefinition( tagName ) except: newStyle = Style.getStyle( tagName ) self.text.tag_add( tagName, regionBegin, regionEnd ) self.text.tag_config( tagName, **newStyle.tagOptions() ) self.text.tag_add( tagName, regionBegin, regionEnd ) del tags[ value ] elif action == 'text': self.text.insert( Tk.END, value ) elif action == 'mark': if value in ( Tk.INSERT, Tk.CURRENT, Tk.SEL, Tk.SEL_FIRST, Tk.SEL_LAST, Tk.END, Tk.ANCHOR ): continue self.text.mark_set( index, value ) elif action == 'image': imageName = value filename,options = self._images[ imageName ] if ('name' in options) and options['name'].startswith( '_image_' ): del options['name'] self.insert_image( Tk.END, filename, **options ) elif action == 'window': pass else: raise Exception( 'Unknown Action.' ) def _generateImageName( self ): imageNameTemplate = '_image_%d' self.NAME_COUNTER += 1 return imageNameTemplate % self.NAME_COUNTER # Event Handling def onFamilyChosen( self, familyName ): family = self._currentFontName.get( ) if family and (family != ''): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'family', family ) def onSizeChosen( self, size ): size = self._currentFontSize.get( ) if size and (size != ''): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'size', size ) def onBold( self ): tagOpts, fontOpts = self.styler.attributesAtIndex( Tk.SEL_FIRST ) try: if fontOpts[ 'weight' ] == Font.BOLD: newValue = Font.NORMAL else: newValue = Font.BOLD except: newValue = Font.BOLD self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'weight', newValue ) def onItalic( self ): tagOpts, fontOpts = self.styler.attributesAtIndex( Tk.SEL_FIRST ) try: if fontOpts[ 'slant' ] == Font.ITALIC: newValue = Font.ROMAN else: newValue = Font.ITALIC except: newValue = Font.ITALIC self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'slant', newValue ) def onUnderline( self ): tagOpts,fontOpts = self.styler.attributesAtIndex( Tk.SEL_FIRST ) try: newValue = tagOpts[ 'underline' ] if newValue in [ True, '1', 'true', 'True' ]: newValue = False else: newValue = True except: newValue = True self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'underline', newValue ) def onOverstrike( self ): tagOpts, fontOpts = self.styler.attributesAtIndex( Tk.SEL_FIRST ) try: newValue = tagOpts[ 'overstrike' ] if newValue in [ True, '1', 'true', 'True' ]: newValue = False else: newValue = True except: newValue = True self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'overstrike', newValue ) def onOffsetChosen( self, offset ): if offset == '': return try: index1 = self.text.index( 'sel.first' ) index2 = self.text.index( 'sel.last' ) except: return self.styler.applyStyleAttribute( index1, index2, 'offset', offset ) def onFGStippleChosen( self, stipple=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'fgstipple', self._currentFGStipple.get() ) def onBGStippleChosen( self, stipple ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'bgstipple', self._currentBGStipple.get() ) def onBorderChange( self, border ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'borderwidth', self._currentBorder.get() ) def onReliefChosen( self, border ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'relief', self._currentRelief.get() ) def onLMargin1Change( self, value=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'lmargin1', self._currentLMargin1.get() ) def onLMargin2Change( self, value=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'lmargin2', self._currentLMargin2.get() ) def onRMarginChange( self, value=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'rmargin', self._currentRMargin.get() ) def onSpacing1Change( self, value=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'spacing1', self._currentSpacing1.get() ) def onSpacing2Change( self, value=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'spacing2', self._currentSpacing2.get() ) def onSpacing3Change( self, value=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'spacing3', self._currentSpacing3.get() ) def onTabChange( self, value=None ): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'tabs', self._currentTabs.get() ) def onApplyParagraph( self ): self.onLMargin1Change( ) self.onLMargin2Change( ) self.onRMarginChange( ) self.onSpacing1Change( ) self.onSpacing2Change( ) self.onSpacing3Change( ) self.onTabChange( ) def onChangeFG( self ): import tkColorChooser newColor = tkColorChooser.askcolor( parent=self )[1] if newColor: self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'foreground', newColor ) def onChangeBG( self ): import tkColorChooser newColor = tkColorChooser.askcolor( parent=self )[1] if newColor: self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'background', newColor ) def onJustifyChosen( self, justify ): justify = self._currentJustify.get( ) if justify and (justify != ''): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'justify', justify ) def onWrapChosen( self, wrap ): wrap = self._currentWrap.get( ) if wrap and (wrap != ''): self.styler.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'wrap', wrap ) def onInsertImage( self ): import tkFileDialog import pickle filename = tkFileDialog.askopenfilename( parent=self, filetypes=[ ( 'GIF Image', '*.gif' ) ] ) if not filename or (filename == ''): return self.insert_image( Tk.INSERT, filename=filename ) def onInsertList( self ): pass def onInsertTable( self ): pass class WP( Tk.Tk ): def __init__( self ): Tk.Tk.__init__( self ) self._toolBar = Tk.Frame( self ) self._openBtn = Tk.Button( self._toolBar, text='open', command=self.onOpen ) self._openBtn.pack( side=Tk.LEFT ) self._saveBtn = Tk.Button( self._toolBar, text='save', command=self.onSave ) self._saveBtn.pack( side=Tk.LEFT ) self._text = Edit( self, font=DEFAULT_TEXT_FONT, toolbars=True ) self._toolBar.pack( side=Tk.TOP, fill=Tk.X ) self._text.pack( side=Tk.TOP, fill=Tk.X ) def onOpen( self ): import tkFileDialog import pickle filename = tkFileDialog.askopenfilename( parent=self ) if not filename or (filename == ''): return self._filename = filename theFile = file( filename, 'r' ) docData = pickle.load( theFile ) if isinstance( docData, (str,unicode) ): self._text.setModel( docData ) else: self._text.setModel( *docData ) def onSave( self ): import tkFileDialog import pickle filename = tkFileDialog.asksaveasfilename( parent=self ) if not filename or (filename == ''): return theFile = file( filename, 'w' ) docData = self._text.getModel( ) pickle.dump( docData, theFile ) if __name__=='__main__': wp = WP( ) wp.mainloop( )