Keyboard Accelerators

Keyboard accelerators are key combinations that generate WM_COMMAND (or, in some cases, WM_SYSCOMMAND) messages. Most often, programs use keyboard accelerators to duplicate the action of common menu options, but they can also perform nonmenu functions. For instance, some Windows programs have an Edit menu that includes a Delete or Clear option; these programs conventionally assign the Del key as a keyboard accelerator for this option. The user can choose the Delete option from the menu by pressing an Alt-key combination or can use the keyboard accelerator simply by pressing the Del key. When the window procedure receives a WM_COMMAND message, it does not have to determine whether the menu or the keyboard accelerator was used.

Why You Should Use Keyboard Accelerators

You might ask: Why should I use keyboard accelerators? Why can't I simply trap WM_ KEYDOWN or WM_CHAR messages and duplicate the menu functions myself? What's the advantage? For a single-window application, you can certainly trap keyboard messages, but one simple advantage of using keyboard accelerators is that you don't need to duplicate the menu and keyboard accelerator logic.

For applications with multiple windows and multiple window procedures, keyboard accelerators become very important. As we've seen, Windows sends keyboard messages to the window procedure for the window that currently has the input focus. For keyboard accelerators, however, Windows sends the WM_COMMAND message to the window procedure whose handle is specified in the Windows function TranslateAccelerator. Generally, this will be your main window, the same window that has the menu, which means that the logic for acting upon keyboard accelerators does not have to be duplicated in every window procedure.

This advantage becomes particularly important if you use modeless dialog boxes (discussed in the next chapter) or child windows on your main window's client area. If a particular keyboard accelerator is defined to move among windows, only one window procedure has to include this logic. The child windows do not receive WM_COMMAND messages from the keyboard accelerators.

Some Rules on Assigning Accelerators

In theory, you can define a keyboard accelerator for almost any virtual key or character key in combination with the Shift key, Ctrl key, or Alt key. However, you should try to achieve some consistency with other applications and avoid interfering with Windows' use of the keyboard. You should avoid using Tab, Enter, Esc, and the Spacebar in keyboard accelerators because these are often used for system functions.

The most common use of keyboard accelerators is for items on the program's Edit menu. The recommended keyboard accelerators for these items changed between Windows 3.0 and Windows 3.1, so it's become common to support both the old and the new accelerators, as shown in the following table:

Function Old Accelerator New Accelerator
UndoAlt+BackspaceCtrl+Z
CutShift+DelCtrl+X
CopyCtrl+InsCtrl+C
PasteShift+InsCtrl+V
Delete or ClearDelDel

Another common accelerator is the F1 function key to invoke help. Avoid use of the F4, F5, and F6 keys because these are often used for special functions in Multiple Document Interface (MDI) programs, which are discussed in Chapter 19.

The Accelerator Table

You can define an accelerator table in Developer Studio. For ease in loading the accelerator table in your program, give it the same text name as your program (and your menu and your icon).

Each accelerator has an ID and a keystroke combination that you define in the Accel Properties dialog box. If you've already defined your menu, the menu IDs will be available in the combo box, so you don't have to retype them.

Accelerators can be either virtual key codes or ASCII characters in combination with the Shift, Ctrl, or Alt keys. You can specify that an ASCII character is to be typed with the Ctrl key by typing a ^ before the letter. You can also pick virtual key codes from a combo box.

When you define keyboard accelerators for a menu item, you should include the key combination in the menu item text. The tab (\t) character separates the text from the accelerator so that the accelerators align in a second column. To notate accelerator keys in a menu, use the text Ctrl, Shift, or Alt followed by a plus sign and the key (for example, Shift+F6 or Ctrl+F6).

Loading the Accelerator Table

Within your program, you use the LoadAccelerators function to load the accelerator table into memory and obtain a handle to it. The LoadAccelerators statement is similar to the LoadIcon, LoadCursor, and LoadMenu statements.

First define a handle to an accelerator table as type HANDLE:

 HANDLE hAccel ; 

Then load the accelerator table:

 hAccel = LoadAccelerators (hInstance, TEXT ("MyAccelerators")) ; 

