Home > Software engineering >  Tkinter - Use characters/bytes offset as index for text widget
Tkinter - Use characters/bytes offset as index for text widget

Time:11-16

I want to delete part of a text widget's content, using only character offset (or bytes if possible).

I know how to do it for lines, words, etc. Looked around a lot of documentations:

Here is an example mre:

import tkinter as tk

root = tk.Tk()

text = tk.Text(root)

txt = """Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Suspendisse enim lorem, aliquam quis quam sit amet, pharetra porta lectus.
Nam commodo imperdiet sapien, in maximus nibh vestibulum nec.
Quisque rutrum massa eget viverra viverra. Vivamus hendrerit ultricies nibh, ac tincidunt nibh eleifend a. Nulla in dolor consequat, fermentum quam quis, euismod dui.
Nam at gravida nisi. Cras ut varius odio, viverra molestie arcu.

Pellentesque scelerisque eros sit amet sollicitudin venenatis.
Proin fermentum vestibulum risus, quis suscipit velit rutrum id.
Phasellus nisl justo, bibendum non dictum vel, fermentum quis ipsum.
Nunc rutrum nulla quam, ac pretium felis dictum in. Sed ut vestibulum risus, suscipit tempus enim.
Nunc a imperdiet augue.
Nullam iaculis consectetur sodales.
Praesent neque turpis, accumsan ultricies diam in, fermentum semper nibh.
Nullam eget aliquet urna, at interdum odio. Nulla in mi elementum, finibus risus aliquam, sodales ante.
Aenean ut tristique urna, sit amet condimentum quam. Mauris ac mollis nisi.
Proin rhoncus, ex venenatis varius sollicitudin, urna nibh fringilla sapien, eu porttitor felis urna eu mi.
Aliquam aliquam metus non lobortis consequat.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean id orci dui."""

text.insert(tk.INSERT, txt)


def test_delete(event=None):
    text.delete() # change this line here

text.pack(fill="both", expand=1)
text.pack_propagate(0)
text.bind('<Control-e>', test_delete)
root.mainloop()

It display an example text inside a variable, inside a text widget. I use a single key binding to test some of the possible ways to do what I want on that piece of text.

I tried a lot of things, both from the documentation(s) and my own desperation:

  • text.delete(0.X): where X is any number. I thought since lines were 1.0, maybe using 0.X would work on chars only. It only work with a single char, regardless of what X is (even with a big number).
  • text.delete(1.1, 1.3): This act on the same line, because I was trying to see if it would delete 3 chars in any direction on the same line. It delete 2 chars instead of 3, and it does so by omitting one char at the start of the first line, and delete 2 char after that.
  • text.delete("end - 9c"): only work at the end (last line), and omit 7 chars starting from EOF, and then delete a single char after that.
  • text.delete(0.1, 0.2): Does not do anything. Same result for other 0.X, 0.X combination.

Example of what I try to achieve:

Using the example text above would take too long, so let's consider a smaller string, say "hello world". Now let's say we use an index that start with 1 (doesn't matter but make things easier to explain), the first char is "h" and the last one is "d". So say I use chars range such as "2-7", that would be "ello wo". Say I want to do "1-8"? -> "hello wo", and now starting from the end, "11-2", "ello world".

This is basically similar to what f.tell() and f.seek() do. I want to do something like that but using only the content inside of the text widget, and then do something on those bytes/chars ranges (in the example above, I'm deleting them, etc).

CodePudding user response:

TL;DR

You can use a relative index similar to f.tell() by giving a starting index and then add or remove lines or characters. For example, text.delete("1.0", "1.0 11c") ("1.0" plus 11 characters)

The canonical documentation for text widget indexes is in the tcl/tk man pages in a section named Indices.


text.delete(0.X): where X is any number. I thought since lines were 1.0, maybe using 0.X would work on chars only. It only work with a single char, regardless of what X is (even with a big number).

I don't know what you mean by "since lines were 1.0". The first part of the index is the line number, the second is the character number. Lines start counting at 1, characters at zero. So, the first character of the widget is "1.0". The first character of line 2 is "2.0", etc.

But yes, text.delete with a single index will only delete one character. That is the defined behavior.

text.delete(1.1, 1.3): This act on the same line, because I was trying to see if it would delete 3 chars in any direction on the same line.

The delete method is documented to delete from the first index to the character before the last index:

"Delete a range of characters from the text. If both index1 and index2 are specified, then delete all the characters starting with the one given by index1 and stopping just before index2"

text.delete("end - 9c"): only work at the end (last line), and omit 7 chars starting from EOF, and then delete a single char after that.

Yes. Again, a single index given to delete will delete just a single character.

text.delete(0.1, 0.2): Does not do anything. Same result for other 0.X, 0.X combination.

0.1 is an invalid index. An index is a string, not a floating point number, and the first number should be 1 or greater. Tkinter has to convert that number to a whole number greater than or equal to 1.So, both 0.1 and 0.2 are both converted to mean "1.0". Like I said earlier, the delete method stops before the second index, so you're deleting everything before character "1.0".

Using the example text above would take too long, so let's consider a smaller string, say "hello world". Now let's say we use an index that start with 1 (doesn't matter but make things easier to explain), the first char is "h" and the last one is "d". So say I use chars range such as "2-7", that would be "ello wo". Say I want to do "1-8"? -> "hello wo", and now starting from the end, "11-2", "ello world".

If "hello world" starts at character position "1.0", and you want to use a relative index to delete a range characters, you can delete it with something like text.delete("1.0", "1.0 11c") ("1.0" plus 11 characters)

CodePudding user response:

I emulated f.seek and combined it with text.delete. It seems what you basically was missing was that you need to take the insertion cursor into account. See the comments in the code

def seek_delete(offset, whence):
    if whence == 0: #from the beginning
        start = '1.0'
        end = f'{start}  {offset} chars'
    elif whence == 1:# from insertion cursor
        current = 'insert'
        if offset >= 0:#positive offset
            start = current
            end = f'{start}  {offset} chars'
        else:#negative offset
            start = f'{current} {offset} chars'
            end = current
    elif whence == 2:#from the end
        start = f'end {offset} chars'
        end = 'end'
    text.delete(start, end)

I have tested it with different values with this binding:

text.bind('<Control-e>', lambda e:seek_delete(-2,1))

As a bonus, you can emulate f.tell quite easy like this:

def tell(event):
    print(text.index('insert'))
  • Related