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

Text editor windows for displaying 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, 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 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.

Getting Started

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 shown in Figure 15-1. Notice the “PY” window icon: this is the handiwork of window protocol tools we wrote earlier in this book.

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

Figure 15-1. PyMailGUI main server list window

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 loaded 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 15-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 may be customized by the user in the
mailconfig
configuration file. You can’t tell in this black-and white book, but by default, mail index lists are Indian red, view windows are a shade of purple, pop-up PyEdit windows are light cyan, and help is steel blue; you can change most of these as you like (see Example 9-11 for help with color definition strings).

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 and displayed in its own view window. Use Ctrl-Click and Shift-Click 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 15-1 to see what sort of help is available; Figure 15-2 shows the help window popup that appears.

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. We could get fancier and spawn a web browser

Figure 15-2. PyMailGUI help popup

to view HTML-formatted help, but simple text does the job here.* 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 15-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.

When a message is selected in the mail list window, PyMailGUI downloads its full text (if it has not yet been downloaded in this session), and an email viewer window appears, as captured in Figure 15-4. View windows are built in response to actions in list windows; this is described next.

Figure 15-3. PyMailGUI source code viewer window

The top portion consists of action buttons (“Parts” to list all message parts, “Split” to save and open parts using a selected directory, and “Cancel” to remove 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, text parts in PyEdit, Windows document types per the Windows Registry, and so on).

The bulk of this window is just another reuse of the
TextEditor
class object we wrote in Chapter 12 for the PyEdit program—PyMailGUI simply attaches an instance of
TextEditor
to every view and compose window in order to get a full-featured text editor component for free. In fact, much on this window is implemented by
TextEditor
, not by PyMailGUI.

For instance, if we pick the Tools menu of the text portion of this window and select its Info entry, we get the standard PyEdit
TextEditor
object’s file text statistics box—the same popup we’d get in the standalone PyEdit text editor and in the PyView image view programs we wrote in Chapter 12 (see Figure 15-5).

In fact, this is the third reuse of
TextEditor
in this book: PyEdit, PyView, and now PyMailGUI all present the same text-editing interface to users, simply because they all use the same
TextEditor
object and code. PyMailGUI both attaches instances of this class for mail viewing and editing, and pops up instances for source-code viewing. For mail views, PyMailGUI customizes text fonts and colors per its own configuration module.

Figure 15-4. PyMailGUI view window

To display email, PyMailGUI inserts its text into an attached
TextEditor
object; to compose email, PyMailGUI presents a
TextEditor
and later fetches all its text to ship over the Net. Besides the obvious simplification here, this code reuse makes it easy to pick up improvements and fixes—any changes in the
TextEditor
object are automatically inherited by PyMailGUI, PyView, and PyEdit. In the current version, for instance, PyMailGUI supports edit undo and redo, just because PyEdit now does, too.

{mospagebreak title=Loading Mail}

Now, let’s go back to the PyMailGUI main server list window, and click the Load button to retrieve incoming email over the POP protocol. PyMailGUI’s load function gets account parameters from the mailconfig module listed later in this chapter,

Figure 15-5.PyMailGUI attached PyEdit info box

so be sure to change this file to reflect your email account parameters (i.e., server names and usernames) if you wish to use PyMailGUI to read your own email.

The account password parameter merits a few extra words. In PyMailGUI, it may come from one of two places:

Local file If you put the name of a local file containing the
password in the
mailconfig
module, PyMailGUI
loads the password from that file as needed.

Popup dialog If you don’t put a password filename in
mailconfig
(or if PyMailGUI can’t load it from the file for whatever
reason), PyMailGUI will instead ask you for your
password anytime it is needed.

Figure 15-6 shows the password input prompt you get if you haven’t stored your password in a local file. Note that the password you type is not shown—a
show=’*’
option for the
Entry
field used in this popup tells Tkinter to echo typed characters as stars (this option is similar in spirit to both the
getpass
console input module we met earlier in the prior chapter, and an HTML
type=password
option we’ll meet in a later chapter). Once entered, the password lives only in memory on your machine; PyMailGUI itself doesn’t store it anywhere in a permanent way.

Also notice that the local file password option requires you to store your password unencrypted in a file on the local client computer. This is convenient (you don’t need to retype a password every time you check email), but it is not generally a good idea on a machine you share with others; leave this setting blank in
mailconfig
if you prefer to always enter your password in a popup.

