The last item on the
preceding list is probably the most substantial. Per
Chapter 13
, a user-configurable setting
in themailconfig
module is used on
a session-wide basis to decode full message bytes into Unicode strings
when fetched, and to encode and decode mail messages stored in
text-mode save files.
More visibly, when composing, the main text and attached text
parts of composed mails may be given explicit Unicode encodings inmailconfig
or via user input; when
viewing, message header information of parsed emails is used to
determine the Unicode types of both the main mail text as well as text
parts opened on demand. In addition, Internationalized mail headers
(e.g., Subject, To, and From) are decoded per email, MIME, and Unicode
standards when displayed according to their own content, and are
automatically encoded if non-ASCII when sent.
Other Unicode policies (and fixes) of
Chapter 13
’smailtools
package are inherited here, too;
see the prior chapter for more details. In summation, here is how all
these policies play out in terms of user interfaces:
When
fetching
mails, a session-wide
user setting is used to decode full message bytes to Unicode
strings, as required by Python’s current email parser; if this
fails, a handful of guesses are applied. Most mail text will
likely be 7 or 8 bit in nature, since original email standards
required ASCII.
When
sending
new mails, user settings
are used to determine Unicode type for the main text part and
any text attachment parts. If these are not set inmailconfig
, the user will instead be
asked for encoding names in the GUI for each text part. These
are ultimately used to add character set headers, and to invoke
MIME encoding. In all cases, the program falls back on UTF-8 if
the user’s encoding setting or input does not work for the text
being sent—for instance, if the user has chosen ASCII for the
main text of a reply to or forward of a non-ASCII message or for
non-ASCII attachments.
When
sending
new mails, if header
lines or the name component of an email address in
address-related lines do not encode properly as ASCII text, we
first encode the header per email Internationalization standard.
This is done per UTF-8 by default, but amailconfig
setting can request a
different encoding. In email address pairs, names which cannot
be encoded are dropped, and only the email address is used. It
is assumed that servers will respect the encoded names in email
addresses.
When
viewing
fetched mail, Unicode
encoding names in message headers are used to decode whenever
possible. The main-text part is decoded intostr
Unicode text per header
information prior to inserting it into a PyEdit component. The
content of all other text parts, as well as all binary parts, is
saved inbytes
form in
binary-mode files, from where the part may be opened later in
the GUI on demand. When such on-demand text parts are opened,
they are displayed in PyEdit pop-up windows by passing to PyEdit
the name of the part’s binary-mode file, as well as the part’s
encoding name obtained from part message headers.
If the encoding name in a text part’s header is absent or
fails to decode, encoding guesses are tried for main-text parts,
and PyEdit’s separate Unicode policies are applied to text parts
opened on demand (see
Chapter 11
—it may prompt for an encoding
if not known). In addition to these rules, HTML text parts are
saved in binary mode and opened in a web browser, relying on the
browser’s own character set support; this may in turn use tags
in the HTML itself, guesses, or user encoding selections.
When
viewing
email, message headers
are automatically decoded per email standards. This includes
both full headers such as Subject, as well as the name
components of all email address fields in address-related
headers such as From, To, and Cc, and allows these components to
be completely encoded or contain encoded substrings. Because
their content gives their MIME and Unicode encodings, no user
interaction is required to decode headers.
In other words, PyMailGUI now supports
Internationalized
message display and composition
for both payloads and headers. For broadest utility, this support is
distributed across multiple packages and examples. For example,
Unicode decoding of full message text on fetches actually occurs deep
in the importedmailtool
package
classes. Because of this, full (unparsed) message text is always
Unicodestr
here. Similarly,
headers are decoded for display here using tools implemented inmailtools
, but headers encoding is
both initiated and performed withinmailtools
itself on sends.
Full text decoding illustrates the types of choices required. It
is done according to thefetchEncoding
variable in themailconfig
module. This user setting is used
across an entire PyMailGUI session to decode fetched messagebytes
to the requiredstr
text prior to parsing, and to save and
load full message text to save files. Users may set this variable to a
Unicode encoding name string which works for their mails’ encodings;
“latin-1”, “utf-8”, and “ascii” are reasonable guesses for most
emails, as email standards originally called for ASCII (though
“latin-1” was required to decode some old mail save files generated by
the prior version). If decoding with this encoding name fails, other
common encodings are attempted, and as a last resort the message is
still displayed if its headers can be decoded, but its body is changed
to an error message; to view such unlikely mails, try running
PyMailGUI again with a different encoding.
In the negatives column, nothing is done about the Unicode
format for the full text of sent mails, apart from that inherited from
Python’s libraries (as we learned in
Chapter 13
,smtplib
attempts to encode per ASCII when
messages are sent, which is one reason that header encoding is
required). And while mail content character sets are fully supported,
the GUI itself still uses English for its labels and buttons.
As explained in
Chapter 13
, this
program’s Unicode polices are a broad but partial solution, because
theemail
package in Python 3.1,
upon which PyMailGUI utterly relies for correct operation, is in a
state of flux for some use cases. An updated version which handles the
Python 3.Xstr
/bytes
distinctions more accurately and
completely is likely to appear in the future; watch this book’s
updates page (see the
Preface
) for future changes
and improvements to this program’s Unicode policies. Hopefully, the
current email package underlying PyMailGUI 3.0 will be available for
some time to come.
Although there is still room for improvement (see the list at
the end of this chapter), the PyMailGUI program is able to provide a
full-featured email interface, represents the most substantial example
in this book, and serves to demonstrate a realistic application of the
Python language and software engineering at large. As its users often
attest, Python may be fun to work with, but it’s also useful for
writing practical and nontrivial software. This example, more than any
other in this book, testifies the same. The next section shows
how.
PyMailGUI is a
multiwindow interface. It consists of the following:
A main mail-server list window opened initially, for online mail
processing
One or more mail save-file list windows for offline mail
processing
One or more mail-view windows for viewing and editing
messages
PyEdit windows for displaying raw mail text, extracted text
parts, and the system’s source code
Nonblocking busy state pop-up dialogs
Assorted pop-up dialogs for opened message parts, help, and
more
Operationally, PyMailGUI runs as a set of parallel threads, which
may overlap in time: one for each active server transfer, and one for each
active offline save file load or deletion. PyMailGUI supports mail save
files, automatic saves of sent messages, configurable fonts and colors,
viewing and adding attachments, main message text extraction, plain text
conversion for HTML, and much more.
To make this case study easier to understand, let’s begin by seeing
what PyMailGUI actually does—its user interaction and email processing
functionality—before jumping into the Python code that implements that
behavior. As you read this part, feel free to jump ahead to the code
listings that appear after the screenshots, but be sure to read this
section, too; this, along with the prior discussion of version changes, is
where some subtleties of PyMailGUI’s design are explained. After this
section, you are invited to study the system’s Python source code listings
on your own for a better and more complete explanation than can be crafted
in English.
OK, it’s
time to take the system out for a test drive. I’m going to
run the following demo on my Windows 7 laptop. It may look slightly
different on different platforms (including other versions of Windows)
thanks to the GUI toolkit’s native-look-and-feel support, but the basic
functionality will be similar.
PyMailGUI is a Python/tkinter program, run by executing its
top-level script file,
PyMailGui.py
. Like other Python
programs, PyMailGUI can be started from the system command line, by
clicking on its filename icon in a file explorer interface, or by
pressing its button in the PyDemos or PyGadgets launcher bar. However it
is started, the first window PyMailGUI presents is captured in
Figure 14-1
, shown after running a
Load to fetch mail headers from my ISP’s email server. Notice the “PY”
window icon: this is the handiwork of window protocol tools we wrote
earlier in this book. Also notice the non-ASCII subject lines here; I’ll
talk about Internationalization features later.
Figure 14-1. PyMailGUI main server list window
This is the PyMailGUI main window—every operation starts here. It
consists of:
A help button (the bar at the top)
A clickable email list area for fetched emails (the middle
section)
A button bar at the bottom for processing messages selected in
the list area
In normal operation, users load their email, select an email from
the list area by clicking on it, and press a button at the bottom to
process it. No mail messages are shown initially; we need to first load
them with the Load button—a simple password input dialog is displayed, a
busy dialog appears that counts down message headers being downloaded to
give a status indication, and the index is filled with messages ready to
be selected.
PyMailGUI’s list windows, such as the one in
Figure 14-1
, display mail header
details in fixed-width columns, up to a maximum size. Mails with
attachments are prefixed with a “*” in mail index list windows, and
fonts and colors in PyMailGUI windows like this one can be customized by
the user in themailconfig
configuration file. You can’t tell in this black-and-white book, but
most of the mail index lists we’ll see are configured to be Indian red,
view windows are light blue, pop-up PyEdit windows are beige instead of
PyEdit’s normal light cyan, and help is steel blue. You can change most
of these as you like, and PyEdit pop-up window appearance can be altered
in the GUI itself (see
Example 8-11
for help with color
definition strings, and watch for alternative configuration examples
ahead).
List windows allow multiple messages to be selected at once—the
action selected at the bottom of the window is applied to all selected
mails. For instance, to view many mails, select them all and press View;
each will be fetched (if needed) and displayed in its own view window.
Use the All check button in the bottom right corner to select or
deselect every mail in the list, and Ctrl-Click and Shift-Click
combinations to select more than one (the standard Windows multiple
selection operations apply—try it).
Before we go any further, though, let’s press the help bar at the
top of the list window in
Figure 14-1
to see what sort of help
is available;
Figure 14-2
shows the
text-based help window pop up that appears—one of two help flavors
available.
Figure 14-2. PyMailGUI text help pop up
The main part of this window is simply a block of text in a
scrolled-text widget, along with two buttons at the bottom. The entire
help text is coded as a single triple-quoted string in the Python
program. As we’ll see in a moment, a fancier option which opens an HTML
rendition of this text in a spawned web browser is also available, but
simple text is sufficient for many people’s tastes.
[
56
]
The Cancel button makes this nonmodal (i.e., nonblocking)
window go away. More interestingly, the Source button pops up PyEdit
text editor viewer windows for all the source files of PyMailGUI’s
implementation;
Figure 14-3
captures one of
these (there are many; this is intended as a demonstration, not as a
development environment). Not every program shows you its source code,
but PyMailGUI follows Python’s open source motif.
Figure 14-3. PyMailGUI text help source code viewer window
New in this edition, help is also displayed in HTML form in a web
browser, in addition to or instead of the scrolled text display just
shown. Choosing help in text, HTML, or both is controlled by a setting
in themailconfig
module. The HTML
flavor uses the Pythonwebbrowser
module to pop up the HTML file in a browser on the local machine, and
currently lacks the source-file opening button of the text display
version (one reason you may wish to display the text viewer, too). HTML
help is captured in
Figure 14-4
.
Figure 14-4. PyMailGUI HTML help display (new in 3.0)
When a message is selected for viewing in the mail list window by
a mouse click and View press, PyMailGUI downloads its full text (if it
has not yet been downloaded in this session), and a formatted email
viewer window appears, as captured in
Figure 14-5
for an existing message in my
account’s inbox.
Figure 14-5. PyMailGUI view window
View windows are built in response to actions in list windows and
take the following form:
The top portion consists of action buttons (Part to list all
message parts, Split to save and open parts using a selected
directory, and Cancel to close this nonmodal window), along with a
section for displaying email header lines (From, To, and
so on
).
In the middle, a row of quick-access buttons for opening
message parts, including attachments, appears. When clicked,
PyMailGUI opens known and generally safe parts according to their
type. Media types may open in a web browser or image viewer, text
parts in PyEdit, HTML in a web browser, Windows document types per
the Windows Registry, and
so
on
.
The bulk of this window (its entire lower portion) is just
another reuse of theTextEditor
class object of the PyEdit program we wrote in
Chapter 11
—PyMailGUI simply attaches an
instance ofTextEditor
to every
view and compose window in order to get a full-featured text editor
component for free. In fact, much on the window shown in
Figure 14-5
is implemented byTextEditor
, not by PyMailGUI.
Reusing PyEdit’s class this way means that all of its tools are at
our disposal for email text—cut and paste, find and goto, saving a copy
of the text to a file, and so on. For instance, the PyEdit Save button
at the bottom left of
Figure 14-5
can be
used to save just the main text of the mail (as we’ll see later,
clicking the leftmost part button in the middle of the screen affords
similar utility, and you can also save the entire message from a list
window). To make this reuse even more concrete, if we pick the Tools
menu of the text portion of this window and select its Info entry, we
get the standard PyEditTextEditor
object’s text statistics box shown in
Figure 14-6
—the same pop up we’d get
in the standalone PyEdit text editor and in the PyView image view
programs we wrote in
Chapter 11
.
Figure 14-6. PyMailGUI attached PyEdit info box
In fact, this is the third reuse ofTextEditor
in this book: PyEdit, PyView, and
now PyMailGUI all present the same text-editing interface to users,
simply because they all use the sameTextEditor
object and code. PyMailGUI uses it
in multiple roles—it both attaches instances of this class for mail
viewing and composition, and pops up instances in independent windows
for some text mail parts, raw message text display, and Python
source-code viewing (we saw the latter in
Figure 14-3
). For mail view
components, PyMailGUI customizes PyEdit text fonts and colors per its
own configuration module; for pop ups, user preferences in a localtextConfig
module are applied.