Home

Wednesday, January 26, 2022

Python 3 - Win10: Tkinter 2 - Word Processor - Text Widget - Line Wrapping - Find Wrap Position 1

#Python 3 - Win10

Tkinter Word Processor

Text Widget: Line Wrapping

Find Wrap Position 1

Brief:

_______________________________________________________________
 
! ! Update ! ! Post With New Code

It has always driven me crazy trying to work with wrapping lines in tkinter. I've spent a while thinking about it and searching for any solutions. But I have one here that works! As usual I have a test environment along with the solution.

My first attempt at solving this problem involved getting the closest index to the @x,y coordinates to the left and right of the inspected wrapline. I was able to get the first and last character of the display line consistently. The only problem was that if the line I was inspecting was not within the visible text area. Querying pixel to index positions was not possible. I needed a solution that was independent of the current view.

I had actually stumbled upon the solution accidentally. I figured at first that I would sum the width of each index in the line until it passed the edge of the Text widget and that would give me the wrap index -1. But that was not working as the line width never exceeded the widget's width. This was because it was being set back to zero at some point in the loop. While debugging I found that the step before the summed width was set to zero it was the inverse of the current line width. After visually debugging the text window I found that the point at which the sum width inverted was the wrap position of the line and the zero sum was a newline character. After I had that I could determine the end position of each wrap line by looking for that width reversal and storing the index of the character.

 

Functionality:

_______________________________________________________________
As far as I can tell it will work in any condition. Now my inner programmer doubts this very much. However, it does seem to be the case.

Improvements:
  • N/A
Issues:
  •  N/A

 

Demo:

_______________________________________________________________
 

 

Code Snippet: 

_______________________________________________________________
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
# TEST UTILITY FUNCTIONS:

def rand_familty(lst):
	return lst[rand.randrange(0, len(lst))]


def sanitize_name(family):
	return family.replace(' ', '_')


def get_validate_family(widget, fam, default):
	widget.tag_add('<f_test>', f"{tk.END} +1c")
	fam, default = sanitize_name(fam), sanitize_name(default)

	try:
		widget.tag_configure('<f_test>', font=f"{fam} 8")
		return fam
	except:
		widget.tag_configure('<f_test>', font=f"{default} 8")
		return default


def valid_rand_family(widget, lst, default):
	return get_validate_family(widget, rand_familty(lst), default)


# WRAP LINE FUNCTIONS:

def wrapline_to_line(widget, search_index) -> [int]:
	line_length 	  = text.count(f"{search_index} linestart", f"{search_index} lineend",
				       "update", "chars", "xpixels"
				       )
	line_px_width 	  = 0
	line_end_index 	  = []
	text_area_width   = text.winfo_width()
	wrap_first_index  = search_index.split('.')[0]

	for char_col_index in range(0, line_length[0]+1):
		step_index  = text.index(f"{wrap_first_index}.{char_col_index}")
		step_plus_1 = f"{step_index} +1c"
		
		char_px_width = text.count(step_index, step_plus_1, "update", "xpixels")
		if None == char_px_width:
			char_px_width = 0
		
		line_px_width += char_px_width

		if 0 > char_px_width:
			line_end_index.append(char_col_index+1)
			line_px_width = 0
		elif char_col_index == line_length[0]:
			line_end_index.append(char_col_index+1)

	return line_end_index


def display_by_line(widget, search_index, wrap_lines_marks):
	line_num = search_index.split('.')[0]
	
	for index, wrap_point in enumerate(wrap_lines_marks):
		start = 0 if 0 == index else wrap_lines_marks[index-1]
		end = wrap_point
		print(text.get(f"{line_num}.{start}", f"{line_num}.{end}"))


# TEST DATA SET:
 
import tkinter as tk
from tkinter import font as tkfont
import random as rand

root 		 = tk.Tk()
text 		 = tk.Text(root, height=4)
text.pack()  

font_fam 	 = tkfont.families()
font_range 	 = [12,24]
search_index = "3.4"

fam_rand1 	 = valid_rand_family(text, font_fam, "Courier")
fam_rand2 	 = valid_rand_family(text, font_fam, "Terminal")
fam_rand3 	 = valid_rand_family(text, font_fam, "Times New Roman")

text.insert('1.0', f"line 1 <{fam_rand1}>1234567890123456789012345678-01234567890123456789012345678901234567890123",
			"<ft_random1>")
text.insert('1.0', f" line 2 <{fam_rand2}>1234567890123456789012345678-01234567890123456789012345678901234567890123",
			"<ft_random2>")
text.insert('1.0', f"  line 3 <{fam_rand3}>1234567890123456789012345678-01234567890123456789012345678901234567890123",
			"<ft_random3>")
text.insert('1.0', f"\t Data Test Set\n", "<ft_2>")
text.insert('1.0', f"\n", "<ft_1")

text.tag_config("<ft_random1>", font=f'{fam_rand1} {rand.randrange(*font_range)}')
text.tag_config("<ft_random2>", font=f'{fam_rand2} {rand.randrange(*font_range)}')
text.tag_config("<ft_random3>", font=f'{fam_rand3} {rand.randrange(*font_range)}')

text.update_idletasks()
text.update()


# TEST EXECUTION:

wrap_lines_marks = wrapline_to_line(text, search_index)
display_by_line(text, search_index,wrap_lines_marks)

root.mainloop()


Credits and Resources:

  • Code formatted via: http://hilite.me/ : Styling: Python, monokai
  1. https://tcl.tk/man/tcl8.6/TkCmd/text.htm
  2. https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-methods.html
  3. https://docs.python.org/3/library/tkinter.ttk.html
  • https://stackoverflow.com/

 

-END-

No comments:

Post a Comment

Conduct: Be nice and don't advertise or plug without adding value to the conversation.