Classic Computer Magazine Archive A.N.A.L.O.G. ISSUE 79 / DECEMBER 1989 / PAGE 48

C-manship:
A Complete
GEM Application,
Part 5


by Clayton Walnurm

Last time, we added the code needed to create a new MicroCheck ST account. Unfortunately, once the account was created, we still weren't able to open it. This month we'll add the program segment that'll not only handle that task but also will enable us to modify the date shown in the date information box at the bottom of the MicroCheck ST screen.
    Listing 1 is the new source code for this month. You should merge it with the combined source code from the previous months and delete the open_acc(), do_new_mnth(), save_month() and get_new_date() stubs from the previous portion.
    Now compile the program and run it. Start a new account. After that procedure is complete, a dialog box will appear, asking for the month you want to work on. Select the month. The account will be opened and the information boxes on the screen will be updated for that account.
    Now choose the New Date option of the Utilities drop-down menu. Another dialog will appear. Type in a new date. When you select the dialog's OK button, the date you have typed will appear in the dateinformation box at the bottom of the screen.
    Let's take a look at the new functions.

Function open_acct()
    This function is called whenever the user wants to open an account or has just finished creating one. It gets as input a pointer to the filename of the account the user wants to open. At the beginning of the function, it attempts to open the .MCK file for the account. If it fails, an alert box warns the user, and no further processing is done.
    If the fopen() call is successful, we read in the information that's stored in the file. The file format is shown below. All of the information is in character format except the account balance, which is a long integer:

Bytes 1-26 Name
Bytes 27-52 Street Address
Bytes 53-68 City
Bytes 69-78 Unused
Bytes 79-81 State
Bytes 92-95 Account balance (long int)

    As the data is read in, it's formatted the way it will appear in the check-entry dialog box. After reading all the data, we close the file and plug the pointers to the name and address string into the ob_spec for each of the appropriate fields in the checkentry dialog box.

check_addr[CHKNAME].ob_spec = chkname;
check_addr[CHKSTREET].ob_spec = chkstreet;
check_addr[CHKNAME].ob_spec = chkcity;

    In the above, check_addr is the address of the check-entry dialog box; CHKNAME, CHKSTREET and CHKCITY are the names of string objects inside the dialog box; and ob_spec is the pointer to the string to be displayed for that object.
    After setting the dialog-box strings, we call do_new_mnth(), which gets the month selection from the user and calls the functions necessary to actually open the files. If the account gets opened okay, the flag loaded will be TRUE, and we'll call set_menu_entries() in order to enable and disable the appropriate entries in the dropdown menu.

Function do_new_mnth()
    Here, we first set the title string of the month-selection dialog box to "NEW MONTH" by placing a pointer to the string (newm) into the object's ob_spec field:

cancdial_addr[CANCSTRG].ob_spec = newm;

    Here, cancdial_addr is the address of the month-selection dialog box and CANCSTRG is a string object within the dialog box.
    The integer value choice, the button on which the user clicked to exit the monthselection dialog box, is returned from a call to get_month(), the function that handles the dialog itself. If the user exited with the OK button, we save the current month's data if it needs to be (saved equals FALSE) and call open_new_month() to open the files.

Function save_month()
    In this function we first take the filename of the file to save (the pointer to which is passed into the function as file) and change the extension to "BAK " We then delete any backup file that may already exist for that month and rename the old data file as the new backup file. We then open a new file with the filename pointed to by file (warning the user with an alert box if we get an error), after which we write that month's data out to the file.
    The first two bytes written are the number of transactions in the file (in integer form). Then, using a for loop, we call save_check() for each check record in the check structure, writing the data to disk, after which we close the file.
    Now all we have to do is save the new account balance. Our call to fseek() moves the file pointer 91 bytes from the beginning of the file, which is where the balance is stored. We save the balance and close the file.

Function open_new_month()
    The first task here is to discover which month the user selected from the month-selection dialog box. We do that by using a for loop to scan through each of the button objects in the dialog to see which one is selected. (Note that this technique will work only if the button objects were created in numerical order when the dialog was first designed.) Based on which button was selected, we set the integer ninth equal to a number from 0 to 12. The value 0 represents the Month 0 file, with the values 1 through 12 representing January through December, respectively. All that's left now is a call to open_month() to read in and process the data for the new month selected.

