Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (68 page)

BOOK: Programming Python
5.82Mb size Format: txt, pdf, ePub
ads
Text

It’s been said that tkinter’s strongest
points may be its
Text
and
Canvas
widgets. Both provide a
remarkable amount of functionality. For instance, the tkinter
Text
widget was powerful enough to implement the
web pages of Grail, an experimental web browser coded in Python;
Text
supports complex font-style settings,
embedded images, unlimited undo and redo, and much more. The tkinter
Canvas
widget,
a general-purpose drawing device, allows for efficient
free-form graphics and has been the basis of sophisticated
image-processing and visualization applications.

In
Chapter 11
, we’ll put these two
widgets to use to implement
text editors (PyEdit), paint programs
(PyDraw), clock
GUIs (PyClock), and image programs
(PyPhoto and PyView). For the purposes of this tour
chapter, though, let’s start out using these
widgets
in simpler ways.
Example 9-10
implements a simple
scrolled-text display, which knows how to fill its display with a text
string or file.

Example 9-10. PP4E\Gui\Tour\scrolledtext.py

"a simple text or file viewer component"
print('PP4E scrolledtext')
from tkinter import *
class ScrolledText(Frame):
def __init__(self, parent=None, text='', file=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH) # make me expandable
self.makewidgets()
self.settext(text, file)
def makewidgets(self):
sbar = Scrollbar(self)
text = Text(self, relief=SUNKEN)
sbar.config(command=text.yview) # xlink sbar and text
text.config(yscrollcommand=sbar.set) # move one moves other
sbar.pack(side=RIGHT, fill=Y) # pack first=clip last
text.pack(side=LEFT, expand=YES, fill=BOTH) # text clipped first
self.text = text
def settext(self, text='', file=None):
if file:
text = open(file, 'r').read()
self.text.delete('1.0', END) # delete current text
self.text.insert('1.0', text) # add at line 1, col 0
self.text.mark_set(INSERT, '1.0') # set insert cursor
self.text.focus() # save user a click
def gettext(self): # returns a string
return self.text.get('1.0', END+'-1c') # first through last
if __name__ == '__main__':
root = Tk()
if len(sys.argv) > 1:
st = ScrolledText(file=sys.argv[1]) # filename on cmdline
else:
st = ScrolledText(text='Words\ngo here') # or not: two lines
def show(event):
print(repr(st.gettext())) # show as raw string
root.bind('', show) # esc = dump text
root.mainloop()

Like the
ScrolledList
in
Example 9-9
, the
ScrolledText
object in this file is designed to be a reusable component
which we’ll also put to work in later examples, but it can also be run
standalone to display text file contents. Also like the last section, this
script is careful to pack the scroll bar first so that it is cut out of
the display last as the window shrinks and arranges for the embedded
Text
object to expand in both
directions as the window grows. When run with a filename argument, this
script makes the window shown in
Figure 9-17
; it embeds a
Text
widget on the left and a cross-linked
Scrollbar
on the right.

Figure 9-17. scrolledtext in action

Just for fun, I populated the text file displayed in the window with
the following code and command lines (and not just because I used to live
near an infamous hotel in Colorado):

C:\...\PP4E\Gui\Tour>
type makefile.py
f = open('jack.txt', 'w')
for i in range(250):
f.write('%03d) All work and no play makes Jack a dull boy.\n' % i)
f.close()
C:\...\PP4E\Gui\Tour>
python makefile.py
C:\...\PP4E\Gui\Tour>
python scrolledtext.py jack.txt
PP4E scrolledtext

To view a file, pass its name on the command line—its text is
automatically displayed in the new window. By default, it is shown in a
font that may vary per platform (and might not be fixed-width on some),
but we’ll pass a
font
option to the
Text
widget in the next example to
change that. Pressing the Escape key fetches and displays the full text
content of the widget as a single string (more on this in a
moment).

Notice the
PP4E scrolledtext
message printed when this script runs. Because there is also a
scrolledtext.py
file in the standard Python
distribution (in module
tkinter
.
scrolled
text
) with a very different implementation
and interface, the one here identifies itself when run or imported, so you
can tell which one you’ve got. If the standard library’s alternative ever
goes away, import the class listed to get a simple text browser, and
adjust any text widget configuration calls to include a
.text
qualifier level (e.g.,
x.text.config
instead of
x.config
; the library version subclasses
Text
directly,
not
Frame
).