As with icons, cursors, and menus, you can use a number for the accelerator table name and then use that number in the LoadAccelerators statement with the MAKEINTRESOURCE macro or enclosed in quotation marks and preceded by a # character.

Translating the Keystrokes

We will now tamper with three lines of code that are common to all the Windows programs we've created so far in this book. The code is the standard message loop:

 while (GetMessage (&msg, NULL, 0, 0)) {      TranslateMessage (&msg) ;      DispatchMessage (&msg) ; } 

Here's how we change it to use the keyboard accelerator table:

 while (GetMessage (&msg, NULL, 0, 0)) {      if (!TranslateAccelerator (hwnd, hAccel, &msg))      {           TranslateMessage (&msg) ;           DispatchMessage (&msg) ;      } } 

The TranslateAccelerator function determines whether the message stored in the msg message structure is a keyboard message. If it is, the function searches for a match in the accelerator table whose handle is hAccel. If it finds a match, it calls the window procedure for the window whose handle is hwnd. If the keyboard accelerator ID corresponds to a menu item in the system menu, the message is WM_SYSCOMMAND. Otherwise, the message is WM_COMMAND.

When TranslateAccelerator returns, the return value is nonzero if the message has been translated (and already sent to the window procedure) and 0 if not. If TranslateAccelerator returns a nonzero value, you should not call TranslateMessage and DispatchMessage but rather should loop back to the GetMessage call.

The hwnd parameter in TranslateMessage looks a little out of place because it's not required in the other three functions in the message loop. Moreover, the message structure itself (the structure variable msg) has a member named hwnd, which is also a handle to a window.

Here's why the function is a little different: The fields of the msg structure are filled in by the GetMessage call. When the second parameter of GetMessage is NULL, the function retrieves messages for all windows belonging to the application. When GetMessage returns, the hwnd member of the msg structure is the window handle of the window that will get the message. However, when TranslateAccelerator translates a keyboard message into a WM_COMMAND or WM_SYSCOMMAND message, it replaces the msg.hwnd window handle with the hwnd window handle specified as the first parameter to the function. That is how Windows sends all keyboard accelerator messages to the same window procedure even if another window in the application currently has the input focus. TranslateAccelerator does not translate keyboard messages when a modal dialog box or message box has the input focus, because messages for these windows do not come through the program's message loop.

In some cases in which another window in your program (such as a modeless dialog box) has the input focus, you may not want keyboard accelerators to be translated. You'll see how to handle this situation in the next chapter.

Receiving the Accelerator Messages

When a keyboard accelerator corresponds to a menu item in the system menu, TranslateAccelerator sends the window procedure a WM_SYSCOMMAND message. Otherwise, TranslateAccelerator sends the window procedure a WM_COMMAND message. The following table shows the types of WM_COMMAND messages you can receive for keyboard accelerators, menu commands, and child window controls:

Accelerator Menu Control
LOWORD (wParam) Accelerator ID Menu ID Control ID
HIWORD (wParam) 1 0 Notification code
lParam 0 0 Child window handle

If the keyboard accelerator corresponds to a menu item, the window procedure also receives WM_INITMENU, WM_INITMENUPOPUP, and WM_MENUSELECT messages, just as if the menu option had been chosen. Programs usually enable and disable items in a popup menu when processing WM_INITMENUPOPUP, so you still have that facility when using keyboard accelerators. If the keyboard accelerator corresponds to a disabled or grayed menu item, TranslateAccelerator does not send the window procedure a WM_COMMAND or WM_SYSCOMMAND message.

If the active window is minimized, TranslateAccelerator sends the window procedure WM_SYSCOMMAND messages—but not WM_COMMAND messages—for keyboard accelerators that correspond to enabled system menu items. TranslateAccelerator also sends that window procedure WM_COMMAND messages for accelerators that do not correspond to any menu items.

POPPAD with a Menu and Accelerators

In Chapter 9, we created a program called POPPAD1 that uses a child window edit control to implement a rudimentary notepad. In this chapter, we'll add File and Edit menus and call it POPPAD2. The Edit items will all be functional; we'll finish the File functions in Chapter 11 and the Print function in Chapter 13. POPPAD2 is shown in Figure 10-11.

