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 xbm
s 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())