Programming the Text Widget

To understand
how this script works at all, though, we have to detour
into a few
Text
widget details here.
Earlier we met the
Entry
and
Message
widgets, which address a subset of the
Text
widget’s uses. The
Text
widget is much richer in both features
and interfaces—it supports both input and display of multiple lines of
text, editing operations for both programs and interactive users,
multiple fonts and colors, and much more.
Text
objects are created, configured, and
packed just like any other widget, but they have properties all their
own.

Text is a Python string

Although the
Text
widget is a
powerful tool, its interface seems to boil down to two
core concepts. First, the content of a
Text
widget is represented as a string in
Python scripts, and multiple lines are separated with the normal
\n
line
terminator. The string
'Words\ngo here'
, for instance, represents
two lines when stored in or fetched from a
Text
widget; it would normally have a
trailing
\n
also, but it doesn’t
have to.

To help illustrate this point, this script binds the Escape key
press to fetch and print the entire contents of the
Text
widget it embeds:

C:\...\PP4E\Gui\Tour>
python scrolledtext.py
PP4E scrolledtext
'Words\ngo here'
'Always look\non the bright\nside of life\n'

When run with arguments, the script stores a file’s contents in
the
Text
widget. When run without
arguments, the script stuffs a simple literal string into the widget,
displayed by the first Escape press output here (recall that
\n
is the escape sequence for the line-end
character). The second output here happens after editing the window’s
text, when pressing Escape in the shrunken window captured in
Figure 9-18
. By default,
Text
widget text is fully editable
using the usual edit operations for your platform.

Figure 9-18. scrolledtext gets a positive outlook

String positions

The second key to
understanding
Text
code has to do with the ways you specify a position in the text
string. Like the listbox,
Text
widgets allow you to specify such a position in a variety of ways. In
Text
, methods that expect a
position to be passed in will accept an index, a mark, or a tag
reference. Moreover, some special operations are invoked with
predefined marks and tags—the insert cursor is mark
INSERT
, and the current selection is tag
SEL
. Since they are fundamental to
Text
and the source of much of its
expressive power, let’s take a closer look at these settings.

Text indexes

Because it is a
multiple-line widget,
Text
indexes identify both a line and a
column. For instance, consider the interfaces of the basic insert,
delete, and fetch text operations used by this script:

self.text.insert('1.0', text)             # insert text at the start
self.text.delete('1.0', END) # delete all current text
return self.text.get('1.0', END+'-1c') # fetch first through last

In all of these, the first argument is an absolute index that
refers to the start of the text string: string
'1.0'
means row 1, column 0 (rows are
numbered from 1 and columns from 0, though
'0.0'
is accepted as a reference to the
start of the text, too). An index
'2.1'
refers to the second character in
the second row.

Like the listbox, text indexes can also be symbolic names: the
END
in the preceding
delete
call refers to the position just
past the last character in the text string (it’s a tkinter variable
preset to string
'end'
).
Similarly, the symbolic index
INSERT
(really, string
'insert'
) refers to the position
immediately after the insert cursor—the place where characters would
appear if typed at the keyboard. Symbolic names such as
INSERT
can also be called marks, described
in a moment.

For added precision, you can add simple arithmetic extensions
to index strings. The index expression
END+'-1c'
in the
get
call in the previous example, for
instance, is really the string
'end-1c'
and refers to one character back
from
END
. Because
END
points to just beyond the last
character in the text string, this expression refers to the last
character itself. The
−1c
extension effectively strips the trailing
\n
that this widget adds to its contents
(and which may add a blank line if saved in a file).

Similar index string extensions let you name characters ahead
(
+1c
), name lines ahead and
behind (
+2l
,
−2l
), and specify things such as line ends
and word starts around an index (
lineend
,
wordstart
). Indexes show up in most
Text
widget calls.

Text marks

