From: Dave McCormick on
Hi All,

I am new to Python and the list so I hope I am posting this correctly...

I am working on a way to have text automatically formated in a Tkiniter
Text widget and would like some input on my code.
Currently I am using Python 2.5 because the server I use has that
installed. Tkinter is tk8.4.

Most of the time when I type red, blue, or green the code works as
expected. When I copy paste text into the widget the last line is
parsed with part of the previous lines
So I guess the problem is in the "looping"?

Here is my code:
from Tkinter import *
root = Tk()
def get_position(event):
start = 1.0
while 1:
pos = Tbox.search("red",END,backwards=TRUE)
if not pos:
break
red = pos + "-1c"
Tbox.tag_add("red", pos, float(pos)+.03)
Tbox.tag_config("red", foreground="red")

pos = Tbox.search("blue",END,backwards=TRUE)
if not pos:
break
blue = pos + "-1c"
Tbox.tag_add("blue", pos, float(pos)+.04)
Tbox.tag_config("blue", foreground="blue")

pos = Tbox.search("green",END,backwards=TRUE)
if not pos:
break
green = pos + "-1c"
Tbox.tag_add("green", pos, float(pos)+.05)
Tbox.tag_config("green", foreground="green")
break

Tbox = Text(root,width=40, height=15, wrap=CHAR)
Tbox.grid(column=0, row=0, sticky=(N+W+E+S))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
Tbox.bind("<KeyRelease>", get_position)
Tbox.focus()
root.mainloop()

Thank you,
Dave
From: John Posner on
On Wed, 30 Dec 2009 12:58:06 -0500, Dave McCormick <mackrackit(a)gmail.com>
wrote:

> Hi All,
>
> I am new to Python and the list so I hope I am posting this correctly...
>
> I am working on a way to have text automatically formated in a Tkiniter
> Text widget and would like some input on my code.
> Currently I am using Python 2.5 because the server I use has that
> installed. Tkinter is tk8.4.
>
> Most of the time when I type red, blue, or green the code works as
> expected. When I copy paste text into the widget the last line is
> parsed with part of the previous lines
> So I guess the problem is in the "looping"?
>
> Here is my code:
> from Tkinter import *
> root = Tk()
> def get_position(event):
> start = 1.0

A couple of problems here: you define "start", but then never use it.
Worse, it looks like you don't understand that a line-column index into a
Tkinter Text widget is a STRING, not a FLOAT.


> while 1: pos = Tbox.search("red",END,backwards=TRUE)

I suggest that you use Tbox.get() instead of Tbox.search(), and then use
Python's more powerful text-search tools. More on this below.


> if not pos:
> break
> red = pos + "-1c"
> Tbox.tag_add("red", pos, float(pos)+.03)
> Tbox.tag_config("red", foreground="red")

You don't want to define the "red" tag every time get_position() is
executed -- that is, every time the user presses a key. Define the
red/green/blue tags just once, right after you create the Text widget.


> pos = Tbox.search("blue",END,backwards=TRUE)
> if not pos:
> break
> blue = pos + "-1c"
> Tbox.tag_add("blue", pos, float(pos)+.04)
> Tbox.tag_config("blue", foreground="blue")
>
> pos = Tbox.search("green",END,backwards=TRUE)
> if not pos:
> break
> green = pos + "-1c"
> Tbox.tag_add("green", pos, float(pos)+.05)
> Tbox.tag_config("green", foreground="green")

The previous 6 lines are almost identical to the 6 lines that precede
them. This is fine for prototyping, but when you find yourself writing
code like this, think about using a loop or a parameterized function call.
For example, you might write this function:

def insert_color_markup(color):
...

.... and then call it as many times as you need to:

insert_color_markup("red")
insert_color_markup("green")
insert_color_markup("blue")

Now, about those text-search tools: the "re" (regular expression) module
include the function "finditer". This is a real power tool, combining
regular expressions and Python iterators -- both of which can be
intimidating to newcomers. But it's just what you want, IMHO. I hope the
following annotated IDLE transcript convinces you:

Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit
(Intel)] on win32
>>> import re

>>> text = """The red ball is red, not green. On other other
.... hand, the green ball has both red and blue highlights.
.... Thank you.
.... """

>>> re.finditer("red", text)
<callable-iterator object at 0x00CC46D0>

... not too exciting, but this is better:

>>> list(re.finditer("red", text))
[<_sre.SRE_Match object at 0x00C01F70>, <_sre.SRE_Match object at
0x00C06E20>, <_sre.
SRE_Match object at 0x00C06E58>]

... this list indicates that we got three hits on the word "red"

>>> [ matchobj.span() for matchobj in re.finditer("red", text) ]
[(4, 7), (16, 19), (77, 80)]

... paydirt: a list of (start,end) pairs for an invocation of
Text.tag_add()

One more hint: forget about the line-column indexes into the contexts of a
Text widget. Just count characters from the beginning, e.g.:

