Designing Portable User Interfaces

The UI is one of the most important design issues to consider when porting applications from one platform to another. John discusses UI design strategies and software-engineering techniques he implemented when porting a DOS application to UNIX.


November 01, 1992
URL:http://www.drdobbs.com/architecture-and-design/designing-portable-user-interfaces/184408874

Figure 1


Copyright © 1992, Dr. Dobb's Journal

Figure 2


Copyright © 1992, Dr. Dobb's Journal

Figure 2


Copyright © 1992, Dr. Dobb's Journal

Figure 1


Copyright © 1992, Dr. Dobb's Journal

Figure 2


Copyright © 1992, Dr. Dobb's Journal

NOV92: DESIGNING PORTABLE USER INTERFACES

DESIGNING PORTABLE USER INTERFACES

Moving from DOS to UNIX

John L. Bradberry

John is development manager at Scientific Concepts, 1033 Franklin Road, Suite 11-295, Marietta, GA 30067.


Numerous software-design issues must be considered when porting applications from one platform to another, among them the question of how to handle the user interface. Luckily, software-engineering techniques exist to help reduce the cost, effort, and maintenance of user-interface (UI) designs and ports. To illustrate some of these techniques, I discuss in this article an application originally written in C for DOS using a text-based menu package I also authored. Eventually, I had to port the program to UNIX, using the XView toolkit to do so. Here I'll focus on how the design of the application's UI made such a port feasible.

If you take an idealized approach to moving application software across platforms, the portability solution seems obvious:

While this type of software "reuse" can be done in almost any language, the implicit requirement is that applications not communicate with a device nor perform any action not completely specified by the language. This usually means your code can't stray outside strict ANSI specifications.

A more realistic view of portability is the higher-level, port-and-fix method shown in Figure 1. The dotted box (domain-dependency region) contains all the low-level device- and machine-specific code that must be reproduced (in principle) for the new environment. The size of the device-dependency region varies from platform to platform. If you're porting in the right direction, you may actually have less work to do!

