Tkinter text widget adding tabs to selected text - python

I am trying to create a text editor for python using tkinter. When the user highlights lines of text and presses tab, I want the program to add a tab in front of each line of selected text, similar to what Idle does. This is the function I have so far:
self.TextBox.bind('<KeyPress-Tab>', self.tabtext)
def tabtext(self, e):
try:
untabbed = self.TextBox.selection_get() # get selected text
lines = untabbed.split('\n') # splits into a list of lines
tabbed = ''
for i in range(len(lines)):
lines[i] = ' ' + lines[i] # adds tabs to each line
tabbed = '\n'.join(lines) # joins list with newline character
old = self.TextBox.get("1.0", tk.END) # gets old text
new = old.replace(untabbed, tabbed) # replaces all instances of highlighted
# text with new text
self.TextBox.delete('1.0', tk.END) # deletes old text
self.TextBox.insert(tkinter.END, new) # adds new text
return 'break' # prevents it from deletion
except:
return
This code works, however if the selected text appears in the text box more than once, it will add tabs to each instance of the selected code. Is there any way to resolve this, maybe involving finding the position of the selected text. Any help would be greatly appreciated.

You can just replace the selected code rather that deleting and re-inserting all of the text.
The first step is to get the index of the start of the line for the selection:
index = self.TextBox.index("sel.first linestart")
Next, delete all of the lines in the selection:
self.TextBox.delete("sel.first linestart", "sel.last lineend")
Finally, insert the new text
self.TextBox.insert(index, tabbed)
Alternate method
Though, if all you're doing is inserting tags, you don't need to delete-and-replace. You can also just insert a tab for every line in the selected range. All you have to do is iterate over the lines. It would look something like this:
def tabtext(self, e):
last = self.TextBox.index("sel.last linestart")
index = self.TextBox.index("sel.first linestart")
while self.TextBox.compare(index,"<=", last):
self.TextBox.insert(index, " ")
index = self.TextBox.index("%s + 1 line" % index)
return "break"

Related

Removing '\n' from a string without using .translate, .replace or strip()

I'm making a simple text-based game as a learning project. I'm trying to add a feature where the user can input 'save' and their stats will be written onto a txt file named 'save.txt' so that after the program has been stopped, the player can then upload their previous stats and play from where they left off.
Here is the code for the saving:
user inputs 'save' and class attributes are saved onto the text file as text, one line at a time
elif first_step == 'save':
f = open("save.txt", "w")
f.write(f'''{player1.name}
{player1.char_type} #value is 'Wizard'
{player1.life}
{player1.energy}
{player1.strength}
{player1.money}
{player1.weapon_lvl}
{player1.wakefulness}
{player1.days_left}
{player1.battle_count}''')
f.close()
But, I also need the user to be able to load their saved stats next time they run the game. So they would enter 'load' and their stats will be updated.
I'm trying to read the text file one line at a time and then the value of that line would become the value of the relevant class attribute in order, one at a time. If I do this without converting it first to a string I get issues, such as some lines being skipped as python is reading 2 lines as one and putting them altogether as a list.
So, I tried the following:
In the below example, I'm only showing the data from the class attributes 'player1.name' and 'player1.char_type' as seen above as to not make this question as short as possible.
elif first_step == 'load':
f = open("save.txt", 'r')
player1.name_saved = f.readline() #reads the first line of the text file and assigns it's value to player1.name_saved
player1.name_saved2 = str(player1.name_saved) # converts the value of player1.name_saved to a string and saves that string in player1.name_saved2
player1.name = player1.name_saved2 #assigns the value of player1.name_saved to the class attribute player1.name
player1.char_type_saved = f.readlines(1) #reads the second line of the txt file and saves it in player1.char_type_saved
player1.char_type_saved2 = str(player1.char_type_saved) #converts the value of player1.char_type_saved into a string and assigns that value to player1.char_type_saved2
At this point, I would assign the value of player1.char_type_saved2 to the class attribute player1.char_type so that the value of player1.char_type enables the player to load the previous character type from the last time they played the game. This should make the value of player1.char_type = 'Wizard' but I'm getting '['Wizard\n']'
I tried the following to remove the brackets and \n:
final_player1.char_type = player1.char_type_saved2.translate({ord(c): None for c in "[']\n" }) #this is intended to remove everything from the string except for Wizard
For some reason, the above only removes the square brackets and punctuation marks but not \n from the end.
I then tried the following to remove \n:
final_player1.char_type = final_player1.char_type.replace("\n", "")
final_player1.char_type is still 'Wizard\n'
I've also tried using strip() but I've been unsuccessful.
If anyone could help me with this I would greatly appreciate it. Sorry if I have overcomplicated this question but it's hard to articulate it without lots of info. Let me know if this is too much or if more info is needed to answer.
If '\n' is always at the end it may be best to use:
s = 'wizard\n'
s = s[:-1]
print(s, s)
Output:
wizard wizard
But I still think strip() is best:
s = 'wizard\n'
s = s.strip()
print(s, s)
Output:
wizard wizard
Normaly it should work with just
char_type = "Wizard\n"
char_type.replace("\n", "")
print(char_type)
The output will be "Wizard"

