Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (90 page)

Other PyEdit examples and screenshots in this book

For other screenshots
showing PyEdit in action, see the coverage of the
following client programs:

  • PyDemos in
    Chapter 10
    deploys
    PyEdit pop-ups to show source-code files.

  • PyView later in this chapter embeds PyEdit to display image
    note files.

  • PyMailGUI in
    Chapter 14
    uses
    PyEdit to display email text, text attachments, and source.

The last of these especially makes heavy use of PyEdit’s
functionality and includes screenshots showing PyEdit displaying
additional Unicode text with Internationalized character sets. In this
role, the text is either parsed from messages or loaded from temporary
files, with encodings determined by mail headers.

PyEdit Changes in Version 2.0 (Third Edition)

I’ve updated this
example in both the third and fourth editions of this
book. Because this chapter is intended to reflect realistic programming
practice, and because this example reflects that way that software
evolves over time, this section and the one following it provide a quick
rundown of some of the major changes made along the way to help you
study the code.

Since the current version inherits all the enhancements of the one
preceding it, let’s begin with the previous version’s additions. In the
third edition, PyEdit was enhanced with:

  • A simple font specification dialog

  • Unlimited undo and redo of editing operations

  • File modified tests whenever content might be erased or
    changed

  • A user configurations module

Here are some quick notes about these extensions.

Font dialog

For the third edition
of the book, PyEdit grew a
font input
dialog
—a simple, three-entry, nonmodal dialog where you can
type the font family, size, and style, instead of picking them from a
list of preset options. Though functional, you can find more
sophisticated tkinter font selection dialogs in both the public domain
and within the implementation of Python’s standard IDLE development
GUI (as mentioned earlier, it is itself a Python/tkinter
program).

Undo, redo, and modified tests

Also new in the
third edition, PyEdit supports unlimited edit
undo and redo
, as well as an accurate
modified
check before quit, open, run, and new
actions to prompt for saves. It now verifies exits or overwrites only
if text has been changed, instead of always asking naïvely. The
underlying Tk 8.4 (or later) library provides an API, which makes both
these enhancements simple—Tk keeps undo and redo stacks automatically.
They are enabled with the
Text
widget’s
undo
configuration option
and are accessed with the widget methods
edit_undo
and
edit_redo
. Similarly,
edit_reset
clears the stacks (e.g., after a
new file open), and
edit_modified
checks or sets the automatic text modified flag.

It’s also possible to undo cuts and pastes right after you’ve
done them (simply paste back from the clipboard or cut the pasted and
selected text), but the new undo/redo operations are more complete and
simpler to use. Undo was a suggested exercise in the second edition of
this book, but it has been made almost trivial by the new Tk
API.

Configuration module

For usability, the
third edition’s version of PyEdit also allows users to
set startup
configuration options
by assigning
variables in a module,
textConfig.py
. If present on the module
search path when PyEdit is imported or run, these assignments give
initial values for font, colors, text window size, and search case
sensitivity. Fonts and colors can be changed interactively in the
menus and windows can be freely resized, so this is largely just a
convenience. Also note that this module’s settings will be inherited
by all instances of PyEdit if it is importable in the client
program—even when it is a pop-up window or an embedded component of
another application. Client applications may define their own version
or configure this file on the module search path per their
needs.

PyEdit Changes in Version 2.1 (Fourth Edition)

Besides the updates
described in the prior section, the following additional
enhancements were made for this current fourth edition of this
book:

  • PyEdit has been ported to run under Python 3.1, and its
    tkinter library.

  • The nonmodal change and font dialogs were fixed to work better
    if multiple instance windows are open: they now use per-dialog
    state.

  • A Quit request in main windows now verifies program exit if
    any other edit windows in the process have changed content, instead
    of exiting silently.

  • There’s a new Grep menu option and dialog for searching
    external files; searches are run in threads to avoid blocking the
    GUI and to allow multiple searches to overlap in time and support
    Unicode text.

  • There was a minor fix for initial positioning when text is
    inserted initially into a newly created editor, reflecting a change
    in underlying libraries.

  • The Run Code option for files now uses the base file name
    instead of the full directory path after a
    chdir
    to better support relative paths;
    allows for command-line arguments to code run from files; and
    inherits a patch made in
    Chapter 5
    ’s
    launch
    modes
    which converts
    /
    to
    \
    in script paths. In addition, this option always now runs an
    update
    between pop-up dialogs to
    ensure proper display.

  • Perhaps most prominently, PyEdit now processes files in such a
    way as to support display and editing of text with arbitrary
    Unicode encodings
    , to the extent allowed by the
    underlying Tk GUI library for Unicode strings. Specifically, Unicode
    is taken into account when opening and saving files; when displaying
    text in the GUI; and when searching files in directories.

The following sections provide additional implementation notes on
these changes.

