Home > database >  tk.Text: Offsetting bgstipple
tk.Text: Offsetting bgstipple

Time:07-15

The script below will create an xbm file that is just a vertical line positioned at either the left, right or center of the xbm. When the xbm is used for bgstipple everything is offset by half of a character width, but without wrap! If I left align the line in the xbm, it appears in the center when used as stipple. If I center align in the xbm, it appears at the end of the character as stipple. If I right align the line it does not appear, at all. I've used other built-in xbms and they do not have this behavior. What am I doing wrong? The below reproduces this behavior 100% of the time, for me.

import tkinter as tk, tkinter.font as tkf
import math


class Text(tk.Text):
    def __init__(self, master, **kwargs):
        tk.Text.__init__(self, master, **dict(font='{Courier New} 14',wrap="none",exportselection=1,takefocus=1))
        self.make_caret_xbm('caret.xbm', self['font'])
        
    def make_caret_xbm(self, path, font, inswidth=1) -> None:
        #prepare
        f    = tkf.Font(font=font)
        h, w = f.metrics('linespace'), f.measure(' ')
        d, m = divmod(w, 8)
        bl   = d (m>0)
        wtmz = f'{"0"*(bl*8)}'
        
        #line at beginning ~ appears in center of character
        lt = f'{"1"*inswidth}{wtmz}'
        
        #line in middle ~ appears at end of character
        cn = f'{"0"*(math.ceil(w/2))}{"1"*inswidth}{wtmz}'
        
        #line at end ~ does not appear
        rt = f'{"0"*(w-inswidth)}{"1"*inswidth}{wtmz}'
        
        #---set-line-type-here---vv
        row  = ','.join(hex(int( lt[(n:=int(i*8)):n 8] ,2)) for i in range(bl))
        
        xbmdata  = ',\n\t'.join(row for i in range(h))
        xbmdata  = (f"#define image_width {w}\n#define image_height {h}\n"
                    "static unsigned char image_bits[] = {\n\t"
                    f'{xbmdata}}};')
                    
        print(xbmdata)
        #save xbm
        with open(path, 'wb') as f: f.write(xbmdata.encode())
        
    def caret(self):
        #create the caret tag and add it to everything
        self.tag_configure('CARET',bgstipple="@caret.xbm",background='#FF0000')
        self.tag_add('CARET','1.0','end')


if __name__ == '__main__':
    class App(tk.Tk):
        def __init__(self, *args, **kwargs):
            tk.Tk.__init__(self, *args, **kwargs)
            #config cell
            self.columnconfigure(0, weight=1)
            self.rowconfigure   (0, weight=1)
            #instantiate editor
            (ed := Text(self)).grid(sticky='nswe')
            #insert text and apply caret to everything
            ed.insert('1.0', 'Hello World')
            ed.caret()
    #run        
    App().mainloop()

CodePudding user response:

Your xbm maker is backwards in logic. Assuming we had a width of 8 (to keep it simple) a line on the left would repeat this: 00000001. Unfortunately, it isn't that simple when trying to make a line ride the edge of text. Firstly, you have to determine the window offset with master.winfo_rootx()-master.winfo_x(), where "master" is referring to the toplevel window. Secondly, the padx property needs to be considered.

The below rewrite works for me, for fixed-width fonts between 12 and 96. It may work for other sizes, but I only tried 12 to 96. Bits are flipped so it fills the background, except for the caret. This allows the "caret" to inherit the background property of the WIDGET, and the stipple will inherit the background property of whatever TAG it shares a spot with. It produces a very convincing caret. The one drawback is you have to tag the entire text widget with a background color that isn't identical to the caret.

I actually only ever tag the entire document when in boxselect or multicaret mode. It flips the widget's background and insertbackground attributes, adding a "background" tag to everything. When neither of those modes are active it flips back and removes the tag.

    #make caret xbm
    @staticmethod
    def caretxbm(w:int, iw:int, px:int, wo:int) -> None:
        iw   = min(w,iw)    #cap 'insertwidth' to width
        d, m = divmod(w, 8)
        b    = d (m>0)      #total byte count for this xbm
        o    = wo px        #window offset   padx
        bl   = b*8-(iw o)   #number of remaining bits
        
        #final bits
        bits = f'{"1"*bl}{"0"*iw}{"1"*o}'
            
        #only one pixel tall because it will repeat
        row  = ','.join(hex(int(bits[(n:=int(i*8)):n 8] ,2)) for i in range(b))
        xbm  = (f"#define image_width {w}\n#define image_height 1\n"
                "static unsigned char image_bits[] = {\n\t"
                f'{row}}};')
                    
        with open(f'caret_{w}_{iw}_{px}_{wo}.xbm', 'wb') as f: f.write(xbm.encode())
  • Related