Moving from DOS to UNIX involves other issues aside from application-code reuse and device interfaces. In going from the single-user/single-task world to the multiuser/multitask world, for instance, you must adapt to significant differences in programming mentality. In the DOS world, you can communicate directly (almost) with any device by reading and writing from the physical-address map. Other than an occasional conflict with TSRs, tasks can assume uncontested control over devices and memory regions in fixed locations. For example, many DOS window libraries (like the one we're porting to UNIX) bypass BIOS services and read/write directly to the video pages.

Also, the idea of protected memory is, in many DOS configurations, wishful thinking. Contrast this to what can happen in a system where your task is one of dozens competing for the same resources. In multitasking environments, the OS must protect tasks from each other as well as from themselves. Figure 2 shows the contrast between the task control offered in single-tasking DOS to the extended environment of multitasking in operating systems such as UNIX. As the left portion of the figure illustrates, DOS programmers can access device-memory maps by simply writing to a fixed location in memory. A single task can even write to areas reserved for DOS itself. The "static" nature of address locations is both a blessing and a curse to many developers.

In contrast, the right side of Figure 2 illustrates a drastically different task-environment structure. Not only are multiple tasks "sharing" one or more CPUs, but a common network-protocol mechanism such as remote procedure calls (RPCs) can allow tasks to communicate from one location to another. It's possible for more than one task to communicate with more than one workstation screen at a time. The nature of this virtual mapping of video services renders hardcoded techniques both undesirable and dangerous in this type of environment.

This contrast of environments seems to make our chances of porting the application without difficulty quite slim indeed. Fortunately, many of the added (implied) "requirements" in the multitasking environment are automatically "handled" for us.

Overview of the DOS-based Library

A few years ago, I wrote a custom, text-based UI library for DOS-based application work. (Because this article focuses on porting the application, the library is not presented here. If you're interested in a copy, contact me at the address on the first page of this article.) Like many other library tools, including a wide variety of X-Window look-alikes developed since then, my text-based window package follows a common basic design.

Text-based systems use video pages residing at locations starting at the fixed segment address of B0000h through B0F9Fh. Using text video maps readily exploits the advantages of relative speed and simplicity (provided your language allows you to write/read directly from memory locations). The effect of real-time window popping is achieved by simply switching between video planes.

You can expand the basic text-based, window-support library from block read/write operations by adding low-level functions like the following: GetVideoPage()/SetVideoPage() to form the foundation for hiding the details of addressing from the application; GetSetAttributeByte() to get or set the attribute of a character at some row or column location on the current page; GetSetAttributeCharacter() to read or write a character at some row or column location on the current page; and ReadWriteString() to use previous lower-level calls, in which a character string is read or written to a specified row and column on the current video page.

Built on top of these routines are higher-level calls for drawing boxes using DOS graphics characters, color control, and so on. To complete the DOS-based UI, add routines for mouse control, keyboard monitoring, and extended prompt routines that allow keyboard editing. Finally--several thousand lines of code later--you have your text-based UI!

The Application

As a test of the issues discussed up to this point, let's examine a personal phone-directory application I ported from DOS to UNIX. Using an address-field layout common to many wordprocessors and labeling packages, the program reads in a database file and displays records that allow the user to "flip" through the files in either direction. In addition, the user can enter characters in the name, phone number, or address fields to search for a particular record.

The DOS version of this program is divided into two small modules: one containing the "generic" portion of the C code for manipulating the record data, the other containing the DOS-specific menu information we hope to replace with its UNIX counterpart later on Listings One (page 130) and Two (page 132) illustrate the contents of the modules. At this point, I won't bother with the header-file contents that are custom or non-ANSI standard C since they'll be replaced in the UNIX version.

Note that Listings One and Two follow the high-level portability model introduced in Figure 1. Both listings contain some of the device- and machine-specific implementations of the DOS video services. However, Listing Two is much more closely bound to the DOS-based library.

In Listing Two, the MENU... keywords represent macros used by lower-level video functions in defining the size and number of lines required for the window box. By definition, the first line following the keyword MENUITEM defines a scroll bar, and the second line represents the help message to be scrolled at the bottom of the display. The quoted character at the end of the second line represents a keyboard character that can be recognized instead of a mouse click to "select" the menu operation. Therefore, this window package will work regardless of whether or not a mouse is present (unlike some other commercially available packages).

You could insist that this menu representation be maintained in the UNIX environment and write lower-level support to "attach" it to the X-Window package. However, this would be equivalent to putting one overcoat on top of another. The calls box_menu_start("Directory Utility", PhoneMain, VFBRWHITE, VFCYAN, VFBRWHITE, DOUBLEBAR, MenuLines, VFBLUE); and Midx=box_select(PhoneMain, VFBRWHITE, VFCYAN, LBUTTON, MenuLines, Marker, VFBLUE); from Listing One illustrate the setup and processing preamble for the window system.

In the first call to box_menu_start(), we specify a title for the menu header, the name of the menu structure, color assignments for the box sides, and the number of lines to be displayed in the menu. If you specify fewer lines to display than defined by the structure, the menu is scrolled within the box.

While the first call to box_menu_start() is displaying the information, a second call deals with event-handling issues. The call to box_select() tells the lower-level event-handling routines which structure to examine and what type of user action other than a key-press to signal the caller about. In this call, the LBUTTON parameter specifies that the left mouse button be recognized along with the specific letter keys noted in the structure.

Overview of the XView Toolkit

In principle, DOS text-based window libraries and X-Window systems share common elements. X applications are much more extensive, however. Here is a summary of the issues pointed out in Dan Heller's book, XView Programming Manual, (O'Reilly & Associates, 1992).

In X a display is not a fixed-size entity located at one location in memory. In X the server receives the protocols necessary to control one or more screens at any location. Instead of writing to a location to render a graphics or text box, communication protocols are used in a client/server-based scheme. Xlib is the lowest-level library used to translate data structures and events.

XView, short for "X Window-System-based Visual/Integrated Environment for Workstations," is a UI toolkit developed by Sun Microsystems as a higher-level interface into the Xlib library. It enables inexperienced X-Window programmers to develop interfaces compatible with the OPEN LOOK GUI, so that all window operations have a similar standard look-and-feel. The form of window structure used in XView is slightly different. Instead of simply forming a box of scrolling lines and waiting for a key-press, you have hundreds of options for configuring buttons, boxes, lists, text, and so on. Consequently, the first thing a new user may notice about XView is that finding an option and setting the associated attributes correctly can be a frustrating experience. Configuring a menu in this context means picking from a shopping list of objects--windows, panels, frames, and the like.

Part of the attribute setting for objects involves specifying the event-handling sequence. This usually translates into setting up a function to process the result of an event registered with another task on your behalf. This may all sound a little confusing, but for the most part you simply use the format of the functions used in the manual examples.

DOS-to-UNIX Portability in C

A couple of important differences between DOS and UNIX must be addressed before restructuring the code for XView. First, the compiler differences between the PC and UNIX can be quite significant. Many DOS programmers assume that code that compiles without errors or warnings will automatically work on UNIX and other environments. This is a mistaken assumption for a variety of reasons, among them compiler warning level and header-file structure conflicts between platforms.

In addition, code that compiles and links on UNIX without errors or warnings is more likely to fail than on DOS. Why? Because, as illustrated in Figure 2, your DOS task is allowed to write almost anywhere (even if you didn't plan it that way). However, memory-protection schemes of OSs such as UNIX deal with such rude program behavior by aborting the task, not rebooting the OS.

The second difference deals with the issue of function prototypes. Many programmers use the ANSI function declarations and prototypes exclusively for functions such as int foo(char c, int Val, char *Mstring);. The older style, quite common in UNIX or with older-generation C programmers, leads to limited error checking and a myriad of other problems. Sun workstations provide two versions of the C compiler (cc or gcc) as choices for the old and new style, respectively. UNIX does provide the lint code-verifier utility for finding pointer and type-coercion bugs. I used the old style (as in the examples in the XView book).

Making the Port

A part of Listing One, which is available electronically (see "Availability" on page 5), contains the code modified to utilize the features of XView. Note that the generic module of Listing One is represented in the XView version with very few alterations. The routines BOOL ReadList(), BOOL SearchField(), and BOOL NextLine() ported without changes.

Also note the XView-specific header files and the definition of global XView "objects" (in the Window Related Control section) used to build the main-menu structure. In the main program section, xv_create is used to register (using the PANEL_NOTIFY_PROC attribute) the menu items with our application by specifying the object type, attributes, and functions used to service the events.

After the panel items are registered with the event handler (notifier), the functions wait patiently to be informed of an event. In this case, unlike in the DOS version, we don't have to worry about actually detecting the event! The line (near the end of main) does this: xv_main_loop(PhoneFrame);.

The PhoneSelect function handles any of the "text" events by asking for the name of the event (xv_get) and checking the string returned. In a similar fashion, the ChoiceSelect function gets an integer code representing a button pushed to execute the desired function.

Conclusions

While the desired principles of window operations remained fairly common between the two platforms, the results obtained were drastically different. We didn't write significantly more code in the move to UNIX, but made use of more of the OS's built-in features. Although I barely scratched the surface in terms of what could be done in XView (albeit painfully), it should be clear that this type of effort is at least feasible.



_DESIGNING PORTABLE USER INTERFACES_
by John L. Bradberry


[LISTING ONE]


/*+=======================================================================
==            Personal Phone Directory Utility                           ==
== author: john l. bradberry            creation date: jan 30,1992       ==
== e-mail: jbrad@cc                     last modified:                   ==
========================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <search.h>
#include <dos.h>
#include <io.h>
#include <stdio.h>
#include <math.h>
#include <time.h>

#include "mstrlib.h"
#include "doskbio.h"
#include "filelib.h"
#include "genlib.h"
/*-------------------------- macros / constants --------------------------*/
#define MAXLABELS    50         /* maximum address records allowed*/
typedef enum {VOID, NAME, ADDRESS, PHONE} KEYTYPE;
typedef enum {OFF, ON} ONOFF;
typedef enum {FALSE, TRUE} BOOL;
/*--------------------------- global variables ---------------------------*/
      static char   *MARKER = "[]"     ;/* record seperator               */
      static char   PHONEFILE[30]      ;/* current data base file name    */
/*------------------------------ structures ------------------------------*/
typedef struct
  {
    char Name[40]                 ;/* name of this person            */
    char Address[4][40]           ;/* maximum of four address fields */
    char PhoneNumber[40]          ;/* phone number (parse later)     */
    char Greeting[20]             ;/* as in Dear Mr/Ms:              */
  }PREC;
struct
  {
    PREC Info[MAXLABELS]          ;/* data base to be read in        */
    int Size                      ;/* number of records in phone list*/
    char SearchString[40]         ;/* string used in search          */
    int SearchKey                 ;/* search index into phone list   */
  } List;
/*------------------------ function prototypes ---------------------------*/
BOOL ReadList();
BOOL SearchField();
BOOL NextLine();
void DispRecord();
void DispLine();
/*----------------------- WINDOW RELATED CONTROL -------------------------*/
#include "dosmenu.h"
/*+=======================================================================
==program main: Phone Directory Utility...               ==
=========================================================================*/
int main()
{
      int       Ival               ;/* temporary variable             */
      BOOL      NoExit             ;/* indicates end of menu mode     */

      char      Stemp[80]          ;/* temporary string               */
      char      NameSearch[40]     ;/* string used in name search     */
      char      AddressSearch[40]  ;/* string used in address search  */
      char      PhoneSearch[40]    ;/* string used in phone search    */
      KEYTYPE   LastType           ;/* last type of search performed  */

      List.SearchKey = 0;
      strcpy(PHONEFILE,"genlist.dat");
      List.SearchString[0]='\0';
      NameSearch[0]='\0';
      AddressSearch[0]='\0';
      PhoneSearch[0]='\0';
/*++++ Display main menu mask... ++++*/
      ClearMain();
/*++++ Sub menu control loop... ++++*/
      NoExit=TRUE;
      if (ReadList(PHONEFILE) == FALSE)
    errout("Data Base File Read Error!");
      DispLine(PHONEFILE, 5, 39, 28, VFBRWHITE, VFBLUE<<4);
      if (List.Size > 0) DispRecord();
      Midx=0;
      while (NoExit)
    {
      Midx=box_select(PhoneMain,VFBRWHITE,VFCYAN,LBUTTON,MenuLines,
                Marker,VFBLUE);
      switch (Midx)
        {
          case 0:
        List.SearchKey = 0;
        get_sval("Enter Database File Name: ",PHONEFILE);
        strim(PHONEFILE);
        DispLine(PHONEFILE, 5, 39, 28, VFBRWHITE, VFBLUE<<4);
        if (ReadList(PHONEFILE) == FALSE)
          errout("Data Base File Read Error!");
        else
          DispRecord();
        break;
          case 1:
        if (List.Size > 0)
          {
            List.SearchKey = (List.SearchKey < List.Size - 1 ?
              List.SearchKey + 1 : 0);
            DispRecord();
          }
        else
          errout("No Valid Data Base!");
        break;
          case 2:
        if (List.Size > 0)
          {
            List.SearchKey = (List.SearchKey > 0 ?
              List.SearchKey - 1 : List.Size - 1);
            DispRecord();
          }
        else
          errout("No Valid Data Base!");
        break;
          case 3:
        if (List.Size > 0)
          {
            List.SearchKey = 0;
            get_sval("Enter Name Search Chars: ",NameSearch);
            strcpy(List.SearchString,NameSearch);
            LastType = NAME;
            SearchField(LastType, List.SearchString);
            DispLine(NameSearch, 8, 39, 28, VFBRWHITE, VFBLUE<<4);
            DispLine(List.SearchString, 11, 39, 28,
              VFBRWHITE, VFBLUE<<4);
            DispRecord();
          }
        else
          errout("No Valid Data Base!");
        break;
          case 4:
        if (List.Size > 0)
          {
            List.SearchKey = 0;
            get_sval("Enter Phone Search Chars: ",PhoneSearch);
            strcpy(List.SearchString,PhoneSearch);
            LastType = PHONE;
            SearchField(LastType, List.SearchString);
            DispLine(PhoneSearch, 9, 39, 28, VFBRWHITE, VFBLUE<<4);
            DispLine(List.SearchString, 11, 39, 28,
              VFBRWHITE, VFBLUE<<4);
            DispRecord();
          }
        else
          errout("No Valid Data Base!");
        break;
          case 5:
        if (List.Size > 0)
          {
            List.SearchKey = 0;
            get_sval("Enter Address Search Chars: ",AddressSearch);
            strcpy(List.SearchString,AddressSearch);
            LastType = ADDRESS;
            SearchField(LastType, List.SearchString);
            DispLine(AddressSearch, 10, 39, 28,
              VFBRWHITE, VFBLUE<<4);
            DispLine(List.SearchString, 11, 39, 28,
              VFBRWHITE, VFBLUE<<4);
            DispRecord();
          }
        else
          errout("No Valid Data Base!");
        break;
          case 6:
        if (List.Size > 0)
          {
            SearchField(LastType, List.SearchString);
            DispRecord();
          }
        else
          errout("No Valid Data Base!");
        break;
          case 7:
        Ival=question("Exit this program to DOS: Y(es)");
        if ((Ival=='y')||(Ival=='Y')||(Ival=='\r'))
          {
             NoExit = FALSE;
          }
        break;
          default:
        cur_posit(MenuRow,MenuCol);
        box_menu_start("Directory Utility",PhoneMain,VFBRWHITE,
          VFCYAN,VFBRWHITE,DOUBLEBAR,MenuLines,VFBLUE);
        break;
        }
    }
/*+++++ Exit and restore CRT to main video page... +++++*/
      setvpage(0);
      clear();
      cur_posit(21,0);
}/* end of main */
/*+========================================================================
==BOOL ReadList: Open user phone data base and read into structure...    ==
=========================================================================*/
BOOL ReadList(Dbase)
char *Dbase;
{
      BOOL      Stcode             ;/* status code returned           */
      char      Stemp[80]          ;/* temporary string               */
      BOOL      NewRecord          ;/* indicates beginning new field  */
      FILE      *FileHandle        ;/* pointer to pipe file           */

      List.Size = -1;
      Stcode = FALSE;
      NewRecord = FALSE;
      FileHandle=fopen(Dbase,"rb");

      if (FileHandle != NULL)
    {
      while ((NextLine(Stemp, FileHandle)) && (List.Size < MAXLABELS -1))
        {
          if (spos(Stemp, MARKER) > 0)
        {
          List.Size++;
          if ((NextLine(List.Info[List.Size].Name, FileHandle)) &&
              (List.Size < MAXLABELS -1))
            {
              NextLine(List.Info[List.Size].Address[0], FileHandle);
              NextLine(List.Info[List.Size].Address[1], FileHandle);
              NextLine(List.Info[List.Size].Address[2], FileHandle);
              NextLine(List.Info[List.Size].Address[3], FileHandle);
              NextLine(List.Info[List.Size].PhoneNumber, FileHandle);
              NextLine(List.Info[List.Size].Greeting, FileHandle);
            }
        }
        }
    }
      if (List.Size > 0) Stcode = TRUE;
      return(Stcode);
}/* end of ReadList */
/*+========================================================================
==BOOL NextLine: Read next line in file...                ==
=========================================================================*/
BOOL NextLine(String, FileHandle)
char *String;
FILE *FileHandle;
{
      BOOL      Stcode         ;/* status code returned       */
      char      Stemp[80]      ;/* temporary string           */
      char      *Sptr          ;/* pointer to string          */
      Stcode = FALSE;
      String[0] = '\0';
      if (fgets(Stemp, sizeof Stemp, FileHandle) != NULL)
    {
      Sptr = strrchr(Stemp,'\r');
      if (Sptr != NULL) *Sptr = ' ';
      Sptr = strrchr(Stemp,'\n');
      if (Sptr != NULL) *Sptr = ' ';
      strim(Stemp);
      strcpy(String,Stemp);
      Stcode = TRUE;
    }
      return(Stcode);
}/* end of NextLine */
/*+=========================================================================
==BOOL SearchField: Search phone for data using key to select field..     ==
==========================================================================*/
BOOL SearchField(Key, Sdata)
KEYTYPE Key;
char *Sdata;
{
      BOOL      Stcode             ;/* status code returned           */
      int       OldSearchKey       ;/* copy of search key returned    */
      BOOL      NoMatch            ;/* indicates search data found    */
      Stcode = FALSE;
      NoMatch = TRUE;
      OldSearchKey = List.SearchKey;
      if (List.SearchKey != 0) List.SearchKey++;
      while((NoMatch) && (List.SearchKey < List.Size))
    {
      switch (Key)
        {
          case NAME:
        if (spos(List.Info[List.SearchKey].Name , Sdata) > 0)
          NoMatch = FALSE;
        break;
          case PHONE:
        if (spos(List.Info[List.SearchKey].PhoneNumber , Sdata) > 0)
          NoMatch = FALSE;
        break;
          case ADDRESS:
        if (spos(List.Info[List.SearchKey].Address[0] , Sdata) > 0)
          NoMatch = FALSE;
        if (spos(List.Info[List.SearchKey].Address[1] , Sdata) > 0)
          NoMatch = FALSE;
        if (spos(List.Info[List.SearchKey].Address[2] , Sdata) > 0)
          NoMatch = FALSE;
        if (spos(List.Info[List.SearchKey].Address[3] , Sdata) > 0)
          NoMatch = FALSE;
        break;
          default:
        puts("Error - bad key");
        }
      if (NoMatch)
        List.SearchKey++;
      else
        Stcode = TRUE;
    }
      if (NoMatch) List.SearchKey = OldSearchKey;
      return(Stcode);
}/* end of SearchField */
/*+=========================================================================
==void DispRecord: Display record on video box of main menu...        ==
==========================================================================*/
void DispRecord()
{
      char      Stemp[80]          ;/* temporary string       */
      sprintf(Stemp,"[%3d] ",List.SearchKey);
      sjoin(Stemp,List.Info[List.SearchKey].Name);
      DispLine(Stemp, TableRow+1,
    TableCol+2, 37, VFBRWHITE, VFMAGENTA<<4);
      DispLine(List.Info[List.SearchKey].Address[0], TableRow+2,
    TableCol+8, 32, VFBRWHITE, VFMAGENTA<<4);
      DispLine(List.Info[List.SearchKey].Address[1], TableRow+3,
    TableCol+8, 32, VFBRWHITE, VFMAGENTA<<4);
      DispLine(List.Info[List.SearchKey].Address[2], TableRow+4,
    TableCol+8, 32, VFBRWHITE, VFMAGENTA<<4);
      DispLine(List.Info[List.SearchKey].Address[3], TableRow+5,
    TableCol+8, 32, VFBRWHITE, VFMAGENTA<<4);
      DispLine(List.Info[List.SearchKey].PhoneNumber, TableRow+6,
    TableCol+8, 32, VFBRWHITE, VFMAGENTA<<4);
}/* end of DispRecord */
/*+=========================================================================
==void DispLine: Display single line on video of main menu...         ==
==========================================================================*/
void DispLine(String, Row, Col, Width, ForColor, BackColor)
char *String;
int Row;
int Col;
int Width;
int ForColor;
int BackColor;
{
      char      Stemp[80]          ;/* temporary string       */
      char      Bline[80]          ;/* blank line             */
      memset(Bline,' ',Width + 1);
      Bline[Width + 1]='\0';
      cputmemstr(Bline,Row,Col,ForColor,BackColor);
      strcpy(Stemp,String);
      Stemp[Width + 1]='\0';
      cputmemstr(Stemp,Row,Col,ForColor,BackColor);
}/* end of DispLine */



[LISTING TWO]


/*+========================================================================
==            Personal Phone Directory Utility                           ==
== author: john l. bradberry            creation date: jan 30,1992       ==
== e-mail: jbrad@cc                     last modified:                   ==
==========================================================================*/
#include <stdio.h>

/*-------------------------- macros / constants --------------------------*/
/*----------------------- WINDOW RELATED CONTROL -------------------------*/
/*--------------------------- window globals -----------------------------*/
      static int    TableRow = 14      ;/* table display row position     */
      static int    TableCol = 15      ;/* table display row position     */
      static int    MenuRow = 4        ;/* menu display row position      */
      static int    MenuCol = 10       ;/* menu display column position   */
      static int    Marker = 15        ;/* menu indicator token           */
      static int    Midx               ;/* index counter into menu table  */
      static int    MenuLines = 8      ;/* number of lines in menu        */
/*------------------------- window structures ----------------------------*/
static DEFINEMENU PhoneMain[] =
   {
   MENUITEM("c) Change Data Base      :                             ",
     "Change name of data base file.", 'c')
   MENUITEM("f) Foward (Next Record)  :  ",
      "Display all fields in next record index.", 'f')
   MENUITEM("b) Backward (Prev Record):  ",
      "Display all fields in previous record index.", 'b')
   MENUITEM("n) Name Search           :",
      "Search name fields in all records for match.", 'n')
   MENUITEM("p) Phone Search          :",
      "Search phone number fields in all records for match.", 'p')
   MENUITEM("a) Address Search        :",
      "Search address fields in all records for match.", 'a')
   MENUITEM("r) Repeat Last Search    :",
      "Repeat last search attempted and find next matching key.", 'r')
   MENUITEM("x) Return to DOS         :",
      "Exit program and return to DOS operating system.", 'x')
   MENUITEMEND
   };
static DEFINETABLE PhoneTable[] =
  {
     TABLEITEM("                                               ")
     TABLEITEM("                                               ")
     TABLEITEM("                                               ")
     TABLEITEM("                                               ")
     TABLEITEM("                                               ")
     TABLEITEM("                                               ")
     TABLEITEMEND
  };
/*------------------------ function prototypes ---------------------------*/
void ClearMain();
/*+=========================================================================
==void ClearMain: Clear screen and display main menu...           ==
==========================================================================*/
void ClearMain(void)
{
      int       Row;        /* row position           */
      int       Col;        /* column position        */
/*++++ Set menu related global variables... ++++*/
      BLINKOFF(atbyte);
      pagenum=PAGE0;
      setvpage(pagenum);
      colclear(VBCYAN);
      boxtype=1;
      shadow=0;
      Row=23;
      Col=0;
      cur_posit(Row,Col);       /* standard prompt position   */
      coleraselin(Row-1,VBBLUE);
      coleraselin(Row,VBBLUE);
      coleraselin(Row+1,VBBLUE);
      cur_posit(1,0);
      coceprt("PHONEMATE - Telephone Directory Utility (Ver 3.1)",
    VFBRWHITE,VBBLUE);
      cur_posit(TableRow,TableCol);
      table_display(" ", PhoneTable,VFLICYAN,VFMAGENTA,VFYELLOW,DOUBLEBAR);
      mensel = MenuLines-1;
      cur_posit(MenuRow,MenuCol);
      box_menu_start("Directory Utility",PhoneMain,VFBRWHITE,
             VFCYAN,VFBRWHITE,DOUBLEBAR,MenuLines,VFBLUE);
}/* end of ClearMain */


[LISTING THREE]


/*+=========================================================================
==            Personal Phone Directory Utility                            ==
== author: john l. bradberry            creation date: jan 30,1992        ==
== e-mail: jbrad@cc                     last modified:                    ==
==========================================================================*/

#include <stdio.h>

#include <xview/xview.h>
#include <xview/frame.h>
#include <xview/panel.h>
#include <xview/notice.h>
#include <xview/cms.h>
#include <xview/tty.h>
#include <xview/font.h>

#include "syshead.h"
/*-------------------------- macros / constants --------------------------*/
#define MAXLABELS    50       /* maximum address records allowed*/
typedef enum {VOID, NAME, ADDRESS, PHONE} KEYTYPE;
typedef enum {OFF, ON} ONOFF;
typedef enum {FALSE, TRUE} BOOL;
/*--------------------------- global variables ---------------------------*/
    static char   *MARKER = "[]"    ;/* record seperator              */
    static char  PHONEFILE[80]      ;/* current data base file name   */
    static KEYTYPE LastType         ;/* last type of search performed */
    static char   NameSearch[80]    ;/* string used in name search    */
    static char   AddressSearch[80] ;/* string used in address search */
    static char   PhoneSearch[80]   ;/* string used in phone search   */
/*------------------------------ structures ------------------------------*/
typedef struct
  {
     char Name[80]                  ;/* name of this person            */
     char Address[4][80]        ;/* maximum of four address fields */
     char PhoneNumber[80]           ;/* phone number (parse later)     */
     char Greeting[80]               ;/* as in Dear Mr/Ms:              */
  }PREC;
struct
    {
    PREC Info[MAXLABELS]        ;/* data base to be read in         */
     int Size                   ;/* number of records in phone list */
    char SearchString[80]       ;/* string used in search           */
     int SearchKey              ;/* search index into phone list    */
  } List;
/*------------------------ function prototypes ---------------------------*/
BOOL ReadList();
BOOL SearchField();
BOOL NextLine();
void DispRecord();
void DispLine();
/*----------------------- WINDOW RELATED CONTROL -------------------------*/
      static int    LineRow = 200      ;/* table display row position     */
      static int    LineCol = 80       ;/* table display col position     */
      Frame     PhoneFrame             ;/* base frame for phone menu      */
      Panel         PhonePanel         ;/* base panel for phone menu      */
      Menu      PhoneMenu              ;/* base menu for phone menu       */
      Panel_item    PhoneFile          ;/* Phone data base file  - handle.*/
      Panel_item    PhoneNameSearch    ;/* Phone name search     - handle.*/
      Panel_item    PhoneNumberSearch  ;/* Phone number search   - handle.*/
      Panel_item    PhoneAddressSearch ;/* Phone address search  - handle.*/
      Panel_item    PhoneChoice        ;/* Phone search options  - handle.*/
/*------------------------ function prototypes ---------------------------*/
void PhoneQuit();
int PhoneSelect();
int ChoiceSelect();
int PhoneForward();
int PhoneBackward();
int RepeatSearch();
/*+========================================================================
== program main: Phone Directory Utility...                              ==
==========================================================================*/
int main()
{
     List.SearchKey = 0;
      strcpy(PHONEFILE,"genlist.dat");
     List.SearchString[0]='\0';
     NameSearch[0]='\0';
     AddressSearch[0]='\0';
     PhoneSearch[0]='\0';
/*+++++ Display main menu mask...++++*/
      PhoneFrame = (Frame)xv_create(NULL, FRAME,
         FRAME_NO_CONFIRM, TRUE,
         FRAME_INHERIT_COLORS, TRUE,
         FRAME_LABEL,
        "PHONEMATE - Telephone Directory Utility (Ver 3.1)",
         NULL);
      PhonePanel = (Panel) xv_create(PhoneFrame, PANEL, NULL);
      PhoneFile  = xv_create(PhonePanel, PANEL_TEXT,
         PANEL_NEXT_ROW, -1,
         PANEL_LABEL_STRING, "Change Data Base",
         PANEL_VALUE,         PHONEFILE,
         PANEL_VALUE_DISPLAY_LENGTH, 50,
         PANEL_VALUE_X,       150,
         PANEL_NOTIFY_PROC,   PhoneSelect,
         NULL);
      PhoneNameSearch  = xv_create(PhonePanel, PANEL_TEXT,
         PANEL_NEXT_ROW, -1,
         PANEL_LABEL_STRING, "Name Search",
         PANEL_VALUE,     NameSearch,
         PANEL_VALUE_DISPLAY_LENGTH, 50,
        PANEL_VALUE_X,       150,
         PANEL_NOTIFY_PROC,   PhoneSelect,
         NULL);
       PhoneNumberSearch = xv_create(PhonePanel, PANEL_TEXT,PANEL_NEXT_ROW, -1,
         PANEL_LABEL_STRING, "Number Search",
         PANEL_VALUE,     PhoneSearch,
         PANEL_VALUE_DISPLAY_LENGTH, 50,
         PANEL_VALUE_X,   150,
         PANEL_NOTIFY_PROC,   PhoneSelect,
         NULL);
       PhoneAddressSearch  = xv_create(PhonePanel, PANEL_TEXT,
         PANEL_NEXT_ROW, -1,
         PANEL_LABEL_STRING, "Address Search",
         PANEL_VALUE,     AddressSearch,
         PANEL_VALUE_DISPLAY_LENGTH, 50,
         PANEL_VALUE_X,       150,
         PANEL_NOTIFY_PROC,   PhoneSelect,
         NULL);
    PhoneChoice = xv_create(PhonePanel, PANEL_CHOICE,
          PANEL_LABEL_STRING,  "Search Options",
          PANEL_NEXT_ROW,      40,
          PANEL_CHOICE_STRINGS,"Repeat Last Search",
                   "Next Record",
                   "Previous Record",

                   NULL,
          PANEL_NOTIFY_PROC,   ChoiceSelect,
          NULL);
      (void) xv_create(PhonePanel, PANEL_BUTTON,
          PANEL_LABEL_STRING,  "Exit Phone Menu System",
          XV_X,            225,
         XV_Y,            450,
        PANEL_NOTIFY_PROC,   PhoneQuit,
         NULL);
        if (ReadList(PHONEFILE) == FALSE)
    errout("Data Base File Read Error!");
      if (List.Size > 0) DispRecord();
/*++++ Sub menu control loop...++++*/
      window_fit(PhoneFrame);
      xv_main_loop(PhoneFrame);
/*++++ Exit and restore CRT to main video page...++++*/
}/* end of main */
/*+=========================================================================
== int PhoneSelect: Process event from phone menu selection...            ==
==========================================================================*/
int PhoneSelect(item, event)
Panel_item item;
Event *event;
{
      char      ItemName[82]       ;/* name of item event         */
    strcpy(ItemName , (char *)xv_get(item, PANEL_LABEL_STRING));
      if (spos(ItemName, "Data Base") > 0)
    {
       strcpy(PHONEFILE , (char *)xv_get(item, PANEL_VALUE));
       strim(PHONEFILE);
          if (ReadList(PHONEFILE) == FALSE)
       errout("Data Base File Read Error!");
      else
          DispRecord();
    }
    else if (spos(ItemName, "Name") > 0)
    {
       strcpy(NameSearch , (char *)xv_get(item, PANEL_VALUE));
        if (List.Size > 0)
          {
          List.SearchKey = 0;
          strcpy(List.SearchString,NameSearch);
          LastType = NAME;
          SearchField(LastType, List.SearchString);
          DispRecord();
          }
     else
         errout("No Valid Data Base!");
        }
      else if (spos(ItemName, "Number") > 0)
    {
       strcpy(PhoneSearch , (char *)xv_get(item, PANEL_VALUE));
         if (List.Size > 0)
       {
           List.SearchKey = 0;
           strcpy(List.SearchString,PhoneSearch);
           LastType = PHONE;
           SearchField(LastType, List.SearchString);
           DispRecord();
         }
        else
        errout("No Valid Data Base!");
      }
      else if (spos(ItemName, "Address") > 0)
    {
       strcpy(AddressSearch , (char *)xv_get(item, PANEL_VALUE));
           if (List.Size > 0)
         {
            List.SearchKey = 0;
         strcpy(List.SearchString,AddressSearch);
           LastType = ADDRESS;
            SearchField(LastType, List.SearchString);
           DispRecord();
           }
      else
          errout("No Valid Data Base!");
    }
      return XV_OK;
}/* end of PhoneSelect */
/*+=========================================================================
==int ChoiceSelect: Call search option function...                        ==
==========================================================================*/
int ChoiceSelect(item, event)
Panel_item item;
Event *event;
{
      int       ChoiceVal          ;/* value of keypress          */

    ChoiceVal =  (int )xv_get(item, PANEL_VALUE);
        if (List.Size > 0)
          {
     switch (ChoiceVal)
        {
          case 0:
        SearchField(LastType, List.SearchString);
          break;
          case 1:
        List.SearchKey = (List.SearchKey < List.Size - 1 ?
        List.SearchKey + 1 : 0);
          break;
          case 2:
        List.SearchKey = (List.SearchKey > 0 ?
        List.SearchKey - 1 : List.Size - 1);
          break;
        }
      DispRecord();
      }
     else
         errout("No Valid Data Base!");
      return XV_OK;
}/* end of ChoiceSelect */
/*+=========================================================================
==void PhoneQuit: Destroy frame and exit menu...                         ==
==========================================================================*/
void PhoneQuit()
{
      xv_destroy_safe(PhoneFrame);
}/* end of PhoneQuit */
/*+=========================================================================
==BOOL ReadList: Open user phone data base and read into structure...     ==
==========================================================================*/
BOOL ReadList(Dbase)
char *Dbase;
{
       BOOL      Stcode         ;/* status code returned           */
       char       Stemp[80]     ;/* temporary string               */
       BOOL      NewRecord      ;/* indicates beginning new field  */
       FILE       *FileHandle   ;/* pointer to pipe file           */
       List.Size = -1;
       Stcode = FALSE;
     NewRecord = FALSE;
     FileHandle=fopen(Dbase,"rb");
      if (FileHandle != NULL)
     {
        while ((NextLine(Stemp, FileHandle)) && (List.Size < MAXLABELS -1))
       {
          if (spos(Stemp, MARKER) > 0)
           {
          List.Size++;
          if ((NextLine(List.Info[List.Size].Name,FileHandle)) &&
            (List.Size < MAXLABELS -1))
            {
               NextLine(List.Info[List.Size].Address[0],
            FileHandle);
              NextLine(List.Info[List.Size].Address[1],
            FileHandle);
              NextLine(List.Info[List.Size].Address[2],
            FileHandle);
              NextLine(List.Info[List.Size].Address[3],
            FileHandle);
              NextLine(List.Info[List.Size].PhoneNumber,
            FileHandle);
              NextLine(List.Info[List.Size].Greeting,
            FileHandle);
            }
         }
          }
        }
      if (List.Size > 0) Stcode = TRUE;
       return(Stcode);
}/* end of ReadList */
/*+=========================================================================
==BOOL NextLine: Read next line in file...                               ==
==========================================================================*/
BOOL NextLine(String, FileHandle)
char *String;
FILE *FileHandle;
{
        BOOL      Stcode         ;/* status code returned       */
        char      Stemp[80]      ;/* temporary string           */
        char      *Sptr          ;/* pointer to string          */
        Stcode = FALSE;
     String[0] = '\0';
    if (fgets(Stemp, sizeof Stemp, FileHandle) != NULL)
       {
          Sptr = strrchr(Stemp,'\r');
        if (Sptr != NULL) *Sptr = ' ';
       Sptr = strrchr(Stemp,'\n');
        if (Sptr != NULL) *Sptr = ' ';
        strim(Stemp);
           strcpy(String,Stemp);
          Stcode = TRUE;
    }
      return(Stcode);
}/* end of NextLine */
/*+=========================================================================
==BOOL SearchField: Search phone for data using key to select field..   ==
==========================================================================*/
BOOL SearchField(Key, Sdata)
KEYTYPE Key;
char *Sdata;
{
      BOOL     Stcode         ;/* status code returned           */
      int      OldSearchKey   ;/* copy of search key returned    */
      BOOL     NoMatch        ;/* indicates search data found    */
      Stcode = FALSE;
      NoMatch = TRUE;
      OldSearchKey = List.SearchKey;
      if (List.SearchKey != 0) List.SearchKey++;
      while((NoMatch) && (List.SearchKey < List.Size))
         {
        switch (Key)
        {
           case NAME:
         if (spos(List.Info[List.SearchKey].Name , Sdata) > 0)
              NoMatch = FALSE;
         break;
         case PHONE:
        if (spos(List.Info[List.SearchKey].PhoneNumber , Sdata) > 0)
               NoMatch = FALSE;
           break;
           case ADDRESS:
        if (spos(List.Info[List.SearchKey].Address[0] , Sdata) > 0)
              NoMatch = FALSE;
        if (spos(List.Info[List.SearchKey].Address[1] , Sdata) > 0)
              NoMatch = FALSE;
        if (spos(List.Info[List.SearchKey].Address[2] , Sdata) > 0)
              NoMatch = FALSE;
        if (spos(List.Info[List.SearchKey].Address[3] , Sdata) > 0)
              NoMatch = FALSE;
        break;
            default:
            puts("Error - bad key");
        }
     if (NoMatch)
          List.SearchKey++;
         else
         Stcode = TRUE;
       }
    if (NoMatch) List.SearchKey = OldSearchKey;
      return(Stcode);
}/* end of SearchField */
/*+=========================================================================
==void DispRecord: Display record on video box of main menu...           ==
==========================================================================*/
void DispRecord()
{
      char     Stemp[80]          ;/* temporary string       */
      DispLine(" ", LineRow+20, 10);
      DispLine(" ", LineRow+40, LineCol);
      DispLine(" ", LineRow+60, LineCol);
      DispLine(" ", LineRow+80, LineCol);
      DispLine(" ", LineRow+100, LineCol);
      DispLine(" ", LineRow+120, LineCol);
      sprintf(Stemp,"[%3d]",List.SearchKey);
      DispLine(Stemp, LineRow+20, 10);
      DispLine(List.Info[List.SearchKey].Name, LineRow+20, LineCol);
      DispLine(List.Info[List.SearchKey].Address[0], LineRow+40, LineCol);
      DispLine(List.Info[List.SearchKey].Address[1], LineRow+60, LineCol);
      DispLine(List.Info[List.SearchKey].Address[2], LineRow+80, LineCol);
      DispLine(List.Info[List.SearchKey].Address[3], LineRow+100, LineCol);
      DispLine(List.Info[List.SearchKey].PhoneNumber, LineRow+120, LineCol);
}/* end of DispRecord */
/*+=========================================================================
==void DispLine: Display single line on video of main menu...            ==
==========================================================================*/
void DispLine(String, Row, Col)
char *String;
int Row;
int Col;
{
      int        Idx               ;/* index into array           */
      char      Slabel[160]        ;/* string label               */
      memset(Slabel,' ',158);
      Slabel[158]='\0';
      Idx = slen(String);
      while (Idx >=0)
    {
      Slabel[Idx] = String[Idx];
      Idx--;
    }
      (void) xv_create(PhonePanel, PANEL_MESSAGE,
          PANEL_LABEL_STRING, Slabel,
          PANEL_LABEL_BOLD,   TRUE,
          XV_X,       Col,
          XV_Y,       Row,
          NULL);
}/* end of DispLine */










Copyright © 1992, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.