Function open_month()
    Since we're now opening a new file, we set the flag saved to TRUE. This flag will remain TRUE until we modify the data somehow. Next, we initialize some variables, then construct the filename for the month we'll be opening.
    After opening the file, if we find that the transaction count is zero (by reading the first two bytes from the file), we ask the user if he'd like to start a new month. We have to do this because if the user has transactions entered into his .AUT file (automatic transactions), they will be added automatically to this month's file when it's opened. This gives the user a chance to change his mind before the transactions are entered.
    If the user chooses to open the file, we call load_auto() to load any automatic transactions. If the user chooses not to open the file, we set everything back the way it was and exit the function.
    Assuming we've opened the file, the flag do_it will be TRUE, so we clear the window, set the loaded flag to TRUE, set up some strings for the display and store the current month into month. Using a while loop, we read in all the checks from the file, keeping a count on the number of deposits and the number of checks as we do. Finally, we initialize some strings and variables, copy the account name and the string ":Edit Mode" into the window's title bar, close the file and vamoose.

Summing It Up
    The rest of the functions presented this month, though they have important roles in the workings of MicroCheck ST, do not really need much discussion. Most of the programming theory used in them has already been covered, so I'll give you only a quick rundown on what they do:
    load_auto() loads any automatic transactions that may be in the user's .AUT file.
    save_check() saves the data for a check to disk.
    read_check() reads the data for a check from the disk.
    clear_window() blanks out the program's window with a white rectangle.
    get_month() brings up the monthselection dialog box and retrieves the user's choice.
    get_new_date() allows the user to change the program's displayed date via a dialog box.
    chk_date() simply makes sure the date dialog box in get_new_date() was filled in correctly by the user.
    updte_buttons() places new data in the information boxes on the bottom of the screen.

And Now, the Great Cop-Out
    It's become apparent to me that to continue on through the entire source code for MicroCheck ST would be forcing you to sit through a lot of boring repetition. The fact is that, in the code we've covered so far, we've looked at all the major topics I wanted to discuss and seen how they work in a full-scale program. I say, "Enough is enough!"
    So we're calling it quits. On this month's disk you will find not only the code for the functions we discussed here, but the entire source file for MicroCheck ST. Study it if you're really interested in all the minor details.
    In the next C-manship, we'll find a brandnew topic for discussion. (And no, at this point I haven't the vaguest idea what it will be.)


Clayton
    Clayton Walnum is the Executive Editor of ANALOG Computing as well as the Associate Editor of VIDEOGAMES & COMPUTER ENTERTAINMENT.


LISTING 1:C

open_acct ( file )
char *file;
{
   int x, len;
   char zip[10], buf[25];

   if ( ( acctfile = fopen ( file, "br" )) == 0 )
      form_alert ( 1, "[1][Can't open the file][CONTINUE]" );
   else {
      fread ( chkname, 1, 26, acctfile );
      fread ( chkstreet, 1, 26, acctfile );
      fread ( chkcity, 1, 16, acctfile );
      fread ( buf, 1, 10, acctfile);
      strcpy ( &chkcity[strlen(chkcity)], ", " );
      fread ( &chkcity[strlen(chkcity)], 1, 3, acctfile );
      strcpy ( &chkcity[strlen(chkcity)], " " );
      fread ( zip, 1, 10, acctfile );
      len = strlen ( chkcity );
      if ( strlen ( zip ) > 5 ) {
         strncpy ( &chkcity[len], zip, 5 );
         chkcity[len+5] = 0;
         strcpy ( &chkcity[strlen(chkcitg)], "-" );
         strcpy ( &chkcity[strlen(chkcity)], &zip[5] );
      )
      else
         strcpy ( &chkcity[strlen(chkcity)], zip );
      fread ( &balance, 4, 1, acctfile );
      if ( fclose ( acctfile ) != 0 )
            form_alert ( 1, "[1][File close error!][OKAY]");
      check_addr[CHKMAME].ob_spec = chkname;
      check_addr[CHKSTREE].ob_spec = chkstreet;
      check_addr[CHKCITY].ob_spec = chkcity;
      do_new_mnth ();
      if ( loaded )
         set_menu_entries ();
      else
         balance = 0;
   )
)