"1.0 + %d chars" % start_position

> break
>
> Tbox = Text(root,width=40, height=15, wrap=CHAR)
> Tbox.grid(column=0, row=0, sticky=(N+W+E+S))
> root.grid_columnconfigure(0, weight=1)
> root.grid_rowconfigure(0, weight=1)
> Tbox.bind("<KeyRelease>", get_position)
> Tbox.focus()
> root.mainloop()
>
> Thank you,
> Dave

I hope this set of hints is helpful, and not too disjointed, terse, or
cryptic. I think this is a cute little app!

-John
From: Dave McCormick on
John,

Thank you for the tips.
I was changing the line-column index to a FLOAT because the search would
return the starting position (pos) of the string, then by making it a
FLOAT and adding the string length I was able to get the end position.
If "red" was on line 1 column 0..
Tbox.tag_add("red", pos, float(pos)+.03)
=
Tbox.tag_add("red", 1.0, 1.3)
It was all I could come up with.

You have convinced me about the re.finditer for this, I think...
Still in the prototyping mode:

def get_position(event):
pos = Tbox.get(1.0, END)
match = [ matchobj.span() for matchobj in
re.finditer("red", pos) ]
print "match ",match #debug to shell

Gives all of START,END pairs just fine. It is the last hint about
line-column indexes that I am have problems with. All of the
documentation I can find about "text.tag_add()" uses line-column for
coordinates.
If I count characters from the beginning how do I know what line the
text is on?

Would you mind making your last hint a bit stronger...

Thanks again!
Dave



John Posner wrote:
> On Wed, 30 Dec 2009 12:58:06 -0500, Dave McCormick
> <mackrackit(a)gmail.com> wrote:
>
>> Hi All,
>>
>> I am new to Python and the list so I hope I am posting this
>> correctly...
>>
>> I am working on a way to have text automatically formated in a
>> Tkiniter Text widget and would like some input on my code.
>> Currently I am using Python 2.5 because the server I use has that
>> installed. Tkinter is tk8.4.
>>
>> Most of the time when I type red, blue, or green the code works as
>> expected. When I copy paste text into the widget the last line is
>> parsed with part of the previous lines
>> So I guess the problem is in the "looping"?
>>
>> Here is my code:
>> from Tkinter import *
>> root = Tk()
>> def get_position(event):
>> start = 1.0
>
> A couple of problems here: you define "start", but then never use it.
> Worse, it looks like you don't understand that a line-column index
> into a Tkinter Text widget is a STRING, not a FLOAT.
>
>
>> while 1: pos = Tbox.search("red",END,backwards=TRUE)
>
> I suggest that you use Tbox.get() instead of Tbox.search(), and then
> use Python's more powerful text-search tools. More on this below.
>
>
>> if not pos:
>> break
>> red = pos + "-1c"
>> Tbox.tag_add("red", pos, float(pos)+.03)
>> Tbox.tag_config("red", foreground="red")
>
> You don't want to define the "red" tag every time get_position() is
> executed -- that is, every time the user presses a key. Define the
> red/green/blue tags just once, right after you create the Text widget.
>
>
>> pos = Tbox.search("blue",END,backwards=TRUE)
>> if not pos:
>> break
>> blue = pos + "-1c"
>> Tbox.tag_add("blue", pos, float(pos)+.04)
>> Tbox.tag_config("blue", foreground="blue")
>>
>> pos = Tbox.search("green",END,backwards=TRUE)
>> if not pos:
>> break
>> green = pos + "-1c"
>> Tbox.tag_add("green", pos, float(pos)+.05)
>> Tbox.tag_config("green", foreground="green")
>
> The previous 6 lines are almost identical to the 6 lines that precede
> them. This is fine for prototyping, but when you find yourself writing
> code like this, think about using a loop or a parameterized function
> call. For example, you might write this function:
>
> def insert_color_markup(color):
> ...
>
> ... and then call it as many times as you need to:
>
> insert_color_markup("red")
> insert_color_markup("green")
> insert_color_markup("blue")
>
> Now, about those text-search tools: the "re" (regular expression)
> module include the function "finditer". This is a real power tool,
> combining regular expressions and Python iterators -- both of which
> can be intimidating to newcomers. But it's just what you want, IMHO. I
> hope the following annotated IDLE transcript convinces you:
>
> Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit
> (Intel)] on win32
>>>> import re
>
>>>> text = """The red ball is red, not green. On other other
> ... hand, the green ball has both red and blue highlights.
> ... Thank you.
> ... """
>
>>>> re.finditer("red", text)
> <callable-iterator object at 0x00CC46D0>
>
> ... not too exciting, but this is better:
>
>>>> list(re.finditer("red", text))
> [<_sre.SRE_Match object at 0x00C01F70>, <_sre.SRE_Match object at
> 0x00C06E20>, <_sre.
> SRE_Match object at 0x00C06E58>]
>
> ... this list indicates that we got three hits on the word "red"
>
>>>> [ matchobj.span() for matchobj in re.finditer("red", text) ]
> [(4, 7), (16, 19), (77, 80)]
>
> ... paydirt: a list of (start,end) pairs for an invocation of
> Text.tag_add()
>
> One more hint: forget about the line-column indexes into the contexts
> of a Text widget. Just count characters from the beginning, e.g.:
>
> "1.0 + %d chars" % start_position
>
>> break
>>
>> Tbox = Text(root,width=40, height=15, wrap=CHAR)
>> Tbox.grid(column=0, row=0, sticky=(N+W+E+S))
>> root.grid_columnconfigure(0, weight=1)
>> root.grid_rowconfigure(0, weight=1)
>> Tbox.bind("<KeyRelease>", get_position)
>> Tbox.focus()
>> root.mainloop()
>>
>> Thank you,
>> Dave
>
> I hope this set of hints is helpful, and not too disjointed, terse, or
> cryptic. I think this is a cute little app!
>
> -John
From: Lie Ryan on
On 1/1/2010 2:24 AM, Dave McCormick wrote:
> If I count characters from the beginning how do I know what line the
> text is on?
> Would you mind making your last hint a bit stronger...

