How to highlight last added text in text widget tkinter - python

I want to highlight a last added text in my text widget.
I have seen an example regarding that How to highlight text in a tkinter Text widget.The problem is that I add a text with "\n". That's why program consider current line as a new line so it highlights the empty line.
Do you have any idea how I can alter the program? Here is my code
import time
import tkinter as tk
from threading import Thread
class MyApp:
def __init__(self, master):
self.master = master
self.text = tk.Text(self.master)
self.text.pack(side="top", fill="both", expand=True)
self.text.tag_configure("current_line", background="#e9e9e9")
self.start_adding_text()
self._highlight_current_line()
def start_adding_text(self):
thrd1 = Thread(target=self.add_tex)
thrd1.start()
def add_tex(self):
text = "This is demo text\n"
for _ in range(20):
self.text.insert(tk.END, text)
time.sleep(0.1)
return
def _highlight_current_line(self, interval=100):
'''Updates the 'current line' highlighting every "interval" milliseconds'''
self.text.tag_remove("current_line", 1.0, "end")
self.text.tag_add("current_line", "insert linestart", "insert lineend+1c")
self.master.after(interval, self._highlight_current_line)
if __name__ == '__main__':
root = tk.Tk()
app = MyApp(master=root)
root.mainloop()

Your function _highlight_current_line is doing what it is supposed to do: it highlights the line of the insert-cursor. But what you want is to highlight the last inserted text which is something different. You can simply create a new tag.
Let's name it 'last_insert':
self.text.tag_configure("last_insert", background="#e9e9e9")
And when you add text, you can specifiy the tag(s) attached to the inserted text:
self.text.insert(tk.END, text, ('last_insert',))
Of course, if you want only the last inserted text to be highlighted, you add this:
self.text.tag_remove("last_insert", 1.0, "end")
Remark: The tkinter function tag_add takes as arguments tag, start, end, where start and end are text indices in the form of a string 'a.b' where a is the line index (starting with 1 at the top) and b is the character inside this line (starting with 0). You can modify the index with expressions (see here: http://effbot.org/tkinterbook/text.htm. Further, "insert" is a mark (read up on aforementioned link) - and "insert linestart" is replaced by tkinter by the index "line.0" where line is the line the insert cursor is currently in.

You could check if you are at the last line and remove your newline:
def add_tex(self):
loop_times=20
text = "This is demo text\n"
for id,_ in enumerate(list(range(loop_times))):
if id==loop_times-1:
text = "This is demo text"
self.text.insert(tk.END, text)
time.sleep(0.1)
return

Related

Tkinter Simple Markdown Parser - Remove markdown tags

I'm working on a simple markdown parse in tkinter. Concept being that headings can be surrounded by asterisk symbols for example *Heading 1*, **Heading 2**.
I'm use regex to find strings in this format, tag them and change the style of the tags.
The item that I am struggling with is removing the asterisk symbols from the text after they've been searched. I tried some code (included by commented out) but it just removes the tagged text.
My code correctly finds *Heading 1* and turns it in to *Heading 1* but doesn't remove the markdown symbols to get Heading 1
Can anyone help me with an algorithm to remove the asterisk symbols from the headings that retains the formatting?
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from tkinter import font
class HelpDialog(tk.Toplevel):
"""Seperate window to show the results of SSO Search"""
def __init__(self, parent,text):
super().__init__(parent)
self.title("Help")
self.defaultfont = font.Font(family="Sans Serif",size=12)
self.textbox = ScrolledText(self,height=40,width=80,font=self.defaultfont)
self.textbox.config(wrap=tk.WORD)
self.textbox.grid()
self.textbox.insert(0.0,text)
self.style()
def style(self):
self.h1font = font.Font(family="Sans Serif", size=18, weight="bold")
self.h2font = font.Font(family="Sans Serif", size=14, weight="bold")
self.h3font = font.Font(family="Sans Serif", size=12, weight="bold", slant="italic")
self.textbox.tag_configure("h1",font=self.h1font)
self.textbox.tag_configure("h2",font=self.h2font)
self.textbox.tag_configure("h3",font=self.h3font)
self.tag_match(r"^[\*]{1}[\w\d -]+[\*]{1}$", "h1")
self.tag_match(r"^[\*]{2}[\w\d -]+[\*]{2}$", "h2")
self.tag_match(r"^[\*]{3}[\w\d -]+[\*]{3}$", "h3")
def tag_match(self,regex,tag):
count = tk.IntVar()
self.textbox.mark_set("matchStart", "1.0")
self.textbox.mark_set("matchEnd", "1.0")
while True:
index = self.textbox.search(regex,"matchEnd","end",count=count,regexp=True)
if index=="": break
self.textbox.mark_set("matchStart",index)
self.textbox.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
self.textbox.tag_add(tag,"matchStart","matchEnd")
#Futile attempt to remove the ** from the headings
#text = self.textbox.get("matchStart", "matchEnd")
#orig_length = len(text)
#text = text.replace("*","").ljust(orig_length, " ")
#self.textbox.delete("matchStart", "matchEnd")
#self.textbox.insert("matchStart", text)
if __name__ == '__main__':
text = """*Heading 1*
A paragraph
**Heading 2**
Some more text
***Heading 3***
Conclusion
"""
root = tk.Tk()
root.withdraw()
HelpDialog(root,text)
The short answer is that you can use the delete method of the text widget to delete the characters at the start and end of the range. You can do simplified math on the indexes to adjust them. So, for example, to delete the character at "matchEnd" (which actually represents the spot just after the last character in the matched range) you can do delete("matchEnd-1c") where -1c is short hand for "minus one character".
At the every end of your loop inside of tag_match, add the following two lines:
self.textbox.delete("matchStart")
self.textbox.delete("matchEnd-1c")
However, this code assumes that the markup is just a single byte. You will need to pass information in to tell the function how many characters on each side of the text to delete, since that information doesn't otherwise exist.
For example, you could pass it in like this:
self.tag_match(r"^[\*]{1}[\w\d -]+[\*]{1}$", "h1", 1)
You will then need to adjust the code that deletes the characters to take this information into account. For example, assuming you pass that number in as the variable n, it would look something like this:
def tag_match(self, regex, tag, n):
...
while True:
...
self.textbox.delete("matchEnd-{}c".format(n), "matchEnd")
self.textbox.delete("matchStart", "matchStart+{}c".format(n))

GUI. User input. Multi-line text box

A simple GUI where a user can input multiline text is needed. Here is my code how can I get the value from the form? Do I have to manually create buttons too?
I like the simplicity of gooey module but it seems its unable to make multiline textbox? What would be the best way to get the subject done?
import tkinter as tk
root=tk.Tk()
text=tk.Text(root)
text.pack()
root.mainloop()
like this (Python2.7):
from Tkinter import *
root=Tk()
text=Text(root)
text.pack()
gui = {}
gui["text"] = text
def keyUp(e):
print e.keycode
oldText = gui["text"].get(1.0,END)
oldText = oldText[:-1] if (oldText[-1] == u"\n") else oldText
if e.keycode in (36,104) :
gui["text"].delete(1.0,END)
if ord(oldText[-1]) != 10 :
newText = oldText + "\n"
gui["text"].insert("1.0",newText)
else :
gui["text"].insert("1.0",oldText)
gui["text"].update()
gui["text"].insert(1.0,u"Re hello\nWorld\n")
gui["text"].bind("<KeyRelease>", lambda event: keyUp(event))
root.mainloop()
Suppress all new line characters on the keyboard.(i got 36(alpha),104(numlock)). Some operating systems may be able to add lines to the writing box(my big Enter Key). Ignore if the last character is a new line character oldText = oldText[:-1] if (oldText[-1] == u"\n") else oldText.Without forgetting, you cannot add blank lines!

Tkinter: How to make characters appear on text widget with a time delay?

def write_text(widget, message, enter_number, slow_type=True):
widget.config(state="normal")
if slow_type:
if len(message) > 0:
widget.insert("insert", message[0])
if len(message) > 1:
widget.after(100, UI.write_text, widget, message[1:], 0)
else:
widget.insert("insert", message)
for i in range(enter_number):
widget.insert("insert", "\n")
widget.config(state="disabled")
widget.see("end")
This is my code to write each characters show up in time delay
But I have a problem:
If I call this method like this (I have a widget named text1).
write_text(text1, ">>>Invalid Input", 1)
write_text(text1, ">>>Try Again...", 2)
Messages blend together something like this >>>>>>ITnrvya lAigda iInn.p.u.t.
I want it to type messages when typing the previous message is over.
What can I do?
P.S. Sorry for my bad English...
Here's something runnable that does what I think you want. The "blended" output you're getting was because the second call you have to write_text() occurs before all the (delayed) processing of the first one has completed, so the text widget effectively gets updates by two separate callback processes.
The code below avoids this issue by putting the characters of the string (and what line they go on) in a Queue which allow them to be retrieved in the same order that they were added.
DELAY = 100 # ms between widget updates
def update_widget(widget):
try:
line_number, text = widget._text_queue.get_nowait()
except queue.Empty:
return # Nothing further to do.
widget.insert('%s.end' % line_number, text)
if widget._text_queue.qsize(): # Anything more to process?
widget.after(DELAY, update_widget, widget)
def write_text(widget, message, line_number, slow_type=True):
if not slow_type:
widget.insert('%s.0' % line_number, message)
else:
for ch in message: # Add each character of message to queue.
widget._text_queue.put((line_number, ch))
update_widget(widget) # Start (or continue) update processing.
def add_text(widget):
widget.delete('1.0', tk.END) # Delete widget's current contents.
# Note: Tkinter always adds a newline at the very end of text widgets, so
# we need one less newline. This makes it possible to insert text onto
# any possible line of the widget -- which could fail if it was empty.
widget.insert('1.0', (widget['height']-1) * '\n') # Init with blank lines.
write_text(widget, ">>>Invalid Input", 1)
write_text(widget, ">>>Try Again...", 2)
if __name__ == '__main__':
root = tk.Tk()
text1 = tk.Text(root, width=40, height=3, bg='skyblue')
text1._text_queue = queue.Queue() # Add a Queue to it for delayed updates.
text1.grid()
button = tk.Button(root, text='Run Test', command=lambda w=text1: add_text(w))
button.grid()
root.mainloop()

Tkinter clearing formatting from syntax highlighting

I have syntax highlighting implemented in Python using Tkinter. For example, I can make it automatically highlight "derp". The problem is that when I modify the string to, say, "dERP"or something similar, it will still highlight the "d" (aka the only remaining original character). How do I clear formatting on this? I've considered creating a tag that will set the background to white for the entire document, but then this creates problems with highlighting.
code:
from Tkinter import *
import sys, os
class easyTex(Text):
def __init__(self,base,**args):
Text.__init__(self,base,**args)
self.tag_configure("default", background="white")
self.tag_configure("search", background="blue")
def highlightPattern(self, pattern, tag):
start = "1.0"
countVar = StringVar()
while True:
pos = self.search(pattern, start, stopindex="end", count=countVar, regexp=True)
if not pos: break
self.tag_add(tag, pos, "%s+%sc" % (pos, countVar.get()))
start = "%s+%dc" % (pos, int(countVar.get()) + 1)
def highlightSyntax(self):
self.highlightPattern(".*", "default")
self.highlightPattern("a red car", "search")
base = Tk()
editor = easyTex(base)
base.bind("<Escape>", lambda e: sys.exit())
base.bind("<Key>", lambda e: editor.highlightSyntax())
editor.pack(fill=BOTH, expand=1)
base.call('wm', 'attributes', '.', '-topmost', True)
mainloop()
(this is using the regex: "a red car":)
To remove the effects of a tag, remove the tag from the range of characters. You can remove a tag with tag_remove, giving it a starting and ending range that you want the tag removed from.
For example, to remove the "search" tag from the entire document, do this:
self.tag_remove("search", "1.0", "end")

Tkinter GUI to Convert Fixed Width File to Delimited File

I am writing a converter code for our Data Department to convert fixed width files into delmited files. Normally we use import the file into Excel, use the text import wizard to set the field lengths, and then just save as a csv. However we have run into the limitation where we have started getting files that are millions of records long, and thus cant be imported into Excel. The files do not always have spaces in between the fields, espicially so between value fields like phone numbers or zip codes. The headers are also often filled completely in with no spaces.
A sample of a typical fixed width file we are dealing with:
SequenSack and PaFull Name****************************]JOB TITLE****************]HOSP NAME******************************]Delivery Address***********************]Alternate 1 Address********************]Calculated Text**********************************]POSTNET Bar
000001T1 P1 Sample A Sample 123 Any Street Anytown 12345-6789 12345678900
000002T1 P1 Sample A Sample Director of Medicine 123 Any Street Po Box 1234 Anytown 12345-6789 12345678900
The program needs to break file into the following delimited fields:
Sequen
Sack and Pa
Full name
Job Title
Hosp Name
Delivery Address
Alternate Address 1
Calculated Text
POSTNET Bar
Each file as a slightly different width of each field depending on the rest of the job. What i am looking for is a GUI oriented delimiter much like the Excel import wizard for fixed width files. I am writing this tool in Python as a part of a larger tool that does many other file operations such as breaking up files into multiple up, reversing a file, converting from delimited to fixed width and check digit checking. I am using Tkinter for the rest of the tools and it would be ideal if the solution use it as well.
Any help appreciated
If I understand the problem correctly (and there's a good chance I don't...), the simplest solution might be to use a text widget.
Make the first line be a series of spaces the same length as the row. Use a couple of alternating tags (eg: "even" and "odd") to give each character an alternate color so they stand out from one another. The second line would be the header, and any remaining lines would be a couple lines of sample data.
Then, set up bindings on the first row to convert a space into an "x" when the user clicks on a character. If they click on an "x", convert it back to a space. They can then go and click on the character that is the start of each column. When the user is done, you can get the first line of the text widget and it will have an "x" for each column. You then just need a little function that translates that into whatever format you need.
It would look roughly like this (though obviously the colors would be different than what appears on this website)
x x x ...
SequenSack and PaFull Name****************************]JOB...
000001T1 P1 Sample A Sample ...
Here's a quick hack to illustrate the general idea. It's a little sloppy but I think it illustrates the technique. When you run it, click on an area in the first row to set or clear a marker. This will cause the header to be highlighted in alternate colors for each marker.
import sys
import Tkinter as tk
import tkFont
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
header = "SequenSack and PaFull Name****************************]JOB TITLE****************]HOSP NAME******************************]Delivery Address***********************]Alternate 1 Address********************]Calculated Text**********************************]POSTNET Bar"
sample = "000001T1 P1 Sample A Sample 123 Any Street Anytown 12345-6789 12345678900"
widget = DelimiterWidget(self, header, sample)
hsb = tk.Scrollbar(orient="horizontal", command=widget.xview)
widget.configure(xscrollcommand=hsb.set)
hsb.pack(side="bottom", fill="x")
widget.pack(side="top", fill="x")
class DelimiterWidget(tk.Text):
def __init__(self, parent, header, samplerow):
fixedFont = tkFont.nametofont("TkFixedFont")
tk.Text.__init__(self, parent, wrap="none", height=3, font=fixedFont)
self.configure(cursor="left_ptr")
self.tag_configure("header", background="gray")
self.tag_configure("even", background="#ffffff")
self.tag_configure("header_even", background="bisque")
self.tag_configure("header_odd", background="lightblue")
self.tag_configure("odd", background="#eeeeee")
markers = " "*len(header)
for i in range(len(header)):
tag = "even" if i%2==0 else "odd"
self.insert("end", " ", (tag,))
self.insert("end", "\n")
self.insert("end", header+"\n", "header")
self.insert("end", samplerow, "sample")
self.configure(state="disabled")
self.bind("<1>", self.on_click)
self.bind("<Double-1>", self.on_click)
self.bind("<Triple-1>", self.on_click)
def on_click(self, event):
'''Handle a click on a marker'''
index = self.index("#%s,%s" % (event.x, event.y))
current = self.get(index)
self.configure(state="normal")
self.delete(index)
(line, column) = index.split(".")
tag = "even" if int(column)%2 == 0 else "odd"
char = " " if current == "x" else "x"
self.insert(index, char, tag)
self.configure(state="disabled")
self.highlight_header()
return "break"
def highlight_header(self):
'''Highlight the header based on marker positions'''
self.tag_remove("header_even", 1.0, "end")
self.tag_remove("header_odd", 1.0, "end")
markers = self.get(1.0, "1.0 lineend")
i = 0
start = "2.0"
tag = "header_even"
while True:
try:
i = markers.index("x", i+1)
end = "2.%s" % i
self.tag_add(tag, start, end)
start = self.index(end)
tag = "header_even" if tag == "header_odd" else "header_odd"
except ValueError:
break
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
edit: I now see that you are looking for a gui. I'll leave this incorrect answer for posterity.
import csv
def fixedwidth2csv(fw_name, csv_name, field_info, headings=None):
with open(fw_name, 'r') as fw_in:
with open(csv_name, 'rb') as csv_out: # 'rb' => 'r' for python 3
wtr = csv.writer(csv_out)
if headings:
wtr.writerow(headings)
for line in fw_in:
wtr.writerow(line[pos:pos+width].strip() for pos, width in field_info)

Categories

Resources