How to convert a text file where data is separated by rows using ids, to an easy lookup (dictionary, json, etc.) preserving whitespacing/lines?

I have a text file that looks like this
start_id=372
text: this is some text with
vartions in
white spacing and such
END_OF_RECORD
start_id=3453
text: Continued for
this record
that has other
variations in whitespacing
END_OF_RECORD
I need to convert this such that I can easily access the data with the preserved whitespacing and lines.
So something like this
result = function('start_id=3453')
result
returns
text: Continued for
this record
that has other
variations in whitespacing
The reason I need to preserve the whitespacing is because I need to look stuff by span. So
result[11:14]
results in
Con
Strategies I have though up of:
I have an algorithm that goes down the lines and searches if the line starts with 'start_id'. When I do, I go down the line until I reach end of line or whitespace, and record this span into a dictionary key.
Then I go down the lines until I hit 'END_OF_RECORD'.
I then somehow copy the whole line span into the dictionary value for that key.
My concerns about this method if there are any edge cases I am not thinking of, and how to copy whole several lines into a python value.
That should be actually quite simple if you use regex... something like this:
import re
dictionary_of_records = {} # records will be stored here
recording = False # this will allow me to prevent starting new record while recording and will hold the id
# matchers
start = re.compile(r'start_id=(\d*)')
end = re.compile(r'END_OF_RECORD')
with open('stack.txt') as file:
for line in file.readlines():
if start.match(line):
if recording:
raise Exception('Attempting to create new record without ending previous one!')
print('Start... matched!')
recording = start.search(line).group(1) # save the id of recording
print(f'Starting record with id {recording}')
current_record_string = '' # make a empty string to save recording to
elif end.match(line):
print('End... matched!')
dictionary_of_records[recording] = current_record_string # save the entry to dict
recording = False # reset recording to False
continue
elif not recording:
continue
else:
current_record_string += line
print(dictionary_of_records)
IIUC:
data = {}
for line in open('/content/deidentified-medical-text-1.0/id.text').readlines():
if line.startswith('START_OF_RECORD='):
id_ = line.strip().split('=')[1]
lines = []
elif line.startswith('||||END_OF_RECORD'):
data[id_] = ''.join(lines)
id_ = None
lines = []
elif id_:
lines.append(line)
>>> data
{372: 'text: this is some text with\nvartions in\n\n\n white spacing and such\n',
3453: 'text: Continued for\n\nthis record\n that has other\n\n\nvariations in whitespacing\n'}
>>> data[3453][11:14]
'Con'

Delete range from Text