From http://infohost.nmt.edu/tcc/help/pubs/tkinter/text-index.html:
"""
+ n chars

From the given index, move forward n characters. This operation
will *cross line boundaries.*
"""
From: John Posner on
On Thu, 31 Dec 2009 10:24:44 -0500, Dave McCormick <mackrackit(a)gmail.com>
wrote:

> John,
>
> Thank you for the tips.
> I was changing the line-column index to a FLOAT because the search would
> return the starting position (pos) of the string, then by making it a
> FLOAT and adding the string length I was able to get the end position.
> If "red" was on line 1 column 0..
> Tbox.tag_add("red", pos, float(pos)+.03)
> =
> Tbox.tag_add("red", 1.0, 1.3)
> It was all I could come up with.

Yup, Dave, I've dug this kind of hole for myself many times!

>
> You have convinced me about the re.finditer for this, I think... Still
> in the prototyping mode:
>
> def get_position(event):
> pos = Tbox.get(1.0, END)
> match = [ matchobj.span() for matchobj in
> re.finditer("red", pos) ]
> print "match ",match #debug to shell

Notes:

* Variable "pos" should be "text" or "complete_text" or something similar.

* The first argument to the get() function must be a string:

wrong ... complete_text = Tbox.get(1.0, END)
right ... complete_text = Tbox.get("1.0", END)

But there's a more important problem. Is this function supposed to handle
*one* word to be colored red, or *all the words* to be colored red? Here's
what you want to do on each user keystroke:

1. Use get() to place the entire contents of the Text widget in a
variable, say "complete_text".

2. Use re.finditer() to generate START,END pairs for the substrings to
be colored red. You might find it easier to create a list from the
iterator, though it isn't really necessary:

start_end_pairs = list(re.finditer("red", complete_text))

3. Loop over the START,END pairs in this list. In each loop, use
tag_add() to tag one of the substrings.

[OOPS: I apologize if my suggestion in the previous post misled you. I
described the output of finditer() as "a list of (start,end) pairs for an
invocation of Text.tag_add()". I should have said "a list of (start,end)
pairs, *WHICH CAN BE LOOPED OVER, FOR A SERIES OF INVOCATIONS* of
Text.tag_add()".]

Note that the 3 steps above only handle the color red. So you want to
place these steps in a function, then call the function for each color:

insert_color_markup(text, "red")
insert_color_markup(text, "green")
insert_color_markup(text, "blue")

For each call, pass in the contents of complete_text as the first argument.

So the function-call hierarchy would be:

get_position() <--- invoked on each keystroke
insert_color_markup() <--- called once for each color
get() then finditer() then loop-driven calls to tag_add()

I hope that makes sense to you!


> Gives all of START,END pairs just fine. It is the last hint about
> line-column indexes that I am have problems with. All of the
> documentation I can find about "text.tag_add()" uses line-column for
> coordinates.

Lie Ryan has already pointed you to clarifying documentation. Be sure to
bookmark http://infohost.nmt.edu/tcc/help/pubs/tkinter/ in your Web
browser!

> If I count characters from the beginning how do I know what line the
> text is on? Would you mind making your last hint a bit stronger...

The point is that *you don't need to*. Handling the text as a simple
sequence of characters is much simpler than worrying about line/column
pairs. (If you were using a different shade of red on different lines, you
*would* need to worry about line/column pairs.)

One more suggestion, which I forgot to make in the previous round. Your
code includes this:

Tbox.grid(column=0, row=0, sticky=(N+W+E+S))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)

You're working too hard here. This is sufficient:

Tbox.pack()

Keep at it, Dave. I've posted a complete solution at
http://cl1p.net/jjp_dynamic_text_color/. But I suggest that you put in a
couple of more hours of coding before peeking at it.

Best,
John