How to retrieve lines from text widget individually - python

I am trying to retrieve each new line in a text widget separately, I have 3 instances of Container class, the first instance prints data as expected but for second instance duplicates of first line are returned
I am using object.get('current linestart', 'current lineend') to return new lines separately
full code: https://pastebin.com/mLR3zbFg
class Container(tk.Frame):
def __init__(self, parent = None, priority = 3, bg = 'bisque'):
self.inpList = []
self.b = tk.Button(self.f, text = 'add', command = lambda: self.add_task(priority))
def add_task(self, priority): # refer 1.2_text for implementation
finished = False
self.t = tk.Text(self.f)
self.t.configure(height = 10, wrap = 'word')
self.t.bind("<Return>", self.save_task)
self.t.pack(fill = tk.X)
def print_all(self):
print(self.inpList)
def save_task(self, event):
td = self.t.get('current linestart', 'current lineend')
self.inpList.append(td)
if __name__ == '__main__':
window = tk.Tk()
window.minsize(300, 600)
p1 = Container(window, priority = 1)
p2 = Container(window, bg = 'blue', priority = 2)
p3 = Container(window, bg = 'red', priority = 3)
window.mainloop()

figbeam's answer could work, but it wouldn't be great if you had a lot of text inputted and it seems like you want to read each line individually. Here's a better solution in my opinion:
According to the docs, current doesn't seem to do what you expect; namely, current will give you to the character closest to your mouse (and only if you actually move your mouse). This could be a reason why you were noticing weird behavior for the widgets that were below but not the one on top.
A better solution is to move to the end of the text, then up one line, and then use the linestart and lineend selectors you had prior. Namely change
td = self.t.get('current linestart', 'current lineend')
to
td = self.t.get('end - 1 lines linestart', 'end - 1 lines lineend')
Things should work as expected after this change!

Is it necessary to read each line separately? Otherwise you could read the lines into a list and then work with the list items separately.
As far as I can tell the delimiter for lines is newline, which can make it look odd if you use wrap.
Here's an example of reading multiple lines into a list:
line_list = textbox.get('1.0', 'end').split('\n')
for line in line_list:
# Process line
Just a thought.

The line td = self.t.get('current linestart', 'current lineend') doesn't work as expected. One solution is to read the whole content of the text box at each update (as suggested also in https://stackoverflow.com/a/55739714/2314737).
In the code substitute the function save_task() with:
def save_task(self, event):
td = self.t.get("1.0",'end').rstrip()
self.inpList = td.split('\n')
This also takes care of any deleted lines that otherwise will stay in td.
See also this similar question: How to read the input(line by line) from a multiline Tkinter Textbox in Python?

Related

How to highlight last added text in text widget tkinter

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

How to dynamically add remove and delete labels created in a for loop from text file

I am trying to display a list of users saved in a text file on a line by line basis and have it updated as people launch and close my program respectively. I can't figure out how to update the labels as in create new ones and delete ones no longer present in the text file as I can't .set() or .config() them as far as I know since the actual text on the labels doesn't have to change.
Here is my code so far.
def list_users(self):
with open("usercheck.txt", "r") as ulst:
self.usr_list = []
for line in ulst:
self.usr_list.append(line)
def online(self):
for self.name in self.usr_list:
self.onlbl = tk.Label(self, text = self.name,bg = "#42f480")
self.onlbl.grid(row = self.onlcnt,column = 5,padx = 0)
self.onlcnt +=1
Running the online function with after just creates duplicates of the same label and does not update the amount of labels. self.onlcnt is 0
The reason might be that you are using the self keyword in your loop variable, which causes the variable to stay constant: don’t.
def online(self):
for name in self.usr_list:
self.onlbl = tk.Label(self, text = name,bg = "#42f480")
self.onlbl.grid(row = self.onlcnt,column = 5,padx = 0)
self.onlcnt +=1
Also you might want to store the Labels in a list, so you can access them later:
def online(self):
try:
self.labels
except AttributeError:
self.labels = []
self.onlcnt = 0
for name in self.usr_list:
onlbl = tk.Label(self, text = name,bg = "#42f480")
onlbl.grid(row = self.onlcnt,column = 5,padx = 0)
self.labels.append(onlbl)
self.onlcnt +=1
root.after(5000, self.online) #run it again

Passing a variable between two functions