Figure 10-11. The POPPAD2 program.

POPPAD2.C

 /*-----------------------------------------------------    POPPAD2.C -- Popup Editor Version 2 (includes menu)                 (c) Charles Petzold, 1998   -----------------------------------------------------*/ #include <windows.h> #include "resource.h" #define ID_EDIT     1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); TCHAR szAppName[] = TEXT ("PopPad2") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                     PSTR szCmdLine, int iCmdShow) {      HACCEL   hAccel ;      HWND     hwnd ;      MSG      msg ;      WNDCLASS wndclass ;      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;      wndclass.lpfnWndProc   = WndProc ;      wndclass.cbClsExtra    = 0 ;      wndclass.cbWndExtra    = 0 ;      wndclass.hInstance     = hInstance ;      wndclass.hIcon         = LoadIcon (hInstance, szAppName) ;      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;      wndclass.lpszMenuName  = szAppName ;      wndclass.lpszClassName = szAppName ;            if (!RegisterClass (&wndclass))      {           MessageBox (NULL, TEXT ("This program requires Windows NT!"),                       szAppName, MB_ICONERROR) ;           return 0 ;      }            hwnd = CreateWindow (szAppName, szAppName,                           WS_OVERLAPPEDWINDOW,                           GetSystemMetrics (SM_CXSCREEN) / 4,                           GetSystemMetrics (SM_CYSCREEN) / 4,                           GetSystemMetrics (SM_CXSCREEN) / 2,                           GetSystemMetrics (SM_CYSCREEN) / 2,                           NULL, NULL, hInstance, NULL) ;            ShowWindow (hwnd, iCmdShow) ;      UpdateWindow (hwnd) ;             hAccel = LoadAccelerators (hInstance, szAppName) ;            while (GetMessage (&msg, NULL, 0, 0))      {           if (!TranslateAccelerator (hwnd, hAccel, &msg))           {                TranslateMessage (&msg) ;                DispatchMessage (&msg) ;           }      }      return msg.wParam ; } AskConfirmation (HWND hwnd) {      return MessageBox (hwnd, TEXT ("Really want to close PopPad2?"),                         szAppName, MB_YESNO | MB_ICONQUESTION) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {      static HWND hwndEdit ;      int         iSelect, iEnable ;            switch (message)      {      case WM_CREATE:           hwndEdit = CreateWindow (TEXT ("edit"), NULL,                               WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |                               WS_BORDER | ES_LEFT | ES_MULTILINE |                               ES_AUTOHSCROLL | ES_AUTOVSCROLL,                               0, 0, 0, 0, hwnd, (HMENU) ID_EDIT,                               ((LPCREATESTRUCT) lParam)->hInstance, NULL) ;           return 0 ;                 case WM_SETFOCUS:           SetFocus (hwndEdit) ;           return 0 ;                 case WM_SIZE:            MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ;           return 0 ;                 case WM_INITMENUPOPUP:           if (lParam == 1)           {                EnableMenuItem ((HMENU) wParam, IDM_EDIT_UNDO,                     SendMessage (hwndEdit, EM_CANUNDO, 0, 0) ?                                    MF_ENABLED : MF_GRAYED) ;                                EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE,                     IsClipboardFormatAvailable (CF_TEXT) ?                                    MF_ENABLED : MF_GRAYED) ;                                iSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0) ;                                if (HIWORD (iSelect) == LOWORD (iSelect))                     iEnable = MF_GRAYED ;                else                     iEnable = MF_ENABLED ;                                EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT,   iEnable) ;                EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY,  iEnable) ;                EnableMenuItem ((HMENU) wParam, IDM_EDIT_CLEAR, iEnable) ;                return 0 ;           }           break ;      case WM_COMMAND:           if (lParam)           {                if (LOWORD (lParam) == ID_EDIT &&                          (HIWORD (wParam) == EN_ERRSPACE ||                           HIWORD (wParam) == EN_MAXTEXT))                     MessageBox (hwnd, TEXT ("Edit control out of space."),                                 szAppName, MB_OK | MB_ICONSTOP) ;                return 0 ;           }           else switch (LOWORD (wParam))           {           case IDM_FILE_NEW:           case IDM_FILE_OPEN:           case IDM_FILE_SAVE:           case IDM_FILE_SAVE_AS:           case IDM_FILE_PRINT:                MessageBeep (0) ;                return 0 ;                      case IDM_APP_EXIT:                SendMessage (hwnd, WM_CLOSE, 0, 0) ;                return 0 ;           case IDM_EDIT_UNDO:                SendMessage (hwndEdit, WM_UNDO, 0, 0) ;                return 0 ;                      case IDM_EDIT_CUT:                SendMessage (hwndEdit, WM_CUT, 0, 0) ;                return 0 ;                      case IDM_EDIT_COPY:                SendMessage (hwndEdit, WM_COPY, 0, 0) ;                return 0 ;                      case IDM_EDIT_PASTE:                SendMessage (hwndEdit, WM_PASTE, 0, 0) ;                return 0 ;                      case IDM_EDIT_CLEAR:                SendMessage (hwndEdit, WM_CLEAR, 0, 0) ;                return 0 ;           case IDM_EDIT_SELECT_ALL:                SendMessage (hwndEdit, EM_SETSEL, 0, -1) ;                return 0 ;                      case IDM_HELP_HELP:                MessageBox (hwnd, TEXT ("Help not yet implemented!"),                            szAppName, MB_OK | MB_ICONEXCLAMATION) ;                return 0 ;                      case IDM_APP_ABOUT:                MessageBox (hwnd, TEXT ("POPPAD2 (c) Charles Petzold, 1998"),                            szAppName, MB_OK | MB_ICONINFORMATION) ;                return 0 ;           }           break ;                 case WM_CLOSE:           if (IDYES == AskConfirmation (hwnd))                DestroyWindow (hwnd) ;           return 0 ;                 case WM_QUERYENDSESSION:           if (IDYES == AskConfirmation (hwnd))                return 1 ;           else                return 0 ;                 case WM_DESTROY:           PostQuitMessage (0) ;           return 0 ;      }      return DefWindowProc (hwnd, message, wParam, lParam) ; } 

