Delete range from Text - python

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.

Related

How do you make the tkinter text widget recognise lines in the index?

I'm working on highlighting chosen parts of a text green in a text widget in Tkinter.
To my understanding, on Tkinter the index for the second line, fourth character of a text would be 2.04. For some reason, it's not recognising a new line and the decimal number keeps increasing into the hundreds so I'm struggling to highlight words once the total characters of the text exceed 99. When it does, it highlights lots of text after the chosen word in green.
I've set the max width of the text widget to 99 characters to make the index create lines but but it's still not doing anything.
For clarification, I'm trying to highlight the word 'calculates' in green with this code.
from tkinter import *
window = Tk()
text_window = Text(window, height=10, width=99, wrap=WORD)
text_window.insert(INSERT, sample_text)
text_window.configure(state=DISABLED)
text_window.pack()
countVar = StringVar()
pos = text_window.search("calculates", "1.0", stopindex="end", count=countVar)
end_num = float(countVar.get()) / 100
position = float(pos)
print(position)
print(end_num)
end_point = position + end_num
text_window.tag_configure("search", background="green")
text_window.tag_add("search", pos, end_point)
window.mainloop()
"2.04" is not valid. It will work, but something like "2.08" won't since 08 is an invalid octal number. A text widget index must be string in the form of a valid integer, a period, and then another valid integer.
The text widget index also supports modifiers, so you can easily add or subtract characters. In your case it would look something like this:
end_point = f"{pos}+{countVar.get()}chars"
or
end_point = f"{pos}+{countVar.get()}c"

wx.lib.agw.ultimatelistctrl. Prohibit increasing the height of the element

There is an object UltimateListCtrl
from wx.lib.agw import ultimatelistctrl as ULC
self._ulc_graphs = ULC.UltimateListCtrl(self, size = (-1, 150),
agwStyle=wx.LC_REPORT|wx.LC_VRULES|wx.LC_HRULES|ULC.ULC_HAS_VARIABLE_ROW_HEIGHT|ULC.ULC_SINGLE_SEL)
This list contains six columns. Last column "Comment". Since This object does not allow direct editing of the element value in the nth column (only the 0th column can be edited). It was decided to insert a button into each row of the last column, clicking on which flies a dialog with wx.TextCtrl. The user enters a comment there, clicks OK and the text of the comment is entered in the last column of the nth item in the list. 5th column (if with 0):
self._ulc_graphs.InsertColumn(5, 'Комментарий', ULC.ULC_FORMAT_CENTER, 100)
# in the loop I add rows to the list
self._ulc_graphs.SetStringItem (self._ulc_graphs_index, 5, '')
button_comment = wx.Button(self._ulc_graphs, -1, '...', size=(23, 23))
self._ulc_graphs.SetItemWindow(self._ulc_graphs_index, 5, button_comment, False)
button_comment.Bind(wx.EVT_BUTTON, self.OnButtonComment)
self._ulcItem_btn[self._ulc_graphs_index] = button_comment
Result:
Of course, forcing a user to write text into one row is a bad idea. Therefore, i included the wx.TE_MULTILINE style for the wx.TextCtrl object. But when I insert multi-line text into the list cell, this cell increases its size: element height = number of lines in the text.
It looks awful, of course:
Can I ban it somehow (height increase)?
I may have misunderstood your problem but why don't you simply replace all line feeds in the dialog result.
Something along these lines:
def OnButtonComment(self,event):
dlg = wx.TextEntryDialog(self, "Comment", caption="Input Data",
value="", style=wx.OK|wx.CANCEL|wx.TE_MULTILINE)
dlg.ShowModal()
txt = dlg.GetValue()
txt = txt.replace('\n',' ')
self.list.SetStringItem(self.index, 1, txt)

Tkinter text widget adding tabs to selected text

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"

Tkinter: inverting what text has a tag in a range of two indexes

How do I create a method that given two indexes it will remove a specified tag from the text that has it and add that tag to the text that does not have it (only within the specified range)?
Here's the working method:
def invert_tag(self, start, end=None, tag=SEL, w=None):
if w==None:
w=self.cur_scroll() #This is my current text widget
i=0
while w.compare(start+"+"+str(i)+"c", "<", end):
if "sel" in w.tag_names(start+"+"+str(i)+"c"):
w.tag_remove(tag, start+"+"+str(i)+"c")
else:
w.tag_add(tag, start+"+"+str(i)+"c")
i+=1

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