Modal dialog state fix

The
change
dialog in
the prior version saved its entry widgets on the text
editor object, which meant that the most recent change dialog’s fields
were used for every change dialog open. This could even lead to
program aborts for finds in an older change dialog window if newer
ones had been closed, since the closed window’s widgets had been
destroyed—an unanticipated usage mode, which has been present since at
least the second edition, and which I’d like to chalk up to operator
error, but which was really a lesson in state retention! The same
phenomenon existed in the
font
dialog—its most
recently opened instance stole the show, though its brute force
exception handler prevented program aborts (it issued error pop ups
instead). To fix, the change and font dialogs now send
per-dialog-window input fields as arguments to their callbacks. We
could instead allow just one of each dialog to be open, but that’s
less functional.

Cross-process change tests on Quit

Though not quite
as grievous, PyEdit also used to ignore changes in other
open edit windows on Quit in main windows. As a policy, on a Quit in
the GUI, pop-up edit windows destroy themselves only, but main edit
windows run a tkinter
quit
to end
the entire program. Although all windows verify closes if their own
content has changed, other edit windows were ignored in the prior
version—quitting a main window could lose changes in other windows
closed on program exit.

To do better, this version keeps a list of all open managed edit
windows in the process; on Quit in main windows it checks them all for
changes, and verifies exit if any have changed. This scheme isn’t
foolproof (it doesn’t address quits run on widgets outside PyEdit’s
scope), but it is an improvement. A more ultimate solution probably
involves redefining or intercepting tkinter’s own
quit
method. To avoid getting too detailed
here, I’ll defer more on this topic until later in this section (see
the

event coverage
ahead); also see the relevant comments near the end of PyEdit’s source
file for implementation notes.

New Grep dialog: Threaded and Unicode-aware file tree
search

In addition,
there is a new Grep option in the Search pull-down menu,
which implements an external file search tool. This tool scans an
entire directory tree for files whose names match a pattern, and which
contain a given search string. Names of matches are popped up in a new
nonmodal scrolled list window, with lines that identify all matches by
filename, line number, and line content. Clicking on a list item opens
the matched file in a new nonmodal and in-process PyEdit pop-up edit
window and automatically moves to and selects the line of the match.
This achieves its goal by reusing much code we wrote earlier:

  • The
    find
    utility we wrote
    in
    Chapter 6
    to do its tree
    walking

  • The scrolled list utility we coded in
    Chapter 9
    for displaying
    matches

  • The form row builder we wrote in
    Chapter 10
    for the nonmodal input
    dialog

  • The existing PyEdit pop-up window mode logic to display
    matched files on request

  • The existing PyEdit go-to callback and logic to move to the
    matched line in a file

Grep threading model

To avoid blocking the GUI
while files are searched during tree walks, Grep runs
searches in parallel
threads
. This also allows
multiple greps to be running at once and to overlap in time
arbitrarily (especially useful if you grep in larger trees, such as
Python’s own library or full source trees). The standard threads,
queues, and
after
timer loops
technique we learned in
Chapter 10
is
applied here—non-GUI producer threads find matches and place them on
a queue to be detected by a timer loop in the main GUI
thread.

As coded, a timer loop is run only when a grep is in progress,
and each grep uses its own thread, timer loop, and queue. There may
be multiple threads and loops running, and there may be other
unrelated threads, queues, and timer loops in the process. For
instance, an attached PyEdit component in
Chapter 14
’s PyMailGUI program can run grep
threads and loops of its own, while PyMailGUI runs its own
email-related threads and queue checker. Each loop’s handler is
dispatched independently from the tkinter event stream processor.
Because of the simpler structure here, the general
threadtools
callback queue of
Chapter 10
is not used here. For more notes
on grep thread implementation see the source code ahead, and compare
to file
_unthreaded
-
text
Editor.py
in the examples package, a
nonthreaded version of PyEdit.

Grep Unicode model

If you study the
Grep option’s code, you’ll notice that it also allows
input of a tree-wide Unicode encoding, and catches and skips any
Unicode decoding error exceptions generated both when processing
file content and walking the tree’s filenames. As we learned in
Chapters
4
and
6
, files opened in text mode in
Python 3.X must be decodable per a provided or platform default
Unicode encoding. This is particular problematic for Grep, as
directory trees may contain files of arbitrarily mixed encoding
types.

In fact, it’s common on Windows to have files with content in
ASCII, UTF-8, and UTF-16 form mixed in the same tree (Notepad’s
“ANSI,” “Utf-8,” and “Unicode”), and even others in trees that
contain content obtained from the Web or email. Opening all these
with UTF-8 would trigger exceptions in Python 3.X, and opening all
these in binary mode yields encoded text that will likely fail to
match a search key string. Technically, to compare at all, we’d
still have to decode the bytes read to text or encode the search key
string to bytes, and the two would only match if the encodings used
both succeed and agree.