Besides
row/column identifier strings, you can also pass
positions as names of marks—symbolic names for a position between
two characters. Unlike absolute row/column positions, marks are
virtual locations that move as new text is inserted or deleted (by
your script or your user). A mark always refers to its original
location, even if that location shifts to a different row and column
over time.

To create a mark, call the
Text
object’s
mark_set
method with a string name and an
index to give its logical location. For instance, this script sets
the insert cursor at the start of the text initially, with a call
like the first one here:

self.text.mark_set(INSERT, '1.0')             # set insert cursor to start
self.text.mark_set('linetwo', '2.0') # mark current line 2

The name
INSERT
is a
predefined special mark that identifies the insert cursor position;
setting it changes the insert cursor’s location. To make a mark of
your own, simply provide a unique name as in the second call here
and use it anywhere you need to specify a text position. The
mark_unset
call deletes marks by
name.

Text tags

In addition to absolute indexes and symbolic mark
names,
the
Text
widget
supports the notion of tags—symbolic names associated with one or
more substrings within the
Text
widget’s string. Tags can be used for many things, but they also
serve to represent a position anywhere you need one: tagged items
are named by their beginning and ending indexes, which can be later
passed to position-based calls.

For example, tkinter provides a built-in tag name,
SEL
—a tkinter name preassigned to string
'sel'
—which automatically refers
to currently selected text. To fetch the text selected (highlighted)
with a mouse, run either of these calls:

text = self.text.get(SEL_FIRST, SEL_LAST)      # use tags for from/to indexes
text = self.text.get('sel.first', 'sel.last') # strings and constants work

The names
SEL_FIRST
and
SEL_LAST
are just preassigned
variables in the tkinter module that refer to the strings used in
the second line here. The text
get
method expects
two indexes; to fetch text names by a tag, add
.first
and
.last
to the tag’s name to get its start
and end indexes.

To tag a substring, call the
Text
widget’s
tag_add
method
with a tag name string and start and stop positions (text can also
be tagged as added in
insert
calls). To remove a tag from all characters in a range of text, call
tag_remove
:

self.text.tag_add('alltext', '1.0', END)  # tag all text in the widget
self.text.tag_add(SEL, index1, index2) # select from index1 up to index2
self.text.tag_remove(SEL, '1.0', END) # remove selection from all text

The first line here creates a new tag that names all text in
the widget—from start through end positions. The second line adds a
range of characters to the built-in
SEL
selection tag—they are automatically
highlighted, because this tag is predefined to configure its members
that way. The third line removes all characters in the text string
from the
SEL
tag (all selections
are unselected). Note that the
tag_remove
call
just untags text within the named range; to really delete a tag
completely, call
tag_delete
instead. Also keep in mind that these calls apply to tags
themselves; to delete actual text use the
delete
method shown earlier.

You can map indexes to tags dynamically, too. For example, the
text
search
method
returns the
row.column
index of
the first occurrence of a string between start and stop positions.
To automatically select the text thus found, simply add its index to
the built-in
SEL
tag:

where = self.text.search(target, INSERT, END)  # search from insert cursor
pastit = where + ('+%dc' % len(target)) # index beyond string found
self.text.tag_add(SEL, where, pastit) # tag and select found string
self.text.focus() # select text widget itself

If you want only one string to be selected, be sure to first
run the
tag_remove
call listed
earlier—this code adds a selection in addition to any selections
that already exist (it may generate multiple selections in the
display). In general, you can add any number of substrings to a tag
to process them as a group.

To summarize: indexes, marks, and tag locations can be used
anytime you need a text position. For instance, the text
see
method scrolls the display to make a
position visible; it accepts all three kinds of position
specifiers:

self.text.see('1.0')          # scroll display to top
self.text.see(INSERT) # scroll display to insert cursor mark
self.text.see(SEL_FIRST) # scroll display to selection tag

Text tags can also be used in broader ways for formatting and
event bindings, but I’ll defer those details until the end of
this section.

BOOK: Programming Python
5.82Mb size Format: txt, pdf, ePub
ads

Other books

Immortal by Dean Crawford
The Perfect Son by Kyion S. Roebuck
Perfect Ten by Nikki Worrell
Blind Faith by Christiane Heggan
Hostile Intent by Michael Walsh
The Seventh Bullet by Daniel D. Victor