Once PyMailGUI fetches your mail parameters and somehow obtains your password, it will next attempt to pull down the header text of all your incoming email from your inbox on your POP email server. On subsequent loads, only newly arrived mails are loaded, if any.

Figure 15-6. PyMailGUI password input dialog

To save time, PyMailGUI fetches message header text only to populate the list window. The full text of messages is fetched later only when a message is selected for viewing or processing, and then only if the full text has not yet been fetched during this session. PyMailGUI reuses the load-mail tools in the
mailtools
module of Chapter 14 to fetch message header text, which in turn uses Python’s standard
poplib
module to retrieve your email.

{mospagebreak title=Threading Model}

Ultimately, mail fetches run over sockets on relatively slow networks. While the download is in progress, the rest of the GUI remains active—you may compose and send other mails at the same time, for instance. To show its progress, the nonblocking dialog of Figure 15-7 is displayed when the mail index is being fetched.

Figure 15-7.Nonblocking progress indicator: Load

In general, all server transfers display such dialogs. Figure 15-8 shows the busy dialog displayed while a full text download of five selected and uncached mails is in progress, in response to a View action. After this download finishes, all five pop up in view windows.

Such server transfers, and other long-running operations, are run in threads to avoid blocking the GUI. They do not disable other actions from running in parallel, as long as those actions would not conflict with a currently running thread. Multiple mail fetches and sends can overlap in time, for instance, and can run in parallel with the GUI itself—the GUI responds to moves, redraws, and resizes during the transfers.

On systems without threads, PyMailGUI instead goes into a blocked state during such long-running operations (it stubs out the thread-spawn operation to perform a simple function call). Because the GUI is essentially dead without threads, covering

Figure 15-8. Nonblocking progress indicator: View

and uncovering the GUI during a mail load on such platforms will erase or otherwise distort its contents. Threads are enabled by default on most platforms that Python run (including Windows), so you probably won’t see such oddness on your machine.

One implementation note: as we learned earlier in this book, only the thread that creates windows should generally update them. As a result, PyMailGUI takes care to not do anything related to the user interface within threads that load, send, or delete email. Instead, the main GUI thread continues responding to user interface events and updates, and uses a timer-based event to watch a queue for exit callbacks to be added by threads, using thread tools implemented earlier in the book. Upon receipt, the GUI thread pulls the callback off the queue and dispatches it to modify the GUI (e.g., to display a fetched message, update the mail index list, or close an email composition window).

{mospagebreak title=Load Server Interface}

Because the load operation is really a socket operation, PyMailGUI automatically connects to your email server using whatever connectivity exists on the machine on which it is run. For instance, if you connect to the Net over a modem and you’re not already connected, Windows automatically pops up the standard connection dialog. On a broadband connection, the interface to your email server is normally automatic.

After PyMailGUI finishes loading your email, it populates the main window’s scrolled listbox with all of the messages on your email server and automatically scrolls to the most recently received message. Figure 15-9 shows what the main window looks like after resizing; the text area in the middle grows and shrinks with the window.

Technically, the Load button fetches all your mail’s header text the first time it is pressed, but it fetches only newly arrived email headers on later presses. PyMailGUI keeps track of the last email loaded, and requests only higher email numbers on later loads. Already loaded mail is kept in memory, in a Python list, to avoid the cost of downloading it again. PyMailGUI does not delete email from your server when it is
loaded; if you really want to not see an email on a later load, you must explicitly delete it.

Entries in the main list show just enough to give the user an idea of what the message contains—each entry gives the concatenation of portions of the message’s “Subject:”, “From:”, “Date:”, and other header lines, separated by
|
characters and prefixed with the message’s POP number (e.g., there are 19 emails in this list). Columns are aligned by determining the maximum size needed for any entry, up to a fixed maximum, and the set of headers displayed can be configured in the
mailconfig
module. Use the horizontal scroll or expand the window to see additional header details.

Figure 15-9. PyMailGUI main window resized

As we’ve seen, a lot of magic happens when downloading email—the client (the machine on which PyMailGUI runs) must connect to the server (your email account machine) over a socket and transfer bytes over arbitrary Internet links. If things go wrong, PyMailGUI pops up standard error dialog boxes to let you know what happened. For example, if you typed an incorrect username or password for your account (in the
mailconfig
module or in the password pop up), you’ll see the message in Figure 15-10. The details displayed here are just the Python exception type and exception data.