I am trying to create a keypad using python tk library. I am running Python version 3.6.3.
1) I have a Text widget in my UI. When I press the backspace button, I want to delete the last character in the Text widget. I am keeping the count of the total number of characters in the Text widget as well.
So far I have tried:
def back():
global char_count # contains total char count
text.delete(char_count)
I also tried to adjust the last line to text.delete(char_count-1) thinking that may be the index count was off by 1 (I wasn't sure if my count matched index in the Text widget). The above code doesn't delete anything.
2) I was also trying to see how a range of text can be deleted. I have checked online and I find to delete the entire Text content, people use:
text.delete("1.0", tk.END)
This works, but if I try another approach to delete everything from the second index as follows, nothing happens:
text.delete("2.0", tk.END)
I wanted to ask what is the right way to delete the last character or a range from the text, assuming the indices to be used are in variables and not hard coded like "2.0" above.
1) The Text widget always insures that the last character in the widget is a newline character, so you could delete the one you want, the second-to-last character, like this:
def back():
text.delete('%s - 2c' % 'end')
No need to keep track of the character count in the Text widget.
A full working sample is here:
import sys
if sys.version_info.major == 3:
import tkinter as tk
else:
import Tkinter as tk
def back():
text.delete('%s - 2c' % tk.END)
root = tk.Tk()
text = tk.Text(root)
text.pack()
tk.Button(root, text='Delete', command = back).pack()
root.mainloop()
2) Watch out for text.delete("2.0", tk.END). "2.0" is the start of the second line. The index of a Text widget has different formats, but the simplest is text string 'X.Y', where X is the line number (starting at 1) and Y is the column number (starting at 0) of that line. And the columns don't always line up, since a tab character will take a single column but look much wider in the Text widget.
You can call the delete() method with variables, like text.delete(startIndex, stopIndex). The trick is making sure that the indexes are valid. '1.0' represents the very first position in the Text widget, and 'end' represents the very last. Have a look at http://effbot.org/tkinterbook/text.htm for a pretty reasonable and concise look at Text widgets and how their indexes work.
There are different ways to manipulate a Text index, but some of the most common are with the text.index(arg) method, which returns an 'X.Y' representation of arg, and the nextpos = text.index('%s + 1 chars' % thispos) format, which allows you to do basic math on an index. In this case, it would set nextpos to the next column. But the '+ 1' can be plus or minus any_number, and the 'chars' can be 'lines' or 'words'. There's a lot to it. But have a look at that effbot.org page.

How can I highlight a full row of text in a QPlainTextEdit widget?

I've made a little editor using a QPlainTextEdit and I'd like to be able to highlight a whole row of text to show which line has an error on.
I can format the text, but I can't work out how to set the cursor position to the start and end position of the text on a specified row.
This snippet shows where I've gotten to:
editor = QtGui.QPlainTextEdit()
fmt = QtGui.QTextCharFormat()
fmt.setUnderlineColor(Qt.red)
fmt.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
# I'd like these values to encompass the whole of say, line 4 of the text
begin = 0
end = 5
cursor = QtGui.QTextCursor(editor.document())
cursor.setPosition(begin, QtGui.QTextCursor.MoveAnchor)
cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
cursor.setCharFormat(fmt)
Can I work out the beginning and end points for the cursor to highlight, from just a row number?
Thanks to Ekrumoro I managed to get this working like so:
editor = QtGui.QPlainTextEdit()
fmt = QtGui.QTextCharFormat()
fmt.setUnderlineColor(Qt.red)
fmt.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
block = editor.document().findBlockByLineNumber(line)
blockPos = block.position()
cursor = QtGui.QTextCursor(editor.document())
cursor.setPosition(blockPos)
cursor.select(QtGui.QTextCursor.LineUnderCursor)
cursor.setCharFormat(fmt)

python-taking part of the text stored within a combo box

I have a combo box on a form that I created. This combo box displays an ID and a name together. I am writing the contents of the combo box to a database. So far it writes correctly when I use this piece of code
self.ui.residentComboBox.currentText()
Now what I want to be able to do is pull only the ID from the combo box instead of the ID and the name together. Can anyone help?
If self.ui.residentComboBox.currentText() returns a string, and the ID is just the first word, you can do this:
self.ui.residentComboBox.currentText().split()[0]
which splits the string into a list of words (separated by whitespace) and then the [0] gives the first item in that list.
For example:
t = "3426523 askew chan"
print t.split()
#['3426523','askew','chan']
print t.split()[0]
#'3426523'

Categories