To allow for mixed encoding trees, the Grep dialog opens in
text mode and allows an encoding name to be input and used to decode
file content for all files in the tree searched. This encoding name
is prefilled with the platform content default for convenience, as
this will often suffice. To search trees of mixed file types, users
may run multiple Greps with different encoding names. The names of
files searched might fail to decode as well, but this is largely
ignored in the current release: they are assumed to satisfy the
platform filename convention, and end the search if they don’t (see
Chapters
4
and
6
for more on filename encoding
issues in Python itself, as well as the
find
walker reused here).

In addition, Grep must take care to catch and recover from
encoding errors, since some files with matching names that it
searches might still not be decodable per the input encoding, and in
fact might not be text files at all. For example, searches in
Python 3.1’s
standard library
(like the example Grep for
%
described earlier) run into a handful of files which do not decode
properly on my Windows machine and would otherwise crash PyEdit.
Binary files which happen to match the filename patterns would fare
even worse.

In general, programs can avoid Unicode encoding errors by
either catching exceptions or opening files in binary mode; since
Grep might not be able to interpret some of the files it visits as
text at all, it takes the former approach. Really, opening even text
files in binary mode to read raw byte strings in 3.X mimics the
behavior of text files in 2.X, and underscores why forcing programs
to deal with Unicode is sometimes a good
thing—
binary mode avoids decoding
exceptions, but probably shouldn’t, because the still-encoded text
might not work as expected. In this case, it might yield invalid
comparison results.

For more details on Grep’s Unicode support, and a set of open
issues and options surrounding it, see the source code listed ahead.
For a suggested enhancement, see also the
re
pattern matching module in
Chapter 19
—a tool we could use to search for
patterns instead of specific
strings.

Update for initial positioning

Also in this version,
text editor updates itself before inserting text into
its text widget at construction time when it is passed an initial file
name in its
loadFirst
argument.
Sometime after the third edition and Python 2.5, either Tk or tkinter
changed such that inserting text before an update call caused the
scroll position to be off by one—the text editor started with line 2
at its top in this mode instead of line 1. This also occurs in the
third edition’s version of this example under Python 2.6, but not 2.5;
adding an
update
correctly
positions at line 1 initially. Obscure but true in
the real world of library dependencies!
[
39
]

Clients of the classes here should also
update
before manually inserting text into a
newly created (or packed) text editor object for accurate positioning;
PyView later in this chapter as well as PyMailGUI in
Chapter 14
now do. PyEdit doesn’t update itself
on every construction, because it may be created early by, or even
hidden in, an enclosing GUI (for instance, this would show a
half-complete window in PyView). Moreover, PyEdit could automatically
update
itself at the start of
setAllText
instead of requiring
this step of clients, but forced
update
is required only once initially after
being packed (not before each text insertion), and this too might be
an undesirable side effect in some conceivable use cases. As a rule of
thumb, adding unrelated operations to methods this way tends to limit
their scope.

Improvements for running code

The Run Code option
in the Tools menu was fixed in three ways that make it
more useful for running code being edited from its external file,
rather than in-process:

  1. After changing to the file’s directory in order to make any
    relative filenames in its code accurate, PyEdit now strips off any
    directory path prefix in the file’s name before launching it,
    because its original directory path may no longer be valid if it
    is
    relative
    instead of absolute. For
    instance, paths of files opened manually are absolute, but file
    paths in PyDemos’s Code pop ups are all relative to the example
    package root and would fail after a
    chdir
    .

  2. PyEdit now correctly uses launcher tools that support
    command-line arguments for file mode on Windows.

  3. PyEdit inherits a fix in the underlying
    launchmodes
    module that changes forward
    slashes in script path names to backslashes (though this was later
    made a moot point by stripping relative path prefixes). PyEdit
    gets by with forward slashes on Windows because
    open
    allows them, but some Windows
    launch tools do not.

Additionally, for both code run from files and code run in
memory, this version adds an
update
call between pop-up dialogs to ensure that later dialogs appear in all
cases (the second occasionally failed to pop up in rare contexts).
Even with these fixes, Run Code is useful but still not fully robust.
For example, if the edited code is not run from a file, it is run
in-process and not spawned off in a thread, and so may block the GUI.
It’s also not clear how best to handle import paths and directories
for files run in nonfile mode, or whether this mode is worth retaining
in general. Improve as desired.

Other books

Ultimate Texas Bachelor by Cathy Gillen Thacker
Rosemary's Baby by Levin, Ira
America's Bravest by Kathryn Shay
Quartet for the End of Time by Johanna Skibsrud
Nam Sense by Arthur Wiknik, Jr.


readsbookonline.com Copyright 2016 - 2024