import Tix as Tk import tkFont import tkSimpleDialog import copy DIALOG_FONT = 'Times 8' DEFAULT_TEXT_FONT = ('Lucida Sans Unicode', 12) class Font( object ): FIELD_NAMES = [ 'family', 'size', 'weight', 'slant', 'offset', 'bold', 'italic' ] TAG_NAME_FORMAT = '#font#%(family)s#%(size)d#%(weight)s#%(slant)s#%(offset)s' TAG_NAME_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 ): try: return self._fontOpts[ key ] except: if key == 'bold': try: return self._fontOpts[ 'weight' ] == 'bold' except: return self.DEFAULT_FONT[ 'weight' ] == 'bold' elif key == 'italic': try: return self._fontOpts[ 'slant' ] == 'italic' except: return self.DEFAULT_FONT[ 'slant' ] == 'italic' else: return self.DEFAULT_FONT[ key ] def fontName( self ): return Font.TAG_NAME_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 Font.FONT_LIBRARY: Font.FONT_LIBRARY[ fontName ] = newFont return Font.FONT_LIBRARY[ fontName ] def tkFont( self ): return tkFont.Font( family=self._fontOpts['family'], size=self._fontOpts['size'], weight=self._fontOpts['weight'], slant=self._fontOpts['slant'] ) @staticmethod def setup( aTextWidget ): Font.DEFAULT_FONT = Font.getFont( aTextWidget['font'] ) Font.FONT_LIBRARY[ 'default' ] = Font.DEFAULT_FONT @staticmethod def getFont( aFontSpec=None, **options ): fontOpts = { } fontStyling = '' if aFontSpec: if isinstance( aFontSpec, (str,unicode) ): if aFontSpec == 'default': return Font.FONT_LIBRARY[ 'default' ] elif aFontSpec.startswith( Font.TAG_NAME_PREFIX ): # We have a font name try: return Font.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']) Font.FONT_LIBRARY[ aFontSpec ] = Font( fontOpts ) return Font.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 = Font.TAG_NAME_FORMAT % options try: return Font.FONT_LIBRARY[ fontName ] except: Font.FONT_LIBRARY[ fontName ] = Font( options ) return Font.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 Font.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 = Font.TAG_NAME_FORMAT % fontOpts try: return Font.FONT_LIBRARY[ fontName ] except: theFontObj = Font( fontOpts ) Font.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' ] TAG_NAME_FORMAT = '#attribute#%(name)s#%(value)s' TAG_NAME_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 Font subobject. (e.g. family, may be requested of a style instance and will be passed to the Font 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: Font 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, rawInit=False, **options ): dict.__init__( self ) self._font = None for key,value in options.iteritems( ): if key == 'font': if isinstance( value, Font ): self._font = value else: self._font = Font( value ) #elif rawInit: else: dict.__setitem__( self, key, value ) #else: #self[ key ] = value def __setitem__( self, key, value ): if key in Font.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, Font ): self._font = value else: self._font = Font.getFont( value ) else: dict.__setitem__( self, key, value ) def __getitem__( self, key ): if key in Font.FIELD_NAMES: return self._font[ key ] elif key == 'font': return self._font elif key in self: return dict.__getitem__( self, key ) #elif key in Style.DEFAULT_STYLE: #return Style.DEFAULT_STYLE[ key ] else: raise KeyError( key ) 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 ): Font.setup( aTextWidget ) Style.DEFAULT_STYLE = Style( rawInit = True, font = Font.DEFAULT_FONT, underline = False, overstrike = False, 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': [ Font.DEFAULT_FONT['family'] ], 'size': [ Font.DEFAULT_FONT['size' ] ], 'weight': [ tkFont.NORMAL, None ], 'slant': [ tkFont.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(Font.TAG_NAME_PREFIX): return Font.getFont( aSpec ) elif aSpec.startswith(Style.TAG_NAME_PREFIX): key,value = aSpec.replace( '_',' ' ).split('#')[2:] theStyle = Style( ) theStyle[ key ] = value return theStyle if options: return Style( **options) class StyleEditor( tkSimpleDialog.Dialog ): SAMPLE_TEXT = '''This is some sample text for the style widget. The style of this text is what is configured for the style named at the top of this widget. The quick brown fox jumped over the lazy dog. This is a second paragraph. The next paragraph will demonstrate tabs. 1\t2\t3\t4\t5\t6\t7\t8\t9\t0 ''' SAMPLE_TAG = 'sampleTag' def __init__( self, styler ): # Fields self._state = 'enabled' self._stext = styler self._currentStylerFont = None # Tk Variables self._currentStyleName = Tk.StringVar( value='' ) self._fontFamily = Tk.StringVar( ) self._fontSize = Tk.StringVar( ) self._bold = Tk.BooleanVar( ) self._italic = Tk.BooleanVar( ) self._underline = Tk.BooleanVar( ) self._overstrike = Tk.BooleanVar( ) self._background = Tk.StringVar( ) self._foreground = Tk.StringVar( ) self._offset = Tk.StringVar( ) self._fgstipple = Tk.StringVar( ) self._bgstipple = Tk.StringVar( ) self._borderwidth = Tk.StringVar( ) self._relief = Tk.StringVar( ) self._justify = Tk.StringVar( ) self._wrap = Tk.StringVar( ) self._lmargin1 = Tk.StringVar( ) self._lmargin2 = Tk.StringVar( ) self._rmargin = Tk.StringVar( ) self._spacing1 = Tk.StringVar( ) self._spacing2 = Tk.StringVar( ) self._spacing3 = Tk.StringVar( ) self._tabs = Tk.StringVar( ) # Widgets self._stylesCombo = None self._bgButton = None self._fgButton = None self._sample = None self.updateGUIControls( Style.DEFAULT_STYLE ) tkSimpleDialog.Dialog.__init__( self, styler.winfo_toplevel(), 'Style Editor' ) def body( self, parent ): parent.winfo_toplevel().wm_resizable( False, True ) parent.option_add( '*font', DIALOG_FONT ) self._setGUIState( 'disabled' ) styleFrame = Tk.Frame( parent ) self._stylesCombo = Tk.ComboBox( styleFrame, label='Style', editable=True, dropdown=True, fancy=False, variable=self._currentStyleName, history=False, command=self.onSelectedStyle, selectmode=Tk.IMMEDIATE ) self._stylesCombo.pack( side=Tk.LEFT, padx=5, pady=5 ) Tk.Button( styleFrame, text='Commit', command=self.commit ).pack( side=Tk.LEFT, padx=5, pady=5 ) Tk.Button( styleFrame, text='Revert', command=self.onRevertStyle ).pack( side=Tk.LEFT, padx=5, pady=5 ) Tk.Button( styleFrame, text='Delete', command=self.onDeleteStyle ).pack( side=Tk.LEFT, padx=5, pady=5 ) Tk.Button( styleFrame, text='Save', command=self.onSaveStyles ).pack( side=Tk.LEFT, padx=5, pady=5 ) Tk.Button( styleFrame, text='Load', command=self.onLoadStyles ).pack( side=Tk.LEFT, padx=5, pady=5 ) styleFrame.grid( row=0, column=0, columnspan=2 ) # Text Styling textFrame = Tk.LabelFrame( parent, label='Text Styling', background='SystemButtonFace', labelside=Tk.ACROSSTOP, padx=5, pady=5 ) row = 0 Tk.Label( textFrame.frame, text='Font:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) fontCombo = Tk.ComboBox( textFrame.frame, editable=True, dropdown=True, fancy=False, variable=self._fontFamily, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) fontCombo.entry.config( ) fontCombo.grid( row=row, column=1, columnspan=2, padx=5, pady=5, sticky=Tk.W ) sizeCombo = Tk.ComboBox( textFrame.frame, editable=True, dropdown=True, fancy=False, variable=self._fontSize, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) sizeCombo.entry.config( width=5 ) sizeCombo.grid( row=row, column=3, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( textFrame.frame, text='Styles:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) Tk.Checkbutton( textFrame.frame, text='Bold', font=DIALOG_FONT + ' bold', variable=self._bold, command=self.onApplyEdits ).grid( row=row, column=1, padx=5, pady=5, sticky=Tk.W ) Tk.Checkbutton( textFrame.frame, text='Italic', font=DIALOG_FONT + ' italic', variable=self._italic, command=self.onApplyEdits ).grid( row=row, column=2, padx=5, pady=5, sticky=Tk.W ) Tk.Checkbutton( textFrame.frame, text='Under', font=DIALOG_FONT + ' underline', variable=self._underline, command=self.onApplyEdits ).grid( row=row, column=3, padx=5, pady=5, sticky=Tk.W ) Tk.Checkbutton( textFrame.frame, text='Over', font=DIALOG_FONT + ' overstrike', variable=self._overstrike, command=self.onApplyEdits ).grid( row=row, column=4, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( textFrame.frame, text='Offset:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( textFrame.frame, text='Normal', variable=self._offset, value='normal', command=self.onApplyEdits ).grid( row=row, column=1, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( textFrame.frame, text='Superscript', variable=self._offset, value='superscript', command=self.onApplyEdits ).grid( row=row, column=2, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( textFrame.frame, text='Subscript', variable=self._offset, value='subscript', command=self.onApplyEdits ).grid( row=row, column=3, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( textFrame.frame, text='Pen:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) colorFrame = Tk.Frame( textFrame.frame ) Tk.Label( colorFrame, text='Foreground' ).grid( row=0, column=1, padx=5, pady=5 ) Tk.Label( colorFrame, text='Background' ).grid( row=0, column=2, padx=5, pady=5 ) Tk.Label( colorFrame, text='Color', anchor=Tk.W ).grid( row=1, column=0, padx=5, pady=5, sticky=Tk.W ) self._fgButton = Tk.Button( colorFrame, text=' '*20, background=self._foreground.get(), command=self.onChooseFGColor ) self._fgButton.grid( row=1, column=1, padx=5, pady=5 ) self._bgButton = Tk.Button( colorFrame,text=' '*20, background=self._background.get(), command=self.onChooseBGColor ) self._bgButton.grid( row=1, column=2, padx=5, pady=5 ) Tk.Label( colorFrame, text='Stipple', anchor=Tk.W ).grid( row=2, column=0, padx=5, pady=5, sticky=Tk.W ) fgStippleCombo = Tk.ComboBox( colorFrame, editable=True, dropdown=True, fancy=False, variable=self._fgstipple, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) fgStippleCombo.entry.config( width=10 ) fgStippleCombo.grid( row=2, column=1, padx=5, pady=5 ) bgStippleCombo = Tk.ComboBox( colorFrame, editable=True, dropdown=True, fancy=False, variable=self._bgstipple, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) bgStippleCombo.entry.config( width=10 ) bgStippleCombo.grid( row=2, column=2, padx=5, pady=5 ) colorFrame.grid( row=row, column=1, columnspan=4, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( textFrame.frame, text='Border:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) bdwidthCombo = Tk.ComboBox( textFrame.frame, label='Width', editable=True, dropdown=True, fancy=False, variable=self._borderwidth, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) bdwidthCombo.entry.config( width=5 ) bdwidthCombo.grid( row=row, column=1, columnspan=2, padx=5, pady=5, sticky=Tk.W ) reliefCombo = Tk.ComboBox( textFrame.frame, label='Relief', editable=True, dropdown=True, fancy=False, variable=self._relief, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) reliefCombo.entry.config( width=10 ) reliefCombo.grid(row=row, column=3, columnspan=2, padx=5, pady=5, sticky=Tk.W ) textFrame.grid( row=1, column=0, padx=2, pady=2, sticky='nsew' ) # Paragraph Styling paragraphFrame = Tk.LabelFrame( parent, label='Paragraph Styling', background='SystemButtonFace', labelside=Tk.ACROSSTOP, padx=5, pady=5 ) row = 0 Tk.Label( paragraphFrame.frame, text='Justify:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( paragraphFrame.frame, text='Left', variable=self._justify, value=Tk.LEFT, command=self.onApplyEdits ).grid( row=row, column=1, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( paragraphFrame.frame, text='Center', variable=self._justify, value=Tk.CENTER, command=self.onApplyEdits ).grid( row=row, column=2, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( paragraphFrame.frame, text='Right', variable=self._justify, value=Tk.RIGHT, command=self.onApplyEdits ).grid( row=row, column=3, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( paragraphFrame.frame, text='Wrap:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( paragraphFrame.frame, text='None', variable=self._wrap, value=Tk.NONE, command=self.onApplyEdits ).grid( row=row, column=1, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( paragraphFrame.frame, text='Char', variable=self._wrap, value=Tk.CHAR, command=self.onApplyEdits ).grid( row=row, column=2, padx=5, pady=5, sticky=Tk.W ) Tk.Radiobutton( paragraphFrame.frame, text='Word', variable=self._wrap, value=Tk.WORD, command=self.onApplyEdits ).grid( row=row, column=3, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( paragraphFrame.frame, text='Margins:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) le = Tk.LabelEntry( paragraphFrame.frame, label='L-Margin1' ) le.label.configure( anchor=Tk.W ) le.entry.configure( textvariable=self._lmargin1, width=4 ) le.entry.bind( '', self.onApplyEdits ) le.grid( row=row, column=1, columnspan=2, padx=5, pady=5, sticky=Tk.W ) le = Tk.LabelEntry( paragraphFrame.frame, label='R-Margin' ) le.label.configure( anchor=Tk.W ) le.entry.configure( textvariable=self._rmargin, width=4 ) le.entry.bind( '', self.onApplyEdits ) le.grid( row=row, column=3, columnspan=2, padx=5, pady=5, sticky=Tk.W ) row += 1 le = Tk.LabelEntry( paragraphFrame.frame, label='L-Margin2' ) le.label.configure( anchor=Tk.W ) le.entry.configure( textvariable=self._lmargin2, width=4 ) le.entry.bind( '', self.onApplyEdits ) le.grid( row=row, column=1, columnspan=2, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( paragraphFrame.frame, text='Spacing:', anchor=Tk.W ).grid( row=row, column=0, padx=5, pady=5, sticky=Tk.W ) Tk.Label( paragraphFrame.frame, text=u'Before \u00B6', anchor=Tk.W ).grid( row=row, column=1, padx=5, pady=5, sticky=Tk.W ) spacing1Combo = Tk.ComboBox( paragraphFrame.frame, editable=True, dropdown=True, fancy=False, variable=self._spacing1, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) spacing1Combo.entry.config( width=10 ) spacing1Combo.grid( row=row, column=2, padx=5, pady=5, sticky=Tk.W ) Tk.Label( paragraphFrame.frame, text=u'After \u00B6', anchor=Tk.W ).grid( row=row, column=3, padx=5, pady=5, sticky=Tk.W ) spacing3Combo = Tk.ComboBox( paragraphFrame.frame, editable=True, dropdown=True, fancy=False, variable=self._spacing3, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) spacing3Combo.entry.config( width=10 ) spacing3Combo.grid( row=row, column=4, padx=5, pady=5, sticky=Tk.W ) row += 1 Tk.Label( paragraphFrame.frame, text='Lines', anchor=Tk.W ).grid( row=row, column=1, padx=5, pady=5, sticky=Tk.W ) spacing2Combo = Tk.ComboBox( paragraphFrame.frame, editable=True, dropdown=True, fancy=False, variable=self._spacing2, history=False, command=self.onApplyEdits, selectmode=Tk.IMMEDIATE ) spacing2Combo.entry.config( width=10 ) spacing2Combo.grid( row=row, column=2, padx=5, pady=5, sticky=Tk.W ) row += 1 le = Tk.Label( paragraphFrame.frame, text='Tabs', anchor=Tk.W ).grid( row=row, column=1, padx=5, pady=5, sticky=Tk.W ) le = Tk.Entry( paragraphFrame.frame, width=20, textvariable=self._tabs ) le.bind( '', self.onApplyEdits ) le.grid( row=row, column=2, columnspan=2, padx=5, pady=5, sticky=Tk.W ) paragraphFrame.grid( row=1,column=1, padx=2, pady=2, sticky='nsew' ) self._sample = StyledText( parent, styles=self._stext.styles(), font=DEFAULT_TEXT_FONT, height=15 ) self._sample.grid( row=2, column=0, columnspan=2, padx=5, pady=5, sticky='nsew' ) # Initialize the widgets # Pack style names into the combo box styleNameList = self._stext.styles().keys() styleNameList.sort( ) for name in styleNameList: self._stylesCombo.insert( Tk.END, name ) # Populate the font family combo box familyList = list(tkFont.families( )) familyList.sort() for family in familyList: if family[0] == '@': continue fontCombo.insert( Tk.END, family ) # Populate the font size combo box for size in xrange( 6,35 ): sizeCombo.insert( Tk.END, '%d' % size ) # Pack Stiple boxes for value in [ '', 'gray12', 'gray25', 'gray50', 'gray75' ]: fgStippleCombo.insert( Tk.END, value ) bgStippleCombo.insert( Tk.END, value ) # Pack Borderwidth box for width in range( 0, 10 ): bdwidthCombo.insert( Tk.END, '%d' % width ) # Pack Border relief box for relief in [ 'flat', 'raised', 'sunken', 'groove', 'ridge', 'solid' ]: reliefCombo.insert( Tk.END, relief ) for value in [ 'None', 'Half Line', 'One Line', 'Two Lines' ]: spacing1Combo.insert( Tk.END, value ) spacing2Combo.insert( Tk.END, value ) spacing3Combo.insert( Tk.END, value ) # Setup the sample text self._sample.text.insert( '1.0', StyleEditor.SAMPLE_TEXT ) self._sample.applyStyle( '1.0', Tk.END, StyleEditor.SAMPLE_TAG ) self._sample.text.config( state=Tk.DISABLED ) self.updateStyleList( ) self.updateGUIControls( ) self.updateSample( ) self._setGUIState( 'enabled' ) # Transdfer Data def update( self, makeThisStyleCurrent=None ): self.updateCurrentFontInfo( makeThisStyleCurrent ) self.updateStyleList( ) self.updateGUIControls( aStyle ) self.updateSample( ) def commit( self ): newStyleName = self._currentStyleName.get() if newStyleName and (newStyleName != ''): self._sample.defineStyle( newStyleName, self.makeStyleObj() ) self.updateStyleList( makeThisStyleCurrent=newStyleName ) def updateStyleList( self, makeThisStyleCurrent=None ): self._stylesCombo.configure( state='disabled' ) self._stylesCombo.subwidget('listbox').delete( '0', Tk.END ) if StyleEditor.SAMPLE_TAG in self._sample.styles(): del self._sample.styles()[ StyleEditor.SAMPLE_TAG ] styleNameList = self._sample.styles().keys( ) styleNameList.sort( ) activate = 0 for index, name in enumerate( styleNameList ): self._stylesCombo.insert( Tk.END, name ) if makeThisStyleCurrent and (name == makeThisStyleCurrent): activate = index if self._stylesCombo.subwidget('listbox').size() > 0: self._stylesCombo.pick( activate ) self._stylesCombo.configure( state='normal' ) def updateGUIControls( self, aStyle=None ): self._state = 'disabled' styleName = self._currentStyleName.get( ) if aStyle: theStyle = aStyle elif styleName and (styleName != ''): theStyle = self._sample.styleDefinition( styleName ) else: theStyle = Style.DEFAULT_STYLE self._currentStylerFont = theStyle[ 'font' ] self._fontFamily.set( self._currentStylerFont[ 'family' ] ) self._fontSize.set( self._currentStylerFont[ 'size' ] ) self._bold.set( True if theStyle[ 'bold' ] else False) self._italic.set( True if theStyle[ 'italic' ] else False ) self._underline.set( True if theStyle[ 'underline' ] else False ) self._overstrike.set( True if theStyle[ 'overstrike' ] else False ) self._background.set( theStyle[ 'background' ] ) self._foreground.set( theStyle[ 'foreground' ] ) self._offset.set( theStyle[ 'offset' ] ) self._fgstipple.set( theStyle[ 'fgstipple' ] ) self._bgstipple.set( theStyle[ 'bgstipple' ] ) self._borderwidth.set(theStyle[ 'borderwidth'] ) self._relief.set( theStyle[ 'relief' ] ) self._justify.set( theStyle[ 'justify' ] ) self._wrap.set( theStyle[ 'wrap' ] ) self._lmargin1.set( theStyle[ 'lmargin1' ] ) self._lmargin2.set( theStyle[ 'lmargin2' ] ) self._rmargin.set( theStyle[ 'rmargin' ] ) self._spacing1.set( theStyle[ 'spacing1' ] ) self._spacing2.set( theStyle[ 'spacing2' ] ) self._spacing3.set( theStyle[ 'spacing3' ] ) self._tabs.set( theStyle[ 'tabs' ] ) if self._fgButton: self._fgButton.config( background=theStyle[ 'foreground' ] ) self._bgButton.config( background=theStyle[ 'background' ] ) self._state = 'enabled' def updateSample( self ): if self._sample: self._sample.defineStyle( StyleEditor.SAMPLE_TAG, self.makeStyleObj() ) def makeStyleObj( self ): try: return Style( font = copy.deepcopy( self._currentStylerFont ), underline = True if self._underline.get( ) else False, overstrike = True if self._overstrike.get( ) else False, background = self._background.get( ), foreground = self._foreground.get( ), fgstipple = self._fgstipple.get( ), bgstipple = self._bgstipple.get( ), borderwidth = self._borderwidth.get( ), relief = self._relief.get( ), justify = self._justify.get( ), wrap = self._wrap.get( ), lmargin1 = self._lmargin1.get( ), lmargin2 = self._lmargin2.get( ), rmargin = self._rmargin.get( ), spacing1 = self._spacing1.get( ), spacing2 = self._spacing2.get( ), spacing3 = self._spacing3.get( ), tabs = self._tabs.get( ) ) except: return Style.DEFAULT_STYLE def _setGUIState( self, newState ): self._state = newState # Handle GUI events def onSelectedStyle( self, styleName ): if self._state == 'disabled': return if self._currentStyleName.get() not in self._sample.styles(): return self.updateGUIControls( ) self.updateSample( ) def onRevertStyle( self ): self.update( ) def onDeleteStyle( self ): self.sample.undefineStyle( self._currentStyleName.get() ) if len(self._sample.styles()) == 0: self._currentStyleName.set('') self.update( self.defaultStyle ) else: self.update( ) def onChooseBGColor( self ): if self._state == 'disabled': return import tkColorChooser newColor = tkColorChooser.askcolor( parent=self )[1] if newColor: self._background.set( newColor ) self._bgButton.configure( background=newColor ) self.updateSample( ) def onChooseFGColor( self ): if self._state == 'disabled': return import tkColorChooser newColor = tkColorChooser.askcolor( parent=self )[1] if newColor: self._foreground.set( newColor ) self._fgButton.configure( background=newColor ) self.updateSample( ) def onApplyEdits( self, event=None ): if self._state == 'disabled': return family = self._fontFamily.get( ) size = int(self._fontSize.get( )) bold = True if self._bold.get() else False italic = True if self._italic.get() else False offset = self._offset.get( ) self._currentStylerFont = Font.getFont( family=family, size=size, bold=bold, italic=italic, offset=offset ) self.updateSample( ) def onSaveStyles( self ): import tkFileDialog import pickle filename = tkFileDialog.asksaveasfilename( parent=self, defaultextension='sty' ) if not filename or (filename == ''): return if StyleEditor.SAMPLE_TAG in self._sample._styles: del self._sample._styles[ StyleEditor.SAMPLE_TAG ] theFile = file( filename, 'w' ) pickle.dump( self._sample.styles(), theFile ) def onLoadStyles( self ): import tkFileDialog import pickle filename = tkFileDialog.askopenfilename( parent=self, defaultextension='sty' ) if not filename or (filename == ''): return theFile = file( filename, 'r' ) styleDefinitions = pickle.load( theFile ) if StyleEditor.SAMPLE_TAG in styleDefinitions: del styleDefinitions[ StyleEditor.SAMPLE_TAG ] for styleName,styleDef in styleDefinitions.iteritems(): self._sample.defineStyle( styleName, styleDef ) styleList = self._sample.styles().keys() styleList.sort() self.updateStyleList( makeThisStyleCurrent=styleList[0] ) # Override def apply( self ): if StyleEditor.SAMPLE_TAG in self._sample.styles(): del self._sample.styles()[ StyleEditor.SAMPLE_TAG ] class StyledText( Tk.ScrolledText ): TAG_NAME_FORMAT = '#attribute#%s#%s' # name, value TAG_NAME_PREFIX = '#attribute#' def __init__( self, parent, styles=None, **options ): Tk.ScrolledText.__init__( self, parent, scrollbar=Tk.AUTO ) self.text.configure( **options ) # The Model self._styles = None self._images = { } self._windows = { } Style.setup( self.text ) self.reinitialize( styles ) def reinitialize( self, styles=None ): if self.text: self.text.delete( '1.0', Tk.END ) if styles is not None: self._styles = styles else: self._styles = { } # Contents def getModel( self ): content = self.text.dump( '1.0', Tk.END ) styles = self._styles images = self._images windows = self._windows return content,styles,images,windows def setModel( self, content, styles=None, images=None, windows=None ): self.reinitialize( styles=styles ) if images: self._images = images if windows: self._windows = windows if isinstance( content, (str,unicode) ): self.text.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.styleDefinition( tagName ).tagOptions() except: newStyle = Style.getStyle( tagName ) newTagOpts = newStyle.tagOptions() self.text.tag_add( tagName, regionBegin, regionEnd ) self.text.tag_config( tagName, **newTagOpts ) 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: ' + str(action) ) self._updateStyleChanges( ) def _makeImage( self, filename ): if filename not in self._images: self._images[ filename ] = Tk.PhotoImage( file=filename ) return self._images[ filename ] # Style Attributes 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( Font.TAG_NAME_PREFIX ): currentFont = Font.getFont( oldTagName ) self.text.tag_remove( oldTagName, beg, end ) break else: currentFont = Font.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( StyledText.TAG_NAME_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 = StyledText.TAG_NAME_FORMAT % ( attributeName, str(attributeValue) ) # Install the new values self.text.tag_add( newTagName, beg, end ) self.text.tag_config( newTagName, **{ attributeName : attributeValue } ) def getStyleAttributes( self, index ): index = self.text.index( index ) styles = self.tagNames( index ) tagOpts = { } fontOpts = { } for styleName in styles: if styleName.startswith( Font.TAG_NAME_PREFIX ): font = Font.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 tagNames( self, index1, index2=None ): tagNames = list(self.text.tag_names( index1 )) if 'sel' in tagNames: 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 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 # Custom Styles def defineStyle( self, styleName, style=None, **options ): if style: theStyle = style else: theStyle = Style( **options ) self._styles[ styleName ] = theStyle if self.text: self.text.tag_config( styleName, **theStyle.tagOptions() ) self._updateStyleChanges( ) def undefineStyle( self, styleName ): del self._styles[ styleName ] if self.text: self.text.tag_delete( styleName ) def styleDefinition( self, styleName ): return self._styles[ styleName ] def styles( self ): return self._styles def applyStyle( self, index1, index2, styleName ): try: index1 = self.text.index( index1 ) index2 = self.text.index( index2 ) except: return for tagName in self.tagNames( index1, index2 ): self.text.tag_remove( tagName, index1, index2 ) self.text.tag_add( styleName, index1, index2 ) def _updateStyleChanges( self ): styleNameList = self._styles.keys( ) # Remove any deleted styles from the text widget tagList = self.text.tag_names( ) for name in set(tagList) - set(styleNameList): self.text.tag_delete( name ) self.event_generate( '<>' ) class TextWriter( Tk.Frame ): def __init__( self, parent, **options ): Tk.Frame.__init__( self, parent ) # The View self.stext = StyledText( self, **options ) 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( ) # Toolbars 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 ) styleFrame = self.styleToolbar( 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 ) styleFrame.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 ) self.stext.pack( expand=1, side=Tk.TOP, fill=Tk.BOTH ) self.stext.bind( '<>', self._updateStyleToolbar ) # GUI Elements def toolbarCombo( self, parent, variable, attributeName, editable=False, width=None, values=[], default=None ): combo = Tk.ComboBox( parent, editable=editable, dropdown=True, fancy=False, variable=variable, history=False, selectmode=Tk.BROWSE, command=self.onChooseAttribute(attributeName,variable) ) if width: combo.subwidget('listbox').config( font=DIALOG_FONT, width=width ) combo.subwidget('entry'). config( font=DIALOG_FONT, width=width ) else: combo.subwidget('listbox').config( font=DIALOG_FONT ) combo.subwidget('entry'). config( font=DIALOG_FONT ) combo.pack( side=Tk.LEFT, padx=2, pady=2 ) for name in values: combo.insert( Tk.END, name ) if default: variable.set( default ) return combo def toolbarEntry( self, parent, label, variable, attributeName, default='', width=None ): le = Tk.LabelEntry( parent, label=label ) le.label.configure( font=DIALOG_FONT ) if width: le.entry.configure( width=width, textvariable=variable ) else: le.entry.configure( textvariable=variable ) le.entry.bind( '', self.onChooseAttribute(attributeName,variable) ) le.pack( side=Tk.LEFT, padx=5, pady=5 ) return le def fontAttributeToolbar( self, parent ): # Create the toolbar widgets fontFrame = Tk.Frame( parent, borderwidth=2, relief=Tk.GROOVE ) lst = list(tkFont.families( )) lst.sort() familyList = [ ] for family in lst: if family[0] != '@': familyList.append(family) fontChooser = self.toolbarCombo( fontFrame, self._currentFontName, 'family', width=15, values=familyList, default=Font.DEFAULT_FONT['family'] ) fontChooser.subwidget('listbox').config( width=30, height=20 ) szList = [ str(x) for x in range(6,31) ] self.toolbarCombo( fontFrame, self._currentFontSize, 'size', values=szList, width=3, default=str(Font.DEFAULT_FONT['size']) ) Tk.Button( fontFrame, relief=Tk.GROOVE, text='B', font=DIALOG_FONT + ' bold', command=self.onToggleAttribute('bold') ).pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( fontFrame, relief=Tk.GROOVE, text='I', font=DIALOG_FONT + ' italic', command=self.onToggleAttribute('italic') ).pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( fontFrame, relief=Tk.GROOVE, text='U', font=DIALOG_FONT + ' underline',command=self.onToggleAttribute('underline') ).pack(side=Tk.LEFT, padx=2, pady=2) Tk.Button( fontFrame, relief=Tk.GROOVE, text='O', font=DIALOG_FONT + ' overstrike',command=self.onToggleAttribute('overstrike') ).pack(side=Tk.LEFT, padx=2, pady=2) self.toolbarCombo( fontFrame, self._currentFontOffset, 'offset', width=9, values=['normal','superscript','subscript'], default='normal' ) 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 ) stippleValues=[ '', 'gray12', 'gray25', 'gray50', 'gray75' ] self.toolbarCombo( fontFrame, self._currentFGStipple, 'fgstipple', width=5, values=stippleValues, default='' ) self.toolbarCombo( fontFrame, self._currentBGStipple, 'bgstipple', width=5, values=stippleValues, default='' ) 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.onChooseAttribute('borderwidth',self._currentBorder) ) le.pack( side=Tk.LEFT, padx=2, pady=2 ) self.toolbarCombo( fontFrame, self._currentRelief, 'relief', width=5, values=[ 'flat','sunken','raised','groove','ridge','solid'], default='flat' ) self._fgColorBtn.config( foreground=self.stext.text['foreground'] ) self._bgColorBtn.config( background=self.stext.text['background'] ) return fontFrame def paragraphAttributeToolbar( self, parent ): # Create the toolbar widgets paragraphFrame = Tk.Frame( parent, borderwidth=2, relief=Tk.GROOVE ) justifyCombo = self.toolbarCombo( paragraphFrame, self._currentJustify, 'justify', width=5, values=[Tk.LEFT,Tk.RIGHT,Tk.CENTER], default=Tk.LEFT) wrapCombo = self.toolbarCombo( paragraphFrame, self._currentWrap, 'wrap', width=5, values=[Tk.NONE,Tk.CHAR,Tk.WORD], default=Tk.NONE) self.toolbarEntry( paragraphFrame, 'L-Margin1', self._currentLMargin1, 'lmargin1', width=4 ) self.toolbarEntry( paragraphFrame, 'L-Margin2', self._currentLMargin2, 'lmargin2', width=4 ) self.toolbarEntry( paragraphFrame, 'R-Margin', self._currentRMargin, 'rmargin', width=4 ) self.toolbarEntry( paragraphFrame, 'Spacing1', self._currentSpacing1, 'spacing1', width=4 ) self.toolbarEntry( paragraphFrame, 'Spacing2', self._currentSpacing2, 'spacing2', width=4 ) self.toolbarEntry( paragraphFrame, 'Spacing3', self._currentSpacing3, 'spacing3', width=4 ) self.toolbarEntry( paragraphFrame, 'Tabs', self._currentTabs, 'tabs' ) Tk.Button( paragraphFrame, font=DIALOG_FONT, text='Apply', command=self.onApplyParagraph ).pack( side=Tk.LEFT, padx=5, pady=5 ) return paragraphFrame def styleToolbar( self, parent ): styleFrame = Tk.Frame( parent, borderwidth=2, relief=Tk.GROOVE ) self._styleCombo = Tk.ComboBox( styleFrame, editable=False, dropdown=True, fancy=False, variable=self._currentStyle, history=False, selectmode=Tk.BROWSE, command=self.onStyleChosen ) self._styleCombo.subwidget('listbox').config( font=DIALOG_FONT ) self._styleCombo.subwidget('entry'). config( font=DIALOG_FONT, width=12 ) self._styleCombo.pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( styleFrame, text='Edit', font=DIALOG_FONT, command=self.editStyles ).pack( side=Tk.LEFT, padx=2, pady=2 ) return styleFrame 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=(lambda:None) ).pack( side=Tk.LEFT, padx=2, pady=2 ) Tk.Button( objectFrame, font=DIALOG_FONT, text='table', command=(lambda:None) ).pack( side=Tk.LEFT, padx=2, pady=2 ) return objectFrame def _updateStyleToolbar( self, event=None ): styleNameList = self.stext.styles( ).keys( ) # Remove any deleted styles from the text widget tagList = self.stext.text.tag_names( ) for name in set(tagList) - set(styleNameList): self.stext.text.tag_delete( name ) # Repopulate the style combo box and text widgets self._styleCombo.subwidget( 'listbox' ).delete( '0', Tk.END ) styleNameList.sort( ) for name in styleNameList: self._styleCombo.insert( Tk.END, name ) self.stext.text.tag_config( name, **self.stext.styleDefinition(name).tagOptions( ) ) # GUI Handlers def onToggleAttribute( self, attributeName ): def _onToggleAttribute( ): tagOpts, fontOpts = self.stext.getStyleAttributes( Tk.SEL_FIRST ) try: if attributeName in Font.FIELD_NAMES: newValue = Font(fontOpts)[ attributeName ] else: newValue = tagOpts[ attributeName ] if newValue in [ True, '1', 'true', 'True' ]: newValue = False else: newValue = True except: newValue = True self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, attributeName, newValue ) return _onToggleAttribute def onChooseAttribute( self, attributeName, tkVariable ): def _onChooseAttribute( value ): value = tkVariable.get( ) if value and (value != ''): self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, attributeName, value ) return _onChooseAttribute def onApplyParagraph( self ): self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'lmargin1', self._currentLMargin1.get() ) self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'lmargin2', self._currentLMargin2.get() ) self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'rmargin', self._currentRMargin.get() ) self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'spacing1', self._currentSpacing1.get() ) self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'spacing2', self._currentSpacing2.get() ) self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'spacing3', self._currentSpacing3.get() ) self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'tabs', self._currentTabs.get() ) def onChangeFG( self ): import tkColorChooser newColor = tkColorChooser.askcolor( parent=self )[1] if newColor: self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'foreground', newColor ) def onChangeBG( self ): import tkColorChooser newColor = tkColorChooser.askcolor( parent=self )[1] if newColor: self.stext.applyStyleAttribute( Tk.SEL_FIRST, Tk.SEL_LAST, 'background', newColor ) def onInsertImage( self ): import tkFileDialog import pickle filename = tkFileDialog.askopenfilename( parent=self, filetypes=[ ( 'GIF Image', '*.gif' ) ] ) if not filename or (filename == ''): return self.stext.text.image_create( Tk.INSERT, image=self.stext._makeImage(filename) ) def onStyleChosen( self, styleName ): if styleName and (styleName != ''): self.stext.applyStyle( Tk.SEL_FIRST, Tk.SEL_LAST, styleName ) def editStyles( self ): StyleEditor( self.stext ) self._updateStyleToolbar( ) import tkFileDialog import pickle if __name__=='__main__': root = Tk.Tk( ) stext = TextWriter( root, font=DEFAULT_TEXT_FONT ) def onOpen( ): filename = tkFileDialog.askopenfilename( parent=root ) if not filename or (filename == ''): return self._filename = filename theFile = file( filename, 'r' ) docData = pickle.load( theFile ) if isinstance( docData, (str,unicode) ): stext.setModel( docData ) else: stext.setModel( *docData ) def onSave( ): filename = tkFileDialog.asksaveasfilename( parent=root ) if not filename or (filename == ''): return theFile = file( filename, 'w' ) docData = stext.getModel( ) pickle.dump( docData, theFile ) # Build GUI toolBar = Tk.Frame( root ) openBtn = Tk.Button( toolBar, text='open', command=onOpen ) openBtn.pack( side=Tk.LEFT ) saveBtn = Tk.Button( toolBar, text='save', command=onSave ) saveBtn.pack( side=Tk.LEFT ) toolBar.pack( side=Tk.TOP, fill=Tk.X ) stext.pack( side=Tk.TOP ) root.mainloop( )