do_new_mnth ()
(
   int choice;

   cancdial_addr[CANCSTRG].ob_spec = newm;
   choice = get_month ();
   if ( choice == CANCOK ] {
      if ( !saved )
         save_month ( monthfile l;
      open_new_month ();
   }
}

save_month ( file )
char *file;
{
   char newmfile[64];
   int x;

   strcpy ( newmfile, file );
   strcpy ( &newmfile[strlen(newmfile)-3], "BAK" );
   Fdelete ( newmfile );
   if ( Frename ( 0, file, newmfile ) == FAILED )
      form_alert ( 1, "[1][Error creating .BAK file!][OK]" );
   if ( ( mfile = fopen ( file, "bw" )) == 0 ) {
      form_alert ( 1, "[1][Disk Error!|Cannot save file!][CONTINUE]" );
      Frename ( 0, newmfile, file l;

   }
else {
   fwrite ( &num_trams, 2, 1, mfile );
   for ( x=0; x<num_trans; ++x )
      save_check ( x, mfile );
   if ( fclose ( mfile ) != 0 )
      form_alert [ 1, "[1][File close error!][OKAY]");
   else
      saved = TRUE;
   if ( ( mfile = fopen ( filename, "br+" )) == NULL )
       form_alert ( 1,
              "[1][Error opening .MCK file!|Cannot update balance.][OK]" );
else {
   fseek ( mfile, 91L, FROM_BEG );
   fwrite ( &balance, 4, 1, mfile );
   if ( fclose ( mfile ) != 0 )
            form_alert ( 1, "[1][File close error!][OK]");
      }
   }
}

open_new_month ()
(
   int mnth, x;

   for ( x=JAN; x<=MZERO; ++x )
      if ( cancdial_addr[xl.ob_state == SELECTED )
         if C x == MZERO l
            minth = 0;
          else
            minth = x-JAN+i;
   sprintf ( cancmnth, "Y.d", ninth l;
   open_month C acct_name, cancnnth );
}

open_month [ file, mnth )
char *file, *mnth;
{
   int x, len, button, do_it, trans_cnt, old_dep_cnt, old_chk_cnt;
   char a[20], new_mfile[64];

   saved = TRUE;
   old_dep_cnt = nun_deps;
   old_chk_cnt = nun_ chks;
   num_chks = nun_deps = 0;
   strcpy ( new_nfile, filename );
   strcpy ( &new_mfile[strlen(new_mfile)-4], mnth );
   strcpy ( &new_mfile[strlen(new_mfile)], ".DAT" );
   if ( ( mfile = fopen ( new_mfile, "br" )) == 0 )
      form_alert ( 1, "[1][Can't open the file][CBNTINUE]" );
   else {
      do_it = TRUE;
      fread ( &trams_cnt, 2, 1, mfile );
      if ( trans_cnt == 0 ) {
         button = form_alert ( 1, "[2][The data file for|this month is \
empty.|Do you want to start|a new month?][YES|NO)" );
         if ( button == YES ( {
            nun_trans = load_auto ();
         }
         else {
            do_it = FALSE;
            num_chks = old_chk_cnt;
            num_deps = old_dep_cnt;
         }
      }
      else
         mum_trans = trans_cnt;
      if ( do_it ) {
         clear_window ();
         loaded = TRUE;
         if ( balance < 0 && balance > [-100) )
            sprintf ( bal_but, "$-%ld.%021d", balance/100,labs(balance%.100) );
         else
            sprintf ( bal_but, "$%ld.%02ld", balance/100,labs(balance%100) );
         strcpy ( monthfile, new_mfile );
         strcpy ( acct_name, file );
         month = atoi ( minth );
         x=0;
         while ( x < trans_cnt ) {
            read_check ( x, mfile );
            if ( strcpy ( checks[xl.number, "9999" ) == MATCH )
               nun_deps += 1;
            else
               nun_chks += 1;
            ++x;
         )
         if ( x > 0 ) {
            strcpy ( cur_chk_num, checks[x-1].number );
            curchknum = atoi ( cur_chk_num );
            if ( strcpy ( cur_chk_num, "9999" ) != MATCH ) {
               curchknum += 1;
               sprintf ( a, "%d", curchknum );
               len = strlen ( a );
               strcpy ( &cur_chk_nun[4-len], a );
            }
         }
         cur_top = edit_top = 0;
         cur_count = num_trams;
         cur_chk_strc = checks;
         strcpy ( windname, acct_name );
         strcpy ( &windnane[strlen(windname)], ": Edit mode" );
         wind_set ( w_h2, WF_NAME, windname, 0, 0 );
         full_draw = TRUE;
         if ( fclose ( mfile ) != 0 )
            form_alert ( 1, "[1][File close error!][OKAY]");
      }
   }
}

load_auto ()
{
   char autonane[64];
   FILE *autofile;
   int x, count;

   count = 0;
   strcpy ( autoname, filename );
   strcpy ( &autonane[strlen(autoname)-4], ".AUT" );
   if ( ( autofile = fopen ( autoname, "br" )) != NULL ) {
      fread ( &count, 2, 1, autofile );
      x=0;
      while ( x < count ) {
         read_check ( x, autofile );
         if ( strcpy ( checks[x].number, "9999" ) == MATCH ) {
            num_deps += 1;
            balance += checks[x].amount;
         }
         else {
            nun_chks += 1;
            balance -= checks[x].amount;
         }
         ++x;
      }
      saved = FALSE;
      if ( fclose ( autofile ) == FAILED )
         form_alert ( 1, "[1][Error closing AUTO file!][CONTINUE]" );
   }
   return [ count l;
}

save_check ( i, f )
int i;
FILE *f;
{
   fwrite [ checks[i].number, 1, 4, f );
   fwrite ( checks[i].payee, 1, 38, f );
   fwrite ( checks[i].memo, 1, 30, f );
   fwrite ( checks[i].date, 1, 8, f );
   fwrite ( &checks[i].amount, 4, 1, f );
   fwrite ( checks[i].cancel, 1, 1, f );
   fwrite ( "THIS SPACE FOR POSSIBLE FUTURE EXPANSION", 1, 40, f );
}

read_check ( i, f )
int i;
FILE f;
{
   fread ( checks[i].number, 1, 4, f );
   fread ( checks[i].pagee, 1, 30, f );
   fread ( checks[i].memo, 1, 30, f );
   fread ( checks[i].date, 1, 8, f );
   fread ( &checks[i].amount, 4, 1, f );
   fread ( checks[i].cancel, 1, 1, f );
   fread ( future_use, 1, 48, f );
}

clear_window ()
{
   GRECT r;

   wind_get [ w_h2, WF_WORKXVWH, &r.g_x, &r.g_y, &r.g_w, &r.g_h );
   draw_rec ( r, 2, 8, WHITE );
}

get_month ()
{
   int choice;
   int dial_x, dial_y, dial_w, dial_h;
   clear_cancdial ();
   form_center ( cancdial_addr, &dial_x, &dial_y, &dial_w, &dial_h );
   form_dial ( FMD_START, 0, 0, 10, 10, dial_x, dial_y, dial_w, dial_h );
   objc_draw ( cancdial_addr, 0, 8, dial_x, dial_y, dial_w, dial_h );

   choice = form_do ( cancdial_addr, 0 );
   cancdial_addr[choice].ob_state = SHADOWED;

   form_dial ( FMD_FINISH, 0, 0, 10, 10, dial_x, dial_y, dial_w, dial_h );
   return ( choice );
}

clear_cancdial ()
{
   int x;

   for ( x=JAN; x<=MZERO; cancdial_addr[x++].ob_state = NORMAL ) ;
   if ( month != -1 )
      if ( month == 0 )
         cancdial_addr[MZER0).ob_state = SELECTED;
      else
         cancdial_addr[month+JAN-1].ob_state = SELECTED;
}

get_new_date ()
{
   int choice, okay;
   int dial_x, dial_y, dial_w, dial_h;
   string = get_tedinfo_str ( newdate_addr, NWDATE );
   string[0] = 0;
   form_center ( newdate_addr, &dial_x, &dial_y, &dial_w, &dial_h );
   form_dial ( FMD_START, 0, 0, 10, 10, dial_x, dial_y, dial_w, dial_h );
   objc_draw ( newdate_addr, 0, 8, dial_x, dial_y, dial_w, dial_h );

   okay = FALSE:

   do {
      choice = form_do ( newdate_addr, NWDATE );
      newdate_addr[choice].ob_state = SHADOWED;

      switch ( choice ) {

         case DATEOK:
            okay = chk_date ();
            if ( !okay )
               objc_draw ( newdate_addr, 0, 8,
                   dial_x, dial_y, dial_w, dial_h );
            else {
               strcpy ( cur_date, string );
               format_date ( date_but, cur_date );
               updte_buttons ();
            }
            break;

         case DATECANC:
            string = get_tedinfo_str ( newdate_addr, NWDATE );
            string[0] = '@';
      }
   }
   while ( okay == FALSE && choice != DATECANC );

   form_dial ( FMD_FINISH, 0, 0, 10, 10, dial_x, dial_y, dial_w, dial_h );
}

chk_date ()
{
   int nnth, day, year, okay;
   char m[3], d[3], y[3];

   string = get_tedinfo_str ( newdate_addr, NWDATE );
   if ( strlen ( string ) == 6 ) {
      strncpy ( m, string, 2 );
      m[2] = 0;
      strncpy ( d, &string[2], 2 );
      d[2] = 0;
      strncpy ( y, &string[4], 2 );
      mnth = atoi ( m );
      day = atoi ( d );
      year = atoi ( y );
      if ( mnth { 0 | mnth >12 | day < 1 | day > 31
           | year < 0 | year > 99 ) {
         okay = FALSE;
      }
      else
         okay = TRUE;
   }
   else
      okay = FALSE;
   if ( !okay ) {
      form_alert ( 1, "[1] [Not a valid date!][CONTINUE]" );
      string[0] = 0;
   }
   return ( okay );
}

 updte_buttons ()
{
    if ( !full ) {
       set_buttons ();
       center_butstring ( bal_but, 35, 194 );
       center_butstring ( trans_but, 131, 194 );
       center_butstring ( check_but, 227, 194 );
       center_butstring ( dep_but, 323, 194 );
       center_butstring ( mnth_but, 419, 194 );
       center_butstring ( date_but, 515, 194 );
   }  
}