The following is a piece of code that I have been working on for awhile. I've been able to compile and run the code without error. However, I am having a difficult time with passing a variable from one function to another in my code.
The problem seems to occur after I run choose() and create self.newLists based on the desired indices. You'll notice that I added print(self.newLists) at the end of this function so that I can check to see if it is producing what I want.
The next function, simplify(), is where my issue arises. When I try to pass self.newLists from the previous function it doesn't seem to produce anything. I also tried printing and/or returning the variable named answer but it returns "none". I've been stumbling over this obstacle for awhile without any progress. Below is the code I am working on along with an example of what I want simplify() to produce.
from tkinter import *
from tkinter.filedialog import askopenfilename
class myFileOpener:
def __init__(self, master):
frame = Frame(master)
frame.pack()
print()
self.newLists = ()
self.printButton = Button(frame, text="Select File", command=self.openfile)
self.printButton.pack(side=LEFT)
self.runButton = Button(frame, text="Run", command=self.combine)
self.runButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command=frame.quit)
self.quitButton.pack(side=LEFT)
def openfile(self):
filename = askopenfilename(parent=root)
self.lines = open(filename)
# print(self.lines.read())
def choose(self):
g = self.lines.readlines()
for line in g:
matrix = line.split()
JD = matrix[2]
mintime = matrix[5]
maxtime = matrix[7]
self.newLists = [JD, mintime, maxtime]
print(self.newLists)
def simplify(self):
dates = {}
for sub in self.newLists:
date = sub[0]
if date not in dates:
dates[date] = []
dates[date].extend(sub[1])
answer = []
for date in sorted(dates):
answer.append([date] + dates[date])
return answer
def combine(self):
self.choose()
self.simplify()
root = Tk()
b = myFileOpener(root)
root.mainloop()
Example of desired output from simplify():
[['2014-158', '20:07:11.881', '20:43:04.546', '20:43:47.447', '21:11:08.997', '21:11:16.697', '21:22:07.717'],
['2014-163', '17:12:09.071', '17:38:08.219', '17:38:28.310', '17:59:25.649', '18:05:59.536', '18:09:53.243', '18:13:47.671', '18:16:53.976', '18:20:31.538', '18:23:02.243']]
It essentially groups times by certain dates.
You are not producing a list of lists. You are resetting self.newLists each loop iteration, to a single list with 3 elements:
for line in g:
matrix = line.split()
JD = matrix[2]
mintime = matrix[5]
maxtime = matrix[7]
self.newLists = [JD, mintime, maxtime]
You need to instead use list.append() to add those 3 elements to a list you set once, outside of the loop:
self.newLists = []
for line in g:
matrix = line.split()
JD = matrix[2]
mintime = matrix[5]
maxtime = matrix[7]
self.newLists.append([JD, mintime, maxtime])
Your simplify method is adding the individual characters of mintime to your output lists:
for sub in self.newLists:
date = sub[0]
if date not in dates:
dates[date] = []
dates[date].extend(sub[1])
You want to use list.append() there, not list.extend(). That loop can be simplified using dict.setdefault() rather than test for the key manually:
for date, mintime, maxtime in self.newLists:
dates.setdefault(date, []).append(mintime)

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)

ReportLab: Flowable too large on page 1 in frame 'normal' of template 'First'

I build PDF using ReportLab. My program has a MyDocTemplate(SimpleDocTemplate) class with two methods: beforePage(self) and afterPage(self) which add header and footer (as PNG image) on every page. There is also a MyDocStyle class which describe ParagraphStyle.
Main method looks like this:
TITLE = Paragraph(Title, MyDocStyle.h1)
TO = Paragraph(To, MyDocStyle.h2)
FROM = Paragraph(From, MyDocStyle.h2)
SUBJECT = Paragraph(Subject, MyDocStyle.h2)
LONG_PARAGRAPH = Paragraph(Text, MyDocStyle.h3)
...
Elements = [TITLE, TO, FROM, SUBJECT, LONG_PARAGRAPH, ...]
doc = MyDocTemplete('output.pdf', pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=4*cm, bottomMargin=4*cm)
doc.build(Elements)
Data comes from CSV files and GUI. From time to time (depends on data length) I receive an error:
Flowable <Spacer at 0x2631120 frame=normal>...(1 x 5.66929133858) too large
on page 1 in frame 'normal'(469.88976378 x 603.118110236) of template 'First'
This exception stop my program. For short Paragraphs I set in MyDocStyle class h2.keepWithNext = 1 however it's not perfect solution. ReportLab split correctly long paragraph if end of paragraph does not "coincide" with end of page (text area).
How can I deal with it?
This error occurs when ReportLab try to split a Spacer over two pages. It seems that the only way to workaround this issue is wrap your Spacer into a KeepTogether element:
elements.append(KeepTogether(Spacer(width, height)))
Solved. Don't use Spacer (e.g. Spacer(1, 0.2*cm)) as a separator for Paragraph. Instead, define spaceBefore and spaceAfter in ParagraphStyle, for example:
ParagraphStyle(name = 'Normal',
fontName = "Verdana",
fontSize = 11,
leading = 15,
alignment = TA_JUSTIFY,
allowOrphans = 0,
spaceBefore = 20,
spaceAfter = 20,
wordWrap = 1)

Categories

Resources