#Python 3 - Win10
Tkinter Word Processor
Text Widget Font Styling 1
Brief:
_______________________________________________________________
This script is a slice of my word processor project. I'm trying to get
styling to function and feel good, or at least something like Microsoft 365
Word.
It feels like I'm groping around in the dark to complete the project. The
built-in tkinter functions don't seem to be of use for acquired and
affecting the data I want. Which I think means I don't understand how they
are meant to be employed. The workings of tkinter are also a bit of a black
box for me, which is frustrating to say the least.
Functionality:
_______________________________________________________________
Improvements:
- N/A
Issues:
- Continuation of previous tags
- While typing quickly continuation of previous tags is lost
- New text is briefly displayed as default before previous tags are applied
-
Note: this is not the case for text inserted within the existing domain
of the tag
Demo:
_______________________________________________________________
Functionality Test:
- General tag handling
- Tag / untag a range
- Fully tag a partially tagged range
- Untag a fully tagged range
- Continue text
- Get previous adjacent tags
-
If no adjacent text, step back through spaces and newlines until text
is found
- Bold text
- Underline text
- Overstrike text
- Change font
- Revert to previous font
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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
import tkinter as tk class Document(tk.Text): #@! Class Variables #================================================================================ children = [] old_insert = "1.0" carrot_moved = False #@! Constructor #================================================================================ def __init__(self, *args, **kewargs): super().__init__(*args, **kewargs) # print(f"<{self.__class__.__name__}> object created <{id(self)}> ") Document.children.append(id(self)) self.set_bindings() self.old_insert = "1.0" # Break bindings for special window hotkeys e.g. 'ctrl-i' self.tag_init() #@! Utility #================================================================================ def get_sel_index(self, event=None) -> (str, str): try: sel_first = self.index(f"{tk.SEL}.first") sel_last = self.index(f"{tk.SEL}.last") except: sel_first = self.index(f"{tk.INSERT}") sel_last = self.index(f"{tk.INSERT} +1c") return sel_first, sel_last #@! Tags #================================================================================ def tag_init(self): tag_list = { 'regular':{ '<b>' :{'foreground':'red'}, '<u>' :{'underline':1, 'underlinefg':'green'}, '<os>':{'overstrike':1, 'overstrikefg':'red'}, '<jl>':{'justify':'left'}, '<jc>':{'justify':'center'}, '<jr>':{'justify':'right'}, '<f>' :{'foreground':'red', 'font':'Helvetica 18 bold underline overstrike italic'}, }, } for tag in tag_list['regular']: self.tag_add(tag, "1.0", "1.2") self.tag_config(tag, tag_list['regular'][tag]) # Broken # print(self.tag_config('<f>')) # Step 2 get only the ranges that intersect the selection def tag_ranges_in_sel(self, tag:str, start:str="", end:str="") -> [int]: """Returns list of first index of intersection tag range""" # def overthinking_it(self, tag, start, end): # Q: What are we really after with all these functions? # A: 1 finding if there is a tag in the selected range # A: 2 finding if the full range IS tagged or NOT # A: 3 finding if part(s) of the selection range ARE tagged # Q: what built in tools are there to do that job? # A: tag_ranges() # Q: are they good enough? # A: maybe? # guard statement ranges = self.tag_ranges(tag) if () == ranges: return [] if start == "": start, end = self.get_sel_index() intersections = [] for index in range(0, len(ranges), 2): range_start = ranges[index] range_end = ranges[index+1] # Q: We want to know if; range does or does NOT intersect selection. if self.compare(range_start, "<", end) and self.compare(range_end, ">", start): intersections.append(index) # print("range is IN selection") else: pass # print("range is OUTside selection") return intersections # Step 3 determine if tag range encompasses the selection def tag_range_match_sel(self, tag:str, range_indices:[], start:str="", end:str="") -> bool: """Returns True if tag range matches selection""" # guard statement ranges = self.tag_ranges(tag) if () == ranges: return False if start == "": start, end = self.get_sel_index() if 1 == len(range_indices): index = range_indices[0] range_start = ranges[index] range_end = ranges[index+1] if self.compare(range_start, "<=", start) and self.compare(range_end, ">=", end): return True return False def check_tags(self, event=None): ranges = self.tag_ranges_in_sel('bold') self.tag_range_match_sel('bold', ranges) def bold(self, event=None) -> None: self.set_tag('<b>') def underline(self, event=None) -> None: self.set_tag('<u>') def overstrike(self, event=None) -> None: self.set_tag('<os>') def justify(self, event=None) -> None: self.set_tag('<jl>') def fon(self, event=None) -> None: self.set_tag('<f>') self.tag_raise('<f>') def set_tag(self, tag:str, event=None) -> None: sel_first, sel_last = self.get_sel_index() tag_detected = self.tag_ranges_in_sel(tag) part_tagged = self.tag_range_match_sel(tag, tag_detected) if not tag_detected or not part_tagged: self.tag_add(tag, sel_first, sel_last) else: self.tag_remove(tag, sel_first, sel_last) def carry_over(self, step): """Continues a style""" index = self.index(f"{tk.INSERT} -{step}c") char = self.get(index) while '\n' == char or ' ' == char: if "1.0" == index: return index = self.index(f"{index} -1c") char = self.get(index) tags = self.tag_names(index) for tag in tags: self.tag_add(tag, index, tk.INSERT) def valid_over(self, event, step): # ISSUE: update is not fast enough, style only continues if typing is slow ignore = ['space', 'Left', 'Right', 'Up', 'Down', 'Control_L', 'Control_R', 'End', 'Home', 'Prior', 'Next'] if event.keysym not in ignore: self.carry_over(step) #@! Status Info #================================================================================ def carrot_move(self, event=None): new_insert = self.index(tk.INSERT) if self.old_insert != new_insert: self.old_insert = new_insert carrot_moved = True print("carrot_move", new_insert) # update index on release else: carrot_moved = False #@! Inputs #================================================================================ def set_bindings(self): self.bind("<Key>", self.any_key_down) self.bind("<KeyRelease>", self.any_key_up) self.bind("<Control-b>", self.bold) self.bind("<Control-u>", self.underline) self.bind("<Control-f>", self.fon) self.bind("<Control-minus>", self.overstrike) self.bind("<Control-bracketright>", self.overstrike) def any_key_down(self, event=None): self.valid_over(event, 1) def any_key_up(self, event=None): self.carrot_move(event) self.valid_over(event, 2) root = tk.Tk() text = Document(root, height=4) text.pack() text.insert("1.0", "This is helvetica font\n", "font_hel") text.insert("1.0", "This is terminal font\n", "font_term") text.tag_config('font_hel', font='Helvetica 12') text.tag_config('font_term', font='Terminal 12') root.mainloop() |
No comments:
Post a Comment
Conduct: Be nice and don't advertise or plug without adding value to the conversation.