POPPAD2.RC (excerpts)

 //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Icon POPPAD2                 ICON    DISCARDABLE     "poppad2.ico" ///////////////////////////////////////////////////////////////////////////// // Menu POPPAD2 MENU DISCARDABLE  BEGIN     POPUP "&File"     BEGIN         MENUITEM "&New",                        IDM_FILE_NEW         MENUITEM "&Open...",                    IDM_FILE_OPEN         MENUITEM "&Save",                       IDM_FILE_SAVE         MENUITEM "Save &As...",                 IDM_FILE_SAVE_AS         MENUITEM SEPARATOR         MENUITEM "&Print",                      IDM_FILE_PRINT         MENUITEM SEPARATOR         MENUITEM "E&xit",                       IDM_APP_EXIT     END     POPUP "&Edit"     BEGIN         MENUITEM "&Undo\tCtrl+Z",               IDM_EDIT_UNDO         MENUITEM SEPARATOR         MENUITEM "Cu&t\tCtrl+X",                IDM_EDIT_CUT         MENUITEM "&Copy\tCtrl+C",               IDM_EDIT_COPY         MENUITEM "&Paste\tCtrl+V",              IDM_EDIT_PASTE         MENUITEM "De&lete\tDel",                IDM_EDIT_CLEAR         MENUITEM SEPARATOR         MENUITEM "&Select All",                 IDM_EDIT_SELECT_ALL     END     POPUP "&Help"     BEGIN         MENUITEM "&Help...",                    IDM_HELP_HELP         MENUITEM "&About PopPad2...",           IDM_APP_ABOUT     END END ///////////////////////////////////////////////////////////////////////////// // Accelerator POPPAD2 ACCELERATORS DISCARDABLE  BEGIN     VK_BACK,        IDM_EDIT_UNDO,          VIRTKEY, ALT, NOINVERT     VK_DELETE,      IDM_EDIT_CLEAR,         VIRTKEY, NOINVERT     VK_DELETE,      IDM_EDIT_CUT,           VIRTKEY, SHIFT, NOINVERT     VK_F1,          IDM_HELP_HELP,          VIRTKEY, NOINVERT     VK_INSERT,      IDM_EDIT_COPY,          VIRTKEY, CONTROL, NOINVERT     VK_INSERT,      IDM_EDIT_PASTE,         VIRTKEY, SHIFT, NOINVERT     "^C",           IDM_EDIT_COPY,          ASCII,  NOINVERT     "^V",           IDM_EDIT_PASTE,         ASCII,  NOINVERT     "^X",           IDM_EDIT_CUT,           ASCII,  NOINVERT     "^Z",           IDM_EDIT_UNDO,          ASCII,  NOINVERT END 

RESOURCE.H (excerpts)

 // Microsoft Developer Studio generated include file. // Used by POPPAD2.RC #define IDM_FILE_NEW                    40001 #define IDM_FILE_OPEN                   40002 #define IDM_FILE_SAVE                   40003 #define IDM_FILE_SAVE_AS                40004 #define IDM_FILE_PRINT                  40005 #define IDM_APP_EXIT                    40006 #define IDM_EDIT_UNDO                   40007 #define IDM_EDIT_CUT                    40008 #define IDM_EDIT_COPY                   40009 #define IDM_EDIT_PASTE                  40010 #define IDM_EDIT_CLEAR                  40011 #define IDM_EDIT_SELECT_ALL             40012 #define IDM_HELP_HELP                   40013 #define IDM_APP_ABOUT                   40014 

POPPAD2.ICO

The POPPAD2.RC resource script file contains the menu and the accelerator table. You'll notice that the accelerators are all indicated within the character strings of the Edit popup menu following the tab (\t) character.

Enabling Menu Items

The major job in the window procedure now involves enabling and graying the options in the Edit menu, which is done when processing the WM_INITMENUPOPUP message. First the program checks to see if the Edit popup is about to be displayed. Because the position index of Edit in the menu (starting with File at 0) is 1, lParam equals 1 if the Edit popup is about to be displayed.

To determine whether the Undo option can be enabled, POPPAD2 sends an EM_CANUNDO message to the edit control. The SendMessage call returns nonzero if the edit control can perform an Undo action, in which case the option is enabled; otherwise, the option is grayed:

 EnableMenuItem (wParam, IDM_UNDO,      SendMessage (hwndEdit, EM_CANUNDO, 0, 0) ?                   MF_ENABLED : MF_GRAYED) ; 

The Paste option should be enabled only if the clipboard currently contains text. We can determine this through the IsClipboardFormatAvailable call with the CF_TEXT identifier:

 EnableMenuItem (wParam, IDM_PASTE,      IsClipboardFormatAvailable (CF_TEXT) ? MF_ENABLED : MF_GRAYED) ; 

The Cut, Copy, and Delete options should be enabled only if text in the edit control has been selected. Sending the edit control an EM_GETSEL message returns an integer containing this information:

 iSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0) ; 

The low word of iSelect is the position of the first selected character; the high word of iSelect is the position of the character following the selection. If these two words are equal, no text has been selected:

 if (HIWORD (iSelect) == LOWORD (iSelect))      iEnable = MF_GRAYED ; else      iEnable = MF_ENABLED ; 

The value of iEnable is then used for the Cut, Copy, and Delete options:

 EnableMenuItem (wParam, IDM_CUT,  iEnable) ; EnableMenuItem (wParam, IDM_COPY, iEnable) ; EnableMenuItem (wParam, IDM_DEL,  iEnable) ; 

Processing the Menu Options

Of course, if we were not using a child window edit control for POPPAD2, we would now be faced with the problems involved with actually implementing the Undo, Cut, Copy, Paste, Clear, and Select All options from the Edit menu. But the edit control makes this process easy, because we merely send the edit control a message for each of these options:

 case IDM_UNDO :      SendMessage (hwndEdit, WM_UNDO, 0, 0) ;      return 0 ; case IDM_CUT :      SendMessage (hwndEdit, WM_CUT, 0, 0) ;      return 0 ; case IDM_COPY :      SendMessage (hwndEdit, WM_COPY, 0, 0) ;      return 0 ;   case IDM_PASTE :      SendMessage (hwndEdit, WM_PASTE, 0, 0) ;      return 0 ; case IDM_DEL :      SendMessage (hwndEdit, WM_DEL, 0, 0) ;      return 0 ; case IDM_SELALL :      SendMessage (hwndEdit, EM_SETSEL, 0, -1) ;      return 0 ; 

Notice that we could have simplified this even further by making the values of IDM_UNDO, IDM_CUT, and so forth equal to the values of the corresponding window messages WM_UNDO, WM_CUT, and so forth.

The About option on the File popup invokes a simple message box:

 case IDM_ABOUT :      MessageBox (hwnd, TEXT ("POPPAD2 (c) Charles Petzold, 1998"),                  szAppName, MB_OK ¦ MB_ICONINFORMATION) ;      return 0 ; 

In the next chapter, we'll make this a dialog box. A message box is also invoked when you select the Help option from this menu or when you press the F1 accelerator key.

The Exit option sends the window procedure a WM_CLOSE message:

 case IDM_EXIT :      SendMessage (hwnd, WM_CLOSE, 0, 0) ;      return 0 ; 

That is precisely what DefWindowProc does when it receives a WM_SYSCOMMAND message with wParam equal to SC_CLOSE.

In previous programs, we have not processed the WM_CLOSE messages in our window procedure but have simply passed them to DefWindowProc. DefWindowProc does something simple with WM_CLOSE: it calls the DestroyWindow function. Rather than send WM_CLOSE messages to DefWindowProc, however, POPPAD2 processes them. (This fact is not so important now, but it will become very important in Chapter 11 when POPPAD can actually edit files.)

 case WM_CLOSE :      if (IDYES == AskConfirmation (hwnd))           DestroyWindow (hwnd) ;      return 0 ; 

AskConfirmation is a function in POPPAD2 that displays a message box asking for confirmation to close the program:

 AskConfirmation (HWND hwnd) {      return MessageBox (hwnd, TEXT ("Really want to close Poppad2?"),                         szAppName, MB_YESNO ¦ MB_ICONQUESTION) ; } 

The message box (as well as the AskConfirmation function) returns IDYES if the Yes button is selected. Only then does POPPAD2 call DestroyWindow. Otherwise, the program is not terminated.

If you want confirmation before terminating a program, you must also process WM_ QUERYENDSESSION messages. Windows begins sending every window procedure a WM_QUERYENDSESSION message when the user chooses to shut down Windows. If any window procedure returns 0 from this message, the Windows session is not terminated. Here's how we handle WM_QUERYENDSESSION:

 case WM_QUERYENDSESSION :      if (IDYES == AskConfirmation (hwnd))           return 1 ;      else           return 0 ; 

The WM_CLOSE and WM_QUERYENDSESSION messages are the only two messages you have to process if you want to ask for user confirmation before ending a program. That's why we made the Exit menu option in POPPAD2 send the window procedure a WM_CLOSE message—by doing so, we avoided asking for confirmation at yet a third point.

If you process WM_QUERYENDSESSION messages, you may also be interested in the WM_ENDSESSION message. Windows sends this message to every window procedure that has previously received a WM_QUERYENDSESSION message. The wParam parameter is 0 if the session fails to terminate because another program has returned 0 from WM_QUERYENDSESSION. The WM_ENDSESSION message essentially answers the question: I told Windows it was OK to terminate me, but did I really get terminated?

Although I've included the normal New, Open, Save, and Save As options in POPPAD2's File menu, they are currently nonfunctional. To process these commands, we need to use dialog boxes. And you're now ready to learn about them.



Programming Windows
Concurrent Programming on Windows
ISBN: 032143482X
EAN: 2147483647
Year: 1998
Pages: 112
Authors: Joe Duffy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net